"[Sync] component created after NetworkSpawn silently never replicates"
▸ SYMPTOM
A component's [Sync] fields update correctly on the owner but are always zero/default on every other peer. No error anywhere: the owner writes a value, a host-side probe confirms it, but the client's proxy instance stays at the initial value forever.
▸ CAUSE
NetworkSpawn runs before OnStart. If a component with [Sync] state is created in OnStart (e.g. via Components.GetOrCreate<MyState>()), it wasn't in the spawn snapshot. Each peer ends up creating its own local instance of the component. The owner's instance has no network-paired counterpart on the client, so [Sync] fields never cross the wire. There's no error because the component exists on both sides; it's just not the same network entity.
This pattern often creeps in when you already have sync-free helper components that are safely created in OnStart via GetOrCreate: extending that pattern to a component with [Sync] fields silently breaks replication.
▸ FIX
Create the component in the spawn path, before go.NetworkSpawn(owner):
// In your spawn/setup code - BEFORE NetworkSpawn
var go = scene.CreateObject();
go.Components.Create<MyCharacter>();
go.Components.Create<MyState>(); // has [Sync] fields - must exist pre-spawn
go.NetworkSpawn(owner);OnStart's GetOrCreate then finds the replicated instance on every peer instead of creating a competing local one.
Rule of thumb: components that only read/tick locally may be created any time. A component that carries [Sync] state must exist before NetworkSpawn.
The law covers engine components too, not just your own
Confirmed on engine 26.07.22: a Rigidbody created during post-spawn assembly is exactly this bug, because Rigidbody.Velocity is itself a [Sync]-backed value the owner publishes each physics step. A factory that builds a bare Rigidbody during local assembly (which runs after NetworkSpawn) gives every proxy an unpaired local body: Velocity reads 0.0 on every remote peer while the same object measurably moves. The fix mirrors the general rule: make the spawn path GetOrCreate the body instead of Create, so a body that already exists (created before NetworkSpawn) gets configured rather than replaced, while solo/offline callers with no pre-existing body still build one exactly as before.
Diagnostic signature: the failure is partial, and the working half is the one you check first
This bug is partial, not total, and the half that keeps working is the half everyone inspects first. Transform/pose rides the network object itself (not a component-level [Sync] field), so a proxy with a post-spawn Rigidbody still moves and looks completely correct. In the measured case position tracked the true value within 0.2 m over 93 seconds. Only the component-level sync (Velocity, and anything else that component publishes) is dead, which reads as "networking works, this one number is broken" and sends you auditing the number instead of the object's construction order. When a networked object misbehaves in exactly one respect while everything else about it looks right, audit construction order before auditing the misbehaving value.
▸ WHY IT WORKS
NetworkSpawn snapshots every component on the GameObject at call time and assigns them network identity. Components added after this point have no cross-peer identity: [Sync] only works when both sides are talking about the same network entity. Creating the component pre-spawn ensures it's included in the snapshot and gets a proper network pair on every connecting peer.
- Extended the law to engine-owned components (Rigidbody.Velocity is [Sync]-backed); added the partial-failure diagnostic signature. Re-verified on engine 26.07.22.
- Published