Files
TeslaSuite/vPOD
CydandClaude Opus 5 2a5a387381 Console: remember Enable Custom Bitmaps, ship the art; bump suite to 4.11.4.5
Settings -> Enable Custom Bitmaps was a static bool with no backing store:
it defaulted to off on every launch, so an operator had to re-tick it each
session and any custom plasma art was silently ignored until they did. New
ConsoleSettings persists machine-level menu toggles as XML in
%ProgramData%\Tesla Console\console.settings, alongside RPDefaults.rpd /
BTDefaults.btd / local.siteconfig. It is loaded once from Main and never
from a static initializer: the differential suite drives PlasmaBitmaps
directly and must keep seeing the original defaults rather than whatever
this machine has saved. A missing file is the first-run case; a corrupt one
is ignored and rewritten by the next toggle, because losing a menu setting
must never stop the console starting.

Custom art is now version-controlled and rolls with the release. New
Console\Plasma Images\ is copied into the package, and the lookup searches
%ProgramData%\Tesla Console\Plasma Images first and the exe-relative folder
second, so a release can never clobber a site's own name bitmaps.
install.bat creates the data-dir folder before the icacls grant so an
unelevated operator can write it. Ships with three 128x32 name bitmaps
(Deadmeat, Muerte, Phrogg); Muerte arrived as "Muerte_128x32-2.bmp", a name
the lookup can never build, so it is renamed to match its pilot.

Two fixes fell out of making the flag sticky. Path.Combine ran on the raw
participant name outside the try block, so with the option on a pilot named
"A:B" threw ArgumentException straight out of egg generation; names that
cannot be a Windows file name now just render procedurally. And the art was
loaded with Image.FromFile, which keeps the bitmap backed by the file and
locked for its whole lifetime — it is copied out through a stream now, so
art can be swapped between missions without restarting the console.

Verified against the built net40 exe: all three shipped bitmaps resolve by
pilot name, ProgramData wins over the shipped copy, a wrong-size file is
ignored, an illegal-character name does not throw, the file is not left
locked, and the settings round-trip and corrupt-file tolerance both hold.
Diff suite 106/106.

Version bumped 4.11.4.4 -> 4.11.4.5 across Console, Launcher, vPOD, the
install/build banners, the diff-suite version assertion and the README's
latest-release pointer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 13:16:11 -05:00
..

vPOD — virtual pod / game-client stand-in

A test tool that impersonates a Tesla game client (Red Planet's rpl4opt.exe or BattleTech's btl4.exe) so the operator consoles can be exercised without real cockpit hardware. It speaks the Munga command/control protocol as a server on TCP 1501 — the console connects to it exactly as it would a real pod — emulates the pod ApplicationState machine, reassembles the streamed egg, and shows everything on a live display.

It also impersonates the pod's TeslaLauncher service (the "Launcher / Site Management" column), so the console's Manage Site — provisioning, Install / Uninstall Product, launch/kill, volume, restart/shutdown — can be tested end-to-end with no cockpit and no console changes. See Site management / virtual launcher.

What it does

  • Listens on TCP 1501 (configurable) and answers the console's StateQuery with a StateResponse, reporting the game (ApplicationID) and the current ApplicationState.
  • Walks the mission lifecycle the console drives it through: WaitingForEgg → LoadingMission → WaitingForLaunch → LaunchingMission → RunningMission → …, reacting to the egg stream and to Run / Stop / Abort / Suspend / Resume messages, and acknowledging the egg.
  • End-mission graceful exit + watchdog restart — on the console's end-mission command the "game exe" exits (the listener closes, the console's connection drops), then a watchdog relaunches it a moment later and it comes back up in WaitingForEgg. This is the real pod's per-game cycle (autoRestart); the Restart game after mission ends (watchdog) checkbox (on by default) toggles it — unchecked, the pod just returns to WaitingForEgg without exiting.
  • Pod Power (Power On / Power Off) — the whole virtual machine: Power Off darkens the game listener AND the launcher / site-management side; Power On boots the launcher (or provisioning beacons) and auto-starts the game, like a real pod booting with an autoRestart product installed.
  • Start Game / Stop Game — just the emulated game "exe", separate from pod power: Stop Game closes the Munga listener (the console's game connection drops) while the pod and its launcher service stay up — the state of a pod whose game client crashed or was killed. Reset returns a running game to WaitingForEgg.
  • Reassembles and shows the egg the console streams (the EggFileMessage chunks), one field per line, with a summary line (adventure / map / scenario / pilot count). The last egg is kept across missions/restarts (so it can be copied for dev use) until the Clear button empties the viewer.
  • Game toggle — a Red Planet ⇄ BattleTech switch on the window changes which ApplicationID the pod reports, live, so one vPOD can stand in for either game. (-app rp|bt sets the initial choice.)
  • A newest-first protocol log of the traffic.

Running it

vPOD.exe [-net <port>] [-app rp|bt] [-lc|-mr] [-host <id>] [-res W H] [-nomanage]
  • -net <port> Munga control port (default 1501).
  • -app rp|bt which game to report initially (also switchable in the UI).
  • -lc / -mr live-camera / mission-review role (cosmetic; the state model is identical to a game machine).
  • -host <id> responding host id reported in state responses (default 1).
  • -res W H accepted and ignored (real clients take it; kept for drop-in launch compatibility).
  • -nomanage vPOD-only: disable the virtual launcher / site-management side (no provisioning beacons, no TCP 53290 listener) — e.g. when a real TeslaLauncher runs on the same machine, or for a second vPOD instance.

Site management / virtual launcher

vPOD's right-hand column is a stand-in for the pod's TeslaLauncher service: the OFB-encrypted, framed-JSON ILauncherService RPC on TCP 53290 that the console's Manage Site and squad/pod panel talk to (Tesla.Contract / Tesla.SecureConfig are the same shared wire libraries both real ends use).

Provisioning (first run)

The console only talks to a pod's launcher after minting a 32-byte session key for it via the SecureConfig Configure flow, so an unprovisioned vPOD behaves like a freshly-imaged pod — minus the NIC/registry changes (the assigned network config is shown in the window but never applied):

  1. Run vPOD (no -nomanage). It broadcasts RQST beacons and displays a Request ID and Passphrase (the real pod shows these on its screen).
  2. In the console: Manage Site — a "Configure <Request ID>" button appears at the bottom. Click it, enter the pod's name/squad, its IP (127.0.0.1 when vPOD runs on the console machine — accepted), any subnet (e.g. 255.255.255.0), and the passphrase from vPOD's window.
  3. The console sends the encrypted config + session key; vPOD stores the key and starts the launcher RPC. The pod row goes healthy (Idle [<n> ms]).

The key persists in %LocalAppData%\vPOD\TeslaKeyStore.key (launcher format), so provisioning is one-time. Reprovision resets to a fresh pod — drops the key, clears the installed-apps store (LaunchApps.json) and returns to beacon mode — pair it with deleting the pod in Manage Site (the console's Reconfigure… does both ends automatically: its ClearStore makes vPOD do the same wipe and beacon again). Extracted packages under C:\Games are left on disk either way.

Product deployments

Right-click the pod row → Install Product ▸ works exactly as against a real pod: the console streams the package zip out-of-band on a second 53290 connection while polling progress on the first; vPOD extracts it into the real C:\Games — the launcher's games root, so products land where their catalog launch entries point — and reports the launcher's usual 050% receive / 5095% extract / 99% Complete progression. Then:

  • Installed apps land in the column's list (and in GetInstalledApps, so the Uninstall menu populates). Registrations persist in %LocalAppData%\vPOD\LaunchApps.json.
  • Packages extract into C:\Games (created on first install if missing; if an admin-owned C:\Games isn't writable by your account, the install reports Failed — fix the folder's ACL or run vPOD elevated). Uninstalling a product removes its C:\Games\<product> folder, like the real launcher. A packaged postinstall.bat is logged and removed unrun by default; the "Run postinstall.bat after install" checkbox makes the install execute it (via cmd /c, waited on up to 5 min) before deleting it, like the real Agent — off by default because it runs package script code on the host machine.
  • Launch/Kill from the squad panel simulate PIDs by default. The "Actually launch apps (real processes)" checkbox switches to the real Agent's behavior: LaunchApp starts the entry's exe from C:\Games (missing exe → the same "registered but not yet installed" error a real pod gives), Kill* terminate the processes, and apps that exit or crash on their own disappear from the console's running list. The indented "Auto-restart after the app exits (watchdog)" checkbox (on by default) adds the Agent's watchdog: an autoRestart entry that exits on its own relaunches after 2 s, while console-ordered kills stay down — exactly the real pod's behavior. Real processes also die when the pod is powered off / rebooted / the vPOD window closes (which also cancels pending watchdog restarts). Caveat: launching a deployed vPOD or a real game client this way will fight the running vPOD for ports 1501/53290. Volume round-trips; Restart/Shutdown power-cycle the virtual pod (dark for a few seconds on restart).

Ports

Port Proto Direction Purpose
1501 TCP console → vPOD Munga game control (existing)
53290 TCP console → vPOD Launcher RPC (ILauncherService)
53291 UDP vPOD → console (broadcast) RQST provisioning beacon
53292 UDP+TCP console → vPOD RPLY config broadcast + RSA key exchange

Same-machine testing needs no firewall changes (loopback). Running vPOD on a different machine needs inbound allows for TCP 1501/53290/53292 and UDP 53292 on the vPOD machine (the console installer already opens its own side).

Not emulated: the console's remote Windows-service control (ServiceController over SCM/SMB, used by some SitePanel service start/stop paths — dormant against real pods too, since it queries service name TeslaLauncherService while the launcher registers as Tesla Application Launcher).

An end-to-end loopback test of this server against the console's real PodManagerConnection client lives in the differential suite: Console/tests/TeslaConsole.DiffTests/VPodLauncherServerTests.cs.

Deploying from the console (Manage Site → Install Product)

vPOD is not in the shipped catalog (Console\RedPlanet\Apps.xml) — it was removed 2026-07-11 since it is a dev tool, never a console-deployed product. To push it to a pod for testing, add it locally via Site Management → Add New Product... (or hand-edit Apps.xml) and build the deployable package first:

pwsh -File pack.ps1        # produces dist\vPOD.zip

The zip lays out vPOD\vPOD.exe (+ Munga Net.dll) so the launcher extracts it to C:\Games\vPOD and a launch entry pointing at C:\Games\vPOD\vPOD.exe works as-is.

Testing locally against the console

The default site ships a local pod at 127.0.0.1. Run vPOD on the console machine, open a game window (e.g. Games → Red Planet: Death Race), and enable the local pod — the console connects to 127.0.0.1:1501 (vPOD), and you can drive Load → Run → Stop and watch vPOD's state and egg viewer follow along.