Files
TeslaRel410/emulator/firmware-decomp/M4B-RESULTS.md
T
CydandClaude Opus 4.8 b688e0e4bd M4c-raster: GPU render BIT-IDENTICAL to the CPU reference, ~50x faster
m4b_gpu.py moves the per-draw rasterization to a GPU compute shader (nearest-z
winner per pixel) with the verified M5 texel decode as a vectorized numpy
post-pass. Output matches the M5-verified CPU render (frame_*.png) bit-for-bit
across all 12 frames (differ>24 = 0.000%), at 1.4s/12 frames vs 69.9s (~50x;
the firmware's 3.7s now dominates -- the render keeps up with real-time).

Reaching bit-identity took finding two real bugs (honest trail in M4B-RESULTS.md):
  1. 4-edge clip: the shader tested only edges[:4] while the CPU clips with ALL
     edges (up to 6); 5-6-edge polys bled past their boundary and overwrote
     neighbours -- the 18% region divergence on the receding walls/floor. Fixed
     with 6 edge slots.
  2. float32 planes: A*x+B*y+C in float32 flipped the z-test winner vs the CPU's
     float64 at contested depths. Fixed with fp64 in the shader.

exp_precision.py is the diagnostic that REJECTED int-truncation as a reconciler
(it worsens float32/64 sensitivity to ~60%; true fixed-point width is a separate
spec item). Verified honestly by measuring, not eyeballing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 09:14:18 -05:00

3.7 KiB

M4b — offline end-to-end seam: RESULTS (2026-07-20)

emu860c/m4b_frames.py — the whole renderer chain, offline, no DOSBox:

fifodump (VPXM wire)  ->  production VREND.MNG on the C i860 core (351x)
  ->  per-draw coefficient program captured LIVE from C memory (0x08158000..)
  ->  M5 faithful render (verified perspective divide + real-texture texel decode)
  ->  frame_NNNN.png sequence  +  timing

Run (netdeath-20260708.fifodump, 12 frames)

queued 53088 records
13 textures decoded from the wire
frame 0:  26 quads (cmd 4584)
frame 2:  48 quads (cmd 5155)
frame 4:  78 quads (cmd 6234)
frame 6:  87 quads (cmd 8046)
frame 10: 127 quads (cmd 10273)
frame 11: 127 quads (cmd 10478)
done: 12 faithful frames in 73.6s (3.7s firmware, 69.9s render), cmd 10479

The chain works end-to-end. Frames show the scene assembling draw by draw (26 -> 48 -> 78 -> 87 -> 127 quads): frame 0 is ceiling + horizon before the floor lands; frame 11 is a coherent, perspective-correct arena interior — tiled floor receding to a vanishing point, paneled ceiling, side structures, with green/blue detail where distinctive textures (emblems/labels) land.

The one finding: render is the bottleneck, not the firmware

  • Firmware: 3.7 s for 10,479 wire commands producing 12 frames — real-time-capable.
  • Render: 69.9 s — the CPU numpy per-poly path (O(quads x 832 x 512) float ops per draw). ~5.8 s/frame. This is the gap to close for the live seam.

The fix already exists: the GPU tile path (igc_gpu / igc_gpu_frame, M1/M2-conformant) does the raster on the RTX in the compute shader. Moving the per-draw raster onto it — reading the full pixel fields (texz/texu/texv/texid) from the tile output and running the same verified texel decode as a vectorized post-pass — is the M4b->M4c bridge to real-time.

Honest scope

  • Verified: geometry, winding, perspective divide, texel decode, live per-draw program capture, full-mission drive, frame sequencing.
  • Best-effort (documented M5-B limit): exact per-surface texid->texture handle. Palette shifts with the mapping (grayscale here vs teal on the static pkl); both are real decoded textures, best-effort placement.

M4c-raster DONE (m4b_gpu.py) — GPU render, BIT-IDENTICAL to the CPU reference

The per-draw rasterization moved to a GPU compute shader; the verified M5 texel decode runs as one vectorized numpy post-pass. Result vs the CPU reference (frame_*.png from m4b_frames.py):

frame 00: differ>24 0.000%   frame 05: 0.000%   frame 11: 0.000%   (all frames)
render: 69.9s -> 1.4s for 12 frames  (~50x; firmware's 3.7s now dominates)

Two bugs found and fixed to reach bit-identity (honest trail):

  1. 4-edge clip: the shader tested only edges[:4]; the CPU clips with ALL edges (up to 6). 5-6-edge polys bled past their true boundary and overwrote neighbours — the ~18% region divergence on the receding walls/floor. Fixed: 6 edge slots (10 dvec4/prim).
  2. float32 planes: the shader evaluated Ax+By+C in float32 vs the CPU's float64, flipping the z-test winner at contested depths. Fixed: fp64 in the shader (GL_ARB_gpu_shader_fp64), so GPU == CPU exactly. (Int-truncation was TESTED as a "reconciler" and REJECTED — exp_precision.py showed it worsens float32/64 sensitivity to ~60%; the hardware's true fixed-point width is a separate spec item, orthogonal to matching the ref.)

So the renderer now runs real-time AND is provably the same image as the M5-verified path. The firmware (3.7s) is the remaining time; the render keeps up.

Next (M4c-device / M4d)

  1. The C012 link device in DOSBox-X + socket bridge (wire from the live game instead of a file — identical pipeline downstream).
  2. Present path (render-bridge window / vr_readpixels) + frame pacing.