6.2 KiB
id, title, status, source_sections, related_topics, key_terms, open_questions
| id | title | status | source_sections | related_topics | key_terms | open_questions | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| steam-networking | Steam networking — the BT_STEAM wire (transport + lobby) | living | docs/GLASS_COCKPIT.md §4; docs/STEAM_TEST.md; commits e79e8fa/48ede2f/f703bb1 |
|
|
|
Steam networking — the BT_STEAM wire
Internet MP for the glass cockpit (dev tooling; gate BT_STEAM, requires BT_GLASS).
Status: CODE COMPLETE 2026-07-18; single-machine verified; live 2-machine test pending.
The three layers
- The wire seam (
L4NET.CPP, always compiled): file-localBTNetSend/Recv/Accept/Closewrappers around the 9 raw Winsock sites — pure code motion when OFF; underBT_STEAMeach offers the op to the transport first. Est. delegates:CheckSocket(peer address),OpenConnectionTCP_OPEN head,GetMyAddress(self token). - The transport (
engine/MUNGA_L4/L4STEAMNET.*, gated):ISteamNetworkingSocketspseudo-SOCKETs (0x5EA0xxxx); engine TCP byte stream over reliable-NoNagle messages with per-connection reassembly rings (recv: empty→WSAEWOULDBLOCK, closed→0); listen channels console=0 / game=1 (CreateListenSocketP2P); install on envBT_STEAM_NET=1; degrades to Winsock at every failure. Pump =SteamAPI_RunCallbacksAND socketsRunCallbacks(the general dispatch carries the async results — sockets-only pumping stalls). - The lobby (
game/glass/btl4lobby.*, gated): ISteamMatchmaking room (btl4=1list filter); pilots publish name/mech/color; the host's GO mints the roster and signals.
THE TOKEN MODEL (the FakeIP lesson) [T2]
FakeIP does NOT survive the per-mission process relaunch (live-disproven prior-art assumption:
menu and mission processes got different FakeIPs; the pod timed out dialing its own stale
address). Roster addresses are therefore opaque ipv4-shaped tokens — 169.254.77.N, port
1501=console / 1502=game — minted by the HOST at GO and mapped to Steam identities;
connections ride ConnectP2P(identity, channel). Handoff to mission processes via env:
BT_FE_MYFAKE (own token → GetMyAddress self-match) + BT_FE_STEAMMAP
(ip=steamid64;...). The engine's roster matching, Waterloo swap logic, and self-identification
all run UNCHANGED on tokens.
Testing
Single-machine (verified): lobby-of-1 GO → token map → egg roster token → mission runs over the
stock console ladder. Live 2-machine procedure + checklist: docs/STEAM_TEST.md. AppID 480
(Spacewar) via auto-written gitignored content/steam_appid.txt.
FIRST REAL MULTI-PLAYER VERIFICATION — playtest night 4 (2026-07-27, 4.11.584) [T2 live]
Players ran three back-to-back Steam lobbies, host-and-join, 3+ players, with no operator console involved — the first genuine field exercise of this path. What worked: hosting and joining (Draco, SAURON, somebodee, RajelAran all succeeded; "steam works for self-hosting? neat"), mission launch, damage application (previously suspected dead over Steam — "panels were changing color", "I was damaged"), 2 kills in one round, clean round end, and the next mission launching after it. SAURON: "3 missions ran smooth ... felt smooth and normal". Load after LAUNCH felt "a bit long" (unquantified).
Found, and filed: #68 (a failed JOIN/HOST exits the process silently — the FE returns on any
BTLobby_JoinAndWait failure, so "Steam not running" and a real crash look identical to a player).
Everything else the night surfaced was NOT Steam-specific and is tracked in the general issues:
#67 (replicant torso twist / muzzle placement), #70 (twist after respawn), #55 (heat readout not
reset on death), #69 (Sunder V1 → Denkou naming).
Lesson for triage: a bug found in a Steam lobby is a Steam bug only if it lives in the lobby / token / transport layer. Everything above the seam is the same arcade mesh — the three replication symptoms this night produced would all reproduce on the relay.
THE EXIT CONTRACT (#163 -> #170, settled 2026-08-12) [T2 field-proven]
Process-exit topology for every glass/steam process: flush -> quiesce -> TerminateProcess.
BTSteamNet_ShutdownAll (L4STEAMNET.cpp) is a QUIESCE: gate steamActive FIRST (stops
marshal/game-thread seam re-entry), CloseConnection(..., linger=true) on every connection
("round over", not a peer-side timeout), close listeners, one sockets->RunCallbacks() drain,
Sleep(60) grace. It deliberately never calls SteamAPI_Shutdown(), and no exit path may
return through the CRT (menu-quit included -- it dies via BTOrderlyDie, btl4console.cpp).
The two-failure history, because each fix's reason constrains the other:
- 883 (no teardown at all): bare
ExitProcessran DLL_PROCESS_DETACH with live SNS threads -> intermittent loader-lock deadlock -> "frozen view of how the game ended" + the menu child hung inSteamAPI_Initagainst the wedged half-dead sibling (#163, night15). - 913 (full
SteamAPI_Shutdownbefore TerminateProcess): at round end every peer closes simultaneously, so status events are ALWAYS in flight; Shutdown tears client state down while steamclient's own service thread dispatches them -> that thread calls literal NULL (0xc0000005 access=8 target=0x0, BaseThreadInitThunk root, ZERO btl4 frames) in the window before TerminateProcess (#170, night16: 7 hits / 3 machines / one module-relative stack). - The stable point: TerminateProcess kills all threads atomically with every pointer still valid (no detach, no dispatch window); the Steam client GCs the session on IPC pipe break -- the standard crashed-game path (883 relaunched all night with no Shutdown ever called). The child-hang needed a WEDGED sibling, which TerminateProcess makes structurally impossible.
Forensics rider: the crash filter (btl4main.cpp BTCrashFilter) now prints foreign frames as
<module.dll>+0xOFFSET (VirtualQuery AllocationBase + GetModuleFileName), so a future
foreign-thread crash names its DLL instead of an unresolvable ASLR address.
Key Relationships
- Rides: glass-cockpit miniconsole (marshal has a Steam-wire branch) · Extends: multiplayer (the arcade mesh, unchanged above the seam)