Files
CydandClaude Fable 5 f7ebde2e0b Add VPX emulation implementation plan for Windows 10 cockpits
emulator/PLAN.md: run the unported DOS Rel 4.10 games in the
surviving cockpits via a DOSBox-X fork with an HLE device
impersonating the Division VPX board at its C012/B004 link
interface (I/O port 0x150 per BTLIVE SETENV.BAT), rendering the
frame stream in OpenGL. Cockpit RIO (COM1) and plasma (COM2) pass
through to the game's original serial drivers (L4RIO.CPP).
Eight phases with acceptance criteria, evidence table, protocol
appendices, risks, and test strategy. Gauge displays deferred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 19:09:33 -05:00

16 KiB
Raw Permalink Blame History

Tesla Rel 4.10 on Windows 10 — VPX Emulation Implementation Plan

Goal

Run the unported DOS versions of Tesla:BattleTech and Tesla:Red Planet (release 4.10) inside the surviving cockpits, whose computers now run Windows 10. The games are complete on disk (sda4/BTLIVE, sda4/RPLIVE — executables, resources, content), but they render through Division Ltd.'s VPX board, hardware that no longer exists in the pods. Everything else the games need still exists or is emulatable.

The approach: a customized DOS emulator whose main addition is a high-level-emulation (HLE) device that impersonates the VPX board at its host interface and renders with modern GPU APIs. The cockpit's real RIO (COM1) and plasma display (COM2) are passed through to the physical serial ports so the game's own drivers operate the actual cockpit hardware.

┌─ Windows 10 cockpit PC ─────────────────────────────────────────────┐
│  ┌─ DOSBox-X (custom fork) ─────────────────────────────────────┐   │
│  │  BTL4 game EXE (DOS, Borland 32-bit, unmodified)             │   │
│  │   ├─ I/O port 0x150..0x153 ──► VPX HLE device ── OpenGL ─────┼─► main display
│  │   ├─ COM1 ── serial passthrough ─────────────────────────────┼─► RIO (cockpit controls)
│  │   ├─ COM2 ── serial passthrough ─────────────────────────────┼─► plasma display
│  │   ├─ SB16 emulation ── host audio ───────────────────────────┼─► cockpit speakers
│  │   ├─ NE2000 emulation ── pcap/slirp ─────────────────────────┼─► pod-to-pod network
│  │   └─ emulated VGA (DOS console; later: gauges) ──► window    │   │
│  └───────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────┘

Why this is feasible — the evidence

Every load-bearing fact below was verified against files in this repository and the Glaze drive dump (sda4/):

Fact Evidence
Host↔board link is an INMOS C012 link adapter, IMS B004-style: polled byte FIFO on 46 I/O ports, no host interrupts, no host DMA sda4/DPL3/LINKIO.C ("simple polled PC / DOS interface, a la IMS B004", setLA(), INPUT_READY/OUTPUT_READY status bits)
The link adapter sits at I/O port 0x150 in the shipped install sda4/BTLIVE/SETENV.BAT: DPLARG=... /device~0x150~ ...
Boot sequence: host loads a transputer monitor, then i860 code segments, over the link DPLARG=/tranny~.\vrendmon.btl~/i860~.\vrnostex.mng~...; sda4/DPL3/VR_COMMS.C (i860_segment, vr_860code_action, vr_860data_action)
Message protocol on the link: ≤1023-byte packets, 32-bit length/route word (sender/target id, iserver flag, broadcast id 0xff) sda4/DPL3/VR_COMMS.C header comment + send_protocol()
Frame sync is polled over the link ("frame ack"), not interrupt-driven sda4/DPL3/VR_COMMS.C (frame ack handling, velocirender_inputstatus)
The board-side renderer's behavior is documented in source sda4/DPL3/VRENDER/ (C + i860 asm, PXPL5SUP/, DMA.TXT tile-loop description, register maps DIVPXMAP.H, DMAENGN.H)
Triangle/command encodings CODE/*/MUNGA_L4/LIBDPL/dpl/vpx/TRICODER.H, VR_PROT.H, DPL_VPX.H
The game natively drives the RIO on COM1 CODE/RP/MUNGA_L4/L4RIO.CPP + L4RIO.HPP (complete driver source); L4CTRL.CPP accepts L4CONTROLS=RIO / RIO:COM1
Plasma display on COM2, optional subsystems sda4/VGL_LABS/BOOTPOD.* (L4PLASMA=com2, L4SOUND, L4GAUGE, L4INTERCOM all optional)
Keyboard fallback for controls exists (dev "office/cart" config) BOOTPOD.*: L4CONTROLS=THRUSTMASTER,KEYBOARD
Complete runnable game installs sda4/BTLIVE/, sda4/RPLIVE/ (EXEs, BTL4.RES, content, BT.BAT/BTGO.BAT/BTNET.BAT launch modes)
Renderer configuration is data-driven from files we have sda4/BTLIVE/BTDPL.INI (fog/lights per arena/time/weather, particle defs, cache lists)
All geometry/material/texture formats already decoded and re-rendered restoration/divformats.py, restoration/vwe-archive.html
Audio: HMI SOS on SB16 sda4/VGL_LABS/AUTOEXEC.POD (SET BLASTER=A220 I5 D1 H5 P330 T6)
Networking: WATTCP/BOOTP over a packet driver CODE/*/MUNGA_L4/NETNUB/

Note the pods ran two sound cards (AWE_FRONT at A220, AWE_REAR at A240 per AUTOEXEC.POD). Phase 6 targets one emulated SB16; rear-channel audio is deferred alongside the gauges.

Platform choice

Fork DOSBox-X (Windows builds, active project, GPL):

  • Built-in serial passthrough to real COM ports (serial1=directserial realport:COM1) — the RIO and plasma work through the game's own drivers with no new code.
  • Built-in SB16, game port, NE2000 (pcap + slirp backends).
  • Runs Borland DPMI/32RTM protected-mode DOS apps well; cycle throttling for a game that expects a Pentium Pro.
  • Clean I/O-port device API for the custom VPX device; access to host OpenGL.

Alternative considered: 86Box (full PCI/Pentium machine emulation). Better absolute fidelity, but heavier, harder to bolt a GL-rendering device into, and per-cycle emulation costs more than we need. DOSBox-X is the right tradeoff; nothing in the plan precludes porting the VPX device to 86Box later.

Phases

Phase 0 — Groundwork (repo + environment)

  • Create emulator/ build tree; vendor a pinned DOSBox-X source snapshot (submodule or subtree) and stand up a Windows build (Visual Studio).
  • Assemble a clean game image: BTLIVE copied into a DOSBox-X mounted directory; replicate AUTOEXEC.POD environment (SET BLASTER=…, L4CONTROLS=KEYBOARD initially, VIDEOFORMAT=svga).
  • Boot the game unmodified and record how far it gets (expected: DPL init fails at the missing link adapter).
  • Acceptance: documented baseline failure mode; build reproducible.

Phase 1 — Interface discovery (logging stub)

  • Add a DOSBox-X device claiming I/O ports 0x1500x157 that logs every read/write with timestamps and returns configurable status bits.
  • Drive the game through init; capture the full port trace. Compare against LINKIO.C semantics (C012 register order: input data, input status, output data, output status, + reset/analyse) and identify the exact register map and any block-transfer (outsw) usage.
  • Verify the trace against VR_COMMS.C's expected send sequence (reset → tranny boot → i860 code/data segments → version handshake).
  • Acceptance: annotated trace of the complete boot conversation; the register map and framing confirmed from the shipped binary, not just the DPL3 sources (guards against protocol drift between the DPL3 snapshot and the LIBDPL.LIB actually linked into Rel 4.10).
  • Implement the C012 FIFO state machine (status bits, byte in/out, reset/analyse behavior) with correct "always ready" pacing.
  • Implement transputer bootstrap acceptance: consume VRENDMON.BTL, emit whatever readiness bytes the monitor protocol expects (normative reference: VR_COMMS.C boot path; the .BTL content itself is discarded — we impersonate, not execute).
  • Accept i860 code/data segment uploads (vrnostex.mng / vrendmon-loaded vrend*.btl variants); discard content; ACK.
  • Implement the message layer: 32-bit length/route word, ≤1023-byte payloads, iserver vs. renderer messages, broadcast id 0xff.
  • Answer version/status queries with values captured from VR_PROT.H / DIVVERS.H conventions; iterate until DPL init returns success.
  • Acceptance: game completes dpl startup with /device 0x150 and proceeds to resource loading; its console shows the same startup banner sequence the INI/logs imply.

Phase 3 — Frame-stream renderer (the core)

  • Decode the per-frame message stream: matrices/viewpoint, object and geometry references, material/texture state, light/fog/clip setup, and the TRICODER.H triangle encoding. Normative references: VR_PROT.H, DPL_VPX.H, DPL.H/DPL_HOST.C (what the host sends), VRENDER/*.c (how the board consumed it).
  • Texture uploads: SVT/BSL texel formats are already decoded in restoration/divformats.py; port that logic to C++ (incl. 4-bit mono bit-slice textures with material color modulation).
  • OpenGL backend in the device: double-buffered FBO, Division shading model (ambient/diffuse/emissive, RGB shading ramps, per-vertex "cooked" colors, linear fog with per-material fog immunity) — semantics already validated visually by restoration/vwe-archive.html.
  • Present in a dedicated borderless window (target: the cockpit's main projector output), independent of the DOSBox-X VGA window.
  • Capture/replay harness: every session can dump the raw link byte-stream; a standalone tool replays dumps into the renderer without DOSBox-X. This makes renderer work testable in isolation and lets us regression-test against golden frames.
  • Acceptance: attract mode / mission renders recognizably; golden-frame comparisons against the scene-archive renderer for the same content.

Phase 4 — Frame pacing and sync

  • Implement the frame-ack poll exactly as the game expects (respond as the board would at ~30 Hz; configurable). RETRACE n in scene data implies frame-rate divisors — honor them.
  • Tune DOSBox-X cycle settings for Pentium-Pro-class throughput; verify game simulation speed against the 30 Hz assumption in game code.
  • Acceptance: stable 30 fps, no spiral-of-death when the host GPU is momentarily slow; timing feels correct in motion.

Phase 5 — Cockpit I/O: RIO and plasma

  • serial1=directserial realport:COM1 (RIO), serial2=…COM2 (plasma); set L4CONTROLS=RIO:COM1, L4PLASMA=com2.
  • Validate against L4RIO.CPP expectations (framing, polling rate, shared-data messages). Risk is low — the game's own driver does the talking — but latency through the emulated UART must be measured; if polled-UART overhead hurts, add a fast path.
  • Keyboard fallback (L4CONTROLS=KEYBOARD) retained for bench testing.
  • Acceptance: cockpit sticks/pedals/buttons drive the game; plasma shows its normal content.

Phase 6 — Audio and network

  • SB16 at A220/I5/D1 (matches AUTOEXEC.POD front card); confirm HMI SOS drivers initialize and stream. Rear card (A240) deferred.
  • NetNub: NE2000 emulation + packet driver, bridged via pcap so two cockpits (or a cockpit and a test PC) see each other; stand up the BOOTP expectations WATTCP has (NetNub/include/BOOTP.H).
  • Acceptance: sound effects + MIDI play; two instances complete a networked mission handshake.

Phase 7 — Deployment hardening

  • Per-pod configuration file (COM mapping, display selection, pod id, game selection BT/RP) mirroring the old VGL_LABS per-pod setup.
  • Autostart: boot-to-game like the original pods (BOOTPOD.BAT equivalent as a Windows service/scheduled task), watchdog restart.
  • Error logging (PREFMODE=ERRORLOG existed in the original scripts — keep the spirit: never strand a pod on a DOS prompt).
  • Acceptance: power-on → attract mode with no operator interaction.

Phase 8 — Deferred (tracked, not in critical path)

  • Gauge displays: original pods drove six instrument displays from the S3 card while the Division board drove the main view. Options, in ascending fidelity: (a) run with L4GAUGE= empty (supported by the game); (b) present the emulated VGA/VESA output in a window on a secondary monitor; (c) split/route to the physical gauge displays. Decide after Phase 5 when the real display topology is measured.
  • Rear audio channel (second SB16/AWE at A240).
  • Intercom (L4INTERCOM).
  • NTSC video mode (/video~ntsc): only if a pod's projector chain still wants NTSC; SVGA path is primary.

Risks and mitigations

Risk Likelihood Mitigation
Protocol drift: Rel 4.10's LIBDPL differs from the DPL3 sources Medium Phase 1 derives the real protocol from the shipped binary's port trace; DPL3RLS + dated VREND/ drops bracket the era
Undocumented bulk-transfer mode (outsw, ok_to_fifo) Medium Still plain port I/O; the trace will show it; implement what's observed
Game validates renderer version/capabilities strictly Medium Answer with values from VR_PROT.H/DIVVERS.H; iterate from the game's error messages (it prints useful diagnostics per VR_COMMS.C style)
Hidden second link adapter / interrupt use Low LINKIO.C is fully polled; the trace will falsify this cheaply
RIO protocol quirks under emulated UART timing LowMedium Driver source is in-repo (L4RIO.CPP); real hardware available for testing
DPMI/32RTM incompatibility in DOSBox-X Low Known-good app class; 86Box as fallback platform
Performance (parsing + GL per frame) Very low 1996 scene complexity; thousands of triangles

Test strategy

  1. Unit: protocol framing round-trip tests against VR_COMMS.C semantics; TRICODER decode against hand-built vectors.
  2. Replay: captured link-stream dumps replayed into the standalone renderer; golden-image diffs (allowing rasterizer tolerance).
  3. Cross-check: the same models rendered by restoration/ tools vs. the emulator renderer must agree on geometry/material interpretation.
  4. Hardware-in-loop: bench PC + real RIO on COM1 before cockpit time; cockpit sessions scripted with a checklist (controls, plasma, audio, network, endurance run).

Sequencing and effort

Phases 02 are the discovery-heavy half; 3 is the largest single block of work; 46 are integration. For one experienced developer:

  • Phase 01: ~12 weeks
  • Phase 2: ~24 weeks
  • Phase 3: ~48 weeks
  • Phase 46: ~35 weeks combined
  • Phase 7: ~12 weeks

Total: roughly 35 months of focused work to "playable in a cockpit," with the gauge displays intentionally out of scope until after that milestone.

Appendix A — C012/B004 register model (from LINKIO.C)

Base address LA (0x150 in the shipped configuration):

Offset Register Behavior
+0 input data read one byte from board→host FIFO
+1 output data write one byte host→board
+2 input status bit 0 = byte available
+3 output status bit 0 = ready to accept
+4/+5 reset / analyse board reset & debug lines (per B004 convention; exact offsets to be confirmed in Phase 1 trace)

Appendix B — message framing (from VR_COMMS.C)

Every message begins with a 32-bit length/route word:

  • bit 31 set → iserver-class message (boot/loader); clear → renderer message
  • bits 2316 → sender id (to host) / target id (from host); 0xff = broadcast
  • low bits → payload byte count (≤1023 for renderer messages)

Boot order (from DPLARG and VR_COMMS.C): board reset → transputer bootstrap (VRENDMON.BTL) → i860 code segment → i860 data segment → version handshake → texture/geometry environment setup → frame loop (commands + triangles, then frame-ack poll).

Appendix C — primary sources

  • Host link layer: sda4/DPL3/LINKIO.C, sda4/DPL3/VR_COMMS.C
  • Protocol constants: CODE/*/MUNGA_L4/LIBDPL/dpl/vpx/VR_PROT.H, TRICODER.H, DPL_VPX.H, DPL_MEM.H
  • Host library behavior: sda4/DPL3/DPL.C, DPL_HOST.C, DPL_LOAD.C
  • Board-side renderer (normative for HLE behavior): sda4/DPL3/VRENDER/
  • Shipped boot config: sda4/BTLIVE/SETENV.BAT (DPLARG), BTDPL.INI
  • Cockpit I/O: CODE/RP/MUNGA_L4/L4RIO.CPP/.HPP, L4CTRL.CPP, sda4/VGL_LABS/BOOTPOD.*, AUTOEXEC.POD
  • Data formats (already reimplemented): restoration/divformats.py