Persist behavior progress in entity snapshots

This commit is contained in:
Meta Vens 2026-06-14 14:08:59 +01:00
commit 3c4fe6fd10
7 changed files with 137 additions and 5 deletions

Binary file not shown.

View file

@ -347,6 +347,25 @@ try
Duration = 5.0
}
},
BehaviorProgress = new[]
{
new AetheriaRuntimeBehaviorProgressCommit
{
OwnerKind = "equipment",
OwnerIndex = 0,
BehaviorIndex = 1,
BehaviorKind = "Cooldown",
Progress = 0.5
},
new AetheriaRuntimeBehaviorProgressCommit
{
OwnerKind = "active_consumable",
OwnerIndex = 0,
BehaviorIndex = 0,
BehaviorKind = "Shield",
Progress = 0.75
}
},
StatGrids = new[]
{
new AetheriaRuntimeEntityStatGridCommit
@ -434,6 +453,12 @@ try
entity.ActiveConsumables.Length == 1 &&
entity.ActiveConsumables[0].ItemKey == "aetheria.item_definition:legacy:smoke:consumable" &&
entity.ActiveConsumables[0].RemainingDuration == 3.0 &&
entity.BehaviorProgress.Length == 2 &&
entity.BehaviorProgress[0].OwnerKind == "equipment" &&
entity.BehaviorProgress[0].BehaviorKind == "Cooldown" &&
entity.BehaviorProgress[0].Progress == 0.5 &&
entity.BehaviorProgress[1].OwnerKind == "active_consumable" &&
entity.BehaviorProgress[1].Progress == 0.75 &&
entity.WeaponGroups.Length == 1 &&
entity.StatGrids.Length == 2 &&
entity.StatGrids.Any(grid => grid.Name == "temperature" && grid.Values.Length == 2 && grid.Values[1] == 281.0) &&

View file

@ -440,10 +440,26 @@ public static class AetheriaRuntimeCommitLogApplier
.Select(group => new AetheriaWeaponGroupSnapshot { EquipmentIndices = group })
.ToArray(),
StatGrids = ToEntityStatGrids(entity.StatGrids),
ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables)
ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables),
BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress)
};
}
private static AetheriaBehaviorProgressSnapshot[] ToBehaviorProgress(
IReadOnlyList<AetheriaRuntimeBehaviorProgressCommit>? progress)
{
return (progress ?? Array.Empty<AetheriaRuntimeBehaviorProgressCommit>())
.Select(entry => new AetheriaBehaviorProgressSnapshot
{
OwnerKind = entry.OwnerKind ?? "",
OwnerIndex = entry.OwnerIndex,
BehaviorIndex = entry.BehaviorIndex,
BehaviorKind = entry.BehaviorKind ?? "",
Progress = entry.Progress
})
.ToArray();
}
private static AetheriaActiveConsumableSnapshot[] ToActiveConsumables(
IReadOnlyList<AetheriaRuntimeActiveConsumableCommit>? consumables)
{

View file

@ -482,6 +482,9 @@ public sealed class AetheriaEntitySnapshot
[Key(20)]
public AetheriaActiveConsumableSnapshot[] ActiveConsumables { get; set; } = [];
[Key(21)]
public AetheriaBehaviorProgressSnapshot[] BehaviorProgress { get; set; } = [];
}
[MessagePackObject]
@ -534,6 +537,25 @@ public sealed class AetheriaActiveConsumableSnapshot
public double Duration { get; set; }
}
[MessagePackObject]
public sealed class AetheriaBehaviorProgressSnapshot
{
[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 double Progress { get; set; }
}
[MessagePackObject]
public sealed class AetheriaActionBarBinding
{

View file

@ -787,10 +787,49 @@ public class ActionGameManager : MonoBehaviour
.Select(group => (IReadOnlyList<int>)group.items.Select(item => entity.Equipment.IndexOf(item)).Where(index => index >= 0).ToArray())
.ToArray() ?? Array.Empty<IReadOnlyList<int>>(),
StatGrids = ProjectEntityStatGrids(entity),
ActiveConsumables = ProjectActiveConsumables(entity)
ActiveConsumables = ProjectActiveConsumables(entity),
BehaviorProgress = ProjectBehaviorProgress(entity)
};
}
private static AetheriaRuntimeBehaviorProgressCommit[] ProjectBehaviorProgress(Entity entity)
{
var progress = new List<AetheriaRuntimeBehaviorProgressCommit>();
for (var ownerIndex = 0; ownerIndex < entity.Equipment.Count; ownerIndex++)
progress.AddRange(ProjectBehaviorProgress("equipment", ownerIndex, entity.Equipment[ownerIndex].Behaviors));
var activeConsumables = entity.ActiveConsumables;
for (var ownerIndex = 0; ownerIndex < activeConsumables.Count; ownerIndex++)
progress.AddRange(ProjectBehaviorProgress("active_consumable", ownerIndex, activeConsumables[ownerIndex].Behaviors));
return progress.ToArray();
}
private static IEnumerable<AetheriaRuntimeBehaviorProgressCommit> ProjectBehaviorProgress(
string ownerKind,
int ownerIndex,
IReadOnlyList<Behavior> behaviors)
{
if (behaviors == null)
yield break;
for (var behaviorIndex = 0; behaviorIndex < behaviors.Count; behaviorIndex++)
{
if (!(behaviors[behaviorIndex] is IProgressBehavior progressBehavior))
continue;
yield return new AetheriaRuntimeBehaviorProgressCommit
{
OwnerKind = ownerKind,
OwnerIndex = ownerIndex,
BehaviorIndex = behaviorIndex,
BehaviorKind = behaviors[behaviorIndex].Data?.Kind ?? "",
Progress = progressBehavior.Progress
};
}
}
private static AetheriaRuntimeActiveConsumableCommit[] ProjectActiveConsumables(Entity entity)
{
return entity.ActiveConsumables?

View file

@ -558,6 +558,9 @@ namespace GameCult.Aetheria.State.Unity
[Key(25)]
public IReadOnlyList<AetheriaRuntimeActiveConsumableCommit> ActiveConsumables { get; set; } = Array.Empty<AetheriaRuntimeActiveConsumableCommit>();
[Key(26)]
public IReadOnlyList<AetheriaRuntimeBehaviorProgressCommit> BehaviorProgress { get; set; } = Array.Empty<AetheriaRuntimeBehaviorProgressCommit>();
}
[MessagePackObject]
@ -591,4 +594,23 @@ namespace GameCult.Aetheria.State.Unity
[Key(3)]
public double Duration { get; set; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeBehaviorProgressCommit
{
[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 double Progress { get; set; }
}
}

View file

@ -142,6 +142,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
They also carry public runtime session state: velocity, target entity
reference, active flag, heatsink toggle, shutdown override, tractor power,
heatstroke/hypothermia accumulators, and active consumable item/timer rows.
Equipped-item and active-consumable behaviors that intentionally expose
`IProgressBehavior` now publish typed progress rows keyed by owner kind,
owner index, behavior index, behavior kind, and progress value.
`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.
@ -761,13 +764,17 @@ First Aetheria surfaces to publish:
entity session state into `AetheriaEntitySnapshot`: velocity, target
references, active/toggle scalars, thermal injury accumulators, and active
consumable timers.
- Done: extend run checkpoint commits and smoke coverage to carry typed
behavior progress rows for equipped-item and active-consumable behaviors
that expose `IProgressBehavior`.
- 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 weapon cooldown/charge/ammo and sensor/shield progress, and any
catalog fields not covered by the stable scalar/fingerprint/payload pass.
such as weapon cooldown/charge/ammo internals and sensor ping state, 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
@ -1159,7 +1166,8 @@ First Aetheria surfaces to publish:
zone -> entity snapshot graph as the `.zone` replacement. The zone smoke
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. The Unity runtime smoke also proves
active consumable timer rows, plus typed behavior progress rows for
`IProgressBehavior` surfaces. 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.