@vgai/engine 0.2.0 → 0.4.0-canary.20260715.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +3 -1
  2. package/package.json +24 -4
  3. package/schemas/engine-api.json +124 -0
  4. package/schemas/engine-api.md +53 -0
  5. package/schemas/engine-capabilities.json +124 -0
  6. package/schemas/inputmap.schema.json +314 -0
  7. package/schemas/mat.schema.json +286 -0
  8. package/schemas/prefab.schema.json +10148 -0
  9. package/schemas/scn2d.schema.json +475 -0
  10. package/schemas/vgai-game.schema.json +383 -0
  11. package/schemas/vscn.schema.json +11007 -0
  12. package/src/adapter/{world-kind.ts → adapter-surface.ts} +6 -6
  13. package/src/adapter/authoring.ts +77 -0
  14. package/src/adapter/first-party-systems.ts +23 -34
  15. package/src/adapter/game-adapter.ts +8 -8
  16. package/src/adapter/host-context.ts +2 -4
  17. package/src/adapter/index.ts +4 -4
  18. package/src/adapter/system-adapter.ts +88 -22
  19. package/src/adapter/vgai-scene-game-adapter.ts +244 -194
  20. package/src/animation/anim-graph-types.ts +12 -43
  21. package/src/animation/animation-clock.ts +479 -0
  22. package/src/animation/camera-ownership.ts +467 -0
  23. package/src/animation/cinematic-cues.ts +451 -0
  24. package/src/animation/clip-map.ts +41 -0
  25. package/src/animation/gsap-registration.ts +184 -0
  26. package/src/animation/theatre-clock-binding.ts +111 -0
  27. package/src/animation/theatre-director.ts +347 -0
  28. package/src/animation/theatre-object-binding.ts +661 -0
  29. package/src/animation/xstate-animation-binding.ts +436 -0
  30. package/src/animation/xstate-animation-meta.ts +319 -0
  31. package/src/audio/index.ts +39 -7
  32. package/src/audio/tone-clock-binding.ts +98 -0
  33. package/src/audio/tone-context.ts +129 -0
  34. package/src/audio/tone-offline-render.ts +167 -0
  35. package/src/audio/wav-encode.ts +119 -0
  36. package/src/character/cloth-sim.ts +533 -0
  37. package/src/character/spring-chain.ts +307 -0
  38. package/src/core/game-loop.ts +57 -2
  39. package/src/core/seeded-random.ts +161 -0
  40. package/src/core/system-runner.ts +20 -3
  41. package/src/core/types.ts +50 -0
  42. package/src/data/data-asset.ts +167 -0
  43. package/src/data/data-check-core.ts +242 -0
  44. package/src/data/data-ref.ts +145 -0
  45. package/src/data/vite-plugin-data.ts +290 -0
  46. package/src/dev/performance-profiler.ts +213 -0
  47. package/src/dev/webgl-gpu-timer.ts +53 -0
  48. package/src/ecs/component-manager.ts +45 -12
  49. package/src/ecs/game-component.ts +95 -11
  50. package/src/humanoid/bake.operation.ts +326 -0
  51. package/src/humanoid/body.ts +663 -0
  52. package/src/humanoid/clips.ts +149 -0
  53. package/src/humanoid/compose.ts +209 -0
  54. package/src/humanoid/generate.ts +189 -0
  55. package/src/humanoid/index.ts +36 -0
  56. package/src/humanoid/schema.ts +108 -0
  57. package/src/humanoid/skeleton.ts +345 -0
  58. package/src/index.ts +48 -0
  59. package/src/input/input-manager.ts +1886 -33
  60. package/src/input/input-types.ts +158 -3
  61. package/src/input/prompt-labels.ts +122 -0
  62. package/src/input/rebind-controller.ts +105 -0
  63. package/src/input/schema.ts +206 -52
  64. package/src/manifest/index.ts +5 -5
  65. package/src/manifest/load.ts +125 -72
  66. package/src/manifest/schema.ts +362 -255
  67. package/src/react/game-state.tsx +135 -32
  68. package/src/react/root-adapter.tsx +49 -0
  69. package/src/react/unmanaged-root-detector.ts +66 -0
  70. package/src/react/use-data.ts +124 -0
  71. package/src/react/use-selection.tsx +135 -0
  72. package/src/runtime/create-runtime.ts +112 -273
  73. package/src/runtime/debug-bridge.ts +483 -0
  74. package/src/runtime/debug-registry.ts +856 -0
  75. package/src/runtime/game.ts +342 -93
  76. package/src/runtime/gameplay-rng-trap.ts +134 -0
  77. package/src/runtime/input-router.ts +7 -7
  78. package/src/runtime/mount-game.ts +40 -38
  79. package/src/runtime/mount-manifest.ts +169 -37
  80. package/src/runtime/render-audio-control.ts +168 -0
  81. package/src/runtime/render-control.ts +522 -0
  82. package/src/runtime/render-seed.ts +79 -0
  83. package/src/runtime/state-bridge.ts +24 -10
  84. package/src/runtime/types.ts +110 -33
  85. package/src/scene/asset-loaders.ts +10 -36
  86. package/src/scene/asset-paths.ts +0 -2
  87. package/src/scene/asset-ref-check.ts +248 -0
  88. package/src/scene/asset-registry.ts +22 -0
  89. package/src/scene/component-registry.ts +14 -3
  90. package/src/scene/defaults.ts +1 -0
  91. package/src/scene/light-camera-factory.ts +11 -3
  92. package/src/scene/parse.ts +133 -0
  93. package/src/scene/scene-apply.ts +55 -4
  94. package/src/scene/scene-loader.ts +91 -123
  95. package/src/scene/scene-types.ts +0 -1
  96. package/src/scene/schema/animation.ts +30 -79
  97. package/src/scene/schema/entity.ts +20 -0
  98. package/src/scene/schema/index.ts +2 -46
  99. package/src/scene/schema/light.ts +16 -1
  100. package/src/scene/schema/material.ts +96 -91
  101. package/src/scene/schema/scene-file.ts +1 -7
  102. package/src/scene/user-data.ts +22 -10
  103. package/src/setup/setup-renderer.ts +10 -3
  104. package/src/tools/define-tool.ts +191 -0
  105. package/src/world2d/authoring-2d.ts +17 -1
  106. package/src/world2d/collision-2d.ts +1 -1
  107. package/src/world2d/pixi-game-adapter.ts +19 -17
  108. package/src/world2d/scene2d-loader.ts +1 -0
  109. package/src/world2d/types.ts +8 -2
  110. package/src/animation/anim-graph.ts +0 -406
  111. package/src/animation/anim-system.ts +0 -28
  112. package/src/animation/property-track.ts +0 -178
  113. package/src/animation/schema.ts +0 -204
  114. package/src/audio/ambient.ts +0 -300
  115. package/src/audio/impacts.ts +0 -212
  116. package/src/audio/movement.ts +0 -140
  117. package/src/audio/musical.ts +0 -200
  118. package/src/audio/ui-sounds.ts +0 -171
  119. package/src/audio/vehicle.ts +0 -235
  120. package/src/audio/weapons.ts +0 -152
  121. package/src/runtime/scene-ui-bridge.ts +0 -86
  122. package/src/runtime/scene-ui-data.ts +0 -119
  123. package/src/scene/schema/ui.ts +0 -602
package/README.md CHANGED
@@ -4,7 +4,9 @@ The vgai game engine: the game loop and phase scheduler, the
4
4
  `GameComponent` model (the world node *is* the entity — no ECS, no mirror
5
5
  tree), Rapier physics integration, the `.vscn.json` scene loader with its
6
6
  Zod schemas (the single source of truth for the scene format), animation
7
- (mixer + `AnimGraph` state machines), audio, input, the adapter seam
7
+ (native `THREE.AnimationMixer` actions bound to XState state machines via
8
+ `bindXStateAnimation` — see `src/animation/xstate-animation-binding.ts`),
9
+ audio, input, the adapter seam
8
10
  (`src/adapter/` — how the editor hosts non-native and unmodified games),
9
11
  and the world2d PixiJS stack.
10
12
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/engine",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.2.0",
5
+ "version": "0.4.0-canary.20260715.0",
6
6
  "type": "module",
7
7
  "description": "The vgai game engine — core loop, GameComponent model, physics, scene loader, adapter seam.",
8
8
  "homepage": "https://github.com/volter-ai/vgai-engine#readme",
@@ -14,12 +14,24 @@
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
+ "vgai": {
18
+ "operations": [
19
+ "./src/humanoid/bake.operation.ts"
20
+ ]
21
+ },
17
22
  "exports": {
18
23
  ".": "./src/index.ts",
19
24
  "./package.json": "./package.json",
20
25
  "./*": "./src/*"
21
26
  },
22
- "files": ["src", "dist"],
27
+ "files": [
28
+ "src",
29
+ "dist",
30
+ "schemas"
31
+ ],
32
+ "engines": {
33
+ "node": ">=22.0.0"
34
+ },
23
35
  "scripts": {
24
36
  "build": "tsc -p tsconfig.build.json"
25
37
  },
@@ -34,8 +46,12 @@
34
46
  "postprocessing": "^6.38.0",
35
47
  "realism-effects": "^1.1.2",
36
48
  "recast-navigation": "^0.43.0",
49
+ "@theatre/core": "0.7.2",
50
+ "gsap": "^3.12.0",
37
51
  "three": "^0.170.0",
38
52
  "three.quarks": "^0.16.0",
53
+ "tone": "^15.1.22",
54
+ "xstate": "^5.32.4",
39
55
  "zod": "^4.3.6"
40
56
  },
41
57
  "peerDependencies": {
@@ -43,8 +59,12 @@
43
59
  "react-dom": ">=18"
44
60
  },
45
61
  "peerDependenciesMeta": {
46
- "react": { "optional": true },
47
- "react-dom": { "optional": true }
62
+ "react": {
63
+ "optional": true
64
+ },
65
+ "react-dom": {
66
+ "optional": true
67
+ }
48
68
  },
49
69
  "devDependencies": {
50
70
  "@types/react": "^19.2.14",
@@ -0,0 +1,124 @@
1
+ {
2
+ "$comment": "AUTO-GENERATED by scripts/generate-engine-manifest.ts — do not edit manually. Run 'npm run generate-engine-manifest'.",
3
+ "phases": [
4
+ {
5
+ "name": "input",
6
+ "description": "Sample input devices (keyboard, mouse, gamepad) at the top of the frame."
7
+ },
8
+ {
9
+ "name": "prePhysics",
10
+ "description": "Apply forces/intents to rigid bodies before the physics step."
11
+ },
12
+ {
13
+ "name": "physics",
14
+ "description": "Step the Rapier physics world (fixed timestep)."
15
+ },
16
+ {
17
+ "name": "postPhysics",
18
+ "description": "Read physics results back; sync transforms from bodies to Object3D."
19
+ },
20
+ {
21
+ "name": "gameLogic",
22
+ "description": "General gameplay behavior. Default phase for GameComponents."
23
+ },
24
+ {
25
+ "name": "animation",
26
+ "description": "Advance animation graphs / mixers."
27
+ },
28
+ {
29
+ "name": "preRender",
30
+ "description": "Final per-frame fixups (cameras, HUD state) before drawing."
31
+ },
32
+ {
33
+ "name": "render",
34
+ "description": "Draw the frame."
35
+ }
36
+ ],
37
+ "phaseOrder": [
38
+ "input",
39
+ "prePhysics",
40
+ "physics",
41
+ "postPhysics",
42
+ "gameLogic",
43
+ "animation",
44
+ "preRender",
45
+ "render"
46
+ ],
47
+ "phaseConstants": {
48
+ "INPUT": "input",
49
+ "PRE_PHYSICS": "prePhysics",
50
+ "PHYSICS": "physics",
51
+ "POST_PHYSICS": "postPhysics",
52
+ "GAME_LOGIC": "gameLogic",
53
+ "ANIMATION": "animation",
54
+ "PRE_RENDER": "preRender",
55
+ "RENDER": "render"
56
+ },
57
+ "gameComponent": {
58
+ "description": "Base class for game components attached to entities. The Three.js Object3D IS the entity — there is no separate entity id.",
59
+ "defaultPhase": "gameLogic",
60
+ "statics": [
61
+ {
62
+ "name": "phase",
63
+ "type": "SystemPhaseName",
64
+ "default": "gameLogic",
65
+ "description": "Which phase this component's update runs in. Override in subclasses."
66
+ },
67
+ {
68
+ "name": "schema",
69
+ "type": "z.ZodObject | undefined",
70
+ "default": null,
71
+ "description": "Optional Zod object schema for authored fields. When set, scene data is validated + defaulted through `schema.parse(data)` before assignment."
72
+ }
73
+ ],
74
+ "fields": [
75
+ {
76
+ "name": "object3D",
77
+ "type": "THREE.Object3D",
78
+ "description": "The Object3D this component is attached to. The Object3D IS the entity."
79
+ },
80
+ {
81
+ "name": "rigidBody",
82
+ "type": "RAPIER.RigidBody | null",
83
+ "description": "The entity's Rapier rigid body, if it has physics."
84
+ },
85
+ {
86
+ "name": "collider",
87
+ "type": "RAPIER.Collider | null",
88
+ "description": "The entity's Rapier collider, if it has physics."
89
+ }
90
+ ],
91
+ "hooks": [
92
+ {
93
+ "name": "init",
94
+ "optional": true,
95
+ "signature": "init?(ctx: GameContext): void | Promise<void>",
96
+ "description": "Called once after the entity is fully constructed (object3D, physics, etc.)."
97
+ },
98
+ {
99
+ "name": "update",
100
+ "optional": false,
101
+ "signature": "update(dt: number, ctx: GameContext): void",
102
+ "description": "Called every fixed timestep, in the component's phase. Required (abstract)."
103
+ },
104
+ {
105
+ "name": "dispose",
106
+ "optional": true,
107
+ "signature": "dispose?(ctx: GameContext): void",
108
+ "description": "Called when the entity is destroyed or the component is removed."
109
+ },
110
+ {
111
+ "name": "onTriggerEnter",
112
+ "optional": true,
113
+ "signature": "onTriggerEnter?(other: THREE.Object3D, ctx: GameContext): void",
114
+ "description": "Called when a sensor overlap with `other` begins."
115
+ },
116
+ {
117
+ "name": "onTriggerExit",
118
+ "optional": true,
119
+ "signature": "onTriggerExit?(other: THREE.Object3D, ctx: GameContext): void",
120
+ "description": "Called when a sensor overlap with `other` ends."
121
+ }
122
+ ]
123
+ }
124
+ }
@@ -0,0 +1,53 @@
1
+ <!-- AUTO-GENERATED by scripts/generate-engine-manifest.ts — do not edit manually. -->
2
+ <!-- Run 'npm run generate-engine-manifest' to regenerate. -->
3
+
4
+ # Engine API Manifest
5
+
6
+ The schema-as-source-of-truth artifact for the engine's imperative surface — generated from the actual code so docs can never drift (see ROADMAP P0.3 / P2.9).
7
+
8
+ ## System Phases
9
+
10
+ Each frame, systems run in this exact order (from `PHASE_ORDER` in `packages/engine/src/core/types.ts`):
11
+
12
+ | # | Phase | Constant | Description |
13
+ | - | ----- | -------- | ----------- |
14
+ | 1 | `input` | `SystemPhase.INPUT` | Sample input devices (keyboard, mouse, gamepad) at the top of the frame. |
15
+ | 2 | `prePhysics` | `SystemPhase.PRE_PHYSICS` | Apply forces/intents to rigid bodies before the physics step. |
16
+ | 3 | `physics` | `SystemPhase.PHYSICS` | Step the Rapier physics world (fixed timestep). |
17
+ | 4 | `postPhysics` | `SystemPhase.POST_PHYSICS` | Read physics results back; sync transforms from bodies to Object3D. |
18
+ | 5 | `gameLogic` | `SystemPhase.GAME_LOGIC` | General gameplay behavior. Default phase for GameComponents. |
19
+ | 6 | `animation` | `SystemPhase.ANIMATION` | Advance animation graphs / mixers. |
20
+ | 7 | `preRender` | `SystemPhase.PRE_RENDER` | Final per-frame fixups (cameras, HUD state) before drawing. |
21
+ | 8 | `render` | `SystemPhase.RENDER` | Draw the frame. |
22
+
23
+ ## GameComponent Contract (v2)
24
+
25
+ Base class for game components attached to entities. The Three.js Object3D IS the entity — there is no separate entity id.
26
+
27
+ Default phase: `gameLogic`
28
+
29
+ ### Static members
30
+
31
+ | Member | Type | Default | Description |
32
+ | ------ | ---- | ------- | ----------- |
33
+ | `phase` | `SystemPhaseName` | `"gameLogic"` | Which phase this component's update runs in. Override in subclasses. |
34
+ | `schema` | `z.ZodObject | undefined` | `null` | Optional Zod object schema for authored fields. When set, scene data is validated + defaulted through `schema.parse(data)` before assignment. |
35
+
36
+ ### Instance fields (wired by ComponentManager)
37
+
38
+ | Field | Type | Description |
39
+ | ----- | ---- | ----------- |
40
+ | `object3D` | `THREE.Object3D` | The Object3D this component is attached to. The Object3D IS the entity. |
41
+ | `rigidBody` | `RAPIER.RigidBody | null` | The entity's Rapier rigid body, if it has physics. |
42
+ | `collider` | `RAPIER.Collider | null` | The entity's Rapier collider, if it has physics. |
43
+
44
+ ### Lifecycle / hooks
45
+
46
+ | Hook | Required | Signature | Description |
47
+ | ---- | -------- | --------- | ----------- |
48
+ | `init` | no | `init?(ctx: GameContext): void | Promise<void>` | Called once after the entity is fully constructed (object3D, physics, etc.). |
49
+ | `update` | yes | `update(dt: number, ctx: GameContext): void` | Called every fixed timestep, in the component's phase. Required (abstract). |
50
+ | `dispose` | no | `dispose?(ctx: GameContext): void` | Called when the entity is destroyed or the component is removed. |
51
+ | `onTriggerEnter` | no | `onTriggerEnter?(other: THREE.Object3D, ctx: GameContext): void` | Called when a sensor overlap with `other` begins. |
52
+ | `onTriggerExit` | no | `onTriggerExit?(other: THREE.Object3D, ctx: GameContext): void` | Called when a sensor overlap with `other` ends. |
53
+
@@ -0,0 +1,124 @@
1
+ {
2
+ "$comment": "Curated by human/agent review, NOT auto-generated (unlike engine-api.json beside it). Every claim was verified against code — see each entry's `evidence`. Staleness is machine-checked at two granularities: `schemaDirHash` (sha256 over packages/engine/src/scene/schema/) and per-entry `evidenceHashes` (sha256 of each cited evidence FILE — catches drift in files outside the schema dir, which is where most entries' evidence lives; T5.5/MASTER-ARCHITECTURE-REVIEW.md §7.2).",
3
+ "_readme": "Read this before authoring scene JSON or engine code, to avoid the negatives below. Refresh flow after a schema-dir OR evidence-file change: `npx tsx scripts/capabilities-hash.ts --check` (fails naming the stale entry) -> re-review affected entries against current code -> `--write` to record fresh hashes. The pre-commit hook runs --check automatically. Each entry's `evidenceHashes` maps the repo-relative file path parsed off the front of each `evidence[]` citation (before any `:line-range` suffix) to a sha256 of that file's current content.",
4
+ "schemaDirPath": "packages/engine/src/scene/schema/",
5
+ "schemaDirHash": "3530962568323eb7f0ea4aca8262c616d59fcc64c5fc5a171e4066f672bb77e1",
6
+ "reviewedAt": "2026-07-01",
7
+ "entries": [
8
+ {
9
+ "id": "physics-no-ccd",
10
+ "area": "physics",
11
+ "status": "unsupported",
12
+ "claim": "No Continuous Collision Detection (CCD). Fast/small dynamic bodies can tunnel through thin colliders in one step.",
13
+ "evidence": [
14
+ "packages/engine/src/scene/scene-loader.ts (the `bodyDesc` switch: fixed/kinematicPositionBased/dynamic only; file under concurrent edit elsewhere this wave, so no line numbers — grep 'RigidBodyDesc')",
15
+ "packages/engine/src/world2d/scene2d-loader.ts:144-154 (2D bodyDesc: same four stock constructors)"
16
+ ],
17
+ "notes": "Grepped packages/engine/src for ccd/CCD/setCcdEnabled: zero matches, 2D and 3D. No call ever sets a ccd flag. Workaround: keep dynamic-body per-step travel small relative to the thinnest collider, or add intermediate colliders.",
18
+ "evidenceHashes": {
19
+ "packages/engine/src/scene/scene-loader.ts": "58ed692dc8bc71ab40839ff5ab48f392f90839734262da2a96bedddc81af09b5",
20
+ "packages/engine/src/world2d/scene2d-loader.ts": "125f51fd58d6a7912a22c9b8a78f280c59f1a9c035343faf1d266ee81efaa9a6"
21
+ }
22
+ },
23
+ {
24
+ "id": "physics-no-enforced-envelope",
25
+ "area": "physics",
26
+ "status": "unsupported",
27
+ "claim": "No enforced limit on entity or physics-body count; practical envelope is untested.",
28
+ "evidence": [
29
+ "packages/engine/src/setup/setup-physics.ts (RAPIER.World construction: no capacity/count config)",
30
+ "packages/engine/src/scene/scene-loader.ts (entity/body creation: no count guard)"
31
+ ],
32
+ "notes": "Grepped for maxBodies/MAX_ENTITIES/entity limit/body limit/entityCount: no matches. No invented numbers — nothing caps entity/body count and no perf test here establishes a safe ceiling. Treat large counts (hundreds+ dynamic bodies) as unvalidated, not 'supported at scale'.",
33
+ "evidenceHashes": {
34
+ "packages/engine/src/setup/setup-physics.ts": "0540c502f420018b24dac8b77c486f30f905f0a05b02ead7ed3899e5aa612ee5",
35
+ "packages/engine/src/scene/scene-loader.ts": "58ed692dc8bc71ab40839ff5ab48f392f90839734262da2a96bedddc81af09b5"
36
+ }
37
+ },
38
+ {
39
+ "id": "input-value-model",
40
+ "area": "input",
41
+ "status": "supported-with-caveats",
42
+ "claim": "F1 (spec §12) added a typed action-value model (digital/scalar/vector2/pointerDelta/pointerPosition, default digital) with real deadzone/combine rules and a valueType-mismatch throw. F2 ('Complete Device Backends') un-rejects mouse_move/gamepad_axis_pair, giving both real readers (mouse_move -> pointerDelta/vector2 off the real mouse-delta accumulator; gamepad_axis_pair -> vector2 off a coupled xAxis/yAxis stick, deadzone-rescaled like gamepad_axis), and adds touch_button/touch_stick (digital/vector2) via setTouchButton/setTouchStick. Real window focus (blur/focus) and pointer-lock state are tracked and flush held REAL-device input on loss (#144: virtual/scheduled machine input gates on enabled only and survives blur); gamepad device identity (id/mapping) is exposed via getGamepadInfo/ActionValueSource.gamepadId. F4 adds getLastActiveDevice() + an optional getPrompt device param. Caveat: pointerPosition still has no real-device backend (test_pointer_position injection only); scalar isn't fed by mouse_move (no axis-select field).",
43
+ "evidence": [
44
+ "packages/engine/src/input/input-types.ts:2-40 (InputBinding union incl. mouse_move/gamepad_axis_pair/touch_button/touch_stick/test_*)",
45
+ "packages/engine/src/input/input-manager.ts:609-1905 (digital/gating/typed getters/inject*/setTouch*/getGamepadInfo/F4 getLastActiveDevice+getPrompt)"
46
+ ],
47
+ "notes": "gamepad disconnect/reconnect needs no special-cased code: poll() re-reads navigator.getGamepads() and every read skips a null slot, so a disconnected gamepad's actions go neutral with a correct isJustReleased edge for free — see input-device-backends-f2.test.ts.",
48
+ "evidenceHashes": {
49
+ "packages/engine/src/input/input-types.ts": "361c2618f5a55ecd8ec6b59689a9ab2a92590d86a694d33d21e825f250675ca0",
50
+ "packages/engine/src/input/input-manager.ts": "15dc63f60713a6f4087d4e761b5da178555984e939328bb40bf271d9ec76ee30"
51
+ }
52
+ },
53
+ {
54
+ "id": "animation-masked-layers",
55
+ "area": "animation",
56
+ "status": "supported",
57
+ "claim": "A native XState machine can drive multiple simultaneous named AnimationMixer layers through parallel regions. Layers support 0..1 weights, normal or additive Three.js blending, and include/exclude masks over named bone/Object3D subtrees.",
58
+ "evidence": [
59
+ "packages/engine/src/animation/xstate-animation-meta.ts (strict layer, weight, blendMode, and boneMask metadata schemas)",
60
+ "packages/engine/src/animation/xstate-animation-binding.ts (parallel-region reconciliation, subtree track filtering, and native additive composition)"
61
+ ],
62
+ "notes": "XState remains the only transition language. Parallel regions use distinct layer names. The binding filters native clip tracks, uses AnimationUtils.makeClipAdditive, can register with ctx.systems, and exposes live owner inspection data until disposal.",
63
+ "evidenceHashes": {
64
+ "packages/engine/src/animation/xstate-animation-meta.ts": "80376ac74819803616c4b396244a971cb9a6b2859c33409d032363bfeb3db44f",
65
+ "packages/engine/src/animation/xstate-animation-binding.ts": "bfccffe15e265fa2d3843dfaad0d3f1b6a7227a23742efa1f0e11182c8efc33a"
66
+ }
67
+ },
68
+ {
69
+ "id": "component-fields-no-nested-objects",
70
+ "area": "scene-schema",
71
+ "status": "unsupported",
72
+ "claim": "Authored `components.<Name>` values in .vscn.json/.prefab.json must be a scalar or homogeneous scalar array — nested objects/arrays-of-objects are rejected before a component's own static schema sees them.",
73
+ "evidence": [
74
+ "packages/engine/src/scene/schema/entity.ts:46-54 (ComponentScalarSchema/ComponentValueSchema/ComponentDataSchema/ComponentsSchema)",
75
+ "packages/engine/src/scene/schema/entity.ts:89 (`components: ComponentsSchema.optional()` on SceneEntitySchema, validated at scene-parse time)",
76
+ "packages/engine/src/scene/component-registry.ts:21-37 (applyComponents' `Klass.schema.parse(data)` runs on data that already passed ComponentDataSchema)"
77
+ ],
78
+ "notes": "The ceiling: `z.union([number, string, boolean, number[], string[], boolean[]])`. A GameComponent's own static `schema` CAN declare z.object() fields, but the scene-file parse rejects a nested value first, so authored JSON never carries one that far. Workaround: flatten (`targetX`/`targetY`/`targetZ` instead of `target:{x,y,z}`).",
79
+ "evidenceHashes": {
80
+ "packages/engine/src/scene/schema/entity.ts": "2b73fec245362168b1824ee06b820c801e7590de95ab1f11176fadad54991f21",
81
+ "packages/engine/src/scene/component-registry.ts": "7ea93439c56fdcd1db915893ebb0f3cabfc08112dbf24c15c76df64a063713e4"
82
+ }
83
+ },
84
+ {
85
+ "id": "ingestion-pixi-tiers",
86
+ "area": "ingestion",
87
+ "status": "supported-with-tiers",
88
+ "claim": "Unmodified OSS PixiJS games run via the world2d seam: manifest 'iframe-reachable' + bundleUrl — in-realm capture, editor edits, overlay persistence. extraDeps must be registry-known (unknown throws); capture failure degrades loudly.",
89
+ "evidence": [
90
+ "docs/BACKBONE-TASKS.md:389-414"
91
+ ],
92
+ "notes": "Track P shipped 2026-07-03; proven: bubbo-bubbo/puzzling-potions/flappy-pixi. Tiers/limits: docs/CAPABILITY-TIERS.md, docs/PIXI-INGEST-LANDING-DESIGN.md.",
93
+ "evidenceHashes": {
94
+ "docs/BACKBONE-TASKS.md": "0a39f9539731af1de5440aa53ad92d463b809fd7458edb7e34df5dac71e911ac"
95
+ }
96
+ },
97
+ {
98
+ "id": "ingestion-react-dom",
99
+ "area": "ingestion",
100
+ "status": "supported-with-caveats",
101
+ "claim": "Unmodified native-React DOM games run via the manifest `ingest-react` identity: vendored byte-for-byte (vendor/react-games/, zero-diff CI), mounted through the react world stack under the host React 19 (deduped). Embed-only in v1 — hosted, sized, disposed; NO authoring (the editor never writes JSX back into an unmodified game, D-N4).",
102
+ "evidence": [
103
+ "docs/BACKBONE-TASKS.md:415-435 (Track N, N0-N4 all done)"
104
+ ],
105
+ "notes": "Scope is native-DOM React ONLY (D-N1): R3F/@pixi/react games are canvas games and ride the three.js/pixi seams instead. Generality limit: ONE proven game (react-rpg — menu-to-dungeon e2e under React 19; both game-2 candidates rejected on evidence, R-N7). Game deps must be host-installed root deps (D-N6). Open R-N6: vendored react games' raw window listeners bypass the play-mode input gate. Design: docs/REACT-INGEST-LANDING-DESIGN.md.",
106
+ "evidenceHashes": {
107
+ "docs/BACKBONE-TASKS.md": "0a39f9539731af1de5440aa53ad92d463b809fd7458edb7e34df5dac71e911ac"
108
+ }
109
+ },
110
+ {
111
+ "id": "engine-supports-summary",
112
+ "area": "summary",
113
+ "status": "supported",
114
+ "claim": "For contrast, the engine DOES support: discrete Rapier bodies (fixed/kinematic/dynamic) with triggers; phase-ordered GameComponents; Zod-validated scene/prefab JSON; masked layered animation state machines with blend trees; boolean+thresholded-analog input actions; direct (unwrapped) Colyseus multiplayer.",
115
+ "evidence": [
116
+ "packages/engine/schemas/engine-api.json (auto-generated positive/imperative-surface manifest beside this file)"
117
+ ],
118
+ "notes": "This file exists for negatives engine-api.json can't express (e.g. 'parses but does nothing'). For the full positive surface, read engine-api.json/.md — regenerated by `npm run generate-engine-manifest`, can't drift.",
119
+ "evidenceHashes": {
120
+ "packages/engine/schemas/engine-api.json": "9cdf401821aaa4065a32eb0c042c3962e27a9bb76b017ee6ab5a282ab4c7c004"
121
+ }
122
+ }
123
+ ]
124
+ }