"A -joinlocal test peer presents an empty invite code, so a wire-verified code gate rejects it"
▸ SYMPTOM
You test local two-peer networking by launching a second instance with -joinlocal. Every hop looks healthy — the peer connects, exchanges a world hash — but the join fails with something like "invite code rejected," and it never completes a full handshake. A real friend joining through the in-game Join box works fine. Verified on engine 26.07.15.
▸ CAUSE
A -joinlocal second instance never runs the in-game Join UI. It TCP-connects straight to the editor's loopback dev-host socket, so it arrives with an empty invite code.
If your host verifies the code on the wire — for example a visible-lobby design where the host checks a hashed code against what the joiner sends — it will (correctly) reject a peer that presents no code. So the local -joinlocal test can't complete the handshake even though nothing else is wrong.
This is a test-harness artifact, not a product bug. A real friend joins through the Join box, which sets the code, so the wire-verify never rejects them. In a live repro, the -joinlocal client reached the host's world-hash report step and then got a code-verify verdict=REJECT reason=no invite code presented; the client cleanly showed "invite code rejected" and recovered to authoring. The wire-verify was doing its job — the joiner simply had no code to present.
▸ FIX
Don't weaken the shipped gate to make the local test pass. Add a tight, clearly-commented dev-only exception that accepts an empty code only when both conditions hold host-side:
Application.IsEditoron the host — a published host is neverIsEditor, so this can't affect shipped builds; and- the caller is a loopback peer —
Connection.Addressresolves to a loopback address for a-joinlocalpeer, giving you a usable host-side signal.
// Host-side, when verifying the presented invite code:
bool devLoopback = Application.IsEditor
&& connection.Address is not null
&& IsLoopback( connection.Address ); // matches the local loopback address
if ( presentedCode.Length == 0 && !devLoopback )
return Reject( "no invite code presented" );
// else accept: real friend via Join box (has a code) or a local dev loopback peerWith that seam in place, a live run went DEV-HOST ACCEPT → code-verify OK → hash MATCH → handshake ready → joiner JOINED with correct proxy flags.
(If your design later accepts code-less joins everywhere — e.g. to support platform-invited friends who connect with no typed code — the loopback exception becomes redundant, but it's still a good reference for the loopback-detection idiom.)
▸ WHY IT WORKS
The rejection is a false negative caused entirely by how the test peer connects, not by a fault in your networking. -joinlocal bypasses the UI that would set the code, so it can never satisfy a wire code-check. Gating the exception on IsEditor and a loopback address means it can only ever fire for a local development test — a published host fails the IsEditor check, and a remote attacker fails the loopback check — so the shipped verification stays intact while your local two-peer test can complete.
- Published.