From be503940fa776b2ca563bba0b13fb254eb3090e1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 19:09:26 +0100 Subject: [PATCH] Add Unity playable world runtime host --- docs/eve-migration-roadmap.md | 8 +- docs/parity-testing-harness.md | 6 + docs/world-state-lowering.md | 15 +- .../Runtime/EveUnityPlayableWorldRuntime.cs | 197 ++++++++++++++++++ .../EveUnitySceneSurfaceLowererTests.cs | 84 ++++++++ .../eve-runtime-capability.json | 4 + .../eveunity-scene-split-handoff.json | 5 +- .../run-eveunity-scene-lifecycle-smoke.ps1 | 2 +- ...un-eveunity-scene-provider-shell-smoke.ps1 | 12 +- ...ntime-owner-conformance-consumer-smoke.ps1 | 1 + tools/parity/parity-manifest.json | 10 +- 11 files changed, 332 insertions(+), 12 deletions(-) create mode 100644 runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 94840c2..275c70d 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -627,6 +627,10 @@ Recently cut: command receipts through `IEveUnitySceneCommandReceiptSource`: pending receipts remain status only, while accepted or reconciled receipts refresh from the provider surface source instead of moving entities locally. + `EveUnityPlayableWorldRuntime` now owns the generic Unity playable-world + composition point: provider surface documents, provider asset manifest + documents, command sink, optional receipt source, asset manifest cache, + presenter, and scene sink are wired without Aetheria imports. `EveUnityPlayableWorldPresenter` now maps provider-authored entity rows and asset refs into scene sink operations, including removal of entities absent from later provider snapshots, without importing Aetheria entity/prefab @@ -643,7 +647,9 @@ Recently cut: `gamecult.eve.unity_playable_world_asset_manifest.v1` and `EveUnityPlayableWorldAssetManifestDocumentSource` now define the typed manifest document loading boundary and feed that cache without importing - Aetheria asset classes. The Unity client boundary is generic before it learns + Aetheria asset classes. `EveUnityLivePlayableWorldAssetProvider` connects + that cache to the active world pointer for GameObject scenes. The Unity + client boundary is generic before it learns live transport. The next blocker is the live generic Unity player loop: implement the CultMesh/CultNet adapter, implement concrete CultMesh/CultCache readers for provider surface and asset manifest documents, and prove diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index a5cf92a..592992b 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -495,6 +495,12 @@ now also declares provider connection with `EveUnityPlayableWorldPresenter`, applies every live snapshot to the scene sink, and delegates player intent back to the advertised provider command boundary. The runtime now also declares +`playableWorldRuntimeHost`: `EveUnityPlayableWorldRuntime` composes provider +surface documents, provider asset manifest documents, command sink, optional +receipt source, asset manifest cache, presenter, and scene sink into one generic +Unity playable-world client. `EveUnityLivePlayableWorldAssetProvider` lets a +GameObject scene resolve prefabs through the active world's provider manifest +without importing provider prefab classes. The runtime now also declares `providerCommandReceipts`: `IEveUnitySceneCommandReceiptSource` and `EveUnitySceneCommandReceipt` keep provider-owned pending/accepted/denied/ reconciled command status distinct from scene state; accepted or reconciled diff --git a/docs/world-state-lowering.md b/docs/world-state-lowering.md index 09535fd..1630b89 100644 --- a/docs/world-state-lowering.md +++ b/docs/world-state-lowering.md @@ -177,6 +177,10 @@ local world truth, and `IEveUnitySceneCommandReceiptSource` reports provider receipts as derived command status. Pending receipts do not move the scene; accepted or reconciled receipts only trigger a refresh from the provider surface source, so the next presentation still comes from daemon-authored state. +`EveUnityPlayableWorldRuntime` is the generic runtime composition entry point: +it wires provider surface documents, provider asset manifest documents, command +sink, optional receipt source, asset manifest cache, presenter, and scene sink +into one Unity playable-world client without importing provider code. `EveUnityPlayableWorldPresenter` maps the provider-authored entity rows and asset refs into scene operations through `IEveUnityPlayableWorldSceneSink` and `IEveUnityPlayableWorldAssetResolver`, including removal of entities that @@ -193,16 +197,17 @@ through data rather than Aetheria code. `gamecult.eve.unity_playable_world_asset loading boundary for those Unity load keys. `IEveUnityPlayableWorldAssetManifestSource` and `EveUnityPlayableWorldAssetManifestCache` key live manifest updates by the `playableWorld.AssetManifest` pointer, so a CultMesh/CultCache source can update -Unity asset bindings without changing the lowerer. Aetheria-specific names -appear only as provider-authored data and command ids; the Unity client code -does not import Aetheria runtime types, prefab classes, or apply movement +Unity asset bindings without changing the lowerer. `EveUnityLivePlayableWorldAssetProvider` +connects that cache to the active world pointer for GameObject scenes. Aetheria-specific +names appear only as provider-authored data and command ids; the Unity client +code does not import Aetheria runtime types, prefab classes, or apply movement locally. This is not yet the final playable client. The remaining cut is a live CultMesh/CultNet provider subscription adapter implementing those document and -command ports, concrete CultMesh/CultCache readers for provider snapshots and +command ports, concrete CultMesh/CultCache readers for provider surface and asset manifest documents, and proof that daemon receipts plus provider -snapshots drive the next rendered Unity frame rather than renderer-local state. +documents drive the next rendered Unity frame rather than renderer-local state. ## Non-Goals diff --git a/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs b/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs new file mode 100644 index 0000000..60027fd --- /dev/null +++ b/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs @@ -0,0 +1,197 @@ +using System; +using GameCult.Eve.Surface; +using UnityEngine; + +#nullable enable + +namespace GameCult.Eve.UnityScene +{ + public sealed class EveUnityPlayableWorldRuntime : IDisposable + { + private readonly EveUnitySceneProviderSurfaceDocumentSource _surfaceSource; + private readonly EveUnitySceneProviderConnection _connection; + private readonly EveUnityPlayableWorldLiveClient _client; + private readonly EveUnityPlayableWorldAssetManifestDocumentSource? _assetManifestSource; + private bool _assetManifestConnected; + + public EveUnityPlayableWorldRuntime( + IEveUnitySceneProviderSurfaceDocumentSource surfaceDocuments, + IEveUnitySceneCommandSink commandSink, + IEveUnityPlayableWorldSceneSink sceneSink, + IEveUnityPlayableWorldAssetManifestDocumentSource? assetManifestDocuments = null, + IEveUnitySceneCommandReceiptSource? receiptSource = null, + IEveUnityPlayableWorldAssetResolver? assetResolver = null, + EveUnityPlayableWorldAssetManifestCache? assetManifests = null) + { + if (surfaceDocuments == null) throw new ArgumentNullException(nameof(surfaceDocuments)); + if (commandSink == null) throw new ArgumentNullException(nameof(commandSink)); + if (sceneSink == null) throw new ArgumentNullException(nameof(sceneSink)); + + AssetManifests = assetManifests ?? new EveUnityPlayableWorldAssetManifestCache(); + _surfaceSource = new EveUnitySceneProviderSurfaceDocumentSource(surfaceDocuments); + _connection = new EveUnitySceneProviderConnection(_surfaceSource, commandSink); + _client = new EveUnityPlayableWorldLiveClient( + _connection, + new EveUnityPlayableWorldPresenter(sceneSink, assetResolver ?? new EveUnityAssetRefResolver()), + receiptSource); + + if (assetManifestDocuments != null) + _assetManifestSource = new EveUnityPlayableWorldAssetManifestDocumentSource(assetManifestDocuments); + } + + public EveUnityPlayableWorldAssetManifestCache AssetManifests { get; } + + public EveUnityPlayableWorldProjection? ActiveWorld => _client.ActiveWorld; + + public EveUnitySceneProjection? ActiveProjection => _client.ActiveProjection; + + public EveUnityPlayableWorldPresentation? LastPresentation => _client.LastPresentation; + + public EveUnitySceneCommandReceipt? LastReceipt => _client.LastReceipt; + + public long ActiveVersion => _client.ActiveVersion; + + public string SourcePointer => _client.SourcePointer; + + public event Action? ReceiptAvailable + { + add { _client.ReceiptAvailable += value; } + remove { _client.ReceiptAvailable -= value; } + } + + public static EveUnityPlayableWorldRuntime CreateForGameObjectScene( + Transform root, + IEveUnitySceneProviderSurfaceDocumentSource surfaceDocuments, + IEveUnitySceneCommandSink commandSink, + IEveUnityPlayableWorldAssetManifestDocumentSource? assetManifestDocuments = null, + IEveUnitySceneCommandReceiptSource? receiptSource = null, + IEveUnityGameObjectAssetProvider? fallbackAssetProvider = null) + { + EveUnityPlayableWorldRuntime? runtime = null; + var assetManifests = new EveUnityPlayableWorldAssetManifestCache(); + var liveAssetProvider = new EveUnityLivePlayableWorldAssetProvider( + assetManifests, + () => runtime?.ActiveWorld, + fallbackAssetProvider ?? new EveUnityResourcesAssetProvider()); + var sceneSink = new EveUnityGameObjectPlayableWorldSceneSink(root, liveAssetProvider); + + runtime = new EveUnityPlayableWorldRuntime( + surfaceDocuments, + commandSink, + sceneSink, + assetManifestDocuments, + receiptSource, + new EveUnityAssetRefResolver(), + assetManifests); + return runtime; + } + + public EveUnityPlayableWorldPresentation Connect() + { + EnsureAssetManifestConnected(); + return _client.Connect(); + } + + public EveUnityPlayableWorldPresentation Refresh() + { + EnsureAssetManifestConnected(); + return _client.Refresh(); + } + + public EveSurfaceCommandRequest SubmitMoveIntent( + string entityId, + float targetX, + float targetY, + float targetZ, + DateTimeOffset? issuedAt = null) + { + return _client.SubmitMoveIntent(entityId, targetX, targetY, targetZ, issuedAt); + } + + public EveSurfaceCommandRequest SubmitFocusIntent( + string entityId, + DateTimeOffset? issuedAt = null) + { + return _client.SubmitFocusIntent(entityId, issuedAt); + } + + public EveSurfaceCommandRequest SubmitTargetIntent( + string sourceEntityId, + string targetEntityId, + DateTimeOffset? issuedAt = null) + { + return _client.SubmitTargetIntent(sourceEntityId, targetEntityId, issuedAt); + } + + public EveSurfaceCommandRequest SubmitActionIntent( + string entityId, + string actionId, + DateTimeOffset? issuedAt = null) + { + return _client.SubmitActionIntent(entityId, actionId, issuedAt); + } + + public void Disconnect() + { + _client.Disconnect(); + + if (_assetManifestConnected && _assetManifestSource != null) + { + AssetManifests.Disconnect(_assetManifestSource); + _assetManifestSource.Disconnect(); + _assetManifestConnected = false; + } + } + + public void Dispose() + { + Disconnect(); + _client.Dispose(); + _assetManifestSource?.Dispose(); + } + + private void EnsureAssetManifestConnected() + { + if (_assetManifestConnected || _assetManifestSource == null) + return; + + _assetManifestSource.Connect(); + AssetManifests.Connect(_assetManifestSource); + _assetManifestConnected = true; + } + } + + public sealed class EveUnityLivePlayableWorldAssetProvider : IEveUnityGameObjectAssetProvider + { + private readonly EveUnityPlayableWorldAssetManifestCache _assetManifests; + private readonly Func _activeWorld; + private readonly IEveUnityGameObjectAssetProvider _fallback; + + public EveUnityLivePlayableWorldAssetProvider( + EveUnityPlayableWorldAssetManifestCache assetManifests, + Func activeWorld, + IEveUnityGameObjectAssetProvider fallback) + { + _assetManifests = assetManifests ?? throw new ArgumentNullException(nameof(assetManifests)); + _activeWorld = activeWorld ?? throw new ArgumentNullException(nameof(activeWorld)); + _fallback = fallback ?? throw new ArgumentNullException(nameof(fallback)); + } + + public GameObject? ResolvePrefab(EveUnityPlayableWorldAssetBinding asset) + { + if (asset == null) throw new ArgumentNullException(nameof(asset)); + + var world = _activeWorld(); + var manifest = world == null ? null : _assetManifests.GetForWorld(world); + if (manifest != null) + { + var provider = new EveUnityManifestGameObjectAssetProvider(manifest, _fallback); + var prefab = provider.ResolvePrefab(asset); + if (prefab != null) + return prefab; + } + + return _fallback.ResolvePrefab(asset); + } + } +} diff --git a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index 4047fea..967e555 100644 --- a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -248,6 +248,90 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(moveIntent.ReceiptSchema, Is.EqualTo("aetheria.eve_command_acceptance_status.v1")); } + [Test] + public void PlayableWorldRuntimeComposesProviderDocumentsAssetsReceiptsAndSceneSink() + { + var surfaceDocuments = new FakeProviderSurfaceDocumentSource(new EveUnitySceneProviderSurfaceDocument( + PlayableArpgDocument(), + Advertisement("aetheria.daemon.game"), + "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", + 1)); + var assetDocuments = new FakeAssetManifestDocumentSource(new EveUnityPlayableWorldAssetManifestDocument( + "cultmesh://aetheria/assets/manifest", + new[] + { + new EveUnityPlayableWorldAssetManifestDocumentEntry( + "cultmesh://aetheria/assets/map/entity/player", + "player", + "Resources/Aetheria/Entities/Vanguard.prefab", + "aetheria.vanguard"), + new EveUnityPlayableWorldAssetManifestDocumentEntry( + "cultmesh://aetheria/assets/map/entity/ship", + "enemy", + "Resources/Aetheria/Entities/Raider.prefab", + "aetheria.raider") + }, + "aetheria")); + var commandSink = new FakeCommandSink("cultmesh-command-sink"); + var receiptSource = new FakeCommandReceiptSource(); + var sceneSink = new FakePlayableWorldSceneSink(); + + using var runtime = new EveUnityPlayableWorldRuntime( + surfaceDocuments, + commandSink, + sceneSink, + assetDocuments, + receiptSource); + + var initialPresentation = runtime.Connect(); + + Assert.That(initialPresentation.ActiveEntities, Is.EqualTo(3)); + Assert.That(runtime.ActiveWorld, Is.Not.Null); + Assert.That(runtime.ActiveWorld!.AssetManifest, Is.EqualTo("cultmesh://aetheria/assets/manifest")); + Assert.That(runtime.AssetManifests.GetForWorld(runtime.ActiveWorld), Is.Not.Null); + Assert.That(sceneSink.Upserts.Count, Is.EqualTo(3)); + + var moveIntent = runtime.SubmitMoveIntent( + "player-vanguard", + 14f, + 0f, + 9f, + DateTimeOffset.Parse("2026-07-09T00:00:00Z")); + + Assert.That(commandSink.Submitted.Count, Is.EqualTo(1)); + Assert.That(commandSink.Submitted[0], Is.SameAs(moveIntent)); + Assert.That(moveIntent.ProviderId, Is.EqualTo("aetheria")); + Assert.That(moveIntent.Command, Is.EqualTo("aetheria.daemon.commands")); + Assert.That(moveIntent.CommandBoundary, Is.EqualTo("aetheria.daemon.commands")); + Assert.That(moveIntent.ReceiptSchema, Is.EqualTo("aetheria.eve_command_acceptance_status.v1")); + + surfaceDocuments.Publish(new EveUnitySceneProviderSurfaceDocument( + PlayableArpgDocument(includeRaider: false, playerPosition: "14,0,9"), + Advertisement("aetheria.daemon.game"), + "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", + 2)); + + Assert.That(runtime.ActiveVersion, Is.EqualTo(2)); + Assert.That(runtime.LastPresentation, Is.Not.Null); + Assert.That(runtime.LastPresentation!.ActiveEntities, Is.EqualTo(2)); + Assert.That(sceneSink.RemovedEntityIds, Does.Contain("raider-scout")); + Assert.That(sceneSink.Upserts[3].entity.EntityId, Is.EqualTo("player-vanguard")); + Assert.That(sceneSink.Upserts[3].entity.PositionX, Is.EqualTo(14f)); + Assert.That(sceneSink.Upserts[3].entity.PositionZ, Is.EqualTo(9f)); + + receiptSource.Publish(new EveUnitySceneCommandReceipt( + "aetheria.daemon.move_intent.accepted", + "aetheria.daemon.move_intent", + "aetheria.daemon.move_intent", + "accepted", + "Aetheria", + "provider-owned-daemon")); + + Assert.That(runtime.LastReceipt, Is.Not.Null); + Assert.That(runtime.LastReceipt!.IsProviderOwned, Is.True); + Assert.That(runtime.LastReceipt.ShouldRefreshProviderSurface, Is.True); + } + [Test] public void PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities() { diff --git a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json index a5b0be2..5102539 100644 --- a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json +++ b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json @@ -24,6 +24,7 @@ "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", + "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", @@ -72,6 +73,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -124,6 +126,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -165,6 +168,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", diff --git a/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json b/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json index ce4dd9e..4d02375 100644 --- a/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json +++ b/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json @@ -19,6 +19,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -41,6 +42,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -63,6 +65,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -73,7 +76,7 @@ "runtimes/incubating/eve-unity-scene/eve-runtime-capability.json" ], "destinationOwner": "EveUnity", - "replacementProof": "EveUnity claims the provider-advertised unity-scene lowering target with provider-agnostic playable-world projection, scene graph projection, typed provider surface document loading, provider snapshot session, live playable-world client composition, provider command receipt observation, playable-world scene presentation, Unity GameObject scene sink, typed provider asset manifest document loading, provider asset manifest resolution, provider asset manifest source/cache, and command boundary evidence; live transport and capture remain separate lifecycle proofs." + "replacementProof": "EveUnity claims the provider-advertised unity-scene lowering target with provider-agnostic playable-world projection, scene graph projection, typed provider surface document loading, provider snapshot session, a playable-world runtime host, live playable-world client composition, provider command receipt observation, playable-world scene presentation, Unity GameObject scene sink, typed provider asset manifest document loading, provider asset manifest resolution, provider asset manifest source/cache, and command boundary evidence; live transport and capture remain separate lifecycle proofs." }, { "id": "unity-scene-sai-plugin-projection", diff --git a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 index ba2a886..dcbb4c7 100644 --- a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 +++ b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 @@ -39,7 +39,7 @@ if ($manifest.incubation.splitHandoff.manifestPath -ne "runtimes/incubating/eve- throw "EveUnity scene manifest missing split handoff path" } -foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource", "embeddedDocuments")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource", "embeddedDocuments")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveUnity scene manifest missing provider-shell feature: $feature" } diff --git a/scripts/run-eveunity-scene-provider-shell-smoke.ps1 b/scripts/run-eveunity-scene-provider-shell-smoke.ps1 index 5089bd8..81ea2f0 100644 --- a/scripts/run-eveunity-scene-provider-shell-smoke.ps1 +++ b/scripts/run-eveunity-scene-provider-shell-smoke.ps1 @@ -54,7 +54,7 @@ foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) { } } -foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveUnity scene provider shell missing supported feature: $feature" } @@ -67,6 +67,7 @@ $expectedFiles = @( "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneClientSession.cs", "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\EveUnityPlayableWorldPresenter.cs", "runtimes\incubating\eve-unity-scene\Runtime\EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldAssetManifest.cs", @@ -112,6 +113,13 @@ foreach ($symbol in @("EveUnityPlayableWorldLiveClient", "IEveUnitySceneCommandR } } +$runtimeHostSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldRuntime.cs") -Raw +foreach ($symbol in @("EveUnityPlayableWorldRuntime", "CreateForGameObjectScene", "EveUnityLivePlayableWorldAssetProvider", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneCommandSink", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandReceiptSource", "EveUnityPlayableWorldAssetManifestCache", "AssetManifests", "ActiveWorld", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Connect", "Refresh", "Disconnect")) { + if (-not $runtimeHostSource.Contains($symbol)) { + throw "EveUnity playable world runtime host 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)) { @@ -155,7 +163,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", "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", "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")) { if (-not $testSource.Contains($symbol)) { throw "EveUnity scene provider shell tests missing symbol: $symbol" } diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 15ee149..b5be002 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -73,6 +73,7 @@ node $consumerScript $consumerExport ` --expect-runtime-feature unity-scene:providerAssetManifestDocumentSource ` --expect-runtime-feature unity-scene:sceneGraphProjection ` --expect-runtime-feature unity-scene:playableWorldProjection ` + --expect-runtime-feature unity-scene:playableWorldRuntimeHost ` --expect-runtime-feature unity-scene:playableWorldScenePresentation ` --expect-runtime-feature unity-scene:unityGameObjectSceneSink ` --expect-runtime-feature unity-scene:providerAssetManifestResolution ` diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index d6db110..e839d7d 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -1041,7 +1041,7 @@ "splitHandoffPath": "runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json", "requiredFixtures": ["aetheria-world", "eve-world-smoke"], "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "providerAssetManifestSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "embeddedDocuments"], + "supportedFeatures": ["providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "providerAssetManifestSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "embeddedDocuments"], "supportedPlugins": [ { "pluginId": "sai.vn", @@ -1072,6 +1072,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -1091,6 +1092,7 @@ "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs", "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/EveUnityPlayableWorldPresenter.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", @@ -1133,6 +1135,10 @@ "path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", "contains": ["EveUnityPlayableWorldLiveClient", "IEveUnitySceneCommandReceiptSource", "EveUnitySceneCommandReceipt", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "LastReceipt", "ReceiptAvailable", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "ShouldRefreshProviderSurface", "IsProviderOwned", "Disconnect"] }, + { + "path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", + "contains": ["EveUnityPlayableWorldRuntime", "CreateForGameObjectScene", "EveUnityLivePlayableWorldAssetProvider", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneCommandSink", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandReceiptSource", "EveUnityPlayableWorldAssetManifestCache", "AssetManifests", "ActiveWorld", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Connect", "Refresh", "Disconnect"] + }, { "path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", "contains": ["IEveUnityPlayableWorldSceneSink", "IEveUnityPlayableWorldAssetResolver", "EveUnityPlayableWorldPresenter", "EveUnityPlayableWorldAssetBinding", "EveUnityPlayableWorldPresentation", "EveUnityAssetRefResolver", "ConfigureWorld", "UpsertEntity", "RemoveEntity", "provider-asset-ref", "unity-generated-placeholder"] @@ -1159,7 +1165,7 @@ }, { "path": "runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", - "contains": ["LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "ProviderSurfaceDocumentSourceFeedsPlayableClientWithoutAetheriaTypes", "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", "norn.map", "cultmesh://aetheria/norn/map", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1"] + "contains": ["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", "norn.map", "cultmesh://aetheria/norn/map", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1"] }, { "path": "tools/eveunity/eveunity-capture-contract.test.mjs",