Decoded from L4RIO.CPP/HPP and verified byte-for-byte against a real
v4.2 board tap: CheckRequest 80 -> board must send TestModeChange ENTER
(8C 01 0D), self-test status stream (85 <status> <unit> <ck>), EXIT
(8C 00 0C), then answer VersionRequest 81 with 86 <maj> <min> <ck>.
Full command map 80-8C both directions, ACK/retransmit rules, and the
~1ms/byte 9600-baud pacing requirement. This is the implementation spec
for the user's vRIO (virtual RIO on COM1): its missing 8C packets are
exactly why the game logs 'RIO never came back from check request!'.
Co-Authored-By: Claude Fable 5 <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>
- 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>
Bring the graphics-dev collaborator's dpl3-revive into the repo as first-class
project code (they've handed it off; it's ours now). This is the proven
Division renderer that our in-process rt_draw has been trying to be.
What's here:
- parser/ B2Z/V2Z/SVT/SCN/SPL/BGF/BMF/BSL decoders (pure Python).
- spec/ reverse-engineered format + the definitive VelociRender wire
protocol (from the original DIVISION source, matches our live
VPX node/action tables exactly).
- source-ref/ read-only copies of the original DIVISION C (BIZREAD.C,
DPLTYPES.H, DPL.H) that define the formats.
- patha/ the "virtual VelociRender board": vrboard.py (24-action protocol
server), vrview.py (numpy software rasterizer, the reference),
vrview_gl.py (moderngl GPU backend, 832x512@60Hz), plus the
run/replay/regress tooling and evidence renders. Drives FLYK/BLADE/
Star Trek demos AND our btl4opt/rpl4opt game binaries.
- viewer/ WebGL archive generators (.py); prebuilt HTML/data regeneratable.
- samples/ small test models/textures.
- bt*.raw.bin real BTL4OPT arena wire captures (kept for offline renderer
testing/regression against OUR game).
.gitignore keeps the multi-hundred-MB demo capture dumps + debug logs +
regeneratable viewer bundles out of history (they stay on disk).
Phase 0 of the integration is validated: their board decodes our bt8 capture
with zero errors (3748 nodes, 507 instances, 4 mechs) and renders our arena
(terrain/dome/sky, correct Division DAC gamma). Plan + status in memory;
integration continues in emulator/RENDERER-COLLAB.md.
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>
Captured a real mission egg delivered from the SheepShaver console to the
DOSBox pod on TCP 1501, no -egg bypass: 8x ReceiveEggFileMessage chunks,
7514 bytes, reassembled byte-perfect (declared notationFileLength=7514).
The egg is the operator's own mission (BattleTech / cavern / night /
freeforall, pilot "cyd", vehicle madcat). This closes the networking
workstream end to end.
- net-tools/decode_egg.py: reassemble an egg from a captured console->pod
TCP payload hex dump (skips interleaved StateQuery polls).
- net-tools/captured_cavern_mission.egg: the delivered egg.
- NET-NOTES.md: milestone writeup + the pod's mission-LOAD crash. The pod
faults loading the mission (page fault, wild ptr 0xFF008B5B, exception
000E at game 00FF:219D, NetNub Munga exit 14) -- a game-side bug in the
mission-load path, separate from networking. Leading suspect: RIO was
disabled for this run and the player vehicle/control setup derefs null.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The emulated SheepShaver console and DOSBox pod now exchange the live
console protocol on TCP 1501. Required chain: rebuild the DOSBox pcap
backend + launch with C:\Windows\System32\Npcap on PATH (npcap DLL
location); a two-TAP Windows bridge (single shared TAP fails); TAP2
MediaStatus=Always-Connected; and binding the pod's pcap to the BRIDGE
MINIPORT (not a member TAP -- member-injected frames aren't re-forwarded),
via realnic=DB5521D (letter-leading GUID fragment; leading-digit is parsed
as an interface index). Captured + decoded the real protocol values
(StateResponse clientID=5/flags=1/host=0/state=1/app=1=BattleTech), which
corrects the earlier synthetic guesses. Full writeup in NET-NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Listens on TCP 1501 as a stand-in pod, logs the console's egg-delivery
bytes, and auto-decodes each NetworkPacket in both byte orders to pin the
framing + PPC/x86 endianness (the two open caveats). Works whether the
console reaches it via a slirp gateway redirect or a bridged segment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records that Router mode is NAT (won't reach the pod) and the console<->pod
link needs bridged TAP on both ends: console TCP/IP manual 200.0.0.1, pod
DOSBox-X switched from slirp to pcap on the same segment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PEF imports (InterfaceLib/MathLib/ObjectSupportLib only; no AppearanceLib,
no Open Transport libs) plus the resource-fork "MacTCP or OpenTransport"
Gestalt check show it is a classic PPC PowerPlant app: runs System 7.1.2
through Mac OS 9 (practical floor 7.5), classic MacTCP API but happy with
either stack. Recommended SheepShaver target: System 7.5.5/7.6.1 with a
7100/7500/8500 old-world ROM, TCP/IP set to 200.0.0.1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Decoded the console<->pod message layout from the MUNGA framework headers
(the send/recv impl did not survive -- only headers + a test harness):
NetworkPacket = 16B NetworkPacketHeader + a Receiver::Message; the egg is
delivered as ReceiveEggFileMessage chunks (seq/totalLen/thisLen +
notationData[1000]), a full egg packet = 1040B (matches the VPX nb<=1040
cap). Console splits the mission notation file into <=1000B chunks; pod
reassembles + ACKs. Two bytes-on-the-wire details (stream framing +
PPC/x86 endianness) flagged for the first live capture. Full byte tables
in NET-NOTES.md.
Also moves the extracted console software (Console 4.10 PPC app + per-venue
Console.ini + fonts/logs, resource forks preserved) into
410console/4_10-console-extracted/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Console 4.10 (PowerPC PEF/CodeWarrior) + Console.ini extracted from the
.sit. Console.ini answers the open protocol question: the console<->pod
TCP port is 1501. Full cockpit endpoint roster recorded; our emulated pod
200.0.0.113 is the cockpit Puck. The real 6100 does not boot but is no
longer needed -- run Console 4.10 under SheepShaver. Only remaining
protocol unknown is the on-stream message framing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ops console is a Power Macintosh 6100/66 (PowerPC) -> SheepShaver, with
the caveat that the 6100 ROM is incompatible (use a 7100/7500/8500 old-world
ROM; PPC apps are ROM-agnostic). Plan to leverage the real 6100: image its
drive for a faithful SheepShaver console and/or bridge it live to the
pcap-pod to capture the real egg protocol.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NE2000 + Novell ODI chain (lsl/NE2000 MLID/odipkt) works against the
emulated card (odipkt at SINT 0x60, Ethernet_II). netnub -f btl4opt spawns
the game as btl4opt
The pods do TCP/IP over Ethernet (WATTCP + NetNub real-mode server;
ops console is master, pushes mission eggs to pods over TCP). Pod is a
static host on an isolated 200.0.0.0/24 LAN (200.0.0.113, console
200.0.0.1); -egg is the dev bypass of this path. Fork already has NE2000
+ pcap/slirp backends; since the emulated card is NE2000 not PCI Lance we
skip the Novell ODI chain and load a Crynwr NE2000 packet driver directly.
NET-NOTES.md has the full architecture + milestone plan (headline goal:
host-side stand-in console pushes an egg over the wire, no -egg bypass).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
make_patch.py applies the two-site fix to RIOv4_2.bin (asserting original
bytes first) -> RIOv4_2_patched.bin (23 bytes changed). Re-disassembling
and diffing confirms the change is confined to $D9DD, $DA21-$DA2E, and the
$DFF0 stub with no downstream desync. Clears the reply-in-progress latch
$2521 on every teardown so a stress collision no longer leaves the board
mute to analog. Flash directly to the DIP-28 W27C512; static verification
only -- dynamic RIO_TAP mash test pending the burned chip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68HC11 recursive-descent disassembler (disasm_6811.py, follows the
pointer-based RX/TX state dispatch) + full disassembly + analysis doc.
Root cause of the stress wedge: an orphaned reply-in-progress latch
($2521). It gates every analog request ($D758); it is set when a reply is
generated ($D84C) but the 4-retry give-up path ($D9DD -> $DA2F) tears down
reply state without clearing it, and the success teardown ($DA00) clears
it only conditionally on $2522. Once leaked, all analog requests are
dropped -> board mute, while RX/event stays alive; only the game-start
host-reset command ($C686) clears it -- matching the field button-resync
ritual exactly. Proposed minimal in-place fix (clear $2521 on every
teardown) documented with byte patches and a hardware validation plan;
untested pending a spare EPROM. RIOv4_2-ANALYSIS.md has the details.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RIOv4_2.bin dumped from our own board EPROM (64KB, code $C000-$FFFF).
Vector table confirms 68HC11: RESET->$C000, SCI serial interrupt->$D630 =
the protocol state machine entry for the planned disassembly. Board patch
plan steps 1-2 done (RIO-NOTES.md updated); next: disassemble from the
SCI handler, find the reply-path wedge (button/event path survives it),
patch, burn to a fresh EPROM, preserve the original.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Un-ignored: the dev drive is the ground truth the restoration and
emulator work constantly reference (DPL3/LIBDPL + VRENDER i860 renderer
source, BT/RP live+dev game trees, VGL_LABS pod boot, scene/audio
content). Kept in-repo for the pod-owner community.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Un-ignored: this is the working copy the emulator actually boots (patched
BTL4OPT.EXE v4 lineage + .orig/.nop14/.pre_idle/.pre_limit backups,
TESTARN.EGG arena1 test egg, REL410 BT+RP trees, VGL_LABS pod boot bats,
VWETEST factory test suites, SB16 Creative utilities). The pristine
untouched image remains ALPHA_1.zip.
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>
highest,highest (HIGH_PRIORITY_CLASS) starved the host desktop; with the
v3 idle-window and v4 retry patches a rare dropout self-recovers, so
ABOVE_NORMAL both ways is the better trade.
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>
410consoleArchive.sit is the StuffIt archive of the 4.10 operations
console that ran the show floor for both BattleTech and Red Planet on a
classic Mac. Preserved as-is; extraction/porting comes later.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ALPHA_1.zip is the archived resource behind the git-ignored ALPHA_1/
working directory (the patched production hard-drive image the
emulator mounts). Also ignore Python __pycache__/ dirs.
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>
The garbled top-strip render was a paging bug, not the video mode. The
pageFcnPtr in L4GAUGE.INI (C000:2616) is a red herring -- the driver's direct-
pointer path is commented out ("THIS DOES NOT WORK"); it pages via portable
VESA int 10h/4F05, which our S3 handles.
Real cause: window granularity. SVGASetPage passes the bank in granularity
units; the blit advances by pageDelta = pageSize/granularity per 64KB window
crossed. The INI's granularityInKB=4 is the Cirrus CL-GD5434's 4KB window
granularity (the pod's actual video card; the "STB Horizon+" board was
Cirrus-based). Our emulated S3 uses 64KB banks, so pageDelta=16 -> every bank
landed 16x too far -> the smear.
Fix (config only): L4GAUGE.INI [640x480x16] granularityInKB=64 (=sizeInKB, so
pageDelta=1, matching the S3's 64KB banks). Full framebuffer now pages
correctly -- 315+ frames, VDB splitter stays ON, display renders as expected
(blank between missions; user confirmed "that is the screen I expect").
ALPHA_1 is git-ignored so the INI edit isn't committed; reproduction + backup
(L4GAUGE.INI.orig) documented in GAUGE-NOTES.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enabling gauges crashed (null-this, illegal 0x478). Root cause was NOT video:
the MUNGA main heap (fixed 6MB default) exhausted loading gauge images. The
heap size comes from getenv("HEAPSIZE") (BTL4OPT.EXE @0x401076; default
0x600000); the pod's PARAMETR.BAT sets it (:POD=15MB, :REVIEW/:LOOP=32MB) but
our minimal setenv launch never did. memsize is irrelevant (it's the game's
own heap, not DOS memory -- verified memsize=127 still gave 6MB). Pods have
32MB RAM so 15MB fits.
Fix (no binary patch): set HEAPSIZE=15000000 + L4GAUGE=640x480x16 in
gauge.conf. Game now runs sustained with gauges on (500+ frames); the VESA
640x480x16 mode switches fine on our emulated S3 and the cockpit instrument
panels draw (DISPLAY/PROGRAM/ENG DATA/TRIGGER CONFIG...).
patch_btl4opt.py also gained Verify_Failed (DEBUG-build assertion) neutral-
ization -- release-equivalent robustness, separate from the heap fix.
Known-open (GAUGE-NOTES.md): the framebuffer renders only a top strip,
garbled -- the game bank-switches via a hardcoded far pointer in L4GAUGE.INI
(C000:2616, the STB Horizon+ card's VESA WinFuncPtr) that isn't valid on our
emulated S3, so only the first bank(s) page in. Fixing bank-switching (or an
LFB mode) is next; then the full buffer can be split into the six 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>
The RIO drops comms if the analog-reply ACK is late by more than a few ms.
Empirically a slower CPU failed sooner, so the latency was dominated by how
fast the game processes the RIO packet and ACKs -- core=dynamic (recompiler)
+ cycles=max keeps the board in sync (user-confirmed). game_rio.conf passes
the real RIO through serial1=directserial realport:COM1.
With real control input the sim advances and then faults (null-ptr deref at
BTL4OPT.EXE CODE+0x123B) -- downstream of the minimal test.egg not setting the
arena objectpath, so VIDEO/GEO/ARENA/thr_tshd.bgf isn't found. That is content
config, separate from the now-working VPX protocol / renderer / RIO. Details
in RIO-NOTES.md.
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>
The production image's VWETEST diagnostic suite provides a clean,
game-independent render harness. FLYK (VGLTEST, 32rtm build, newer
token-based sync) + clear.scn drives the ENTIRE VPX protocol through
the emulated board with zero errors: boot, iserver handshake, i860
download, token sync, scene build, draw_scene, frame-ack, clean exit
('Exiting rendering'). This validates the VPX emulation for an
arbitrary DPL renderer, not just the game.
Notes: the CYCLE flyk is a DOS/4GW build using the OLDER DPL3-style
velocirender_sync (action-check) and needs separate handling; the
VPX/DBE0151 iserver board test + reference TGAs are a future
golden-image validation avenue.
Adds RENDER-HARNESS.md and harness configs (flyk/cycle/alpha1).
Next (Phase 3): flyk DIVRGB.SCN color bars -> decode FIFO geometry
(same DIV-BIZ2 formats as restoration/divformats.py) -> OpenGL.
Co-Authored-By: Claude Fable 5 <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>