From a1f5c385af085be99a2995f794c707b433fae659 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 22 Jun 2026 14:23:24 +0100 Subject: [PATCH] Route Unity shell state reads through Verse client --- Aetheria.State.Verify/Program.cs | 51 ++++++++++++++++--- Assets/Scripts/Gameplay/ActionGameManager.cs | 40 +++++++++++++-- Assets/Scripts/UI/MainMenu.cs | 14 +++-- .../Runtime/AetheriaRuntimeVerseClient.cs | 25 +++++++++ 4 files changed, 116 insertions(+), 14 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 9d4fb2c08..70f5d4b75 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -553,7 +553,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 client state authority: Unity gameplay/UI read typed state through the shared Verse client instead of direct store spelunking"); Console.WriteLine("Verse host authority: daemon-owned typed verse host settings now drive provider advertisement, operations telemetry, and served Verse discovery"); Console.WriteLine("Client target boot authority: Unity boot resolves the active Verse through a typed client target instead of local path folklore"); Console.WriteLine("Verse replica authority: remote client targets resolve to cache-only replica .cc files fed from the daemon"); @@ -8121,6 +8121,10 @@ static void RequireAetheriaRuntimeVerseClientContract(string root) "AetheriaRuntimeVerseDocument", "GetLatestAuthoritativeRunFrameAsync()", "GetObservedDaemonStateAsync()", + "OpenRuntimeCatalog()", + "GetPlayerSettingsAsync()", + "GetVerseHostSettingsAsync()", + "GetLoadoutTemplatesAsync()", "SubmitDaemonCommandAsync(", "AetheriaRuntimeVerseRecordKeys.DaemonCommand(command.CommandId)", "DaemonGameTuiSurface { get; }", @@ -8636,7 +8640,7 @@ static void RequireMainMenuVerseHostProjection(string root) var requiredMainMenuSymbols = new[] { "LatestVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot)", - "AetheriaRuntimeStateReader.ReadVerseHostSettings(stateBoot.StateFilePath)", + "GetVerseHostSettingsAsync()", "AetheriaRuntimeMainMenuSurfaceBuilder.ProjectRoot(", "AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(" }; @@ -10197,7 +10201,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) if (missingPaths.Length > 0) { throw new InvalidOperationException( - "Runtime state reader authority cannot be verified because required files are missing: " + + "Verse client state authority cannot be verified because required files are missing: " + string.Join(", ", missingPaths)); } @@ -10306,9 +10310,11 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var requiredActionGameManagerSymbols = new[] { - "AetheriaRuntimeStateReader.ReadPlayerSettings", - "AetheriaRuntimeStateReader.ReadLoadoutTemplates", - "AetheriaRuntimeStateReader.OpenRuntimeCatalog", + "AetheriaRuntimeVerseClient", + "ResolveRuntimeVerseClient(", + "GetPlayerSettingsAsync()", + "GetLoadoutTemplatesAsync()", + "OpenRuntimeCatalog()", "ResolveDaemonObserver()", "LastObservedState" }; @@ -10320,10 +10326,26 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) if (missingActionGameManagerSymbols.Length > 0) { throw new InvalidOperationException( - "ActionGameManager does not route typed state reads through the shared runtime reader: " + + "ActionGameManager does not route typed state reads through the shared Verse client: " + string.Join(", ", missingActionGameManagerSymbols)); } + var forbiddenActionGameManagerReaderSymbols = new[] + { + "AetheriaRuntimeStateReader.ReadPlayerSettings", + "AetheriaRuntimeStateReader.ReadLoadoutTemplates", + "AetheriaRuntimeStateReader.OpenRuntimeCatalog" + }; + var actionGameManagerReaderHits = forbiddenActionGameManagerReaderSymbols + .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (actionGameManagerReaderHits.Length > 0) + { + throw new InvalidOperationException( + "ActionGameManager still reads typed state through the runtime file reader instead of the Verse client: " + + string.Join(", ", actionGameManagerReaderHits)); + } + if (!daemonObserver.Contains("AetheriaRuntimeVerseClient", StringComparison.Ordinal) || !daemonObserver.Contains(".OpenAsync(statePath", StringComparison.Ordinal) || !daemonObserver.Contains("GetObservedDaemonStateAsync()", StringComparison.Ordinal)) @@ -10351,6 +10373,21 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "MainMenu still reads daemon frames through the runtime file reader instead of the Verse client."); } + var forbiddenMainMenuReaderSymbols = new[] + { + "AetheriaRuntimeStateReader.ReadPlayerSettings", + "AetheriaRuntimeStateReader.ReadVerseHostSettings" + }; + var mainMenuReaderHits = forbiddenMainMenuReaderSymbols + .Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (mainMenuReaderHits.Length > 0) + { + throw new InvalidOperationException( + "MainMenu still reads typed state through the runtime file reader instead of the Verse client: " + + string.Join(", ", mainMenuReaderHits)); + } + if (!eveSurfacePresenter.Contains("AetheriaRuntimeVerseClient.OpenAsync(", StringComparison.Ordinal) || !eveSurfacePresenter.Contains("ReadDaemonSurface(statePath)", StringComparison.Ordinal)) { diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 318a13e24..f70506694 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -79,6 +79,9 @@ public class ActionGameManager : MonoBehaviour public static string RuntimeStateFilePath => _runtimeStateFilePath ??= AetheriaRuntimeStateBoot.Inspect(GameDataDirectory).StateFilePath; + private static AetheriaRuntimeVerseClient _runtimeVerseClient; + private static string _runtimeVerseClientStatePath; + private static RuntimePlayerSettings _runtimePlayerSettings; public static RuntimePlayerSettings RuntimePlayerSettings { @@ -160,7 +163,10 @@ public class ActionGameManager : MonoBehaviour var settings = CreateDefaultRuntimePlayerSettings(); try { - var stored = AetheriaRuntimeStateReader.ReadPlayerSettings(RuntimeStateFilePath); + var stored = ResolveRuntimeVerseClient(RuntimeStateFilePath) + .GetPlayerSettingsAsync() + .GetAwaiter() + .GetResult(); if (stored == null) return settings; @@ -223,7 +229,10 @@ public class ActionGameManager : MonoBehaviour { try { - var loadouts = AetheriaRuntimeStateReader.ReadLoadoutTemplates(stateFilePath); + var loadouts = ResolveRuntimeVerseClient(stateFilePath) + .GetLoadoutTemplatesAsync() + .GetAwaiter() + .GetResult(); _observedLoadoutTemplates.Clear(); _observedLoadoutTemplates.AddRange(loadouts); @@ -2016,6 +2025,7 @@ public class ActionGameManager : MonoBehaviour private void OnDisable() { Input.Dispose(); + DisposeRuntimeVerseClient(); } void Start() @@ -2038,7 +2048,7 @@ public class ActionGameManager : MonoBehaviour $"Aetheria runtime state file is missing at {stateBoot.StateFilePath}; gameplay requires an authoritative daemon mirror."); } - RuntimeCatalog = AetheriaRuntimeStateReader.OpenRuntimeCatalog(stateBoot.StateFilePath); + RuntimeCatalog = ResolveRuntimeVerseClient(stateBoot.StateFilePath).OpenRuntimeCatalog(); Debug.Log($"Aetheria runtime catalog: {RuntimeCatalog.Items.Count} items, {RuntimeCatalog.Corporations.Count} corporations, {RuntimeCatalog.NameFiles.Count} name files"); if (RuntimeCatalog == null) @@ -2232,6 +2242,30 @@ public class ActionGameManager : MonoBehaviour _endDragCallback = null; } + private static AetheriaRuntimeVerseClient ResolveRuntimeVerseClient(string stateFilePath) + { + if (_runtimeVerseClient != null && + string.Equals(_runtimeVerseClientStatePath, stateFilePath, StringComparison.Ordinal)) + { + return _runtimeVerseClient; + } + + DisposeRuntimeVerseClient(); + _runtimeVerseClient = AetheriaRuntimeVerseClient + .OpenAsync(stateFilePath, "unity-action-game", startServer: false, pullOnOpen: true) + .GetAwaiter() + .GetResult(); + _runtimeVerseClientStatePath = stateFilePath; + return _runtimeVerseClient; + } + + private static void DisposeRuntimeVerseClient() + { + _runtimeVerseClient?.Dispose(); + _runtimeVerseClient = null; + _runtimeVerseClientStatePath = null; + } + public bool HasDragTarget => _endDragCallback != null; public void EndDrag() diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index b0b7a4ef7..605d95ca6 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -165,14 +165,17 @@ public class MainMenu : MonoBehaviour } } - private static AetheriaRuntimePlayerSettingsSnapshot LatestPlayerSettings(AetheriaRuntimeStateBootReport stateBoot) + private AetheriaRuntimePlayerSettingsSnapshot LatestPlayerSettings(AetheriaRuntimeStateBootReport stateBoot) { if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) return null; try { - return AetheriaRuntimeStateReader.ReadPlayerSettings(stateBoot.StateFilePath); + return ResolveVerseClient(stateBoot) + .GetPlayerSettingsAsync() + .GetAwaiter() + .GetResult(); } catch (Exception ex) { @@ -181,14 +184,17 @@ public class MainMenu : MonoBehaviour } } - private static AetheriaRuntimeVerseHostSettingsSnapshot LatestVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot) + private AetheriaRuntimeVerseHostSettingsSnapshot LatestVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot) { if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) return null; try { - return AetheriaRuntimeStateReader.ReadVerseHostSettings(stateBoot.StateFilePath); + return ResolveVerseClient(stateBoot) + .GetVerseHostSettingsAsync() + .GetAwaiter() + .GetResult(); } catch (Exception ex) { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index bb36f39b9..404d91877 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -243,6 +244,30 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest); } + public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog() + { + ThrowIfDisposed(); + return AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath); + } + + public Task GetPlayerSettingsAsync() + { + ThrowIfDisposed(); + return Task.FromResult(AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath)); + } + + public Task GetVerseHostSettingsAsync() + { + ThrowIfDisposed(); + return Task.FromResult(AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath)); + } + + public Task> GetLoadoutTemplatesAsync() + { + ThrowIfDisposed(); + return Task.FromResult(AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(StatePath)); + } + public async Task GetObservedDaemonStateAsync() { ThrowIfDisposed();