Restore Continue current entity from typed state
This commit is contained in:
parent
672b133ee1
commit
8b8a86f6ae
7 changed files with 164 additions and 8 deletions
Binary file not shown.
|
|
@ -907,6 +907,7 @@ try
|
|||
packageZones[0].DroppedPickups[0].Item.ItemKey != "aetheria.item_definition:legacy:smoke:ore" ||
|
||||
packageZones[0].DroppedPickups[0].Item.Quantity != 11 ||
|
||||
packageEntities.Count != 1 ||
|
||||
packageEntities[0].RecordKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" ||
|
||||
packageEntities[0].Name != "Unity Smoke Ship" ||
|
||||
packageEntities[0].FactionKey != "aetheria.corporation:legacy:smoke:faction" ||
|
||||
packageEntities[0].Equipment[0].Quality != 0.9 ||
|
||||
|
|
|
|||
|
|
@ -791,6 +791,24 @@ static void RequireMainMenuContinueRunState(string root)
|
|||
var actionGameManager = File.Exists(actionGameManagerPath)
|
||||
? File.ReadAllText(actionGameManagerPath)
|
||||
: throw new InvalidOperationException("Cannot verify Continue run authority; ActionGameManager.cs is missing.");
|
||||
var packageSnapshotPath = Path.Combine(
|
||||
root,
|
||||
"Packages",
|
||||
"org.gamecult.aetheria.state",
|
||||
"Runtime",
|
||||
"AetheriaRuntimeCatalogSnapshot.cs");
|
||||
var packageSnapshot = File.Exists(packageSnapshotPath)
|
||||
? File.ReadAllText(packageSnapshotPath)
|
||||
: throw new InvalidOperationException("Cannot verify Continue entity identity; package runtime snapshots are missing.");
|
||||
var packageStorePath = Path.Combine(
|
||||
root,
|
||||
"Packages",
|
||||
"org.gamecult.aetheria.state",
|
||||
"Runtime",
|
||||
"AetheriaRuntimeCatalogStore.cs");
|
||||
var packageStore = File.Exists(packageStorePath)
|
||||
? File.ReadAllText(packageStorePath)
|
||||
: throw new InvalidOperationException("Cannot verify Continue entity readback; package runtime store is missing.");
|
||||
|
||||
var requiredMenuSymbols = new[]
|
||||
{
|
||||
|
|
@ -823,6 +841,11 @@ static void RequireMainMenuContinueRunState(string root)
|
|||
"AetheriaRuntimeRunStateSnapshot ContinueRunState",
|
||||
"ResolveStartZone(continuingRun)",
|
||||
"if (continuingRun != null)",
|
||||
"RestoreCurrentEntityFromTypedRun(continuingRun)",
|
||||
"ReadEntitySnapshots(RuntimeStateFilePath)",
|
||||
"entity.RecordKey",
|
||||
"CreateEntityConstructionBlueprint(entitySnapshot, true)",
|
||||
"BindToEntity(entity)",
|
||||
"ContinueRunState = null",
|
||||
"RestoreDroppedPickupsFromTypedZoneState"
|
||||
};
|
||||
|
|
@ -837,6 +860,25 @@ static void RequireMainMenuContinueRunState(string root)
|
|||
"ActionGameManager no longer has the typed Continue boot path: " +
|
||||
string.Join(", ", missingGameplaySymbols));
|
||||
}
|
||||
|
||||
var requiredPackageSymbols = new[]
|
||||
{
|
||||
"public string RecordKey",
|
||||
"ReadEntitySnapshotPayload(record.Key, record.Payload)"
|
||||
};
|
||||
|
||||
var missingPackageSymbols = requiredPackageSymbols
|
||||
.Where(symbol =>
|
||||
!packageSnapshot.Contains(symbol, StringComparison.Ordinal) &&
|
||||
!packageStore.Contains(symbol, StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
if (missingPackageSymbols.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Unity package entity readback no longer preserves record identity for Continue: " +
|
||||
string.Join(", ", missingPackageSymbols));
|
||||
}
|
||||
}
|
||||
|
||||
static void RequirePropertiesPanelReadOnlyInspector(string root)
|
||||
|
|
|
|||
|
|
@ -250,6 +250,45 @@ public class ActionGameManager : MonoBehaviour
|
|||
return blueprint;
|
||||
}
|
||||
|
||||
private EntityConstructionBlueprint CreateEntityConstructionBlueprint(AetheriaRuntimeEntitySnapshot entity, bool isCurrentEntity)
|
||||
{
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
var hull = CreateEquippableLoadoutItem(new AetheriaRuntimeLoadoutItemSnapshot(
|
||||
entity.HullItemKey,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
false));
|
||||
if (hull == null)
|
||||
return null;
|
||||
|
||||
EntityConstructionBlueprint blueprint = string.Equals(entity.Kind, "orbital", StringComparison.OrdinalIgnoreCase)
|
||||
? new OrbitalEntityConstructionBlueprint()
|
||||
: new ShipConstructionBlueprint
|
||||
{
|
||||
Position = new float3((float)entity.PositionX, (float)entity.PositionY, (float)entity.PositionZ),
|
||||
Direction = new float2((float)entity.DirectionX, (float)entity.DirectionY),
|
||||
IsPlayerShip = isCurrentEntity
|
||||
};
|
||||
|
||||
blueprint.Name = entity.Name ?? "";
|
||||
blueprint.FactionKey = entity.FactionKey ?? "";
|
||||
blueprint.Faction = ParseLegacyGuidFromReferenceKey(blueprint.FactionKey, "aetheria.corporation");
|
||||
blueprint.Hull = hull;
|
||||
blueprint.Equipment = CreateEquippableSlots(entity.Equipment);
|
||||
blueprint.CargoBays = CreateEquippableSlots(entity.CargoBays);
|
||||
blueprint.DockingBays = CreateEquippableSlots(entity.DockingBays);
|
||||
blueprint.CargoContents = CreateCargoBayContents(entity.CargoContents);
|
||||
blueprint.DockingBayContents = CreateCargoBayContents(entity.DockingBayContents);
|
||||
blueprint.DockingBayAssignments = entity.DockingBayAssignments.ToArray();
|
||||
blueprint.WeaponGroups = entity.WeaponGroups.Select(group => group.ToArray()).ToArray();
|
||||
blueprint.Children = Array.Empty<EntityConstructionBlueprint>();
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
private (int2 position, EquippableItem item)[] CreateEquippableSlots(
|
||||
IReadOnlyList<AetheriaRuntimeLoadoutItemSlotSnapshot> slots)
|
||||
{
|
||||
|
|
@ -259,6 +298,21 @@ public class ActionGameManager : MonoBehaviour
|
|||
.ToArray();
|
||||
}
|
||||
|
||||
private (int2 position, EquippableItem item)[] CreateEquippableSlots(
|
||||
IReadOnlyList<AetheriaRuntimeEntityItemSlotSnapshot> slots)
|
||||
{
|
||||
return slots
|
||||
.Select(slot => (position: new int2(slot.X, slot.Y), item: CreateEquippableLoadoutItem(new AetheriaRuntimeLoadoutItemSnapshot(
|
||||
slot.ItemKey,
|
||||
slot.Quality,
|
||||
slot.Durability,
|
||||
slot.Quantity,
|
||||
slot.Enabled,
|
||||
slot.OverrideShutdown))))
|
||||
.Where(slot => slot.item != null)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private (int2 position, ItemInstance item)[][] CreateCargoBayContents(
|
||||
IReadOnlyList<AetheriaRuntimeCargoBayLoadoutSnapshot> bays)
|
||||
{
|
||||
|
|
@ -2428,6 +2482,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
|
||||
if (continuingRun != null)
|
||||
{
|
||||
RestoreCurrentEntityFromTypedRun(continuingRun);
|
||||
ContinueRunState = null;
|
||||
return;
|
||||
}
|
||||
|
|
@ -2447,6 +2502,50 @@ public class ActionGameManager : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
private void RestoreCurrentEntityFromTypedRun(AetheriaRuntimeRunStateSnapshot run)
|
||||
{
|
||||
if (run == null ||
|
||||
run.CurrentZoneIndex < 0 ||
|
||||
run.CurrentZoneEntityIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entityKey = $"global:aetheria.run_state.{run.RunId}.zone.{run.CurrentZoneIndex}.entity.{run.CurrentZoneEntityIndex}.v1";
|
||||
AetheriaRuntimeEntitySnapshot entitySnapshot;
|
||||
try
|
||||
{
|
||||
entitySnapshot = AetheriaRuntimeCatalogStore.ReadEntitySnapshots(RuntimeStateFilePath)
|
||||
.FirstOrDefault(entity => string.Equals(entity.RecordKey, entityKey, StringComparison.Ordinal));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to read typed current entity snapshot {entityKey}: {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
var blueprint = CreateEntityConstructionBlueprint(entitySnapshot, true);
|
||||
if (blueprint == null)
|
||||
{
|
||||
Debug.LogWarning($"Typed current entity snapshot {entityKey} could not be lowered into a runtime entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(ItemManager, Zone, blueprint, true);
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
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();
|
||||
BindToEntity(entity);
|
||||
}
|
||||
|
||||
private static GalaxyZone ResolveStartZone(AetheriaRuntimeRunStateSnapshot run)
|
||||
{
|
||||
if (CurrentGalaxy?.Zones == null || CurrentGalaxy.Zones.Length == 0)
|
||||
|
|
|
|||
|
|
@ -847,6 +847,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
public sealed class AetheriaRuntimeEntitySnapshot
|
||||
{
|
||||
public AetheriaRuntimeEntitySnapshot(
|
||||
string recordKey,
|
||||
string name,
|
||||
string kind,
|
||||
double positionX,
|
||||
|
|
@ -882,6 +883,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
int visibilitySourceCount,
|
||||
IReadOnlyList<AetheriaRuntimeEntityContactSnapshot> contacts)
|
||||
{
|
||||
RecordKey = recordKey;
|
||||
Name = name;
|
||||
Kind = kind;
|
||||
PositionX = positionX;
|
||||
|
|
@ -918,6 +920,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
Contacts = contacts;
|
||||
}
|
||||
|
||||
public string RecordKey { get; }
|
||||
public string Name { get; }
|
||||
public string Kind { get; }
|
||||
public double PositionX { get; }
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
schemaName != EntitySnapshotSchema)
|
||||
continue;
|
||||
|
||||
entities.Add(ReadEntitySnapshotPayload(record.Payload));
|
||||
entities.Add(ReadEntitySnapshotPayload(record.Key, record.Payload));
|
||||
}
|
||||
|
||||
return entities.OrderBy(entity => entity.Name, StringComparer.Ordinal).ToArray();
|
||||
|
|
@ -534,7 +534,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
return pickups;
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeEntitySnapshot ReadEntitySnapshotPayload(byte[] payload)
|
||||
private static AetheriaRuntimeEntitySnapshot ReadEntitySnapshotPayload(string recordKey, byte[] payload)
|
||||
{
|
||||
var reader = new MessagePackReader(payload);
|
||||
var fields = reader.ReadArrayHeader();
|
||||
|
|
@ -570,6 +570,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
var contacts = ReadFieldEntityContacts(ref reader, fields, 29);
|
||||
SkipRemaining(ref reader, fields, 30);
|
||||
return new AetheriaRuntimeEntitySnapshot(
|
||||
recordKey,
|
||||
name,
|
||||
kind,
|
||||
position.X,
|
||||
|
|
|
|||
|
|
@ -967,13 +967,23 @@ First Aetheria surfaces to publish:
|
|||
`AetheriaRuntimeCatalogStore`, selects the newest run, regenerates the
|
||||
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. Entity/player reconstruction from `AetheriaEntitySnapshot`
|
||||
remains future Continue work, not a fake restored ship.
|
||||
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
|
||||
through the existing construction blueprint path, restores hull,
|
||||
equipment, cargo, docking contents, weapon groups, position, direction,
|
||||
velocity, shutdown override, and tractor power, then binds the lowered
|
||||
entity as the current player ship.
|
||||
- Remaining: add typed documents/mappers for runtime object graphs,
|
||||
complete Continue/run entity reconstruction from typed state, typed
|
||||
behavior factory construction, remaining behavior-private state not covered
|
||||
by progress, weapon, sensor, radiator, reactor, or capacitor rows, and any
|
||||
catalog fields not covered by the stable scalar/fingerprint/payload pass.
|
||||
complete full Continue/run entity graph reconstruction from typed state
|
||||
for non-current entities, child/docked entity activation, targets,
|
||||
contacts, active consumables, behavior-private state not covered by
|
||||
progress, weapon, sensor, radiator, reactor, or capacitor rows,
|
||||
heatstroke/hypothermia runtime restore, typed behavior factory
|
||||
construction, and any catalog fields not covered by the stable
|
||||
scalar/fingerprint/payload pass.
|
||||
|
||||
4. Runtime cutover
|
||||
- Done: add a Unity-facing typed catalog read facade and smoke proving it can
|
||||
|
|
|
|||
Loading…
Reference in a new issue