Demote current entity slot from canonical run state

This commit is contained in:
Meta Vens 2026-06-16 17:27:28 +01:00
commit 9bc8ec125d
11 changed files with 60 additions and 26 deletions

Binary file not shown.

View file

@ -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],

View file

@ -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[]

View file

@ -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",

View file

@ -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),

View file

@ -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; } = [];

View file

@ -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

View file

@ -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(

View file

@ -690,7 +690,6 @@ namespace GameCult.Aetheria.State.Unity
int entranceZoneIndex,
int exitZoneIndex,
int currentZoneIndex,
int currentZoneEntityIndex,
string currentEntityKey,
IReadOnlyList<int> discoveredZoneIndices,
IReadOnlyList<string> 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<int> DiscoveredZoneIndices { get; }
public IReadOnlyList<string> ZoneKeys { get; }

View file

@ -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)

View file

@ -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