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

268 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.h`** — `serial<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.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 directserial `rxpollus`/`rxburst`/`rxdelay`
low-latency knobs. Smoke-tested end-to-end 2026-07-12.
- **`serialrio.cpp` / `serialrio.h`** — `serial<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.