Restore behavior runtime state from typed Continue

This commit is contained in:
Meta Vens 2026-06-16 09:01:05 +01:00
commit 01fc810eb4
23 changed files with 406 additions and 13 deletions

Binary file not shown.

View file

@ -854,6 +854,11 @@ static void RequireMainMenuContinueRunState(string root)
"string.Equals(entitySnapshot.RecordKey, currentEntityKey",
"BindToEntity(currentEntity)",
"RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot)",
"RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, restoredEntities)",
"ResolveRuntimeBehavior(entity, weaponState.OwnerKind, weaponState.OwnerIndex, weaponState.BehaviorIndex)",
"lockWeapon.RestoreRuntimeState(",
"drive.RestoreRuntimeState(",
"resourceScanner.RestoreRuntimeState(",
"entity.RestoreStatGrids(entitySnapshot.StatGrids)",
"RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)",
"entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled",

View file

@ -182,8 +182,12 @@ docked children, into exact typed entity snapshot records. Continue restore
lowers the current zone entity graph from those records, including position,
velocity, target/contact state, child/docking relationships,
shutdown/heatsink state, thermal exposure, active consumable timers, and typed
stat grids for temperature, thermal mass, armor, max armor, and hull conductivity. Broader runtime object graphs
such as behavior-private simulation state remain legacy until dedicated typed runtime documents exist. Live behavior
stat grids for temperature, thermal mass, armor, max armor, and hull
conductivity. Continue also lowers typed weapon and behavior runtime rows back
into live equipped-item and active-consumable behavior instances through
behavior-owned restore methods; construction/loadout blueprints do not own
behavior replay. Broader runtime object graphs without typed rows 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

View file

@ -2612,6 +2612,7 @@ public class ActionGameManager : MonoBehaviour
entity.RestoreStatGrids(entitySnapshot.StatGrids);
entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia);
RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot);
RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, restoredEntities);
RestoreEntityContactsFromTypedSnapshot(entity, entitySnapshot, restoredEntities);
if (restoredEntities.TryGetValue(entitySnapshot.TargetEntityKey, out var target))
entity.Target.Value = target;
@ -2738,6 +2739,184 @@ public class ActionGameManager : MonoBehaviour
}
}
private void RestoreRuntimeBehaviorStateFromTypedSnapshot(
Entity entity,
AetheriaRuntimeEntitySnapshot snapshot,
IReadOnlyDictionary<string, Entity> restoredEntities)
{
foreach (var weaponState in snapshot.WeaponStates)
{
if (!(ResolveRuntimeBehavior(entity, weaponState.OwnerKind, weaponState.OwnerIndex, weaponState.BehaviorIndex) is Weapon weapon))
continue;
if (weapon is LockWeapon lockWeapon)
{
restoredEntities.TryGetValue(weaponState.LockTargetEntityKey, out var lockTarget);
lockWeapon.RestoreRuntimeState(
weaponState.Firing,
weaponState.Ammo,
weaponState.BurstRemaining,
(float)weaponState.BurstTimer,
(float)weaponState.BurstInterval,
(float)weaponState.CooldownProgress,
weaponState.CoolingDown,
(float)weaponState.LockProgress,
lockTarget);
}
else if (weapon is ChargedWeapon chargedWeapon)
{
chargedWeapon.RestoreRuntimeState(
weaponState.Firing,
weaponState.Ammo,
weaponState.BurstRemaining,
(float)weaponState.BurstTimer,
(float)weaponState.BurstInterval,
(float)weaponState.CooldownProgress,
weaponState.CoolingDown,
weaponState.Charging,
weaponState.Charged,
(float)weaponState.Charge);
}
else if (weapon is ConstantWeapon constantWeapon)
{
constantWeapon.RestoreRuntimeState(
weaponState.Firing,
weaponState.Ammo,
(float)weaponState.AmmoIntervalProgress,
(float)weaponState.ReloadProgress,
weaponState.Reloading);
}
else if (weapon is InstantWeapon instantWeapon)
{
instantWeapon.RestoreRuntimeState(
weaponState.Firing,
weaponState.Ammo,
weaponState.BurstRemaining,
(float)weaponState.BurstTimer,
(float)weaponState.BurstInterval,
(float)weaponState.CooldownProgress,
weaponState.CoolingDown);
}
else
{
weapon.RestoreRuntimeState(weaponState.Firing);
}
}
foreach (var behaviorState in snapshot.BehaviorStates)
{
var behavior = ResolveRuntimeBehavior(entity, behaviorState.OwnerKind, behaviorState.OwnerIndex, behaviorState.BehaviorIndex);
switch (behavior)
{
case Sensor sensor:
sensor.RestoreRuntimeState(
behaviorState.Pinging,
(float)behaviorState.PingCooldown,
(float)behaviorState.PingLerp,
(float)behaviorState.PingRadius);
break;
case Radiator radiator:
radiator.RestoreRuntimeState(
(float)behaviorState.RadiatorTemperature,
(float)behaviorState.Emissivity,
(float)behaviorState.PumpedHeat,
(float)behaviorState.WasteHeat,
(float)behaviorState.EnergyUsage);
break;
case Reactor reactor:
reactor.RestoreRuntimeState(
(float)behaviorState.ReactorDraw,
(float)behaviorState.ReactorLoadRatio);
break;
case Capacitor capacitor:
capacitor.RestoreRuntimeState(
(float)behaviorState.CapacitorCharge,
(float)behaviorState.CapacitorCapacity,
(float)behaviorState.CapacitorEfficiency);
break;
case AetherDrive drive:
drive.RestoreRuntimeState(
new float3((float)behaviorState.AetherDriveAxisX, (float)behaviorState.AetherDriveAxisY, (float)behaviorState.AetherDriveAxisZ),
new float3((float)behaviorState.AetherDriveThrustX, (float)behaviorState.AetherDriveThrustY, (float)behaviorState.AetherDriveThrustZ),
new float3((float)behaviorState.AetherDriveRpmX, (float)behaviorState.AetherDriveRpmY, (float)behaviorState.AetherDriveRpmZ),
(float)behaviorState.AetherDriveMaximumRpm,
new float2((float)behaviorState.AetherDriveThrustDirectionX, (float)behaviorState.AetherDriveThrustDirectionY));
break;
case ResourceScanner resourceScanner:
resourceScanner.RestoreRuntimeState(
ParseLegacyGuidFromReferenceKey(behaviorState.ResourceScannerTargetBodyId, "aetheria.body"),
behaviorState.ResourceScannerAsteroidIndex,
(float)behaviorState.ResourceScannerScanTime,
(float)behaviorState.ResourceScannerRange,
(float)behaviorState.ResourceScannerMinimumDensity,
(float)behaviorState.ResourceScannerScanDuration);
break;
case MiningTool miningTool:
miningTool.RestoreRuntimeState(
ParseLegacyGuidFromReferenceKey(behaviorState.MiningToolAsteroidBeltId, "aetheria.body"),
behaviorState.MiningToolAsteroidIndex,
(float)behaviorState.MiningToolRange);
break;
case Thruster thruster:
thruster.RestoreRuntimeState(
(float)behaviorState.ThrusterAxis,
(float)behaviorState.ThrusterThrust);
break;
case Shield shield:
shield.RestoreRuntimeState(
(float)behaviorState.ShieldEfficiency,
(float)behaviorState.ShieldEnergyUsage);
break;
case VelocityLimit velocityLimit:
velocityLimit.RestoreRuntimeState((float)behaviorState.VelocityLimit);
break;
case Thermotoggle thermotoggle:
thermotoggle.TargetTemperature = (float)behaviorState.ThermotoggleTargetTemperature;
break;
case Switch switchBehavior:
switchBehavior.Activated = behaviorState.SwitchActivated;
break;
case Trigger trigger:
trigger.RestoreRuntimeState(behaviorState.TriggerPulled);
break;
case StatModifier statModifier:
statModifier.RestoreRuntimeState(
behaviorState.StatModifierApplied,
behaviorState.StatModifierExecuted);
break;
case TurretController turretController:
turretController.RestoreRuntimeState(
(float)behaviorState.TurretControllerShotSpeed,
behaviorState.TurretControllerPredictShots);
break;
}
}
}
private static Behavior ResolveRuntimeBehavior(Entity entity, string ownerKind, int ownerIndex, int behaviorIndex)
{
var behaviors = ResolveRuntimeBehaviorList(entity, ownerKind, ownerIndex);
return behaviors != null && behaviorIndex >= 0 && behaviorIndex < behaviors.Count
? behaviors[behaviorIndex]
: null;
}
private static IReadOnlyList<Behavior> ResolveRuntimeBehaviorList(Entity entity, string ownerKind, int ownerIndex)
{
if (entity == null || ownerIndex < 0)
return null;
switch (ownerKind)
{
case "equipment":
return ownerIndex < entity.Equipment.Count ? entity.Equipment[ownerIndex].Behaviors : null;
case "active_consumable":
return ownerIndex < entity.ActiveConsumables.Count ? entity.ActiveConsumables[ownerIndex].Behaviors : null;
default:
return null;
}
}
private static GalaxyZone ResolveStartZone(AetheriaRuntimeRunStateSnapshot run)
{
if (CurrentGalaxy?.Zones == null || CurrentGalaxy.Zones.Length == 0)

View file

@ -135,4 +135,18 @@ public class AetherDrive : Behavior
return false;
}
public void RestoreRuntimeState(
float3 axis,
float3 thrust,
float3 rpm,
float maximumRpm,
float2 thrustDirection)
{
Axis = axis;
Thrust = thrust;
Rpm = rpm;
MaximumRpm = maximumRpm;
ThrustDirection = thrustDirection;
}
}

View file

@ -43,4 +43,11 @@ public class Capacitor : Behavior
Efficiency = Evaluate(_efficiency);
return true;
}
public void RestoreRuntimeState(float charge, float capacity, float efficiency)
{
Charge = clamp(charge, 0, capacity);
Capacity = capacity;
Efficiency = efficiency;
}
}

View file

@ -168,5 +168,29 @@ public class ChargedWeapon : InstantWeapon
_charging = false;
}
}
}
public void RestoreRuntimeState(
bool firing,
int ammo,
int burstRemaining,
float burstTimer,
float burstInterval,
float cooldownProgress,
bool coolingDown,
bool charging,
bool charged,
float charge)
{
base.RestoreRuntimeState(
firing,
ammo,
burstRemaining,
burstTimer,
burstInterval,
cooldownProgress,
coolingDown);
_charging = charging;
_charged = charged;
_charge = charge;
}
}

View file

@ -134,4 +134,18 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
OnStopFiring?.Invoke();
}
}
public void RestoreRuntimeState(
bool firing,
int ammo,
float ammoIntervalProgress,
float reloadProgress,
bool reloading)
{
base.RestoreRuntimeState(firing);
_ammo = ammo;
_ammoInterval = ammoIntervalProgress;
_reload = reloadProgress;
_reloading = reloading;
}
}

View file

@ -194,4 +194,22 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
Trigger();
base.Activate();
}
public void RestoreRuntimeState(
bool firing,
int ammo,
int burstRemaining,
float burstTimer,
float burstInterval,
float cooldownProgress,
bool coolingDown)
{
base.RestoreRuntimeState(firing);
_ammo = ammo;
_burstRemaining = burstRemaining;
_burstTimer = burstTimer;
_burstInterval = burstInterval;
_cooldown = cooldownProgress;
_coolingDown = coolingDown;
}
}

View file

@ -93,5 +93,28 @@ public class LockWeapon : InstantWeapon
return base.Execute(dt);
}
}
public void RestoreRuntimeState(
bool firing,
int ammo,
int burstRemaining,
float burstTimer,
float burstInterval,
float cooldownProgress,
bool coolingDown,
float lockProgress,
Entity lockTarget)
{
base.RestoreRuntimeState(
firing,
ammo,
burstRemaining,
burstTimer,
burstInterval,
cooldownProgress,
coolingDown);
_lock = lockProgress;
_target = lockTarget;
_locking = lockTarget != null;
}
}

View file

@ -62,4 +62,11 @@ public class MiningTool : Behavior
return false;
}
public void RestoreRuntimeState(Guid asteroidBelt, int asteroid, float range)
{
AsteroidBelt = asteroidBelt;
Asteroid = asteroid;
Range = range;
}
}

View file

@ -90,4 +90,18 @@ public class Radiator : Behavior, IAlwaysUpdatedBehavior, IInitializableBehavior
{
RadiatorTemperature = Temperature;
}
public void RestoreRuntimeState(
float radiatorTemperature,
float emissivity,
float pumpedHeat,
float wasteHeat,
float energyUsage)
{
RadiatorTemperature = radiatorTemperature;
Emissivity = emissivity;
PumpedHeat = pumpedHeat;
WasteHeat = wasteHeat;
EnergyUsage = energyUsage;
}
}

View file

@ -141,4 +141,10 @@ public class Reactor : Behavior, IOrderedBehavior, IDisposable
foreach(var sub in _subscriptions)
sub.Dispose();
}
public void RestoreRuntimeState(float draw, float currentLoadRatio)
{
Draw = draw;
CurrentLoadRatio = currentLoadRatio;
}
}

View file

@ -93,4 +93,20 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
MinimumDensity = Evaluate(_minimumDensity);
ScanDuration = Evaluate(_scanDuration);
}
public void RestoreRuntimeState(
Guid scanTarget,
int asteroid,
float scanTime,
float range,
float minimumDensity,
float scanDuration)
{
_scanTarget = scanTarget;
Asteroid = asteroid;
_scanTime = scanTime;
Range = range;
MinimumDensity = minimumDensity;
ScanDuration = scanDuration;
}
}

View file

@ -151,4 +151,17 @@ public class Sensor : Behavior, IEventBehavior
}
return true;
}
public void RestoreRuntimeState(
bool pinging,
float pingCooldown,
float pingLerp,
float pingRadius)
{
_pinging = pinging;
_pingCooldown = pingCooldown;
_pingLerp = pingLerp;
_pingRadius = pingRadius;
_pingedEntities.Clear();
}
}

View file

@ -48,4 +48,10 @@ public class Shield : Behavior, IProgressBehavior
}
public virtual float Progress => Item.ThermalPerformance;
public void RestoreRuntimeState(float efficiency, float energyUsage)
{
Efficiency = efficiency;
EnergyUsage = energyUsage;
}
}

View file

@ -125,6 +125,17 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
RemoveModifier();
_executed = false;
}
public void RestoreRuntimeState(bool applied, bool executed)
{
if (_stats == null)
Initialize();
if (applied && !_applied)
ApplyModifier();
if (!applied && _applied)
RemoveModifier();
_executed = executed;
}
}
public enum StatModifierType

View file

@ -76,4 +76,10 @@ public class Thruster : Behavior, IAnalogBehavior
}
return false;
}
public void RestoreRuntimeState(float axis, float thrust)
{
Axis = axis;
Thrust = thrust;
}
}

View file

@ -31,4 +31,9 @@ public class Trigger : Behavior, IActivatedBehavior
public void Deactivate()
{
}
public void RestoreRuntimeState(bool pulled)
{
_pulled = pulled;
}
}

View file

@ -84,4 +84,10 @@ public class TurretController : Behavior, IInitializableBehavior
}
return true;
}
public void RestoreRuntimeState(float shotSpeed, bool predictShots)
{
_shotSpeed = shotSpeed;
_predictShots = predictShots;
}
}

View file

@ -33,4 +33,9 @@ public class VelocityLimit : Behavior
Entity.Velocity = normalize(Entity.Velocity) * Limit;
return true;
}
public void RestoreRuntimeState(float limit)
{
Limit = limit;
}
}

View file

@ -172,6 +172,11 @@ public abstract class Weapon : Behavior, IActivatedBehavior
_firing = false;
}
public virtual void RestoreRuntimeState(bool firing)
{
_firing = firing;
}
public float EvaluateRange()
{
return Evaluate(_range);

View file

@ -194,7 +194,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
publish their active target temperature. Switches publish activated state,
triggers publish pending pulled state, and stat modifiers publish
applied/executed flags plus target-stat count. Turret controllers publish
initialized weapon count, shot speed, and predictive-aim flag.
initialized weapon count, shot speed, and predictive-aim flag. Continue
restore lowers those typed weapon and behavior rows back into the live
equipped-item and active-consumable behavior instances through narrow
behavior-owned restore methods; construction/loadout blueprints do not own
behavior replay.
`EntityConstructionBlueprint` 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.
@ -980,18 +984,19 @@ First Aetheria surfaces to publish:
typed stat grids for temperature, thermal mass, armor, max armor, and
hull-conductivity axes, then reconnects target/contact rows among restored
entities, restores child/docking relationships from typed child keys and
docking-bay assignments, and binds the saved current entity as the player
ship or docked current ship.
docking-bay assignments, restores typed weapon and behavior runtime rows
into live behavior instances, and binds the saved current entity as the
player ship or docked current ship.
- Done: project run checkpoint entity snapshots from the flattened live
entity graph, not only `Zone.Entities`, so docked child ships can receive
typed entity records and stable child/docking references instead of
disappearing from the `.zone` replacement graph.
- Remaining: add typed documents/mappers for runtime object graphs,
complete full Continue/run entity graph reconstruction from typed state
for 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.
- Remaining: add typed documents/mappers for runtime object graphs not
covered by progress, weapon, sensor, radiator, reactor, capacitor, drive,
scanner, mining, thruster, shield, limiter, thermotoggle, switch, trigger,
stat-modifier, or turret rows; complete 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