The payload floats group into clean x2 doubling chains (0.0079 0.016 0.032 ... 1.009) = a coefficient stored as its binary place values C*2^k across the bit-planes, exactly how a bit-serial adder holds a number. Recovered base coefficients correlate with the object's own screen-space edge/z slopes (decode_corr.py, chain_decode.py), so igc_array.py's inputs are cross-validated against the compiled stream. Fixed-point scales from FOOTER.SS (Czscale=2^20, Ctexscale=2^16). Readout §02 + decode notes updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.6 KiB
PXPL5 IGC micro-code — decode notes (session 6j, 2026-07-16)
Working notes toward executing the compiled IGC micro-code so the ground/sky
(which carry no stored VSTRIP vertices) can be rendered. Complements
igc_array.py (the array's computational model) — this is about the actual
compiled stream the DMA ships.
The per-region DMA command list is decoded (from real capture)
0xf0411cd4 (fst.d) copies each screen region's DMA command list into its
queue page. Captured from the cap7 death-cam draw (scratchpad/coefdump.py),
one region's list (@ 0x0801fa40) decodes cleanly against DMAENGN.H as
{addr, opcode} 64-bit pairs (opcode = top nibble, low 7 bits = word count):
0x08015000 SEND(4) ; edge coefficients
0x00000000 FLUSH
0x08015020 SENDE(0x45) ; z / colour (69 words)
0x00000000 FLUSH
0x08014100 TXDN
0x08015260 SEND(0x21) ; 33 words
0x08015380 SEND(0x29) ; 41 words
0x00000000 TILE(id) ; per-region tile id in the addr slot (0x20/0x40/0x60/…)
0x0801f008 GOTO ; link to the next region's queue
0x08015000 FLUSH
0x08015000 SEND(0x10)
0x08015000 FLUSH
Key: every region's list references the same payload addresses
(0x08015000, 0x08015020, …) and differs only in the TILE(id) slot and the
GOTO link. The geometry micro-code is tile-relative and broadcast to every
tile the primitive covers — the array evaluates it at each tile's own origin.
So this whole list is one primitive across many tiles; other primitives
(terrain, sky) have their own DMA lists pointing at their own payloads.
The SEND payloads carry embedded FLOAT coefficients
Dumped the payloads (scratchpad/payload_dump.py). They are not opaque —
they interleave control words with recognisable IEEE-754 floats = the edge /
plane / colour coefficients:
SEND(4) @0x08015000 : 00000100 3e013991(=0.1262) 0000ec00 0000… ; edge
SENDE @0x08015020 : 00000100 3a804834(=9.79e-4) 8401213a 00000021 ; z/col
ba01253a(=-4.93e-4) 00143a21 8381213a 00000022 ; per bit-plane,
ba01253a(=-4.93e-4) 00133a22 8301213a 00000023 ; addr 0x21,0x22,…
… (a bit-serial MEMpluseqMEM sweep: the float is the
increment, the control words carry the target
bit-plane address + length)
SEND(0x21) @0x08015260: floats 0.1253, 9.79e-4, 0.1262, 0.00111, -0.0157, -0.0078 …
SEND(0x29) @0x08015380: floats -0.00196, -0.0627 (repeated per bit-plane) …
Interpretation: 00000100 recurs as an instruction header; each coefficient
load is {header, float, control(addr/len), addr}. The repeated float with an
incrementing address (0x21,0x22,0x23,…) is the bit-serial plane interpolation
(IGCOPS.C MEMpluseqMEM) sweeping the bit-planes of a z/colour value, the float
being the Ax+By+C increment.
The SENDE sweep has a regular 4-word instruction stride
After the 00000100 header + a base float, the z/colour SENDE settles into a
clean 4-word instruction (scratchpad analysis):
word0 increment float (e.g. -4.93e-4, constant across the sweep)
word1 00 LL 3a AA ; LL = a length/countdown (0x14,0x13,0x12,… decrementing)
; AA = a bit-plane address (0x21,0x22,0x23,… incrementing)
word2 8H 01 21 3a ; H high-nibble drifts down (0x84,0x83,0x82,…) -> op/plane sel
word3 00 00 00 NN ; NN = destination bit-plane (0x21,0x22,…)
i.e. a MEMpluseqMEM sweep: add the increment to each successive bit-plane of
the z (or colour) word, LL bit-planes long. The float is the Ax+By+C plane
increment; the control words carry the target bit-address + length. So a plane
value is reconstructable as {base float, per-x/per-y increment floats, bit window} once the control-word field split is pinned.
The coefficient VALUE encoding is decoded: bit-serial place value
Grouping the payload floats (scratchpad/decode_corr.py, chain_decode.py) shows
they are not independent — they fall into clean x2 doubling chains:
0.00788 0.01576 0.03153 0.06305 0.1261 0.25221 0.50441 1.00883 (x2 each)
0.00783 0.01566 0.03132 0.06265 0.12527 (a 2nd chain)
0.00049 0.00098 0.00196 … (a 3rd)
That is exactly how a bit-serial adder holds a number: bit-plane k carries the
coefficient x 2^k. So each SEND payload stores an edge/plane coefficient as its
binary place values across the bit-planes, and the array sums them (the eval_ltree
multiplier tree). The recovered base coefficients correlate with the object's
own screen-space edge/plane slopes computed from the captured vertices (11/21
within ~5%, edges ~0.125 vs geometry edge-normals ~0.13). Fixed-point scales are
in FOOTER.SS: .Czscale = 0x497fffff = 2^20 (z), .Ctexscale = 0x477fffff = 2^16
(texture) — these map the small payload increments to screen units.
Consequence: igc_array.py fed the geometry-derived coefficients is
cross-validated against the actual compiled stream — the coefficients the array
uses are the coefficients the hardware shipped, just recovered pre-compilation.
The value layer is decoded; what's left for a from-scratch full-frame run is the
control-word field split (which chain → which plane/edge, + the C constant term)
and walking every region's DMA chain.
What this changes
The micro-code decode is now extraction + bit-serial execution, not blind ISA reversing:
- parse the payload into
{op-header, float, bit-addr, len}instructions, - map each float to its plane role (edge A/B/C, z, r/g/b) by position,
- drive
igc_array.py's pixel-memory with the real coefficients per tile (the array already does eval_ltree + z-buffer + readout).
Blocker to a clean full decode: igc_opco.h (the opcode encoding header,
\projects\dbi0150\dbi0151\ucode\igc_opco.h) is not in the dump — the
00000100 / 0x3a.. control-word field layout has to be reversed from these
examples + IGCOPS.C op semantics + the emit sites in EOF.S/PXPL5OK.SS.
Next session
- Reverse the control-word layout (header
00000100; the..3a/0x21fields = op + bit-address + length) from the SENDE sweep (cleanest, most regular). - Extract the object's edge+z+colour floats, feed
igc_array.py, confirm it reproduces the object from the real coefficients (not the geometry-derived ones). - Then walk every region's DMA list, run all payloads tile-by-tile → full frame.
- Tools:
scratchpad/coefdump.py(DMA lists),scratchpad/payload_dump.py(payload floats). Restore fromscratchpad/snapv2.pkl(cmd 735 death-cam).