Add Unity asset manifest document contract

This commit is contained in:
Meta Vens 2026-07-09 18:55:17 +01:00
commit 35416a6711
19 changed files with 362 additions and 30 deletions

View file

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

View file

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

View file

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

View file

@ -35,6 +35,27 @@ namespace GameCult.Eve.UnityScene
public IReadOnlyList<EveUnityPlayableWorldAssetManifestEntry> Entries { get; }
public static EveUnityPlayableWorldAssetManifest FromDocument(EveUnityPlayableWorldAssetManifestDocument document)
{
if (document == null) throw new ArgumentNullException(nameof(document));
var entries = new List<EveUnityPlayableWorldAssetManifestEntry>();
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<EveUnityPlayableWorldAssetManifestDocument> 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<EveUnityPlayableWorldAssetManifest>? 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<EveUnityPlayableWorldAssetManifestDocumentEntry> entries,
string providerId = "",
string schema = SchemaId)
{
ManifestRef = manifestRef ?? "";
Entries = entries ?? Array.Empty<EveUnityPlayableWorldAssetManifestDocumentEntry>();
ProviderId = providerId ?? "";
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
}
public string Schema { get; }
public string ManifestRef { get; }
public string ProviderId { get; }
public IReadOnlyList<EveUnityPlayableWorldAssetManifestDocumentEntry> 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(

View file

@ -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<EveUnityPlayableWorldAssetManifestDocument>? DocumentAvailable;
public void Publish(EveUnityPlayableWorldAssetManifestDocument document)
{
CurrentDocument = document;
DocumentAvailable?.Invoke(document);
}
}
}
}

View file

@ -20,6 +20,7 @@
"providerSurfaceSource",
"livePlayableWorldClient",
"providerCommandReceipts",
"providerAssetManifestDocumentSource",
"sceneGraphProjection",
"playableWorldProjection",
"playableWorldScenePresentation",

View file

@ -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.",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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": [

View file

@ -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"
}
]
}

View file

@ -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", () => {