From 635bf6bbf07beeff0b0d2a27f6a964077b06884d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 28 Jun 2026 02:19:55 +0100 Subject: [PATCH] Route daemon reads through managed documents --- Aetheria.State.Verify/Program.cs | 62 +++++++++++++++++++ .../Tests/DaemonRuntimeDocumentTests.cs | 13 ++++ .../Runtime/AetheriaClient.cs | 43 ++++++++++--- .../Runtime/AetheriaClientState.cs | 41 ++++++++++++ .../Runtime/AetheriaRuntimeVerseClient.cs | 6 ++ 5 files changed, 156 insertions(+), 9 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index cb48376db..1d96ef507 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -12833,6 +12833,14 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var gameplayBootShell = File.Exists(gameplayBootShellPath) ? File.ReadAllText(gameplayBootShellPath) : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityGameplayBootShell.cs is missing."); + var aetheriaClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs"); + var aetheriaClient = File.Exists(aetheriaClientPath) + ? File.ReadAllText(aetheriaClientPath) + : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaClient.cs is missing."); + var aetheriaClientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs"); + var aetheriaClientState = File.Exists(aetheriaClientStatePath) + ? File.ReadAllText(aetheriaClientStatePath) + : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaClientState.cs is missing."); var requiredActionGameManagerSymbols = new[] { @@ -12924,6 +12932,60 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "Observed zone-render state acquisition must live behind AetheriaUnityObservedFrameApplier."); } + var requiredDaemonStateFacadeSymbols = new[] + { + "public AetheriaClientDaemonState Daemon { get; }", + "public sealed class AetheriaClientDaemonState", + "CultMeshDocumentHandle ProviderAdvertisement", + "CultMeshDocumentHandle Health", + "CultMeshDocumentHandle CommandBoundary", + "CultMeshDocumentHandle LatestFrame", + "CultMeshDocumentHandle LatestSoaView" + }; + var missingDaemonStateFacadeSymbols = requiredDaemonStateFacadeSymbols + .Where(symbol => !aetheriaClientState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingDaemonStateFacadeSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientState must expose daemon service state as managed typed document handles: " + + string.Join(", ", missingDaemonStateFacadeSymbols)); + } + + var requiredDaemonClientFacadeSymbols = new[] + { + "State.Daemon.LatestFrame.LatestAsync()", + "State.Daemon.LatestSoaView.LatestAsync()", + "State.Daemon.Health.LatestAsync()" + }; + var missingDaemonClientFacadeSymbols = requiredDaemonClientFacadeSymbols + .Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingDaemonClientFacadeSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClient daemon compatibility methods must read managed typed state documents: " + + string.Join(", ", missingDaemonClientFacadeSymbols)); + } + + var forbiddenDaemonClientBypassSymbols = new[] + { + "return _verse.GetObservedDaemonStateAsync();", + "return _verse.GetLatestAuthoritativeRunFrameAsync();", + "return _verse.GetHealthAsync();", + "return _verse.GetLatestSoaViewAsync();", + "var frame = await _verse.GetLatestFrameAsync()" + }; + var daemonClientBypassHits = forbiddenDaemonClientBypassSymbols + .Where(symbol => aetheriaClient.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (daemonClientBypassHits.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClient still bypasses managed daemon documents for compatibility reads: " + + string.Join(", ", daemonClientBypassHits)); + } + var forbiddenActionGameManagerReaderSymbols = new[] { "AetheriaRuntimeStateReader.ReadPlayerSettings", diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 3cedc0e69..90e69db4f 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -215,6 +215,8 @@ public class DaemonRuntimeDocumentTests using var zoneRenderReactive = client.State .Reactive(); var legacyCurrentEntity = client.CurrentEntityAsync().GetAwaiter().GetResult(); + var legacyObserved = client.ObserveAsync().GetAwaiter().GetResult(); + var legacyAuthoritativeFrame = client.LatestAuthoritativeRunFrameAsync().GetAwaiter().GetResult(); var dockingState = client.Aetheria() .DockingState .LatestAsync() @@ -226,6 +228,15 @@ public class DaemonRuntimeDocumentTests Assert.AreSame(client.State.Catalog, client.Document()); Assert.AreSame(client.State.ZoneRender, client.Document()); Assert.AreSame(client.State.LatestFrame, client.Document()); + Assert.AreSame(client.State.Daemon.LatestFrame, client.State.LatestFrame); + Assert.AreSame(client.State.Daemon.LatestSoaView, client.State.LatestSoaView); + Assert.AreSame( + client.State.Daemon.ProviderAdvertisement, + client.Document()); + Assert.AreSame(client.State.Daemon.Health, client.Document()); + Assert.AreSame( + client.State.Daemon.CommandBoundary, + client.Document()); Assert.AreSame(client.State.Settings.Player, client.Document()); Assert.AreSame(client.State.Settings.VerseHost, client.Document()); Assert.AreSame( @@ -247,6 +258,8 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual(AetheriaRuntimeDaemonSchemas.Frame, latestFrame.Schema); Assert.AreEqual(frame.FrameId, latestFrame.FrameId); Assert.AreEqual(frame.FrameId, latestFrameByType.FrameId); + Assert.AreEqual(frame.FrameId, legacyObserved.Frame.FrameId); + Assert.AreEqual(frame.FrameId, legacyAuthoritativeFrame.FrameId); Assert.AreEqual(AetheriaRuntimeCatalogSnapshot.SchemaId, client.State.Catalog.Sources.Last().SchemaId); Assert.GreaterOrEqual(catalog.Items.Count, 0); Assert.AreEqual(AetheriaRuntimePlayerSettingsDocument.SchemaId, playerSettings.Schema); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs index ce8edc312..73aa7afcb 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs @@ -128,10 +128,25 @@ namespace GameCult.Aetheria.State.Verse pullOnOpen: pullOnOpen); } - public Task ObserveAsync() + public async Task ObserveAsync() { ThrowIfDisposed(); - return _verse.GetObservedDaemonStateAsync(); + var frame = await State.Daemon.LatestFrame.LatestAsync().ConfigureAwait(false); + if (frame == null) + return null; + + var soaView = await State.Daemon.LatestSoaView.LatestAsync().ConfigureAwait(false); + if (soaView == null || + !string.Equals(soaView.Schema, AetheriaRuntimeDaemonSchemas.SoaView, StringComparison.Ordinal)) + { + soaView = null; + } + + return new AetheriaRuntimeObservedDaemonState( + frame, + soaView, + AetheriaRuntimeDaemonFrameStore.GetFramePath(StatePath), + AetheriaRuntimeDaemonSoaViewStore.GetViewPath(StatePath)); } public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog() @@ -170,10 +185,20 @@ namespace GameCult.Aetheria.State.Verse entityKey ?? ""); } - public Task LatestAuthoritativeRunFrameAsync() + public async Task LatestAuthoritativeRunFrameAsync() { ThrowIfDisposed(); - return _verse.GetLatestAuthoritativeRunFrameAsync(); + var frame = await State.Daemon.LatestFrame.LatestAsync().ConfigureAwait(false); + if (frame == null || + !frame.IsAuthoritative || + frame.Run == null || + frame.Run.Zones == null || + frame.Run.Zones.Count == 0) + { + return null; + } + + return frame; } public async Task MapViewportAsync( @@ -291,10 +316,10 @@ namespace GameCult.Aetheria.State.Verse return await State.Details.Inventory(entityIndex).LatestAsync().ConfigureAwait(false); } - public Task DaemonHealthAsync() + public async Task DaemonHealthAsync() { ThrowIfDisposed(); - return _verse.GetHealthAsync(); + return await State.Daemon.Health.LatestAsync().ConfigureAwait(false); } public Task AuthorityStatusAsync() @@ -308,10 +333,10 @@ namespace GameCult.Aetheria.State.Verse : null); } - public Task SoaViewAsync() + public async Task SoaViewAsync() { ThrowIfDisposed(); - return _verse.GetLatestSoaViewAsync(); + return await State.Daemon.LatestSoaView.LatestAsync().ConfigureAwait(false); } public Task DaemonGameSurfaceAsync() @@ -451,7 +476,7 @@ namespace GameCult.Aetheria.State.Verse private async Task RequireFrameAsync() { - var frame = await _verse.GetLatestFrameAsync().ConfigureAwait(false); + var frame = await State.Daemon.LatestFrame.LatestAsync().ConfigureAwait(false); if (frame == null) throw new InvalidOperationException("Aetheria local client has no daemon frame yet."); return frame; diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 69b4dc6c6..294e7fdbd 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -13,6 +13,9 @@ namespace GameCult.Aetheria.State.Verse private readonly CultMeshDocumentCatalog _documents; internal AetheriaClientState( + CultMeshDocumentHandle providerAdvertisement, + CultMeshDocumentHandle health, + CultMeshDocumentHandle commandBoundary, CultMeshDocumentHandle latestFrame, CultMeshDocumentHandle latestSoaView, CultMeshDocumentHandle catalog, @@ -38,6 +41,12 @@ namespace GameCult.Aetheria.State.Verse CultMeshDocumentHandle starbridgeSummary, Func> starbridgePlayerSeat) { + Daemon = new AetheriaClientDaemonState( + providerAdvertisement, + health, + commandBoundary, + latestFrame, + latestSoaView); LatestFrame = latestFrame ?? throw new ArgumentNullException(nameof(latestFrame)); LatestSoaView = latestSoaView ?? throw new ArgumentNullException(nameof(latestSoaView)); Catalog = catalog ?? throw new ArgumentNullException(nameof(catalog)); @@ -64,6 +73,9 @@ namespace GameCult.Aetheria.State.Verse starbridgeSummary, starbridgePlayerSeat); _documents = CultMesh.Documents( + Daemon.ProviderAdvertisement, + Daemon.Health, + Daemon.CommandBoundary, LatestFrame, LatestSoaView, Catalog, @@ -81,6 +93,8 @@ namespace GameCult.Aetheria.State.Verse Starbridge.Session); } + public AetheriaClientDaemonState Daemon { get; } + public CultMeshDocumentHandle LatestFrame { get; } public CultMeshDocumentHandle LatestSoaView { get; } @@ -182,6 +196,33 @@ namespace GameCult.Aetheria.State.Verse } } + public sealed class AetheriaClientDaemonState + { + internal AetheriaClientDaemonState( + CultMeshDocumentHandle providerAdvertisement, + CultMeshDocumentHandle health, + CultMeshDocumentHandle commandBoundary, + CultMeshDocumentHandle latestFrame, + CultMeshDocumentHandle latestSoaView) + { + ProviderAdvertisement = providerAdvertisement ?? throw new ArgumentNullException(nameof(providerAdvertisement)); + Health = health ?? throw new ArgumentNullException(nameof(health)); + CommandBoundary = commandBoundary ?? throw new ArgumentNullException(nameof(commandBoundary)); + LatestFrame = latestFrame ?? throw new ArgumentNullException(nameof(latestFrame)); + LatestSoaView = latestSoaView ?? throw new ArgumentNullException(nameof(latestSoaView)); + } + + public CultMeshDocumentHandle ProviderAdvertisement { get; } + + public CultMeshDocumentHandle Health { get; } + + public CultMeshDocumentHandle CommandBoundary { get; } + + public CultMeshDocumentHandle LatestFrame { get; } + + public CultMeshDocumentHandle LatestSoaView { get; } + } + public sealed class AetheriaClientCurrentState { internal AetheriaClientCurrentState( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index 44beae7cd..d7776ca3e 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -735,6 +735,12 @@ namespace GameCult.Aetheria.State.Verse CultMesh.ProjectionSource("catalog:aetheria.loadout_templates")); return new AetheriaClientState( + Document( + AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement), + Document( + AetheriaRuntimeVerseRecordKeys.DaemonHealth), + Document( + AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary), Document( AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest), Document(