From 794e65f4ad814aa9f0bfff211627991854841bc0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 07:52:01 +0100 Subject: [PATCH] Restore Continue entity graph from typed state --- .voidbot/state/aetheria.cc | Bin 148555 -> 148602 bytes Aetheria.State.Verify/Program.cs | 10 +- Aetheria.State/README.md | 10 +- Assets/Scripts/Gameplay/ActionGameManager.cs | 126 +++++++++++++++---- Assets/Scripts/Zone Display/ZoneRenderer.cs | 5 +- docs/aetheria-perfect-machine-map.md | 18 +-- 6 files changed, 125 insertions(+), 44 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 0ebdf5bdd51bdc9b5d5889084e60286122df49ea..61e56102ad60305715c20e612b257c90b5f26f3d 100644 GIT binary patch delta 1756 zcmah~J#QUF6xFUxgb_#-1Za>hhysWAW#YtsRw$eh+z<&FM3DkC-ktY$l--$SX7JDcOGAMWp`qZTutbBXBY`O6Cj1E+D$dM)*bYKeZ{O^ld(S=hoV)jKul#*` z<@>wgW9w^Mqs_s1V|4wq;A|x~!!wI};V0ek1$VqqOEh1N*nbCJ&_@P1#|giwx_Rw%i{GJcIoHszXjYm8TYD?6@}*R{&tM(udF zkjOP>u^~R=x~%h+WF*T?qAV3MMh1t~@D!-flg1r~)bT^P<`Z$yLy}C*PPOh|cP+aM z03*>BGt|+?3Y#eXGAQm*s`j}C4i8hQucS}tArqhRSuBGPymtLO&8JYA(jQ)mxQ7>r${DfDH#{{(X?|b5gJm}n=v?#n z3jymiJE$=cnKIi`DXh^gv%dE)MJZ(cjMgx<`M@x_C8 zhi{2%EXUnQX=fGBe{=c7x-1`p<)#e)Zq;~YwwkCnLG@rg{PNtnHz8+Cpq^|krnW8N z6zFvB6;y3s4U)qrFNPlk;rR>IosIAX_xsaExbehywHWKwU;Tw6_iC^iKJULK5eWopPVz*?l@HK@md}E=Q#YRJMtf=!wi6}k|GTyF z+pU!!zhlSy>znxo6mTt65*20ow%MhJB=q!KUS&j}n z%j|BabBUwPPz&BmRAEI?Vb54@dt)t9)6*5za_p%|QJ;vJn5nm#RiM#Wr4b4>0bZ6; zpaDBAF@>bk8VlQ_KVhd}L|#d5g(|>x2HzQMrd2i0;Id!j4xpWFPhQzfY1$lrn@BG5 z`_YXp4|67X*sxRZ(qziUJJj+WhZVTg$kQp5sEsfdEavo++e%}XjzgVu2{|{W3tZ<) zcj=$~_9rh=$SNtF3Tc&CvFD)keKc_c(G&fIa2mK8s4tH0!Xy((276qJlv@%aCD95) zlnEAfZg?5kCAEr1fr%iJl?B`{X^kkxb#$JcK4(B_S5t;c1aBy;=6VVvfii^&se-yz zUq`F6giA@9XM)bFv|AnYUs!a+h=sZ%Y60Al^tQy`+W_`{bm{}H$7o-6S)c_iaC+uk zI5J?cc&2%odE>OnL{o~Y6H|RCVmYQdN0)kHAQ8lQRNL`7b}b#TbvFJd_!19E~9>v7l^u#$mX{7*l~b~ zsz}-)f}t4O-cS=)2ZXxn`s_ip8nOD{K6`#fju8#t%>(tVg6Ivg(2yM5MJyW$9#XTE zeCo`VA`zC@3{_R6#Ddh;@D1(1 z_u`f@Xifd`@6mJhmE+Nwi*JN6geT5}ck{t8ID7nk(u~Et{PWWMkGrR~w--qrDq3?QZvdx{Jtr9>y_sS3q;u zJjTA>x*TQom1o&AhdZyaam4!P>z@Yf1^4`Gzy>GoEHIh++y)!C7oTphv+W1q)mPj% uqQ1SseqNLY{ string.Equals(entity.RecordKey, entityKey, StringComparison.Ordinal)); + entitySnapshots = AetheriaRuntimeCatalogStore.ReadEntitySnapshots(RuntimeStateFilePath) + .Where(entity => entity.RecordKey.StartsWith(zoneEntityKeyPrefix, StringComparison.Ordinal)) + .OrderBy(entity => EntityIndexFromRecordKey(entity.RecordKey)) + .ToArray(); } catch (Exception ex) { - Debug.LogError($"Failed to read typed current entity snapshot {entityKey}: {ex}"); + Debug.LogError($"Failed to read typed entity snapshots for zone {zoneEntityKeyPrefix}: {ex}"); return; } - var blueprint = CreateEntityConstructionBlueprint(entitySnapshot, true); - if (blueprint == null) + if (entitySnapshots.Length == 0) { - Debug.LogWarning($"Typed current entity snapshot {entityKey} could not be lowered into a runtime entity."); + Debug.LogWarning($"No typed entity snapshots found for restored zone {zoneEntityKeyPrefix}."); return; } - var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(ItemManager, Zone, blueprint, true); - if (entity == null) - return; + ReplaceZoneEntitiesFromTypedSnapshots(entitySnapshots, currentEntityKey); + } - entity.Position = new float3((float)entitySnapshot.PositionX, (float)entitySnapshot.PositionY, (float)entitySnapshot.PositionZ); - entity.Direction = new float2((float)entitySnapshot.DirectionX, (float)entitySnapshot.DirectionY); - entity.Velocity = new float2((float)entitySnapshot.VelocityX, (float)entitySnapshot.VelocityY); - entity.OverrideShutdown = entitySnapshot.OverrideShutdown; - entity.TractorPower = (float)entitySnapshot.TractorPower; - entity.Zone = Zone; - Zone.Entities.Add(entity); - entity.Activate(); - entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled; - entity.RestoreStatGrids(entitySnapshot.StatGrids); - entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia); - RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot); - BindToEntity(entity); + private void ReplaceZoneEntitiesFromTypedSnapshots( + IReadOnlyList entitySnapshots, + string currentEntityKey) + { + foreach (var existingEntity in Zone.Entities.ToArray()) + { + existingEntity.Deactivate(); + Zone.Entities.Remove(existingEntity); + } + Zone.Agents.Clear(); + + var restoredEntities = new Dictionary(); + foreach (var entitySnapshot in entitySnapshots) + { + var blueprint = CreateEntityConstructionBlueprint( + entitySnapshot, + string.Equals(entitySnapshot.RecordKey, currentEntityKey, StringComparison.Ordinal)); + if (blueprint == null) + { + Debug.LogWarning($"Typed entity snapshot {entitySnapshot.RecordKey} could not be lowered into a runtime entity."); + continue; + } + + var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(ItemManager, Zone, blueprint, true); + if (entity == null) + continue; + + entity.Position = new float3((float)entitySnapshot.PositionX, (float)entitySnapshot.PositionY, (float)entitySnapshot.PositionZ); + entity.Direction = new float2((float)entitySnapshot.DirectionX, (float)entitySnapshot.DirectionY); + entity.Velocity = new float2((float)entitySnapshot.VelocityX, (float)entitySnapshot.VelocityY); + entity.OverrideShutdown = entitySnapshot.OverrideShutdown; + entity.TractorPower = (float)entitySnapshot.TractorPower; + entity.Zone = Zone; + Zone.Entities.Add(entity); + restoredEntities[entitySnapshot.RecordKey] = entity; + } + + foreach (var entity in restoredEntities.Values) + entity.Activate(); + + foreach (var entitySnapshot in entitySnapshots) + { + if (!restoredEntities.TryGetValue(entitySnapshot.RecordKey, out var entity)) + continue; + + entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled; + entity.RestoreStatGrids(entitySnapshot.StatGrids); + entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia); + RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot); + RestoreEntityContactsFromTypedSnapshot(entity, entitySnapshot, restoredEntities); + if (restoredEntities.TryGetValue(entitySnapshot.TargetEntityKey, out var target)) + entity.Target.Value = target; + } + + if (restoredEntities.TryGetValue(currentEntityKey, out var currentEntity)) + BindToEntity(currentEntity); + } + + private void RestoreEntityContactsFromTypedSnapshot( + Entity entity, + AetheriaRuntimeEntitySnapshot snapshot, + IReadOnlyDictionary restoredEntities) + { + foreach (var contact in snapshot.Contacts) + { + if (!restoredEntities.TryGetValue(contact.TargetEntityKey, out var target)) + continue; + + entity.EntityInfoGathered[target] = (float)contact.InfoGathered; + entity.EntityHostility[target] = contact.Hostile; + if (contact.Visible && !entity.VisibleEntities.Contains(target)) + entity.VisibleEntities.Add(target); + if (contact.Visible && contact.Hostile && !entity.VisibleEnemies.Contains(target)) + entity.VisibleEnemies.Add(target); + if (contact.Visible && !contact.Hostile && !entity.VisibleFriendlies.Contains(target)) + entity.VisibleFriendlies.Add(target); + } + } + + private static int EntityIndexFromRecordKey(string recordKey) + { + var match = Regex.Match(recordKey ?? "", @"\.entity\.(\d+)\.v1$"); + return match.Success && int.TryParse(match.Groups[1].Value, out var index) + ? index + : int.MaxValue; } private void RestoreActiveConsumablesFromTypedEntitySnapshot(Entity entity, AetheriaRuntimeEntitySnapshot snapshot) diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index bd0a06ab3..145d04532 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -338,7 +338,10 @@ public class ZoneRenderer : MonoBehaviour } } - Destroy(EntityInstances[entity].gameObject); + if (!EntityInstances.TryGetValue(entity, out var instance)) + return; + + Destroy(instance.gameObject); EntityInstances.Remove(entity); } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index ebf7c4556..6002d0dcd 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -968,21 +968,21 @@ First Aetheria surfaces to publish: galaxy from the saved generation seed, and passes the selected run to `ActionGameManager` so boot enters the saved current zone and lowers typed pickup rows. - - Done: restore the current player entity from typed state during Continue - boot. Package entity readback now preserves `AetheriaEntitySnapshot` record - identity, and `ActionGameManager` resolves the exact - `RunId + ZoneIndex + CurrentZoneEntityIndex` entity record, lowers it + - Done: restore the current zone's entity graph from typed state during + Continue boot. Package entity readback preserves `AetheriaEntitySnapshot` + record identity, and `ActionGameManager` resolves the exact + `RunId + ZoneIndex` entity-record prefix, removes generated zone entities + and their agents as Continue authority, lowers all typed entity snapshots through the existing construction blueprint path, restores hull, equipment, cargo, docking contents, weapon groups, position, direction, velocity, shutdown override, heatsink toggle, tractor power, heatstroke/hypothermia exposure, active consumable item/timer rows, - and typed stat grids for temperature, thermal mass, armor, max armor, - and hull-conductivity axes, then binds the lowered entity as the current - player ship. + typed stat grids for temperature, thermal mass, armor, max armor, and + hull-conductivity axes, then reconnects target/contact rows among restored + entities and binds the saved current entity as the 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, restored active-consumable behavior runtime internals, + for child/docked entity activation, 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.