Files
TeslaRel410/emulator/firmware-decomp/M5-TEXTURING.md
T
CydandClaude Opus 4.8 b74b0c89e8 M5-A: the faithful IGC perspective divide, verified against the divlogo oracle
igc_divide.py implements EOF.C's texdivide bit-serial restoring division on
the pixel bit-memory, and extracts the texel per the '3 int | 8 texel | 5
subtexel' result layout ((field>>9)&0xff). Verified against the divlogo's
KNOWN mapping (u:0.001->0.999 across screen x[532,660]): the extracted texel
sweeps monotonically 0->251, and a fast float equivalent (texu/texz*2048)
tracks the bit-serial result to +/-1. This fixes the missing scale factor
(the '3 integer bits' = x8) that made earlier renders wash out. M5 plan in
M5-TEXTURING.md. Next: M5-B scene-graph material binding, then integrate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 22:13:10 -05:00

2.5 KiB

M5 — faithful texturing (the last mile to recognizable frames)

Fresh, deliberate effort. Geometry, perspective structure, and the texel format are all solved from the live wire (M3). What remains is making the textures land on the surfaces correctly, which is two real sub-projects — not scale-guessing.

Sub-project A: the faithful bit-serial perspective divide

The IGC computes per-pixel texel coords by an actual restoring division, not a float. From EOF.C perspective_divides / texdivide:

IGC_CLEAR(texz + texzbits, 1)              // lose 1 bit of texz
texdivide(texu, texz+1, 17)                // texu <- texu / (texz>>1), 17 sig bits
texdivide(texv, texz+1, 17)
// texdivide: for i in 0..sigbits-1:
//   SETENABS
//   MEMgeMEM(num, denom, divbits)         // en = mem[num:divbits] >= mem[denom:divbits]
//   MEMminuseqMEM(num, denom, divbits, divbits)  // if en: num -= denom
//   ENABintoMEM(res)                      // quotient bit
//   res--, denom++, divbits--

Result layout (comment): "16 bits == 3 (0..7.99 integer) | 8 (0..255 texel) | 5 sub-texel". So the texel index is an 8-bit slice of the divided field.

Oracle: the #if divlogo block (EOF.C ~612-660) builds a KNOWN mapping — the Division logo, texture id 0, size 64, into screen rect x[532,660] y[382,440], with u:[0.001,0.999], v:[0.999,0.001]. uscale = (1<<(texubits-3))*0.999 ~= 2^17. A correct divide makes the extracted texel sweep 0->63 linearly across the rect. This is the unit test (igc_divide.py) — implement the exact op loop, feed the logo planes, assert the texel sweep. No ground-truth screenshot needed.

Sub-project B: DPL scene-graph material binding

Per-quad texture = the texmap bound to that object, set by the flush before each draw (observed: flush [0x72, 0xd, ...], 0x72 = a PLAYER texmap handle). Resolve by tracking create/flush/list_add into a scene graph: object -> material/texmap -> texture handle, then map each draw's primitives to their object's texture. The texid 6-bit field in the packet indexes a bound-slot table the flush maintains.

Sequence

  1. A: faithful divide + divlogo unit test (self-contained, verifiable). ← start
  2. B: scene-graph material tracker (from the wire).
  3. Integrate: full battle frame, correct per-quad textures, perspective-correct texels. Re-run against a battle frame; the arena should read as real imagery.

Everything upstream (firmware on C core, geometry, texel decode) is committed; this note scopes the honest remainder.