diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 39c0ed707..d43378b8f 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 cb13004ca..740a20793 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -765,10 +765,15 @@ static void RequireTypedBehaviorBodyKeys(string root) "MiningToolAsteroidBeltId", "ParseLegacyGuidFromReferenceKey", "ParseLegacyIdFromReferenceKey", + "ParseBodyGuidFromKey(", "private static string OrbitKey(Guid", "private static string BodyKey(Guid", "$\"aetheria.orbit:legacy:", - "$\"aetheria.body:legacy:" + "$\"aetheria.body:legacy:", + "public Guid ScanTarget", + "public Guid AsteroidBelt", + "RestoreRuntimeState(Guid scanTarget", + "RestoreRuntimeState(Guid asteroidBelt" }; var hits = checkedFiles @@ -782,7 +787,7 @@ static void RequireTypedBehaviorBodyKeys(string root) if (hits.Length > 0) { throw new InvalidOperationException( - "Runtime behavior body references must be named typed BodyKey surfaces; GUID parsing is confined to ParseBodyGuidFromKey: " + + "Runtime behavior body references must stay on typed BodyKey surfaces, with Zone owning key resolution and behaviors no longer storing raw GUID body ids: " + string.Join("; ", hits)); } @@ -797,7 +802,10 @@ static void RequireTypedBehaviorBodyKeys(string root) "public string BodyKey { get; }", "public string OrbitKey { get; }", "public string ParentOrbitKey { get; }", - "public string GetBodyKey(Guid bodyId)" + "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)" }; var missingZoneSymbols = requiredZoneSymbols .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) @@ -808,6 +816,44 @@ static void RequireTypedBehaviorBodyKeys(string root) "Zone runtime body/orbit objects must own typed projection key surfaces: " + string.Join(", ", missingZoneSymbols)); } + + var requiredBehaviorSymbols = new Dictionary + { + [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")] = new[] + { + "private string _scanTargetBodyKey = \"\";", + "public string ScanTargetBodyKey", + "Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt)", + "Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet)", + "RestoreRuntimeState(", + "string scanTargetBodyKey" + }, + [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "MiningTool.cs")] = new[] + { + "public string AsteroidBeltBodyKey = \"\";", + "Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt)", + "Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid)", + "Entity.Zone.MineAsteroid(", + "AsteroidBeltBodyKey,", + "string asteroidBeltBodyKey" + } + }; + var missingBehaviorSymbols = requiredBehaviorSymbols + .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 (missingBehaviorSymbols.Length > 0) + { + throw new InvalidOperationException( + "ResourceScanner and MiningTool must own typed body-key runtime references rather than raw GUID fields: " + + string.Join("; ", missingBehaviorSymbols)); + } } static void RequireTypedFactionShellLinks(string root) diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index a902d7366..cb5df3399 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -123,11 +123,12 @@ Run checkpoint commit DTOs carry typed `OrbitKey`, `ParentOrbitKey`, and `OrbitLegacyId`, `ParentLegacyId`, and `BodyLegacyId` fallback fields are deleted. Behavior-state runtime commits and readback snapshots carry typed body-key fields for resource scanner targets and mining tool asteroid belts; -the old raw body ID commit/readback field names are deleted. Unity still parses -those body keys into GUIDs only at the current `Zone` body runtime boundary. -`Zone`, `Planet`, `AsteroidBelt`, and `Orbit` now own the runtime -body/orbit key surfaces used for projection, and `ActionGameManager` no longer -formats body/orbit keys locally while checkpointing state. +the old raw body ID commit/readback field names are deleted. `ResourceScanner` +and `MiningTool` now retain typed body-key runtime state directly, while +`Zone` owns parsing and resolution at the current Unity body runtime boundary. +`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. 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 81a13ff21..1d4f62561 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -349,17 +349,6 @@ public class ActionGameManager : MonoBehaviour return instance; } - private static Guid ParseBodyGuidFromKey(string bodyKey) - { - if (string.IsNullOrWhiteSpace(bodyKey)) - return Guid.Empty; - - var runtimeBodyId = bodyKey.StartsWith(Zone.BodyKeyPrefix, StringComparison.OrdinalIgnoreCase) - ? bodyKey.Substring(Zone.BodyKeyPrefix.Length) - : bodyKey; - return Guid.TryParse(runtimeBodyId, out var id) ? id : Guid.Empty; - } - public static Galaxy CurrentGalaxy; public static bool IsTutorial; public static AetheriaRuntimeRunStateSnapshot ContinueRunState { get; set; } @@ -1119,7 +1108,7 @@ public class ActionGameManager : MonoBehaviour { state = new AetheriaRuntimeBehaviorStateCommit { - ResourceScannerTargetBodyKey = zone?.GetBodyKey(resourceScanner.ScanTarget) ?? "", + ResourceScannerTargetBodyKey = resourceScanner.ScanTargetBodyKey ?? "", ResourceScannerAsteroidIndex = resourceScanner.Asteroid, ResourceScannerScanTime = resourceScanner.ScanTime, ResourceScannerRange = resourceScanner.Range, @@ -1131,7 +1120,7 @@ public class ActionGameManager : MonoBehaviour { state = new AetheriaRuntimeBehaviorStateCommit { - MiningToolAsteroidBeltKey = zone?.GetBodyKey(miningTool.AsteroidBelt) ?? "", + MiningToolAsteroidBeltKey = miningTool.AsteroidBeltBodyKey ?? "", MiningToolAsteroidIndex = miningTool.Asteroid, MiningToolRange = miningTool.Range }; @@ -2820,7 +2809,7 @@ public class ActionGameManager : MonoBehaviour break; case ResourceScanner resourceScanner: resourceScanner.RestoreRuntimeState( - ParseBodyGuidFromKey(behaviorState.ResourceScannerTargetBodyKey), + behaviorState.ResourceScannerTargetBodyKey, behaviorState.ResourceScannerAsteroidIndex, (float)behaviorState.ResourceScannerScanTime, (float)behaviorState.ResourceScannerRange, @@ -2829,7 +2818,7 @@ public class ActionGameManager : MonoBehaviour break; case MiningTool miningTool: miningTool.RestoreRuntimeState( - ParseBodyGuidFromKey(behaviorState.MiningToolAsteroidBeltKey), + behaviorState.MiningToolAsteroidBeltKey, behaviorState.MiningToolAsteroidIndex, (float)behaviorState.MiningToolRange); break; diff --git a/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs b/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs index aefced964..7c3217d0e 100644 --- a/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs +++ b/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs @@ -9,7 +9,7 @@ using static Unity.Mathematics.math; public class MiningTool : Behavior { - public Guid AsteroidBelt; + public string AsteroidBeltBodyKey = ""; public int Asteroid; private readonly PerformanceStat _damagePerSecond; @@ -45,14 +45,14 @@ public class MiningTool : Behavior public override bool Execute(float dt) { Range = Evaluate(_range); - var belt = Entity.Zone.AsteroidBelts[AsteroidBelt]; - if (AsteroidBelt != Guid.Empty && - Entity.Zone.AsteroidExists(AsteroidBelt, Asteroid) && + if (Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) && + !string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) && + Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid) && length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range) { Entity.Zone.MineAsteroid( Entity, - AsteroidBelt, + AsteroidBeltBodyKey, Asteroid, Evaluate(_damagePerSecond) * dt, Evaluate(_efficiency), @@ -63,9 +63,9 @@ public class MiningTool : Behavior return false; } - public void RestoreRuntimeState(Guid asteroidBelt, int asteroid, float range) + public void RestoreRuntimeState(string asteroidBeltBodyKey, int asteroid, float range) { - AsteroidBelt = asteroidBelt; + AsteroidBeltBodyKey = asteroidBeltBodyKey ?? ""; Asteroid = asteroid; Range = range; } diff --git a/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs b/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs index b48c2ccf3..4738fd7b1 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs @@ -15,21 +15,22 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior private readonly PerformanceStat _minimumDensity; private readonly PerformanceStat _scanDuration; private float _scanTime; - private Guid _scanTarget; + private string _scanTargetBodyKey = ""; public float Range { get; private set; } public float MinimumDensity { get; private set; } public float ScanDuration { get; private set; } public float ScanTime => _scanTime; - public Guid ScanTarget + public string ScanTargetBodyKey { - get => _scanTarget; + get => _scanTargetBodyKey; set { - if (value != _scanTarget) + value ??= ""; + if (!string.Equals(value, _scanTargetBodyKey, StringComparison.Ordinal)) { - _scanTarget = value; + _scanTargetBodyKey = value; _scanTime = 0; } } @@ -57,7 +58,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior public override bool Execute(float dt) { - if (Entity.Zone.AsteroidBelts.TryGetValue(ScanTarget, out var belt)) + if (Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt)) { if (Asteroid > -1 && belt.ContainsAsteroid(Asteroid) && @@ -72,7 +73,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior return true; } } - else if (Entity.Zone.PlanetInstances.TryGetValue(ScanTarget, out var planet)) + else if (Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet)) { if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planet.OrbitId)) < Range) { @@ -95,14 +96,14 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior } public void RestoreRuntimeState( - Guid scanTarget, + string scanTargetBodyKey, int asteroid, float scanTime, float range, float minimumDensity, float scanDuration) { - _scanTarget = scanTarget; + _scanTargetBodyKey = scanTargetBodyKey ?? ""; Asteroid = asteroid; _scanTime = scanTime; Range = range; diff --git a/Assets/Scripts/ServerShared/Zone.cs b/Assets/Scripts/ServerShared/Zone.cs index a9eae6d53..d0c6673df 100644 --- a/Assets/Scripts/ServerShared/Zone.cs +++ b/Assets/Scripts/ServerShared/Zone.cs @@ -126,6 +126,27 @@ 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); + } + + public bool TryGetAsteroidBelt(string bodyKey, out AsteroidBelt belt) + { + return AsteroidBelts.TryGetValue(ParseBodyGuid(bodyKey), out belt); + } + public void Update(float deltaTime) { _time += deltaTime; @@ -220,6 +241,11 @@ public class Zone public bool AsteroidExists(Guid planetDataID, int asteroid) => AsteroidBelts[planetDataID].ContainsAsteroid(asteroid); + public bool AsteroidExists(string asteroidBeltKey, int asteroid) + { + return TryGetAsteroidBelt(asteroidBeltKey, out var belt) && belt.ContainsAsteroid(asteroid); + } + private void UpdateAsteroidTransforms(Guid planetDataID) { var belt = AsteroidBelts[planetDataID]; @@ -286,6 +312,12 @@ public class Zone } } + public void MineAsteroid(Entity miner, string asteroidBeltKey, int asteroid, float damage, float efficiency, float penetration) + { + if (TryGetAsteroidBelt(asteroidBeltKey, out var belt)) + MineAsteroid(miner, belt.ID, asteroid, damage, efficiency, penetration); + } + public SecurityLevel GetSecurityLevel(float2 pos) { if (GalaxyZone.Owner==null) return SecurityLevel.Open; diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 8ee503789..7570b9587 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -920,10 +920,11 @@ First Aetheria surfaces to publish: - Done: extend behavior state rows for AetherDrive axis, thrust, RPM, maximum RPM, and thrust direction, so drive rotor state has a typed snapshot/readback path. - - Done: extend behavior state rows for ResourceScanner target body, + - Done: extend behavior state rows for ResourceScanner target body key, asteroid index, scan timer, range, minimum density, and duration, so - scanner progress is typed state instead of live-only behavior memory. - - Done: extend behavior state rows for MiningTool asteroid belt id, + scanner progress and target ownership are typed state instead of + live-only behavior memory. + - Done: extend behavior state rows for MiningTool asteroid belt key, asteroid index, and range, so active mining target state is typed snapshot/readback state. - Done: extend behavior state rows for Thruster axis, thrust, and torque, @@ -1360,9 +1361,10 @@ First Aetheria surfaces to publish: references, item-use behavior references, and runtime commit item/hull surfaces now carry typed item keys. Runtime behavior resource-scanner and mining-tool body references are named and serialized as typed body keys - through commit/readback state, with GUID parsing confined to the current - Unity `Zone` body runtime boundary. Remaining broader simulation paths - still derive legacy GUIDs until those structures move to typed 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. - 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