diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 921f5fd50..bceb553d2 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index e83e28554..efafe588c 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -846,6 +846,9 @@ static void RequireMainMenuContinueRunState(string root) "entity.RecordKey", "CreateEntityConstructionBlueprint(entitySnapshot, true)", "BindToEntity(entity)", + "RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot)", + "RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)", + "entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled", "ContinueRunState = null", "RestoreDroppedPickupsFromTypedZoneState" }; @@ -879,6 +882,27 @@ static void RequireMainMenuContinueRunState(string root) "Unity package entity readback no longer preserves record identity for Continue: " + string.Join(", ", missingPackageSymbols)); } + + var entitySourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Entity.cs"); + var entitySource = File.Exists(entitySourcePath) + ? File.ReadAllText(entitySourcePath) + : throw new InvalidOperationException("Cannot verify Continue entity restore ownership; Entity.cs is missing."); + var requiredEntityRestoreSymbols = new[] + { + "RestoreThermalExposure", + "RestoreActiveConsumable", + "new ConsumableItemEffect(item, this, remainingDuration, duration)" + }; + var missingEntityRestoreSymbols = requiredEntityRestoreSymbols + .Where(symbol => !entitySource.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (missingEntityRestoreSymbols.Length > 0) + { + throw new InvalidOperationException( + "Entity no longer exposes narrow runtime-owned restore primitives for typed Continue state: " + + string.Join(", ", missingEntityRestoreSymbols)); + } } static void RequirePropertiesPanelReadOnlyInspector(string root) diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index ac4c85456..662968395 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -176,9 +176,11 @@ sessions publish typed state-host liveness. Zone checkpoints persist generated orbit/body rows into `AetheriaZoneState`, so `ZoneConstructionBlueprint` is a one-shot construction input rather than the retained surface carrying celestial graph facts. Entity checkpoints persist hull simulation grids into `AetheriaEntitySnapshot`, so -`RuntimeEntityBlueprint` is no longer the only surface carrying those live -values. Broader runtime object graphs and behavior-private simulation state -remain legacy until dedicated typed runtime documents exist. Live behavior +`EntityConstructionBlueprint` is no longer the only surface carrying those live +values. Continue restore lowers the current player entity from the exact typed +entity snapshot record, including position, velocity, shutdown/heatsink state, +thermal exposure, and active consumable timers. Broader runtime object graphs +and behavior-private simulation state remain legacy until dedicated typed runtime documents exist. Live behavior instances expose typed behavior kind and group through `Behavior.Kind` and `Behavior.Group`; behavior construction now reads typed payload fields through `RuntimeBehaviorDefinition` instead of projecting through config DTOs. Stat diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 04457c1b4..18447c545 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -2543,9 +2543,36 @@ public class ActionGameManager : MonoBehaviour entity.Zone = Zone; Zone.Entities.Add(entity); entity.Activate(); + entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled; + entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia); + RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot); BindToEntity(entity); } + private void RestoreActiveConsumablesFromTypedEntitySnapshot(Entity entity, AetheriaRuntimeEntitySnapshot snapshot) + { + foreach (var activeConsumable in snapshot.ActiveConsumables) + { + var item = CreateLoadoutItem(new AetheriaRuntimeLoadoutItemSnapshot( + activeConsumable.ItemKey, + activeConsumable.Quality, + 1, + 1, + true, + false)) as ConsumableItem; + if (item == null) + { + Debug.LogWarning($"Typed active consumable {activeConsumable.ItemKey} could not be lowered for restored entity {snapshot.RecordKey}."); + continue; + } + + entity.RestoreActiveConsumable( + item, + (float)activeConsumable.RemainingDuration, + (float)activeConsumable.Duration); + } + } + private static GalaxyZone ResolveStartZone(AetheriaRuntimeRunStateSnapshot run) { if (CurrentGalaxy?.Zones == null || CurrentGalaxy.Zones.Length == 0) diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 26fb259e4..f57559215 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -315,6 +315,20 @@ public abstract class Entity _activeConsumables.Add(new ConsumableItemEffect(item, this)); } + public void RestoreThermalExposure(float heatstroke, float hypothermia) + { + Heatstroke = saturate(heatstroke); + Hypothermia = saturate(hypothermia); + } + + public void RestoreActiveConsumable(ConsumableItem item, float remainingDuration, float duration) + { + if (item == null) + return; + + _activeConsumables.Add(new ConsumableItemEffect(item, this, remainingDuration, duration)); + } + public ConsumableItemEffect FindActiveConsumable(string itemKey) { return _activeConsumables.FirstOrDefault(ac => ac.Item?.ItemKey == itemKey); @@ -1182,13 +1196,20 @@ public class ConsumableItemEffect private readonly BezierCurve _effectiveness; public ConsumableItemEffect(ConsumableItem item, Entity entity) + : this(item, entity, null, null) + { + } + + public ConsumableItemEffect(ConsumableItem item, Entity entity, float? remainingDuration, float? duration) { Item = item; Entity = entity; RuntimeItem = entity.ItemManager.GetRuntimeItem(item); - _duration = RuntimeItem?.Duration > 0 ? (float)RuntimeItem.Duration : 0; + _duration = duration.GetValueOrDefault(RuntimeItem?.Duration > 0 ? (float)RuntimeItem.Duration : 0); _effectiveness = CreateEffectivenessCurve(RuntimeItem); - RemainingDuration = _duration; + RemainingDuration = remainingDuration.HasValue + ? clamp(remainingDuration.Value, 0, Duration) + : _duration; Behaviors = entity.ItemManager.CreateRuntimeBehaviors(this); } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index b7a9a3a07..56c2b59a4 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -974,16 +974,16 @@ First Aetheria surfaces to publish: `RunId + ZoneIndex + CurrentZoneEntityIndex` entity record, lowers it through the existing construction blueprint path, restores hull, equipment, cargo, docking contents, weapon groups, position, direction, - velocity, shutdown override, and tractor power, then binds the lowered - entity as the current player ship. + velocity, shutdown override, heatsink toggle, tractor power, + heatstroke/hypothermia exposure, and active consumable item/timer rows, + then binds the lowered entity as the current player ship. - Remaining: add typed documents/mappers for runtime object graphs, complete full Continue/run entity graph reconstruction from typed state for non-current entities, child/docked entity activation, targets, - contacts, active consumables, behavior-private state not covered by - progress, weapon, sensor, radiator, reactor, or capacitor rows, - heatstroke/hypothermia runtime restore, typed behavior factory - construction, and any catalog fields not covered by the stable - scalar/fingerprint/payload pass. + contacts, restored active-consumable behavior runtime internals, + behavior-private state not covered by progress, weapon, sensor, radiator, + reactor, or capacitor rows, typed behavior factory construction, 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