RP's cockpit displays decoded near-empty (top-strip only, a copy off-screen at
VRAM 0x100000) while BT's were full. Root cause was NOT the VDB decode (byte
split/palettes/stride all correct, proven by BT) but an SVGA bank-switch
GRANULARITY mismatch: RP's REL410/RP/GAUGE/L4GAUGE.INI [640x480x16] used
granularityInKB=4 vs BT's 64 on the same STB Horizon+ (CL-GD5434). DOSBox-X's
CL-GD5434 bank emulation handles 64KB granularity (BT full) but mishandles the
4KB-granular paged gauge writes, scattering them off-screen.
FIX (config, no rebuild): RP L4GAUGE.INI granularityInKB 4->64 (matches BT,
same card). VERIFIED live: VRAM content scan went from 0x0+0x100000 (top-strip
+ off-screen) to the FULL framebuffer 0x0..0x90000 populated; radar +
upper-center MFD confirmed correct, rest under reference check.
Also commits the VDB diagnostic tooling that found it (vpxlog.cpp, all
default-off): VDB_SCAN (VRAM content locator -- decisive), VDB_PALDUMP,
pixel-mask value logging, VDB_REALSTRIDE + live mode/stride/start logging,
VDB_BASE read-base override, VDB_APPLYMASK DAC-mask honoring. Full writeup in
VDB-NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RP experiment finding: RP runs on the BT pod emulator (shared MUNGA stack) but
its cockpit VDB heads mis-decode. Root causes captured live (VDB_PALDUMP +
vpxresp log): (1) framebuffer identical to BT (mode 0x111, 640x480); (2) RP
loads pal0 (BT's dynamic color-radar palette) as ALL ZEROS -> radar head black;
(3) RP actively drives the VGA-DAC pixel-mask register that BT leaves at 0xFF,
and pal_draw ignored it -> likely the MFD garble.
Device change: pixel-mask writes now log their VALUE on change
("# VDB pixel-mask[gN] = 0xXX"); pal_draw ANDs the palette index with the
group mask under VDB_APPLYMASK=1 (default off = BT-identical); masks default to
0xFF in vdb_reset so an un-driven group still decodes. Built clean under MINGW64.
Also: Mac Console 4.10 retired from regular use (host .NET console is the daily
driver); tap2_mirror.py PARKED with revive notes for console A/B only. The
SendToRxAdapters registry stays set (serves the .NET host console).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instance body word 9 is the INTERSECT mask (verified live: terrain,
sky, buildings and enemy replicant mechs carry 0xffffffff; the player's
own mech parts, laser beams, missiles and effects carry 0). The raycast
now ANDs it against the query mask from the 0x26 arm -- exactly the
real board's exclusion. The min-range and hidden-until-armed gates
retire: point-blank enemy hulls are targetable again, and own-ordnance
exclusion is authored by the game itself.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device raycast/scene parser (vpxlog.cpp):
- Connection lists: accumulate the WHOLE batch before slicing (records
straddling burst boundaries were dropped + misaligned the rest), do
not wipe polys per batch header (floor ships tri batch + quad batch;
only the last survived), keep every loop index (closing-dup drop
halved each quad). Together these were the "holey ground" the
range-finder fell through.
- Own-mech exclusion by minimum pick range (15) instead of the
dcs_parent ancestry walk: sibling link edges made the FLOOR and SKY
read as the player's subtree, so ground/sky aims never resolved.
Measured: own barrels/flash live within ~8 of the eye; sky/ground
are real pickable entities (sky.mod, afloor) -- firing into them
fires and misses, per the operator's arcade experience.
EMU8000 core (emu8k.cpp): clamp the oscillator output after cubic
interpolation. Crest overshoot on UNFILTERED voices overflowed int32
in the volume multiply and wrapped the crest negative for 1-3 samples
-- the speech/effects crackle (recorded signature: +0.7 flipping to
-0.7 at crests, random phase vs the chunk grid). Filter paths already
clamped; the bypass path now matches.
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>
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>
- rt_draw shading replaced with the dpl3-revive software-rasterizer model
(vrview.py draw(), proven on our own arena captures): lit = sceneAmb +
sum |N.L|*col double-sided; c = emis + diff*lit (tex*base*1.275 when
textured); Division 10-bit-DAC gamma 1/1.25 on output. Default light rig
matches theirs (ambient 0.35 + one 0.65 sun) until wire lights arrive.
- Real wire lights decoded per their SceneCache.rebuild(): lmodel type-6 /
light type-0xe bodies (dcs @d+12, type @d+16: 2=ambient 3=directional,
rgb @d+20), directional aim = light DCS +Z row, sanity-clamped; frame
carries summed ambient + up to 2 suns.
- Projection: the hardcoded post-projection X flip (SMPTE-era) made the
game view steer mirrored (turn right, view swings left). Game path
(ydown) now projects without it -- matches dpl3-revive, which has no
lateral mirror; demo/test path keeps the flip. User-verified: native
window steering now correct.
Fork source (emulator/src/src/hardware/vpxlog.cpp) in sync; rebuilt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Console queues a mission -> egg over the wire -> pod loads + runs it -> all
cockpit heads render -> mission ends on the console timer. Four fixes:
1. ne2000.cpp: don't BX_PANIC on the TCR inhibit-CRC / auto-tx-disable bits.
A full production boot runs the packet driver's internal loopback
self-test, which sets those bits; Bochs' NE2000 aborted the whole
emulator. Record them in TCR state instead (harmless for an emulated NIC;
pcap/host does framing+CRC). Also fixes a latent coll_prio bit-4 vs bit-3
round-trip bug. Committed copy in vpx-device/.
2. Confirmed the mission-load page fault (00FF:219D) was RIO-OFF; booting
with the RIO live (net_full.conf) loads and runs the mission cleanly.
3. net_loop.conf + net-boot/loop.bat: GO.BAT-style loop so the pod stays
connected to the console and picks up missions as they're queued (the
real pod relaunches netnub after each mission/idle disconnect).
4. HEADLINE: blank MFD/radar heads were an I/O port collision. The VDB
video splitter is hardwired at 0x300-0x31A (palettes 0x300/0x308/0x310);
the NE2000 at nicbase=300 swallowed the game's VDB palette writes ->
vdb_pal stayed zero -> pal_draw decoded every head to black. Same VDB
spam corrupted NIC RX, dropping the console EndMission (mission overran
its timer). Fix: move the NIC to 0x340 (DOSBox nicbase=340 + DOS NET.CFG
PORT 340; must agree). VDB keeps 0x300. Config-only, no rebuild.
Adds: net_full.conf, net_loop.conf, net-boot/ (ODI drivers, NET.CFG@340,
loop.bat, README), vpx-device/{ne2000,ethernet_pcap}.cpp; updates NET-NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendored 86Box EMU8000 core (GPL-2, emu8k.cpp/.h + shim) plus vweawe.cpp:
two cards at the production addresses (0x620/0x640 register triplets),
rear-card DSP/mixer stub at 0x240, synthesis on an autonomous render
thread with direct winmm output so sustained voices ride through
emulation-thread stalls (RIO staging retries) like the real silicon did.
WC sample-counter interpolation between render chunks keeps HMI SOS
busy-waits fast. VWE_AWE32/VWE_AWE_ROM/VWE_AWE_RAM_KB/VWE_AWE_SHIFT/
VWE_AWE_LEAD_MS/VWE_AWE_DUMP/VWE_AWE_LOG env knobs.
emulator/roms: the AWE32 1MGM GM ROM dumped from our own pod card (SF2)
plus the reconstructed raw image the emulated EMU8000 loads. The HMI
driver refuses the AUDIO*.RES SoundFont upload without it (banks declare
irom=1MGM) -- that was the root cause of the first silent runs; full
debug chain in SOUND-NOTES.md. Repo serves the pod-owner community only.
.gitignore: drop the ROM excludes and un-ignore ALPHA_1/ and sda4/
(committed separately).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device renderer (vpxlog.cpp, commit-copy of the fork source):
- View-flush fog decode (enable/near/far/rgb at d+72.., mode 5 linear,
garbage-guarded; VPX_NOFOG=1 diagnostic) applied per fragment.
- Full dpl_MATERIAL parse: emissive/ambient/diffuse/opacity/specular/
shininess (opacity+specular parsed, not yet applied); fixed-offset
texture/ramp refs with lazy resolution, rebake on material/ramp reflush.
- Gallery shading model (restoration/gallery_template.html) as a GLSL 1.10
program over the existing immediate-mode path, fixed-function fallback:
c = (matAmb*sceneAmb + matDiff*mix(ramp.lo,ramp.hi,acc))*tex + matEmis,
linear fog with immunity knob. Textured polys use an identity light-ramp
(texels already baked through the material ramp). Newell flat normals for
polys without wire normals. Interim sun/ambient until the light node is
decoded: VPX_AMBIENT / VPX_SUN env overrides.
RENDERER-COLLAB.md: shared working doc with the external-renderer team --
verified FIFO action/node tables, struct offsets, scene assembly and
coordinate conventions, available data taps, open questions (incl. the
type-7 object-vs-light flush conflict).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New layout mode showing every cockpit display as its own desktop
window -- the two MFD heads split into their individual color wires,
which is what the pentapus cable does on the real pod:
- wins 5-9 = the five mono MFDs (win5 LL = HEAD A red, win6 LR =
HEAD A green, win7 UL = HEAD B red, win8 UC = HEAD B green,
win9 UR = HEAD B blue), each decoding framebuffer bits 8-15 through
its head's palette and rendering its single wire as green-phosphor
brightness (G=v, R=B=v/8).
- Radar (win0) rotated 90 degrees clockwise and shown portrait
480x640 -- the pod's radar CRT is mounted sideways -- centered
between the two lower MFDs.
- All displays at native size: MFDs 640x480, uppers at
(20,20)/(680,20)/(1340,20), lowers at (20,560)/(1340,560) aligned
under the outer uppers; radar at (760,560); Division main 800x600
at (2020,20).
- The DOSBox SDL main screen is auto-parked centered under the
Division window (one-shot EnumWindows by title + SetWindowPos).
- Geometry overridable per window via VPX_WIN<g>/VPX_MAIN as usual;
win<g>.bmp dumps cover g=5-9, radar dumps rotated as displayed.
VPX_COCKPIT takes precedence if both modes are set; cockpit and
debug layouts unchanged (regression-checked).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The RIO cockpit board now runs sustained sessions with button mashing;
dropouts self-heal in ~1-3s. Documented in RIO-NOTES.md:
- directserial RIO_TAP=<path> (host env): logs every TX/RX byte with
host-us + emu-ms timestamps, plus config/RTS/DTR/break lines. This
instrument found every root cause below.
- Confs: rxburst:16 restored (no-burst reply pacing made the game ACK
~14ms late -> board dropped on the first long analog stream; the old
'rxburst corrupts boot' belief was the then-unpatched PCSPAK crash).
priority=highest,highest (unfocused DOSBox was demoted and blew the
ACK deadline).
- BTL4OPT.EXE patch lineage (in ALPHA_1/, zip left pristine):
v2 full DISABLE_AND_DIE NOPs (v1 left the IRQ/RTS-retract prologue
live -> first protocol error deafened the driver), v3 TXMAXIDLE 4->32
(kills the button-press ACK-window livelock), v4 analog retry limit
15s->0.5s (dev value was 0.2s; recovery now near-instant).
- Board firmware patch plan recorded for the EPROM-dump route.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On each palette reload, diff against the previous captured palette and log how
many of the 256 entries changed plus the first few index:old->new. Confirms
the static/dynamic split: pal1/pal2 load once (static base structure -- red in
odd entries, etc.), pal0 is rewritten ~28x. In this capture pal0 is doing the
intro FadeToWhite -- ~216/256 entries ramping in lockstep from 0 to ~60/63 --
i.e. animating the whole display brightness via the palette without redrawing
the framebuffer. Selective per-element flashing (targeted small deltas) would
need an active mission to observe; this probe is the instrument for it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refine the display decode for pairing: window 0 renders the color radar from
bits 0-7 (3:3:2 RGB); windows 1-8 each render a single bit (8..15) as
white/black. Each bit turns out to be a recognizable display plane -- e.g.
bits 8 & 12 are both the weapons panel, bits 9/11/13 the systems panel -- so
the mono displays' 2-bit fields are scattered (out of order) across the word.
This fan-out lets the bits be re-paired into their six displays by eye.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gauge framebuffer is an encoded stream: each 16bpp pixel packs all six
cockpit displays. Decoded live into per-display windows:
bits 0-5 = the COLOR radar/tactical display, as 6-bit RGB (2 bits/channel)
bits 6-7 = mono display 1 (nav scope)
bits 8-9 = mono display 2 (weapons/systems)
bits 10-11 = mono display 3 (sensor cluster)
bits 12-13 = mono display 4
bits 14-15 = mono display 5
That's 6 + 5x2 = 16 bits exactly -> six displays (1 color + 5 mono), matching
the pod hardware. A 7th window (bits 16-17) confirms the budget: it's black.
Each display now renders in its own 640x480 window from the shared framebuffer
(vga.mem.linear). Mono screens show as brightness; the three VDB palettes are
the per-display color maps (next: apply them).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the palette-swatch windows with ones that render DOSBox's live gauge
framebuffer -- the M_LIN16 16bpp video memory (vga.mem.linear, 640x480, 5:6:5,
start=vga.config.real_start) via glDrawPixels. Each window now shows the real
gauge image, matching the DOSBox screen ("similar output"), confirmed live.
Diagnostic: the VDB splitter-on log now prints the framebuffer mode
(0x111 / M_LIN16 / 640x480) -- confirming direct color, so the palettes are
not index LUTs.
All three windows currently show the shared framebuffer identically; the
per-display palette decode (how the VDB carves the encoded 16bpp stream into
the six monitor outputs) is the next step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the in-render-window palette strips with three separate 640x480
windows, one per VDB display palette (secondary/aux1/aux2). Each has its own
GL context (rt_main wglMakeCurrent-s each per tick) and shows the palette as a
16x16 color grid, redrawn every 50ms so it animates live regardless of the VPX
render cadence. make_gl_window() factored out; strips + VPX_VDBSTRIP removed.
Confirmed live: aux1 = red/green/olive/black status checkerboard, aux2 = a
4-region color map (green/blue/red/purple, 64 entries each), secondary = the
animated one (black between flashes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Draw the three VDB display palettes as 256-color bars along the bottom of the
GL render window, updated every frame straight from the VDB storage -- a live
view (like the Division output) instead of static snapshots. This matters
because pal0 (secondary) animates and the earlier file dumps caught pal0/pal2
at their SVGAZeroPalette init (all black); live, pal2 is actually a rich
4-region color map (green/blue/red/gray-purple, 64 entries each) and pal1 the
red/green/olive status palette. Toggle with VPX_VDBSTRIP=0.
Also: VDB_PALDUMP now keeps the most-lit snapshot per group (max non-black
bytes) so file dumps no longer capture the zero-init. VDBPalette storage moved
above the render thread so rt_draw can read it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VDB_PALDUMP=<prefix> writes each completed 768-byte palette load to
<prefix>N.rgb (256 RGB entries per VDB display group). render_vdb_palettes.py
renders each as a 16x16 swatch grid + linear ramp.
First capture: pal1 (aux1) is a structured red/green/olive/black color map
(2-bit R x 2-bit G) -- likely the color display's status coloring; pal0
(secondary, dynamic ~29 reloads) and pal2 (aux2) are grayscale, sampled black
mid-animation. Palettes are the per-display color maps the VDB applies when
splitting the framebuffer to the six monitors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The VDB device was only logging the splitter-clock strobe; the gauge code's
palette writes were hitting the handler but being dropped. Cause: I mapped the
palette register groups at Adam's base offsets (0x300/0x308/0x310, sub 0-3),
but per L4VB16.CPP's "+2" convention and L4SVGA16.ASM SVGAWriteFullPalette
(mov dx,base; add dx,2 -> write-addr; inc dx -> data) the real registers are
at base+2: secondary 0x302-0x305, aux1 0x30A-0x30D, aux2 0x312-0x315, VGA-DAC
layout within each (0 mask, 1 read-addr, 2 write-addr, 3 data).
With the offsets corrected the device now captures the full palettes -- e.g.
"VDB pal0 loaded 768 data bytes" (256 RGB) + pal1 -- the per-display color
maps the VDB uses to colorize each of the six monitors. Foundation for
reconstructing the individual displays.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lighting: stride-8/9 vertices carry a normal (floats 3-5, uv at 6-7). The
backend transforms normals by the instance rotation and lights with a single
directional sun (GL_LIGHT0, smooth, two-sided, GL_COLOR_MATERIAL). Terrain
and hulls shade instead of reading flat.
LOD: the lod flush has no switch distances -- the host keeps the active LOD
at the child-list head and re-orders over the wire, so children[0] is right
and LOD changes come through as the sim runs.
The crash that halted the game once the RIO drove the sim was not ours: fault
at 0047E1D1 writing 0xFFFFFFFF is the RIO driver's DISABLE_AND_DIE debug
macro (PCSPAK.ASM, DIE_ON_ERROR=1), which deliberately faults on a serial-tx
anomaly -- error 3 = body char >0x7F during a RIO packet, i.e. a glitch on a
board reset. Release build (DIE_ON_ERROR=0) compiles it out and recovers
(and al,07Fh). patch_btl4opt.py NOPs all 12 sites by signature (with .orig
backup) = release behavior; game then survives RIO resets (1200+ frames).
(ALPHA_1 is git-ignored, so the tool ships, not the patched binary.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Texturing (validated live -- game-live-textured.png, the ravine's brown dirt
terrain): the wire model is Division's intensity+ramp scheme. action 26
uploads 8-bit intensity texels ([node][nbytes][w][h] + rows; type 13 =
texture); texmap (12) references the texture; material (11) references its
texmap and a ramp (14: lo/hi RGB); texel color = lerp(lo, hi, i/255). The
backend bakes RGBA per material, uploads to GL, maps with wire UVs
(stride-5: floats 3-4; stride-8/9: floats 6-7).
Serial (directserial fork options, tracked in vpx-device/serialport/):
- rxpollus:<us> -- receive poll tick (stock 1ms); 100us discovers inbound
bytes ~10x sooner. Validated: sim advanced, camera moved with real RIO.
- rxburst:<n> -- stock DOSBox re-serializes received bytes at emulated wire
speed (~1ms/byte at 9600) though they already paid wire time on the real
cable; a 15-byte analog reply gained ~14ms, blowing the RIO's few-ms ACK
window and dropping the game into its 15-second retry fallback
(L4CTRL.CPP limit=15.0 //0.2). rxburst:16 delivers buffered bytes 16x
faster. game_rio.conf: realport:COM1 rxpollus:100 rxburst:16.
Crash-on-advance fixed: *_TSHD.BGF terrain shadows live only in arena
subdirs the search path misses; null shadow renderable was dereferenced once
the sim moved. All 11 copied into VIDEO/GEO in the working image; game now
runs sustained (560+ frames, textured, no crash). Details in RIO-NOTES.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The full DPL hierarchy the game uses (vs flyk's flat scene) is now decoded
and rendered:
- stride-aware set_geom_verts (header word 3 = floats/vertex: 3/4/5/8/9;
mech meshes carry normals + UVs)
- instances are list_add children of DCS nodes; instance flush field 4 ->
object; object->lod->geogroup->geometry; dcs_link builds the articulation
tree of 4x4s (payload floats 4..19, row-major, row 3 = translation)
- game world is y-down (DCS matrices carry a reflection); projection flips
x (Division mirror) and y
render_game.py reconstructs a captured game stream offline: the mission
arena (10km, 246 instances, 330 geometries), the player's Thor at the
camera, six enemy mechs 1.5km north -- game-mech-decoded.png shows one with
real hull/armor/glass materials; game-cockpit-decoded.png the cockpit view.
The live backend (vpxlog.cpp) gained the same traversal and now draws the
game's out-the-window view in real time (game-live-gl.png): sky, arena
floor to the horizon, own gun barrels at frame bottom.
Next: texturing (action-26 texel maps + UVs), lighting from wire normals,
per-frame articulation once the RIO drives the sim.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The VDB is the ISA card that fans the PC's Cirrus Logic framebuffer to the six
secondary cockpit displays (5 mono + 1 color), dividing the pixel clock. From
the driver (CODE/RP/MUNGA_L4/L4SVGA16.ASM, L4VB16.CPP; "Adam's port decoder
design" -- Adam G., VWE hardware):
0x300/0x308/0x310 three VGA-DAC-style palette groups
0x31A / 0x319 splitter high-color clock divider ON / OFF
New emulated VDB device in vpxlog.cpp at I/O 0x300-0x31A (active when logging,
disable with VDB=0): decodes palette write-address/data/mask/read-address and
the clock strobes, recording palette contents + splitter state so the six-
display encoding can be decoded later. Validated: with gauges enabled
(gauge.conf, setenv arg4=g) the game issues "splitter clock ON (0x31A)" ->
captured. The board is write-only (game never reads it, per driver + owner),
so its absence is not the crash.
Crash diagnosis (BTL4OPT.EXE CODE+0x123B): the faulting routine is a heap
free() with boundary-tag coalescing; it was handed a garbage block pointer
(value 2). Symptom of upstream corruption, reached only when the RIO is in
sync and the sim advances. HISTORY.md gains a "Cockpit display hardware -- the
VDB" section with the attribution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- vpxlog.cpp: VPX_FIFODUMP=<path> records every FIFO burst ('VPXM' records)
- decode_fifodump.py: action census + payload dumps of a capture
- render_capture.py: reconstruct the DPL scene graph from a capture and
software-render each draw_scene frame (camera, view, materials, geometry
all taken from the wire)
- divrgb.conf + divrgb.fifodump: flyk divrgb.scn capture fixture
- divrgb-decoded.png / divrgb-frame0.png: first images ever produced from
the Rel 4.10 VPX protocol without a real board -- the textbook SMPTE
color-bar pattern, validating verts/conns/materials/camera in one shot
- PHASE3-PROGRESS.md: the established Rel 4.10 wire protocol (action map,
node types, message layouts); RENDER-HARNESS.md updated
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Disassembled BTL4OPT.EXE (PE-in-DOS32RTM) to crack the last unknowns:
- velocirender_sync (@0x48D220): sends action 0x2D with a data token and
checks cmp token, reply.node[0] (the 'unexpected action' text only prints
the action; the real check is the echoed token). Device now echoes the
token in node[0]. Sync passes.
- Speculative-poll gating: only feed a reply after consecutive empty polls
(a blocking inRecord spin), not single handle_iserver_stuff() checks.
- Frame-ack: velocirender_frameack expects action 9 (vr_draw_scene); device
tracks outstanding draw_scene and replies 9. Frame-ack passes.
The game now passes sync, loads renderer config, builds the scene
(create/flush/list_add/draw_scene), completes frame-ack, and enters its own
content loader (L4VIDEO.cpp) loading BattleTech models. Remaining issues are
content packaging/paths and an empty DPL config -- not VPX protocol. The
board emulation is functionally complete for boot + scene setup + frame-ack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RIO (Phase 5) validated with real hardware: with a physical RIO on
COM1 and serial1=directserial realport:COM1, the game's 1996 RIO
driver talks to the board — the 'RIO never came back' error is gone
and expected activity was seen on the RIO board.
VPX FIFO fast path decoded (OUTSW.ASM + capture): after i860 boot the
render protocol pumps 16-bit words to a FIFO data port seen as
0x154/0x155 byte writes, payload starting with the 4-byte action. The
device now extracts FIFO message actions (confirmed vr_init=0 with the
args string, vr_create=1 with node type 0x2D) and echoes replies with
handler-specific overrides from board source VR_REMOT.C (init/statistics
reply action = 1).
Blocked on one unknown: velocirender_sync checks the init reply against
a constant that is neither 0 nor 1, so the Rel 4.10 board differs from
the DPL3 dev source. Needs the Rel 4.10 LIBDPL source or disassembly of
BTL4OPT.EXE at the format string (file offset 0x107772). Device has a
VPX_INIT_REPLY experiment hook for candidate values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The VPX responder device now drives production BattleTech v4.10
through the full transputer/i860 boot, far past the Phase 1 wall:
- iserver boot handshake solved: feeds 3 well-formed 'version' (tag 42)
iserver requests, satisfying startup_handshake()'s 3 transactions.
- i860 download solved: parses the outbound framed renderer messages
(vr_860args/code/data/bss) and absorbs them, staying byte-aligned.
- echo-action reply model validated against board source
(VRENDER/VR_REMOT.C reply() echoes the received action); device
tracks and echoes the last outbound action generally.
Remaining sub-protocol identified and characterized: after i860 boot
the host switches to a FIFO fast path (OUTSW.ASM: 0x40 tag + REP OUTSW
16-bit words to FIFO port 0x154/0x155, gated by ok_to_fifo at 0x160).
The current build stops at velocirender_sync because the device only
implements the slow byte path. PHASE2-PROGRESS.md documents the exact
next steps (FIFO transport + render loop -> Phase 3 OpenGL backend).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Built DOSBox-X from source with a custom VPX link-adapter logging
device (vpx-device/vpxlog.cpp) and captured the game's outbound boot
sequence. Findings:
- Register map confirmed against LINKIO.C: outputData 0x151,
outputStatus 0x153 (polled bit0), resetRoot 0x160, analyseRoot 0x161.
- Reset preamble captured exactly (analyse=0, reset 0->1->0 + status inits).
- The game streams 85298 bytes to outputData that are BYTE-FOR-BYTE
identical to VRENDMON.BTL; first byte 0xF0 = transputer boot-from-link
primary-bootstrap length. Protocol stage 1 (reset + monitor download)
fully characterized; no hidden bulk/interrupt path.
- Production cockpit dump ALPHA_1 added (git-ignored): its BT is v1.1.0.6
with a VRENDMON.BTL byte-identical to the captured stream, so this
result reflects the exact cockpit software. ALPHA_1 is the reference
image going forward (carries RP + production pod/network boot chain).
Adds analyze_capture.py, capture.conf, PHASE1-RESULTS.md. DOSBox-X
source tree and capture artifacts are git-ignored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>