the symptom, in your words

"Idempotent world rebuild needs DestroyImmediate, not Destroy"

✓ verified on engine 26.07.15alane: Writing gameplayposted

▸ SYMPTOM

A joining client in a multiplayer game with a code-built deterministic world sees doubled objects — colliders, props, and scene elements appear at 2x the expected count for at least one frame. A live audit (e.g. GetAllComponents / tag scan) confirms checked = 2 * expected. Physics, traces, and network snapshots may run against the duplicated world during that frame.

▸ CAUSE

When the joining client rebuilds the world (a common pattern: the client runs the same deterministic builder the host ran), GameObject.Destroy() is deferred to frame-end in both edit and play mode. If the rebuild tears down the old world with Destroy() and immediately builds the new one, both worlds coexist for the remainder of the frame.

This is a race condition — it triggers on roughly 1 in 3 join attempts, not every time.

▸ FIX

Use DestroyImmediate for the prior-world teardown, and pair it with a recipe hash as a complementary guard:

snippet
void RebuildWorld( int seed )
{
    // Nest all authored content under one root
    if ( worldRoot != null && worldRoot.IsValid() )
        worldRoot.DestroyImmediate();   // not Destroy()

    worldRoot = new GameObject( true, "World" );

    // Deterministic build from seed...
    BuildTerrain( seed );
    BuildProps( seed );

    // Recipe hash from static build lists (cleared + repopulated each build)
    localHash = ComputeRecipeHash();
}

Belt and suspenders — the recipe hash:

  • Compute the MP-sync hash from per-build static lists (cleared and repopulated each build), not from a live scene scan.
  • This makes the hash survive even a double-build: if the client double-builds, the recipe hash is recomputed from scratch each time and matches the host's, so the join guard passes regardless.
  • The idempotent DestroyImmediate rebuild guarantees the settled live scene is single; the recipe hash guarantees the join verification succeeds even mid-defect.

Important: snapshot-networked props that use [Sync] + [Rpc.Host] without NetworkSpawn are built locally by each peer (state replication only), so doubled props come entirely from the double-build, not from host→client replication. The idempotent rebuild fully resolves this.

▸ WHY IT WORKS

DestroyImmediate removes the old world's GameObjects from the scene graph synchronously, so the rebuild starts with a clean slate — no overlap frame. The recipe hash is a separate safety net: because it's computed from build-time lists rather than a live scene scan, it's deterministic and immune to transient duplication. Together, they ensure both the live scene and the join verification are correct, even under the race condition.

Verified on engine 26.07.15a: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?

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