diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 3c0480543..624f89c3f 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 de4bc0db1..443241371 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -366,6 +366,46 @@ try Progress = 0.75 } }, + WeaponStates = new[] + { + new AetheriaRuntimeWeaponStateCommit + { + OwnerKind = "equipment", + OwnerIndex = 0, + BehaviorIndex = 1, + BehaviorKind = "InstantWeapon", + Firing = true, + Ammo = 2, + BurstRemaining = 3, + BurstTimer = 0.25, + BurstInterval = 0.125, + CooldownProgress = 0.5, + CoolingDown = true + }, + new AetheriaRuntimeWeaponStateCommit + { + OwnerKind = "equipment", + OwnerIndex = 1, + BehaviorIndex = 0, + BehaviorKind = "ChargedWeapon", + Ammo = 1, + Charging = true, + Charged = false, + Charge = 0.8 + }, + new AetheriaRuntimeWeaponStateCommit + { + OwnerKind = "active_consumable", + OwnerIndex = 0, + BehaviorIndex = 0, + BehaviorKind = "ConstantWeapon", + Firing = true, + Ammo = 1, + Reloading = true, + ReloadProgress = 0.4, + AmmoIntervalProgress = 0.6 + } + }, StatGrids = new[] { new AetheriaRuntimeEntityStatGridCommit @@ -459,6 +499,20 @@ try entity.BehaviorProgress[0].Progress == 0.5 && entity.BehaviorProgress[1].OwnerKind == "active_consumable" && entity.BehaviorProgress[1].Progress == 0.75 && + entity.WeaponStates.Length == 3 && + entity.WeaponStates[0].BehaviorKind == "InstantWeapon" && + entity.WeaponStates[0].Firing && + entity.WeaponStates[0].Ammo == 2 && + entity.WeaponStates[0].BurstRemaining == 3 && + entity.WeaponStates[0].CooldownProgress == 0.5 && + entity.WeaponStates[0].CoolingDown && + entity.WeaponStates[1].BehaviorKind == "ChargedWeapon" && + entity.WeaponStates[1].Charging && + entity.WeaponStates[1].Charge == 0.8 && + entity.WeaponStates[2].OwnerKind == "active_consumable" && + entity.WeaponStates[2].BehaviorKind == "ConstantWeapon" && + entity.WeaponStates[2].Reloading && + entity.WeaponStates[2].AmmoIntervalProgress == 0.6 && 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 8f3884892..7d1decd24 100644 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs @@ -441,10 +441,38 @@ public static class AetheriaRuntimeCommitLogApplier .ToArray(), StatGrids = ToEntityStatGrids(entity.StatGrids), ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables), - BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress) + BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress), + WeaponStates = ToWeaponStates(entity.WeaponStates) }; } + private static AetheriaWeaponStateSnapshot[] ToWeaponStates( + IReadOnlyList? states) + { + return (states ?? Array.Empty()) + .Select(state => new AetheriaWeaponStateSnapshot + { + OwnerKind = state.OwnerKind ?? "", + OwnerIndex = state.OwnerIndex, + BehaviorIndex = state.BehaviorIndex, + BehaviorKind = state.BehaviorKind ?? "", + Firing = state.Firing, + Ammo = state.Ammo, + BurstRemaining = state.BurstRemaining, + BurstTimer = state.BurstTimer, + BurstInterval = state.BurstInterval, + CooldownProgress = state.CooldownProgress, + CoolingDown = state.CoolingDown, + Charging = state.Charging, + Charged = state.Charged, + Charge = state.Charge, + Reloading = state.Reloading, + ReloadProgress = state.ReloadProgress, + AmmoIntervalProgress = state.AmmoIntervalProgress + }) + .ToArray(); + } + private static AetheriaBehaviorProgressSnapshot[] ToBehaviorProgress( IReadOnlyList? progress) { diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index 2ba8c0e6e..49316939c 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -485,6 +485,9 @@ public sealed class AetheriaEntitySnapshot [Key(21)] public AetheriaBehaviorProgressSnapshot[] BehaviorProgress { get; set; } = []; + + [Key(22)] + public AetheriaWeaponStateSnapshot[] WeaponStates { get; set; } = []; } [MessagePackObject] @@ -556,6 +559,61 @@ public sealed class AetheriaBehaviorProgressSnapshot public double Progress { get; set; } } +[MessagePackObject] +public sealed class AetheriaWeaponStateSnapshot +{ + [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 bool Firing { get; set; } + + [Key(5)] + public int Ammo { get; set; } + + [Key(6)] + public int BurstRemaining { get; set; } + + [Key(7)] + public double BurstTimer { get; set; } + + [Key(8)] + public double BurstInterval { get; set; } + + [Key(9)] + public double CooldownProgress { get; set; } + + [Key(10)] + public bool CoolingDown { get; set; } + + [Key(11)] + public bool Charging { get; set; } + + [Key(12)] + public bool Charged { get; set; } + + [Key(13)] + public double Charge { get; set; } + + [Key(14)] + public bool Reloading { get; set; } + + [Key(15)] + public double ReloadProgress { get; set; } + + [Key(16)] + public double AmmoIntervalProgress { get; set; } +} + [MessagePackObject] public sealed class AetheriaActionBarBinding { diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index be72265ac..62038ef39 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -788,10 +788,75 @@ public class ActionGameManager : MonoBehaviour .ToArray() ?? Array.Empty>(), StatGrids = ProjectEntityStatGrids(entity), ActiveConsumables = ProjectActiveConsumables(entity), - BehaviorProgress = ProjectBehaviorProgress(entity) + BehaviorProgress = ProjectBehaviorProgress(entity), + WeaponStates = ProjectWeaponStates(entity) }; } + private static AetheriaRuntimeWeaponStateCommit[] ProjectWeaponStates(Entity entity) + { + var states = new List(); + + for (var ownerIndex = 0; ownerIndex < entity.Equipment.Count; ownerIndex++) + states.AddRange(ProjectWeaponStates("equipment", ownerIndex, entity.Equipment[ownerIndex].Behaviors)); + + var activeConsumables = entity.ActiveConsumables; + for (var ownerIndex = 0; ownerIndex < activeConsumables.Count; ownerIndex++) + states.AddRange(ProjectWeaponStates("active_consumable", ownerIndex, activeConsumables[ownerIndex].Behaviors)); + + return states.ToArray(); + } + + private static IEnumerable ProjectWeaponStates( + string ownerKind, + int ownerIndex, + IReadOnlyList behaviors) + { + if (behaviors == null) + yield break; + + for (var behaviorIndex = 0; behaviorIndex < behaviors.Count; behaviorIndex++) + { + if (!(behaviors[behaviorIndex] is Weapon weapon)) + continue; + + var state = new AetheriaRuntimeWeaponStateCommit + { + OwnerKind = ownerKind, + OwnerIndex = ownerIndex, + BehaviorIndex = behaviorIndex, + BehaviorKind = weapon.Data?.Kind ?? "", + Firing = weapon.Firing, + Ammo = weapon.Ammo + }; + + if (weapon is InstantWeapon instant) + { + state.BurstRemaining = instant.BurstRemaining; + state.BurstTimer = instant.BurstTimer; + state.BurstInterval = instant.BurstInterval; + state.CooldownProgress = instant.CooldownProgress; + state.CoolingDown = instant.CoolingDown; + } + + if (weapon is ChargedWeapon charged) + { + state.Charging = charged.Charging; + state.Charged = charged.Charged; + state.Charge = charged.Charge; + } + + if (weapon is ConstantWeapon constant) + { + state.Reloading = constant.Reloading; + state.ReloadProgress = constant.ReloadProgress; + state.AmmoIntervalProgress = constant.AmmoIntervalProgress; + } + + yield return state; + } + } + private static AetheriaRuntimeBehaviorProgressCommit[] ProjectBehaviorProgress(Entity entity) { var progress = new List(); diff --git a/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs index fed140047..d41740293 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs @@ -95,6 +95,8 @@ public class ChargedWeapon : InstantWeapon { get => _charge; } + public bool Charging => _charging; + public bool Charged => _charged; public ChargedWeapon(ChargedWeaponData data, EquippedItem item) : base(data, item) { diff --git a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs index ee0c842c0..f38c2923c 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs @@ -35,6 +35,9 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior { get => _ammo; } + public float AmmoIntervalProgress => _ammoInterval; + public float ReloadProgress => _reload; + public bool Reloading => _reloading; public float Progress { @@ -147,4 +150,4 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior OnStopFiring?.Invoke(); } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs index b5842a1b0..4f0c5c0d9 100644 --- a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs @@ -59,6 +59,11 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior { get => _ammo; } + public int BurstRemaining => _burstRemaining; + public float BurstTimer => _burstTimer; + public float BurstInterval => _burstInterval; + public float CooldownProgress => _cooldown; + public bool CoolingDown => _coolingDown; public virtual float Progress => saturate(_cooldown); public event Action OnReloadBegin; @@ -197,4 +202,4 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior Trigger(); base.Activate(); } -} \ No newline at end of file +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs index 964fd0046..8d1bff018 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -561,6 +561,9 @@ namespace GameCult.Aetheria.State.Unity [Key(26)] public IReadOnlyList BehaviorProgress { get; set; } = Array.Empty(); + + [Key(27)] + public IReadOnlyList WeaponStates { get; set; } = Array.Empty(); } [MessagePackObject] @@ -613,4 +616,59 @@ namespace GameCult.Aetheria.State.Unity [Key(4)] public double Progress { get; set; } } + + [MessagePackObject] + public sealed class AetheriaRuntimeWeaponStateCommit + { + [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 bool Firing { get; set; } + + [Key(5)] + public int Ammo { get; set; } + + [Key(6)] + public int BurstRemaining { get; set; } + + [Key(7)] + public double BurstTimer { get; set; } + + [Key(8)] + public double BurstInterval { get; set; } + + [Key(9)] + public double CooldownProgress { get; set; } + + [Key(10)] + public bool CoolingDown { get; set; } + + [Key(11)] + public bool Charging { get; set; } + + [Key(12)] + public bool Charged { get; set; } + + [Key(13)] + public double Charge { get; set; } + + [Key(14)] + public bool Reloading { get; set; } + + [Key(15)] + public double ReloadProgress { get; set; } + + [Key(16)] + public double AmmoIntervalProgress { get; set; } + } } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 813fb14d9..9f80cad5b 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -145,6 +145,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. 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. + Weapon behaviors now also publish typed runtime rows for firing state, ammo, + burst timing, cooldown progress, charged-weapon charging/charge state, and + constant-weapon reload/ammo-interval state. `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. @@ -767,14 +770,17 @@ First Aetheria surfaces to publish: - 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: extend run checkpoint commits and smoke coverage to carry typed + weapon behavior state rows for instant, charged, and constant weapon + runtime internals. - 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 internals and sensor ping state, and - any catalog fields not covered by the stable scalar/fingerprint/payload - pass. + such as sensor ping state, radiator/reactor/capacitor runtime internals, + 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 @@ -1167,7 +1173,7 @@ First Aetheria surfaces to publish: 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, plus typed behavior progress rows for - `IProgressBehavior` surfaces. The Unity runtime smoke also proves + `IProgressBehavior` surfaces and typed weapon runtime rows. 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.