diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 9bd61e2bd..3baabdc07 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs index d1e5871a4..ea8eaecf5 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -462,7 +462,6 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s EntranceZoneIndex = 0, ExitZoneIndex = 1, CurrentZoneIndex = 0, - CurrentZoneEntityIndex = 0, CurrentEntityKey = entityKey.ToString(), GenerationSeed = 424242, DiscoveredZoneIndices = [0], diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs index e3a100255..573ac7226 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -242,7 +242,6 @@ try RunId = "smoke-run", GenerationSeed = 424242, CurrentZoneIndex = 0, - CurrentZoneEntityIndex = 0, CurrentEntityKey = "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1", DiscoveredZoneIndices = new[] { 0 }, ActionBarBindings = new[] diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 17e531c77..dda0ae756 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -1905,7 +1905,7 @@ static void RequireMainMenuContinueRunState(string root) "ReadEntitySnapshots(RuntimeStateFilePath)", "entity.RecordKey", "run.CurrentEntityKey", - "ResolveCurrentEntityRecordKey(run, zoneEntityKeyPrefix)", + "ResolveCurrentEntityRecordKey(run)", "ReplaceZoneEntitiesFromTypedSnapshots", "Zone.Agents.Clear()", "FlattenEntityGraph(Zone)", @@ -1956,6 +1956,23 @@ static void RequireMainMenuContinueRunState(string root) string.Join(", ", forbiddenGameplayHits)); } + var canonicalRunStatePath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"); + var canonicalRunState = File.Exists(canonicalRunStatePath) + ? File.ReadAllText(canonicalRunStatePath) + : throw new InvalidOperationException("Cannot verify canonical run-state authority; AetheriaRuntimeStateDocuments.cs is missing."); + + if (canonicalRunState.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Canonical typed run state still exposes integer current-entity slot authority."); + } + + if (packageSnapshot.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Unity package runtime run snapshot still exposes integer current-entity slot authority."); + } + var requiredPackageSymbols = new[] { "public string RecordKey", diff --git a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs index 8314ab535..5ad851043 100644 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs @@ -237,7 +237,6 @@ public static class AetheriaRuntimeCommitLogApplier EntranceZoneIndex = checkpoint.EntranceZoneIndex, ExitZoneIndex = checkpoint.ExitZoneIndex, CurrentZoneIndex = checkpoint.CurrentZoneIndex, - CurrentZoneEntityIndex = checkpoint.CurrentZoneEntityIndex, DiscoveredZoneIndices = ToIntArray(checkpoint.DiscoveredZoneIndices), ActionBarBindings = ToActionBarBindings(checkpoint.ActionBarBindings), FactionRelationships = ToFactionRelationships(checkpoint.FactionRelationships), diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index 770370c12..c1a654ff8 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -213,9 +213,6 @@ public sealed class AetheriaRunState [Key(4)] public int CurrentZoneIndex { get; set; } - [Key(5)] - public int CurrentZoneEntityIndex { get; set; } - [Key(6)] public int[] DiscoveredZoneIndices { get; set; } = []; diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 9a63eb8ac..47dd00f31 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -327,7 +327,9 @@ preserve equipment slots, weapon groups, and simulation stat grids without reviving `PlayerSettings.msgpack`, `.loadout`, or `.zone` serialization. The run document also carries `CurrentEntityKey`, so Continue restore binds the saved player entity by exact typed record identity instead of reconstructing it -from a transient entity-slot index. +from a transient entity-slot index. Canonical typed run state no longer stores +`CurrentZoneEntityIndex`; older stored runs are translated through the package +reader into `CurrentEntityKey` when that compatibility seam is needed. `Aetheria.State.Verify` opens a materialized state file and checks that the typed migration ledger matches the actual item, corporation, and name-file diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 20a80791b..64d7c2f57 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -610,7 +610,6 @@ public class ActionGameManager : MonoBehaviour EntranceZoneIndex = ZoneIndex(CurrentGalaxy?.Entrance), ExitZoneIndex = ZoneIndex(CurrentGalaxy?.Exit), CurrentZoneIndex = currentZoneIndex, - CurrentZoneEntityIndex = IndexOfEntity(entityGraph, CurrentEntity), CurrentEntityKey = CurrentEntityRecordKey(runId, currentZoneIndex, entityGraph, CurrentEntity), GenerationSeed = CurrentGalaxy?.GenerationSeed ?? 0, DiscoveredZoneIndices = CurrentGalaxy?.DiscoveredZones @@ -2503,7 +2502,7 @@ public class ActionGameManager : MonoBehaviour } var zoneEntityKeyPrefix = $"global:aetheria.run_state.{run.RunId}.zone.{run.CurrentZoneIndex}.entity."; - var currentEntityKey = ResolveCurrentEntityRecordKey(run, zoneEntityKeyPrefix); + var currentEntityKey = ResolveCurrentEntityRecordKey(run); if (string.IsNullOrWhiteSpace(currentEntityKey)) { Debug.LogWarning($"Typed run {run.RunId} does not identify a current entity for restored zone {zoneEntityKeyPrefix}."); @@ -2550,18 +2549,11 @@ public class ActionGameManager : MonoBehaviour : $"global:aetheria.run_state.{runId}.zone.{currentZoneIndex}.entity.{currentZoneEntityIndex}.v1"; } - private static string ResolveCurrentEntityRecordKey( - AetheriaRuntimeRunStateSnapshot run, - string zoneEntityKeyPrefix) + private static string ResolveCurrentEntityRecordKey(AetheriaRuntimeRunStateSnapshot run) { - if (!string.IsNullOrWhiteSpace(run.CurrentEntityKey)) - { - return run.CurrentEntityKey; - } - - return run.CurrentZoneEntityIndex < 0 + return string.IsNullOrWhiteSpace(run.CurrentEntityKey) ? "" - : $"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1"; + : run.CurrentEntityKey; } private void ReplaceZoneEntitiesFromTypedSnapshots( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs index c5a85ad1b..d9f24c5ee 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs @@ -690,7 +690,6 @@ namespace GameCult.Aetheria.State.Unity int entranceZoneIndex, int exitZoneIndex, int currentZoneIndex, - int currentZoneEntityIndex, string currentEntityKey, IReadOnlyList discoveredZoneIndices, IReadOnlyList zoneKeys, @@ -704,7 +703,6 @@ namespace GameCult.Aetheria.State.Unity EntranceZoneIndex = entranceZoneIndex; ExitZoneIndex = exitZoneIndex; CurrentZoneIndex = currentZoneIndex; - CurrentZoneEntityIndex = currentZoneEntityIndex; CurrentEntityKey = currentEntityKey; DiscoveredZoneIndices = discoveredZoneIndices; ZoneKeys = zoneKeys; @@ -719,7 +717,6 @@ namespace GameCult.Aetheria.State.Unity public int EntranceZoneIndex { get; } public int ExitZoneIndex { get; } public int CurrentZoneIndex { get; } - public int CurrentZoneEntityIndex { get; } public string CurrentEntityKey { get; } public IReadOnlyList DiscoveredZoneIndices { get; } public IReadOnlyList ZoneKeys { get; } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs index e47eaea05..9ec079692 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs @@ -456,7 +456,7 @@ namespace GameCult.Aetheria.State.Unity var entranceZoneIndex = ReadFieldInt32(ref reader, fields, 2); var exitZoneIndex = ReadFieldInt32(ref reader, fields, 3); var currentZoneIndex = ReadFieldInt32(ref reader, fields, 4); - var currentZoneEntityIndex = ReadFieldInt32(ref reader, fields, 5); + var legacyCurrentZoneEntityIndex = ReadFieldInt32(ref reader, fields, 5, -1); var discoveredZoneIndices = ReadFieldInt32Array(ref reader, fields, 6); var zoneKeys = ReadFieldStringArray(ref reader, fields, 7); var actionBarBindings = ReadFieldActionBarBindings(ref reader, fields, 8); @@ -464,6 +464,11 @@ namespace GameCult.Aetheria.State.Unity var updatedAtUtc = ReadFieldString(ref reader, fields, 10); var generationSeed = ReadFieldUInt32(ref reader, fields, 11); var currentEntityKey = ReadFieldString(ref reader, fields, 12); + if (string.IsNullOrWhiteSpace(currentEntityKey)) + { + currentEntityKey = LegacyCurrentEntityKey(runId, currentZoneIndex, legacyCurrentZoneEntityIndex); + } + SkipRemaining(ref reader, fields, 13); return new AetheriaRuntimeRunStateSnapshot( runId, @@ -471,7 +476,6 @@ namespace GameCult.Aetheria.State.Unity entranceZoneIndex, exitZoneIndex, currentZoneIndex, - currentZoneEntityIndex, currentEntityKey, discoveredZoneIndices, zoneKeys, @@ -1085,6 +1089,22 @@ namespace GameCult.Aetheria.State.Unity return index >= fields ? 0 : reader.ReadInt32(); } + private static int ReadFieldInt32(ref MessagePackReader reader, int fields, int index, int fallback) + { + if (index >= fields) + { + return fallback; + } + + if (reader.NextMessagePackType == MessagePackType.Nil) + { + reader.ReadNil(); + return fallback; + } + + return reader.ReadInt32(); + } + private static uint ReadFieldUInt32(ref MessagePackReader reader, int fields, int index) { return index >= fields ? 0 : reader.ReadUInt32(); @@ -1689,6 +1709,16 @@ namespace GameCult.Aetheria.State.Unity : $"aetheria.item_definition:legacy:{legacyItemId.Trim()}"; } + private static string LegacyCurrentEntityKey(string runId, int zoneIndex, int entityIndex) + { + if (string.IsNullOrWhiteSpace(runId) || zoneIndex < 0 || entityIndex < 0) + { + return ""; + } + + return $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1"; + } + private static string CorporationKey(string legacyCorporationId) { return string.IsNullOrWhiteSpace(legacyCorporationId) diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 8f1a6503e..fe6167e6a 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -995,7 +995,9 @@ First Aetheria surfaces to publish: into live behavior instances, and binds the saved current entity by `AetheriaRunState.CurrentEntityKey` so Continue restore follows exact typed entity identity instead of reconstructing the player ship from an integer - slot. + slot. Canonical run state and package runtime snapshots no longer keep the + old current-entity slot field; the package reader only synthesizes the key + from older stored runs when that compatibility seam is encountered. - Done: project run checkpoint entity snapshots from the flattened live entity graph, not only `Zone.Entities`, so docked child ships can receive typed entity records and stable child/docking references instead of