diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 4fedda7f7..6714b531d 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs index d85150b74..be6d66656 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -185,7 +185,8 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s { ItemKey = AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString(), Quality = 0.95, - Durability = 0.8 + Durability = 0.8, + Enabled = false } } ], @@ -209,7 +210,8 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s ItemKey = AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString(), Quality = 0.9, Durability = 0.8, - Quantity = 1 + Quantity = 1, + Enabled = false } ], CargoContents = @@ -609,6 +611,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether if (loadout?.RootEntity.Hull.ItemKey != AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString() || loadout.RootEntity.Equipment.Length != 1 || + loadout.RootEntity.Equipment[0].Item.Enabled || loadout.RootEntity.WeaponGroups.Length != 1) { throw new InvalidOperationException("Loadout template did not survive flush/reopen."); @@ -646,6 +649,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether entitySnapshot.Equipment[0].Quality != 0.9 || entitySnapshot.Equipment[0].Durability != 0.8 || entitySnapshot.Equipment[0].Quantity != 1 || + entitySnapshot.Equipment[0].Enabled || entitySnapshot.CargoContents.Length != 1 || entitySnapshot.CargoContents[0].Items[0].Item.Quantity != 7 || entitySnapshot.DockingBayAssignments.Length != 1 || diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs index db0952155..38b9b0bd7 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -184,7 +184,8 @@ try { ItemDefinitionLegacyId = "smoke:weapon", Quality = 0.9, - Durability = 0.8 + Durability = 0.8, + Enabled = false } } }, @@ -343,7 +344,8 @@ try { ItemDefinitionLegacyId = "smoke:weapon", Quality = 0.9, - Durability = 0.8 + Durability = 0.8, + Enabled = false } } }, @@ -667,6 +669,7 @@ try loadout.RootEntity.Hull.ItemKey == "aetheria.item_definition:legacy:smoke:hull" && loadout.RootEntity.Equipment.Length == 1 && loadout.RootEntity.Equipment[0].Position.X == 1 && + !loadout.RootEntity.Equipment[0].Item.Enabled && loadout.RootEntity.CargoContents.Length == 1 && loadout.RootEntity.CargoContents[0].Items[0].Item.Quantity == 5 && run?.ZoneKeys.Length == 1 && @@ -702,6 +705,7 @@ try entity.Equipment[0].Quality == 0.9 && entity.Equipment[0].Durability == 0.8 && entity.Equipment[0].Quantity == 1 && + !entity.Equipment[0].Enabled && entity.Velocity.X == 4.0 && entity.Velocity.Y == -2.0 && entity.TargetEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" && @@ -816,6 +820,7 @@ try packageLoadouts.Count != 1 || packageLoadouts[0].RootEntity.Hull.ItemKey != "aetheria.item_definition:legacy:smoke:hull" || packageLoadouts[0].RootEntity.Equipment[0].X != 1 || + packageLoadouts[0].RootEntity.Equipment[0].Item.Enabled || packageLoadouts[0].RootEntity.CargoContents[0].Items[0].Item.Quantity != 5 || packageRuns.Count != 1 || packageRuns[0].RunId != "smoke-run" || @@ -841,6 +846,7 @@ try packageEntities[0].Equipment[0].Quality != 0.9 || packageEntities[0].Equipment[0].Durability != 0.8 || packageEntities[0].Equipment[0].Quantity != 1 || + packageEntities[0].Equipment[0].Enabled || packageEntities[0].VelocityX != 4.0 || packageEntities[0].VelocityY != -2.0 || packageEntities[0].TargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || diff --git a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs index 32f9553f8..18f317221 100644 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs @@ -168,7 +168,8 @@ public static class AetheriaRuntimeCommitLogApplier ItemKey = ReferenceKey("aetheria.item_definition", item.ItemDefinitionLegacyId ?? ""), Quality = item.Quality, Durability = item.Durability, - Quantity = item.Quantity + Quantity = item.Quantity, + Enabled = item.Enabled }; } @@ -205,7 +206,8 @@ public static class AetheriaRuntimeCommitLogApplier ItemKey = item.ItemKey, Quality = item.Quality, Durability = item.Durability, - Quantity = item.Quantity + Quantity = item.Quantity, + Enabled = item.Enabled }; }) .ToArray(); diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index 3705f65d7..6096bad4e 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -168,6 +168,9 @@ public sealed class AetheriaLoadoutItem [Key(3)] public int Quantity { get; set; } = 1; + + [Key(4)] + public bool Enabled { get; set; } = true; } [MessagePackObject] @@ -568,6 +571,9 @@ public sealed class AetheriaEntityItemSlot [Key(4)] public int Quantity { get; set; } = 1; + + [Key(5)] + public bool Enabled { get; set; } = true; } [MessagePackObject] diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 48ca71776..183a6d7a1 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -260,8 +260,12 @@ public class ActionGameManager : MonoBehaviour return ItemManager.CreateSimpleCommodityInstance(typedItem, Math.Max(1, item.Quantity)); var instance = ItemManager.CreateCraftedInstance(typedItem, (float)item.Quality); - if (instance is EquippableItem equippable && item.Durability > 0) - equippable.Durability = (float)item.Durability; + if (instance is EquippableItem equippable) + { + equippable.Enabled.Value = item.Enabled; + if (item.Durability > 0) + equippable.Durability = (float)item.Durability; + } return instance; } @@ -524,7 +528,8 @@ public class ActionGameManager : MonoBehaviour ItemDefinitionLegacyId = item.ItemId == Guid.Empty ? "" : item.ItemId.ToString("D"), Quality = item is CraftedItemInstance crafted ? crafted.Quality : 1.0, Durability = item is EquippableItem equippable ? equippable.Durability : 1.0, - Quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1 + Quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1, + Enabled = item is not EquippableItem enabledItem || enabledItem.Enabled.Value }; } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs index e858486bb..56b585e46 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs @@ -636,18 +636,20 @@ namespace GameCult.Aetheria.State.Unity public sealed class AetheriaRuntimeLoadoutItemSnapshot { - public AetheriaRuntimeLoadoutItemSnapshot(string itemKey, double quality, double durability, int quantity) + public AetheriaRuntimeLoadoutItemSnapshot(string itemKey, double quality, double durability, int quantity, bool enabled) { ItemKey = itemKey; Quality = quality; Durability = durability; Quantity = quantity; + Enabled = enabled; } public string ItemKey { get; } public double Quality { get; } public double Durability { get; } public int Quantity { get; } + public bool Enabled { get; } } public sealed class AetheriaRuntimeLoadoutItemSlotSnapshot @@ -973,7 +975,7 @@ namespace GameCult.Aetheria.State.Unity public sealed class AetheriaRuntimeEntityItemSlotSnapshot { - public AetheriaRuntimeEntityItemSlotSnapshot(int x, int y, string itemKey, double quality, double durability, int quantity) + public AetheriaRuntimeEntityItemSlotSnapshot(int x, int y, string itemKey, double quality, double durability, int quantity, bool enabled) { X = x; Y = y; @@ -981,6 +983,7 @@ namespace GameCult.Aetheria.State.Unity Quality = quality; Durability = durability; Quantity = quantity; + Enabled = enabled; } public int X { get; } @@ -989,6 +992,7 @@ namespace GameCult.Aetheria.State.Unity public double Quality { get; } public double Durability { get; } public int Quantity { get; } + public bool Enabled { get; } } public sealed class AetheriaRuntimeEntityStatGridSnapshot diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs index c2cf52761..28301d210 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs @@ -769,14 +769,16 @@ namespace GameCult.Aetheria.State.Unity var quality = ReadFieldDouble(ref reader, slotFields, 2, 1); var durability = ReadFieldDouble(ref reader, slotFields, 3, 1); var quantity = ReadFieldInt32(ref reader, slotFields, 4); - SkipRemaining(ref reader, slotFields, 5); + var enabled = ReadFieldBool(ref reader, slotFields, 5, true); + SkipRemaining(ref reader, slotFields, 6); slots[slot] = new AetheriaRuntimeEntityItemSlotSnapshot( position.X, position.Y, itemKey, quality, durability, - quantity <= 0 ? 1 : quantity); + quantity <= 0 ? 1 : quantity, + enabled); } return slots; @@ -1058,6 +1060,11 @@ namespace GameCult.Aetheria.State.Unity return index < fields && reader.ReadBoolean(); } + private static bool ReadFieldBool(ref MessagePackReader reader, int fields, int index, bool fallback) + { + return index >= fields ? fallback : reader.ReadBoolean(); + } + private static long ReadFieldInt64(ref MessagePackReader reader, int fields, int index) { return index >= fields ? 0 : reader.ReadInt64(); @@ -1220,13 +1227,14 @@ namespace GameCult.Aetheria.State.Unity var quality = ReadFieldDouble(ref reader, itemFields, 1, 1); var durability = ReadFieldDouble(ref reader, itemFields, 2, 1); var quantity = ReadFieldInt32(ref reader, itemFields, 3); - SkipRemaining(ref reader, itemFields, 4); - return new AetheriaRuntimeLoadoutItemSnapshot(itemKey, quality, durability, quantity); + var enabled = ReadFieldBool(ref reader, itemFields, 4, true); + SkipRemaining(ref reader, itemFields, 5); + return new AetheriaRuntimeLoadoutItemSnapshot(itemKey, quality, durability, quantity, enabled); } private static AetheriaRuntimeLoadoutItemSnapshot EmptyLoadoutItem() { - return new AetheriaRuntimeLoadoutItemSnapshot("", 1, 1, 1); + return new AetheriaRuntimeLoadoutItemSnapshot("", 1, 1, 1, true); } private static IReadOnlyList ReadFieldLoadoutItemSlots(ref MessagePackReader reader, int fields, int index) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs index 655e93363..3367fbc56 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -184,6 +184,9 @@ namespace GameCult.Aetheria.State.Unity [Key(3)] public int Quantity { get; set; } = 1; + + [Key(4)] + public bool Enabled { get; set; } = true; } [MessagePackObject] diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index e90de0da0..0032d88f6 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -158,8 +158,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. contact rows for gathered information, hostility, and visible classification, and active consumable item/timer rows. Entity equipment, cargo-bay, and docking-bay slot rows preserve item-instance - facts needed for restore: item key, quality, durability, and quantity. Those - facts are no longer discarded when pending checkpoint commands become + facts needed for restore: item key, quality, durability, quantity, and enabled + state. Those facts are no longer discarded when pending checkpoint commands become canonical `AetheriaEntitySnapshot` documents or package readback DTOs. Cargo bay contents, docking bay contents, and docking bay child assignments also persist as typed snapshot rows, so inventories and docked-child @@ -850,6 +850,8 @@ First Aetheria surfaces to publish: - Done: preserve entity snapshot item-instance facts for equipment, cargo-bay, and docking-bay slot rows: item key, quality, durability, and quantity now survive canonical state and Unity package readback. + - Done: preserve item enabled state on typed loadout items and entity item + slots, so disabled equipment is no longer only runtime blueprint state. - Done: preserve cargo bay contents, docking bay contents, and docking bay child assignments in typed entity snapshots. - Done: preserve aggregate entity visibility and visibility-source count in