Files
vt1200/xie/evidence/class3_registration_impossible.txt
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

79 lines
3.6 KiB
Plaintext

STRUCTURAL RESULT: a class-3 resource with a stored ID cannot be created through
slot 8 at all. The class and the registration are mutually exclusive inside the
only routine that produces either.
All offsets are FILE offsets (Ghidra addresses); the CALLA convention is
file = immediate - 0x7E40000 (verified: 0x7EA78DA -> 0x678DA).
1) slot 8 dispatches op 1 and op 2 to the SAME routine, file 0x678DA:
6696C DEC A8 ; JREQ 0x66912 op 1 ---\
66970 DEC A8 ; JREQ 0x66912 op 2 ---/ -> 0x7EA78DA
2) The only op-dependent value passed on is (op == 2) ? 1 : 0:
6799C MOVE *A9(0x20),A11,1 ; word0 = op
679A2 SUBK 0x2,A11
679A4 MOVK 0x1,A11
679A6 JREQ 0x679AA ; op==2 -> arg = 1
679A8 SUB A11,A11 ; else -> arg = 0
3) That flag alone decides the class, at 0x6CAF2 in file 0x6CA82:
6CAF2 MOVE *A13(0xfec0),A11,1 ; the flag
6CAF6 JREQ 0x6CAFC
6CAF8 MOVK 0x2,A11 ; flag != 0 -> class 2
6CAFA JR 0x6CAFE
6CAFC MOVK 0x3,A11 ; flag == 0 -> class 3
6CAFE MOVB *A11,*A9(0x20) ; obj->byte4 = class (0x20 bits = 4 bytes)
So class = 3 iff op == 1, and class = 2 iff op == 2.
4) The ID store and the table registration live ONLY in the loop body at 0x67AFC,
which is reached only when DSJ jumps:
67A20 MOVE *A9(0x20),A11,1 ; A11 = op
67A24 DSJ A11,0x00067afc ; op-1 != 0 -> jump to the registering body
67A28 ... ; op-1 == 0 -> photoflo path (see 5)
67AFC MOVE *A13(0x20),A11,1
67B00 JRNE 0x67B04
67B02 MOVE *A13,A11,1
67B04 MOVE *A9(0x40),*A11(0x0),1 ; *** ID -> obj+0 ***
67B0E MOVE @0x1ad980,A11,0
67B16 MOVI 0x3f566ed0,A7 ; the XIE handler pointer
67B1E MOVE *A13(0x20),A7,1
67B22 JRNE 0x67B26
67B24 MOVE *A13,A7,1
67B26 MOVE A7,*A14+
67B28 MOVE @0x1ad990,A7,0
67B30 MOVE *A9(0x40),*A14+ ; the id
67B34 CALLA 0x07f16ca2 ; *** register in the resource table ***
op = 1 -> DSJ decrements to 0, does not jump -> 0x67A28: the ID is NEVER
stored and the resource is NEVER registered.
op = 2 -> DSJ decrements to 1, jumps -> ID stored at obj+0 and registered.
5) What op=1 does instead is create a PHOTOflo, not a resource:
67A28 MOVK 0x14,A11 ; 20-word allocation
67A32 CALLA 0x07f63a30
67A42 MOVI 0x3f544b10,A8 ; "error allocating photoflo" string
67A88 MOVK 0x1,A7
67A8A MOVB *A7,*A11(0x20) ; that object's class = 1
67AB0 CALLA 0x07ea72a4 ; bind helper
This is why every measured create op=1 object has class 3 (from step 3) but
id@0 == 0 (step 4): they are two different objects, and the class-3 one was
never registered.
6) Measured consequence, reproduced many times this session:
create op=2 -> class 2, id@0 = ID, query OK, PUT_TILE -> BadMatch (8)
create op=1 -> class 3, id@0 = 0, query BadIDChoice, PUT_TILE -> BadIDChoice
PUT_TILE's lookup demands class 3 (slot 19 pushes MOVK 0x3 before the typed
lookup). 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 are mutually exclusive in the implementation.
7) Also measured, and it rules out the remaining "add levels" idea:
op=4 [newid, existing] increments a 16-bit counter on the existing resource
(obj word at byte 6): 1 -> 2 -> 3 after two calls, and RESPTR shows NO new
allocation. PUT_TILE on that resource stays BadMatch (8) at every count,
including when the counter is 3.