From 58dc11e56d039eafc1b56b2559e5656a50b85435 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 4 Sep 2026 12:25:46 -0500 Subject: [PATCH 01/10] test: cover the timing namespace relocation in the API updater project Adds the input and the assertions for moving NetworkTime, NetworkTimeSystem and NetworkTickSystem out of Unity.Netcode, ahead of the move itself. Assets/Runtime/DeprecatedTimingUsage.cs names all three in every reference form the editor project already covers, plus a constructor call, and names each one fully qualified at least once so a blocked run can tell "was not rewritten" from "was never referenced". EXPECTED_TYPES becomes EXPECTED_MOVES, grouped by relocation. The old list derived the 3.x name by substituting the namespace prefix, which only works while every move shares one destination; stating the namespace pair once per move keeps a type's two names from drifting and admits a second destination. Both match counts now take a trailing-token boundary, without which Unity.Netcode.NetworkTime also counts every Unity.Netcode.NetworkTimeSystem. --collision-stub is the regression test for why the move exists. It installs an assembly occupying the two names Netcode for Entities would take, and inverts the expectation for exactly those two: a reference that still resolves never reaches the MovedFrom data, so it cannot be migrated. NetworkTickSystem is deliberately absent from the stub and must still migrate, so a pass proves both halves rather than merely failing. The stub lives in a folder ending in '~' and is inert until the flag copies it in. Not run locally: there is no Python on the development machine, so this is verified by /ci apiupdater. The namespace-only form of MovedFrom is also unmeasured until then - every case in the AGENTS.md table moved the assembly too - and AGENTS.md now records that gap along with the one reference form the assertions deliberately do not depend on. --- apiupdaterproject/AGENTS.md | 49 ++++- .../CollisionStub~/N4E.CollisionStub.asmdef | 14 ++ .../Assets/CollisionStub~/N4ECollisionStub.cs | 22 +++ apiupdaterproject/Assets/Runtime.meta | 8 + .../Assets/Runtime/DeprecatedTimingUsage.cs | 45 +++++ .../Runtime/DeprecatedTimingUsage.cs.meta | 11 ++ apiupdaterproject/README.md | 45 ++++- apiupdaterproject/run_upgrade_test.py | 186 +++++++++++++----- 8 files changed, 320 insertions(+), 60 deletions(-) create mode 100644 apiupdaterproject/Assets/CollisionStub~/N4E.CollisionStub.asmdef create mode 100644 apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs create mode 100644 apiupdaterproject/Assets/Runtime.meta create mode 100644 apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs create mode 100644 apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs.meta diff --git a/apiupdaterproject/AGENTS.md b/apiupdaterproject/AGENTS.md index e1d405fbd4..1828649784 100644 --- a/apiupdaterproject/AGENTS.md +++ b/apiupdaterproject/AGENTS.md @@ -7,15 +7,17 @@ what it is and how to run it; this file covers why it is built this way and what * This is a standalone Unity project at the repo root. It is not part of `testproject` or `minimalproject`, and the package does not reference it. -* It validates one thing end to end: that a project written against the **NGO 2.x** editor API is - migrated automatically by Unity's API updater when the package is upgraded to **3.x**. +* It validates one thing end to end: that a project written against the **NGO 2.x** API is migrated + automatically by Unity's API updater when the package is upgraded to **3.x**. Two relocations are + covered — the editor namespaces, and the runtime timing types. * **The mechanism it tests does not live here.** The `[MovedFrom]` attributes are on the real types in - `com.unity.netcode.gameobjects/Editor/**`. This project only consumes them. -* **Do not "fix" the sources under `Assets/Editor`.** They are deliberately written against the 2.x - API and are the input to the test. A helpful cleanup there silently guts it. -* The expected-type list in `run_upgrade_test.py` is frozen: it enumerates the public editor API of - `develop-2.0.0`, which is released and cannot change. It only needs extending if a public editor - type is relocated again within 3.x. + `com.unity.netcode.gameobjects/Editor/**` and `com.unity.netcode.gameobjects/Runtime/Timing/**`. + This project only consumes them. +* **Do not "fix" the sources under `Assets/Editor` or `Assets/Runtime`.** They are deliberately + written against the 2.x API and are the input to the test. A helpful cleanup there silently guts it. +* The two editor blocks of `EXPECTED_MOVES` in `run_upgrade_test.py` are frozen: they enumerate the + public editor API of `develop-2.0.0`, which is released and cannot change. A block only needs + extending if a public type is relocated again within 3.x — as the timing types were. * CI runs it on demand only — comment `/ci apiupdater` on a PR. See `.yamato/api-updater-test.yml`. * **Opening this project locally mutates it.** Unity rewrites `ProjectVersion.txt` to whatever editor opened it, and the package manager can add builtin modules to `Packages/manifest.json` that only @@ -63,6 +65,37 @@ alias, type alias, base type, `typeof`, and generic type argument. The dead `using Unity.Netcode.Editor;` directives are removed and namespace aliases are rewritten in place rather than expanded at each use. +## The timing move is a namespace-only relocation, and that is a different case + +Every row measured above was a namespace **and** assembly move. The timing types keep their assembly +(`Unity.Netcode.Runtime`), so they carry `[MovedFrom(true, "Unity.Netcode", null, null)]` — a null +`sourceAssembly`, which `MovedFromAttributeData.Set` records as `assemblyHasChanged = false`. + +Two things about that are worth knowing before trusting it: + +* **The null form is the documented one.** The attribute's own comment states that any null string is + read as "has not changed" and its value is taken from the decorated type, and there is a + single-argument `MovedFromAttribute(string sourceNamespace)` constructor that does exactly + `Set(true, ns, null, null)`. Passing the real assembly name instead would set `assemblyHasChanged` + for a change that did not happen. +* **It has not been measured here.** The table above has no namespace-only row. `Assets/Runtime` plus + the timing block in `EXPECTED_MOVES` is what settles it; a `/ci apiupdater` run is the proof. + +Do not reach for `AffectsAPIUpdater` to reason about this. It reads +`!classHasChanged && !assemblyHasChanged`, which would make it false for the editor move — and the +editor move demonstrably works, so whatever that property gates, it is not script rewriting. + +### Open form: a namespace alias whose target survives + +`Assets/Runtime/DeprecatedTimingUsage.cs` contains `using TimeNs = Unity.Netcode;` used as +`TimeNs.NetworkTickSystem`. This is **not** the same case as the editor project's +`using Cfg = Unity.Netcode.Editor.Configuration;`: there the alias target itself stopped resolving and +was rewritten in place, whereas `Unity.Netcode` still exists and still holds `NetworkBehaviour` and +the rest. So the alias target cannot be rewritten and the use site has to be. The assertions do not +depend on this form — `NetworkTickSystem` is also referenced by simple name and fully qualified — so +if the updater leaves that one line alone the run still passes. Read the rewritten source rather than +assuming it was handled. + ## Known gap: assembly definition references The updater rewrites C# source only; it does not touch `.asmdef` files. diff --git a/apiupdaterproject/Assets/CollisionStub~/N4E.CollisionStub.asmdef b/apiupdaterproject/Assets/CollisionStub~/N4E.CollisionStub.asmdef new file mode 100644 index 0000000000..990f624227 --- /dev/null +++ b/apiupdaterproject/Assets/CollisionStub~/N4E.CollisionStub.asmdef @@ -0,0 +1,14 @@ +{ + "name": "N4E.CollisionStub", + "rootNamespace": "Unity.Netcode", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": true +} diff --git a/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs b/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs new file mode 100644 index 0000000000..f3d1844471 --- /dev/null +++ b/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs @@ -0,0 +1,22 @@ +// Stands in for a second package occupying Unity.Netcode.NetworkTime and +// Unity.Netcode.NetworkTimeSystem, which is what Netcode for Entities does once the casing of its +// Unity.NetCode namespace is corrected. +// +// Only those two names collide. NetworkTickSystem deliberately is not declared here, so a +// --collision-stub run asserts both halves of the finding in one pass: the updater migrates +// NetworkTickSystem, and it cannot migrate the two whose old names still resolve. +// +// Inert until run_upgrade_test.py --collision-stub copies this folder into place. Unity does not +// import a directory whose name ends in '~'. +namespace Unity.Netcode +{ + public struct NetworkTime + { + public int ServerTick; + } + + public class NetworkTimeSystem + { + public uint EffectiveInputLatencyTicks; + } +} diff --git a/apiupdaterproject/Assets/Runtime.meta b/apiupdaterproject/Assets/Runtime.meta new file mode 100644 index 0000000000..8babe3aec7 --- /dev/null +++ b/apiupdaterproject/Assets/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6883b96a65c44c9c8738cec1134d7372 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs b/apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs new file mode 100644 index 0000000000..580d147358 --- /dev/null +++ b/apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs @@ -0,0 +1,45 @@ +// Update only if the relocated runtime timing API changes. Deliberately written against the +// pre-move namespace: this file is the test's input, not code to clean up. See ../../README.md. +// +// Each of the three types is named at least once in fully qualified form, so a --collision-stub run +// can tell "was not rewritten" apart from "was never referenced". +#pragma warning disable 169 // Ignore field is never used warnings + +using System; +using System.Collections.Generic; +using Unity.Netcode; +using TimeNs = Unity.Netcode; +using TimeValue = Unity.Netcode.NetworkTime; + +namespace ApiUpdaterProject +{ + // Unity.Netcode -> Unity.Netcode.GameObjects.Timing + internal class DeprecatedTimingUsage + { + // using directive plus simple name + private NetworkTime m_SimpleName; + private NetworkTimeSystem m_TimeSystem; + private NetworkTickSystem m_TickSystem; + + // Fully qualified + private Unity.Netcode.NetworkTime m_TimeFullyQualified; + private Unity.Netcode.NetworkTimeSystem m_TimeSystemFullyQualified; + private Unity.Netcode.NetworkTickSystem m_TickSystemFullyQualified; + + // Through a namespace alias, and through a type alias + private TimeNs.NetworkTickSystem m_ThroughNamespaceAlias; + private TimeValue m_ThroughTypeAlias; + + // As a generic type argument + private List m_AsGenericArgument; + + // typeof + private Type TimeSystemType => typeof(NetworkTimeSystem); + + // Constructor call, and as a return type + private NetworkTime Construct(uint tickRate) => new NetworkTime(tickRate, 0d); + + // As a parameter type + private static double TickOf(NetworkTime time) => time.TickWithPartial; + } +} diff --git a/apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs.meta b/apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs.meta new file mode 100644 index 0000000000..560f2be9b5 --- /dev/null +++ b/apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9442f064ea741998cc38366ada397f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/apiupdaterproject/README.md b/apiupdaterproject/README.md index 6fa786b521..984feb3b27 100644 --- a/apiupdaterproject/README.md +++ b/apiupdaterproject/README.md @@ -13,6 +13,18 @@ NGO 3.0 renamed the editor assembly and its namespaces: | `Unity.Netcode.Editor.CodeGen` | `Unity.Netcode.GameObjects.Editor.CodeGen` | | `Unity.Netcode.PackageChecker.Editor` | `Unity.Netcode.GameObjects.PackageChecker.Editor` | +It also relocated the runtime timing types out of the root namespace, so that correcting the casing +of Netcode for Entities' `Unity.NetCode` namespace does not collide with them: + +| 2.x | 3.x | +| --- | --- | +| `Unity.Netcode.NetworkTime` | `Unity.Netcode.GameObjects.Timing.NetworkTime` | +| `Unity.Netcode.NetworkTimeSystem` | `Unity.Netcode.GameObjects.Timing.NetworkTimeSystem` | +| `Unity.Netcode.NetworkTickSystem` | `Unity.Netcode.GameObjects.Timing.NetworkTickSystem` | + +The **assembly is unchanged** for the timing move — only the namespace — so those three carry a null +`sourceAssembly`, which the attribute reads as "unchanged". + ### NGO v2.x.x Unity.Netcode.Editor changes @@ -25,7 +37,9 @@ file or the DeprecatedApiUsageQualified.cs files are updated to reflect the adde | --- | --- | | `Assets/Editor/DeprecatedApiUsage.cs` | Every public 2.x editor type through `using` + simple name | | `Assets/Editor/DeprecatedApiUsageQualified.cs` | Fully qualified names, namespace alias, type alias, base type, `typeof`, generic | +| `Assets/Runtime/DeprecatedTimingUsage.cs` | The three relocated timing types, in every reference form plus a constructor call | | `Assets/UpgradeProbeBehaviour.cs` | The `MonoBehaviour` used as the `NetcodeEditorBase` type argument | +| `Assets/CollisionStub~/` | An assembly occupying the two colliding timing names. Inert — Unity does not import a folder whose name ends in `~` — until `--collision-stub` copies it in | `UpgradeProbeBehaviour` exists so the test does not name NGO's `NetworkManager`: com.unity.transport 6.6.0 — the builtin on some 6000.6 editors — ships a `Unity.Netcode.NetworkManager` of its own in @@ -38,8 +52,8 @@ resolved transport version. ## Running it locally `run_upgrade_test.py` imports the project in batch mode with `-accept-apiupdate`, then asserts that -every 2.x type reference under `Assets/Editor` was rewritten and that none survived. It restores the -2.x sources when it finishes, so it can be re-run. Windows, macOS and Linux. +every 2.x type reference under `Assets/Editor` and `Assets/Runtime` was rewritten and that none +survived. It restores the 2.x sources when it finishes, so it can be re-run. Windows, macOS and Linux. ```sh python run_upgrade_test.py --unity --clean --keep-updated-sources @@ -50,6 +64,24 @@ python run_upgrade_test.py --unity --clean --keep-updated-sources | `--unity` | Omit it if `UNITY_EDITOR_PATH` is set, or if the hub has the version named in `ProjectSettings/ProjectVersion.txt`. | | `--clean` | Purges `Library` and `Temp` first for a cold import. | | `--keep-updated-sources` | Leaves the rewritten sources in place so `git diff` shows exactly what the updater produced. | +| `--collision-stub` | Adds an assembly occupying `Unity.Netcode.NetworkTime` and `NetworkTimeSystem`, then **inverts** the expectation for those two. See below. | + +### The `--collision-stub` run + +This is the regression test for the reason the timing move exists. With the stub installed, a 2.x +reference to `NetworkTime` still resolves — to the stub — so it never fails to resolve, never reaches +the `MovedFrom` data, and cannot be migrated. `NetworkTickSystem` is deliberately **not** in the stub, +so the same run asserts that one still migrates. A pass therefore proves both halves: + +| Type | Expected under `--collision-stub` | +| --- | --- | +| `Unity.Netcode.NetworkTime` | **not** rewritten | +| `Unity.Netcode.NetworkTimeSystem` | **not** rewritten | +| `Unity.Netcode.NetworkTickSystem` | rewritten | +| every editor type | rewritten | + +If a future change ever makes the two blocked rows pass as "rewritten", the mechanism has changed and +the one-sided-move conclusion needs revisiting. Default hub locations, if you need to pass `--unity` explicitly — note that on macOS the binary is inside the `.app` bundle rather than beside it: @@ -73,6 +105,15 @@ Every relocated public editor type carries The arguments are `autoUpdateAPI, sourceNamespace, sourceAssembly, sourceClassName` — a null class name means the type name itself did not change. +The three relocated timing types carry + +```csharp +[MovedFrom(true, "Unity.Netcode", null, null)] +``` + +with `sourceAssembly` null because `Unity.Netcode.Runtime` keeps its name: any null argument is read +as "this did not change", and its value is taken from the decorated type. + A 2.x reference no longer resolves, so the compiler reports CS0246/CS0234. Unity's `ScriptUpdater` consults the `MovedFrom` data extracted from the referenced assemblies, matches the old namespace/assembly, and rewrites the reference. Nothing extra ships: no skeleton assembly, no diff --git a/apiupdaterproject/run_upgrade_test.py b/apiupdaterproject/run_upgrade_test.py index ab5d33b0f0..7055b971dd 100644 --- a/apiupdaterproject/run_upgrade_test.py +++ b/apiupdaterproject/run_upgrade_test.py @@ -1,12 +1,20 @@ #!/usr/bin/env python3 """ -Verifies that Unity's API updater rewrites NGO 2.x editor API references to their NGO 3.x -Unity.Netcode.GameObjects.Editor equivalents. Runs on Windows, macOS and Linux. +Verifies that Unity's API updater rewrites NGO 2.x API references to their NGO 3.x equivalents: +editor types to Unity.Netcode.GameObjects.Editor, and the runtime timing types to +Unity.Netcode.GameObjects.Timing. Runs on Windows, macOS and Linux. -Imports the project in batch mode with -accept-apiupdate, then asserts that every -Unity.Netcode.Editor reference under Assets/Editor was rewritten and that no stale reference +Imports the project in batch mode with -accept-apiupdate, then asserts that every relocated +reference under Assets/Editor and Assets/Runtime was rewritten and that no stale reference survived. The 2.x sources are restored on exit so the test can be re-run. +With --collision-stub, a stub assembly is added that occupies Unity.Netcode.NetworkTime and +Unity.Netcode.NetworkTimeSystem, standing in for a second package that has taken those names. The +expectation then inverts for exactly those two: the updater is driven by resolution failure, so a +name another assembly still resolves never reaches the MovedFrom data and cannot be migrated. +NetworkTickSystem is deliberately absent from the stub and must still migrate, which is what makes +the run prove both halves rather than merely fail. + Note that this script can be run from anywhere; paths are resolved relative to the script itself. """ @@ -20,30 +28,57 @@ import tempfile PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) -SOURCE_DIR = os.path.join(PROJECT_PATH, 'Assets', 'Editor') +SOURCE_DIRS = [os.path.join(PROJECT_PATH, 'Assets', 'Editor'), + os.path.join(PROJECT_PATH, 'Assets', 'Runtime')] LOG_FILE = os.path.join(PROJECT_PATH, 'upgrade-test.log') -# Every 2.x type the sources reference. The 3.x name is derived, so the pair cannot drift. -# Frozen: this is the public editor API of develop-2.0.0, which is released and will not change. -# Extend it by hand if a public editor type is ever relocated again within 3.x. -EXPECTED_TYPES = [ - 'Unity.Netcode.Editor.HiddenScriptEditor', - 'Unity.Netcode.Editor.UnityTransportEditor', - 'Unity.Netcode.Editor.NetworkAnimatorEditor', - 'Unity.Netcode.Editor.NetworkRigidbodyEditor', - 'Unity.Netcode.Editor.NetworkRigidbody2DEditor', - 'Unity.Netcode.Editor.NetcodeEditorBase', - 'Unity.Netcode.Editor.NetworkBehaviourEditor', - 'Unity.Netcode.Editor.NetworkManagerEditor', - 'Unity.Netcode.Editor.NetworkManagerHelper', - 'Unity.Netcode.Editor.NetworkObjectEditor', - 'Unity.Netcode.Editor.NetworkRigidbodyBaseEditor', - 'Unity.Netcode.Editor.NetworkTransformEditor', - 'Unity.Netcode.Editor.NetworkPrefabsEditor', - 'Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings', - 'Unity.Netcode.Editor.Configuration.NetworkPrefabProcessor', +# A directory whose name ends in '~' is not imported, so the stub is inert until it is copied in. +STUB_SOURCE = os.path.join(PROJECT_PATH, 'Assets', 'CollisionStub~') +STUB_TARGET = os.path.join(PROJECT_PATH, 'Assets', 'CollisionStub') + +# Every 2.x type the sources reference, grouped by the move that relocated it. Stating the namespace +# pair once per move means a type's old and new names cannot drift apart. +# +# The two editor entries are frozen: they are the public editor API of develop-2.0.0, which is +# released and will not change. Extend a list only when a public type is relocated again within 3.x. +EXPECTED_MOVES = [ + ('Unity.Netcode.Editor', 'Unity.Netcode.GameObjects.Editor', [ + 'HiddenScriptEditor', + 'UnityTransportEditor', + 'NetworkAnimatorEditor', + 'NetworkRigidbodyEditor', + 'NetworkRigidbody2DEditor', + 'NetcodeEditorBase', + 'NetworkBehaviourEditor', + 'NetworkManagerEditor', + 'NetworkManagerHelper', + 'NetworkObjectEditor', + 'NetworkRigidbodyBaseEditor', + 'NetworkTransformEditor', + 'NetworkPrefabsEditor', + ]), + ('Unity.Netcode.Editor.Configuration', 'Unity.Netcode.GameObjects.Editor.Configuration', [ + 'NetcodeForGameObjectsProjectSettings', + 'NetworkPrefabProcessor', + ]), + ('Unity.Netcode', 'Unity.Netcode.GameObjects.Timing', [ + 'NetworkTime', + 'NetworkTimeSystem', + 'NetworkTickSystem', + ]), ] +# The names the --collision-stub assembly occupies; under it these must NOT be rewritten. +# Keep in sync with Assets/CollisionStub~/N4ECollisionStub.cs. +STUB_OCCUPIED = ['Unity.Netcode.NetworkTime', 'Unity.Netcode.NetworkTimeSystem'] + + +def expected_pairs(): + """Yields (old fully qualified name, new fully qualified name) for every relocated type.""" + for old_namespace, new_namespace, names in EXPECTED_MOVES: + for name in names: + yield f"{old_namespace}.{name}", f"{new_namespace}.{name}" + def find_editor_binary(path): """ @@ -138,7 +173,7 @@ def purge_tree(path): def copy_flat(from_dir, to_dir): - """Copies the files of a flat directory. Assets/Editor has no subdirectories.""" + """Copies the files of a flat directory. The source directories have no subdirectories.""" os.makedirs(to_dir, exist_ok=True) for entry in os.listdir(from_dir): source = os.path.join(from_dir, entry) @@ -146,16 +181,45 @@ def copy_flat(from_dir, to_dir): shutil.copy2(source, os.path.join(to_dir, entry)) +def backup_sources(backup_root): + """Copies every source directory into its own subdirectory of the backup root.""" + for source_dir in SOURCE_DIRS: + copy_flat(source_dir, os.path.join(backup_root, os.path.basename(source_dir))) + + +def restore_sources(backup_root): + """Restores every source directory from the backup root.""" + for source_dir in SOURCE_DIRS: + copy_flat(os.path.join(backup_root, os.path.basename(source_dir)), source_dir) + + def read_sources(): - """Returns the concatenated text of every .cs file under Assets/Editor.""" + """Returns the concatenated text of every .cs file in the source directories.""" parts = [] - for entry in sorted(os.listdir(SOURCE_DIR)): - if entry.endswith('.cs'): - with open(os.path.join(SOURCE_DIR, entry), encoding='utf-8-sig') as handle: - parts.append(handle.read()) + for source_dir in SOURCE_DIRS: + for entry in sorted(os.listdir(source_dir)): + if entry.endswith('.cs'): + with open(os.path.join(source_dir, entry), encoding='utf-8-sig') as handle: + parts.append(handle.read()) return '\n'.join(parts) +def install_stub(): + """Copies the collision stub into Assets so the editor imports it.""" + if not os.path.isdir(STUB_SOURCE): + sys.exit(f"Collision stub not found at {STUB_SOURCE}") + shutil.copytree(STUB_SOURCE, STUB_TARGET, dirs_exist_ok=True) + print(f"Installed the collision stub at {STUB_TARGET}") + + +def remove_stub(): + """Removes the collision stub and the .meta the editor generated beside it.""" + shutil.rmtree(STUB_TARGET, ignore_errors=True) + generated_meta = STUB_TARGET + '.meta' + if os.path.isfile(generated_meta): + os.remove(generated_meta) + + def run_editor(unity): """Imports the project in batch mode with the API updater enabled.""" if os.path.exists(LOG_FILE): @@ -175,31 +239,42 @@ def run_editor(unity): print(f"Editor exit code: {result.returncode}") -def assert_rewritten(): - """Prints a per-type result table and returns the number of types that were not rewritten.""" +def assert_rewritten(collision_stub): + """ + Prints a per-type result table and returns the number of types whose outcome was not the expected + one. Under the collision stub the expectation inverts for the names the stub occupies. + """ all_text = read_sources() - failures = 0 - print(f"\n{'TYPE':<72} {'UPDATED':>8} {'STALE':>6} RESULT") - for old in EXPECTED_TYPES: - new = old.replace('Unity.Netcode.', 'Unity.Netcode.GameObjects.', 1) + # Both counts need a trailing-token boundary: a following word character or dot means the match + # is really part of a longer name. Without it 'Unity.Netcode.NetworkTime' also counts every + # 'Unity.Netcode.NetworkTimeSystem'. + boundary = r'(?![\w.])' - updated = len(re.findall(re.escape(new), all_text)) - # The old name survives only as a distinct token: a trailing word character or dot means this - # is really part of the longer new name. - stale = len(re.findall(re.escape(old) + r'(?![\w.])', all_text)) + failures = 0 + print(f"\n{'TYPE':<72} {'UPDATED':>8} {'STALE':>6} {'EXPECT':>8} RESULT") + for old, new in expected_pairs(): + updated = len(re.findall(re.escape(new) + boundary, all_text)) + stale = len(re.findall(re.escape(old) + boundary, all_text)) + + blocked = collision_stub and old in STUB_OCCUPIED + if blocked: + # The old name still resolves to the stub, so the reference must have been left alone. + passed = updated == 0 and stale > 0 + else: + passed = updated > 0 and stale == 0 - passed = updated > 0 and stale == 0 if not passed: failures += 1 - print(f"{old:<72} {updated:>8} {stale:>6} {'PASS' if passed else 'FAIL'}") + expect = 'blocked' if blocked else 'moved' + print(f"{old:<72} {updated:>8} {stale:>6} {expect:>8} {'PASS' if passed else 'FAIL'}") return failures def main(): parser = argparse.ArgumentParser( - description="Verifies that Unity's API updater migrates NGO 2.x editor API references to 3.x.") + description="Verifies that Unity's API updater migrates NGO 2.x API references to 3.x.") parser.add_argument('--unity', default='', help='Editor binary. Defaults to UNITY_EDITOR_PATH, then to the hub install ' 'matching ProjectSettings/ProjectVersion.txt.') @@ -207,14 +282,19 @@ def main(): help='Delete Library and Temp first, for a cold import.') parser.add_argument('--keep-updated-sources', action='store_true', help='Leave the rewritten sources in place instead of restoring the originals.') + parser.add_argument('--collision-stub', action='store_true', + help='Add an assembly occupying Unity.Netcode.NetworkTime and ' + 'NetworkTimeSystem, and assert those two are NOT migrated while ' + 'NetworkTickSystem still is.') args = parser.parse_args() unity = resolve_unity(args.unity) print(f"Editor: {unity}") print(f"Project: {PROJECT_PATH}") + total = sum(1 for _ in expected_pairs()) backup_dir = tempfile.mkdtemp(prefix='ngo-apiupdater-') - copy_flat(SOURCE_DIR, backup_dir) + backup_sources(backup_dir) try: if args.clean: @@ -224,24 +304,30 @@ def main(): print(f"Removing {stale} ...") purge_tree(target) + if args.collision_stub: + install_stub() + run_editor(unity) - failures = assert_rewritten() + failures = assert_rewritten(args.collision_stub) print('') if failures == 0: - print(f"PASS: all {len(EXPECTED_TYPES)} deprecated editor types were rewritten.") + print(f"PASS: all {total} relocated types behaved as expected.") else: - print(f"FAIL: {failures} of {len(EXPECTED_TYPES)} types were not rewritten. See {LOG_FILE}") + print(f"FAIL: {failures} of {total} types did not. See {LOG_FILE}") if args.keep_updated_sources: - print(f"Rewritten sources left in place under Assets/Editor (backup: {backup_dir}).") + dirs = ', '.join(os.path.basename(d) for d in SOURCE_DIRS) + print(f"Rewritten sources left in place under Assets/{{{dirs}}} (backup: {backup_dir}).") return 0 if failures == 0 else 1 finally: # Restore on every exit path, including Ctrl-C, so an interrupted run never leaves the - # rewritten sources behind as the next run's input. + # rewritten sources or the stub behind as the next run's input. + if args.collision_stub: + remove_stub() if not args.keep_updated_sources: - copy_flat(backup_dir, SOURCE_DIR) + restore_sources(backup_dir) shutil.rmtree(backup_dir, ignore_errors=True) From a7c8e62b0e817730d56ab2a5577f0f7fd0bfa97a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 4 Sep 2026 12:39:30 -0500 Subject: [PATCH 02/10] chore: move the timing types to Unity.Netcode.GameObjects.Timing Runtime/Timing moves out of the Unity.Netcode root so that correcting the casing of Netcode for Entities' Unity.NetCode namespace does not collide with it. Two of the names are the collision: NetworkTime and NetworkTimeSystem exist in both SDKs as unrelated types - a time value here, an IComponentData carrying prediction-loop state there - and two assemblies exporting one fully qualified name is CS0433, which no user can work around in source. The three public types carry [MovedFrom(true, "Unity.Netcode", null, null)], so existing scripts are rewritten on upgrade. sourceAssembly is null because Unity.Netcode.Runtime keeps its name and a null argument is read as "unchanged"; the editor relocation passed an assembly name because that one genuinely moved assemblies. The three internal types in the folder carry nothing - the attribute only matters for API the updater has to migrate. Consumers take an import rather than a qualified name at each site. Qualification was the first approach and it is wrong here: .editorconfig sets IDE0001 to error, so a fully qualified name that the simplifier can shorten fails the Standards job. That leaves NGO's own references reading the bare name, which is correct as long as nothing else occupies Unity.Netcode.NetworkTime - if Netcode for Entities takes those names without vacating them, this assembly stops compiling wherever both packages are installed. That is deliberate: it fails early and loudly in our own CI rather than silently in user projects, and the recommendation both halves of this work rest on is that neither SDK keeps those names in the shared root. Files whose declared namespace is exactly Unity.Netcode cannot use an import for the two colliding names at all - the enclosing namespace's members beat both using directives and using aliases (CS0576) - so if the one-sided case ever has to be supported, those five files need qualified names and the rest do not. Sub-namespaces such as Unity.Netcode.Components never walk that far up and are fine either way. NetworkTimeSystem and AnticipationSystem now import Unity.Netcode themselves, and the five Components.NetworkTransform doc references in NetworkTimeSystem are spelled in full, since that prefix was only reachable from inside the root namespace. Compile-checked: runtime (with and without UNITY_EDITOR), editor, runtime tests and editor tests all clean, the last except the known environmental CS0656 on BytePackerTests. The UNIFIED_NETCODE path is not covered - the harness has no Entities or NetCode references - so UnifiedNetcodeTransport was checked by reading it: IRealTimeProvider at line 207 is a type position, so its import is needed. --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 ++++ .../Components/AnticipatedNetworkTransform.cs | 1 + .../Interpolator/BufferedLinearInterpolator.cs | 1 + .../Runtime/Components/NetworkTransform.cs | 1 + .../Runtime/Core/ComponentFactory.cs | 1 + .../Runtime/Core/NetworkManager.cs | 1 + .../Messages/ConnectionApprovedMessage.cs | 1 + .../Runtime/Messaging/Messages/TimeSyncMessage.cs | 2 ++ .../NetworkVariable/AnticipatedNetworkVariable.cs | 1 + .../NetworkVariable/Collections/NetworkList.cs | 1 + .../NetworkVariable/NetworkVariableBase.cs | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 1 + .../Runtime/Timing/AnticipationSystem.cs | 3 ++- .../Runtime/Timing/IRealTimeProvider.cs | 2 +- .../Runtime/Timing/NetworkTickSystem.cs | 4 +++- .../Runtime/Timing/NetworkTime.cs | 4 +++- .../Runtime/Timing/NetworkTimeSystem.cs | 15 +++++++++------ .../Runtime/Timing/RealTimeProvider.cs | 2 +- .../Runtime/Transports/UTP/UnityTransport.cs | 1 + .../Transports/Unified/UnifiedNetcodeTransport.cs | 1 + .../Tests/Editor/InterpolatorTests.cs | 1 + .../Editor/Timing/ClientNetworkTimeSystemTests.cs | 1 + .../Tests/Editor/Timing/NetworkTimeTests.cs | 1 + .../Editor/Timing/ServerNetworkTimeSystemTests.cs | 1 + .../Tests/Editor/Timing/TimingTestHelper.cs | 1 + .../Tests/Runtime/NetworkManagerEventsTests.cs | 1 + .../Tests/Runtime/TestHelpers/MockTimeProvider.cs | 2 ++ .../Runtime/TestHelpers/NetcodeIntegrationTest.cs | 1 + .../Runtime/Timing/NetworkTimeSystemTests.cs | 1 + .../Tests/Runtime/Timing/TimeIntegrationTest.cs | 1 + 30 files changed, 48 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ceaff931bb..cd0a67ecb7 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -17,6 +17,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - `Unity.Netcode.Editor.CodeGen` → `Unity.Netcode.GameObjects.Editor.CodeGen` - `Unity.Netcode.Editor.PackageChecker` → `Unity.Netcode.GameObjects.Editor.PackageChecker` - `Unity.Netcode.Editor.Tests` → `Unity.Netcode.GameObjects.Editor.Tests` +- The timing types moved out of the `Unity.Netcode` namespace into `Unity.Netcode.GameObjects.Timing`. The assembly is unchanged, and existing scripts are migrated automatically when the package is upgraded. + - `Unity.Netcode.NetworkTime` → `Unity.Netcode.GameObjects.Timing.NetworkTime` + - `Unity.Netcode.NetworkTimeSystem` → `Unity.Netcode.GameObjects.Timing.NetworkTimeSystem` + - `Unity.Netcode.NetworkTickSystem` → `Unity.Netcode.GameObjects.Timing.NetworkTickSystem` ### Deprecated diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index 00bd431cbb..26d1e939e2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -1,4 +1,5 @@ using Unity.Mathematics; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.Runtime; using UnityEngine; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 4cee25748e..1f87d6f08c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; namespace Unity.Netcode diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 9f4b04d368..67947ba75d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Text; using Unity.Mathematics; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.Runtime; using UnityEngine; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs b/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs index 6733429ee2..304ae05efb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Unity.Netcode.GameObjects.Timing; namespace Unity.Netcode { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0a3fee02d7..d1230999a3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -7,6 +7,7 @@ using Unity.NetCode; #endif using Unity.Netcode.Components; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.Logging; using Unity.Netcode.Runtime; // TODO-UNIFIED: When: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 2a7eee8be7..62c3391b46 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Unity.Collections; +using Unity.Netcode.GameObjects.Timing; namespace Unity.Netcode { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs index e3ab1dfe32..bb820ece16 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -1,3 +1,5 @@ +using Unity.Netcode.GameObjects.Timing; + namespace Unity.Netcode { internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index 36e0199a5a..a09314e8cb 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -1,5 +1,6 @@ using System; using Unity.Mathematics; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; namespace Unity.Netcode diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index acf139b0d1..d8509e23d7 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using Unity.Collections; +using Unity.Netcode.GameObjects.Timing; namespace Unity.Netcode { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 25abeb20e9..dd52c55782 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; namespace Unity.Netcode diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index b0a3c6b6b0..96f43d69f2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.Logging; using UnityEngine; using Object = UnityEngine.Object; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs index abf0dbd60a..155b489145 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Unity.Netcode; -namespace Unity.Netcode +namespace Unity.Netcode.GameObjects.Timing { internal interface IAnticipationEventReceiver { diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs b/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs index cd20e38a0b..0f6fe9b7cf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs @@ -1,4 +1,4 @@ -namespace Unity.Netcode +namespace Unity.Netcode.GameObjects.Timing { internal interface IRealTimeProvider { diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs index 821f727ed3..606342332c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs @@ -1,13 +1,15 @@ using System; using Unity.Profiling; +using UnityEngine.Scripting.APIUpdating; -namespace Unity.Netcode +namespace Unity.Netcode.GameObjects.Timing { /// /// Provides discretized time. /// This is useful for games that require ticks happening at regular interval on the server and clients. /// [Serializable] + [MovedFrom(true, "Unity.Netcode", null, null)] public class NetworkTickSystem { #if DEBUG diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index e452bbb187..dc245acb1b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -1,8 +1,9 @@ using System; using UnityEngine; using UnityEngine.Assertions; +using UnityEngine.Scripting.APIUpdating; -namespace Unity.Netcode +namespace Unity.Netcode.GameObjects.Timing { /// /// A struct to represent a point of time in a networked game. @@ -10,6 +11,7 @@ namespace Unity.Netcode /// This struct is meant to replace the Unity API for multiplayer gameplay. /// [Serializable] + [MovedFrom(true, "Unity.Netcode", null, null)] public struct NetworkTime { private double m_TimeSec; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index a967052a16..8339cf093f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -1,8 +1,10 @@ using System; +using Unity.Netcode; using Unity.Profiling; using UnityEngine; +using UnityEngine.Scripting.APIUpdating; -namespace Unity.Netcode +namespace Unity.Netcode.GameObjects.Timing { /// /// is a standalone system which can be used to run a network time simulation. @@ -11,6 +13,7 @@ namespace Unity.Netcode /// effort at predicting what the server tick will be when a given network action is processed on the server. /// [Serializable] + [MovedFrom(true, "Unity.Netcode", null, null)] public class NetworkTimeSystem { /// @@ -85,12 +88,12 @@ public class NetworkTimeSystem /// /// For a distributed authority network topology, this latency is between the client and the /// distributed authority service instance.
- /// Note: uses this value plus an additional global - /// offset when interpolation + /// Note: uses this value plus an additional global + /// offset when interpolation /// is enabled.
- /// To see the current tick latency:
- /// -
- /// -
+ /// To see the current tick latency:
+ /// -
+ /// -
///
public int TickLatency = 1; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs b/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs index 51a2044f7b..94623b9ba0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs @@ -1,6 +1,6 @@ using UnityEngine; -namespace Unity.Netcode +namespace Unity.Netcode.GameObjects.Timing { internal class RealTimeProvider : IRealTimeProvider { diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index f8ce9d815c..7feed2d3f3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -11,6 +11,7 @@ using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.Runtime; using Unity.Networking.Transport; using Unity.Networking.Transport.Error; diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index 6cb855e2db..c9fda0cd31 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -7,6 +7,7 @@ using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.NetCode; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.Transports.UTP; using UnityEngine; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index 8b3016ea73..c018cd0ff1 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; namespace Unity.Netcode.GameObjects.EditorTests { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs index 7b435523ea..f810c45c28 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs @@ -1,5 +1,6 @@ using System; using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; namespace Unity.Netcode.GameObjects.EditorTests diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs index 93403ef555..84e1d59911 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; using Random = System.Random; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs index 4c6560aea5..ed381358eb 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; namespace Unity.Netcode.GameObjects.EditorTests diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs index ffa62cc265..38306054e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Unity.Netcode.GameObjects.Timing; using UnityEngine; using Random = System.Random; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 32d998c0b7..5bb9c29f67 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MockTimeProvider.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MockTimeProvider.cs index 070cb41dae..4bda57834e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MockTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MockTimeProvider.cs @@ -1,3 +1,5 @@ +using Unity.Netcode.GameObjects.Timing; + namespace Unity.Netcode.TestHelpers.Runtime { /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index f7a1ba1001..6dbf9fe2e1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -9,6 +9,7 @@ #if UNIFIED_NETCODE using Unity.NetCode; #endif +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; using UnityEngine; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs index 8dca0b69d6..ec16f951b6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs @@ -1,5 +1,6 @@ using System.Collections; using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.Assertions.Comparers; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs index d5e404a16e..b250e72196 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using NUnit.Framework; +using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; From 101de99a69b2a3a1d2e929ecce5519473e92fcf5 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 4 Sep 2026 13:09:52 -0500 Subject: [PATCH 03/10] docs: repoint the NetworkTimeSystem API cross-reference at the new namespace xref targets are fully qualified UIDs, so the one in networktime-ticks.md stopped resolving when the type moved. DocFX renders an unresolved xref as its raw text, which reads as a broken link on the published page rather than failing anything in this repo - there is no docfx job here. Nothing else in Documentation~ needs changing. Every code sample that touches these types reaches them through NetworkManager (LocalTime.TimeAsFloat, NetworkTickSystem.Tick), which never names the type and is unaffected; the remaining mentions are prose or links to this same page. The old names in apiupdaterproject/README.md and the CHANGELOG are the 2.x side of before/after tables and are correct as they stand. No upgrade note added. The samples on that page do not need the new import, so a note about it would be advice for a reader the page does not have, and the landed editor relocation set the precedent of updating the affected sample without prose. The CHANGELOG entry covers the move. --- .../Documentation~/advanced-topics/networktime-ticks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md index 500b3d4b7c..87d25f3b66 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md @@ -183,6 +183,6 @@ For games with short play sessions casting the time to float is safe or `TimeAsF > [!NOTE] > The properties of the `NetworkTimeSystem` should be left untouched on the server/host. Changing the values on the client is sufficient to change the behavior of the time system. -The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. Refer to the [API docs](xref:Unity.Netcode.NetworkTimeSystem) for information about the properties which can be modified. All properties can be safely adjusted at runtime. For instance, buffer values can be increased for a player with a bad connection. +The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. Refer to the [API docs](xref:Unity.Netcode.GameObjects.Timing.NetworkTimeSystem) for information about the properties which can be modified. All properties can be safely adjusted at runtime. For instance, buffer values can be increased for a player with a bad connection. From b446914f6350655e9f498d25c4968b0e52e93217 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 4 Sep 2026 13:54:17 -0500 Subject: [PATCH 04/10] ci: run the API updater test in both modes The --collision-stub mode was added with the timing relocation but never wired into the job, so /ci apiupdater only ever exercised the default path and the regression test for the reason the move exists could not actually run in CI. Two sequential commands rather than one invocation: each needs its own cold import, since the assertion is meaningless against a Library that already holds rewritten sources. The script removes the stub and restores the 2.x sources on every exit path, so the second run starts from the state the first one did. The on-demand trigger is `pull_request.comment eq "apiupdater"` with no draft exclusion, so this runs on #4150 while it sits in draft. Neither mode needs Netcode for Entities present - the default mode tests NGO's own 2.x to 3.x migration, and the stub mode uses the local stub rather than the real package - so the run does not have to wait on the N4E namespace work. --- .yamato/api-updater-test.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.yamato/api-updater-test.yml b/.yamato/api-updater-test.yml index 8662e698cc..5c343fd9c8 100644 --- a/.yamato/api-updater-test.yml +++ b/.yamato/api-updater-test.yml @@ -2,13 +2,20 @@ --- # DESCRIPTION-------------------------------------------------------------------------- - # This job validates the NGO 2.x -> 3.x upgrade path for editor scripts. + # This job validates the NGO 2.x -> 3.x upgrade path for user scripts. # NGO 3.0 renamed the editor assembly and its namespaces (Unity.Netcode.Editor -> - # Unity.Netcode.GameObjects.Editor), and every relocated public type carries a [MovedFrom] so that - # Unity's API updater rewrites a 2.x project's editor scripts automatically on upgrade. - # apiupdaterproject holds editor code written against the 2.x API; the job imports it with + # Unity.Netcode.GameObjects.Editor) and moved the runtime timing types (Unity.Netcode.NetworkTime + # and friends -> Unity.Netcode.GameObjects.Timing), and every relocated public type carries a + # [MovedFrom] so that Unity's API updater rewrites a 2.x project's scripts automatically on upgrade. + # apiupdaterproject holds code written against the 2.x API; the job imports it with # -accept-apiupdate and asserts that every 2.x type reference was rewritten and none survived. # See apiupdaterproject/README.md. + # + # The second run adds an assembly that occupies Unity.Netcode.NetworkTime and NetworkTimeSystem, + # standing in for another package taking those names, and inverts the expectation for exactly those + # two: the updater is driven by resolution failure, so a name that still resolves cannot be + # migrated. NetworkTickSystem is absent from the stub and must still migrate, so a pass proves both + # halves. This is the regression test for why the timing types were moved at all. # TECHNICAL CONSIDERATIONS--------------------------------------------------------------- @@ -18,7 +25,9 @@ # The script restores the 2.x sources when it finishes, so the checkout is left unmodified and the # job is safe to re-run on the same agent. # --clean purges Library first: the assertion is meaningless against a warm Library that already - # holds rewritten sources from a previous run. + # holds rewritten sources from a previous run. That is also why the two runs are sequential + # commands rather than one - each needs its own cold import, and the script removes the stub and + # restores the sources on every exit path, so the second run starts from the same state as the first. {% for platform in test_platforms.default -%} {% for editor in validation_editors.default -%} @@ -31,6 +40,7 @@ api_updater_test_{{ platform.name }}_{{ editor }}: commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor # Installing basic editor for the import - python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean + - python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean --collision-stub artifacts: logs: paths: From 6013f45e2fdf380ee7a31c3a24b7c0bcaba08e2f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 6 Sep 2026 17:56:26 -0500 Subject: [PATCH 05/10] fix: reach the moved NetworkTimeSystem through an alias Netcode for Entities 6.7.0 corrected the casing of its namespace, so it now declares Unity.Netcode.NetworkTimeSystem. The enclosing namespace is searched ahead of any using directive, so once the timing types leave Unity.Netcode the five files that still spell the name bare bind to theirs instead, and Unity.Netcode.Runtime stops compiling in any project that has both packages: 16 errors across 6 files, all of them cascading from the NetworkManager.NetworkTimeSystem property picking up the wrong type. The collision stub is corrected to match what N4E actually shipped. It sub-namespaced NetworkTime into Unity.Netcode.NetcodeTime but left NetworkTimeSystem in the shared root, so only that one name is occupied. --- .../Assets/CollisionStub~/N4ECollisionStub.cs | 17 ++++++----------- apiupdaterproject/run_upgrade_test.py | 12 ++++++------ .../Runtime/Core/NetworkManager.cs | 9 +++++++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs b/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs index f3d1844471..f8f41ac6df 100644 --- a/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs +++ b/apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs @@ -1,20 +1,15 @@ -// Stands in for a second package occupying Unity.Netcode.NetworkTime and -// Unity.Netcode.NetworkTimeSystem, which is what Netcode for Entities does once the casing of its -// Unity.NetCode namespace is corrected. +// Stands in for a second package occupying Unity.Netcode.NetworkTimeSystem, which is what Netcode +// for Entities does as of 6.7.0: its casing correction moved 204 files into Unity.Netcode, and it +// sub-namespaced NetworkTime into Unity.Netcode.NetcodeTime but left NetworkTimeSystem behind. // -// Only those two names collide. NetworkTickSystem deliberately is not declared here, so a -// --collision-stub run asserts both halves of the finding in one pass: the updater migrates -// NetworkTickSystem, and it cannot migrate the two whose old names still resolve. +// Only that one name collides. NetworkTime and NetworkTickSystem deliberately are not declared here, +// so a --collision-stub run asserts both halves of the finding in one pass: those two migrate, and +// the one whose old name still resolves cannot. // // Inert until run_upgrade_test.py --collision-stub copies this folder into place. Unity does not // import a directory whose name ends in '~'. namespace Unity.Netcode { - public struct NetworkTime - { - public int ServerTick; - } - public class NetworkTimeSystem { public uint EffectiveInputLatencyTicks; diff --git a/apiupdaterproject/run_upgrade_test.py b/apiupdaterproject/run_upgrade_test.py index 7055b971dd..9c7f91bc0a 100644 --- a/apiupdaterproject/run_upgrade_test.py +++ b/apiupdaterproject/run_upgrade_test.py @@ -8,12 +8,12 @@ reference under Assets/Editor and Assets/Runtime was rewritten and that no stale reference survived. The 2.x sources are restored on exit so the test can be re-run. -With --collision-stub, a stub assembly is added that occupies Unity.Netcode.NetworkTime and -Unity.Netcode.NetworkTimeSystem, standing in for a second package that has taken those names. The -expectation then inverts for exactly those two: the updater is driven by resolution failure, so a +With --collision-stub, a stub assembly is added that occupies Unity.Netcode.NetworkTimeSystem, +standing in for Netcode for Entities, which still declares that name in the shared root namespace. +The expectation then inverts for exactly that one: the updater is driven by resolution failure, so a name another assembly still resolves never reaches the MovedFrom data and cannot be migrated. -NetworkTickSystem is deliberately absent from the stub and must still migrate, which is what makes -the run prove both halves rather than merely fail. +NetworkTime and NetworkTickSystem are deliberately absent from the stub and must still migrate, which +is what makes the run prove both halves rather than merely fail. Note that this script can be run from anywhere; paths are resolved relative to the script itself. """ @@ -70,7 +70,7 @@ # The names the --collision-stub assembly occupies; under it these must NOT be rewritten. # Keep in sync with Assets/CollisionStub~/N4ECollisionStub.cs. -STUB_OCCUPIED = ['Unity.Netcode.NetworkTime', 'Unity.Netcode.NetworkTimeSystem'] +STUB_OCCUPIED = ['Unity.Netcode.NetworkTimeSystem'] def expected_pairs(): diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index d1230999a3..2b4a763205 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -24,6 +24,11 @@ using PackageInfo = UnityEditor.PackageManager.PackageInfo; #endif using UnityEngine.SceneManagement; +// Netcode for Entities also declares Unity.Netcode.NetworkTimeSystem. The enclosing namespace is +// searched before any using directive, so inside Unity.Netcode the bare name binds to theirs and the +// import above is never consulted. An alias is the only spelling that reaches ours from here, since +// IDE0001 rules out qualifying the name and an alias sharing it is silently ignored. +using GameObjectsNetworkTimeSystem = Unity.Netcode.GameObjects.Timing.NetworkTimeSystem; @@ -1017,7 +1022,7 @@ public NetworkPrefabHandler PrefabHandler /// Accessor property for the of the NetworkManager. /// Prefer the use of the LocalTime and ServerTime properties /// - public NetworkTimeSystem NetworkTimeSystem { get; private set; } + public GameObjectsNetworkTimeSystem NetworkTimeSystem { get; private set; } /// /// Accessor property for the of the NetworkManager. @@ -1310,7 +1315,7 @@ internal void Initialize(bool server) ConnectionManager.Initialize(this); // The remaining systems can then be initialized - NetworkTimeSystem = server ? NetworkTimeSystem.ServerTimeSystem() : new NetworkTimeSystem(1.0 / NetworkConfig.TickRate); + NetworkTimeSystem = server ? GameObjectsNetworkTimeSystem.ServerTimeSystem() : new GameObjectsNetworkTimeSystem(1.0 / NetworkConfig.TickRate); NetworkTickSystem = NetworkTimeSystem.Initialize(this); AnticipationSystem = new AnticipationSystem(this); From 4f64cd2d554216bc1072b5b713af2839d6074734 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 6 Sep 2026 19:52:36 -0500 Subject: [PATCH 06/10] style: drop the imports and qualifications the new namespace makes redundant Unity.Netcode.GameObjects.Timing is nested inside Unity.Netcode, so the root namespace is already in scope there through the enclosing namespace chain. The two imports the move added to AnticipationSystem.cs and NetworkTimeSystem.cs were never needed, and the Unity.Netcode. prefix on the five NetworkTransform doc references shortens to Components. for the same reason - both are IDE0005 and IDE0001, which the Standards job treats as errors. NetworkManagerEventsTests.cs took an import it does not use: its only mention of NetworkTimeSystem is inside a comment. Matches the diff the Standards job produced. Running the tool locally finds nothing on dotnet 10.0.400, which is the version divergence its own failure message warns about, so this was applied from the CI output rather than reproduced here. --- .../Runtime/Timing/AnticipationSystem.cs | 1 - .../Runtime/Timing/NetworkTimeSystem.cs | 11 +++++------ .../Tests/Runtime/NetworkManagerEventsTests.cs | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs index 155b489145..0e86fd24fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Unity.Netcode; namespace Unity.Netcode.GameObjects.Timing { diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 8339cf093f..9432ccda98 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -1,5 +1,4 @@ using System; -using Unity.Netcode; using Unity.Profiling; using UnityEngine; using UnityEngine.Scripting.APIUpdating; @@ -88,12 +87,12 @@ public class NetworkTimeSystem /// /// For a distributed authority network topology, this latency is between the client and the /// distributed authority service instance.
- /// Note: uses this value plus an additional global - /// offset when interpolation + /// Note: uses this value plus an additional global + /// offset when interpolation /// is enabled.
- /// To see the current tick latency:
- /// -
- /// -
+ /// To see the current tick latency:
+ /// -
+ /// -
///
public int TickLatency = 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 5bb9c29f67..32d998c0b7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using NUnit.Framework; -using Unity.Netcode.GameObjects.Timing; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; From 8ee6b99f61dce49bd5b34193825264d2b195e87e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 6 Sep 2026 22:29:01 -0500 Subject: [PATCH 07/10] docs: describe the collision stub as what N4E actually shipped The job description still said the stub occupies both NetworkTime and NetworkTimeSystem, which is what the stub looked like before 6013f45e2 corrected it. N4E 6.7.0 sub-namespaced NetworkTime into Unity.Netcode.NetcodeTime and left NetworkTimeSystem in the shared root, so only one name is occupied and only that one expectation inverts. --- .yamato/api-updater-test.yml | 103 ++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/.yamato/api-updater-test.yml b/.yamato/api-updater-test.yml index 5c343fd9c8..678fd6d4b0 100644 --- a/.yamato/api-updater-test.yml +++ b/.yamato/api-updater-test.yml @@ -1,51 +1,52 @@ -{% metadata_file .yamato/project.metafile %} ---- - -# DESCRIPTION-------------------------------------------------------------------------- - # This job validates the NGO 2.x -> 3.x upgrade path for user scripts. - # NGO 3.0 renamed the editor assembly and its namespaces (Unity.Netcode.Editor -> - # Unity.Netcode.GameObjects.Editor) and moved the runtime timing types (Unity.Netcode.NetworkTime - # and friends -> Unity.Netcode.GameObjects.Timing), and every relocated public type carries a - # [MovedFrom] so that Unity's API updater rewrites a 2.x project's scripts automatically on upgrade. - # apiupdaterproject holds code written against the 2.x API; the job imports it with - # -accept-apiupdate and asserts that every 2.x type reference was rewritten and none survived. - # See apiupdaterproject/README.md. - # - # The second run adds an assembly that occupies Unity.Netcode.NetworkTime and NetworkTimeSystem, - # standing in for another package taking those names, and inverts the expectation for exactly those - # two: the updater is driven by resolution failure, so a name that still resolves cannot be - # migrated. NetworkTickSystem is absent from the stub and must still migrate, so a pass proves both - # halves. This is the regression test for why the timing types were moved at all. - - -# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- - # apiupdaterproject/Packages/manifest.json references the package by relative path - # (file:../../com.unity.netcode.gameobjects), so the job tests the package as it sits in the repo - # and needs no package-pack dependency. - # The script restores the 2.x sources when it finishes, so the checkout is left unmodified and the - # job is safe to re-run on the same agent. - # --clean purges Library first: the assertion is meaningless against a warm Library that already - # holds rewritten sources from a previous run. That is also why the two runs are sequential - # commands rather than one - each needs its own cold import, and the script removes the stub and - # restores the sources on every exit path, so the second run starts from the same state as the first. - -{% for platform in test_platforms.default -%} -{% for editor in validation_editors.default -%} -api_updater_test_{{ platform.name }}_{{ editor }}: - name : API Updater Test - NGO 2.x editor scripts upgrade [{{ platform.name }}, {{ editor }}] - agent: - type: {{ platform.type }} - image: {{ platform.image }} - flavor: {{ platform.flavor }} - commands: - - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor # Installing basic editor for the import - - python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean - - python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean --collision-stub - artifacts: - logs: - paths: - - "apiupdaterproject/upgrade-test.log" - dependencies: - - .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors -{% endfor -%} -{% endfor -%} +{% metadata_file .yamato/project.metafile %} +--- + +# DESCRIPTION-------------------------------------------------------------------------- + # This job validates the NGO 2.x -> 3.x upgrade path for user scripts. + # NGO 3.0 renamed the editor assembly and its namespaces (Unity.Netcode.Editor -> + # Unity.Netcode.GameObjects.Editor) and moved the runtime timing types (Unity.Netcode.NetworkTime + # and friends -> Unity.Netcode.GameObjects.Timing), and every relocated public type carries a + # [MovedFrom] so that Unity's API updater rewrites a 2.x project's scripts automatically on upgrade. + # apiupdaterproject holds code written against the 2.x API; the job imports it with + # -accept-apiupdate and asserts that every 2.x type reference was rewritten and none survived. + # See apiupdaterproject/README.md. + # + # The second run adds an assembly that occupies Unity.Netcode.NetworkTimeSystem, which is what + # Netcode for Entities does as of 6.7.0 - it sub-namespaced NetworkTime out of the shared root but + # left NetworkTimeSystem in it - and inverts the expectation for that one name: the updater is + # driven by resolution failure, so a name that still resolves cannot be migrated. NetworkTime and + # NetworkTickSystem are absent from the stub and must still migrate, so a pass proves both halves. + # This is the regression test for why the timing types were moved at all. + + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # apiupdaterproject/Packages/manifest.json references the package by relative path + # (file:../../com.unity.netcode.gameobjects), so the job tests the package as it sits in the repo + # and needs no package-pack dependency. + # The script restores the 2.x sources when it finishes, so the checkout is left unmodified and the + # job is safe to re-run on the same agent. + # --clean purges Library first: the assertion is meaningless against a warm Library that already + # holds rewritten sources from a previous run. That is also why the two runs are sequential + # commands rather than one - each needs its own cold import, and the script removes the stub and + # restores the sources on every exit path, so the second run starts from the same state as the first. + +{% for platform in test_platforms.default -%} +{% for editor in validation_editors.default -%} +api_updater_test_{{ platform.name }}_{{ editor }}: + name : API Updater Test - NGO 2.x editor scripts upgrade [{{ platform.name }}, {{ editor }}] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + commands: + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor # Installing basic editor for the import + - python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean + - python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean --collision-stub + artifacts: + logs: + paths: + - "apiupdaterproject/upgrade-test.log" + dependencies: + - .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors +{% endfor -%} +{% endfor -%} From 59c036a68ec3a508c25e53422c3e4096f3c99fea Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 6 Sep 2026 22:38:18 -0500 Subject: [PATCH 08/10] docs: correct the collision stub docs and qualify the migration claim Two review findings, both accurate. The README still described the stub as occupying both NetworkTime and NetworkTimeSystem, which is what it looked like before 6013f45e2 corrected it to match N4E 6.7.0. The expectation table was the harmful part: it listed NetworkTime as not-rewritten when it now migrates, so a correct run would read as a failure. The CLI help was already right. The CHANGELOG claimed migration is automatic without qualification. It is not, for exactly the case this move exists to address: when another installed package still declares the name in Unity.Netcode the old reference resolves, never reaches the MovedFrom data and is left alone. The --collision-stub run asserts that as a deliberate negative result, so the test was right and the user-facing sentence was wrong. --- apiupdaterproject/README.md | 247 +++++++++++---------- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 2 files changed, 125 insertions(+), 124 deletions(-) diff --git a/apiupdaterproject/README.md b/apiupdaterproject/README.md index 984feb3b27..c3b0b1dd75 100644 --- a/apiupdaterproject/README.md +++ b/apiupdaterproject/README.md @@ -1,123 +1,124 @@ -# API updater upgrade-path project - -This project validates that an **NGO 2.x** project's scripts are migrated automatically when upgrading to **NGO 3.x**. - - -NGO 3.0 renamed the editor assembly and its namespaces: - -| 2.x | 3.x | -| --- | --- | -| `Unity.Netcode.Editor` (assembly) | `Unity.Netcode.GameObjects.Editor` | -| `Unity.Netcode.Editor` (namespace) | `Unity.Netcode.GameObjects.Editor` | -| `Unity.Netcode.Editor.Configuration` | `Unity.Netcode.GameObjects.Editor.Configuration` | -| `Unity.Netcode.Editor.CodeGen` | `Unity.Netcode.GameObjects.Editor.CodeGen` | -| `Unity.Netcode.PackageChecker.Editor` | `Unity.Netcode.GameObjects.PackageChecker.Editor` | - -It also relocated the runtime timing types out of the root namespace, so that correcting the casing -of Netcode for Entities' `Unity.NetCode` namespace does not collide with them: - -| 2.x | 3.x | -| --- | --- | -| `Unity.Netcode.NetworkTime` | `Unity.Netcode.GameObjects.Timing.NetworkTime` | -| `Unity.Netcode.NetworkTimeSystem` | `Unity.Netcode.GameObjects.Timing.NetworkTimeSystem` | -| `Unity.Netcode.NetworkTickSystem` | `Unity.Netcode.GameObjects.Timing.NetworkTickSystem` | - -The **assembly is unchanged** for the timing move — only the namespace — so those three carry a null -`sourceAssembly`, which the attribute reads as "unchanged". - - -### NGO v2.x.x Unity.Netcode.Editor changes - -If there is a need to add new API to NGO v2.x.x, the above table should be updated and the DeprecatedApiUsage.cs -file or the DeprecatedApiUsageQualified.cs files are updated to reflect the added API. - -## Contents - -| Path | What it covers | -| --- | --- | -| `Assets/Editor/DeprecatedApiUsage.cs` | Every public 2.x editor type through `using` + simple name | -| `Assets/Editor/DeprecatedApiUsageQualified.cs` | Fully qualified names, namespace alias, type alias, base type, `typeof`, generic | -| `Assets/Runtime/DeprecatedTimingUsage.cs` | The three relocated timing types, in every reference form plus a constructor call | -| `Assets/UpgradeProbeBehaviour.cs` | The `MonoBehaviour` used as the `NetcodeEditorBase` type argument | -| `Assets/CollisionStub~/` | An assembly occupying the two colliding timing names. Inert — Unity does not import a folder whose name ends in `~` — until `--collision-stub` copies it in | - -`UpgradeProbeBehaviour` exists so the test does not name NGO's `NetworkManager`: com.unity.transport -6.6.0 — the builtin on some 6000.6 editors — ships a `Unity.Netcode.NetworkManager` of its own in -`Unity.Networking.Transport.NetcodeInterop`, which makes any `NetworkManager` reference from an -auto-referencing assembly like `Assembly-CSharp-Editor` CS0433-ambiguous. The type argument is -incidental to what is being measured, so a local `MonoBehaviour` keeps the test independent of the -resolved transport version. - - -## Running it locally - -`run_upgrade_test.py` imports the project in batch mode with `-accept-apiupdate`, then asserts that -every 2.x type reference under `Assets/Editor` and `Assets/Runtime` was rewritten and that none -survived. It restores the 2.x sources when it finishes, so it can be re-run. Windows, macOS and Linux. - -```sh -python run_upgrade_test.py --unity --clean --keep-updated-sources -``` - -| Option | | -| --- | --- | -| `--unity` | Omit it if `UNITY_EDITOR_PATH` is set, or if the hub has the version named in `ProjectSettings/ProjectVersion.txt`. | -| `--clean` | Purges `Library` and `Temp` first for a cold import. | -| `--keep-updated-sources` | Leaves the rewritten sources in place so `git diff` shows exactly what the updater produced. | -| `--collision-stub` | Adds an assembly occupying `Unity.Netcode.NetworkTime` and `NetworkTimeSystem`, then **inverts** the expectation for those two. See below. | - -### The `--collision-stub` run - -This is the regression test for the reason the timing move exists. With the stub installed, a 2.x -reference to `NetworkTime` still resolves — to the stub — so it never fails to resolve, never reaches -the `MovedFrom` data, and cannot be migrated. `NetworkTickSystem` is deliberately **not** in the stub, -so the same run asserts that one still migrates. A pass therefore proves both halves: - -| Type | Expected under `--collision-stub` | -| --- | --- | -| `Unity.Netcode.NetworkTime` | **not** rewritten | -| `Unity.Netcode.NetworkTimeSystem` | **not** rewritten | -| `Unity.Netcode.NetworkTickSystem` | rewritten | -| every editor type | rewritten | - -If a future change ever makes the two blocked rows pass as "rewritten", the mechanism has changed and -the one-sided-move conclusion needs revisiting. - -Default hub locations, if you need to pass `--unity` explicitly — note that on macOS the binary is -inside the `.app` bundle rather than beside it: - -| | | -| --- | --- | -| Windows | `C:\Program Files\Unity\Hub\Editor\\Editor\Unity.exe` | -| macOS | `/Applications/Unity/Hub/Editor//Unity.app/Contents/MacOS/Unity` | -| Linux | `$HOME/Unity/Hub/Editor//Editor/Unity` | - - -## How the migration works - -Every relocated public editor type carries - -```csharp -[MovedFrom(true, "Unity.Netcode.Editor", "Unity.Netcode.Editor", null)] -``` - -(`"Unity.Netcode.Editor.Configuration"` as the source namespace for the two types that were in it). -The arguments are `autoUpdateAPI, sourceNamespace, sourceAssembly, sourceClassName` — a null class -name means the type name itself did not change. - -The three relocated timing types carry - -```csharp -[MovedFrom(true, "Unity.Netcode", null, null)] -``` - -with `sourceAssembly` null because `Unity.Netcode.Runtime` keeps its name: any null argument is read -as "this did not change", and its value is taken from the decorated type. - -A 2.x reference no longer resolves, so the compiler reports CS0246/CS0234. Unity's `ScriptUpdater` -consults the `MovedFrom` data extracted from the referenced assemblies, matches the old -namespace/assembly, and rewrites the reference. Nothing extra ships: no skeleton assembly, no -duplicate API surface. - -`MovedFrom` is consulted **only** for references that fail to resolve. That is why the old namespace -must not be kept alive by anything — a type that still resolves never reaches the MovedFrom path. +# API updater upgrade-path project + +This project validates that an **NGO 2.x** project's scripts are migrated automatically when upgrading to **NGO 3.x**. + + +NGO 3.0 renamed the editor assembly and its namespaces: + +| 2.x | 3.x | +| --- | --- | +| `Unity.Netcode.Editor` (assembly) | `Unity.Netcode.GameObjects.Editor` | +| `Unity.Netcode.Editor` (namespace) | `Unity.Netcode.GameObjects.Editor` | +| `Unity.Netcode.Editor.Configuration` | `Unity.Netcode.GameObjects.Editor.Configuration` | +| `Unity.Netcode.Editor.CodeGen` | `Unity.Netcode.GameObjects.Editor.CodeGen` | +| `Unity.Netcode.PackageChecker.Editor` | `Unity.Netcode.GameObjects.PackageChecker.Editor` | + +It also relocated the runtime timing types out of the root namespace, so that correcting the casing +of Netcode for Entities' `Unity.NetCode` namespace does not collide with them: + +| 2.x | 3.x | +| --- | --- | +| `Unity.Netcode.NetworkTime` | `Unity.Netcode.GameObjects.Timing.NetworkTime` | +| `Unity.Netcode.NetworkTimeSystem` | `Unity.Netcode.GameObjects.Timing.NetworkTimeSystem` | +| `Unity.Netcode.NetworkTickSystem` | `Unity.Netcode.GameObjects.Timing.NetworkTickSystem` | + +The **assembly is unchanged** for the timing move — only the namespace — so those three carry a null +`sourceAssembly`, which the attribute reads as "unchanged". + + +### NGO v2.x.x Unity.Netcode.Editor changes + +If there is a need to add new API to NGO v2.x.x, the above table should be updated and the DeprecatedApiUsage.cs +file or the DeprecatedApiUsageQualified.cs files are updated to reflect the added API. + +## Contents + +| Path | What it covers | +| --- | --- | +| `Assets/Editor/DeprecatedApiUsage.cs` | Every public 2.x editor type through `using` + simple name | +| `Assets/Editor/DeprecatedApiUsageQualified.cs` | Fully qualified names, namespace alias, type alias, base type, `typeof`, generic | +| `Assets/Runtime/DeprecatedTimingUsage.cs` | The three relocated timing types, in every reference form plus a constructor call | +| `Assets/UpgradeProbeBehaviour.cs` | The `MonoBehaviour` used as the `NetcodeEditorBase` type argument | +| `Assets/CollisionStub~/` | An assembly occupying the one colliding timing name. Inert — Unity does not import a folder whose name ends in `~` — until `--collision-stub` copies it in | + +`UpgradeProbeBehaviour` exists so the test does not name NGO's `NetworkManager`: com.unity.transport +6.6.0 — the builtin on some 6000.6 editors — ships a `Unity.Netcode.NetworkManager` of its own in +`Unity.Networking.Transport.NetcodeInterop`, which makes any `NetworkManager` reference from an +auto-referencing assembly like `Assembly-CSharp-Editor` CS0433-ambiguous. The type argument is +incidental to what is being measured, so a local `MonoBehaviour` keeps the test independent of the +resolved transport version. + + +## Running it locally + +`run_upgrade_test.py` imports the project in batch mode with `-accept-apiupdate`, then asserts that +every 2.x type reference under `Assets/Editor` and `Assets/Runtime` was rewritten and that none +survived. It restores the 2.x sources when it finishes, so it can be re-run. Windows, macOS and Linux. + +```sh +python run_upgrade_test.py --unity --clean --keep-updated-sources +``` + +| Option | | +| --- | --- | +| `--unity` | Omit it if `UNITY_EDITOR_PATH` is set, or if the hub has the version named in `ProjectSettings/ProjectVersion.txt`. | +| `--clean` | Purges `Library` and `Temp` first for a cold import. | +| `--keep-updated-sources` | Leaves the rewritten sources in place so `git diff` shows exactly what the updater produced. | +| `--collision-stub` | Adds an assembly occupying `Unity.Netcode.NetworkTimeSystem`, then **inverts** the expectation for that one name. See below. | + +### The `--collision-stub` run + +This is the regression test for the reason the timing move exists. The stub occupies the one name +Netcode for Entities 6.7.0 still declares in the shared root: with it installed, a 2.x reference to +`NetworkTimeSystem` still resolves — to the stub — so it never fails to resolve, never reaches the +`MovedFrom` data, and cannot be migrated. `NetworkTime` and `NetworkTickSystem` are deliberately +**not** in the stub, so the same run asserts those still migrate. A pass therefore proves both halves: + +| Type | Expected under `--collision-stub` | +| --- | --- | +| `Unity.Netcode.NetworkTimeSystem` | **not** rewritten | +| `Unity.Netcode.NetworkTime` | rewritten | +| `Unity.Netcode.NetworkTickSystem` | rewritten | +| every editor type | rewritten | + +If a future change ever makes the blocked row pass as "rewritten", the mechanism has changed and the +one-sided-move conclusion needs revisiting. + +Default hub locations, if you need to pass `--unity` explicitly — note that on macOS the binary is +inside the `.app` bundle rather than beside it: + +| | | +| --- | --- | +| Windows | `C:\Program Files\Unity\Hub\Editor\\Editor\Unity.exe` | +| macOS | `/Applications/Unity/Hub/Editor//Unity.app/Contents/MacOS/Unity` | +| Linux | `$HOME/Unity/Hub/Editor//Editor/Unity` | + + +## How the migration works + +Every relocated public editor type carries + +```csharp +[MovedFrom(true, "Unity.Netcode.Editor", "Unity.Netcode.Editor", null)] +``` + +(`"Unity.Netcode.Editor.Configuration"` as the source namespace for the two types that were in it). +The arguments are `autoUpdateAPI, sourceNamespace, sourceAssembly, sourceClassName` — a null class +name means the type name itself did not change. + +The three relocated timing types carry + +```csharp +[MovedFrom(true, "Unity.Netcode", null, null)] +``` + +with `sourceAssembly` null because `Unity.Netcode.Runtime` keeps its name: any null argument is read +as "this did not change", and its value is taken from the decorated type. + +A 2.x reference no longer resolves, so the compiler reports CS0246/CS0234. Unity's `ScriptUpdater` +consults the `MovedFrom` data extracted from the referenced assemblies, matches the old +namespace/assembly, and rewrites the reference. Nothing extra ships: no skeleton assembly, no +duplicate API surface. + +`MovedFrom` is consulted **only** for references that fail to resolve. That is why the old namespace +must not be kept alive by anything — a type that still resolves never reaches the MovedFrom path. diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 519cead3c2..1b691aaafc 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -19,7 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - `Unity.Netcode.Editor.CodeGen` → `Unity.Netcode.GameObjects.Editor.CodeGen` - `Unity.Netcode.Editor.PackageChecker` → `Unity.Netcode.GameObjects.Editor.PackageChecker` - `Unity.Netcode.Editor.Tests` → `Unity.Netcode.GameObjects.Editor.Tests` -- The timing types moved out of the `Unity.Netcode` namespace into `Unity.Netcode.GameObjects.Timing`. The assembly is unchanged, and existing scripts are migrated automatically when the package is upgraded. +- The timing types moved out of the `Unity.Netcode` namespace into `Unity.Netcode.GameObjects.Timing`. The assembly is unchanged, and existing scripts are migrated automatically when the package is upgraded. The exception is a reference another installed package still resolves under `Unity.Netcode`: the updater only rewrites references that fail to resolve, so those have to be updated by hand. - `Unity.Netcode.NetworkTime` → `Unity.Netcode.GameObjects.Timing.NetworkTime` - `Unity.Netcode.NetworkTimeSystem` → `Unity.Netcode.GameObjects.Timing.NetworkTimeSystem` - `Unity.Netcode.NetworkTickSystem` → `Unity.Netcode.GameObjects.Timing.NetworkTickSystem` From 9022abdaddc555283d60417c20b3c5c881dd8996 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 6 Sep 2026 22:38:28 -0500 Subject: [PATCH 09/10] test: assert the namespace alias use site was rewritten DeprecatedTimingUsage.cs reaches NetworkTickSystem through 'using TimeNs = Unity.Netcode;', and the per-type counts cannot see that site: 'TimeNs.NetworkTickSystem' contains neither the old nor the new fully qualified name, so it contributes to neither updated nor stale. The editor runs with -ignoreCompilerErrors, so an updater that left it unresolved would still produce a passing run off the other reference forms. The type alias on the line above is fine - it spells Unity.Netcode.NetworkTime in full, so it already counts toward stale. Separate commit because it cannot be verified here - there is no Python on the machine this was written on, so the next /ci apiupdater run is the first execution. Either outcome is informative: a pass closes the hole, and a failure means the updater does not follow namespace aliases, which is a finding rather than a defect in this assertion. --- apiupdaterproject/run_upgrade_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apiupdaterproject/run_upgrade_test.py b/apiupdaterproject/run_upgrade_test.py index 9c7f91bc0a..e0aadfbf9c 100644 --- a/apiupdaterproject/run_upgrade_test.py +++ b/apiupdaterproject/run_upgrade_test.py @@ -73,6 +73,14 @@ STUB_OCCUPIED = ['Unity.Netcode.NetworkTimeSystem'] +# Reference forms the per-type counts above cannot see, because the source never spells the type's +# fully qualified name. 'TimeNs.NetworkTickSystem' in DeprecatedTimingUsage.cs goes through a +# namespace alias and so matches neither the old nor the new spelling: without asserting on it +# directly, the updater could leave that site unresolved and the run would still pass on the strength +# of the other reference forms. The editor runs with -ignoreCompilerErrors, so nothing else catches it. +UNQUALIFIED_FORMS = ['TimeNs.NetworkTickSystem'] + + def expected_pairs(): """Yields (old fully qualified name, new fully qualified name) for every relocated type.""" for old_namespace, new_namespace, names in EXPECTED_MOVES: @@ -269,6 +277,13 @@ def assert_rewritten(collision_stub): expect = 'blocked' if blocked else 'moved' print(f"{old:<72} {updated:>8} {stale:>6} {expect:>8} {'PASS' if passed else 'FAIL'}") + for form in UNQUALIFIED_FORMS: + survived = len(re.findall(re.escape(form) + boundary, all_text)) + passed = survived == 0 + if not passed: + failures += 1 + print(f"{form:<72} {'-':>8} {survived:>6} {'moved':>8} {'PASS' if passed else 'FAIL'}") + return failures From 3ded45248d273978155e87aa72a443f6c100a9ff Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 6 Sep 2026 23:56:59 -0500 Subject: [PATCH 10/10] fix: reach the moved NetworkTime through an alias as well The unified job failed to compile with two CS0104 on NetworkManager.LocalTime and ServerTime: 'NetworkTime' is ambiguous between Unity.Netcode.GameObjects.Timing.NetworkTime and Unity.NetCode.NetworkTime. This is a second, distinct mechanism from the one 6013f45e2 addressed, and the stub used there could not produce it. That stub modelled N4E after its casing correction, where the colliding names are members of the enclosing Unity.Netcode namespace and therefore win over any import. The N4E that the pinned editor actually bundles is still Unity.NetCode, so both names arrive as imports instead - Unity.NetCode alongside Unity.Netcode.GameObjects.Timing - and two imports offering the same simple name is an ambiguity rather than a silent rebind. Before the move NGO's own NetworkTime was a member of Unity.Netcode, which is why the two imports coexisted for as long as they did. The alias fixes both mechanisms, so this holds whichever casing is installed. NetworkTime and NetworkTimeSystem are the only two names at risk: they are the sole intersection between Runtime/Timing/ and what N4E declares, confirmed against the 6000.7.0a5 bundled package. Only NetworkManager.cs is affected, being the only file that imports both namespaces and names either type in a type position. --- .../Runtime/Core/NetworkManager.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b0ea20d786..a3289e937e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -24,10 +24,13 @@ using PackageInfo = UnityEditor.PackageManager.PackageInfo; #endif using UnityEngine.SceneManagement; -// Netcode for Entities also declares Unity.Netcode.NetworkTimeSystem. The enclosing namespace is -// searched before any using directive, so inside Unity.Netcode the bare name binds to theirs and the -// import above is never consulted. An alias is the only spelling that reaches ours from here, since -// IDE0001 rules out qualifying the name and an alias sharing it is silently ignored. +// Netcode for Entities declares NetworkTime and NetworkTimeSystem too, and reaching ours by the bare +// name fails against either casing of their namespace. Under Unity.NetCode both arrive as imports and +// the reference is CS0104 ambiguous; once that is corrected to Unity.Netcode they become members of +// the enclosing namespace, which is searched ahead of any import, and the bare name silently binds to +// theirs. An alias is the only spelling that survives both: IDE0001 rules out qualifying the name, and +// an alias sharing it is ignored rather than applied. +using GameObjectsNetworkTime = Unity.Netcode.GameObjects.Timing.NetworkTime; using GameObjectsNetworkTimeSystem = Unity.Netcode.GameObjects.Timing.NetworkTimeSystem; @@ -877,12 +880,12 @@ public struct ConnectionApprovalRequest /// /// The local /// - public NetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default; + public GameObjectsNetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default; /// /// The on the server /// - public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default; + public GameObjectsNetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default; /// /// Gets or sets if the application should be set to run in background