Files
TeslaRel410/emulator/DEPLOYMENT-PLAN.md
T
CydandClaude Fable 5 198b73eb96 Deploy: freeze the render bridge to renderer.exe -- archive is now complete
render-bridge/freeze.ps1: PyInstaller onefile of live_bridge.py + Dave's
vrboard/vrview/vrview_gl (+glcontext collected for moderngl) -> dist\
renderer.exe (~33 MB). GL backend live-verified from a pure-dist launch
(no --root/--renderer overrides) on 2026-07-10. package.ps1 bundles the
frozen exe automatically when present; DEPLOYMENT-PLAN's renderer OPEN
item is resolved. Ignore the build-artifact dirs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:57:14 -05:00

216 lines
12 KiB
Markdown

# Pod-bay deployment plan (network-agnostic archive)
How the two emulated titles (BattleTech + Red Planet, the only DOSBox games)
ship into the existing TeslaConsole / TeslaLauncher pod-bay architecture as a
self-contained, air-gapped, statically-addressed install. Everything here is
settled unless marked **OPEN**.
**Built so far:** the supervising launch entry-point (`pod-launch/`, C# Job-Object
supervisor -- kill-cascade verified) and the install-side artifacts
(`deploy/`: `postinstall.bat`, `configure.ps1`, `net_*.conf.tmpl` -- rendering +
NIC-bind + `bayIP+100` + WATTCP-stamp verified against a scratch tree). Remaining
build work is in the OPEN list.
## The existing contract (do NOT change it)
- A pod bay is a Windows PC on a **self-contained, air-gapped network** (no
venue LAN, no internet) shared by the console + up to **32 pods** (~140 units
left worldwide -- a bounded preservation fleet, not a scaling problem).
- **All addresses are static, assigned by TeslaConsole.** No DHCP.
- **TeslaConsole -> TeslaLauncher**: transmits a zip; the zip has ONE folder +
a `postinstall.bat` at its root; TeslaLauncher extracts to the games root
(`C:\games` almost always) and runs `postinstall.bat` **elevated**.
- **TeslaConsole registers/invokes the launch** -- it tells TeslaLauncher to
execute a command in the game dir with args (args select game/mode).
`postinstall.bat` does NOT register anything.
- **TeslaLauncher also STOPS the app** -- e.g. on a console "kill" call when a
DOSBox hangs. Its control path must stay alive independent of DOSBox.
- Only **BT/RP 4.10 are emulated** (DOSBox); the rest of the catalog runs
**native** (preservation project). For every native title, TeslaLauncher and
the game share the bay's single IP as one process space; **these two are the
only exception**, because the game's TCP stack lives inside DOSBox, separate
from the host.
## Network model -- THE core of this plan
### Why a bridge, not slirp/NAT (settled, source-proven)
The pods form a **direct peer-to-peer TCP mesh**, not star-through-console:
each pod `TCP_OPEN`s to the other pods' real IPs from the egg roster
(`MasterMode`/`SlaveMode` decides who dials), tracked separately from the
console stream. Evidence: `NETNUB_TCP_OPEN` ("Opens a TCP stream to another
computer", NETNUB.HPP), `OpenConnection(...,int internet_address)`,
`NetworkStartupMode{SlaveMode,MasterMode}`, `numberOfMungaHostsConnected` vs
`numberOfConsoleHostsConnected` (L4NET.HPP). See memory `pod-multiplayer-mesh`.
=> NAT can't carry a pod dialing a same-subnet peer, so the emulated NIC must be
a **real bridged host** on the segment.
### Two IPs per bay -- launcher keeps the bay IP; the DOS game gets `bayIP+100`
The launcher's IP never moves. The DOSBox guest gets a **second** address so it
can be a first-class host without stealing the bay IP:
| Role | Address | Owner | Used for |
|---|---|---|---|
| **Bay / control IP** | e.g. `10.0.0.5` (static, console-assigned) | host Windows / TeslaLauncher | zip, launch, **kill** -- unchanged from native titles; alive even if DOSBox hangs |
| **Game IP** | `bayIP + 100` = `10.0.0.105` | DOSBox guest (bridged, own emulated-NIC MAC) | egg identity, mesh peer target, `:1501` |
Both live at once on the **one** physical NIC (host MAC = bay IP, emulated NE2000
MAC = game IP). The `+100` offset on the last octet is safe forever at this
scale: 32 bays fit in `.1-.32`, games in `.101-.132`, no wrap, no collision.
### The `+100` convention -- two edits, both derive the same value
1. **`postinstall.bat`** reads the host's static IP and stamps the guest
`WATTCP.CFG my_ip = bayIP+100` (netmask `255.255.255.0`, gateway harmless --
air-gapped, no routing; the mesh is all same-subnet L2).
2. **TeslaConsole** -- a per-title **"runs in DOSBox" flag**. When set, apply
`+100` at the single point where the console resolves a bay to its game
endpoint, so BOTH inherit it:
- the **roster entries written into the egg** (peers dial `+100`), AND
- the **address the console itself dials** to deliver the egg / poll
StateQuery / send StopMission (`bayIP+100:1501`, not the bay IP -- else the
SYN hits the launcher, not the DOS game).
**Blanket `+100` is valid because every host in a BT/RP egg is itself a DOSBox
pod.** Only the BT/RP 4.10 engine is emulated, and ALL of its roles -- cockpit,
camera ship, mission review -- are that same one DOSBoxed binary; every other
catalog title runs native. So a BT/RP mission roster is entirely emulated and
takes `+100` uniformly. The console's own address is never a roster entry (pods
reply on the console's inbound socket), so it's never offset.
### What this retires (dev-only artifacts, gone in deployment)
`SendToRxAdapters`, the Windows Network Bridge, the two-TAP setup, and
`tap2_mirror.py` all existed ONLY because dev co-located the console on the pod
PC and had to loop pcap frames back to the host stack. On the real air-gapped
segment the console is a separate machine and hears every pod over the wire.
Also gone: the machine-specific `realnic=DB5521D` GUID (postinstall binds the
one NIC) and the `200.0.0.x` hardcoding (postinstall stamps the assigned IPs).
**Co-located smoke-test caveats (all hit 2026-07-10, first dist smoke test;
end-to-end egg->mission achieved same night):** running TeslaConsole on the
pod PC itself brings dev-era requirements back. On the real segment, with the
console on its own machine, none of these apply:
1. Npcap `SendToRxAdapters` must list the pod NIC's `\Device\{GUID}` (restart
the npcap service after setting it -- the stop fails silently while DOSBox
holds a capture).
2. The host must NOT hold the game IP. A leftover static at bayIP+100 (the
old bridge address) makes the console dial itself -- source = destination,
the SYN never reaches the wire, `SynSent` forever.
3. **Disable NIC checksum offload (+ LSO) on the pod NIC**
(`Disable-NetAdapterChecksumOffload`/`-NetAdapterLso`). The host's OWN
outbound frames are captured BEFORE the hardware fills in IP/TCP checksums;
WATTCP silently discards every bad-checksum IP packet while still answering
checksum-less ARP -- so ARP resolves, the NIC accepts the frames, and the
pod stays dead silent at the TCP layer (netnub prints "discarding...").
This was the final console-connect blocker. Frames arriving over the real
wire always carry completed checksums, so separate-machine deployments are
immune.
Also learned same night, not co-location-specific: **bt/rp REQUIRE sound**
(`--no-sound` strips the AWE32; the FAST SOS clock never ticks and BTL4OPT
freezes in the RIO-reset busy-wait), and **the pod requires a packaged
renderer** (pod-launch refuses to start without one unless `--no-bridge`;
the smoke test ran with the dev tree's render bridge via `--root`) -- closes
the "freeze renderer vs bundle Python" OPEN item in favor of: MUST ship one.
Related guard: `configure.ps1` refuses to guess when several candidate bay
IPs survive its physical-adapter filter (`-BayIp x.x.x.x` disambiguates, and
also allows deliberately binding a virtual adapter on a dev rig);
`-ConsoleIp` sets the WATTCP gateway/nameserver (default: the bay IP).
## The archive (self-contained -- air-gap forbids any download)
Single root folder + `postinstall.bat`. Everything the install needs is inside:
- `dosbox-x.exe` -- a **static build** (182 MB; imports only Windows system DLLs,
empty delay-import table), so **no MinGW/SDL/VC++ DLLs to bundle**; the only
runtime DLL, `wpcap.dll`, comes from the Npcap install
- `ALPHA_1\` game content (REL410\BT, REL410\RP, SB16, GAUGE, ...) + `net-boot\`
drivers (LSL/NE2000/ODIPKT/NET.CFG @ PORT 340/INT 10, loop.bat)
- ~~Npcap silent installer (bundled)~~ **retired 2026-07-10 (operator
consensus): Npcap is installed MANUALLY, once per cockpit PC** -- a one-time
affair that survives game updates. postinstall detects an existing install
and only warns when absent (a bundled `deploy\npcap.exe` is still honored
if shipped)
- The renderer -- **RESOLVED 2026-07-10: frozen to one exe** (PyInstaller
onefile, `render-bridge\freeze.ps1` -> `dist\renderer.exe`, ~33 MB, GL
backend live-verified from a pure-dist launch). `package.ps1` bundles it
automatically when present.
- `net_*.conf` **templates** with `@@ROOT@@` / `@@REALNIC@@` tokens, launch
dispatcher, `postinstall.bat`
## `postinstall.bat` responsibilities (elevated, one-shot, no network fetch)
Implemented in `deploy/` (`postinstall.bat` = thin wrapper; `configure.ps1` does
2-4):
1. **Check Npcap** (manual-install policy, 2026-07-10): detect the existing
service and move on; warn clearly if absent. Silent `/S` install only if a
bundled `deploy\npcap.exe` was shipped.
2. **Bind the NIC**: pick the one active adapter, write a safe letter-leading
`realnic=` fragment (a contiguous substring of ONE GUID block, so it matches
`\Device\NPF_{GUID}`; avoids the leading-digit-as-index trap).
3. **Stamp game identity**: `WATTCP.CFG my_ip = <host static IP> + 100` in every
`ALPHA_1\...\WATTCP.CFG`, plus a stable `macaddr = 02:00:<gameIP hex>`.
4. **Relocate paths**: render `net_*.conf.tmpl` -> `net_*.conf`, filling
`@@ROOT@@` (the package dir), `@@REALNIC@@`, `@@MACADDR@@`.
NOT done here: registering the launch entry-point (console does that), firewall
rules, SendToRx, TAP, or mirror.
## Launch dispatch
TeslaConsole invokes a **single entry-point** in the game dir with args
selecting game/mode. The dispatcher maps args -> conf + renderer:
`bt`/looped -> `net_loop.conf`; `rp` -> `net_rp.conf`; the "alternate games/
modes" map onto the decoded `ExitCodeID` set (RunBattleTech/RunRedPlanet +
SinglePlayer variants, RunCamera, RunMissionReview, test patterns,
TestPlasmaDisplay, ResetRIO).
**The entry-point MUST be a supervising parent, not fire-and-forget** -- it stays
authoritative for the whole session, lifetime == the game's:
- It creates a **Windows Job Object** with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`,
launches DOSBox-X **and** the render bridge *into* the job, then blocks on
DOSBox (also doing the window focus/layout, folding in `focus_dosbox.ps1`).
- **Kill the entry-point -> DOSBox + renderer die with it.** The kernel enforces
the teardown when the job's last handle closes (the instant the entry-point
process dies), so it holds even on a hard `TerminateProcess` -- which is
exactly the hung-DOSBox case, where no cooperative shutdown can be relied on.
No orphaned emulator/renderer, ever.
- When DOSBox exits on its own (mission end), the supervisor tears down the job
(renderer too) and returns DOSBox's exit code.
Ship the entry-point as a small **compiled supervisor exe** (C#/C++) that creates
the job, `CreateProcess`es DOSBox + the bridge *into* it, and blocks on
`WaitForSingleObject(dosbox)`. It **must NOT be a batch file / `cmd.exe`, nor use
`start` or `Start-Process`** -- all of those DECOUPLE from the child: a `start`ed
/ `Start-Process`ed DOSBox is an independent process, and even a foreground
`dosbox.exe` under `cmd.exe` is NOT killed when cmd is (Windows doesn't reap
children with the parent gone, absent a job object). The current
`launch_pod.ps1`/`pod_deploy.ps1` do exactly this wrong (`Start-Process` +
pid-files, then exit, leaving DOSBox running) and must be replaced by the
supervisor.
**Kill flow:** console -> TeslaLauncher terminates the entry-point process ->
job closes -> DOSBox + renderer die. Graceful mission-end still flows to the game
as StopMission on `:1501`, after which the supervisor exits with DOSBox.
## Provisioning rules
- Bays static in `.1-.32`, game endpoints auto-fall in `.101-.132`, console + its
special hosts elsewhere in the /24. All on one flat, air-gapped L2 segment.
- Dumb unmanaged switch; no venue cooperation needed.
## OPEN items
1. Renderer packaging -- freeze (PyInstaller, one exe) vs embeddable Python
(with David).
2. Per-rig display RECTS (already isolated in `pod_deploy.ps1`; deployment
variant of the window layout).
Related: `NET-NOTES.md`, `LAUNCH.md`, `ENV-VARS.md`, memory
`pod-multiplayer-mesh`, `pod-deployment`.