Persist entity contact state in typed snapshots
This commit is contained in:
parent
cb9c5ca21f
commit
1f8a1576f8
10 changed files with 160 additions and 4 deletions
Binary file not shown.
|
|
@ -233,6 +233,16 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
|
|||
DockingBayAssignments = [-1],
|
||||
Visibility = 12.75,
|
||||
VisibilitySourceCount = 3,
|
||||
Contacts =
|
||||
[
|
||||
new AetheriaEntityContactSnapshot
|
||||
{
|
||||
TargetEntityKey = entityKey.ToString(),
|
||||
InfoGathered = 0.85,
|
||||
Hostile = true,
|
||||
Visible = true
|
||||
}
|
||||
],
|
||||
WeaponGroups =
|
||||
[
|
||||
new AetheriaWeaponGroupSnapshot
|
||||
|
|
@ -627,6 +637,11 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
entitySnapshot.DockingBayAssignments[0] != -1 ||
|
||||
entitySnapshot.Visibility != 12.75 ||
|
||||
entitySnapshot.VisibilitySourceCount != 3 ||
|
||||
entitySnapshot.Contacts.Length != 1 ||
|
||||
entitySnapshot.Contacts[0].TargetEntityKey != entityKey.ToString() ||
|
||||
entitySnapshot.Contacts[0].InfoGathered != 0.85 ||
|
||||
!entitySnapshot.Contacts[0].Hostile ||
|
||||
!entitySnapshot.Contacts[0].Visible ||
|
||||
entitySnapshot.WeaponGroups.Length != 1 ||
|
||||
entitySnapshot.WeaponStates.Length != 1 ||
|
||||
entitySnapshot.WeaponStates[0].BehaviorKind != "LockWeapon" ||
|
||||
|
|
|
|||
|
|
@ -359,6 +359,16 @@ try
|
|||
DockingBayAssignments = new[] { -1 },
|
||||
Visibility = 12.75,
|
||||
VisibilitySourceCount = 3,
|
||||
Contacts = new[]
|
||||
{
|
||||
new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
TargetEntityIndex = 0,
|
||||
InfoGathered = 0.85,
|
||||
Hostile = true,
|
||||
Visible = true
|
||||
}
|
||||
},
|
||||
WeaponGroups = new[] { new[] { 0 } },
|
||||
ActiveConsumables = new[]
|
||||
{
|
||||
|
|
@ -693,6 +703,11 @@ try
|
|||
entity.DockingBayAssignments[0] == -1 &&
|
||||
entity.Visibility == 12.75 &&
|
||||
entity.VisibilitySourceCount == 3 &&
|
||||
entity.Contacts.Length == 1 &&
|
||||
entity.Contacts[0].TargetEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" &&
|
||||
entity.Contacts[0].InfoGathered == 0.85 &&
|
||||
entity.Contacts[0].Hostile &&
|
||||
entity.Contacts[0].Visible &&
|
||||
entity.ActiveConsumables[0].ItemKey == "aetheria.item_definition:legacy:smoke:consumable" &&
|
||||
entity.ActiveConsumables[0].RemainingDuration == 3.0 &&
|
||||
entity.BehaviorProgress.Length == 2 &&
|
||||
|
|
@ -818,6 +833,11 @@ try
|
|||
packageEntities[0].DockingBayAssignments[0] != -1 ||
|
||||
packageEntities[0].Visibility != 12.75 ||
|
||||
packageEntities[0].VisibilitySourceCount != 3 ||
|
||||
packageEntities[0].Contacts.Count != 1 ||
|
||||
packageEntities[0].Contacts[0].TargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" ||
|
||||
packageEntities[0].Contacts[0].InfoGathered != 0.85 ||
|
||||
!packageEntities[0].Contacts[0].Hostile ||
|
||||
!packageEntities[0].Contacts[0].Visible ||
|
||||
packageEntities[0].ActiveConsumables[0].ItemKey != "aetheria.item_definition:legacy:smoke:consumable" ||
|
||||
packageEntities[0].BehaviorProgress.Count != 2 ||
|
||||
packageEntities[0].WeaponStates.Count != 4 ||
|
||||
|
|
|
|||
|
|
@ -460,10 +460,28 @@ public static class AetheriaRuntimeCommitLogApplier
|
|||
DockingBayContents = ToCargoBays(entity.DockingBayContents),
|
||||
DockingBayAssignments = ToIntArray(entity.DockingBayAssignments),
|
||||
Visibility = entity.Visibility,
|
||||
VisibilitySourceCount = entity.VisibilitySourceCount
|
||||
VisibilitySourceCount = entity.VisibilitySourceCount,
|
||||
Contacts = ToEntityContacts(entity.Contacts, runId, zoneIndex)
|
||||
};
|
||||
}
|
||||
|
||||
private static AetheriaEntityContactSnapshot[] ToEntityContacts(
|
||||
IReadOnlyList<AetheriaRuntimeEntityContactCommit>? contacts,
|
||||
string runId,
|
||||
int zoneIndex)
|
||||
{
|
||||
return (contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
|
||||
.Where(contact => contact.TargetEntityIndex >= 0)
|
||||
.Select(contact => new AetheriaEntityContactSnapshot
|
||||
{
|
||||
TargetEntityKey = EntityKey(runId, zoneIndex, contact.TargetEntityIndex).ToString(),
|
||||
InfoGathered = contact.InfoGathered,
|
||||
Hostile = contact.Hostile,
|
||||
Visible = contact.Visible
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static AetheriaBehaviorStateSnapshot[] ToBehaviorStates(
|
||||
IReadOnlyList<AetheriaRuntimeBehaviorStateCommit>? states)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -509,6 +509,26 @@ public sealed class AetheriaEntitySnapshot
|
|||
|
||||
[Key(28)]
|
||||
public int VisibilitySourceCount { get; set; }
|
||||
|
||||
[Key(29)]
|
||||
public AetheriaEntityContactSnapshot[] Contacts { get; set; } = [];
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaEntityContactSnapshot
|
||||
{
|
||||
[Key(0)]
|
||||
[CultReference(typeof(AetheriaEntitySnapshot))]
|
||||
public string TargetEntityKey { get; set; } = "";
|
||||
|
||||
[Key(1)]
|
||||
public double InfoGathered { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public bool Hostile { get; set; }
|
||||
|
||||
[Key(3)]
|
||||
public bool Visible { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
|
|||
|
|
@ -787,6 +787,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
.ToArray() ?? Array.Empty<int>(),
|
||||
Visibility = entity.Visibility,
|
||||
VisibilitySourceCount = entity.VisibilitySources.Count,
|
||||
Contacts = ProjectEntityContacts(zone, entity),
|
||||
ChildEntityIndices = entity.Children?
|
||||
.Select(child => zone.Entities.IndexOf(child))
|
||||
.Where(index => index >= 0)
|
||||
|
|
@ -802,6 +803,25 @@ public class ActionGameManager : MonoBehaviour
|
|||
};
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeEntityContactCommit[] ProjectEntityContacts(Zone zone, Entity entity)
|
||||
{
|
||||
return entity.EntityInfoGathered
|
||||
.Select(contact =>
|
||||
{
|
||||
var targetIndex = zone.Entities.IndexOf(contact.Key);
|
||||
entity.EntityHostility.TryGetValue(contact.Key, out var hostile);
|
||||
return new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
TargetEntityIndex = targetIndex,
|
||||
InfoGathered = contact.Value,
|
||||
Hostile = hostile,
|
||||
Visible = entity.VisibleEntities.Contains(contact.Key)
|
||||
};
|
||||
})
|
||||
.Where(contact => contact.TargetEntityIndex >= 0)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeCargoBayLoadoutCommit[] ProjectRuntimeCargoBays<TBay>(IEnumerable<TBay> bays)
|
||||
where TBay : EquippedCargoBay
|
||||
{
|
||||
|
|
|
|||
|
|
@ -788,7 +788,8 @@ namespace GameCult.Aetheria.State.Unity
|
|||
IReadOnlyList<AetheriaRuntimeCargoBayLoadoutSnapshot> dockingBayContents,
|
||||
IReadOnlyList<int> dockingBayAssignments,
|
||||
double visibility,
|
||||
int visibilitySourceCount)
|
||||
int visibilitySourceCount,
|
||||
IReadOnlyList<AetheriaRuntimeEntityContactSnapshot> contacts)
|
||||
{
|
||||
Name = name;
|
||||
Kind = kind;
|
||||
|
|
@ -823,6 +824,7 @@ namespace GameCult.Aetheria.State.Unity
|
|||
DockingBayAssignments = dockingBayAssignments;
|
||||
Visibility = visibility;
|
||||
VisibilitySourceCount = visibilitySourceCount;
|
||||
Contacts = contacts;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
|
@ -858,6 +860,23 @@ namespace GameCult.Aetheria.State.Unity
|
|||
public IReadOnlyList<int> DockingBayAssignments { get; }
|
||||
public double Visibility { get; }
|
||||
public int VisibilitySourceCount { get; }
|
||||
public IReadOnlyList<AetheriaRuntimeEntityContactSnapshot> Contacts { get; }
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeEntityContactSnapshot
|
||||
{
|
||||
public AetheriaRuntimeEntityContactSnapshot(string targetEntityKey, double infoGathered, bool hostile, bool visible)
|
||||
{
|
||||
TargetEntityKey = targetEntityKey;
|
||||
InfoGathered = infoGathered;
|
||||
Hostile = hostile;
|
||||
Visible = visible;
|
||||
}
|
||||
|
||||
public string TargetEntityKey { get; }
|
||||
public double InfoGathered { get; }
|
||||
public bool Hostile { get; }
|
||||
public bool Visible { get; }
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeActionBarBindingSnapshot
|
||||
|
|
|
|||
|
|
@ -537,7 +537,8 @@ namespace GameCult.Aetheria.State.Unity
|
|||
var dockingBayAssignments = ReadFieldInt32Array(ref reader, fields, 26);
|
||||
var visibility = ReadFieldDouble(ref reader, fields, 27);
|
||||
var visibilitySourceCount = ReadFieldInt32(ref reader, fields, 28);
|
||||
SkipRemaining(ref reader, fields, 29);
|
||||
var contacts = ReadFieldEntityContacts(ref reader, fields, 29);
|
||||
SkipRemaining(ref reader, fields, 30);
|
||||
return new AetheriaRuntimeEntitySnapshot(
|
||||
name,
|
||||
kind,
|
||||
|
|
@ -571,7 +572,27 @@ namespace GameCult.Aetheria.State.Unity
|
|||
dockingBayContents,
|
||||
dockingBayAssignments,
|
||||
visibility,
|
||||
visibilitySourceCount);
|
||||
visibilitySourceCount,
|
||||
contacts);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AetheriaRuntimeEntityContactSnapshot> ReadFieldEntityContacts(ref MessagePackReader reader, int fields, int index)
|
||||
{
|
||||
if (index >= fields) return Array.Empty<AetheriaRuntimeEntityContactSnapshot>();
|
||||
var count = reader.ReadArrayHeader();
|
||||
var contacts = new AetheriaRuntimeEntityContactSnapshot[count];
|
||||
for (var contact = 0; contact < count; contact++)
|
||||
{
|
||||
var contactFields = reader.ReadArrayHeader();
|
||||
var targetEntityKey = ReadFieldString(ref reader, contactFields, 0);
|
||||
var infoGathered = ReadFieldDouble(ref reader, contactFields, 1);
|
||||
var hostile = ReadFieldBool(ref reader, contactFields, 2);
|
||||
var visible = ReadFieldBool(ref reader, contactFields, 3);
|
||||
SkipRemaining(ref reader, contactFields, 4);
|
||||
contacts[contact] = new AetheriaRuntimeEntityContactSnapshot(targetEntityKey, infoGathered, hostile, visible);
|
||||
}
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
private static Vector2Value ReadFieldVector2(ref MessagePackReader reader, int fields, int index)
|
||||
|
|
|
|||
|
|
@ -585,6 +585,25 @@ namespace GameCult.Aetheria.State.Unity
|
|||
|
||||
[Key(33)]
|
||||
public int VisibilitySourceCount { get; set; }
|
||||
|
||||
[Key(34)]
|
||||
public IReadOnlyList<AetheriaRuntimeEntityContactCommit> Contacts { get; set; } = Array.Empty<AetheriaRuntimeEntityContactCommit>();
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
[Key(0)]
|
||||
public int TargetEntityIndex { get; set; } = -1;
|
||||
|
||||
[Key(1)]
|
||||
public double InfoGathered { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public bool Hostile { get; set; }
|
||||
|
||||
[Key(3)]
|
||||
public bool Visible { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ 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, aggregate visibility/source-count state,
|
||||
contact rows for gathered information, hostility, and visible classification,
|
||||
and active consumable item/timer rows.
|
||||
Entity equipment, cargo-bay, and docking-bay slot rows preserve item-instance
|
||||
facts needed for restore: item key, quality, durability, and quantity. Those
|
||||
|
|
@ -850,6 +851,9 @@ First Aetheria surfaces to publish:
|
|||
- Done: preserve aggregate entity visibility and visibility-source count in
|
||||
typed entity snapshots, so `Entity.VisibilitySources` no longer hides
|
||||
untyped session pressure.
|
||||
- Done: preserve entity contact rows for gathered info, hostility, and
|
||||
visible classification, so detection/contact state is typed snapshot data
|
||||
instead of only reactive runtime collections.
|
||||
- Remaining: add typed documents/mappers for runtime object graphs,
|
||||
typed behavior factory construction, remaining behavior-private state not
|
||||
covered by progress, weapon, sensor, radiator, reactor, or capacitor rows,
|
||||
|
|
|
|||
Loading…
Reference in a new issue