diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc
index a1d39d0e2..eff5665a0 100644
Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ
diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs
index c52ad34bb..fb3fbe761 100644
--- a/Aetheria.State.Unity.Smoke/Program.cs
+++ b/Aetheria.State.Unity.Smoke/Program.cs
@@ -9,6 +9,7 @@ var statePath = args.Length > 1
await using var client = await AetheriaRuntimeCatalogClient.OpenAsync(statePath);
var catalog = client.ReadCatalog();
+var packageCatalog = AetheriaRuntimeCatalogStore.OpenReadOnly(statePath);
var surface = await client.ReadCatalogSurfaceAsync();
if (catalog.Items.Count != 115)
@@ -16,6 +17,15 @@ if (catalog.Items.Count != 115)
throw new InvalidOperationException($"Expected 115 runtime catalog items, found {catalog.Items.Count}.");
}
+if (packageCatalog.Items.Count != catalog.Items.Count ||
+ packageCatalog.Corporations.Count != catalog.Corporations.Count ||
+ packageCatalog.NameFiles.Count != catalog.NameFiles.Count)
+{
+ throw new InvalidOperationException(
+ $"Package catalog store mismatch: {packageCatalog.Items.Count}/{packageCatalog.Corporations.Count}/{packageCatalog.NameFiles.Count} " +
+ $"!= {catalog.Items.Count}/{catalog.Corporations.Count}/{catalog.NameFiles.Count}.");
+}
+
if (catalog.TradeItems.Count == 0)
{
throw new InvalidOperationException("Runtime catalog has no trade items.");
@@ -28,6 +38,14 @@ if (catalog.EquipmentItems.Count == 0)
var shaped = catalog.Items.FirstOrDefault(item => item.ShapeCells.Count > 0)
?? throw new InvalidOperationException("Runtime catalog has no typed shape masks.");
+var packageShaped = packageCatalog.FindItemByLegacyId(shaped.LegacyId);
+if (packageShaped == null ||
+ packageShaped.Name != shaped.Name ||
+ packageShaped.ShapeCells.Count != shaped.ShapeCells.Count)
+{
+ throw new InvalidOperationException($"Package catalog store did not read the expected typed item payload for {shaped.Name}.");
+}
+
if (shaped.ShapeCells.Count != shaped.OccupiedCells)
{
throw new InvalidOperationException(
@@ -90,6 +108,12 @@ if (catalog.GetManufacturer(manufactured) == null)
throw new InvalidOperationException($"Runtime catalog manufacturer lookup failed for {manufactured.Name}.");
}
+var packageCorporation = packageCatalog.Corporations.FirstOrDefault(corporation => !string.IsNullOrWhiteSpace(corporation.GeonameFileLegacyId));
+if (packageCorporation == null || packageCatalog.GetNameFile(packageCorporation) == null)
+{
+ throw new InvalidOperationException("Package catalog store did not read corporation/name-file links.");
+}
+
if (surface?.Schema != "gamecult.eve.surface.v1" ||
surface.Surface.Id != AetheriaCatalogSurfaceProjector.SurfaceId)
{
diff --git a/Aetheria.State.Unity/Aetheria.State.Unity.csproj b/Aetheria.State.Unity/Aetheria.State.Unity.csproj
index fcf138bea..20570ddc7 100644
--- a/Aetheria.State.Unity/Aetheria.State.Unity.csproj
+++ b/Aetheria.State.Unity/Aetheria.State.Unity.csproj
@@ -9,6 +9,7 @@
+
diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs
index 5fb7824ad..39868a2a5 100644
--- a/Assets/Scripts/Gameplay/ActionGameManager.cs
+++ b/Assets/Scripts/Gameplay/ActionGameManager.cs
@@ -67,6 +67,7 @@ public class ActionGameManager : MonoBehaviour
public static Galaxy CurrentGalaxy;
public static bool IsTutorial;
+ public static AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; private set; }
public GameSettings Settings;
//public string StarterShipTemplate = "Longinus";
@@ -235,7 +236,15 @@ public class ActionGameManager : MonoBehaviour
var stateBoot = AetheriaRuntimeStateBoot.Inspect(GameDataDirectory);
Debug.Log($"Aetheria typed state file: {stateBoot.StateFilePath}");
if (!stateBoot.StateFileExists)
+ {
Debug.LogWarning("Aetheria typed state file is missing. Run the Aetheria.State importer before treating legacy catalog data as runtime state.");
+ }
+ else
+ {
+ RuntimeCatalog = AetheriaRuntimeCatalogStore.OpenReadOnly(stateBoot.StateFilePath);
+ Debug.Log($"Aetheria typed runtime catalog: {RuntimeCatalog.Items.Count} items, {RuntimeCatalog.Corporations.Count} corporations, {RuntimeCatalog.NameFiles.Count} name files");
+ }
+
Debug.Log($"Aetheria legacy item catalog: {LegacyAetherDatabasePath}");
ItemManager = new ItemManager(
LegacyCatalog,
diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs
new file mode 100644
index 000000000..99830826e
--- /dev/null
+++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs
@@ -0,0 +1,428 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MessagePack;
+
+#nullable enable
+
+namespace GameCult.Aetheria.State.Unity
+{
+ public static class AetheriaRuntimeCatalogStore
+ {
+ private const string ItemDefinitionSchema = "aetheria.item_definition";
+ private const string CorporationSchema = "aetheria.corporation";
+ private const string NameFileSchema = "aetheria.name_file";
+
+ public static AetheriaRuntimeCatalogSnapshot OpenReadOnly(string stateFilePath)
+ {
+ if (string.IsNullOrWhiteSpace(stateFilePath))
+ throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath));
+
+ var catalog = ReadSchemaCatalog(stateFilePath);
+ var items = new List();
+ var corporations = new List();
+ var nameFiles = new List();
+
+ foreach (var record in ReadRecords(stateFilePath))
+ {
+ if (!catalog.TryGetValue(record.SchemaId, out var schemaName))
+ continue;
+
+ if (schemaName == ItemDefinitionSchema)
+ items.Add(ReadItem(record.Payload));
+ else if (schemaName == CorporationSchema)
+ corporations.Add(ReadCorporation(record.Payload));
+ else if (schemaName == NameFileSchema)
+ nameFiles.Add(ReadNameFile(record.Payload));
+ }
+
+ return new AetheriaRuntimeCatalogSnapshot(
+ items.ToArray(),
+ corporations.ToArray(),
+ nameFiles.ToArray());
+ }
+
+ private static Dictionary ReadSchemaCatalog(string stateFilePath)
+ {
+ if (!File.Exists(stateFilePath))
+ throw new FileNotFoundException("Aetheria typed state file was not found.", stateFilePath);
+
+ var reader = new MessagePackReader(File.ReadAllBytes(stateFilePath));
+ var snapshotFields = reader.ReadArrayHeader();
+ if (snapshotFields < 2)
+ throw new InvalidDataException("CultCache snapshot is missing its embedded schema catalog.");
+
+ reader.Skip();
+ var schemaCount = reader.ReadArrayHeader();
+ var catalog = new Dictionary(StringComparer.Ordinal);
+ for (var index = 0; index < schemaCount; index++)
+ {
+ var fieldCount = reader.ReadArrayHeader();
+ var schemaId = fieldCount > 0 ? ReadString(ref reader) : "";
+ var schemaName = fieldCount > 1 ? ReadString(ref reader) : "";
+ for (var field = 2; field < fieldCount; field++)
+ reader.Skip();
+
+ if (!string.IsNullOrWhiteSpace(schemaId) && !string.IsNullOrWhiteSpace(schemaName))
+ catalog[schemaId] = schemaName;
+ }
+
+ return catalog;
+ }
+
+ private static IReadOnlyList ReadRecords(string stateFilePath)
+ {
+ var records = new List();
+ var reader = new MessagePackReader(File.ReadAllBytes(stateFilePath));
+ var snapshotFields = reader.ReadArrayHeader();
+ if (snapshotFields > 0) reader.Skip();
+ if (snapshotFields > 1) reader.Skip();
+ if (snapshotFields > 2)
+ {
+ var recordCount = reader.ReadArrayHeader();
+ for (var index = 0; index < recordCount; index++)
+ records.Add(ReadPersistedRecord(ref reader));
+ }
+
+ var recordDirectory = stateFilePath + ".records";
+ if (!Directory.Exists(recordDirectory))
+ return records;
+
+ foreach (var recordFile in Directory.EnumerateFiles(recordDirectory, "*.msgpack").OrderBy(path => path, StringComparer.Ordinal))
+ {
+ var recordReader = new MessagePackReader(File.ReadAllBytes(recordFile));
+ records.Add(ReadPersistedRecord(ref recordReader));
+ }
+
+ return records;
+ }
+
+ private static PersistedRecord ReadPersistedRecord(ref MessagePackReader reader)
+ {
+ var fieldCount = reader.ReadArrayHeader();
+ if (fieldCount > 0) reader.Skip();
+ var schemaId = fieldCount > 1 ? ReadString(ref reader) : "";
+ if (fieldCount > 2) reader.Skip();
+ var payload = Array.Empty();
+ if (fieldCount > 3)
+ {
+ var sequence = reader.ReadBytes();
+ if (sequence.HasValue)
+ payload = sequence.Value.ToArray();
+ }
+ for (var field = 4; field < fieldCount; field++)
+ reader.Skip();
+ return new PersistedRecord(schemaId, payload);
+ }
+
+ private static AetheriaRuntimeCatalogItem ReadItem(byte[] payload)
+ {
+ var reader = new MessagePackReader(payload);
+ var fields = reader.ReadArrayHeader();
+ var name = ReadFieldString(ref reader, fields, 0);
+ var category = ReadFieldString(ref reader, fields, 1);
+ var legacyId = ReadFieldString(ref reader, fields, 2);
+ var description = ReadFieldString(ref reader, fields, 3);
+ var mass = ReadFieldDouble(ref reader, fields, 4);
+ var volume = ReadFieldDouble(ref reader, fields, 5);
+ SkipField(ref reader, fields, 6);
+ var manufacturerLegacyId = ReadFieldString(ref reader, fields, 7);
+ var price = ReadFieldInt32(ref reader, fields, 8);
+ var shapeWidth = ReadFieldInt32(ref reader, fields, 9);
+ var shapeHeight = ReadFieldInt32(ref reader, fields, 10);
+ var occupiedCells = ReadFieldInt32(ref reader, fields, 11);
+ var hardpointType = ReadFieldString(ref reader, fields, 12);
+ var hullType = ReadFieldString(ref reader, fields, 13);
+ var behaviorKinds = ReadFieldStringArray(ref reader, fields, 14);
+ SkipField(ref reader, fields, 15);
+ var maxStack = ReadFieldInt32(ref reader, fields, 16);
+ SkipField(ref reader, fields, 17);
+ SkipField(ref reader, fields, 18);
+ var durability = ReadFieldDouble(ref reader, fields, 19);
+ var weaponRange = ReadFieldString(ref reader, fields, 20);
+ var weaponCaliber = ReadFieldString(ref reader, fields, 21);
+ var weaponType = ReadFieldString(ref reader, fields, 22);
+ var weaponFireTypes = ReadFieldString(ref reader, fields, 23);
+ var weaponModifiers = ReadFieldString(ref reader, fields, 24);
+ var shapeCells = ReadFieldShapeCells(ref reader, fields, 25);
+ var interiorShapeWidth = ReadFieldInt32(ref reader, fields, 26);
+ var interiorShapeHeight = ReadFieldInt32(ref reader, fields, 27);
+ var interiorOccupiedCells = ReadFieldInt32(ref reader, fields, 28);
+ var interiorShapeCells = ReadFieldShapeCells(ref reader, fields, 29);
+ var hardpoints = ReadFieldHardpoints(ref reader, fields, 30);
+ var behaviorPayloads = ReadFieldBehaviorPayloads(ref reader, fields, 31);
+ SkipRemaining(ref reader, fields, 32);
+
+ return new AetheriaRuntimeCatalogItem(
+ legacyId,
+ name,
+ category,
+ description,
+ manufacturerLegacyId,
+ price,
+ mass,
+ volume,
+ shapeWidth,
+ shapeHeight,
+ occupiedCells,
+ shapeCells,
+ interiorShapeWidth,
+ interiorShapeHeight,
+ interiorOccupiedCells,
+ interiorShapeCells,
+ hardpoints,
+ behaviorPayloads,
+ hardpointType,
+ hullType,
+ behaviorKinds,
+ maxStack,
+ durability,
+ weaponRange,
+ weaponCaliber,
+ weaponType,
+ weaponFireTypes,
+ weaponModifiers);
+ }
+
+ private static AetheriaRuntimeCorporation ReadCorporation(byte[] payload)
+ {
+ var reader = new MessagePackReader(payload);
+ var fields = reader.ReadArrayHeader();
+ var name = ReadFieldString(ref reader, fields, 0);
+ var legacyId = ReadFieldString(ref reader, fields, 1);
+ var shortName = ReadFieldString(ref reader, fields, 2);
+ var description = ReadFieldString(ref reader, fields, 3);
+ var geonameFileLegacyId = ReadFieldString(ref reader, fields, 4);
+ var bossHullLegacyId = ReadFieldString(ref reader, fields, 5);
+ var influenceDistance = ReadFieldInt32(ref reader, fields, 6);
+ var allegianceCount = ReadFieldInt32(ref reader, fields, 7);
+ SkipRemaining(ref reader, fields, 8);
+ return new AetheriaRuntimeCorporation(
+ legacyId,
+ name,
+ shortName,
+ description,
+ geonameFileLegacyId,
+ bossHullLegacyId,
+ influenceDistance,
+ allegianceCount);
+ }
+
+ private static AetheriaRuntimeNameFile ReadNameFile(byte[] payload)
+ {
+ var reader = new MessagePackReader(payload);
+ var fields = reader.ReadArrayHeader();
+ var name = ReadFieldString(ref reader, fields, 0);
+ var legacyId = ReadFieldString(ref reader, fields, 1);
+ var nameCount = ReadFieldInt32(ref reader, fields, 2);
+ var sampleNames = ReadFieldStringArray(ref reader, fields, 3);
+ SkipRemaining(ref reader, fields, 4);
+ return new AetheriaRuntimeNameFile(legacyId, name, nameCount, sampleNames);
+ }
+
+ private static string ReadFieldString(ref MessagePackReader reader, int fields, int index)
+ {
+ return index >= fields ? "" : ReadString(ref reader);
+ }
+
+ private static int ReadFieldInt32(ref MessagePackReader reader, int fields, int index)
+ {
+ return index >= fields ? 0 : reader.ReadInt32();
+ }
+
+ private static double ReadFieldDouble(ref MessagePackReader reader, int fields, int index)
+ {
+ return index >= fields ? 0 : reader.ReadDouble();
+ }
+
+ private static IReadOnlyList ReadFieldStringArray(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields) return Array.Empty();
+ var count = reader.ReadArrayHeader();
+ var values = new string[count];
+ for (var item = 0; item < count; item++)
+ values[item] = ReadString(ref reader);
+ return values;
+ }
+
+ private static IReadOnlyList ReadFieldShapeCells(ref MessagePackReader reader, int fields, int index)
+ {
+ return index >= fields ? Array.Empty() : ReadShapeCells(ref reader);
+ }
+
+ private static IReadOnlyList ReadShapeCells(ref MessagePackReader reader)
+ {
+ var count = reader.ReadArrayHeader();
+ var cells = new AetheriaRuntimeShapeCell[count];
+ for (var cell = 0; cell < count; cell++)
+ {
+ var fields = reader.ReadArrayHeader();
+ var x = ReadFieldInt32(ref reader, fields, 0);
+ var y = ReadFieldInt32(ref reader, fields, 1);
+ SkipRemaining(ref reader, fields, 2);
+ cells[cell] = new AetheriaRuntimeShapeCell(x, y);
+ }
+
+ return cells;
+ }
+
+ private static IReadOnlyList ReadFieldHardpoints(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields) return Array.Empty();
+ var count = reader.ReadArrayHeader();
+ var hardpoints = new AetheriaRuntimeHardpoint[count];
+ for (var hardpoint = 0; hardpoint < count; hardpoint++)
+ {
+ var hardpointFields = reader.ReadArrayHeader();
+ var type = ReadFieldString(ref reader, hardpointFields, 0);
+ var positionX = ReadFieldInt32(ref reader, hardpointFields, 1);
+ var positionY = ReadFieldInt32(ref reader, hardpointFields, 2);
+ var shapeWidth = ReadFieldInt32(ref reader, hardpointFields, 3);
+ var shapeHeight = ReadFieldInt32(ref reader, hardpointFields, 4);
+ var occupiedCells = ReadFieldInt32(ref reader, hardpointFields, 5);
+ var shapeCells = ReadFieldShapeCells(ref reader, hardpointFields, 6);
+ var transform = ReadFieldString(ref reader, hardpointFields, 7);
+ var rotation = ReadFieldString(ref reader, hardpointFields, 8);
+ var armor = ReadFieldDouble(ref reader, hardpointFields, 9);
+ SkipRemaining(ref reader, hardpointFields, 10);
+ hardpoints[hardpoint] = new AetheriaRuntimeHardpoint(
+ type,
+ positionX,
+ positionY,
+ shapeWidth,
+ shapeHeight,
+ occupiedCells,
+ shapeCells,
+ transform,
+ rotation,
+ armor);
+ }
+
+ return hardpoints;
+ }
+
+ private static IReadOnlyList ReadFieldBehaviorPayloads(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields) return Array.Empty();
+ var count = reader.ReadArrayHeader();
+ var payloads = new AetheriaRuntimeBehaviorPayload[count];
+ for (var payload = 0; payload < count; payload++)
+ {
+ var payloadFields = reader.ReadArrayHeader();
+ var unionKey = ReadFieldInt32(ref reader, payloadFields, 0);
+ var kind = ReadFieldString(ref reader, payloadFields, 1);
+ var group = ReadFieldInt32(ref reader, payloadFields, 2);
+ var fieldsValue = ReadFieldBehaviorFields(ref reader, payloadFields, 3);
+ SkipRemaining(ref reader, payloadFields, 4);
+ payloads[payload] = new AetheriaRuntimeBehaviorPayload(unionKey, kind, group, fieldsValue);
+ }
+
+ return payloads;
+ }
+
+ private static IReadOnlyList ReadFieldBehaviorFields(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields) return Array.Empty();
+ var count = reader.ReadArrayHeader();
+ var values = new AetheriaRuntimeBehaviorField[count];
+ for (var field = 0; field < count; field++)
+ {
+ var fieldCount = reader.ReadArrayHeader();
+ var key = ReadFieldInt32(ref reader, fieldCount, 0);
+ var value = ReadFieldBehaviorValue(ref reader, fieldCount, 1);
+ SkipRemaining(ref reader, fieldCount, 2);
+ values[field] = new AetheriaRuntimeBehaviorField(key, value);
+ }
+
+ return values;
+ }
+
+ private static AetheriaRuntimeBehaviorValue ReadFieldBehaviorValue(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields)
+ return EmptyBehaviorValue();
+
+ var valueFields = reader.ReadArrayHeader();
+ var kind = ReadFieldString(ref reader, valueFields, 0);
+ var stringValue = ReadFieldString(ref reader, valueFields, 1);
+ var numberValue = ReadFieldDouble(ref reader, valueFields, 2);
+ var boolValue = valueFields > 3 && reader.ReadBoolean();
+ var legacyIdValue = ReadFieldString(ref reader, valueFields, 4);
+ var children = ReadFieldBehaviorValues(ref reader, valueFields, 5);
+ var mapEntries = ReadFieldBehaviorMapEntries(ref reader, valueFields, 6);
+ SkipRemaining(ref reader, valueFields, 7);
+ return new AetheriaRuntimeBehaviorValue(kind, stringValue, numberValue, boolValue, legacyIdValue, children, mapEntries);
+ }
+
+ private static IReadOnlyList ReadFieldBehaviorValues(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields) return Array.Empty();
+ var count = reader.ReadArrayHeader();
+ var values = new AetheriaRuntimeBehaviorValue[count];
+ for (var value = 0; value < count; value++)
+ values[value] = ReadFieldBehaviorValue(ref reader, 1, 0);
+ return values;
+ }
+
+ private static IReadOnlyList ReadFieldBehaviorMapEntries(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index >= fields) return Array.Empty();
+ var count = reader.ReadArrayHeader();
+ var entries = new AetheriaRuntimeBehaviorMapEntry[count];
+ for (var entry = 0; entry < count; entry++)
+ {
+ var entryFields = reader.ReadArrayHeader();
+ var key = ReadFieldString(ref reader, entryFields, 0);
+ var value = ReadFieldBehaviorValue(ref reader, entryFields, 1);
+ SkipRemaining(ref reader, entryFields, 2);
+ entries[entry] = new AetheriaRuntimeBehaviorMapEntry(key, value);
+ }
+
+ return entries;
+ }
+
+ private static AetheriaRuntimeBehaviorValue EmptyBehaviorValue()
+ {
+ return new AetheriaRuntimeBehaviorValue(
+ "nil",
+ "",
+ 0,
+ false,
+ "",
+ Array.Empty(),
+ Array.Empty());
+ }
+
+ private static string ReadString(ref MessagePackReader reader)
+ {
+ return reader.ReadString() ?? "";
+ }
+
+ private static void SkipField(ref MessagePackReader reader, int fields, int index)
+ {
+ if (index < fields)
+ reader.Skip();
+ }
+
+ private static void SkipRemaining(ref MessagePackReader reader, int fields, int firstUnhandledIndex)
+ {
+ for (var field = firstUnhandledIndex; field < fields; field++)
+ reader.Skip();
+ }
+
+ private readonly struct PersistedRecord
+ {
+ public PersistedRecord(string schemaId, byte[] payload)
+ {
+ SchemaId = schemaId;
+ Payload = payload;
+ }
+
+ public string SchemaId { get; }
+
+ public byte[] Payload { get; }
+ }
+ }
+}
diff --git a/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef b/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef
index 8d4d3aa6f..efe1f5133 100644
--- a/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef
+++ b/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef
@@ -1,7 +1,9 @@
{
"name": "GameCult.Aetheria.State.Unity",
"rootNamespace": "GameCult.Aetheria.State.Unity",
- "references": [],
+ "references": [
+ "MessagePack"
+ ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md
index eed66dd02..07e7adfeb 100644
--- a/docs/aetheria-perfect-machine-map.md
+++ b/docs/aetheria-perfect-machine-map.md
@@ -27,10 +27,13 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
the Unity boot-time state-file existence probe through
`AetheriaRuntimeStateBoot`. `ActionGameManager` consumes that package-owned
boot report and warns when `aetheria-world.cc` is missing before treating
- legacy catalog data as runtime state. `LegacyCatalogBoundary` no longer names
- or owns typed state paths. Local run saves, loadouts, player settings files,
- zone files, and generated keyboard layout caches no longer write bespoke
- durable files. The legacy
+ legacy catalog data as runtime state. When the typed state file exists,
+ `ActionGameManager` opens a package-owned read-only runtime catalog snapshot
+ from the `.cc` directory store before constructing the temporary legacy
+ `ItemManager`. `LegacyCatalogBoundary` no longer names or owns typed state
+ paths. Local run saves, loadouts, player settings files, zone files, and
+ generated keyboard layout caches no longer write bespoke durable files. The
+ legacy
`PlayerSettings` runtime object is no longer decorated as a MessagePack
persistence shape.
- Shared domain state is still partly built around `DatabaseEntry`, GUID
@@ -121,11 +124,13 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
Unity-visible immutable catalog read-model contract for
trade/equipment/behavior/hardpoint/manufacturer/corporation/name queries,
typed item shape masks, typed interior masks, hardpoints, and behavior
- payloads. The SDK-style `Aetheria.State.Unity` facade includes that package
- source and owns only the `.cc` opener plus mapper from `Aetheria.State`
- documents into the package contract. It reads the published Eve surface
- without deserializing legacy `DatabaseEntry` objects. It is not a simulation
- owner and does not write state.
+ payloads. It also owns a read-only Unity-compatible CultCache directory-store
+ opener for the known Aetheria catalog schemas, so Unity runtime code can read
+ `aetheria-world.cc` catalog records without `DatabaseEntry` or
+ `LegacyCatalogCache`. The SDK-style `Aetheria.State.Unity` facade includes
+ that package source and owns the full `Aetheria.State` mapper plus Eve surface
+ read path for .NET smokes. Neither is a simulation owner and neither writes
+ state.
- `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
@@ -214,10 +219,12 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
`ILegacyCatalogReader` until typed runtime catalog boot replaces it; `Galaxy`
has been moved off the legacy reader and now consumes `ItemManager`.
The embedded Unity state package owns the read-only runtime catalog model
- contract for future Unity consumers; the SDK-style `Aetheria.State.Unity`
- facade maps typed `.cc` documents into that contract for current .NET smokes.
- Neither writes state or owns simulation. The legacy cache no longer has a
- public mutation surface.
+ contract and the read-only known-schema `.cc` catalog opener. `ActionGameManager`
+ now exposes that typed package snapshot at boot. The SDK-style
+ `Aetheria.State.Unity` facade maps typed `.cc` documents into the same
+ contract for full .NET smokes and Eve surface reads. Neither writes state or
+ owns simulation. The legacy cache no longer has a public mutation surface, but
+ it still materializes `ItemManager`.
- Inputs: Unity gameplay code, legacy catalog files, typed state documents, and
CultMesh server state.
- Outputs: `Aetheria.State` emits `.cc` state and CultMesh documents. Legacy
@@ -425,10 +432,11 @@ First Aetheria surfaces to publish:
- Migration smoke proves stable old catalog payload fields can be read once and
converted without old readers remaining on the live runtime path.
- Unity runtime catalog smoke proves read-only catalog consumers can open the
- typed `.cc` store through the SDK-style `Aetheria.State.Unity` opener and
- receive package-owned runtime catalog read models without `DatabaseEntry` or
- `LegacyCatalogCache`, including typed item masks, interior masks, and
- hardpoint definitions, plus typed behavior payloads.
+ typed `.cc` store through both the SDK-style `Aetheria.State.Unity` opener and
+ the embedded package-owned read-only catalog opener, receiving the same
+ package-owned runtime catalog read models without `DatabaseEntry` or
+ `LegacyCatalogCache`, including typed item masks, interior masks, hardpoint
+ definitions, corporation/name-file links, and typed behavior payloads.
- 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