diff --git a/Aetheria.State.Unity/Aetheria.State.Unity.csproj b/Aetheria.State.Unity/Aetheria.State.Unity.csproj
index b16eb26e8..0b12ca5e6 100644
--- a/Aetheria.State.Unity/Aetheria.State.Unity.csproj
+++ b/Aetheria.State.Unity/Aetheria.State.Unity.csproj
@@ -11,6 +11,7 @@
+
diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs
index 5e10c8a55..63447bf65 100644
--- a/Aetheria.State.Verify/Program.cs
+++ b/Aetheria.State.Verify/Program.cs
@@ -54,6 +54,7 @@ RequireMainMenuContinueRunState(root);
RequireDeadPropertiesPanelShellDeleted(root);
RequireTypedBehaviorMetadataCoverage(root);
RequireNameToolsUsesUiToolkit(root);
+RequireRuntimeStateReaderOwnsUnityStateAcquisition(root);
RequireNoDeadRuntimeProjectionCaches(root);
RequireNoDeadInspectorMetadata(root);
RequireRuntimeSimulationTuningCommits(root);
@@ -532,6 +533,7 @@ Console.WriteLine("Main-menu Continue authority: Continue selects typed run stat
Console.WriteLine("Generic popup inspector shell: PropertiesPanel, PropertiesList, and DropdownMenu are deleted from source and serialized assets");
Console.WriteLine("Typed behavior metadata authority: live heat/mining/thermotoggle payload kinds stay owned by package metadata");
Console.WriteLine("NameTools editor shell: the remaining name helper window lowers through UI Toolkit instead of IMGUI");
+Console.WriteLine("Runtime state reader authority: Unity gameplay/UI read typed state through a shared runtime reader instead of direct store spelunking");
Console.WriteLine("Verse host authority: daemon-owned typed verse host settings now drive provider advertisement and operations telemetry");
Console.WriteLine("Runtime simulation tuning authority: UI writes flow through gameplay checkpoint commits");
Console.WriteLine("Hull conductivity authority: inventory UI toggles flow through gameplay checkpoint commits");
@@ -3829,6 +3831,15 @@ static void RequireMainMenuContinueRunState(string root)
var packageSnapshot = File.Exists(packageSnapshotPath)
? File.ReadAllText(packageSnapshotPath)
: throw new InvalidOperationException("Cannot verify Continue entity identity; package runtime snapshots are missing.");
+ var runtimeStateReaderPath = Path.Combine(
+ root,
+ "Packages",
+ "org.gamecult.aetheria.state",
+ "Runtime",
+ "AetheriaRuntimeStateReader.cs");
+ var runtimeStateReader = File.Exists(runtimeStateReaderPath)
+ ? File.ReadAllText(runtimeStateReaderPath)
+ : throw new InvalidOperationException("Cannot verify Continue entity readback; shared runtime state reader is missing.");
var packageStorePath = Path.Combine(
root,
"Packages",
@@ -3837,12 +3848,12 @@ static void RequireMainMenuContinueRunState(string root)
"AetheriaRuntimeCatalogStore.cs");
var packageStore = File.Exists(packageStorePath)
? File.ReadAllText(packageStorePath)
- : throw new InvalidOperationException("Cannot verify Continue entity readback; package runtime store is missing.");
+ : throw new InvalidOperationException("Cannot verify Continue entity payload readback; package runtime store is missing.");
var requiredMenuSymbols = new[]
{
"LatestContinueRun",
- "AetheriaRuntimeCatalogStore",
+ "AetheriaRuntimeStateReader",
"ReadRunStates(ActionGameManager.RuntimeStateFilePath)",
"ContinueGame(continueRun)",
"ActionGameManager.ContinueRunState = run",
@@ -3860,6 +3871,12 @@ static void RequireMainMenuContinueRunState(string root)
string.Join(", ", missingMenuSymbols));
}
+ if (!runtimeStateReader.Contains("ReadRunStates", StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException(
+ "Shared runtime state reader no longer exposes typed run-state lookup for Continue.");
+ }
+
if (mainMenu.Contains("AddButton(\"Continue\", null)", StringComparison.Ordinal))
{
throw new InvalidOperationException("MainMenu Continue regressed to a null button.");
@@ -4182,6 +4199,141 @@ static void RequireNameToolsUsesUiToolkit(string root)
}
}
+static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root)
+{
+ var runtimeStateReaderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateReader.cs");
+ var unityFacadeProjectPath = Path.Combine(root, "Aetheria.State.Unity", "Aetheria.State.Unity.csproj");
+ var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
+ var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
+ var eveSurfacePresenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs");
+
+ var requiredPaths = new[]
+ {
+ runtimeStateReaderPath,
+ unityFacadeProjectPath,
+ actionGameManagerPath,
+ mainMenuPath,
+ eveSurfacePresenterPath
+ };
+
+ var missingPaths = requiredPaths
+ .Where(path => !File.Exists(path))
+ .Select(path => Path.GetRelativePath(root, path))
+ .ToArray();
+
+ if (missingPaths.Length > 0)
+ {
+ throw new InvalidOperationException(
+ "Runtime state reader authority cannot be verified because required files are missing: " +
+ string.Join(", ", missingPaths));
+ }
+
+ var runtimeStateReader = File.ReadAllText(runtimeStateReaderPath);
+ var unityFacadeProject = File.ReadAllText(unityFacadeProjectPath);
+ var actionGameManager = File.ReadAllText(actionGameManagerPath);
+ var mainMenu = File.ReadAllText(mainMenuPath);
+ var eveSurfacePresenter = File.ReadAllText(eveSurfacePresenterPath);
+
+ var requiredReaderSymbols = new[]
+ {
+ "public static class AetheriaRuntimeStateReader",
+ "OpenRuntimeCatalog",
+ "ReadPlayerSettings",
+ "ReadLoadoutTemplates",
+ "ReadRunStates",
+ "ReadZoneStates",
+ "ReadEntitySnapshots",
+ "ReadEveSurface"
+ };
+
+ var missingReaderSymbols = requiredReaderSymbols
+ .Where(symbol => !runtimeStateReader.Contains(symbol, StringComparison.Ordinal))
+ .ToArray();
+
+ if (missingReaderSymbols.Length > 0)
+ {
+ throw new InvalidOperationException(
+ "Shared runtime state reader is incomplete: " +
+ string.Join(", ", missingReaderSymbols));
+ }
+
+ if (!unityFacadeProject.Contains("AetheriaRuntimeStateReader.cs", StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException(
+ "Aetheria.State.Unity.csproj does not include the shared runtime state reader.");
+ }
+
+ var requiredActionGameManagerSymbols = new[]
+ {
+ "AetheriaRuntimeStateReader.ReadPlayerSettings",
+ "AetheriaRuntimeStateReader.ReadLoadoutTemplates",
+ "AetheriaRuntimeStateReader.OpenRuntimeCatalog",
+ "AetheriaRuntimeStateReader.ReadZoneStates",
+ "AetheriaRuntimeStateReader.ReadEntitySnapshots"
+ };
+
+ var missingActionGameManagerSymbols = requiredActionGameManagerSymbols
+ .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
+ .ToArray();
+
+ if (missingActionGameManagerSymbols.Length > 0)
+ {
+ throw new InvalidOperationException(
+ "ActionGameManager does not route typed state reads through the shared runtime reader: " +
+ string.Join(", ", missingActionGameManagerSymbols));
+ }
+
+ if (!mainMenu.Contains("AetheriaRuntimeStateReader", StringComparison.Ordinal) ||
+ !mainMenu.Contains("ReadRunStates(ActionGameManager.RuntimeStateFilePath)", StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException(
+ "MainMenu no longer routes Continue-run lookup through the shared runtime state reader.");
+ }
+
+ if (!eveSurfacePresenter.Contains("AetheriaRuntimeStateReader.ReadEveSurface", StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException(
+ "Aetheria Eve surface presenter no longer routes provider surface lookup through the shared runtime state reader.");
+ }
+
+ var forbiddenDirectStoreSymbols = new Dictionary
+ {
+ [actionGameManagerPath] = new[]
+ {
+ "AetheriaRuntimeCatalogStore.ReadPlayerSettings",
+ "AetheriaRuntimeCatalogStore.ReadLoadoutTemplates",
+ "AetheriaRuntimeCatalogStore.OpenReadOnly",
+ "AetheriaRuntimeCatalogStore.ReadZoneStates",
+ "AetheriaRuntimeCatalogStore.ReadEntitySnapshots"
+ },
+ [mainMenuPath] = new[]
+ {
+ "AetheriaRuntimeCatalogStore.ReadRunStates"
+ },
+ [eveSurfacePresenterPath] = new[]
+ {
+ "AetheriaRuntimeCatalogStore.ReadEveSurfaces"
+ }
+ };
+
+ var directStoreHits = forbiddenDirectStoreSymbols
+ .SelectMany(entry =>
+ {
+ var source = File.ReadAllText(entry.Key);
+ return entry.Value
+ .Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
+ .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}");
+ })
+ .ToArray();
+
+ if (directStoreHits.Length > 0)
+ {
+ throw new InvalidOperationException(
+ "Unity gameplay/UI still reads typed state directly from the raw store instead of the shared runtime state reader: " +
+ string.Join("; ", directStoreHits));
+ }
+}
+
static void RequireRuntimeSimulationTuningCommits(string root)
{
var requiredActionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
@@ -4679,7 +4831,7 @@ static void RequireDroppedPickupCheckpointState(string root)
"ProjectDroppedPickups",
"DroppedPickups = ProjectDroppedPickups(zone)",
"RestoreDroppedPickupsFromTypedZoneState",
- "AetheriaRuntimeCatalogStore.ReadZoneStates(RuntimeStateFilePath)",
+ "AetheriaRuntimeStateReader.ReadZoneStates(RuntimeStateFilePath)",
"ZoneRenderer.DropItem("
},
[Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[]
diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs
index 08e5cacbf..ed42ac347 100644
--- a/Assets/Scripts/Gameplay/ActionGameManager.cs
+++ b/Assets/Scripts/Gameplay/ActionGameManager.cs
@@ -182,7 +182,7 @@ public class ActionGameManager : MonoBehaviour
var settings = CreateDefaultRuntimePlayerSettings();
try
{
- var stored = AetheriaRuntimeCatalogStore.ReadPlayerSettings(RuntimeStateFilePath);
+ var stored = AetheriaRuntimeStateReader.ReadPlayerSettings(RuntimeStateFilePath);
if (stored == null)
return settings;
@@ -245,7 +245,7 @@ public class ActionGameManager : MonoBehaviour
{
try
{
- var loadouts = AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(stateFilePath);
+ var loadouts = AetheriaRuntimeStateReader.ReadLoadoutTemplates(stateFilePath);
LoadoutTemplates.Clear();
LoadoutTemplates.AddRange(loadouts);
@@ -2308,7 +2308,7 @@ public class ActionGameManager : MonoBehaviour
}
else
{
- RuntimeCatalog = AetheriaRuntimeCatalogStore.OpenReadOnly(stateBoot.StateFilePath);
+ RuntimeCatalog = AetheriaRuntimeStateReader.OpenRuntimeCatalog(stateBoot.StateFilePath);
Debug.Log($"Aetheria typed runtime catalog: {RuntimeCatalog.Items.Count} items, {RuntimeCatalog.Corporations.Count} corporations, {RuntimeCatalog.NameFiles.Count} name files");
}
@@ -2629,7 +2629,7 @@ public class ActionGameManager : MonoBehaviour
AetheriaRuntimeZoneStateSnapshot zoneState;
try
{
- zoneState = AetheriaRuntimeCatalogStore.ReadZoneStates(RuntimeStateFilePath)
+ zoneState = AetheriaRuntimeStateReader.ReadZoneStates(RuntimeStateFilePath)
.FirstOrDefault(zone => string.Equals(zone.RecordKey, zoneKey, StringComparison.Ordinal));
}
catch (Exception ex)
@@ -2781,7 +2781,7 @@ public class ActionGameManager : MonoBehaviour
AetheriaRuntimeEntitySnapshot[] entitySnapshots;
try
{
- entitySnapshots = AetheriaRuntimeCatalogStore.ReadEntitySnapshots(RuntimeStateFilePath)
+ entitySnapshots = AetheriaRuntimeStateReader.ReadEntitySnapshots(RuntimeStateFilePath)
.Where(entity => entity.RecordKey.StartsWith(zoneEntityKeyPrefix, StringComparison.Ordinal))
.OrderBy(entity => EntityIndexFromRecordKey(entity.RecordKey))
.ToArray();
diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs
index df85ebafd..fdf4b6156 100644
--- a/Assets/Scripts/UI/MainMenu.cs
+++ b/Assets/Scripts/UI/MainMenu.cs
@@ -160,7 +160,7 @@ public class MainMenu : MonoBehaviour
{
try
{
- return AetheriaRuntimeCatalogStore
+ return AetheriaRuntimeStateReader
.ReadRunStates(ActionGameManager.RuntimeStateFilePath)
.Where(run => !string.IsNullOrWhiteSpace(run.RunId))
.OrderByDescending(run => run.UpdatedAtUtc, StringComparer.Ordinal)
diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs
index 20789c9d4..2fe172327 100644
--- a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs
+++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs
@@ -1,6 +1,5 @@
using System;
using System.IO;
-using System.Linq;
using GameCult.Aetheria.State.Unity;
using GameCult.Eve.Surface;
using GameCult.Eve.UnityUIToolkit;
@@ -51,8 +50,7 @@ namespace GameCult.Aetheria.EveRuntime
return;
}
- var surface = AetheriaRuntimeCatalogStore.ReadEveSurfaces(statePath)
- .FirstOrDefault(candidate => string.Equals(candidate.Surface.Id, surfaceId, StringComparison.Ordinal));
+ var surface = AetheriaRuntimeStateReader.ReadEveSurface(statePath, surfaceId);
if (surface == null)
{
root.Add(BuildError($"Eve surface not found: {surfaceId}"));
diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs
new file mode 100644
index 000000000..8dd693027
--- /dev/null
+++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using GameCult.Eve.Surface;
+
+#nullable enable
+
+namespace GameCult.Aetheria.State.Unity
+{
+ // Unity runtime shells acquire typed state through this port so the transport can move to the daemon without spelunking every consumer.
+ public static class AetheriaRuntimeStateReader
+ {
+ public static AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog(string stateFilePath)
+ {
+ return AetheriaRuntimeCatalogStore.OpenReadOnly(stateFilePath);
+ }
+
+ public static AetheriaRuntimePlayerSettingsSnapshot? ReadPlayerSettings(string stateFilePath)
+ {
+ return AetheriaRuntimeCatalogStore.ReadPlayerSettings(stateFilePath);
+ }
+
+ public static IReadOnlyList ReadLoadoutTemplates(string stateFilePath)
+ {
+ return AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(stateFilePath);
+ }
+
+ public static IReadOnlyList ReadRunStates(string stateFilePath)
+ {
+ return AetheriaRuntimeCatalogStore.ReadRunStates(stateFilePath);
+ }
+
+ public static IReadOnlyList ReadZoneStates(string stateFilePath)
+ {
+ return AetheriaRuntimeCatalogStore.ReadZoneStates(stateFilePath);
+ }
+
+ public static IReadOnlyList ReadEntitySnapshots(string stateFilePath)
+ {
+ return AetheriaRuntimeCatalogStore.ReadEntitySnapshots(stateFilePath);
+ }
+
+ public static EveSurfaceDocument? ReadEveSurface(string stateFilePath, string surfaceId)
+ {
+ return AetheriaRuntimeCatalogStore.ReadEveSurfaces(stateFilePath)
+ .FirstOrDefault(candidate => string.Equals(candidate.Surface.Id, surfaceId, StringComparison.Ordinal));
+ }
+ }
+}