Persist docked entity graph in typed checkpoints
This commit is contained in:
parent
794e65f4ad
commit
c8b58fa44f
5 changed files with 122 additions and 21 deletions
Binary file not shown.
|
|
@ -846,6 +846,9 @@ static void RequireMainMenuContinueRunState(string root)
|
|||
"entity.RecordKey",
|
||||
"ReplaceZoneEntitiesFromTypedSnapshots",
|
||||
"Zone.Agents.Clear()",
|
||||
"FlattenEntityGraph(Zone)",
|
||||
"RestoreChildAndDockingRelationships",
|
||||
"RestoreCurrentEntityBinding",
|
||||
"RestoreEntityContactsFromTypedSnapshot",
|
||||
"entity.Target.Value = target",
|
||||
"string.Equals(entitySnapshot.RecordKey, currentEntityKey",
|
||||
|
|
|
|||
|
|
@ -177,11 +177,13 @@ 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 zone entity graph from exact typed
|
||||
entity snapshot records, including position, velocity, target/contact state,
|
||||
values. Run checkpoints project the flattened live entity graph, including
|
||||
docked children, into exact typed entity snapshot records. Continue restore
|
||||
lowers the current zone entity graph from those records, including position,
|
||||
velocity, target/contact state, child/docking relationships,
|
||||
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
|
||||
such as 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
|
||||
|
|
|
|||
|
|
@ -619,6 +619,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
|
||||
private AetheriaRuntimeRunCheckpointCommit ProjectRunCheckpoint()
|
||||
{
|
||||
var entityGraph = FlattenEntityGraph(Zone);
|
||||
return new AetheriaRuntimeRunCheckpointCommit
|
||||
{
|
||||
RunId = IsTutorial ? "tutorial" : "local",
|
||||
|
|
@ -626,7 +627,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
EntranceZoneIndex = ZoneIndex(CurrentGalaxy?.Entrance),
|
||||
ExitZoneIndex = ZoneIndex(CurrentGalaxy?.Exit),
|
||||
CurrentZoneIndex = ZoneIndex(Zone?.GalaxyZone),
|
||||
CurrentZoneEntityIndex = Zone?.Entities?.IndexOf(CurrentEntity) ?? -1,
|
||||
CurrentZoneEntityIndex = entityGraph.IndexOf(CurrentEntity),
|
||||
GenerationSeed = CurrentGalaxy?.GenerationSeed ?? 0,
|
||||
DiscoveredZoneIndices = CurrentGalaxy?.DiscoveredZones
|
||||
.Select(ZoneIndex)
|
||||
|
|
@ -696,6 +697,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
private AetheriaRuntimeZoneSnapshotCommit ProjectZoneSnapshot(Zone zone)
|
||||
{
|
||||
var galaxyZone = zone.GalaxyZone;
|
||||
var entityGraph = FlattenEntityGraph(zone);
|
||||
return new AetheriaRuntimeZoneSnapshotCommit
|
||||
{
|
||||
ZoneIndex = ZoneIndex(galaxyZone),
|
||||
|
|
@ -715,8 +717,8 @@ public class ActionGameManager : MonoBehaviour
|
|||
OwnerFactionIndex = FactionIndex(galaxyZone?.Owner),
|
||||
Orbits = ProjectZoneOrbits(zone),
|
||||
Bodies = ProjectZoneBodies(zone),
|
||||
Entities = zone.Entities
|
||||
.Select((entity, index) => ProjectEntitySnapshot(zone, entity, index))
|
||||
Entities = entityGraph
|
||||
.Select((entity, index) => ProjectEntitySnapshot(entityGraph, entity, index))
|
||||
.ToArray(),
|
||||
DroppedPickups = ProjectDroppedPickups(zone)
|
||||
};
|
||||
|
|
@ -913,7 +915,29 @@ public class ActionGameManager : MonoBehaviour
|
|||
};
|
||||
}
|
||||
|
||||
private AetheriaRuntimeEntitySnapshotCommit ProjectEntitySnapshot(Zone zone, Entity entity, int entityIndex)
|
||||
private static List<Entity> FlattenEntityGraph(Zone zone)
|
||||
{
|
||||
var entities = new List<Entity>();
|
||||
if (zone == null)
|
||||
return entities;
|
||||
|
||||
var visited = new HashSet<Entity>();
|
||||
foreach (var entity in zone.Entities)
|
||||
AddEntityAndChildren(entity);
|
||||
return entities;
|
||||
|
||||
void AddEntityAndChildren(Entity entity)
|
||||
{
|
||||
if (entity == null || !visited.Add(entity))
|
||||
return;
|
||||
|
||||
entities.Add(entity);
|
||||
foreach (var child in entity.Children)
|
||||
AddEntityAndChildren(child);
|
||||
}
|
||||
}
|
||||
|
||||
private AetheriaRuntimeEntitySnapshotCommit ProjectEntitySnapshot(IReadOnlyList<Entity> entityGraph, Entity entity, int entityIndex)
|
||||
{
|
||||
return new AetheriaRuntimeEntitySnapshotCommit
|
||||
{
|
||||
|
|
@ -927,7 +951,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
DirectionY = entity.Direction.y,
|
||||
VelocityX = entity.Velocity.x,
|
||||
VelocityY = entity.Velocity.y,
|
||||
TargetEntityIndex = entity.Target?.Value == null ? -1 : zone.Entities.IndexOf(entity.Target.Value),
|
||||
TargetEntityIndex = entity.Target?.Value == null ? -1 : entityGraph.IndexOf(entity.Target.Value),
|
||||
IsActive = entity.Active,
|
||||
HeatsinksEnabled = entity.HeatsinksEnabled,
|
||||
OverrideShutdown = entity.OverrideShutdown,
|
||||
|
|
@ -946,9 +970,9 @@ public class ActionGameManager : MonoBehaviour
|
|||
.ToArray() ?? Array.Empty<int>(),
|
||||
Visibility = entity.Visibility,
|
||||
VisibilitySourceCount = entity.VisibilitySources.Count,
|
||||
Contacts = ProjectEntityContacts(zone, entity),
|
||||
Contacts = ProjectEntityContacts(entityGraph, entity),
|
||||
ChildEntityIndices = entity.Children?
|
||||
.Select(child => zone.Entities.IndexOf(child))
|
||||
.Select(child => entityGraph.IndexOf(child))
|
||||
.Where(index => index >= 0)
|
||||
.ToArray() ?? Array.Empty<int>(),
|
||||
WeaponGroups = entity.WeaponGroups?
|
||||
|
|
@ -957,17 +981,17 @@ public class ActionGameManager : MonoBehaviour
|
|||
StatGrids = ProjectEntityStatGrids(entity),
|
||||
ActiveConsumables = ProjectActiveConsumables(entity),
|
||||
BehaviorProgress = ProjectBehaviorProgress(entity),
|
||||
WeaponStates = ProjectWeaponStates(zone, entity),
|
||||
WeaponStates = ProjectWeaponStates(entityGraph, entity),
|
||||
BehaviorStates = ProjectBehaviorStates(entity)
|
||||
};
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeEntityContactCommit[] ProjectEntityContacts(Zone zone, Entity entity)
|
||||
private static AetheriaRuntimeEntityContactCommit[] ProjectEntityContacts(IReadOnlyList<Entity> entityGraph, Entity entity)
|
||||
{
|
||||
return entity.EntityInfoGathered
|
||||
.Select(contact =>
|
||||
{
|
||||
var targetIndex = zone.Entities.IndexOf(contact.Key);
|
||||
var targetIndex = entityGraph.IndexOf(contact.Key);
|
||||
entity.EntityHostility.TryGetValue(contact.Key, out var hostile);
|
||||
return new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
|
|
@ -1179,22 +1203,22 @@ public class ActionGameManager : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeWeaponStateCommit[] ProjectWeaponStates(Zone zone, Entity entity)
|
||||
private static AetheriaRuntimeWeaponStateCommit[] ProjectWeaponStates(IReadOnlyList<Entity> entityGraph, Entity entity)
|
||||
{
|
||||
var states = new List<AetheriaRuntimeWeaponStateCommit>();
|
||||
|
||||
for (var ownerIndex = 0; ownerIndex < entity.Equipment.Count; ownerIndex++)
|
||||
states.AddRange(ProjectWeaponStates(zone, "equipment", ownerIndex, entity.Equipment[ownerIndex].Behaviors));
|
||||
states.AddRange(ProjectWeaponStates(entityGraph, "equipment", ownerIndex, entity.Equipment[ownerIndex].Behaviors));
|
||||
|
||||
var activeConsumables = entity.ActiveConsumables;
|
||||
for (var ownerIndex = 0; ownerIndex < activeConsumables.Count; ownerIndex++)
|
||||
states.AddRange(ProjectWeaponStates(zone, "active_consumable", ownerIndex, activeConsumables[ownerIndex].Behaviors));
|
||||
states.AddRange(ProjectWeaponStates(entityGraph, "active_consumable", ownerIndex, activeConsumables[ownerIndex].Behaviors));
|
||||
|
||||
return states.ToArray();
|
||||
}
|
||||
|
||||
private static IEnumerable<AetheriaRuntimeWeaponStateCommit> ProjectWeaponStates(
|
||||
Zone zone,
|
||||
IReadOnlyList<Entity> entityGraph,
|
||||
string ownerKind,
|
||||
int ownerIndex,
|
||||
IReadOnlyList<Behavior> behaviors)
|
||||
|
|
@ -1243,7 +1267,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
if (weapon is LockWeapon lockWeapon)
|
||||
{
|
||||
state.LockProgress = lockWeapon.LockProgress;
|
||||
state.LockTargetEntityIndex = lockWeapon.LockTarget == null ? -1 : zone.Entities.IndexOf(lockWeapon.LockTarget);
|
||||
state.LockTargetEntityIndex = lockWeapon.LockTarget == null ? -1 : entityGraph.IndexOf(lockWeapon.LockTarget);
|
||||
}
|
||||
|
||||
yield return state;
|
||||
|
|
@ -2577,6 +2601,8 @@ public class ActionGameManager : MonoBehaviour
|
|||
foreach (var entity in restoredEntities.Values)
|
||||
entity.Activate();
|
||||
|
||||
RestoreChildAndDockingRelationships(entitySnapshots, restoredEntities);
|
||||
|
||||
foreach (var entitySnapshot in entitySnapshots)
|
||||
{
|
||||
if (!restoredEntities.TryGetValue(entitySnapshot.RecordKey, out var entity))
|
||||
|
|
@ -2592,7 +2618,71 @@ public class ActionGameManager : MonoBehaviour
|
|||
}
|
||||
|
||||
if (restoredEntities.TryGetValue(currentEntityKey, out var currentEntity))
|
||||
BindToEntity(currentEntity);
|
||||
RestoreCurrentEntityBinding(currentEntity);
|
||||
}
|
||||
|
||||
private void RestoreChildAndDockingRelationships(
|
||||
IReadOnlyList<AetheriaRuntimeEntitySnapshot> entitySnapshots,
|
||||
IReadOnlyDictionary<string, Entity> restoredEntities)
|
||||
{
|
||||
foreach (var entitySnapshot in entitySnapshots)
|
||||
{
|
||||
if (!restoredEntities.TryGetValue(entitySnapshot.RecordKey, out var parent))
|
||||
continue;
|
||||
|
||||
foreach (var childKey in entitySnapshot.ChildEntityKeys)
|
||||
{
|
||||
if (!restoredEntities.TryGetValue(childKey, out var child) ||
|
||||
child == parent ||
|
||||
child.Parent == parent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
child.RemoveParent();
|
||||
child.SetParent(parent);
|
||||
}
|
||||
|
||||
for (var bayIndex = 0; bayIndex < entitySnapshot.DockingBayAssignments.Count; bayIndex++)
|
||||
{
|
||||
var childIndex = entitySnapshot.DockingBayAssignments[bayIndex];
|
||||
if (childIndex < 0 ||
|
||||
childIndex >= entitySnapshot.ChildEntityKeys.Count ||
|
||||
bayIndex >= parent.DockingBays.Count ||
|
||||
!restoredEntities.TryGetValue(entitySnapshot.ChildEntityKeys[childIndex], out var child) ||
|
||||
!(child is Ship ship))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dockingBay = parent.DockingBays[bayIndex];
|
||||
dockingBay.DockedShip = ship;
|
||||
if (ship.Parent != parent)
|
||||
{
|
||||
ship.RemoveParent();
|
||||
ship.SetParent(parent);
|
||||
}
|
||||
if (Zone.Entities.Contains(ship))
|
||||
Zone.Entities.Remove(ship);
|
||||
ship.Deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreCurrentEntityBinding(Entity currentEntity)
|
||||
{
|
||||
if (currentEntity.Parent != null)
|
||||
{
|
||||
var dockingBay = currentEntity.Parent.DockingBays.FirstOrDefault(bay => bay.DockedShip == currentEntity);
|
||||
if (dockingBay != null && currentEntity.Parent is OrbitalEntity)
|
||||
{
|
||||
CurrentEntity = currentEntity;
|
||||
DoDock(currentEntity.Parent, dockingBay);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BindToEntity(currentEntity);
|
||||
}
|
||||
|
||||
private void RestoreEntityContactsFromTypedSnapshot(
|
||||
|
|
|
|||
|
|
@ -979,10 +979,16 @@ First Aetheria surfaces to publish:
|
|||
heatstroke/hypothermia exposure, active consumable item/timer rows,
|
||||
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.
|
||||
entities, restores child/docking relationships from typed child keys and
|
||||
docking-bay assignments, and binds the saved current entity as the player
|
||||
ship or docked current ship.
|
||||
- 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
|
||||
disappearing from the `.zone` replacement graph.
|
||||
- Remaining: add typed documents/mappers for runtime object graphs,
|
||||
complete full Continue/run entity graph reconstruction from typed state
|
||||
for child/docked entity activation, restored active-consumable behavior runtime internals,
|
||||
for 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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue