Render bridge (live_bridge.py, vrview_gl.py): - Hat glances render (left/right frame the canopy, hat-down = clean rear). Root bug was a stale _ckpt['fix'] key -> KeyError every glance frame -> render aborted (screen froze on hold, snapped back on release). The glance itself is the authentic eye-DCS action-0x1f reflush fp_cam already applies. - Torso twist turret-true (root-axis yaw, zero parallax); lasers follow torso. - Rear glance drops the canopy shell for a clean view (original-hardware behavior); mission-fade shroud 9fd hidden. - Wireframe debug mode (VRVIEW_WIREFRAME / 'w' key), scene-pass scoped. - Renderer output = 832x512, the dPL3 board's native framebuffer res. DOSBox-X fork: namedpipe serial backend (serialnamedpipe.cpp/.h) for vRIO/vPLASMA, replacing com0com; overlapped non-blocking I/O; typed frames (0x00 data / 0x01 DTR+RTS). Tracked copies + apply steps in vpx-device. Docs: COCKPIT-CAGE-NOTES (full glance/twist/rear forensics), XP-PORT-PLAN (back-burnered), RIO-NOTES (namedpipe + keypad), pipe/egg conf variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
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.batat its root; TeslaLauncher extracts to the games root (C:\gamesalmost always) and runspostinstall.batelevated. - TeslaConsole registers/invokes the launch -- it tells TeslaLauncher to
execute a command in the game dir with args (args select game/mode).
postinstall.batdoes 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_OPENs 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
postinstall.batreads the host's static IP and stamps the guestWATTCP.CFG my_ip = bayIP+100(netmask255.255.255.0, gateway harmless -- air-gapped, no routing; the mesh is all same-subnet L2).- TeslaConsole -- a per-title "runs in DOSBox" flag. When set, apply
+100at 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).
- the roster entries written into the egg (peers dial
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:
- Npcap
SendToRxAdaptersmust list the pod NIC's\Device\{GUID}(restart the npcap service after setting it -- the stop fails silently while DOSBox holds a capture). - 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,
SynSentforever. - 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).
STATUS 2026-07-11: the complete archive is INSTALL-VERIFIED. The final
TeslaPod410.zip(renderer bundled, ~212 MB) was extracted fresh toC:\Games, configured, and launched end-to-end on the co-located test rig -- works as expected. Remaining external per-cockpit step: the one-time manual Npcap install (+ the co-located caveats below when the console shares the machine).
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 installALPHA_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 bundleddeploy\npcap.exeis 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.ps1bundles it automatically when present. net_*.conftemplates 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):
- Check Npcap (manual-install policy, 2026-07-10): detect the existing
service and move on; warn clearly if absent. Silent
/Sinstall only if a bundleddeploy\npcap.exewas shipped. - 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). - Stamp game identity:
WATTCP.CFG my_ip = <host static IP> + 100in everyALPHA_1\...\WATTCP.CFG, plus a stablemacaddr = 02:00:<gameIP hex>. - 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 infocus_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, CreateProcesses 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 started
/ Start-Processed 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
- Per-rig display RECTS (already isolated in
pod_deploy.ps1; deployment variant of the window layout).
(Renderer packaging was OPEN here; RESOLVED 2026-07-10 -- frozen renderer.exe,
see the archive section. An XP/Win11 fleet-floor port is planned but
BACK-BURNERED: XP-PORT-PLAN.md.)
Related: NET-NOTES.md, LAUNCH.md, ENV-VARS.md, memory
pod-multiplayer-mesh, pod-deployment.