Terminology: BattleTech missions, not races

"Race" is Red Planet's term (it's a racing game) and rode in with the
RPL4LOBBY port. In BattleTech the gameplay unit is a MISSION. Sweep it out
of the front-end / lobby / marshal code + the roadmap and steamification
digest: rename the public API BTLobby_Push/PullRaceResults ->
Push/PullMissionResults, the internal PrimeHostedRace -> PrimeHostedMission,
the deferred marshal InstallNetworkRace -> InstallNetworkMission, and the
user-facing strings ("STEAM MISSION LOBBY", "L A U N C H  M I S S I O N",
"HOST/JOIN STEAM MISSION"). Comments follow. No behavior change; both gates
(default + BT412_STEAM) still build + link clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 23:29:38 -05:00
co-authored by Claude Opus 4.8
parent a2949166ea
commit 101562978d
8 changed files with 49 additions and 49 deletions
+5 -5
View File
@@ -154,7 +154,7 @@ Verified after merge: clean build; solo front-end mode; loopback MP through the
- In-process **LocalConsole marshal** (`btl4console.{hpp,cpp}`): NOT a thread — it runs on
the engine's per-frame hook (`gPerFrameHook`, added to `engine/MUNGA/APPMGR.*`, called on
the game thread in `RunMissions`), so its `application->Dispatch(StopMissionMessage)` is
safe. Owns the mission clock and stops the race at the chosen `length=`; snapshots
safe. Owns the mission clock and stops it at the chosen `length=`; snapshots
per-pilot kills/deaths at the stop (`BTResolveRosterPilot`/`BTPilotKills`/`BTPilotDeaths`).
- **Score intake + results screen**: BT's own kills/deaths model (no `gConsoleScoreSink`
that is RP-layer; BT has no working mission-end wire flow, msgID 0x18 stays a stub). A
@@ -163,7 +163,7 @@ Verified after merge: clean build; solo front-end mode; loopback MP through the
- **Single-binary loop** = RELAUNCH per mission (`CreateProcessW` after the marshal stop),
NOT an in-process `for(;;)` — in-process re-init crashes on the stale reconstructed
`gBT*` globals (cdb-traced to `BTL4VideoRenderer::MakeEntityRenderables`
`gBTTerrainEntity`). Verified: 3 distinct PIDs across the menu→race→results→menu cycle.
`gBTTerrainEntity`). Verified: 3 distinct PIDs across the menu→mission→results→menu cycle.
- WinMain launch-mode branch (`BTFrontEnd_LastLaunchMode()`): 0 solo, 1 lobby host, 2
lobby member (see Phase 6).
- **Phase 6 (Steam lobby) — CODE LANDED 2026-07-16; live 3-machine test DEFERRED.**
@@ -171,16 +171,16 @@ Verified after merge: clean build; solo front-end mode; loopback MP through the
— stubs without `BT412_STEAM`, full `ISteamMatchmaking` room under it (both configs
link clean). What works: the room screen; member data (`ip`/`cp`/`gp`/`nm`/`vh`/`cl`/`bd` =
FakeIP + fake console/game ports + persona + mech/color/badge); the nonced `go` launch
roster → `SteamNetTransport_RegisterPeer` for every peer; `Push`/`PullRaceResults` over
roster → `SteamNetTransport_RegisterPeer` for every peer; `Push`/`PullMissionResults` over
lobby data (`res` key) rebuilding the shared score sheet via `BTLocalConsole_ClearResults`/
`InjectResult`. The menu offers HOST/JOIN when `BTLobby_Available()` (Steam transport up,
installed by WinMain on `BT412STEAM` env). A host launch bakes every member into the egg's
`[pilots]` (mesh `FakeIP:1502` addresses, round-robin drop zones) via
`BTFrontEnd_SetHostedPilots`; a member enters as a network pod on `:1501`
(`SetNetworkCommonFlatAddress` + `gConsoleLossEndsMission=True`).
**Deferred / not built:** the host→member **wire egg-feed marshal** (`InstallNetworkRace`
**Deferred / not built:** the host→member **wire egg-feed marshal** (`InstallNetworkMission`
the owner streaming eggs + RunMission to remote pods, as `tools/btconsole.py` does). Without
it a member waits for a console connection nothing supplies, so a live host+member race
it a member waits for a console connection nothing supplies, so a live host+member mission
is blocked on it. The lobby object does NOT survive the per-mission relaunch (the
single-binary limitation — everyone returns to a fresh menu, not the room).
`docs/STEAM-3-MACHINE-TEST.md` for BT is not yet authored.
+6 -6
View File
@@ -139,7 +139,7 @@ identical-to-baseline → take RP412's file verbatim, diverged → hand-apply th
console on its own thread over NetTransport — egg chunks + ACK, RunMission ×2
(mirror btconsole.py), stays connected (fixes the console-loss listener bug for
solo), owns the mission clock, StopMission at `length=` expiry, launcher-role loop,
`InstallNetworkRace` marshal for remote pods.
`InstallNetworkMission` marshal for remote pods.
- **5c Score intake — genuinely new for BT** (no mission-end flow exists): a
`gConsoleScoreSink` equivalent through `BTPlayer`; v1 results screen = kills/deaths
per pilot from the Comm-MFD `pilotList` tally at stop.
@@ -160,7 +160,7 @@ identical-to-baseline → take RP412's file verbatim, diverged → hand-apply th
> `Application::StopMissionMessage` at the chosen length; it snapshots per-pilot
> kills/deaths at the stop (BT's own score model — msgID 0x18 stays a stub). The
> single-binary loop is a RELAUNCH per mission (`CreateProcessW`; avoids the stale
> `gBT*`-global re-init crash), showing a GDI "MISSION COMPLETE" scoreboard between races.
> `gBT*`-global re-init crash), showing a GDI "MISSION COMPLETE" scoreboard between missions.
> WinMain (`game/btl4main.cpp`) wires front-end-mode detection, the marshal arm, the
> results screen + relaunch, and the launch-mode branch (solo/host/member). Details in
> `context/steamification.md`.
@@ -170,7 +170,7 @@ identical-to-baseline → take RP412's file verbatim, diverged → hand-apply th
- **`game/reconstructed/btl4lobby.cpp`** (skeleton: `RPL4LOBBY.cpp` — mechanics
generic): lobby owner = console; member data = FakeIP + fake console/game ports +
persona + loadout (**mech**/color/badge); nonced launch roster → `RegisterPeer` all →
owner hosts via egg+marshal, members join as network pods; Push/PullRaceResults
owner hosts via egg+marshal, members join as network pods; Push/PullMissionResults
distribute the score sheet. FE gains HOST/JOIN screens.
- Author `docs/STEAM-3-MACHINE-TEST.md` for BT (adapt RP412's; keep its gotchas:
disable Steam Input on 480, one Steam account per machine, abort key).
@@ -183,14 +183,14 @@ identical-to-baseline → take RP412's file verbatim, diverged → hand-apply th
> `BT412_STEAM`, full ISteamMatchmaking room under it). Built: the room screen (green-on-black,
> like the menu), member data exchange (FakeIP + fake console/game ports + persona +
> mech/color/badge), the nonced "go" launch roster → `SteamNetTransport_RegisterPeer` all peers,
> and Push/PullRaceResults over lobby data. The menu shows HOST/JOIN when `BTLobby_Available()`
> and Push/PullMissionResults over lobby data. The menu shows HOST/JOIN when `BTLobby_Available()`
> (Steam transport up); a launch routes through `BTFrontEnd_LastLaunchMode()`: **host** builds the
> egg with every member as a `[pilots]` entry + owns the marshal clock, **member** enters as a
> network pod on `:1501` (`SetNetworkCommonFlatAddress` + `gConsoleLossEndsMission`). WinMain
> installs the Steam transport on `BT412STEAM` env. **Remaining / deferred:** the host→member
> **wire egg-feed marshal** (`InstallNetworkRace` — the owner streaming eggs to remote pods) is
> **wire egg-feed marshal** (`InstallNetworkMission` — the owner streaming eggs to remote pods) is
> NOT built; a member currently waits for a console connection that only that marshal provides, so
> a live host+member race needs it. The lobby OBJECT does not survive the per-mission relaunch
> a live host+member mission needs it. The lobby OBJECT does not survive the per-mission relaunch
> (documented single-binary limitation — everyone lands on a fresh menu, not back in the room).
> `docs/STEAM-3-MACHINE-TEST.md` for BT is not yet authored. Details in `context/steamification.md`.
+7 -7
View File
@@ -370,7 +370,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
// STEAM NETWORKING (BT412_STEAM build + BT412STEAM env): bring the Steam
// transport up BEFORE the front end so the lobby is available (its HOST/JOIN
// buttons appear only when a FakeIP was allocated) and the race mesh routes
// buttons appear only when a FakeIP was allocated) and the mission mesh routes
// over SDR/FakeIP. Any failure silently leaves the process on Winsock TCP.
#ifdef BT412_STEAM
if (getenv("BT412STEAM") != NULL && atoi(getenv("BT412STEAM")) != 0)
@@ -549,8 +549,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
extern void BTFrontEnd_ShowResults();
extern void BTLocalConsole_Install(int mission_seconds);
extern int BTLocalConsole_MissionCompleted();
extern void BTLobby_PushRaceResults();
extern void BTLobby_PullRaceResults();
extern void BTLobby_PushMissionResults();
extern void BTLobby_PullMissionResults();
int launch_mode = 0; // 0 solo, 1 lobby host, 2 lobby member
if (front_end_mode)
@@ -649,15 +649,15 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
// The mission ran (not the menu-closed early return) if we front-end-moded
// and the window still lives (a user window-close destroys hWnd -> quit, no
// relaunch). Solo/host end via the local marshal; a member ends when the
// host stops the race (console-loss), so it has no local completion flag.
// host stops the mission (console-loss), so it has no local completion flag.
bool mission_ran = front_end_mode && IsWindow(hWnd)
&& (launch_mode == 2 || BTLocalConsole_MissionCompleted());
if (mission_ran)
{
// Distribute (host) or collect (member) the shared score sheet over the
// lobby so every pod shows the same board; solo needs neither.
if (launch_mode == 1) BTLobby_PushRaceResults();
else if (launch_mode == 2) BTLobby_PullRaceResults();
if (launch_mode == 1) BTLobby_PushMissionResults();
else if (launch_mode == 2) BTLobby_PullMissionResults();
// Scoreboard first (kills/deaths snapshotted at the stop), then relaunch.
ShowWindow(hWnd, SW_HIDE);
@@ -672,7 +672,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
std::cout << "[launcher] mission ended -- relaunched for the next race"
std::cout << "[launcher] mission ended -- relaunched for the next mission"
<< std::endl << std::flush;
}
}
+2 -2
View File
@@ -91,7 +91,7 @@ namespace
(GetTickCount() - gStartTick) >= (unsigned long)(gMissionSeconds * 1000))
{
// The console clock expired: snapshot the final scores (the
// game state is still live here), then stop the race exactly
// game state is still live here), then stop the mission exactly
// as the arcade console did (StopMissionMessage on our pod).
DEBUG_STREAM << "[marshal] time expired -- stopping mission\n" << std::flush;
SnapshotResults();
@@ -114,7 +114,7 @@ void BTLocalConsole_Install(int mission_seconds)
gStartTick = 0;
gWatched = 0;
// Marshaled: the in-process console owns the clock, so a console-loss
// (there is none locally) would end the race -- harmless for solo.
// (there is none locally) would end the mission -- harmless for solo.
gConsoleLossEndsMission = True;
gPerFrameHook = &ConsoleTick;
DEBUG_STREAM << "[marshal] installed (length " << mission_seconds << "s)\n" << std::flush;
+4 -4
View File
@@ -259,7 +259,7 @@ namespace
}
//---------------------------------------------------------------------------//
// Defaults: a solo race on grass, one local pilot in a Black Hawk.
// Defaults: a solo mission on grass, one local pilot in a Black Hawk.
//---------------------------------------------------------------------------//
void BTFeMission_Default(BTFeMission *mission)
{
@@ -369,7 +369,7 @@ int BTFeMission_WriteEgg(const BTFeMission *mission, const char *path)
}
//===========================================================================//
// THE MINICONSOLE MENU -- the in-game race setup that replaces the operator
// THE MINICONSOLE MENU -- the in-game mission setup that replaces the operator
// console. A GDI-painted green-on-black terminal (RP412 RPL4FE pattern): the
// catalogs render as clickable columns, a pilot-name edit box, and a LAUNCH
// button; clicking LAUNCH fills a BTFeMission from the selections and writes
@@ -424,8 +424,8 @@ const char *ItemName(int g, int i) {
case GColor: return kMenuColors[i].name; case GTime: return kMenuTimes[i].name;
case GWeather: return kMenuWeather[i].name; case GLength: return kMenuLengths[i].name;
case GLaunch: return "L A U N C H";
case GHost: return "HOST STEAM RACE";
case GJoin: return "JOIN STEAM RACE";
case GHost: return "HOST STEAM MISSION";
case GJoin: return "JOIN STEAM MISSION";
}
return "";
}
+1 -1
View File
@@ -1,7 +1,7 @@
//===========================================================================//
// btl4fe.hpp -- BT412 in-game front end: local mission-egg builder.
//
// Replaces the external operator console for solo / host play: a race is
// Replaces the external operator console for solo / host play: a mission is
// configured from an in-process catalog and the mission egg is built LOCALLY
// (the exact NotationFile the console used to stream), then fed through the
// standard -egg load path (L4Application::SetEggNotationFileName).
+12 -12
View File
@@ -3,7 +3,7 @@
//
// Ported from RP412 RPL4LOBBY: the ISteamMatchmaking room, member loadout
// exchange (FakeIP + ports + persona + mech/color/badge), the launch roster,
// and the post-race score sheet. Compiled to stubs without BT412_STEAM.
// and the post-mission score sheet. Compiled to stubs without BT412_STEAM.
//===========================================================================//
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
@@ -23,8 +23,8 @@ int BTLobby_InRoom() { return 0; }
int BTLobby_Host(void *, void *) { return LobbyRoomLeft; }
int BTLobby_Join(void *, void *) { return LobbyRoomLeft; }
int BTLobby_Room(void *, void *) { return LobbyRoomLeft; }
void BTLobby_PushRaceResults() { }
void BTLobby_PullRaceResults() { }
void BTLobby_PushMissionResults() { }
void BTLobby_PullMissionResults() { }
#else
@@ -45,8 +45,8 @@ extern int BTLocalConsole_GetResult(int index, int *kills, int *deaths);
extern void BTLocalConsole_ClearResults();
extern void BTLocalConsole_InjectResult(int slot, int kills, int deaths, const char *name);
extern const char *BTLocalConsole_GetResultName(int slot);
// (The remote-pod network-race marshal -- collating every pod's scores over the
// wire -- is deferred; the owner publishes its local snapshot for now.)
// (The remote-pod network-mission marshal -- collating every pod's scores over
// the wire -- is deferred; the owner publishes its local snapshot for now.)
namespace
{
@@ -160,9 +160,9 @@ namespace
members[i].gamePort, members[i].id.ConvertToUint64());
}
// Owner side: prime the hosted-race env (BT412HOSTPODS/ADDR/PORT) + the
// Owner side: prime the hosted-mission env (BT412HOSTPODS/ADDR/PORT) + the
// hosted-pilot loadouts the front end feeds into the egg.
void PrimeHostedRace(MemberInfo *members, int count) {
void PrimeHostedMission(MemberInfo *members, int count) {
static char pods_env[512], addr_env[64], port_env[32];
CSteamID self = SteamUser()->GetSteamID();
FEHostedPilot pilots[kMaxLobbyMembers]; int pilot_count = 0;
@@ -205,7 +205,7 @@ namespace
FillRect(mem, &cl, (HBRUSH) GetStockObject(BLACK_BRUSH)); SetBkMode(mem, TRANSPARENT);
SelectObject(mem, room->titleFont); SetTextColor(mem, kGreenBright);
RECT title = cl; title.top = cl.bottom/28; title.bottom = title.top + cl.bottom/10;
DrawTextA(mem, "STEAM RACE LOBBY", -1, &title, DT_CENTER|DT_TOP|DT_SINGLELINE);
DrawTextA(mem, "STEAM MISSION LOBBY", -1, &title, DT_CENTER|DT_TOP|DT_SINGLELINE);
SelectObject(mem, room->textFont);
int row_h = cl.bottom/16, top = cl.bottom/4, left = cl.right/4, right = 3*cl.right/4;
char text[128];
@@ -226,7 +226,7 @@ namespace
:"Waiting for the host to launch...", -1, &hint, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
HBRUSH frame = CreateSolidBrush(kGreenBright);
if (IsOwner()) { FrameRect(mem, &room->launchRect, frame); SetTextColor(mem, kGreenBright);
DrawTextA(mem, "L A U N C H R A C E", -1, &room->launchRect, DT_CENTER|DT_VCENTER|DT_SINGLELINE); }
DrawTextA(mem, "L A U N C H M I S S I O N", -1, &room->launchRect, DT_CENTER|DT_VCENTER|DT_SINGLELINE); }
FrameRect(mem, &room->leaveRect, frame); SetTextColor(mem, kGreenBright);
DrawTextA(mem, "LEAVE LOBBY", -1, &room->leaveRect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
DeleteObject(frame);
@@ -311,7 +311,7 @@ namespace
}
SteamMatchmaking()->SetLobbyData(gLobby, kGoKey, go);
RegisterRoster(room.members, room.memberCount);
PrimeHostedRace(room.members, room.memberCount);
PrimeHostedMission(room.members, room.memberCount);
outcome = LobbyLaunchHost; break;
}
}
@@ -391,7 +391,7 @@ int BTLobby_Room(void *instance, void *main_window) {
return RunRoom((HINSTANCE)instance, (HWND)main_window);
}
void BTLobby_PushRaceResults() {
void BTLobby_PushMissionResults() {
if (!gInLobby || !IsOwner() || BTLocalConsole_ResultCount() == 0) return;
SteamAPI_RunCallbacks();
char sheet[700]; sprintf(sheet, "%d:", gLastGoNonce);
@@ -405,7 +405,7 @@ void BTLobby_PushRaceResults() {
gShownResultsNonce = gLastGoNonce;
}
void BTLobby_PullRaceResults() {
void BTLobby_PullMissionResults() {
if (!gInLobby || IsOwner() || gLastGoNonce == gShownResultsNonce) return;
const char *sheet = NULL; DWORD deadline = GetTickCount() + 8000;
for (;;) {
+12 -12
View File
@@ -6,10 +6,10 @@
// console/game ports + persona + loadout (mech/color/badge) as lobby member
// data; the lobby OWNER is the console. Launching writes a "go" roster into
// lobby data -- each pod registers every peer with the Steam transport and
// enters the race: the owner through the hosted-race marshal (it builds the
// egg and marshals the members over the wire), the members as network pods
// waiting for the owner's console connection. The lobby OBJECT outlives races
// (the single-binary payoff -- after a race everyone lands back in the room).
// enters the mission: the owner through the hosted-mission marshal (it builds
// the egg and marshals the members over the wire), the members as network pods
// waiting for the owner's console connection. The lobby OBJECT outlives
// missions (the single-binary payoff -- afterward everyone lands back in the room).
//
// Compiled to stubs without BT412_STEAM (single-player + LAN -net unaffected).
//===========================================================================//
@@ -20,26 +20,26 @@ enum BTLobbyOutcome
{
LobbyRoomLeft = 0, // player left the lobby -> back to the menu
LobbyRoomClosed, // window went away -> quit
LobbyLaunchHost, // owner launched: hosted-race path is primed
LobbyLaunchMember // owner launched: enter the race as a network pod
LobbyLaunchHost, // owner launched: hosted-mission path is primed
LobbyLaunchMember // owner launched: enter the mission as a network pod
};
// True when the Steam transport is up (the lobby UI is offered in the menu).
int BTLobby_Available();
// True while we sit in a lobby (races return to the room).
// True while we sit in a lobby (missions return to the room).
int BTLobby_InRoom();
// Create a lobby / find-and-join one, then run the room screen.
int BTLobby_Host(void *instance, void *main_window);
int BTLobby_Join(void *instance, void *main_window);
// Re-enter the room of the lobby we are already in (post-race).
// Re-enter the room of the lobby we are already in (post-mission).
int BTLobby_Room(void *instance, void *main_window);
// Post-race score sheet: the owner publishes its console's results into lobby
// data; members pull them so the same results screen shows everywhere.
void BTLobby_PushRaceResults();
void BTLobby_PullRaceResults();
// Post-mission score sheet: the owner publishes its console's results into
// lobby data; members pull them so the same results screen shows everywhere.
void BTLobby_PushMissionResults();
void BTLobby_PullMissionResults();
#endif // BTL4LOBBY_HPP