From 17dfd24340fe3346433f90457cb48cb8786dff39 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Sun, 1 Nov 2026 23:55:27 +0200 Subject: [PATCH] docs(xie): the VT1200 ROM has no DCT (JPEG) scheme -- measured The question was whether this ROM answers "unsupported" for XieK_DCT. It does, at both layers, and it behaves exactly like an out-of-range scheme value. SetTransport (minor 20), raw wire request, a fresh bitonal photomap per scheme: PCM / G31D / G32D / G42D -> accepted DCT (4) and 5 -> BadIDChoice(14), minor_op 20 So the ROM does not even implement the reference server's IsBitonal_ rules (G42D is accepted on a 256-level photomap, where XieTransport.c would BadMatch), but it does refuse 4 and above. Export (minor 21) with the photomap's compression byte forced to each value (the server never initialises it -- it holds the heap fill 0x55, which is why harness/mame/xie_fix.lua pokes file 0x1B256): 0..3 -> pixmap 4096/4096, no X error 4 -> BadValue, nothing drawn 5 -> BadValue, nothing drawn And the ROM carries no JPEG machinery: no "JPEG"/"DCT" string, and the standard Annex K quantisation table is absent in both zigzag and natural order (no flat 8x8 either). A DCT codec carries one of those; this ROM carries none. "DDX_RENDER: unsupported compression scheme" exists in the ROM but did not fire in these runs, so it belongs to a path these probes do not reach -- recorded as an open detail rather than claimed as the explanation. Probes: xie/xiescheme.c (libXie, one scheme per connection) and xie/xie_schemes_raw.py (raw protocol, exact error codes). --- xie/XIE_COMPRESSION_SCHEMES.md | 68 +++++++++++++ xie/xie_schemes_raw.py | 76 +++++++++++++++ xie/xiescheme.c | 173 +++++++++++++++++++++++++++++++++ 3 files changed, 317 insertions(+) create mode 100644 xie/xie_schemes_raw.py create mode 100644 xie/xiescheme.c diff --git a/xie/XIE_COMPRESSION_SCHEMES.md b/xie/XIE_COMPRESSION_SCHEMES.md index 8846501..11f9666 100644 --- a/xie/XIE_COMPRESSION_SCHEMES.md +++ b/xie/XIE_COMPRESSION_SCHEMES.md @@ -111,6 +111,69 @@ JPEG baseline/lossless (X11R6.1, two values) — but the 2.3 protocol that the terminal speaks has only the single Scheme-4 DCT code, with no baseline/lossless split and no lossless predictor field in the wire struct. +## What THIS ROM actually does (measured, not inferred) + +Two probes were built for this (`xiescheme.c` via libXie, and +`xie_schemes_raw.py` which speaks the wire protocol directly): + +### 1. At SetTransport (minor 20), raw request, fresh bitonal photomap each time + +``` +PCM SetTransport -> ACCEPTED +G31D SetTransport -> ACCEPTED +G32D SetTransport -> ACCEPTED +G42D SetTransport -> ACCEPTED +DCT SetTransport -> REFUSED ERROR code=14 (BadIDChoice) minor_op=20 +5 SetTransport -> REFUSED ERROR code=14 (BadIDChoice) minor_op=20 +``` + +So **DCT is answered exactly like an out-of-range scheme value.** Note this is +also evidence that the ROM does *not* implement the reference server's +`IsBitonal_` rules: G42D is accepted even on a 256-level photomap, where +`XieTransport.c` would answer BadMatch. + +### 2. At Export (minor 21), with the photomap's compression byte forced + +The client's `XieExport` to a depth-1 pixmap, for each value of the photomap's +`compression_scheme` byte (`harness/mame/xie_fix.lua` pokes it at file +`0x1B256`; the server never initialises it, so it holds the heap fill `0x55`): + +``` +scheme 0 PCM export: pixmap 4096/4096 no X error +scheme 1 G31D export: pixmap 4096/4096 no X error +scheme 2 G32D export: pixmap 4096/4096 no X error +scheme 3 G42D export: pixmap 4096/4096 no X error +scheme 4 DCT export: FAILED BadValue, minor 21 +scheme 5 (bogus) export: FAILED BadValue, minor 21 +``` + +**DCT and a bogus value are treated identically; 0..3 are accepted.** So in this +ROM the valid set is `0..3` and scheme 4 has no implementation — it is refused, +not silently mis-decoded. + +### 3. The ROM has no JPEG machinery + +Searched `vt1200_v22.bin` (md5 `b46a3e442ccb86134d2356bd70d22fd2`): + +* no `JPEG` / `DCT` string (the only compression-related strings are + `DDX_RENDER: unsupported compression scheme`, `error initializing scalution` + and `5 - Scalution Test`); +* the standard JPEG Annex K quantisation table is **absent**, in both the + zigzag order code stores and the natural row order — and no flat 8x8 table + either. A DCT codec carries one of these; this ROM carries none. + +`DDX_RENDER: unsupported compression scheme` exists but did **not** fire in any +of the runs above (0 occurrences in the MAME debug log for schemes 4 and 5), so +it belongs to a path these probes do not reach — reported as an open detail +rather than claimed as the explanation. + +### 4. Cross-check: PCM is the only scheme that helps in practice + +The working display path uses `XieK_PCM` throughout, because the transport is +`XieK_Tile` on a bitonal image and, per the reference rules above, that admits +PCM only. On a depth-1 server there is no non-bitonal visual, so `XieK_DCT` is +unreachable by protocol rule as well as unimplemented. + ## Cross-reference: the DTJ article `docs/dec/DTJ-1991-v03-04-bitonal-image-accelerators.pdf` — "Hardware @@ -136,3 +199,8 @@ DTJ Vol. 3 No. 4, Fall 1991). Reading it against the headers above: So the article's "image board" is the bitonal G3/G4 decompressor, and JPEG/DCT is the scheme XIE defines for the continuous-tone data type that the same protocol carries — the two correspond exactly to `XieK_G42D` and `XieK_DCT`. + +This is consistent with the measurement above: the ROM has the G3/G4 half +(`0..3` accepted, scalution test present) and no DCT half (scheme 4 refused, +no JPEG tables). The terminal is depth 1, so the continuous-tone data type the +DCT scheme exists for has no visual to land on. diff --git a/xie/xie_schemes_raw.py b/xie/xie_schemes_raw.py new file mode 100644 index 0000000..2e83b16 --- /dev/null +++ b/xie/xie_schemes_raw.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Ask the server, in raw XIE 2.3 protocol, one question: + + for each XieK_ compression scheme, does SetTransport accept it? + +It builds a fresh bitonal photomap per scheme (a photomap that already has a +transport must match it on a second SetTransport, which would turn every later +attempt into a BadMatch for the wrong reason) and then sends a real +xieSetTransportReq (minor 20, 140 bytes) and prints whatever comes back. + +Usage: xie_schemes_raw.py [host] +""" +import os +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(HERE, "evidence")) + +from probe23 import Cli, describe_all # noqa: E402 + +NAMES = {0: "PCM", 1: "G31D", 2: "G32D", 3: "G42D", 4: "DCT", 5: "(5)"} + + +def make_photomap(c, slot, levels=2): + """xieCreatePhotoReq: bitonal 64x64, 1 component, 2 levels. + + A distinct resource id per call -- reusing one after the first photomap owns + it is a BadIDChoice, which would mask the answer we are after. + """ + pid = c.new_id(0x0AAAA + slot * 0x100) + w = [2, pid, 64, 64, 0, 0, levels, 0, 0, 0x00000100, 0] + out = c.send(8, 12, b"".join(struct.pack(" 1 else 2 + schemes = [int(argv[2])] if len(argv) > 2 else (0, 1, 2, 3, 4, 5) + mode = int(argv[3]) if len(argv) > 3 else 1 + print("levels=%d mode=%d (%s)" % (levels, mode, + "GetStream" if mode == 1 else "PutStream")) + for scheme in schemes: + pid, created = make_photomap(c, scheme, levels) + if "ERROR" in created: + print("%-5s create FAILED: %s" % (NAMES[scheme], created)) + continue + r = set_transport(c, pid, scheme, mode) + kind = describe_all(r) + if "ERROR" in kind: + verdict = "REFUSED" + elif "NO REPLY" in kind or "NO PACKETS" in kind: + verdict = "ACCEPTED" + else: + verdict = "?" + print("%-5s SetTransport -> %-8s %s" % (NAMES[scheme], verdict, kind)) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/xie/xiescheme.c b/xie/xiescheme.c new file mode 100644 index 0000000..0b4ddae --- /dev/null +++ b/xie/xiescheme.c @@ -0,0 +1,173 @@ +/* + * What does THIS ROM answer for each XieK_ compression scheme? + * + * Built against DEC XIE 2.3 (libXie from DEC's own sources). For every scheme + * value 0..5 it issues a real SetTransport (via XieSetStream) in GetStream mode + * on a bitonal photomap, and then on a grayscale photomap if the server will + * create one -- and reports the protocol error the server returns. + * + * Xlib's default handler exits on an error, so a catching handler is installed. + */ +#include +#include +#include + +#include +#include +#include + +/* the client never calls the DDX dispatch table; satisfy the linker */ +unsigned long (*xieDdxProc[1])() = { 0 }; + +static int last_error; +static int last_req_minor; +static char last_text[64]; + +static int catch(Display *dpy, XErrorEvent *e) +{ + last_error = e->error_code; + last_req_minor = e->minor_code; + XGetErrorText(dpy, e->error_code, last_text, sizeof last_text); + return 0; +} + +static const char *scheme_name(int s) +{ + switch (s) { + case XieK_PCM: return "PCM "; + case XieK_G31D: return "G31D "; + case XieK_G32D: return "G32D "; + case XieK_G42D: return "G42D "; + case XieK_DCT: return "DCT "; + default: return "5 "; + } +} + +/* Create a photomap and push pixel data into it so it is a real image. */ +static XiePhotomap make_map(Display *dpy, Window win, int bits, int ncomp, + int cls, const char *tag) +{ + XieImage img; + XiePhotomap pm; + unsigned long lvl[3]; + unsigned int fill; + int w = 64, h = 64, nbytes; + + lvl[0] = lvl[1] = lvl[2] = (unsigned long) bits; + + last_error = 0; + img = XieCreateImage((unsigned long) w, (unsigned long) h, + XieK_PCM, (unsigned char) XieK_BandByPlane, + (unsigned char) cls, ncomp, lvl, + (unsigned char) XieK_PP0, (unsigned char) XieK_LP270); + XSync(dpy, False); + if (!img || last_error) { + printf(" [%s] XieCreateImage FAILED err=%d (%s)\n", tag, last_error, + last_error ? last_text : "no error, null handle"); + return 0; + } + + if (!uBase_(img, 0)) { + nbytes = (int) uArSize_(img, 0); + if (nbytes <= 0) nbytes = (w + 7) / 8 * h; + uBase_(img, 0) = (unsigned char *) calloc(1, (size_t) nbytes); + uArSize_(img, 0) = nbytes; + OwnData_(img) = 1; + } + nbytes = (int) uArSize_(img, 0); + fill = ((bits + 7) / 8) == 1 ? 0xAA : 0x00; + memset(uBase_(img, 0), fill, (size_t) nbytes); + + pm = XieCreatePhoto(dpy, XieK_Photomap, (unsigned long) w, (unsigned long) h, + (unsigned char) cls, ncomp, lvl, + (unsigned char) XieK_ZeroDark, 1.0, + (unsigned char) XieK_BandByPlane, + (unsigned char) XieK_BandByPlane); + XSync(dpy, False); + if (!pm || last_error) { + printf(" [%s] XieCreatePhoto FAILED err=%d\n", tag, last_error); + return 0; + } + XiePutTile(pm, img, XieK_AllPlanes, 0, 0, 0, 0, + (unsigned long) w, (unsigned long) h); + XSync(dpy, False); + if (last_error) { + /* SetTransport only needs the photomap to exist, so carry on and let the + * transport test report whatever the server says. */ + printf(" [%s] XiePutTile failed err=%d (%s) -- continuing with an empty map\n", + tag, last_error, last_text); + last_error = 0; + } + printf(" [%s] photomap ready (arSize=%d)\n", tag, nbytes); + return pm; +} + +static void try_scheme(Display *dpy, XiePhoto pm, int scheme, int mode, + const char *mode_name) +{ + int i; + for (i = 0; i < 8; i++) last_text[i] = 0; + last_error = 0; + last_req_minor = -1; + XieSetStream(pm, 0, (unsigned char) mode, (unsigned char) scheme, + 0L, (unsigned char) XieK_BandByPlane, 1L, 8L); + XSync(dpy, False); + if (last_error) + printf(" %s in %-9s -> ERROR %d %s (minor %d)\n", + scheme_name(scheme), mode_name, last_error, last_text, + last_req_minor); + else + printf(" %s in %-9s -> ACCEPTED\n", scheme_name(scheme), mode_name); +} + +int main(int argc, char **argv) +{ + Display *dpy; + Window win; + XSetWindowAttributes swa; + XiePhotomap bm, gm; + const char *disp = argc > 1 ? argv[1] : "192.168.99.2:0"; + + dpy = XOpenDisplay(disp); + if (!dpy) { fprintf(stderr, "cannot open %s\n", disp); return 1; } + XSetErrorHandler(catch); + printf("display %s, root depth %d\n", disp, + DefaultDepth(dpy, DefaultScreen(dpy))); + + swa.background_pixel = 0; + win = XCreateWindow(dpy, DefaultRootWindow(dpy), 150, 200, 64, 64, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWBackPixel, &swa); + XSync(dpy, False); + + { + int scheme = argc > 2 ? atoi(argv[2]) : XieK_PCM; + int bits = (argc > 3 && !strcmp(argv[3], "gray")) ? 256 : 2; + int cls = (bits == 2) ? XieK_Bitonal : XieK_GrayScale; + const char *tag = (bits == 2) ? "bitonal" : "gray"; + + printf("\n--- %s photomap, scheme %s ---\n", tag, scheme_name(scheme)); + bm = make_map(dpy, win, bits, 1, cls, tag); + if (bm) { + try_scheme(dpy, bm, scheme, XieK_GetStream, "GetStream"); + /* and, if that was accepted, actually ask for the data */ + if (!last_error) { + unsigned char *buf = 0; + unsigned long got = 0; + last_error = 0; + printf(" XieSetStream accepted; calling XieGetStream...\n"); + XieGetStream(bm, 0, &buf, 65536L, &got); + XSync(dpy, False); + if (last_error) + printf(" XieGetStream -> ERROR %d %s\n", last_error, last_text); + else + printf(" XieGetStream -> ok, %lu bytes, first %02X %02X %02X %02X\n", + got, buf ? buf[0] : 0, buf ? buf[1] : 0, + buf ? buf[2] : 0, buf ? buf[3] : 0); + } + } + } + + XCloseDisplay(dpy); + return 0; +}