Add Unity playable world presentation boundary

This commit is contained in:
Meta Vens 2026-07-09 18:14:33 +01:00
commit eef8a5b7aa
11 changed files with 346 additions and 82 deletions

View file

@ -613,11 +613,15 @@ Recently cut:
`EveUnitySceneClientSession` now consumes provider surface snapshots and emits
movement/focus/target/action intents through the advertised command boundary,
while `EveUnitySceneProviderConnection` defines the generic surface source
and command sink ports that a CultMesh/CultNet adapter must implement. 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, instantiate the projected world in Unity, and wait for provider
receipts/next frames rather than renderer-local simulation.
and command sink ports that a CultMesh/CultNet adapter must implement.
`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
classes. 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, provide Unity `GameObject`/prefab-backed scene sink
and asset resolver implementations, and wait for provider receipts/next 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

@ -484,9 +484,12 @@ active surface and emits `gamecult.eve.command.v1` intents without Aetheria
types. It also declares `providerSurfaceSource` through
`EveUnitySceneProviderConnection`, `IEveUnitySceneProviderSurfaceSource`, and
`IEveUnitySceneCommandSink`, so the live CultMesh/CultNet adapter has a narrow
runtime-owned port to implement. The remaining blocker is that live adapter,
Unity player instantiation, and Unity screenshot or frame-capture PNG production
from EveUnity.
runtime-owned port to implement. 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 remaining blocker is
that live adapter, Unity `GameObject`/prefab-backed scene sink and asset
resolver, 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

@ -163,14 +163,19 @@ radii, asset refs, controllability, and command affordances. The generic
`gamecult.eve.command.v1` intents through the advertised command boundary.
`EveUnitySceneProviderConnection` adds the live-source port shape:
`IEveUnitySceneProviderSurfaceSource` supplies current and updated snapshots,
while `IEveUnitySceneCommandSink` receives command envelopes. Aetheria-specific
names appear only as provider-authored data and command ids; the Unity client
code does not import Aetheria runtime types or apply movement locally.
while `IEveUnitySceneCommandSink` receives command envelopes.
`EveUnityPlayableWorldPresenter` maps the provider-authored entity rows and
asset refs into scene operations through `IEveUnityPlayableWorldSceneSink` and
`IEveUnityPlayableWorldAssetResolver`, including removal of entities that
disappear from later provider snapshots. 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, Unity player instantiation of the projected world, and proof that daemon
receipts drive the next frame rather than renderer-local state.
ports, Unity `GameObject`/prefab-backed implementations of the scene sink and
asset resolver, and proof that daemon receipts drive the next frame rather than
renderer-local state.
## Non-Goals

View file

@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
#nullable enable
namespace GameCult.Eve.UnityScene
{
public interface IEveUnityPlayableWorldSceneSink
{
void ConfigureWorld(EveUnityPlayableWorldProjection world);
void UpsertEntity(EveUnityPlayableWorldEntity entity, EveUnityPlayableWorldAssetBinding asset);
void RemoveEntity(string entityId);
}
public interface IEveUnityPlayableWorldAssetResolver
{
EveUnityPlayableWorldAssetBinding Resolve(EveUnityPlayableWorldEntity entity);
}
public sealed class EveUnityPlayableWorldPresenter
{
private readonly IEveUnityPlayableWorldSceneSink _sceneSink;
private readonly IEveUnityPlayableWorldAssetResolver _assetResolver;
private readonly HashSet<string> _knownEntityIds = new HashSet<string>(StringComparer.Ordinal);
public EveUnityPlayableWorldPresenter(
IEveUnityPlayableWorldSceneSink sceneSink,
IEveUnityPlayableWorldAssetResolver assetResolver)
{
_sceneSink = sceneSink ?? throw new ArgumentNullException(nameof(sceneSink));
_assetResolver = assetResolver ?? throw new ArgumentNullException(nameof(assetResolver));
}
public EveUnityPlayableWorldPresentation Apply(EveUnitySceneProjection projection)
{
if (projection == null) throw new ArgumentNullException(nameof(projection));
if (projection.PlayableWorld == null)
throw new InvalidOperationException("Unity playable world presentation requires a playableWorld projection.");
return Apply(projection.PlayableWorld);
}
public EveUnityPlayableWorldPresentation Apply(EveUnityPlayableWorldProjection world)
{
if (world == null) throw new ArgumentNullException(nameof(world));
var nextEntityIds = new HashSet<string>(StringComparer.Ordinal);
_sceneSink.ConfigureWorld(world);
var upserted = 0;
foreach (var entity in world.Entities)
{
if (string.IsNullOrWhiteSpace(entity.EntityId))
continue;
nextEntityIds.Add(entity.EntityId);
_sceneSink.UpsertEntity(entity, _assetResolver.Resolve(entity));
upserted++;
}
var removed = 0;
var previousEntityIds = new List<string>(_knownEntityIds);
foreach (var entityId in previousEntityIds)
{
if (nextEntityIds.Contains(entityId))
continue;
_sceneSink.RemoveEntity(entityId);
_knownEntityIds.Remove(entityId);
removed++;
}
foreach (var entityId in nextEntityIds)
_knownEntityIds.Add(entityId);
return new EveUnityPlayableWorldPresentation(
world.WorldRootId,
world.PlayerEntityId,
world.InputProfile,
world.CameraRig,
upserted,
removed,
_knownEntityIds.Count);
}
}
public sealed class EveUnityPlayableWorldAssetBinding
{
public EveUnityPlayableWorldAssetBinding(
string assetRef,
string entityKind,
string presentationKind)
{
AssetRef = assetRef ?? "";
EntityKind = entityKind ?? "";
PresentationKind = presentationKind ?? "";
}
public string AssetRef { get; }
public string EntityKind { get; }
public string PresentationKind { get; }
}
public sealed class EveUnityPlayableWorldPresentation
{
public EveUnityPlayableWorldPresentation(
string worldRootId,
string playerEntityId,
string inputProfile,
string cameraRig,
int upsertedEntities,
int removedEntities,
int activeEntities)
{
WorldRootId = worldRootId ?? "";
PlayerEntityId = playerEntityId ?? "";
InputProfile = inputProfile ?? "";
CameraRig = cameraRig ?? "";
UpsertedEntities = upsertedEntities;
RemovedEntities = removedEntities;
ActiveEntities = activeEntities;
}
public string WorldRootId { get; }
public string PlayerEntityId { get; }
public string InputProfile { get; }
public string CameraRig { get; }
public int UpsertedEntities { get; }
public int RemovedEntities { get; }
public int ActiveEntities { get; }
}
public sealed class EveUnityAssetRefResolver : IEveUnityPlayableWorldAssetResolver
{
public EveUnityPlayableWorldAssetBinding Resolve(EveUnityPlayableWorldEntity entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
return new EveUnityPlayableWorldAssetBinding(
entity.AssetRef,
entity.EntityKind,
string.IsNullOrWhiteSpace(entity.AssetRef) ? "unity-generated-placeholder" : "provider-asset-ref");
}
}
}

View file

@ -198,6 +198,46 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(projectionUpdates, Is.EqualTo(2));
}
[Test]
public void PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities()
{
var lowerer = new EveUnitySceneSurfaceLowerer();
var sink = new FakePlayableWorldSceneSink();
var presenter = new EveUnityPlayableWorldPresenter(sink, new EveUnityAssetRefResolver());
var firstProjection = lowerer.Lower(PlayableArpgDocument(), Advertisement("aetheria.daemon.game"));
var firstPresentation = presenter.Apply(firstProjection);
Assert.That(firstPresentation.WorldRootId, Is.EqualTo("aetheria.daemon.game.playable"));
Assert.That(firstPresentation.PlayerEntityId, Is.EqualTo("player-vanguard"));
Assert.That(firstPresentation.InputProfile, Is.EqualTo("arpg-third-person"));
Assert.That(firstPresentation.CameraRig, Is.EqualTo("third-person-orbit"));
Assert.That(firstPresentation.UpsertedEntities, Is.EqualTo(3));
Assert.That(firstPresentation.RemovedEntities, Is.EqualTo(0));
Assert.That(firstPresentation.ActiveEntities, Is.EqualTo(3));
Assert.That(sink.ConfiguredWorlds.Count, Is.EqualTo(1));
Assert.That(sink.Upserts.Count, Is.EqualTo(3));
Assert.That(sink.Upserts[0].entity.EntityId, Is.EqualTo("player-vanguard"));
Assert.That(sink.Upserts[0].entity.PositionX, Is.EqualTo(0f));
Assert.That(sink.Upserts[0].asset.AssetRef, Is.EqualTo("cultmesh://aetheria/assets/map/entity/player"));
Assert.That(sink.Upserts[0].asset.PresentationKind, Is.EqualTo("provider-asset-ref"));
var secondProjection = lowerer.Lower(
PlayableArpgDocument(includeRaider: false, playerPosition: "5,0,2"),
Advertisement("aetheria.daemon.game"));
var secondPresentation = presenter.Apply(secondProjection);
Assert.That(secondPresentation.UpsertedEntities, Is.EqualTo(2));
Assert.That(secondPresentation.RemovedEntities, Is.EqualTo(1));
Assert.That(secondPresentation.ActiveEntities, Is.EqualTo(2));
Assert.That(sink.ConfiguredWorlds.Count, Is.EqualTo(2));
Assert.That(sink.RemovedEntityIds.Count, Is.EqualTo(1));
Assert.That(sink.RemovedEntityIds[0], Is.EqualTo("raider-scout"));
Assert.That(sink.Upserts[3].entity.EntityId, Is.EqualTo("player-vanguard"));
Assert.That(sink.Upserts[3].entity.PositionX, Is.EqualTo(5f));
Assert.That(sink.Upserts[3].entity.PositionZ, Is.EqualTo(2f));
}
[Test]
public void SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState()
{
@ -323,8 +363,79 @@ namespace GameCult.Eve.UnityScene.Tests
Array.Empty<EveCommandTemplate>());
}
private static EveSurfaceDocument PlayableArpgDocument()
private static EveSurfaceDocument PlayableArpgDocument(
bool includeRaider = true,
string playerPosition = "0,0,0")
{
var playableChildren = new List<EveSurfaceComponent>
{
PlayableEntity(
"aetheria.daemon.game.entity.player",
"player-vanguard",
"player",
"Vanguard",
"player",
"cultmesh://aetheria/assets/map/entity/player",
playerPosition,
"35",
"1.15",
true,
true,
"aetheria.daemon.focus",
"aetheria.daemon.move_intent",
"",
"")
};
if (includeRaider)
{
playableChildren.Add(PlayableEntity(
"aetheria.daemon.game.entity.raider",
"raider-scout",
"enemy",
"Raider Scout",
"raider",
"cultmesh://aetheria/assets/map/entity/ship",
"9,0,14",
"220",
"0.9",
true,
false,
"",
"",
"aetheria.daemon.target",
""));
}
playableChildren.Add(PlayableEntity(
"aetheria.daemon.game.entity.station",
"anchor-station",
"station",
"Anchor Station",
"neutral",
"cultmesh://aetheria/assets/map/entity/station",
"-18,0,6",
"0",
"3.4",
true,
false,
"aetheria.daemon.focus",
"",
"",
""));
playableChildren.Add(new EveSurfaceComponent(
"aetheria.daemon.game.flow",
"field.vector3d",
new Dictionary<string, string>(StringComparer.Ordinal)
{
["fieldId"] = "aetheria.zone.flow",
["bind"] = "aetheria.daemon.soaView.flowField3d",
["visualizer"] = "particles",
["bounds"] = "-32,0,-32,32,24,32"
},
Array.Empty<EveSurfaceComponent>()));
return new EveSurfaceDocument(
"aetheria",
"game.runtime",
@ -354,68 +465,7 @@ namespace GameCult.Eve.UnityScene.Tests
["targetCommand"] = "aetheria.daemon.target",
["actionCommand"] = "aetheria.daemon.use_equipment"
},
new[]
{
PlayableEntity(
"aetheria.daemon.game.entity.player",
"player-vanguard",
"player",
"Vanguard",
"player",
"cultmesh://aetheria/assets/map/entity/player",
"0,0,0",
"35",
"1.15",
true,
true,
"aetheria.daemon.focus",
"aetheria.daemon.move_intent",
"",
""),
PlayableEntity(
"aetheria.daemon.game.entity.raider",
"raider-scout",
"enemy",
"Raider Scout",
"raider",
"cultmesh://aetheria/assets/map/entity/ship",
"9,0,14",
"220",
"0.9",
true,
false,
"",
"",
"aetheria.daemon.target",
""),
PlayableEntity(
"aetheria.daemon.game.entity.station",
"anchor-station",
"station",
"Anchor Station",
"neutral",
"cultmesh://aetheria/assets/map/entity/station",
"-18,0,6",
"0",
"3.4",
true,
false,
"aetheria.daemon.focus",
"",
"",
""),
new EveSurfaceComponent(
"aetheria.daemon.game.flow",
"field.vector3d",
new Dictionary<string, string>(StringComparer.Ordinal)
{
["fieldId"] = "aetheria.zone.flow",
["bind"] = "aetheria.daemon.soaView.flowField3d",
["visualizer"] = "particles",
["bounds"] = "-32,0,-32,32,24,32"
},
Array.Empty<EveSurfaceComponent>())
},
playableChildren,
new[]
{
new CultMeshStateBindingDescriptor("run", "cultmesh://aetheria/run/current"),
@ -605,5 +655,30 @@ namespace GameCult.Eve.UnityScene.Tests
Submitted.Add(request);
}
}
private sealed class FakePlayableWorldSceneSink : IEveUnityPlayableWorldSceneSink
{
public List<EveUnityPlayableWorldProjection> ConfiguredWorlds { get; } = new List<EveUnityPlayableWorldProjection>();
public List<(EveUnityPlayableWorldEntity entity, EveUnityPlayableWorldAssetBinding asset)> Upserts { get; } =
new List<(EveUnityPlayableWorldEntity entity, EveUnityPlayableWorldAssetBinding asset)>();
public List<string> RemovedEntityIds { get; } = new List<string>();
public void ConfigureWorld(EveUnityPlayableWorldProjection world)
{
ConfiguredWorlds.Add(world);
}
public void UpsertEntity(EveUnityPlayableWorldEntity entity, EveUnityPlayableWorldAssetBinding asset)
{
Upserts.Add((entity, asset));
}
public void RemoveEntity(string entityId)
{
RemovedEntityIds.Add(entityId);
}
}
}
}

View file

@ -20,6 +20,7 @@
"providerSurfaceSource",
"sceneGraphProjection",
"playableWorldProjection",
"playableWorldScenePresentation",
"embeddedDocuments"
],
"supportedPlugins": [
@ -63,6 +64,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",
@ -111,6 +113,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",
@ -148,6 +151,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",

View file

@ -18,6 +18,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",
@ -36,6 +37,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef",
"tools/eveunity/eveunity-release-contract.mjs",
"tools/eveunity/eveunity-release-contract.test.mjs",
@ -54,6 +56,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",
@ -61,7 +64,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, 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, playable-world scene presentation, and command boundary evidence; live transport and capture remain separate lifecycle proofs."
},
{
"id": "unity-scene-sai-plugin-projection",
@ -150,6 +153,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 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 the provider-agnostic playable-world scene lowerer outside Eve incubation.",
"EveUnity owns the Unity scene Sai projection adapter outside Eve incubation without importing Sai story state or VN/Ink internals.",
"EveUnity owns the Unity scene Norn projection adapter outside Eve incubation without importing Norn implementation state.",

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", "sceneGraphProjection", "playableWorldProjection", "embeddedDocuments")) {
foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "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", "sceneGraphProjection", "playableWorldProjection")) {
foreach ($feature in @("providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation")) {
if (-not (@($manifest.supportedFeatures) -contains $feature)) {
throw "EveUnity scene provider shell missing supported feature: $feature"
}
@ -66,6 +66,7 @@ $expectedFiles = @(
"runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneSurfaceLowerer.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneClientSession.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneProviderConnection.cs",
"runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldPresenter.cs",
"runtimes\incubating\eve-unity-scene\Runtime\SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes\incubating\eve-unity-scene\Runtime\NornGraphUnitySceneProjectionAdapter.cs",
"runtimes\incubating\eve-unity-scene\Runtime\TeXMathUnitySceneProjectionAdapter.cs",
@ -101,6 +102,13 @@ foreach ($symbol in @("IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneComm
}
}
$playableWorldPresenterSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldPresenter.cs") -Raw
foreach ($symbol in @("IEveUnityPlayableWorldSceneSink", "IEveUnityPlayableWorldAssetResolver", "EveUnityPlayableWorldPresenter", "EveUnityPlayableWorldAssetBinding", "EveUnityPlayableWorldPresentation", "EveUnityAssetRefResolver", "ConfigureWorld", "UpsertEntity", "RemoveEntity", "provider-asset-ref", "unity-generated-placeholder")) {
if (-not $playableWorldPresenterSource.Contains($symbol)) {
throw "EveUnity playable world presenter missing symbol: $symbol"
}
}
$saiAdapterSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\SaiVisualNovelUnitySceneProjectionAdapter.cs") -Raw
foreach ($symbol in @("SaiVisualNovelUnitySceneProjectionAdapter", "sai.vn", "vn.stage", "story.choose", "story.continue", "story.jump", "gamecult.eve.plugin_abi.v1", "sidecar-advertised-plugin-abi", "Sai")) {
if (-not $saiAdapterSource.Contains($symbol)) {
@ -123,7 +131,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", "FakeProviderSurfaceSource", "FakeCommandSink", "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", "FakeProviderSurfaceSource", "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")) {
if (-not $testSource.Contains($symbol)) {
throw "EveUnity scene provider shell tests missing symbol: $symbol"
}

View file

@ -68,6 +68,7 @@ node $consumerScript $consumerExport `
--expect-runtime-feature unity-scene:providerSurfaceSource `
--expect-runtime-feature unity-scene:sceneGraphProjection `
--expect-runtime-feature unity-scene:playableWorldProjection `
--expect-runtime-feature unity-scene:playableWorldScenePresentation `
--expect-runtime-feature electron-shell:providerAdvertisements `
--expect-runtime-feature electron-shell:commandTransport `
--expect-runtime-feature electron-shell:surfaceTreeProjection `

View file

@ -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", "sceneGraphProjection", "playableWorldProjection", "embeddedDocuments"],
"supportedFeatures": ["providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "sceneGraphProjection", "playableWorldProjection", "playableWorldScenePresentation", "embeddedDocuments"],
"supportedPlugins": [
{
"pluginId": "sai.vn",
@ -1070,6 +1070,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",
@ -1085,6 +1086,7 @@
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneClientSession.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs",
"runtimes/incubating/eve-unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs",
@ -1120,6 +1122,10 @@
"path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs",
"contains": ["IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneCommandSink", "EveUnitySceneProviderConnection", "SnapshotAvailable", "CurrentSnapshot", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect"]
},
{
"path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs",
"contains": ["IEveUnityPlayableWorldSceneSink", "IEveUnityPlayableWorldAssetResolver", "EveUnityPlayableWorldPresenter", "EveUnityPlayableWorldAssetBinding", "EveUnityPlayableWorldPresentation", "EveUnityAssetRefResolver", "ConfigureWorld", "UpsertEntity", "RemoveEntity", "provider-asset-ref", "unity-generated-placeholder"]
},
{
"path": "runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
"contains": ["SaiVisualNovelUnitySceneProjectionAdapter", "sai.vn", "vn.stage", "story.choose", "story.continue", "story.jump", "gamecult.eve.plugin_abi.v1", "sidecar-advertised-plugin-abi", "Sai"]
@ -1134,7 +1140,7 @@
},
{
"path": "runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs",
"contains": ["LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "FakeProviderSurfaceSource", "FakeCommandSink", "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", "FakeProviderSurfaceSource", "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"]
},
{
"path": "tools/eveunity/eveunity-capture-contract.test.mjs",