@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
@@ -14,12 +14,11 @@ import { installRenderControlHarness, isRenderModeRequested } from './render-con
14
14
  * by `createGameRuntime` and every headless test harness
15
15
  * (`test/game-root.test.ts`, `test/frame-order.test.ts`,
16
16
  * `test/game-two-roots.test.ts`) so there is exactly one code path for this
17
- * wiring (T7.1 slice 1, id-generalized in slice 3 for the two-world proof — stage
18
- * 3) — `physics`/`collisions`/`camera`/ `frame` are populated from the mount's
17
+ * wiring. `physics`/`collisions`/`camera`/`frame` are populated from the mount's
19
18
  * first-party `GameContext`/ `MountedSetupThreeRoot` when available, left
20
19
  * `undefined` otherwise (an external adapter's mount has none of these
21
20
  * first-party handles — `frame` in particular is what makes
22
- * `GameInternal.runFrame` (T7.1 slice 2) fall back to a single opaque
21
+ * `GameInternal.runFrame` fall back to a single opaque
23
22
  * `mounted.update` call per substep for such a world).
24
23
  */
25
24
  function firstPartyExtras(mounted) {
@@ -34,10 +33,8 @@ function firstPartyExtras(mounted) {
34
33
  }
35
34
  /**
36
35
  * Register a three world onto the Game shell. `opts.id` defaults to
37
- * `'main'` — the single-world callers (`createGameRuntime`, slice-1/2 tests)
38
- * are unaffected; a second/third world (T7.1 slice 3's two-world proof, and
39
- * real multi-world manifests later) pass an explicit id. This is the ONE
40
- * registration code path for a three world, whatever its id.
36
+ * `'main'`; multi-root callers pass an explicit id. This is the one
37
+ * registration path for a three world, whatever its id.
41
38
  */
42
39
  export function registerThreeRoot(game, adapter, mounted, opts) {
43
40
  const world = createRootInstance({
@@ -73,13 +70,13 @@ export function registerPixiRoot(game, adapter, mounted, opts) {
73
70
  return world;
74
71
  }
75
72
  /**
76
- * Register a react world onto the Game shell (T6.2 slice 1) — the react
73
+ * Register a React world onto the Game shell — the DOM
77
74
  * analog of {@link registerThreeRoot}/{@link registerPixiRoot}. A react
78
75
  * world has no `frame` hooks (react's own
79
76
  * `createRoot` schedules its commits; `GameInternal.runFrame` correctly
80
77
  * leaves a world with no `frame` untouched by its opaque-`update` fallback
81
78
  * too, since `MountedReactGame` declares no `update`). `adapter` is typed
82
- * as the real {@link ReactRootAdapter} shape (T7.5, same reasoning as
79
+ * as the real {@link ReactRootAdapter} shape (same reasoning as
83
80
  * `registerPixiRoot`'s doc comment above) — `RootInstance.adapter` only
84
81
  * needs `.id`, so this passes through with zero cast.
85
82
  */
@@ -106,7 +103,7 @@ export async function createGameRuntime(config) {
106
103
  return createRootsGameRuntime(config);
107
104
  }
108
105
  // ---------------------------------------------------------------------------
109
- // Worlds path (T6.1 slice 1)
106
+ // Universal root host
110
107
  // ---------------------------------------------------------------------------
111
108
  /** A stand-in `THREE.WebGLRenderer` for headless (`headless:true`) three
112
109
  * roots — mirrors `SetupThreeRootAdapter.mount`'s own `headlessComposer`
@@ -162,11 +159,8 @@ async function mountOneThreeRoot(spec, ctx) {
162
159
  // resize-staleness (the roots-path `resize()` below always calls
163
160
  // `setSize(rw, rh, false)`, so nothing else was ever going to update this
164
161
  // canvas' CSS after mount). `createHostRenderer`'s own default
165
- // (`updateStyle:true`) is intentionally left alone the LEGACY
166
- // single-canvas path (editor play-mode's non-multi-world branch) still
167
- // relies on that construction-time stamp + its own later `updateStyle:true`
168
- // resizes for byte-identical behavior; this fix touches only the
169
- // roots-path canvas, after the fact.
162
+ // (`updateStyle:true`) is intentionally left alone; the host owns CSS layout
163
+ // and reasserts the container-relative size after renderer construction.
170
164
  canvas.style.width = '100%';
171
165
  canvas.style.height = '100%';
172
166
  renderer.setPixelRatio(dpr);
@@ -239,7 +233,7 @@ async function mountOnePixiRoot(spec, ctx) {
239
233
  };
240
234
  }
241
235
  /**
242
- * Mount one react `RootMountSpec` (T6.2 slice 1) — the react sibling of
236
+ * Mount one React `RootMountSpec` — the DOM sibling of
243
237
  * `mountOneThreeRoot`/`mountOnePixiRoot`. Unlike its canvas-backed siblings
244
238
  * this returns NO `routerEntry`: a react world's DOM-root layer participates in
245
239
  * D5's z-order/box stacking (the caller still creates and positions its `<div>`
@@ -269,7 +263,7 @@ layer) {
269
263
  // An absolutely-positioned layer with no width/height collapses to zero
270
264
  // content size, so a child's
271
265
  // own position:absolute offsets resolve against a degenerate containing
272
- // block (found by the T6.2 slice-3 e2e — clicks landed outside the game).
266
+ // block; otherwise clicks land outside the game.
273
267
  layer.style.width = '100%';
274
268
  layer.style.height = '100%';
275
269
  const reactHost = { container: layer, game };
@@ -289,8 +283,8 @@ layer) {
289
283
  };
290
284
  }
291
285
  /**
292
- * The roots-path implementer behind {@link createGameRuntime} (T6.1 slice
293
- * 1; react roots added T6.2 slice 1). Builds ONE surface per world — a
286
+ * The universal host implementer behind {@link createGameRuntime}. It builds
287
+ * one surface per world — a
294
288
  * canvas for three/canvas, a DOM-root `<div>` layer for react — stacked
295
289
  * per D5 §1, z-order/ties exactly matching
296
290
  * `manifest/load.ts`'s sort, ONE `Game`, and registers every world onto it
@@ -303,16 +297,11 @@ layer) {
303
297
  */
304
298
  /**
305
299
  * Dev/e2e-only `window.__vgaiScene`/`__vgaiCamera` exposure for the roots
306
- * path's DEFAULT world (E4) — split out of `createRootsGameRuntime` purely
307
- * to keep that function's own cyclomatic complexity down. Mirrors the
308
- * legacy single-world path's identical exposure (above, in this same file),
309
- * using the SAME "first three world, else none" default-world rule the
310
- * `GameSession.scene`/`.camera` getters alias (`Game.defaultRoot`): a
311
- * non-threejs default world (or none at all) publishes neither global,
312
- * exactly like those getters return `undefined` in that case. Returns a
313
- * retraction callback — identity-guarded (a newer session's globals must
314
- * never be clobbered by an older session's stop, same reason the legacy
315
- * path's own cleanup guards it) — that is a no-op when nothing was
300
+ * path's default world — split out of `createRootsGameRuntime` purely
301
+ * to keep that function's own cyclomatic complexity down. It uses the
302
+ * "first three world, else none" rule: a non-Three default world publishes
303
+ * neither global. Returns an identity-guarded retraction callback so an
304
+ * older session's stop cannot clobber a newer session's globals. It is a no-op when nothing was
316
305
  * published (non-DEV build, or non-threejs default world).
317
306
  */
318
307
  function installDefaultRootDevGlobals(game) {
@@ -451,26 +440,15 @@ async function createRootsGameRuntime(config) {
451
440
  // --- Surface stack (D5 §1): DOM/z-order follows zOrder, ties -> array order
452
441
  // — computed FIRST (bottom -> top) so both the z-index assignment below and
453
442
  // the router's default-claim rule share one definition. A react world's
454
- // DOM-root layer shares this SAME stacking pass (T6.2 slice 1 "one
455
- // stacking model, no special case") even though it is a `<div>`, not a
443
+ // DOM-root layer shares this SAME stacking pass even though it is a `<div>`, not a
456
444
  // canvas, and carries no `hitTest` (the router never sees react entries at
457
445
  // all — see the dispatch loop below). ---
458
- // Dev layers sit TOPMOST, above every non-dev root, whatever zOrder either
459
- // declares: a dev GUI the game can paint over is not a dev GUI. Expressed as
460
- // two independent `stackOrder` passes concatenated rather than a magic
461
- // zOrder value, so `stackOrder`'s one rule (ascending zOrder, ties -> array
462
- // order) still governs WITHIN each band and nothing has to reason about what
463
- // number is "high enough".
464
446
  const claimEntries = mountSpecs.map((spec) => ({
465
447
  id: spec.id,
466
448
  zOrder: spec.zOrder ?? 0,
467
449
  hitTest: spec.hitTest,
468
- dev: spec.dev === true,
469
450
  }));
470
- const stacked = [
471
- ...stackOrder(claimEntries.filter((e) => !e.dev)),
472
- ...stackOrder(claimEntries.filter((e) => e.dev)),
473
- ];
451
+ const stacked = stackOrder(claimEntries);
474
452
  const bottomId = stacked[0]?.id;
475
453
  const kindById = new Map(mountSpecs.map((spec) => [spec.id, spec.kind]));
476
454
  const surfacesById = new Map();
@@ -485,12 +463,6 @@ async function createRootsGameRuntime(config) {
485
463
  // surface.ts`, which is the one door the capture/staleness/screenshot
486
464
  // sites now ask (ARCHITECTURE-CORE §The editor protocol, zero inference).
487
465
  surface.dataset['vgaiRootId'] = entry.id;
488
- // The capture's marker (`composite-screenshot.ts` reads exactly this
489
- // attribute): a dev layer is excluded from a play capture unless the
490
- // capture asks for it, because a contaminated "does the game look right"
491
- // frame is worthless.
492
- if (entry.dev)
493
- surface.dataset['vgaiDevLayer'] = 'true';
494
466
  if (!isReact) {
495
467
  const canvas = surface;
496
468
  canvas.width = w;
@@ -509,7 +481,7 @@ async function createRootsGameRuntime(config) {
509
481
  // too (not just in `mountOneThreeRoot`) means every surface, whatever
510
482
  // kind, starts container-relative from its very first paint, before any
511
483
  // per-kind mount work has even run.
512
- // `contain:layout paint` makes each surface the CONTAINING BLOCK for
484
+ // `contain:layout paint` makes each surface the containing block for
513
485
  // `position:fixed` descendants (and clips overflow to the world's
514
486
  // rectangle): full-screen game UI written the natural way (`fixed;
515
487
  // inset:0`) then fills the WORLD, not the page. Without it, `fixed` UI
@@ -579,17 +551,7 @@ async function createRootsGameRuntime(config) {
579
551
  const router = createInputRouter(container, routerEntries);
580
552
  started = true;
581
553
  loop.start();
582
- // Expose scene & camera for dev tools / e2e tests — mirrors the legacy
583
- // single-world path's identical dev-only exposure above, generalized to
584
- // the roots path's DEFAULT world (E4): `mountGameFromManifest`/
585
- // `mountManifestRoots` route every caller (including a
586
- // single-threejs-world scaffold project) through THIS path, so a caller
587
- // migrating off the legacy `{canvas, adapter}` call must not silently
588
- // lose `window.__vgaiScene`/`__vgaiCamera` — real e2e/dev tooling
589
- // depends on them
590
- // (`packages/editor/e2e/tests/04-standalone-game.spec.ts`). Split into
591
- // its own top-level helper (with its retraction counterpart below)
592
- // purely to keep this function's own cyclomatic complexity down.
554
+ // Expose the default Three root's scene and camera for dev/e2e tooling.
593
555
  const retractDevGlobals = installDefaultRootDevGlobals(game);
594
556
  // G3/FT-11 — publish `window.__vgaiRender` for the deterministic capture
595
557
  // host. Installed at the TAIL of the mount (same position as
@@ -599,39 +561,48 @@ async function createRootsGameRuntime(config) {
599
561
  const retractRenderHarness = renderMode && renderModeLocation !== undefined
600
562
  ? installSessionRenderHarness(game, renderModeLocation, config.renderControl?.target)
601
563
  : undefined;
564
+ let resolveStopComplete;
565
+ const stopComplete = new Promise((resolve) => {
566
+ resolveStopComplete = resolve;
567
+ });
568
+ let stopping = false;
602
569
  function fullCleanup() {
603
- loop.stop();
604
- router.dispose();
605
- for (const entry of mountedEntries) {
606
- entry.mounted.dispose();
607
- entry.renderer?.dispose();
608
- entry.renderer?.forceContextLoss();
609
- // Remove from the element's CURRENT parent, not the mount-time
610
- // `container`: the editor's Game panel re-parents the live surfaces
611
- // when its mount element swaps (fill <-> device preset, W2c), and
612
- // `container.removeChild` would throw NotFoundError after such a move.
613
- // `container` remains the fallback for hosts whose element stand-ins
614
- // never wire `parentNode` (headless unit fixtures); an element already
615
- // detached by such a host is a no-op via the catch.
616
- try {
617
- (entry.element.parentNode ?? container).removeChild(entry.element);
618
- }
619
- catch {
620
- /* already detached — nothing to remove */
570
+ if (stopping)
571
+ return;
572
+ stopping = true;
573
+ try {
574
+ loop.stop();
575
+ router.dispose();
576
+ for (const entry of mountedEntries) {
577
+ entry.mounted.dispose();
578
+ entry.renderer?.dispose();
579
+ entry.renderer?.forceContextLoss();
580
+ // Remove from the element's CURRENT parent, not the mount-time
581
+ // `container`: the editor's Game panel re-parents the live surfaces
582
+ // when its mount element swaps (fill <-> device preset, W2c), and
583
+ // `container.removeChild` would throw NotFoundError after such a move.
584
+ // `container` remains the fallback for hosts whose element stand-ins
585
+ // never wire `parentNode` (headless unit fixtures); an element already
586
+ // detached by such a host is a no-op via the catch.
587
+ try {
588
+ (entry.element.parentNode ?? container).removeChild(entry.element);
589
+ }
590
+ catch {
591
+ /* already detached — nothing to remove */
592
+ }
621
593
  }
594
+ game.dispose();
595
+ retractDevGlobals();
596
+ retractRenderHarness?.();
597
+ }
598
+ finally {
599
+ void Promise.allSettled(mountedEntries.map((entry) => entry.mounted.disposeComplete ?? Promise.resolve())).then(() => resolveStopComplete());
622
600
  }
623
- game.dispose();
624
- retractDevGlobals();
625
- retractRenderHarness?.();
626
601
  }
627
602
  return {
628
603
  stop: fullCleanup,
629
- // D10/T7.6: `Game.play` fans out per-world pausable/loop-gate/audio-gate
630
- // semantics itself now (see the legacy path's identical comment above) —
631
- // a `pausable: false` world (a menu/HUD world) keeps ticking while every
632
- // other world freezes, which the old blind `for (const entry of
633
- // mountedEntries) entry.mounted.setPaused?.(true)` fan-out (with no
634
- // `pausable` check at all) could never express.
604
+ stopComplete,
605
+ // `Game.play` owns per-world pause, loop-gate, and audio-gate semantics.
635
606
  pause() {
636
607
  game.play.pause();
637
608
  },
@@ -655,24 +626,6 @@ async function createRootsGameRuntime(config) {
655
626
  entry.mounted.resize?.(safeWidth, safeHeight);
656
627
  }
657
628
  },
658
- // `GameSession.scene`/`.camera`/`.mounted` alias `Game.defaultRoot` (T6.1
659
- // slice 1's "first three world, else first world" rule) — a legacy
660
- // convenience shaped for the threejs-only past. T7.5 narrows the read via
661
- // the `kind` discriminant instead of a blind `.scene`/`.camera` cast
662
- // through a nonexistent property (identical behavior to before: still
663
- // `undefined` for a roots-path session whose default world isn't
664
- // three — real per-world surface routing for that case is T7.6's).
665
- get scene() {
666
- const m = game.defaultRoot.mounted;
667
- return (m.kind === 'three' ? m.scene : undefined);
668
- },
669
- get camera() {
670
- const m = game.defaultRoot.mounted;
671
- return (m.kind === 'three' ? m.camera : undefined);
672
- },
673
- get mounted() {
674
- return game.defaultRoot.mounted;
675
- },
676
629
  game,
677
630
  };
678
631
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * THE one owner of "is this a development context". Every reader that has to
3
+ * answer that question calls {@link devBuildEnabled}; there is deliberately NO
4
+ * second source of truth — not a module-level cached boolean, not a
5
+ * `globalThis` flag, not a per-host copy of the `import.meta.env` read below.
6
+ * Instrumentation that ships to players because two places disagreed about
7
+ * what "dev" means is exactly the failure this single owner exists to make
8
+ * impossible.
9
+ *
10
+ * Ownership, stated in one place (the build rule):
11
+ * - OWNER: this function. It resolves the answer; nothing else derives it.
12
+ * - SHARERS: the two three-root adapters (`adapter/setup-three-root-adapter.ts`
13
+ * and `world3d-react/r3f-adapter.tsx`), which seed live render vitals only
14
+ * under it, and the `static-batch` capability's mutation watch and `Frozen`
15
+ * warning. Any future dev-only instrument calls this too, with its own
16
+ * `override`.
17
+ * - TEARDOWN: none. This is a pure predicate over build config and one
18
+ * caller-supplied argument — it owns no resource, allocates nothing, and
19
+ * has no lifecycle to end.
20
+ *
21
+ * The three inputs, highest precedence first:
22
+ * 1. `override` — the explicit per-call answer. A headless test, a capture
23
+ * harness, or a host that knows better passes `true`/`false` and gets
24
+ * exactly that. Passing `undefined` (or omitting it) means "decide for
25
+ * me" and falls through. The editor's own preview mount is the worked
26
+ * case: it is a dev session by definition even when the editor SPA it
27
+ * runs inside is a production build.
28
+ * 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case.
29
+ * 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_BUILD=true`.
30
+ * Instrumenting a production bundle is a real, legitimate choice (an
31
+ * internal playtest build, a QA build), and it must be an opt-in someone
32
+ * had to type, never something a default drifts into.
33
+ *
34
+ * Anything else — a production build with no opt-in — is `false`.
35
+ */
36
+ export declare function devBuildEnabled(override?: boolean | undefined): boolean;
37
+ //# sourceMappingURL=dev-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-build.d.ts","sourceRoot":"","sources":["../../src/runtime/dev-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAavE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * THE one owner of "is this a development context". Every reader that has to
3
+ * answer that question calls {@link devBuildEnabled}; there is deliberately NO
4
+ * second source of truth — not a module-level cached boolean, not a
5
+ * `globalThis` flag, not a per-host copy of the `import.meta.env` read below.
6
+ * Instrumentation that ships to players because two places disagreed about
7
+ * what "dev" means is exactly the failure this single owner exists to make
8
+ * impossible.
9
+ *
10
+ * Ownership, stated in one place (the build rule):
11
+ * - OWNER: this function. It resolves the answer; nothing else derives it.
12
+ * - SHARERS: the two three-root adapters (`adapter/setup-three-root-adapter.ts`
13
+ * and `world3d-react/r3f-adapter.tsx`), which seed live render vitals only
14
+ * under it, and the `static-batch` capability's mutation watch and `Frozen`
15
+ * warning. Any future dev-only instrument calls this too, with its own
16
+ * `override`.
17
+ * - TEARDOWN: none. This is a pure predicate over build config and one
18
+ * caller-supplied argument — it owns no resource, allocates nothing, and
19
+ * has no lifecycle to end.
20
+ *
21
+ * The three inputs, highest precedence first:
22
+ * 1. `override` — the explicit per-call answer. A headless test, a capture
23
+ * harness, or a host that knows better passes `true`/`false` and gets
24
+ * exactly that. Passing `undefined` (or omitting it) means "decide for
25
+ * me" and falls through. The editor's own preview mount is the worked
26
+ * case: it is a dev session by definition even when the editor SPA it
27
+ * runs inside is a production build.
28
+ * 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case.
29
+ * 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_BUILD=true`.
30
+ * Instrumenting a production bundle is a real, legitimate choice (an
31
+ * internal playtest build, a QA build), and it must be an opt-in someone
32
+ * had to type, never something a default drifts into.
33
+ *
34
+ * Anything else — a production build with no opt-in — is `false`.
35
+ */
36
+ export function devBuildEnabled(override) {
37
+ if (override !== undefined)
38
+ return override;
39
+ // `import.meta` is cast whole, not just its `.env`: this module is reachable
40
+ // from programs whose tsconfig does not pull in `vite/client` (the session
41
+ // client's, for one, which reaches the three adapter transitively), and there
42
+ // `ImportMeta` has no declared `env` at all. The cast keeps the single owner
43
+ // of the dev answer importable from ANY program rather than forcing every
44
+ // downstream tsconfig to adopt Vite's ambient types.
45
+ const env = import.meta.env;
46
+ if (env?.DEV === true)
47
+ return true;
48
+ return env?.VITE_VGAI_DEV_BUILD === 'true';
49
+ }
@@ -1,22 +1,8 @@
1
1
  /**
2
- * Game root T7.1 slice 1 ("introduce Game internally") + slice 2 ("world
3
- * registry + ordered frame algorithm"), extended in T7.3 slice 1 with real
4
- * `'canvas'`-kind `RootInstance` support (`stage`/`physics2d`, a real
5
- * `pixiStage()`) so a canvas adapter can register its world onto a real `Game`
6
- * instead of driving its own loop, and in T7.3 slice 2 with a
7
- * construction-time stage check (symmetric with the three/scene check).
8
- * T6.2 slice 1 adds real `'react'`-kind
9
- * `RootInstance` support (`container`, a real `reactRoot()`) — the DOM-root
10
- * world surface `runtime/create-runtime.ts`'s roots path now mounts, with
11
- * `physics`/`collisions`/`camera`/`frame` `undefined` for them exactly
12
- * like an opaque/foreign mount.
13
- *
14
- * (D6), particularly §4 (the frame algorithm) and §8 stage 2, for the full
15
- * target shape. This file holds the Game/ RootInstance shell, the NEW
16
- * game-scoped `SystemRunner`, and the host-facing `runFrame` frame executor.
17
- * The public surface is NOT frozen yet — do not add these exports to a
18
- * barrel/index; only the in-scope runtime/adapter files import this module
19
- * directly. The `gpu` per-world cache is the one remaining slice-1 omission.
2
+ * The surface-neutral Game root and its declaration-ordered root registry.
3
+ * Three, Canvas, and DOM roots share one game-scoped loop, input manager,
4
+ * system runner, state bridge, debug registry, clock, and seeded RNG. Each
5
+ * RootInstance retains its own mounted surface and optional capabilities.
20
6
  */
21
7
  import type * as PIXI from 'pixi.js';
22
8
  import type * as THREE from 'three';
@@ -33,7 +19,6 @@ import { InputManager } from '../input/input-manager';
33
19
  import type { CollisionSystem } from '../physics/collision-system';
34
20
  import type { PhysicsRegistry } from '../physics/physics-registry';
35
21
  import type { Physics2DRegistry } from '../pixi/physics-registry';
36
- import type { AudioContext as GameAudio } from '../setup/setup-audio';
37
22
  import { type RunTicksOptions } from './debug-registry';
38
23
  import { type GameStateBridge } from './state-bridge';
39
24
  import type { GameContext, PlaytestContext } from './types';
@@ -54,10 +39,7 @@ export type GameLoop = ReturnType<typeof createGameLoop>;
54
39
  * `runFrame`. `GameLoop.timeScale` (unaffected by this) remains the orthogonal
55
40
  * "speed up/slow down" axis — pausing never touches it, so the host's
56
41
  * accumulator/rAF loop keeps ticking at its normal cadence, which is what makes
57
- * "paused still renders" possible (the previous `GameSession.pause()`
58
- * implementation set `loop.timeScale = 0`, which starves the accumulator — and,
59
- * before WO-P13, stopped rendering with it; that was the bug this control
60
- * surface replaces, not a compatible behavior to keep).
42
+ * "paused still renders" possible.
61
43
  */
62
44
  export interface PlayState {
63
45
  /** Whether the game is currently paused (game-level — see the per-world
@@ -95,11 +77,9 @@ export interface PlayState {
95
77
  }
96
78
  /**
97
79
  * The kinds of render surface a world can be. `'three'` and `'canvas'`
98
- * roots are both real; `'react'` is named
99
- * here so the type is already shaped for T7.4 and no caller has to widen a
100
- * union later.
80
+ * roots are canvas-backed; `'dom'` roots own a DOM layer.
101
81
  *
102
- * Re-exported from `adapter/adapter-surface.ts` (T7.5) — moved there so
82
+ * Re-exported from `adapter/adapter-surface.ts` so
103
83
  * `adapter/root-adapter.ts`'s kind-tagged `MountedRoot` types can name it
104
84
  * without an import cycle back to this file. This re-export keeps every
105
85
  * existing `import type { AdapterSurface } from '../runtime/game'` call site
@@ -107,7 +87,7 @@ export interface PlayState {
107
87
  */
108
88
  export type AdapterSurface = AdapterSurfaceLeaf;
109
89
  /**
110
- * A world's per-phase frame hooks (T7.1 slice 2).
90
+ * A world's per-phase frame hooks.
111
91
  * Populated on a `RootInstance` only for first-party mounts — an opaque/
112
92
  * foreign mount has no phase-partitioned entry point, so it stays
113
93
  * `undefined` and the Game's frame executor (`GameInternal.runFrame`) falls
@@ -116,7 +96,7 @@ export type AdapterSurface = AdapterSurfaceLeaf;
116
96
  export interface RootFrameHooks {
117
97
  /** Run this world's engine systems + component ticks + world-bound game
118
98
  * systems for one phase. For a first-party world this delegates to the
119
- * SAME `SystemRunner.runPhase` its (legacy) `mounted.update` uses. */
99
+ * same `SystemRunner.runPhase` as its direct `mounted.update` entry. */
120
100
  runPhase(phase: SystemPhaseName, dt: number): void;
121
101
  /** Run once per substep, after ALL phases have run for ALL roots this
122
102
  * substep (mirrors where `mounted.update`'s post-`systems.run` work sat
@@ -126,14 +106,12 @@ export interface RootFrameHooks {
126
106
  }
127
107
  /**
128
108
  * A single world instance: the unit of adaptation.
129
- * Slice-1 subset — `gpu` (per-world GPU resource cache, §6.2) is omitted
130
- * until the slice that builds it.
131
109
  */
132
110
  export interface RootInstance {
133
- /** Manifest id (T3.1). Slice 1 always registers exactly one: `'main'`. */
111
+ /** Manifest root id. */
134
112
  readonly id: string;
135
113
  readonly kind: AdapterSurface;
136
- /** Per-world play/pause semantics (D10, T7.6). Slice 1 always `true`. */
114
+ /** Per-world play/pause semantics. */
137
115
  readonly pausable: boolean;
138
116
  /** The RootAdapter that produced `mounted` — first-party or external.
139
117
  * Deliberately narrower than `RootAdapter<K>` (T7.5): this field is only
@@ -157,7 +135,7 @@ export interface RootInstance {
157
135
  * descriptively for a non-pixijs world, or a canvas world built without
158
136
  * a `stage` (see `RootInstanceInit.stage`). */
159
137
  pixiStage(): PIXI.Container;
160
- /** Kind-narrowed accessor for react roots (T6.2 slice 1): returns the
138
+ /** Kind-narrowed accessor for DOM roots: returns the
161
139
  * DOM-root layer `<div>` the host mounted this world's react tree into
162
140
  * (the SAME element passed as `container` to `createRootInstance` —
163
141
  * identity matters, mirroring `threeScene()`/`pixiStage()`'s "same
@@ -176,7 +154,7 @@ export interface RootInstance {
176
154
  readonly physics2d?: Physics2DRegistry | undefined;
177
155
  /** Kind-typed via `mounted` in T7.5; `unknown` here deliberately. */
178
156
  readonly camera?: unknown;
179
- /** Phase-partitioned frame entry point (T7.1 slice 2) — present only for
157
+ /** Phase-partitioned frame entry point — present only for
180
158
  * first-party mounts. `undefined` for an opaque/foreign mount, which
181
159
  * `GameInternal.runFrame` drives via its single `mounted.update` call
182
160
  * instead (unless it `drivesOwnLoop`, in which case it isn't ticked at
@@ -229,8 +207,8 @@ export declare function createRootInstance(init: RootInstanceInit): RootInstance
229
207
  /**
230
208
  * Type guard for whether a `MountedThreeRoot` is a first-party
231
209
  * `SetupThreeRootAdapter` mount (has a live `GameContext` at `.ctx`). Used to
232
- * decide whether a world's `physics`/`collisions`/`camera` (and, on `Game`,
233
- * `input`/`audio`) can be populated from it — an external
210
+ * decide whether a world's `physics`/`collisions`/`camera` can be populated
211
+ * from it — an external
234
212
  * adapter's mount has none of these first-party handles.
235
213
  *
236
214
  * Checks the `firstParty: true` brand (checklist item 1), NOT `'ctx' in
@@ -275,13 +253,9 @@ export interface DebrisOwner {
275
253
  */
276
254
  export declare function disposeDebrisSubtree(obj: THREE.Object3D, owners: ReadonlyArray<DebrisOwner>): void;
277
255
  /**
278
- * The Game root. Owns the one loop, the raw-asset
279
- * cache, the world registry, and (T7.1 slice 2) the game-scoped
280
- * `SystemRunner`. `input`/`audio` remain slice-1 late
281
- * additions: they delegate to the default world's first-party mount so
282
- * existing single-world call sites keep working; hoisting them to true
283
- * Game ownership (the target shape) is a later slice's
284
- * work, not this one's.
256
+ * The Game root. Owns the loop, raw-asset cache, world registry, input,
257
+ * and game-scoped `SystemRunner`. Surface-specific capabilities remain on
258
+ * their mounted roots or in the aggregated `SystemAdapters` contract.
285
259
  */
286
260
  export interface Game {
287
261
  readonly loop: GameLoop;
@@ -291,18 +265,16 @@ export interface Game {
291
265
  /** Per-game diagnostic store. Disabled by default; the editor enables it on demand. */
292
266
  readonly profiler: PerformanceProfiler;
293
267
  /**
294
- * The game-scoped `SystemRunner` (T7.1 slice 2) a
295
- * NEW bucket, separate from any world's own runner. Within each phase,
268
+ * The game-scoped `SystemRunner`, separate from any world's own runner.
269
+ * Within each phase,
296
270
  * `GameInternal.runFrame` runs THIS runner's `runPhase` first, before any
297
271
  * world's engine systems/component ticks/world-bound game systems (e.g.
298
272
  * `ctx.systems.add`, which stays world-bound to the default world — see
299
273
  * `runtime/types.ts`). Empty for every existing game (nothing registers
300
- * against it yet), so `runFrame`'s behavior for a single-world game is
301
- * unchanged by its presence.
274
+ * against it), so an empty runner has no frame cost beyond dispatch.
302
275
  */
303
276
  readonly systems: SystemRunner;
304
- /** Declaration-ordered. Slice 1 registers exactly one (the default
305
- * three world) — this is the SAME array reference `registerRoot`
277
+ /** Declaration-ordered. This is the same array reference `registerRoot`
306
278
  * mutates, not a snapshot, so holders (e.g. `GameContext.roots`) observe
307
279
  * later registrations. */
308
280
  readonly roots: ReadonlyArray<RootInstance>;
@@ -338,9 +310,6 @@ export interface Game {
338
310
  subscribeSystemAdapters?(listener: () => void): () => void;
339
311
  /** The one game-owned `InputManager`, shared by every first-party root. */
340
312
  readonly input: InputManager;
341
- /** Delegates to the default world's first-party audio context. Throws
342
- * when the default world is not a first-party mount. */
343
- readonly audio: GameAudio;
344
313
  /**
345
314
  * Frame-versioned state bridge (T7.4 slice 1). Bumped once per completed
346
315
  * `runFrame`, after all phases of all roots and all `endFrame` hooks (see
@@ -421,28 +390,18 @@ export interface GameInternal extends Game {
421
390
  * else: world.mounted.update?.(dt) // opaque world fallback
422
391
  * ```
423
392
  *
424
- * For today's single first-party world this is byte-identical to the
425
- * legacy `mounted.update(dt)` (`game.systems` is empty; the one world's
426
- * `frame.runPhase` delegates to the SAME `SystemRunner.runPhase` its
427
- * `update` used; `endFrame` is the same `postFrame` call). A
428
- * `drivesOwnLoop` world is never ticked here at all — matching its
429
- * exclusion from the legacy `!mountedRef.drivesOwnLoop` guard. An opaque
430
- * host-driven world (no `frame`) gets exactly one `update(dt)` call per
431
- * substep, after the phase loop — unchanged cadence from today.
393
+ * A single first-party world and its direct `mounted.update(dt)` entry run
394
+ * the same `SystemRunner` and `postFrame` work. A `drivesOwnLoop` world is
395
+ * never ticked here. An opaque host-driven world (no `frame`) gets exactly
396
+ * one `update(dt)` call per substep, after the phase loop.
432
397
  *
433
398
  * D10/T7.6 play-state addendum: when `Game.play.paused` is true, every
434
399
  * `pausable` (and non-`drivesOwnLoop`) world skips every phase EXCEPT
435
400
  * `render` (still called, every substep, with `dt` forced to `0`) and skips
436
401
  * its `endFrame`/opaque-`update` call entirely — a `pausable: false` world
437
- * is completely unaffected. `opts.ignorePause` runs this call as if nothing
438
- * were paused, regardless of the live `paused` flag; kept for external
439
- * byte-compatibility (no first-party caller passes it today grep finds
440
- * none) but is no longer how `Game.play.step()` works (§7.1-2 fix,
441
- * probe4: the old `ignorePause` full-frame re-run double-ticked every
442
- * already-running `pausable: false` world, since D10's ordinary loop never
443
- * stops ticking them). `Game.play.step()` now drives this function via the
444
- * internal-only `onlyFrozen` mode instead (see `runFrameImpl` — not part of
445
- * this public, host-facing signature): it ticks EXACTLY the currently
402
+ * is completely unaffected. `Game.play.step()` drives this function via the
403
+ * internal-only `onlyFrozen` mode (see `runFrameImpl` not part of this
404
+ * public, host-facing signature): it ticks exactly the currently
446
405
  * frozen set (host-driven, `pausable`, and `paused`) through every phase +
447
406
  * `endFrame` with the real `dt` (not the render-phase's forced `0`), and
448
407
  * touches no other world at all — a natural no-op while not paused, since
@@ -459,7 +418,6 @@ export interface GameInternal extends Game {
459
418
  * `runTicks` sets it per tick for its `render: 'none' | 'last'` modes.
460
419
  */
461
420
  runFrame(dt: number, opts?: {
462
- ignorePause?: boolean;
463
421
  skipRenderPhases?: boolean;
464
422
  }): void;
465
423
  /**
@@ -546,8 +504,16 @@ export interface GameInternal extends Game {
546
504
  * the conventional `/inputmaps/default.inputmap.json` (or the adapter's
547
505
  * configured `inputMapPath`); runtime additions go through
548
506
  * `ctx.input.registerAction` instead.
507
+ *
508
+ * `{ optional: true }` is the CONVENTIONAL-PATH probe a root uses when the
509
+ * project never named a map: an absent file resolves quietly (a brand-new
510
+ * scaffold declares no actions yet), pins nothing — so a sibling root may
511
+ * still declare its own map — and yields to a map another root already
512
+ * owns. A map that exists but fails to parse is as loud either way.
549
513
  */
550
- loadInputMap(path: string): Promise<void>;
514
+ loadInputMap(path: string, options?: {
515
+ optional?: boolean;
516
+ }): Promise<void>;
551
517
  /** Release game-owned resources after every mounted root has disposed. */
552
518
  dispose(): void;
553
519
  }