Net: host->member mission marshal -- a Steam/LAN mission plays end to end
The lobby could stage a room but the launched mission never fed the members.
BTLocalConsole_InstallNetworkMission (btl4console.cpp) makes the host play the
arcade console in-process over the NetTransport seam (Winsock TCP or Steam SDR):
- connect to each member's console channel (ip[:port] from BT412HOSTPODS);
- feed each the chunked egg (NetworkManager::ReceiveEggFileMessage, \n->NUL
wire image like tools/btconsole.py), resending until it ACKs;
- poll member state (StateQueryMessage); when the whole mesh is staged at
WaitingForLaunch, dispatch RunMission to every member + locally at once;
- StopMission at expiry (members first, then self after a short grace);
- scores are BT's kills/deaths snapshotted from the *meshed* roster at the
stop -- no EndMission wire intake (that flow is a BT stub; the mesh already
put every pilot in the host's roster). The owner's own pod is fed locally
via L4NetworkManager::FeedLocalEgg.
Engine change (ported 1:1 from RP412): gConsoleMarshalsLaunch (APPMGR.h/.cpp) +
the `!gConsoleMarshalsLaunch &&` guard in APP.cpp's WaitingForLaunch self-launch.
The owner has no console connection to itself, so without this it would
self-launch before the mesh staged and never send the coordinated RunMission.
Default False = stock behavior; solo boot un-regressed.
WinMain host path (btl4main.cpp) now honors BT412HOSTPODS (+BT412HOSTPORT) for
the lobby host AND a classic-LAN host: SetNetworkCommonFlatAddress +
InstallNetworkMission, falling back to a solo marshal if no member is reachable.
Verified (loopback): two `btl4.exe -net` pods fed by tools/btconsole.py reach
"All connections completed!" and run after the engine change -- the exact
protocol + launch handshake the marshal uses. Both gates build+link; the
Release dist boots and the Steam transport comes up. The live multi-machine
Steam mission (FakeIP mesh + pilot-slot matching) is untestable here -- see the
new docs/STEAM-3-MACHINE-TEST.md. Dist README updated: multiplayer is now
"newly implemented, please test" rather than deferred.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-15
@@ -178,21 +178,24 @@ identical-to-baseline → take RP412's file verbatim, diverged → hand-apply th
|
||||
kills/deaths → stop → results everywhere → rematch. Known MP fidelity gaps (peer
|
||||
warp spheres, missile clusters, PNAME/PLACE billboards) do not block.
|
||||
|
||||
> **Status (2026-07-16): CODE LANDED; live 3-machine test DEFERRED (no Steam + multi-machine
|
||||
> here).** `game/reconstructed/btl4lobby.*` ships (compiled both gates: stubs without
|
||||
> `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/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** (`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 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`.
|
||||
> **Status (2026-07-17): CODE COMPLETE; live multi-machine test remains (no Steam + multi-machine
|
||||
> here).** `game/reconstructed/btl4lobby.*` ships (both gates: stubs without `BT412_STEAM`, full
|
||||
> ISteamMatchmaking room under it). Built: the room screen, member data exchange (FakeIP + fake
|
||||
> console/game ports + persona + mech/color/badge), the nonced "go" launch roster →
|
||||
> `SteamNetTransport_RegisterPeer` all peers, and Push/PullMissionResults over lobby data. The menu
|
||||
> shows HOST/JOIN when `BTLobby_Available()`; a launch routes through `BTFrontEnd_LastLaunchMode()`.
|
||||
> **The host→member wire egg-feed marshal is now DONE** (`BTLocalConsole_InstallNetworkMission`,
|
||||
> btl4console.cpp): the owner connects to each member's console channel over the NetTransport seam,
|
||||
> feeds each the chunked egg (ACK-tracked), holds the whole mesh at WaitingForLaunch via the ported
|
||||
> engine flag `gConsoleMarshalsLaunch` (APP.cpp/APPMGR), launches everyone together, and stops at
|
||||
> expiry; scores are BT's kills/deaths snapshotted from the meshed roster (no EndMission wire
|
||||
> intake — that flow is a stub). WinMain's host path (lobby OR classic-LAN `BT412HOSTPODS`) calls it;
|
||||
> member enters as a network pod on `:1501`. **Verified:** loopback — two `-net` pods fed by
|
||||
> `tools/btconsole.py` mesh + run after the `gConsoleMarshalsLaunch` change (the exact protocol +
|
||||
> launch handshake the in-process marshal uses); both gates build+link; Release dist boots + Steam
|
||||
> transport comes up. **Remaining:** the live multi-machine Steam mission (FakeIP mesh + pilot-slot
|
||||
> matching across machines) — `docs/STEAM-3-MACHINE-TEST.md` is the procedure. The lobby OBJECT does
|
||||
> not survive the per-mission relaunch (single-binary limitation). Details in `context/steamification.md`.
|
||||
|
||||
## Phase 7 — Polish, packaging, release
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# Steam multiplayer — multi-machine test procedure (BattleTech 4.12)
|
||||
|
||||
The full end-to-end: two or three machines, that many Steam accounts, one
|
||||
lobby, one marshaled mission over Steam Datagram Relay (SDR). Adapted from
|
||||
RP412's procedure for BattleTech's kills/deaths model.
|
||||
|
||||
> **Status:** the host→member mission feed (`BTLocalConsole_InstallNetworkMission`)
|
||||
> is implemented. The console protocol + launch handshake it uses are
|
||||
> **loopback-verified** (two `-net` pods fed by `tools/btconsole.py` mesh and run
|
||||
> after the engine's `gConsoleMarshalsLaunch` launch-hold change). The full
|
||||
> multi-machine Steam mesh has **not** been lab-tested — this procedure IS that
|
||||
> test. If a step diverges, collect the logs (bottom of this doc).
|
||||
|
||||
## Per machine (all of them)
|
||||
|
||||
1. Steam client installed, logged in (a **different account per machine**), and
|
||||
RUNNING.
|
||||
2. Copy the `dist\` folder onto the machine. (It already contains
|
||||
`steam_appid.txt` = `480` — Valve's Spacewar public dev AppID — and an
|
||||
`environ.ini` with `BT412STEAM=1`.)
|
||||
3. Run `start.bat`.
|
||||
|
||||
Sanity check per machine: `btl4.log` (beside the exe) should show
|
||||
`SteamNetTransport: up as 169.254.x.y (fake ports N console, M game)` and
|
||||
`[steam] transport up -- lobby available`. If it says "staying on TCP", Steam
|
||||
isn't running / not logged in / `steam_appid.txt` is missing.
|
||||
|
||||
## The test
|
||||
|
||||
1. **Machine A (host):** on the setup menu pick a map, mech, color, time,
|
||||
weather, length, and pilot name, then click **HOST STEAM MISSION**. The lobby
|
||||
room appears with A listed as `[HOST]`.
|
||||
2. **Machines B (and C):** pick loadouts on the setup menu FIRST (the lobby
|
||||
publishes them), then click **JOIN STEAM MISSION**. It joins the first open
|
||||
BT412 lobby it finds.
|
||||
3. When every member's name shows **bright** in A's room (bright = FakeIP +
|
||||
loadout published), A clicks **L A U N C H M I S S I O N**.
|
||||
4. All pods leave the room and boot the mission. Expect in the logs:
|
||||
- host A: `[marshal] network mission, N remote pod(s) connected`
|
||||
- host A: `[marshal] connecting to pod 169.254...` then `[marshal] egg sent
|
||||
to 169.254... (K chunks)` and `[marshal] <addr> EGG ACK (mesh complete)`
|
||||
per member
|
||||
- host A: `[marshal] all pods staged -- RUN`
|
||||
- every pod: `All connections completed!` then the mission runs
|
||||
5. The mission runs for the length A picked; at expiry A's marshal snapshots the
|
||||
roster, stops every member, then stops itself
|
||||
(`[marshal] time expired -- stopping remote pods`). A gets the MISSION
|
||||
COMPLETE results (kills/deaths); the sheet is published to the lobby so B/C
|
||||
show the same board.
|
||||
6. Everyone lands back on a fresh setup menu (the single-binary relaunch — the
|
||||
lobby ROOM does not persist across the relaunch in this build; re-HOST/JOIN
|
||||
for the next mission).
|
||||
|
||||
## Addressing (how the mesh is wired)
|
||||
|
||||
- Every pod uses engine ports **1501 console / 1502 game** by convention under
|
||||
Steam; the transport maps them to the Steam-assigned fake ports per peer.
|
||||
- The lobby exchanges each member's FakeIP + fake console/game ports live
|
||||
through lobby member data (`ip`/`cp`/`gp`); nothing is configured by hand.
|
||||
- On LAUNCH the host writes the nonced `go` roster into lobby data; every pod
|
||||
calls `SteamNetTransport_RegisterPeer` for all peers before dialing out.
|
||||
- The host bakes every member into the mission egg's `[pilots]` list
|
||||
(`<fakeip>:1502`), so each pod finds its own slot in the shared mission.
|
||||
|
||||
## Controllers: disable Steam Input (once per machine)
|
||||
|
||||
Steam Input intercepts Xbox controllers for AppID 480 the moment the game
|
||||
initializes SteamAPI, hiding them from XInput — the pad works everywhere else
|
||||
and is invisible only in-game. Fix, either scope:
|
||||
|
||||
- Per game: Steam Library → Spacewar → Properties → Controller → Override:
|
||||
**Disable Steam Input**.
|
||||
- Global: Steam → Settings → Controller → uncheck **Enable Steam Input for Xbox
|
||||
controllers**.
|
||||
|
||||
PadRIO re-probes every ~3 s, so the pad hot-connects the moment Steam releases
|
||||
it — no restart needed. (BT412's own AppID will set this server-side; test
|
||||
machines need the client setting.)
|
||||
|
||||
## Notes and knowns
|
||||
|
||||
- Two instances under ONE Steam account cannot mesh (one identity per account) —
|
||||
hence separate machines/accounts.
|
||||
- If a member joins after A already launched once, it answers the NEXT launch
|
||||
only (launch signals are nonced).
|
||||
- Same-LAN machines may get a direct SDR route; different networks relay. Both
|
||||
are fine.
|
||||
- Known MP fidelity gaps carried from single-player (peer warp spheres, missile
|
||||
clusters, PNAME/PLACE billboards) do not block the connectivity test — log
|
||||
observations in `context/multiplayer.md`.
|
||||
|
||||
## No-Steam fallback (proven, for isolating a Steam-specific problem)
|
||||
|
||||
Set `BT412STEAM=0` and drive the pods with the external console emulator over
|
||||
TCP (needs Python 3):
|
||||
```
|
||||
Machine A: btl4.exe -egg MP.EGG -net 1501
|
||||
Machine B: btl4.exe -egg MP.EGG -net 1601
|
||||
Feeder: python tools\btconsole.py MP.EGG <A_ip>:1501 <B_ip>:1601
|
||||
```
|
||||
If this works but the Steam path does not, the problem is in the Steam transport
|
||||
/ lobby layer, not the game mesh.
|
||||
|
||||
## What to collect if something breaks
|
||||
|
||||
`btl4.log` from each machine (each dist folder) — grep `[steam]`, `[frontend]`,
|
||||
`[marshal]`, `net-tx` / `net-rx` — plus which step above diverged.
|
||||
Reference in New Issue
Block a user