From 75f136e9ff28879e699cb1c1a00d6e4331237549 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 18 Jul 2026 00:05:51 -0500 Subject: [PATCH] KB: step-4 closeout -- Steam wire documented, live-test procedure, router docs/GLASS_COCKPIT.md gains the step-4 ledger (seam/transport/lobby + THE FAKEIP LESSON); NEW context/steam-networking.md topic (+ router row) and docs/STEAM_TEST.md (the 2-machine live procedure = the step-4 exit criterion on user hardware). context/glass-cockpit.md: steps 3+4 status. Final purity re-check: pod build clean, forced-walk mission runs, ZERO glass/steam tags in the pod log. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 1 + content/LAST.EGG | 68 ++++++++++++++++++------------------- context/glass-cockpit.md | 12 +++++-- context/steam-networking.md | 46 +++++++++++++++++++++++++ docs/GLASS_COCKPIT.md | 28 +++++++++++++++ docs/STEAM_TEST.md | 38 +++++++++++++++++++++ 6 files changed, 156 insertions(+), 37 deletions(-) create mode 100644 context/steam-networking.md create mode 100644 docs/STEAM_TEST.md diff --git a/CLAUDE.md b/CLAUDE.md index a187e34..e232218 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,6 +74,7 @@ precise than anything you can infer. | Multiplayer, replication, netcode | `context/multiplayer.md` | | Pod hardware, monitors, RIO, MFD surfaces | `context/pod-hardware.md` | | Glass cockpit desktop dev layer (BT_GLASS/BT_STEAM gates, PadRIO, miniconsole, Steam) | `context/glass-cockpit.md` | +| Steam internet MP (the wire seam, identity tokens, lobby) | `context/steam-networking.md` | | Content archives, maps, where data lives | `context/content-archives.md` | | Offsets, ClassIDs, addresses, resource types, env vars | `context/decomp-reference.md` | | What we don't know / deferred systems | `context/open-questions.md` | diff --git a/content/LAST.EGG b/content/LAST.EGG index 57c3236..61255bd 100644 --- a/content/LAST.EGG +++ b/content/LAST.EGG @@ -5,7 +5,7 @@ scenario=freeforall time=day weather=clear temperature=27 -length=60 +length=600 [ordinals] bitmap=Ordinal::BitMap::1 bitmap=Ordinal::BitMap::2 @@ -156,12 +156,12 @@ x=128 y=32 width=8 [pilots] -pilot=169.254.77.1:1502 -[169.254.77.1:1502] +pilot=200.0.0.96 +[200.0.0.96] hostType=0 advancedDamage=1 loadzones=1 -name=Dev +name=Aeolus bitmapindex=1 experience=expert badge=VGL @@ -172,34 +172,34 @@ vehicle=bhk1 vehicleValue=1000 color=White [largebitmap] -bitmap=BitMap::Large::Dev -[BitMap::Large::Dev] +bitmap=BitMap::Large::Aeolus +[BitMap::Large::Aeolus] bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 -bitmap=00000000000000000000000000000000 -bitmap=00000000000FFC000000000000000000 -bitmap=00000000000FFE000000000000000000 -bitmap=00000000000FFF000000000000000000 -bitmap=00000000000E07800000000000000000 -bitmap=00000000000E0381F070700000000000 -bitmap=00000000000E01C3F870700000000000 -bitmap=00000000000E01C7FC70700000000000 -bitmap=00000000000E01CF1C38E00000000000 -bitmap=00000000000E01CE0E38E00000000000 -bitmap=00000000000E01CFFE38E00000000000 -bitmap=00000000000E01CFFE1DC00000000000 -bitmap=00000000000E01CE001DC00000000000 -bitmap=00000000000E038E000D800000000000 -bitmap=00000000000E07870E0F800000000000 -bitmap=00000000000FFF07FC0F800000000000 -bitmap=00000000000FFE03FC07000000000000 -bitmap=00000000000FFC01F007000000000000 -bitmap=00000000000000000000000000000000 -bitmap=00000000000000000000000000000000 +bitmap=000003FE0000000001E0000000000000 +bitmap=000007FF0000000001E0000000000000 +bitmap=00000F8F8000000001E0000000000000 +bitmap=00000F078000000001E0000000000000 +bitmap=00000F0781FF00FF81E0F0781FC00000 +bitmap=00000F0783FF81FFC1E0F0783FE00000 +bitmap=00000F0787C7C3E3E1E0F0783C700000 +bitmap=00000F078783C3C1E1E0F07878300000 +bitmap=00000F078783C3C1E1E0F07878000000 +bitmap=00000FFF8783C3C1E1E0F0787C000000 +bitmap=00000FFF8783C3C1E1E0F0787F000000 +bitmap=00000F0787FFC3C1E1E0F0783FC00000 +bitmap=00000F0787FFC3C1E1E0F0781FE00000 +bitmap=00000F07878003C1E1E0F07807F00000 +bitmap=00000F07878003C1E1E0F07801F00000 +bitmap=00000F07878003C1E1E0F07800F00000 +bitmap=00000F078780C3C1E1E0F07860F00000 +bitmap=00000F0787C1C3E3E1E078F871E00000 +bitmap=00000F0783FF81FFC1E07FF83FE00000 +bitmap=00000F0781FF00FF81E03F781FC00000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 @@ -210,15 +210,15 @@ x=128 y=32 width=8 [smallbitmap] -bitmap=BitMap::Small::Dev -[BitMap::Small::Dev] -bitmap=00000000000000000000000000000000 -bitmap=0000000000000000000007C000000000 -bitmap=000006600000000000000633D8C00000 -bitmap=0000063678C00000000006366D800000 -bitmap=00000637ED800000000006360D800000 -bitmap=0000066667000000000007C3C7000000 +bitmap=BitMap::Small::Aeolus +[BitMap::Small::Aeolus] bitmap=00000000000000000000000000000000 +bitmap=0000000000000000003E00000C000000 +bitmap=006300000C00000000631F0F8CC63C00 +bitmap=00633198CCC66200007F3198CCC66000 +bitmap=00633F98CCC6780000633018CCC61E00 +bitmap=00633018CCC6060000633098CCC64600 +bitmap=00631F0F8C7E3C000000000000000000 bitmap=00000000000000000000000000000000 x=64 y=16 diff --git a/context/glass-cockpit.md b/context/glass-cockpit.md index a349b41..2d7498e 100644 --- a/context/glass-cockpit.md +++ b/context/glass-cockpit.md @@ -61,9 +61,15 @@ copied from them. (`BT_BUTTON_TRAP=1` restores) — each `[FAIL]` log = a missing L4 override to reconstruct. Verified: authentic-path input (engine push → streamed `.CTL` → mapper), panel-click survival, 2-node loopback MP un-regressed on the pod build. Detail: `docs/GLASS_COCKPIT.md`. -- Step 3 (miniconsole): pending. Architecture: the console stays a WIRE PEER — an in-process - worker-thread console client over loopback; no engine-loop hooks. -- Step 4 (Steam): pending. +- **Step 3 (miniconsole): DONE 2026-07-17 [T2].** `game/glass/btl4fe.*` (menu + console-shape + egg writer, golden-tested vs MP.EGG) + `btl4console.*` (the marshal: a worker-thread console + CLIENT speaking the btconsole.py wire verbatim; `content\marshal.log`). ZERO engine hooks — + the engine runs the stock console ladder. Per-mission process relaunch; `BT_FE_*` env arms the + marshal (menu relaunches clear it). Verified: solo cycle (menu→mission→60s clock→stop→menu) + and menu-hosted 2-node LAN. +- **Step 4 (Steam): CODE COMPLETE 2026-07-18 [T2 single-machine].** See [[steam-networking]] — + the wire seam + `L4STEAMNET` identity-token transport + `btl4lobby`. Live 2-account + cross-machine session pending (`docs/STEAM_TEST.md`). ## Key Relationships - Extends: [[pod-hardware]] (RIO input path) · Uses: [[gauges-hud]] dev-gauge modes · diff --git a/context/steam-networking.md b/context/steam-networking.md new file mode 100644 index 0000000..7237208 --- /dev/null +++ b/context/steam-networking.md @@ -0,0 +1,46 @@ +--- +id: steam-networking +title: "Steam networking — the BT_STEAM wire (transport + lobby)" +status: living +source_sections: "docs/GLASS_COCKPIT.md §4; docs/STEAM_TEST.md; commits e79e8fa/48ede2f/f703bb1" +related_topics: [glass-cockpit, multiplayer] +key_terms: [PadRIO, miniconsole] +open_questions: ["live 2-account cross-machine session (docs/STEAM_TEST.md) — the step-4 exit criterion"] +--- +# 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 +1. **The wire seam** (`L4NET.CPP`, always compiled): file-local `BTNetSend/Recv/Accept/Close` + wrappers around the 9 raw Winsock sites — pure code motion when OFF; under `BT_STEAM` each + offers the op to the transport first. Est. delegates: `CheckSocket` (peer address), + `OpenConnection` TCP_OPEN head, `GetMyAddress` (self token). +2. **The transport** (`engine/MUNGA_L4/L4STEAMNET.*`, gated): `ISteamNetworkingSockets` + pseudo-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 env `BT_STEAM_NET=1`; degrades to + Winsock at every failure. Pump = `SteamAPI_RunCallbacks` AND sockets `RunCallbacks` (the + general dispatch carries the async results — sockets-only pumping stalls). +3. **The lobby** (`game/glass/btl4lobby.*`, gated): ISteamMatchmaking room (`btl4=1` list + 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`. + +## Key Relationships +- Rides: [[glass-cockpit]] miniconsole (marshal has a Steam-wire branch) · Extends: + [[multiplayer]] (the arcade mesh, unchanged above the seam) diff --git a/docs/GLASS_COCKPIT.md b/docs/GLASS_COCKPIT.md index 97ce9c8..cd2697f 100644 --- a/docs/GLASS_COCKPIT.md +++ b/docs/GLASS_COCKPIT.md @@ -133,3 +133,31 @@ menu; menu relaunches CLEAR the handoff — the leak re-armed a marshal in cycle Official Valve zip (provided by Cyd at the repo root), extracted to `extern/steamworks_sdk_164/` and trimmed to `public/` + `redistributable_bin/` + Readme (12.5 MB); zip removed. Consumed only under `-DBT_STEAM=ON`. + +## 2026-07-17/18 — Step 4: the Steam wire — CODE COMPLETE (commits e79e8fa, 48ede2f, f703bb1) + +**4a (e79e8fa) — the wire seam:** file-local `BTNetSend/Recv/Accept/Close` wrappers around the +9 raw Winsock sites in `L4NET.CPP`; OFF builds are pure code motion. Regression: 2-node loopback +MP through the seam — mesh forms, full 31/31 mission on both nodes. + +**4b (48ede2f) — the transport:** gated TU `L4STEAMNET` — pseudo-SOCKETs (0x5EA0xxxx) behind the +seam; the engine's TCP byte stream rides reliable-NoNagle Steam messages re-assembled into +per-connection rings (recv semantics preserved: empty→WSAEWOULDBLOCK, closed→0); seam +completions: `CheckSocket` (peer = token address), `OpenConnection` TCP_OPEN delegate, +`GetMyAddress` self-token. Install on `BT_STEAM_NET=1`; graceful degrade to Winsock at every +step. BOTH pumps needed (`SteamAPI_RunCallbacks` + sockets `RunCallbacks`). + +**4c (f703bb1) — the lobby + THE FAKEIP LESSON [T2]:** prior art assumed FakeIP survives the +per-mission process relaunch — DISPROVEN live (menu process got 169.254.124.110, the mission +process got 169.254.54.234; the pod timed out connecting to its own stale address). Redesign: +roster addresses are opaque ipv4-shaped TOKENS (`169.254.77.N`, ports 1501 console / 1502 game) +minted by the host at GO and mapped to Steam IDENTITIES; connections ride +`ConnectP2P(identity, channel)` (console=0, game=1); the map hands off via env +(`BT_FE_MYFAKE`/`BT_FE_STEAMMAP`). `btl4lobby`: ISteamMatchmaking room (`btl4=1` filter), +pilots publish name/mech/color, host GO mints tokens + writes `btl4map` + `btl4go`. + +**Verified single-machine (ON/ON, live Steam client):** menu → HOST STEAM LOBBY → room → GO → +map `169.254.77.1=` → egg roster carries the token → mission process +`[steamnet] up ... 1 roster token(s) incl. self` → pod self-matches → stock console ladder → +mission RUNS (46 ticks). 3-config build matrix green throughout. **Remaining (user hardware):** +the live 2-account cross-machine session — procedure in `docs/STEAM_TEST.md`. diff --git a/docs/STEAM_TEST.md b/docs/STEAM_TEST.md new file mode 100644 index 0000000..e176b0c --- /dev/null +++ b/docs/STEAM_TEST.md @@ -0,0 +1,38 @@ +# STEAM_TEST — the live 2-machine internet-MP procedure (step 4 exit criterion) + +Everything below the wire is verified single-machine (see `docs/GLASS_COCKPIT.md` §4); the ONE +untested leg is an actual cross-machine `ConnectP2P` session. Needs: **two machines, each with +its own logged-in Steam account**, both accounts able to see each other's lobbies (same region +or Steam friends; AppID 480 = Spacewar, no ownership needed). + +## Build + stage (each machine) +1. `cmake -S . -B build-steam -G "Visual Studio 16 2019" -A Win32 -DCMAKE_GENERATOR_INSTANCE=... -DBT_GLASS=ON -DBT_STEAM=ON` +2. `cmake --build build-steam --config Debug` +3. Steam client running + logged in. `content\steam_appid.txt` (480) is auto-written on first run. + +## Procedure +1. **Machine A (host):** run `build-steam\Debug\btl4.exe` zero-arg from `content\` → the + Mission Console menu. Set MAP/LENGTH (60 s recommended for the first attempt), MODE = + `HOST STEAM LOBBY`, ENTER → the lobby room appears. +2. **Machine B (member):** same exe zero-arg → MODE = `JOIN STEAM LOBBY`, ENTER → the room + should list both pilots within ~10 s. (No lobby found → check both accounts' regions, + `marshal.log` `[lobby]` lines both sides.) +3. **A presses ENTER** (GO). Both machines relaunch into mission processes; A's marshal feeds + itself over TCP loopback and B over the Steam wire. + +## What to check (in order, both machines' `content\btl4.log` + `marshal.log`) +1. `[steamnet] up -- identity , 2 roster token(s) incl. self` — both sides. +2. A `marshal.log`: `egg -> 169.254.77.2:1501` (B's token) — the Steam-wire console feed. +3. B `btl4.log`: `[steamnet] incoming connection queued on channel 0` (the console feed + arriving), then the egg ladder. +4. Both: `RunMission #1/#2`, then `[tick]` lines reaching the full subsystem count — and + `[steamnet] connection N up` on channel 1 (the game mesh crossing the internet). +5. In-game: see the peer mech, drive, shoot it (cross-pod damage), mission stop at clock, + host returns to the menu. + +## Known limitations / notes +- The lobby does NOT survive the per-mission relaunch — re-host/re-join per mission. +- The token map is minted at GO; a member joining after GO is not in the mission. +- Firewall: no inbound rules needed (SDR relays); Steam client must be allowed out. +- Fail cleanly = both sides stay on Winsock and log why (`SteamAPI_Init failed` / token + misses); nothing in the pod path is touched either way.