From 949907de65882ff3224943acfe045ee54cedf2bf Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 13 Jun 2026 14:36:54 +0100 Subject: [PATCH] Use typed runtime item catalog --- .voidbot/state/aetheria.cc | Bin 32017 -> 32508 bytes Assets/Scripts/Gameplay/ActionGameManager.cs | 12 +- .../Scripts/Gameplay/LegacyCatalogBoundary.cs | 22 -- .../Gameplay/LegacyCatalogBoundary.cs.meta | 11 - .../AetheriaRuntimeItemCatalog.cs | 338 ++++++++++++++++++ .../AetheriaRuntimeItemCatalog.cs.meta | 3 + .../ServerShared/CultCache/DatabaseEntry.cs | 10 +- .../CultCache/LegacyCatalogCache.cs | 114 ------ .../CultCache/LegacyCatalogCache.cs.meta | 11 - Assets/Scripts/ServerShared/ItemManager.cs | 4 +- docs/aetheria-perfect-machine-map.md | 135 +++---- 11 files changed, 415 insertions(+), 245 deletions(-) delete mode 100644 Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs delete mode 100644 Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs.meta create mode 100644 Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs create mode 100644 Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs.meta delete mode 100644 Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs delete mode 100644 Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs.meta diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index f7dcc977330216bd7843f4801ea512a80b92be8a..b2e898444b5d9e43283367febdcd7b9ac017f11c 100644 GIT binary patch delta 1711 zcmaJ?OK%)S5M~_{90(7gSc0HHf$SKFce0TmY+@8)d-JdyKNQ>|# zu~{EjMM$|IB%(Rw2OvNo4n+F{5C^WnElPwUaplArfkbuB%wn*@WoEjktLm$-zN)_Y zN9E7ID&KxF@aV+Ux!SZ>ubthxSsBiL82Gr{IWz5cOFr5Sgm_-w!;tW(_QxLGN=vVm!WA zd1KbMJ}$8smzZE}RM1R$6zIN#BIts7665*8hl`V4pMAayOa##6f(#n;v&l4G;bMD^ z-e4ploq;RZW`1XZFc;=?G8F++o$-Ue=9wSjxN}PI``DLIYr?k5{OvWHRK)jCc@5Ov zas_322{R$=qCyBTLW6-%158DHcPLb=(Zg$723F>mM0R;7qKz(tZ8 z?R{xbXrk)wk7XIO;p7R zb?l++*SDZ+s}>b5UA?)W$_d|Mw3K7WcDPi~V!n}z)3(ASVm{GM#F1x^O$!TRNkn+J z=@@}uh?9sDRW^^?%lLr-pK=2o%9M~QjO7kGz}*9g3wRV~zY0rKAa@0-99zt`5hBR~ znVNCHYbj4+6!d9<(+4+1s+F)WDJPXWWNwj?xO4fAxC14Z0jDn$b-b^Mfl#We*wVi| zbg6gi&JS>s_D?;yzL0$vja^j&HpI8?6ZEG^krT+eaSifX{s@ljSW9NI=W(s{!dRfi;gHY z+k5*wJZ6t)o8IGN`_y0XR%GbgZYgcN?2}W^_3kf(l}GYy&QstQk3Se+=Dl-bU}im& zmxo{9$A{Bx$u!|#(K%5RR>>b)C5eDp}Us%JkRdbYeVCTi{unY{L0e`m6|RSsv*C|Ow5C#JJc U)vaR~%vxSsXB;T>CEqI&k@2Cy%(rnB%3n>J&O1uVm zM3X>NK{dif5h&oI4vI8@X&PpSg@;~1Sh)6iu0}z+{6)$9Dup^xASovy^`Rm~2s3Hj ziDp@|jUHI33+Xdn1ufj+G}d+jBh$_Jbs_%S2#NM;X<0-q+hM^2 z>_>1diKihT+(%A~uYksgNYZ5{Vg+8T3y(GBiV42KU`sSFtxC}LwMEixmBFJe7Ku^7>UNtYBWyv1@!wXHRLJVg@V zl(f1Apc@gg&O}Zlu_dKt?4`kyMgj3$AveQQa~B{Yx@PXxx37GhjCCw23gJ^wH4z6s z4pn1oS_(h*C{!&m=52I*5wR=q7gx5kbt$*fAuOnA>6A8IZG?*{iey= z4eHB<8gd)*mFSBrYKQ`sgt?j_TErO$kbdQ5L|vw>5c z=KegCN|!m)XbSfzWJ0S@+`ygt=fyP1U%9%~d|etg3zeR{d(sXjOOQbVTP%SNH0T diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 87b6ff146..9166b5a6a 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -36,8 +36,6 @@ public class ActionGameManager : MonoBehaviour get => _gameDataDirectory ??= new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData"); } - private static string LegacyAetherDatabasePath => LegacyItemCatalogBoundary.GetLegacyItemCatalogPath(GameDataDirectory); - private static ILegacyItemCatalogReader LegacyCatalog => LegacyItemCatalogBoundary.GetCatalog(GameDataDirectory); private static PlayerSettings _playerSettings; public static PlayerSettings PlayerSettings @@ -245,9 +243,15 @@ public class ActionGameManager : MonoBehaviour 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}"); + if (RuntimeCatalog == null) + { + throw new InvalidOperationException("Aetheria typed runtime catalog is required before gameplay boot."); + } + + var runtimeItemCatalog = new AetheriaRuntimeItemCatalog(RuntimeCatalog); + DatabaseLinkBase.BindRuntimeItemCatalog(runtimeItemCatalog); ItemManager = new ItemManager( - LegacyCatalog, + runtimeItemCatalog, Settings.GameplaySettings, Debug.Log); ZoneRenderer.ItemManager = ItemManager; diff --git a/Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs b/Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs deleted file mode 100644 index 44659da96..000000000 --- a/Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.IO; - -public static class LegacyItemCatalogBoundary -{ - private static LegacyItemCatalogCache _catalogCache; - - public static string GetLegacyItemCatalogPath(DirectoryInfo gameDataDirectory) - { - return Path.Combine(gameDataDirectory.FullName, "AetherDB.msgpack"); - } - - public static ILegacyItemCatalogReader GetCatalog(DirectoryInfo gameDataDirectory) - { - if (_catalogCache != null) return _catalogCache; - - _catalogCache = new LegacyItemCatalogCache(); - _catalogCache.AddBackingStore(new SingleFileMessagePackBackingStore(GetLegacyItemCatalogPath(gameDataDirectory))); - _catalogCache.PullAllBackingStores(); - DatabaseLinkBase.BindLegacyItemCatalog(_catalogCache); - return _catalogCache; - } -} diff --git a/Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs.meta b/Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs.meta deleted file mode 100644 index ba5030ce6..000000000 --- a/Assets/Scripts/Gameplay/LegacyCatalogBoundary.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f32bcbdbd7db4a3595f11f4540b90a3b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs b/Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs new file mode 100644 index 000000000..5cf4227d2 --- /dev/null +++ b/Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs @@ -0,0 +1,338 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using GameCult.Aetheria.State.Unity; +using MessagePack; +using Unity.Mathematics; + +public interface IRuntimeItemCatalogReader +{ + ItemData Get(Guid guid); + T Get(Guid guid) where T : ItemData; +} + +public sealed class AetheriaRuntimeItemCatalog : IRuntimeItemCatalogReader +{ + private readonly Dictionary _items; + private static readonly IReadOnlyDictionary BehaviorTypesByUnionKey = typeof(BehaviorData) + .GetCustomAttributes() + .ToDictionary(attribute => attribute.Key, attribute => attribute.SubType); + + public AetheriaRuntimeItemCatalog(AetheriaRuntimeCatalogSnapshot catalog) + { + if (catalog == null) throw new ArgumentNullException(nameof(catalog)); + + _items = catalog.Items + .Select(ProjectItem) + .Where(item => item != null) + .ToDictionary(item => item.ID, item => item); + } + + public ItemData Get(Guid guid) + { + ItemData item; + _items.TryGetValue(guid, out item); + return item; + } + + public T Get(Guid guid) where T : ItemData + { + return Get(guid) as T; + } + + private static ItemData ProjectItem(AetheriaRuntimeCatalogItem item) + { + if (!Guid.TryParse(item.LegacyId, out var id)) + { + return null; + } + + var projected = CreateItemData(item); + if (projected == null) + { + return null; + } + + projected.ID = id; + projected.Name = item.Name; + projected.Description = item.Description; + projected.Manufacturer = ParseGuid(item.ManufacturerLegacyId); + projected.Mass = (float)item.Mass; + projected.Shape = ProjectShape(item.ShapeWidth, item.ShapeHeight, item.ShapeCells); + projected.Price = item.Price; + + if (projected is SimpleCommodityData simple) + { + simple.MaxStack = item.MaxStack; + } + + if (projected is EquippableItemData equippable) + { + equippable.Durability = (float)item.Durability; + equippable.Behaviors = item.BehaviorPayloads + .Select(ProjectBehavior) + .Where(behavior => behavior != null) + .ToList(); + } + + if (projected is GearData gear) + { + gear.Hardpoint = ParseEnum(item.HardpointType, HardpointType.Tool); + } + + if (projected is CargoBayData cargoBay) + { + cargoBay.InteriorShape = ProjectShape(item.InteriorShapeWidth, item.InteriorShapeHeight, item.InteriorShapeCells); + } + + if (projected is WeaponItemData weapon) + { + weapon.WeaponRange = ParseEnum(item.WeaponRange, WeaponRange.Short); + weapon.WeaponCaliber = ParseEnum(item.WeaponCaliber, WeaponCaliber.Small); + weapon.WeaponType = ParseEnum(item.WeaponType, WeaponType.Laser); + weapon.WeaponFireTypes = ParseFlags(item.WeaponFireTypes, WeaponFireType.None); + weapon.WeaponModifiers = ParseFlags(item.WeaponModifiers, WeaponModifiers.None); + } + + if (projected is HullData hull) + { + hull.HullType = ParseEnum(item.HullType, HullType.Ship); + hull.Hardpoints = item.Hardpoints.Select(ProjectHardpoint).ToList(); + } + + return projected; + } + + private static ItemData CreateItemData(AetheriaRuntimeCatalogItem item) + { + switch (item.Category) + { + case "SimpleCommodityData": + return new SimpleCommodityData(); + case "CompoundCommodityData": + return new CompoundCommodityData(); + case "ConsumableItemData": + return new ConsumableItemData(); + case "GearData": + return new GearData(); + case "CargoBayData": + return new CargoBayData(); + case "DockingBayData": + return new DockingBayData(); + case "WeaponItemData": + return new WeaponItemData(); + case "HullData": + return new HullData(); + default: + return !string.IsNullOrWhiteSpace(item.HardpointType) ? new GearData() : null; + } + } + + private static Shape ProjectShape(int width, int height, IReadOnlyList cells) + { + var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1)); + foreach (var coordinate in shape.AllCoordinates) + { + shape[coordinate] = false; + } + + foreach (var cell in cells) + { + shape[new int2(cell.X, cell.Y)] = true; + } + + return shape; + } + + private static HardpointData ProjectHardpoint(AetheriaRuntimeHardpoint hardpoint) + { + return new HardpointData + { + Type = ParseEnum(hardpoint.Type, HardpointType.Hull), + Position = new int2(hardpoint.PositionX, hardpoint.PositionY), + Shape = ProjectShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells), + Transform = hardpoint.Transform, + Rotation = ParseEnum(hardpoint.Rotation, ItemRotation.None), + Armor = (float)hardpoint.Armor + }; + } + + private static BehaviorData ProjectBehavior(AetheriaRuntimeBehaviorPayload payload) + { + if (!BehaviorTypesByUnionKey.TryGetValue(payload.UnionKey, out var behaviorType)) + { + return null; + } + + var behavior = Activator.CreateInstance(behaviorType) as BehaviorData; + if (behavior == null) + { + return null; + } + + behavior.Group = payload.Group; + + foreach (var field in GetKeyedFields(behaviorType)) + { + var payloadField = payload.Fields.FirstOrDefault(candidate => candidate.Key == field.key); + if (payloadField == null) + { + continue; + } + + field.field.SetValue(behavior, ConvertValue(payloadField.Value, field.field.FieldType)); + } + + return behavior; + } + + private static IEnumerable<(int key, FieldInfo field)> GetKeyedFields(Type type) + { + return type + .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Select(field => (attribute: field.GetCustomAttribute(), field)) + .Where(entry => entry.attribute?.IntKey != null) + .Select(entry => (entry.attribute.IntKey.GetValueOrDefault(), entry.field)); + } + + private static object ConvertValue(AetheriaRuntimeBehaviorValue value, Type targetType) + { + if (targetType == typeof(string)) return value.StringValue ?? ""; + if (targetType == typeof(bool)) return value.BoolValue; + if (targetType == typeof(float)) return (float)value.NumberValue; + if (targetType == typeof(double)) return value.NumberValue; + if (targetType == typeof(int)) return checked((int)value.NumberValue); + if (targetType == typeof(uint)) return checked((uint)value.NumberValue); + if (targetType == typeof(Guid)) return ParseGuid(value.LegacyIdValue); + if (targetType == typeof(float2)) return ConvertFloat2(value); + if (targetType == typeof(float3)) return ConvertFloat3(value); + if (targetType == typeof(float4)) return ConvertFloat4(value); + if (targetType == typeof(int2)) return ConvertInt2(value); + if (targetType.IsEnum) return ParseEnumValue(value, targetType); + + if (targetType.IsArray) + { + return ConvertArray(value, targetType.GetElementType()); + } + + if (typeof(IList).IsAssignableFrom(targetType) && targetType.IsGenericType) + { + return ConvertList(value, targetType); + } + + if (targetType.GetCustomAttribute() != null) + { + return ConvertKeyedObject(value, targetType); + } + + return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + } + + private static object ConvertArray(AetheriaRuntimeBehaviorValue value, Type elementType) + { + var array = Array.CreateInstance(elementType, value.Children.Count); + for (var index = 0; index < value.Children.Count; index++) + { + array.SetValue(ConvertValue(value.Children[index], elementType), index); + } + + return array; + } + + private static object ConvertList(AetheriaRuntimeBehaviorValue value, Type targetType) + { + var elementType = targetType.GetGenericArguments()[0]; + var list = Activator.CreateInstance(targetType) as IList; + foreach (var child in value.Children) + { + list.Add(ConvertValue(child, elementType)); + } + + return list; + } + + private static object ConvertKeyedObject(AetheriaRuntimeBehaviorValue value, Type targetType) + { + var instance = Activator.CreateInstance(targetType); + var children = value.Children; + foreach (var field in GetKeyedFields(targetType)) + { + if (field.key >= 0 && field.key < children.Count) + { + field.field.SetValue(instance, ConvertValue(children[field.key], field.field.FieldType)); + } + } + + return instance; + } + + private static object ParseEnumValue(AetheriaRuntimeBehaviorValue value, Type targetType) + { + if (!string.IsNullOrWhiteSpace(value.StringValue)) + { + return Enum.Parse(targetType, value.StringValue, true); + } + + return Enum.ToObject(targetType, checked((int)value.NumberValue)); + } + + private static Guid ParseGuid(string value) + { + return Guid.TryParse(value, out var result) ? result : Guid.Empty; + } + + private static T ParseEnum(string value, T fallback) where T : struct + { + return Enum.TryParse(value, true, out T parsed) ? parsed : fallback; + } + + private static T ParseFlags(string value, T fallback) where T : struct + { + if (string.IsNullOrWhiteSpace(value)) + { + return fallback; + } + + return value + .Split('|') + .Select(part => part.Trim()) + .Where(part => part.Length > 0) + .Aggregate(fallback, (current, part) => + Enum.TryParse(part, true, out T parsed) + ? (T)Enum.ToObject(typeof(T), Convert.ToInt32(current) | Convert.ToInt32(parsed)) + : current); + } + + private static float2 ConvertFloat2(AetheriaRuntimeBehaviorValue value) + { + return new float2( + value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, + value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0); + } + + private static float3 ConvertFloat3(AetheriaRuntimeBehaviorValue value) + { + return new float3( + value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, + value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0, + value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0); + } + + private static float4 ConvertFloat4(AetheriaRuntimeBehaviorValue value) + { + return new float4( + value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, + value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0, + value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0, + value.Children.Count > 3 ? (float)value.Children[3].NumberValue : 0); + } + + private static int2 ConvertInt2(AetheriaRuntimeBehaviorValue value) + { + return new int2( + value.Children.Count > 0 ? checked((int)value.Children[0].NumberValue) : 0, + value.Children.Count > 1 ? checked((int)value.Children[1].NumberValue) : 0); + } +} diff --git a/Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs.meta b/Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs.meta new file mode 100644 index 000000000..914c8d57a --- /dev/null +++ b/Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 897dd3ee37f64ef987d554516646c5cf +timeCreated: 1781359840 diff --git a/Assets/Scripts/ServerShared/CultCache/DatabaseEntry.cs b/Assets/Scripts/ServerShared/CultCache/DatabaseEntry.cs index e1eea974b..41d6f51f2 100644 --- a/Assets/Scripts/ServerShared/CultCache/DatabaseEntry.cs +++ b/Assets/Scripts/ServerShared/CultCache/DatabaseEntry.cs @@ -68,7 +68,7 @@ public abstract class DatabaseEntry public class DatabaseLink : DatabaseLinkBase where T : ItemData { [IgnoreMember] - public T Value => ResolveLegacyItemCatalog(LinkID); + public T Value => ResolveRuntimeItemCatalog(LinkID); } [MessagePackObject] @@ -78,17 +78,17 @@ public class DatabaseLinkBase public Guid LinkID; [IgnoreMember] - private static ILegacyItemCatalogReader Catalog { get; set; } + private static IRuntimeItemCatalogReader Catalog { get; set; } - protected static T ResolveLegacyItemCatalog(Guid linkId) where T : ItemData + protected static T ResolveRuntimeItemCatalog(Guid linkId) where T : ItemData { if (Catalog == null) - throw new InvalidOperationException("Legacy item link resolution is not bound. Open LegacyItemCatalogBoundary before reading legacy item object graphs."); + throw new InvalidOperationException("Runtime item catalog resolution is not bound. Open typed Aetheria runtime state before reading item object graphs."); return Catalog.Get(linkId); } - public static void BindLegacyItemCatalog(ILegacyItemCatalogReader catalog) + public static void BindRuntimeItemCatalog(IRuntimeItemCatalogReader catalog) { if (catalog == null) throw new ArgumentNullException(nameof(catalog)); diff --git a/Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs b/Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs deleted file mode 100644 index d37bbfc78..000000000 --- a/Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs +++ /dev/null @@ -1,114 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using System; -using System.Collections.Generic; -using System.IO; -using MessagePack; -using UniRx; - -public interface ILegacyItemCatalogReader -{ - ItemData Get(Guid guid); - T Get(Guid guid) where T : ItemData; -} - -public class LegacyItemCatalogCache : ILegacyItemCatalogReader -{ - - private readonly object addLock = new object(); - - private List _backingStores = new List(); - - private readonly Dictionary _entries = new Dictionary(); - - public void AddBackingStore(CacheBackingStore store) - { - _backingStores.Add(store); - store.EntryAdded.Subscribe(entry => - { - AddInternal(entry, store); - }); - } - - public void PullAllBackingStores() - { - foreach(var store in _backingStores) store.PullAll(); - } - - private void AddInternal(DatabaseEntry entry, CacheBackingStore source = null) - { - lock(addLock) - { - if (entry is ItemData item) - { - _entries[item.ID] = item; - } - } - } - - public ItemData Get(Guid guid) - { - ItemData entry; - _entries.TryGetValue(guid, out entry); - return entry; - } - - public T Get(Guid guid) where T : ItemData - { - return Get(guid) as T; - } -} - -public abstract class CacheBackingStore -{ - protected CacheBackingStore() - { - EntryAdded = new Subject(); - } - - public abstract void PullAll(); - - public Subject EntryAdded { get; } - - protected Dictionary Entries = new Dictionary(); -} - -public abstract class SingleFileBackingStore : CacheBackingStore -{ - public FileInfo FileInfo { get; } - - public abstract DatabaseEntry[] Deserialize(byte[] data); - - public SingleFileBackingStore(string filePath) - { - FileInfo = new FileInfo(filePath); - } - - public override void PullAll() - { - if (!FileInfo.Exists) return; - - foreach (var entry in Deserialize(File.ReadAllBytes(FileInfo.FullName))) - { - Entries[entry.ID] = entry; - EntryAdded.OnNext(entry); - } - } - -} - -public class SingleFileMessagePackBackingStore : SingleFileBackingStore -{ - public SingleFileMessagePackBackingStore(string filePath) : base(filePath) - { - RegisterResolver.Register(); - } - - public override DatabaseEntry[] Deserialize(byte[] data) - { - return MessagePackSerializer.Deserialize(data); - } -} - diff --git a/Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs.meta b/Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs.meta deleted file mode 100644 index b4bfbdf1f..000000000 --- a/Assets/Scripts/ServerShared/CultCache/LegacyCatalogCache.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a43b39c6991618b43aed4a24ffcdc0ff -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/ItemManager.cs b/Assets/Scripts/ServerShared/ItemManager.cs index 5b6cccb99..428852189 100644 --- a/Assets/Scripts/ServerShared/ItemManager.cs +++ b/Assets/Scripts/ServerShared/ItemManager.cs @@ -30,7 +30,7 @@ public class ItemManager // private Guid _forceLoadZone; - private readonly ILegacyItemCatalogReader _itemData; + private readonly IRuntimeItemCatalogReader _itemData; public GameplaySettings GameplaySettings { get; } // public double Time @@ -46,7 +46,7 @@ public class ItemManager // private readonly Dictionary Tier = new Dictionary(); - public ItemManager(ILegacyItemCatalogReader itemData, GameplaySettings settings, Action logger) + public ItemManager(IRuntimeItemCatalogReader itemData, GameplaySettings settings, Action logger) { _itemData = itemData; GameplaySettings = settings; diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 3733a622c..ad38a7251 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -17,21 +17,20 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. ## Current Mechanism -- Unity client/runtime lives under `Assets/`. Gameplay still reads the legacy - item/name catalog through a private `ActionGameManager` boot helper, which is - now the only runtime entrypoint that calls `LegacyItemCatalogBoundary.GetCatalog`. - `ActionGameManager` no longer owns a global legacy cache. +- Unity client/runtime lives under `Assets/`. Gameplay now opens the typed + runtime catalog from `GameData/aetheria-world.cc` and projects current + `ItemData` DTOs through `AetheriaRuntimeItemCatalog`. `ActionGameManager` no + longer owns or opens a legacy catalog cache. `GameCult.Aetheria.State.Unity`, the embedded Unity package under `Packages/org.gamecult.aetheria.state`, owns the Unity runtime path to `GameData/aetheria-world.cc` through `AetheriaRuntimeStateBoundary` and owns 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. When the typed state file exists, + boot report and throws if `aetheria-world.cc` is missing before gameplay + boot. 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`. `LegacyItemCatalogBoundary` no longer names or owns typed state - paths. Local run saves, loadouts, player settings files, zone files, and + `ItemManager`. 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 @@ -42,12 +41,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `Economy.Shared` wrapper have been removed from live source. The stale `StrategyGameManager.csbak` backup file and unused Unity asset MessagePack formatter classes have also been deleted, so the remaining direct - `MessagePackSerializer` calls are the two legacy catalog deserializers and - their resolver setup. + `MessagePackSerializer` calls are no longer present in live runtime source. - Local legacy catalog data remains in `GameData/AetherDB.msgpack` and - `GameData/NameFile/*.msgpack` as migration inputs. The Unity runtime legacy - catalog bridge opens only `GameData/AetherDB.msgpack`; `NameFile/*.msgpack` - is no longer a runtime backing store. The old + `GameData/NameFile/*.msgpack` as migration inputs only. Unity gameplay no + longer opens those MessagePack files at runtime. The old `PlayerSettings.msgpack`, `.loadout`, `.zone`, and `GameData/KeyboardLayouts/*.msgpack` authority paths are disabled or deleted. The old `SavedGame`/`SavedZone` DTOs and `Galaxy` save-loader constructor are @@ -75,26 +72,17 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `EntityPack` serialization. Unity has not wired its save/loadout UI to this document yet, so the old in-memory `EntityPack` path is still a runtime construction helper, not durable authority. -- The private `ActionGameManager` catalog boot helper delegates to - `LegacyItemCatalogBoundary`, which opens `LegacyItemCatalogCache` as the only - concrete pull-only catalog cache. Runtime consumers receive - `ILegacyItemCatalogReader`, so the old `AetherDB.msgpack` backing store may hydrate - in-memory item domain objects for the current Unity runtime, but the cache now - admits only `ItemData` entries and cannot resolve generic `DatabaseEntry` - object graphs. This path cannot - push or delete legacy files. The legacy backing-store - write/realtime APIs, public cache mutation methods, enumeration methods, and - global-setting lookup methods have been deleted; only backing-store pull - hydration can populate it, and only single-ID reads can consume it. The - backing-store serializer methods are also deleted, so the legacy cache - implementation exposes deserialization only. UI code no longer reaches into - `LegacyItemCatalogBoundary.GetCatalog` directly. -- `ItemManager` no longer exposes the raw `ILegacyItemCatalogReader` as public +- `ActionGameManager` opens `AetheriaRuntimeCatalogStore` over + `aetheria-world.cc`, projects it through `AetheriaRuntimeItemCatalog`, and + binds `DatabaseLinkBase` to that typed runtime item reader before gameplay + objects read `ItemInstance.Data.Value`. The old `LegacyItemCatalogBoundary`, + `LegacyItemCatalogCache`, and runtime MessagePack deserializer path have been + deleted. +- `ItemManager` no longer exposes the raw runtime item catalog reader as public gameplay/UI API. Temporary item instantiation bridges now go through the narrow `ItemManager.GetCatalogEntry` method. `ItemManager` no longer - exposes a legacy catalog enumeration API. This does not remove the legacy reader under - the hood; it prevents old catalog authority from leaking into every caller - that only needs a domain lookup. The item properties UI no longer uses + exposes a legacy catalog enumeration API. This prevents catalog authority from + leaking into every caller that only needs a domain lookup. The item properties UI no longer uses `ItemManager` for manufacturer display; it resolves the manufacturer through the package-owned `ActionGameManager.RuntimeCatalog` typed snapshot. Entity restore and loadout manufacturer-distance weighting no longer use `ItemManager` @@ -106,20 +94,18 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. item candidate selection before hydrating selected legacy item DTOs by ID for exact fitting checks and behavior construction. The unused `TradeMenuDebug` script has been deleted instead of preserving an old uGUI debug path that - turned typed trade rows back into legacy `ItemData` objects. -- `Galaxy` generation no longer accepts `ILegacyItemCatalogReader` or `ItemManager`. + turned typed trade rows back into legacy `ItemData` objects. That hydration + now comes from typed state, not `AetherDB.msgpack`. +- `Galaxy` generation no longer accepts a runtime item catalog reader or `ItemManager`. Sector and tutorial generation receive the package-owned typed runtime catalog. `Galaxy` projects typed corporation v2 records into temporary legacy `Faction` DTOs, including allegiance edges, for the existing simulation shape and resolves full name arrays from `aetheria.name_file.v2` records. The - legacy catalog runtime no longer opens the old `GameData/NameFile/*.msgpack` - directory. - `ActionGameManager` still has a - private legacy catalog boot helper that feeds `ItemManager` for item/entity - systems; menu/generation paths no longer receive the legacy reader directly. + runtime no longer opens the old `GameData/NameFile/*.msgpack` directory. - `DatabaseLink.Value` can resolve legacy links only after - `LegacyItemCatalogBoundary` binds the pull-only item catalog cache. Legacy catalog - construction no longer grabs global `DatabaseLinkBase` authority. + `ActionGameManager` binds the typed runtime item catalog projected from + `aetheria-world.cc`. MessagePack catalog construction no longer grabs global + `DatabaseLinkBase` authority. - `Economy.Server` now starts the modern `Aetheria.State` CultMesh node and no longer owns RethinkDB/LiteNetLib state. - `Aetheria.State.Import` writes a typed quarantine manifest and migration @@ -154,8 +140,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. typed item shape masks, typed interior masks, hardpoints, and behavior 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 - `LegacyItemCatalogCache`. The SDK-style `Aetheria.State.Unity` facade includes + `aetheria-world.cc` catalog records without `DatabaseEntry` or a MessagePack + catalog cache. 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. @@ -258,33 +244,28 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. The embedded `GameCult.Aetheria.State.Unity` package owns Unity's runtime typed state file path plus the boot-time state-file probe, and is the landing zone for future typed catalog boot. - `LegacyItemCatalogBoundary` is the only named owner for opening the old item catalog - and binding legacy `DatabaseLink` resolution until catalog migration lands. - `ActionGameManager` has a private boot helper that calls it only to construct - `ItemManager`. Runtime code receives narrow `ItemManager` catalog methods - where gameplay only needs lookups. `ItemManager` still receives - `ILegacyItemCatalogReader` until typed runtime catalog boot replaces it; `Galaxy`, - entity restore, and faction-distance loadout weighting have been moved off - legacy faction catalog reads. + `ActionGameManager` opens the package-owned typed runtime catalog snapshot + and binds `DatabaseLink` resolution through `AetheriaRuntimeItemCatalog`. + Runtime code receives narrow `ItemManager` catalog methods where gameplay + only needs item lookups. `Galaxy`, entity restore, and faction-distance + loadout weighting have been moved off legacy faction catalog reads. The embedded Unity state package owns the read-only runtime catalog model 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`. The legacy cache is item-only; old - `Faction`, `NameFile`, global, zone, player, task, and body entries are no - longer accepted as runtime catalog entries. Item properties manufacturer display is - a typed snapshot consumer; loadout generation is the remaining runtime - single-ID item hydration bridge. The old trade debug UI and console `give` - command have been deleted. No live - caller can enumerate legacy catalog entries through `ItemManager` or - `ILegacyItemCatalogReader`. + owns simulation. The runtime no longer has a MessagePack catalog cache. + `AetheriaRuntimeItemCatalog` materializes temporary `ItemData` DTOs from typed + item records for the old simulation object model. Item properties + manufacturer display is a typed snapshot consumer; loadout generation is the + remaining runtime single-ID item lookup bridge. The old trade debug UI and + console `give` command have been deleted. No live caller can enumerate legacy + catalog entries through `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 - catalog reads emit in-memory domain objects only; the old local save and - editor catalog write paths have been cut. Migrated catalog documents are +- Outputs: `Aetheria.State` emits `.cc` state and CultMesh documents. Runtime + typed item catalog projection emits in-memory domain objects only; the old + 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 and loadout templates have typed state documents and node put/get paths; Unity's menu, @@ -303,9 +284,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. generated keyboard layout caches, and server-side `DatabaseCache` paths. - Shared paths: manual gameplay edits, editor edits, server updates, file load, file save, and migration all need to converge on one typed commit primitive. -- Deletion line: no new behavior should be added to `LegacyItemCatalogBoundary`, the - old `DatabaseEntry`, `LegacyItemCatalogCache`, or MessagePack catalog paths - except bounded migration readers that emit typed `Aetheria.State` documents. +- Deletion line: no new behavior should be added to old `DatabaseEntry` or + MessagePack catalog paths except bounded migration readers that emit typed + `Aetheria.State` documents. ## Target Authority Map @@ -321,11 +302,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `DatabaseEntry.ID`, global cache statics, and any compatibility reader. - Shared paths: gameplay input, editor edits, import/deep-load, replication, simulation ticks, and tests all call the same typed state service. -- Deletion line: after catalog migration smokes pass, replace - `ActionGameManager`'s private legacy catalog boot helper and `ItemManager` - boot with the typed runtime catalog package, then delete or quarantine the - remaining `LegacyItemCatalogCache`/`DatabaseEntry` runtime catalog dependency and - remove old MessagePack backing store classes from live Unity source. +- Deletion line: replace the remaining `DatabaseEntry`/`ItemData` runtime DTO + projection with native typed item instances, then remove old MessagePack + catalog metadata from live Unity source once import-only migration no longer + needs it. ## Intended Change @@ -451,11 +431,14 @@ First Aetheria surfaces to publish: remain migration inputs only. - Done: narrow `ILegacyItemCatalogReader` and `DatabaseLink` to `ItemData`; `LegacyItemCatalogCache` ignores non-item entries from `AetherDB.msgpack`. - - Replace `ActionGameManager` cache bootstrap with the new state runtime. + - Done: replace `ActionGameManager` cache bootstrap with + `AetheriaRuntimeItemCatalog`, a typed-state-backed projection from + `AetheriaRuntimeCatalogSnapshot`; delete `LegacyItemCatalogBoundary`, + `LegacyItemCatalogCache`, and the runtime MessagePack deserializer path. - Convert domain references from GUID/base-class patterns to typed record refs. - - Remove runtime dependency on `LegacyItemCatalogCache` and `DatabaseEntry` as - owners. + - Remove runtime dependency on `DatabaseEntry`/`ItemData` DTOs as item + instance owners. 5. Mesh host - Replace `Economy.Server` RethinkDB and LiteNetLib database authority with a @@ -518,9 +501,9 @@ First Aetheria surfaces to publish: - Unity runtime catalog smoke proves read-only catalog consumers can open the 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 - `LegacyItemCatalogCache`, including typed item masks, interior masks, hardpoint - definitions, corporation/name-file links, and typed behavior payloads. + 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. - 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