Files
TeslaSuite/FIRESTORM-CTCL.md
T
CydandClaude Opus 5 ad6f566834 Document CTCL: what commanding FireStorm from the console would take
BT4/RP4 speak Munga (TCP 1501); BattleTech FireStorm is a MechWarrior 4
conversion and speaks CTCL instead - pod launcher on TCP 1000, game on
1001. Researched from the FireStorm sources at C:\VWE\firestorm and
written up in FIRESTORM-CTCL.md: roles, framing, message/order tables,
the mission handshake (including how the server pod is designated - the
console picks it positionally, there is no election), and the session
rendezvous by game-name GUID over LAN enumeration.

The console already launches FireStorm via Apps.xml; what it cannot do
is build or run a mission. Three blockers: the bit-packed NMP blob is
the MW4 engine's own MWNetMissionParameters serialization, the mech and
map identifiers (including a 64-bit map CRC) come from installed game
content, and CTCL carries no in-mission telemetry at all.

Decision recorded: a pure C# CTCL client is rejected (the NMP layout is
engine-internal and would need reworking on every FireStorm change);
pod-level control is unnecessary since Site Management already covers
it; teaching MW4 to accept a plain mission spec and build the NMP
pod-side is the option if this is ever picked up. Parked for now.

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

14 KiB
Raw Blame History

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, 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:

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:18534PI.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_NetStartGamePreConnectCTCL_DefaultHostSetup(0)Mech4CreateGame(g_szGameName, pilot, NULL). AdvertiseThisGame = 0 — LAN enumeration only, never published to the Zone.
  • Clients (CTCL_DoJoinGame :1745CTCL_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_BOTSserver only server → _EGR_OkCreateSession CTCL_DoCreateGame
C_SetMechsclients 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 — 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