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>
114 lines
5.4 KiB
Markdown
114 lines
5.4 KiB
Markdown
# 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 A*x+B*y+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 % ntex` sensitivity,
|
|
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)
|
|
1. Present window (`--present` uses pygame; coded, needs a display to verify) or
|
|
reuse the render-bridge GL window.
|
|
2. Bring-up against the REAL DOSBox pod (set VPX_FIFOSOCK, launch, connect) —
|
|
transport proven by vpxlog; gated on pod stability (see the cockpit memory).
|
|
3. Frame pacing / present timing.
|