com.unity.netcode.gameobjects icon indicating copy to clipboard operation
com.unity.netcode.gameobjects copied to clipboard

NetworkTransform should not replicate NaN orientations

Open Edvinas01 opened this issue 2 months ago • 5 comments

Description

Reproduce Steps

  1. Setup a project where you can easily apply NaN position to a NetworkTransform.
  2. Build that project and launch two players, A and B.
  3. Connect player A to session C.
  4. Set NaN position/rotation to player A's transform.
  5. Connect player B to session C.
  6. Notice errors in console and rendering breaking (rendering breaks in builds).

Expected Outcome

NaN position/rotation is not replicated to clients.

Screenshots

This is a video demonstrating the issue, Editor on the left, Build on the right. Note, its not possible to set NaN positions in Editor, only in builds (also sorry for long video, bad internet today):

https://github.com/user-attachments/assets/7769786a-f464-48f5-80a4-2750ac4756ef

Environment

  • OS: Windows 10
  • Unity Version: 6000.0.58f1
  • Netcode Version: 2.5.0/2.5.1
  • Netcode Topology: Distributed Authority

Additional Context

In my case I encountered an issue where a client spawns a Rigidbody inside themselves in a Standalone VR game. Once this happens game, the player's position becomes NaN (don't ask how, took a really long time to figure this one out 😅). When this happens, other clients start getting error spam on their end due to NetworkTransform replicating NaN.

Edvinas01 avatar Oct 01 '25 23:10 Edvinas01

This is a good catch and definitely one that's worth fixing. Thanks for reporting it!

EmandM avatar Oct 07 '25 16:10 EmandM

Note: Potentially could be fixed by #3664, but should not begin investigation until that PR is merged.

NoelStephensUnity avatar Oct 14 '25 23:10 NoelStephensUnity

@Edvinas01

Since this is definitely an edge case scenario, any additional details you could provide will help us determine the best fix for this issue. It sounds like something might be parented under something else that possibly has no scale or the like... but from what it sounds like the NaN issue is the symptom of an issue that occurs just prior to the NaN assignment. So, things like whether you are using NetworkRigidbody or not and any other details pertaining to the spawned objects (i.e. "client spawns a Rigidbody inside themselves"... how is it spawned and what else is contained in that object).

We already appreciate you filing this issue and if you have the time additional details will help greatly.

NoelStephensUnity avatar Oct 15 '25 00:10 NoelStephensUnity

@Edvinas01

Since this is definitely an edge case scenario, any additional details you could provide will help us determine the best fix for this issue. It sounds like something might be parented under something else that possibly has no scale or the like... but from what it sounds like the NaN issue is the symptom of an issue that occurs just prior to the NaN assignment. So, things like whether you are using NetworkRigidbody or not and any other details pertaining to the spawned objects (i.e. "client spawns a Rigidbody inside themselves"... how is it spawned and what else is contained in that object).

We already appreciate you filing this issue and if you have the time additional details will help greatly.

I agree, this is indeed an edge case scenario. My argument is more towards simply not replicating NaNs as it's not useful to other clients and will only result errors. Regardless, to add more details, this was quite a nasty bug:

  • When a network player despawns, we spawn a "corpse" of the player using OnNetworkPreDespawn (we check HasAuthority == false` and if it passes, we spawn).
  • Essentially we instantiate a "head" mesh which has a Rigidbody and no network objects on it, as it's visible only locally for each player and does not collide (it was colliding before) with other dynamic objects.
  • In some rare cases (essentially to repro we would spam connect/disconnect for up to 30min), when a player fails to join a session, spawning the local player prefab would fail and OnNetworkPreDespawn would somehow be fired regardless. When this happens, HasAuthority and other variables are not setup properly (e.g., HasAuthority = false on all clients, even the owner), meaning we'd spawn the "corpse" mesh on the owner client as well.
  • During this moment, if the mesh gets spawned exactly where the actual local player Rigidbody is at that moment, physics would bug out and the Rigidbody would set the transform orientation to NaN from outside of our code. Once I found out how this was happening, I managed to repro this without netcode by binding a button to spawn a corpse locally, however even then I'd have to spam this button for 1-5min to repro in a build. Also, it was not possible to trigger this inside the Editor, only builds.
  • The fix for us was moving the "corpse" meshes to a separate layer to ignore collisions with player objects.

Regarding the OnNetworkPreDespawn, I cannot reliably reproduce this so I can't comment much on that, but I'm certain this was the case as commenting out the override would fix the issue when I was debugging. If I manage to get a repro at some point in a testing project, I'll report this bug. Also, when I say "when a player fails to join a session/spawning the local player prefab would fail", I mean the player actually does join the session, but in a partial state - we sometimes get players joining a session where their player prefab and scene objects are not replicated fully and are invisible, yet their initial transform state, in this case NaN, somehow gets synced. I've been facing the partial state issue since starting to use DA, when our player sync was just a NetworkTransform, so I don't think it's related to our custom code (also no exceptions are thrown so...), maybe it could be related to when we spawn the player :/

Edvinas01 avatar Oct 15 '25 09:10 Edvinas01

@Edvinas01 Thank you for this and if you do come across it please let me know. Regarding NetworkTransform actually synchronizing NaN values, this shouldn't be actually possible since float.NaN will result in errors if you try to apply a Vector3 consisting of all NaN values to transform.position. You would see something like this error message on the non-authority instances (where the stack trace would be originating from within NetworkTransform):

transform.position assign attempt for 'InSceneObjectToDespawn' is not valid. Input position is { NaN, NaN, NaN }.

What most likely is happening is that the Rigidbody body inside of a Rigidbody issue is happening on both the authority and non-authority side which then sets the transform to NaN (on both sides) deeper within the UnityEngine itself.

One additional way to handle this would be to make the newly spawned player's collider(s) ignore the dead body version of the player's collider(s) using Physics.IgnoreCollision like during Awake or the like, check to see if a player spawn point could be inside of the dead body, ignore collision if so, and then once the player is outside of the dead body collider you could re-enabled collisions between the two.

NoelStephensUnity avatar Oct 20 '25 18:10 NoelStephensUnity