diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 624f89c3f..58050242d 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 443241371..865b1104a 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -406,6 +406,52 @@ try AmmoIntervalProgress = 0.6 } }, + BehaviorStates = new[] + { + new AetheriaRuntimeBehaviorStateCommit + { + OwnerKind = "equipment", + OwnerIndex = 0, + BehaviorIndex = 2, + BehaviorKind = "Sensor", + Pinging = true, + PingCooldown = 0.25, + PingLerp = 0.5, + PingRadius = 1200.0, + PingedEntityCount = 3 + }, + new AetheriaRuntimeBehaviorStateCommit + { + OwnerKind = "equipment", + OwnerIndex = 1, + BehaviorIndex = 0, + BehaviorKind = "Radiator", + RadiatorTemperature = 450.0, + Emissivity = 0.8, + PumpedHeat = 12.0, + WasteHeat = 1.5, + EnergyUsage = 2.25 + }, + new AetheriaRuntimeBehaviorStateCommit + { + OwnerKind = "equipment", + OwnerIndex = 2, + BehaviorIndex = 0, + BehaviorKind = "Reactor", + ReactorDraw = 4.5, + ReactorLoadRatio = 1.25 + }, + new AetheriaRuntimeBehaviorStateCommit + { + OwnerKind = "equipment", + OwnerIndex = 3, + BehaviorIndex = 0, + BehaviorKind = "Capacitor", + CapacitorCharge = 7.5, + CapacitorCapacity = 10.0, + CapacitorEfficiency = 0.95 + } + }, StatGrids = new[] { new AetheriaRuntimeEntityStatGridCommit @@ -513,6 +559,18 @@ try entity.WeaponStates[2].BehaviorKind == "ConstantWeapon" && entity.WeaponStates[2].Reloading && entity.WeaponStates[2].AmmoIntervalProgress == 0.6 && + entity.BehaviorStates.Length == 4 && + entity.BehaviorStates[0].BehaviorKind == "Sensor" && + entity.BehaviorStates[0].Pinging && + entity.BehaviorStates[0].PingRadius == 1200.0 && + entity.BehaviorStates[1].BehaviorKind == "Radiator" && + entity.BehaviorStates[1].RadiatorTemperature == 450.0 && + entity.BehaviorStates[1].PumpedHeat == 12.0 && + entity.BehaviorStates[2].BehaviorKind == "Reactor" && + entity.BehaviorStates[2].ReactorLoadRatio == 1.25 && + entity.BehaviorStates[3].BehaviorKind == "Capacitor" && + entity.BehaviorStates[3].CapacitorCharge == 7.5 && + entity.BehaviorStates[3].CapacitorEfficiency == 0.95 && 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 7d1decd24..994ad3b6c 100644 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs @@ -442,10 +442,40 @@ public static class AetheriaRuntimeCommitLogApplier StatGrids = ToEntityStatGrids(entity.StatGrids), ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables), BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress), - WeaponStates = ToWeaponStates(entity.WeaponStates) + WeaponStates = ToWeaponStates(entity.WeaponStates), + BehaviorStates = ToBehaviorStates(entity.BehaviorStates) }; } + private static AetheriaBehaviorStateSnapshot[] ToBehaviorStates( + IReadOnlyList? states) + { + return (states ?? Array.Empty()) + .Select(state => new AetheriaBehaviorStateSnapshot + { + OwnerKind = state.OwnerKind ?? "", + OwnerIndex = state.OwnerIndex, + BehaviorIndex = state.BehaviorIndex, + BehaviorKind = state.BehaviorKind ?? "", + Pinging = state.Pinging, + PingCooldown = state.PingCooldown, + PingLerp = state.PingLerp, + PingRadius = state.PingRadius, + PingedEntityCount = state.PingedEntityCount, + RadiatorTemperature = state.RadiatorTemperature, + Emissivity = state.Emissivity, + PumpedHeat = state.PumpedHeat, + WasteHeat = state.WasteHeat, + EnergyUsage = state.EnergyUsage, + ReactorDraw = state.ReactorDraw, + ReactorLoadRatio = state.ReactorLoadRatio, + CapacitorCharge = state.CapacitorCharge, + CapacitorCapacity = state.CapacitorCapacity, + CapacitorEfficiency = state.CapacitorEfficiency + }) + .ToArray(); + } + private static AetheriaWeaponStateSnapshot[] ToWeaponStates( IReadOnlyList? states) { diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index 49316939c..abd5d2bfe 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -488,6 +488,9 @@ public sealed class AetheriaEntitySnapshot [Key(22)] public AetheriaWeaponStateSnapshot[] WeaponStates { get; set; } = []; + + [Key(23)] + public AetheriaBehaviorStateSnapshot[] BehaviorStates { get; set; } = []; } [MessagePackObject] @@ -614,6 +617,67 @@ public sealed class AetheriaWeaponStateSnapshot public double AmmoIntervalProgress { get; set; } } +[MessagePackObject] +public sealed class AetheriaBehaviorStateSnapshot +{ + [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 Pinging { get; set; } + + [Key(5)] + public double PingCooldown { get; set; } + + [Key(6)] + public double PingLerp { get; set; } + + [Key(7)] + public double PingRadius { get; set; } + + [Key(8)] + public int PingedEntityCount { get; set; } + + [Key(9)] + public double RadiatorTemperature { get; set; } + + [Key(10)] + public double Emissivity { get; set; } + + [Key(11)] + public double PumpedHeat { get; set; } + + [Key(12)] + public double WasteHeat { get; set; } + + [Key(13)] + public double EnergyUsage { get; set; } + + [Key(14)] + public double ReactorDraw { get; set; } + + [Key(15)] + public double ReactorLoadRatio { get; set; } + + [Key(16)] + public double CapacitorCharge { get; set; } + + [Key(17)] + public double CapacitorCapacity { get; set; } + + [Key(18)] + public double CapacitorEfficiency { get; set; } +} + [MessagePackObject] public sealed class AetheriaActionBarBinding { diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 62038ef39..2b9f83ea3 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -789,10 +789,89 @@ public class ActionGameManager : MonoBehaviour StatGrids = ProjectEntityStatGrids(entity), ActiveConsumables = ProjectActiveConsumables(entity), BehaviorProgress = ProjectBehaviorProgress(entity), - WeaponStates = ProjectWeaponStates(entity) + WeaponStates = ProjectWeaponStates(entity), + BehaviorStates = ProjectBehaviorStates(entity) }; } + private static AetheriaRuntimeBehaviorStateCommit[] ProjectBehaviorStates(Entity entity) + { + var states = new List(); + + for (var ownerIndex = 0; ownerIndex < entity.Equipment.Count; ownerIndex++) + states.AddRange(ProjectBehaviorStates("equipment", ownerIndex, entity.Equipment[ownerIndex].Behaviors)); + + var activeConsumables = entity.ActiveConsumables; + for (var ownerIndex = 0; ownerIndex < activeConsumables.Count; ownerIndex++) + states.AddRange(ProjectBehaviorStates("active_consumable", ownerIndex, activeConsumables[ownerIndex].Behaviors)); + + return states.ToArray(); + } + + private static IEnumerable ProjectBehaviorStates( + string ownerKind, + int ownerIndex, + IReadOnlyList behaviors) + { + if (behaviors == null) + yield break; + + for (var behaviorIndex = 0; behaviorIndex < behaviors.Count; behaviorIndex++) + { + var behavior = behaviors[behaviorIndex]; + AetheriaRuntimeBehaviorStateCommit state = null; + + if (behavior is Sensor sensor) + { + state = new AetheriaRuntimeBehaviorStateCommit + { + Pinging = sensor.Pinging, + PingCooldown = sensor.Cooldown, + PingLerp = sensor.PingLerp, + PingRadius = sensor.PingRadius, + PingedEntityCount = sensor.PingedEntityCount + }; + } + else if (behavior is Radiator radiator) + { + state = new AetheriaRuntimeBehaviorStateCommit + { + RadiatorTemperature = radiator.RadiatorTemperature, + Emissivity = radiator.Emissivity, + PumpedHeat = radiator.PumpedHeat, + WasteHeat = radiator.WasteHeat, + EnergyUsage = radiator.EnergyUsage + }; + } + else if (behavior is Reactor reactor) + { + state = new AetheriaRuntimeBehaviorStateCommit + { + ReactorDraw = reactor.Draw, + ReactorLoadRatio = reactor.CurrentLoadRatio + }; + } + else if (behavior is Capacitor capacitor) + { + state = new AetheriaRuntimeBehaviorStateCommit + { + CapacitorCharge = capacitor.Charge, + CapacitorCapacity = capacitor.Capacity, + CapacitorEfficiency = capacitor.Efficiency + }; + } + + if (state == null) + continue; + + state.OwnerKind = ownerKind; + state.OwnerIndex = ownerIndex; + state.BehaviorIndex = behaviorIndex; + state.BehaviorKind = behavior.Data?.Kind ?? ""; + yield return state; + } + } + private static AetheriaRuntimeWeaponStateCommit[] ProjectWeaponStates(Entity entity) { var states = new List(); diff --git a/Assets/Scripts/ServerShared/Behaviors/Sensor.cs b/Assets/Scripts/ServerShared/Behaviors/Sensor.cs index d8156c217..5f4e8fdc8 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Sensor.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Sensor.cs @@ -66,6 +66,9 @@ public class Sensor : Behavior, IEventBehavior { get => _pingRadius; } + public float PingLerp => _pingLerp; + public bool Pinging => _pinging; + public int PingedEntityCount => _pingedEntities.Count; public float PingBrightness => pow(1 - _pingLerp, _data.PingRadiusExponent); @@ -153,4 +156,4 @@ public class Sensor : Behavior, IEventBehavior } return true; } -} \ 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 8d1bff018..4163a2557 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -564,6 +564,9 @@ namespace GameCult.Aetheria.State.Unity [Key(27)] public IReadOnlyList WeaponStates { get; set; } = Array.Empty(); + + [Key(28)] + public IReadOnlyList BehaviorStates { get; set; } = Array.Empty(); } [MessagePackObject] @@ -671,4 +674,65 @@ namespace GameCult.Aetheria.State.Unity [Key(16)] public double AmmoIntervalProgress { get; set; } } + + [MessagePackObject] + public sealed class AetheriaRuntimeBehaviorStateCommit + { + [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 Pinging { get; set; } + + [Key(5)] + public double PingCooldown { get; set; } + + [Key(6)] + public double PingLerp { get; set; } + + [Key(7)] + public double PingRadius { get; set; } + + [Key(8)] + public int PingedEntityCount { get; set; } + + [Key(9)] + public double RadiatorTemperature { get; set; } + + [Key(10)] + public double Emissivity { get; set; } + + [Key(11)] + public double PumpedHeat { get; set; } + + [Key(12)] + public double WasteHeat { get; set; } + + [Key(13)] + public double EnergyUsage { get; set; } + + [Key(14)] + public double ReactorDraw { get; set; } + + [Key(15)] + public double ReactorLoadRatio { get; set; } + + [Key(16)] + public double CapacitorCharge { get; set; } + + [Key(17)] + public double CapacitorCapacity { get; set; } + + [Key(18)] + public double CapacitorEfficiency { get; set; } + } } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 9f80cad5b..19cf7228b 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -148,6 +148,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. 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. + Sensor, radiator, reactor, and capacitor behaviors publish typed state rows + for ping state, radiator temperature/throughput, reactor draw/load, and + capacitor charge/capacity/efficiency. `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. @@ -773,12 +776,15 @@ First Aetheria surfaces to publish: - Done: extend run checkpoint commits and smoke coverage to carry typed weapon behavior state rows for instant, charged, and constant weapon runtime internals. + - Done: extend run checkpoint commits and smoke coverage to carry typed + behavior state rows for sensor ping state plus radiator, reactor, and + capacitor 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 sensor ping state, radiator/reactor/capacitor runtime internals, + typed behavior factory construction, remaining behavior-private state not + covered by progress, weapon, sensor, radiator, reactor, or capacitor rows, and any catalog fields not covered by the stable scalar/fingerprint/ payload pass. @@ -1173,7 +1179,8 @@ 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 and typed weapon runtime rows. The Unity runtime smoke also proves + `IProgressBehavior` surfaces, typed weapon runtime rows, and typed + sensor/radiator/reactor/capacitor behavior state 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.