tag

#networking

39 items (4 guides · 35 fixes)

guides
guide
Delta-log save for deterministic procedural worlds

When the world is deterministically generated from a spec, a save is {version, spec, edits[]}, never a geometry snapshot. Load = validate → regenerate → replay edits. Same format works for save, co-op edit sync, and late-join replay.

guide
Networking methods: spec replication + host-authority patterns

Two proven architectures on top of the engine networking primitives: replicate the generator spec (not the geometry) for deterministic worlds, and retrofit local-only gameplay systems to host-authority without breaking single-player.

guide
P2P peer-hosted servers: from working co-op code to a friend actually joining

The operational recipe for shipping player-hosted (P2P) multiplayer in s&box (lobby mechanics, invite codes, the join handshake liveness contract, replication traps, and the three-rung testing ladder), covering the layer the official docs don't document and where live multi-peer sessions actually break.

guide
Proximity voice chat: the built-in Sandbox.Voice component

The complete method for wiring s&box's built-in Voice component into a networked game (push-to-talk, 3D positional playback, custom falloff curves, speaking indicators, and lip-sync), with no third-party voice SDK.

fixes
26.07.22
"[Sync] component created after NetworkSpawn silently never replicates"

A component with [Sync] fields added in OnStart (after NetworkSpawn) never replicates: each peer creates its own local instance and the sync pair silently never matches. The law covers engine components too: a Rigidbody built post-spawn is exactly this bug (Velocity is [Sync]-backed).

26.07.15a
"A -joinlocal dev-host client CAN read the host's Networking.SetData lobby metadata"

A second editor instance launched with -joinlocal is not a real Steam lobby member, yet Networking.GetData still resolves the host's Networking.SetData values -- they arrive over the ServerDataMsg the host sends on connect, not via lobby membership. The message can lag scene load by a few frames, so read GetData in a short poll loop.

26.08.05
"A -joinlocal peer joins whoever owns 127.0.0.1:55333, not whoever is in play mode"

A -joinlocal second instance connects to whichever process holds the loopback port 127.0.0.1:55333, so the precondition to check is socket ownership, not which editor is in play mode.

26.07.15
"A -joinlocal test peer presents an empty invite code, so a wire-verified code gate rejects it"

A -joinlocal second instance never runs the in-game Join UI: it connects straight to the editor's loopback dev-host socket, so it presents an empty invite code. Any host that verifies the code on the wire will correctly reject it, breaking the local two-peer test even though every other hop is healthy. It's a harness artifact, not a product bug.

26.07.15a
"A code-built world can run TWICE on a network join and stack a second world on the first"

A joining client can build a code-generated world twice -- once on local StartupScene load, once on the networked scene handoff. A non-idempotent world author that clears its tracking lists but never destroys prior GameObjects stacks a whole second world. Make the author idempotent, and compute any sync hash from the build recipe, not a live scene scan.

26.07.22
"A dedicated server never creates a Steam lobby, so lobby-based join code does nothing"

Networking.CreateLobbyAsync branches on Application.IsDedicatedServer straight into the dedicated-server path and never opens a lobby, so lobby-creation code written for peer-hosted play does nothing useful on a dedicated server.

26.07.15a
"A second s&box instance on the same Steam account can make P2P joins fail rendezvous"

Running a second s&box process under the same Steam account (an editor or dev instance alongside the published client) correlates with P2P joins to a live remote host failing rendezvous inside the engine's fixed ~3.5s connect budget. Close every second same-account instance before drawing any conclusion from a P2P connectivity failure.

26.07.08e
"Character facing derived from velocity flips 180 degrees on every pendulum reversal"

Deriving a visual's facing from horizontal velocity causes 180-degree snaps on any momentum reversal. Lock facing to an attach-time azimuth for pendulums and oscillators.

26.07.22
"Citizen animgraph combat params are unwrapped and unnetworked"

The stock citizen animgraph ships combat params that CitizenAnimationHelper does NOT wrap -- drive them with renderer.Set() directly, and replicate them yourself because the engine only networks locomotion.

26.07.22
"Connection.Stats reads zero on a local two-instance session"

Networking.HostStats and Connection.Stats read zero on a local loopback two-instance session, including Ping and ConnectionQuality, so a bandwidth instrument built on those fields latches zero even while traffic flows. Count bytes at the application layer instead.

26.07.08e
"CreateLobby is async: IsActive is still false on the same frame"

Networking.CreateLobby is async: Networking.IsActive is still false on the same frame, so any branch on IsActive takes the wrong path. Gate on your own synchronous mode enum instead.

26.07.08e
"Dedicated server with an unpublished package: clients can't join"

A headless sbox-server running an unpublished local .sbproj boots and creates a lobby, but every joiner fails with 'Package local.<ident> wasn't found!': publish the package first (Hidden visibility is sufficient).

26.07.15a
"Dev-host streaming skips loose data files, breaking deterministic sync"

Dev-host streaming sends assemblies and compiled assets but NOT loose data files read via FileSystem.Mounted: a joining client that loads a raw JSON manifest builds differently, silently.

26.07.08e
"FromHost field on a runtime singleton does not replicate without NetworkSpawn"

A [Sync(SyncFlags.FromHost)] field on a runtime-created singleton never replicates: the object needs NetworkSpawn, not just NetworkMode.Snapshot.

26.07.15a
"Gate the exact lobby you connect to, not query result [0]"

When a lobby query drives a connect, validate the exact lobby candidate you will connect to (not results[0]), or a peer can connect to a lobby it never validated (build-skew, wrong-host connect that no gate catches).

26.07.15a
"Idempotent world rebuild needs DestroyImmediate, not Destroy"

Deferred Destroy() leaves the old code-built world overlapping the fresh one for a frame: use DestroyImmediate for teardown before rebuild, and pair it with a recipe hash for belt-and-suspenders join verification.

26.07.15a
"Joining client's singleton claim grabs the host's character"

A static Instance claimed behind if (!IsProxy) in OnStart grabs the host's character on a joining client: the client camera follows the wrong player forever.

26.07.08e
"Joining client's static state wiped by networked scene handoff"

A joining client's static join state (invite code, mode, attempt ID) gets wiped by the networked scene handoff: the bootstrap's OnEnabled resets statics before the join handshake uses them.

26.07.22
"Local two-peer multiplayer testing with -joinlocal"

sbox.exe -joinlocal +instanceid 1 gives you a real second peer against an editor host with no publish, no second Steam account, and no lobby discovery. It has no host selector: it joins whichever local editor is in PLAY MODE, so no other editor on the machine may be playing.

26.07.22
"Networking API facts to check against the installed build before you compile"

A batch of networking API facts, verified from the installed build's source and XML, that overturn common assumptions when you plan multiplayer code before it touches the compiler. LobbyInformation is a struct, so a null guard does not compile. Networking.Connections is deprecated in favour of Connection.All and emits CS0618. Connection.MaxChunkSize is internal, so game code hard-codes the 131072 value instead of referencing the symbol. NetworkAccessor exposes Owner, OwnerTransfer, and OrphanedMode as get-only, with AssignOwnership, SetOwnerTransfer, and SetOrphanedMode called after NetworkSpawn. Networking.TryConnectSteamId exists and is public. SB1000 whitelisting is assembly-level, so the real constraint on a networking call is public versus internal accessibility, not the whitelist. A green dotnet build does not mean an editor-green result for networked types.

26.07.08e
"Null check on a destroyed GameObject still NREs: use IsValid()"

A cached Component/GameObject reference guarded with == null still throws NullReferenceException after the object is destroyed: only IsValid() catches destroyed objects.

26.07.15a
"OnDisabled nulls a singleton, blocking re-adoption forever"

A singleton claimed in OnEnabled and nulled in OnDisabled traps any re-adopt poll that gates on Instance.IsValid(): the entity is never re-driven after a disable.

26.07.08e
"Owner-only simulation field reads frozen state on network proxies"

Any cross-peer consumer (UI, host validators, scorers, range checks) that reads an owner-only simulation field gets frozen state on network proxies. Anchor off the replicated transform or redirect the field's getter.

26.07.08e
"Owner-simulated networking"

[Sync] owner→proxies, IsProxy early-out; FromHost for shared truth. Every object carrying a [Sync] field has to be NetworkSpawned, singletons included.

26.07.08e
"Published-build client join reloads the assembly, wiping all statics"

A published-build client join reloads the game assembly, wiping all statics. The reconstruct-not-reset fix from the scene-handoff case has nothing to reconstruct from unless join intent is persisted to disk.

26.07.15a
"Push-to-talk silently dead: Voice action missing from Input.config"

The Sandbox.Voice component defaults PushToTalkInput to 'voice', but not every project ships a matching 'Voice' InputAction. PTT is a dead key with zero errors until you add it to Input.config.

26.07.15
"QueryLobbies never finds your Hidden lobby"

Networking.QueryLobbies appends a hidden:0 filter unless you pass a truthy hidden key, so a Hidden lobby is structurally excluded from every ordinary query. The bool overload param is includeServers, not hidden-inclusion. And editor hosts force Private privacy, which no filter overrides.

26.07.15a
"Quit-to-menu teardown runs inside DisconnectScope: networking is still active"

Quit-to-menu tears the game scene down inside Networking.DisconnectScope, so networking is still active during game-side teardown. A local quit is distinguishable from a host disband, and teardown code can still send graceful goodbyes.

26.07.08e
"Rpc.Broadcast on a non-networked GameObject runs locally only -- no warning"

An [Rpc.Broadcast] called on a component whose GameObject is not networked executes locally and silently never crosses the wire -- no warning at any log level.

26.07.08e
"s&box ships a headless dedicated server that can run an unpublished local project"

App 1892930 via SteamCMD runs a headless server with +game pointing at a local .sbproj: clients stream code/assets, no sbox.game publish required.

26.07.22
"Sandbox.Json.Serialize always writes indented JSON"

Sandbox.Json.Serialize always emits pretty-printed JSON and exposes no public option to disable it: every network payload pays a whitespace tax. Re-drive the same serializer through a compact writer for a 41–56% size cut.

26.07.08e
"Spawning a joiner's character clobbers the host's camera target"

On the host, spawning a joiner's character clobbers the host's own camera-target singleton because IsProxy is false at Components.Create time: re-resolve the claim at the first OnFixedUpdate.

26.07.15a
"Steam lobby metadata survives host game switch"

A Steam lobby survives its host switching to a different s&box game with stale metadata: a joiner connects and loads the wrong game's content.

26.07.15a
"The P2P join handshake has a fixed ~3-second connect budget"

The joiner's engine-side connect budget after entering a Steam lobby is a fixed ~3 seconds that game code cannot extend: any host-side delay exhausts it fast, so join recovery must be a fresh retry, never a longer wait.

26.07.15a
"Unclean host exit poisons the Steam P2P transport between two peers"

The C# layer of s&box never closes per-pair Steam P2P sessions, so a host crash or task-kill poisons the pairwise transport state between two SteamIDs until Steam-side expiry clears it, typically minutes.

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord