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>
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>
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>
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>