"Local two-peer multiplayer testing with -joinlocal"
▸ SYMPTOM
You need to test multiplayer behavior with two real peers but don't want to publish, set up a dedicated server, or use a second Steam account.
▸ CAUSE
The engine has a built-in local two-peer testing mode, but it's not prominently documented. While an editor play session is hosting, it opens a TCP socket on 127.0.0.1:55333 that accepts direct-connect clients.
▸ FIX
While an editor play session is hosting (lobby created, networking active):
sbox.exe -joinlocal +instanceid 1 -sw -720The instance auto-connects to the local host at bootstrap, presents a fake per-instance SteamId with a random persona (e.g. "Wiggum"), completes the dev-host handshake (assemblies stream from the host's network table), and spawns as a genuine second player. This is the same mechanism the editor's "new game instance" toolbar button uses.
Key traps
- Launch after the host is live. Verify the host's networking is active first: rapid re-host iteration can transiently fail
CreateLobby(~15-20 s settle clears it). - There is no host selector:
-joinlocaljoins whichever local editor is in PLAY MODE, silently. The flag cannot be pointed at a chosen host; it connects to whatever in-process editor session it finds. With more than one editor playing on the machine, the peer joins an arbitrary one. Verified on engine 26.07.22: a two-peer walk meant for one project's branch instead connected to a completely different sibling editor's session. The peer's log carried none of the intended branch's tags, only the other host's, because it had joined that session and was running that build. The engine's own doc string calls the flag "an instance that launches and joins an in-process editor session"; note the target does not need to read as MP-hosting. It joins whichever session is in play mode. The strict precondition is that NO other s&box editor on the machine is in play mode at all, not merely "no other MP host live." Practical check: enumerate running editors and confirm none is playing before launching the peer. - Invite-code verification rejects the
-joinlocalpeer: it TCP-connects directly and never runs the code-entry UI, so it presents an empty code. Add a dev-only seam (accept empty code whenApplication.IsEditorand the caller is loopback) rather than weakening the shipped gate. - The client truncates
logs/sbox.log: host evidence must come from re-fetchable probes or the editor console, not the shared log file. - A failed join permanently wedges the editor's game package mount: plan an editor restart after every failed attempt.
- Eyeball the client window at least once. Host-side probes structurally cannot see a client whose camera/UI is wrong.
Blind spots
A -joinlocal peer never enters an invite code, never exercises the assembly reload (same process), and never tests FileSystem.Data carry. Bugs that only bite on a real published join are invisible here. Multiple live-proven bugs have sailed through green local runs for this reason: always follow up with a real published-build test (rung 3).
▸ WHY IT WORKS
The editor host advertises dev-host=true, which lets clients tolerate a missing local package and stream assemblies instead. The +instanceid flag gives each client a unique fake SteamId so the engine treats them as separate players. This is loopback-only (same machine), which is both its strength (zero setup) and its limitation (can't test real network conditions or Steam backend behavior).
- Added the no-host-selector caveat and corrected the precondition to 'no other editor in play mode' (not merely 'no other MP host live'). Re-verified on engine 26.07.22.
- Published