Restore Continue entity graph from typed state

This commit is contained in:
Meta Vens 2026-06-16 07:52:01 +01:00
commit 794e65f4ad
6 changed files with 125 additions and 44 deletions

Binary file not shown.

View file

@ -841,11 +841,15 @@ static void RequireMainMenuContinueRunState(string root)
"AetheriaRuntimeRunStateSnapshot ContinueRunState",
"ResolveStartZone(continuingRun)",
"if (continuingRun != null)",
"RestoreCurrentEntityFromTypedRun(continuingRun)",
"RestoreEntityGraphFromTypedRun(continuingRun)",
"ReadEntitySnapshots(RuntimeStateFilePath)",
"entity.RecordKey",
"CreateEntityConstructionBlueprint(entitySnapshot, true)",
"BindToEntity(entity)",
"ReplaceZoneEntitiesFromTypedSnapshots",
"Zone.Agents.Clear()",
"RestoreEntityContactsFromTypedSnapshot",
"entity.Target.Value = target",
"string.Equals(entitySnapshot.RecordKey, currentEntityKey",
"BindToEntity(currentEntity)",
"RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot)",
"entity.RestoreStatGrids(entitySnapshot.StatGrids)",
"RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)",

View file

@ -177,11 +177,11 @@ orbit/body rows into `AetheriaZoneState`, so `ZoneConstructionBlueprint` is a
one-shot construction input rather than the retained surface carrying celestial graph facts. Entity checkpoints
persist hull simulation grids into `AetheriaEntitySnapshot`, so
`EntityConstructionBlueprint` is no longer the only surface carrying those live
values. Continue restore lowers the current player entity from the exact typed
entity snapshot record, including position, velocity, shutdown/heatsink state,
thermal exposure, active consumable timers, and typed stat grids for
temperature, thermal mass, armor, max armor, and hull conductivity. Broader runtime object graphs
and behavior-private simulation state remain legacy until dedicated typed runtime documents exist. Live behavior
values. Continue restore lowers the current zone entity graph from exact typed
entity snapshot records, including position, velocity, target/contact state,
shutdown/heatsink state, thermal exposure, active consumable timers, and typed
stat grids for temperature, thermal mass, armor, max armor, and hull conductivity. Broader runtime object graphs
such as child/docked activation and behavior-private simulation state remain legacy until dedicated typed runtime documents exist. Live behavior
instances expose typed behavior kind and group through `Behavior.Kind` and
`Behavior.Group`; behavior construction now reads typed payload fields through
`RuntimeBehaviorDefinition` instead of projecting through config DTOs. Stat

View file

@ -24,6 +24,7 @@ using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using quaternion = Unity.Mathematics.quaternion;
using Regex = System.Text.RegularExpressions.Regex;
using Random = UnityEngine.Random;
public class ActionGameManager : MonoBehaviour
@ -2482,7 +2483,7 @@ public class ActionGameManager : MonoBehaviour
if (continuingRun != null)
{
RestoreCurrentEntityFromTypedRun(continuingRun);
RestoreEntityGraphFromTypedRun(continuingRun);
ContinueRunState = null;
return;
}
@ -2502,7 +2503,7 @@ public class ActionGameManager : MonoBehaviour
}
}
private void RestoreCurrentEntityFromTypedRun(AetheriaRuntimeRunStateSnapshot run)
private void RestoreEntityGraphFromTypedRun(AetheriaRuntimeRunStateSnapshot run)
{
if (run == null ||
run.CurrentZoneIndex < 0 ||
@ -2511,43 +2512,116 @@ public class ActionGameManager : MonoBehaviour
return;
}
var entityKey = $"global:aetheria.run_state.{run.RunId}.zone.{run.CurrentZoneIndex}.entity.{run.CurrentZoneEntityIndex}.v1";
AetheriaRuntimeEntitySnapshot entitySnapshot;
var zoneEntityKeyPrefix = $"global:aetheria.run_state.{run.RunId}.zone.{run.CurrentZoneIndex}.entity.";
var currentEntityKey = $"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1";
AetheriaRuntimeEntitySnapshot[] entitySnapshots;
try
{
entitySnapshot = AetheriaRuntimeCatalogStore.ReadEntitySnapshots(RuntimeStateFilePath)
.FirstOrDefault(entity => string.Equals(entity.RecordKey, entityKey, StringComparison.Ordinal));
entitySnapshots = AetheriaRuntimeCatalogStore.ReadEntitySnapshots(RuntimeStateFilePath)
.Where(entity => entity.RecordKey.StartsWith(zoneEntityKeyPrefix, StringComparison.Ordinal))
.OrderBy(entity => EntityIndexFromRecordKey(entity.RecordKey))
.ToArray();
}
catch (Exception ex)
{
Debug.LogError($"Failed to read typed current entity snapshot {entityKey}: {ex}");
Debug.LogError($"Failed to read typed entity snapshots for zone {zoneEntityKeyPrefix}: {ex}");
return;
}
var blueprint = CreateEntityConstructionBlueprint(entitySnapshot, true);
if (blueprint == null)
if (entitySnapshots.Length == 0)
{
Debug.LogWarning($"Typed current entity snapshot {entityKey} could not be lowered into a runtime entity.");
Debug.LogWarning($"No typed entity snapshots found for restored zone {zoneEntityKeyPrefix}.");
return;
}
var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(ItemManager, Zone, blueprint, true);
if (entity == null)
return;
ReplaceZoneEntitiesFromTypedSnapshots(entitySnapshots, currentEntityKey);
}
entity.Position = new float3((float)entitySnapshot.PositionX, (float)entitySnapshot.PositionY, (float)entitySnapshot.PositionZ);
entity.Direction = new float2((float)entitySnapshot.DirectionX, (float)entitySnapshot.DirectionY);
entity.Velocity = new float2((float)entitySnapshot.VelocityX, (float)entitySnapshot.VelocityY);
entity.OverrideShutdown = entitySnapshot.OverrideShutdown;
entity.TractorPower = (float)entitySnapshot.TractorPower;
entity.Zone = Zone;
Zone.Entities.Add(entity);
entity.Activate();
entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled;
entity.RestoreStatGrids(entitySnapshot.StatGrids);
entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia);
RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot);
BindToEntity(entity);
private void ReplaceZoneEntitiesFromTypedSnapshots(
IReadOnlyList<AetheriaRuntimeEntitySnapshot> entitySnapshots,
string currentEntityKey)
{
foreach (var existingEntity in Zone.Entities.ToArray())
{
existingEntity.Deactivate();
Zone.Entities.Remove(existingEntity);
}
Zone.Agents.Clear();
var restoredEntities = new Dictionary<string, Entity>();
foreach (var entitySnapshot in entitySnapshots)
{
var blueprint = CreateEntityConstructionBlueprint(
entitySnapshot,
string.Equals(entitySnapshot.RecordKey, currentEntityKey, StringComparison.Ordinal));
if (blueprint == null)
{
Debug.LogWarning($"Typed entity snapshot {entitySnapshot.RecordKey} could not be lowered into a runtime entity.");
continue;
}
var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(ItemManager, Zone, blueprint, true);
if (entity == null)
continue;
entity.Position = new float3((float)entitySnapshot.PositionX, (float)entitySnapshot.PositionY, (float)entitySnapshot.PositionZ);
entity.Direction = new float2((float)entitySnapshot.DirectionX, (float)entitySnapshot.DirectionY);
entity.Velocity = new float2((float)entitySnapshot.VelocityX, (float)entitySnapshot.VelocityY);
entity.OverrideShutdown = entitySnapshot.OverrideShutdown;
entity.TractorPower = (float)entitySnapshot.TractorPower;
entity.Zone = Zone;
Zone.Entities.Add(entity);
restoredEntities[entitySnapshot.RecordKey] = entity;
}
foreach (var entity in restoredEntities.Values)
entity.Activate();
foreach (var entitySnapshot in entitySnapshots)
{
if (!restoredEntities.TryGetValue(entitySnapshot.RecordKey, out var entity))
continue;
entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled;
entity.RestoreStatGrids(entitySnapshot.StatGrids);
entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia);
RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot);
RestoreEntityContactsFromTypedSnapshot(entity, entitySnapshot, restoredEntities);
if (restoredEntities.TryGetValue(entitySnapshot.TargetEntityKey, out var target))
entity.Target.Value = target;
}
if (restoredEntities.TryGetValue(currentEntityKey, out var currentEntity))
BindToEntity(currentEntity);
}
private void RestoreEntityContactsFromTypedSnapshot(
Entity entity,
AetheriaRuntimeEntitySnapshot snapshot,
IReadOnlyDictionary<string, Entity> restoredEntities)
{
foreach (var contact in snapshot.Contacts)
{
if (!restoredEntities.TryGetValue(contact.TargetEntityKey, out var target))
continue;
entity.EntityInfoGathered[target] = (float)contact.InfoGathered;
entity.EntityHostility[target] = contact.Hostile;
if (contact.Visible && !entity.VisibleEntities.Contains(target))
entity.VisibleEntities.Add(target);
if (contact.Visible && contact.Hostile && !entity.VisibleEnemies.Contains(target))
entity.VisibleEnemies.Add(target);
if (contact.Visible && !contact.Hostile && !entity.VisibleFriendlies.Contains(target))
entity.VisibleFriendlies.Add(target);
}
}
private static int EntityIndexFromRecordKey(string recordKey)
{
var match = Regex.Match(recordKey ?? "", @"\.entity\.(\d+)\.v1$");
return match.Success && int.TryParse(match.Groups[1].Value, out var index)
? index
: int.MaxValue;
}
private void RestoreActiveConsumablesFromTypedEntitySnapshot(Entity entity, AetheriaRuntimeEntitySnapshot snapshot)

View file

@ -338,7 +338,10 @@ public class ZoneRenderer : MonoBehaviour
}
}
Destroy(EntityInstances[entity].gameObject);
if (!EntityInstances.TryGetValue(entity, out var instance))
return;
Destroy(instance.gameObject);
EntityInstances.Remove(entity);
}

View file

@ -968,21 +968,21 @@ First Aetheria surfaces to publish:
galaxy from the saved generation seed, and passes the selected run to
`ActionGameManager` so boot enters the saved current zone and lowers typed
pickup rows.
- Done: restore the current player entity from typed state during Continue
boot. Package entity readback now preserves `AetheriaEntitySnapshot` record
identity, and `ActionGameManager` resolves the exact
`RunId + ZoneIndex + CurrentZoneEntityIndex` entity record, lowers it
- Done: restore the current zone's entity graph from typed state during
Continue boot. Package entity readback preserves `AetheriaEntitySnapshot`
record identity, and `ActionGameManager` resolves the exact
`RunId + ZoneIndex` entity-record prefix, removes generated zone entities
and their agents as Continue authority, lowers all typed entity snapshots
through the existing construction blueprint path, restores hull,
equipment, cargo, docking contents, weapon groups, position, direction,
velocity, shutdown override, heatsink toggle, tractor power,
heatstroke/hypothermia exposure, active consumable item/timer rows,
and typed stat grids for temperature, thermal mass, armor, max armor,
and hull-conductivity axes, then binds the lowered entity as the current
player ship.
typed stat grids for temperature, thermal mass, armor, max armor, and
hull-conductivity axes, then reconnects target/contact rows among restored
entities and binds the saved current entity as the player ship.
- Remaining: add typed documents/mappers for runtime object graphs,
complete full Continue/run entity graph reconstruction from typed state
for non-current entities, child/docked entity activation, targets,
contacts, restored active-consumable behavior runtime internals,
for child/docked entity activation, restored active-consumable behavior runtime internals,
behavior-private state not covered by progress, weapon, sensor, radiator,
reactor, or capacitor rows, typed behavior factory construction, and any
catalog fields not covered by the stable scalar/fingerprint/payload pass.