diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs
index 4ee1484af..d7232dd37 100644
--- a/Aetheria.State.Verify/Program.cs
+++ b/Aetheria.State.Verify/Program.cs
@@ -12904,10 +12904,16 @@ static void RequireMainMenuContinueRunState(string root)
"Unity still owns daemon entity snapshot projection; package runtime should lower typed daemon entity snapshots.");
}
+ var zoneRenderDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeRtsViewportDocuments.cs");
+ var zoneRenderDocuments = File.Exists(zoneRenderDocumentsPath)
+ ? File.ReadAllText(zoneRenderDocumentsPath)
+ : throw new InvalidOperationException("Cannot verify Continue entity snapshot access; AetheriaRuntimeRtsViewportDocuments.cs is missing.");
var daemonEntitySnapshotProjectorPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEntitySnapshotProjector.cs");
- var daemonEntitySnapshotProjector = File.Exists(daemonEntitySnapshotProjectorPath)
- ? File.ReadAllText(daemonEntitySnapshotProjectorPath)
- : throw new InvalidOperationException("Cannot verify Continue entity projection; AetheriaRuntimeEntitySnapshotProjector.cs is missing.");
+ if (File.Exists(daemonEntitySnapshotProjectorPath))
+ {
+ throw new InvalidOperationException(
+ "AetheriaRuntimeEntitySnapshotProjector is dead access chaff; AetheriaRuntimeZoneRenderDocument owns managed entity snapshot access.");
+ }
var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs");
var observedEntityRestorer = File.Exists(observedEntityRestorerPath)
? File.ReadAllText(observedEntityRestorerPath)
@@ -13007,27 +13013,27 @@ static void RequireMainMenuContinueRunState(string root)
string.Join(", ", gameplayCatalogPolicyHits));
}
- var requiredEntitySnapshotProjectorSymbols = new[]
+ var requiredZoneRenderSnapshotSymbols = new[]
{
- "public static class AetheriaRuntimeEntitySnapshotProjector",
- "public static AetheriaRuntimeEntitySnapshot[] CreateSnapshots(",
- "public static string DaemonEntityRecordKey(string runId, int zoneIndex, int entityIndex)",
+ "public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()",
+ "public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(",
+ "public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)",
"new AetheriaRuntimeEntitySnapshot(",
"entity.EntityIndex,",
- "CreateItemSlots(entity.Equipment)",
+ "CreateEntityItemSlots(entity.Equipment)",
"CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)",
"CreateBehaviorStates(entity.BehaviorStates)",
"CreateCargoBays(entity.CargoContents)",
"new AetheriaRuntimeEntityContactSnapshot("
};
- var missingEntitySnapshotProjectorSymbols = requiredEntitySnapshotProjectorSymbols
- .Where(symbol => !daemonEntitySnapshotProjector.Contains(symbol, StringComparison.Ordinal))
+ var missingZoneRenderSnapshotSymbols = requiredZoneRenderSnapshotSymbols
+ .Where(symbol => !zoneRenderDocuments.Contains(symbol, StringComparison.Ordinal))
.ToArray();
- if (missingEntitySnapshotProjectorSymbols.Length > 0)
+ if (missingZoneRenderSnapshotSymbols.Length > 0)
{
throw new InvalidOperationException(
- "Daemon entity snapshot projection must live in the runtime package outside ActionGameManager and Unity Assets: " +
- string.Join(", ", missingEntitySnapshotProjectorSymbols));
+ "Managed daemon entity snapshot access must live on AetheriaRuntimeZoneRenderDocument instead of a projector-shaped helper: " +
+ string.Join(", ", missingZoneRenderSnapshotSymbols));
}
var forbiddenManagerSnapshotProjectionSymbols = new[]
@@ -13204,7 +13210,7 @@ static void RequireMainMenuContinueRunState(string root)
"private bool TryRestoreEntityGraphFromZoneRender(",
"if (string.IsNullOrWhiteSpace(render.RunId))",
"Aetheria zone-render feed does not identify a run id.",
- "AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, render.ZoneIndex, render.EntitySnapshots)",
+ "render.CreateEntitySnapshots()",
".OrderBy(entity => entity.EntityIndex)",
"_entityRestorer.TryApplyInPlace(",
"zoneRenderer?.ApplyZoneRender(render)",
@@ -13827,6 +13833,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
var daemonOperationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs");
var daemonRuntimeOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs");
var daemonIntentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonIntentState.cs");
+ var zoneRenderDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeRtsViewportDocuments.cs");
var daemonObserverPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs");
var pilotCommandSenderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotCommandSender.cs");
var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs");
@@ -13897,9 +13904,14 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
"Unity still owns daemon entity snapshot projection; package runtime should lower typed daemon entity snapshots.");
}
- var daemonEntitySnapshotProjector = File.Exists(daemonEntitySnapshotProjectorPath)
- ? File.ReadAllText(daemonEntitySnapshotProjectorPath)
- : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaRuntimeEntitySnapshotProjector.cs is missing.");
+ var zoneRenderDocuments = File.Exists(zoneRenderDocumentsPath)
+ ? File.ReadAllText(zoneRenderDocumentsPath)
+ : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaRuntimeRtsViewportDocuments.cs is missing.");
+ if (File.Exists(daemonEntitySnapshotProjectorPath))
+ {
+ throw new InvalidOperationException(
+ "AetheriaRuntimeEntitySnapshotProjector is dead access chaff; AetheriaRuntimeZoneRenderDocument owns managed entity snapshot access.");
+ }
var observedEntityRestorer = File.Exists(observedEntityRestorerPath)
? File.ReadAllText(observedEntityRestorerPath)
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedEntityRestorer.cs is missing.");
@@ -14030,15 +14042,15 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
string.Join(", ", missingSceneWiringObserverSymbols));
}
- if (!daemonEntitySnapshotProjector.Contains("new AetheriaRuntimeEntitySnapshot(", StringComparison.Ordinal) ||
- !daemonEntitySnapshotProjector.Contains("CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)", StringComparison.Ordinal) ||
- !daemonEntitySnapshotProjector.Contains("entity.EntityIndex,", StringComparison.Ordinal))
+ if (!zoneRenderDocuments.Contains("new AetheriaRuntimeEntitySnapshot(", StringComparison.Ordinal) ||
+ !zoneRenderDocuments.Contains("CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)", StringComparison.Ordinal) ||
+ !zoneRenderDocuments.Contains("entity.EntityIndex,", StringComparison.Ordinal))
{
throw new InvalidOperationException(
- "Unity observer entity projection no longer carries typed daemon entity indices through the dedicated daemon snapshot projector.");
+ "Unity observer entity snapshot access no longer carries typed daemon entity indices through AetheriaRuntimeZoneRenderDocument.");
}
- if (daemonEntitySnapshotProjector.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) ||
+ if (zoneRenderDocuments.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) ||
observedFrameApplier.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) ||
observedEntityRestorer.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal))
{
@@ -14115,13 +14127,35 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
"Unity observer zone context lowering no longer flows through the dedicated observed zone context factory.");
}
+ var requiredZoneRenderSnapshotSymbols = new[]
+ {
+ "public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()",
+ "public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(",
+ "public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)",
+ "new AetheriaRuntimeEntitySnapshot(",
+ "CreateEntityItemSlots(entity.Equipment)",
+ "CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)",
+ "CreateBehaviorStates(entity.BehaviorStates)",
+ "CreateCargoBays(entity.CargoContents)",
+ "new AetheriaRuntimeEntityContactSnapshot("
+ };
+ var missingZoneRenderSnapshotSymbols = requiredZoneRenderSnapshotSymbols
+ .Where(symbol => !zoneRenderDocuments.Contains(symbol, StringComparison.Ordinal))
+ .ToArray();
+ if (missingZoneRenderSnapshotSymbols.Length > 0)
+ {
+ throw new InvalidOperationException(
+ "Managed daemon entity snapshot access must live on AetheriaRuntimeZoneRenderDocument instead of a projector-shaped helper: " +
+ string.Join(", ", missingZoneRenderSnapshotSymbols));
+ }
+
var requiredObservedFrameApplierSymbols = new[]
{
"public sealed class AetheriaUnityObservedFrameApplier",
"public bool ApplyLatestZoneRender()",
"observer.LastRenderView?.ZoneRender",
"private bool TryRestoreEntityGraphFromZoneRender(",
- "AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, render.ZoneIndex, render.EntitySnapshots)",
+ "render.CreateEntitySnapshots()",
".OrderBy(entity => entity.EntityIndex)",
"_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())",
"_entityRestorer.TryApplyInPlace(",
diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs b/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs
index 2980b2249..ab457c46b 100644
--- a/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs
+++ b/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs
@@ -90,7 +90,7 @@ public sealed class AetheriaUnityObservedFrameApplier
return false;
}
- var entitySnapshots = AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, render.ZoneIndex, render.EntitySnapshots)
+ var entitySnapshots = render.CreateEntitySnapshots()
.OrderBy(entity => entity.EntityIndex)
.ToArray();
diff --git a/GameCult.Aetheria.State.Unity.csproj b/GameCult.Aetheria.State.Unity.csproj
index 0b0db4484..b9688c828 100644
--- a/GameCult.Aetheria.State.Unity.csproj
+++ b/GameCult.Aetheria.State.Unity.csproj
@@ -123,7 +123,6 @@
-
diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEntitySnapshotProjector.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEntitySnapshotProjector.cs
deleted file mode 100644
index 116bf7469..000000000
--- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEntitySnapshotProjector.cs
+++ /dev/null
@@ -1,255 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-#nullable enable
-
-namespace GameCult.Aetheria.State.Verse
-{
- public static class AetheriaRuntimeEntitySnapshotProjector
- {
- public static AetheriaRuntimeEntitySnapshot[] CreateSnapshots(
- string runId,
- AetheriaRuntimeZoneSnapshotCommit? zone)
- {
- if (zone == null)
- return Array.Empty();
-
- return CreateSnapshots(runId, zone.ZoneIndex, zone.Entities);
- }
-
- public static AetheriaRuntimeEntitySnapshot[] CreateSnapshots(
- string runId,
- int zoneIndex,
- IReadOnlyList? entities)
- {
- return (entities ?? Array.Empty())
- .Where(entity => entity != null)
- .Select(entity => CreateSnapshot(runId, zoneIndex, entity))
- .ToArray();
- }
-
- public static string DaemonEntityRecordKey(string runId, int zoneIndex, int entityIndex)
- {
- return string.IsNullOrWhiteSpace(runId)
- ? ""
- : $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1";
- }
-
- private static AetheriaRuntimeEntitySnapshot CreateSnapshot(
- string runId,
- int zoneIndex,
- AetheriaRuntimeEntitySnapshotCommit entity)
- {
- return new AetheriaRuntimeEntitySnapshot(
- DaemonEntityRecordKey(runId, zoneIndex, entity.EntityIndex),
- entity.EntityIndex,
- entity.Name ?? "",
- entity.Kind ?? "",
- entity.PositionX,
- entity.PositionY,
- entity.PositionZ,
- entity.DirectionX,
- entity.DirectionY,
- entity.FactionKey ?? "",
- entity.HullItemKey ?? "",
- CreateItemSlots(entity.Equipment),
- CreateItemSlots(entity.CargoBays),
- CreateItemSlots(entity.DockingBays),
- (entity.ChildEntityIndices ?? Array.Empty())
- .Where(index => index >= 0)
- .Select(index => DaemonEntityRecordKey(runId, zoneIndex, index))
- .ToArray(),
- (entity.WeaponGroups ?? Array.Empty>())
- .Select(group => (IReadOnlyList)(group ?? Array.Empty()).ToArray())
- .ToArray(),
- (entity.StatGrids ?? Array.Empty())
- .Select(grid => new AetheriaRuntimeEntityStatGridSnapshot(
- grid.Name ?? "",
- grid.Width,
- grid.Height,
- (grid.Values ?? Array.Empty()).ToArray()))
- .ToArray(),
- entity.VelocityX,
- entity.VelocityY,
- entity.TargetEntityIndex < 0 ? "" : DaemonEntityRecordKey(runId, zoneIndex, entity.TargetEntityIndex),
- entity.IsActive,
- entity.HeatsinksEnabled,
- entity.OverrideShutdown,
- entity.TractorPower,
- entity.Heatstroke,
- entity.Hypothermia,
- (entity.ActiveConsumables ?? Array.Empty())
- .Select(consumable => new AetheriaRuntimeActiveConsumableSnapshot(
- consumable.ItemKey ?? "",
- consumable.Quality,
- consumable.RemainingDuration,
- consumable.Duration))
- .ToArray(),
- (entity.BehaviorProgress ?? Array.Empty())
- .Select(progress => new AetheriaRuntimeBehaviorProgressSnapshot(
- progress.OwnerKind ?? "",
- progress.OwnerIndex,
- progress.BehaviorIndex,
- progress.BehaviorKind ?? "",
- progress.Progress))
- .ToArray(),
- CreateWeaponStates(runId, zoneIndex, entity.WeaponStates),
- CreateBehaviorStates(entity.BehaviorStates),
- CreateCargoBays(entity.CargoContents),
- CreateCargoBays(entity.DockingBayContents),
- (entity.DockingBayAssignments ?? Array.Empty()).ToArray(),
- entity.Visibility,
- entity.VisibilitySourceCount,
- (entity.Contacts ?? Array.Empty())
- .Where(contact => contact != null && contact.TargetEntityIndex >= 0)
- .Select(contact => new AetheriaRuntimeEntityContactSnapshot(
- DaemonEntityRecordKey(runId, zoneIndex, contact.TargetEntityIndex),
- contact.InfoGathered,
- contact.Hostile,
- contact.Visible))
- .ToArray(),
- entity.ShutdownPerformance);
- }
-
- private static AetheriaRuntimeEntityItemSlotSnapshot[] CreateItemSlots(
- IReadOnlyList? slots)
- {
- return (slots ?? Array.Empty())
- .Where(slot => slot?.Item != null)
- .Select(slot => new AetheriaRuntimeEntityItemSlotSnapshot(
- slot.X,
- slot.Y,
- slot.Item!.ItemKey ?? "",
- slot.Item.Quality,
- slot.Item.Durability,
- slot.Item.Quantity,
- slot.Item.Enabled,
- slot.Item.OverrideShutdown,
- slot.Item.Temperature))
- .ToArray();
- }
-
- private static AetheriaRuntimeCargoBayLoadoutSnapshot[] CreateCargoBays(
- IReadOnlyList? cargoBays)
- {
- return (cargoBays ?? Array.Empty())
- .Select(bay => new AetheriaRuntimeCargoBayLoadoutSnapshot(
- CreateLoadoutSlots(bay?.Items)))
- .ToArray();
- }
-
- private static AetheriaRuntimeLoadoutItemSlotSnapshot[] CreateLoadoutSlots(
- IReadOnlyList? slots)
- {
- return (slots ?? Array.Empty())
- .Where(slot => slot?.Item != null)
- .Select(slot => new AetheriaRuntimeLoadoutItemSlotSnapshot(
- slot.X,
- slot.Y,
- new AetheriaRuntimeLoadoutItemSnapshot(
- slot.Item!.ItemKey ?? "",
- slot.Item.Quality,
- slot.Item.Durability,
- slot.Item.Quantity,
- slot.Item.Enabled,
- slot.Item.OverrideShutdown,
- slot.Item.Temperature)))
- .ToArray();
- }
-
- private static AetheriaRuntimeWeaponStateSnapshot[] CreateWeaponStates(
- string runId,
- int zoneIndex,
- IReadOnlyList? weaponStates)
- {
- return (weaponStates ?? Array.Empty())
- .Where(state => state != null)
- .Select(state => new AetheriaRuntimeWeaponStateSnapshot(
- state.OwnerKind ?? "",
- state.OwnerIndex,
- state.BehaviorIndex,
- state.BehaviorKind ?? "",
- state.Firing,
- state.Ammo,
- state.BurstRemaining,
- state.BurstTimer,
- state.BurstInterval,
- state.CooldownProgress,
- state.CoolingDown,
- state.Charging,
- state.Charged,
- state.Charge,
- state.Reloading,
- state.ReloadProgress,
- state.AmmoIntervalProgress,
- state.LockProgress,
- state.LockTargetEntityIndex < 0 ? "" : DaemonEntityRecordKey(runId, zoneIndex, state.LockTargetEntityIndex)))
- .ToArray();
- }
-
- private static AetheriaRuntimeBehaviorStateSnapshot[] CreateBehaviorStates(
- IReadOnlyList? behaviorStates)
- {
- return (behaviorStates ?? Array.Empty())
- .Where(state => state != null)
- .Select(state => new AetheriaRuntimeBehaviorStateSnapshot(
- state.OwnerKind ?? "",
- state.OwnerIndex,
- state.BehaviorIndex,
- state.BehaviorKind ?? "",
- state.Pinging,
- state.PingCooldown,
- state.PingLerp,
- state.PingRadius,
- state.PingedEntityCount,
- state.RadiatorTemperature,
- state.Emissivity,
- state.PumpedHeat,
- state.WasteHeat,
- state.EnergyUsage,
- state.ReactorDraw,
- state.ReactorLoadRatio,
- state.CapacitorCharge,
- state.CapacitorCapacity,
- state.CapacitorEfficiency,
- state.AetherDriveAxisX,
- state.AetherDriveAxisY,
- state.AetherDriveAxisZ,
- state.AetherDriveThrustX,
- state.AetherDriveThrustY,
- state.AetherDriveThrustZ,
- state.AetherDriveRpmX,
- state.AetherDriveRpmY,
- state.AetherDriveRpmZ,
- state.AetherDriveMaximumRpm,
- state.AetherDriveThrustDirectionX,
- state.AetherDriveThrustDirectionY,
- state.ResourceScannerTargetBodyKey ?? "",
- state.ResourceScannerAsteroidIndex,
- state.ResourceScannerScanTime,
- state.ResourceScannerRange,
- state.ResourceScannerMinimumDensity,
- state.ResourceScannerScanDuration,
- state.MiningToolAsteroidBeltKey ?? "",
- state.MiningToolAsteroidIndex,
- state.MiningToolRange,
- state.ThrusterAxis,
- state.ThrusterThrust,
- state.ThrusterTorque,
- state.ShieldEfficiency,
- state.ShieldEnergyUsage,
- state.VelocityLimit,
- state.ThermotoggleTargetTemperature,
- state.SwitchActivated,
- state.TriggerPulled,
- state.StatModifierApplied,
- state.StatModifierExecuted,
- state.StatModifierTargetStatCount,
- state.TurretControllerWeaponCount,
- state.TurretControllerShotSpeed,
- state.TurretControllerPredictShots))
- .ToArray();
- }
- }
-}
diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEntitySnapshotProjector.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEntitySnapshotProjector.cs.meta
deleted file mode 100644
index d01e8c8a1..000000000
--- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEntitySnapshotProjector.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 1625cd76fccf6284bb9d97aa5b755a59
\ No newline at end of file
diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsViewportDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsViewportDocuments.cs
index 9a156254a..6d24030fc 100644
--- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsViewportDocuments.cs
+++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsViewportDocuments.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using GameCult.Caching;
using MessagePack;
@@ -879,6 +880,255 @@ namespace GameCult.Aetheria.State.Verse
public IReadOnlyList Bodies { get; set; } =
Array.Empty();
+ public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)
+ {
+ return string.IsNullOrWhiteSpace(runId)
+ ? ""
+ : $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1";
+ }
+
+ public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()
+ {
+ return CreateEntitySnapshots(RunId, ZoneIndex, EntitySnapshots);
+ }
+
+ public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(
+ string runId,
+ AetheriaRuntimeZoneSnapshotCommit? zone)
+ {
+ if (zone == null)
+ return Array.Empty();
+
+ return CreateEntitySnapshots(runId, zone.ZoneIndex, zone.Entities);
+ }
+
+ public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(
+ string runId,
+ int zoneIndex,
+ IReadOnlyList? entities)
+ {
+ return (entities ?? Array.Empty())
+ .Where(entity => entity != null)
+ .Select(entity => CreateEntitySnapshot(runId, zoneIndex, entity))
+ .ToArray();
+ }
+
+ private static AetheriaRuntimeEntitySnapshot CreateEntitySnapshot(
+ string runId,
+ int zoneIndex,
+ AetheriaRuntimeEntitySnapshotCommit entity)
+ {
+ return new AetheriaRuntimeEntitySnapshot(
+ EntityRecordKey(runId, zoneIndex, entity.EntityIndex),
+ entity.EntityIndex,
+ entity.Name ?? "",
+ entity.Kind ?? "",
+ entity.PositionX,
+ entity.PositionY,
+ entity.PositionZ,
+ entity.DirectionX,
+ entity.DirectionY,
+ entity.FactionKey ?? "",
+ entity.HullItemKey ?? "",
+ CreateEntityItemSlots(entity.Equipment),
+ CreateEntityItemSlots(entity.CargoBays),
+ CreateEntityItemSlots(entity.DockingBays),
+ (entity.ChildEntityIndices ?? Array.Empty())
+ .Where(index => index >= 0)
+ .Select(index => EntityRecordKey(runId, zoneIndex, index))
+ .ToArray(),
+ (entity.WeaponGroups ?? Array.Empty>())
+ .Select(group => (IReadOnlyList)(group ?? Array.Empty()).ToArray())
+ .ToArray(),
+ (entity.StatGrids ?? Array.Empty())
+ .Select(grid => new AetheriaRuntimeEntityStatGridSnapshot(
+ grid.Name ?? "",
+ grid.Width,
+ grid.Height,
+ (grid.Values ?? Array.Empty()).ToArray()))
+ .ToArray(),
+ entity.VelocityX,
+ entity.VelocityY,
+ entity.TargetEntityIndex < 0 ? "" : EntityRecordKey(runId, zoneIndex, entity.TargetEntityIndex),
+ entity.IsActive,
+ entity.HeatsinksEnabled,
+ entity.OverrideShutdown,
+ entity.TractorPower,
+ entity.Heatstroke,
+ entity.Hypothermia,
+ (entity.ActiveConsumables ?? Array.Empty())
+ .Select(consumable => new AetheriaRuntimeActiveConsumableSnapshot(
+ consumable.ItemKey ?? "",
+ consumable.Quality,
+ consumable.RemainingDuration,
+ consumable.Duration))
+ .ToArray(),
+ (entity.BehaviorProgress ?? Array.Empty())
+ .Select(progress => new AetheriaRuntimeBehaviorProgressSnapshot(
+ progress.OwnerKind ?? "",
+ progress.OwnerIndex,
+ progress.BehaviorIndex,
+ progress.BehaviorKind ?? "",
+ progress.Progress))
+ .ToArray(),
+ CreateWeaponStates(runId, zoneIndex, entity.WeaponStates),
+ CreateBehaviorStates(entity.BehaviorStates),
+ CreateCargoBays(entity.CargoContents),
+ CreateCargoBays(entity.DockingBayContents),
+ (entity.DockingBayAssignments ?? Array.Empty()).ToArray(),
+ entity.Visibility,
+ entity.VisibilitySourceCount,
+ (entity.Contacts ?? Array.Empty())
+ .Where(contact => contact != null && contact.TargetEntityIndex >= 0)
+ .Select(contact => new AetheriaRuntimeEntityContactSnapshot(
+ EntityRecordKey(runId, zoneIndex, contact.TargetEntityIndex),
+ contact.InfoGathered,
+ contact.Hostile,
+ contact.Visible))
+ .ToArray(),
+ entity.ShutdownPerformance);
+ }
+
+ private static AetheriaRuntimeEntityItemSlotSnapshot[] CreateEntityItemSlots(
+ IReadOnlyList? slots)
+ {
+ return (slots ?? Array.Empty())
+ .Where(slot => slot?.Item != null)
+ .Select(slot => new AetheriaRuntimeEntityItemSlotSnapshot(
+ slot.X,
+ slot.Y,
+ slot.Item!.ItemKey ?? "",
+ slot.Item.Quality,
+ slot.Item.Durability,
+ slot.Item.Quantity,
+ slot.Item.Enabled,
+ slot.Item.OverrideShutdown,
+ slot.Item.Temperature))
+ .ToArray();
+ }
+
+ private static AetheriaRuntimeCargoBayLoadoutSnapshot[] CreateCargoBays(
+ IReadOnlyList? cargoBays)
+ {
+ return (cargoBays ?? Array.Empty())
+ .Select(bay => new AetheriaRuntimeCargoBayLoadoutSnapshot(
+ CreateLoadoutSlots(bay?.Items)))
+ .ToArray();
+ }
+
+ private static AetheriaRuntimeLoadoutItemSlotSnapshot[] CreateLoadoutSlots(
+ IReadOnlyList? slots)
+ {
+ return (slots ?? Array.Empty())
+ .Where(slot => slot?.Item != null)
+ .Select(slot => new AetheriaRuntimeLoadoutItemSlotSnapshot(
+ slot.X,
+ slot.Y,
+ new AetheriaRuntimeLoadoutItemSnapshot(
+ slot.Item!.ItemKey ?? "",
+ slot.Item.Quality,
+ slot.Item.Durability,
+ slot.Item.Quantity,
+ slot.Item.Enabled,
+ slot.Item.OverrideShutdown,
+ slot.Item.Temperature)))
+ .ToArray();
+ }
+
+ private static AetheriaRuntimeWeaponStateSnapshot[] CreateWeaponStates(
+ string runId,
+ int zoneIndex,
+ IReadOnlyList? weaponStates)
+ {
+ return (weaponStates ?? Array.Empty())
+ .Where(state => state != null)
+ .Select(state => new AetheriaRuntimeWeaponStateSnapshot(
+ state.OwnerKind ?? "",
+ state.OwnerIndex,
+ state.BehaviorIndex,
+ state.BehaviorKind ?? "",
+ state.Firing,
+ state.Ammo,
+ state.BurstRemaining,
+ state.BurstTimer,
+ state.BurstInterval,
+ state.CooldownProgress,
+ state.CoolingDown,
+ state.Charging,
+ state.Charged,
+ state.Charge,
+ state.Reloading,
+ state.ReloadProgress,
+ state.AmmoIntervalProgress,
+ state.LockProgress,
+ state.LockTargetEntityIndex < 0 ? "" : EntityRecordKey(runId, zoneIndex, state.LockTargetEntityIndex)))
+ .ToArray();
+ }
+
+ private static AetheriaRuntimeBehaviorStateSnapshot[] CreateBehaviorStates(
+ IReadOnlyList? behaviorStates)
+ {
+ return (behaviorStates ?? Array.Empty())
+ .Where(state => state != null)
+ .Select(state => new AetheriaRuntimeBehaviorStateSnapshot(
+ state.OwnerKind ?? "",
+ state.OwnerIndex,
+ state.BehaviorIndex,
+ state.BehaviorKind ?? "",
+ state.Pinging,
+ state.PingCooldown,
+ state.PingLerp,
+ state.PingRadius,
+ state.PingedEntityCount,
+ state.RadiatorTemperature,
+ state.Emissivity,
+ state.PumpedHeat,
+ state.WasteHeat,
+ state.EnergyUsage,
+ state.ReactorDraw,
+ state.ReactorLoadRatio,
+ state.CapacitorCharge,
+ state.CapacitorCapacity,
+ state.CapacitorEfficiency,
+ state.AetherDriveAxisX,
+ state.AetherDriveAxisY,
+ state.AetherDriveAxisZ,
+ state.AetherDriveThrustX,
+ state.AetherDriveThrustY,
+ state.AetherDriveThrustZ,
+ state.AetherDriveRpmX,
+ state.AetherDriveRpmY,
+ state.AetherDriveRpmZ,
+ state.AetherDriveMaximumRpm,
+ state.AetherDriveThrustDirectionX,
+ state.AetherDriveThrustDirectionY,
+ state.ResourceScannerTargetBodyKey ?? "",
+ state.ResourceScannerAsteroidIndex,
+ state.ResourceScannerScanTime,
+ state.ResourceScannerRange,
+ state.ResourceScannerMinimumDensity,
+ state.ResourceScannerScanDuration,
+ state.MiningToolAsteroidBeltKey ?? "",
+ state.MiningToolAsteroidIndex,
+ state.MiningToolRange,
+ state.ThrusterAxis,
+ state.ThrusterThrust,
+ state.ThrusterTorque,
+ state.ShieldEfficiency,
+ state.ShieldEnergyUsage,
+ state.VelocityLimit,
+ state.ThermotoggleTargetTemperature,
+ state.SwitchActivated,
+ state.TriggerPulled,
+ state.StatModifierApplied,
+ state.StatModifierExecuted,
+ state.StatModifierTargetStatCount,
+ state.TurretControllerWeaponCount,
+ state.TurretControllerShotSpeed,
+ state.TurretControllerPredictShots))
+ .ToArray();
+ }
+
}
[MessagePackObject]