Route daemon reads through managed documents

This commit is contained in:
Meta Vens 2026-06-28 02:19:55 +01:00
commit 635bf6bbf0
5 changed files with 156 additions and 9 deletions

View file

@ -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<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement",
"CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> Health",
"CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary",
"CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> LatestFrame",
"CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> 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",

View file

@ -215,6 +215,8 @@ public class DaemonRuntimeDocumentTests
using var zoneRenderReactive = client.State
.Reactive<AetheriaRuntimeZoneRenderDocument>();
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<AetheriaRuntimeCatalogSnapshot>());
Assert.AreSame(client.State.ZoneRender, client.Document<AetheriaRuntimeZoneRenderDocument>());
Assert.AreSame(client.State.LatestFrame, client.Document<AetheriaRuntimeDaemonFrameDocument>());
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<AetheriaRuntimeDaemonProviderAdvertisementDocument>());
Assert.AreSame(client.State.Daemon.Health, client.Document<AetheriaRuntimeDaemonHealthDocument>());
Assert.AreSame(
client.State.Daemon.CommandBoundary,
client.Document<AetheriaRuntimeDaemonCommandBoundaryDocument>());
Assert.AreSame(client.State.Settings.Player, client.Document<AetheriaRuntimePlayerSettingsDocument>());
Assert.AreSame(client.State.Settings.VerseHost, client.Document<AetheriaRuntimeVerseHostSettingsDocument>());
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);

View file

@ -128,10 +128,25 @@ namespace GameCult.Aetheria.State.Verse
pullOnOpen: pullOnOpen);
}
public Task<AetheriaRuntimeObservedDaemonState?> ObserveAsync()
public async Task<AetheriaRuntimeObservedDaemonState?> 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<AetheriaRuntimeDaemonFrameDocument?> LatestAuthoritativeRunFrameAsync()
public async Task<AetheriaRuntimeDaemonFrameDocument?> 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<AetheriaRuntimeRtsViewportDocument> MapViewportAsync(
@ -291,10 +316,10 @@ namespace GameCult.Aetheria.State.Verse
return await State.Details.Inventory(entityIndex).LatestAsync().ConfigureAwait(false);
}
public Task<AetheriaRuntimeDaemonHealthDocument?> DaemonHealthAsync()
public async Task<AetheriaRuntimeDaemonHealthDocument?> DaemonHealthAsync()
{
ThrowIfDisposed();
return _verse.GetHealthAsync();
return await State.Daemon.Health.LatestAsync().ConfigureAwait(false);
}
public Task<AetheriaRuntimeVerseAuthorityPolicyDocument?> AuthorityStatusAsync()
@ -308,10 +333,10 @@ namespace GameCult.Aetheria.State.Verse
: null);
}
public Task<AetheriaRuntimeDaemonSoaViewDocument?> SoaViewAsync()
public async Task<AetheriaRuntimeDaemonSoaViewDocument?> SoaViewAsync()
{
ThrowIfDisposed();
return _verse.GetLatestSoaViewAsync();
return await State.Daemon.LatestSoaView.LatestAsync().ConfigureAwait(false);
}
public Task<global::Aetheria.State.Documents.EveSurfaceState?> DaemonGameSurfaceAsync()
@ -451,7 +476,7 @@ namespace GameCult.Aetheria.State.Verse
private async Task<AetheriaRuntimeDaemonFrameDocument> 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;

View file

@ -13,6 +13,9 @@ namespace GameCult.Aetheria.State.Verse
private readonly CultMeshDocumentCatalog _documents;
internal AetheriaClientState(
CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> providerAdvertisement,
CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> health,
CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> commandBoundary,
CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> latestFrame,
CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> latestSoaView,
CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> catalog,
@ -38,6 +41,12 @@ namespace GameCult.Aetheria.State.Verse
CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionSummaryDocument> starbridgeSummary,
Func<string, CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument>> 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<AetheriaRuntimeDaemonFrameDocument> LatestFrame { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> LatestSoaView { get; }
@ -182,6 +196,33 @@ namespace GameCult.Aetheria.State.Verse
}
}
public sealed class AetheriaClientDaemonState
{
internal AetheriaClientDaemonState(
CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> providerAdvertisement,
CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> health,
CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> commandBoundary,
CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> latestFrame,
CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> 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<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> Health { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> LatestFrame { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> LatestSoaView { get; }
}
public sealed class AetheriaClientCurrentState
{
internal AetheriaClientCurrentState(

View file

@ -735,6 +735,12 @@ namespace GameCult.Aetheria.State.Verse
CultMesh.ProjectionSource("catalog:aetheria.loadout_templates"));
return new AetheriaClientState(
Document<AetheriaRuntimeDaemonProviderAdvertisementDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement),
Document<AetheriaRuntimeDaemonHealthDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonHealth),
Document<AetheriaRuntimeDaemonCommandBoundaryDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary),
Document<AetheriaRuntimeDaemonFrameDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest),
Document<AetheriaRuntimeDaemonSoaViewDocument>(