Open typed catalog from Unity state package
This commit is contained in:
parent
57985666d1
commit
12dcd0c39f
7 changed files with 490 additions and 18 deletions
Binary file not shown.
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeCatalogSnapshot.cs" Link="AetheriaRuntimeCatalogSnapshot.cs" />
|
||||
<Compile Include="..\Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeCatalogStore.cs" Link="AetheriaRuntimeCatalogStore.cs" />
|
||||
<ProjectReference Include="..\Aetheria.State\Aetheria.State.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<AetheriaRuntimeCatalogItem>();
|
||||
var corporations = new List<AetheriaRuntimeCorporation>();
|
||||
var nameFiles = new List<AetheriaRuntimeNameFile>();
|
||||
|
||||
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<string, string> 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<string, string>(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<PersistedRecord> ReadRecords(string stateFilePath)
|
||||
{
|
||||
var records = new List<PersistedRecord>();
|
||||
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<byte>();
|
||||
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<string> ReadFieldStringArray(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<string>();
|
||||
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<AetheriaRuntimeShapeCell> ReadFieldShapeCells(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
return index >= fields ? Array.Empty<AetheriaRuntimeShapeCell>() : ReadShapeCells(ref reader);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AetheriaRuntimeShapeCell> 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<AetheriaRuntimeHardpoint> ReadFieldHardpoints(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<AetheriaRuntimeHardpoint>();
|
||||
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<AetheriaRuntimeBehaviorPayload> ReadFieldBehaviorPayloads(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<AetheriaRuntimeBehaviorPayload>();
|
||||
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<AetheriaRuntimeBehaviorField> ReadFieldBehaviorFields(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<AetheriaRuntimeBehaviorField>();
|
||||
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<AetheriaRuntimeBehaviorValue> ReadFieldBehaviorValues(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<AetheriaRuntimeBehaviorValue>();
|
||||
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<AetheriaRuntimeBehaviorMapEntry> ReadFieldBehaviorMapEntries(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<AetheriaRuntimeBehaviorMapEntry>();
|
||||
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<AetheriaRuntimeBehaviorValue>(),
|
||||
Array.Empty<AetheriaRuntimeBehaviorMapEntry>());
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"name": "GameCult.Aetheria.State.Unity",
|
||||
"rootNamespace": "GameCult.Aetheria.State.Unity",
|
||||
"references": [],
|
||||
"references": [
|
||||
"MessagePack"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue