Files
vt1200/xie/GHIDRA_XIE_IMAGE_PATH.md
T
arti d8d54bb990 feat(xie): a client-chosen XIE image is displayed on the VT1200
The XIE path now runs end to end: XieCreateImage -> XieCreatePhoto ->
XiePutTile -> XieExport(photomap -> depth-1 pixmap) -> XCopyArea(pixmap ->
window), and the client's tile appears on screen at the client's own window
position, pixel-exact.

TWO FAULTS BEHIND THE LONG DEAD END

1. Device model (harness/mame/vt1200.cpp, opt-in VT1200_ACCEL=1).  The
   accelerator's output port 0x5600200 returns the pixel stream the firmware
   pushed into the FIFO -- the firmware then stores it into the drawable itself
   (file 0x125528: MOVE *B6,*B1 ; ADDK 0x10,B1).  The FIFO read pointer was
   never reset between transfers, so consecutive exports read stale words and
   the same fill produced 759 then 516 set pixels.  Reset m_fifo_in/out on the
   data-register write; the export is now deterministic.

2. Measurement, not the device.
   - dwt/screenshot.py without --real reads 1024x1024 at a 128-byte row stride,
     but the VR319 default programs 1280x1024 at a 2048-bit (256-byte) pitch.
     That is the "shifted" picture; --real is the correct read.
   - The earlier "0 differing bytes over 128 KiB" VRAM comparison stepped peek
     by 1024 *bit* addresses, so it scanned rows 0..63 only -- never the window
     at rows 200..263.  Over the real raster the white/black diff is 576 bytes
     spanning exactly those rows.
   - New instrument: decclient does a server-side XGetImage readback of the
     pixmap and window and counts set pixels.  That separated "the server never
     drew it" from "the framebuffer view is wrong".

EVIDENCE (reproduced twice, the second time from a fresh machine)

  no export vs 0xFF : 4096 / 4096 pixels differ
  no export vs 0x00 :    0 / 4096   (the window background is black)
  0xFF      vs 0x00 : 4096 / 4096
  0xAA      vs 0x0F : 2048 / 4096   (the two stripe phases)

Tile byte 0xAA reproduces as 1-pixel vertical stripes, 0x0F as 4-pixel, with
no shear -- so the placement is right and the pixels are the client's.

Contents: xie/ holds DEC XIE 2.3 headers, 84 man pages and complete
client+server source; a libXie.a and decclient built from DEC's own code; the
XIE probes; and evidence/ with every measurement, including explicit
retractions of the earlier wrong readings (VRAM_IDENTICAL_FOR_BOTH_FILLS and
friends).  xie/evidence/XIE_IMAGE_DISPLAYED.md is the report; frames are in
xie/evidence/png/.

The accelerator model stays opt-in: it answers the status poll and relays the
pixel stream, but performs no compositing, so it is a stand-in and the verified
default configuration is unchanged (a no-env run still parks in the level-0
diagnostics menu at PC=3FDCE400).
2026-11-01 23:22:25 +02:00

19 KiB
Raw Blame History

XIE 2.3 — the minimum path to put one 1-bit image on the VT1200

See GHIDRA_XIE_PIPELINE.md (final pass): the BadMatch gate is resource byte 5 bit 0, set from the create request's level word at byte 28; with a non-zero level entry the create (slot 8 op 2) produces an exportable class-2 resource. Exact 12/8/16-word payloads are there.

Third pass. Program vt1200_v22.bin (md5 b46a3e442ccb86134d2356bd70d22fd2). The Ghidra session from passes 1–2 is gone; I re-opened the ROM and this report's addresses come from the new session a03f3d788b874b1a976a55417ab9f2d3 (same program, same base rom:00000000, same language tms34010:LE:32:default). All addresses are file/byte offsets, so they are identical to the earlier reports; mame_bit = 0x3F200000 + off*8.

New raw material: xie/ghidra-evidence/image_path/swapper_slotNN_HHHHHH.txt — the 24 request byte-swappers, one per implemented slot. These give the exact 32-bit field map of every request, which is the main new evidence here.

Read with passes 1 and 2 (GHIDRA_XIE.md, GHIDRA_XIE_HANDLERS.md).


1. Executive summary

Nothing in the ROM creates an image resource through a plain single-id request. Every 2-word and 3-word resource slot (11, 12, 13, 15, 22, 24, 33, 40, 44) begins with LookupPhotos and answers BadIDChoice for an unused id — that was measured on the wire in pass 2 and it is confirmed structurally here. Creation must therefore live inside the multi-operation classes 7, 8, 10, and those are the only slots whose selector semantics are still open.

What is now nailed down:

  • Request field maps for all 25 slots, from the byte-swappers (§3). Slots 7, 9 and 10 carry nothing but 32-bit words; slots 12 and 22 are literally {header, id}.
  • Slot 19 is the tile-put request. It clamps (x, y), adds (w, h) to get the right/bottom edges, rejects a fully clipped rectangle with BadValue, iterates count elements writing x and y into per-element records, and uses the request tail as pixel data. Layout: {id@4, x@8, y@12, w@16, h@20, count@24} + payload (§4). This is DEC's XIEP_PUT_TILE ("Transports random tiles of uncompressed image data from the client to the server") — confidence high on structure, medium on the name.
  • Slot 21 is the export candidate: 16 words, {id@4, then ten 32-bit words at 8…44, two 16-bit sub-words}, and it starts by resolving the id through LookupPhotos — i.e. "take this image resource and produce something from it". DEC: XIEP_EXPORT "Creates a new server PIXMAP resource object".
  • Slots 20 and 43 are the only ones shaped like a fixed descriptor (35 words / 10 words); slot 20 additionally seeds a 25-element notion.

The biggest blocker is a single missing fact: which request creates the image resource, and what class code it wants. Without it nothing can be put or exported. Second blocker: whether the export targets a client-created Pixmap (5.0 model) or creates one itself (DEC wording). §7 gives the exact probes that settle both in four requests.


2. Slot table — resource / transport / export family

"fields" is the set of 32-bit request words proven by the byte-swapper (w = word index, byte offset = 4×w), so it is the wire layout, not an inference.

slot best name conf 5.0 / DEC counterpart words 32-bit fields (byte off) reply
7 resource create class — op 1/2 = create-by-value / by-reference [I] XIEP_CREATE_BY_VALUE, XIEP_CREATE_BY_REFERENCE 4 4 (op), 8 (id), 12 (class) no
8 resource class, 4 sub-handlers (ops 1,2,4,5,6) [I] resource services ≥3 4, 8, 12, 16, 20 no
9 3-word op on a resource [U] — 3 4, 8 no
10 5-operation class (query/export shaped) [I] XIEP_QUERY_RESOURCE / XIEP_QUERY_EXPORT / XIEP_EXPORT 3 4 (op), 8 yes
11 resource op with a second parameter [U] — 3 4, 8 no
12 delete / free (id only) [I] XIEP_DELETE_RESOURCE, XIEP_FREE_EXPORT 2 4 no
13 delete / free (id only) [I] as above 2 4 no
14 resource op, id + one long + two half-words [U] — 4 4, 8 no
15 pipeline op (photoflo family) [U] XIEP_BIND_PHOTOFLO? 3 4, 8 no
18 pipeline op [U] XIEP_EXECUTE_PHOTOFLO? ≥4 4, 8 no
19 PUT_TILE — x/y/w/h + counted payload [C] structure XIEP_PUT_TILE ≥8 + data 4, 8, 12, 16, 20, 24 no
20 fixed descriptor (photoflo definition) [I] XIEP_EXECUTE_PHOTOFLO / CREATE_PHOTOFLO 35 4, 8, 16 (+16-bit at 12) no
21 EXPORT — id + big parameter block [I] XIEP_EXPORT 16 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44 no
22 delete / free (id only) [I] XIEP_DELETE_RESOURCE 2 4 no
24 QUERY_RESOURCE (only photo slot with a reply) [C] XIEP_QUERY_RESOURCE 4 4, 8 (+16-bit at 12) yes
33 generic photo op, class from the request [I] XIEP_QUERY_EXPORT? 4 4 (id), 8 (class), 12 no
40 generic photo op, class from the request [I] — 4 4, 8 no
43 large photo op (10 words) [U] XIEP_PUT_STREAM? 10 4, 8, 16, 20, 24 (+ one 16-bit field near 14) no
44 generic photo op, class from the request [I] — 5 4, 8, 12, 16 no

Session slots (1–6) are unchanged from pass 2: 1 INIT_SESSION, 2 TERM_SESSION, 3 QUERY_OP_DEFAULTS, 4 SET_OP_DEFAULTS, 5 mode-selecting setter, 6 QUERY_EVENTS.


3. How the field maps were recovered [C]

For a byte-swapped client, XieSProc calls the per-slot stub; each stub byte-swaps stuff->length and then every 32-bit request word in place, then re-dispatches into XieProc. One 32-bit word at byte B produces three MOVB instructions:

rom:00069c04 MMTM SP,0x74                     ; slot 11's swapper
rom:00069c0a MOVE *A13(0xffe0),A11,0x1
rom:00069c0e MOVE *A11(0x40),A10,0x1          ; stuff
rom:00069c12 MOVB *A10(0x10),*A9              ; ---- swap stuff->length
rom:00069c16 MOVB *A10(0x18),*A10(0x10)
rom:00069c1c MOVB *A9,*A10(0x18)
rom:00069c20 MOVB *A10(0x20),*A9              ; ---- word at byte 4
rom:00069c24 MOVB *A10(0x38),*A10(0x20)
rom:00069c2a MOVB *A9,*A10(0x38)
rom:00069c2e MOVB *A10(0x28),*A9              ;      (bytes 5/6 of the same word)
rom:00069c32 MOVB *A10(0x30),*A10(0x28)
rom:00069c38 MOVB *A9,*A10(0x30)
rom:00069c3c MOVB *A10(0x40),*A9              ; ---- word at byte 8
...
rom:00069c5e MOVE *A10(0x8),A11,0x0           ; minor opcode
rom:00069c64 ADDI 0xd3f90,A11                 ; -> XieProc's proc table

Applied to all 24 stubs, the swapped-word sets are:

slot 32-bit words at byte reading
1 — (16-bit at 4 and 6 only) version pair {major, minor}
2 — no arguments at all
3 4 one 32-bit argument (the 2.3 handler ignores it)
4 4 one 32-bit value → per-client record
5 — every argument is ≤16 bits
7 4, 8, 12 {op, id, class}
8 4, 8, 12, 16, 20 parameter run
9, 10 4, 8 {op, value}
11 4, 8 {id, param}
12, 13, 22 4 {id}
14 4, 8 {id, param} + half-words
15 4, 8 {a, b}
18 4, 8 {a, b}
19 4, 8, 12, 16, 20, 24 {id, x, y, w, h, count}
20 4, 8, 16 plus 16-bit fields at 12
21 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44 id + ten parameters
24 4, 8 {id, param}
33 4, 8, 12 {id, class, param}
40 4, 8 {id, class}
43 4, 8, 16, 20, 24 (+16-bit at 14)
44 4, 8, 12, 16

This is why slots 12/13/22 are "delete shaped" and 33/40/44 are "generic photo handle" slots: 33 passes stuff->class (byte 8) as the last argument to LookupPhotos, so the client names the resource class, while 11/12/13/14/15/ 21/22/24 hard-code it (0xFFFF = any, or 0).


4. Slot 19 = the tile-put path [C for structure]

Length check and the identification call:

rom:0006a782 MOVE *A9(0x10),A11,0x0     ; stuff->length
rom:0006a786 SUBK 0x8,A11
rom:0006a788 JRNC 0x0006a790            ; length >= 8 words
rom:0006a78a MOVK 0x10,A8               ; else BadLength
rom:0006a790 MOVE A14,-*SP,0x1
rom:0006a792 MOVE *A9(0xc0),*A14+,0x1   ; arg = stuff word @ byte 24
rom:0006a796 MOVK 0x3,A11
rom:0006a798 MOVE A11,*A14+,0x1         ; arg = 3
rom:0006a79a MOVE *A9(0x20),*A14+,0x1   ; arg = id @ byte 4
rom:0006a7a2 MOVE A7,*A14+,0x1
rom:0006a7aa MOVE *A13(0xffe0),*A14+,0x1
rom:0006a7ae CALLA 0x07eab966           ; 0x6B966  (pipeline attach, arg 3)

Then the rectangle algebra, which is what makes this the put path:

rom:0006a7be MOVE *A9(0x40),A11,0x1     ; w1 = x      (byte 8)
rom:0006a7c2 JRLE 0x0006a7ca            ;   clamp to 0
rom:0006a7cc MOVE A11,*A13(0x80)        ;   local x
rom:0006a7d0 MOVE *A9(0x60),A7,0x1      ; w2 = y      (byte 12)
rom:0006a7de MOVE A7,*A13(0xa0)         ;   local y
rom:0006a7e2 MOVE *A9(0x80),A7,0x1      ; w3 = width  (byte 16)
rom:0006a7e6 MOVE *A9(0x40),A5,0x1      ;   + x
rom:0006a7ea ADD A5,A7
rom:0006a7ee MOVE *A5(0x80),A5,0x1      ;   compare with dst width  -> clip
...
rom:0006a812 MOVE *A9(0xa0),A7,0x1      ; w4 = height (byte 20)
rom:0006a816 MOVE *A9(0x60),A5,0x1      ;   + y                        -> clip
...
rom:0006a84e JRLE 0x0006a854
rom:0006a850 MOVE A5,A8
rom:0006a852 JRGT 0x0006a87c            ; rectangle non-empty -> go on
rom:0006a856 MOVK 0x2,A5                ; else error 2 = BadValue
rom:0006a85a MOVE *A9(0x20),*A14+,0x1   ;   (client, major, minor, id, 2)
rom:0006a870 CALLA 0x07effa5e           ;   SendErrorToClient

and the payload loop:

rom:0006a88c MOVE *A9(0xc0),*A13(0x40)  ; loop counter = word @ byte 24
rom:0006a896 JREQ 0x0006aa28            ; zero elements -> skip
rom:0006a89e BTST 0x0,A11               ; bit 0 of that word = direction/flag
rom:0006a8ac SLL 0x5,A5                 ; element stride 32 bits
rom:0006a8b0 MOVE *A5(0x160),A5,0x1
rom:0006a8b4 MOVE *A13(0x80),*A5(0xa8),0x1   ; element.x = x
rom:0006a8c6 MOVE *A13(0xa0),*A5(0xe8),0x1   ; element.y = y
rom:0006a8d8 MOVE *A13(0x80),A7,0x1
rom:0006a8dc MOVE *A13(0xc0),A11,0x1
rom:0006a8e0 ADD A11,A7                      ; x += width

Layout (bytes): 0 hdr | 4 id | 8 x | 12 y | 16 width | 20 height | 24 count | 28 two 16-bit fields (not byte-swapped as a long) | 32… payload. Minimum length 8 words = 32 bytes, i.e. header only; pixel bytes follow and are covered by the same length.

Not proven: whether count counts bytes, rows, or tile records, and whether the payload is raw 1-bit rows packed 8-per-byte or bit-addressed. The BTST 0x0 on the same word suggests a flags/count word; the loop's stride-32-bit element walk suggests it is a count of tiles, not bytes.


5. Export candidates

Slot 21 — 16 words, resolves the id then works on it:

rom:00062d58 SUBK 0x10,A11 / JREQ        ; exactly 16 words
rom:00062d68 MOVE *A9(0x20),*A14+,0x1    ; arg = id @ byte 4
rom:00062d6c MOVE A13,A7 / MOVE A7,*A14+ ; out pointer 1
rom:00062d72 MOVE A13,*A14+              ; out pointer 2
rom:00062d78 CALLA 0x07ea6fa0            ; LookupPhotos(client, id, &a, &b, 0)
rom:00062d84 JRNE 0x000630ea             ; lookup error -> return it
rom:00062d88 MOVE *A13(0x20),A11,0x1     ; the resolved resource

It then walks the ten 32-bit parameters at bytes 8…44. A 44-byte parameter block after a resource id is exactly what an export descriptor looks like (destination pixmap, x, y, w, h, visual/format, stride, flags). [I]

Slot 10 — five operations, the only multi-op slot that replies, so if 2.3 has a "query export context" it is one of its five ops. Its request is only three words {op@4, value@8}, so its ops must be id-free or take the id in value. [I]

Slots 33/40/44 pass a client-chosen class to LookupPhotos and then a parameter; they are the "do something to resource of class " form and are the most likely home of export-by-class. [I]


6. Best sequence

Everything is little-endian; len is in words including the 4-byte header. Step confidence in brackets.

[0] connect, QueryExtension("Xie") -> opcode 129                       [C]

[1] INIT_SESSION   minor 1, len 2
      02 00 03 00                       (client speaks 2.3; any value accepted)
      -> 64-byte reply, major 2 minor 3                                 [C]

[2] CREATE RESOURCE   minor 7, len 4                                    [I]
      op    = 01 00 00 00
      id    = 2a 00 00 00              (0x2A = our photomap id)
      class = ?? ?? ?? ??              (see probe P1)
      -> expect Success (no packet). BadRequest with bad=0x2a means the
         class word or op is wrong.

[3] PUT_TILE   minor 19, len 8 + payload                                [I-high]
      00 00 00 00 | 2a 00 00 00 | 00 00 00 00 | 00 00 00 00 |
      40 00 00 00 | 40 00 00 00 | <?count?>  | 00 00 00 00 |
      <512 bytes of 1-bit pixel data = 128 words>
      total len = 136
      -> Success, or BadValue(2) if the rectangle clips away entirely,
         BadIDChoice(14) if the id did not resolve.

[4] EXPORT   minor 21, len 16                                           [I]
      id = 2a 00 00 00, then ten parameter words (probe P2)
      -> Success expected; the pixmap id is client-supplied (DEC wording)

[5] TERM_SESSION   minor 2, len 1                                       [C]
      -> no packet; just close.

6.1 The 64×64 checkerboard

64 px × 64 rows at 1 bit = 8 bytes/row = 512 bytes = 128 words. A checkerboard of 8×8 blocks is a single repeated 32-bit pattern per byte-pair; the simplest generator is "row r, byte b: 0xAA if ((r/8 + b) & 1) else 0x55".

row 0..7   : AA 55 AA 55 AA 55 AA 55  (per byte, 8 bytes)
row 8..15  : 55 AA 55 AA 55 AA 55 AA
...

Which of the two bit orders is "on" does not matter for a first display test.

6.2 Shorter path, if it exists

If XIEP_EXPORT really creates the pixmap itself, step [4] returns the pixmap id in the slot-10 reply and the client can skip creating a pixmap. If instead the 5.0 model holds, the client must first CreatePixmap(depth=1, 64x64) and pass that XID in step [4]; then XCopyArea(pixmap -> window) displays it. Both variants use the same four XIE requests; only the pixmap's origin differs.


7. Probes that settle the open fields (for the worker with server access)

Each probe is one connection: INIT (minor 1) → probe → TERM (minor 2).

id request variants observation
P1 create: minor 7, len 4, {op, id=0x2A, class} class ∈ {0, 1, 2, 3, 4}, op ∈ {1, 2} the pair that returns Success is create + the image class. BadRequest (code 1) with bad=0x2a ⇒ id rejected; BadIDChoice (14) ⇒ op is a lookup, not a create
P2 export: minor 21, len 16, {id=0x2A, …} zero-fill the ten parameters, then vary word at byte 8 (likely the destination pixmap or a format) BadIDChoice ⇒ slot 21 is not the export from this id; Success ⇒ export, and the reply/absence of a reply tells whether the pixmap comes from the client
P3 put: minor 19, len 8 (header only, no data) and len 136 with a checkerboard count word at byte 24 ∈ {1, 64, 128, 512} Success on the first, and BadValue on a zero-size rect, confirms the geometry; a length-related error identifies the count unit
P4 query after put: minor 24, len 4, {id=0x2A, 0} — a reply whose words change after P3 proves the data landed (the only photo slot that can reply)
P5 delete: minor 12 and 22, len 2, {id=0x2A} — whichever returns Success deletes the resource (the other is FREE_EXPORT)

Order matters: P1 first (nothing else can work without a resource), then P3, then P4 to prove the pixels landed, then P2.


8. Server-side constraints visible in the ROM

  • Every handler checks stuff->length exactly (or >= for 8, 18, 19); a wrong length always yields BadLength (16) with minor_op = 0 (pass 1 §7). A 2.3 client must therefore get every length exactly right.
  • Slot 19's clipping uses the destination resource's width/height read from the resolved resource record (*A5(0x80), *A5(0xa0)), so the image resource must carry real dimensions before a put. That is the strongest argument that the create step (P1) supplies width/height somewhere — note slot 7's request is only {op, id, class}, so dimensions most likely come from slot 8 or 21 rather than 7. This is the main open risk in the sequence above.
  • Slot 19 rejects a fully clipped rectangle with BadValue (2) and a bad id with BadIDChoice (14), both carrying the real minor opcode.
  • Export target type (Pixmap vs Window) is not visible in any handler I read; DEC's manual says a PIXMAP resource is created. [U]
  • 1-bit only: the terminal is StaticGray, effective depth 1 (live probe), and DEC's restrictions say "resource-manipulation parameter values are restricted to those compatible with bitonal images".

9. Why this should display

XIE 2.3 on this server is the DEC/AGE-Logic image pipeline: a resource is created and filled by transport (PUT_TILE), then exported into a pixmap the X server owns. Once the pixels are in a pixmap, an ordinary X client can XCopyArea them into the Terminal Manager's window — no XIE involvement after the export. The three steps map one-to-one onto DEC's documented functions (XIEP_CREATE_* → XIEP_PUT_TILE → XIEP_EXPORT), the field maps come from the server's own byte-swappers rather than from guesswork, and slot 19's geometry code is unmistakably a clipped rectangle blit. The uncertainty is in which slot implements create, not in whether the pipeline exists.


10. Confirmed / Inferred / Unknown

Confirmed

  1. Request field maps for all 25 implemented slots (§3), from the 24 byte-swap stubs.
  2. Slot 19 is a clipped-rectangle tile put with {id, x, y, w, h, count} and a payload, minimum 8 words (§4).
  3. Slots 12, 13 and 22 are {header, id} only; 11/14/15/18/24 take {id, one more long}; 33 takes {id, class, param}.
  4. Slot 21 resolves an id and then reads ten 32-bit parameters.
  5. Slots 1–6 are the session/event group (pass 2).

Inferred

  1. Slot 7 (ops 1/2) is create-by-value / create-by-reference.
  2. Slot 21 is export; slot 10 hosts query-export; 12/22 are delete/free.
  3. Slot 19's count counts tile records, not bytes.

Unknown

  1. Which slot creates the resource and where the dimensions live — the blocker.
  2. Pixmap vs Window as the export target, and who allocates the pixmap id.
  3. Whether the payload of slot 19 is packed rows or bit-addressed data.
  4. The class code values (0 vs 0xFFFF vs a client number).

11. Reproduction

  1. program_list_open → empty; re-open with program_open(path="/home/arti/code/k-space/vt1200/vt1200_v22.bin", language="tms34010:LE:32:default", project_location=…, read_only=false) → session a03f3d788b874b1a976a55417ab9f2d3.
  2. Swapper listings are pre-extracted in xie/ghidra-evidence/image_path/swapper_slotNN_HHHHHH.txt.
  3. Handler bodies: xie/ghidra-evidence/handlers/slotNN_HHHHHH.txt (pass 2).
  4. To re-derive a field map, collect the MOVB *(A10|A11)(0xNN),*A9 offsets in a stub: a 32-bit word at byte B shows up as offsets 8B, 8B+8, 8B+16, 8B+24; the length swap is the pair at 0x10/0x18 and must be ignored.