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 sources: xieperf, XIElib and the sample server extension
STATUS (updated): a client-chosen XIE image IS displayed on the VT1200. The working path is
XieCreateImage -> XieCreatePhoto -> XiePutTile -> XieExport(photomap -> depth-1 Pixmap) -> XCopyArea(pixmap -> window), run withVT1200_ACCEL=1and the compression poke inharness/mame/xie_fix.lua. Two faults were removed: the accelerator's output-FIFO read pointer was never reset between transfers, anddwt/screenshot.pyneeds--real(1280 wide, 256-byte stride) for the VR319 default. Seeevidence/XIE_IMAGE_DISPLAYED.mdandevidence/png/xie_window_tiles.png. Everything below this banner that says "NO client-chosen XIE image displayed" is historical and is superseded; the corrections are listed in that report.
Vendored copy of the XIE (X Image Extension) sources from X11R6.1
(X Consortium, March 1996). The point of interest is xieperf, the XIE
test/benchmark client, which is the natural first client to point at the
VT1200's DECwindows server because that server advertises a Xie extension:
$ xdpyinfo -display 192.168.99.2:0
...
number of extensions: 2
ServerManagementExtension
Xie
...
vendor string: DECWINDOWS DigitalEquipmentCorp.
version number: 11.0
...
depth of root window: 1 plane
What is here
| path | contents |
|---|---|
xc/programs/xieperf/ |
xieperf client: 35 .c files, xieperf.h, Imakefile, xieperf.man, and the images/ test images (image.001–image.014) |
xc/lib/XIE/ |
XIElib, the client library xieperf links against |
xc/include/extensions/XIE*.h |
XIE.h, XIEproto.h, XIEprotost.h — wire protocol and client API headers |
xc/programs/Xserver/XIE/ |
the X11R6 sample server-side XIE implementation (reference only; not needed to build xieperf) |
xc/doc/hardcopy/XIE/ |
XIE protocol spec, XIElib spec and sample-implementation architecture, as plain text |
280 files, about 8.3 MB. PostScript copies of the documents were dropped;
the .txt renderings are kept.
Provenance
Downloaded 2026-11-01 from the official X.Org release archive:
- https://www.x.org/releases/X11R6.1/tars/xc-1.tar.gz
—
xc/programs/xieperf,xc/lib/XIE,xc/include/extensions/XIE*.h,xc/programs/Xserver/XIE - https://www.x.org/releases/X11R6.1/tars/xc-3.tar.gz
—
xc/doc/hardcopy/XIE(documents)
Checksums of the original tarballs:
d138b68392d4ca49939fd0da46180763a23e82838fb30cb964ed2c5bc6d6c8d6 xc-1.tar.gz
cec75d6ae72d437783c72c2892d637a8a2acfd0f2088358ed024064a1a687976 xc-2.tar.gz
a980821b36607773eba8735ad5608e03c503fe139656f4e3beb64c1b5049fab9 xc-3.tar.gz
xc-2.tar.gz holds the same specs as nroff sources under
xc/doc/specs/XIE/; it was not needed for this copy.
Other places the same tree is available:
- Browsable single files (Debian's
vnc4source package bundles an XFree86 4.3-eraxc): https://sources.debian.org/src/vnc4/4.1.1%2BX4.3.0-37.6/unix/xc/programs/xieperf/ and https://sources.debian.org/src/vnc4/4.1.1%2BX4.3.0-37.6/unix/xc/lib/XIE/ - XFree86 3.3.6 mirror: https://github.com/ljmccarthy/XFree86-3.3.6
- X11R6 (1994) and later releases: https://www.x.org/releases/X11R6/, https://www.x.org/releases/X11R6.1/, https://www.x.org/releases/X11R6.4/
XIE was removed from the reference X11 distribution in X11R6.7 (2004), so
there is no current distro package for xieperf.
Licence
X Consortium / MIT-style terms for the X11 code, plus the AGE Logic notice
on the XIE sources. Both are reproduced at the top of every source file and
in xieperf.man; the AGE Logic terms permit use, modification and sale
provided the notice is preserved.
Using it against the VT1200 — status
It builds and connects, and it does identify the server, but it cannot yet
run the test suite: the VT1200 implements XIE protocol 2.3, while
xieperf speaks 5.0. Verified against 192.168.99.2:0:
xieperf - XIE performance program, version 1.0
XIE V2.3
FULL server
Screen visual class is StaticGray
Colormap has 2 entries
Effective depth is 1-bit
...
Class name: ColorAlloc
X Error of failed request: BadLength
Major opcode of failed request: 129 (Xie)
Minor opcode of failed request: 0 ()
The request that kills it is the client's 5.0 QueryTechniques (minor 2). In
2.3 that slot is not QueryTechniques: the ROM dispatch table says slot 2
accepts exactly one word and returns success without a reply, while the
5.0 request is two words and gets BadLength. So xieperf hangs at one word
and dies at two.
The ROM analysis (GHIDRA_XIE.md) found 46 request slots, 25 implemented.
Only slot 1 can be matched to its 5.0 counterpart; the numbering is otherwise
not 5.0's. No slot implements QueryTechniques or any technique-capability
reply, so -showtechs cannot work against this server.
Raw X11 probes (no XIElib) plus the ROM table give this map:
| minor | 5.0 request the client thinks it is | what 2.3 slot N actually does |
|---|---|---|
| 1 | QueryImageExtension |
same request (2 words); replies 2.3, class 0x20, 8 levels |
| 2 | QueryTechniques |
1 word, success with no reply; not a query |
| 6 | QueryColorList |
1 word (bare header) → reply, length 0 (no argument) |
| 11 | QueryPhotomap |
3 words (5.0: 2); id at byte 4 → BadIDChoice if null |
| 23 | GetClientData |
not implemented → BadRequest |
| 24 | QueryPhotoflo |
4 words (5.0: 3); id at byte 4 → BadIDChoice if null |
BadLength always arrives with minor_op = 0 because the handlers return 16
to the dispatcher and the generic error path has no minor to report; errors XIE
sends itself do carry the real minor (minor 23 reports BadRequest with
minor_op 23). So the 0 in the error is a server-side artefact, not evidence
about which request failed.
The full evidence is in evidence/ (xie_wire_probe.txt,
xieperf_showtechs_vt1200.txt, xieprobe_vt1200.txt) and
ghidra-evidence/ (dispatch_table.txt, listings.txt,
length_sweep_wire.txt).
So: the extension is real and answers XIE requests; xieperf 5.0 is the
wrong client for it. A minimal 2.3 session client now exists:
./xiectl23.py # against 192.168.99.2:0
extension present=1 opcode=129 first_event=64 first_error=0
init (1,2w): REPLY len=8 010102000800000002000300...
qdefs (3,2w): REPLY len=0 0141030000...
qevents(6,1w): REPLY len=0 010004000000000002000000 01000000 01000000 01000000
term (2,1w): TIMEOUT # Success, no reply
Order matters: QUERY_EVENTS returns 2,1,1,1 only after INIT_SESSION;
sent alone it returns zeroes. In the INIT reply only major 2 / minor 3 are
stable across connections; the tail words vary, so do not parse them as a fixed
capability record.
That is the session half. Live probing has since established the resource
rules: the per-connection id base/mask, the reserved bit in new ids, slot 8
op 2 as a working create, and the exact reason slot 21 refuses to export
(BadMatch from resource flag +0x28). The exportable object lives in the
photoflo/pipeline layer, not in slot 8. See
evidence/xie_create_export_probe.txt, GHIDRA_XIE_IMAGE_PATH.md and
GHIDRA_XIE_PIPELINE.md (when present).
Is there an XIE client inside the ROM?
No. The ROM contains the XIE server only. A MAME trace with a breakpoint
on XieProc (0x3F525CC0) saw zero hits in three runs:
| run | configuration | XieProc hits |
|---|---|---|
| plain boot → level-0 diagnostics | 1200 frames | 0 |
VT1200_SKIP_LEVEL1=1 → OS/Terminal Manager |
4500 frames | 0 |
forced jump into the Scalution test (0x35EA8) |
900 frames | 0 |
| control: same tracer on the menu idle PC | 60 frames | 4,634,965 |
The control run matters: MAME discards debugger printf output unless it is
started with -debuglog, which is why the first zeroes were not yet
trustworthy. With -debuglog the control fires, so the zeroes are real.
The “5 - Scalution Test” is a hardware diagnostic: its body writes the
image-option/scaler registers (0xCA2B0, 0xCA330, 0xCA3A0, 0x5600020,
0x5600030, 0x5600100) directly. It never calls the server or builds an XIE
request. The ROM’s only Xie strings are the server registration messages.
So there is no working 2.3 client sequence to replay. The client in this
directory is ours, and the PUT_TILE band gate still has to be settled from the
server model plus live probes. Report: MAME_XIE_TRACE.md; reusable tracer:
harness/mame/xie_trace.lua.
Clients and tools in this directory
| file | what it is | status |
|---|---|---|
xiectl23.py |
minimal XIE 2.3 session client: INIT_SESSION → QUERY_OP_DEFAULTS → QUERY_EVENTS → TERM_SESSION | verified against the VT1200 |
probe23.py |
live raw-X11 probe with a persistent connection: session, defaults, raw slots, create/sweep | used for all wire evidence |
xshow.c / xshow |
1-bit image viewer using core X11 PutImage; reads the window back and compares on-pixel counts | verified on the VT1200 (MATCH), not XIE |
xshow is the display half and an honest fallback, not a pure XIE image
client. The XIE path itself is now much further than the session: the create
request with a non-zero level word at byte 28 (2 = 1 bpp) produces an
exportable resource, and slot 21 exports it into a real X window + GC with no
error.
Slot 20 arms the resource's bands. The request that succeeds is 35 words with
id@4, byte8 = 64, byte12 = 2, byte14 = 1, byte16 = 1, byte13 = 1
(mode). After that, PUT_TILE no longer returns BadAccess; it reaches the
class check:
- class-2 resource (
create op 2) → BadMatch:PUT_TILEpasses class 3. - class-3 resource (
create op 1) → BadIDChoice.
So the target is a class-3 resource with byte 40 == 0. WITHDRAWN — the
byte-40 rule was a misreading. Measured instead:
create op 1andcreate op 2call the same routine (file0x678DA); the only op-dependent value it passes is(op == 2) ? 1 : 0, andopis also the level loop count (DSJat0x67A24).create op 1→ object class 3 butid@0 == 0: the ID is stored nowhere in the object, so the table lookup can never find it →BadIDChoice.create op 2→ object class 2 withid@0set → addressable.- Only payload word 1 is accepted as the ID (any other position → create
BadIDChoice). - Byte 40 is a copy-out field: the helper (file
0x66FA0) copies*obj(0x140)out when the caller passes a non-NULL out-param, and only the NULL-out-param variant tests it — raising0xE, not0x8. So patching byte 40 could never have fixedPUT_TILE, which is what was measured.
So the real missing piece is the request that both selects class 3 and
stores an ID. Evidence: evidence/class3_id_not_stored.txt.
The create opcode space is exhausted
Every accepted create opcode has now been tested at its own required length:
op=1 12 words create OK allocates query BadIDChoice put BadIDChoice
op=2 12 words create OK allocates query REPLY put BadMatch
op=3 BadIDChoice
op=4 6 words "OK" allocates NOTHING, ID never addressable
op=5 7 words "OK" allocates NOTHING, ID never addressable
op=6 >=6 words "OK" existing resource left intact and queryable
op 4 and op 5 are not creates — both begin by looking up an existing
resource (op 4 looks up payload word 2 through 0x7EA6FA0; op 6 looks up
payload word 1 through 0x7F174A4). Only op 2 yields an addressable
resource, and its class is 2. So no create opcode produces a class-3 resource
registered by ID. Evidence: evidence/create_opcode_sweep.txt.
Slot 11 is not the class-3 bind
slot 11 needs exactly 3 words (2 ids) and performs two typed lookups
(call #1 pushes the literal 0xffff, call #2 pushes 0). With two valid,
queryable class-2 resources:
A=0040C000 class=2 b40=0 B=0040D000 class=2 b40=0
slot11 [A,B] -> BadIDChoice (14)
slot11 [B,A] -> BadIDChoice (14)
after: both unchanged, both still queryable, put -> BadAccess (10)
The operand types are fixed inside the handler, so slot 11 cannot be steered to
a class-3 bind from the request. Evidence:
evidence/slot11_bind_and_second_allocator.txt.
The element is created by EXPORT, not filled by PUT_TILE
element = *( *(res+44) + 0xd8 ) is 0 after create, after arm1, and after
PUT_TILE; it only becomes non-zero inside EXPORT (0x1B234:
EX_BAND a5=003DC4B0 elem@d8=003FDA10). No write to element+0x28 was ever
observed. So PUT_TILE does not populate the element — EXPORT builds it — and
the source image pointer it copies stays 0. Evidence:
evidence/element_pointer_timing.txt.
Export resolves the resource; only src_base is empty
Values written by the export copy block (0x1B296), named against the
firmware's own dump strings:
rp+0x5a8 = 0x0040C000 src_res_id -> our resource, correctly resolved
rp+0x5d0 = 64 src_width
rp+0x5f0 = 64 src_height
rp+0x610 = 0 src_base <-- the pixel buffer: MISSING
rp+0x738/0x758 = 64/64 dst_img PWIDTH/PHEIGHT
So EXPORT is not failing to find the resource — it has the id and the
64×64 size. The single empty field is src_base, loaded at 0x1B238
(MOVE *A5(0x28),*A11(0x610)), which no write watchpoint ever saw written.
The missing datum is a pixel buffer for the source image, produced only by the
import. Evidence: evidence/export_renderparams_map.txt.
slot 47 is a byte-swapped dispatch trampoline — and is unreachable
file 0x697B4 swaps the bytes of request words at bit offsets 0x10/0x18,
0x20/0x28, 0x30/0x38, then re-dispatches through the same table at
0xD3F90 (SLL 5 ; ADDI 0xd3f90 ; MOVE *A11,A11 ; CALL A11).
Tested and closed: with breakpoints on the trampoline body
(0x3F54BFF0 table read, 0x3F54C000 CALL — count 36→38, no command errors),
minor 47 returned BadRequest for plain and byte-swapped argument words, and
the trampoline never executed (0 TRAMP hits). So there is no
client-reachable byte-swapped request family. It is also self-referential: the
swaps cover offsets 2–7 only, never the minor byte at offset 1, so a client
request for minor 47 could only re-enter it.
The default handler 0x64C3C (shared by every unimplemented slot and by slot 0)
is the BadRequest responder — which is why probing unimplemented slots
returned ERR1. Evidence: evidence/trampoline_unreachable.txt.
src_base sweep across every remaining slot — none writes it
Each remaining implemented slot was exercised at its own required length with
[resource_id, 0, 0…], in a full create→arm1→<slot>→PUT_TILE→arm2→EXPORT
sequence, reading rp+0x610 from the export breakpoint and diffing the window
against its pre-export content:
slot 18,40,44,33,14,15,9,13,24 : src_base = 00000000 window changed = False
Every export still resolves the source id and the 64×64 geometry
(5a8=<id> 5d0=40 5f0=40), and 610=0 in every case. Coverage is now
complete: every implemented slot (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 18 19 20 21 22 24 33 40 43 44, plus the BadRequest default and the unreachable
trampoline) has been exercised against a created resource.
This is not a proof of impossibility: only one argument shape per slot was
tried. Evidence: evidence/src_base_sweep_all_slots.txt.
Multi-operand shapes fail at the same typed-lookup wall
The bind-family handlers (33 40 44 18) and 43 were then given two and
three live ids. Two findings:
- The third operand is an id, not a flag:
slot33 [A,B,t]fort=1..9returnsBadIDChoicewithbad = t(andt=0→BadValue). - With three valid ids the lookup still fails, because it is typed:
slot33 [A,B,A] / [A,B,B] / [A,B,C] / [A,A,A] -> BadIDChoice, bad = 3rd id
slot40 [A,B,A] / [A,B,B] -> BadValue, bad = A
slot44 [A,B,A,0] / [A,B,B,0] -> BadValue, bad = A
slot18 [A,B,0] -> BadMatch
slot43 [A,B,0...] -> BadValue
Every multi-operand request fails at a typed lookup of one of its own
operands: the operand exists and is queryable, but lacks the class the handler
requires. That is the same wall as PUT_TILE, reached from four more
directions. src_base stayed 0 and no window changed. Evidence:
evidence/multi_operand_shapes.txt.
The single wall, restated precisely
Every client-reachable path — single- and multi-operand, all implemented slots —
fails for one reason: the server's resource model requires a resource class that
its create path will not issue with a stored id (create op 1 builds a class-3
object but leaves id@0 = 0). Until that class can be created addressably, no
request reaches an import that would set src_base.
Why that class cannot be created addressably — CORRECTED
Superseded. The "mutually exclusive" argument below is wrong:
op=1does reach the registering body. Measured with breakpoints on0x67B04/0x67B34,create op=1registers id0040C000→ object0036A950, which is a class-1 photoflo (id@0set,w=3259088 h=1,query → BadAccess).create op=2registers a class-2 resource (003FD7D0,w=64 h=64,query → REPLY). The class-3 object seen earlier (class 3,id@0 = 0) is an intermediate that is never the registered object. So the accurate statement is: the id is registered to a class-1 photoflo or a class-2 resource; no create opcode registers a class-3 object. Evidence:evidence/registration_hits_corrected.txt.
Registration audit: only class 1 and class 2 ever get an id
A full audit with breakpoints on 0x67B04 (ID store) and 0x67B34 (register):
arm1 / put / arm2 / slot43 / export / arm1(photoflo) / put(photoflo) -> 0 hits
create op=2 id=0040C000 -> REGIDSTORE obj=003FDA10 (class 2, query REPLY)
create op=1 id=0040D000 -> REGIDSTORE obj=00385890 (class 1, query BadAccess)
Registration happens only inside create, and only those two object kinds ever
receive an id. The class-3 object built by the allocator (file 0x6CA82) never
appears in a REGIDSTORE.
Walking the photoflo's +0x160 chain the way export does
(0x1B230 MOVE *A5(0x160),A5):
lvl0 00385890 id@0=0040D000 class=1 p44=00386010 <- resource-like
lvl1 00386010 id@0=003859D0 class=80 p44=00386290 <- first word is a pointer
lvl2 00386290 id@0=00386150 class=144 p44=003C7150
lvl3 003C7150 id@0=003863D0 class=144 p44=00000000 chain ends
Only lvl0 has a resource layout; from lvl1 the first word is a pointer and the
"class" byte is 0x50/0x90, so those are internal blocks, not resource
structs, and carry no client id. So there is no client-visible id whose
registered object is class 3 — the two ids a client can obtain are registered
to class 2 and class 1. Evidence: evidence/photoflo_chain_no_id.txt.
The armF state gets furthest: BadAccess instead of BadMatch
Tracing the one configuration never followed before (I = image, F = photoflo):
create I 0x40C000 -> REGIDSTORE id=0040C000 obj=003FDC50 ; REGISTER
create F 0x40D000 -> REGIDSTORE id=0040D000 obj=00388090 ; REGISTER
arm F then PUT_TILE I -> S19LOOK a8=0000000A (BadAccess, 10)
arm I then PUT_TILE I -> S19LOOK a8=00000008 (BadMatch, 8)
PUT_TILE F (unarmed) -> S19LOOK a8=0000000E (BadIDChoice, 14)
Arming the photoflo genuinely moves the typed lookup past the stage that yields
BadMatch and stops it at a BadAccess check — a real state difference and the
furthest any configuration has reached. No configuration reached the handoff
(0x6B640) or wrote src_base; the registration sites get 0 hits outside
create.
Caveat, recorded because it invalidates a column I have been quoting: the
LOOK breakpoint at 0x6BA38 prints cls=01 for both the armF and armI cases,
and earlier printed an incrementing 01,02,03 across calls. So A7 there is the
export element, not the resource struct — its cls/b40 columns are not
resource fields. Only S19LOOK's return code (a8 at 0x6A7B4) is trustworthy.
Evidence: evidence/armF_lookup_codes.txt.
Three more assumptions tested and falsified
Chasing the BadAccess, three structural guesses were each disproved within one
step — recorded so nobody repeats them (evidence/three_wrong_assumptions.txt):
0x7F638C2is not the allocator — file0x1238C2is a printf-style logger (format descriptors0x3fb1cea0etc.). The real allocation is0x7EACA82→ file0x6CA82.0x7F174A4is not the XIE resource lookup — file0xD74A4indexes a table at0x21FFE0byN = (id & 0xFFF00000) >> 12, bounds-checked against a count of 16 at0x225870. That is the core-X client index; for our idsN = 1024, out of range, bucket reads 0.@0x1AD980/@0x1AD990/@0x1AD9A0are not XIE list heads — they read0x02000002,0x04000200,0x00000400, and following them as pointers reads zeros.
The direct observations stand (S19LOOK a8 = 0x08 / 0x0A / 0x0E at 0x6A7B4),
but every explanation built on those three functions is now void.
The slot-19 lookup is file 0x6B966 — and it does not produce the 0x8
rom:0006a7ae CALLA 0x07eab966 → file 0x6B966 (not the 0x66FA0 helper).
Its flow:
6B990 CALLA 0x07F174A4 ; returns an object pointer
6B99A JRNE 0x6BA8C ; null -> MOVK 0xe (BadIDChoice)
6BA8C A8 = object->byte4 ; class byte
6BA90..6BA9C class 1 -> 0x6B9C4 ; class 2 or 3 -> 0x6BA1E ; else 0xe
6BA1E arg3==NULL ? require object->byte40 == 0 (else 0xe) : copy byte40 out
6B9C4 (class 1) requires arg3 != NULL, then a two-step byte8->byte8 /
byte12->byte8 deref
It raises only 0xE and 0xA — there is no 0x8 route before 0x6BBC0.
Measurements that contradict a simple story, recorded as-is:
- sweeping request payload word 5 (the
arg6the code branches on) over0,1,2,0x100,0x10000,0x80000000,0xFFFFFFFF,0xFFFFFFFEgivesBadMatchevery time —arg6has no observable effect; - the
0xE-build site (0x6BA86) did fire during a put whose reply wasERR8; - the
0xAsite (0x6BAC6) and an in-functionMOVK 0x8site (0x6BB4C) did not fire during that put; - the shared reply helper
0x7EFFA5Edid not fire at all.
So the ERR8 the client receives is not produced by any site we can see, and
site-based attribution is unreliable. Evidence:
evidence/lookup_is_6B966.txt.
Bounded next step: disassemble the remainder of 0x6B966 (0x6BBC0..0x6BBE4)
and the 0x6BAEA path, logging A8 at every return, to find the 0x8's true
origin. Until that is known, BadMatch cannot be interpreted.
Both properties the import needs are decided in the same routine
(file 0x678DA):
679A2 SUBK 0x2,A11 ; MOVK 0x1,A11
679A6 JREQ 0x679AA ; op==2 -> flag 1
679A8 SUB A11,A11 ; else -> flag 0
...
6CAF6 JREQ 0x6CAFC
6CAF8 MOVK 0x2,A11 ; flag!=0 -> class 2
6CAFC MOVK 0x3,A11 ; flag==0 -> class 3 <-- 6CAFE stores it
...
67A20 MOVE *A9(0x20),A11 ; A11 = op
67A24 DSJ A11,0x67AFC ; op-1 != 0 -> jump to the REGISTERING body
67A28 ... ; op-1 == 0 -> photoflo path
67B04 MOVE *A9(0x40),*A11(0x0) ; ID -> obj+0
67B34 CALLA 0x07F16CA2 ; register in the resource table
op = 1→ flag 0 → class 3, butDSJfalls through to the photoflo path (0x7F63A30, 20 words, error string "error allocating photoflo"), so the ID is never stored and never registered.op = 2→ flag 1 → class 2, andDSJjumps so the ID is stored and the resource registered.
PUT_TILE requires class 3 (slot 19 pushes MOVK 0x3 before the typed lookup),
and the only class-3 producer never registers. So the import is unreachable —
not because a request shape is missing, but because the two properties the
lookup requires cannot coexist in the implementation.
op=4 was also ruled out: it increments a counter on the target (obj word at
byte 6: 1→2→3 after two calls, no new allocation) and PUT_TILE stays
BadMatch at every count. Evidence:
evidence/class3_registration_impossible.txt.
MAME trace: the limit we reached
A single MAME instance on the host tap (-networkprovider taptun,
-cfg_directory harness/mame/cfg-net) makes the emulated terminal available at
192.168.99.2, and harness/mame/xie_resource.lua traces XieProc requests
and the resource lookup. With -debuglog, every request appears as
XIEHIT buf=... 0181 0002 ..., and the lookup check prints
LOOK a7=... id=... class=... b36=... b40=....
Results:
PUT_TILEcan be forced through the class check with a debugger branch skip (0x6BAF6→0x6BBCE); it then returns success — a false success, see below.The exported window is still independent of the tile data: solid-white and solid-black tiles produce the same 64×64 pattern. That pattern is the XIE export default/dither, not the supplied pixels.WITHDRAWN.- Therefore the missing piece is the band/execute format (the photoflo element/bind layer), not just the class check. The class skip only lets the request through; the data never reaches the export sink.
Correction: export has never painted anything
The 8888…2222… pattern is the window's own default background, not XIE
output. Measured on one instance, one connection, 64×64 window:
BASELINE (window created+mapped+GC, NO export) nz=512 first=88888888888888882222222222222222
AFTER (create→arm1→PUT_TILE→arm2→EXPORT) nz=512 first=88888888888888882222222222222222
identical to baseline: True
So every white-vs-black comparison was vacuous. The measured reason export draws nothing: the chain is intact but has no source image —
EXPORTENTRY
EXPORTFLAG f=01 ; global @0xA6220 non-zero, render path taken
EX_BAND a5=003DC4B0 elem@d8=003FDA10 ; photoflo→band→element IS present
EX_ELEM a5=003FDA10 f28=00000000 ; element+0x28 == 0 → no imported image
The driver copies element+0x28 into the render params (0x1B238 MOVE *A5(0x28),*A11(0x610)), and the per-element render loop
(0x1BC38..0x1BF30, element types 1–5 = crop/scale/rotate/mirror/export)
is never reached. Because the import never runs (PUT_TILE → BadMatch),
nothing fills element+0x28. Evidence:
evidence/export_paints_nothing.txt, evidence/export_chain_trace.log.
The correct acceptance test is therefore "export output differs from a no-export baseline", not "export matches a known pattern".
A band dump at slot 20's comparison (0x6AB88) shows the arming state
f1=01 f2=02 f4=00010000 f8=0 f12=0 f16=0. The second execute produces no
BAND line at all: it fails in the lookup/state path before the band
comparison, so no choice of the 35-word comparison fields can reach it.
The 0x6BA38 pointer was then tested as the resource: a second connection's
before/after peek at the same address (0x003FDA10) returned the terminal
message string "Connection request from IP host 192.168.99.1", identical
before and after PUT_TILE. So that breakpoint is an unrelated lookup, not
the XIE resource; the requested resource diff cannot be obtained with it.
GHIDRA_XIE_IMPORT.md's mapping of that path is therefore wrong. Evidence:
evidence/mame_look_not_resource.txt.
The real resource was then located by scanning the XIE table entry for the
client id (id followed by handler pointer 0x3F566ED0; resource pointer at
+12). At 0x003FDC50, the struct is
id | class=02 | width=64 | height=64 | band-ptr@36 | ptr@44. Its band record
is at resource+36 (0x008FE730). A full before/after diff around
PUT_TILE shows:
- resource struct: byte-identical
- band record: byte-identical
- XIE globals
0x1AD800..0x1ADB00: no changed bytes - second slot-20 execute: still
BadMatch
So the post-put gate is not in the resource, the band record, or that
global block. Evidence: evidence/mame_state_diff.txt.
Write watchpoints during PUT_TILE then identified the gate:
WPHIT res addr=003FDC80 data=00000000 ; resource byte 48 = 0
WPHIT band addr=008F6750 data=00000001 ; band byte 12 = 1
Slot 20 ANDs band+0x60 (byte 12) with request byte 16 and requires zero, so
after the put the second execute must use v16 = 0:
arm1 v16=1 -> OK
put -> OK
arm2 v16=0 -> OK
arm2 v16=2/3/4/0x10 -> BadMatch
That solves the arm-2 gate. Export is still independent of the pixels:
solid-white and solid-black both give nz=512 first=88888888…2222…. The band
sink pointer band+0x30 is zero; poking it to the resource’s +44 pointer did
not change that.
Slot 7 {op=1, type=0, id} was then tested as the bind: with it PUT_TILE
returns BadIDChoice, and the export pattern is unchanged. So slot 7 is not
the sink bind either. Evidence: evidence/mame_bind_slot7.txt.
Static decode of the payload handoff 0x7EAB640 (file 0x6B640) shows it is
the import-element executor: it selects element type 1/2/3 from
client+8, takes the band subobject at band+0xd8, calls 0x7E80008, and
clears the transient payload pointer at band+0x28. band+0x30 is not used
by the handoff at all. So the band sink assumption was wrong; the missing
piece is the three-element pipeline definition and its export element, not the
handoff target. Evidence: evidence/payload_handoff_6b640.txt.
A bit/byte correction matters: *A9(0xd8) is byte 27. The create writes the
band record pointer at byte 27 of the +44 array (u32@27 = 0x003FDA10);
the +36 array has zero at that byte but slot 20 does not use it for the
handoff. So the handoff itself is not a no-op; the element array used by
slot 20 is the one that lacks the record.
Attempting a three-element pipeline (create with band count 3 and levels
[2,2,2]) makes arm 1 fail BadMatch; the one-band path works but export is
still data-independent.
Populating the +36 element record by rdebug poke (r36+27 ← 0x003FDA10, the
valid band record from the +44 array) also leaves export unchanged:
solid-white and solid-black both give nz=512 first=88888888…2222…. So the
export path is not driven by that element array either. Evidence:
evidence/element_poke_no_effect.txt, evidence/pipeline_3_elements.txt.
Calling slot 21 before PUT_TILE also gives the same pattern. Slot 21
allocates a 0xC8-byte export context, calls 0x7E5B152, then frees the context
at 0x630B0; the export process block reads the band’s transient payload
pointer at band+0x28 (byte 5), which slot 19 clears after the handoff. No
ordering of the current requests captures the tile. Evidence:
evidence/export_before_put.txt.
The device-dependent dispatcher 0x7E80008 (file 0x40008) was instrumented
at its CALL A11 (0x40086): no DDCALL occurred during PUT_TILE. The
tile data does not reach a DD element worker at all; the dispatcher’s early
type checks reject before the worker call, or the handoff does not reach it.
Instrumenting the whole path then showed the stronger result: with
XIEHIT confirming that minor 19 reaches XieProc, none of the slot-19
entry (0x6A76E), the handoff (0x6B640), or the DD dispatcher breakpoints
fire. The “PUT_TILE OK” is the debugger branch-skip taking an early return,
not an import. The earlier put OK results are therefore not evidence of
import, and the minor-19 handler mapping itself needs re-verification.
Evidence: evidence/class_skip_false_success.txt, evidence/no_dd_worker.txt.
The live dispatch table then confirmed entry 19 is correct
(0x3F553B70 → file 0x6A76E), and with the branch skip removed the real
trace is:
XIEHIT ... 1381 0088 ... ; minor 19 reaches XieProc
S19ENTRY ; 0x6A76E reached
S19LOOK a8=00000008 ; lookup returns BadMatch
put -> ERR8 bad=id minor=19
no S19HANDOFF ; 0x7EAB640 never called
So the real path is blocked at the lookup: it needs a class-3 resource that is
registered by ID. Evidence: evidence/dispatch_entry19.txt.
Superseded: the "byte 40 == 0" requirement stated here and below was a misreading. Byte 40 is a copy-out field, and the actual reason class 3 is unreachable is that
create op 1never stores the ID atobj+0. Seeevidence/class3_id_not_stored.txtand the correction near the top of this file. The paragraphs that follow record what was tried under the wrong assumption; they are kept for history, not as conclusions.
The bind/copy helper 0x672A4 explains why no class-3 resource qualifies:
for class 2 or 3 it executes resource+0x140 = source pointer
(0x673F4). Slot 11 is a two-resource bind (lookup id@4, lookup id@8, call
0x672A4), so any class-2/3 resource created or bound through this helper has
a non-zero byte 40. No request in the recovered set produces class-3 with
byte 40 clear; slot 43 calls the same helper, and its type descriptors/inputs
remain the only untried route. Evidence: evidence/bind_672a4.txt.
Slot 43 with the source id duplicated as its destination field
([A, A, 0…]) partially changes the resource — the lookup error moves from
BadMatch to BadAccess once — but the state is unstable: with slot-20
arming it returns to BadMatch, and the real PUT_TILE never passes. No
class-3 resource with byte 40 = 0 and a usable band is produced. Evidence:
evidence/slot43_self.txt.
Instrumenting slot 43 directly (0x65BD0 object, 0x65C46 ID store) for
[A, 0] gives:
S43OBJ a8=00385D90 ; object allocation hit
S43ID ; ID store NEVER hit
slot43 -> BadValue
query 0 / put 0 -> BadIDChoice
The hit object is a temporary process block; poking its first word to a valid
ID B still leaves B unaddressable (BadIDChoice). Slot 43 fails before the ID
store, so it does not create an addressable class-3 resource. Evidence:
evidence/slot43_instrumentation.txt.
A later attempt to locate the live resource by a field-filtered scan also
failed: the 0x60000..0x180000 hits were class-0 copies, and
0x3E0000..0x400000 contained no matching struct. The resource pointer is not
stable across runs, so the class-byte patch could not be applied to the real
resource. Evidence: evidence/resource_scan_fail.txt.
A deterministic capture at 0x6CC0E (RESPTR a7=…) finally gave the real
pointer. For an op-1 (class-3) resource, byte 40 was 0x003C73D0; patching it
to 0 still made the real PUT_TILE return BadIDChoice:
resource 0x003FDC50 class 3, byte40 0x3C73D0 -> 0
put -> ERR14 S19LOOK a8=0000000E (BadIDChoice) no S19HANDOFF
So a class-3 resource is not in the lookup table slot 19 searches, even with
byte 40 forced to 0. The class-3 namespace is separate; patching the struct
does not register it for PUT_TILE. Evidence: evidence/class3_b40_patch.txt.
The remaining gap is the destination/sink for the tile data and the export
draw path. Evidence: evidence/mame_put_gate.txt,
evidence/mame_put_watchpoints.log.
That leaves the same conclusion as before, now with a concrete invalidated
assumption: the post-PUT_TILE state is not reachable through the recovered
addresses, and the 2.3 element/state format remains unknown.
Evidence: evidence/xie_export_window_vt1200.png,
evidence/xie_export_crop.png, evidence/mame_xie_resource.log,
evidence/mame_band_arm2.log, evidence/mame_look_dump.log.
This is the honest ceiling of the current work: the server is understood
well enough to create, arm, put and export, but the undocumented pipeline
element format is still required to make the exported image be the client's
pixels. A working DEC XIE 2.3 client library or protocol document is the
missing external input; nothing in the ROM supplies it (MAME_XIE_TRACE.md
shows the built-in image test drives hardware directly and never calls XIE).
./xiectl23.py # XIE 2.3 session
./probe23.py defaults # set/query op defaults
./probe23.py cs7 0xC0AAAA 0xFFFF # create probe + resource sweep
gcc -O2 -o xshow xshow.c -lX11
./xshow -display 192.168.99.2:0 -pattern checker -seconds 30
Other server properties that will matter once a 2.3 image client exists:
- Depth 1 / StaticGray. The VT1200 root is 1 bit deep, so pipelines that
end in
ExportDrawablemust match that visual (ConvertToIndex/Dither/Point);ExportPhotomaptests display nothing. - Image files. Run from this directory or pass
-images xc/programs/xieperf/images.
DEC documentation
docs/dec/ holds DEC's own manuals, downloaded rather than guessed at:
DECimage-1200-install-use-...pdf— the closest DEC document to this server. Appendix C says the terminal "uses a subset of the XIE protocol", lists the supported functions (session/resource/transport/geometric/process control) and the pipeline restrictions.VT1200-user-guide-...pdfandVT1200-release-notes-V2.0-...pdf— no XIE content, but they establish what the base terminal documents.DTJ-1991-v03-04-...pdf— DEC Technical Journal article on the bitonal image accelerator, citing X Image Extension Concepts, Version 2.4.VXT2000-user-information-XIE3.0.pdf— the later DEC terminal that explicitly documents XIE protocol 3.0.DECwindows-Motif-OpenVMS-XIE-support.pdf— DECwindows Motif shipped XIE 3.0 (XIE$SHRLIB.EXE+ headers), not 5.0 and not the VT1200's 2.3.
See docs/dec/README.md for quotes, part numbers, provenance, and the
software-media checks. DEC did not publish the 2.3 wire encoding in these
manuals, and the surviving Ultrix UWS tapes checked carry no XIE development
kit. So request numbers and lengths still come from the live probes and the
ROM analysis.
The closest surviving source is already in this tree:
xc/programs/Xserver/XIE/ is DEC/AGE's server-side implementation (protocol
5.0, session.c by Dean Verheiden, AGE Logic, 1993), the direct descendant of
the ROM's dispatcher. It is the structural map the Ghidra work is using.
Building
It builds on a modern toolchain, without imake, using the host's X11 headers and libraries:
./build.sh
# built: .../xie/build/xieperf/xieperf
Requires gcc, libX11-dev, libXmu-dev (and ar). build.sh stages the
XIE headers as <X11/extensions/...>, compiles xc/lib/XIE into
build/libXIE.a, then compiles and links the 35 xieperf sources against
it with -lXmu -lX11 -lm.
Flags that matter: -std=gnu89 keeps the K&R-era source legal on GCC 16,
-fcommon preserves its tentative definitions, and -w hides the expected
prototype warnings. This was verified with GCC 16.2.1.
The original imake build (Imakefile: $(XIELIB) $(XMULIB) $(XLIB),
MathLibrary) still works in an X11R6.1 tree, but needs the whole imake
configuration.
Local fixes for DECwindows and modern libX11
Two changes were made to the vendored xc/lib/XIE/init.c (both marked with
comments in the source):
-
Display-lock deadlock.
XieInitializeholds the display lock and then callsXInitExtension, a public Xlib function that takes the same lock. Old single-threaded Xlib madeLockDisplaya no-op unlessXInitThreadshad been called, so the bug was invisible; on a modern threaded libX11 it self-deadlocks inpthread_mutex_lockinsideXQueryExtension. The lock is now dropped aroundXInitExtensionand re-taken afterwards. -
Extension name. XIE's canonical name is
"XIE", but DECwindows registers it as"Xie", and extension names are case-sensitive. On the VT1200:XQueryExtension("XIE") = 0 XQueryExtension("Xie") = 1, opcode=129 event=64 error=0XieInitializenow tries"XIE"and falls back to"Xie".
Without both fixes xieperf either hangs or reports XIE not supported on this display! — neither reflects the server's actual XIE support.
Upstream bug: -showtechs alone prints usage
In xieperf.c the -showtechs branch sets showTechs but not foundOne,
and main calls usage() when foundOne is false. So this is not a build
problem:
./xieperf -display 192.168.99.2:0 -showtechs # prints usage, exits
./xieperf -display 192.168.99.2:0 -showtechs -all # works; -all sets foundOne
-showtechs only prints the capability list and exits before running any
test, so the extra -all has no other effect.
The BadMatch gate: object->byte8 must equal 3
The 0x8 is now located. At 0x6BAF4 the lookup compares one byte of the object
against the literal 3 that slot 19 hardcodes:
6BAEE MOVE *A13(0xff40),A11,0x0 ; = 3
6BAF2 MOVE *A7,A7,0x0 ; 8-bit field
6BAF4 CMP A7,A11
6BAF6 JREQ 0x6BBCE ; equal -> selector dispatch
6BAF8 ... MOVK 0x8 ; else BadMatch
Measured at the CMP: GATE a7=00000001 a11=00000003. This is exactly the
branch once "skipped" with pc = 0x3F55DE70 to make PUT_TILE report success —
that was never a real path, and we now know what it bypassed.
byte8 is arm-controlled: the arm request sets it to the 16-bit field at
payload bytes 8..9 (v12), measured v12 = 0,1,2,3 → byte8 = 0,1,2,3, with
arm = OK for v16 ∈ {0,1} and arm = ERR8 for v16 ∈ {2,3}.
Correction: BadAccess(10) is the unarmed path, not progress. w9=0x100
with no arm gives put = ERR10 and the gate is never reached; with w9=0x300
the arm fails, so the put was simply unarmed. With v12=3 armed (byte8=3) the
gate is still not reached and neither 0x8 site fires, yet put returns ERR8
— and sweeping the tile geometry (64×64, 1×1, 63,63, 128×128, …) changes
nothing. So ERR8 has at least two origins in slot 19, and the one that fires
with byte8=3 is earlier than the lookup.
Evidence: evidence/badmatch_gate_located.txt, evidence/arm_sets_byte8.txt.
byte8 cannot be raised by any request except create
Clean instrumentation (harness/mame/xie_min.lua, one breakpoint) and
corrected field offsets (byte N = ptr + N*8, since ptr is a bit address).
byte8 was previously read at ptr+8, which is byte 1 — those numbers were
wrong and are superseded.
Verified mapping from create word 9:
w9 = 0x000 0x100 0x200 0x300 0x400
b8 = 0 1 2 3 4
b9 = 1 1 1 3 1 (b9 = (b8==3) ? 3 : 1)
Sweeping every other implemented request against a w9=0x100 (byte8=1)
resource:
slot 43 (len 10) BadValue b8 1 -> 1
slot 44 (len 5) BadValue b8 1 -> 1
slot 40 (len 4) BadValue b8 1 -> 1
slot 33 (len 4) BadValue b8 1 -> 1
slot 24 (len 4) REPLY b8 1 -> 1
slot 19 (len 8) BadAccess b8 1 -> 1
No implemented request moves byte8. Only create does, via word 9's high
byte. With the arm sweep (v12 = 0..6 × v16 = 0..3, 28 shapes) the measured
incompatibility stands:
byte8=1 -> arm OK, put = BadMatch (gate: 1 != 3)
byte8=3 -> arm ERR8 (all 28 shapes), put = BadAccess (unarmed)
So within the client-reachable request set, byte8 cannot be raised to 3 while
keeping the resource arm-able. The import cannot reach the handoff, src_base
stays 0, and EXPORT has nothing to draw. Evidence:
evidence/byte8_slot_sweep.txt.
Harness note: with several requests outstanding on one connection the reply
stream misaligns (arm/create replies are often not read back). That does not
affect these results — byte8 and the slot-19 error come from independent
reads — but future runs should drain between requests or use one request per
connection.
The wall in code: arm needs byte8==1, import needs byte8==3
Found by bisecting slot 20's body with generated bit addresses
(harness/mame/xie_min9.lua, bit = 0x3F200000 + file*8 — hand arithmetic had
produced several wrong addresses earlier), valid ids, no-reply model:
w9=0x100 v14=1 : JREQ_6AB82 -> TGT_6AC34 -> EXIT_6AFDC -> SILENT (success)
w9=0x100 v14=2 : JREQ_6AB82 -> TGT_6AC34 -> E8_2_6AC5C -> BadMatch
w9=0x300 v14=3 : JREQ_6AB82 -> TGT_6AC34 -> E8_4_6ACD4 -> BadMatch
w9=0x300 v14=1 : JREQ_6AB82 -> TGT_6AC34 -> E8_4_6ACD4 -> BadMatch
The discriminating check:
6ACBE MOVE *A11(0x40),A7,0x0 ; A7 := object->byte8
6ACC2 DSJS A7,0x6ACD2 ; byte8==1 -> OK chain
...
6ACCA MOVE *A9(0x68),A7,0x0 ; request byte 13 (mode)
6ACCE SUBK 0x3,A7
6ACD0 JRNE 0x6AD34 ; mode != 3 -> OK path
6ACD2 MOVK 0x8 ; else BadMatch
So:
| request | requires |
|---|---|
| arm (slot 20) | object->byte8 == 1 (0x6ACBE) |
| import (slot 19) | object->byte8 == 3 (0x6BAF4) |
and evidence/byte8_slot_sweep.txt shows no request changes byte8 except
create (word9>>8). With the client-reachable request set, a resource cannot be
both arm-able and import-eligible — which is why every configuration ends in
BadMatch / BadAccess / BadIDChoice, src_base never becomes non-zero, and
EXPORT has nothing to draw.
(The v14 value is validated separately at 0x6AC5C; mode != 3 is required at
0x6AD0.) Evidence: evidence/arm_requires_byte8_eq1.txt.
Closure: no request writes byte8
A write watchpoint on the created resource's byte8, armed across a complete
cycle with valid ids and the no-reply model:
create -> silent (success) obj = 003FDC50
watch installed at 003FDC90 byte8 read-back = 1
arm1 -> silent (success)
put -> BadMatch
arm2 -> BadMatch
export -> silent (success)
byte8 WRITE EVENTS AFTER CREATE: 0
Nothing after create writes byte8. So the only route to byte8 == 3 is
create word9 = 0x300, and such a resource cannot be armed; an unarmed
resource's import returns BadAccess without reaching the gate.
Conclusion, with evidence: a byte8 transition into the importable state is
not reachable from the client request set. The import cannot be driven,
src_base stays 0, and EXPORT has nothing to draw. This closes the last
request-level door in this investigation.
Residual possibilities are all outside that set: an internal path the watch
never observed (nothing writes it in a normal cycle), an unimplemented minor, or
the real DEC 2.3 sequence differing entirely — which its documentation would
state. Evidence: evidence/byte8_write_watch_closure.txt.
The three residual items, tested
The previous closure was limited to one watchpoint on one resource; all three
residuals are now tested (evidence/residual_items_tested.txt):
- Static search for internal byte-8 writers across every disassembled XIE
handler. Exactly one candidate exists — slot 20
0x6ABF6..0x6AC00(*A11(0x40) |= request payload byte 16) — and it is dead code: it is guarded by*A13(0x40) != 0at0x6AB82, while*A13(0x40)is only assigned later at0x6AD92from a freshly allocated params block. The measured fired-path confirms every arm goes0x6AB82 → 0x6AC34and never enters that block. - Runtime watch on a
w9=0x300resource (the type the import wants) across four arm attempts, allBadMatch: 0 write events. - Unimplemented minors as an alternate import path: all 21 unassigned
minors × lengths 2,3,4,6,8,12 = 126 requests, every one
BadRequest.
So there is no executing internal writer, no runtime writer, and no second import
entry point. The byte8 transition into the importable state is unreachable from
the client, src_base stays 0, and EXPORT has nothing to draw.
Slot 43 decoded: a reporting request, not an image source
Length is exactly 10 words. It reads two resource ids (payload words 0 and 1,
looked up through 0x7EA6FA0 at 0x65640) plus two ROI fields (words 4 and 5),
falling back to the resource's own width/height at *A13(0x1c0)+0x80/0xa0.
The "descriptor tables containing 3.0 / 438.0" are arrays of IEEE doubles,
not descriptors — 0x3F533740 reads 00 00 00 00 00 00 08 40 = 3.0, and each
MOVI points 8 bytes further into the same array. Each load is followed by two
MOVE *A8+,*A14+ (one double) and a call into the printf family
(0x7FC1CE6 / 0x7FC18F6 / 0x7FC1CDE), the same helpers used by the export
timing print and by the logger at 0x7F638C2.
Tested live with valid ids and the no-reply model — 17 payload shapes,
including doubles in every position, single-id and two-id forms, and ROI-shaped
values: every one returned BadValue. Its exact ROI preconditions were not
reconstructed, but its body is printf-dominated and nothing observed connects it
to src_base or to any image data plane. It is therefore not credible as the
image-source element. Evidence: evidence/slot43_decoded.txt.
Direct watch on the src_base slot, and slots 15 / 22
The src_base slot itself (rp+0x610) was watched, with rp captured at the
export breakpoint (0x1B296, A11 = rp) — rp = 00913330, slot = 00913940.
create/arm1/put/export#1 put BadMatch, export#1 BadMatch
slot15 [id,0] silent (success)
slot22 [id] silent (success)
slot15 [id,id] silent (success)
arm2, export#2 silent (success)
rp+0x610 after export#1 = 00000000
rp+0x610 after export#2 = 00000000
18 writes at 00913940; only XIE writer is pc=3F2D91F0 (file 0x1B23E, the known
`MOVE *A5(0x28),*A11(0x610)` copy) and it writes 0. The 0x55/0xB055/0x90E6
values are heap-fill and allocator metadata.
Slots 15 and 22 are permissive (they accept [id,0], [id,id], [id]) but
neither produces a non-zero write to src_base. Also confirmed: export returns
BadMatch if arm2 has not been issued, and is silent/success once it has —
consistent with the earlier arm2 gate.
So the source image buffer is never produced, by any path watched. Evidence:
evidence/srcbase_watch_and_slots15_22.txt.
Slot 43 excluded: it validates doubles and prints, it does not source images
BadValue comes from a double comparison against its table: it loads a double
(0x3F533740 = 3.0, 0x3F533780, 0x3F533900, … successive 8-byte entries) and
calls 0x7FC1CE6 / 0x7FC18F6 / 0x7FC1CDE:
658B8 CALLA 0x07FC1CDE
658BE JREQ 0x658CA ; equal -> continue
658C0 MOVK 0x2,A7 ; else BadValue
Its one element-chain block copies each element's +0x1a8 into a frame-local
array for printing (659E2 *A11(0x140) = *A7(0x1a8), with A11 = A13 + i*4),
not into the resource. Every store goes to frame locals or the params block; it
never writes resource+0x140, never writes rp+0x610, and allocates no image
buffer.
Empirically, with a write watch on the real src_base (rp = 0091F090 →
0091F6A0), four slot-43 shapes all returned BadValue and the 64 watch events
were all 00000000 or 00000055 (allocator fill) from export's own copy
(pc=3F2D91F0) and allocator PCs — no non-zero image pointer.
So slot 43 is a reporting/describe request and cannot be the source of the image.
This closes the last implemented slot whose acceptance condition was unresolved.
Evidence: evidence/slot43_excluded.txt.
Slot 43: acceptance precondition still not satisfied (honest status)
The table is little-endian doubles (TMS34010 is LE): 0x3F533780 = 3.0,
0x3F5337C0 = 871.0, 0x3F533800 = 873.0, 0x3F533840 = 217.0,
0x3F533880 = 219.0.
Logging the two frame locals pushed as the operand pair at the double-compare
sites (0x656B0 = "CMP740", 0x658B8 = "CMP900"):
[id,0,0,0,...] BadValue CMP740 a0=00000000 a1=00000000
[id,0,3.0hi,0,...] BadValue CMP740 a0=00000000 a1=00000000
[id,0,0,3.0hi,0,...] BadValue CMP740 a0=FFF7D264 a1=BFE800FF
CMP900 — the site that raises BadValue at 0x658C0 — never fired, so the
rejection happens earlier than the comparison I was chasing, and the doubles
are not read from the payload words tried.
Status of the slot-43 claim: it is excluded on code reading (a per-element
reporting loop copying element+0x1a8 into a frame-local array, then printf) and
on watched evidence that no slot-43 request produced a non-zero write to
src_base — not on a completed, accepted request. Its exact acceptance
precondition remains unknown. This is recorded rather than glossed: the
implemented surface is not fully exercised until that precondition is found.
Evidence: evidence/slot43_compare_operands.txt.
The render driver itself returns 2 to slot 43
Instrumented slot-43 tail (9 breakpoints, generated addresses, valid ids, isolated connection, create drained first):
T_LOOKUP_OK 0x65646 lookup returned 0
T_RENDER_RET 0x65BFE a8 = 00000002 <-- the DRIVER returned 2
T_JREQ 0x65C02 non-zero path
T_FREE 0x65C06
(T_MERGE 0x65C46 did not fire)
So slot 43 calls 0x07E5B152 (file 0x1B152, the render/export driver) and that
call returns 2 — which is what the client sees as BadValue. Neither of slot
43's own MOVK 0x2 sites is involved.
The single MOVK 0x2 in the driver's first 240 instructions (0x1B360) is an
element type write (MOVB *A11,*A5(0x58)), not an error code, so the 2 comes
from another exit of 0x1B152.
Caveat recorded: after T_FREE (0x65C06) the code falls through to 0x65C46,
yet that breakpoint did not fire. Either it was dropped (despite only 9 armed) or
the free call does not return normally; I did not resolve which, so the tail path
after 0x65C06 is not fully established.
What is established: the failure is inside the render driver, not in slot 43's
checks, not in the import lookup, and not the byte8 gate. An accepted slot 43
would drive the render path directly. Evidence:
evidence/render_driver_returns_2.txt.
The manual (EK-V120I-IN-001) and the documented display path
docs/EK-V120I-IN-001_installing-DECimage-1200.txt — Appendix C, Table C–1 —
gives the semantic model this project had been inferring:
- supported requests:
INIT_SESSION,SET/QUERY_OP_DEFAULTS,TERM_SESSION,CREATE_BY_VALUE,CREATE_BY_REFERENCE,DELETE_RESOURCE,QUERY_RESOURCE,EXPORT,FREE_EXPORT,QUERY_EXPORT,SET/ABORT_TRANSPORT,PUT_TILE,PUT_STREAM,SCALE,ROTATE,MIRROR,CROP,EXECUTE_PHOTOFLO,ABORT_PHOTOFLO,BIND_PHOTOFLO,TAP_PHOTOFLO,SELECT/QUERY_EVENTS; - "Always use pipelines"; elements limited to
scale, crop, rotate, mirror, export, at most one of each, must always end with export, prefer that order, max 15; XIEP_EXPORT"Creates a new server PIXMAP resource object", and images are displayed "by copying from a pixmap … usingXCopyAreaorXCopyPlane";SET_OP_DEFAULTS: "Bits/pixel parameter value must be 1";- rotate only 0/90/180/270; width/height must be 0 or match the rotated source; tap only on an empty pipeline; no DCT; no server→client transport;
- errors: "The client receives a Bad Request or Bad Match or Bad Value X error … Do not invoke an XIE protocol request that is implemented, but violates its restricted use."
That last point reframes much of this investigation: BadMatch/BadValue are
documented, expected responses to a restriction violation (including "create
non-bitonal image objects"), not bugs — and byte8 is most likely the
bits/pixel field, which must be 1.
Implemented the documented display path (evidence/export_query_copyarea.txt):
EXPORT → QUERY_EXPORT (op 3/4 reply, 64×64 + pointers + resource id) →
XCopyArea → GetImage. The export context reply does not carry a usable
pixmap XID — its id-like field varies per run (0x00920101, 0x00380101,
0x00330101, 0x00360101) and looks like a heap pointer; XCopyArea rejects
each with BadDrawable. Window unchanged for solid-white and solid-black.