live_render.py connects to vpxlog.cpp's existing VPX_FIFOSOCK tee (the DOSBox-X C012 device), consumes the game's wire AS IT ARRIVES, and runs the firmware and the socket CONCURRENTLY -- pumping the socket whenever the firmware's receive point drains -- rendering each draw with the shared verified GPU raster. gpu_raster.py factors the M4c-raster renderer out of m4b_gpu so offline and live share ONE render path (the 6-edge fp64 raster + verified texel decode). feed_sock.py mimics vpxlog's FIFOSOCK server (streams a real capture in delayed chunks) so the live path is validated end-to-end WITHOUT the flaky live pod. Result: 12 frames streamed live at 2.3 fps; with --pin (equalized texture availability) the live frames are BIT-IDENTICAL to the offline reference (12/12 differ>24 = 0.000%) -- the seam is faithful. The default incremental texture model is more authentic (the board only has texels it has received); its divergence from offline is purely the M5-B tid%ntex placeholder, orthogonal. Live topology now closed end-to-end offline: game/capture -> C012 VPX_FIFOSOCK -> live_render -> firmware -> GPU -> frames. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.4 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):
- 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).
- 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.
M4c-device DONE (live_render.py) — the LIVE socket seam, proven
The transport already existed: vpxlog.cpp's VPX_FIFOSOCK tee listens on
127.0.0.1:PORT and streams the game's wire live (while locally answering the
board->host FIFO so the game never hangs). live_render.py connects as the
client, consumes the wire AS IT ARRIVES, runs the production firmware and the
socket CONCURRENTLY (pump the socket when the firmware's receive point drains),
and renders each draw with the shared verified GPU raster (gpu_raster.py).
Validated WITHOUT the live pod via feed_sock.py (mimics vpxlog's FIFOSOCK
server, streams the real netdeath capture in delayed chunks -> genuinely
exercises the concurrent path):
live done: 12 frames in 5.2s = 2.3 fps (streamed live over the socket)
live (pinned textures) vs offline reference: 12/12 frames differ>24 = 0.000%
Bit-identical. The live firmware+raster seam reproduces the offline reference exactly. Two texture-availability models:
- default (incremental): the renderer uses only texels RECEIVED so far —
authentic to the real board (its texture RAM only holds what was uploaded).
Early frames differ from offline because offline had the full set retro-
actively; this is the M5-B best-effort binding's
tid % ntexsensitivity, orthogonal to the seam. --pin <cap>: pre-load the full texture set -> equalizes availability -> bit-identical to offline, isolating and PROVING the seam is faithful.
gpu_raster.py is now the single shared renderer (offline m4b_gpu + live).
Next (M4d)
- Present window (
--presentuses pygame; coded, needs a display to verify) or reuse the render-bridge GL window. - Bring-up against the REAL DOSBox pod (set VPX_FIFOSOCK, launch, connect) — transport proven by vpxlog; gated on pod stability (see the cockpit memory).
- Frame pacing / present timing.