Add Unity playable world projection proof
This commit is contained in:
parent
b03ddc1083
commit
09874ad026
16 changed files with 829 additions and 23 deletions
|
|
@ -603,6 +603,16 @@ Recently cut:
|
|||
Norn, and TeX remain sidecar plugin projections when a visual-novel surface
|
||||
is lowered. The remaining EveUnity blocker is an owner-repo Unity screenshot
|
||||
or frame-capture PNG, not absence of semantic scene projection evidence.
|
||||
- Unity scene lowering now extracts a provider-authored playable ARPG world
|
||||
model from the Aetheria daemon game surface without importing Aetheria runtime
|
||||
types. The Aetheria fixture carries generic `world.scene3d`,
|
||||
`world.entity3d`, asset-manifest, input-profile, camera, movement, focus, and
|
||||
target command affordances; `EveUnitySceneSurfaceLowerer` exposes them as a
|
||||
`PlayableWorld` projection, and the JSON capture artifact exports the same
|
||||
`playableWorld` shape under `gamecult.eve.unity_scene_projection.v1`. The
|
||||
next blocker is the live generic Unity client loop: subscribe to the Aetheria
|
||||
daemon surface over CultMesh/CultNet, instantiate the projected world, emit
|
||||
`gamecult.eve.command.v1` input, and wait for provider receipts/next frames.
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -474,9 +474,12 @@ Unity scene semantic capture evidence follows the same rule without pretending
|
|||
it is a rendered Unity frame. `tools/eveunity/eveunity-scene-capture-artifact.mjs`
|
||||
lowers the Aetheria world surface into `gamecult.eve.unity_scene_projection.v1`
|
||||
and writes `artifacts/eveunity-scene-capture/latest/unity-scene-projection.json`.
|
||||
It records scene graph projection, embedded slots, and sidecar plugin projection
|
||||
metadata. The remaining blocker is Unity screenshot or frame-capture PNG
|
||||
production from EveUnity.
|
||||
It records scene graph projection, embedded slots, sidecar plugin projection
|
||||
metadata, and a `playableWorld` extraction for generic `world.scene3d` /
|
||||
`world.entity3d` ARPG data: state pointer, asset manifest, camera/input profile,
|
||||
entity transforms, asset refs, and daemon command affordances. The remaining
|
||||
blocker is a live generic Unity client loop 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,
|
||||
|
|
|
|||
|
|
@ -152,6 +152,22 @@ Toolkit, Unity scene, Electron shell, and TUI lowerers are lowering targets;
|
|||
Aetheria remains the owner of world state, assets, command acceptance, and
|
||||
receipts.
|
||||
|
||||
The Unity scene lowerer now has a first playable-world extraction proof. The
|
||||
fixture carries a generic `world.scene3d` node with a daemon state pointer,
|
||||
asset manifest ref, third-person input/camera profile, movement/focus/target
|
||||
command names, `world.entity3d` rows, and a `field.vector3d` volume field. The
|
||||
Unity scene runtime lowers that into `playableWorld` projection evidence:
|
||||
entity ids, kinds, labels, positions, radii, asset refs, controllability, and
|
||||
command affordances. 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.
|
||||
|
||||
This is not yet the final playable client. The remaining cut is a generic
|
||||
EveUnity scene client that subscribes to the live Aetheria daemon surface over
|
||||
CultMesh/CultNet, instantiates the projected world in a Unity player, emits
|
||||
input as `gamecult.eve.command.v1`, and proves daemon receipts drive the next
|
||||
frame rather than renderer-local state.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
Eve does not own provider gameplay rules.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace GameCult.Eve.UnityScene
|
|||
advertisedSurface.WorldInteraction.CommandBoundary,
|
||||
advertisedSurface.WorldInteraction.ReceiptSchema,
|
||||
advertisedSurface.WorldInteraction.Ownership,
|
||||
BuildPlayableWorld(document.Surface.Root),
|
||||
BuildSceneGraph(document.Surface.Root));
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +94,103 @@ namespace GameCult.Eve.UnityScene
|
|||
children);
|
||||
}
|
||||
|
||||
private static EveUnityPlayableWorldProjection? BuildPlayableWorld(EveSurfaceComponent root)
|
||||
{
|
||||
var worldRoot = FindFirst(root, component => string.Equals(component.Kind, "world.scene3d", StringComparison.Ordinal));
|
||||
if (worldRoot == null)
|
||||
return null;
|
||||
|
||||
var entities = new List<EveUnityPlayableWorldEntity>();
|
||||
foreach (var component in Flatten(worldRoot))
|
||||
{
|
||||
if (string.Equals(component.Kind, "world.entity3d", StringComparison.Ordinal))
|
||||
entities.Add(BuildPlayableEntity(component));
|
||||
}
|
||||
|
||||
return new EveUnityPlayableWorldProjection(
|
||||
worldRoot.Id,
|
||||
worldRoot.GetProp("statePointerId", worldRoot.GetProp("worldStatePointerId")),
|
||||
worldRoot.GetProp("assetManifest", worldRoot.GetProp("assetManifestUri")),
|
||||
worldRoot.GetProp("inputProfile"),
|
||||
worldRoot.GetProp("cameraRig"),
|
||||
worldRoot.GetProp("playerEntityId"),
|
||||
worldRoot.GetProp("movementCommand"),
|
||||
worldRoot.GetProp("focusCommand"),
|
||||
worldRoot.GetProp("targetCommand"),
|
||||
worldRoot.GetProp("actionCommand"),
|
||||
entities);
|
||||
}
|
||||
|
||||
private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component)
|
||||
{
|
||||
var position = ParseVector3(component.GetProp("position"));
|
||||
return new EveUnityPlayableWorldEntity(
|
||||
component.Id,
|
||||
component.GetProp("entityId", component.Id),
|
||||
component.GetProp("entityKind", component.GetProp("kind")),
|
||||
component.GetProp("label", component.GetProp("name")),
|
||||
component.GetProp("faction"),
|
||||
component.GetProp("assetRef", component.GetProp("meshRef", component.GetProp("prefabRef"))),
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
ParseFloat(component.GetProp("rotationY", component.GetProp("yaw")), 0f),
|
||||
ParseFloat(component.GetProp("radius"), 0f),
|
||||
ParseBoolean(component.GetProp("selectable")),
|
||||
ParseBoolean(component.GetProp("controllable")),
|
||||
component.GetProp("focusCommand"),
|
||||
component.GetProp("moveCommand"),
|
||||
component.GetProp("targetCommand"),
|
||||
component.GetProp("actionCommand"));
|
||||
}
|
||||
|
||||
private static EveSurfaceComponent? FindFirst(
|
||||
EveSurfaceComponent component,
|
||||
Predicate<EveSurfaceComponent> predicate)
|
||||
{
|
||||
if (predicate(component))
|
||||
return component;
|
||||
foreach (var child in component.Children)
|
||||
{
|
||||
var found = FindFirst(child, predicate);
|
||||
if (found != null)
|
||||
return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<EveSurfaceComponent> Flatten(EveSurfaceComponent component)
|
||||
{
|
||||
yield return component;
|
||||
foreach (var child in component.Children)
|
||||
{
|
||||
foreach (var nested in Flatten(child))
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
|
||||
private static (float x, float y, float z) ParseVector3(string value)
|
||||
{
|
||||
var parts = (value ?? "").Split(',');
|
||||
return (
|
||||
parts.Length > 0 ? ParseFloat(parts[0], 0f) : 0f,
|
||||
parts.Length > 1 ? ParseFloat(parts[1], 0f) : 0f,
|
||||
parts.Length > 2 ? ParseFloat(parts[2], 0f) : 0f);
|
||||
}
|
||||
|
||||
private static float ParseFloat(string value, float fallback)
|
||||
{
|
||||
return float.TryParse(value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var parsed)
|
||||
? parsed
|
||||
: fallback;
|
||||
}
|
||||
|
||||
private static bool ParseBoolean(string value)
|
||||
{
|
||||
return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "1", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static EveUnityScenePluginProjection? BuildPluginProjection(EveSurfaceComponent component)
|
||||
{
|
||||
if (SaiVisualNovelAdapter.CanProject(component))
|
||||
|
|
@ -123,6 +221,13 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(componentKind))
|
||||
return "empty";
|
||||
if (string.Equals(componentKind, "world.scene3d", StringComparison.Ordinal))
|
||||
return "playable-world-root";
|
||||
if (string.Equals(componentKind, "world.entity3d", StringComparison.Ordinal))
|
||||
return "playable-world-entity";
|
||||
if (string.Equals(componentKind, "field.vector3d", StringComparison.Ordinal) ||
|
||||
string.Equals(componentKind, "field.scalar3d", StringComparison.Ordinal))
|
||||
return "world-field-3d";
|
||||
if (string.Equals(componentKind, "vn.stage", StringComparison.Ordinal))
|
||||
return "sai-vn-scene-stage";
|
||||
if (string.Equals(componentKind, "panel.dialogue", StringComparison.Ordinal) ||
|
||||
|
|
@ -140,7 +245,8 @@ namespace GameCult.Eve.UnityScene
|
|||
return "plugin-placeholder";
|
||||
if (string.Equals(componentKind, "surface.slot", StringComparison.Ordinal))
|
||||
return "embedded-surface-slot";
|
||||
if (componentKind.StartsWith("world.", StringComparison.Ordinal))
|
||||
if (componentKind.StartsWith("world.", StringComparison.Ordinal) ||
|
||||
componentKind.StartsWith("field.", StringComparison.Ordinal))
|
||||
return "world-projection-node";
|
||||
if (componentKind.StartsWith("text.", StringComparison.Ordinal) || string.Equals(componentKind, "label", StringComparison.Ordinal))
|
||||
return "scene-label";
|
||||
|
|
@ -157,6 +263,7 @@ namespace GameCult.Eve.UnityScene
|
|||
string commandBoundary,
|
||||
string receiptSchema,
|
||||
string ownership,
|
||||
EveUnityPlayableWorldProjection? playableWorld,
|
||||
EveUnitySceneNode root)
|
||||
{
|
||||
ProviderId = providerId ?? "";
|
||||
|
|
@ -165,6 +272,7 @@ namespace GameCult.Eve.UnityScene
|
|||
CommandBoundary = commandBoundary ?? "";
|
||||
ReceiptSchema = receiptSchema ?? "";
|
||||
Ownership = ownership ?? "";
|
||||
PlayableWorld = playableWorld;
|
||||
Root = root ?? throw new ArgumentNullException(nameof(root));
|
||||
}
|
||||
|
||||
|
|
@ -180,9 +288,139 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
public string Ownership { get; }
|
||||
|
||||
public EveUnityPlayableWorldProjection? PlayableWorld { get; }
|
||||
|
||||
public EveUnitySceneNode Root { get; }
|
||||
}
|
||||
|
||||
public sealed class EveUnityPlayableWorldProjection
|
||||
{
|
||||
public EveUnityPlayableWorldProjection(
|
||||
string worldRootId,
|
||||
string statePointerId,
|
||||
string assetManifest,
|
||||
string inputProfile,
|
||||
string cameraRig,
|
||||
string playerEntityId,
|
||||
string movementCommand,
|
||||
string focusCommand,
|
||||
string targetCommand,
|
||||
string actionCommand,
|
||||
IReadOnlyList<EveUnityPlayableWorldEntity> entities)
|
||||
{
|
||||
WorldRootId = worldRootId ?? "";
|
||||
StatePointerId = statePointerId ?? "";
|
||||
AssetManifest = assetManifest ?? "";
|
||||
InputProfile = inputProfile ?? "";
|
||||
CameraRig = cameraRig ?? "";
|
||||
PlayerEntityId = playerEntityId ?? "";
|
||||
MovementCommand = movementCommand ?? "";
|
||||
FocusCommand = focusCommand ?? "";
|
||||
TargetCommand = targetCommand ?? "";
|
||||
ActionCommand = actionCommand ?? "";
|
||||
Entities = entities ?? Array.Empty<EveUnityPlayableWorldEntity>();
|
||||
}
|
||||
|
||||
public string WorldRootId { get; }
|
||||
|
||||
public string StatePointerId { get; }
|
||||
|
||||
public string AssetManifest { get; }
|
||||
|
||||
public string InputProfile { get; }
|
||||
|
||||
public string CameraRig { get; }
|
||||
|
||||
public string PlayerEntityId { get; }
|
||||
|
||||
public string MovementCommand { get; }
|
||||
|
||||
public string FocusCommand { get; }
|
||||
|
||||
public string TargetCommand { get; }
|
||||
|
||||
public string ActionCommand { get; }
|
||||
|
||||
public int EntityCount => Entities.Count;
|
||||
|
||||
public IReadOnlyList<EveUnityPlayableWorldEntity> Entities { get; }
|
||||
}
|
||||
|
||||
public sealed class EveUnityPlayableWorldEntity
|
||||
{
|
||||
public EveUnityPlayableWorldEntity(
|
||||
string nodeId,
|
||||
string entityId,
|
||||
string entityKind,
|
||||
string label,
|
||||
string faction,
|
||||
string assetRef,
|
||||
float positionX,
|
||||
float positionY,
|
||||
float positionZ,
|
||||
float rotationY,
|
||||
float radius,
|
||||
bool selectable,
|
||||
bool controllable,
|
||||
string focusCommand,
|
||||
string moveCommand,
|
||||
string targetCommand,
|
||||
string actionCommand)
|
||||
{
|
||||
NodeId = nodeId ?? "";
|
||||
EntityId = entityId ?? "";
|
||||
EntityKind = entityKind ?? "";
|
||||
Label = label ?? "";
|
||||
Faction = faction ?? "";
|
||||
AssetRef = assetRef ?? "";
|
||||
PositionX = positionX;
|
||||
PositionY = positionY;
|
||||
PositionZ = positionZ;
|
||||
RotationY = rotationY;
|
||||
Radius = radius;
|
||||
Selectable = selectable;
|
||||
Controllable = controllable;
|
||||
FocusCommand = focusCommand ?? "";
|
||||
MoveCommand = moveCommand ?? "";
|
||||
TargetCommand = targetCommand ?? "";
|
||||
ActionCommand = actionCommand ?? "";
|
||||
}
|
||||
|
||||
public string NodeId { get; }
|
||||
|
||||
public string EntityId { get; }
|
||||
|
||||
public string EntityKind { get; }
|
||||
|
||||
public string Label { get; }
|
||||
|
||||
public string Faction { get; }
|
||||
|
||||
public string AssetRef { get; }
|
||||
|
||||
public float PositionX { get; }
|
||||
|
||||
public float PositionY { get; }
|
||||
|
||||
public float PositionZ { get; }
|
||||
|
||||
public float RotationY { get; }
|
||||
|
||||
public float Radius { get; }
|
||||
|
||||
public bool Selectable { get; }
|
||||
|
||||
public bool Controllable { get; }
|
||||
|
||||
public string FocusCommand { get; }
|
||||
|
||||
public string MoveCommand { get; }
|
||||
|
||||
public string TargetCommand { get; }
|
||||
|
||||
public string ActionCommand { get; }
|
||||
}
|
||||
|
||||
public sealed class EveUnitySceneNode
|
||||
{
|
||||
public EveUnitySceneNode(
|
||||
|
|
|
|||
|
|
@ -57,6 +57,43 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(nornNode.PluginProjection.SemanticOwner, Is.EqualTo("Norn"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LowerExtractsPlayableArpgWorldFromGenericScene3dSurface()
|
||||
{
|
||||
var lowerer = new EveUnitySceneSurfaceLowerer();
|
||||
var projection = lowerer.Lower(PlayableArpgDocument(), Advertisement("aetheria.daemon.game"));
|
||||
|
||||
Assert.That(projection.Root.Children[0].SceneObjectKind, Is.EqualTo("playable-world-root"));
|
||||
Assert.That(projection.Root.Children[0].Children[0].SceneObjectKind, Is.EqualTo("playable-world-entity"));
|
||||
Assert.That(projection.Root.Children[0].Children[3].SceneObjectKind, Is.EqualTo("world-field-3d"));
|
||||
Assert.That(projection.PlayableWorld, Is.Not.Null);
|
||||
Assert.That(projection.PlayableWorld!.WorldRootId, Is.EqualTo("aetheria.daemon.game.playable"));
|
||||
Assert.That(projection.PlayableWorld.StatePointerId, Is.EqualTo("cultmesh://aetheria/run/current"));
|
||||
Assert.That(projection.PlayableWorld.AssetManifest, Is.EqualTo("cultmesh://aetheria/assets/manifest"));
|
||||
Assert.That(projection.PlayableWorld.InputProfile, Is.EqualTo("arpg-third-person"));
|
||||
Assert.That(projection.PlayableWorld.CameraRig, Is.EqualTo("third-person-orbit"));
|
||||
Assert.That(projection.PlayableWorld.PlayerEntityId, Is.EqualTo("player-vanguard"));
|
||||
Assert.That(projection.PlayableWorld.MovementCommand, Is.EqualTo("aetheria.daemon.move_intent"));
|
||||
Assert.That(projection.PlayableWorld.FocusCommand, Is.EqualTo("aetheria.daemon.focus"));
|
||||
Assert.That(projection.PlayableWorld.TargetCommand, Is.EqualTo("aetheria.daemon.target"));
|
||||
Assert.That(projection.PlayableWorld.EntityCount, Is.EqualTo(3));
|
||||
|
||||
var player = FindEntity(projection.PlayableWorld, "player-vanguard");
|
||||
Assert.That(player.EntityKind, Is.EqualTo("player"));
|
||||
Assert.That(player.AssetRef, Is.EqualTo("cultmesh://aetheria/assets/map/entity/player"));
|
||||
Assert.That(player.PositionX, Is.EqualTo(0f));
|
||||
Assert.That(player.PositionY, Is.EqualTo(0f));
|
||||
Assert.That(player.PositionZ, Is.EqualTo(0f));
|
||||
Assert.That(player.Controllable, Is.True);
|
||||
Assert.That(player.MoveCommand, Is.EqualTo("aetheria.daemon.move_intent"));
|
||||
|
||||
var raider = FindEntity(projection.PlayableWorld, "raider-scout");
|
||||
Assert.That(raider.EntityKind, Is.EqualTo("enemy"));
|
||||
Assert.That(raider.PositionX, Is.EqualTo(9f));
|
||||
Assert.That(raider.PositionZ, Is.EqualTo(14f));
|
||||
Assert.That(raider.TargetCommand, Is.EqualTo("aetheria.daemon.target"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState()
|
||||
{
|
||||
|
|
@ -182,6 +219,164 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Array.Empty<EveCommandTemplate>());
|
||||
}
|
||||
|
||||
private static EveSurfaceDocument PlayableArpgDocument()
|
||||
{
|
||||
return new EveSurfaceDocument(
|
||||
"aetheria",
|
||||
"game.runtime",
|
||||
"Aetheria daemon game",
|
||||
1,
|
||||
"2026-07-09T00:00:00Z",
|
||||
new EveSurfaceTree(
|
||||
"aetheria.daemon.game",
|
||||
new EveSurfaceComponent(
|
||||
"aetheria.daemon.game.root",
|
||||
"surface",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
new[]
|
||||
{
|
||||
new EveSurfaceComponent(
|
||||
"aetheria.daemon.game.playable",
|
||||
"world.scene3d",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["statePointerId"] = "cultmesh://aetheria/run/current",
|
||||
["assetManifest"] = "cultmesh://aetheria/assets/manifest",
|
||||
["inputProfile"] = "arpg-third-person",
|
||||
["cameraRig"] = "third-person-orbit",
|
||||
["playerEntityId"] = "player-vanguard",
|
||||
["movementCommand"] = "aetheria.daemon.move_intent",
|
||||
["focusCommand"] = "aetheria.daemon.focus",
|
||||
["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>())
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new CultMeshStateBindingDescriptor("run", "cultmesh://aetheria/run/current"),
|
||||
new CultMeshStateBindingDescriptor("assets", "cultmesh://aetheria/assets/manifest")
|
||||
})
|
||||
}),
|
||||
Array.Empty<EveStyleToken>()),
|
||||
new[]
|
||||
{
|
||||
new EveCommandTemplate(CultMesh.OperationBinding("aetheria.daemon.commands", "Aetheria daemon commands"))
|
||||
});
|
||||
}
|
||||
|
||||
private static EveSurfaceComponent PlayableEntity(
|
||||
string nodeId,
|
||||
string entityId,
|
||||
string entityKind,
|
||||
string label,
|
||||
string faction,
|
||||
string assetRef,
|
||||
string position,
|
||||
string rotationY,
|
||||
string radius,
|
||||
bool selectable,
|
||||
bool controllable,
|
||||
string focusCommand,
|
||||
string moveCommand,
|
||||
string targetCommand,
|
||||
string actionCommand)
|
||||
{
|
||||
return new EveSurfaceComponent(
|
||||
nodeId,
|
||||
"world.entity3d",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["entityId"] = entityId,
|
||||
["entityKind"] = entityKind,
|
||||
["label"] = label,
|
||||
["faction"] = faction,
|
||||
["assetRef"] = assetRef,
|
||||
["position"] = position,
|
||||
["rotationY"] = rotationY,
|
||||
["radius"] = radius,
|
||||
["selectable"] = selectable ? "true" : "false",
|
||||
["controllable"] = controllable ? "true" : "false",
|
||||
["focusCommand"] = focusCommand,
|
||||
["moveCommand"] = moveCommand,
|
||||
["targetCommand"] = targetCommand,
|
||||
["actionCommand"] = actionCommand
|
||||
},
|
||||
Array.Empty<EveSurfaceComponent>());
|
||||
}
|
||||
|
||||
private static EveUnityPlayableWorldEntity FindEntity(
|
||||
EveUnityPlayableWorldProjection playableWorld,
|
||||
string entityId)
|
||||
{
|
||||
foreach (var entity in playableWorld.Entities)
|
||||
{
|
||||
if (string.Equals(entity.EntityId, entityId, StringComparison.Ordinal))
|
||||
return entity;
|
||||
}
|
||||
throw new AssertionException($"Playable entity not found: {entityId}");
|
||||
}
|
||||
|
||||
private static EveSurfaceDocument SaiDocument()
|
||||
{
|
||||
return new EveSurfaceDocument(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"providerAdvertisements",
|
||||
"commandTransport",
|
||||
"sceneGraphProjection",
|
||||
"playableWorldProjection",
|
||||
"embeddedDocuments"
|
||||
],
|
||||
"supportedPlugins": [
|
||||
|
|
@ -54,7 +55,7 @@
|
|||
"targetId": "unity-scene",
|
||||
"surfaceKinds": ["interactive-world"],
|
||||
"projectionKinds": ["provider-authored-world-surface"],
|
||||
"supportLevel": "unity-scene-graph-command-surface",
|
||||
"supportLevel": "unity-playable-world-scene-command-surface",
|
||||
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
|
||||
"evidencePaths": [
|
||||
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
|
||||
|
|
|
|||
|
|
@ -55,7 +55,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 scene graph projection and command boundary evidence; capture remains a separate lifecycle proof."
|
||||
"replacementProof": "EveUnity claims the provider-advertised unity-scene lowering target with provider-agnostic playable-world projection, scene graph projection, and command boundary evidence; live transport and capture remain separate lifecycle proofs."
|
||||
},
|
||||
{
|
||||
"id": "unity-scene-sai-plugin-projection",
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
"scripts/run-eveunity-scene-capture-smoke.ps1"
|
||||
],
|
||||
"destinationOwner": "EveUnity",
|
||||
"replacementProof": "EveUnity builds a provider-advertisement-backed Unity scene capture request and deterministic gamecult.eve.unity_scene_projection.v1 JSON projection artifact now; the split is not complete until EveUnity attaches Unity scene screenshot or frame-capture PNG artifacts to conformance output for at least one provider-authored world surface."
|
||||
"replacementProof": "EveUnity builds a provider-advertisement-backed Unity scene capture request and deterministic gamecult.eve.unity_scene_projection.v1 JSON projection artifact with playableWorld evidence now; the split is not complete until EveUnity attaches Unity scene screenshot or frame-capture PNG artifacts to conformance output for at least one provider-authored world surface."
|
||||
}
|
||||
],
|
||||
"contractInputs": [
|
||||
|
|
@ -140,12 +140,13 @@
|
|||
"EveUnity publishes a Unity scene runtime capability manifest.",
|
||||
"EveUnity builds the provider-agnostic Unity scene UPM package artifact.",
|
||||
"EveUnity owns a provider-agnostic scene graph lowerer that consumes advertised worldInteraction boundaries without importing Aetheria internals.",
|
||||
"EveUnity owns the provider-agnostic scene graph lowerer outside Eve incubation.",
|
||||
"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.",
|
||||
"EveUnity owns the Unity scene TeX projection adapter outside Eve incubation without importing TeX implementation state.",
|
||||
"EveUnity lowers at least one non-Aetheria provider-authored world surface into Unity scene objects.",
|
||||
"EveUnity lowers Aetheria's game world surface through provider advertisements without importing Aetheria internals.",
|
||||
"EveUnity lowers Aetheria's game world surface into generic playableWorld projection data through provider advertisements without importing Aetheria internals.",
|
||||
"A generic EveUnity scene client subscribes to the live Aetheria daemon surface over CultMesh/CultNet without Aetheria project code.",
|
||||
"Unity scene input emits gamecult.eve.command.v1 intents and renders provider receipts.",
|
||||
"Unity scene screenshot or frame-capture PNG artifacts are attached to conformance output."
|
||||
]
|
||||
|
|
|
|||
|
|
@ -49,11 +49,156 @@
|
|||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"playableWorld": {
|
||||
"$ref": "#/$defs/playableWorld"
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/$defs/sceneNode"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"vector3": {
|
||||
"type": "object",
|
||||
"required": ["x", "y", "z"],
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"type": "number"
|
||||
},
|
||||
"z": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"playableEntity": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"nodeId",
|
||||
"entityId",
|
||||
"entityKind",
|
||||
"assetRef",
|
||||
"position",
|
||||
"selectable",
|
||||
"controllable"
|
||||
],
|
||||
"properties": {
|
||||
"nodeId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"entityKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"faction": {
|
||||
"type": "string"
|
||||
},
|
||||
"assetRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"position": {
|
||||
"$ref": "#/$defs/vector3"
|
||||
},
|
||||
"rotationY": {
|
||||
"type": "number"
|
||||
},
|
||||
"radius": {
|
||||
"type": "number"
|
||||
},
|
||||
"selectable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"controllable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"focusCommand": {
|
||||
"type": "string"
|
||||
},
|
||||
"moveCommand": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetCommand": {
|
||||
"type": "string"
|
||||
},
|
||||
"actionCommand": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"playableWorld": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"worldRootId",
|
||||
"statePointerId",
|
||||
"assetManifest",
|
||||
"inputProfile",
|
||||
"cameraRig",
|
||||
"playerEntityId",
|
||||
"movementCommand",
|
||||
"entityCount",
|
||||
"entities"
|
||||
],
|
||||
"properties": {
|
||||
"worldRootId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"statePointerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"assetManifest": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"inputProfile": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"cameraRig": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"playerEntityId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"movementCommand": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"focusCommand": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetCommand": {
|
||||
"type": "string"
|
||||
},
|
||||
"actionCommand": {
|
||||
"type": "string"
|
||||
},
|
||||
"entityCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"entities": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/playableEntity"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"embeddedDocument": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -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", "sceneGraphProjection", "embeddedDocuments")) {
|
||||
foreach ($feature in @("providerAdvertisements", "commandTransport", "sceneGraphProjection", "playableWorldProjection", "embeddedDocuments")) {
|
||||
if (-not (@($manifest.supportedFeatures) -contains $feature)) {
|
||||
throw "EveUnity scene manifest missing provider-shell feature: $feature"
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ $worldSurfaceLoweringClaim = $worldSurfaceLoweringClaims | Where-Object { $_.tar
|
|||
if (-not $worldSurfaceLoweringClaim) {
|
||||
throw "EveUnity scene manifest missing unity-scene world-surface lowering claim"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.supportLevel -ne "unity-scene-graph-command-surface") {
|
||||
if ($worldSurfaceLoweringClaim.supportLevel -ne "unity-playable-world-scene-command-surface") {
|
||||
throw "Unexpected EveUnity scene world-surface support level: $($worldSurfaceLoweringClaim.supportLevel)"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.ownership -ne "runtime-lowers-provider-world-surface-without-owning-world-state") {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ if (-not (@($worldSurfaceLoweringClaim.surfaceKinds) -contains "interactive-worl
|
|||
if (-not (@($worldSurfaceLoweringClaim.projectionKinds) -contains "provider-authored-world-surface")) {
|
||||
throw "EveUnity scene world-surface claim must support provider-authored-world-surface projections"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.supportLevel -ne "unity-scene-graph-command-surface") {
|
||||
if ($worldSurfaceLoweringClaim.supportLevel -ne "unity-playable-world-scene-command-surface") {
|
||||
throw "Unexpected EveUnity scene support level: $($worldSurfaceLoweringClaim.supportLevel)"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.ownership -ne "runtime-lowers-provider-world-surface-without-owning-world-state") {
|
||||
|
|
@ -54,7 +54,7 @@ foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) {
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($feature in @("providerAdvertisements", "commandTransport", "sceneGraphProjection")) {
|
||||
foreach ($feature in @("providerAdvertisements", "commandTransport", "sceneGraphProjection", "playableWorldProjection")) {
|
||||
if (-not (@($manifest.supportedFeatures) -contains $feature)) {
|
||||
throw "EveUnity scene provider shell missing supported feature: $feature"
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ foreach ($relativePath in $expectedFiles) {
|
|||
}
|
||||
|
||||
$lowererSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneSurfaceLowerer.cs") -Raw
|
||||
foreach ($symbol in @("EveUnitySceneSurfaceLowerer", "EveUnitySceneProviderSurfaceAdvertisement", "WorldInteraction", "CommandBoundary", "ReceiptSchema", "EveSurfaceCommandRequest", "BuildSceneGraph", "BuildPluginProjection", "SaiVisualNovelUnitySceneProjectionAdapter", "TeXMathUnitySceneProjectionAdapter", "EveUnityScenePluginProjection", "EveUnitySceneNode", "SceneObjectKind", "unity-scene")) {
|
||||
foreach ($symbol in @("EveUnitySceneSurfaceLowerer", "EveUnitySceneProviderSurfaceAdvertisement", "WorldInteraction", "CommandBoundary", "ReceiptSchema", "EveSurfaceCommandRequest", "BuildSceneGraph", "BuildPlayableWorld", "EveUnityPlayableWorldProjection", "EveUnityPlayableWorldEntity", "BuildPluginProjection", "SaiVisualNovelUnitySceneProjectionAdapter", "TeXMathUnitySceneProjectionAdapter", "EveUnityScenePluginProjection", "EveUnitySceneNode", "SceneObjectKind", "playable-world-root", "unity-scene")) {
|
||||
if (-not $lowererSource.Contains($symbol)) {
|
||||
throw "EveUnity scene provider shell lowerer missing symbol: $symbol"
|
||||
}
|
||||
|
|
@ -107,7 +107,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", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "world-projection-node", "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", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "world-projection-node", "playable-world-root", "playable-world-entity", "world-field-3d", "arpg-third-person", "cultmesh://aetheria/assets/manifest", "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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ node $consumerScript $consumerExport `
|
|||
--expect-runtime-feature unity-scene:providerAdvertisements `
|
||||
--expect-runtime-feature unity-scene:commandTransport `
|
||||
--expect-runtime-feature unity-scene:sceneGraphProjection `
|
||||
--expect-runtime-feature unity-scene:playableWorldProjection `
|
||||
--expect-runtime-feature electron-shell:providerAdvertisements `
|
||||
--expect-runtime-feature electron-shell:commandTransport `
|
||||
--expect-runtime-feature electron-shell:surfaceTreeProjection `
|
||||
|
|
@ -85,7 +86,7 @@ node $consumerScript $consumerExport `
|
|||
--expect-runtime-world-field unity-uitoolkit:unity-uitoolkit:supportLevel:ui-toolkit-semantic-command-surface `
|
||||
--expect-runtime-world-field unity-uitoolkit:unity-uitoolkit:ownership:runtime-lowers-provider-world-surface-without-owning-world-state `
|
||||
--expect-runtime-world-target unity-scene:unity-scene `
|
||||
--expect-runtime-world-field unity-scene:unity-scene:supportLevel:unity-scene-graph-command-surface `
|
||||
--expect-runtime-world-field unity-scene:unity-scene:supportLevel:unity-playable-world-scene-command-surface `
|
||||
--expect-runtime-world-field unity-scene:unity-scene:ownership:runtime-lowers-provider-world-surface-without-owning-world-state `
|
||||
--expect-runtime-world-target electron-shell:electron-shell `
|
||||
--expect-runtime-world-field electron-shell:electron-shell:supportLevel:electron-shell-surface-tree-command-surface `
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export function buildUnitySceneProjection(surfaceDocument, providerAdvertisement
|
|||
if (document.surfaceId !== selected.surfaceId) {
|
||||
throw new Error(`Surface document ${document.surfaceId} does not match advertised Unity scene surface ${selected.surfaceId}.`);
|
||||
}
|
||||
const playableWorld = buildPlayableWorld(document.root);
|
||||
|
||||
return {
|
||||
type: "unity-scene-projection",
|
||||
|
|
@ -53,6 +54,7 @@ export function buildUnitySceneProjection(surfaceDocument, providerAdvertisement
|
|||
commandBoundary: selected.worldInteraction.commandBoundary,
|
||||
receiptSchema: selected.worldInteraction.receiptSchema,
|
||||
ownership: selected.worldInteraction.ownership,
|
||||
...(playableWorld ? { playableWorld } : {}),
|
||||
root: buildSceneNode(document.root),
|
||||
};
|
||||
}
|
||||
|
|
@ -153,6 +155,9 @@ function normalizeEmbeddedDocuments(value) {
|
|||
|
||||
function sceneObjectKind(componentKind) {
|
||||
if (!componentKind) return "empty";
|
||||
if (componentKind === "world.scene3d") return "playable-world-root";
|
||||
if (componentKind === "world.entity3d") return "playable-world-entity";
|
||||
if (componentKind === "field.vector3d" || componentKind === "field.scalar3d") return "world-field-3d";
|
||||
if (componentKind === "vn.stage") return "sai-vn-scene-stage";
|
||||
if (componentKind === "panel.dialogue" || componentKind === "text.dialogue") return "sai-vn-scene-dialogue";
|
||||
if (componentKind === "rail.actions") return "sai-vn-scene-action-rail";
|
||||
|
|
@ -166,6 +171,87 @@ function sceneObjectKind(componentKind) {
|
|||
return "scene-node";
|
||||
}
|
||||
|
||||
function buildPlayableWorld(root) {
|
||||
const world = findComponent(root, component => firstString(component?.kind) === "world.scene3d");
|
||||
if (!world) return null;
|
||||
const props = objectValue(world.props);
|
||||
const entities = flattenComponents(world)
|
||||
.filter(component => firstString(component.kind) === "world.entity3d")
|
||||
.map(component => buildPlayableEntity(component));
|
||||
return {
|
||||
worldRootId: firstString(world.id),
|
||||
statePointerId: firstString(props.statePointerId, props.worldStatePointerId),
|
||||
assetManifest: firstString(props.assetManifest, props.assetManifestUri),
|
||||
inputProfile: firstString(props.inputProfile),
|
||||
cameraRig: firstString(props.cameraRig),
|
||||
playerEntityId: firstString(props.playerEntityId),
|
||||
movementCommand: firstString(props.movementCommand),
|
||||
focusCommand: firstString(props.focusCommand),
|
||||
targetCommand: firstString(props.targetCommand),
|
||||
actionCommand: firstString(props.actionCommand),
|
||||
entityCount: entities.length,
|
||||
entities,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPlayableEntity(component) {
|
||||
const props = objectValue(component.props);
|
||||
const [x, y, z] = parseVector3(firstString(props.position));
|
||||
return {
|
||||
nodeId: firstString(component.id),
|
||||
entityId: firstString(props.entityId, component.id),
|
||||
entityKind: firstString(props.entityKind, props.kind),
|
||||
label: firstString(props.label, props.name),
|
||||
faction: firstString(props.faction),
|
||||
assetRef: firstString(props.assetRef, props.meshRef, props.prefabRef),
|
||||
position: { x, y, z },
|
||||
rotationY: parseNumber(firstString(props.rotationY, props.yaw), 0),
|
||||
radius: parseNumber(firstString(props.radius), 0),
|
||||
selectable: parseBoolean(props.selectable),
|
||||
controllable: parseBoolean(props.controllable),
|
||||
focusCommand: firstString(props.focusCommand),
|
||||
moveCommand: firstString(props.moveCommand),
|
||||
targetCommand: firstString(props.targetCommand),
|
||||
actionCommand: firstString(props.actionCommand),
|
||||
};
|
||||
}
|
||||
|
||||
function findComponent(component, predicate) {
|
||||
const source = objectValue(component);
|
||||
if (predicate(source)) return source;
|
||||
for (const child of Array.isArray(source.children) ? source.children : []) {
|
||||
const found = findComponent(child, predicate);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function flattenComponents(component) {
|
||||
const source = objectValue(component);
|
||||
return [
|
||||
source,
|
||||
...((Array.isArray(source.children) ? source.children : []).flatMap(flattenComponents)),
|
||||
];
|
||||
}
|
||||
|
||||
function parseVector3(value) {
|
||||
const values = String(value || "")
|
||||
.split(",")
|
||||
.map(part => parseNumber(part.trim(), 0));
|
||||
return [values[0] || 0, values[1] || 0, values[2] || 0];
|
||||
}
|
||||
|
||||
function parseNumber(value, fallback) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
function parseBoolean(value) {
|
||||
if (typeof value === "boolean") return value;
|
||||
if (typeof value === "number") return value !== 0;
|
||||
return String(value || "").toLowerCase() === "true";
|
||||
}
|
||||
|
||||
function buildPluginProjection(componentKind, component) {
|
||||
const props = objectValue(component.props);
|
||||
if (componentKind === "vn.stage") {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,19 @@ test("builds Unity scene projection capture artifact from provider advertisement
|
|||
assert.equal(projection.artifactKind, "json-projection");
|
||||
assert.equal(projection.captureKind, "unity-scene-projection-json");
|
||||
assert.equal(projection.root.id, "aetheria.daemon.game.root");
|
||||
assert.equal(projection.root.children[1].children[0].sceneObjectKind, "world-projection-node");
|
||||
assert.equal(projection.playableWorld.worldRootId, "aetheria.daemon.game.playable");
|
||||
assert.equal(projection.playableWorld.statePointerId, "cultmesh://aetheria/run/current");
|
||||
assert.equal(projection.playableWorld.assetManifest, "cultmesh://aetheria/assets/manifest");
|
||||
assert.equal(projection.playableWorld.inputProfile, "arpg-third-person");
|
||||
assert.equal(projection.playableWorld.cameraRig, "third-person-orbit");
|
||||
assert.equal(projection.playableWorld.playerEntityId, "player-vanguard");
|
||||
assert.equal(projection.playableWorld.movementCommand, "aetheria.daemon.move_intent");
|
||||
assert.equal(projection.playableWorld.entityCount, 3);
|
||||
const player = projection.playableWorld.entities.find(entity => entity.entityId === "player-vanguard");
|
||||
assert.equal(player.assetRef, "cultmesh://aetheria/assets/map/entity/player");
|
||||
assert.deepEqual(player.position, { x: 0, y: 0, z: 0 });
|
||||
assert.equal(player.controllable, true);
|
||||
assert.equal(projection.root.children[1].children[0].sceneObjectKind, "playable-world-root");
|
||||
});
|
||||
|
||||
test("builds Unity scene capture artifact for a generic world provider", () => {
|
||||
|
|
|
|||
|
|
@ -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", "sceneGraphProjection", "embeddedDocuments"],
|
||||
"supportedFeatures": ["providerAdvertisements", "commandTransport", "sceneGraphProjection", "playableWorldProjection", "embeddedDocuments"],
|
||||
"supportedPlugins": [
|
||||
{
|
||||
"pluginId": "sai.vn",
|
||||
|
|
@ -1064,7 +1064,7 @@
|
|||
"targetId": "unity-scene",
|
||||
"surfaceKinds": ["interactive-world"],
|
||||
"projectionKinds": ["provider-authored-world-surface"],
|
||||
"supportLevel": "unity-scene-graph-command-surface",
|
||||
"supportLevel": "unity-playable-world-scene-command-surface",
|
||||
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
|
||||
"evidencePaths": [
|
||||
"runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
|
||||
|
|
@ -1106,7 +1106,7 @@
|
|||
"expectedSourceSymbols": [
|
||||
{
|
||||
"path": "runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs",
|
||||
"contains": ["EveUnitySceneSurfaceLowerer", "EveUnitySceneProviderSurfaceAdvertisement", "WorldInteraction", "CommandBoundary", "ReceiptSchema", "EveSurfaceCommandRequest", "BuildSceneGraph", "BuildEmbeddedDocumentSlots", "BuildPluginProjection", "SaiVisualNovelUnitySceneProjectionAdapter", "TeXMathUnitySceneProjectionAdapter", "EveUnitySceneEmbeddedDocumentSlot", "EveUnityScenePluginProjection", "EveUnitySceneNode", "SceneObjectKind", "unity-scene"]
|
||||
"contains": ["EveUnitySceneSurfaceLowerer", "EveUnitySceneProviderSurfaceAdvertisement", "WorldInteraction", "CommandBoundary", "ReceiptSchema", "EveSurfaceCommandRequest", "BuildSceneGraph", "BuildPlayableWorld", "EveUnityPlayableWorldProjection", "EveUnityPlayableWorldEntity", "BuildEmbeddedDocumentSlots", "BuildPluginProjection", "SaiVisualNovelUnitySceneProjectionAdapter", "TeXMathUnitySceneProjectionAdapter", "EveUnitySceneEmbeddedDocumentSlot", "EveUnityScenePluginProjection", "EveUnitySceneNode", "SceneObjectKind", "playable-world-root", "unity-scene"]
|
||||
},
|
||||
{
|
||||
"path": "runtimes/incubating/eve-unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs",
|
||||
|
|
@ -1122,7 +1122,7 @@
|
|||
},
|
||||
{
|
||||
"path": "runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs",
|
||||
"contains": ["LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "world-projection-node", "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", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "world-projection-node", "playable-world-root", "playable-world-entity", "world-field-3d", "arpg-third-person", "cultmesh://aetheria/assets/manifest", "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",
|
||||
|
|
@ -1130,11 +1130,11 @@
|
|||
},
|
||||
{
|
||||
"path": "tools/eveunity/eveunity-scene-capture-artifact.mjs",
|
||||
"contains": ["buildUnitySceneCaptureArtifact", "buildUnitySceneProjection", "gamecult.eve.unity_scene_projection.v1", "unity-scene-projection-json", "writeUnitySceneCaptureArtifact", "request-output"]
|
||||
"contains": ["buildUnitySceneCaptureArtifact", "buildUnitySceneProjection", "buildPlayableWorld", "gamecult.eve.unity_scene_projection.v1", "unity-scene-projection-json", "writeUnitySceneCaptureArtifact", "request-output"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveunity/eveunity-scene-capture-artifact.test.mjs",
|
||||
"contains": ["builds Unity scene projection capture artifact", "builds Unity scene capture artifact for a generic world provider", "marks Sai required and Norn TeX optional sidecar plugin projections", "gamecult.eve.unity_scene_projection.v1", "unity-scene-projection-json", "optional-nested", "eve.world-smoke.commands"]
|
||||
"contains": ["builds Unity scene projection capture artifact", "builds Unity scene capture artifact for a generic world provider", "marks Sai required and Norn TeX optional sidecar plugin projections", "playableWorld", "arpg-third-person", "aetheria.daemon.move_intent", "gamecult.eve.unity_scene_projection.v1", "unity-scene-projection-json", "optional-nested", "eve.world-smoke.commands"]
|
||||
},
|
||||
{
|
||||
"path": "tools/eveunity/eveunity-release-contract.test.mjs",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
"purpose": "Proves Aetheria can expose daemon-owned interactive world surfaces through Eve without moving game truth into Eve.",
|
||||
"exitCriteria": "Move to Aetheria when generated level state, assets, command receipts, and scenario conformance become product-owned fixtures.",
|
||||
"asserts": [
|
||||
"world.scene3d playable ARPG root",
|
||||
"world.entity3d provider-authored entity rows",
|
||||
"field.vector3d world projection",
|
||||
"daemon asset manifest refs",
|
||||
"generic Unity playableWorld projection",
|
||||
"field.surface2d world projection",
|
||||
"daemon command boundary",
|
||||
"provider-owned bindings",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,99 @@
|
|||
"direction": "horizontal"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "aetheria.daemon.game.playable",
|
||||
"kind": "world.scene3d",
|
||||
"props": {
|
||||
"label": "Daemon ARPG View",
|
||||
"statePointerId": "cultmesh://aetheria/run/current",
|
||||
"assetManifest": "cultmesh://aetheria/assets/manifest",
|
||||
"inputProfile": "arpg-third-person",
|
||||
"cameraRig": "third-person-orbit",
|
||||
"playerEntityId": "player-vanguard",
|
||||
"movementCommand": "aetheria.daemon.move_intent",
|
||||
"focusCommand": "aetheria.daemon.focus",
|
||||
"targetCommand": "aetheria.daemon.target",
|
||||
"actionCommand": "aetheria.daemon.use_equipment"
|
||||
},
|
||||
"stateBindings": [
|
||||
{
|
||||
"target": "run",
|
||||
"pointerId": "cultmesh://aetheria/run/current"
|
||||
},
|
||||
{
|
||||
"target": "assets",
|
||||
"pointerId": "cultmesh://aetheria/assets/manifest"
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"id": "aetheria.daemon.game.entity.player",
|
||||
"kind": "world.entity3d",
|
||||
"props": {
|
||||
"entityId": "player-vanguard",
|
||||
"entityKind": "player",
|
||||
"label": "Vanguard",
|
||||
"faction": "player",
|
||||
"assetRef": "cultmesh://aetheria/assets/map/entity/player",
|
||||
"position": "0,0,0",
|
||||
"rotationY": "35",
|
||||
"radius": "1.15",
|
||||
"selectable": "true",
|
||||
"controllable": "true",
|
||||
"focusCommand": "aetheria.daemon.focus",
|
||||
"moveCommand": "aetheria.daemon.move_intent"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "aetheria.daemon.game.entity.raider",
|
||||
"kind": "world.entity3d",
|
||||
"props": {
|
||||
"entityId": "raider-scout",
|
||||
"entityKind": "enemy",
|
||||
"label": "Raider Scout",
|
||||
"faction": "raider",
|
||||
"assetRef": "cultmesh://aetheria/assets/map/entity/ship",
|
||||
"position": "9,0,14",
|
||||
"rotationY": "220",
|
||||
"radius": "0.9",
|
||||
"selectable": "true",
|
||||
"targetCommand": "aetheria.daemon.target"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "aetheria.daemon.game.entity.station",
|
||||
"kind": "world.entity3d",
|
||||
"props": {
|
||||
"entityId": "anchor-station",
|
||||
"entityKind": "station",
|
||||
"label": "Anchor Station",
|
||||
"faction": "neutral",
|
||||
"assetRef": "cultmesh://aetheria/assets/map/entity/station",
|
||||
"position": "-18,0,6",
|
||||
"rotationY": "0",
|
||||
"radius": "3.4",
|
||||
"selectable": "true",
|
||||
"focusCommand": "aetheria.daemon.focus"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "aetheria.daemon.game.flow",
|
||||
"kind": "field.vector3d",
|
||||
"props": {
|
||||
"label": "Volumetric Flow",
|
||||
"fieldId": "aetheria.zone.flow",
|
||||
"bind": "aetheria.daemon.soaView.flowField3d",
|
||||
"visualizer": "particles",
|
||||
"bounds": "-32,0,-32,32,24,32"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "aetheria.daemon.game.gravity",
|
||||
"kind": "field.surface2d",
|
||||
|
|
|
|||
Loading…
Reference in a new issue