From 16a92b0173ccbef815a75b73652c6c19f0d7d532 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 14:29:04 +0100 Subject: [PATCH] Persist behavior runtime state in checkpoints --- .voidbot/state/aetheria.cc | Bin 86575 -> 86763 bytes Aetheria.State.Unity.Smoke/Program.cs | 58 +++++++++++++ .../AetheriaRuntimeCommitLogApplier.cs | 32 ++++++- .../AetheriaRuntimeStateDocuments.cs | 64 ++++++++++++++ Assets/Scripts/Gameplay/ActionGameManager.cs | 81 +++++++++++++++++- .../Scripts/ServerShared/Behaviors/Sensor.cs | 5 +- .../AetheriaRuntimeStateCommitDocument.cs | 64 ++++++++++++++ docs/aetheria-perfect-machine-map.md | 13 ++- 8 files changed, 311 insertions(+), 6 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 624f89c3fea299aa4768f54116b8832e241ea645..58050242d361d2a43dc614f7ab56f60c57d30088 100644 GIT binary patch delta 1209 zcmZux&ud*p6y_w2q=5>tEt*8i7#CW6dGF>muT5SxCa;YZ(qc(l3aKEIx#zt*?R_&h zGjntM7E($CL05XgS-2A0`eUJPBuf!gy6B>dZWP3oAh_y(;Ca7pBNQ%|xo5t6=6v7z z=6ru=?BBa%KinIcsGh1XER~mP!R9A$H2-zv<04Y)MtpD%p2^cuH$DXBk4I0AG``zC zeqitT0a!fs+zE~@LL>Ry7DjwWYU8(0gN1ef_h~4-s2hg!a~EdO00QUV%>CNn2w2vn-*3EfldXRO(`TQ z(P2_K&0Hrz!bd1>B$^`r{Wsv+dk-#0(lUCilX1*A8x-_ED6^?)a)BkLv}4VbfCepb zz<%mj?%)&2q$1IjKwD-TK4`%7rM`$Z(rFwLjw#-z{CG2osX`ZbSQeoo@lqm0)Tu?x zMO?8+h9PZ`(vo&KYoVg<3AV*s`Sy=T;2;0RG8}%z_7txqIzvO6p=tAYe>o0~;<5Gj zm!Wa1s65l=1{>5Qw6bX%=~T4z?kUynIir5^Fk}j|{n2il`y5gw^bQ(gQ`kYO9ZnWg z&X&I=86s^quxUTr{nB9b_pAQXHzA&U`7Ac2LRwsHvi!2Y6L#}3b* zs8<*3K~N5YVDpaSE&I9ua1E-n_xi_wZ3cJmtHG`Lrnn~lw>4NlIG7L%i}}ORsqPr5 wQLg#kVDTGo!B0i#&&!(9x4+5 z0MnQ`crtfYGz2bp*!UL!B7x=c_UA$~(U*CK4`*{0K_FF&iwSM`~bS{&d z%w&hgZj7wIM&0JZB5JGtXnK`|$HH(`G(T&P6RWl6@uvLg?qJKoU<=AkT+9%vT`Cz# zZ&DHnZqyV~Xu5W0IhA;WaXS^A;YhbqxIkS}W^@qWQ|#M=n#4%sda0AQ57<-_<|!i8suK2q2vi{i+=7+u{(b94*MR?-betrF<3Tv76D zOl-K5_+YNy4&WJ%19kwH3aG^#W+ik1X_>%eaVVA~oHDFk!b3;^JS0nSk-~I94G2>2 zRv^WpkAz#UkPoQSq2BDim()32tJWTmR)3OC^XE3Y-Z?N&9mYZF26A&{2KjyIIA{?r zIJ3aO=d1-|x1?p`w?M5&UYIEo*>KR_;OvB6&ZMmU^ zF^T7Q(2hB3NBymt?BwW(Ii5rVCcl9C&wpwVYItQ{E}-R(=yT8Js=u21;&m{QY>974 mDBcil=5!H>cuh>en&>ev?xLMIF_#Tj$pKzM-#e1n`1(ItTbsTB 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.