Use generic Starbridge seat documents

This commit is contained in:
Meta Vens 2026-06-29 00:36:41 +01:00
commit f093b422d1
4 changed files with 92 additions and 17 deletions

View file

@ -8129,9 +8129,7 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root
"public Task<TDocument> LatestAsync<TDocument>()",
"public TDocument Latest<TDocument>()",
"public Task<CultMeshReactiveDocument<TDocument>> ReactiveAsync<TDocument>(",
"public CultMeshReactiveDocument<TDocument> Reactive<TDocument>(",
"public AetheriaRuntimeStarbridgePlayerSeatDocument LatestPlayerSeat(string seatId)",
"public CultMeshReactiveDocument<AetheriaRuntimeStarbridgePlayerSeatDocument> ReactivePlayerSeat("
"public CultMeshReactiveDocument<TDocument> Reactive<TDocument>("
};
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<TDocument>(stringseatId,outCultMeshDocumentHandle<TDocument>document)",
"publicCultMeshDocumentHandle<TDocument>Document<TDocument>(stringseatId)",
"publicTask<TDocument>LatestAsync<TDocument>(stringseatId)",
"publicTDocumentLatest<TDocument>(stringseatId)",
"publicTask<CultMeshReactiveDocument<TDocument>>ReactiveAsync<TDocument>(stringseatId,",
"publicCultMeshReactiveDocument<TDocument>Reactive<TDocument>(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<AetheriaRuntimeStarbridgePlayerSeatDocument>(seat.SeatId)",
StringComparison.Ordinal) ||
!checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains(
".Latest<AetheriaRuntimeStarbridgePlayerSeatDocument>(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)

View file

@ -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<AetheriaRuntimeStarbridgePlayerSeatDocument>(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<AetheriaRuntimeStarbridgePlayerSeatDocument>(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);

View file

@ -797,28 +797,57 @@ namespace GameCult.Aetheria.State.Verse
return _playerSeat(seatId);
}
public Task<AetheriaRuntimeStarbridgePlayerSeatDocument> LatestPlayerSeatAsync(string seatId)
public bool TryGetDocument<TDocument>(
string seatId,
out CultMeshDocumentHandle<TDocument> document)
where TDocument : class
{
return PlayerSeat(seatId).LatestAsync();
if (typeof(TDocument) == typeof(AetheriaRuntimeStarbridgePlayerSeatDocument))
{
document = (CultMeshDocumentHandle<TDocument>)(object)PlayerSeat(seatId);
return true;
}
document = null!;
return false;
}
public AetheriaRuntimeStarbridgePlayerSeatDocument LatestPlayerSeat(string seatId)
public CultMeshDocumentHandle<TDocument> Document<TDocument>(string seatId)
where TDocument : class
{
return LatestPlayerSeatAsync(seatId).ConfigureAwait(false).GetAwaiter().GetResult();
if (TryGetDocument<TDocument>(seatId, out var document))
return document;
throw new NotSupportedException(
$"Aetheria Starbridge state does not expose a parameterized document for {typeof(TDocument).FullName}.");
}
public Task<CultMeshReactiveDocument<AetheriaRuntimeStarbridgePlayerSeatDocument>> ReactivePlayerSeatAsync(
public Task<TDocument> LatestAsync<TDocument>(string seatId)
where TDocument : class
{
return Document<TDocument>(seatId).LatestAsync();
}
public TDocument Latest<TDocument>(string seatId)
where TDocument : class
{
return LatestAsync<TDocument>(seatId).ConfigureAwait(false).GetAwaiter().GetResult();
}
public Task<CultMeshReactiveDocument<TDocument>> ReactiveAsync<TDocument>(
string seatId,
CultMeshReactiveDocumentOptions? options = null)
where TDocument : class
{
return PlayerSeat(seatId).ReactiveAsync(options);
return Document<TDocument>(seatId).ReactiveAsync(options);
}
public CultMeshReactiveDocument<AetheriaRuntimeStarbridgePlayerSeatDocument> ReactivePlayerSeat(
public CultMeshReactiveDocument<TDocument> Reactive<TDocument>(
string seatId,
CultMeshReactiveDocumentOptions? options = null)
where TDocument : class
{
return PlayerSeat(seatId).Reactive(options);
return Document<TDocument>(seatId).Reactive(options);
}
}

View file

@ -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