Repository navigation
Dedicated server: mixed-authority NetworkObject is removed from NetworkTransformUpdate, so its owner-authoritative NetworkTransform never updates server-side #4159
Description
Activity
- addedstat:importStatus - Issue is going to be saved internallyStatus - Issue is going to be saved internallytype:bugBug ReportBug Report
on Sep 17, 2026 - addedstat:importedStatus - Issue is tracked internally at UnityStatus - Issue is tracked internally at Unitystat:reply-neededAwaiting reply from Unity accountAwaiting reply from Unity accountand removedstat:importStatus - Issue is going to be saved internallyStatus - Issue is going to be saved internally
on Sep 21, 2026 Hi @Laumania,
I think there might be something else "a-foot" here.
The removal of NetworkTransform instances for updating interpolation on the authority side is for performance purposes (i.e. no reason to invoke a method specific to interpolation when you are the authority instance).Authority removes itself from the update but then registers itself for the network tick update which is when it checks for and sends any deltas on the transform.
It does not invoke OnUpdate or OnFixedUpdate as those are non-authority side only methods that handle interpolation.
If you are placing authority script/logic to control the motion of an object within OnUpdate or OnFixedUpdate, then that would indeed cause issues as those do not get invoked on authority instances.
You can resolve this issue by overriding OnInitialize and for the child authority instances that need to move around you can register for any specific NetworkUpdateStage where you will need to make your NetworkTransform derived child class implement the
INetworkUpdateSysteminterface (i.e. NetworkUpdateStage.Update).However, before you do that... I have some questions that could change my mind about the above potential solution:
- Could you describe what you are trying to accomplish with a root owner authoritative NetworkTransform that then has up to 11 nested server authoritative NetworkTransform instances?
- Are the server authoritative instances set to synchronize in world or local space?
- Local space would follow the root parent where world space will preserve the initial spawn position until the authority moves it.
- addedstat:awaiting-responseAwaiting response from author. This label should be added manually.Awaiting response from author. This label should be added manually.and removedstat:reply-neededAwaiting reply from Unity accountAwaiting reply from Unity account
on Sep 21, 2026 Thanks for the reply. I have since changed the architecture of how I do this, so its not a problem anymore for me, but still might be a "bug".
It was a ragdoll system that was located under the player, but owned/controled by host. However, its changed so its by it self and host controlled.Anyway, this was a bug identified by Claude (AI), so I will include his response here below in case it makes it more clear what the problem was, or you can just close this issue, its fine too.
Claude's response:
Thanks for looking at it — but I think the core of the report got missed, and the two lines you linked are the clearest way to show it.
The first one isn't the authority instance removing itself. It passes the NetworkObject:
com.unity.netcode.gameobjects/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
Line 3781 in 47bd434
m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); and that lands in a dictionary keyed per NetworkObject with no reference count:
com.unity.netcode.gameobjects/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Lines 294 to 309 in 47bd434
internal void NetworkTransformRegistration(NetworkObject networkObject, bool onUpdate = true, bool register = true) { if (onUpdate) { if (register) { if (!NetworkTransformUpdate.ContainsKey(networkObject.NetworkObjectId)) { NetworkTransformUpdate.Add(networkObject.NetworkObjectId, networkObject); } } else { NetworkTransformUpdate.Remove(networkObject.NetworkObjectId); } } Compare that with your second link,
RegisterForTickUpdate(), which genuinely is per instance —NetworkTransforms.Add(this).Per-object removal, per-instance add. So on a NetworkObject whose NetworkTransforms don't all share an authority mode, every authority-side instance evicts the whole object, non-authority siblings included. That's the bug. We're not asking for OnUpdate on authority instances, and we have no authority motion logic in OnUpdate/OnFixedUpdate — the complaint is that the non-authority root stops getting OnUpdate, because 11 authority-side children removed the object it was registered under.
Three things that I think rule out "working as intended":
OnUpdate()already early-outs onCanCommitToTransform, so the per-instance cost the eviction avoids is a single branch. The dictionary removal only pays for itself when every NetworkTransform on the object is authority-side — which is the one case it can't distinguish.- NGO already anticipates mixed authority on one object:
InternalOnNetworkPostSpawnskips children wherechild.AuthorityMode != AuthorityMode. - It works on a host and fails on a dedicated server, entirely from the
IsConnectedClientgate, and which side wins depends on component order. Neither reads like intent.
Still present on
develop-2.0.0(NetworkManager.cs:307) andrelease/3.0.0(:313), so it isn't a 2.13.2-only thing.On the INetworkUpdateSystem suggestion: that does work, applied to the non-authority instance — register for PreLateUpdate and call base.OnUpdate(). Arguably tidier than the post-spawn workaround in the report. What neither can do is rescue a stock NetworkTransform on such an object, which is why I'd still like it fixed at the registration. That case is real for us: user mods can add one to a player prefab.
Your questions:
- The 11 server-authoritative children are replicated ragdoll pose slots. The player root is owner-authoritative so the client drives its own movement; when a player is knocked down the server owns the ragdoll and writes the bone pose into those children for the other peers to read. One object, two authorities.
- Local space for all 11 (
InLocalSpace: 1); the owner-authoritative root is world space. It can't be the cause though — the object is never inNetworkTransformUpdateat all, so nothing is applied in either space, and the registration path never reads the space setting.
We have since restructured so the slots live on their own server-owned NetworkObject, so we're not blocked on this. Happy to put together a minimal repro project if that would help.
Reacted by Noel Stephens- addedstat:reply-neededAwaiting reply from Unity accountAwaiting reply from Unity accountand removedstat:awaiting-responseAwaiting response from author. This label should be added manually.Awaiting response from author. This label should be added manually.
on Sep 21, 2026 Per-object removal, per-instance add. So on a NetworkObject whose NetworkTransforms don't all share an authority mode, every authority-side instance evicts the whole object, non-authority siblings included.
Right. That would make more sense.
Let me look at that area and get a fix in for that as it needs to gate on removal if the root or a parent above it has an inverted authority motional model from the children.Reacted by Mads Laumann- addedstat:awaiting-responseAwaiting response from author. This label should be added manually.Awaiting response from author. This label should be added manually.and removedstat:reply-neededAwaiting reply from Unity accountAwaiting reply from Unity account
on Sep 21, 2026 @Laumania
The fix for this should land in the next 2.x.x and 3.x.x versions of NGO. 👍Reacted by Mads Laumann@Laumania The fix for this should land in the next 2.x.x and 3.x.x versions of NGO. 👍
Awesome! Thanks for quick response and fix :)
Reacted by Noel Stephens- addedstat:reply-neededAwaiting reply from Unity accountAwaiting reply from Unity accountand removedstat:awaiting-responseAwaiting response from author. This label should be added manually.Awaiting response from author. This label should be added manually.
on Sep 25, 2026 - removedstat:reply-neededAwaiting reply from Unity accountAwaiting reply from Unity account
on Oct 7, 2026
Description
On a dedicated server (
NetworkManager.StartServer(), i.e.IsServer && !IsConnectedClient), the server's copy of an owner-authoritativeNetworkTransformnever updates when the same NetworkObject also carries one or more server-authoritativeNetworkTransforms on child objects. The owning client keeps sending state, the server receives and interpolates it, but the transform is never written — it stays at the position given at connection approval for the lifetime of the object. A host with the identical prefab is unaffected.Root cause (traced in 2.13.2 source and confirmed by reading the registry at runtime, details below):
NetworkManager.NetworkTransformUpdate— the collection that drives non-authorityOnUpdate(), which is the only code path that applies an interpolated position to the transform — is keyed per NetworkObject with no reference count. EveryNetworkTransform.InternalInitializationcalls:So on a NetworkObject with mixed authority, every authority-side transform's initialization removes the entire object, evicting its non-authority siblings. Spawn initialization runs in component order (root first), so the non-authority root adds the object and each authority-side child then removes it.
A host survives because
NetworkTransform.InternalOnNetworkPostSpawnends with:whose
InternalInitializationre-run on the non-authority root re-adds the object after the children evicted it.IsConnectedClientis false on a dedicated server, so that never runs there; and the finalize's only other caller,NotifyNetworkObjectsSynchronized, fires once at server start, never for a late-joining client's object. So the first block of that same method deliberately raisesIsSynchronizing("special case for client-server where a server is spawning an owner authoritative NetworkObject") and nothing on a pure server ever completes it.Reproduce Steps
NetworkTransformsubclass withOnIsServerAuthoritative() => false(owner authority). Add one or more child GameObjects each with a plainNetworkTransformleft atAuthorityMode = Server. (Ours: two owner-authoritative on root/child, eleven server-authoritative children used as replicated pose slots.)StartServer()(notStartHost()), client-server topology.SpawnAsPlayerObject(clientId).client.PlayerObject.transform.position, and checkNetworkManager.NetworkTransformUpdate.ContainsKey(playerObject.NetworkObjectId).Actual Outcome
(0.00, 1.00, 0.00)while the client stood 110 m away).NetworkTransformUpdatedoes not contain the player NetworkObject (ContainsKey == false).CanCommitToTransform = trueand produces state each tick; server-side it reportsCanCommitToTransform = false,IsSpawned = true,enabled = true— willing to receive, butOnUpdate()never runs.Expected Outcome
The non-authority root should remain registered for
OnUpdate()regardless of how many authority-side sibling transforms share the NetworkObject, on a dedicated server exactly as on a host.Screenshots
N/A — runtime readouts above.
Environment
StartServer) — not reproducible on a hostAdditional Context
Two things we ruled out while tracing it, in case they save time:
SynchronizeState.IsSynchronizingis not what blocks reception — it readsTrueon the server before and after the workaround and no stage of the receive path (TransformStateUpdate,OnNetworkStateChanged,ApplyUpdatedState,UpdateInterpolation) gates on it; and setting the serializedAuthorityModetoOwneron the root changes nothing, sinceCanCommitToTransformderives fromIsServerAuthoritative().Workaround we are shipping, in our
NetworkTransformsubclass — it re-runs the finalize on a server that is not also a client, which re-registers the object viaInternalInitialization:It works, but only for transforms of a type we control — a plain
NetworkTransformset to owner authority on such an object is still evicted. A reference count onNetworkTransformUpdate(or registering per transform rather than per object) would fix it at the source; alternatively the post-spawn finalize could run for a non-connected-client server too, since its first block already targets exactly that case.