Skip to content

fix: hybrid spawn world selection, hybrid parenting compatibility, and parent revert pose - #4202

Open
NoelStephensUnity wants to merge 14 commits into
develop-3.x.xfrom
fix/hybrid-spawn-world-selection
Open

NoelStephensUnity wants to merge 14 commits into
develop-3.x.xfrom
fix/hybrid-spawn-world-selection

Conversation

@NoelStephensUnity

@NoelStephensUnity NoelStephensUnity commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Purpose of this PR

Hybrid prefab instances now always spawn their GhostObject in their own NetworkManager's world, NGO parenting of hybrid instances no longer produces visual artifacts, and a rejected NGO parent change now restores the object's original position. This PR bundles four related hybrid changes, one NGO parenting fix and one test-compile fix.

Spawning a hybrid instance in its NetworkManager's world: N4E spawns a ghost into its active world. A hybrid player prefab, InstantiateAndSpawn, or Instantiate followed by Spawn could therefore spawn the ghost into a client world and fail with "Invalid world ... for spawn". Hybrid prefabs now delay their ghost spawn (GhostObject.DelaySpawning), and the spawn authority spawns the ghost in its own NetworkManager's world before OnNetworkSpawn. The integration test harness no longer sets N4E's active world itself.

Parent replication compatibility: when GhostObject.ParentReplication is enabled, N4E replicates the parent of a hybrid instance and the NGO parenting API and processing are disabled for those instances: TrySetParent / TryRemoveParent log a warning and return false, OnTransformParentChanged early-exits, and the spawn message no longer carries a parent (one less byte per parented hybrid spawn). When hybrid parent replication is enabled, the normal transform.SetParent and direct assignment of transform.parent are how user scripts activate parenting.

NGO parenting of hybrid instances requires worldPositionStays disabled: when NGO handles the parenting (ParentReplication disabled), a reparent with worldPositionStays enabled made clients apply the new parent before the ghost's rebased local transform arrived, so the object popped by the full reparent offset, and with full-tick smoothing it stayed displaced. Users attempting this will now see an error and the parent change does not happen:

  • TrySetParent(parent, true) / TryRemoveParent(true) return false. Note: both default to true, so TrySetParent(parent) must become TrySetParent(parent, false) for hybrid instances.
  • A direct transform.parent = parent (or SetParent(parent, true)) is reverted to the previous parent and local transform. A direct SetParent(parent, false) is accepted and synchronized as worldPositionStays disabled.
  • Parenting in local space (worldPositionStays disabled) still works as expected. A hybrid instance parented under a non-hybrid one still logs the mixed-timelines warning.

A rejected parent change restores the original position (NGO): when NGO rejects a parent change (for example a client without parenting authority, an unspawned NetworkObject, or a parent without a NetworkObject), it reverted the parent by assigning transform.parent, which keeps the world position. A change made with worldPositionStays disabled had already moved the object, so the parent was restored but the object kept its moved position. The position, rotation and scale from before the change are now captured in OnBeforeTransformParentChanged and restored along with the parent.

Hybrid instances need a hybrid session: NGO only checked hybrid prefabs when they were registered, not when an instance was spawned. An unregistered hybrid prefab instance, for example one spawned through a prefab handler, spawned into a session that never entered hybrid mode. Spawning one now logs an error and the spawn does not happen.

NetworkRigidbody tests never compiled: the runtime test assembly was missing the com.unity.modules.physics2d version define, so NetworkRigidbodyTest compiled to nothing on every branch. It now compiles and runs.

PR Scope:

  • NetworkSpawnManager: ghost spawn in the NetworkManager's world for hybrid instances, and hybrid instances are rejected when no hybrid prefab was registered before the session started.
  • NetworkObjectBridge: hybrid prefabs set DelaySpawning.
  • NetworkObject: parent replication disabling NGO parenting, worldPositionStays rejection, the direct parent change check, and restoring the original pose when a parent change is reverted (OnBeforeTransformParentChanged).
  • Tests: HybridSpawnWorldSelectionTests, HybridParentingTests, NetworkObjectParentRevertTests, testproject NetworkObjectSpawning hybrid fixture re-enabled, the runtime test asmdef physics2d define (the two 3D-only physics fixtures now guard on COM_UNITY_MODULES_PHYSICS alone), and UnifiedHybridPrefabValidationTests.HybridSpawnWithoutHybridPrefabsIsRejected.

Issues found, but were not fixed for this pass and require one or more follow-up PRs

  • A child surviving its parent's despawn with ParentReplication enabled is intermittent until N4E PR 129044 ships.

The affected test (ParentReplication_ParentDespawned_ChildSurvivesAtRoot, parent replication enabled) is ignored until trunk PR-129044 lands.

Jira ticket

MTT-15275 ---> NGO side.

Changelog

  • Fixed: Issue where a rejected parent change, for example one made by a client without parenting authority, restored the NetworkObject's parent but left it at the moved position when the change was made with worldPositionStays disabled. The original position, rotation and scale are now restored.

Documentation

  • No documentation changes or additions were necessary.

Testing & QA (How your changes can be verified during release Playtest)

Functional Testing

Manual testing :

  • Manual testing done

Automated tests:

  • Covered by existing automated tests
  • Covered by new automated tests

Does the change require QA team to:

  • Review automated tests?
  • Execute manual tests?
  • Provide feedback about the PR?

Up-port

Not applicable.

Backports

The parent revert fix will be ported to develop-2.0.0 in a separate PR. The hybrid changes exist on develop-3.x.x only.

N4E spawns a ghost into its active world, which is whichever world updated last when several worlds share a process. Hybrid player prefabs, InstantiateAndSpawn and Instantiate followed by Spawn could therefore spawn the ghost into a client world and fail with "Invalid world ... for spawn".

Hybrid prefabs now delay their ghost spawn (GhostObject.DelaySpawning, available in N4E 7.1.0), and the spawn authority spawns the ghost in its own NetworkManager's world before OnNetworkSpawn. The integration test harness no longer sets N4E's active world itself.

Adds HybridSpawnWorldSelectionTests, which make a client world active before each spawn.
…enable hybrid NetworkObjectSpawning

N4E makes the server world active again after each client world update, so making a client world active from the test body no longer reached the player spawn, and LateJoinPlayerSpawnsGhostInServerWorld passed without the fix. A server-side prefab handler now makes the client world active right before the player is instantiated.

Re-adds the UnifiedHost fixture to the testproject NetworkObjectSpawning tests, now that InstantiateAndSpawn selects the NetworkManager's world for hybrid prefabs. SpawnOnSynchronizedNotification is skipped under hybrid (in-scene placed NetworkObjects).
When a hybrid prefab's GhostObject has ParentReplication enabled, Netcode for
Entities owns parenting and NGO stands down:
- TrySetParent and TryRemoveParent log a warning and return false.
- OnTransformParentChanged and ApplyNetworkParenting exit silently.
- The spawn message no longer carries the parent; the ghost's spawn does.
- Despawning a parent finds first generation children by hierarchy.

ParentReplication only exists in com.unity.netcode 7.0.1+, so this is gated on
the new UNIFIED_NETCODE_PARENT_REPLICATION version define.

When NGO handles parenting of a hybrid instance, the authority logs at
Developer level when the result can show visual artifacts: re-parenting with
worldPositionStays enabled, or parenting where only one of child and parent is
a hybrid instance.
…he child in place

Ignored until N4E syncs a reparent made without parent replication to the entity.
Unity.Netcode.Runtime.Tests declared a versionDefine for com.unity.modules.physics but not for
com.unity.modules.physics2d, while Unity.Netcode.Runtime declares both. Tests/Runtime/Physics/
NetworkRigidbodyTest.cs opens with

    #if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D

so COM_UNITY_MODULES_PHYSICS2D was never defined for the assembly the file belongs to and the
whole fixture compiled to nothing - three TestFixture variants over five rigidbody
interpolation configurations, the only NetworkRigidbody integration coverage there is. It
reported no results rather than failing, so the suite has been green on tests that do not
exist. Confirmed absent from the built Unity.Netcode.Runtime.Tests.dll, and present on
develop-2.0.0 as well.

The two sibling files guarded on the same pair use || rather than &&, so they were unaffected.
PHYSICS2D_LINEAR is deliberately not added - no test references it.

With the define in place the fixture compiles and passes 3/3 on 6000.7.0a5.
UNIFIED_NETCODE already requires N4E 7.2.0, and GhostObject.ParentReplication ships from 7.1.0, so the separate N4E >= 7.0.1 define is no longer needed.
A hybrid instance's ghost replicates its local transform. With worldPositionStays the authority rebases that local transform at the reparent, but clients apply ParentSyncMessage ahead of the interpolated snapshot carrying the rebased values, so the object pops by the full reparent offset. With host smoothing it also stayed displaced.

When GhostObject.ParentReplication is off, TrySetParent and TryRemoveParent with worldPositionStays enabled now log an error and return false. A direct Transform parent change, which carries no worldPositionStays value, is accepted when it kept the local transform and otherwise reverted to the previous parent and local transform, captured in OnBeforeTransformParentChanged.

Hybrid fixtures that parented with worldPositionStays enabled now disable it.
When NGO rejected a parent change (no parenting authority, an unspawned NetworkObject, a parent without a NetworkObject), it reverted by assigning transform.parent, which keeps the world position. A change made with worldPositionStays disabled had already moved the object, so the parent came back but the object stayed at the moved position.

OnBeforeTransformParentChanged now captures the local position, rotation and scale before every parent change, and a revert restores the cached parent with those values. The hybrid direct parent change check uses the same capture instead of its own.

Adds NetworkObjectParentRevertTests (Host, Server, DAHost).
@NoelStephensUnity
NoelStephensUnity requested review from a team and EmandM as code owners October 20, 2026 15:27

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The runtime changes have focused regression coverage, but the expanded test compilation needs a compatibility correction.

Reviewed commit 4e9e5f3

🤖 Helpful? 👍/👎

Short sentences, N4E instead of the full name, lists for separate cases, and full XML docs on the ShouldCheckForSpawnedPlayers override. MakeClientWorldActive becomes an inlined MakeWorldActive(NetworkManager) helper without a comment that restated its name.
NetworkTransformOwnershipTests and RigidbodyContactEventManagerTests use Rigidbody, NetworkRigidbody
and RigidbodyContactEventManager, which only exist with com.unity.modules.physics. Now that the test
assembly defines COM_UNITY_MODULES_PHYSICS2D, their PHYSICS || PHYSICS2D guard would fail to compile
in a project with Physics2D but without 3D physics.
…efabs

Only prefab registration rejected hybrid prefabs. An unregistered hybrid prefab instance, for
example one spawned through a prefab handler, spawned into a session that never entered hybrid
mode and ran its ghost spawn, ownership and bridge code without a unified world. The spawn
authority now logs an error and refuses the spawn.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant