diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 92e2cc499..9e2369172 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 edea6dc19..70f665cfe 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -212,6 +212,25 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s Quantity = 1 } ], + CargoContents = + [ + new AetheriaCargoBayLoadout + { + Items = + [ + new AetheriaLoadoutItemSlot + { + Position = new AetheriaGridCoord { X = 2, Y = 3 }, + Item = new AetheriaLoadoutItem + { + ItemKey = AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString(), + Quantity = 7 + } + } + ] + } + ], + DockingBayAssignments = [-1], WeaponGroups = [ new AetheriaWeaponGroupSnapshot @@ -471,6 +490,10 @@ 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.CargoContents.Length != 1 || + entitySnapshot.CargoContents[0].Items[0].Item.Quantity != 7 || + entitySnapshot.DockingBayAssignments.Length != 1 || + entitySnapshot.DockingBayAssignments[0] != -1 || entitySnapshot.WeaponGroups.Length != 1 || entitySnapshot.StatGrids.Length != 1 || entitySnapshot.StatGrids[0].Values.Length != 4) diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs index b08a4750f..134401f27 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -337,6 +337,26 @@ try } } }, + CargoContents = new[] + { + new AetheriaRuntimeCargoBayLoadoutCommit + { + Items = new[] + { + new AetheriaRuntimeLoadoutItemSlotCommit + { + X = 2, + Y = 3, + Item = new AetheriaRuntimeLoadoutItemCommit + { + ItemDefinitionLegacyId = "smoke:ore", + Quantity = 7 + } + } + } + } + }, + DockingBayAssignments = new[] { -1 }, WeaponGroups = new[] { new[] { 0 } }, ActiveConsumables = new[] { @@ -542,6 +562,10 @@ try entity.Heatstroke == 0.25 && entity.Hypothermia == 0.125 && entity.ActiveConsumables.Length == 1 && + entity.CargoContents.Length == 1 && + entity.CargoContents[0].Items[0].Item.Quantity == 7 && + entity.DockingBayAssignments.Length == 1 && + entity.DockingBayAssignments[0] == -1 && entity.ActiveConsumables[0].ItemKey == "aetheria.item_definition:legacy:smoke:consumable" && entity.ActiveConsumables[0].RemainingDuration == 3.0 && entity.BehaviorProgress.Length == 2 && @@ -621,6 +645,10 @@ try packageEntities[0].VelocityY != -2.0 || packageEntities[0].TargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || packageEntities[0].ActiveConsumables.Count != 1 || + packageEntities[0].CargoContents.Count != 1 || + packageEntities[0].CargoContents[0].Items[0].Item.Quantity != 7 || + packageEntities[0].DockingBayAssignments.Count != 1 || + packageEntities[0].DockingBayAssignments[0] != -1 || packageEntities[0].ActiveConsumables[0].ItemKey != "aetheria.item_definition:legacy:smoke:consumable" || packageEntities[0].BehaviorProgress.Count != 2 || packageEntities[0].WeaponStates.Count != 3 || diff --git a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs index 0c1836c47..f059fa2f5 100644 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs @@ -455,7 +455,10 @@ public static class AetheriaRuntimeCommitLogApplier ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables), BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress), WeaponStates = ToWeaponStates(entity.WeaponStates), - BehaviorStates = ToBehaviorStates(entity.BehaviorStates) + BehaviorStates = ToBehaviorStates(entity.BehaviorStates), + CargoContents = ToCargoBays(entity.CargoContents), + DockingBayContents = ToCargoBays(entity.DockingBayContents), + DockingBayAssignments = ToIntArray(entity.DockingBayAssignments) }; } diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index 7325e39a9..ffc55f8b6 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -494,6 +494,15 @@ public sealed class AetheriaEntitySnapshot [Key(23)] public AetheriaBehaviorStateSnapshot[] BehaviorStates { get; set; } = []; + + [Key(24)] + public AetheriaCargoBayLoadout[] CargoContents { get; set; } = []; + + [Key(25)] + public AetheriaCargoBayLoadout[] DockingBayContents { get; set; } = []; + + [Key(26)] + public int[] DockingBayAssignments { get; set; } = []; } [MessagePackObject] diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 0b114d78e..4e7593fc1 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -780,6 +780,11 @@ public class ActionGameManager : MonoBehaviour Equipment = ProjectEquippedSlots(entity.Equipment), CargoBays = ProjectEquippedSlots(entity.CargoBays), DockingBays = ProjectEquippedSlots(entity.DockingBays), + CargoContents = ProjectRuntimeCargoBays(entity.CargoBays), + DockingBayContents = ProjectRuntimeCargoBays(entity.DockingBays), + DockingBayAssignments = entity.DockingBays? + .Select(bay => entity.Children.IndexOf(bay.DockedShip)) + .ToArray() ?? Array.Empty(), ChildEntityIndices = entity.Children? .Select(child => zone.Entities.IndexOf(child)) .Where(index => index >= 0) @@ -795,6 +800,24 @@ public class ActionGameManager : MonoBehaviour }; } + private static AetheriaRuntimeCargoBayLoadoutCommit[] ProjectRuntimeCargoBays(IEnumerable bays) + where TBay : EquippedCargoBay + { + return bays? + .Select(bay => new AetheriaRuntimeCargoBayLoadoutCommit + { + Items = bay.Cargo? + .Select(slot => new AetheriaRuntimeLoadoutItemSlotCommit + { + X = slot.Value.x, + Y = slot.Value.y, + Item = ProjectLoadoutItem(slot.Key) + }) + .ToArray() ?? Array.Empty() + }) + .ToArray() ?? Array.Empty(); + } + private static AetheriaRuntimeBehaviorStateCommit[] ProjectBehaviorStates(Entity entity) { var states = new List(); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs index 43b29d93c..cdb8439d0 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs @@ -783,7 +783,10 @@ namespace GameCult.Aetheria.State.Unity IReadOnlyList activeConsumables, IReadOnlyList behaviorProgress, IReadOnlyList weaponStates, - IReadOnlyList behaviorStates) + IReadOnlyList behaviorStates, + IReadOnlyList cargoContents, + IReadOnlyList dockingBayContents, + IReadOnlyList dockingBayAssignments) { Name = name; Kind = kind; @@ -813,6 +816,9 @@ namespace GameCult.Aetheria.State.Unity BehaviorProgress = behaviorProgress; WeaponStates = weaponStates; BehaviorStates = behaviorStates; + CargoContents = cargoContents; + DockingBayContents = dockingBayContents; + DockingBayAssignments = dockingBayAssignments; } public string Name { get; } @@ -843,6 +849,9 @@ namespace GameCult.Aetheria.State.Unity public IReadOnlyList BehaviorProgress { get; } public IReadOnlyList WeaponStates { get; } public IReadOnlyList BehaviorStates { get; } + public IReadOnlyList CargoContents { get; } + public IReadOnlyList DockingBayContents { get; } + public IReadOnlyList DockingBayAssignments { get; } } public sealed class AetheriaRuntimeActionBarBindingSnapshot diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs index 52e6fabde..76da4267b 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs @@ -532,7 +532,10 @@ namespace GameCult.Aetheria.State.Unity var behaviorProgress = ReadFieldBehaviorProgress(ref reader, fields, 21); var weaponStates = ReadFieldWeaponStates(ref reader, fields, 22); var behaviorStates = ReadFieldBehaviorStates(ref reader, fields, 23); - SkipRemaining(ref reader, fields, 24); + var cargoContents = ReadFieldCargoBayLoadouts(ref reader, fields, 24); + var dockingBayContents = ReadFieldCargoBayLoadouts(ref reader, fields, 25); + var dockingBayAssignments = ReadFieldInt32Array(ref reader, fields, 26); + SkipRemaining(ref reader, fields, 27); return new AetheriaRuntimeEntitySnapshot( name, kind, @@ -561,7 +564,10 @@ namespace GameCult.Aetheria.State.Unity activeConsumables, behaviorProgress, weaponStates, - behaviorStates); + behaviorStates, + cargoContents, + dockingBayContents, + dockingBayAssignments); } private static Vector2Value ReadFieldVector2(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 3b3fa66e8..6eb706a2c 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -570,6 +570,15 @@ namespace GameCult.Aetheria.State.Unity [Key(28)] public IReadOnlyList BehaviorStates { get; set; } = Array.Empty(); + + [Key(29)] + public IReadOnlyList CargoContents { get; set; } = Array.Empty(); + + [Key(30)] + public IReadOnlyList DockingBayContents { get; set; } = Array.Empty(); + + [Key(31)] + public IReadOnlyList DockingBayAssignments { get; set; } = Array.Empty(); } [MessagePackObject] diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 350bc437a..70751b7f6 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -157,6 +157,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. facts needed for restore: item key, quality, durability, and quantity. 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 + placement do not need a bespoke `.zone` or runtime-blueprint save payload. Equipped-item and active-consumable behaviors that intentionally expose `IProgressBehavior` now publish typed progress rows keyed by owner kind, owner index, behavior index, behavior kind, and progress value. @@ -807,6 +810,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 cargo bay contents, docking bay contents, and docking bay + child assignments in typed entity snapshots. - Remaining: add typed documents/mappers for runtime object graphs, typed behavior factory construction, remaining behavior-private state not covered by progress, weapon, sensor, radiator, reactor, or capacitor rows,