diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 61db3a2..867981d 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -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 diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index b856e4b..cab7fe6 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -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, diff --git a/docs/world-state-lowering.md b/docs/world-state-lowering.md index d76526b..47d76cb 100644 --- a/docs/world-state-lowering.md +++ b/docs/world-state-lowering.md @@ -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. diff --git a/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs b/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs index ecde456..6c02fef 100644 --- a/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs +++ b/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs @@ -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(); + 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 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 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 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(); + } + + 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 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( diff --git a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index b4d3e17..37a144a 100644 --- a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -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()); } + 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(StringComparer.Ordinal), + new[] + { + new EveSurfaceComponent( + "aetheria.daemon.game.playable", + "world.scene3d", + new Dictionary(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(StringComparer.Ordinal) + { + ["fieldId"] = "aetheria.zone.flow", + ["bind"] = "aetheria.daemon.soaView.flowField3d", + ["visualizer"] = "particles", + ["bounds"] = "-32,0,-32,32,24,32" + }, + Array.Empty()) + }, + new[] + { + new CultMeshStateBindingDescriptor("run", "cultmesh://aetheria/run/current"), + new CultMeshStateBindingDescriptor("assets", "cultmesh://aetheria/assets/manifest") + }) + }), + Array.Empty()), + 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(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()); + } + + 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( diff --git a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json index ba87f5b..d22e45e 100644 --- a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json +++ b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json @@ -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", diff --git a/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json b/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json index 64e2ef0..2d03310 100644 --- a/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json +++ b/runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json @@ -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." ] diff --git a/schemas/gamecult.eve.unity_scene_projection.v1.schema.json b/schemas/gamecult.eve.unity_scene_projection.v1.schema.json index bfac367..f4b2708 100644 --- a/schemas/gamecult.eve.unity_scene_projection.v1.schema.json +++ b/schemas/gamecult.eve.unity_scene_projection.v1.schema.json @@ -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": [ diff --git a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 index b41d91d..c38e96f 100644 --- a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 +++ b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 @@ -39,7 +39,7 @@ if ($manifest.incubation.splitHandoff.manifestPath -ne "runtimes/incubating/eve- throw "EveUnity scene manifest missing split handoff path" } -foreach ($feature in @("providerAdvertisements", "commandTransport", "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") { diff --git a/scripts/run-eveunity-scene-provider-shell-smoke.ps1 b/scripts/run-eveunity-scene-provider-shell-smoke.ps1 index 40af870..d684b5e 100644 --- a/scripts/run-eveunity-scene-provider-shell-smoke.ps1 +++ b/scripts/run-eveunity-scene-provider-shell-smoke.ps1 @@ -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" } diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 1005577..c6f2512 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -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 ` diff --git a/tools/eveunity/eveunity-scene-capture-artifact.mjs b/tools/eveunity/eveunity-scene-capture-artifact.mjs index 4805d66..8e18e29 100644 --- a/tools/eveunity/eveunity-scene-capture-artifact.mjs +++ b/tools/eveunity/eveunity-scene-capture-artifact.mjs @@ -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") { diff --git a/tools/eveunity/eveunity-scene-capture-artifact.test.mjs b/tools/eveunity/eveunity-scene-capture-artifact.test.mjs index 65a7b3f..1a7f254 100644 --- a/tools/eveunity/eveunity-scene-capture-artifact.test.mjs +++ b/tools/eveunity/eveunity-scene-capture-artifact.test.mjs @@ -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", () => { diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 22427e3..a922b4d 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -1040,7 +1040,7 @@ "splitHandoffPath": "runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json", "requiredFixtures": ["aetheria-world", "eve-world-smoke"], "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "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", diff --git a/web/fixtures/aetheria-world-surface.conformance.json b/web/fixtures/aetheria-world-surface.conformance.json index 4b4d2b4..881b309 100644 --- a/web/fixtures/aetheria-world-surface.conformance.json +++ b/web/fixtures/aetheria-world-surface.conformance.json @@ -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", diff --git a/web/fixtures/aetheria-world-surface.json b/web/fixtures/aetheria-world-surface.json index 0607c16..86be042 100644 --- a/web/fixtures/aetheria-world-surface.json +++ b/web/fixtures/aetheria-world-surface.json @@ -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",