diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 6aea53ea3..f7baae957 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -9279,9 +9279,18 @@ static void RequireAetheriaRuntimeVerseClientContract(string root) "GetLatestAuthoritativeRunFrameAsync()", "GetObservedDaemonStateAsync()", "OpenRuntimeCatalog()", + "private AetheriaClientState? _aetheriaState", + "return Aetheria().Catalog.Latest()", + "return _aetheriaState ??= CreateAetheriaStateFacade();", "GetPlayerSettingsAsync()", + "Aetheria().Settings.Player.LatestAsync()", + "Aetheria().Settings.VerseHost.LatestAsync()", "VerseHostSettingsAsync()", "AetheriaRuntimeLoadoutTemplatesDocument", + "ReadRuntimeCatalogSnapshot()", + "ReadLoadoutTemplatesDocument()", + "ReadPlayerSettingsDocument()", + "ReadVerseHostSettingsDocument()", "CreateEveSurfaceStateRefResolver(", "AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver(", "SubmitDaemonCommandAsync(", @@ -9323,6 +9332,23 @@ static void RequireAetheriaRuntimeVerseClientContract(string root) "AetheriaRuntimeVerseClient still routes Eve state-ref resolution through the file-backed compatibility reader."); } + var forbiddenClientCompatibilityStoreBypasses = new[] + { + "return Task.FromResult(AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath));", + "return Task.FromResult(AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath));", + "() => Task.FromResult(OpenRuntimeCatalog())", + "OpenRuntimeCatalog());" + }; + var clientCompatibilityStoreBypassHits = forbiddenClientCompatibilityStoreBypasses + .Where(symbol => client.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (clientCompatibilityStoreBypassHits.Length > 0) + { + throw new InvalidOperationException( + "AetheriaRuntimeVerseClient compatibility reads still bypass managed typed catalog/settings documents: " + + string.Join(", ", clientCompatibilityStoreBypassHits)); + } + if (client.Contains("public async Task SubmitDaemonCommandAsync(", StringComparison.Ordinal) || client.Contains("public async Task SubmitEveCommandAsync(", StringComparison.Ordinal)) { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index 8680e9eea..aee188d84 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -131,6 +131,7 @@ namespace GameCult.Aetheria.State.Verse public const string DefaultRuntimeId = "aetheria-verse-client"; private readonly CultMeshNode _node; + private AetheriaClientState? _aetheriaState; private bool _disposed; private AetheriaRuntimeVerseClient(string statePath, string runtimeId, CultMeshNode node) @@ -262,25 +263,27 @@ namespace GameCult.Aetheria.State.Verse public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog() { ThrowIfDisposed(); - return AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath); + return Aetheria().Catalog.Latest(); } public AetheriaClientState Aetheria() { ThrowIfDisposed(); - return CreateAetheriaStateFacade(); + return _aetheriaState ??= CreateAetheriaStateFacade(); } - public Task GetPlayerSettingsAsync() + public async Task GetPlayerSettingsAsync() { ThrowIfDisposed(); - return Task.FromResult(AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath)); + var document = await Aetheria().Settings.Player.LatestAsync().ConfigureAwait(false); + return document.ToSnapshot(); } - public Task GetVerseHostSettingsAsync() + public async Task GetVerseHostSettingsAsync() { ThrowIfDisposed(); - return Task.FromResult(AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath)); + var document = await Aetheria().Settings.VerseHost.LatestAsync().ConfigureAwait(false); + return document.ToSnapshot(); } public async Task GetObservedDaemonStateAsync() @@ -725,13 +728,12 @@ namespace GameCult.Aetheria.State.Verse .Select(change => change.Document!); var catalogDocument = CatalogDocument( "aetheria.catalog.runtime", - () => Task.FromResult(OpenRuntimeCatalog()), + () => Task.FromResult(ReadRuntimeCatalogSnapshot()), AetheriaRuntimeCatalogSnapshot.SchemaId, CultMesh.ProjectionSource("catalog:aetheria.runtime")); var loadoutTemplatesDocument = CatalogDocument( "aetheria.catalog.loadout_templates", - () => Task.FromResult(new AetheriaRuntimeLoadoutTemplatesDocument( - AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(StatePath))), + () => Task.FromResult(ReadLoadoutTemplatesDocument()), AetheriaRuntimeLoadoutTemplatesDocument.SchemaId, CultMesh.ProjectionSource("catalog:aetheria.loadout_templates")); var starbridgeScenarioDocument = Document( @@ -762,14 +764,12 @@ namespace GameCult.Aetheria.State.Verse loadoutTemplatesDocument, CatalogDocument( "aetheria.settings.player", - () => Task.FromResult(AetheriaRuntimePlayerSettingsDocument.FromSnapshot( - AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath))), + () => Task.FromResult(ReadPlayerSettingsDocument()), AetheriaRuntimePlayerSettingsDocument.SchemaId, CultMesh.ProjectionSource("catalog:aetheria.player_settings")), CatalogDocument( "aetheria.settings.verse_host", - () => Task.FromResult(AetheriaRuntimeVerseHostSettingsDocument.FromSnapshot( - AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath))), + () => Task.FromResult(ReadVerseHostSettingsDocument()), AetheriaRuntimeVerseHostSettingsDocument.SchemaId, CultMesh.ProjectionSource("catalog:aetheria.verse_host_settings")), ProjectedDocument( @@ -912,7 +912,7 @@ namespace GameCult.Aetheria.State.Verse frame, scenario, session, - OpenRuntimeCatalog()); + ReadRuntimeCatalogSnapshot()); } static string IndexedDocumentId(string prefix, int index) @@ -963,5 +963,28 @@ namespace GameCult.Aetheria.State.Verse CultMesh.ProjectionSource(key.ToString()) }); } + + private AetheriaRuntimeCatalogSnapshot ReadRuntimeCatalogSnapshot() + { + return AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath); + } + + private AetheriaRuntimeLoadoutTemplatesDocument ReadLoadoutTemplatesDocument() + { + return new AetheriaRuntimeLoadoutTemplatesDocument( + AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(StatePath)); + } + + private AetheriaRuntimePlayerSettingsDocument ReadPlayerSettingsDocument() + { + return AetheriaRuntimePlayerSettingsDocument.FromSnapshot( + AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath)); + } + + private AetheriaRuntimeVerseHostSettingsDocument ReadVerseHostSettingsDocument() + { + return AetheriaRuntimeVerseHostSettingsDocument.FromSnapshot( + AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath)); + } } }