From f093b422d113d62b66a9aa37ea9698a5aaddac89 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 29 Jun 2026 00:36:41 +0100 Subject: [PATCH] Use generic Starbridge seat documents --- Aetheria.State.Verify/Program.cs | 53 ++++++++++++++++--- .../StarbridgePlayerSeatDocumentTests.cs | 9 +++- .../Runtime/AetheriaClientState.cs | 45 +++++++++++++--- docs/aetheria-verse-client-contract.md | 2 +- 4 files changed, 92 insertions(+), 17 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index b013ea53a..59ddb9c32 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -8129,9 +8129,7 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root "public Task LatestAsync()", "public TDocument Latest()", "public Task> ReactiveAsync(", - "public CultMeshReactiveDocument Reactive(", - "public AetheriaRuntimeStarbridgePlayerSeatDocument LatestPlayerSeat(string seatId)", - "public CultMeshReactiveDocument ReactivePlayerSeat(" + "public CultMeshReactiveDocument Reactive(" }; var missingClientSymbols = requiredClientSymbols .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) @@ -8314,6 +8312,46 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root string.Join(", ", survivingCurrentConvenienceWrappers)); } + var starbridgeState = clientState.Split( + "public sealed class AetheriaClientStarbridgeState", + StringSplitOptions.None).Last(); + var compactStarbridgeState = CompactSource(starbridgeState); + var requiredStarbridgeParameterizedSymbols = new[] + { + "publicboolTryGetDocument(stringseatId,outCultMeshDocumentHandledocument)", + "publicCultMeshDocumentHandleDocument(stringseatId)", + "publicTaskLatestAsync(stringseatId)", + "publicTDocumentLatest(stringseatId)", + "publicTask>ReactiveAsync(stringseatId,", + "publicCultMeshReactiveDocumentReactive(stringseatId," + }; + var missingStarbridgeParameterizedSymbols = requiredStarbridgeParameterizedSymbols + .Where(symbol => !compactStarbridgeState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingStarbridgeParameterizedSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientStarbridgeState must expose generic parameterized document access for player-seat state: " + + string.Join(", ", missingStarbridgeParameterizedSymbols)); + } + + var forbiddenStarbridgeConvenienceWrappers = new[] + { + "LatestPlayerSeat(", + "LatestPlayerSeatAsync(", + "ReactivePlayerSeat(", + "ReactivePlayerSeatAsync(" + }; + var survivingStarbridgeConvenienceWrappers = forbiddenStarbridgeConvenienceWrappers + .Where(symbol => starbridgeState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (survivingStarbridgeConvenienceWrappers.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientStarbridgeState must use generic parameterized document access instead of named player-seat latest/reactive wrappers: " + + string.Join(", ", survivingStarbridgeConvenienceWrappers)); + } + var observedDaemonState = File.ReadAllText(observedDaemonStatePath); if (!observedDaemonState.Contains("public static bool TryCreateCurrent(", StringComparison.Ordinal) || !observedDaemonState.Contains("new AetheriaRuntimeObservedDaemonState(currentFrame, currentSoaView, currentZoneRender)", StringComparison.Ordinal)) @@ -8432,14 +8470,17 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root } if (!checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".ReactivePlayerSeat(seat.SeatId)", + ".Reactive(seat.SeatId)", + StringComparison.Ordinal) || + !checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( + ".Latest(seat.SeatId)", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Starbridge player-seat examples must use the named managed reactive accessor instead of fetching the raw document handle."); + "Starbridge player-seat examples must use generic parameterized managed document access instead of named wrappers or raw handle walks."); } - Console.WriteLine("Domain document accessors: managed Aetheria state uses generic fixed-document reads plus named parameterized viewport/detail/Starbridge handles"); + Console.WriteLine("Domain document accessors: managed Aetheria state uses generic fixed-document reads plus generic parameterized viewport/detail/Starbridge access"); } static void RequireVerseHostSettingsAuthority(string root) diff --git a/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs b/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs index a3784d17b..753cdd815 100644 --- a/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs +++ b/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs @@ -82,10 +82,15 @@ public class StarbridgePlayerSeatDocumentTests .State .Starbridge; using var reactive = handle.Reactive(); - using var aetheriaSeat = aetheriaStarbridge.ReactivePlayerSeat(seat.SeatId); + using var aetheriaSeat = aetheriaStarbridge + .Reactive(seat.SeatId); Assert.AreEqual("seat-support", handle.Latest().SeatId); - Assert.AreEqual("seat-support", client.State.Starbridge.LatestPlayerSeat(seat.SeatId).SeatId); + Assert.AreEqual( + "seat-support", + client.State.Starbridge + .Latest(seat.SeatId) + .SeatId); Assert.AreEqual("item:cockpit:support", reactive.Current.CockpitItemKey); Assert.AreEqual("item:cockpit:support", aetheriaSeat.Current.CockpitItemKey); Assert.AreEqual("entity:pod:support", reactive.Current.EscapePodEntityKey); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 005f84e12..7562b9c3c 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -797,28 +797,57 @@ namespace GameCult.Aetheria.State.Verse return _playerSeat(seatId); } - public Task LatestPlayerSeatAsync(string seatId) + public bool TryGetDocument( + string seatId, + out CultMeshDocumentHandle document) + where TDocument : class { - return PlayerSeat(seatId).LatestAsync(); + if (typeof(TDocument) == typeof(AetheriaRuntimeStarbridgePlayerSeatDocument)) + { + document = (CultMeshDocumentHandle)(object)PlayerSeat(seatId); + return true; + } + + document = null!; + return false; } - public AetheriaRuntimeStarbridgePlayerSeatDocument LatestPlayerSeat(string seatId) + public CultMeshDocumentHandle Document(string seatId) + where TDocument : class { - return LatestPlayerSeatAsync(seatId).ConfigureAwait(false).GetAwaiter().GetResult(); + if (TryGetDocument(seatId, out var document)) + return document; + + throw new NotSupportedException( + $"Aetheria Starbridge state does not expose a parameterized document for {typeof(TDocument).FullName}."); } - public Task> ReactivePlayerSeatAsync( + public Task LatestAsync(string seatId) + where TDocument : class + { + return Document(seatId).LatestAsync(); + } + + public TDocument Latest(string seatId) + where TDocument : class + { + return LatestAsync(seatId).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public Task> ReactiveAsync( string seatId, CultMeshReactiveDocumentOptions? options = null) + where TDocument : class { - return PlayerSeat(seatId).ReactiveAsync(options); + return Document(seatId).ReactiveAsync(options); } - public CultMeshReactiveDocument ReactivePlayerSeat( + public CultMeshReactiveDocument Reactive( string seatId, CultMeshReactiveDocumentOptions? options = null) + where TDocument : class { - return PlayerSeat(seatId).Reactive(options); + return Document(seatId).Reactive(options); } } diff --git a/docs/aetheria-verse-client-contract.md b/docs/aetheria-verse-client-contract.md index 6c48f2224..51d8d4974 100644 --- a/docs/aetheria-verse-client-contract.md +++ b/docs/aetheria-verse-client-contract.md @@ -1,6 +1,6 @@ # Aetheria Verse Client Contract -Aetheria clients should enter the runtime through CultMesh typed state, not through +Aetheria clients should enter the runtime through CultMesh typed Verse records, not through daemon internals. The daemon owns simulation authority over canonical shared documents; it does not own a separate private truth that is then copied into client state. Unity, tools, test harnesses, and later non-Unity clients open the