From ccbaba2a0506a3f8a29afca08333381968c3340b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 18:43:48 +0100 Subject: [PATCH] Add Unity command receipt boundary --- docs/eve-migration-roadmap.md | 9 +- docs/parity-testing-harness.md | 9 +- docs/world-state-lowering.md | 13 ++- .../EveUnityPlayableWorldLiveClient.cs | 100 +++++++++++++++++- .../EveUnitySceneSurfaceLowererTests.cs | 85 +++++++++++++++ .../eve-runtime-capability.json | 1 + .../eveunity-scene-split-handoff.json | 8 +- .../run-eveunity-scene-lifecycle-smoke.ps1 | 2 +- ...un-eveunity-scene-provider-shell-smoke.ps1 | 6 +- ...ntime-owner-conformance-consumer-smoke.ps1 | 1 + tools/parity/parity-manifest.json | 6 +- 11 files changed, 218 insertions(+), 22 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index b25349e..7722c70 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -617,7 +617,10 @@ Recently cut: `EveUnityPlayableWorldLiveClient` now composes the provider connection with the playable-world presenter, so provider snapshots immediately drive scene presentation and Unity input still exits through the provider command sink - rather than local simulation. + rather than local simulation. The same live client now observes provider + 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. `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 @@ -634,8 +637,8 @@ Recently cut: Unity client boundary is generic before it learns live transport. The next blocker is the live generic Unity player loop: implement that CultMesh/CultNet adapter, implement the concrete CultMesh/CultCache asset-manifest source, and - route provider receipts/next frames through the live client rather than - renderer-local simulation. + prove provider receipts plus daemon snapshots drive rendered Unity frames + rather than renderer-local simulation. - EveElectron capture lifecycle now carries the same kind of structured pending capture contract. `captureContract` names the Electron shell runtime, capture kind, PNG artifact pattern, conformance attachment point, required Aetheria diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index 3e1bae8..2862b7f 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -489,6 +489,11 @@ runtime-owned port to implement. The runtime 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 +`providerCommandReceipts`: `IEveUnitySceneCommandReceiptSource` and +`EveUnitySceneCommandReceipt` keep provider-owned pending/accepted/denied/ +reconciled command status distinct from scene state; accepted or reconciled +receipts can refresh the provider snapshot, but receipts do not mutate world +entities. The runtime now also declares `playableWorldScenePresentation`: `EveUnityPlayableWorldPresenter` maps provider-authored entity rows and asset refs into scene sink operations while removing entities absent from later provider snapshots. The runtime now also @@ -504,8 +509,8 @@ and `EveUnityPlayableWorldAssetManifestCache` key live manifest updates by the `playableWorld.AssetManifest` pointer, giving the future CultMesh/CultCache reader a narrow slot that does not change scene lowering. The remaining blocker is the live CultMesh/CultNet adapter, a concrete CultMesh/CultCache provider -asset-manifest source, receipt-aware refresh through the live client, and Unity -screenshot or frame-capture PNG production from EveUnity. +asset-manifest source, and Unity screenshot or frame-capture PNG production from +EveUnity. Runtime capture probe coverage is exported as `runtimeCaptureProbeCoverage[]`. Each record joins runtime status, capture owner, lifecycle capture contract, diff --git a/docs/world-state-lowering.md b/docs/world-state-lowering.md index 658d8de..9e6263e 100644 --- a/docs/world-state-lowering.md +++ b/docs/world-state-lowering.md @@ -166,8 +166,11 @@ radii, asset refs, controllability, and command affordances. The generic while `IEveUnitySceneCommandSink` receives command envelopes. `EveUnityPlayableWorldLiveClient` composes that connection with the playable world presenter: every provider snapshot is lowered into a presentation pass, -and Unity input still emits provider-owned command intents rather than mutating -local world truth. +Unity input still emits provider-owned command intents rather than mutating +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. `EveUnityPlayableWorldPresenter` maps the provider-authored entity rows and asset refs into scene operations through `IEveUnityPlayableWorldSceneSink` and `IEveUnityPlayableWorldAssetResolver`, including removal of entities that @@ -188,9 +191,9 @@ locally. This is not yet the final playable client. The remaining cut is a live CultMesh/CultNet provider subscription adapter implementing those source/sink -ports, a concrete CultMesh/CultCache asset-manifest source implementation, -receipt-aware refresh over the same live client, and proof that daemon receipts -drive the next rendered Unity frame rather than renderer-local state. +ports, a concrete CultMesh/CultCache asset-manifest source implementation, and +proof that daemon receipts plus provider snapshots drive the next rendered +Unity frame rather than renderer-local state. ## Non-Goals diff --git a/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs b/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs index 278c4fe..64acffd 100644 --- a/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs +++ b/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs @@ -9,14 +9,21 @@ namespace GameCult.Eve.UnityScene { private readonly EveUnitySceneProviderConnection _connection; private readonly EveUnityPlayableWorldPresenter _presenter; + private readonly IEveUnitySceneCommandReceiptSource? _receiptSource; + private readonly bool _refreshOnTerminalReceipt; private bool _connected; + private bool _receiptConnected; public EveUnityPlayableWorldLiveClient( EveUnitySceneProviderConnection connection, - EveUnityPlayableWorldPresenter presenter) + EveUnityPlayableWorldPresenter presenter, + IEveUnitySceneCommandReceiptSource? receiptSource = null, + bool refreshOnTerminalReceipt = true) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); _presenter = presenter ?? throw new ArgumentNullException(nameof(presenter)); + _receiptSource = receiptSource; + _refreshOnTerminalReceipt = refreshOnTerminalReceipt; } public EveUnitySceneProjection? ActiveProjection => _connection.ActiveProjection; @@ -25,13 +32,18 @@ namespace GameCult.Eve.UnityScene public EveUnityPlayableWorldPresentation? LastPresentation { get; private set; } + public EveUnitySceneCommandReceipt? LastReceipt { get; private set; } + public long ActiveVersion => _connection.ActiveVersion; public string SourcePointer => _connection.SourcePointer; + public event Action? ReceiptAvailable; + public EveUnityPlayableWorldPresentation Connect() { EnsureSubscribed(); + EnsureReceiptSubscribed(); _connection.Connect(); return RequirePresentation(); } @@ -84,6 +96,12 @@ namespace GameCult.Eve.UnityScene _connected = false; } + if (_receiptConnected && _receiptSource != null) + { + _receiptSource.ReceiptAvailable -= OnReceiptAvailable; + _receiptConnected = false; + } + _connection.Disconnect(); } @@ -102,11 +120,29 @@ namespace GameCult.Eve.UnityScene _connected = true; } + private void EnsureReceiptSubscribed() + { + if (_receiptSource == null || _receiptConnected) + return; + + _receiptSource.ReceiptAvailable += OnReceiptAvailable; + _receiptConnected = true; + } + private void OnProjectionUpdated(EveUnitySceneProjection projection) { LastPresentation = _presenter.Apply(projection); } + private void OnReceiptAvailable(EveUnitySceneCommandReceipt receipt) + { + LastReceipt = receipt ?? throw new ArgumentNullException(nameof(receipt)); + ReceiptAvailable?.Invoke(receipt); + + if (_refreshOnTerminalReceipt && receipt.ShouldRefreshProviderSurface) + Refresh(); + } + private EveUnityPlayableWorldPresentation RequirePresentation() { if (LastPresentation == null) @@ -114,4 +150,66 @@ namespace GameCult.Eve.UnityScene return LastPresentation; } } + + public interface IEveUnitySceneCommandReceiptSource + { + event Action ReceiptAvailable; + } + + public sealed class EveUnitySceneCommandReceipt + { + public EveUnitySceneCommandReceipt( + string receiptId, + string command, + string commandId, + string state, + string ownerRepo, + string authority, + string schema = "gamecult.eve.command_receipt.v1", + string providerId = "", + string surfaceId = "", + string message = "", + DateTimeOffset? issuedAtUtc = null) + { + ReceiptId = receiptId ?? ""; + Command = command ?? ""; + CommandId = commandId ?? ""; + State = state ?? ""; + OwnerRepo = ownerRepo ?? ""; + Authority = authority ?? ""; + Schema = string.IsNullOrWhiteSpace(schema) ? "gamecult.eve.command_receipt.v1" : schema; + ProviderId = providerId ?? ""; + SurfaceId = surfaceId ?? ""; + Message = message ?? ""; + IssuedAtUtc = issuedAtUtc; + } + + public string Schema { get; } + + public string ReceiptId { get; } + + public string Command { get; } + + public string CommandId { get; } + + public string State { get; } + + public string OwnerRepo { get; } + + public string Authority { get; } + + public string ProviderId { get; } + + public string SurfaceId { get; } + + public string Message { get; } + + public DateTimeOffset? IssuedAtUtc { get; } + + public bool IsProviderOwned => !string.IsNullOrWhiteSpace(OwnerRepo) && !string.Equals(OwnerRepo, "EveUnity", StringComparison.Ordinal); + + public bool ShouldRefreshProviderSurface => + string.Equals(State, "accepted", StringComparison.OrdinalIgnoreCase) || + string.Equals(State, "reconciled", StringComparison.OrdinalIgnoreCase); + } } diff --git a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index cf04588..6b40f1e 100644 --- a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -307,6 +307,76 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(sceneSink.ConfiguredWorlds.Count, Is.EqualTo(2)); } + [Test] + public void LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement() + { + var source = new FakeProviderSurfaceSource( + "aetheria", + "aetheria.daemon.game", + "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", + new EveUnitySceneProviderSurfaceSnapshot( + PlayableArpgDocument(), + Advertisement("aetheria.daemon.game"), + "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", + 1)); + var commandSink = new FakeCommandSink("cultmesh-command-sink"); + var receiptSource = new FakeCommandReceiptSource(); + var sceneSink = new FakePlayableWorldSceneSink(); + using var connection = new EveUnitySceneProviderConnection(source, commandSink); + using var client = new EveUnityPlayableWorldLiveClient( + connection, + new EveUnityPlayableWorldPresenter(sceneSink, new EveUnityAssetRefResolver()), + receiptSource); + + client.Connect(); + client.SubmitMoveIntent("player-vanguard", 30f, 0f, 30f); + + Assert.That(sceneSink.Upserts[0].entity.PositionX, Is.EqualTo(0f)); + Assert.That(sceneSink.ConfiguredWorlds.Count, Is.EqualTo(1)); + + receiptSource.Publish(new EveUnitySceneCommandReceipt( + "aetheria.daemon.move_intent.pending", + "aetheria.daemon.commands", + "aetheria.daemon.move_intent", + "pending", + "Aetheria", + "AetheriaRuntimeDaemonCommandBoundaryDocument", + "aetheria.eve_command_acceptance_status.v1", + "aetheria", + "aetheria.daemon.game")); + + Assert.That(client.LastReceipt, Is.Not.Null); + Assert.That(client.LastReceipt!.State, Is.EqualTo("pending")); + Assert.That(client.LastReceipt.IsProviderOwned, Is.True); + Assert.That(sceneSink.ConfiguredWorlds.Count, Is.EqualTo(1)); + + source.Stage(new EveUnitySceneProviderSurfaceSnapshot( + PlayableArpgDocument(includeRaider: false, playerPosition: "30,0,30"), + Advertisement("aetheria.daemon.game"), + "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", + 2)); + + receiptSource.Publish(new EveUnitySceneCommandReceipt( + "aetheria.daemon.move_intent.reconciled", + "aetheria.daemon.commands", + "aetheria.daemon.move_intent", + "reconciled", + "Aetheria", + "AetheriaRuntimeDaemonCommandBoundaryDocument", + "aetheria.eve_command_acceptance_status.v1", + "aetheria", + "aetheria.daemon.game")); + + Assert.That(client.LastReceipt.State, Is.EqualTo("reconciled")); + Assert.That(client.ActiveVersion, Is.EqualTo(2)); + Assert.That(client.LastPresentation, Is.Not.Null); + Assert.That(client.LastPresentation!.ActiveEntities, Is.EqualTo(2)); + Assert.That(sceneSink.ConfiguredWorlds.Count, Is.EqualTo(2)); + Assert.That(sceneSink.Upserts[3].entity.EntityId, Is.EqualTo("player-vanguard")); + Assert.That(sceneSink.Upserts[3].entity.PositionX, Is.EqualTo(30f)); + Assert.That(sceneSink.Upserts[3].entity.PositionZ, Is.EqualTo(30f)); + } + [Test] public void AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes() { @@ -808,6 +878,11 @@ namespace GameCult.Eve.UnityScene.Tests public event Action? SnapshotAvailable; + public void Stage(EveUnitySceneProviderSurfaceSnapshot snapshot) + { + CurrentSnapshot = snapshot; + } + public void Publish(EveUnitySceneProviderSurfaceSnapshot snapshot) { CurrentSnapshot = snapshot; @@ -815,6 +890,16 @@ namespace GameCult.Eve.UnityScene.Tests } } + private sealed class FakeCommandReceiptSource : IEveUnitySceneCommandReceiptSource + { + public event Action? ReceiptAvailable; + + public void Publish(EveUnitySceneCommandReceipt receipt) + { + ReceiptAvailable?.Invoke(receipt); + } + } + private sealed class FakeCommandSink : IEveUnitySceneCommandSink { public FakeCommandSink(string sinkKind) diff --git a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json index e79c2d2..6d026a8 100644 --- a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json +++ b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json @@ -19,6 +19,7 @@ "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", + "providerCommandReceipts", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", 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 2a17775..3a0ed6b 100644 --- a/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json +++ b/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json @@ -73,7 +73,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, provider snapshot session, live playable-world client composition, playable-world scene presentation, Unity GameObject scene sink, 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, provider snapshot session, live playable-world client composition, provider command receipt observation, playable-world scene presentation, Unity GameObject scene sink, 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", @@ -122,7 +122,7 @@ "runtimes/incubating/eve-unity-scene/eve-runtime-capability.json" ], "destinationOwner": "EveUnity", - "replacementProof": "Unity scene input emits gamecult.eve.command.v1 intents through a provider-snapshot client session, IEveUnitySceneProviderSurfaceSource, IEveUnitySceneCommandSink, and advertised command boundaries, then renders provider receipts instead of mutating local world truth." + "replacementProof": "Unity scene input emits gamecult.eve.command.v1 intents through a provider-snapshot client session, IEveUnitySceneProviderSurfaceSource, IEveUnitySceneCommandSink, IEveUnitySceneCommandReceiptSource, and advertised command boundaries, then observes provider receipts and refreshes from provider snapshots instead of mutating local world truth." }, { "id": "unity-scene-capture-lifecycle", @@ -162,7 +162,7 @@ "EveUnity owns a provider-agnostic scene graph lowerer that consumes advertised worldInteraction boundaries without importing Aetheria internals.", "EveUnity owns a provider-snapshot Unity scene client session that lowers Aetheria's surface and emits command intents without importing Aetheria project code.", "EveUnity owns provider surface source and command sink ports that a live CultMesh/CultNet adapter can implement without changing scene lowering.", - "EveUnity owns a live playable-world client coordinator that applies provider snapshots through the presenter and emits command intents through the advertised provider command sink without local simulation.", + "EveUnity owns a live playable-world client coordinator that applies provider snapshots through the presenter, observes provider command receipts, and emits command intents through the advertised provider command sink without local simulation.", "EveUnity owns a playable-world presenter that maps provider-authored entity rows and asset refs into Unity scene operations without importing Aetheria entity or prefab classes.", "EveUnity owns a generic Unity GameObject scene sink and asset-provider hook for playable-world rows without importing Aetheria entity or prefab classes.", "EveUnity owns a provider asset manifest resolver that maps provider-authored asset refs and entity kinds to Unity load keys without importing Aetheria asset classes.", @@ -174,7 +174,7 @@ "EveUnity lowers at least one non-Aetheria provider-authored world surface into Unity scene objects.", "EveUnity lowers Aetheria's game world surface into generic playableWorld projection data through provider advertisements without importing Aetheria internals.", "A generic EveUnity scene client subscribes to the live Aetheria daemon surface over CultMesh/CultNet without Aetheria project code.", - "Unity scene input emits gamecult.eve.command.v1 intents and renders provider receipts.", + "Unity scene input emits gamecult.eve.command.v1 intents, observes provider receipts, and refreshes rendered state from provider snapshots.", "Unity scene screenshot or frame-capture PNG artifacts are attached to conformance output." ] } diff --git a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 index a58512a..c51b2d6 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", "livePlayableWorldClient", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource", "embeddedDocuments")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", "sceneGraphProjection", "playableWorldProjection", "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 4fb337f..100c613 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", "livePlayableWorldClient", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveUnity scene provider shell missing supported feature: $feature" } @@ -106,7 +106,7 @@ foreach ($symbol in @("IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneComm } $liveClientSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldLiveClient.cs") -Raw -foreach ($symbol in @("EveUnityPlayableWorldLiveClient", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "Disconnect")) { +foreach ($symbol in @("EveUnityPlayableWorldLiveClient", "IEveUnitySceneCommandReceiptSource", "EveUnitySceneCommandReceipt", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "LastReceipt", "ReceiptAvailable", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "ShouldRefreshProviderSurface", "IsProviderOwned", "Disconnect")) { if (-not $liveClientSource.Contains($symbol)) { throw "EveUnity playable world live client missing symbol: $symbol" } @@ -155,7 +155,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", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "FakeProviderSurfaceSource", "FakeAssetManifestSource", "FakeCommandSink", "FakePlayableWorldSceneSink", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "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", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "FakeProviderSurfaceSource", "FakeCommandReceiptSource", "FakeAssetManifestSource", "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 e7c69bb..ab9f3b2 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -67,6 +67,7 @@ node $consumerScript $consumerExport ` --expect-runtime-feature unity-scene:providerSurfaceSession ` --expect-runtime-feature unity-scene:providerSurfaceSource ` --expect-runtime-feature unity-scene:livePlayableWorldClient ` + --expect-runtime-feature unity-scene:providerCommandReceipts ` --expect-runtime-feature unity-scene:sceneGraphProjection ` --expect-runtime-feature unity-scene:playableWorldProjection ` --expect-runtime-feature unity-scene:playableWorldScenePresentation ` diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 7b41037..7a4beb6 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -1040,7 +1040,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", "livePlayableWorldClient", "providerAssetManifestSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "embeddedDocuments"], + "supportedFeatures": ["providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "embeddedDocuments"], "supportedPlugins": [ { "pluginId": "sai.vn", @@ -1130,7 +1130,7 @@ }, { "path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", - "contains": ["EveUnityPlayableWorldLiveClient", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "Disconnect"] + "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/EveUnityPlayableWorldPresenter.cs", @@ -1158,7 +1158,7 @@ }, { "path": "runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", - "contains": ["LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "FakeProviderSurfaceSource", "FakeAssetManifestSource", "FakeCommandSink", "FakePlayableWorldSceneSink", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "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", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "FakeProviderSurfaceSource", "FakeCommandReceiptSource", "FakeAssetManifestSource", "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",