VPX_PF_WATCH (fork, cpu/paging.cpp): on a guest page fault at the watched linear address, dump guest registers, the last 64 serial RX deliveries with guest cs:eip at each, the code bytes at the faulting EIP and the stack top. serialnamedpipe's doReceive feeds the ring. Armed in podrun.sh and launch_pod.ps1. The first catch decoded the residual fault completely: CS:EIP 00FF:000066D4 in the DPMI host, EBX = F000CA60 -- an IVT entry read as a dword, segment F000 offset CA60, a BIOS default interrupt handler -- and the faulting access is [EBX+0x3004], whose 0x80000000 segment-base wrap gives exactly cr2 7000FA64. The host probes a word 0x3004 bytes past a real-mode vector value treated as a flat pointer: harmless for its own low-memory handlers, a fault for BIOS F000:xxxx defaults. The serial ring shows a steady 1-byte/1-3ms vRIO stream with nothing special at the fault -- the stream determines which vectors get walked, not the crash itself. The 0x3004 appears nowhere in DPMI32VM.OVL or 32RTM.EXE as an immediate, so the probe now also dumps code bytes at EIP; faulthunt.sh loops runs until the next catch. Shipped baseline streak: 4/4 clean -- consistent with exposure, not yet discriminating. The inside view now loads the COCKPIT skeleton: the fleet-wide X-variant naming convention (MAD->MAX etc., all 64 skeletons present) selects the same 25-joint chain with a single object -- max_cop.bgf, the MAX_COP canopy shell with the PUNCH-texel windows from the capture forensics. The donor names the same mechanism from the decomp side (inside = SkeletonType_A with '_cop' selection). Fallback to the body skeleton when no X file exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
16 KiB
16 KiB
VPX + AWE32 devices — DOSBox-X integration
Our original source for the emulated pod hardware. Kept here under
version control because the DOSBox-X source tree itself
(emulator/src/, ~490 MB) is git-ignored.
vpxlog.cpp— the Division VPX link adapter (INMOS C012 at I/O base0x150), grown through Phase 3: iserver handshake responder, FIFO scene decode, GL Division renderer (gallery shading model), VDB video-head splitter windows. RIO lamp/button bezel (B2, 2026-07-24, explode layout):pal_drawrings each mono-MFD head with its 8 RIO buttons (red, 4 top / 4 bottom, tucked under a 640x500 display so a 10px lip shows) and the radar with the amber Secondary/Screen side columns + a centered bottom indicator strip, lit fromserialrio'sRIO_GetPanelState(off/dim/bright + white-hot press; flashing lamps blink at the RioLampState rate). No labels -- the display shows each button's function. Click-to-press:rt_wndprochit-tests a click against the bezel and presses that RIO address (thread-safe via serialrio's queuedRIO_HostButton); head windows areWS_EX_NOACTIVATEso a click never pulls foreground off DOSBox (which would demote the emu thread and stop the SDL pad poll). No-op when noserial=rioport is present.serialplasma.cpp/serialplasma.h—serial<n>=plasmabackend (2026-07-24): the pod's 128×32 dot-matrix plasma readout on COM2, emulated in-fork likeserial=rio(no vPLASMA process, no pipe). The game (L4PLASMA.CPP) renders into its own 1bpp buffer and streams changed rows with a singleESC Pgraphics command — receive-only, no replies — so the device is just that parser filling a 128×32 framebuffer (transcribed from vPLASMAPlasmaProtocol+VPlasmaDevice, graphics path; the factory text-mode/fonts aren't used by the game and aren't rendered, but their escape operands are still consumed so the parser can't desync).vpxlogdraws it as an amber-on-black window in the explode layout (seamPLASMA_GetFrame), lazily created once the port exists, parked centered under the lower-left MFD (VPX_PLASMA="x,y,w,h"overrides). Real pods keepdirectserial realport:COM2; the vPLASMA app keepsnamedpipe pipe:vplasma.vweawe.cpp— the dual-AWE32 sound device: two vendored EMU8000 cores at 0x620/0x640 (+0x400/+0x800 triplets), rear-card DSP/mixer stub at 0x240, autonomous render thread with direct winmm output. Needs the GM ROM (emulator/roms/awe32.raw) viaVWE_AWE_ROMor SoundFont uploads are refused (banks declareirom=1MGM). Seeemulator/SOUND-NOTES.md.emu8k.cpp/emu8k.h/emu8k_shim.h— EMU8000 wavetable core vendored from 86Box (GPL-2.0-or-later, same license as DOSBox-X), with a minimal shim; local changes are listed in the emu8k.cpp header.serialnamedpipe.cpp/serialnamedpipe.h—serial<n>=namedpipe pipe:<name>backend (2026-07-12): serial-over-named-pipe for vRIO/vPLASMA, replacing com0com. DOSBox is the pipe CLIENT with 500ms background retry; typed frames carry data (0x00 len bytes) and DTR/RTS line state (0x01 bits); the full wire contract is in the header comment (pinned with the vRIO session). Supports the directserialrxpollus/rxburst/rxdelaylow-latency knobs. Smoke-tested end-to-end 2026-07-12.serialrio.cpp/serialrio.h—serial<n>=riobackend (2026-07-21, keyboard field 2026-07-22): the cockpit RIO board emulated in-fork. It speaks the device side of the RIO 9600-8N1 protocol straight to the game's UART (no external process, no pipe) and sources pilot input from an SDL game controller + the host keyboard inside the emulator. Because the game↔board round trip is in-process at the emulated UART's own cadence — like the real ISA UART on a pod — the serial round-trip dropouts (livelock,TXMAXIDLE,rxburst, 15s retry) can't occur. The protocol state machine + input mapping are transcribed from the validated vRIO app (C:\VWE\vrioVRio.Core:VRioDevice, theProtocolcodec,Input/InputRouter+BindingProfileFormat); the transport, wire pacer, panel UI and thread locks are dropped (single emulator thread). RX byte pacing reuses directserial's state machine (samerxpollus/rxburstknobs), draining a queue the board itself fills. Keyboard field (B1): sdlmain offers every SDL key toRIO_HostKeyEvent()first; while capture is ON (default) bound keys press RIO addresses or drive axes and are swallowed; unbound keys and Ctrl/Alt/Gui chords pass through (host hotkeys keep working; modifier keys BOUND as inputs are exempt from the chord rule). Default field: number/QWERTY rows = upper MFD bank, home/bottom rows = lower bank, F1–F12 = Secondary/Screen, arrows+Space = hat+main, and keyboard FLIGHT controls (operator 2026-07-22): numpad 8/2/4/6 = stick, 7/9 = pedals, LShift/LCtrl = throttle slew ±0.7/s, numpad-5 = throttle zero; the internal keypad is unbound (mission-review plumbing only). PAUSE or SCROLL LOCK toggles capture panel↔DOS (toggling off releases everything; needed at the netnub loop / DOS prompts);togglekey:<sdl-name>adds a third toggle key for keyboards lacking both. Focus loss releases held keys (RIO_HostFocusLostinGFX_LosingFocus). A bindings file in vRIO's grammar REPLACES the default profile (confbindings:<path>, else envVWE_RIO_BINDINGS; key-axis modesdeflect|slew|rate|set, sign may be a separate token; vRIO's .NET key names are aliased so a vRIObindings.txtports over). Conf:serial1=rio [pad:<n>] [inverty] [bindings:<path>] [togglekey:<key>] [rxpollus:<us>] [rxburst:<n>]. Additive — real pods keepdirectserial realport:COM1; vRIO-over-pipe keepsnamedpipe pipe:vrio. Confs:net_loop_rio.conf/net_rp_rio.conf;pod-launch --rio. B0 (pad) LIVE-CONFIRMED 2026-07-22 from the repacked dist; keyboard-field live test pending.steamlink.cpp/steamlink.h— the Steam transport core (2026-07-24): guest Ethernet frames overISteamNetworkingMessages, so a private group can fly missions over the internet. Shared VERBATIM by two consumers — the fork'sbackend=steamand the host's standalonevwe-steam-gw.exesteam↔pcap gateway — which is why it is a free-function namespace and not a class. L2, not L3, is the whole point: the 1995 mesh dials peers by raw IP on one flat subnet and ARPs for them, and NAT/hole-punching has nowhere to put an ARP. Steamworks is bound at RUNTIME (LoadLibrary/GetProcAddressonsteam_api64.dll), exactly as the pcap backend bindsWPCAP.DLL, so no SDK is needed to build or link. Load-bearing: everything ships Reliable + NoNagle (WATTCP's DOS-era retransmit timers are multi-second, so an outer retransmit costs one RTT where an inner one costs seconds — the long rationale is in the header, read it before "optimizing" to unreliable), and the API is not thread-safe by design (one thread, pumped from the NE2000 tick). Wire format, session file and env handoff are frozen inemulator/steam/SESSION-CONTRACT.md.ethernet_steam.cpp/ethernet_steam.h— the[ne2000] backend=steamEthernetConnectionthat plugssteamlinkinto the emulated NIC.Pump()runs at the top ofGetPackets(), whichNE2000_Pollercalls off aTIMER_AddTickHandler— i.e. once per emulated millisecond — so that tick is the Steam callback pump and there is no new thread or timer anywhere. RX policy deliberately mirrors the pcap backend's BPF (acceptdst==selfor broadcast, drop other multicast) sobackend=steamandbackend=pcapare indistinguishable from the guest's side, andmacaddr=must be a stable literal — neverrandom— because the MAC is what peers route to. Same power-of-2-throttledSTEAM TX/RX #nlog lines as pcap. Operator runbook:emulator/STEAM-NOTES.md.
Applying to a DOSBox-X source checkout
Tested against DOSBox-X v2026.06.02, MSYS2 mingw64.
- Copy the devices in:
cp emulator/vpx-device/vpxlog.cpp emulator/src/src/hardware/ cp emulator/vpx-device/emu8k.cpp emulator/src/src/hardware/ cp emulator/vpx-device/emu8k.h emulator/src/src/hardware/ cp emulator/vpx-device/emu8k_shim.h emulator/src/src/hardware/ cp emulator/vpx-device/vweawe.cpp emulator/src/src/hardware/ - Add them to the hardware build — in
src/src/hardware/Makefile.am, appendvpxlog.cpp emu8k.cpp vweawe.cpptolibhardware_a_SOURCES(we inserted them afterglide.cpp; if you edit the generatedMakefile/Makefile.inby hand instead of re-running automake, also mirror the$(OBJEXT)list, the.Podepfile list, and the depfile include markers — grep for howvpxlogappears and copy the pattern). - Call the inits — in
src/src/gui/sdlmain.cpp, declarevoid VPXLOG_Init();andvoid VWEAWE_Init();next to the other*_Init()prototypes; callVPXLOG_Init();right afterGLIDE_Init();andVWEAWE_Init();right afterSBLASTER_Init();(it needs the mixer initialized). 3b. The namedpipe serial backend:plus four small stock edits:cp emulator/vpx-device/serialnamedpipe.cpp emulator/src/src/hardware/serialport/ cp emulator/vpx-device/serialnamedpipe.h emulator/src/src/hardware/serialport/src/src/hardware/serialport/Makefile.am: appendserialnamedpipe.cpp serialnamedpipe.htolibserial_a_SOURCES(same generated-Makefile caveat as step 2).src/include/serialport.h: addSERIAL_TYPE_NAMED_PIPEtoSerialTypesEunder#if defined(WIN32)(beforeSERIAL_TYPE_DIRECT_SERIAL).src/src/hardware/serialport/serialport.cpp:#include "serialnamedpipe.h"; add thetype=="namedpipe"case to BOTH dispatch switches (SERIALPORTS ctor + the SERIAL command) and"namedpipe"to theserialTypes[]string table — grep for how"file"appears and copy the pattern.src/src/dosbox.cpp: add"namedpipe"to theserials[]allowed-values list (without this the config parser silently falls back todummy). 3c. The in-fork RIO serial backend (needs SDL2 — the whole-program link already pulls SDL2 in, and the vendored SDL2 include dir is on the global compile flags, so no per-subdir cflag edits):
plus the same four stock edits as the namedpipe backend:cp emulator/vpx-device/serialrio.cpp emulator/src/src/hardware/serialport/ cp emulator/vpx-device/serialrio.h emulator/src/src/hardware/serialport/src/src/hardware/serialport/Makefile.am: appendserialrio.cpp serialrio.htolibserial_a_SOURCES(same generated- Makefile caveat as step 2).src/include/serialport.h: addSERIAL_TYPE_RIOtoSerialTypesE(UNguarded — SDL is cross-platform, unlike the WIN32-only namedpipe; placed right afterSERIAL_TYPE_MOUSE).src/src/hardware/serialport/serialport.cpp:#include "serialrio.h"; add thetype=="rio"case to BOTH dispatch switches and"rio"to theserialTypes[]string table (copy thenamedpipepattern; keep it unguarded).src/src/dosbox.cpp: add"rio"to theserials[]allowed-values list.src/src/gui/sdlmain.cpp(the B1 keyboard field, three small edits): (a) declare the hooks aboveGFX_LosingFocus:bool RIO_HostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods); void RIO_HostFocusLost(void);(b) callRIO_HostFocusLost();insideGFX_LosingFocusright afterMAPPER_LosingFocus();(c) in GFX_Events' SDL2case SDL_KEYDOWN: case SDL_KEYUP:block, right after the SCREEN_GAMELINK early-out, offer the event toRIO_HostKeyEvent((int)event.key.keysym.scancode, event.type == SDL_KEYDOWN, event.key.repeat != 0, event.key.keysym.mod)andbreak;when it returns true (consumed keys must never reach the mapper). 3d. The in-fork plasma serial backend (same seam pattern as RIO):
plus the four stock edits (copy thecp emulator/vpx-device/serialplasma.cpp emulator/src/src/hardware/serialport/ cp emulator/vpx-device/serialplasma.h emulator/src/src/hardware/serialport/riopattern exactly):src/src/hardware/serialport/Makefile.am: appendserialplasma.cpp serialplasma.htolibserial_a_SOURCES.src/include/serialport.h: addSERIAL_TYPE_PLASMAright afterSERIAL_TYPE_RIO(unguarded).src/src/hardware/serialport/serialport.cpp:#include "serialplasma.h"; add thetype=="plasma"case to BOTH dispatch switches and"plasma"toserialTypes[].src/src/dosbox.cpp: add"plasma"to theserials[]allowed-values list. (The plasma WINDOW is drawn byvpxlog.cppvia thePLASMA_GetFrameextern — already in this dir'svpxlog.cpp, so it lands with step 1.) 3e. The Steam ethernet backend ([ne2000] backend=steam— internet play; see../STEAM-NOTES.mdand../steam/SESSION-CONTRACT.md). These aremisc/files, nothardware/: they sit beside the pcap backend they are modelled on.
plus three stock edits (grep for howcp emulator/vpx-device/steamlink.cpp emulator/src/src/misc/ cp emulator/vpx-device/steamlink.h emulator/src/src/misc/ cp emulator/vpx-device/ethernet_steam.cpp emulator/src/src/misc/ cp emulator/vpx-device/ethernet_steam.h emulator/src/src/misc/ethernet_pcapappears and copy the pattern):src/src/misc/Makefile.am: appendsteamlink.cpp ethernet_steam.cpptolibmisc_a_SOURCES(we put them afterethernet_nothing.cpp). The hand-mirrored generated-Makefile caveat from step 2 does not apply here:build-mingw-sdl2re-runs./autogen.shbefore configuring, soMakefile.in/Makefileare regenerated from the.am.src/src/misc/ethernet.cpp:#include "ethernet_steam.h"; add thebackend == "steam"case toOpenEthernetConnection()next to the pcap one —new SteamEthernetConnection, settings fromcontrol->GetSection("ethernet, steam")— inside#ifdef WIN32(the transport's DLL loader is Windows-only; keep the guard or a Linux build breaks at link). Deliberately NOT in theautoladder: steam is only ever selected explicitly, since an internet session is never how a pod boots by default. Add theelse if (backend == "steam")arm to the failureLOG_MSGchain too, otherwise a steam backend that fails to initialize falls out as "Unknown ethernet backend".src/src/dosbox.cpp: add"steam"to thebackendopts[]allowed-values list (without this the config parser refuses the value and silently falls back toauto— same trap as theserials[]list in 3b), and register the settings section next to"ethernet, pcap":secprop = control->AddSection_prop("ethernet, steam", &Null_Init, true);with string propspeers,lobby,session(all defaulting toenv) and intchannel(0). Those names and defaults are frozen in SESSION-CONTRACT.md §2 — the fork and the gateway both parse them. No new.conf.tmplis needed: the backend is selected per session with-set "ne2000 backend=steam"on the DOSBox-X command line (pod-launch --steamappends it), so the existingnet_loop_rio.conf/net_rp_rio.confare reused as-is. The Steamworks SDK is OPTIONAL.steamlink.cppcompiles by default against hand-declared ABI structs and bindssteam_api64.dllat runtime, so there is nothing to install to build and nothing to link against. To validate those structs against the real headers, drop the SDK atemulator/steamworks/(git-ignored, same pattern asemulator/src/) and build with-DVWE_STEAM_USE_SDK_HEADERSplus the SDK'spublic/dir on the include path — worth doing once after an SDK bump, then go back to the plain build. A build made without the SDK still runs:backend=steamjust fails Init with a readable message whensteam_api64.dllis missing or Steam isn't running, exactly the way a missing Npcap fails pcap.
- Build:
Output:
cd emulator/src ./build-mingw-sdl2 --enable-debug=heavysrc/src/dosbox-x.exe.
Running
set VPXLOG=C:\VWE\TeslaRel410\emulator\vpxlog.txt
emulator\src\src\dosbox-x.exe -conf emulator\capture.conf
python emulator\analyze_capture.py
With VPXLOG unset the device is inert and the build behaves like stock
DOSBox-X.