diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index d035e6c2c..80b36b59a 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 27588da3c..832248105 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -18,6 +18,7 @@ RequireTypedOrbitTaskKeys(root); RequireTypedOrbitalEntityOrbitKeys(root); RequireTypedOrbitConsumerKeys(root); RequireKeyedOrbitRuntimeWrappers(root); +RequireNativeZoneKeyResolution(root); RequireTypedFactionShellLinks(root); RequireFactionKeyIdentity(root); RequireEveRuntimeBootstrap(root); @@ -807,7 +808,6 @@ static void RequireTypedBehaviorBodyKeys(string root) "public string OrbitKey { get; }", "public string ParentOrbitKey { get; }", "public string GetBodyKey(Guid bodyId)", - "public static Guid ParseBodyGuid(string bodyKey)", "public bool TryGetPlanet(string bodyKey, out Planet planet)", "public bool TryGetAsteroidBelt(string bodyKey, out AsteroidBelt belt)" }; @@ -899,7 +899,6 @@ static void RequireTypedOrbitTaskKeys(string root) : throw new InvalidOperationException("Cannot verify orbit-key task ownership; Zone source is missing."); var requiredZoneSymbols = new[] { - "public static Guid ParseOrbitGuid(string orbitKey)", "public bool TryGetOrbit(string orbitKey, out Orbit orbit)", "public float2 GetOrbitPosition(string orbitKey)" }; @@ -1151,7 +1150,8 @@ static void RequireKeyedOrbitRuntimeWrappers(string root) { "public Orbit Orbit;", "public Orbit Orbit { get; }", - "AsteroidBelts[belt.ID] = new AsteroidBelt(belt, Orbits[belt.Orbit]);", + "var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.Orbit]);", + "AsteroidBelts[belt.ID] = runtimeBelt;", "belt.NewOrbitPosition = GetOrbitPosition(belt.Orbit.ParentOrbitKey);", "public AsteroidBelt(AsteroidBeltConstructionData data, Orbit orbit)", "Orbit = orbit;" @@ -1206,6 +1206,62 @@ static void RequireTypedFactionShellLinks(string root) } } +static void RequireNativeZoneKeyResolution(string root) +{ + var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); + var zoneSource = File.Exists(zoneSourcePath) + ? File.ReadAllText(zoneSourcePath) + : throw new InvalidOperationException("Cannot verify native Zone key resolution; Zone source is missing."); + + var forbiddenSymbols = new[] + { + "public static Guid ParseOrbitGuid(string orbitKey)", + "public static Guid ParseBodyGuid(string bodyKey)", + "PlanetInstances.TryGetValue(ParseBodyGuid(bodyKey), out planet)", + "AsteroidBelts.TryGetValue(ParseBodyGuid(bodyKey), out belt)", + "Orbits.TryGetValue(ParseOrbitGuid(orbitKey), out orbit)", + "return GetOrbitPosition(ParseOrbitGuid(orbitKey));", + "return GetOrbitVelocity(ParseOrbitGuid(orbitKey));" + }; + var hits = forbiddenSymbols + .Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal)) + .Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}") + .ToArray(); + if (hits.Length > 0) + { + throw new InvalidOperationException( + "Zone key-facing APIs must resolve typed body/orbit keys natively instead of reparsing GUIDs from those keys: " + + string.Join("; ", hits)); + } + + var requiredSymbols = new[] + { + "private readonly Dictionary _planetsByBodyKey = new Dictionary(StringComparer.Ordinal);", + "private readonly Dictionary _asteroidBeltsByBodyKey = new Dictionary(StringComparer.Ordinal);", + "private readonly Dictionary _orbitsByKey = new Dictionary(StringComparer.Ordinal);", + "_orbitsByKey[runtimeOrbit.OrbitKey] = runtimeOrbit;", + "_asteroidBeltsByBodyKey[runtimeBelt.BodyKey] = runtimeBelt;", + "_planetsByBodyKey[runtimeSun.BodyKey] = runtimeSun;", + "_planetsByBodyKey[runtimeGas.BodyKey] = runtimeGas;", + "_planetsByBodyKey[runtimePlanet.BodyKey] = runtimePlanet;", + "return _planetsByBodyKey.TryGetValue(bodyKey ?? \"\", out planet);", + "return _asteroidBeltsByBodyKey.TryGetValue(bodyKey ?? \"\", out belt);", + "return _orbitsByKey.TryGetValue(orbitKey ?? \"\", out orbit);", + "return TryGetOrbit(orbitKey, out var orbit)", + "? GetOrbitPosition(orbit.ID)", + "? GetOrbitVelocity(orbit.ID)" + }; + var missingSymbols = requiredSymbols + .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingSymbols.Length > 0) + { + throw new InvalidOperationException( + "Zone must keep native key-indexed runtime lookup tables for body and orbit resolution: " + + string.Join(", ", missingSymbols)); + } +} + static void RequireFactionKeyIdentity(string root) { var checkedFiles = new[] diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 6e34da480..87f4d0ded 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -146,6 +146,10 @@ reading wrapper GUID orbit fields directly. orbit GUID fields either: they keep `OrbitKey` plus their `Orbit` runtime object, and `Zone` lowers asteroid transforms through `belt.Orbit.ParentOrbitKey` instead of reading a raw wrapper orbit GUID. +`Zone`'s own public key-facing lookup surface is also now natively keyed: +body and orbit lookups resolve through key-indexed runtime dictionaries rather +than reparsing GUIDs from `BodyKey` or `OrbitKey` strings before touching the +Guid-indexed substrate. The package runtime catalog snapshot indexes items by canonical item key; Unity gameplay, HUD, inventory, trade, ship, and zone helper lookups use `ItemKey` directly instead of deriving `ItemId` and searching by legacy GUID. diff --git a/Assets/Scripts/ServerShared/Zone.cs b/Assets/Scripts/ServerShared/Zone.cs index b0006d2c5..ca4a70a38 100644 --- a/Assets/Scripts/ServerShared/Zone.cs +++ b/Assets/Scripts/ServerShared/Zone.cs @@ -26,6 +26,10 @@ public class Zone public Dictionary Orbits = new Dictionary(); public Dictionary AsteroidBelts = new Dictionary(); public PlanetSettings Settings; + + private readonly Dictionary _planetsByBodyKey = new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _asteroidBeltsByBodyKey = new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _orbitsByKey = new Dictionary(StringComparer.Ordinal); private HashSet _updatedOrbits = new HashSet(); @@ -58,7 +62,9 @@ public class Zone foreach (var orbit in blueprint.Orbits) { - Orbits.Add(orbit.ID, new Orbit(Settings, orbit)); + var runtimeOrbit = new Orbit(Settings, orbit); + Orbits.Add(orbit.ID, runtimeOrbit); + _orbitsByKey[runtimeOrbit.OrbitKey] = runtimeOrbit; } foreach (var planet in blueprint.Bodies) @@ -66,16 +72,24 @@ public class Zone switch (planet) { case AsteroidBeltConstructionData belt: - AsteroidBelts[belt.ID] = new AsteroidBelt(belt, Orbits[belt.Orbit]); + var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.Orbit]); + AsteroidBelts[belt.ID] = runtimeBelt; + _asteroidBeltsByBodyKey[runtimeBelt.BodyKey] = runtimeBelt; break; case SunConstructionData sun: - PlanetInstances.Add(sun.ID, new Sun(settings, sun, Orbits[planet.Orbit])); + var runtimeSun = new Sun(settings, sun, Orbits[planet.Orbit]); + PlanetInstances.Add(sun.ID, runtimeSun); + _planetsByBodyKey[runtimeSun.BodyKey] = runtimeSun; break; case GasGiantConstructionData gas: - PlanetInstances.Add(gas.ID, new GasGiant(settings, gas, Orbits[planet.Orbit])); + var runtimeGas = new GasGiant(settings, gas, Orbits[planet.Orbit]); + PlanetInstances.Add(gas.ID, runtimeGas); + _planetsByBodyKey[runtimeGas.BodyKey] = runtimeGas; break; default: - PlanetInstances.Add(planet.ID, new Planet(settings, planet, Orbits[planet.Orbit])); + var runtimePlanet = new Planet(settings, planet, Orbits[planet.Orbit]); + PlanetInstances.Add(planet.ID, runtimePlanet); + _planetsByBodyKey[runtimePlanet.BodyKey] = runtimePlanet; break; } } @@ -119,17 +133,6 @@ public class Zone return id == Guid.Empty ? "" : $"{BodyKeyPrefix}{id:D}"; } - public static Guid ParseOrbitGuid(string orbitKey) - { - if (string.IsNullOrWhiteSpace(orbitKey)) - return Guid.Empty; - - var runtimeOrbitId = orbitKey.StartsWith(OrbitKeyPrefix, StringComparison.OrdinalIgnoreCase) - ? orbitKey.Substring(OrbitKeyPrefix.Length) - : orbitKey; - return Guid.TryParse(runtimeOrbitId, out var id) ? id : Guid.Empty; - } - public string GetBodyKey(Guid bodyId) { if (bodyId == Guid.Empty) @@ -141,30 +144,19 @@ public class Zone return BodyKey(bodyId); } - public static Guid ParseBodyGuid(string bodyKey) - { - if (string.IsNullOrWhiteSpace(bodyKey)) - return Guid.Empty; - - var runtimeBodyId = bodyKey.StartsWith(BodyKeyPrefix, StringComparison.OrdinalIgnoreCase) - ? bodyKey.Substring(BodyKeyPrefix.Length) - : bodyKey; - return Guid.TryParse(runtimeBodyId, out var id) ? id : Guid.Empty; - } - public bool TryGetPlanet(string bodyKey, out Planet planet) { - return PlanetInstances.TryGetValue(ParseBodyGuid(bodyKey), out planet); + return _planetsByBodyKey.TryGetValue(bodyKey ?? "", out planet); } public bool TryGetAsteroidBelt(string bodyKey, out AsteroidBelt belt) { - return AsteroidBelts.TryGetValue(ParseBodyGuid(bodyKey), out belt); + return _asteroidBeltsByBodyKey.TryGetValue(bodyKey ?? "", out belt); } public bool TryGetOrbit(string orbitKey, out Orbit orbit) { - return Orbits.TryGetValue(ParseOrbitGuid(orbitKey), out orbit); + return _orbitsByKey.TryGetValue(orbitKey ?? "", out orbit); } public void Update(float deltaTime) @@ -234,7 +226,9 @@ public class Zone public float2 GetOrbitPosition(string orbitKey) { - return GetOrbitPosition(ParseOrbitGuid(orbitKey)); + return TryGetOrbit(orbitKey, out var orbit) + ? GetOrbitPosition(orbit.ID) + : float2.zero; } public float2 GetOrbitVelocity(Guid orbit) @@ -246,7 +240,9 @@ public class Zone public float2 GetOrbitVelocity(string orbitKey) { - return GetOrbitVelocity(ParseOrbitGuid(orbitKey)); + return TryGetOrbit(orbitKey, out var orbit) + ? GetOrbitVelocity(orbit.ID) + : float2.zero; } public int NearestAsteroid(Guid planetDataID, float2 position) diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index aad9530d0..c9b084781 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1375,9 +1375,11 @@ First Aetheria surfaces to publish: `ResourceScanner` orbit readers now also consume the wrapper `OrbitKey` edge instead of wrapper GUID orbit fields. `Planet` and `AsteroidBelt` runtime wrappers no longer publish separate wrapper orbit GUID fields - either; they retain `OrbitKey` plus their runtime `Orbit` object. Remaining - broader simulation paths still derive legacy GUIDs until those structures - move to typed keys. + either; they retain `OrbitKey` plus their runtime `Orbit` object. `Zone`'s + key-facing lookup surface also resolves through native key-indexed runtime + dictionaries instead of reparsing GUIDs from key strings. Remaining broader + simulation paths still derive legacy GUIDs until those structures move to + typed keys. - Done: delete the `ItemInstance.ItemId` legacy-GUID projection; diagnostics now report `ItemKey` so item instances expose only typed item-key identity. - Done: delete `Entity`/`EquippedCargoBay` cargo and consumable GUID lookup