From 21a319a67deb88d5418fbfc2ccc7ebab6dff786e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 13 Jun 2026 10:01:20 +0100 Subject: [PATCH] Import typed behavior payloads --- .voidbot/state/aetheria.cc | Bin 22326 -> 22504 bytes Aetheria.State.Import/Program.cs | 96 ++++++++++++-- Aetheria.State.Unity.Smoke/Program.cs | 15 +++ .../AetheriaRuntimeCatalogSnapshot.cs | 119 ++++++++++++++++++ Aetheria.State.Verify/Program.cs | 38 ++++++ .../Documents/AetheriaItemDefinition.cs | 64 ++++++++++ Aetheria.State/README.md | 30 ++--- GameData/aetheria-world.cc | Bin 17677 -> 17941 bytes ...6bcb5217c5fee889541667442528a98491.msgpack | 4 +- ...a275af9147897cd1f60b23a18cc48b7fee.msgpack | 4 +- ...3921217d7ba077f48246e0b33fc1493431.msgpack | 4 +- ...3fbff428c15ab389a42d0d2aa0e75980fd.msgpack | 2 +- ...6b93b4259541f580967a52464d9686f3aa.msgpack | 4 +- ...fdee0c7ff471c8bb4b4e0b2067eb36da09.msgpack | 4 +- ...cc1c87e1e40afcab2bb8dd8f889fe0918b.msgpack | 2 +- ...1ac1edd3dffac04996b12d9f9d0e8304f1.msgpack | 2 +- ...1368ec2948a11425e377fa92a71d575637.msgpack | 4 +- ...8858c36442af01034165db67b886e94ce3.msgpack | 4 +- ...2cd5bdbf86136a558044079cba7a029ac8.msgpack | 4 +- ...6ba52bd445978bb0cb399047cf64aa722f.msgpack | 4 +- ...3e8ff05893f872c2437a5621f2a2eec71b.msgpack | 4 +- ...9bb06b3f45a746ac5f7739b1cbd896443f.msgpack | 4 +- ...07d0fb3e1d5d58fbc176520a100614f404.msgpack | 2 +- ...48df04cfa2250b7101fc00f9c6f2c1317c.msgpack | 4 +- ...06e97680a29efe7d1436947e3a8eba4c95.msgpack | 4 +- ...2ccd793983d2af31967b7d70a977fd2e02.msgpack | 4 +- ...f91cb8ca685ea96318a795771ae6d911eb.msgpack | 4 +- ...db27aa2b805705e823bed4877c4623cf88.msgpack | 4 +- ...5eead201bac20496fa5a2649e4b1522a37.msgpack | 4 +- ...fa0290206edd7b2e0dc08b0f9842dea0f2.msgpack | 4 +- ...dd6eeb3d8311f3753af60c2b5596719b15.msgpack | 4 +- ...d7e3a5d8fa1509efb8a9249d0e48891299.msgpack | 2 +- ...c76a739617023ffa2c2aeb091cf5580b7d.msgpack | 2 +- ...f5bd89832b6e55959ea4902bfa15bc8117.msgpack | 4 +- ...d52fa612f479ccbad76307db432c0b6147.msgpack | 2 +- ...268eff507c40681c6b1955fa786f99066c.msgpack | 2 +- ...ad0cb1b8ec9229ee0525bb8571edb6ea32.msgpack | 4 +- ...616a532f076ae0a66f2257f9c7899100a9.msgpack | 4 +- ...ae26acc72f64a9ca536b895d59a8675c66.msgpack | 4 +- ...44c85439d1f3caa1caaf24beb34e7d8d50.msgpack | 2 +- ...9500e6c9192f76c38393eb84bee3345ee0.msgpack | 4 +- ...dd6ef83562c3b82a0e03c21a37c241f5b8.msgpack | 4 +- ...5cdcddbcded4980427149dc10ba9eb08a1.msgpack | 2 +- ...baf1c52a76a0e7adce5acb06a2d7f5bd79.msgpack | 4 +- ...79872da971faf89ceef4c395a64f1b5e88.msgpack | 4 +- ...60257125aef40381db5877aa16c02b39e4.msgpack | 4 +- ...ea1ff3e3f204925bb28ccb1d45d2b3d732.msgpack | 4 +- ...786b7af0cd4f808a703d5f242c9b0fd372.msgpack | 2 +- ...05ae7658005ee7019c3810650baf8e6c4f.msgpack | 4 +- ...d65f26dcd9e6b2bc9ef89cb16dd16e4b07.msgpack | 4 +- ...4187f90237d4386c76e3b847376b466dd3.msgpack | 4 +- ...042b3f00710055061bb4b6d9b2306d14c3.msgpack | 2 +- ...36ce1066ae558fc09801457a30674f515b.msgpack | 4 +- ...e298fc6ceb8352b047986733e405d6006b.msgpack | 4 +- ...7cb628668b9e3842a59c0e2dd106a820bb.msgpack | 4 +- ...b30b7614eb776ee14b16bed525675b67b8.msgpack | 4 +- ...6321b14e59b2b62a7e914e74771273097a.msgpack | 4 +- ...d39c7f7412772d7ad9adc1f1db3f39cc10.msgpack | 4 +- ...e152a6e77f714229898548c45b02cc72d6.msgpack | 4 +- ...0ecea7ff27435bd9e9d534bcb0c1b892ef.msgpack | 4 +- ...daf7ada6d75ceaa0a07f1e0dc46d65003f.msgpack | 4 +- ...25c2c88303bdbc1a54a178c895086e6c55.msgpack | 4 +- ...320b70986559345e184297437f13bcae7d.msgpack | 4 +- ...1f7c425775d76ba34c55a9ae879e27b3d8.msgpack | 2 +- ...8181b2c9794fa3a70f803c67de8d0dcff0.msgpack | 4 +- ...918704bf6ad447ce7a4733664d3888359d.msgpack | 4 +- ...9358f522e1794e5a675f77b06e99ee061e.msgpack | 2 +- ...72d6a82431bf8c842bc57b1c7f3bae4876.msgpack | 4 +- ...78de0d154287774728c30a461ed0d9e334.msgpack | 4 +- ...5a77b77ba285ba04fa6e1c678d86988867.msgpack | 4 +- ...cb33184e1aade105b24afaba265b148ce4.msgpack | 4 +- ...92a3874ff91d1e08024120231bb836c2bb.msgpack | 4 +- ...30a284a5876d9f55597d457cebba6a2226.msgpack | 4 +- ...04c4d886f2bf72ae35b733603966e28d61.msgpack | 4 +- ...a65e76836067e0661b91eb2fa4cc5c2b3f.msgpack | 4 +- ...6550ff7ce20b046a653debb4e7bc10e047.msgpack | 2 +- ...2888041296cc8e256b3774e29b1b969c4b.msgpack | 4 +- ...eb069ff9e1ed707a2590cedcffc835ac23.msgpack | 4 +- ...199f861a6986234742cd7d603c9d8cbe86.msgpack | 4 +- ...699d5fc930104242470efeddc8f9500222.msgpack | 4 +- ...ef7f34c156794d697606252db56928e23b.msgpack | 4 +- ...d30298201bd0eced6bfa9c3b7e01d9448c.msgpack | 4 +- ...946ba31ec32b37bfe4b0829825c4e85029.msgpack | 2 +- ...cb793e7bcf5f7afa2c328d12d6561d0f1e.msgpack | 4 +- ...56919b8dd915d9fef8f2361b77d9bf6b99.msgpack | 4 +- ...386b0b8e8f80cfe0ad311061b682ecff95.msgpack | 4 +- ...1c82be1f818ee634833d6e3e637e694344.msgpack | 4 +- ...4f2a1a72a54f72c42a4e4c1d0f231586ba.msgpack | 4 +- ...34144722b989cf3cf2f03431cabc1f3929.msgpack | 4 +- ...a34a37e5fcb7892a29eb02cf5da1c338de.msgpack | 4 +- ...c97b1604f21df8132d6305f350e80ba5b7.msgpack | 4 +- ...c595e7f3aea98935f9e9a6aafdf8b4d9d2.msgpack | 4 +- ...1539aa45529f4c740066eb5301ed36edfd.msgpack | 4 +- ...2e4971d71ab5b8b38ac0c89c3f9a764764.msgpack | 2 +- ...79e8b3e7a51e9d5145e08f639a88de38be.msgpack | 4 +- ...cb2be74c711de0d5b22f9160aba0ee7ee9.msgpack | 2 +- ...65d32cdc881f54a82297e048ac6adff8ff.msgpack | 4 +- ...0336d40c18572d3f6ed6318a4008af5a89.msgpack | 4 +- ...fcd8fa1e9858c270d58ccf5d2170a3842c.msgpack | 4 +- ...d2064953cbde740647c3a820bacf9c27c3.msgpack | 4 +- ...33d98be75491a3b1b3af0d080505b31b21.msgpack | 4 +- ...76f8e8486ae0dd84c256d8c672c54837ec.msgpack | 4 +- ...7dd3b2bd29116afbf8857d8b5afdc85c8f.msgpack | 4 +- ...26af1122625e375d29d192aacad668e2a7.msgpack | 4 +- ...0d87791a49bb911b2e7ab294e1303e180c.msgpack | 4 +- ...85198b8b38c36fc5417df965fe098c7523.msgpack | 4 +- ...fdf046f5a7951655cf83dc22938e7563b2.msgpack | 4 +- ...f4651415e50cce4eb13240370a1394b928.msgpack | 4 +- ...52a044e5c42314b43945ded225f3009bf9.msgpack | 4 +- ...b76604f01f891d11f6bc9c079621d2b45c.msgpack | 4 +- ...dd6056bad45424aaadc0ed86534053fd43.msgpack | 2 +- ...6a84bb1ad648c6e3f72452f8ee47f98cb5.msgpack | 4 +- ...28230b2636da2c91d4e12ec6c804c8a04a.msgpack | 4 +- ...480afdb5047dee177edeac207ce337e083.msgpack | 4 +- ...7f81911af70edf42cb5275ce8f96dee707.msgpack | 2 +- ...07128f325b6f8981e61bf216400dd09f8e.msgpack | 4 +- ...f4dc349da455027ef5db10f14d88553894.msgpack | 4 +- ...1ade7f7e67031a1f69bec307690212bb53.msgpack | 4 +- ...3ff4da3985d54c314bcaf6ceca29a0d80e.msgpack | 4 +- ...1b1a624f836079caf3f9c892cbc94d0aae.msgpack | 4 +- ...01600dd1985f3e1ddeeb16b10593afc5ab.msgpack | 2 +- ...08e1a83f1dc14096339f67d56c0e0869ff.msgpack | 4 +- ...06d60f0e24b919fdf36a025cfddee87011.msgpack | 4 +- ...0f1c261fbbb7f1961d8acd057a3b287e38.msgpack | 4 +- ...5b07b6b9ec7bba75debf9915460dbe3e1a.msgpack | 4 +- ...2762b088b45c2b873b2816cc7dd4e7672c.msgpack | 4 +- ...e79aecf53d9558fd48290599da73065788.msgpack | 4 +- ...3dd2a11f08fa54e6ffc699ee7ff032399d.msgpack | 2 +- ...36c731b877c242d1dd966801858145a431.msgpack | 4 +- ...8668f9d2c05806983d89a8e9227a63871e.msgpack | 4 +- ...e2540502e68596a3603f76389e57028805.msgpack | 4 +- ...9ab8ab7bd5c5792eaffa4226a6c04eb105.msgpack | 4 +- ...b464103bd76e410747a9c9b7ff24e3c4e1.msgpack | 4 +- ...5ee0f0a8c3001293f5702aa2424e24427e.msgpack | 2 +- ...731eadcb421407c89d781610905a20f024.msgpack | 2 +- ...4a3699e1985d69a91cf8dcb7f4c66bddf5.msgpack | 4 +- ...25da6d94408d6a3d1d9118f6ee39b59f6b.msgpack | 4 +- ...27c780283d518bb03ec1796d41885bd88a.msgpack | 4 +- ...0f473896745b146e5990046afe53ac54b8.msgpack | 2 +- ...e9c2093d642b67286400600a6215e90683.msgpack | 4 +- ...8f723d3a0a7f48de7e52fe545a530c2559.msgpack | 4 +- ...3f7d34327c64a8a33c3dfb1809131717a1.msgpack | 4 +- ...42f2cef8f92eb11513d698d60a8ad234c4.msgpack | 4 +- ...d4f7d77f094191b2e9d7590b6795950780.msgpack | 4 +- ...c4bd13b358f1ffb0937b14289d35ab2d53.msgpack | 4 +- ...6b87f1ba0566a44f3d381b757edb40d25d.msgpack | 4 +- ...e4f0b2d619dceb69ec354fe56f99488b42.msgpack | 2 +- ...beb23a1fa47461adda98ef10295021480f.msgpack | 4 +- ...d3f4d154424deaf2f21dd15d213684d46f.msgpack | 4 +- ...8da4a63ef12a1dc5126b11ebba43840395.msgpack | 2 +- docs/aetheria-perfect-machine-map.md | 27 ++-- 151 files changed, 610 insertions(+), 293 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 4ec42eec4da6042ee697a98fc1f4c9880b976661..8017522ec96aaaa3aac36db2245e0a858ee4fbba 100644 GIT binary patch delta 1581 zcmaJ>U5gw=6m`~sSrZKd%|Q13`6(rTld~`&pGwe54FF4 ztljx;VR3!qjrC5W*>2`9?kwl`7QUz=9Ymhb)5l#AS?g>}a*iH(Vd28}<5!OaD>p8^ z!!BYY1@$-*L#Yi7L>6liS`vmy>3td)wrqwRlCb2m0UkEf*lmg+VhV&4vEC!=gyY7y zK`dnQTkVtl-Slg)3)_W|BRC^>*io&XvJxT5L zDyy0o(e~;rjB0U+RG~szeZ7j;tqhssV-xuG?iK*{*{}*Y|MF!T&2Ji?MM#MbIkvPf zU8GY-epx+X`J8elYut9MEFj*$_g<=;Oc?u?aZG_$)|oVLmJV6=sd9m&dmk{8-Xs^v zk);3)N>;&hik1e;ss@|lBB|&a8_U!~eyFiIr5$DL59^`F2<;S{&CZIzX_FC@WMz`Z z!c#+h0FEb5gOPAi<1Vrg?ai-ZEZkg*gaNW(LJ$if=5%{-_CyDgQs0eBJJe~j7es+BPwa<=ne_|z^D(y7~ z^5jDw&U^h&yqpdl0CU!D&=B2kkzIgG$+NBQX#~>@9TBi0bdOy+{w+(gS zLZRGs76YwLHCOYcGjAO$F6LHq_Zuhn?C+1QjCar6tvz$6ELbmbEuDR8vAnfgxW~6o z6mP9He}^yUH@AN-Bdybjly`iq)tY?&b?4J3rtMn!%Fglet#^M}e6k3vpWgi+qar>P delta 1401 zcmaJ>&ubGw6gFv*)S`%3MX>cGB~7ZK0(;G5m-riyslBG)7k*96PAZLo+iJ&sw7Nl<}DO^}KxntyKC zggIX_rKCrGIf^eh9v2Ghc=9I95>e-65{y*1vBumw{_M1p7V)SY0uwv0W8F*Gu!1-{ zCk$%actE2`llu@MEZ)Wxek+{FL5&zx$$Utr`OT(fDs%CS8 zB$7=dIgyyr5Ou*+0IN8;$+5M|6(~UwL4rz9k1(M%M&Udv!p8hS^ZUH7d_jTU@|ou# z_z^?v2{s8diH5H-H)ZR%1|3ExmqV@l~kq%X805^91wZJ`$0MqAe6&uV>Rc~hSa zLBnD;MHZT_Y3nlR${3#hlkzbvRe;A5;VsPB z&MF`i=)wFe;u;ngS7}XtJ#czsvo$sNBwH>oqLWdnNJJzxUX$3n@t!2mfe1l@XqOUO zMyt|VIQJ>HG+&`L<{}yK68%}}uR^hpwfsy`2NwiyGOe`CZ)^%$+nETDo;@BuEKXMD zu3;t^#v7U){NO05^_k=NXmq4W_^x6OMXiu_(0hfP5)&M5QoHr5__((`dj*NYHPk!u zfA53#{y%IV?q0YXKD~UT7_FYhleA5zjUJYiunq(nzMeUMto_;*$5x&xQb!dAT6;5F znbwE$;hvr}8E#ciC6C^C{OI*ox6{Z(`(Zp7{;2X?d~0kx{MmIfytjBiIaVs@W3B$B Kk3DIqwDKDR9Q+6X diff --git a/Aetheria.State.Import/Program.cs b/Aetheria.State.Import/Program.cs index beb8ac206..d2144571f 100644 --- a/Aetheria.State.Import/Program.cs +++ b/Aetheria.State.Import/Program.cs @@ -313,7 +313,14 @@ internal static class LegacyCatalogReader var shape = ReadShape(payload, 5); var interiorShape = ReadInteriorShape(unionKey, payload, shape); var hardpoints = unionKey == 3 ? ReadHardpoints(payload, 23) : []; - var behaviorKinds = ReadBehaviorKinds(payload, unionKey == 31 || unionKey is 2 or 3 or 29 or 30 ? 11 : 10); + var behaviorKey = unionKey == 31 || unionKey is 2 or 3 or 29 or 30 ? 11 : 10; + var behaviorPayloads = ReadBehaviorPayloads(payload, behaviorKey); + var behaviorKinds = behaviorPayloads + .Select(behavior => behavior.Kind) + .Where(kind => !string.IsNullOrWhiteSpace(kind)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(kind => kind, StringComparer.OrdinalIgnoreCase) + .ToArray(); var summary = new AetheriaLegacyCatalogEntrySummary { LegacyId = legacyId, @@ -345,10 +352,11 @@ internal static class LegacyCatalogReader InteriorOccupiedCells = interiorShape.OccupiedCells, InteriorShapeCells = interiorShape.Cells, Hardpoints = hardpoints, + BehaviorPayloads = behaviorPayloads, HardpointType = GetHardpointType(unionKey, payload), HullType = unionKey == 3 ? GetEnumName(payload, 25, HullTypes) : "", BehaviorKinds = behaviorKinds, - BehaviorCount = behaviorKinds.Length, + BehaviorCount = behaviorPayloads.Length, MaxStack = unionKey == 0 ? GetInt(payload, 9) : 0, Stackable = false, Duration = 0, @@ -640,7 +648,7 @@ internal static class LegacyCatalogReader }; } - private static string[] ReadBehaviorKinds(IReadOnlyDictionary payload, int key) + private static AetheriaBehaviorPayload[] ReadBehaviorPayloads(IReadOnlyDictionary payload, int key) { if (!payload.TryGetValue(key, out var value) || value is not object?[] behaviors) { @@ -648,18 +656,17 @@ internal static class LegacyCatalogReader } return behaviors - .Select(ReadBehaviorKind) - .Where(kind => !string.IsNullOrWhiteSpace(kind)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(kind => kind, StringComparer.OrdinalIgnoreCase) + .Select(ReadBehaviorPayload) + .Where(behavior => behavior != null) + .Cast() .ToArray(); } - private static string ReadBehaviorKind(object? value) + private static AetheriaBehaviorPayload? ReadBehaviorPayload(object? value) { - if (value is not object?[] union || union.Length < 1) + if (value is not object?[] union || union.Length < 2) { - return ""; + return null; } var unionKey = union[0] switch @@ -668,7 +675,74 @@ internal static class LegacyCatalogReader int intValue => intValue, _ => -1 }; - return BehaviorUnionNames.TryGetValue(unionKey, out var name) ? name : $"behavior:{unionKey}"; + var fields = union[1] is object?[] payload + ? payload + .Select((fieldValue, index) => new AetheriaBehaviorField + { + Key = index, + Value = ReadBehaviorValue(fieldValue) + }) + .ToArray() + : []; + return new AetheriaBehaviorPayload + { + UnionKey = unionKey, + Kind = BehaviorUnionNames.TryGetValue(unionKey, out var name) ? name : $"behavior:{unionKey}", + Group = fields.FirstOrDefault(field => field.Key == 0)?.Value.Kind == "number" + ? checked((int) fields[0].Value.NumberValue) + : 0, + Fields = fields + }; + } + + private static AetheriaBehaviorValue ReadBehaviorValue(object? value) + { + if (value == null) + { + return new AetheriaBehaviorValue { Kind = "nil" }; + } + + return value switch + { + bool boolValue => new AetheriaBehaviorValue { Kind = "bool", BoolValue = boolValue }, + string stringValue => new AetheriaBehaviorValue { Kind = "string", StringValue = stringValue }, + byte[] bytes when bytes.Length == 16 => new AetheriaBehaviorValue + { + Kind = "legacy-id", + LegacyIdValue = new Guid(bytes).ToString("D") + }, + byte[] bytes => new AetheriaBehaviorValue + { + Kind = "binary", + StringValue = Convert.ToHexString(bytes).ToLowerInvariant() + }, + object?[] array => new AetheriaBehaviorValue + { + Kind = "array", + Children = array.Select(ReadBehaviorValue).ToArray() + }, + Dictionary map => new AetheriaBehaviorValue + { + Kind = "map", + MapEntries = map + .OrderBy(entry => entry.Key, StringComparer.Ordinal) + .Select(entry => new AetheriaBehaviorMapEntry + { + Key = entry.Key, + Value = ReadBehaviorValue(entry.Value) + }) + .ToArray() + }, + double doubleValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = doubleValue }, + float floatValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = floatValue }, + long integerValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = integerValue }, + int integerValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = integerValue }, + _ => new AetheriaBehaviorValue + { + Kind = "unknown", + StringValue = value.ToString() ?? "" + } + }; } private static Dictionary CreateEnumMap(string[] names) diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs index 9d3b0a3ae..de2cd97b5 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -62,6 +62,20 @@ if (!catalog.FindItemsByBehavior(behaviorKind).Any()) throw new InvalidOperationException($"Runtime catalog behavior lookup failed for {behaviorKind}."); } +var behaviorHost = catalog.Items.FirstOrDefault(item => item.BehaviorPayloads.Count > 0) + ?? throw new InvalidOperationException("Runtime catalog has no typed behavior payloads."); +if (!behaviorHost.BehaviorPayloads.All(payload => behaviorHost.BehaviorKinds.Contains(payload.Kind))) +{ + throw new InvalidOperationException( + $"Runtime behavior payload kind index mismatch for {behaviorHost.Name}."); +} + +var behaviorPayload = behaviorHost.BehaviorPayloads.First(); +if (behaviorPayload.Fields.Count == 0) +{ + throw new InvalidOperationException($"Runtime behavior payload has no fields for {behaviorHost.Name}."); +} + var equipment = catalog.EquipmentItems.First(); if (!catalog.FindItemsByHardpoint(equipment.HardpointType).Any()) { @@ -85,5 +99,6 @@ Console.WriteLine($"Aetheria Unity runtime catalog smoke passed: {statePath}"); Console.WriteLine($"Items/trade/equipment: {catalog.Items.Count}/{catalog.TradeItems.Count}/{catalog.EquipmentItems.Count}"); Console.WriteLine($"Shape mask sample: {shaped.Name} {shaped.ShapeWidth}x{shaped.ShapeHeight}/{shaped.ShapeCells.Count}"); Console.WriteLine($"Interior/hardpoint sample: {interior.Name} {interior.InteriorShapeCells.Count}; {hardpointHost.Name} {hardpoint.Type} {hardpoint.ShapeCells.Count}"); +Console.WriteLine($"Behavior payload sample: {behaviorHost.Name} {behaviorPayload.Kind}/{behaviorPayload.Fields.Count}"); Console.WriteLine($"Behavior sample: {behaviorKind}"); Console.WriteLine($"Eve surface: {surface.Surface.Id}"); diff --git a/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshot.cs b/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshot.cs index 88570f85c..1a4de3f4c 100644 --- a/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshot.cs +++ b/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshot.cs @@ -113,6 +113,7 @@ public sealed class AetheriaRuntimeCatalogItem int interiorOccupiedCells, IReadOnlyList interiorShapeCells, IReadOnlyList hardpoints, + IReadOnlyList behaviorPayloads, string hardpointType, string hullType, IReadOnlyList behaviorKinds, @@ -141,6 +142,7 @@ public sealed class AetheriaRuntimeCatalogItem InteriorOccupiedCells = interiorOccupiedCells; InteriorShapeCells = interiorShapeCells; Hardpoints = hardpoints; + BehaviorPayloads = behaviorPayloads; HardpointType = hardpointType; HullType = hullType; BehaviorKinds = behaviorKinds; @@ -170,6 +172,7 @@ public sealed class AetheriaRuntimeCatalogItem public int InteriorOccupiedCells { get; } public IReadOnlyList InteriorShapeCells { get; } public IReadOnlyList Hardpoints { get; } + public IReadOnlyList BehaviorPayloads { get; } public string HardpointType { get; } public string HullType { get; } public IReadOnlyList BehaviorKinds { get; } @@ -201,6 +204,7 @@ public sealed class AetheriaRuntimeCatalogItem item.InteriorOccupiedCells, item.InteriorShapeCells.Select(AetheriaRuntimeShapeCell.FromState).ToArray(), item.Hardpoints.Select(AetheriaRuntimeHardpoint.FromState).ToArray(), + item.BehaviorPayloads.Select(AetheriaRuntimeBehaviorPayload.FromState).ToArray(), item.HardpointType, item.HullType, item.BehaviorKinds, @@ -232,6 +236,121 @@ public sealed class AetheriaRuntimeShapeCell } } +public sealed class AetheriaRuntimeBehaviorPayload +{ + public AetheriaRuntimeBehaviorPayload( + int unionKey, + string kind, + int group, + IReadOnlyList fields) + { + UnionKey = unionKey; + Kind = kind; + Group = group; + Fields = fields; + } + + public int UnionKey { get; } + + public string Kind { get; } + + public int Group { get; } + + public IReadOnlyList Fields { get; } + + public static AetheriaRuntimeBehaviorPayload FromState(AetheriaBehaviorPayload payload) + { + return new AetheriaRuntimeBehaviorPayload( + payload.UnionKey, + payload.Kind, + payload.Group, + payload.Fields.Select(AetheriaRuntimeBehaviorField.FromState).ToArray()); + } +} + +public sealed class AetheriaRuntimeBehaviorField +{ + public AetheriaRuntimeBehaviorField(int key, AetheriaRuntimeBehaviorValue value) + { + Key = key; + Value = value; + } + + public int Key { get; } + + public AetheriaRuntimeBehaviorValue Value { get; } + + public static AetheriaRuntimeBehaviorField FromState(AetheriaBehaviorField field) + { + return new AetheriaRuntimeBehaviorField(field.Key, AetheriaRuntimeBehaviorValue.FromState(field.Value)); + } +} + +public sealed class AetheriaRuntimeBehaviorMapEntry +{ + public AetheriaRuntimeBehaviorMapEntry(string key, AetheriaRuntimeBehaviorValue value) + { + Key = key; + Value = value; + } + + public string Key { get; } + + public AetheriaRuntimeBehaviorValue Value { get; } + + public static AetheriaRuntimeBehaviorMapEntry FromState(AetheriaBehaviorMapEntry entry) + { + return new AetheriaRuntimeBehaviorMapEntry(entry.Key, AetheriaRuntimeBehaviorValue.FromState(entry.Value)); + } +} + +public sealed class AetheriaRuntimeBehaviorValue +{ + public AetheriaRuntimeBehaviorValue( + string kind, + string stringValue, + double numberValue, + bool boolValue, + string legacyIdValue, + IReadOnlyList children, + IReadOnlyList mapEntries) + { + Kind = kind; + StringValue = stringValue; + NumberValue = numberValue; + BoolValue = boolValue; + LegacyIdValue = legacyIdValue; + Children = children; + MapEntries = mapEntries; + } + + public string Kind { get; } + + public string StringValue { get; } + + public double NumberValue { get; } + + public bool BoolValue { get; } + + public string LegacyIdValue { get; } + + public IReadOnlyList Children { get; } + + public IReadOnlyList MapEntries { get; } + + public static AetheriaRuntimeBehaviorValue FromState(AetheriaBehaviorValue value) + { + return new AetheriaRuntimeBehaviorValue( + value.Kind, + value.StringValue, + value.NumberValue, + value.BoolValue, + value.LegacyIdValue, + value.Children.Select(FromState).ToArray(), + value.MapEntries.Select(AetheriaRuntimeBehaviorMapEntry.FromState).ToArray()); + } +} + public sealed class AetheriaRuntimeHardpoint { public AetheriaRuntimeHardpoint( diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 6bc83ae0e..bf1b4af60 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -48,6 +48,13 @@ var interiorShapeItems = items.Count(item => item.InteriorShapeCells.Length > 0) var hardpointHostItems = items.Count(item => item.Hardpoints.Length > 0); var hardpointCount = items.Sum(item => item.Hardpoints.Length); var behaviorItems = items.Count(item => item.BehaviorCount > 0 && item.BehaviorKinds.Length > 0); +var behaviorPayloadItems = items.Count(item => item.BehaviorPayloads.Length > 0); +var behaviorPayloadCount = items.Sum(item => item.BehaviorPayloads.Length); +var behaviorFieldCount = items.SelectMany(item => item.BehaviorPayloads).Sum(behavior => behavior.Fields.Length); +var behaviorLegacyRefCount = items + .SelectMany(item => item.BehaviorPayloads) + .SelectMany(behavior => behavior.Fields) + .Count(field => ContainsBehaviorValueKind(field.Value, "legacy-id")); var hardpointItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HardpointType)); var hullItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullType)); var weaponItems = items.Count(item => @@ -148,6 +155,29 @@ if (behaviorItems == 0) throw new InvalidOperationException("Typed item definitions did not import any behavior fingerprints."); } +if (behaviorPayloadItems != behaviorItems || behaviorPayloadCount == 0 || behaviorFieldCount == 0) +{ + throw new InvalidOperationException( + $"Typed behavior payload import mismatch: payloadItems={behaviorPayloadItems}, behaviorItems={behaviorItems}, payloads={behaviorPayloadCount}, fields={behaviorFieldCount}."); +} + +foreach (var item in items.Where(item => item.BehaviorPayloads.Length > 0)) +{ + if (item.BehaviorPayloads.Length != item.BehaviorCount) + { + throw new InvalidOperationException( + $"Typed item behavior payload count mismatch for {item.Name}: payloads={item.BehaviorPayloads.Length}, count={item.BehaviorCount}."); + } + + foreach (var behavior in item.BehaviorPayloads) + { + if (string.IsNullOrWhiteSpace(behavior.Kind) || behavior.UnionKey < 0 || behavior.Fields.Length == 0) + { + throw new InvalidOperationException($"Typed item behavior payload is incomplete for {item.Name}."); + } + } +} + if (hardpointItems == 0) { throw new InvalidOperationException("Typed item definitions did not import any hardpoint types."); @@ -256,6 +286,7 @@ Console.WriteLine($"Item definitions: {items.Length}"); Console.WriteLine($"Priced/manufactured/shaped items: {pricedItems}/{manufacturedItems}/{shapedItems}"); Console.WriteLine($"Shape masks: {shapedMaskItems}"); Console.WriteLine($"Interior masks/hardpoint hosts/hardpoints: {interiorShapeItems}/{hardpointHostItems}/{hardpointCount}"); +Console.WriteLine($"Behavior payload items/payloads/fields/legacy refs: {behaviorPayloadItems}/{behaviorPayloadCount}/{behaviorFieldCount}/{behaviorLegacyRefCount}"); Console.WriteLine($"Behavior/hardpoint/hull/weapon items: {behaviorItems}/{hardpointItems}/{hullItems}/{weaponItems}"); Console.WriteLine($"Typed catalog trade items: {tradeItems.Length}"); Console.WriteLine($"Eve catalog surface: {surface.Surface.Id} ({surface.Surface.Root.Children.Length} root children)"); @@ -278,6 +309,13 @@ static void RequireCount(AetheriaMigrationLedger ledger, string documentType, in } } +static bool ContainsBehaviorValueKind(AetheriaBehaviorValue value, string kind) +{ + return string.Equals(value.Kind, kind, StringComparison.OrdinalIgnoreCase) || + value.Children.Any(child => ContainsBehaviorValueKind(child, kind)) || + value.MapEntries.Any(entry => ContainsBehaviorValueKind(entry.Value, kind)); +} + static async Task RequireLegacyLookupAsync( string legacyId, Func> lookup, diff --git a/Aetheria.State/Documents/AetheriaItemDefinition.cs b/Aetheria.State/Documents/AetheriaItemDefinition.cs index 41e13087c..48951c6fd 100644 --- a/Aetheria.State/Documents/AetheriaItemDefinition.cs +++ b/Aetheria.State/Documents/AetheriaItemDefinition.cs @@ -105,6 +105,9 @@ public sealed class AetheriaItemDefinition [Key(30)] public AetheriaItemHardpoint[] Hardpoints { get; set; } = []; + + [Key(31)] + public AetheriaBehaviorPayload[] BehaviorPayloads { get; set; } = []; } [MessagePackObject] @@ -150,3 +153,64 @@ public sealed class AetheriaItemHardpoint [Key(9)] public double Armor { get; set; } } + +[MessagePackObject] +public sealed class AetheriaBehaviorPayload +{ + [Key(0)] + public int UnionKey { get; set; } + + [Key(1)] + public string Kind { get; set; } = ""; + + [Key(2)] + public int Group { get; set; } + + [Key(3)] + public AetheriaBehaviorField[] Fields { get; set; } = []; +} + +[MessagePackObject] +public sealed class AetheriaBehaviorField +{ + [Key(0)] + public int Key { get; set; } + + [Key(1)] + public AetheriaBehaviorValue Value { get; set; } = new(); +} + +[MessagePackObject] +public sealed class AetheriaBehaviorMapEntry +{ + [Key(0)] + public string Key { get; set; } = ""; + + [Key(1)] + public AetheriaBehaviorValue Value { get; set; } = new(); +} + +[MessagePackObject] +public sealed class AetheriaBehaviorValue +{ + [Key(0)] + public string Kind { get; set; } = "nil"; + + [Key(1)] + public string StringValue { get; set; } = ""; + + [Key(2)] + public double NumberValue { get; set; } + + [Key(3)] + public bool BoolValue { get; set; } + + [Key(4)] + public string LegacyIdValue { get; set; } = ""; + + [Key(5)] + public AetheriaBehaviorValue[] Children { get; set; } = []; + + [Key(6)] + public AetheriaBehaviorMapEntry[] MapEntries { get; set; } = []; +} diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 14d18ecdf..184cdc59e 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -48,15 +48,14 @@ into `Aetheria.State`. The current checked-in catalog maps to 115 item definitions, 12 factions, and 12 name files. Typed item definitions include legacy manufacturer IDs, price, shape dimensions, occupied cell counts, full shape-cell masks, interior shape masks for hull/cargo equipment, hull hardpoint -definitions, and -stable equipment facets: hardpoint type, hull type, behavior kind fingerprints, -stack size, durability, and weapon range/caliber/type/fire/modifier -classifications. +definitions, behavior kind fingerprints, typed recursive behavior payloads, and +stable equipment facets: hardpoint type, hull type, stack size, durability, and +weapon range/caliber/type/fire/modifier classifications. Typed corporation documents include the legacy short name, the true description field from key 3, name-file and boss-hull legacy IDs, influence distance, -allegiance count, and music bank IDs. Runtime object graphs, full behavior -payloads, and simulation state remain legacy until dedicated typed documents -exist. +allegiance count, and music bank IDs. Runtime object graphs, typed behavior +factory construction, and simulation state remain legacy until dedicated typed +runtime documents exist. Legacy GUID references that are `Guid.Empty` are imported as absent references, not as resolvable catalog links. @@ -77,10 +76,10 @@ It opens `aetheria-world.cc`, emits immutable catalog read models for trade, equipment, behavior, hardpoint, manufacturer, corporation, and name-file queries, exposes typed item shape masks for layout and fitting consumers, and exposes typed interior masks and hardpoints for equipment/cargo layout -consumers, and can read the published Eve catalog surface. It does not -deserialize legacy `DatabaseEntry` objects and does not write state. Unity can -use this as the first package boundary once CultLib/Eve runtime packaging is -available. +consumers, exposes typed behavior payload read models, and can read the +published Eve catalog surface. It does not deserialize legacy `DatabaseEntry` +objects and does not write state. Unity can use this as the first package +boundary once CultLib/Eve runtime packaging is available. `Aetheria.State.Verify` opens a materialized state file and checks that the typed migration ledger matches the actual item, corporation, and name-file @@ -88,9 +87,12 @@ records in the `.cc` store. It also verifies the canonical legacy-ID lookup API for one migrated record of each catalog kind and checks that expanded item and corporation catalog facts survived materialization. The verifier also exercises the typed catalog snapshot query surface, shape-mask invariants, and the -published Eve catalog surface. Legacy content currently includes at least one -overhanging hardpoint; the verifier preserves the payload and checks hardpoint -local masks rather than silently converting content repair into migration. +typed behavior payload tree, and the published Eve catalog surface. The current +catalog materializes 65 behavior payloads, 661 behavior fields, and 18 +behavior-side legacy-ID references. Legacy content currently includes at least +one overhanging hardpoint; the verifier preserves the payload and checks +hardpoint local masks rather than silently converting content repair into +migration. Use it after import when `GameData/aetheria-world.cc` changes. `Aetheria.State.Unity.Smoke` opens the materialized state through the runtime diff --git a/GameData/aetheria-world.cc b/GameData/aetheria-world.cc index 137530c0b462e3f4334ccad64cb606c77b3afe53..8078af039560b6b97e7aff280f86ad03071ab166 100644 GIT binary patch delta 338 zcmbu4JxT;Y5JnwvgE3iH5kwT-5sW72->E4BS$8fVdV=byZdB9-1;HzbMvY*g;1L!k z_Zp(Csj)|}<4KJ^c)@#okN4TrLpFcOCqWRlQh_%Pg5+RS1r=KjEJR>3=&U3oLd~oY z3_E27DyvWl@{%zw@`HvzXtEVpkfw4rjYCBy5zRW;1p!EsE)i_&)(00*Afm5r>euRP z^(0R0di=?Uagc3xZ!5>6_&dlqv@DA&y7a|00JaO-t>?Rk6N^WS``_`WypXScu6j1mo<_2;0YW$+q;NXEQKNj>Q!PPwn8N2kQCaXKAd97#iP$a zku3l+1SQ5O88Pf#&H|ay0tbj>h9F#6jV1Zepvh