diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index b7a170583..3c0480543 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs index d1d3f9924..de4bc0db1 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -347,6 +347,25 @@ try Duration = 5.0 } }, + BehaviorProgress = new[] + { + new AetheriaRuntimeBehaviorProgressCommit + { + OwnerKind = "equipment", + OwnerIndex = 0, + BehaviorIndex = 1, + BehaviorKind = "Cooldown", + Progress = 0.5 + }, + new AetheriaRuntimeBehaviorProgressCommit + { + OwnerKind = "active_consumable", + OwnerIndex = 0, + BehaviorIndex = 0, + BehaviorKind = "Shield", + Progress = 0.75 + } + }, StatGrids = new[] { new AetheriaRuntimeEntityStatGridCommit @@ -434,6 +453,12 @@ try entity.ActiveConsumables.Length == 1 && entity.ActiveConsumables[0].ItemKey == "aetheria.item_definition:legacy:smoke:consumable" && entity.ActiveConsumables[0].RemainingDuration == 3.0 && + entity.BehaviorProgress.Length == 2 && + entity.BehaviorProgress[0].OwnerKind == "equipment" && + entity.BehaviorProgress[0].BehaviorKind == "Cooldown" && + entity.BehaviorProgress[0].Progress == 0.5 && + entity.BehaviorProgress[1].OwnerKind == "active_consumable" && + entity.BehaviorProgress[1].Progress == 0.75 && entity.WeaponGroups.Length == 1 && entity.StatGrids.Length == 2 && entity.StatGrids.Any(grid => grid.Name == "temperature" && grid.Values.Length == 2 && grid.Values[1] == 281.0) && diff --git a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs index c6907d188..8f3884892 100644 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs @@ -440,10 +440,26 @@ public static class AetheriaRuntimeCommitLogApplier .Select(group => new AetheriaWeaponGroupSnapshot { EquipmentIndices = group }) .ToArray(), StatGrids = ToEntityStatGrids(entity.StatGrids), - ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables) + ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables), + BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress) }; } + private static AetheriaBehaviorProgressSnapshot[] ToBehaviorProgress( + IReadOnlyList? progress) + { + return (progress ?? Array.Empty()) + .Select(entry => new AetheriaBehaviorProgressSnapshot + { + OwnerKind = entry.OwnerKind ?? "", + OwnerIndex = entry.OwnerIndex, + BehaviorIndex = entry.BehaviorIndex, + BehaviorKind = entry.BehaviorKind ?? "", + Progress = entry.Progress + }) + .ToArray(); + } + private static AetheriaActiveConsumableSnapshot[] ToActiveConsumables( IReadOnlyList? consumables) { diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index fcd9336ba..2ba8c0e6e 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -482,6 +482,9 @@ public sealed class AetheriaEntitySnapshot [Key(20)] public AetheriaActiveConsumableSnapshot[] ActiveConsumables { get; set; } = []; + + [Key(21)] + public AetheriaBehaviorProgressSnapshot[] BehaviorProgress { get; set; } = []; } [MessagePackObject] @@ -534,6 +537,25 @@ public sealed class AetheriaActiveConsumableSnapshot public double Duration { get; set; } } +[MessagePackObject] +public sealed class AetheriaBehaviorProgressSnapshot +{ + [Key(0)] + public string OwnerKind { get; set; } = ""; + + [Key(1)] + public int OwnerIndex { get; set; } = -1; + + [Key(2)] + public int BehaviorIndex { get; set; } = -1; + + [Key(3)] + public string BehaviorKind { get; set; } = ""; + + [Key(4)] + public double Progress { get; set; } +} + [MessagePackObject] public sealed class AetheriaActionBarBinding { diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index a43d8d701..be72265ac 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -787,10 +787,49 @@ public class ActionGameManager : MonoBehaviour .Select(group => (IReadOnlyList)group.items.Select(item => entity.Equipment.IndexOf(item)).Where(index => index >= 0).ToArray()) .ToArray() ?? Array.Empty>(), StatGrids = ProjectEntityStatGrids(entity), - ActiveConsumables = ProjectActiveConsumables(entity) + ActiveConsumables = ProjectActiveConsumables(entity), + BehaviorProgress = ProjectBehaviorProgress(entity) }; } + private static AetheriaRuntimeBehaviorProgressCommit[] ProjectBehaviorProgress(Entity entity) + { + var progress = new List(); + + for (var ownerIndex = 0; ownerIndex < entity.Equipment.Count; ownerIndex++) + progress.AddRange(ProjectBehaviorProgress("equipment", ownerIndex, entity.Equipment[ownerIndex].Behaviors)); + + var activeConsumables = entity.ActiveConsumables; + for (var ownerIndex = 0; ownerIndex < activeConsumables.Count; ownerIndex++) + progress.AddRange(ProjectBehaviorProgress("active_consumable", ownerIndex, activeConsumables[ownerIndex].Behaviors)); + + return progress.ToArray(); + } + + private static IEnumerable ProjectBehaviorProgress( + string ownerKind, + int ownerIndex, + IReadOnlyList behaviors) + { + if (behaviors == null) + yield break; + + for (var behaviorIndex = 0; behaviorIndex < behaviors.Count; behaviorIndex++) + { + if (!(behaviors[behaviorIndex] is IProgressBehavior progressBehavior)) + continue; + + yield return new AetheriaRuntimeBehaviorProgressCommit + { + OwnerKind = ownerKind, + OwnerIndex = ownerIndex, + BehaviorIndex = behaviorIndex, + BehaviorKind = behaviors[behaviorIndex].Data?.Kind ?? "", + Progress = progressBehavior.Progress + }; + } + } + private static AetheriaRuntimeActiveConsumableCommit[] ProjectActiveConsumables(Entity entity) { return entity.ActiveConsumables? diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs index 8e01bd9f2..964fd0046 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -558,6 +558,9 @@ namespace GameCult.Aetheria.State.Unity [Key(25)] public IReadOnlyList ActiveConsumables { get; set; } = Array.Empty(); + + [Key(26)] + public IReadOnlyList BehaviorProgress { get; set; } = Array.Empty(); } [MessagePackObject] @@ -591,4 +594,23 @@ namespace GameCult.Aetheria.State.Unity [Key(3)] public double Duration { get; set; } } + + [MessagePackObject] + public sealed class AetheriaRuntimeBehaviorProgressCommit + { + [Key(0)] + public string OwnerKind { get; set; } = ""; + + [Key(1)] + public int OwnerIndex { get; set; } = -1; + + [Key(2)] + public int BehaviorIndex { get; set; } = -1; + + [Key(3)] + public string BehaviorKind { get; set; } = ""; + + [Key(4)] + public double Progress { get; set; } + } } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 0327f4869..813fb14d9 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -142,6 +142,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. They also carry public runtime session state: velocity, target entity reference, active flag, heatsink toggle, shutdown override, tractor power, heatstroke/hypothermia accumulators, and active consumable item/timer rows. + 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. `RuntimeEntityBlueprint` still exists for construction/loadout projection, but those live hull grids and session scalars are not blueprint fields and cannot be restored by the old blueprint projector path. @@ -761,13 +764,17 @@ First Aetheria surfaces to publish: entity session state into `AetheriaEntitySnapshot`: velocity, target references, active/toggle scalars, thermal injury accumulators, and active consumable timers. + - Done: extend run checkpoint commits and smoke coverage to carry typed + behavior progress rows for equipped-item and active-consumable behaviors + that expose `IProgressBehavior`. - Done: cut live simulation grids out of `RuntimeEntityBlueprint`; loadout and construction templates no longer capture or restore temperature, armor, max-armor, or hull-conductivity state. - Remaining: add typed documents/mappers for runtime object graphs, typed behavior factory construction, behavior-private simulation state - such as weapon cooldown/charge/ammo and sensor/shield progress, and any - catalog fields not covered by the stable scalar/fingerprint/payload pass. + such as weapon cooldown/charge/ammo internals and sensor ping state, and + any catalog fields not covered by the stable scalar/fingerprint/payload + pass. 4. Runtime cutover - Done: add a Unity-facing typed catalog read facade and smoke proving it can @@ -1159,7 +1166,8 @@ First Aetheria surfaces to publish: zone -> entity snapshot graph as the `.zone` replacement. The zone smoke includes orbit/body rows for generated celestial state, and the entity smoke includes typed simulation stat grids, public entity session scalars, and - active consumable timer rows. The Unity runtime smoke also proves + active consumable timer rows, plus typed behavior progress rows for + `IProgressBehavior` surfaces. The Unity runtime smoke also proves package-owned readback of typed loadout templates from `.cc` records. It also proves `aetheria.runtime_session.v1` is advertised and survives reopen as the typed daemon-session signal.