The previous commit's attribution was wrong and is corrected in the roadmap.
WRONG: 'the crash is the skeleton walk's' rested on 0 crashes in 2 runs with
the walk disabled -- a 25% coin flip presented as evidence. The oldest
preserved dump has no [skl] line and ends on the old 'couldn't figure out how
to MakeEntityRenderables' fallback, so it predates the walk and faults
identically.
WRONG: 'it lands at different points each time'. Every dump carries the same
numbers to the byte (cr2 7000FA64, EIP 66D9). What varies is how far the log
gets, not where the fault is.
Resolving the address through btl4opt.map names it exactly:
EulerAngles::operator=(const LinearMatrix&)+0x19, a read through the matrix
reference. A binary scan finds all three call sites of that operator pass a
stack local, so the 1.79GB pointer still has no static explanation -- that is
now its own open item rather than a guess.
Two theories killed cleanly by the new BT_STACK_LOG probe and a binary scan:
ESP drift (drift=0 over 2002 frames; exactly one callee-cleans function exists
in the whole binary) and an undersized stack (our PE and the shipped one have
identical 1MB/8K geometry).
What the probe found matters more: the application is parked in state=2, which
is LoadingMission, not WaitingForLaunch. Priority 0 is where the interest
manager queues renderer events during load, the gate needs that priority empty,
and it never empties -- so the mission never launches and the renderer holds a
blank screen by design. The fault arrives ~30s into that wait. Runs that DO
launch never print a single [launch] line. So 'crashes half the time' and
'hangs during load' are one event seen twice.
A 15x disagreement between two clocks looked like a reconstruction slip --
BTL4.CPP passes GetTicksPerSecond() where ApplicationManager wants a frame
rate. Checked against the shipped binary before touching it: same instruction
sequence, same kind of static float pushed. Authentic. Documented so nobody
'fixes' it.
Also swept every subsystem DefaultData against its real C++ base. Fifteen
chain past their immediate parent, but fourteen skip only classes that add no
handlers and no attributes, and no class's attribute-ID base disagrees with its
index chain -- so there are no gap slots there. The one real defect: Generator
is a HeatSink but chained to Subsystem::MessageHandlers, so it ignored every
ToggleCooling message. Fixed (compiles next build).
Tooling: podrun.sh stages the build over BTL4REC.EXE, exports the host-side VPX
board env -- without which the run dies at the iserver handshake rather than
merely rendering nothing -- and archives every run's log, marking it -CRASH
when it faulted. Before this the only preserved dump was an accident, on a rig
where each run costs four minutes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Added BT_NO_SKL, which skips the skeleton build so ONE executable can be run
with and without it. That is the only clean way to test an intermittent
fault, and it settles attribution:
walk enabled ~50% of runs die (page fault at 66D9) at DIFFERENT points
walk disabled 0 crashes in 2 runs
shipped exe 0 crashes in 2 runs, same rig and conf
So it is our new code, not the rig, and the varying fault point points at
memory corruption surfacing later rather than a bad instruction in the walk.
Hypotheses killed by reading the private library headers: the matrix array is
the right size (dpl_MATRIX is float32[4][4]); and neither s_dplobject nor the
common dpl_node header retains an object name, weakening the dangling-string
theory (a private name cache inside the library is still possible and is the
best surviving suspect).
Also checked something that would have been much worse than a crash:
~NotationFile REWRITES its file when dirty, and these are the game's shipped
.SKL data files. MAD.SKL is untouched, and ReadSKLFile now carries the
engine's own Verify(!IsDirty()) guard.
Next tests are listed cheapest-first in the roadmap: keep the NotationFile
alive, copy the Object= names, count board allocations, and re-check whether
MakeEntityRenderables runs twice per mech.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pod updates every few seconds. Measured by sampling a head window every
2s (emulator/render-bridge/headrate.py): OURS changed in 5 of 12 samples,
SHIPPED in 1 of 12. So the reconstruction is not slower than the shipped
binary -- the emulated board is just expensive. Caveat recorded with it:
ours was being driven by the throttle hooks while the shipped exe ignores
them and sat parked, so treat those as same-order, not a win.
What did cost us 3x was mine: BT_MECH_LOG/BT_LAUNCH_LOG write per-frame lines
and DEBUG_STREAM=cout is redirected to COM3, so every one goes through an
emulated serial port. Turning them off took the wire from 480 to ~1480
bytes/sec. pod_render_quiet.conf is the conf to use for timing work.
And a correction to my own earlier framing: wire bytes/sec is NOT a frame
rate. Shipped pushes ~8900 B/s against our ~1480 and the difference is
CONTENT, not speed -- shipped submits the mech and we do not:
OURS: L4VIDEO.cpp wrong video resource type for object mad.skl
SHIPPED: (no such line)
The mech's model resource is a SKELETON. The engine's default
MakeEntityRenderables accepts only Object/Rubble and rejects anything else
with exactly that message, because skeletons are the GAME renderer's job. So
our arena renders but the MECH IS ABSENT, and with it the cockpit interior --
one unimplemented path explaining both the missing model and the lower wire
volume.
Next brick is now precisely scoped: answer MechClassID by reading the .SKL
notation pages into a dpl_DCS hierarchy and hanging the per-node .BGF
geometry off it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
emulator/render-bridge/first-3d-frame.png: the arena from the pod -- sky,
horizon, ground, the arena structures along the skyline -- at ~31fps on the
VelociRender bridge.
The whole chain now works in our build: MakeVideoRenderer ->
BTL4VideoRenderer over DPLRenderer -> board boot -> Renderer::LoadMission ->
DPLReadEnvironment for the art paths -> 40/40 arena objects loaded -> mission
launch -> per-frame submission over the wire.
The mistake worth remembering: the run was never stalled after
InitializePlayerLink. I called that from a 150-second sample and it was just
too short. CheckLoadMessageHandler reposts every second and will not advance
until the min-priority event queue drains, and with the video renderer in the
mission that takes minutes. A BT_LAUNCH_LOG trace showed the queue draining
and then both RunMissionMessageHandler calls, the plasma display, and the
first sensor tick -- matching the shipped binary line for line. The renderer
deliberately holds a blank screen until RunningMission (L4VIDEO.CPP:5100), so
'black' was the app still loading, not a rendering bug.
Zero unbuildable entities, zero geometry failures. The frame is static only
because the mech is parked with no input, and the camera in the bridge title
is the bridge's own viewer, not the game's eyepoint.
Also banked the launch-gate trace (BT_LAUNCH_LOG) behind an env var.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DPLRenderer::LoadMissionImplementation (L4VIDEO.CPP:6007) is not empty: it
calls DPLReadEnvironment (which opens L4DPLCFG/btdpl.ini and sets the dPL
object/material/texmap paths from the objectpath= entries) and then
LoadNameBitmaps. The 'bring-up no-op' installed earlier therefore did not do
nothing -- it REPLACED that, so the loader never learned where the art lives
and every dpl_LoadObject returned NULL.
I justified that no-op by pointing at VideoRenderer's bare Tell and
GaugeRenderer's identical one; neither is our base. Check the ACTUAL base
before overriding in this engine and chain it unless there is a reason not
to. DPLReadEnvironment is private to DPLRenderer, so chaining is the only
way a game renderer can reach it at all.
before: 40x 'couldn't load object', 'NULL instance', run ends,
wire ~24 bytes/sec
after: 0 failures, run continues, wire ~12 KB/s (1.17MB climbing),
bridge live at 88fps
Proved it was ours and not the rig by running the SHIPPED exe under the same
conf: it loaded every object. That A/B is cheap and is the right first move
whenever the pod misbehaves.
Still black: the bridge camera sits at its default (0,10,0), so the wire
carries state and object loads rather than a populated scene. The mech and
arena entities still need MakeEntityRenderables bodies.
Rig improvements, both from the user: nosound is fine on the SLOW clock (it
is the FAST SOS clock that needs the AWE32), which saves ~4 min and ~300MB of
audio taps per run; and serial3=file + '> COM3' gives a live unbuffered log,
so a run that does NOT crash is finally readable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With every authored watcher resolving, the pod run reaches the mission, draws
the FULL COCKPIT with the board booted and audio running, and exits GAME-RC=0
with no Fail. It builds no 3-D scene, and the engine says why:
Entity 1:1 class3035 couldn't figure out how to MakeEntityRenderables
L4VIDEO.cpp couldn't load object sky.bgf (then the whole arena)
NULL instance
VideoRenderer::MakeEntityRenderables (VIDREND.CPP:231) is the BOTTOM of a
virtual chain -- its comment says so outright -- and BTL4VideoRenderer does
not override it, so every entity falls through, no scene graph is built, and
the geometry that would hang off it never loads.
CORRECTS an earlier note in the roadmap: I recorded the first full-rig run as
proving '.BGF loading was never a reconstruction problem, only a missing
bridge', because the complaint count was zero. It was zero because the run
Failed at a watcher long before reaching geometry. Now that it gets there,
the loads are attempted and fail -- for want of renderables, not the bridge.
Next brick is the real btl4vid body, shape pinned by the surviving sibling
header RPL4VID.HPP: override MakeEntityRenderables, plus ReadSKLFile /
RecurseSKLFile to walk the .SKL skeleton pages into a dpl_DCS hierarchy and
load the .BGF geometry per node. Renderable content from the BT411 donor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under the documented rig (launch_pod.ps1 with the GL bridge up) the 'couldn't
load object' count went to ZERO. .BGF loading was never a reconstruction
problem, only a missing bridge.
What blocks now is a series of authored AttributeWatchers: BTL4.RES binds
them BY NAME and the engine Fails outright on any that does not resolve, so
each is a name our subsystems must publish. Five rungs climbed, each
run-verified: UnstablePercentage, SpeedEffect (Myomers table), AnimationState
+ CollisionState/CollisionNormal + ReduceButton (Mech), and the full Torso
table (all six authentic names mapped onto existing members).
The method that makes this cheap is banked: the shipped binary's string pool
carries each class's attribute names CONTIGUOUSLY in ID ORDER after the class
name, and in every case so far our member declarations sit in the same order
-- which confirms the layout and lets ids be pinned rather than guessed.
Intersecting those names with BTL4.RES gives the exact work list.
A THIRD miswired SharedData found on the way: Myomers carried Subsystem's
tables where it derives from PoweredSubsystem, the same defect as
MissileLauncher (5.3.33). Worth a sweep across every subsystem.
Staged member TYPES are provisional and documented as such: AttributeWatcherOf
reads *(T*)attributePointer and the instantiation comes from the resource,
which the string pool does not reveal. Nothing drives them yet, so settle the
types with the models that write them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gauge framebuffer is PLANE-PACKED: L4GAUGE.CFG gives each port a bit
mask, not a rectangle, so all six heads share the same x/y and are separated
only by bit -- the packing the VDB splits into the pod's physical screens.
Comparing RGB was therefore measuring nothing useful; the "magenta artifact"
just meant the wrong heads were lit at that pixel.
New instruments (emulator/render-bridge/gauge-ab/, with a README):
planes.py per-head scoreboard -- shipped vs ours, missing vs extra, per
port mask, recovering the 16-bit word from DOSBox's RGB565
BT_VIS_LOG a complete cockpit widget map from the single dispatch every
gauge widget is built through (MethodDescription::Execute),
printing name + port + authored position
ab.sh stages the fresh build over BTL4REC.EXE and kills any running
DOSBox before launching -- see below
The fix: the Comm page's pilot roster is the POD roster (the viewpoint mech's
ControlsMapper pilot array, mapper attributes 15/16), not the mission's
player list. It is zero in a solo mission, so the shipped page draws empty;
ours resolved the local player and painted its icon in colour 0xff. The gate
belongs in the row source, not PilotList::Execute -- Execute's empty branch
still has to run, because erasing is what the shipped page draws.
head shipped ours missing extra (was)
Eng1 17981 17981 0 0 (0 / 1030)
Eng2 17981 17981 0 0
Eng3 17981 17981 0 0
Comm 15656 13557 2099 0 (2099 / 1253)
Heat 21374 20913 566 105 (566 / 990)
Title-band magenta 404 -> 0. The 2099 Comm pixels we are missing were
missing before the gate, so it is a strict improvement.
Method note, which cost more than the bug: the rig's confs run BTL4REC.EXE
out of the mount while the build writes build410/btl4opt.exe, and nothing
connected the two. Three measurements ran an hour-old binary, so a change
that "did nothing" three times had never been tested -- and the correct first
hypothesis was discarded on that evidence. ab.sh now re-stages every launch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading the art settled where the titles live: qsensors/qmyomers/qermlas/
qppc/qstrk6.pcc are 299x26 TITLE BANNERS, while the qblh0..7.pcc strings
the cfg hands vehicleSubSystems are 77x120 MECH ICONS (already lit by the
5.3.28 placement fix). So the authored auxScreenLabel IS the panel title
-- it just belongs on the MFD panel, not only the ENG page.
SubsystemCluster now builds a titleBanner child from the cached label.
Placement was MEASURED against the shipped framebuffer through the A/B rig
(BackgroundBitmap places by left/BOTTOM): a bring-up BT_TITLE_DY knob, then
a green-pixel correlation over the title band, then baked at x+0x09,
y+0xb3 from the panel origin. "SENSOR CLUSTER", "MYOMERS", "TYPE II 65
TONS" and both "ERMED LASER RANGE 500M" banners now render where the
shipped binary puts them.
Score progression vs the shipped cockpit on identical GAUGE content:
5.3.26 birth 90.1% identical / 82% coverage
5.3.28 authored screens 93.2% / 85%
5.3.29 titles tuned 94.8% / 91%
Also: grab.ps1 now focuses the target window before capturing -- the
screen-copy grabbed whatever was in front, which cost two junk captures
(a white frame and an unrelated 3-D app window).
Gauge fight 11/11 rounds and smoke both clean, zero faults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ran the reconstructed exe and the shipped ALPHA_1 binary against the SAME
GAUGE content, same mount, gauges on, and diffed the 640x480x16
framebuffers. OUR EXE DRAWS THE COCKPIT: 90.1% pixel-identical on the
first run, and every difference traced to a single root cause.
The authored aux-screen block (screen number / background placement /
label) lives in the subsystem resource -- which is freed with the Mech
ctor's stream buffer (the 5.3.24 use-after-free landmine). Both this tree
and the BT411 port had stubbed it, which is why BT411's displays show the
same artifacting. PoweredSubsystem now caches the block at ctor time with
accessors, and btl4gau2 uses the AUTHORED screen instead of a roster-order
stand-in and builds the placement strip art. Panels snapped onto their
authored screens -- weapon dials now land on the same panels as the
shipped exe, mech icons appear on the sensor/myomer panels, generator
letters correct -- taking the match to 93.2% identical / 85% coverage.
Remaining differences are donor-inherited stubs: the panel title art (now
proven to come from the q-strip statusImage path, not auxScreenLabel --
the label blit is wired and guarded regardless), the pilot name bitmap,
and some lamp frames.
The A/B rig is banked at emulator/render-bridge/gauge-ab/ (both confs +
the window-grab script) so the comparison is repeatable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bring the pod's 128x32 plasma readout in-fork, completing the self-contained
glass cockpit -- no vPLASMA process, no pipe.
- serialplasma.{cpp,h}: CSerialPlasma, a serial<n>=plasma backend that parses
the game's ESC P graphics stream (L4PLASMA streams whole changed rows;
receive-only, no replies) into a 128x32 1bpp framebuffer. Transcribed from
vPLASMA's PlasmaProtocol + VPlasmaDevice (graphics path); the factory
text-mode/fonts aren't used by the game and aren't rendered, but their escape
operands are still consumed so the parser can't desync. Seam PLASMA_GetFrame()
for the renderer.
- vpxlog.cpp: draw the framebuffer as an amber-on-black window in the explode
layout, lazily created once a plasma port exists, parked centered under the
lower-left MFD (VPX_PLASMA="x,y,w,h" overrides); WS_EX_NOACTIVATE so it can't
steal DOSBox focus.
- Register SERIAL_TYPE_PLASMA (serialport.h/.cpp, dosbox.cpp -- documented in
the vpx-device README step 3d, since those stock files live in the git-ignored
src tree).
- Confs: serial2 in the _rio confs + deploy templates switches from
namedpipe pipe:vplasma to the in-fork plasma.
Live-validated 2026-07-24. Real pods keep directserial realport:COM2; the
standalone vPLASMA app keeps namedpipe pipe:vplasma.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Emulate the cockpit RIO board natively inside the DOSBox-X fork, driven by
an SDL game controller + host keyboard -- no external vRIO process and no
named pipe. The game<->board round trip runs in-process at the emulated
UART's own cadence, so the serial round-trip dropouts (livelock, TXMAXIDLE,
rxburst, 15s retry) can't occur.
- serialrio.{cpp,h}: RIO 9600-8N1 device state machine + protocol codec +
input mapping, transcribed from the vRIO app (VRioDevice + Protocol +
InputRouter/BindingProfileFormat), minus transport/pacer/UI/locks.
B0 = gamepad (5 axes + joystick column). B1 = keyboard field: MFD banks on
the letter rows, F-keys = Secondary/Screen, numpad = flight controls,
LShift/LCtrl throttle slew; PAUSE/ScrollLock toggle panel<->DOS; vRIO-
grammar bindings file via bindings:/VWE_RIO_BINDINGS.
- vpx-device/README.md: device notes + apply steps (the DOSBox-X src tree is
git-ignored, so this dir is the tracked source of truth; the three sdlmain
keyboard-hook edits are documented there).
- net_{loop,rp}_rio.conf + deploy templates; render-bridge/
gauge_arena_rio_sound.conf (standalone -egg trim); pod-launch --rio
(mutually exclusive with --pipe).
Additive: real pods keep directserial realport:COM1; vRIO-over-pipe keeps
namedpipe pipe:vrio. Validated live 2026-07-22 (pad + keyboard field +
console networking) on the dist install.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per user request, bumped the cockpit MFD/radar windows from 0.6 to 0.75 of
native 640x480 (MFD 384->480, radar 288->360 logical). Live-verified on the
secondary: MFDs now render 331x266 (was 267x218, ~+24% after the primary's DPI
downscale), radar 251x346, all six still fit the 1707x1067 secondary with
margin, renderer unchanged on the primary. No wire stall.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds -Explode: the pentapus cockpit instruments (radar + 5 mono MFDs, the VDB
video-splitter decode) render on the SECONDARY monitor while the reconstructed
i860+GPU 3D renderer stays the main out-the-window view on the primary. The
window rects auto-fit to whatever the non-primary display is (detected via
System.Windows.Forms.Screen, so it survives a monitor rearrange), scaled to
~0.6 and offset into that display's virtual-desktop region; the DOSBox SVGA
screen parks clear of the cluster.
The cockpit windows live in vpxlog's rt_main thread, which only starts under
VPX_RENDER=1 -- the same flag whose DOSBox-thread scene-graph decode previously
stalled wire delivery (fixed earlier by removing it). Feared this would bite
again; MEASURED and it does NOT: with VPX_EXPLODE + VPX_NOMAIN the heavy main-
window 3D render (rt_draw, gated on the main window existing) is skipped, so
rt_main only does the cheap per-tick MFD/radar blits and the DOSBox thread keeps
up. Live-verified: all 6 cockpit windows placed on the secondary (DISPLAY1),
renderer on the primary, wire advancing normally to cmd 23k+ (no command-8
stall), frames flowing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
User-visible bug: connected the reconstructed renderer to the REAL running pod
for the first time (M4d demo) and the render fell increasingly behind the live
game, plus looked near-grayscale. Root-caused with direct profiling instead of
guessing:
1. VPX_RENDER=1 was set in launch_live.ps1 (copied from launch_pod.ps1's
defaults without thinking it through) -- it starts vpxlog.cpp's OWN native
GL render thread (rt_main), which contends with our renderer for the same
CPU/GL resources. Live-caught: with it set, wire delivery stalled at wire
command 8 for 90+s; removing it, the same mission reached command 21,488
in the same window. This was the dominant cause of the visible lag.
2. gpu_raster.build_prims() scanned the whole per-draw program window
(0x08158000-0x08170000, ~24k words) via a Python-level r32() call per word
every frame. Added emu860c.dump_range(lo,hi)->bytes (one bulk C read) and
rewrote build_prims to index a numpy array instead -- verified byte-identical
dump_range output first, then reconfirmed the whole render is STILL
bit-identical to the CPU reference (frames 0/5/11, differ>24 = 0.000%).
3. Profiling after (1) showed the per-texture compositing loop was the real
remaining cost: 62ms/frame doing full-832x512-frame fancy-indexed texture
sampling for EACH of 13 textures, regardless of how few pixels use each one.
Added dpl_sampler.composite_masked (sample+composite only the pixels a
texture actually covers; verified equivalent to composite() on a partial
mask in dpl_sampler's own conformance test) and hoisted the invariant
out of the loop.
Combined: render time 95.8ms -> 54.9ms/frame (~1.75x from this change alone,
~2.1x vs the original ~117ms/frame baseline), still bit-identical to the CPU
reference throughout.
launch_live.ps1: removed VPX_RENDER, renamed the automatic-variable shadow
-> (flagged by PSScriptAnalyzer), added -Pin <fifodump> to
pre-load the full texture set (bit-identical to offline) instead of the
incremental default, for a demo that wants correct color immediately rather
than waiting on the live mission's own upload order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VPX_RENDER=1 was copied in from launch_pod.ps1's defaults without thinking it
through -- it starts vpxlog.cpp's OWN native GL render thread (rt_main),
which is redundant with (and contends against) our reconstructed renderer
riding the FIFOSOCK tee. Live-caught: with it set, wire delivery stalled at
command 8 for 90+s (window rendered, but slowly/poorly per live observation);
removing it, the same mission reached command 21,488 (10 frames) in the same
window. My two earlier successful ad-hoc bring-up tests had already removed
this env var by hand -- the script just hadn't caught up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reconstructed renderer now runs live behind the REAL game: dosbox-x.exe
(arena mission) streams its wire over vpxlog's VPX_FIFOSOCK and live_render.py
renders it in real time -- 18-20 live frames from the actual running game,
coherent perspective-correct arena. The real binary opens the FIFOSOCK listener
at device init and accepts our client (verified standalone first).
launch_live.ps1: turnkey one-command bring-up (lean pod + reconstructed renderer
on the socket, no pentapus/bridge; -Sound / -Port / -PodOnly / -Present).
Serial: switch the render-bridge confs from 'directserial realport:COM1/COM2'
(com0com retired, COM1/COM2 gone) to the fork's namedpipe backend
(serial1=namedpipe pipe:vrio, serial2=namedpipe pipe:vplasma). DOSBox is the
pipe client; the vRIO/vPLASMA apps are servers; an unconnected pipe = unplugged
cable so the mission still renders. Live-verified: both pipes CONNECTED to
running servers (Serial1 -> \.\pipe\vrio, Serial2 -> \.\pipe\vplasma), RIO
input flowing, game rendered faster than the COM-less run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Predator/IR vision: reverse-engineered from the original firmware and
confirmed by the build team -- it is the Division board's TEXTURE-VALUE RAMP
mode (a "check your texture maps" diagnostic the devs hijacked), NOT a
grayscale squash or a false-colour palette. Located in VREND.MNG (effect
handler @0xe6c0, wire action 0x1b, type -1 ON / -2 OFF); ramp colours from
VR_DRAW.C. Renderer reworked to match: vrview_gl now does the 4-ramp
lerp(color0,color1,luminance(texel)) in the mesh pass (grayscale+defog
removed). Live-rendered on a new night-clear arena egg; crew A/B verdict
pending.
Firmware-decomp toolchain (emulator/firmware-decomp/), all built from the
project's own artifacts and validated:
- coff860.py i860 COFF reader (symbols/sections), names match AS860 source
- derive860.py derives the i860 opcode map from matched .S<->.O pairs
- dis860.py i860 disassembler (98% on clean ground truth; proven on
VREND.MNG -- velocirender_statistics decodes correctly)
- sigmatch860.py reloc-invariant signature matcher onto the stripped image
- i860-encoding.md / FIRMWARE-SYMBOLS.txt / README.md
PVISION-IMPLEMENTATION-GUIDE.md: self-contained hand-off for the BT411 team.
HARDWARE-ARCHITECTURE.md + hardware-photos/ (15 board shots): the Division
VelociRender card is a 2-board stack driving a 3-processor pipeline --
INMOS IMS T425-J25S (comms/control, runs vrendmon.btl) + Intel i860 XP-50 (FP
geometry, runs vrender.mng) + Division PXPL IGC 5.2 ASIC with ~48x PXPL EMC
5.1 (UNC Pixel-Planes-5 SIMD array; "EMC" = the firmware's configEMCs) +
Analog Devices ADV7150 RAMDAC + NTSC. Plus the VWE Video Distribution Board
(P/N 1404: AMD MACH130 + 3x Brooktree Bt477) for the 3-VGA-head cockpit split.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- IR / predator-vision thermal: the cockpit IR button toggles it via the
wire (dpl_Effect on action 0x1b, mode -1 ON / -2 OFF -> board.pvision).
vrview_gl present pass remaps scene luminance to a thermal palette
(default heat/Predator; mono|green|amber via VRVIEW_PVISION_PALETTE;
VRVIEW_PVISION / bridge 'v' key force it on). Exact palette + HUD-exempt
pending crew review.
- Texture filter default flipped to bilinear (operator preference); the
i860 board itself point-sampled, so VRVIEW_FILTER=nearest reverts. (A CRT
present-pass bleed/scanline prototype was built and rejected; removed.)
- Searchlight: no code needed -- night A/B proved it is a VIEW-FOG push-back
(fog near 5->60, far 400->500 on), already rendered by the existing view
fog path. Not a light cone. Added TESTNITE.EGG (arena1/night/fog) +
gauge_arena_night_pipe.conf for the test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
render-bridge/freeze.ps1: PyInstaller onefile of live_bridge.py + Dave's
vrboard/vrview/vrview_gl (+glcontext collected for moderngl) -> dist\
renderer.exe (~33 MB). GL backend live-verified from a pure-dist launch
(no --root/--renderer overrides) on 2026-07-10. package.ps1 bundles the
frozen exe automatically when present; DEPLOYMENT-PLAN's renderer OPEN
item is resolved. Ignore the build-artifact dirs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real-pod deployment launcher for the original VDB + octopus 4-head wiring:
VPX_COCKPIT borderless head windows (radar/win0 800,0; 3-color MFD/win4 1440,0;
2-color MFD/win3 2080,0; all 640x480) + the GL bridge as the main out-the-window
view (0,0, 800x600 via new BRIDGE_W/BRIDGE_H in live_bridge.py). Editable RECTS
at the top of the script for other rigs.
focus_dosbox.ps1 restores the deployment window state: renderer -> always-on-top
(WS_EX_TOPMOST, game view never covered) + DOSBox-X -> 10,10 with foreground
focus (keeps the emu thread at foreground priority so the RIO doesn't starve).
Coexist cleanly: topmost renderer stays above the focused-but-non-topmost DOSBox.
pod_deploy calls it last; re-run anytime the stack is disturbed. LAUNCH.md
documents both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RP's high-frequency checkerboard track floor aliases/moires without mipmaps
(reads as "flashing geometry" in motion; user's texture-interpolation call).
vrview_gl _tex() now builds mipmaps + trilinear/anisotropic filtering under
VRVIEW_MIPMAP=1 (keeps point-sample look up close; off by default = authentic
i860, BT-identical). launch_pod.ps1 gains -Mipmap so it's set at BOOT -- never
restart the bridge mid-mission (GL-context churn starves the RIO ACK deadline).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pilot sees the escape-pod/wreck interior at death instead of the portal
ride. Findings (DEATH-SEQUENCE-NOTES.md): two death variants exist --
respawn deaths transit the chain camera to the portal at the origin
(fp_cam's <100m vehicle-distance guard rejects exactly that ride = the
fix target); mission-end deaths (tonight's capture) have no transit on
the wire at all, just the fog fade at the wreck. At death the 0x1f
articulation batches switch to driving origin-anchored nodes = the
portal diorama animating; type-3 view flushes decode as projection+fog
only (hither/yon at floats 12/13). Reference capture
captures/netdeath-20260708.fifodump (full networked mission incl. death)
+ replay probes in probes/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The main loop rendered EVERY draw_scene in arrival order, so any render
slowdown played the mission slower than real time and the view drifted
seconds behind the game (user: "renderer fell way out of sync"); the
backpressure hides in the socket, so backlog= stayed 0. Now each pass
slices all complete records, applies every state record in order, and
presents only the newest frame; superseded presents are counted in the
new skipped= report field. Live verdict over a full mission:
frames=10169 skipped=3 backlog=0B -- locked to the wire.
Also from the tuner session: seat trim granularity 0.1 (0.5 was too
jumpy) and a HUD size trim (vrview_gl u_scale + VRVIEW_HUDSCALE env,
live +/- keys) -- tuners stay enabled for pilot feedback; seat trim
turns out to be per-mech (Mad Cat vs Thor want different values).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plasma serial2 addition put COM2 (IRQ 3) in conflict with nicirq=3:
netnub phase worked, then BTL4OPT opened the plasma port and NIC RX died,
so the pod never checked in to the console. NIC now on IRQ 10 in all net
confs + NET.CFG INT 10 (both sides must agree). Verified live: two full
console missions with plasma running.
Also: launch_pod.ps1 prepends the Npcap dir to PATH itself (a bare shell
gave "NO PACKET DRIVER FOUND"); net_diag.conf = packet-stack-only boot
with the NCSA Telnet suite on E: for DOS-side TCP tests; net_loop.conf
promoted to full production parity (sound + plasma + IRQ 10) -- the
GO.BAT-style instant netnub relaunch is also what lets the console
recover its zombie TCP session between missions (see NET-NOTES).
NET-NOTES: full 7/08-09 forensics writeup (TAP RX counter lie, console
IP .10, SBK re-upload measured, zombie-session mechanics).
LAST.EGG = the operator's missions from tonight's runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- fp_cam works before the first vehicle articulation (a freshly dropped
mech sends no 0x1f until it moves; bailing out left the raw chain
convention showing the inside of the mech at every drop)
- The FLYK stand-in +90-deg yaw correction no longer applies to munga
content (it yawed the player's own mech -- and every animated model --
90 degrees: "looking through the right shoulder")
- With fog on, the clear colour saturates to the fog colour (background
at infinite distance); the pre-drop hold is FULLY black, as period
pilots confirm
- net_full.conf: COM2 plasma passthrough + setenv plasma flag
Networked console session verified live: black hold at ready, flash +
fade on console launch, drop-bay doors present, own mech visible, and
the full death sequence captured on the wire (fade-to-black, escape-pod
interior via near-fog exemption, world reload, white flash, blue-violet
respawn fade = the blue swirly's home).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stale-aim saga, root-caused live with the operator:
- The device raycast walked only lod[0] of each object; BT content is
authored ADDITIVE_LODS (arena wall = posts lod + panel lod), so the
aim ray flew BETWEEN the skeleton pieces of whatever was targeted --
94% miss rate (pick stats now log this).
- Misses fell back to a plain frame ack carrying no sect data, so the
game kept a seconds-old target: "aim updates every few seconds",
stationary shots stale. The pick is now answered EVERY armed frame;
a miss is a valid all-zero reply that clears targetEntity (aiming at
open sky un-targets -- authentic misfire, not stale fire).
- Raycast walks ALL lod children; own-articulation exclusion via the
CAM backchannel 7th field (vehicle root DCS) stops mid-volley picks
of the player's own muzzle flash / arms.
Renderer: same ADDITIVE_LODS fix -- when every LOD switch window is
the degenerate (0,1e9) fallback, draw the union of all lods. The
arena wall ring (user-confirmed live) went from floating post slivers
to the full textured wall. Bridge reports its wire backlog.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three WAVs written from the render thread: per-card PRE-limiter
streams as float32 (clipping baked in upstream shows as flat tops
above full scale) and the POST-limiter int16 mix (what the user
hears). Headers re-patched every second so files survive a hard
kill. launch_pod.ps1 arms it into the work dir by default.
Diagnosis logic: flat tops in a card tap = per-voice clipping inside
the EMU8000 core; clean cards + dirty mix = limiter artifact; all
clean = winmm path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BT411's mapscan (full RES container format) run against our RES settles
the walls question: arena1/arenall and arena2/arenall2 place the open
city set only; the wall1/2/3 segments belong to the RAV map (18+8+5
placements: a canyon maze), and cavern/grass/dbase carry the wall10k
outer ring. Nothing was ever dropped by the renderer.
TESTRAV.EGG = TESTARN.EGG with map=rav; gauge_rav_sound.conf boots it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
L4TIMER selects the game clock (L4TIME.CPP): FAST = HMI SOS interrupt
clock at 28 Hz, unset = polled 18.2 Hz BIOS tick. Not a speed switch --
finer time quantum, smoother sim dt and timing cadences.
Per the operator: the flag tracked the deployed fleet's hardware --
original Tesla pods ran Pentium Pro 90s (SLOW), later fleets Pentium
Pro 200s (FAST). The emulated pod has no ISR-headroom constraint, so
FAST is our default from here on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renderer (dpl3-revive/patha):
- Instance visibility honored at DRAW time (stored word 4 = live
dpl_SetInstanceVisibility field): laser beams (4-frame pulses) and
missile models now render; cache tags w3 0/1 instances 'gated'
- PSFX world bursts (dcs=0 muzzle/impact/explosion) rendered; psfx
storage list-based so one-shot bursts stop collapsing onto one key
- fix_degenerate applied to INSTANCE chains (was camera-only): the
beam chain rides four rank-2 rig DCSs; composed rank-2 drew beams
away from the guns. Offline f1635 now shows both arm beams
converging on the aim point
- First-person canopy cull: the always-armed cockpit model at the
head drew as a vertical line down screen center (user-confirmed
fixed live)
Device (vpx-device/vpxlog.cpp):
- CAM backchannel on the fifosock: bridge streams its validated
camera (+ vehicle root DCS); raycast_pick aims along the player's
real look instead of the static view-node pose, which had locked
every missile/beam onto one fixed wrong world point
- raycast_pick skips hidden-until-armed instances and the player's
own articulation subtree: picking own beams created a target
feedback loop, picking own missiles retargeted salvos mid-flight,
and a picked missile's deleted DCS froze the game (arena2 crash)
Sound (vpx-device/vweawe.cpp): stereo-linked peak limiter replaces
the hard output clamp (the "generator out" crackle); VWE_AWE_LOG
reports pre-limit peaks + duck counts, enabled in launch_pod.ps1.
Missile-fire crackle persists: per-voice EMU8000 clamp suspected.
Eggs/confs: TESTAR2.EGG + gauge_arena2_sound.conf (map=arena2 city
variant); cavern.egg (console-era reference); plasma display on COM2
in the gauge confs; gauge_arena_net_sound.conf (slirp net stack).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vpx-device/vpxlog.cpp -- WEAPONS FIRE. The game's fire gate is
targetReticle.targetEntity (mech+0x388), filled every frame by a Division-board
reticle pick (dpl_RapidSectPixel) our HLE never answered, so every shot
misfired. The device now casts the camera centre ray against the live scene
(Moller-Trumbore) and returns the full hit -- instance + DCS + geogroup +
geometry -- piggybacked on the draw_scene reply; terrain is a valid target so
you can fire and miss. Sending geogroup=0 was hanging the game (GetAppSpecific
on a null); returning the real geogroup fixed it. Pick is default-on with a
single VPX_NO_PICK escape hatch. Also swaps the upper-left/right MFD explode
windows (screen location only -- decode untouched, real cause TBD on a pod).
dpl3-revive/patha/vrview.py -- HAT-GLANCE fix at the source. One cockpit DCS
(0xa2c) flushes a rank-2 rotation (shifted body -> wrong read window) that
collapsed the camera chain to rank 2 and smeared the head glance onto the wrong
axis (left/right hat read as pitch). chain_matrix(fix_degenerate) treats a
degenerate chain DCS as identity: preserves the look exactly and keeps stick-Y
torso pitch working (an earlier bridge-level yaw/pitch swap broke stick-Y and
was reverted). User-verified live: hat all 4 dirs + stick-Y both correct.
render-bridge/launch_pod.ps1 -- launch the bridge with pyw (windowless) so no
console window parks over the cockpit displays (Start-Process ignores
-WindowStyle once stdout/stderr are redirected).
render-bridge/live_bridge.py -- surface bridge render errors, flush the status
line; reverted glance-swap note.
Also vendored HUD (dpl2d) renderer work in vrboard/vrview_gl and RENDERER-COLLAB
/ RIO notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- emulator/render-bridge/: the pod->Dave's-renderer live bridge (rescued
from session scratchpad): live_bridge.py (first-person cam from the 0x1f
pose, arrow-key eye-height trim), fp/chase offline renders, fifobridge,
diagnostics, gauge_arena[_sound].conf, and launch_pod.ps1 (one-command
pod+bridge restart: pentapus VPX_EXPLODE + AWE32 sound + GL bridge).
- _backend.py: renderer selection -- vrview_gl.GLRenderer (moderngl) by
default, VRVIEW_SOFT=1 for the software reference; backend-agnostic
frame save via last_frame.
- GPU backend runs on side-by-side CPython 3.13 (moderngl/glcontext cp313
wheels; no MSVC needed): 63fps headless / 60.7 windowed vs 21fps software
on the same scene, and the GL path also draws the vertex-alpha cloud dome.
- vrview.py: VRVIEW_GAMMA env selects DAC gamma (1.25 live-renderer legacy
vs 1.7 per the original GAMMA.C) for A/B against the real pod.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>