@vgai/engine 0.5.15 → 0.5.17

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 (220) hide show
  1. package/dist/adapter/adapter-module.d.ts +229 -33
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +222 -50
  4. package/dist/adapter/authoring-seam-contract.d.ts +530 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
  6. package/dist/adapter/authoring-seam-contract.js +162 -0
  7. package/dist/adapter/authoring.d.ts +237 -50
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/authoring.js +2 -1
  10. package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
  11. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
  12. package/dist/adapter/entrypoint-selection-source.js +151 -0
  13. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
  16. package/dist/adapter/first-party-systems.d.ts +13 -3
  17. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  18. package/dist/adapter/first-party-systems.js +10 -3
  19. package/dist/adapter/index.d.ts +7 -2
  20. package/dist/adapter/index.d.ts.map +1 -1
  21. package/dist/adapter/index.js +4 -0
  22. package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
  23. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  24. package/dist/adapter/ingest/contract-system-adapters.js +13 -25
  25. package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
  26. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
  27. package/dist/adapter/ingest/game-contract-seams.js +62 -0
  28. package/dist/adapter/ingest/game-contract.d.ts +5 -3
  29. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  30. package/dist/adapter/ingest/game-contract.js +4 -1
  31. package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
  32. package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
  33. package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
  34. package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
  35. package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
  36. package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
  37. package/dist/adapter/ingest/scene-capture.d.ts +38 -13
  38. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  39. package/dist/adapter/ingest/scene-capture.js +101 -24
  40. package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
  41. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  42. package/dist/adapter/ingest/visible-capture-window.js +46 -7
  43. package/dist/adapter/native-debug-module.d.ts +43 -0
  44. package/dist/adapter/native-debug-module.d.ts.map +1 -0
  45. package/dist/adapter/native-debug-module.js +230 -0
  46. package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
  47. package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
  48. package/dist/adapter/object3d-authoring-subject.js +25 -0
  49. package/dist/adapter/root-adapter.d.ts +25 -56
  50. package/dist/adapter/root-adapter.d.ts.map +1 -1
  51. package/dist/adapter/root-adapter.js +4 -20
  52. package/dist/adapter/root-seam-contract.d.ts +129 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +37 -0
  55. package/dist/adapter/seam-evidence.d.ts +118 -0
  56. package/dist/adapter/seam-evidence.d.ts.map +1 -0
  57. package/dist/adapter/seam-evidence.js +231 -0
  58. package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
  59. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  60. package/dist/adapter/setup-three-root-adapter.js +11 -12
  61. package/dist/adapter/system-adapter.d.ts +6 -1
  62. package/dist/adapter/system-adapter.d.ts.map +1 -1
  63. package/dist/adapter/system-seam-contract.d.ts +354 -0
  64. package/dist/adapter/system-seam-contract.d.ts.map +1 -0
  65. package/dist/adapter/system-seam-contract.js +95 -0
  66. package/dist/animation/xstate-animation-binding.d.ts +6 -0
  67. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-binding.js +142 -4
  69. package/dist/asset-formats/collider.d.ts +1 -1
  70. package/dist/asset-formats/mesh.d.ts +2 -2
  71. package/dist/asset-formats/particles.d.ts +1 -1
  72. package/dist/canvas-react/engine-bridge.d.ts +6 -6
  73. package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
  74. package/dist/canvas-react/engine-bridge.js +2 -2
  75. package/dist/canvas-react/index.d.ts +2 -2
  76. package/dist/canvas-react/index.d.ts.map +1 -1
  77. package/dist/canvas-react/index.js +1 -1
  78. package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
  79. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
  80. package/dist/canvas-react/pixi-react-adapter.js +129 -64
  81. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  82. package/dist/canvas-react/pixi-react-root-factory.js +4 -0
  83. package/dist/canvas-react/world-context.d.ts +22 -12
  84. package/dist/canvas-react/world-context.d.ts.map +1 -1
  85. package/dist/canvas-react/world-context.js +12 -51
  86. package/dist/data/curve.d.ts +1 -1
  87. package/dist/data/curve.js +2 -2
  88. package/dist/dev/instruments.d.ts +4 -4
  89. package/dist/dev/instruments.js +4 -4
  90. package/dist/dev/register-render-vitals.d.ts +2 -2
  91. package/dist/dev/register-render-vitals.js +2 -2
  92. package/dist/dev/static-batch-advisor.d.ts +1 -1
  93. package/dist/dev/static-batch-advisor.js +2 -2
  94. package/dist/ecs/user-data.d.ts +5 -0
  95. package/dist/ecs/user-data.d.ts.map +1 -1
  96. package/dist/ecs/user-data.js +3 -0
  97. package/dist/input/input-manager.d.ts +64 -0
  98. package/dist/input/input-manager.d.ts.map +1 -1
  99. package/dist/input/input-manager.js +149 -0
  100. package/dist/manifest/load.d.ts +0 -3
  101. package/dist/manifest/load.d.ts.map +1 -1
  102. package/dist/manifest/load.js +0 -4
  103. package/dist/manifest/schema.d.ts +9 -11
  104. package/dist/manifest/schema.d.ts.map +1 -1
  105. package/dist/manifest/schema.js +2 -23
  106. package/dist/pixi/authoring.d.ts +33 -0
  107. package/dist/pixi/authoring.d.ts.map +1 -1
  108. package/dist/pixi/authoring.js +301 -2
  109. package/dist/pixi/ingest.d.ts +2 -1
  110. package/dist/pixi/ingest.d.ts.map +1 -1
  111. package/dist/pixi/ingest.js +4 -2
  112. package/dist/pixi/scene-capture.d.ts +9 -1
  113. package/dist/pixi/scene-capture.d.ts.map +1 -1
  114. package/dist/pixi/scene-capture.js +9 -6
  115. package/dist/react/world-state.d.ts +12 -0
  116. package/dist/react/world-state.d.ts.map +1 -1
  117. package/dist/react/world-state.js +14 -0
  118. package/dist/render/particles-factory.d.ts +13 -6
  119. package/dist/render/particles-factory.d.ts.map +1 -1
  120. package/dist/render/particles-factory.js +6 -3
  121. package/dist/render/soft-particle-depth.d.ts +3 -3
  122. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  123. package/dist/render/soft-particle-depth.js +8 -13
  124. package/dist/runtime/create-runtime.d.ts +25 -62
  125. package/dist/runtime/create-runtime.d.ts.map +1 -1
  126. package/dist/runtime/create-runtime.js +58 -105
  127. package/dist/runtime/dev-build.d.ts +37 -0
  128. package/dist/runtime/dev-build.d.ts.map +1 -0
  129. package/dist/runtime/dev-build.js +49 -0
  130. package/dist/runtime/game.d.ts +38 -72
  131. package/dist/runtime/game.d.ts.map +1 -1
  132. package/dist/runtime/game.js +40 -55
  133. package/dist/runtime/mount-game.d.ts.map +1 -1
  134. package/dist/runtime/mount-game.js +12 -1
  135. package/dist/runtime/mount-manifest.d.ts +0 -13
  136. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  137. package/dist/runtime/mount-manifest.js +5 -25
  138. package/dist/runtime/types.d.ts +18 -0
  139. package/dist/runtime/types.d.ts.map +1 -1
  140. package/dist/setup/setup-renderer.d.ts +3 -4
  141. package/dist/setup/setup-renderer.d.ts.map +1 -1
  142. package/dist/setup/setup-renderer.js +3 -4
  143. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  144. package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
  145. package/dist/world3d-react/engine-bridge.js +29 -4
  146. package/dist/world3d-react/index.d.ts +1 -1
  147. package/dist/world3d-react/index.js +1 -1
  148. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  149. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  150. package/dist/world3d-react/r3f-adapter.js +132 -28
  151. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  152. package/dist/world3d-react/r3f-root-factory.js +14 -0
  153. package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
  154. package/dist/world3d-react/rapier-physics-bridge.js +3 -3
  155. package/dist/world3d-react/world-context.d.ts +52 -3
  156. package/dist/world3d-react/world-context.d.ts.map +1 -1
  157. package/dist/world3d-react/world-context.js +94 -64
  158. package/dist-config/config.js.map +1 -1
  159. package/package.json +1 -1
  160. package/schemas/engine-capabilities.json +8 -8
  161. package/schemas/vgai-project.schema.json +1 -6
  162. package/src/adapter/adapter-module.ts +405 -72
  163. package/src/adapter/authoring-seam-contract.ts +211 -0
  164. package/src/adapter/authoring.ts +242 -50
  165. package/src/adapter/entrypoint-selection-source.ts +159 -0
  166. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
  167. package/src/adapter/first-party-systems.ts +17 -3
  168. package/src/adapter/index.ts +51 -1
  169. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  170. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  171. package/src/adapter/ingest/game-contract.ts +9 -4
  172. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  173. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  174. package/src/adapter/ingest/scene-capture.ts +126 -23
  175. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  176. package/src/adapter/native-debug-module.ts +307 -0
  177. package/src/adapter/object3d-authoring-subject.ts +50 -0
  178. package/src/adapter/root-adapter.ts +25 -56
  179. package/src/adapter/root-seam-contract.ts +57 -0
  180. package/src/adapter/seam-evidence.ts +336 -0
  181. package/src/adapter/setup-three-root-adapter.ts +13 -15
  182. package/src/adapter/system-adapter.ts +6 -1
  183. package/src/adapter/system-seam-contract.ts +121 -0
  184. package/src/animation/xstate-animation-binding.ts +151 -4
  185. package/src/canvas-react/engine-bridge.ts +6 -6
  186. package/src/canvas-react/index.ts +2 -1
  187. package/src/canvas-react/pixi-react-adapter.tsx +143 -77
  188. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  189. package/src/canvas-react/world-context.ts +26 -101
  190. package/src/data/curve.ts +2 -2
  191. package/src/dev/instruments.ts +4 -4
  192. package/src/dev/register-render-vitals.ts +2 -2
  193. package/src/dev/static-batch-advisor.ts +2 -2
  194. package/src/ecs/user-data.ts +5 -0
  195. package/src/input/input-manager.ts +145 -0
  196. package/src/manifest/load.ts +0 -7
  197. package/src/manifest/schema.ts +2 -25
  198. package/src/pixi/authoring.ts +329 -2
  199. package/src/pixi/ingest.ts +9 -2
  200. package/src/pixi/scene-capture.ts +20 -5
  201. package/src/react/world-state.tsx +15 -0
  202. package/src/render/particles-factory.ts +16 -6
  203. package/src/render/soft-particle-depth.ts +8 -13
  204. package/src/runtime/create-runtime.ts +79 -158
  205. package/src/runtime/dev-build.ts +49 -0
  206. package/src/runtime/game.ts +72 -113
  207. package/src/runtime/mount-game.ts +19 -1
  208. package/src/runtime/mount-manifest.ts +5 -37
  209. package/src/runtime/types.ts +22 -0
  210. package/src/setup/setup-renderer.ts +3 -4
  211. package/src/world3d-react/engine-bridge.ts +29 -4
  212. package/src/world3d-react/index.ts +1 -1
  213. package/src/world3d-react/r3f-adapter.tsx +140 -31
  214. package/src/world3d-react/r3f-root-factory.tsx +14 -0
  215. package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
  216. package/src/world3d-react/world-context.ts +111 -87
  217. package/dist/runtime/dev-layers.d.ts +0 -34
  218. package/dist/runtime/dev-layers.d.ts.map +0 -1
  219. package/dist/runtime/dev-layers.js +0 -46
  220. package/src/runtime/dev-layers.ts +0 -46
@@ -11,7 +11,8 @@
11
11
  * the ONE game-scoped registry (`getDebugRegistry(host.game)`), so a
12
12
  * component's effect can `ctx.debug.registerStateProvider(...)` and be
13
13
  * visible to `vgai eval`/the editor's debug panels.
14
- * 2. **`ctx.input` is the game-owned `InputManager`** (`host.game.input` the
14
+ * 2. **`ctx.input` is the game-owned `InputManager` when a Game hosts the
15
+ * root** (`host.game.input` — the
15
16
  * same instance every sibling root shares), and this module registers the
16
17
  * per-world debug-registry seams (`setVirtualInputTarget`/
17
18
  * `setInputActionsSource`/`setInputTraceSource`) that make `game.input.*`
@@ -19,13 +20,14 @@
19
20
  * `DEBUG_INPUT_UNAVAILABLE`. The project's input map loads through
20
21
  * `Game.loadInputMap` (the game-owned, load-once path every root shares).
21
22
  * A missing/unparseable map degrades LOUDLY without failing the mount — a
22
- * canvas world with no declared actions is legal.
23
- * 3. **Absent subsystems fail loudly at the ctx surface.** A canvas world
24
- * builds no THREE scene/camera, no Rapier world, no composer, no first-party
25
- * audio/particles/debug-draw, and is handed no `AssetCache` (the canvas host
26
- * context carries none — see `adapter/host-context.ts`). Reading those
27
- * `ctx` fields yields a throwing getter naming the limitation and the
28
- * sanctioned alternative, never a silent `undefined`.
23
+ * canvas world with no declared actions is legal. A bare/foreign mount
24
+ * receives `null`, visibly in the type, rather than a throwing getter.
25
+ * 3. **Absent subsystems are absent from the type.** A canvas world builds no
26
+ * THREE scene/camera, Rapier world, composer, first-party audio/particles/
27
+ * debug-draw, or Three-flavoured `AssetCache`. Its explicit
28
+ * {@link CanvasGameContext} cannot name those fields, so misuse fails at
29
+ * the call site during typechecking instead of from a throwing getter at
30
+ * runtime.
29
31
  *
30
32
  * The Pixi handles themselves are deliberately NOT re-exposed here: a component
31
33
  * reaches the live `Application` (and therefore `app.stage`, `app.renderer`,
@@ -42,6 +44,7 @@ import {
42
44
  DEFAULT_SEEDED_RANDOM_SEED,
43
45
  getSeededRandom,
44
46
  } from '../core/seeded-random';
47
+ import type { SimClock } from '../core/sim-clock';
45
48
  import { createSimClock, getSimClock, type SimClockInternal } from '../core/sim-clock';
46
49
  import { createSystemRunner } from '../core/system-runner';
47
50
  import {
@@ -49,7 +52,7 @@ import {
49
52
  type DebugRegistry,
50
53
  getDebugRegistry,
51
54
  } from '../runtime/debug-registry';
52
- import type { GameContext } from '../runtime/types';
55
+ import type { ComponentContextBase } from '../runtime/types';
53
56
 
54
57
  /** The conventional input-map location every scaffolded project ships. Shared
55
58
  * verbatim with the three lane — a project has ONE input map, whatever surface
@@ -70,12 +73,20 @@ export interface CanvasRootContextOptions {
70
73
  readonly inputMapPath?: string | null | undefined;
71
74
  }
72
75
 
76
+ /** The engine services a Canvas component genuinely receives. Pixi's own
77
+ * `useApplication()` and `Assets` remain the doors to rendering and assets.
78
+ * `disposeAfter` is excluded because it accepts a `THREE.Object3D`; Canvas
79
+ * code schedules `displayObject.destroy()` with `clock.after()` instead. */
80
+ export type CanvasGameContext = ComponentContextBase & {
81
+ readonly clock: Omit<SimClock, 'disposeAfter'>;
82
+ };
83
+
73
84
  /** What {@link createCanvasRootContext} returns — the engine runtime one canvas
74
85
  * world owns, plus the wiring hooks the adapter's `mount()` drives. */
75
86
  export interface CanvasRootRuntime {
76
- /** The (partial, loud-on-absence) `GameContext` this world's components read
77
- * through `useGameContext`. See this module's header for what is present. */
78
- readonly ctx: GameContext;
87
+ /** The explicit Canvas context this world's components read through
88
+ * `useGameContext`. See {@link CanvasGameContext}. */
89
+ readonly ctx: CanvasGameContext;
79
90
  /** The world-local phase runner `mount().update(dt)` ticks. */
80
91
  readonly systems: ReturnType<typeof createSystemRunner>;
81
92
  /** The mounted world's `SystemAdapters` bag (`mounted.systems`) —
@@ -95,32 +106,6 @@ export interface CanvasRootRuntime {
95
106
  dispose(): void;
96
107
  }
97
108
 
98
- /** Install a throwing getter for a `GameContext` field this world genuinely
99
- * does not build — a component reaching for it fails LOUDLY at its own call
100
- * site, naming the limitation, instead of reading `undefined` and breaking
101
- * somewhere downstream. Non-enumerable so spreads/serialization of the ctx
102
- * never trip it. */
103
- function defineAbsentCtxField(
104
- target: Record<string, unknown>,
105
- worldId: string,
106
- key: string,
107
- why: string,
108
- ): void {
109
- Object.defineProperty(target, key, {
110
- configurable: true,
111
- enumerable: false,
112
- get(): never {
113
- throw new Error(
114
- `canvas world "${worldId}": ctx.${key} is not available — ${why} ` +
115
- '(A canvas world ctx provides: input, systems, clock, debug, random, game, roots, ' +
116
- "registerSystemAdapter. The Pixi runtime itself comes from @pixi/react's " +
117
- 'useApplication() — app.stage, app.renderer, app.ticker — and assets from Pixi`s own ' +
118
- 'Assets.)',
119
- );
120
- },
121
- });
122
- }
123
-
124
109
  /**
125
110
  * Build the engine runtime for one canvas world from a `CanvasHostContext`.
126
111
  * React-free on purpose: everything here is plain engine wiring the adapter
@@ -145,13 +130,14 @@ export function createCanvasRootContext(
145
130
 
146
131
  const ctxRaw: Record<string, unknown> = {
147
132
  systems,
133
+ input: host.game?.input ?? null,
148
134
  registerSystemAdapter: (kind: keyof SystemAdapters, adapter: SystemAdapters[typeof kind]) => {
149
135
  // biome-ignore lint/suspicious/noExplicitAny: same per-key record write the three lane performs; correct by construction
150
136
  (systemAdapters as any)[kind] = adapter;
151
137
  host.game?.notifySystemAdaptersChanged();
152
138
  },
153
139
  };
154
- const ctx = ctxRaw as unknown as GameContext;
140
+ const ctx = ctxRaw as unknown as CanvasGameContext;
155
141
  if (host.game) {
156
142
  ctx.game = host.game;
157
143
  ctx.roots = host.game.roots;
@@ -188,7 +174,7 @@ export function createCanvasRootContext(
188
174
  );
189
175
  },
190
176
  });
191
- ctx.clock = clock;
177
+ ctxRaw['clock'] = clock;
192
178
 
193
179
  // --- ctx.input + the debug-registry input seams — the game-owned
194
180
  // InputManager, polled by the game-scoped runner each frame; this world
@@ -198,7 +184,6 @@ export function createCanvasRootContext(
198
184
  if (host.game) {
199
185
  const game = host.game;
200
186
  const input = game.input;
201
- ctxRaw['input'] = input;
202
187
  debugRegistry.setInputActionsSource(id, () =>
203
188
  input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
204
189
  );
@@ -239,68 +224,8 @@ export function createCanvasRootContext(
239
224
  );
240
225
  });
241
226
  }
242
- } else {
243
- defineAbsentCtxField(
244
- ctxRaw,
245
- id,
246
- 'input',
247
- 'input is game-owned and this mount has no Game host (host.game is absent — bare ' +
248
- 'harness/foreign host); mount through the vgai host to get the shared InputManager.',
249
- );
250
227
  }
251
228
 
252
- // --- Loud absence for everything a canvas world genuinely does not build. ---
253
- const threeWhy =
254
- 'a canvas world renders through PixiJS and owns no THREE scene graph; the world root is ' +
255
- "the Pixi stage, reached with @pixi/react's useApplication().app.stage.";
256
- defineAbsentCtxField(ctxRaw, id, 'scene', threeWhy);
257
- defineAbsentCtxField(ctxRaw, id, 'camera', threeWhy);
258
- defineAbsentCtxField(
259
- ctxRaw,
260
- id,
261
- 'sceneIndex',
262
- 'the live scene index walks a THREE.Scene; a canvas world has none (walk the Pixi display ' +
263
- 'tree from useApplication().app.stage instead).',
264
- );
265
- const rapierWhy =
266
- 'canvas worlds do not build the first-party Rapier runtime; drive physics from inside the ' +
267
- 'tree with the 2D physics library of your choice.';
268
- defineAbsentCtxField(ctxRaw, id, 'rapierWorld', rapierWhy);
269
- defineAbsentCtxField(ctxRaw, id, 'rapier', rapierWhy);
270
- defineAbsentCtxField(ctxRaw, id, 'physics', rapierWhy);
271
- defineAbsentCtxField(ctxRaw, id, 'collisions', rapierWhy);
272
- defineAbsentCtxField(
273
- ctxRaw,
274
- id,
275
- 'assets',
276
- 'the canvas host context carries no AssetCache (that cache is a GLTF/texture loader ' +
277
- "resolving THREE objects); load textures/spritesheets with Pixi's own `Assets`.",
278
- );
279
- defineAbsentCtxField(
280
- ctxRaw,
281
- id,
282
- 'composer',
283
- 'a canvas world has no postprocessing EffectComposer; use Pixi filters on a Container.',
284
- );
285
- defineAbsentCtxField(
286
- ctxRaw,
287
- id,
288
- 'audio',
289
- 'canvas worlds build no first-party audio context; use Web Audio directly (or @pixi/sound).',
290
- );
291
- defineAbsentCtxField(
292
- ctxRaw,
293
- id,
294
- 'particles',
295
- 'canvas worlds build no three.quarks particle runtime; use a Pixi particle container.',
296
- );
297
- defineAbsentCtxField(
298
- ctxRaw,
299
- id,
300
- 'debugDraw',
301
- 'canvas worlds build no debug-draw helper; draw into a Pixi `Graphics` in the tree.',
302
- );
303
-
304
229
  return {
305
230
  ctx,
306
231
  systems,
package/src/data/curve.ts CHANGED
@@ -59,7 +59,7 @@ export interface CurveAxes {
59
59
  y: { min: number; max: number; label?: string };
60
60
  }
61
61
 
62
- /** How the marker rides out to the emitted JSON Schema — the editor's and the dev menu's detection key, the exact shape `dataRef` uses with `x-vgai-ref`. */
62
+ /** How the marker rides out to the emitted JSON Schema — the editor's detection key, the exact shape `dataRef` uses with `x-vgai-ref`. */
63
63
  const CURVE_META_KEY = 'x-vgai-curve';
64
64
 
65
65
  function axisLabel(axes: CurveAxes, axis: 'x' | 'y'): string {
@@ -155,7 +155,7 @@ export function curve(axes: CurveAxes): z.ZodType<CurveValue> {
155
155
  * Read a curve field's declared axes back off a LIVE Zod schema (as opposed to
156
156
  * the emitted JSON Schema, which the editor's Data panel reads instead).
157
157
  *
158
- * This is the dev-menu capability's detection door: it derives its rows by
158
+ * This is the dev-tools capability's detection door: it derives its rows by
159
159
  * walking the game's own `TuningSchema` object, so it needs the axes from the
160
160
  * schema in hand. Wrappers (`.optional()`, `.default(…)`, `.describe(…)` chains
161
161
  * that re-wrap) hide the metadata behind an inner type, so a bounded unwrap
@@ -25,11 +25,11 @@
25
25
  * game registry.
26
26
  *
27
27
  * ── THE DOOR ────────────────────────────────────────────────────────────────
28
- * This module is IMPLEMENTATION ONLY. It publishes nothing itself: the dev-menu
29
- * capability's own registry adopts this list (`installDevMenuInstruments`) and
28
+ * This module is IMPLEMENTATION ONLY. It publishes nothing itself: the dev-tools
29
+ * capability's own registry adopts this list (`installDevToolsInstruments`) and
30
30
  * publishes each instrument through the SAME door every declaration in that
31
31
  * capability uses — one provider per reading (`instrument.timescale`), one
32
- * command per action (`instrument.timescale.set`), plus the `dev-menu` index
32
+ * command per action (`instrument.timescale.set`), plus the `dev-tools` index
33
33
  * provider. That is deliberate and load-bearing: the registry IS the product,
34
34
  * so every reader of an instrument reaches the SAME registration.
35
35
  *
@@ -43,7 +43,7 @@
43
43
  * ── RESOURCE OWNERSHIP, STATED ONCE ─────────────────────────────────────────
44
44
  * {@link createDevInstruments} owns everything it allocates: the frame-time
45
45
  * ring buffer and its ONE `game.onRenderStep` subscription. SHARER: the
46
- * dev-menu capability's `DevMenuBridge`, which creates one set per mounted
46
+ * dev-tools capability's `DevToolsBridge`, which creates one set per mounted
47
47
  * game. TEARDOWN: the returned {@link DevInstrumentSet.dispose} — the ONE path
48
48
  * that ends it. Nothing else here holds a resource; the time-scale, pause and
49
49
  * collider instruments only drive state that `Game` already owns.
@@ -36,8 +36,8 @@
36
36
  * the drawing buffer halves was never fill-bound.
37
37
  *
38
38
  * ── GATING ──────────────────────────────────────────────────────────────────
39
- * The caller gates. Both seeders call this only under `devLayersEnabled()`
40
- * (`runtime/dev-layers.ts` — the ONE owner of "is this a dev context"), so a
39
+ * The caller gates. Both seeders call this only under `devBuildEnabled()`
40
+ * (`runtime/dev-build.ts` — the ONE owner of "is this a dev context"), so a
41
41
  * ship build registers nothing and pays nothing. Each also requires a `Game`
42
42
  * shell (there is no profiler to fold without one) and a non-headless mount
43
43
  * (nothing brackets a submission, so the readings could only ever be empty).
@@ -6,7 +6,7 @@
6
6
  * require someone to ASK, and both require that someone to already know that
7
7
  * static batching exists, is possible here, and is spelled `<Frozen>`. An
8
8
  * agent building a game does not know any of that, so the measurement has to
9
- * do the routing itself — the same idiom as the dev-menu's unconfigured-section
9
+ * do the routing itself — the same idiom as dev-tools' unconfigured-section
10
10
  * warning: the reading names the exact edit.
11
11
  *
12
12
  * ── THE DECISION IS PURE, THE SCHEDULE IS NOT ───────────────────────────────
@@ -146,7 +146,7 @@ export function decideStaticBatchAdvisory(
146
146
  * Once per PAGE, not once per module evaluation — a hot reload re-runs this
147
147
  * module, and an advisory that reappears on every save is one that gets muted
148
148
  * along with everything else on the console. Same mechanism, and the same
149
- * reason, as the dev menu's unconfigured-section warning.
149
+ * reason, as dev-tools' unconfigured-section warning.
150
150
  */
151
151
  const WARNED_KEY = '__vgaiStaticBatchAdvised';
152
152
 
@@ -111,6 +111,8 @@
111
111
  * JSX props for renderer/editor integration.
112
112
  * - `constraints` — live project-owned spatial constraints projected
113
113
  * into the shared Inspector and viewport.
114
+ * - `authoringSubject` — transient identity/inspection for an ecosystem-native
115
+ * subject represented by an Object3D proxy.
114
116
  * - `splineControlPoint` — index of a spline control-point drag handle.
115
117
  * - `vcDirIdx` — view-cube face direction index (0=+X,1=-X,2=+Y,...).
116
118
  */
@@ -118,6 +120,7 @@
118
120
  import type * as THREE from 'three';
119
121
  import type { ParticleSystem } from 'three.quarks';
120
122
  import type { ConstraintMark } from '../adapter/constraint';
123
+ import type { Object3DAuthoringSubjectMark } from '../adapter/object3d-authoring-subject';
121
124
  import type { RapierEditableBody } from '../adapter/rapier-physics-adapter';
122
125
  import type { ReflectionProbeMark } from '../adapter/reflection-probe';
123
126
  import type { AnimationRuntimeInspection } from '../animation/runtime-inspection';
@@ -197,6 +200,7 @@ export interface UserDataSchema {
197
200
  skeletonEnabled: boolean;
198
201
  envObject: boolean;
199
202
  constraints: readonly ConstraintMark[];
203
+ authoringSubject: Object3DAuthoringSubjectMark;
200
204
  reflectionProbe: ReflectionProbeMark;
201
205
  vcDirIdx: number;
202
206
  /** W1a navmesh path-probe preview polyline: the routed waypoints, mirrored
@@ -252,6 +256,7 @@ export const UserDataKeys = {
252
256
  skeletonEnabled: 'skeletonEnabled',
253
257
  envObject: 'envObject',
254
258
  constraints: 'constraints',
259
+ authoringSubject: 'authoringSubject',
255
260
  reflectionProbe: 'reflectionProbe',
256
261
  vcDirIdx: 'vcDirIdx',
257
262
  navPathPoints: 'navPathPoints',
@@ -310,6 +310,9 @@ export class InputManager {
310
310
  private mouseButtonsJustUp = new Set<number>();
311
311
  private mouseDeltaX = 0;
312
312
  private mouseDeltaY = 0;
313
+ /** Last `mousemove` client position. Device-poll only — action maps read delta, not this. */
314
+ private mouseClientX = 0;
315
+ private mouseClientY = 0;
313
316
  private gamepads: (Gamepad | null)[] = [];
314
317
  // Gamepad button edge tracking (P1.6c). Keys are `${gamepadIndex}:${buttonIndex}`.
315
318
  // `Prev` is last frame's pressed set; `Down` is this frame's; the just-down/up
@@ -568,6 +571,8 @@ export class InputManager {
568
571
  if (e.movementX !== 0 || e.movementY !== 0) this.lastActiveDevice = 'mouse';
569
572
  this.mouseDeltaX += e.movementX;
570
573
  this.mouseDeltaY += e.movementY;
574
+ this.mouseClientX = e.clientX;
575
+ this.mouseClientY = e.clientY;
571
576
  };
572
577
 
573
578
  /** F2 — real window blur: drop focus and flush held REAL-device state
@@ -674,10 +679,32 @@ export class InputManager {
674
679
  * data reaches `isPressed`/`isJustPressed`/`isJustReleased`.
675
680
  */
676
681
  async loadMap(url: string) {
682
+ await this.fetchMap(url, false);
683
+ }
684
+
685
+ /**
686
+ * `loadMap`'s OPTIONAL sibling: apply the map at `url` when the project
687
+ * ships one and report `false` — SILENTLY — when it does not exist.
688
+ *
689
+ * The host loads the conventional map path at mount for every three/canvas
690
+ * root, including the scaffolded shape that has declared no actions yet, so
691
+ * "no map on disk" is the ordinary state of a brand-new project and must not
692
+ * print an error on every boot. Everything else stays exactly as loud as
693
+ * `loadMap`: a map that EXISTS and is malformed (or any non-404 failure)
694
+ * still logs and throws.
695
+ */
696
+ async loadMapIfPresent(url: string): Promise<boolean> {
697
+ return this.fetchMap(url, true);
698
+ }
699
+
700
+ /** Shared fetch/validate/apply for {@link loadMap} and
701
+ * {@link loadMapIfPresent}; `optional` decides only what a 404 means. */
702
+ private async fetchMap(url: string, optional: boolean): Promise<boolean> {
677
703
  let data: InputMapFile;
678
704
  try {
679
705
  const res = await fetch(resolveUrl(url));
680
706
  if (!res.ok) {
707
+ if (optional && res.status === 404) return false;
681
708
  throw new Error(`HTTP ${res.status} ${res.statusText}`);
682
709
  }
683
710
  const json = await res.json();
@@ -692,6 +719,7 @@ export class InputManager {
692
719
  throw err instanceof AssetParseError ? err : new Error(message);
693
720
  }
694
721
  this.applyParsedMap(data);
722
+ return true;
695
723
  }
696
724
 
697
725
  /**
@@ -1690,6 +1718,99 @@ export class InputManager {
1690
1718
  return { x: this.mouseDeltaX, y: this.mouseDeltaY };
1691
1719
  }
1692
1720
 
1721
+ /**
1722
+ * Device-poll door (not an action binding). Every getter checks
1723
+ * {@link inputActive} at READ time so a poll while play is off or the
1724
+ * tab is unfocused is inert — the same predicate `onKeyDown` / `onMouseMove`
1725
+ * already use. Inventing an implicit `registerAction` per key would define
1726
+ * bindings the project's own `.inputmap` never named.
1727
+ */
1728
+ isDeviceKeyPressed(code: string): boolean {
1729
+ if (!this.inputActive) return false;
1730
+ return this.keysDown.has(code);
1731
+ }
1732
+
1733
+ isDeviceKeyJustPressed(code: string): boolean {
1734
+ if (!this.inputActive) return false;
1735
+ return this.keysJustDown.has(code);
1736
+ }
1737
+
1738
+ isDeviceKeyJustReleased(code: string): boolean {
1739
+ if (!this.inputActive) return false;
1740
+ return this.keysJustUp.has(code);
1741
+ }
1742
+
1743
+ /** `button` is the DOM `MouseEvent.button` index (0 left, 1 middle, 2 right). */
1744
+ isDeviceMouseButtonPressed(button: number): boolean {
1745
+ if (!this.inputActive) return false;
1746
+ return this.mouseButtons.has(button);
1747
+ }
1748
+
1749
+ isDeviceMouseButtonJustPressed(button: number): boolean {
1750
+ if (!this.inputActive) return false;
1751
+ return this.mouseButtonsJustDown.has(button);
1752
+ }
1753
+
1754
+ isDeviceMouseButtonJustReleased(button: number): boolean {
1755
+ if (!this.inputActive) return false;
1756
+ return this.mouseButtonsJustUp.has(button);
1757
+ }
1758
+
1759
+ /**
1760
+ * This-frame mouse delta in DOM space (Y +down). Callers that need Unity
1761
+ * `Pointer.delta` (Y-up) flip Y themselves.
1762
+ */
1763
+ getDeviceMouseDelta(): { x: number; y: number } {
1764
+ if (!this.inputActive) return { x: 0, y: 0 };
1765
+ return { x: this.mouseDeltaX, y: this.mouseDeltaY };
1766
+ }
1767
+
1768
+ /** Last `mousemove` client position in DOM space (origin top-left, Y +down). */
1769
+ getDeviceMousePosition(): { x: number; y: number } {
1770
+ if (!this.inputActive) return { x: 0, y: 0 };
1771
+ return { x: this.mouseClientX, y: this.mouseClientY };
1772
+ }
1773
+
1774
+ hasDeviceGamepad(): boolean {
1775
+ if (!this.inputActive) return false;
1776
+ return this.gamepads.some((pad) => pad !== null);
1777
+ }
1778
+
1779
+ isDeviceGamepadButtonPressed(button: number): boolean {
1780
+ if (!this.inputActive) return false;
1781
+ const index = this.firstConnectedGamepadIndex();
1782
+ if (index < 0) return false;
1783
+ return this.gamepadButtonsDown.has(`${index}:${button}`);
1784
+ }
1785
+
1786
+ isDeviceGamepadButtonJustPressed(button: number): boolean {
1787
+ if (!this.inputActive) return false;
1788
+ const index = this.firstConnectedGamepadIndex();
1789
+ if (index < 0) return false;
1790
+ return this.gamepadButtonsJustDown.has(`${index}:${button}`);
1791
+ }
1792
+
1793
+ isDeviceGamepadButtonJustReleased(button: number): boolean {
1794
+ if (!this.inputActive) return false;
1795
+ const index = this.firstConnectedGamepadIndex();
1796
+ if (index < 0) return false;
1797
+ return this.gamepadButtonsJustUp.has(`${index}:${button}`);
1798
+ }
1799
+
1800
+ getDeviceGamepadAxis(axis: number): number {
1801
+ if (!this.inputActive) return 0;
1802
+ const index = this.firstConnectedGamepadIndex();
1803
+ if (index < 0) return 0;
1804
+ return this.gamepads[index]?.axes[axis] ?? 0;
1805
+ }
1806
+
1807
+ private firstConnectedGamepadIndex(): number {
1808
+ for (let i = 0; i < this.gamepads.length; i++) {
1809
+ if (this.gamepads[i] !== null) return i;
1810
+ }
1811
+ return -1;
1812
+ }
1813
+
1693
1814
  /**
1694
1815
  * F4 (spec §12 "Schema and Examples" — the F3 prompt-switching gap): which
1695
1816
  * device family most recently produced real input, or `null` before any
@@ -2295,6 +2416,30 @@ export class InputManager {
2295
2416
  element.addEventListener('click', this.onPointerLockClick);
2296
2417
  }
2297
2418
 
2419
+ /**
2420
+ * Release pointer lock on the element {@link requestPointerLock} wired.
2421
+ *
2422
+ * Godot's `Input.mouse_mode = MOUSE_MODE_VISIBLE` is this call: drop the
2423
+ * lock if this manager holds it, and unwire the click-to-lock listener so a
2424
+ * later click does not recapture. Idempotent. A host with no
2425
+ * `document.exitPointerLock` (headless) still unwires — the lock never
2426
+ * existed there.
2427
+ *
2428
+ * Only the lock THIS manager requested is released. An editor viewport
2429
+ * holding lock on a different element is not ours to drop.
2430
+ */
2431
+ exitPointerLock() {
2432
+ const el = this.pointerLockElement;
2433
+ if (el) {
2434
+ el.removeEventListener('click', this.onPointerLockClick);
2435
+ this.pointerLockElement = null;
2436
+ }
2437
+ this.pointerLocked = false;
2438
+ const doc = typeof document !== 'undefined' ? document : undefined;
2439
+ if (!doc || typeof doc.exitPointerLock !== 'function') return;
2440
+ if (el && doc.pointerLockElement === el) doc.exitPointerLock();
2441
+ }
2442
+
2298
2443
  /** Clean up event listeners */
2299
2444
  dispose() {
2300
2445
  if (this.disposed) return;
@@ -79,9 +79,6 @@ export interface ResolvedAdapterRoot {
79
79
  readonly entry: string | undefined;
80
80
  readonly zOrder: number;
81
81
  readonly pausable: boolean;
82
- /** `dev: true` — this root is a DEV LAYER, not shipped game content. See
83
- * `manifest/schema.ts`'s `dev` field for the four readers. */
84
- readonly dev: boolean;
85
82
  readonly loop: 'gated' | 'self-driven';
86
83
  }
87
84
 
@@ -256,9 +253,6 @@ function checkEngineVersionPin(version: string): void {
256
253
  * resolution; `entry` drives the entry cross-field rule and is
257
254
  * carried through; `zOrder` drives the final sort; `pausable`/`loop` are
258
255
  * carried through as-is (their consumers are the runtime/CLI, T3.2/T3.3);
259
- * `dev` is carried through for the four dev-layer readers (`mountManifestRoots`'s
260
- * ship gate, the host's topmost stacking pass, the play compositor's capture
261
- * exclusion, and the editor's chrome-not-content seam);
262
256
  * `description` (T3.3
263
257
  * slice 3) is carried through as-is — its consumer today is
264
258
  * `resolveIngestDescriptor` (packages/editor/src/adapter-resolver.ts), which
@@ -276,7 +270,6 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
276
270
  entry: root.entry,
277
271
  zOrder: root.zOrder,
278
272
  pausable: root.pausable,
279
- dev: root.dev,
280
273
  loop: root.loop,
281
274
  };
282
275
  }
@@ -315,19 +315,6 @@ export const AdapterRootSchema = z
315
315
  .boolean()
316
316
  .default(true)
317
317
  .describe('Whether play-mode pause/step applies to this root'),
318
- dev: z
319
- .boolean()
320
- .default(false)
321
- .describe(
322
- "Marks this root as a DEV LAYER: the game's own dev GUI, not shipped game content. " +
323
- 'Four readers act on it. `mountManifestRoots` mounts it only when dev layers are ' +
324
- 'enabled (`devLayersEnabled`, runtime/dev-layers.ts), so a production build carries ' +
325
- 'no dev GUI; the host stacks every dev root ABOVE every non-dev root (a dev layer ' +
326
- 'sits topmost, and as a DOM layer it steals no input from the game while closed); ' +
327
- 'the play compositor EXCLUDES dev layers from a capture unless the capture asks for ' +
328
- 'them; and the editor mounts a dev root as edit-time CHROME rather than authorable ' +
329
- 'content (no hierarchy entry, no OID stamping, no JSX write-back, not selectable).',
330
- ),
331
318
  loop: z
332
319
  .enum(['gated', 'self-driven'])
333
320
  .default('gated')
@@ -475,17 +462,9 @@ export const GameManifestSchema = z
475
462
  // root): a second root of the same medium parsed cleanly and was then
476
463
  // silently never mounted, which is the failure mode this rejects.
477
464
  //
478
- // DEV LAYERS ARE COUNTED SEPARATELY. A `dev: true` root is not content
479
- // competing for the medium — it is the game's dev GUI stacked above
480
- // everything (`create-runtime.ts`'s stacking pass), and the editor
481
- // mounts it as chrome, not as the one dom document. Counting it with
482
- // the content roots would make the ratified shape — a game with a HUD
483
- // and a dev panel, both `dom` — unrepresentable. The one-per-medium
484
- // rule still applies WITHIN the dev band, for the same
485
- // silently-never-mounted reason.
486
465
  const idsByMedium = new Map<string, string[]>();
487
466
  for (const root of roots) {
488
- const medium = `${root.dev ? 'dev ' : ''}${rootMedium(root)}`;
467
+ const medium = rootMedium(root);
489
468
  const ids = idsByMedium.get(medium);
490
469
  if (ids) ids.push(root.id);
491
470
  else idsByMedium.set(medium, [root.id]);
@@ -507,9 +486,7 @@ export const GameManifestSchema = z
507
486
  "The Game's explicit, non-empty adapter-root composition. There is no implicit root. " +
508
487
  'Root ids are unique, and a project declares at most ONE world root per medium — ' +
509
488
  'one `three`, one `dom`, one canvas-surface root (first-party canvas, canvas ingest ' +
510
- 'and canvas module are the same medium). A `dev: true` root is counted in its own ' +
511
- 'band: a dev layer is chrome stacked above the game, not content competing for the ' +
512
- 'medium, so a game may declare a `dom` HUD and a `dom` dev layer — but not two of either.',
489
+ 'and canvas module are the same medium).',
513
490
  ),
514
491
  authoring: z
515
492
  .object({