Files
BT412/docs/STEAM-3-MACHINE-TEST.md
T
CydandClaude Opus 4.8 77f019ed4f 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>
2026-07-17 07:30:17 -05:00

5.2 KiB

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.