From ee60d1d5d35a4d1919cf411a8b6137a4c1df99d9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 16:59:04 +0100 Subject: [PATCH] Persist asteroid belt state in typed snapshots --- .voidbot/state/aetheria.cc | Bin 86208 -> 86260 bytes Aetheria.State.Smoke/Program.cs | 19 +++++++- Aetheria.State.Unity.Smoke/Program.cs | 20 +++++++- .../AetheriaRuntimeCommitLogApplier.cs | 32 +++++++++++-- .../AetheriaRuntimeStateDocuments.cs | 20 ++++++++ Assets/Scripts/Gameplay/ActionGameManager.cs | 37 +++++++++++---- .../Runtime/AetheriaRuntimeCatalogSnapshot.cs | 18 +++++++- .../Runtime/AetheriaRuntimeCatalogStore.cs | 43 +++++++++++++++++- .../AetheriaRuntimeStateCommitDocument.cs | 19 ++++++++ docs/aetheria-perfect-machine-map.md | 8 +++- 10 files changed, 193 insertions(+), 23 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index a673484249eb1773c2976ffa6c3f37136876a583..4fedda7f760bbde19d09fc00dc722a9a5d3230fd 100644 GIT binary patch delta 1280 zcmb7^ziSjh6vvtT&;;xxMz9&Q2!Y%h^6R2T^peXJLNp#p6HUluXD&NtcV?ZL&2h1i zm{t@$aO)r#P$F7g8w9bkursax1p&1aeEaKik$~9Uz1ey1`+e{8W^cRm$9Cttown}r z%R_l9KRUGV4E5AMwmr@Q`8XikAEbe?!8p*>fl*_tzoXILfyVP^`Uo=)Q=H_8HcUxQ zb1TdZ?_WXoP@#~9FoXZWdm3-?USm&Q9`8$FcB1cTrh=+Z3kZ95+fCSRbh$=n?V^z$zauKirA2Xg>NQM zUst9({4#;g-@!WYJfb*DlWI?{F>K3qrl7F3W0=rfiv7294okIbL455iCSWPU4%U99hji@jPv!&2R1-5}?*hox#->rd4so_*sHC`e18s-9yvMx?WO{6@& z5-F^2PWL?gRtij@SX$^JC;;OOSEX9RAS!Svc4|~b9dFHJ71KtRlSa<9^l>KETxjWI zMM>}IplB@8!r}ZWbb67%fVCer%8Ko!u6LGaOa3 zT05ctk13tZLZ7)sHNFlk{o ze19D+)4NkdNWpF59$s(amL)!RC;q(>*49hdkNw&D!O~FpbOa4AE+qzhAP-mvNC}N8 zOjlw9OeD2va{)nhvl9HywKzCbY)hDQ_`85Uhf7mvq;Yl%mC%LV2$fvsW;a`z==m_O z5`x0bA}XFt9;kf%puIQzUPOE8do(_lK2RsZPt(XxqnF}WDx5E&M_D9(*&07f=tFlF H8eRAUcihxS delta 1192 zcmZvcziSjh6vvsEXwsxh4ne{TDI{{2GtvAq(Inno&L0tj2tr8VWOp_@-rmkKGn;T& zL=6an*c>=@3ei;BNE!3Z|BqkHti z*yzZ)^Am>D$f@?nj>jhw%Xs_eu|iuMyPut(*GQq=-3erSd$@CNxRXpx76vKPE|Z*= zEa7Wv>5|e^GZXZZ)sr(dUvu38n`vfgF8p%pWDaIX-jrBTVVy=DF&ns#9!(k*E`lqR<{oxa1nS$c z!PObQ<}tvkN^yr zOfk8^6z0IR2@P-%i2|Q78-1gOa~81+_d{&SOkE^rSH#*qqy&;-tkN=!iN(!IMuWi1 zS`<#6Jpu=7)N0_YT7btl7{+7C=))48mtb!{to3c}R|WV8o7y%|2&klZ?fuh_q8Jp= zmM5q^0u>wpyNmKEEhK|YAsc|}3OyU#mMwA1knHy=a;~S)%ZceqM!SrlW6q0#6w!RR z3774iL4G4Zy_XUBkFhuX^s-+$oFB&bL7sdyPQ;2 zN3>c@ZF#S`GL8~P4ERtv^L>syBV(pDPX>Aq>(lJdle^;+73vEg*XCvV%*A3+DLG2z za9AX3^I(Da}1sBnhwz+S!kM2E{xB z4wp**-8B%Dk? bodies) + IReadOnlyList? bodies, + string runId, + int zoneIndex) { return (bodies ?? Array.Empty()) .Select(body => new AetheriaBodySnapshot @@ -316,7 +318,7 @@ public static class AetheriaRuntimeCommitLogApplier GravityRadiusMultiplier = body.GravityRadiusMultiplier, GravityDepthMultiplier = body.GravityDepthMultiplier, GravityDepthExponent = body.GravityDepthExponent, - Asteroids = ToAsteroidSnapshots(body.Asteroids), + Asteroids = ToAsteroidSnapshots(body.Asteroids, runId, zoneIndex), GasGiantVisual = ToGasGiantVisual(body.GasGiantVisual), SunVisual = ToSunVisual(body.SunVisual) }) @@ -336,7 +338,9 @@ public static class AetheriaRuntimeCommitLogApplier } private static AetheriaAsteroidSnapshot[] ToAsteroidSnapshots( - IReadOnlyList? asteroids) + IReadOnlyList? asteroids, + string runId, + int zoneIndex) { return (asteroids ?? Array.Empty()) .Select(asteroid => new AetheriaAsteroidSnapshot @@ -344,7 +348,25 @@ public static class AetheriaRuntimeCommitLogApplier Distance = asteroid.Distance, Phase = asteroid.Phase, Size = asteroid.Size, - RotationSpeed = asteroid.RotationSpeed + RotationSpeed = asteroid.RotationSpeed, + Damage = asteroid.Damage, + RespawnTimer = asteroid.RespawnTimer, + MiningAccumulators = ToAsteroidMiningAccumulators(asteroid.MiningAccumulators, runId, zoneIndex) + }) + .ToArray(); + } + + private static AetheriaAsteroidMiningAccumulatorSnapshot[] ToAsteroidMiningAccumulators( + IReadOnlyList? accumulators, + string runId, + int zoneIndex) + { + return (accumulators ?? Array.Empty()) + .Where(accumulator => accumulator.MinerEntityIndex >= 0) + .Select(accumulator => new AetheriaAsteroidMiningAccumulatorSnapshot + { + MinerEntityKey = EntityKey(runId, zoneIndex, accumulator.MinerEntityIndex).ToString(), + Amount = accumulator.Amount }) .ToArray(); } diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index 2c074ddac..3705f65d7 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -347,6 +347,26 @@ public sealed class AetheriaAsteroidSnapshot [Key(3)] public double RotationSpeed { get; set; } + + [Key(4)] + public double Damage { get; set; } + + [Key(5)] + public double RespawnTimer { get; set; } + + [Key(6)] + public AetheriaAsteroidMiningAccumulatorSnapshot[] MiningAccumulators { get; set; } = []; +} + +[MessagePackObject] +public sealed class AetheriaAsteroidMiningAccumulatorSnapshot +{ + [Key(0)] + [CultReference(typeof(AetheriaEntitySnapshot))] + public string MinerEntityKey { get; set; } = ""; + + [Key(1)] + public double Amount { get; set; } } [MessagePackObject] diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 08a761b0c..48ca71776 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -625,7 +625,7 @@ public class ActionGameManager : MonoBehaviour .ToArray() ?? Array.Empty(), OwnerFactionIndex = FactionIndex(galaxyZone?.Owner), Orbits = ProjectZoneOrbits(zone.Blueprint), - Bodies = ProjectZoneBodies(zone.Blueprint), + Bodies = ProjectZoneBodies(zone), Entities = zone.Entities .Select((entity, index) => ProjectEntitySnapshot(zone, entity, index)) .ToArray() @@ -648,16 +648,17 @@ public class ActionGameManager : MonoBehaviour .ToArray() ?? Array.Empty(); } - private static AetheriaRuntimeBodySnapshotCommit[] ProjectZoneBodies(RuntimeZoneBlueprint blueprint) + private static AetheriaRuntimeBodySnapshotCommit[] ProjectZoneBodies(Zone zone) { - return blueprint?.Planets? + return zone?.Blueprint?.Planets? .OrderBy(body => body.ID) - .Select(ProjectZoneBody) + .Select(body => ProjectZoneBody(zone, body)) .ToArray() ?? Array.Empty(); } - private static AetheriaRuntimeBodySnapshotCommit ProjectZoneBody(BodyData body) + private static AetheriaRuntimeBodySnapshotCommit ProjectZoneBody(Zone zone, BodyData body) { + zone.AsteroidBelts.TryGetValue(body.ID, out var asteroidBelt); return new AetheriaRuntimeBodySnapshotCommit { BodyLegacyId = LegacyId(body.ID), @@ -678,7 +679,7 @@ public class ActionGameManager : MonoBehaviour GravityDepthMultiplier = body.GravityDepthMultiplier, GravityDepthExponent = body.GravityDepthExponent, Asteroids = body is AsteroidBeltData belt - ? ProjectAsteroids(belt.Asteroids) + ? ProjectAsteroids(zone, belt, asteroidBelt) : Array.Empty(), GasGiantVisual = body is GasGiantData gas ? ProjectGasGiantVisual(gas) @@ -689,15 +690,31 @@ public class ActionGameManager : MonoBehaviour }; } - private static AetheriaRuntimeAsteroidCommit[] ProjectAsteroids(Asteroid[] asteroids) + private static AetheriaRuntimeAsteroidCommit[] ProjectAsteroids( + Zone zone, + AsteroidBeltData beltData, + AsteroidBelt asteroidBelt) { - return asteroids? - .Select(asteroid => new AetheriaRuntimeAsteroidCommit + return beltData.Asteroids? + .Select((asteroid, asteroidIndex) => new AetheriaRuntimeAsteroidCommit { Distance = asteroid.Distance, Phase = asteroid.Phase, Size = asteroid.Size, - RotationSpeed = asteroid.RotationSpeed + RotationSpeed = asteroid.RotationSpeed, + Damage = asteroidBelt != null && asteroidBelt.Damage.TryGetValue(asteroidIndex, out var damage) ? damage : 0, + RespawnTimer = asteroidBelt != null && asteroidBelt.RespawnTimers.TryGetValue(asteroidIndex, out var respawnTimer) ? respawnTimer : 0, + MiningAccumulators = asteroidBelt == null + ? Array.Empty() + : asteroidBelt.MiningAccumulator + .Where(pair => pair.Key.Item2 == asteroidIndex) + .Select(pair => new AetheriaRuntimeAsteroidMiningAccumulatorCommit + { + MinerEntityIndex = zone.Entities.IndexOf(pair.Key.Item1), + Amount = pair.Value + }) + .Where(accumulator => accumulator.MinerEntityIndex >= 0) + .ToArray() }) .ToArray() ?? Array.Empty(); } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs index 3baffd7eb..e858486bb 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs @@ -935,7 +935,17 @@ namespace GameCult.Aetheria.State.Unity public sealed class AetheriaRuntimeBodySnapshot { - public AetheriaRuntimeBodySnapshot(string bodyId, string kind, string name, string orbitId, double mass, int resourceCount, int asteroidCount) + public AetheriaRuntimeBodySnapshot( + string bodyId, + string kind, + string name, + string orbitId, + double mass, + int resourceCount, + int asteroidCount, + int damagedAsteroidCount, + int respawningAsteroidCount, + int asteroidMiningAccumulatorCount) { BodyId = bodyId; Kind = kind; @@ -944,6 +954,9 @@ namespace GameCult.Aetheria.State.Unity Mass = mass; ResourceCount = resourceCount; AsteroidCount = asteroidCount; + DamagedAsteroidCount = damagedAsteroidCount; + RespawningAsteroidCount = respawningAsteroidCount; + AsteroidMiningAccumulatorCount = asteroidMiningAccumulatorCount; } public string BodyId { get; } @@ -953,6 +966,9 @@ namespace GameCult.Aetheria.State.Unity public double Mass { get; } public int ResourceCount { get; } public int AsteroidCount { get; } + public int DamagedAsteroidCount { get; } + public int RespawningAsteroidCount { get; } + public int AsteroidMiningAccumulatorCount { get; } } public sealed class AetheriaRuntimeEntityItemSlotSnapshot diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs index 643c3778d..c2cf52761 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs @@ -702,7 +702,7 @@ namespace GameCult.Aetheria.State.Unity var mass = ReadFieldDouble(ref reader, bodyFields, 4); var resourceCount = CountAndSkipFieldArray(ref reader, bodyFields, 5); SkipFields(ref reader, bodyFields, 6, 10); - var asteroidCount = CountAndSkipFieldArray(ref reader, bodyFields, 10); + var asteroidSummary = ReadFieldAsteroidSummary(ref reader, bodyFields, 10); SkipRemaining(ref reader, bodyFields, 11); bodies[body] = new AetheriaRuntimeBodySnapshot( bodyId, @@ -711,12 +711,51 @@ namespace GameCult.Aetheria.State.Unity orbitId, mass, resourceCount, - asteroidCount); + asteroidSummary.Count, + asteroidSummary.DamagedCount, + asteroidSummary.RespawningCount, + asteroidSummary.MiningAccumulatorCount); } return bodies; } + private static AsteroidSummary ReadFieldAsteroidSummary(ref MessagePackReader reader, int fields, int index) + { + if (index >= fields) return new AsteroidSummary(0, 0, 0, 0); + var count = reader.ReadArrayHeader(); + var damagedCount = 0; + var respawningCount = 0; + var miningAccumulatorCount = 0; + for (var asteroid = 0; asteroid < count; asteroid++) + { + var asteroidFields = reader.ReadArrayHeader(); + SkipFields(ref reader, asteroidFields, 0, 4); + if (ReadFieldDouble(ref reader, asteroidFields, 4) > 0) damagedCount++; + if (ReadFieldDouble(ref reader, asteroidFields, 5) > 0) respawningCount++; + miningAccumulatorCount += CountAndSkipFieldArray(ref reader, asteroidFields, 6); + SkipRemaining(ref reader, asteroidFields, 7); + } + + return new AsteroidSummary(count, damagedCount, respawningCount, miningAccumulatorCount); + } + + private readonly struct AsteroidSummary + { + public AsteroidSummary(int count, int damagedCount, int respawningCount, int miningAccumulatorCount) + { + Count = count; + DamagedCount = damagedCount; + RespawningCount = respawningCount; + MiningAccumulatorCount = miningAccumulatorCount; + } + + public int Count { get; } + public int DamagedCount { get; } + public int RespawningCount { get; } + public int MiningAccumulatorCount { get; } + } + private static IReadOnlyList ReadFieldEntityItemSlots(ref MessagePackReader reader, int fields, int index) { if (index >= fields) return Array.Empty(); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs index 795d83d31..655e93363 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -401,6 +401,25 @@ namespace GameCult.Aetheria.State.Unity [Key(3)] public double RotationSpeed { get; set; } + + [Key(4)] + public double Damage { get; set; } + + [Key(5)] + public double RespawnTimer { get; set; } + + [Key(6)] + public IReadOnlyList MiningAccumulators { get; set; } = Array.Empty(); + } + + [MessagePackObject] + public sealed class AetheriaRuntimeAsteroidMiningAccumulatorCommit + { + [Key(0)] + public int MinerEntityIndex { get; set; } = -1; + + [Key(1)] + public double Amount { get; set; } } [MessagePackObject] diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index eebfb8175..e90de0da0 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -146,8 +146,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. parent IDs, distance, phase, fixed positions, body kind/name/orbit, resources, gravity/body multipliers, asteroid belt entries, and gas/sun visual parameters now survive through the typed pending commit lane into - `AetheriaZoneState`. `RuntimeZoneBlueprint` feeds this projector, but it is - no longer the only place generated celestial graph facts can live. + `AetheriaZoneState`. Asteroid belt runtime damage, respawn timers, and + miner accumulators also persist on typed asteroid rows. `RuntimeZoneBlueprint` + feeds static generated geometry, but it is no longer the only place generated + celestial graph facts can live. - Run checkpoint entity snapshots include typed simulation stat grids for temperature, thermal mass, armor, max armor, and hull-conductivity axes. They also carry public runtime session state: velocity, target entity @@ -840,6 +842,8 @@ First Aetheria surfaces to publish: entity snapshot documents, so the `.zone` replacement graph is not only writable through the state node but also inspectable by runtime package consumers. + - Done: preserve asteroid belt runtime state on typed asteroid rows: + damage, respawn timers, and miner accumulator references. - Done: persist the actual galaxy generation seed in typed run state, so future Continue/restore work has a reproducible topology body before applying current-zone/entity snapshots.