diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index d43378b8f..f42fcb0f8 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 740a20793..eef47458e 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -14,6 +14,7 @@ RequireTypedRuntimeFactionKeys(root); RequireTypedGalaxyFactionRelationships(root); RequireRuntimeCatalogKeyOnlyLookups(root); RequireTypedBehaviorBodyKeys(root); +RequireTypedOrbitTaskKeys(root); RequireTypedFactionShellLinks(root); RequireFactionKeyIdentity(root); RequireEveRuntimeBootstrap(root); @@ -856,6 +857,95 @@ static void RequireTypedBehaviorBodyKeys(string root) } } +static void RequireTypedOrbitTaskKeys(string root) +{ + var checkedFiles = new[] + { + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "States", "MoveTo.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "PatrolOrbits.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "StationTowing.cs") + }; + + var forbiddenSymbols = new[] + { + "public Guid Orbit { get; set; }", + "Guid[] Circuit", + "public Guid OrbitParent", + "Select(x => x.Key)" + }; + + 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-targeting agent tasks and move states must stay on typed OrbitKey surfaces, with Zone owning orbit-key resolution: " + + string.Join("; ", hits)); + } + + var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); + var zoneSource = File.Exists(zoneSourcePath) + ? File.ReadAllText(zoneSourcePath) + : 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)" + }; + var missingZoneSymbols = requiredZoneSymbols + .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingZoneSymbols.Length > 0) + { + throw new InvalidOperationException( + "Zone must own typed orbit-key parsing and resolution for agent/runtime orbit movement: " + + string.Join(", ", missingZoneSymbols)); + } + + var requiredTaskSymbols = new Dictionary + { + [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "States", "MoveTo.cs")] = new[] + { + "public string OrbitKey { get; set; } = \"\";", + "_agent.Ship.Zone.GetOrbitPosition(OrbitKey)" + }, + [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "PatrolOrbits.cs")] = new[] + { + "public string[] Circuit = Array.Empty();", + "public string CurrentTarget", + "patrolMoveState.OrbitKey = CurrentTarget" + }, + [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "StationTowing.cs")] = new[] + { + "public string OrbitParentKey = \"\";" + } + }; + var missingTaskSymbols = requiredTaskSymbols + .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 (missingTaskSymbols.Length > 0) + { + throw new InvalidOperationException( + "Orbit-targeting agent tasks must retain typed orbit keys rather than raw GUIDs: " + + string.Join("; ", missingTaskSymbols)); + } +} + static void RequireTypedFactionShellLinks(string root) { var checkedFiles = new[] diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index cb5df3399..5e04eb7b0 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -129,6 +129,9 @@ and `MiningTool` now retain typed body-key runtime state directly, while `Zone`, `Planet`, `AsteroidBelt`, and `Orbit` now own the runtime body/orbit key surfaces used for projection, and `ActionGameManager` no longer formats or resolves body/orbit keys locally while checkpointing or restoring state. +Orbit-targeting agent runtime also now retains typed orbit keys directly: +`PatrolOrbitsTask`, `MoveToOrbitState`, and `StationTowing` no longer own raw +orbit GUID targets, and `Zone` resolves orbit keys for agent orbit movement. 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/Agents/States/MoveTo.cs b/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs index 12c84fc18..00008b365 100644 --- a/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs +++ b/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs @@ -37,8 +37,8 @@ public class MoveToEntityState : MoveToState public class MoveToOrbitState : MoveToState { - public Guid Orbit { get; set; } + public string OrbitKey { get; set; } = ""; public MoveToOrbitState(Agent agent) : base(agent) { } - protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(Orbit); + protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(OrbitKey); } diff --git a/Assets/Scripts/ServerShared/Agents/Tasks/PatrolOrbits.cs b/Assets/Scripts/ServerShared/Agents/Tasks/PatrolOrbits.cs index 653764217..461608a6d 100644 --- a/Assets/Scripts/ServerShared/Agents/Tasks/PatrolOrbits.cs +++ b/Assets/Scripts/ServerShared/Agents/Tasks/PatrolOrbits.cs @@ -3,13 +3,13 @@ using System; public class PatrolOrbitsTask : AgentTask { public override TaskType Type => TaskType.Defend; - public Guid[] Circuit; + public string[] Circuit = Array.Empty(); } public class PatrolOrbitsState : BaseState { public PatrolOrbitsTask Task; - public Guid CurrentTarget + public string CurrentTarget { get => Task.Circuit[_currentTargetIndex]; } @@ -25,7 +25,7 @@ public class PatrolOrbitsState : BaseState var patrolMoveState = new MoveToOrbitState(agent); Transitions.Add(new StateTransition(patrolMoveState, () => true, - () => patrolMoveState.Orbit = CurrentTarget)); + () => patrolMoveState.OrbitKey = CurrentTarget)); patrolMoveState.Transitions.Add(new StateTransition(this, () => patrolMoveState.Distance < 10, NextTarget)); } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Agents/Tasks/StationTowing.cs b/Assets/Scripts/ServerShared/Agents/Tasks/StationTowing.cs index f138f74aa..dcc69098d 100644 --- a/Assets/Scripts/ServerShared/Agents/Tasks/StationTowing.cs +++ b/Assets/Scripts/ServerShared/Agents/Tasks/StationTowing.cs @@ -10,7 +10,7 @@ public class StationTowing : AgentTask public OrbitalEntity Station; - public Guid OrbitParent; + public string OrbitParentKey = ""; public float OrbitDistance; } diff --git a/Assets/Scripts/ServerShared/Zone.cs b/Assets/Scripts/ServerShared/Zone.cs index d0c6673df..6b8964705 100644 --- a/Assets/Scripts/ServerShared/Zone.cs +++ b/Assets/Scripts/ServerShared/Zone.cs @@ -100,7 +100,11 @@ public class Zone { var agent = new Minion(ship); var task = new PatrolOrbitsTask(); - task.Circuit = Orbits.OrderBy(_ => _itemManager.Random.NextFloat()).Take(4).Select(x => x.Key).ToArray(); + task.Circuit = Orbits.Values + .OrderBy(_ => _itemManager.Random.NextFloat()) + .Take(4) + .Select(orbit => orbit.OrbitKey) + .ToArray(); agent.Task = task; return agent; } @@ -115,6 +119,17 @@ 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) @@ -147,6 +162,11 @@ public class Zone return AsteroidBelts.TryGetValue(ParseBodyGuid(bodyKey), out belt); } + public bool TryGetOrbit(string orbitKey, out Orbit orbit) + { + return Orbits.TryGetValue(ParseOrbitGuid(orbitKey), out orbit); + } + public void Update(float deltaTime) { _time += deltaTime; @@ -212,6 +232,11 @@ public class Zone return Orbits[orbitID].Position; } + public float2 GetOrbitPosition(string orbitKey) + { + return GetOrbitPosition(ParseOrbitGuid(orbitKey)); + } + public float2 GetOrbitVelocity(Guid orbit) { if (Orbits.ContainsKey(orbit)) diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 7570b9587..3a50c4d8a 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1201,6 +1201,9 @@ First Aetheria surfaces to publish: - Done: rename `OrbitalEntity.OrbitData` to `OrbitId` and delete the unused `Zone.CreateOrbit` construction-row writer; runtime entities no longer expose an orbit id through DTO-shaped naming. + - Done: move patrol/tow orbit targets onto typed orbit keys: `PatrolOrbitsTask`, + `MoveToOrbitState`, and `StationTowing` now retain keyed orbit references, + and `Zone` owns orbit-key parsing/resolution for agent orbit movement. - Done: move live asteroid-belt simulation, scanning, and mesh setup reads off `AsteroidBeltConstructionData`; `Zone`, `ResourceScanner`, and `AsteroidBeltUI` consume `AsteroidBelt` runtime asteroid/resource/orbit properties, and @@ -1363,8 +1366,10 @@ First Aetheria surfaces to publish: mining-tool body references are named and serialized as typed body keys through commit/readback state, with the behaviors retaining keyed runtime state directly and `Zone` owning GUID parsing/resolution at the current - Unity body runtime boundary. Remaining broader simulation paths still - derive legacy GUIDs until those structures move to typed keys. + Unity body runtime boundary. Patrol/tow orbit task targets now also retain + typed orbit keys with `Zone` owning orbit-key resolution. 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 @@ -1779,6 +1784,10 @@ First Aetheria surfaces to publish: return. `Zone`, `Planet`, `AsteroidBelt`, and `Orbit` now own the runtime body/orbit key surfaces consumed by `ActionGameManager` projection, so local body/orbit key formatters may not return there either. +- `Aetheria.State.Verify` guards orbit-targeting agent runtime naming: + patrol/tow task orbit references and move-state orbit targets use `OrbitKey` + fields, and `Zone` owns orbit-key parsing/resolution instead of agent-local + raw GUID targets. - `Aetheria.State.Verify` guards temporary `Faction` shell links: geoname and boss-hull relationships use typed keys, not legacy GUID projection fields. - `Aetheria.State.Verify` guards temporary `Faction` identity: equality,