Net: Steam lobby (btl4lobby) + launch-mode wiring (Workstream C.2)

Port RP412's RPL4LOBBY to BT: an ISteamMatchmaking room stands in for the
arcade Site-Management screen. Compiled both gates -- stubs without
BT412_STEAM, the full room under it (both configs build + link clean).

Lobby (game/reconstructed/btl4lobby.*):
- Room screen (green-on-black, like the menu), owner = console.
- Member data: FakeIP + fake console/game ports + persona + mech/color/badge
  (ip/cp/gp/nm/vh/cl/bd keys).
- Nonced "go" launch roster -> SteamNetTransport_RegisterPeer for every peer.
- Push/PullRaceResults over lobby data rebuild the shared score sheet.

Front end (btl4fe.*): HOST/JOIN buttons when BTLobby_Available() (Steam
transport up); the menu loop exits on a steam action and BTFrontEnd_Run
routes the lobby outcome -- host builds the egg with every member as a
[pilots] mesh entry (BTFrontEnd_SetHostedPilots), member returns launch
mode 2. Results screen prefers the marshal's collated result names.

Marshal (btl4console.*): add GetResultName / ClearResults / InjectResult so
the lobby owner can refill the sheet from the collated wire scores; Result
gains a name field.

WinMain (btl4main.cpp): install the Steam transport on BT412STEAM env
(before the front end, so the lobby is offered); branch on
BTFrontEnd_LastLaunchMode() -- host owns the marshal clock + joins the mesh,
member enters as a network pod on :1501 (SetNetworkCommonFlatAddress +
gConsoleLossEndsMission); Push (host) / Pull (member) results, then relaunch.

CMake: btl4lobby.cpp joins bt410_l4; that lib gets BT412_STEAM + the
Steamworks include under the gate. build-steam/ gitignored.

Deferred (untestable here -- needs Steam + multiple machines): the
host->member wire egg-feed marshal (InstallNetworkRace); a member currently
waits for a console connection nothing supplies, so a live host+member race
is blocked on it. The lobby object does not survive the per-mission
relaunch. docs/STEAM-3-MACHINE-TEST.md for BT not yet authored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 23:18:36 -05:00
co-authored by Claude Opus 4.8
parent 2f5870e486
commit a2949166ea
12 changed files with 769 additions and 31 deletions
+56 -5
View File
@@ -368,6 +368,21 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
std::cout << "BattleTech v4.10 (reconstructed port)" << std::endl << std::flush;
// 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
// over SDR/FakeIP. Any failure silently leaves the process on Winsock TCP.
#ifdef BT412_STEAM
if (getenv("BT412STEAM") != NULL && atoi(getenv("BT412STEAM")) != 0)
{
extern Logical SteamNetTransport_Install();
if (SteamNetTransport_Install())
std::cout << "[steam] transport up -- lobby available" << std::endl << std::flush;
else
std::cout << "[steam] transport unavailable -- TCP fallback" << std::endl << std::flush;
}
#endif
// CPU pin (timing stability). BT_AFFINITY overrides the mask; =0 disables --
// required for multi-instance runs (two instances pinned to core 0 starve
// each other, and the L4NET connect-retry loop busy-waits).
@@ -530,13 +545,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
//=======================================================================//
extern int BTFrontEnd_Run();
extern int BTFrontEnd_LastMissionSeconds();
extern int BTFrontEnd_LastLaunchMode();
extern void BTFrontEnd_ShowResults();
extern void BTLocalConsole_Install(int mission_seconds);
extern int BTLocalConsole_MissionCompleted();
extern void BTLobby_PushRaceResults();
extern void BTLobby_PullRaceResults();
int launch_mode = 0; // 0 solo, 1 lobby host, 2 lobby member
if (front_end_mode)
{
ShowWindow(hWnd, SW_HIDE); // the menu is its own window
ShowWindow(hWnd, SW_HIDE); // the menu (and lobby room) are their own windows
if (!BTFrontEnd_Run())
{
std::cout << "[frontend] menu closed -- exiting" << std::endl << std::flush;
@@ -545,12 +564,33 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
}
ShowWindow(hWnd, SW_SHOW);
launch_mode = BTFrontEnd_LastLaunchMode();
int secs = BTFrontEnd_LastMissionSeconds();
const char *ov = getenv("BT_MISSION_SECONDS");
if (ov != 0) secs = atoi(ov);
BTLocalConsole_Install(secs);
std::cout << "[frontend] menu LAUNCH -> marshal armed (" << secs << "s)"
<< std::endl << std::flush;
if (launch_mode == 2)
{
// LOBBY MEMBER: no local egg -- the mission arrives over the wire
// from the host (the lobby already registered the mesh peers).
// Become a network pod on the session port; the host's marshal owns
// the clock, and console-loss ends our mission when the host stops.
L4Application::SetNetworkCommonFlatAddress(1501);
gConsoleLossEndsMission = True;
std::cout << "[frontend] lobby MEMBER -> network pod on :1501"
<< std::endl << std::flush;
}
else
{
// SOLO or LOBBY HOST: the egg was built locally and the in-process
// marshal owns the mission clock (the host is also the console). A
// host additionally joins the mesh on the session port.
if (launch_mode == 1)
L4Application::SetNetworkCommonFlatAddress(1501);
BTLocalConsole_Install(secs);
std::cout << "[frontend] menu LAUNCH (" << (launch_mode == 1 ? "host" : "solo")
<< ") -> marshal armed (" << secs << "s)" << std::endl << std::flush;
}
}
{
@@ -606,8 +646,19 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
// restarted the pod per mission. A `-egg`/`-net` pass, or a mission that
// ended some other way (window closed), just exits.
//-------------------------------------------------------------------//
if (front_end_mode && BTLocalConsole_MissionCompleted() && IsWindow(hWnd))
// 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.
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();
// Scoreboard first (kills/deaths snapshotted at the stop), then relaunch.
ShowWindow(hWnd, SW_HIDE);
BTFrontEnd_ShowResults();