Files
TeslaRel410/emulator/vpx-device/README.md
T
CydandClaude Fable 5 f2687a3146 BT410 5.3.72: the fault caught in the act; the inside view gets its cockpit
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>
2026-07-29 18:09:22 -05:00

16 KiB
Raw Blame History

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 base 0x150), 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_draw rings 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 from serialrio's RIO_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_wndproc hit-tests a click against the bezel and presses that RIO address (thread-safe via serialrio's queued RIO_HostButton); head windows are WS_EX_NOACTIVATE so a click never pulls foreground off DOSBox (which would demote the emu thread and stop the SDL pad poll). No-op when no serial=rio port is present.
  • serialplasma.cpp / serialplasma.hserial<n>=plasma backend (2026-07-24): the pod's 128×32 dot-matrix plasma readout on COM2, emulated in-fork like serial=rio (no vPLASMA process, no pipe). The game (L4PLASMA.CPP) renders into its own 1bpp buffer and streams changed rows with a single ESC P graphics command — receive-only, no replies — so the device is just that parser filling a 128×32 framebuffer (transcribed from vPLASMA PlasmaProtocol + VPlasmaDevice, graphics path; the factory text-mode/fonts aren't used by the game and aren't rendered, but their escape operands are still consumed so the parser can't desync). vpxlog draws it as an amber-on-black window in the explode layout (seam PLASMA_GetFrame), lazily created once the port exists, parked centered under the lower-left MFD (VPX_PLASMA="x,y,w,h" overrides). Real pods keep directserial realport:COM2; the vPLASMA app keeps namedpipe 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) via VWE_AWE_ROM or SoundFont uploads are refused (banks declare irom=1MGM). See emulator/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.hserial<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 directserial rxpollus/rxburst/rxdelay low-latency knobs. Smoke-tested end-to-end 2026-07-12.
  • serialrio.cpp / serialrio.hserial<n>=rio backend (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\vrio VRio.Core: VRioDevice, the Protocol codec, 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 (same rxpollus/rxburst knobs), draining a queue the board itself fills. Keyboard field (B1): sdlmain offers every SDL key to RIO_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, F1F12 = 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_HostFocusLost in GFX_LosingFocus). A bindings file in vRIO's grammar REPLACES the default profile (conf bindings:<path>, else env VWE_RIO_BINDINGS; key-axis modes deflect|slew|rate|set, sign may be a separate token; vRIO's .NET key names are aliased so a vRIO bindings.txt ports over). Conf: serial1=rio [pad:<n>] [inverty] [bindings:<path>] [togglekey:<key>] [rxpollus:<us>] [rxburst:<n>]. Additive — real pods keep directserial realport:COM1; vRIO-over-pipe keeps namedpipe 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 over ISteamNetworkingMessages, so a private group can fly missions over the internet. Shared VERBATIM by two consumers — the fork's backend=steam and the host's standalone vwe-steam-gw.exe steam↔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/GetProcAddress on steam_api64.dll), exactly as the pcap backend binds WPCAP.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 in emulator/steam/SESSION-CONTRACT.md.
  • ethernet_steam.cpp / ethernet_steam.h — the [ne2000] backend=steam EthernetConnection that plugs steamlink into the emulated NIC. Pump() runs at the top of GetPackets(), which NE2000_Poller calls off a TIMER_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 (accept dst==self or broadcast, drop other multicast) so backend=steam and backend=pcap are indistinguishable from the guest's side, and macaddr= must be a stable literal — never random — because the MAC is what peers route to. Same power-of-2-throttled STEAM TX/RX #n log 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.

  1. 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/
    
  2. Add them to the hardware build — in src/src/hardware/Makefile.am, append vpxlog.cpp emu8k.cpp vweawe.cpp to libhardware_a_SOURCES (we inserted them after glide.cpp; if you edit the generated Makefile/Makefile.in by hand instead of re-running automake, also mirror the $(OBJEXT) list, the .Po depfile list, and the depfile include markers — grep for how vpxlog appears and copy the pattern).
  3. Call the inits — in src/src/gui/sdlmain.cpp, declare void VPXLOG_Init(); and void VWEAWE_Init(); next to the other *_Init() prototypes; call VPXLOG_Init(); right after GLIDE_Init(); and VWEAWE_Init(); right after SBLASTER_Init(); (it needs the mixer initialized). 3b. The namedpipe serial backend:
    cp emulator/vpx-device/serialnamedpipe.cpp emulator/src/src/hardware/serialport/
    cp emulator/vpx-device/serialnamedpipe.h   emulator/src/src/hardware/serialport/
    
    plus four small stock edits:
    • src/src/hardware/serialport/Makefile.am: append serialnamedpipe.cpp serialnamedpipe.h to libserial_a_SOURCES (same generated-Makefile caveat as step 2).
    • src/include/serialport.h: add SERIAL_TYPE_NAMED_PIPE to SerialTypesE under #if defined(WIN32) (before SERIAL_TYPE_DIRECT_SERIAL).
    • src/src/hardware/serialport/serialport.cpp: #include "serialnamedpipe.h"; add the type=="namedpipe" case to BOTH dispatch switches (SERIALPORTS ctor + the SERIAL command) and "namedpipe" to the serialTypes[] string table — grep for how "file" appears and copy the pattern.
    • src/src/dosbox.cpp: add "namedpipe" to the serials[] allowed-values list (without this the config parser silently falls back to dummy). 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):
    cp emulator/vpx-device/serialrio.cpp emulator/src/src/hardware/serialport/
    cp emulator/vpx-device/serialrio.h   emulator/src/src/hardware/serialport/
    
    plus the same four stock edits as the namedpipe backend:
    • src/src/hardware/serialport/Makefile.am: append serialrio.cpp serialrio.h to libserial_a_SOURCES (same generated- Makefile caveat as step 2).
    • src/include/serialport.h: add SERIAL_TYPE_RIO to SerialTypesE (UNguarded — SDL is cross-platform, unlike the WIN32-only namedpipe; placed right after SERIAL_TYPE_MOUSE).
    • src/src/hardware/serialport/serialport.cpp: #include "serialrio.h"; add the type=="rio" case to BOTH dispatch switches and "rio" to the serialTypes[] string table (copy the namedpipe pattern; keep it unguarded).
    • src/src/dosbox.cpp: add "rio" to the serials[] allowed-values list.
    • src/src/gui/sdlmain.cpp (the B1 keyboard field, three small edits): (a) declare the hooks above GFX_LosingFocus: bool RIO_HostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods); void RIO_HostFocusLost(void); (b) call RIO_HostFocusLost(); inside GFX_LosingFocus right after MAPPER_LosingFocus(); (c) in GFX_Events' SDL2 case SDL_KEYDOWN: case SDL_KEYUP: block, right after the SCREEN_GAMELINK early-out, offer the event to RIO_HostKeyEvent((int)event.key.keysym.scancode, event.type == SDL_KEYDOWN, event.key.repeat != 0, event.key.keysym.mod) and break; when it returns true (consumed keys must never reach the mapper). 3d. The in-fork plasma serial backend (same seam pattern as RIO):
    cp emulator/vpx-device/serialplasma.cpp emulator/src/src/hardware/serialport/
    cp emulator/vpx-device/serialplasma.h   emulator/src/src/hardware/serialport/
    
    plus the four stock edits (copy the rio pattern exactly):
    • src/src/hardware/serialport/Makefile.am: append serialplasma.cpp serialplasma.h to libserial_a_SOURCES.
    • src/include/serialport.h: add SERIAL_TYPE_PLASMA right after SERIAL_TYPE_RIO (unguarded).
    • src/src/hardware/serialport/serialport.cpp: #include "serialplasma.h"; add the type=="plasma" case to BOTH dispatch switches and "plasma" to serialTypes[].
    • src/src/dosbox.cpp: add "plasma" to the serials[] allowed-values list. (The plasma WINDOW is drawn by vpxlog.cpp via the PLASMA_GetFrame extern — already in this dir's vpxlog.cpp, so it lands with step 1.) 3e. The Steam ethernet backend ([ne2000] backend=steam — internet play; see ../STEAM-NOTES.md and ../steam/SESSION-CONTRACT.md). These are misc/ files, not hardware/: they sit beside the pcap backend they are modelled on.
    cp 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/
    
    plus three stock edits (grep for how ethernet_pcap appears and copy the pattern):
    • src/src/misc/Makefile.am: append steamlink.cpp ethernet_steam.cpp to libmisc_a_SOURCES (we put them after ethernet_nothing.cpp). The hand-mirrored generated-Makefile caveat from step 2 does not apply here: build-mingw-sdl2 re-runs ./autogen.sh before configuring, so Makefile.in/Makefile are regenerated from the .am.
    • src/src/misc/ethernet.cpp: #include "ethernet_steam.h"; add the backend == "steam" case to OpenEthernetConnection() next to the pcap one — new SteamEthernetConnection, settings from control->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 the auto ladder: steam is only ever selected explicitly, since an internet session is never how a pod boots by default. Add the else if (backend == "steam") arm to the failure LOG_MSG chain too, otherwise a steam backend that fails to initialize falls out as "Unknown ethernet backend".
    • src/src/dosbox.cpp: add "steam" to the backendopts[] allowed-values list (without this the config parser refuses the value and silently falls back to auto — same trap as the serials[] list in 3b), and register the settings section next to "ethernet, pcap": secprop = control->AddSection_prop("ethernet, steam", &Null_Init, true); with string props peers, lobby, session (all defaulting to env) and int channel (0). Those names and defaults are frozen in SESSION-CONTRACT.md §2 — the fork and the gateway both parse them. No new .conf.tmpl is needed: the backend is selected per session with -set "ne2000 backend=steam" on the DOSBox-X command line (pod-launch --steam appends it), so the existing net_loop_rio.conf / net_rp_rio.conf are reused as-is. The Steamworks SDK is OPTIONAL. steamlink.cpp compiles by default against hand-declared ABI structs and binds steam_api64.dll at 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 at emulator/steamworks/ (git-ignored, same pattern as emulator/src/) and build with -DVWE_STEAM_USE_SDK_HEADERS plus the SDK's public/ 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=steam just fails Init with a readable message when steam_api64.dll is missing or Steam isn't running, exactly the way a missing Npcap fails pcap.
  4. Build:
    cd emulator/src
    ./build-mingw-sdl2 --enable-debug=heavy
    
    Output: src/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.