Project active zone snapshots into typed state

This commit is contained in:
Meta Vens 2026-06-13 15:37:05 +01:00
commit 30e73a7b79
6 changed files with 405 additions and 28 deletions

Binary file not shown.

View file

@ -144,8 +144,59 @@ try
TutorialPassed = true,
ActionBarInputs = new[] { "<Keyboard>/1" }
});
AetheriaRuntimeStateCommitLog.QueueRunCheckpoint(
commitSmokeStatePath,
new AetheriaRuntimeRunCheckpointCommit
{
RunId = "smoke-run",
CurrentZoneIndex = 0,
CurrentZoneEntityIndex = 0,
DiscoveredZoneIndices = new[] { 0 },
Zones = new[]
{
new AetheriaRuntimeZoneSnapshotCommit
{
ZoneIndex = 0,
Name = "Unity Smoke Zone",
PositionX = 12.0,
PositionY = -4.0,
AdjacentZoneIndices = new[] { 1 },
OwnerFactionIndex = 0,
Entities = new[]
{
new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 0,
Name = "Unity Smoke Ship",
Kind = "ship",
PositionX = 1.0,
PositionY = 2.0,
PositionZ = 3.0,
DirectionX = 0.0,
DirectionY = 1.0,
HullItemLegacyId = "smoke:hull",
Equipment = new[]
{
new AetheriaRuntimeLoadoutItemSlotCommit
{
X = 0,
Y = 0,
Item = new AetheriaRuntimeLoadoutItemCommit
{
ItemLegacyId = "smoke:weapon",
Quality = 0.9,
Durability = 0.8
}
}
},
WeaponGroups = new[] { new[] { 0 } }
}
}
}
}
});
var pending = AetheriaRuntimeStateCommitLog.ReadPending(commitSmokeStatePath);
if (pending.Count != 1 ||
if (pending.Count != 2 ||
pending[0].Kind != AetheriaRuntimeCommitKind.PlayerSettings ||
pending[0].Schema != AetheriaRuntimeStateCommitLog.CommitSchema ||
pending[0].Path != commit.Path)
@ -156,12 +207,20 @@ try
await using var commitNode = await AetheriaStateNode.OpenAsync(commitSmokeStatePath, "aetheria-unity-runtime-commit-smoke");
var report = await AetheriaRuntimeCommitLogApplier.ApplyPendingAsync(commitNode);
var settings = await commitNode.GetPlayerSettingsAsync();
var run = await commitNode.GetRunStateAsync(new("global:aetheria.run_state.smoke-run.v1"));
var zone = await commitNode.GetZoneStateAsync(new("global:aetheria.run_state.smoke-run.zone.0.v1"));
var entity = await commitNode.GetEntitySnapshotAsync(new("global:aetheria.run_state.smoke-run.zone.0.entity.0.v1"));
if (report.AppliedPlayerSettings != 1 ||
report.AppliedRunCheckpoints != 1 ||
settings?.PlayerName != "Unity smoke" ||
settings.Input.ActionBarInputs.Length != 1 ||
run?.ZoneKeys.Length != 1 ||
zone?.EntityKeys.Length != 1 ||
entity?.Equipment.Length != 1 ||
entity.WeaponGroups.Length != 1 ||
AetheriaRuntimeStateCommitLog.ReadPending(commitSmokeStatePath).Count != 0)
{
throw new InvalidOperationException("Runtime state commit log did not apply queued settings through the typed state node.");
throw new InvalidOperationException("Runtime state commit log did not apply queued settings/run snapshots through the typed state node.");
}
}
finally
@ -176,4 +235,4 @@ Console.WriteLine($"Interior/hardpoint sample: {interior.Name} {interior.Interio
Console.WriteLine($"Behavior payload sample: {behaviorHost.Name} {behaviorPayload.Kind}/{behaviorPayload.Fields.Count}");
Console.WriteLine($"Behavior sample: {behaviorKind}");
Console.WriteLine($"Eve surface: {surface.Surface.Id}");
Console.WriteLine("Runtime state commit log smoke: player settings command queued, applied, and cleared");
Console.WriteLine("Runtime state commit log smoke: settings and run zone/entity snapshots queued, applied, and cleared");

View file

@ -50,6 +50,31 @@ public static class AetheriaRuntimeCommitLogApplier
break;
case "run_checkpoint":
var run = command.RunState ?? throw MissingPayload(command);
var zoneKeys = new List<string>();
foreach (var zone in command.ZoneSnapshots)
{
var zoneKey = ZoneKey(run.RunId, zone.ZoneIndex);
var entityKeys = new List<string>();
foreach (var entity in zone.Entities)
{
var entityKey = EntityKey(run.RunId, zone.ZoneIndex, entity.EntityIndex);
entity.Snapshot.ChildEntityKeys = entity.Snapshot.ChildEntityKeys
.Select(key => key.StartsWith("pending:entity:", StringComparison.Ordinal)
&& int.TryParse(key["pending:entity:".Length..], out var childIndex)
? EntityKey(run.RunId, zone.ZoneIndex, childIndex).ToString()
: key)
.ToArray();
await node.PutEntitySnapshotAsync(entityKey, entity.Snapshot).ConfigureAwait(false);
entityKeys.Add(entityKey.ToString());
}
zone.State.EntityKeys = entityKeys.ToArray();
await node.PutZoneStateAsync(zoneKey, zone.State).ConfigureAwait(false);
zoneKeys.Add(zoneKey.ToString());
}
if (zoneKeys.Count > 0)
run.ZoneKeys = zoneKeys.ToArray();
await node.PutRunStateAsync(RunKey(run.RunId), run).ConfigureAwait(false);
report.AppliedRunCheckpoints++;
break;
@ -98,7 +123,8 @@ public static class AetheriaRuntimeCommitLogApplier
kind,
commandId,
createdAtUtc,
ReadRunState(ref reader, createdAtUtc),
ReadRunState(ref reader, createdAtUtc, out var zoneSnapshots),
zoneSnapshots,
path),
_ => throw new InvalidDataException($"Unknown Aetheria runtime commit kind '{kind}' in {path}.")
};
@ -184,10 +210,13 @@ public static class AetheriaRuntimeCommitLogApplier
};
}
private static AetheriaRunState ReadRunState(ref MessagePackReader reader, string updatedAtUtc)
private static AetheriaRunState ReadRunState(
ref MessagePackReader reader,
string updatedAtUtc,
out AetheriaPendingZoneSnapshot[] zoneSnapshots)
{
var fields = reader.ReadArrayHeader();
return new AetheriaRunState
var run = new AetheriaRunState
{
RunId = ReadFieldString(ref reader, fields, 0),
IsTutorial = ReadFieldBool(ref reader, fields, 1),
@ -198,6 +227,97 @@ public static class AetheriaRuntimeCommitLogApplier
DiscoveredZoneIndices = ReadIntArray(ref reader, fields, 6),
UpdatedAtUtc = updatedAtUtc
};
zoneSnapshots = ReadZoneSnapshots(ref reader, fields, 7);
return run;
}
private static AetheriaPendingZoneSnapshot[] ReadZoneSnapshots(ref MessagePackReader reader, int fields, int index)
{
if (index >= fields) return [];
var count = reader.ReadArrayHeader();
var zones = new AetheriaPendingZoneSnapshot[count];
for (var item = 0; item < count; item++)
{
var itemFields = reader.ReadArrayHeader();
var zoneIndex = ReadFieldInt32(ref reader, itemFields, 0);
var state = new AetheriaZoneState
{
Name = ReadFieldString(ref reader, itemFields, 1),
Position = new AetheriaVector2
{
X = ReadFieldDouble(ref reader, itemFields, 2),
Y = ReadFieldDouble(ref reader, itemFields, 3)
},
AdjacentZoneIndices = ReadIntArray(ref reader, itemFields, 4),
FactionIndices = ReadIntArray(ref reader, itemFields, 5),
OwnerFactionIndex = ReadFieldInt32(ref reader, itemFields, 6)
};
zones[item] = new AetheriaPendingZoneSnapshot(
zoneIndex,
state,
ReadEntitySnapshots(ref reader, itemFields, 7));
}
return zones;
}
private static AetheriaPendingEntitySnapshot[] ReadEntitySnapshots(ref MessagePackReader reader, int fields, int index)
{
if (index >= fields) return [];
var count = reader.ReadArrayHeader();
var entities = new AetheriaPendingEntitySnapshot[count];
for (var item = 0; item < count; item++)
{
var itemFields = reader.ReadArrayHeader();
var entityIndex = ReadFieldInt32(ref reader, itemFields, 0);
var snapshot = new AetheriaEntitySnapshot
{
Name = ReadFieldString(ref reader, itemFields, 1),
Kind = ReadFieldString(ref reader, itemFields, 2),
Position = new AetheriaVector3
{
X = ReadFieldDouble(ref reader, itemFields, 3),
Y = ReadFieldDouble(ref reader, itemFields, 4),
Z = ReadFieldDouble(ref reader, itemFields, 5)
},
Direction = new AetheriaVector2
{
X = ReadFieldDouble(ref reader, itemFields, 6),
Y = ReadFieldDouble(ref reader, itemFields, 7)
},
FactionKey = ReferenceKey("aetheria.corporation", ReadFieldString(ref reader, itemFields, 8)),
HullItemKey = ReferenceKey("aetheria.item_definition", ReadFieldString(ref reader, itemFields, 9)),
Equipment = ReadEntityItemSlots(ref reader, itemFields, 10),
CargoBays = ReadEntityItemSlots(ref reader, itemFields, 11),
DockingBays = ReadEntityItemSlots(ref reader, itemFields, 12),
ChildEntityKeys = ReadIntArray(ref reader, itemFields, 13)
.Select(childIndex => $"pending:entity:{childIndex}")
.ToArray(),
WeaponGroups = ReadWeaponGroups(ref reader, itemFields, 14)
};
entities[item] = new AetheriaPendingEntitySnapshot(entityIndex, snapshot);
}
return entities;
}
private static AetheriaEntityItemSlot[] ReadEntityItemSlots(ref MessagePackReader reader, int fields, int index)
{
var slots = ReadItemSlots(ref reader, fields, index);
return slots
.Select(slot => new AetheriaEntityItemSlot
{
Position = slot.Position,
ItemKey = slot.Item.ItemKey
})
.ToArray();
}
private static AetheriaWeaponGroupSnapshot[] ReadWeaponGroups(ref MessagePackReader reader, int fields, int index)
{
return ReadIntArrayArray(ref reader, fields, index)
.Select(group => new AetheriaWeaponGroupSnapshot { EquipmentIndices = group })
.ToArray();
}
private static AetheriaStoryFileHash[] ReadStoryFileHashes(ref MessagePackReader reader, int fields, int index)
@ -350,6 +470,16 @@ public static class AetheriaRuntimeCommitLogApplier
return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.v1");
}
private static CultRecordKey ZoneKey(string runId, int zoneIndex)
{
return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.zone.{zoneIndex}.v1");
}
private static CultRecordKey EntityKey(string runId, int zoneIndex, int entityIndex)
{
return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.zone.{zoneIndex}.entity.{entityIndex}.v1");
}
private static string ReferenceKey(string documentName, string legacyId)
{
return string.IsNullOrWhiteSpace(legacyId) ? "" : $"{documentName}:legacy:{legacyId}";
@ -380,7 +510,8 @@ public static class AetheriaRuntimeCommitLogApplier
string path,
AetheriaPlayerSettings? playerSettings,
AetheriaLoadoutTemplate? loadoutTemplate,
AetheriaRunState? runState)
AetheriaRunState? runState,
AetheriaPendingZoneSnapshot[] zoneSnapshots)
{
Kind = kind;
CommandId = commandId;
@ -389,11 +520,12 @@ public static class AetheriaRuntimeCommitLogApplier
PlayerSettings = playerSettings;
LoadoutTemplate = loadoutTemplate;
RunState = runState;
ZoneSnapshots = zoneSnapshots;
}
public static PendingCommand Empty(string kind, string commandId, string createdAtUtc, string path)
{
return new PendingCommand(kind, commandId, createdAtUtc, path, null, null, null);
return new PendingCommand(kind, commandId, createdAtUtc, path, null, null, null, []);
}
public static PendingCommand ForPlayerSettings(
@ -403,7 +535,7 @@ public static class AetheriaRuntimeCommitLogApplier
AetheriaPlayerSettings settings,
string path)
{
return new PendingCommand(kind, commandId, createdAtUtc, path, settings, null, null);
return new PendingCommand(kind, commandId, createdAtUtc, path, settings, null, null, []);
}
public static PendingCommand ForLoadoutTemplate(
@ -413,7 +545,7 @@ public static class AetheriaRuntimeCommitLogApplier
AetheriaLoadoutTemplate loadout,
string path)
{
return new PendingCommand(kind, commandId, createdAtUtc, path, null, loadout, null);
return new PendingCommand(kind, commandId, createdAtUtc, path, null, loadout, null, []);
}
public static PendingCommand ForRunState(
@ -421,9 +553,10 @@ public static class AetheriaRuntimeCommitLogApplier
string commandId,
string createdAtUtc,
AetheriaRunState run,
AetheriaPendingZoneSnapshot[] zoneSnapshots,
string path)
{
return new PendingCommand(kind, commandId, createdAtUtc, path, null, null, run);
return new PendingCommand(kind, commandId, createdAtUtc, path, null, null, run, zoneSnapshots);
}
public string Kind { get; }
@ -433,5 +566,35 @@ public static class AetheriaRuntimeCommitLogApplier
public AetheriaPlayerSettings? PlayerSettings { get; }
public AetheriaLoadoutTemplate? LoadoutTemplate { get; }
public AetheriaRunState? RunState { get; }
public AetheriaPendingZoneSnapshot[] ZoneSnapshots { get; }
}
private readonly struct AetheriaPendingZoneSnapshot
{
public AetheriaPendingZoneSnapshot(
int zoneIndex,
AetheriaZoneState state,
AetheriaPendingEntitySnapshot[] entities)
{
ZoneIndex = zoneIndex;
State = state;
Entities = entities;
}
public int ZoneIndex { get; }
public AetheriaZoneState State { get; }
public AetheriaPendingEntitySnapshot[] Entities { get; }
}
private readonly struct AetheriaPendingEntitySnapshot
{
public AetheriaPendingEntitySnapshot(int entityIndex, AetheriaEntitySnapshot snapshot)
{
EntityIndex = entityIndex;
Snapshot = snapshot;
}
public int EntityIndex { get; }
public AetheriaEntitySnapshot Snapshot { get; }
}
}

View file

@ -336,15 +336,86 @@ public class ActionGameManager : MonoBehaviour
.Select(ZoneIndex)
.Where(index => index >= 0)
.OrderBy(index => index)
.ToArray() ?? Array.Empty<int>()
.ToArray() ?? Array.Empty<int>(),
Zones = Zone == null ? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>() : new[] { ProjectZoneSnapshot(Zone) }
};
}
private AetheriaRuntimeZoneSnapshotCommit ProjectZoneSnapshot(Zone zone)
{
var galaxyZone = zone.GalaxyZone;
return new AetheriaRuntimeZoneSnapshotCommit
{
ZoneIndex = ZoneIndex(galaxyZone),
Name = galaxyZone?.Name ?? "",
PositionX = galaxyZone?.Position.x ?? 0,
PositionY = galaxyZone?.Position.y ?? 0,
AdjacentZoneIndices = galaxyZone?.AdjacentZones
.Select(ZoneIndex)
.Where(index => index >= 0)
.OrderBy(index => index)
.ToArray() ?? Array.Empty<int>(),
FactionIndices = galaxyZone?.Factions?
.Select(FactionIndex)
.Where(index => index >= 0)
.OrderBy(index => index)
.ToArray() ?? Array.Empty<int>(),
OwnerFactionIndex = FactionIndex(galaxyZone?.Owner),
Entities = zone.Entities
.Select((entity, index) => ProjectEntitySnapshot(zone, entity, index))
.ToArray()
};
}
private AetheriaRuntimeEntitySnapshotCommit ProjectEntitySnapshot(Zone zone, Entity entity, int entityIndex)
{
return new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = entityIndex,
Name = entity.Name ?? "",
Kind = entity is Ship ? "ship" : entity is OrbitalEntity ? "orbital" : "entity",
PositionX = entity.Position.x,
PositionY = entity.Position.y,
PositionZ = entity.Position.z,
DirectionX = entity.Direction.x,
DirectionY = entity.Direction.y,
FactionLegacyId = entity.Faction?.ID.ToString("D") ?? "",
HullItemLegacyId = entity.Hull?.Data?.LinkID.ToString("D") ?? "",
Equipment = ProjectEquippedSlots(entity.Equipment),
CargoBays = ProjectEquippedSlots(entity.CargoBays),
DockingBays = ProjectEquippedSlots(entity.DockingBays),
ChildEntityIndices = entity.Children?
.Select(child => zone.Entities.IndexOf(child))
.Where(index => index >= 0)
.ToArray() ?? Array.Empty<int>(),
WeaponGroups = entity.WeaponGroups?
.Select(group => (IReadOnlyList<int>)group.items.Select(item => entity.Equipment.IndexOf(item)).Where(index => index >= 0).ToArray())
.ToArray() ?? Array.Empty<IReadOnlyList<int>>()
};
}
private static AetheriaRuntimeLoadoutItemSlotCommit[] ProjectEquippedSlots(IEnumerable<EquippedItem> slots)
{
return slots?
.Select(slot => new AetheriaRuntimeLoadoutItemSlotCommit
{
X = slot.Position.x,
Y = slot.Position.y,
Item = ProjectLoadoutItem(slot.EquippableItem)
})
.ToArray() ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
}
private static int ZoneIndex(GalaxyZone zone)
{
return CurrentGalaxy?.Zones == null || zone == null ? -1 : Array.IndexOf(CurrentGalaxy.Zones, zone);
}
private static int FactionIndex(Faction faction)
{
return CurrentGalaxy?.Factions == null || faction == null ? -1 : Array.IndexOf(CurrentGalaxy.Factions, faction);
}
public void SaveLoadout(EntityPack pack)
{
Loadouts.RemoveAll(loadout => loadout.Name == pack.Name);

View file

@ -117,6 +117,38 @@ namespace GameCult.Aetheria.State.Unity
public int CurrentZoneIndex { get; set; } = -1;
public int CurrentZoneEntityIndex { get; set; } = -1;
public IReadOnlyList<int> DiscoveredZoneIndices { get; set; } = Array.Empty<int>();
public IReadOnlyList<AetheriaRuntimeZoneSnapshotCommit> Zones { get; set; } = Array.Empty<AetheriaRuntimeZoneSnapshotCommit>();
}
public sealed class AetheriaRuntimeZoneSnapshotCommit
{
public int ZoneIndex { get; set; } = -1;
public string Name { get; set; } = "";
public double PositionX { get; set; }
public double PositionY { get; set; }
public IReadOnlyList<int> AdjacentZoneIndices { get; set; } = Array.Empty<int>();
public IReadOnlyList<int> FactionIndices { get; set; } = Array.Empty<int>();
public int OwnerFactionIndex { get; set; } = -1;
public IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> Entities { get; set; } = Array.Empty<AetheriaRuntimeEntitySnapshotCommit>();
}
public sealed class AetheriaRuntimeEntitySnapshotCommit
{
public int EntityIndex { get; set; } = -1;
public string Name { get; set; } = "";
public string Kind { get; set; } = "";
public double PositionX { get; set; }
public double PositionY { get; set; }
public double PositionZ { get; set; }
public double DirectionX { get; set; }
public double DirectionY { get; set; }
public string FactionLegacyId { get; set; } = "";
public string HullItemLegacyId { get; set; } = "";
public IReadOnlyList<AetheriaRuntimeLoadoutItemSlotCommit> Equipment { get; set; } = Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
public IReadOnlyList<AetheriaRuntimeLoadoutItemSlotCommit> CargoBays { get; set; } = Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
public IReadOnlyList<AetheriaRuntimeLoadoutItemSlotCommit> DockingBays { get; set; } = Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
public IReadOnlyList<int> ChildEntityIndices { get; set; } = Array.Empty<int>();
public IReadOnlyList<IReadOnlyList<int>> WeaponGroups { get; set; } = Array.Empty<IReadOnlyList<int>>();
}
public static class AetheriaRuntimeStateCommitLog
@ -321,7 +353,7 @@ namespace GameCult.Aetheria.State.Unity
private static void WriteRunCheckpoint(ref MessagePackWriter writer, AetheriaRuntimeRunCheckpointCommit run)
{
writer.WriteArrayHeader(7);
writer.WriteArrayHeader(8);
writer.Write(run.RunId ?? "");
writer.Write(run.IsTutorial);
writer.Write(run.EntranceZoneIndex);
@ -329,6 +361,50 @@ namespace GameCult.Aetheria.State.Unity
writer.Write(run.CurrentZoneIndex);
writer.Write(run.CurrentZoneEntityIndex);
WriteInts(ref writer, run.DiscoveredZoneIndices);
WriteZoneSnapshots(ref writer, run.Zones);
}
private static void WriteZoneSnapshots(ref MessagePackWriter writer, IReadOnlyList<AetheriaRuntimeZoneSnapshotCommit>? zones)
{
zones ??= Array.Empty<AetheriaRuntimeZoneSnapshotCommit>();
writer.WriteArrayHeader(zones.Count);
foreach (var zone in zones)
{
writer.WriteArrayHeader(8);
writer.Write(zone.ZoneIndex);
writer.Write(zone.Name ?? "");
writer.Write(zone.PositionX);
writer.Write(zone.PositionY);
WriteInts(ref writer, zone.AdjacentZoneIndices);
WriteInts(ref writer, zone.FactionIndices);
writer.Write(zone.OwnerFactionIndex);
WriteEntitySnapshots(ref writer, zone.Entities);
}
}
private static void WriteEntitySnapshots(ref MessagePackWriter writer, IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit>? entities)
{
entities ??= Array.Empty<AetheriaRuntimeEntitySnapshotCommit>();
writer.WriteArrayHeader(entities.Count);
foreach (var entity in entities)
{
writer.WriteArrayHeader(15);
writer.Write(entity.EntityIndex);
writer.Write(entity.Name ?? "");
writer.Write(entity.Kind ?? "");
writer.Write(entity.PositionX);
writer.Write(entity.PositionY);
writer.Write(entity.PositionZ);
writer.Write(entity.DirectionX);
writer.Write(entity.DirectionY);
writer.Write(entity.FactionLegacyId ?? "");
writer.Write(entity.HullItemLegacyId ?? "");
WriteItemSlots(ref writer, entity.Equipment);
WriteItemSlots(ref writer, entity.CargoBays);
WriteItemSlots(ref writer, entity.DockingBays);
WriteInts(ref writer, entity.ChildEntityIndices);
WriteIntLists(ref writer, entity.WeaponGroups);
}
}
private static void WriteStrings(ref MessagePackWriter writer, IReadOnlyList<string>? values)

View file

@ -32,9 +32,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
from the `.cc` directory store before constructing the temporary legacy
`ItemManager`. Player settings saves, loadout saves, shutdown checkpoints,
and wormhole transition checkpoints now queue typed `.cc.pending` Verse
commit commands through the embedded runtime state package. `Aetheria.State`
owns applying those command envelopes into canonical `AetheriaPlayerSettings`,
`AetheriaLoadoutTemplate`, and `AetheriaRunState` documents through
commit commands through the embedded runtime state package. Run checkpoint
commands include the current zone plus its runtime entity snapshots.
`Aetheria.State` owns applying those command envelopes into canonical
`AetheriaPlayerSettings`, `AetheriaLoadoutTemplate`, `AetheriaRunState`,
`AetheriaZoneState`, and `AetheriaEntitySnapshot` documents through
`AetheriaStateNode`. Local run saves, loadouts, player settings files, zone
files, and generated keyboard layout caches no longer write bespoke durable files. The
legacy
@ -67,9 +69,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
state, and entity snapshots. The state smoke writes a run referencing a zone,
a zone referencing an entity snapshot, and an entity snapshot carrying
position, direction, faction, hull, equipment slots, weapon groups, and a stat
grid. This proves the `.zone` replacement graph is durable typed state; Unity
`ZonePack` and `EntityPack` are still runtime construction projections until
the runtime package is wired.
grid. This proves the `.zone` replacement graph is durable typed state. Unity
now queues current-zone/current-entity-collection snapshots through the
runtime commit log during run checkpoints; `ZonePack` and `EntityPack` remain
runtime construction/loadout projections rather than durable file formats.
- `Aetheria.State` now defines `AetheriaLoadoutTemplate` as the typed Verse
replacement for bespoke `.loadout` files. It stores structured hull,
equipment, cargo bay, docking bay, child-entity, assignment, and weapon-group
@ -157,7 +160,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
loadout-template, and run-checkpoint command envelopes under
`aetheria-world.cc.pending`. This log is command-only: it cannot decide
canonical state, and the `Aetheria.State` node applicator deletes applied
commands after writing typed documents.
commands after writing typed documents. Run checkpoint envelopes now carry
current-zone and entity snapshots so the old `.zone` file path has a live
typed runtime projection path.
- `AetheriaCatalogSurfaceProjector` now emits the first provider-owned Eve
surface from typed catalog state. The importer materializes a
`gamecult.eve.surface.v1` catalog operator document at
@ -298,11 +303,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
local save and editor catalog write paths have been cut. Migrated catalog documents are
addressed through `AetheriaCatalogKeys` and `AetheriaStateNode` legacy-ID
methods, not importer-local string concatenation. Player settings, loadout
templates, and lightweight run checkpoints have a shared Unity commit-log
primitive plus a typed state node applicator. Run, zone, and entity snapshot
state also have typed node put/get paths; full zone/entity runtime snapshot
projection still needs to move through the same command/apply spine. The old
`SaveState`/`SaveZone` command surface is gone.
templates, and run checkpoints have a shared Unity commit-log primitive plus
a typed state node applicator. Run checkpoints now carry the current zone and
its entity snapshots through that same command/apply spine. Whole-galaxy
unloaded-zone projection still needs to be added once lazy generation has a
typed state owner. The old `SaveState`/`SaveZone` command surface is gone.
The catalog operator UI is emitted as a typed Eve surface document derived
from the catalog snapshot.
- Derived state: UI panels, generated keyboard layouts, editor rows, typed
@ -513,6 +518,9 @@ First Aetheria surfaces to publish:
- Done: replace warning-only player settings, loadout, shutdown, and
wormhole save paths with typed `.cc.pending` Verse commit commands and a
state-node applicator that writes canonical typed documents.
- Done: extend run checkpoint commands to carry current-zone and entity
snapshots into canonical `AetheriaZoneState` and `AetheriaEntitySnapshot`
documents.
- Done: stop legacy catalog pull/read paths from writing entries back to
their source backing store.
- Done: delete legacy catalog backing-store write/realtime APIs.
@ -551,9 +559,9 @@ First Aetheria surfaces to publish:
package-owned runtime catalog read models without a MessagePack catalog cache,
including typed item masks, interior masks, hardpoint definitions,
corporation/name-file links, and typed behavior payloads. It also proves the
Unity runtime state commit log can queue a player-settings command, the
`Aetheria.State` node can apply it into canonical typed state, and the
command is cleared after application.
Unity runtime state commit log can queue player-settings and run snapshot
commands, the `Aetheria.State` node can apply them into canonical typed
settings/run/zone/entity state, and commands are cleared after application.
- Unity play smoke proves runtime UI reads from Eve surfaces and sends commands
through the shared state service.
- UI Toolkit lowering parity compares Aetheria surfaces against the Eve browser