vPOD has outgrown its home inside the console's folder: it now emulates both halves of a pod (Munga game client + TeslaLauncher service / provisioning), so it lives at the repo root beside Console/, Launcher/, Contract/ and SecureConfig/, like the peer it has become. Accompanying changes: project references rebased (Contract, SecureConfig, the console's vendored Munga Net.dll), solution + DiffTests reference paths, the console csproj's now-obsolete vPOD source exclusion removed, and the root README / Apps.xml / vPOD README path mentions updated. pack.ps1 is self-relative and now emits vPOD/dist/vPOD.zip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
156 lines
8.3 KiB
Markdown
156 lines
8.3 KiB
Markdown
# 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](#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 `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 its
|
||
virtual games root and reports the launcher's usual `0–50%` receive / `50–95%`
|
||
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 under `%LocalAppData%\vPOD\Games\` (the virtual `C:\Games`).
|
||
A packaged `postinstall.bat` is logged and removed but **never executed**.
|
||
- **Launch/Kill** from the squad panel are simulated (fake PIDs, no real
|
||
process); 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`), and really launching
|
||
deployed exes (a second game client would fight vPOD for its own ports).
|
||
|
||
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 a catalog product (`Console\RedPlanet\Apps.xml`, id `0041C870-…`) with Game
|
||
Client / Live Camera / Mission Review entries, so it appears in **Manage Site →
|
||
Install Product** like any game. 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 the catalog entry launches `C:\Games\vPOD\vPOD.exe`.
|
||
|
||
## 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.
|