From 1f8a1576f8e460d0e73696ebe11ac7cbf4ffee63 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 16:51:56 +0100 Subject: [PATCH] Persist entity contact state in typed snapshots --- .voidbot/state/aetheria.cc | Bin 86102 -> 86208 bytes Aetheria.State.Smoke/Program.cs | 15 +++++++++++ Aetheria.State.Unity.Smoke/Program.cs | 20 ++++++++++++++ .../AetheriaRuntimeCommitLogApplier.cs | 20 +++++++++++++- .../AetheriaRuntimeStateDocuments.cs | 20 ++++++++++++++ Assets/Scripts/Gameplay/ActionGameManager.cs | 20 ++++++++++++++ .../Runtime/AetheriaRuntimeCatalogSnapshot.cs | 21 ++++++++++++++- .../Runtime/AetheriaRuntimeCatalogStore.cs | 25 ++++++++++++++++-- .../AetheriaRuntimeStateCommitDocument.cs | 19 +++++++++++++ docs/aetheria-perfect-machine-map.md | 4 +++ 10 files changed, 160 insertions(+), 4 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 955bd9ee62b7b097b4f10d6590b6ce463f3a0327..a673484249eb1773c2976ffa6c3f37136876a583 100644 GIT binary patch delta 1149 zcmaJ>zfTlF6wV+Rv9QqSB}U^@E0Q}9^zK9uIJpBg6vV{FP&l_c$BwrmwKUP%#+VrY1mFIY7-Bbh^UZtT`@S#p<;BRymm?ou4~& zU%ghdjTSjG*dN--OVulJ>D(YXVvc2~s$FQ2(%{%IknQcU;oY%ea=l)Uv!$Xw8ZhT4+`lklDQnroxuLxrV{^LFtI=tu>Is&!@{y>394^~|@3xotTCQ=4ZYQueG zR|YCv*x;JCeFivHYu@2b0<(OU%-MIdWS)Gpzh=q#8n%LbY6sWVAQ*G8c|5(?Gl6gs zCGFdhv8y*pNyG#F1g}9wxIw@`wjZH1pcppQMn#EC2_wa582T9zkey;|vTU0=U0ldW z@c9PA_sAtaIMVl1tp{7J)0_KEi7FeQt_u`GswiIj2Z(Blk3!n_C3VMP5j{lOaX)RO zV$hSS3usE@yWsU*h1No9>KznwIa78bE#1x~oygT(3YCoaL&eb_C4rufT_UJr$xbCf{!}K8x%UP|U_&?WEUDHtJOdS~n zJc*{=n(b>nt^f1)%{C$x;C_GGSz(xw3*ljJGeH z?(X|rWSWqt_QxW*eCllx+Bey*+LoGRG5-l_)#P)q{U+HfYSVG8_$=yEDdcC1Y~_%8 T`fPV+iF_J8o_!(p=?8xR*jbzK delta 1038 zcmZ`&O-NKx6z2LzKM)Z}X@7@`z;Gs=nHV)OhfxWFveYOE>H6-OnVWC!ecX57$g~l0 z*&^x*cNG*-i&kyIWeP>x+5~PZ+O=@ezB}*D=%Bg0-}&x2=ljk%>#y3szifa1w&hUf z%1}C+Ol1dWpOMb+bIX$kGLVfB+4{JF4E9IJfi@srD?M#%J#Azl+kJteE}`NyQQFb+)@yz=ehi}Lu*8s zr_KU!p(VJyQn!U;xIEsu^kZ5f)i548mAt#{=v1W3_UPL_z?Gtiaj7blIAbWFpk#TM zHa`gyfws+Wx3MVI0$ACB%YdQ78Ny;I@;g>E6H7|CSV6bRS8Ic;prFEf%WVsY3|TK& z{f%rjcPL{(!J~@0Zu9pr_qCfmH@8ocTbG9Kpd%IgzuTAN@g?H%4P7G-@6S#u`3N1m z1$#V+X1~XxWWF~0*xq>5^|jlcxFgGDt_>L_D+7HqqPCJT1WYyUGTfR=nWq_&CfCh}965V*H`DyikzZymPcmyK z@+3vbs<|;n&L4iaLo~}{BwxS6bYFC-=I0pscpyIBbbq+jdOVb^9p=k8kqu&hbiih2 Wf-E? contacts, + string runId, + int zoneIndex) + { + return (contacts ?? Array.Empty()) + .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? states) { diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs index bbd1df5d4..2c074ddac 100644 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs @@ -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] diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 4f7061103..08a761b0c 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -787,6 +787,7 @@ public class ActionGameManager : MonoBehaviour .ToArray() ?? Array.Empty(), 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(IEnumerable bays) where TBay : EquippedCargoBay { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs index 2a2bc4d48..3baffd7eb 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs @@ -788,7 +788,8 @@ namespace GameCult.Aetheria.State.Unity IReadOnlyList dockingBayContents, IReadOnlyList dockingBayAssignments, double visibility, - int visibilitySourceCount) + int visibilitySourceCount, + IReadOnlyList 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 DockingBayAssignments { get; } public double Visibility { get; } public int VisibilitySourceCount { get; } + public IReadOnlyList 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 diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs index 9a0f75fd7..643c3778d 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs @@ -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 ReadFieldEntityContacts(ref MessagePackReader reader, int fields, int index) + { + if (index >= fields) return Array.Empty(); + 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) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs index 02d93c112..795d83d31 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs @@ -585,6 +585,25 @@ namespace GameCult.Aetheria.State.Unity [Key(33)] public int VisibilitySourceCount { get; set; } + + [Key(34)] + public IReadOnlyList Contacts { get; set; } = Array.Empty(); + } + + [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] diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 05abfcc92..eebfb8175 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -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,