Specs everything a game team needs to deploy and command a title in a Tesla pod bay: package zip + Apps.xml catalog contract, launch/watchdog semantics, the full Munga wire spec (framing, message set, state machine, egg envelope), the dedicated game-console escape hatch, the modern bay address plan, LC/MR presentation roles, score-sheet printing, and RIO board integration (native preferred over the RIOJoy shim). XP support is documented as a nice-to-have for new games; the floor remains suite-side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
26 KiB
Deploying and commanding a game in a Tesla pod bay
Audience: developers of games being brought to the Tesla cockpit pods — current and future titles alike. This is the integration contract from the game's point of view: what your package must look like so the operator console can deploy it to pods, and what your executable must speak so the console can command it through a mission.
Everything here is implemented and validated in this repo (TeslaSuite v4.11.4.x) — file references point at the authoritative source.
1. The big picture: two independent channels
A pod (cockpit PC) runs two things that matter to you:
| Channel | Port | Who listens | Purpose |
|---|---|---|---|
| Launcher RPC | TCP 53290 | TeslaLauncher.exe (pod tray app) |
Deploy: install/uninstall packages, register launch entries, launch/kill your exe, volume, reboot |
| Munga game control | TCP 1501 | your game exe | Command: mission load (the "egg"), run/stop/abort/suspend/resume, state polling, in-mission events |
These are separate. The launcher channel is fully game-agnostic — any exe can be deployed and launched with zero code changes to your game. The Munga channel is what your game implements if the console is to drive missions in it.
That split gives two integration tiers:
- Tier 0 — deploy + launch only. The console installs your package, starts and stops your exe, and keeps it alive (watchdog). Your game runs its own show. Examples in the shipped catalog: BattleTech Firestorm, RIOJoy. Requires only §2 (a package + a catalog entry).
- Tier 1 — full mission command. Your game is a Munga TCP server; the console
streams it a mission egg, drives the state machine, and receives scoring
events. Examples: Red Planet 4.11 (
rpl4opt.exe), BattleTech 4.11 (btl4.exe), TeslaRel410 (supervisor wrapping the DOS games). Requires §2 + §3, plus a console-side game module (§3.7).
There is one escape hatch: a game that Munga control would not serve well may ship its own dedicated game console instead (§3.9) — deployment still goes through Tier 0 unchanged.
2. Deployment spec (Tier 0 — every game needs this)
2.1 The package zip
The console's Manage Site → Install Product streams a zip to the pod; the
launcher extracts it into the games root C:\Games (the zip is opened at
that root, not inside a product folder). Lay the zip out as:
YourGame.zip
├── YourGame\ ← your product folder → becomes C:\Games\YourGame\
│ ├── yourgame.exe
│ ├── (data files...)
│ └── pre-uninstall.bat ← optional; run on uninstall (driver/config removal)
└── postinstall.bat ← optional; at ZIP ROOT; run once after extract, then deleted
Rules and lifecycle (implementation: Launcher/TeslaLauncher.cs, Launcher/MiniZip.cs):
- Everything must extract under one
C:\Games\<Product>\folder (plus the optional rootpostinstall.bat). Uninstall deletesC:\Games\<Product>recursively — don't scatter files elsewhere unlesspostinstall.batputs them there andpre-uninstall.batremoves them. postinstall.bat(zip root) runs after extraction with the launcher's token — the kiosk account is an Administrator, so driver installs work (RIOJoy installs ViGEmBus this way). It is waited on, then deleted.pre-uninstall.bat(inside your product folder) runs before the folder is deleted on uninstall.- Zip format: stored + deflate (+ ZIP64) — the launcher uses its own extractor
(
MiniZip.cs), no exotic compression methods. - Install progress reported to the operator: 0–50% receive, 50–95% extract, ~96% postinstall, 99–100% complete.
- OS range: pods run Windows XP SP3 through Windows 11 on one image. The suite itself is deliberately held to the XP floor (one net40 binary set) to retain compatibility with the original cockpit hardware. For new games, XP support is a nice-to-have, not a requirement: meeting it (native exes: x86 + XP-safe API surface; .NET exes: net40 — runs in-place on Win10/11's 4.8 runtime) lets your game reach the original-hardware pods too. If you skip it, note modern-pods-only in your catalog entry's comment so operators don't push it to XP-era machines.
Existing package builders to crib from: vPOD/pack.ps1
(minimal) and TeslaRel410's deploy\package.ps1 (in its own repo — produces
an Install-Product-ready zip with postinstall).
2.2 The catalog entry (Apps.xml)
The console's product menu is data-driven from
Console/RedPlanet/Apps.xml (parser:
Console/TeslaConsole/AppRegistry.cs).
Your game ships as one <Product> element:
<Product id="NEW-GUID-HERE" ← fresh Guid; also the install key
name="Your Game" ← friendly name (menus, status text)
menuText="Your Game..." ← exact Install Product submenu text
hostTypeDialog="false"> ← "true" only if you have LC/MR roles
<Launch key="NEW-GUID-HERE" ← first entry reuses the product id
displayName="Your Game" ← name in the pod's app list
exe="C:\Games\YourGame\yourgame.exe"
args="-net 1501{res}" ← whatever your exe takes; see below
workingDirectory="" ← optional; defaults to exe's folder
autoRestart="true"
hostType="None" /> ← GameClient|LiveCamera|MissionReview|None
</Product>
- Key convention (documented in the Apps.xml header — follow it exactly):
generate ONE fresh Guid for the product id; the first
<Launch>reuses it; each additional<Launch>increments the last hex digit (+1, +2…, wrapping F→0). Never append-1/-2to the string — keys parse asSystem.Guidand a suffixed string silently collapses toGuid.Empty. {res}inargsexpands to-res W Hwhen the operator picks a custom resolution, else to nothing. Only use it if your exe accepts-res W H(the RP411 engine convention); a game with different resolution flags just pins them inargs.autoRestart="true"enables the pod watchdog (§2.3). Almost always what you want for a game client.hostTypeDialog="true"+ per-entryhostTypeis for games with separate live-camera / mission-review roles (RP/BT use-lc/-mrflags) — see §4 for what those stations do. A plain game ships one entry withhostType="None"andhostTypeDialog="false".- XML gotcha: comments in this file must not contain
--—XmlDocument.Loadthrows and the whole catalog comes up empty.
Registering entries on pods does not require reinstalling files: the
console's Register Product on Pods context action pushes the catalog's
launch entries to connected pods over the InstallApp RPC. The wire shape is
LaunchData { LaunchPair{LaunchKey, DisplayName}, WorkingDirectory, ExeFile, Arguments, AutoRestart } (Contract/WireContract.cs).
2.3 Launch / kill / watchdog semantics
What the launcher does with your entry (Launcher/TeslaLauncher.cs):
- LaunchApp starts
exewithargs, working directory =workingDirectoryor the exe's folder. Missing exe → clean "registered but not yet installed" error at the console (register-first / install-later is supported). - Kill terminates the process. Console-ordered kills stay down.
- Watchdog: an
autoRestartentry whose process exits on its own is relaunched ~2 s later. The original games lean on this for their per-mission cycle:rpl4opt/btl4terminate after each mission and the watchdog brings a fresh process up waiting for the next egg. That exit-and-relaunch cycle is not strictly required (FireStorm doesn't do it): a game may instead stay resident and return itself to a dark waiting state, ready for the next group (§3.6). Either way, design your exe so a cold start goes straight to that ready state with no menus in the way — the watchdog is still your crash recovery. - Your process runs in the auto-logged-in kiosk session (account
Firestorm, Administrator, UAC disabled) — desktop, audio, and DirectX/OpenAL are all available. Firewall is disabled on pods; don't ship your own rules.
3. Command spec (Tier 1 — the Munga protocol)
Reference implementations, in order of usefulness:
- vPOD/MungaPodServer.cs — the framing, complete and commented (vPOD is a working software pod; the console can't tell it from a real one).
- vPOD/PodSimulator.cs — the pod-side state machine and egg handling.
- Console/TeslaConsole/MungaGame.cs — the console side you're talking to.
- The typed message classes live in the vendored
Console/lib/Munga Net.dll; the C++ originals are in the RP411 game repo.
3.1 Transport
Your game is the TCP server. Listen on TCP 1501; the console connects to
<podIP>:1501 and keeps one connection open. One console at a time (a new
connection replaces the old — see MungaPodServer.AcceptLoop). Convention: the
port is passed on your command line (-net 1501) rather than hardcoded.
3.2 Framing (little-endian throughout)
Every message, both directions:
[16-byte NetworkPacketHeader][MungaMessage]
header: int32 ClientID | int32 GameID | int32 FromHost | int32 Timestamp(ms tick)
message: int32 MessageLength | int32 MessageID | int32 Flags | body...
MessageLength counts the 12-byte message base but not the 16-byte header.
Messages are dispatched by (ClientID, MessageID) pairs.
3.3 Message set
Console → pod (what you must accept):
| ClientID | MessageID | Message | Your reaction |
|---|---|---|---|
| Application | 3 | StateQuery |
reply StateResponse(host, state, appId) |
| Application | 4 | CheckLoad |
(load probe) |
| Application | 5 | RunMission |
WaitingForLaunch → LaunchingMission → RunningMission |
| Application | 6 | StopMission |
RunningMission → EndingMission → exit (§3.6) |
| Application | 8 | SuspendMission |
RunningMission → SuspendingMission (pause) |
| Application | 9 | ResumeMission |
SuspendingMission → ResumingMission → RunningMission |
| Application | 10 | LoadMission |
WaitingForEgg → LoadingMission |
| Application | 11 | AbortMission |
AbortingMission → WaitingForEgg (no results) |
| Application | 12 | LightsOutMission |
cockpit lights-out |
| NetworkManager | 3 | EggFile |
egg chunk — buffer it (§3.5) |
Pod → console (what you send): StateResponse (answer to every StateQuery),
AcknowledgeEggFile (NetworkManager 4, once the egg is complete), and the
in-mission event messages (§3.6).
3.4 Identity and state
ApplicationID— which game this pod is running, reported in everyStateResponse. The enum lives inMunga Net.dll:RPL4 = 0(Red Planet),BTL4 = 1(BattleTech), plusNDL4. A brand-new title needs a new value agreed with the TeslaSuite side (the console mapsApplicationID→ game module), or it reuses an existing one if it's a port of that game.ApplicationState— the cockpit state machine. The values the console drives/observes:InitializingState, WaitingForEgg, LoadingMission, WaitingForLaunch, LaunchingMission, RunningMission, SuspendingMission, ResumingMission, EndingMission, AbortingMission, CreatingMission.- The console polls
StateQueryabout once per second and gates every operator action on your reported state. Report honestly — the console's UI ("busy, must stop first", ready-to-run, etc.) is driven entirely by it.
The normal lifecycle:
boot → InitializingState → WaitingForEgg
← egg streamed (console sends it when it sees WaitingForEgg)
→ LoadingMission → WaitingForLaunch (send AcknowledgeEggFile)
← RunMission
→ LaunchingMission → RunningMission
← StopMission (once, at mission end)
→ EndingMission → back to WaitingForEgg, either by:
exiting (watchdog relaunches a fresh process — the original games), or
resetting in-process to a dark waiting state
3.5 The egg (mission definition)
The console streams the mission as EggFileMessage chunks of ≤1000 bytes
(index, totalLength, thisLength, buffer). Reassemble by index until
totalLength bytes have arrived, then send AcknowledgeEggFileMessage and
move to LoadingMission.
Content: ASCII, INI-style sections, with every key=value separated by NUL
(\0) on the wire (the console builds it with \n separators and replaces them
before encoding). Parse by section name — section order is not guaranteed.
General shape (full field-by-field spec for an existing game:
410console/battletech-port/BATTLETECH-PORT-SPEC.md §2):
[mission] adventure= map= scenario= time= weather= temperature= length=...
[pilots] pilot=<podIP> ← one line per participant
[<podIP>] hostType= name= vehicle= dropzone= color= ... ← per-participant
[ordinals] 1st–4th place plasma bitmaps (128×32)
[BitMap::Large::<pilot>] 128×32 pilot-name plasma bitmap
[BitMap::Small::<pilot>] 64×16 variant
- Participants are keyed by pod IP.
hostTypeassigns the pod's role:0= game machine,2= mission review / camera,3= console (Console/TeslaConsole/HostType.cs). - The
[BitMap::*]/[ordinals]sections are pre-rendered graphics for the cockpit's 128×32 plasma scoreboard — the console authors them; your game just forwards them to the plasma display if the cockpit has one. - Egg content is game-specific; the envelope above (chunking, ack, NUL delimiting, sections) is fixed.
3.6 Mission end, events, results
- In-mission events are pod → console
MungaMessages. Red Planet sendsScored / Killed / Damaged / Boost / ScoreUpdate; BattleTech's set maps its DamageMatrix/KillMarker model. Your game defines its own set, but the console module (§3.7) must know how to decode it — coordinate the two. - Mission end / egress: the console is the sole mission timekeeper. It
sends one
StopMissionwhen mission time expires. Any end-of-mission ritual (RP/BT hold pilots in the cockpit ~30 s of egress) is the game's own behavior after receiving it — the console does not send a second stop. After egress the game must end up back in a dark waiting state reportingWaitingForEgg, ready for the next group. The original games get there by exiting — the launcher watchdog relaunches a fresh process (§2.3) and the console reconnects — but staying resident and resetting in-process is equally valid (FireStorm-style); the console only acts on the state you report and tolerates either a dropped-and-reconnected or a continuously open socket. AbortMissionis the operator bailing out: return toWaitingForEgg(viaAbortingMission), no results expected.
3.7 The console side of Tier 1
Commanding a game isn't only pod-side work — the console needs a per-game module
that builds the egg and provides the mission UI:
Console/TeslaConsole.<YourGame>/ mirroring
Console/TeslaConsole.RedPlanet/
(mission classes + ToEggString(), a config XML catalog of maps/vehicles/
scenarios, the game pane driving MungaGame). The BattleTech port spec
(BATTLETECH-PORT-SPEC.md)
is the worked example of adding one — budget for it in your plan, and open the
conversation with the TeslaSuite maintainers early (ApplicationID assignment,
event vocabulary, egg fields).
3.8 If your game already has its own control protocol
Precedent: BattleTech FireStorm (MechWarrior 4) speaks CTCL on ports
1000/1001, not Munga — researched and parked in
FIRESTORM-CTCL.md. The standing guidance: do not teach
the console a second protocol. Put an adapter on the pod that speaks Munga to
the console and your native protocol to the game (TeslaRel410 does exactly this:
pod-launch.exe supervises the DOS game and fronts for it). The console then
sees a normal Munga pod.
3.9 Escape hatch: a dedicated game console
If Munga control — direct or through a §3.8 adapter — would be limiting or detrimental to the game experience (the mission model doesn't map to the egg/state machine, the game needs richer or real-time operator control than load/run/stop, the adapter would cost fidelity), you may instead build a separate game console: a purpose-built operator application for your game, run on the console computer alongside TeslaConsole. FireStorm is the precedent — MechWarrior 4 venues ran their own dedicated console rather than bending the game to the Tesla mission model (FIRESTORM-CTCL.md).
Rules if you take this path:
- It ships inside the same deployment zip as the pod-side game. One Install
Product archive is the whole product — no separate installer, no side-channel
distribution. Put it in a subfolder of your product
(e.g.
YourGame\GameConsole\); the pod-side extraction just carries the folder along, and the operator runs it from that same archive on the console machine. - It runs on the console computer, never on pods.
- TeslaConsole still owns deployment and process lifecycle (§2): install/uninstall, launch/kill, and the watchdog all stay on the launcher channel. Your game console owns only in-game command — it is a replacement for §3.1–3.7, not for §2.
- Don't collide with the suite's ports on either end: 1501, 53290, 53291/53292 are spoken for.
4. Presentation: live camera, mission review, score sheets
A pod bay is more than cockpits: spectators watch the current game on a bay display, players coming out of the pods watch a replay at the mission-review station, and everyone walks away with a printed score sheet. Plan for all three.
4.1 The bay address plan
Modern bay deployments follow the FireStorm/CTCL site layout: squads of eight
pods per address decade, with the 9/10 slots of each decade reserved for
stations.
| Address | Station |
|---|---|
x.x.x.1–8 |
Pods, squad 1 |
x.x.x.9 |
Live camera |
x.x.x.10 |
Operator console |
x.x.x.11–18 |
Pods, squad 2 |
x.x.x.19 |
Mission review |
x.x.x.20 |
Score-sheet printer |
x.x.x.21–28 |
Pods, squad 3 |
x.x.x.31–38 |
Pods, squad 4 — and so on: pods at 1–8 of every further decade, 9/10 slots reserved for future stations |
(Legacy RP/BT-era installs used the 200.0.0.x scheme with the console at
.1 and pods from .11. The suite doesn't hardcode either — addressing is
per-site via Manage Site — but new bays should follow the plan above.)
4.2 Live camera — presenting the current game
The live-cam station runs your game exe in a spectator role, rendering the running mission on the bay display. The RP/BT model, which the console generalizes:
- The station is a pod like any other — installed, launched, watchdogged —
whose catalog launch entry has
hostType="LiveCamera"; RP/BT pass a-lcflag so the exe boots into the camera role (§2.2). - The console enrolls every enabled camera station in the mission as a
camera participant: it receives the same egg and walks the same Munga
state machine as a game pod, but its participant block says
hostType=2,vehicle=camera,name=Camera,loadzones=0(RPCamera.cs). - Your game's job in the role: observe the running mission and render a spectator view — no cockpit input, no scoring participation. The camera's view of the action travels over the game's own network traffic between pods; the console only issues the egg and state commands.
4.3 Mission review — replaying the finished game
The mission-review station replays the completed mission for the players
who just climbed out. To the console it looks exactly like the live cam — a
hostType="MissionReview" catalog entry (-mr flag in RP/BT), enrolled as an
egg camera participant — the difference is entirely inside your game: the MR
role captures the mission as it runs and replays it on demand afterwards.
Replay capture and playback are the game's responsibility. The console does
not record or transport replay data: RP/BT capture from the game's own network
traffic; FireStorm writes {guid}.mr files pod-side and its console points the
review station at them (FIRESTORM-CTCL.md).
4.4 Score sheets
Players get a printed score sheet. How it works for a Tier 1 game:
- During the mission the console builds results from your in-mission event messages (§3.6) via the game module's mission recorder (RPMissionRecorder.cs). The event vocabulary is what makes score sheets possible — a game that reports no events has nothing to print.
- The game module renders the results as a print document
(RPPrintDocument.cs).
The operator's Auto Print checkbox prints each mission as it ends;
Print Last Mission reprints on demand. Output goes to the bay's
score-sheet printer (
x.x.x.20, a network printer configured on the console machine). - A §3.9 dedicated game console owns its own scoring and printing (FireStorm:
the game writes
{guid}.prfiles pod-side, the console hands them to themw4printhelper) — it should print to the same bay printer.
5. Pod environment reference
| Fact | Value |
|---|---|
| OS range | Windows XP SP3 → Windows 11, one binary set (.NET products: net40) |
| Session | auto-login kiosk account Firestorm (Administrator, UAC off) |
| Games root | C:\Games\<Product>\ |
| Launcher state | <CommonAppData>\TeslaLauncher\ (LaunchApps.xml, key store, log) |
| Network plan | squads of 8 pods per address decade; live cam .9, console .10, mission review .19, printer .20 — see §4.1 |
| Ports | 1501 TCP game control (your game listens) · 53290 TCP launcher RPC · 53291/53292 UDP first-boot provisioning |
| Cockpit extras | 128×32 plasma scoreboard on COM2; RIO board cockpit controls — native integration preferred, RIOJoy shim optional (§5.1) |
| Firewall | disabled by the pod installer |
5.1 Cockpit controls: the RIO board
The cockpit's controls come in through the RIO board. Two integration paths:
- Native RIO integration — preferred. Talk to the board directly, as the
original games do (FireStorm's
CRIOMAIN.CPPis the documented precedent). This is the only path that reaches the board's output side — the cockpit lighting — which native games get as a feedback channel to the player. A game that only reads a gamepad can't touch it. - RIOJoy — optional shim. A deployable catalog product that feeds RIO board input into a virtual gamepad (RioGamepad HID via the ViGEmBus driver; Win10+ only) with per-game mapping profiles. Zero game-side changes — if your engine already reads a standard gamepad, a RIOJoy profile gets a cockpit playable. Input only: no lighting, no feedback.
A RIOJoy profile is a fine way to get playable quickly during development; plan on native RIO integration for the real deployment so the cockpit lighting works for your game. (The board interface and the feeder implementation live in the RIOJoy repo.)
6. Testing your integration without a pod bay
- vPOD/ is a full software pod: it emulates the launcher side
(install your zip into a real
C:\Games, launch/kill with the real watchdog semantics) and the Munga side. Two ways to use it:- Test your package/catalog entry: run vPOD on any machine, provision it from the console (README walk-through), Install Product your zip, launch — with "Actually launch apps" checked your real exe runs.
- Test your Munga implementation: point the console at your game instead
of vPOD (the shipped site has a
localpod at127.0.0.1— run your exe with-net 1501on the console machine). vPOD's egg viewer is also handy: drive a mission at vPOD, copy the egg it captures, and use it as a fixture for your parser.
- Console-side changes are pinned by the differential suite (Console/tests/TeslaConsole.DiffTests).
7. Checklists
To make your game deployable (Tier 0):
- Package zip:
<Product>\folder + optional rootpostinstall.bat, optional<Product>\pre-uninstall.bat(§2.1) - Runs on your target pod OS range — XP SP3 support is a nice-to-have that reaches the original hardware (x86 + net40 if .NET); if skipped, catalog comment says modern-pods-only
Apps.xml<Product>entry, key convention respected (§2.2)- Cold start reaches gameplay/ready state unattended (kiosk + watchdog)
- Cockpit controls wired: native RIO integration preferred (enables lighting feedback), RIOJoy profile acceptable (§5.1)
- Install → launch → kill → uninstall verified against vPOD
To make your game commandable (Tier 1), additionally (or, if Munga control would hurt the game: a dedicated game console per §3.9, shipped in the same deployment zip):
- TCP server on 1501 (
-netarg), Munga framing per §3.2 StateQuery→StateResponsewith an agreedApplicationID- Egg reassembly +
AcknowledgeEggFile+ state walk toRunningMission Stop/Abort/Suspend/Resumehonored; after mission end, back to a darkWaitingForEgg— by exiting (watchdog relaunch) or by in-process reset- Event vocabulary agreed with the console module — rich enough for score sheets (§4.4)
- Live-camera and mission-review roles: spectator rendering + replay capture/playback in the game (§4.2–4.3), LC/MR catalog entries (§2.2)
- Console game module exists or is planned (§3.7)