diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 7722c70..879cf8a 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -633,12 +633,16 @@ Recently cut: `IEveUnityPlayableWorldAssetManifestSource` and `EveUnityPlayableWorldAssetManifestCache` key live manifest updates by the `playableWorld.AssetManifest` pointer, so the future CultMesh/CultCache - reader can update Unity asset bindings without changing scene lowering. The - 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 - prove provider receipts plus daemon snapshots drive rendered Unity frames - rather than renderer-local simulation. + reader can update Unity asset bindings without changing scene lowering. + `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 + live transport. The next blocker is the live generic Unity player loop: + implement the CultMesh/CultNet adapter, implement concrete CultMesh/CultCache + readers for provider snapshots and asset manifest documents, and 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 2862b7f..93b905c 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -507,10 +507,15 @@ entity kinds to Unity resource/prefab keys through data. The runtime now also declares `providerAssetManifestSource`: `IEveUnityPlayableWorldAssetManifestSource` 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, and Unity screenshot or frame-capture PNG production from -EveUnity. +reader a narrow slot that does not change scene lowering. The runtime now also +declares `providerAssetManifestDocumentSource`: +`gamecult.eve.unity_playable_world_asset_manifest.v1`, +`IEveUnityPlayableWorldAssetManifestDocumentSource`, and +`EveUnityPlayableWorldAssetManifestDocumentSource` convert provider-authored +manifest documents into the runtime cache without importing Aetheria asset +classes. The remaining blocker is the live CultMesh/CultNet adapter, concrete +CultMesh/CultCache readers for provider snapshots and asset manifests, 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 9e6263e..93475ba 100644 --- a/docs/world-state-lowering.md +++ b/docs/world-state-lowering.md @@ -181,19 +181,22 @@ is the first Unity-native implementation: it creates or updates generic `IEveUnityGameObjectAssetProvider` hook. `EveUnityPlayableWorldAssetManifest` and `EveUnityManifestGameObjectAssetProvider` add the provider-manifest mapping step: provider asset refs and entity kinds become Unity resource/prefab keys -through data rather than Aetheria code. `IEveUnityPlayableWorldAssetManifestSource` -and `EveUnityPlayableWorldAssetManifestCache` now key live manifest updates by -the `playableWorld.AssetManifest` pointer, so a future 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 +through data rather than Aetheria code. `gamecult.eve.unity_playable_world_asset_manifest.v1`, +`IEveUnityPlayableWorldAssetManifestDocumentSource`, and +`EveUnityPlayableWorldAssetManifestDocumentSource` define the typed document +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 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, and -proof that daemon receipts plus provider snapshots drive the next rendered -Unity frame rather than renderer-local state. +ports, concrete CultMesh/CultCache readers for provider snapshots and asset +manifest documents, 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/EveUnityPlayableWorldAssetManifest.cs b/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs index be21b5c..d6dd81a 100644 --- a/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs +++ b/runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs @@ -35,6 +35,27 @@ namespace GameCult.Eve.UnityScene public IReadOnlyList Entries { get; } + public static EveUnityPlayableWorldAssetManifest FromDocument(EveUnityPlayableWorldAssetManifestDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + var entries = new List(); + foreach (var entry in document.Entries) + { + if (entry == null) + continue; + + entries.Add(new EveUnityPlayableWorldAssetManifestEntry( + entry.AssetRef, + entry.EntityKind, + entry.ResourcesPath, + entry.PrefabKey, + entry.PresentationKind)); + } + + return new EveUnityPlayableWorldAssetManifest(document.ManifestRef, entries); + } + public EveUnityPlayableWorldAssetManifestEntry? Find(EveUnityPlayableWorldAssetBinding asset) { if (asset == null) throw new ArgumentNullException(nameof(asset)); @@ -130,6 +151,115 @@ namespace GameCult.Eve.UnityScene } } + public interface IEveUnityPlayableWorldAssetManifestDocumentSource + { + string ManifestRef { get; } + + EveUnityPlayableWorldAssetManifestDocument CurrentDocument { get; } + + event Action DocumentAvailable; + } + + public sealed class EveUnityPlayableWorldAssetManifestDocumentSource : IEveUnityPlayableWorldAssetManifestSource, IDisposable + { + private readonly IEveUnityPlayableWorldAssetManifestDocumentSource _documentSource; + private bool _connected; + + public EveUnityPlayableWorldAssetManifestDocumentSource( + IEveUnityPlayableWorldAssetManifestDocumentSource documentSource) + { + _documentSource = documentSource ?? throw new ArgumentNullException(nameof(documentSource)); + CurrentManifest = EveUnityPlayableWorldAssetManifest.FromDocument(_documentSource.CurrentDocument); + } + + public string ManifestRef => CurrentManifest.ManifestRef; + + public EveUnityPlayableWorldAssetManifest CurrentManifest { get; private set; } + + public event Action? ManifestAvailable; + + public void Connect() + { + if (_connected) + return; + + _documentSource.DocumentAvailable += OnDocumentAvailable; + _connected = true; + } + + public void Disconnect() + { + if (!_connected) + return; + + _documentSource.DocumentAvailable -= OnDocumentAvailable; + _connected = false; + } + + public void Dispose() + { + Disconnect(); + } + + private void OnDocumentAvailable(EveUnityPlayableWorldAssetManifestDocument document) + { + CurrentManifest = EveUnityPlayableWorldAssetManifest.FromDocument(document); + ManifestAvailable?.Invoke(CurrentManifest); + } + } + + public sealed class EveUnityPlayableWorldAssetManifestDocument + { + public const string SchemaId = "gamecult.eve.unity_playable_world_asset_manifest.v1"; + + public EveUnityPlayableWorldAssetManifestDocument( + string manifestRef, + IReadOnlyList entries, + string providerId = "", + string schema = SchemaId) + { + ManifestRef = manifestRef ?? ""; + Entries = entries ?? Array.Empty(); + ProviderId = providerId ?? ""; + Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema; + } + + public string Schema { get; } + + public string ManifestRef { get; } + + public string ProviderId { get; } + + public IReadOnlyList Entries { get; } + } + + public sealed class EveUnityPlayableWorldAssetManifestDocumentEntry + { + public EveUnityPlayableWorldAssetManifestDocumentEntry( + string assetRef, + string entityKind, + string resourcesPath, + string prefabKey, + string presentationKind = "provider-asset-ref") + { + AssetRef = assetRef ?? ""; + EntityKind = entityKind ?? ""; + ResourcesPath = resourcesPath ?? ""; + PrefabKey = prefabKey ?? ""; + PresentationKind = presentationKind ?? ""; + } + + public string AssetRef { get; } + + public string EntityKind { get; } + + public string ResourcesPath { get; } + + public string PrefabKey { get; } + + public string PresentationKind { get; } + } + public sealed class EveUnityPlayableWorldAssetManifestEntry { public EveUnityPlayableWorldAssetManifestEntry( diff --git a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index 6b40f1e..781a740 100644 --- a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -484,6 +484,62 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(afterDisconnectPlayer!.ResourcesPath, Is.EqualTo("Aetheria/Entities/VanguardMk2")); } + [Test] + public void AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering() + { + var lowerer = new EveUnitySceneSurfaceLowerer(); + var projection = lowerer.Lower(PlayableArpgDocument(), Advertisement("aetheria.daemon.game")); + Assert.That(projection.PlayableWorld, Is.Not.Null); + + var documentSource = 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") + }, + "aetheria")); + using var manifestSource = new EveUnityPlayableWorldAssetManifestDocumentSource(documentSource); + var cache = new EveUnityPlayableWorldAssetManifestCache(); + manifestSource.Connect(); + cache.Connect(manifestSource); + + var manifest = cache.GetForWorld(projection.PlayableWorld!); + Assert.That(manifest, Is.Not.Null); + Assert.That(manifest!.ManifestRef, Is.EqualTo("cultmesh://aetheria/assets/manifest")); + var player = manifest.Find(new EveUnityPlayableWorldAssetBinding( + "cultmesh://aetheria/assets/map/entity/player", + "player", + "provider-asset-ref")); + Assert.That(player, Is.Not.Null); + Assert.That(player!.ResourcesPath, Is.EqualTo("Aetheria/Entities/Vanguard")); + Assert.That(player.PrefabKey, Is.EqualTo("aetheria.vanguard")); + + documentSource.Publish(new EveUnityPlayableWorldAssetManifestDocument( + "cultmesh://aetheria/assets/manifest", + new[] + { + new EveUnityPlayableWorldAssetManifestDocumentEntry( + "cultmesh://aetheria/assets/map/entity/player", + "player", + "Resources/Aetheria/Entities/VanguardAuthority.prefab", + "aetheria.vanguard.authority") + }, + "aetheria")); + + var updated = cache.GetForWorld(projection.PlayableWorld); + var updatedPlayer = updated!.Find(new EveUnityPlayableWorldAssetBinding( + "cultmesh://aetheria/assets/map/entity/player", + "player", + "provider-asset-ref")); + Assert.That(updatedPlayer, Is.Not.Null); + Assert.That(updatedPlayer!.ResourcesPath, Is.EqualTo("Aetheria/Entities/VanguardAuthority")); + Assert.That(updatedPlayer.PrefabKey, Is.EqualTo("aetheria.vanguard.authority")); + } + [Test] public void SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState() { @@ -961,5 +1017,25 @@ namespace GameCult.Eve.UnityScene.Tests ManifestAvailable?.Invoke(manifest); } } + + private sealed class FakeAssetManifestDocumentSource : IEveUnityPlayableWorldAssetManifestDocumentSource + { + public FakeAssetManifestDocumentSource(EveUnityPlayableWorldAssetManifestDocument currentDocument) + { + CurrentDocument = currentDocument; + } + + public string ManifestRef => CurrentDocument.ManifestRef; + + public EveUnityPlayableWorldAssetManifestDocument CurrentDocument { get; private set; } + + public event Action? DocumentAvailable; + + public void Publish(EveUnityPlayableWorldAssetManifestDocument document) + { + CurrentDocument = document; + DocumentAvailable?.Invoke(document); + } + } } } diff --git a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json index 6d026a8..78f82cd 100644 --- a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json +++ b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json @@ -20,6 +20,7 @@ "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", + "providerAssetManifestDocumentSource", "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 3a0ed6b..604e212 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, 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." + "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, 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", @@ -165,6 +165,7 @@ "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 gamecult.eve.unity_playable_world_asset_manifest.v1 document loading into its asset manifest cache without importing Aetheria asset 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.", "EveUnity owns a provider asset manifest source/cache boundary keyed by playableWorld assetManifest pointers so live CultMesh/CultCache loading can update Unity asset bindings without changing scene lowering.", "EveUnity owns the provider-agnostic playable-world scene lowerer outside Eve incubation.", diff --git a/schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json b/schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json new file mode 100644 index 0000000..011407c --- /dev/null +++ b/schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "gamecult.eve.unity_playable_world_asset_manifest.v1", + "title": "Eve Unity Playable World Asset Manifest", + "type": "object", + "required": ["schema", "manifestRef", "entries"], + "properties": { + "schema": { + "const": "gamecult.eve.unity_playable_world_asset_manifest.v1" + }, + "manifestRef": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string" + }, + "entries": { + "type": "array", + "items": { + "type": "object", + "required": ["resourcesPath"], + "properties": { + "assetRef": { + "type": "string" + }, + "entityKind": { + "type": "string" + }, + "resourcesPath": { + "type": "string", + "minLength": 1 + }, + "prefabKey": { + "type": "string" + }, + "presentationKind": { + "type": "string" + } + }, + "anyOf": [ + { + "required": ["assetRef"] + }, + { + "required": ["entityKind"] + } + ], + "additionalProperties": true + } + } + }, + "additionalProperties": true +} diff --git a/scripts/run-aetheria-conformance-consumer-smoke.ps1 b/scripts/run-aetheria-conformance-consumer-smoke.ps1 index 902349f..ab617da 100644 --- a/scripts/run-aetheria-conformance-consumer-smoke.ps1 +++ b/scripts/run-aetheria-conformance-consumer-smoke.ps1 @@ -39,6 +39,7 @@ try { node $consumerScript $consumerExport ` --expect-pack provider ` --expect-schema gamecult.eve.provider_handoff.v1 ` + --expect-schema gamecult.eve.unity_playable_world_asset_manifest.v1 ` --expect-fixture aetheria-world ` --expect-provider aetheria ` --expect-scenario aetheria-world-command-replay ` @@ -110,6 +111,7 @@ try { --expect-provider-handoff-move aetheria:provider-advertisement:current:exists:aetheria.provider-advertisement.json ` --expect-provider-handoff-move aetheria:interactive-world-surface:current:exists:aetheria-world-surface.json ` --expect-provider-handoff-move aetheria:interactive-world-surface:current:exists:aetheria-world-surface.conformance.json ` + --expect-provider-handoff-move aetheria:unity-playable-world-asset-manifest:current:exists:aetheria.unity-playable-world-asset-manifest.json ` --expect-provider-handoff-move aetheria:provider-scenario:current:exists:aetheria-world-scenario.json ` --expect-provider-handoff-move aetheria:provider-scenario:current:exists:run-aetheria-conformance-consumer-smoke.ps1 if ($LASTEXITCODE -ne 0) { diff --git a/scripts/run-eveconformance-handoff-smoke.ps1 b/scripts/run-eveconformance-handoff-smoke.ps1 index 769cc57..7d9c863 100644 --- a/scripts/run-eveconformance-handoff-smoke.ps1 +++ b/scripts/run-eveconformance-handoff-smoke.ps1 @@ -92,6 +92,7 @@ foreach ($contract in @( "gamecult.eve.provider_handoff.v1", "gamecult.eve.electron_shell_projection.v1", "gamecult.eve.unity_scene_projection.v1", + "gamecult.eve.unity_playable_world_asset_manifest.v1", "gamecult.eve.tui_grid.v1", "gamecult.eve.web_layout_probe.v1", "gamecult.eve.runtime_capability.v1", @@ -130,6 +131,7 @@ if (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory "index.json")) { "gamecult.eve.provider_handoff.v1", "gamecult.eve.electron_shell_projection.v1", "gamecult.eve.unity_scene_projection.v1", + "gamecult.eve.unity_playable_world_asset_manifest.v1", "gamecult.eve.tui_grid.v1", "gamecult.eve.web_layout_probe.v1", "gamecult.eve.runtime_capability.v1", diff --git a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 index c51b2d6..70f7f76 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", "providerCommandReceipts", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource", "embeddedDocuments")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "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 100c613..1e288e9 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", "providerCommandReceipts", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveUnity scene provider shell missing supported feature: $feature" } @@ -127,7 +127,7 @@ foreach ($symbol in @("EveUnityGameObjectPlayableWorldSceneSink", "IEveUnityGame } $assetManifestSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldAssetManifest.cs") -Raw -foreach ($symbol in @("EveUnityPlayableWorldAssetManifest", "IEveUnityPlayableWorldAssetManifestSource", "EveUnityPlayableWorldAssetManifestCache", "EveUnityPlayableWorldAssetManifestEntry", "EveUnityManifestGameObjectAssetProvider", "ManifestAvailable", "GetForWorld", "CreateGameObjectAssetProvider", "ManifestRef", "ResourcesPath", "PrefabKey", "NormalizeResourcesPath", "ResolvePrefab", "Resources.Load")) { +foreach ($symbol in @("EveUnityPlayableWorldAssetManifest", "IEveUnityPlayableWorldAssetManifestSource", "IEveUnityPlayableWorldAssetManifestDocumentSource", "EveUnityPlayableWorldAssetManifestDocumentSource", "EveUnityPlayableWorldAssetManifestDocument", "EveUnityPlayableWorldAssetManifestDocumentEntry", "FromDocument", "DocumentAvailable", "gamecult.eve.unity_playable_world_asset_manifest.v1", "EveUnityPlayableWorldAssetManifestCache", "EveUnityPlayableWorldAssetManifestEntry", "EveUnityManifestGameObjectAssetProvider", "ManifestAvailable", "GetForWorld", "CreateGameObjectAssetProvider", "ManifestRef", "ResourcesPath", "PrefabKey", "NormalizeResourcesPath", "ResolvePrefab", "Resources.Load")) { if (-not $assetManifestSource.Contains($symbol)) { throw "EveUnity playable world asset manifest 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", "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")) { +foreach ($symbol in @("LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering", "FakeProviderSurfaceSource", "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 ab9f3b2..b7e1620 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -48,6 +48,7 @@ node $consumerScript $consumerExport ` --expect-schema gamecult.eve.electron_shell_projection.v1 ` --expect-schema gamecult.eve.unity_uitoolkit_projection.v1 ` --expect-schema gamecult.eve.unity_scene_projection.v1 ` + --expect-schema gamecult.eve.unity_playable_world_asset_manifest.v1 ` --expect-schema gamecult.eve.tui_grid.v1 ` --expect-schema gamecult.eve.web_layout_probe.v1 ` --expect-runtime-status web:active ` @@ -68,6 +69,7 @@ node $consumerScript $consumerExport ` --expect-runtime-feature unity-scene:providerSurfaceSource ` --expect-runtime-feature unity-scene:livePlayableWorldClient ` --expect-runtime-feature unity-scene:providerCommandReceipts ` + --expect-runtime-feature unity-scene:providerAssetManifestDocumentSource ` --expect-runtime-feature unity-scene:sceneGraphProjection ` --expect-runtime-feature unity-scene:playableWorldProjection ` --expect-runtime-feature unity-scene:playableWorldScenePresentation ` diff --git a/tools/conformance/eveconformance-handoff.json b/tools/conformance/eveconformance-handoff.json index 5583128..f979f44 100644 --- a/tools/conformance/eveconformance-handoff.json +++ b/tools/conformance/eveconformance-handoff.json @@ -28,6 +28,7 @@ "schemas/gamecult.eve.provider_handoff.v1.schema.json", "schemas/gamecult.eve.electron_shell_projection.v1.schema.json", "schemas/gamecult.eve.unity_scene_projection.v1.schema.json", + "schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json", "schemas/gamecult.eve.tui_grid.v1.schema.json", "schemas/gamecult.eve.web_layout_probe.v1.schema.json", "schemas/gamecult.eve.runtime_capability.v1.schema.json", @@ -94,6 +95,7 @@ "gamecult.eve.provider_handoff.v1", "gamecult.eve.electron_shell_projection.v1", "gamecult.eve.unity_scene_projection.v1", + "gamecult.eve.unity_playable_world_asset_manifest.v1", "gamecult.eve.tui_grid.v1", "gamecult.eve.web_layout_probe.v1", "gamecult.eve.runtime_capability.v1", diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 7a4beb6..6f6696e 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -33,6 +33,7 @@ "gamecult.eve.electron_shell_projection.v1": "schemas/gamecult.eve.electron_shell_projection.v1.schema.json", "gamecult.eve.unity_uitoolkit_projection.v1": "schemas/gamecult.eve.unity_uitoolkit_projection.v1.schema.json", "gamecult.eve.unity_scene_projection.v1": "schemas/gamecult.eve.unity_scene_projection.v1.schema.json", + "gamecult.eve.unity_playable_world_asset_manifest.v1": "schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json", "gamecult.eve.tui_grid.v1": "schemas/gamecult.eve.tui_grid.v1.schema.json" }, "conformancePacks": [ @@ -387,7 +388,7 @@ "scenarioPath": "web/fixtures/aetheria-world-scenario.json", "handoffPath": "web/fixtures/aetheria-provider-handoff.json", "requiredFixtures": ["aetheria-world"], - "expectedSchemas": ["gamecult.eve.surface.v1", "gamecult.eve.command.v1", "aetheria.runtime.daemon.frame.v1"], + "expectedSchemas": ["gamecult.eve.surface.v1", "gamecult.eve.command.v1", "gamecult.eve.unity_playable_world_asset_manifest.v1", "aetheria.runtime.daemon.frame.v1"], "expectedSurfaces": ["aetheria.daemon.game", "aetheria.daemon.editor"], "expectedSurfaceKinds": { "aetheria.daemon.game": "interactive-world", @@ -1040,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", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "embeddedDocuments"], + "supportedFeatures": ["providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "providerAssetManifestSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "embeddedDocuments"], "supportedPlugins": [ { "pluginId": "sai.vn", @@ -1142,7 +1143,7 @@ }, { "path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", - "contains": ["EveUnityPlayableWorldAssetManifest", "IEveUnityPlayableWorldAssetManifestSource", "EveUnityPlayableWorldAssetManifestCache", "EveUnityPlayableWorldAssetManifestEntry", "EveUnityManifestGameObjectAssetProvider", "ManifestAvailable", "GetForWorld", "CreateGameObjectAssetProvider", "ManifestRef", "ResourcesPath", "PrefabKey", "NormalizeResourcesPath", "ResolvePrefab", "Resources.Load"] + "contains": ["EveUnityPlayableWorldAssetManifest", "IEveUnityPlayableWorldAssetManifestSource", "IEveUnityPlayableWorldAssetManifestDocumentSource", "EveUnityPlayableWorldAssetManifestDocumentSource", "EveUnityPlayableWorldAssetManifestDocument", "EveUnityPlayableWorldAssetManifestDocumentEntry", "FromDocument", "DocumentAvailable", "gamecult.eve.unity_playable_world_asset_manifest.v1", "EveUnityPlayableWorldAssetManifestCache", "EveUnityPlayableWorldAssetManifestEntry", "EveUnityManifestGameObjectAssetProvider", "ManifestAvailable", "GetForWorld", "CreateGameObjectAssetProvider", "ManifestRef", "ResourcesPath", "PrefabKey", "NormalizeResourcesPath", "ResolvePrefab", "Resources.Load"] }, { "path": "runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", @@ -1158,7 +1159,7 @@ }, { "path": "runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", - "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"] + "contains": ["LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering", "FakeProviderSurfaceSource", "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", @@ -1274,7 +1275,7 @@ }, { "path": "web/provider-advertisements.test.mjs", - "contains": ["preserves Sai required and optional nested plugin requirements from advertisement", "merges local fixture transport with advertised Aetheria world interaction", "merges generic world fixture transport with advertised world interaction", "requiresPlugins", "worldInteraction", "optional-nested", "aetheria.daemon.commands", "eve.world-smoke.commands"] + "contains": ["preserves Sai required and optional nested plugin requirements from advertisement", "merges local fixture transport with advertised Aetheria world interaction", "loads Aetheria Unity playable world asset manifest as provider-authored data", "gamecult.eve.unity_playable_world_asset_manifest.v1", "merges generic world fixture transport with advertised world interaction", "requiresPlugins", "worldInteraction", "optional-nested", "aetheria.daemon.commands", "eve.world-smoke.commands"] }, { "path": "web/local-provider-catalog.json", diff --git a/web/fixtures/aetheria-provider-handoff.json b/web/fixtures/aetheria-provider-handoff.json index f1736a8..be84dac 100644 --- a/web/fixtures/aetheria-provider-handoff.json +++ b/web/fixtures/aetheria-provider-handoff.json @@ -27,6 +27,14 @@ ], "replacementProof": "Aetheria owns RTS/ARPG world surface fixtures, generated level state, and assets." }, + { + "id": "unity-playable-world-asset-manifest", + "destinationOwner": "Aetheria", + "currentPaths": [ + "web/fixtures/aetheria.unity-playable-world-asset-manifest.json" + ], + "replacementProof": "Aetheria owns provider-authored Unity playable-world asset manifest documents; EveUnity only consumes the typed manifest contract." + }, { "id": "provider-scenario", "destinationOwner": "Aetheria", @@ -40,6 +48,7 @@ "contractInputs": [ "gamecult.eve.surface.v1", "gamecult.eve.command.v1", + "gamecult.eve.unity_playable_world_asset_manifest.v1", "gamecult.eve.provider_advertisement.v1", "gamecult.eve.provider_scenario.v1", "gamecult.eve.conformance_export.v1" diff --git a/web/fixtures/aetheria.provider-advertisement.json b/web/fixtures/aetheria.provider-advertisement.json index d9bbd84..5c39370 100644 --- a/web/fixtures/aetheria.provider-advertisement.json +++ b/web/fixtures/aetheria.provider-advertisement.json @@ -38,7 +38,8 @@ "aetheria.runtime.daemon.editor_surface.v1", "aetheria.runtime.daemon.command.v1", "gamecult.eve.surface.v1", - "gamecult.eve.command.v1" + "gamecult.eve.command.v1", + "gamecult.eve.unity_playable_world_asset_manifest.v1" ], "witnesses": [ { @@ -65,6 +66,11 @@ "kind": "cultmesh-record", "ref": "AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary", "summary": "Aetheria daemon typed command boundary record." + }, + { + "kind": "cultmesh-record", + "ref": "cultmesh://aetheria/assets/manifest", + "summary": "Provider-authored Unity playable-world asset manifest consumed by EveUnity without importing Aetheria prefab classes." } ], "surfaces": [ diff --git a/web/fixtures/aetheria.unity-playable-world-asset-manifest.json b/web/fixtures/aetheria.unity-playable-world-asset-manifest.json new file mode 100644 index 0000000..7a0c985 --- /dev/null +++ b/web/fixtures/aetheria.unity-playable-world-asset-manifest.json @@ -0,0 +1,21 @@ +{ + "schema": "gamecult.eve.unity_playable_world_asset_manifest.v1", + "providerId": "aetheria", + "manifestRef": "cultmesh://aetheria/assets/manifest", + "entries": [ + { + "assetRef": "cultmesh://aetheria/assets/map/entity/player", + "entityKind": "player", + "resourcesPath": "resources://Aetheria/Entities/Vanguard.prefab", + "prefabKey": "aetheria.vanguard", + "presentationKind": "provider-asset-ref" + }, + { + "assetRef": "cultmesh://aetheria/assets/map/entity/raider", + "entityKind": "enemy", + "resourcesPath": "resources://Aetheria/Entities/Raider.prefab", + "prefabKey": "aetheria.raider", + "presentationKind": "provider-asset-ref" + } + ] +} diff --git a/web/provider-advertisements.test.mjs b/web/provider-advertisements.test.mjs index 22fee4d..f72a7aa 100644 --- a/web/provider-advertisements.test.mjs +++ b/web/provider-advertisements.test.mjs @@ -5,6 +5,7 @@ import { findProviderCatalogEntry, mergeProviderAdvertisement } from "./provider const catalog = JSON.parse(readFileSync(new URL("./local-provider-catalog.json", import.meta.url), "utf8")); const aetheriaAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8")); +const aetheriaUnityAssetManifest = JSON.parse(readFileSync(new URL("./fixtures/aetheria.unity-playable-world-asset-manifest.json", import.meta.url), "utf8")); const worldSmokeAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/eve-world-smoke.provider-advertisement.json", import.meta.url), "utf8")); const saiAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/sai-vn.provider-advertisement.json", import.meta.url), "utf8")); @@ -20,6 +21,18 @@ test("merges local fixture transport with advertised Aetheria world interaction" assert.equal(surface.worldInteraction.commandBoundary, "aetheria.daemon.commands"); assert.equal(surface.worldInteraction.receiptSchema, "aetheria.eve_command_acceptance_status.v1"); assert.equal(provider.localAdvertisement.providerId, "aetheria"); + assert.ok(aetheriaAdvertisement.schemas.includes("gamecult.eve.unity_playable_world_asset_manifest.v1")); +}); + +test("loads Aetheria Unity playable world asset manifest as provider-authored data", () => { + assert.equal(aetheriaUnityAssetManifest.schema, "gamecult.eve.unity_playable_world_asset_manifest.v1"); + assert.equal(aetheriaUnityAssetManifest.providerId, "aetheria"); + assert.equal(aetheriaUnityAssetManifest.manifestRef, "cultmesh://aetheria/assets/manifest"); + + const byKind = new Map(aetheriaUnityAssetManifest.entries.map(entry => [entry.entityKind, entry])); + assert.equal(byKind.get("player").assetRef, "cultmesh://aetheria/assets/map/entity/player"); + assert.equal(byKind.get("player").resourcesPath, "resources://Aetheria/Entities/Vanguard.prefab"); + assert.equal(byKind.get("enemy").prefabKey, "aetheria.raider"); }); test("merges generic world fixture transport with advertised world interaction", () => {