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>
16 KiB
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 4–6 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:
BTLIVEcopied into a DOSBox-X mounted directory; replicateAUTOEXEC.PODenvironment (SET BLASTER=…,L4CONTROLS=KEYBOARDinitially,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 0x150–0x157 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.Csemantics (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).
Phase 2 — Link + boot protocol emulation
- 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.Cboot path; the.BTLcontent itself is discarded — we impersonate, not execute). - Accept i860 code/data segment uploads (
vrnostex.mng/vrendmon-loadedvrend*.btlvariants); 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.Hconventions; iterate until DPL init returns success. - Acceptance: game completes
dplstartup with/device 0x150and 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.Htriangle 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 nin 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); setL4CONTROLS=RIO:COM1,L4PLASMA=com2.- Validate against
L4RIO.CPPexpectations (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.PODfront 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_LABSper-pod setup. - Autostart: boot-to-game like the original pods (
BOOTPOD.BATequivalent as a Windows service/scheduled task), watchdog restart. - Error logging (
PREFMODE=ERRORLOGexisted 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 | Low–Medium | 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
- Unit: protocol framing round-trip tests against
VR_COMMS.Csemantics; TRICODER decode against hand-built vectors. - Replay: captured link-stream dumps replayed into the standalone renderer; golden-image diffs (allowing rasterizer tolerance).
- Cross-check: the same models rendered by
restoration/tools vs. the emulator renderer must agree on geometry/material interpretation. - 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 0–2 are the discovery-heavy half; 3 is the largest single block of work; 4–6 are integration. For one experienced developer:
- Phase 0–1: ~1–2 weeks
- Phase 2: ~2–4 weeks
- Phase 3: ~4–8 weeks
- Phase 4–6: ~3–5 weeks combined
- Phase 7: ~1–2 weeks
Total: roughly 3–5 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 23–16 → 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