Files
TeslaRel410/emulator/PHASE3-PROGRESS.md
T
CydandClaude Opus 4.8 57524d4a68 Phase 3c: fix production vr_sync abort; full game runs through renderer
Root cause: vpx_max_postboot_acks=200, a Phase-2 bring-up guard, capped how
many post-boot replies the device would ever feed. A real BattleTech session
issues an unbounded stream of sync/frame/render replies and aborted with
"velocirender_receive timed out - sends_wo_rcv" at exactly the 200th ack
(143 sync + 55 frame-ack + 2 render). Cap is now effectively unlimited
(0x7fffffff; override VPX_MAX_ACKS for diagnostics).

After the fix the full game (game.conf, RIO disabled, cycles=max) runs
indefinitely: 2500+ syncs, 1264+ frame-acks, no abort, into L4VIDEO content
load (Thor mech + terrain from ALPHA_1/REL410/BT/VIDEO/GEO -- 841 real .bgf
models; test.egg is only mission params). Window still shows background:
the game uses the full DPL hierarchy (instance/object/lod/geogroup + DCS
transforms) which the flat flyk-tuned scene walk doesn't traverse yet -- that
is Phase 3d, documented in PHASE3-PROGRESS.md.

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

8.8 KiB
Raw Blame History

Phase 3 — Render Backend: Progress

Status (2026-07-03): Phase 3a + 3b + 3c 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. 3c — the production vr_sync abort is fixed and the full BattleTech v4.10 game now runs indefinitely through the emulated board, drawing 1000s of frames (was hard- capped at 200). First images ever produced from the Rel 4.10 VPX protocol without a real board. Next (3d): traverse the game's DPL hierarchy + DCS transforms so its mechs actually appear.

decoded DIVRGB calibration screen

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 each draw_scene commits.

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 1820, VREND.MNG). The DPL3 vr_action enum holds for 023 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]
1821 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 1012 (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)

live GL window rendering DIVRGB

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/conns continuations) are tracked with geom_active / conn_active state, same as the offline decoder.
  • Projection: glFrustum from the view-node window rect scaled by near/window_distance; a glScalef(-1,1,1) handles Division's mirrored screen-x. Camera is the row-major 3×3 from action 31 loaded as the modelview rotation, then glTranslatef(-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.

3c. The full game runs through the live renderer (sync abort fixed)

The production vr_sync abort is fixed, and BattleTech v4.10 now runs indefinitely through the emulated board + live renderer. Root cause was a Phase-2 bring-up guard left in the device: vpx_max_postboot_acks = 200 capped how many post-boot replies the device would ever feed. A real game session issues an unbounded stream of sync/frame/render replies; the game aborted with velocirender_receive timed out — sends_wo_rcv the instant the device fell silent — at exactly the 200th ack (measured: 143 sync + 55 frame-ack + 2 render = 200). The cap is now effectively unlimited (0x7fffffff, override with VPX_MAX_ACKS). Same run after the fix: 2500+ syncs, 1264+ frame-acks, no abort. The game progresses past sync into L4VIDEO.cpp content/entity setup (loading the Thor mech and terrain) and draws continuously.

Contrary to the earlier note, the content is on the production image: 841 .bgf models under ALPHA_1/REL410/BT/VIDEO/GEO (incl. BUTTEE.BGF, MSLR.BGF, the THR* Thor mech). test.egg is only mission parameters (as the pod owner said); the geometry loads from VIDEO/GEO regardless.

Game render config: game.conf (RIO disabled so the missing cockpit board doesn't stall the control manager; cycles=max).

What the game's wire stream looks like (vs. flyk)

The game geometry path is heavier and different from flyk's flat DIVRGB:

  • action 26 (0x1A) dominates — texture texel upload. 32-byte headers [node][size][w][h] (0x1000=64×64, 0x4000=128×128 tiles) then 256-byte texel rows. 2880+ of these = the mission texture set streaming in.
  • action 28 (0x1C): material/light parameter blocks.
  • actions 23/25 (verts/conns) still carry mesh geometry but are a small fraction of the stream.
  • The scene graph is the full DPL hierarchy — zone → dcs → instance → object → lod → geogroup → geometry — with DCS transform matrices positioning each mech/prop. flyk's DIVRGB put geometry directly in geogroups at world coordinates.

Remaining (Phase 3d: game geometry on screen)

The window currently shows only the view background for the game because the live backend's scene_publish_frame() walks the flat flyk layout (geogroup→geometry at world coords). To draw the game's mechs it must:

  1. Traverse the full hierarchy (instance/object/lod/geogroup) and apply DCS transform matrices (accumulate the 4×4s down the graph) so each mech lands at its world position; pick a LOD.
  2. Arena content search path: the game logs couldn't load object thr_tshd.bgf — that object lives in VIDEO/GEO/ARENA/ and …/POLAR/ (arena-specific), not the top GEO dir; the geometry search path needs the selected arena subdir (a setenv/config detail, not protocol).
  3. Texturing (action 26 texel maps + SVT), lighting, depth/material. Current backend is flat-shaded untextured polys.
  4. DOS/4GW sync variant for the CYCLE flyk yip.scn fixture (Red Planet geometry) — the older action-check sync.