diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 55687ffed..92149a1bc 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 3998f885d..a64f5b25b 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -16,6 +16,7 @@ RequireRuntimeCatalogKeyOnlyLookups(root); RequireTypedBehaviorBodyKeys(root); RequireTypedOrbitTaskKeys(root); RequireTypedOrbitalEntityOrbitKeys(root); +RequireTypedOrbitConsumerKeys(root); RequireTypedFactionShellLinks(root); RequireFactionKeyIdentity(root); RequireEveRuntimeBootstrap(root); @@ -1040,6 +1041,84 @@ static void RequireTypedOrbitalEntityOrbitKeys(string root) } } +static void RequireTypedOrbitConsumerKeys(string root) +{ + var checkedFiles = new[] + { + Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"), + Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs") + }; + + var forbiddenSymbols = new[] + { + "planet => planet.OrbitId == followOrbit", + "planet => planet.OrbitId == rootOrbit", + "body => body.OrbitId == orbit.ID", + "body => body.Orbit == orbit.ID", + "GetOrbitPosition(planetInstance.OrbitId)", + "Zone.Orbits[planetInstance.OrbitId].Parent", + "zone.Orbits[belt.Orbit]", + "GetOrbitPosition(planet.OrbitId)" + }; + + var hits = checkedFiles + .Where(File.Exists) + .SelectMany(path => File.ReadLines(path) + .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) + .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) + .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "Orbit consumer edges must read typed OrbitKey surfaces rather than wrapper GUID fields in gameplay, renderer, and sensor code: " + + string.Join("; ", hits)); + } + + var requiredSymbols = new Dictionary + { + [Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[] + { + "Zone.Orbits.Values.MinBy(orbit => lengthsq(Zone.GetOrbitPosition(orbit.OrbitKey) - entityPosition))", + "planet => planet.OrbitKey == followOrbit.OrbitKey", + "planet => planet.OrbitKey == rootOrbit.OrbitKey", + "Zone.TryGetOrbit(rootOrbit.ParentOrbitKey, out var parentOrbit)", + "Zone.GetOrbitPosition(followOrbit.OrbitKey)", + "Zone.GetOrbitPosition(rootOrbit.OrbitKey)" + }, + [Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[] + { + "body => body.OrbitKey == orbit.OrbitKey", + "Zone.GetOrbitPosition(planetInstance.OrbitKey)", + "Zone.GetOrbitPosition(planetInstance.Orbit.ParentOrbitKey)" + }, + [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")] = new[] + { + "Entity.Zone.GetOrbitPosition(planet.OrbitKey)" + } + }; + + var missingSymbols = requiredSymbols + .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) + .SelectMany(pair => + { + var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; + return pair.Value + .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) + .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); + }) + .ToArray(); + + if (missingSymbols.Length > 0) + { + throw new InvalidOperationException( + "Orbit consumer edges must stay keyed at the public runtime wrapper surface: " + + string.Join("; ", missingSymbols)); + } +} + static void RequireTypedFactionShellLinks(string root) { var checkedFiles = new[] diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 822f744d7..65efc3ff5 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -137,6 +137,11 @@ rule: `OrbitalEntity` and `OrbitalEntityConstructionBlueprint` retain `OrbitKey`, runtime loadout generation seeds the empty value as `""`, and `Zone` resolves orbit keys for orbital entity movement and dock camera parent lookup. +Renderer and gameplay orbit consumers now stay on that keyed edge too: +intro-cutscene planet selection, zone planet/belt loading, gas-giant parent +lighting, asteroid-belt UI setup, and `ResourceScanner` planet range checks all +read `OrbitKey` from runtime wrappers and ask `Zone` to resolve it instead of +reading wrapper GUID orbit fields directly. 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/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index c590e635f..13a5f2dd7 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -2901,20 +2901,21 @@ public class ActionGameManager : MonoBehaviour { ZoneRenderer.PerspectiveEntity = ship; var entityPosition = ship.Position.xz; - var followOrbit = Zone.Orbits.Keys.MinBy(o => lengthsq(Zone.GetOrbitPosition(o) - entityPosition)); - var followPlanetId = Zone.PlanetInstances.Values.First(planet => planet.OrbitId == followOrbit).ID; + var followOrbit = Zone.Orbits.Values.MinBy(orbit => lengthsq(Zone.GetOrbitPosition(orbit.OrbitKey) - entityPosition)); + var followPlanetId = Zone.PlanetInstances.Values.First(planet => planet.OrbitKey == followOrbit.OrbitKey).ID; var followPlanet = ZoneRenderer.Planets[followPlanetId]; DockCamera.Follow = followPlanet.Body.transform; var rootOrbit = followOrbit; - while (Zone.Orbits[rootOrbit].Parent != Guid.Empty) - rootOrbit = Zone.Orbits[rootOrbit].Parent; - var rootPlanetId = Zone.PlanetInstances.Values.First(planet => planet.OrbitId == rootOrbit).ID; + while (!string.IsNullOrWhiteSpace(rootOrbit.ParentOrbitKey) && + Zone.TryGetOrbit(rootOrbit.ParentOrbitKey, out var parentOrbit)) + rootOrbit = parentOrbit; + var rootPlanetId = Zone.PlanetInstances.Values.First(planet => planet.OrbitKey == rootOrbit.OrbitKey).ID; var rootPlanet = ZoneRenderer.Planets[rootPlanetId]; DockCamera.LookAt = rootPlanet.Body.transform; var shipVelocity = ship.GetBehavior().Limit; - var followOrbitPosition = Zone.GetOrbitPosition(followOrbit); - var shipDirection = normalize(Zone.GetOrbitPosition(rootOrbit) - followOrbitPosition); + var followOrbitPosition = Zone.GetOrbitPosition(followOrbit.OrbitKey); + var shipDirection = normalize(Zone.GetOrbitPosition(rootOrbit.OrbitKey) - followOrbitPosition); ship.Position.xz = followOrbitPosition - shipDirection * shipVelocity * IntroDuration; var startTime = Time.time; diff --git a/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs b/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs index 4738fd7b1..a57272da1 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs @@ -75,7 +75,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior } else if (Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet)) { - if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planet.OrbitId)) < Range) + if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planet.OrbitKey)) < Range) { _scanTime += dt; if (_scanTime > ScanDuration) diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index 145d04532..8100928a4 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -191,14 +191,14 @@ public class ZoneRenderer : MonoBehaviour SlimeRenderer.ZoneRadius = zone.Radius; foreach (var orbit in zone.Orbits.Values) { - var planet = zone.PlanetInstances.Values.FirstOrDefault(body => body.OrbitId == orbit.ID); + var planet = zone.PlanetInstances.Values.FirstOrDefault(body => body.OrbitKey == orbit.OrbitKey); if (planet != null) { LoadPlanet(planet); continue; } - var belt = zone.AsteroidBelts.Values.FirstOrDefault(body => body.Orbit == orbit.ID); + var belt = zone.AsteroidBelts.Values.FirstOrDefault(body => body.OrbitKey == orbit.OrbitKey); if (belt != null) LoadAsteroidBelt(belt); } @@ -506,7 +506,7 @@ public class ZoneRenderer : MonoBehaviour foreach (var planet in Planets) { var planetInstance = Zone.PlanetInstances[planet.Key]; - var p = Zone.GetOrbitPosition(planetInstance.OrbitId); + var p = Zone.GetOrbitPosition(planetInstance.OrbitKey); var height = Zone.GetHeight(p); if (-height > maxDepth) maxDepth = -height; planet.Value.transform.position = new Vector3(p.x, 0, p.y); @@ -517,7 +517,7 @@ public class ZoneRenderer : MonoBehaviour Zone.Time * ((GasGiant) Zone.PlanetInstances[planet.Key]).GravityWavesSpeed); if (!(planet.Value is SunObject)) { - var toParent = normalize(Zone.GetOrbitPosition(Zone.Orbits[planetInstance.OrbitId].Parent) - p); + var toParent = normalize(Zone.GetOrbitPosition(planetInstance.Orbit.ParentOrbitKey) - p); gasGiantObject.SunMaterial.LightingDirection = new Vector3(toParent.x, 0, toParent.y); } } @@ -628,7 +628,6 @@ public class AsteroidBeltUI private Vector3[] _normals; private Vector2[] _uvs; private int[] _indices; - private Guid _orbitParent; private Mesh _mesh; private float _size; private Zone _zone; @@ -647,8 +646,6 @@ public class AsteroidBeltUI _zone = zone; Filter = meshFilter; _collider = collider; - var orbit = zone.Orbits[belt.Orbit]; - _orbitParent = orbit.Parent; _vertices = new Vector3[_belt.AsteroidCount * 4]; _normals = new Vector3[_belt.AsteroidCount * 4]; _uvs = new Vector2[_belt.AsteroidCount * 4]; diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 76064fdf9..06a32fcad 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1370,8 +1370,10 @@ First Aetheria surfaces to publish: typed orbit keys with `Zone` owning orbit-key resolution. Orbital runtime entities and runtime construction blueprints now also retain typed `OrbitKey` values, with `Zone` owning orbital movement and dock-camera - parent-orbit resolution for that seam. Remaining broader simulation paths - still derive legacy GUIDs until those structures move to typed keys. + parent-orbit resolution for that seam. Renderer, intro-cutscene, and + `ResourceScanner` orbit readers now also consume the wrapper `OrbitKey` + edge instead of wrapper GUID orbit fields. 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