The emulated VPX board now draws frames in real time. The FIFO burst assembler feeds an in-process DPL scene store; each vr_draw_scene publishes a frame snapshot to a dedicated WGL window thread that renders it fixed-function. - vpxlog.cpp: scene_burst() decode + VScene store + rt_main GL window thread; glFrustum from the view-node window rect, glScalef(-1,1,1) for Division's mirrored screen-x, action-31 camera as modelview. No build change (opengl32 already linked). - Validated: flyk divrgb.scn opens the "VPX VelociRender (emulated)" window and draws the SMPTE bars live via the real camera.spl spline camera (divrgb-live-gl.png), matching the offline render_capture.py decode. - Game path (alpha1.conf) opens the window + draws background but hits the pre-existing production btdpl.ini vr_sync timeout (sends_wo_rcv) -- not a 3b issue. Remaining work tracked in PHASE3-PROGRESS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.1 KiB
Phase 3 — Render Backend: Progress
Status (2026-07-03): Phase 3a + 3b complete. 3a — the render command stream is fully captured and decoded, and a captured DIVRGB frame reconstructed to pixels offline. 3b — a live OpenGL window built into the emulated board draws each frame in real time inside DOSBox-X. First images ever produced from the Rel 4.10 VPX protocol without a real board.
That is flyk divrgb.scn — Division's SMPTE color-bar calibration scene —
rendered entirely from the FIFO command stream captured by the emulated VPX
board: geometry, connectivity, materials, camera, viewport and background all
come from the wire, none from the scene files. divrgb-frame0.png is the same
capture drawn with the actual frame-0 spline camera the app sent.
3a. Full FIFO capture
vpxlog.cpp now records every FIFO burst when VPX_FIFODUMP=<path> is set
(records: 'VPXM' magic, u32 length, raw bytes; one record per burst between
outputData tag writes). The capture used here: divrgb.fifodump
(1297 messages) from divrgb.conf.
Tools:
decode_fifodump.py <dump> [--hex N] [--action A]— action census + payload hexdumps.render_capture.py <dump> [-o out.png] [--frame N] [--eye x,y,z]— reconstructs the scene graph and software-renders the frame eachdraw_scenecommits.
The Rel 4.10 wire protocol (established from this capture)
One burst per message: [action:4][payload], packetized at 508 bytes.
The i860 image download itself rides the FIFO in this build (actions 18–20,
VREND.MNG). The DPL3 vr_action enum holds for 0–23 but Rel 4.10 extends
and re-purposes the tail:
| action | meaning | payload |
|---|---|---|
| 0 | init | args string (/device~0x150~/video~svga~…) |
| 1 | create | [type][name] — the host assigns node names (1,2,3…); the board's create reply node value is ignored |
| 3 | flush | [name][type][node struct] (see node types below) |
| 7 | dcs_link | [parent][child] |
| 9 | draw_scene | [view?][0][0][1][1.0f] — commits the frame |
| 11 | list_add | [parent][child] |
| 18–21 | 860code/data/bss/args | i860 download (FIFO in this build) |
| 23 | set_geom_verts (Rel4.10; DPL3 had 22) | hdr [name][0][n_verts][3][n_blocks][1][5][n_verts][1.0f], then float32 x,y,z per vertex |
| 25 | set_geom_conns (new) | hdr [name][n_polys][loop_len][0], then indices; each poly a closed loop (last=first) |
| 31 | camera (new) | [?][view][3×3 rotation, row-major][eye x,y,z] per frame |
| 45 (0x2D) | sync | token ping (see PHASE2) |
Node types (from create/flush pairs in this capture):
| type | node | flush payload highlights |
|---|---|---|
| 2 | texture/ramp? | one word, 0xFFFFFFFF / 0x0FFFFFFF |
| 3 | view | window l,b,r,t (±1 × ±0.6154), window-plane distance 1.3, viewport 832×512, near 2, far 12000, background RGB |
| 4 | light | |
| 5 | dcs | 4×4 matrix |
| 6 | material (old-style) | RGB floats |
| 7 | object | |
| 8 | lod | 2×4 bounds + lists |
| 9 | geogroup | bounds; payload int 14 = material node name |
| 10 | geometry | [geo_type][n_polys][n_verts]…[radius] |
| 11 | material | floats 10–12 (payload) = diffuse RGB |
Graph: list_add links object→lod→geogroup→geometry (and zone-level nodes to
node 0); geogroups bind materials by name. The 13 DIVRGB bars decode to the
textbook SMPTE pattern — 7 color bars, the reverse strip, and the −I/white/+Q
PLUGE row — which validates vertices, connectivity, materials, camera and
window mapping in one image. (Division screen x runs opposite to a GL-style
eye space; without negating x the pattern comes out mirrored.)
3b. Live OpenGL backend (DONE)
VPX_RENDER=1 turns on a live render backend built into vpxlog.cpp
(Windows/WGL). The same burst assembler that feeds VPX_FIFODUMP now also
feeds an in-process scene store (scene_burst() → struct VScene), and each
vr_draw_scene publishes a frame snapshot to a dedicated OpenGL window thread
(rt_main) that draws it with fixed-function GL. No build-system change was
needed — opengl32 was already linked, and vpxlog.cpp is already in
hardware/.
Validated: flyk divrgb.scn (divrgb.conf with VPX_RENDER=1) opens the
"VPX VelociRender (emulated)" window and draws the SMPTE bars live, framed by
the real camera.spl spline camera the app sends — matching the offline
render_capture.py decode. The window runs on its own thread, so it keeps
displaying the last frame after the DOS app exits.
Design notes:
- Multi-burst payloads (
set_geom_verts/connscontinuations) are tracked withgeom_active/conn_activestate, same as the offline decoder. - Projection:
glFrustumfrom the view-node window rect scaled bynear/window_distance; aglScalef(-1,1,1)handles Division's mirrored screen-x. Camera is the row-major 3×3 from action 31 loaded as the modelview rotation, thenglTranslatef(-eye). - Frame handoff is a critical-section-guarded
VFrame+ auto-reset event; the emulator thread never blocks on GL.
Game path (alpha1.conf, full BattleTech v4.10): the window opens and draws
the view background, but the run hits the pre-existing production-path sync
timeout (velocirender_receive timed out — sends_wo_rcv=3, then
failed in vr_sync) — the btdpl.ini/netnub launch bursts several sends
before its first receive, which the POLL_THRESHOLD gating stalls. This is not
a 3b issue (the flyk clean-launch path renders fine); it is the same
production-sync item still open from Phase 2.
Remaining
- Production-path vr_sync timeout (
sends_wo_rcv): the game'sbtdpl.ini/netnub launch path. Needed before the game itself renders. - Content:
test.eggcarries no models, so even past sync the game has nobuttee.bgf/mslr.bgfgeometry to draw — needs a real mission/content tree from the pod image. - DOS/4GW sync variant for the CYCLE
flyk yip.scnfixture (Red Planet geometry) — the older action-check sync. - Texturing (
set_texmap_texels, action 23-data + SVT), lighting, and depth/material niceties; current backend is flat-shaded untextured polys.

