Add generic EveUnity playable world host

This commit is contained in:
Meta Vens 2026-07-09 19:34:18 +01:00
commit d40be46a48
8 changed files with 371 additions and 2 deletions

View file

@ -0,0 +1,184 @@
using System;
using GameCult.Eve.Surface;
using UnityEngine;
#nullable enable
namespace GameCult.Eve.UnityScene
{
public sealed class EveUnityPlayableWorldClientHost : MonoBehaviour
{
[SerializeField] private Transform? sceneRoot;
[SerializeField] private MonoBehaviour? providerSurfaceDocuments;
[SerializeField] private MonoBehaviour? commandSink;
[SerializeField] private MonoBehaviour? assetManifestDocuments;
[SerializeField] private MonoBehaviour? receiptSource;
[SerializeField] private MonoBehaviour? fallbackAssetProvider;
[SerializeField] private bool connectOnEnable = true;
[SerializeField] private bool refreshInUpdate = true;
[SerializeField] private float refreshIntervalSeconds = 0.1f;
private float _nextRefreshAt;
public EveUnityPlayableWorldRuntime? Runtime { get; private set; }
public EveUnityPlayableWorldProjection? ActiveWorld => Runtime?.ActiveWorld;
public EveUnitySceneProjection? ActiveProjection => Runtime?.ActiveProjection;
public EveUnityPlayableWorldPresentation? LastPresentation => Runtime?.LastPresentation;
public EveUnitySceneCommandReceipt? LastReceipt => Runtime?.LastReceipt;
public long ActiveVersion => Runtime?.ActiveVersion ?? 0;
public void Configure(
Transform? sceneRoot,
MonoBehaviour providerSurfaceDocuments,
MonoBehaviour commandSink,
MonoBehaviour? assetManifestDocuments = null,
MonoBehaviour? receiptSource = null,
MonoBehaviour? fallbackAssetProvider = null)
{
this.sceneRoot = sceneRoot;
this.providerSurfaceDocuments = providerSurfaceDocuments;
this.commandSink = commandSink;
this.assetManifestDocuments = assetManifestDocuments;
this.receiptSource = receiptSource;
this.fallbackAssetProvider = fallbackAssetProvider;
}
public EveUnityPlayableWorldPresentation Connect()
{
Disconnect();
RefreshProviderSources();
Runtime = EveUnityPlayableWorldRuntime.CreateForGameObjectScene(
sceneRoot == null ? transform : sceneRoot,
Required<IEveUnitySceneProviderSurfaceDocumentSource>(
providerSurfaceDocuments,
nameof(providerSurfaceDocuments)),
Required<IEveUnitySceneCommandSink>(
commandSink,
nameof(commandSink)),
Optional<IEveUnityPlayableWorldAssetManifestDocumentSource>(assetManifestDocuments),
Optional<IEveUnitySceneCommandReceiptSource>(receiptSource),
Optional<IEveUnityGameObjectAssetProvider>(fallbackAssetProvider));
return Runtime.Connect();
}
public EveUnityPlayableWorldPresentation Refresh()
{
RefreshProviderSources();
if (Runtime == null)
return Connect();
return Runtime.Refresh();
}
public EveSurfaceCommandRequest SubmitMoveIntent(
string entityId,
float targetX,
float targetY,
float targetZ,
DateTimeOffset? issuedAt = null)
{
return RequireRuntime().SubmitMoveIntent(entityId, targetX, targetY, targetZ, issuedAt);
}
public EveSurfaceCommandRequest SubmitFocusIntent(
string entityId,
DateTimeOffset? issuedAt = null)
{
return RequireRuntime().SubmitFocusIntent(entityId, issuedAt);
}
public EveSurfaceCommandRequest SubmitTargetIntent(
string sourceEntityId,
string targetEntityId,
DateTimeOffset? issuedAt = null)
{
return RequireRuntime().SubmitTargetIntent(sourceEntityId, targetEntityId, issuedAt);
}
public EveSurfaceCommandRequest SubmitActionIntent(
string entityId,
string actionId,
DateTimeOffset? issuedAt = null)
{
return RequireRuntime().SubmitActionIntent(entityId, actionId, issuedAt);
}
public void Disconnect()
{
Runtime?.Dispose();
Runtime = null;
}
private void OnEnable()
{
if (connectOnEnable)
Connect();
}
private void Update()
{
if (!refreshInUpdate || Runtime == null)
return;
if (Time.unscaledTime < _nextRefreshAt)
return;
_nextRefreshAt = Time.unscaledTime + Math.Max(0.01f, refreshIntervalSeconds);
Refresh();
}
private void OnDisable()
{
Disconnect();
}
private void OnDestroy()
{
Disconnect();
}
private EveUnityPlayableWorldRuntime RequireRuntime()
{
if (Runtime == null)
throw new InvalidOperationException("Eve Unity playable world client host is not connected.");
return Runtime;
}
private void RefreshProviderSources()
{
RefreshProvider(providerSurfaceDocuments);
RefreshProvider(assetManifestDocuments);
RefreshProvider(receiptSource);
RefreshProvider(commandSink);
}
private static void RefreshProvider(MonoBehaviour? behaviour)
{
if (behaviour is IEveUnityProviderRefreshSource refreshSource)
refreshSource.Refresh();
}
private static T Required<T>(MonoBehaviour? behaviour, string fieldName) where T : class
{
var resolved = behaviour as T;
if (resolved == null)
throw new InvalidOperationException(
$"Eve Unity playable world client host requires '{fieldName}' to implement {typeof(T).Name}.");
return resolved;
}
private static T? Optional<T>(MonoBehaviour? behaviour) where T : class
{
return behaviour as T;
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: df3f3a1abd3f14541bf6d7ec59343661

View file

@ -0,0 +1,7 @@
namespace GameCult.Eve.UnityScene
{
public interface IEveUnityProviderRefreshSource
{
void Refresh();
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: babddb726f9747e43a7789eb6b584fad

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using NUnit.Framework;
using UnityEngine;
#nullable enable
@ -332,6 +333,57 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(runtime.LastReceipt.ShouldRefreshProviderSurface, Is.True);
}
[Test]
public void PlayableWorldClientHostMountsInterfaceProviderWithoutProviderTypes()
{
var rootObject = new GameObject("generic-eve-world-root");
var hostObject = new GameObject("generic-eve-client");
hostObject.SetActive(false);
try
{
var provider = hostObject.AddComponent<FakePlayableWorldProviderComponent>();
provider.Set(
new EveUnitySceneProviderSurfaceDocument(
PlayableArpgDocument(),
Advertisement("aetheria.daemon.game"),
"cultmesh://aetheria/eve/surfaces/aetheria.daemon.game",
1),
new EveUnityPlayableWorldAssetManifestDocument(
"cultmesh://aetheria/assets/manifest",
new[]
{
new EveUnityPlayableWorldAssetManifestDocumentEntry(
"cultmesh://aetheria/assets/map/entity/player",
"player",
"",
"aetheria.vanguard")
},
"aetheria"));
var host = hostObject.AddComponent<EveUnityPlayableWorldClientHost>();
host.Configure(rootObject.transform, provider, provider, provider, provider);
var presentation = host.Connect();
var moveIntent = host.SubmitMoveIntent("player-vanguard", 3f, 0f, 4f);
Assert.That(presentation.ActiveEntities, Is.EqualTo(3));
Assert.That(host.ActiveWorld, Is.Not.Null);
Assert.That(host.ActiveWorld!.PlayerEntityId, Is.EqualTo("player-vanguard"));
Assert.That(host.ActiveWorld.AssetManifest, Is.EqualTo("cultmesh://aetheria/assets/manifest"));
Assert.That(rootObject.transform.childCount, Is.EqualTo(3));
Assert.That(provider.RefreshCount, Is.EqualTo(1));
Assert.That(provider.Submitted.Count, Is.EqualTo(1));
Assert.That(provider.Submitted[0], Is.SameAs(moveIntent));
Assert.That(moveIntent.CommandBoundary, Is.EqualTo("aetheria.daemon.commands"));
}
finally
{
UnityEngine.Object.DestroyImmediate(hostObject);
UnityEngine.Object.DestroyImmediate(rootObject);
}
}
[Test]
public void PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities()
{
@ -1197,5 +1249,75 @@ namespace GameCult.Eve.UnityScene.Tests
DocumentAvailable?.Invoke(document);
}
}
private sealed class FakePlayableWorldProviderComponent :
MonoBehaviour,
IEveUnitySceneProviderSurfaceDocumentSource,
IEveUnityPlayableWorldAssetManifestDocumentSource,
IEveUnitySceneCommandSink,
IEveUnitySceneCommandReceiptSource,
IEveUnityProviderRefreshSource
{
public List<EveSurfaceCommandRequest> Submitted { get; } = new List<EveSurfaceCommandRequest>();
public int RefreshCount { get; private set; }
public string SinkKind => "fake-provider-command-sink";
public string ManifestRef => CurrentDocument.ManifestRef;
public EveUnitySceneProviderSurfaceDocument CurrentSurfaceDocument { get; private set; } =
new EveUnitySceneProviderSurfaceDocument(
PlayableArpgDocument(),
Advertisement("aetheria.daemon.game"),
"cultmesh://aetheria/eve/surfaces/aetheria.daemon.game",
1);
public EveUnityPlayableWorldAssetManifestDocument CurrentDocument { get; private set; } =
new EveUnityPlayableWorldAssetManifestDocument(
"cultmesh://aetheria/assets/manifest",
Array.Empty<EveUnityPlayableWorldAssetManifestDocumentEntry>(),
"aetheria");
EveUnitySceneProviderSurfaceDocument IEveUnitySceneProviderSurfaceDocumentSource.CurrentDocument =>
CurrentSurfaceDocument;
public event Action<EveUnitySceneProviderSurfaceDocument>? DocumentAvailable;
public event Action<EveUnityPlayableWorldAssetManifestDocument>? AssetManifestDocumentAvailable;
event Action<EveUnityPlayableWorldAssetManifestDocument> IEveUnityPlayableWorldAssetManifestDocumentSource.DocumentAvailable
{
add => AssetManifestDocumentAvailable += value;
remove => AssetManifestDocumentAvailable -= value;
}
public event Action<EveUnitySceneCommandReceipt>? ReceiptAvailable;
public void Set(
EveUnitySceneProviderSurfaceDocument surfaceDocument,
EveUnityPlayableWorldAssetManifestDocument assetManifest)
{
CurrentSurfaceDocument = surfaceDocument;
CurrentDocument = assetManifest;
}
public void Refresh()
{
RefreshCount++;
DocumentAvailable?.Invoke(CurrentSurfaceDocument);
AssetManifestDocumentAvailable?.Invoke(CurrentDocument);
}
public void Submit(EveSurfaceCommandRequest request)
{
Submitted.Add(request);
}
public void PublishReceipt(EveUnitySceneCommandReceipt receipt)
{
ReceiptAvailable?.Invoke(receipt);
}
}
}
}

View file

@ -22,6 +22,7 @@
"livePlayableWorldClient",
"providerCommandReceipts",
"providerAssetManifestDocumentSource",
"playableWorldClientHost",
"sceneGraphProjection",
"playableWorldProjection",
"playableWorldRuntimeHost",
@ -74,6 +75,8 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityProviderRefreshSource.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs",
@ -127,6 +130,8 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityProviderRefreshSource.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs",
@ -169,6 +174,8 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityProviderRefreshSource.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs",

View file

@ -15,6 +15,7 @@ $surfaceProject = Join-Path $AetheriaRoot "GameCult.Eve.Surface.csproj"
$aetheriaEveRuntimePackage = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.eve-runtime\package.json"
$aetheriaEveRuntimeAsmdef = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.eve-runtime\Runtime\GameCult.Aetheria.EveRuntime.asmdef"
$aetheriaSceneBridge = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.eve-runtime\Runtime\AetheriaEveUnitySceneProviderBridge.cs"
$aetheriaSceneProviderComponent = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.eve-runtime\Runtime\AetheriaEveUnitySceneProviderComponent.cs"
$aetheriaGameSurfaceBuilder = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonGameSurfaceBuilder.cs"
if (-not (Test-Path $manifestPath)) {
throw "Aetheria Unity package manifest not found: $manifestPath"
@ -34,6 +35,9 @@ if (-not (Test-Path $aetheriaEveRuntimeAsmdef)) {
if (-not (Test-Path $aetheriaSceneBridge)) {
throw "Aetheria Eve Unity scene provider bridge not found: $aetheriaSceneBridge"
}
if (-not (Test-Path $aetheriaSceneProviderComponent)) {
throw "Aetheria Eve Unity scene provider component not found: $aetheriaSceneProviderComponent"
}
if (-not (Test-Path $aetheriaGameSurfaceBuilder)) {
throw "Aetheria daemon game surface builder not found: $aetheriaGameSurfaceBuilder"
}
@ -85,6 +89,8 @@ foreach ($symbol in @(
"IEveUnitySceneProviderSurfaceDocumentSource",
"IEveUnityPlayableWorldAssetManifestDocumentSource",
"IEveUnitySceneCommandSink",
"IEveUnityProviderRefreshSource",
"ManifestRef",
"AetheriaEveRuntimeUnityHooks.RequireControl"
)) {
if (-not $sceneBridge.Contains($symbol)) {
@ -92,6 +98,26 @@ foreach ($symbol in @(
}
}
$sceneProviderComponent = Get-Content -Raw -LiteralPath $aetheriaSceneProviderComponent
foreach ($symbol in @(
"AetheriaEveUnitySceneProviderComponent",
"MonoBehaviour",
"IEveUnitySceneProviderSurfaceDocumentSource",
"IEveUnityPlayableWorldAssetManifestDocumentSource",
"IEveUnitySceneCommandSink",
"IEveUnityProviderRefreshSource",
"AetheriaEveUnitySceneProviderBridge",
"stateFilePathOverride",
"surfaceId",
"runtimeId",
"Refresh",
"Submit"
)) {
if (-not $sceneProviderComponent.Contains($symbol)) {
throw "Aetheria Eve Unity scene provider component missing symbol: $symbol"
}
}
$gameSurfaceBuilder = Get-Content -Raw -LiteralPath $aetheriaGameSurfaceBuilder
foreach ($symbol in @(
'"world.scene3d"',

View file

@ -54,7 +54,7 @@ foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) {
}
}
foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) {
foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "playableWorldClientHost", "sceneGraphProjection", "playableWorldProjection", "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) {
if (-not (@($manifest.supportedFeatures) -contains $feature)) {
throw "EveUnity scene provider shell missing supported feature: $feature"
}
@ -68,6 +68,8 @@ $expectedFiles = @(
"runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneProviderConnection.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldLiveClient.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldRuntime.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldClientHost.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityProviderRefreshSource.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldPresenter.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityGameObjectPlayableWorldSceneSink.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldAssetManifest.cs",
@ -120,6 +122,23 @@ foreach ($symbol in @("EveUnityPlayableWorldRuntime", "CreateForGameObjectScene"
}
}
$clientHostSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldClientHost.cs") -Raw
foreach ($symbol in @("EveUnityPlayableWorldClientHost", "MonoBehaviour", "providerSurfaceDocuments", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneCommandSink", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandReceiptSource", "IEveUnityGameObjectAssetProvider", "IEveUnityProviderRefreshSource", "Configure", "CreateForGameObjectScene", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect")) {
if (-not $clientHostSource.Contains($symbol)) {
throw "EveUnity playable world client host missing symbol: $symbol"
}
}
if ($clientHostSource.Contains("Aetheria")) {
throw "EveUnity playable world client host must remain provider-agnostic and cannot reference Aetheria"
}
$refreshSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityProviderRefreshSource.cs") -Raw
foreach ($symbol in @("IEveUnityProviderRefreshSource", "Refresh")) {
if (-not $refreshSource.Contains($symbol)) {
throw "EveUnity provider refresh source missing symbol: $symbol"
}
}
$playableWorldPresenterSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldPresenter.cs") -Raw
foreach ($symbol in @("IEveUnityPlayableWorldSceneSink", "IEveUnityPlayableWorldAssetResolver", "EveUnityPlayableWorldPresenter", "EveUnityPlayableWorldAssetBinding", "EveUnityPlayableWorldPresentation", "EveUnityAssetRefResolver", "ConfigureWorld", "UpsertEntity", "RemoveEntity", "provider-asset-ref", "unity-generated-placeholder")) {
if (-not $playableWorldPresenterSource.Contains($symbol)) {
@ -163,7 +182,7 @@ foreach ($symbol in @("TeXMathUnitySceneProjectionAdapter", "tex.math", "embed.t
}
$testSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Tests\Editor\EveUnitySceneSurfaceLowererTests.cs") -Raw
foreach ($symbol in @("LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "ProviderSurfaceDocumentSourceFeedsPlayableClientWithoutAetheriaTypes", "PlayableWorldRuntimeComposesProviderDocumentsAssetsReceiptsAndSceneSink", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering", "FakeProviderSurfaceSource", "FakeProviderSurfaceDocumentSource", "FakeCommandReceiptSource", "FakeAssetManifestSource", "FakeAssetManifestDocumentSource", "FakeCommandSink", "FakePlayableWorldSceneSink", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "pending", "reconciled", "world-projection-node", "playable-world-root", "playable-world-entity", "world-field-3d", "arpg-third-person", "cultmesh://aetheria/assets/manifest", "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", "aetheria.daemon.move_intent", "sai-vn-scene-stage", "norn-graph-scene-projection", "tex-math-scene-projection", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1")) {
foreach ($symbol in @("LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "ProviderSurfaceDocumentSourceFeedsPlayableClientWithoutAetheriaTypes", "PlayableWorldRuntimeComposesProviderDocumentsAssetsReceiptsAndSceneSink", "PlayableWorldClientHostMountsInterfaceProviderWithoutProviderTypes", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering", "FakeProviderSurfaceSource", "FakeProviderSurfaceDocumentSource", "FakeCommandReceiptSource", "FakeAssetManifestSource", "FakeAssetManifestDocumentSource", "FakeCommandSink", "FakePlayableWorldSceneSink", "FakePlayableWorldProviderComponent", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "pending", "reconciled", "world-projection-node", "playable-world-root", "playable-world-entity", "world-field-3d", "arpg-third-person", "cultmesh://aetheria/assets/manifest", "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", "aetheria.daemon.move_intent", "sai-vn-scene-stage", "norn-graph-scene-projection", "tex-math-scene-projection", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1")) {
if (-not $testSource.Contains($symbol)) {
throw "EveUnity scene provider shell tests missing symbol: $symbol"
}