Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0deb8303ee | ||
|
|
8d6c1e7881 | ||
|
|
ad6f566834 |
@@ -121,6 +121,27 @@
|
|||||||
hostType="None" />
|
hostType="None" />
|
||||||
</Product>
|
</Product>
|
||||||
|
|
||||||
|
<!-- Descent 3 (open-source engine + site-owned GOG assets; C:\VWE\Descent3
|
||||||
|
repo, venue\pack-dist.ps1 builds the package). Pod input arrives through
|
||||||
|
the RIOJoy "Descent 3" profile (RioGamepad HID), merged by the package's
|
||||||
|
postinstall. No {res} token: D3 takes -width/-height, not " -res W H" -
|
||||||
|
resolution is pinned to the pod main screen. Single process, no
|
||||||
|
supervisor; kill = terminate Descent3.exe. The args boot straight into
|
||||||
|
the campaign's first level (phase-0 free flight); when the D3_VENUE
|
||||||
|
Munga build lands, args change to "-venue -nointro{res} -framecap 60"
|
||||||
|
and LC/MR role entries get added (keys ...971/...972 reserved). -->
|
||||||
|
<Product id="B7E6D3A0-52C4-4F19-9A8E-6D40C1F2A970"
|
||||||
|
name="Descent 3"
|
||||||
|
menuText="Descent 3..."
|
||||||
|
hostTypeDialog="false">
|
||||||
|
<Launch key="B7E6D3A0-52C4-4F19-9A8E-6D40C1F2A970"
|
||||||
|
displayName="Descent 3"
|
||||||
|
exe="C:\Games\Descent3\Descent3.exe"
|
||||||
|
args="-pilot pod -nointro -nooutragelogo -fullscreen -width 800 -height 600 -framecap 60 -mission d3 -loadlevel 1"
|
||||||
|
autoRestart="true"
|
||||||
|
hostType="None" />
|
||||||
|
</Product>
|
||||||
|
|
||||||
<!-- TeslaRel410 - the DOSBox-X preservation pods (C:\VWE\TeslaRel410 repo).
|
<!-- TeslaRel410 - the DOSBox-X preservation pods (C:\VWE\TeslaRel410 repo).
|
||||||
The package (emulator\dist\TeslaPod410.zip, built by deploy\package.ps1)
|
The package (emulator\dist\TeslaPod410.zip, built by deploy\package.ps1)
|
||||||
extracts to C:\Games (postinstall.bat + TeslaPod410\); pod-launch.exe is
|
extracts to C:\Games (postinstall.bat + TeslaPod410\); pod-launch.exe is
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ namespace TeslaConsole.DiffTests
|
|||||||
=> _fx.Recovered.Run("CatalogEntry", new[] { _catalog, launchKey, w, h });
|
=> _fx.Recovered.Run("CatalogEntry", new[] { _catalog, launchKey, w, h });
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Catalog_Has_Five_Products_And_Fourteen_Entries()
|
public void Catalog_Has_Six_Products_And_Fifteen_Entries()
|
||||||
=> Assert.Equal("products=5;entries=14",
|
=> Assert.Equal("products=6;entries=15",
|
||||||
_fx.Recovered.Run("CatalogSummary", new[] { _catalog }));
|
_fx.Recovered.Run("CatalogSummary", new[] { _catalog }));
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -101,6 +101,22 @@ namespace TeslaConsole.DiffTests
|
|||||||
@"BattleTech 4.11 MR|f4c957fd-72f7-4c5f-8971-28095007e8d1|C:\Games\BT411\btl4.exe|-net 1501 -mr|C:\Games\BT411|True",
|
@"BattleTech 4.11 MR|f4c957fd-72f7-4c5f-8971-28095007e8d1|C:\Games\BT411\btl4.exe|-net 1501 -mr|C:\Games\BT411|True",
|
||||||
Entry("F4C957FD-72F7-4C5F-8971-28095007E8D1"));
|
Entry("F4C957FD-72F7-4C5F-8971-28095007E8D1"));
|
||||||
|
|
||||||
|
// Descent 3 — open-source engine + site GOG assets (C:\VWE\Descent3 repo,
|
||||||
|
// venue\pack-dist.ps1 package). Single GameClient entry, resolution pinned
|
||||||
|
// in args ({res} absent — D3 takes -width/-height, not " -res W H").
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Descent3_Matches_Expected()
|
||||||
|
=> Assert.Equal(
|
||||||
|
@"Descent 3|b7e6d3a0-52c4-4f19-9a8e-6d40c1f2a970|C:\Games\Descent3\Descent3.exe|-pilot pod -nointro -nooutragelogo -fullscreen -width 800 -height 600 -framecap 60 -mission d3 -loadlevel 1|C:\Games\Descent3|True",
|
||||||
|
Entry("B7E6D3A0-52C4-4F19-9A8E-6D40C1F2A970"));
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Descent3_Resolution_Choice_Has_No_Effect()
|
||||||
|
=> Assert.Equal(
|
||||||
|
@"Descent 3|b7e6d3a0-52c4-4f19-9a8e-6d40c1f2a970|C:\Games\Descent3\Descent3.exe|-pilot pod -nointro -nooutragelogo -fullscreen -width 800 -height 600 -framecap 60 -mission d3 -loadlevel 1|C:\Games\Descent3|True",
|
||||||
|
Entry("B7E6D3A0-52C4-4F19-9A8E-6D40C1F2A970", "1024", "768"));
|
||||||
|
|
||||||
// TeslaRel410 — the DOSBox-X preservation pods. All six entries launch
|
// TeslaRel410 — the DOSBox-X preservation pods. All six entries launch
|
||||||
// pod-launch.exe; the mode arg ("bt"/"rp") selects the game, LC/MR boot
|
// pod-launch.exe; the mode arg ("bt"/"rp") selects the game, LC/MR boot
|
||||||
// identically (the console assigns the role via the egg hostType), and
|
// identically (the console assigns the role via the egg hostType), and
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
# Commanding FireStorm from TeslaConsole — CTCL research
|
||||||
|
|
||||||
|
Researched 2026-07-28. **Status: parked.** No code was written; this is the record of
|
||||||
|
what CTCL is, what integrating it would cost, and which option we'd take if we ever
|
||||||
|
pick it up.
|
||||||
|
|
||||||
|
BT4 and RP4 speak **Munga** (TCP 1501) — the protocol the console already drives via
|
||||||
|
the vendored `Munga Net.dll`. BattleTech **FireStorm** is a MechWarrior 4 total
|
||||||
|
conversion and speaks something else entirely: **CTCL**, the coin-op / LAN-centre
|
||||||
|
control layer the Korean team added to the MW4 engine (every call site is bracketed
|
||||||
|
`// jcem - begin` / `// jcem - end`; the acronym is never expanded in the source).
|
||||||
|
|
||||||
|
Source for everything below is the FireStorm repo at `C:\VWE\firestorm`
|
||||||
|
(`gitea.mysticmachines.com/VWE/firestorm.git`). Paths in this document are relative to
|
||||||
|
`firestorm/`. That repo's `LAUNCHER-AND-MW4.md` already documents the **launcher**
|
||||||
|
link; the **game** link (the mission handshake on port 1001) is documented here for the
|
||||||
|
first time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where we are today
|
||||||
|
|
||||||
|
The console can already **launch** FireStorm on a pod — it is a product in
|
||||||
|
[`Console/RedPlanet/Apps.xml`](Console/RedPlanet/Apps.xml), pointing at
|
||||||
|
`C:\Games\MW4\launcher.exe`. So today a FireStorm pod runs:
|
||||||
|
|
||||||
|
```
|
||||||
|
TeslaLauncher (ours, TCP 53290)
|
||||||
|
└─ launcher.exe (MW4's CTCL agent, listens TCP 1000)
|
||||||
|
└─ MW4.exe -ctcltype 2 (the game, listens TCP 1001) ← started from c:\ctcl.ini [config] run=
|
||||||
|
```
|
||||||
|
|
||||||
|
What the console **cannot** do is build or run a mission. That is the gap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CTCL in one page
|
||||||
|
|
||||||
|
### Roles (`Gameleap/code/ctcls/ctcl_params.h:28-33`)
|
||||||
|
|
||||||
|
| Value | Role | Who | Listens |
|
||||||
|
|-------|------|-----|---------|
|
||||||
|
| `_ECTCL_Launcher` = 0 | pod agent | `Launcher.exe` | 1000 |
|
||||||
|
| `_ECTCL_Console` = 1 | operator console | `MW4.exe` with **no** `-ctcltype` | — (dials out) |
|
||||||
|
| `_ECTCL_Game` = 2 | pod running the game | `MW4.exe -ctcltype 2` | 1001 |
|
||||||
|
| `_ECTCL_CameraShip` = 3 | spectator / camera pod | `MW4.exe -ctcltype 3` | 1001 |
|
||||||
|
| `_ECTCL_None` = 4 | CTCL disabled | `MW4.exe -dragon` | — |
|
||||||
|
|
||||||
|
The console is *the same binary as the game*. It reads `c:\ctcl.ini` `[teslas]` for the
|
||||||
|
pod list and opens two sockets per pod (1000 + 1001). Pods listen; the console never does.
|
||||||
|
|
||||||
|
### Framing (`Gameleap/code/Launcher/mugSocs.cpp`)
|
||||||
|
|
||||||
|
A tiny hand-rolled "MUG" socket library. Frame is:
|
||||||
|
|
||||||
|
```
|
||||||
|
uint16 length (big-endian, = 1 + payload) uint8 cmd payload…
|
||||||
|
```
|
||||||
|
|
||||||
|
Payload fields are described by printf-style format strings
|
||||||
|
(`CPacket::vAssemble`, `mugSocs.cpp:1078`):
|
||||||
|
|
||||||
|
| code | meaning |
|
||||||
|
|------|---------|
|
||||||
|
| `b` `B` | uint8 |
|
||||||
|
| `w` `W` | uint16, network byte order |
|
||||||
|
| `n` `d` `D` | uint32, network byte order |
|
||||||
|
| `f` / `F` / `6` | float / double / int64 (host order) |
|
||||||
|
| `s` | NUL-terminated string, inline |
|
||||||
|
| `S` | uint16 length + NUL-terminated string |
|
||||||
|
| `x` | uint16 length + raw bytes |
|
||||||
|
| `X` | raw bytes, no length prefix |
|
||||||
|
|
||||||
|
### Messages (`Gameleap/code/Launcher/ctcl.h:73-91`)
|
||||||
|
|
||||||
|
| id | name | direction | notes |
|
||||||
|
|----|------|-----------|-------|
|
||||||
|
| 1 | `C_GameInfo` / `S_GameInfo` | console ↔ launcher (:1000) | poll ~1 Hz; reply is `applType, applState, gameState, gameTime, isServer` |
|
||||||
|
| 10 | `C_OrderAppl` | console → launcher **or** game | routed by value: **≥100 → :1000, <100 → :1001** |
|
||||||
|
| 19 | `C_ErrorStartGame` | console → all | abort, back to main menu |
|
||||||
|
| 20 | `C_ReadyStartGame` | console → each pod (:1001) | the big one — NMP blob + that pod's player record |
|
||||||
|
| 21 | `C_BOTS` | console → **server pod only** | full roster incl. bots; doubles as "you are the host, create it" |
|
||||||
|
| 22 | `C_SetMechs` | console → **clients only** | "join now" |
|
||||||
|
| 23 | `C_GetReady` | — | **declared but dead** — no handler in any packet map |
|
||||||
|
| 24 | `C_DoLaunch` | console → server | drop |
|
||||||
|
| 30 | `S_GameReturn` | game → console | progress / error, `_EGR_*` codes |
|
||||||
|
| 40 | `C_SendFile` / `S_SendFile` | both ways | print + mission-review file *notifications* only |
|
||||||
|
| 50 | `C_InviteCOOP` / `S_InviteCOOP` | both ways | coin-op invite; irrelevant to console-run games |
|
||||||
|
|
||||||
|
### Orders (`ctcl_params.h:40-46`)
|
||||||
|
|
||||||
|
| order | value | handled by | effect |
|
||||||
|
|-------|-------|-----------|--------|
|
||||||
|
| `_CTCL_Order_Terminate` | 0 | game | `gos_TerminateApplication()` |
|
||||||
|
| `_CTCL_Order_EndMission` | 1 | game | drop out of the mission |
|
||||||
|
| `_CTCL_Order_Launch` | 100 | launcher | `RunExec()` the `c:\ctcl.ini` `[games]` command line |
|
||||||
|
| `_CTCL_Order_Shutdown` | 101 | launcher | `ExitWindowsEx(EWX_POWEROFF)` |
|
||||||
|
| `_CTCL_Order_Reboot` | 102 | launcher | `ExitWindowsEx(EWX_REBOOT)` |
|
||||||
|
| `_CTCL_Order_Unload` | 103 | launcher | `PostQuitMessage(0)` — quit the launcher |
|
||||||
|
|
||||||
|
### Pod state (`ctcl_params.h:1-26`)
|
||||||
|
|
||||||
|
The launcher has no visibility into the game process; it reads five ints out of
|
||||||
|
`ctcls.dll`'s `.SHARED_DATA` section (a process-spanning shared segment the game writes):
|
||||||
|
`applType` (`_EAT_MW4`), `applState` (PreLaunch/Launched/PostLaunch),
|
||||||
|
`gameState` (Idle/Preparing/Running/Closing), `gameTime` (seconds), `isServer`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The mission handshake
|
||||||
|
|
||||||
|
### Who hosts — the console decides, there is no election
|
||||||
|
|
||||||
|
While validating the player list (`Gameleap/code/mw4/Code/MW4/MW4Shell.cpp:13492-13504`)
|
||||||
|
the console records the first cameraship pod (`:13421`) and the first ordinary pilot pod
|
||||||
|
(`:13432`), then:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (nCameraship != -1) g_nServer = nCameraship; // cameraship hosts if one is playing
|
||||||
|
else g_nServer = n1stTesla; // otherwise the topmost pilot pod
|
||||||
|
```
|
||||||
|
|
||||||
|
**The server designation is positional** — whatever pilot ordering the operator UI shows
|
||||||
|
*is* the host-selection UI. The original console offered no explicit control over it.
|
||||||
|
|
||||||
|
Each pod learns its role from one byte in `C_ReadyStartGame`
|
||||||
|
(`mw4/Code/MW4Application/ctcl.cpp:1544`); the receiver sets `g_bIsServer = (bType == 1)`
|
||||||
|
(`:822`). The server's IP travels in the same packet but is discarded on arrival
|
||||||
|
(`MW4Shell/MWApplication.cpp:18534` — `PI.m_dwAddr = 0; // dwAddr;`).
|
||||||
|
|
||||||
|
### Rendezvous is by name, not address
|
||||||
|
|
||||||
|
The console generates `g_guidGameDatas = gos_GenerateUniqueGUID()` per mission and ships
|
||||||
|
it in `C_ReadyStartGame`; every pod formats it identically into `g_szGameName`
|
||||||
|
(`ctcl.cpp:824-827`).
|
||||||
|
|
||||||
|
- **Server** (`CTCL_DoCreateGame`, `MW4Application.cpp:1708`): `gos_NetStartGame` →
|
||||||
|
`PreConnect` → `CTCL_DefaultHostSetup(0)` → `Mech4CreateGame(g_szGameName, pilot, NULL)`.
|
||||||
|
`AdvertiseThisGame = 0` — LAN enumeration only, never published to the Zone.
|
||||||
|
- **Clients** (`CTCL_DoJoinGame` `:1745` → `CTCL_CheckJoinGame` `:1768`): open the LAN
|
||||||
|
browser, poll `gos_GameIsExist(g_szGameName)` until it appears, then
|
||||||
|
`gos_JoinGame(...)`. 25-second timeout.
|
||||||
|
|
||||||
|
### The sequence (`mw4/Code/MW4Application/ctcl.cpp:1486-1670`, state var `g_nMech4Comm`)
|
||||||
|
|
||||||
|
Its whole purpose is to **serialize create-before-join** — a client that browses before
|
||||||
|
the host exists just burns its timeout.
|
||||||
|
|
||||||
|
| console sends | waits for | pod does |
|
||||||
|
|---|---|---|
|
||||||
|
| `C_ReadyStartGame` → all pods | every pod → `_EGR_PreparingStarted` | stores roster + NMP |
|
||||||
|
| `C_BOTS` → **server only** | server → `_EGR_OkCreateSession` | `CTCL_DoCreateGame` |
|
||||||
|
| `C_SetMechs` → **clients only** | server → `_EGR_OkLaunchReady` | `CTCL_DoJoinGame`, browse + join |
|
||||||
|
| *(operator presses Launch)* | | |
|
||||||
|
| `C_DoLaunch` → server | | everyone drops |
|
||||||
|
|
||||||
|
Failures come back as `_EGR_ErrCreateSession` / `_EGR_ErrJoinSession` on `S_GameReturn`;
|
||||||
|
the console responds by broadcasting `C_ErrorStartGame` (`ctcl.cpp:1520-1533`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The three blockers
|
||||||
|
|
||||||
|
### 1. The NMP blob
|
||||||
|
|
||||||
|
`C_ReadyStartGame` carries an opaque byte array the console produces by calling the MW4
|
||||||
|
engine's own `MWNetMissionParameters::SaveParameters()`
|
||||||
|
(`mw4/Code/MW4/MWApplication.cpp:749-864`) — a **bit-packed** stream: ~60 fields at
|
||||||
|
widths of 1, 2, 3, 5, 7, 8, 16, 32 and 64 bits, then 8 × `TeamParameters` (`:513-535`,
|
||||||
|
another 10 × 32-bit allow-masks each), a byte-align, then eight length-prefixed strings.
|
||||||
|
|
||||||
|
Reproducing it in C# means porting `Stuff::DynamicMemoryStream::WriteBits` bit-for-bit
|
||||||
|
and getting every field width right. **A one-bit drift silently corrupts every field
|
||||||
|
after it**, and the layout changes whenever the FireStorm codebase adds a parameter — the
|
||||||
|
existing `// MSL 5.05 Advance Mode` / `// MSL 5.06 Armor Mode` markers in that function
|
||||||
|
are exactly that happening twice already.
|
||||||
|
|
||||||
|
### 2. Content-derived identifiers
|
||||||
|
|
||||||
|
The CTCL console is a full MW4 install because it needs game data:
|
||||||
|
|
||||||
|
- `m_nMechIndex` / `m_fileID` / `m_recordID` come from the console's own sorted mech
|
||||||
|
resource table (`shl->m_mechIDs`, built from the installed `.erf` content).
|
||||||
|
- `m_mapID` indexes the console's `scenarios[]` table.
|
||||||
|
- `m_mapClientCRC` is a **64-bit CRC of the map's `.mw4` file**
|
||||||
|
(`MWApplication.cpp:7048`) which every pod recomputes and compares (`:7538-7553`);
|
||||||
|
a mismatch makes the pod decide it doesn't have the map.
|
||||||
|
|
||||||
|
So console and pods must run byte-identical content, and any external console needs that
|
||||||
|
table regenerated per FireStorm build.
|
||||||
|
|
||||||
|
### 3. No telemetry
|
||||||
|
|
||||||
|
CTCL gives the console `gameState` and `gameTime` (seconds) and **nothing else** — no
|
||||||
|
kills, no damage, no scores. The whole `BTGame`/`RPGame` model (live scoreboard,
|
||||||
|
`BTMissionRecorder`, `BTPrintDocument`) has no CTCL equivalent.
|
||||||
|
|
||||||
|
FireStorm results instead land as files on the pod: the game writes
|
||||||
|
`{guid}.pr` (print) and `{guid}.mr` (mission review) into `\mw4files`
|
||||||
|
(`mw4/Code/MW4/recscore.cpp:246-249`). `S_SendFile` is only a *notification*; the console
|
||||||
|
reads the file over SMB as `\\<pod>\mw4files\{guid}.pr` and hands it to the `mw4print`
|
||||||
|
helper via `WM_COPYDATA`. Mission review is replayed by sending the path to the
|
||||||
|
cameraship pod flagged `m_bMissionReview`.
|
||||||
|
|
||||||
|
Also free, for the same reason: **plasma displays and the RIO board are the game's own
|
||||||
|
business** on FireStorm pods (`PLASMA_Do`, `mw4/Code/MW4/CRIOMAIN.CPP:468`, gated by
|
||||||
|
`-noplasma`). The console does not drive them, unlike BT4/RP4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Options considered, and the decision
|
||||||
|
|
||||||
|
**A — Pure C# CTCL client in TeslaConsole.** Port the protocol *and* the bit-packed NMP
|
||||||
|
serializer *and* a per-build content-table extractor. No game-side change.
|
||||||
|
**Rejected:** the NMP layout is engine-internal and unversioned, so this would need
|
||||||
|
reworking every time the FireStorm codebase changes. Maintenance nightmare for the
|
||||||
|
value returned.
|
||||||
|
|
||||||
|
**B — Teach MW4 a console-friendly protocol.** We have a working VC6 build environment for
|
||||||
|
FireStorm (`build-env/`, with a verified Release build in `rel.bin/`). Add a message on
|
||||||
|
:1001 that accepts a plain mission spec (map name, rules, per-player mech names) and let
|
||||||
|
the *server pod* build the NMP locally with the engine's own serializer. The console then
|
||||||
|
never touches bit-packing, map CRCs or resource IDs.
|
||||||
|
**Viable — this is the option if we ever do it.** Cost is a FireStorm rebuild and a
|
||||||
|
redeploy to every pod.
|
||||||
|
|
||||||
|
**C — Pod-level control only** (`C_GameInfo` + `C_OrderAppl`: state, launch, terminate,
|
||||||
|
end mission, shutdown, reboot).
|
||||||
|
**Not needed.** Site Management already gives us reboot/shutdown/launch/kill through our
|
||||||
|
own TeslaLauncher; those same functions being built into MW4's `launcher.exe` is
|
||||||
|
redundant with what we already use. And if mission parameters still have to be set on the
|
||||||
|
MW4 console, there is no reason to come back to the TeslaConsole just to start and stop
|
||||||
|
the mission — the operator is already sitting at the machine that can do it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## If B is ever picked up
|
||||||
|
|
||||||
|
Rough shape of the work:
|
||||||
|
|
||||||
|
1. **Game side (C++/VC6).** New packet id on :1001 carrying a text/JSON mission spec.
|
||||||
|
Handler calls the existing `CTCL_DefaultHostSetup` + the `MW4Shell` parameter setters
|
||||||
|
(`MAP_ID_PARAMETER` etc. — they already recompute `m_mapClientCRC` and the mech
|
||||||
|
ResourceIDs locally), then enters the normal `g_nMech4Comm` path. Reuse
|
||||||
|
`C_ReadyStartGame`/`C_BOTS`/`C_SetMechs`/`C_DoLaunch` unchanged between pods.
|
||||||
|
Remember: `ctcl.cpp` exists as **four hand-copied siblings** (Launcher,
|
||||||
|
MW4Application, MW4GameEd2, Tools\AnimScript) — change every copy.
|
||||||
|
2. **Console side (C#).** A `CtclGame` sibling to
|
||||||
|
[`Console/TeslaConsole/MungaGame.cs`](Console/TeslaConsole/MungaGame.cs) — same
|
||||||
|
connect/poll/own shape, ~400 lines — plus a `FSGame` pane modelled on `BTGame`, minus
|
||||||
|
everything telemetry-driven. `Pod`/`Site`/`AppRegistry` and the TeslaLauncher RPC are
|
||||||
|
already protocol-agnostic and need no change.
|
||||||
|
3. **vPOD.** A CTCL mode: listen on 1000/1001, answer `C_GameInfo`, walk the `_EGR_*`
|
||||||
|
ladder. Without it none of this is testable off-cockpit.
|
||||||
|
4. **Results.** Decide whether to parse `.pr` files ourselves or keep shelling out to
|
||||||
|
`mw4print`.
|
||||||
|
|
||||||
|
### Open questions
|
||||||
|
|
||||||
|
- **Which FireStorm build is canonical?** `FS Build V4H`, `FS507C_20160909` and
|
||||||
|
`FS507D_20161015` are all on this machine; content tables and map CRCs differ per build.
|
||||||
|
- **`ctcl.ini` ownership.** CTCL's pod list is hardcoded to `c:\ctcl.ini` and uses the
|
||||||
|
2005-era `200.0.0.x` scheme; TeslaConsole owns pod addressing via `Site`. Either the
|
||||||
|
console writes `ctcl.ini` at Install Product time, or the pod list becomes console-side
|
||||||
|
only (pods only ever read `[games]` + `[config]`).
|
||||||
|
- **Server designation UI.** Positional today; worth making explicit if pods differ in spec.
|
||||||
|
- **Known shipped bug** (`Gameleap/code/Launcher/ctcl.cpp:1310`): the remote-launch table
|
||||||
|
looks up the cameraship command under key `#1`, but every shipped ini writes `*1`. A
|
||||||
|
console `Launch` aimed at a cameraship pod silently does nothing. Cameraships were
|
||||||
|
evidently autostarted, not console-launched.
|
||||||
|
- `_EAT_RP` = 2 exists in `ctcl_params.h` alongside `_EAT_MW4` = 1 — CTCL had a Red Planet
|
||||||
|
application type. Vestigial as far as this repo is concerned; the second `[games]` slot.
|
||||||
|
|
||||||
|
## Source map
|
||||||
|
|
||||||
|
| Thing | Where (relative to `C:\VWE\firestorm`) |
|
||||||
|
|-------|------|
|
||||||
|
| Protocol + manager, game copy | `Gameleap/code/mw4/Code/MW4Application/ctcl.cpp` |
|
||||||
|
| Protocol + manager, launcher copy | `Gameleap/code/Launcher/ctcl.cpp` (built `/D CTCL_LAUNCHER`) |
|
||||||
|
| Constants (roles, orders, states) | `ctcl_params.h` (four copies) |
|
||||||
|
| Message ids + structs | `ctcl.h` |
|
||||||
|
| Socket library / framing | `Gameleap/code/Launcher/mugSocs.cpp`, `mugsocs.h` |
|
||||||
|
| Shared-state DLL | `Gameleap/code/ctcls/ctcls.cpp` |
|
||||||
|
| NMP serialization | `Gameleap/code/mw4/Code/MW4/MWApplication.cpp:749-864` |
|
||||||
|
| Session create/join | `Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp:1708-1800` |
|
||||||
|
| Console mission build | `Gameleap/code/mw4/Code/MW4/MW4Shell.cpp:13369-13548` |
|
||||||
|
| Console UI (shell script) | `Gameleap/mw4/Content/ShellScripts/ConLobby.script` |
|
||||||
|
| Launcher link (already documented) | `LAUNCHER-AND-MW4.md` |
|
||||||
@@ -0,0 +1,510 @@
|
|||||||
|
# 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/TeslaLauncher.cs),
|
||||||
|
[Launcher/MiniZip.cs](Launcher/MiniZip.cs)):
|
||||||
|
|
||||||
|
- **Everything must extract under one `C:\Games\<Product>\` folder** (plus the
|
||||||
|
optional root `postinstall.bat`). Uninstall deletes `C:\Games\<Product>`
|
||||||
|
recursively — don't scatter files elsewhere unless `postinstall.bat` puts them
|
||||||
|
there and `pre-uninstall.bat` removes 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](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](Console/RedPlanet/Apps.xml) (parser:
|
||||||
|
[Console/TeslaConsole/AppRegistry.cs](Console/TeslaConsole/AppRegistry.cs)).
|
||||||
|
Your game ships as one `<Product>` element:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<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`/`-2` to the string — keys parse as
|
||||||
|
`System.Guid` and a suffixed string silently collapses to `Guid.Empty`.
|
||||||
|
- **`{res}`** in `args` expands to ` -res W H` when 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 in `args`.
|
||||||
|
- **`autoRestart="true"`** enables the pod watchdog (§2.3). Almost always what
|
||||||
|
you want for a game client.
|
||||||
|
- **`hostTypeDialog="true"` + per-entry `hostType`** is for games with separate
|
||||||
|
live-camera / mission-review roles (RP/BT use `-lc` / `-mr` flags) — see §4
|
||||||
|
for what those stations do. A plain game ships one entry with
|
||||||
|
`hostType="None"` and `hostTypeDialog="false"`.
|
||||||
|
- XML gotcha: comments in this file must not contain `--` — `XmlDocument.Load`
|
||||||
|
throws 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](Contract/WireContract.cs)).
|
||||||
|
|
||||||
|
### 2.3 Launch / kill / watchdog semantics
|
||||||
|
|
||||||
|
What the launcher does with your entry
|
||||||
|
([Launcher/TeslaLauncher.cs](Launcher/TeslaLauncher.cs)):
|
||||||
|
|
||||||
|
- **LaunchApp** starts `exe` with `args`, working directory = `workingDirectory`
|
||||||
|
or 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 `autoRestart` entry whose process **exits on its own** is
|
||||||
|
relaunched ~2 s later. The original games lean on this for their per-mission
|
||||||
|
cycle: `rpl4opt`/`btl4` **terminate 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](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](vPOD/PodSimulator.cs)** — the pod-side state machine
|
||||||
|
and egg handling.
|
||||||
|
- **[Console/TeslaConsole/MungaGame.cs](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 every
|
||||||
|
`StateResponse`. The enum lives in `Munga Net.dll`: `RPL4 = 0` (Red Planet),
|
||||||
|
`BTL4 = 1` (BattleTech), plus `NDL4`. **A brand-new title needs a new value**
|
||||||
|
agreed with the TeslaSuite side (the console maps `ApplicationID` → 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 `StateQuery` about **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](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**. `hostType` assigns the pod's role:
|
||||||
|
`0` = game machine, `2` = mission review / camera, `3` = console
|
||||||
|
([Console/TeslaConsole/HostType.cs](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 `MungaMessage`s. Red Planet sends
|
||||||
|
`Scored / 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** `StopMission` when 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** reporting
|
||||||
|
`WaitingForEgg`, 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.
|
||||||
|
- `AbortMission` is the operator bailing out: return to `WaitingForEgg`
|
||||||
|
(via `AbortingMission`), 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/](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](410console/battletech-port/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](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](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 `-lc`
|
||||||
|
flag 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](Console/TeslaConsole.RedPlanet/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](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](Console/TeslaConsole.RedPlanet/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](Console/TeslaConsole.RedPlanet/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}.pr` files pod-side, the console hands them to the
|
||||||
|
`mw4print` helper) — 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.CPP` is 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/](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:
|
||||||
|
1. **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.
|
||||||
|
2. **Test your Munga implementation:** point the console at your game instead
|
||||||
|
of vPOD (the shipped site has a `local` pod at `127.0.0.1` — run your exe
|
||||||
|
with `-net 1501` on 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](Console/tests/TeslaConsole.DiffTests)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Checklists
|
||||||
|
|
||||||
|
**To make your game deployable (Tier 0):**
|
||||||
|
|
||||||
|
- [ ] Package zip: `<Product>\` folder + optional root `postinstall.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 (`-net` arg), Munga framing per §3.2
|
||||||
|
- [ ] `StateQuery` → `StateResponse` with an agreed `ApplicationID`
|
||||||
|
- [ ] Egg reassembly + `AcknowledgeEggFile` + state walk to `RunningMission`
|
||||||
|
- [ ] `Stop/Abort/Suspend/Resume` honored; after mission end, back to a dark
|
||||||
|
`WaitingForEgg` — 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)
|
||||||
@@ -60,6 +60,12 @@ Release packages for all three are attached to the
|
|||||||
baselines** — both are now built from source (`Contract/`, `SecureConfig/`).
|
baselines** — both are now built from source (`Contract/`, `SecureConfig/`).
|
||||||
- `Console/RedPlanet/Apps.xml` — the data-driven product catalog (see the console's
|
- `Console/RedPlanet/Apps.xml` — the data-driven product catalog (see the console's
|
||||||
Site Management → Add Product / Register Product on Pods).
|
Site Management → Add Product / Register Product on Pods).
|
||||||
|
- [`GAME-INTEGRATION.md`](GAME-INTEGRATION.md) — the integration spec for games being
|
||||||
|
brought to the pods: package/catalog requirements to **deploy** a game, and the Munga
|
||||||
|
protocol contract to **command** one. Start here when adding a new title.
|
||||||
|
- [`FIRESTORM-CTCL.md`](FIRESTORM-CTCL.md) — research notes on driving **BattleTech
|
||||||
|
FireStorm** (MechWarrior 4) from the console. FireStorm speaks CTCL, not Munga; the
|
||||||
|
document specs that protocol, the three blockers, and why the work is parked.
|
||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user