# 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: 1. parse the payload into `{op-header, float, bit-addr, len}` instructions, 2. map each float to its plane role (edge A/B/C, z, r/g/b) by position, 3. 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` / `0x21` fields = 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 from `scratchpad/snapv2.pkl` (cmd 735 death-cam).