@vgai/engine 0.5.14 → 0.5.16

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 (177) hide show
  1. package/dist/adapter/adapter-module.d.ts +228 -17
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +224 -42
  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 -1
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
  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 +21 -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 +124 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +36 -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 +2 -3
  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 +43 -29
  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/ecs/user-data.d.ts +5 -0
  87. package/dist/ecs/user-data.d.ts.map +1 -1
  88. package/dist/ecs/user-data.js +3 -0
  89. package/dist/input/input-manager.d.ts +49 -0
  90. package/dist/input/input-manager.d.ts.map +1 -1
  91. package/dist/input/input-manager.js +127 -0
  92. package/dist/manifest/schema.d.ts +9 -9
  93. package/dist/manifest/schema.js +1 -1
  94. package/dist/pixi/authoring.d.ts.map +1 -1
  95. package/dist/pixi/authoring.js +220 -1
  96. package/dist/pixi/ingest.d.ts +2 -1
  97. package/dist/pixi/ingest.d.ts.map +1 -1
  98. package/dist/pixi/ingest.js +4 -2
  99. package/dist/pixi/scene-capture.d.ts +9 -1
  100. package/dist/pixi/scene-capture.d.ts.map +1 -1
  101. package/dist/pixi/scene-capture.js +9 -6
  102. package/dist/render/particles-factory.d.ts +13 -6
  103. package/dist/render/particles-factory.d.ts.map +1 -1
  104. package/dist/render/particles-factory.js +6 -3
  105. package/dist/runtime/create-runtime.d.ts +23 -46
  106. package/dist/runtime/create-runtime.d.ts.map +1 -1
  107. package/dist/runtime/create-runtime.js +22 -67
  108. package/dist/runtime/game.d.ts +29 -71
  109. package/dist/runtime/game.d.ts.map +1 -1
  110. package/dist/runtime/game.js +15 -54
  111. package/dist/runtime/mount-game.d.ts.map +1 -1
  112. package/dist/runtime/mount-game.js +12 -1
  113. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  114. package/dist/runtime/mount-manifest.js +4 -5
  115. package/dist/runtime/types.d.ts +18 -0
  116. package/dist/runtime/types.d.ts.map +1 -1
  117. package/dist/setup/setup-renderer.d.ts +3 -4
  118. package/dist/setup/setup-renderer.d.ts.map +1 -1
  119. package/dist/setup/setup-renderer.js +3 -4
  120. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  121. package/dist/world3d-react/engine-bridge.js +1 -1
  122. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  123. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  124. package/dist/world3d-react/r3f-adapter.js +50 -21
  125. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  126. package/dist/world3d-react/r3f-root-factory.js +4 -0
  127. package/dist/world3d-react/world-context.d.ts +9 -3
  128. package/dist/world3d-react/world-context.d.ts.map +1 -1
  129. package/dist/world3d-react/world-context.js +5 -18
  130. package/package.json +1 -1
  131. package/schemas/engine-capabilities.json +8 -8
  132. package/schemas/vgai-project.schema.json +1 -1
  133. package/src/adapter/adapter-module.ts +407 -50
  134. package/src/adapter/authoring-seam-contract.ts +211 -0
  135. package/src/adapter/authoring.ts +242 -50
  136. package/src/adapter/entrypoint-selection-source.ts +159 -0
  137. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
  138. package/src/adapter/first-party-systems.ts +17 -3
  139. package/src/adapter/index.ts +51 -1
  140. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  141. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  142. package/src/adapter/ingest/game-contract.ts +9 -4
  143. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  144. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  145. package/src/adapter/ingest/scene-capture.ts +126 -23
  146. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  147. package/src/adapter/native-debug-module.ts +307 -0
  148. package/src/adapter/object3d-authoring-subject.ts +50 -0
  149. package/src/adapter/root-adapter.ts +21 -56
  150. package/src/adapter/root-seam-contract.ts +56 -0
  151. package/src/adapter/seam-evidence.ts +336 -0
  152. package/src/adapter/setup-three-root-adapter.ts +4 -6
  153. package/src/adapter/system-adapter.ts +6 -1
  154. package/src/adapter/system-seam-contract.ts +121 -0
  155. package/src/animation/xstate-animation-binding.ts +151 -4
  156. package/src/canvas-react/engine-bridge.ts +6 -6
  157. package/src/canvas-react/index.ts +2 -1
  158. package/src/canvas-react/pixi-react-adapter.tsx +55 -34
  159. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  160. package/src/canvas-react/world-context.ts +26 -101
  161. package/src/ecs/user-data.ts +5 -0
  162. package/src/input/input-manager.ts +122 -0
  163. package/src/manifest/schema.ts +1 -1
  164. package/src/pixi/authoring.ts +236 -1
  165. package/src/pixi/ingest.ts +9 -2
  166. package/src/pixi/scene-capture.ts +20 -5
  167. package/src/render/particles-factory.ts +16 -6
  168. package/src/runtime/create-runtime.ts +41 -106
  169. package/src/runtime/game.ts +41 -111
  170. package/src/runtime/mount-game.ts +19 -1
  171. package/src/runtime/mount-manifest.ts +4 -5
  172. package/src/runtime/types.ts +22 -0
  173. package/src/setup/setup-renderer.ts +3 -4
  174. package/src/world3d-react/engine-bridge.ts +1 -1
  175. package/src/world3d-react/r3f-adapter.tsx +64 -24
  176. package/src/world3d-react/r3f-root-factory.tsx +4 -0
  177. package/src/world3d-react/world-context.ts +11 -36
@@ -1,25 +1,11 @@
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
 
22
- // TYPE-ONLY pixi import (T7.3 slice 1): `game.ts` must never value-import
8
+ // TYPE-ONLY pixi import: `game.ts` must never value-import
23
9
  // `pixi.js` — a value import would pull the Pixi runtime into 3D-only
24
10
  // bundles that never touch a canvas root. Every pixi-typed field below is erased
25
11
  // at compile time; nothing here constructs or calls into Pixi.
@@ -48,7 +34,6 @@ import type { PhysicsRegistry } from '../physics/physics-registry';
48
34
  // TYPE-ONLY (same rule as the pixi import above): this lives under `pixi/`,
49
35
  // but `game.ts` only ever names its TYPE.
50
36
  import type { Physics2DRegistry } from '../pixi/physics-registry';
51
- import type { AudioContext as GameAudio } from '../setup/setup-audio';
52
37
  import {
53
38
  createDebugRegistry,
54
39
  DebugError,
@@ -89,10 +74,7 @@ const DISPLAY_RATE_PHASES: readonly SystemPhaseName[] = PHASE_ORDER.filter(
89
74
  * `runFrame`. `GameLoop.timeScale` (unaffected by this) remains the orthogonal
90
75
  * "speed up/slow down" axis — pausing never touches it, so the host's
91
76
  * accumulator/rAF loop keeps ticking at its normal cadence, which is what makes
92
- * "paused still renders" possible (the previous `GameSession.pause()`
93
- * implementation set `loop.timeScale = 0`, which starves the accumulator — and,
94
- * before WO-P13, stopped rendering with it; that was the bug this control
95
- * surface replaces, not a compatible behavior to keep).
77
+ * "paused still renders" possible.
96
78
  */
97
79
  export interface PlayState {
98
80
  /** Whether the game is currently paused (game-level — see the per-world
@@ -131,11 +113,9 @@ export interface PlayState {
131
113
 
132
114
  /**
133
115
  * The kinds of render surface a world can be. `'three'` and `'canvas'`
134
- * roots are both real; `'react'` is named
135
- * here so the type is already shaped for T7.4 and no caller has to widen a
136
- * union later.
116
+ * roots are canvas-backed; `'dom'` roots own a DOM layer.
137
117
  *
138
- * Re-exported from `adapter/adapter-surface.ts` (T7.5) — moved there so
118
+ * Re-exported from `adapter/adapter-surface.ts` so
139
119
  * `adapter/root-adapter.ts`'s kind-tagged `MountedRoot` types can name it
140
120
  * without an import cycle back to this file. This re-export keeps every
141
121
  * existing `import type { AdapterSurface } from '../runtime/game'` call site
@@ -144,7 +124,7 @@ export interface PlayState {
144
124
  export type AdapterSurface = AdapterSurfaceLeaf;
145
125
 
146
126
  /**
147
- * A world's per-phase frame hooks (T7.1 slice 2).
127
+ * A world's per-phase frame hooks.
148
128
  * Populated on a `RootInstance` only for first-party mounts — an opaque/
149
129
  * foreign mount has no phase-partitioned entry point, so it stays
150
130
  * `undefined` and the Game's frame executor (`GameInternal.runFrame`) falls
@@ -153,7 +133,7 @@ export type AdapterSurface = AdapterSurfaceLeaf;
153
133
  export interface RootFrameHooks {
154
134
  /** Run this world's engine systems + component ticks + world-bound game
155
135
  * systems for one phase. For a first-party world this delegates to the
156
- * SAME `SystemRunner.runPhase` its (legacy) `mounted.update` uses. */
136
+ * same `SystemRunner.runPhase` as its direct `mounted.update` entry. */
157
137
  runPhase(phase: SystemPhaseName, dt: number): void;
158
138
  /** Run once per substep, after ALL phases have run for ALL roots this
159
139
  * substep (mirrors where `mounted.update`'s post-`systems.run` work sat
@@ -164,14 +144,12 @@ export interface RootFrameHooks {
164
144
 
165
145
  /**
166
146
  * A single world instance: the unit of adaptation.
167
- * Slice-1 subset — `gpu` (per-world GPU resource cache, §6.2) is omitted
168
- * until the slice that builds it.
169
147
  */
170
148
  export interface RootInstance {
171
- /** Manifest id (T3.1). Slice 1 always registers exactly one: `'main'`. */
149
+ /** Manifest root id. */
172
150
  readonly id: string;
173
151
  readonly kind: AdapterSurface;
174
- /** Per-world play/pause semantics (D10, T7.6). Slice 1 always `true`. */
152
+ /** Per-world play/pause semantics. */
175
153
  readonly pausable: boolean;
176
154
  /** The RootAdapter that produced `mounted` — first-party or external.
177
155
  * Deliberately narrower than `RootAdapter<K>` (T7.5): this field is only
@@ -195,7 +173,7 @@ export interface RootInstance {
195
173
  * descriptively for a non-pixijs world, or a canvas world built without
196
174
  * a `stage` (see `RootInstanceInit.stage`). */
197
175
  pixiStage(): PIXI.Container;
198
- /** Kind-narrowed accessor for react roots (T6.2 slice 1): returns the
176
+ /** Kind-narrowed accessor for DOM roots: returns the
199
177
  * DOM-root layer `<div>` the host mounted this world's react tree into
200
178
  * (the SAME element passed as `container` to `createRootInstance` —
201
179
  * identity matters, mirroring `threeScene()`/`pixiStage()`'s "same
@@ -214,7 +192,7 @@ export interface RootInstance {
214
192
  readonly physics2d?: Physics2DRegistry | undefined;
215
193
  /** Kind-typed via `mounted` in T7.5; `unknown` here deliberately. */
216
194
  readonly camera?: unknown;
217
- /** Phase-partitioned frame entry point (T7.1 slice 2) — present only for
195
+ /** Phase-partitioned frame entry point — present only for
218
196
  * first-party mounts. `undefined` for an opaque/foreign mount, which
219
197
  * `GameInternal.runFrame` drives via its single `mounted.update` call
220
198
  * instead (unless it `drivesOwnLoop`, in which case it isn't ticked at
@@ -399,8 +377,8 @@ export function createRootInstance(init: RootInstanceInit): RootInstance {
399
377
  /**
400
378
  * Type guard for whether a `MountedThreeRoot` is a first-party
401
379
  * `SetupThreeRootAdapter` mount (has a live `GameContext` at `.ctx`). Used to
402
- * decide whether a world's `physics`/`collisions`/`camera` (and, on `Game`,
403
- * `input`/`audio`) can be populated from it — an external
380
+ * decide whether a world's `physics`/`collisions`/`camera` can be populated
381
+ * from it — an external
404
382
  * adapter's mount has none of these first-party handles.
405
383
  *
406
384
  * Checks the `firstParty: true` brand (checklist item 1), NOT `'ctx' in
@@ -505,13 +483,9 @@ function debrisOwnersOf(world: RootInstance): DebrisOwner[] {
505
483
  }
506
484
 
507
485
  /**
508
- * The Game root. Owns the one loop, the raw-asset
509
- * cache, the world registry, and (T7.1 slice 2) the game-scoped
510
- * `SystemRunner`. `input`/`audio` remain slice-1 late
511
- * additions: they delegate to the default world's first-party mount so
512
- * existing single-world call sites keep working; hoisting them to true
513
- * Game ownership (the target shape) is a later slice's
514
- * work, not this one's.
486
+ * The Game root. Owns the loop, raw-asset cache, world registry, input,
487
+ * and game-scoped `SystemRunner`. Surface-specific capabilities remain on
488
+ * their mounted roots or in the aggregated `SystemAdapters` contract.
515
489
  */
516
490
  export interface Game {
517
491
  readonly loop: GameLoop;
@@ -521,18 +495,16 @@ export interface Game {
521
495
  /** Per-game diagnostic store. Disabled by default; the editor enables it on demand. */
522
496
  readonly profiler: PerformanceProfiler;
523
497
  /**
524
- * The game-scoped `SystemRunner` (T7.1 slice 2) a
525
- * NEW bucket, separate from any world's own runner. Within each phase,
498
+ * The game-scoped `SystemRunner`, separate from any world's own runner.
499
+ * Within each phase,
526
500
  * `GameInternal.runFrame` runs THIS runner's `runPhase` first, before any
527
501
  * world's engine systems/component ticks/world-bound game systems (e.g.
528
502
  * `ctx.systems.add`, which stays world-bound to the default world — see
529
503
  * `runtime/types.ts`). Empty for every existing game (nothing registers
530
- * against it yet), so `runFrame`'s behavior for a single-world game is
531
- * unchanged by its presence.
504
+ * against it), so an empty runner has no frame cost beyond dispatch.
532
505
  */
533
506
  readonly systems: SystemRunner;
534
- /** Declaration-ordered. Slice 1 registers exactly one (the default
535
- * three world) — this is the SAME array reference `registerRoot`
507
+ /** Declaration-ordered. This is the same array reference `registerRoot`
536
508
  * mutates, not a snapshot, so holders (e.g. `GameContext.roots`) observe
537
509
  * later registrations. */
538
510
  readonly roots: ReadonlyArray<RootInstance>;
@@ -568,9 +540,6 @@ export interface Game {
568
540
  subscribeSystemAdapters?(listener: () => void): () => void;
569
541
  /** The one game-owned `InputManager`, shared by every first-party root. */
570
542
  readonly input: InputManager;
571
- /** Delegates to the default world's first-party audio context. Throws
572
- * when the default world is not a first-party mount. */
573
- readonly audio: GameAudio;
574
543
  /**
575
544
  * Frame-versioned state bridge (T7.4 slice 1). Bumped once per completed
576
545
  * `runFrame`, after all phases of all roots and all `endFrame` hooks (see
@@ -653,28 +622,18 @@ export interface GameInternal extends Game {
653
622
  * else: world.mounted.update?.(dt) // opaque world fallback
654
623
  * ```
655
624
  *
656
- * For today's single first-party world this is byte-identical to the
657
- * legacy `mounted.update(dt)` (`game.systems` is empty; the one world's
658
- * `frame.runPhase` delegates to the SAME `SystemRunner.runPhase` its
659
- * `update` used; `endFrame` is the same `postFrame` call). A
660
- * `drivesOwnLoop` world is never ticked here at all — matching its
661
- * exclusion from the legacy `!mountedRef.drivesOwnLoop` guard. An opaque
662
- * host-driven world (no `frame`) gets exactly one `update(dt)` call per
663
- * substep, after the phase loop — unchanged cadence from today.
625
+ * A single first-party world and its direct `mounted.update(dt)` entry run
626
+ * the same `SystemRunner` and `postFrame` work. A `drivesOwnLoop` world is
627
+ * never ticked here. An opaque host-driven world (no `frame`) gets exactly
628
+ * one `update(dt)` call per substep, after the phase loop.
664
629
  *
665
630
  * D10/T7.6 play-state addendum: when `Game.play.paused` is true, every
666
631
  * `pausable` (and non-`drivesOwnLoop`) world skips every phase EXCEPT
667
632
  * `render` (still called, every substep, with `dt` forced to `0`) and skips
668
633
  * its `endFrame`/opaque-`update` call entirely — a `pausable: false` world
669
- * is completely unaffected. `opts.ignorePause` runs this call as if nothing
670
- * were paused, regardless of the live `paused` flag; kept for external
671
- * byte-compatibility (no first-party caller passes it today grep finds
672
- * none) but is no longer how `Game.play.step()` works (§7.1-2 fix,
673
- * probe4: the old `ignorePause` full-frame re-run double-ticked every
674
- * already-running `pausable: false` world, since D10's ordinary loop never
675
- * stops ticking them). `Game.play.step()` now drives this function via the
676
- * internal-only `onlyFrozen` mode instead (see `runFrameImpl` — not part of
677
- * this public, host-facing signature): it ticks EXACTLY the currently
634
+ * is completely unaffected. `Game.play.step()` drives this function via the
635
+ * internal-only `onlyFrozen` mode (see `runFrameImpl` not part of this
636
+ * public, host-facing signature): it ticks exactly the currently
678
637
  * frozen set (host-driven, `pausable`, and `paused`) through every phase +
679
638
  * `endFrame` with the real `dt` (not the render-phase's forced `0`), and
680
639
  * touches no other world at all — a natural no-op while not paused, since
@@ -690,7 +649,7 @@ export interface GameInternal extends Game {
690
649
  * calls `runFrame(fixedDt)` with no opts) is frame-exact and unchanged.
691
650
  * `runTicks` sets it per tick for its `render: 'none' | 'last'` modes.
692
651
  */
693
- runFrame(dt: number, opts?: { ignorePause?: boolean; skipRenderPhases?: boolean }): void;
652
+ runFrame(dt: number, opts?: { skipRenderPhases?: boolean }): void;
694
653
  /**
695
654
  * WO-P13 — run ONE display frame's presentation pass: the registered
696
655
  * `onRenderStep` callbacks, then the `preRender` and `render` phases across
@@ -781,14 +740,6 @@ export interface GameInternal extends Game {
781
740
  dispose(): void;
782
741
  }
783
742
 
784
- function describeMismatch(handle: 'input' | 'audio', world: RootInstance): string {
785
- return (
786
- `Game.${handle}: default world "${world.id}" (kind: ${world.kind}) is not a first-party ` +
787
- "mount — hoisting these to true Game ownership is a later slice's work; available only " +
788
- 'via a first-party default world today'
789
- );
790
- }
791
-
792
743
  /**
793
744
  * Construct the (host-internal) Game shell. Callers: `createGameRuntime`
794
745
  * builds this BEFORE mounting its one adapter, then registers the default
@@ -879,7 +830,7 @@ export function createGame(opts: {
879
830
  getTick: () => tick,
880
831
  getSimT: () => simT,
881
832
  // D15/T-D15.5 — "the manifest's first/default world" for the debug
882
- // registry's world-addressed input-target compatibility surface. Every
833
+ // registry's world-addressed input-target surface. Every
883
834
  // first-party root now registers the SAME game-owned InputManager, but the
884
835
  // stable default id still keeps explicit/implicit debug routing coherent.
885
836
  // Use the SAME "first three world, else first world" rule
@@ -913,14 +864,6 @@ export function createGame(opts: {
913
864
  return roots.find((w) => w.kind === 'three') ?? roots[0]!;
914
865
  }
915
866
 
916
- function requireFirstPartyCtx(handle: 'input' | 'audio') {
917
- const world = requireDefaultRoot();
918
- if (!isFirstPartyMounted(world.mounted)) {
919
- throw new Error(describeMismatch(handle, world));
920
- }
921
- return world.mounted.ctx;
922
- }
923
-
924
867
  // --- Game.systemAdapters aggregation (§7.1-3, probe1) --------------------
925
868
  // Warn-once-per-colliding-key state, scoped to this Game instance (a fresh
926
869
  // Game gets a fresh warn history) — deliberately NOT reset by anything
@@ -1023,7 +966,7 @@ export function createGame(opts: {
1023
966
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the frame algorithm (now with D10's per-world pause gate + the onlyFrozen step()-only mode) is one cohesive nested loop over phases/roots — splitting it would obscure the ordering contract documented on GameInternal.runFrame
1024
967
  function runFrameImpl(
1025
968
  dt: number,
1026
- frameOpts?: { ignorePause?: boolean; onlyFrozen?: boolean; skipRenderPhases?: boolean },
969
+ frameOpts?: { onlyFrozen?: boolean; skipRenderPhases?: boolean },
1027
970
  ): void {
1028
971
  // D15 (T-D15.3) — brackets the ENTIRE frame body (every phase, every
1029
972
  // world, both the `onlyFrozen` and normal branches below converge on the
@@ -1033,7 +976,6 @@ export function createGame(opts: {
1033
976
  // declare `determinism.seededRandom`).
1034
977
  if (rngTrapEnabled) rngTrap.enable();
1035
978
  profiler.beginFrame();
1036
- const ignorePause = frameOpts?.ignorePause ?? false;
1037
979
  // D15/T-D15.4 + WO-P13: the render-phase skip. Two callers set it, for the
1038
980
  // same reason — this substep is not the thing that paints. `runTicks`'s
1039
981
  // `render: 'none'|'last'` fast-forward sets it on every tick it doesn't
@@ -1048,9 +990,6 @@ export function createGame(opts: {
1048
990
  // one and only caller. It ticks EXACTLY the currently-frozen set (host-driven,
1049
991
  // `pausable`, and `paused`) through every phase + `endFrame`, with the REAL
1050
992
  // `dt` (not the render-phase's forced `0`), and touches no other world at all
1051
- // — §7.1-2 fix, probe4: the previous `ignorePause` full-frame re-run ticked
1052
- // EVERY host-driven world, double-ticking every already-running
1053
- // `pausable: false` world (D10's ordinary loop never stops ticking them).
1054
993
  const onlyFrozen = frameOpts?.onlyFrozen ?? false;
1055
994
  // Checklist item 7: snapshot the world count ONCE at entry and iterate
1056
995
  // by index in both loops below. A world registered mid-frame (e.g. from
@@ -1068,10 +1007,9 @@ export function createGame(opts: {
1068
1007
  break;
1069
1008
  }
1070
1009
  } else if (
1071
- ignorePause ||
1072
- (world.mounted.drivesOwnLoop
1010
+ world.mounted.drivesOwnLoop
1073
1011
  ? !paused || !world.pausable || !world.mounted.setPaused
1074
- : !paused || !world.pausable)
1012
+ : !paused || !world.pausable
1075
1013
  ) {
1076
1014
  inputFrameActive = true;
1077
1015
  break;
@@ -1169,13 +1107,12 @@ export function createGame(opts: {
1169
1107
  for (let i = 0; i < n; i++) {
1170
1108
  const world = roots[i]!;
1171
1109
  if (world.mounted.drivesOwnLoop) continue;
1172
- // D10/T7.6: a `pausable` world under an active (non-ignored) pause
1110
+ // D10/T7.6: a `pausable` world under an active pause
1173
1111
  // skips every phase except `render` — its render still runs, every
1174
1112
  // substep, but with `dt` forced to `0` (deterministic: no
1175
1113
  // time-based render effect silently keeps animating a "frozen"
1176
- // scene). A `pausable: false` world (or any world while
1177
- // `ignorePause`) is unaffected.
1178
- const frozen = !ignorePause && paused && world.pausable;
1114
+ // scene). A `pausable: false` world is unaffected.
1115
+ const frozen = paused && world.pausable;
1179
1116
  if (frozen && phase !== SystemPhase.RENDER) continue;
1180
1117
  const phaseDt = frozen ? 0 : dt;
1181
1118
  // Checklist item 2: isolate each world's per-phase work — one
@@ -1199,7 +1136,7 @@ export function createGame(opts: {
1199
1136
  if (world.mounted.drivesOwnLoop) continue;
1200
1137
  // A fully-frozen world gets no `endFrame`/opaque-`update` call either —
1201
1138
  // there is nothing to "end the frame" of when nothing ran this substep.
1202
- const frozen = !ignorePause && paused && world.pausable;
1139
+ const frozen = paused && world.pausable;
1203
1140
  if (frozen) continue;
1204
1141
  advanced = true;
1205
1142
  if (world.frame) {
@@ -1346,9 +1283,6 @@ export function createGame(opts: {
1346
1283
  for (const listener of systemAdapterListeners) listener();
1347
1284
  },
1348
1285
  input,
1349
- get audio() {
1350
- return requireFirstPartyCtx('audio').audio;
1351
- },
1352
1286
  state: stateBridge,
1353
1287
  play: {
1354
1288
  get paused() {
@@ -1387,11 +1321,7 @@ export function createGame(opts: {
1387
1321
  }
1388
1322
  }
1389
1323
  }
1390
- // §7.1-2 fix (probe4): tick EXACTLY the frozen (host-driven, pausable,
1391
- // paused) set — not `{ ignorePause: true }`, which re-ran a FULL
1392
- // extra frame for every host-driven world (including already-running
1393
- // `pausable: false` ones — a double-tick, since D10's ordinary loop
1394
- // never stops ticking them). See `runFrameImpl`'s `onlyFrozen` mode.
1324
+ // Tick exactly the frozen (host-driven, pausable, paused) set.
1395
1325
  runFrameImpl(dt, { onlyFrozen: true });
1396
1326
  },
1397
1327
  },
@@ -1486,7 +1416,7 @@ export function createGame(opts: {
1486
1416
  );
1487
1417
  }
1488
1418
  },
1489
- runFrame(dt: number, frameOpts?: { ignorePause?: boolean; skipRenderPhases?: boolean }): void {
1419
+ runFrame(dt: number, frameOpts?: { skipRenderPhases?: boolean }): void {
1490
1420
  runFrameImpl(dt, frameOpts);
1491
1421
  },
1492
1422
  runRenderFrame(alpha: number, displayDt: number): void {
@@ -17,6 +17,11 @@
17
17
  // even the 'three' convenience export is a plain function the CALLER
18
18
  // registers, not something this module wires up automatically.
19
19
 
20
+ import {
21
+ installNativeDebugBindings,
22
+ type NativeDebugBinding,
23
+ nativeDebugBindingFromEntryModule,
24
+ } from '../adapter/native-debug-module';
20
25
  import type { RootAdapter } from '../adapter/root-adapter';
21
26
  import type { ResolvedAdapterRoot } from '../manifest/load';
22
27
  import type { GameSession } from './create-runtime';
@@ -209,6 +214,15 @@ function worldIsSelfSufficient(_world: ResolvedAdapterRoot): boolean {
209
214
  return false;
210
215
  }
211
216
 
217
+ function nativeDebugForEntry(
218
+ world: ResolvedAdapterRoot,
219
+ entryModule: unknown,
220
+ ): NativeDebugBinding[] {
221
+ if (entryModule === undefined || world.adapter.type !== 'builtin') return [];
222
+ const binding = nativeDebugBindingFromEntryModule(world.id, entryModule);
223
+ return binding ? [binding] : [];
224
+ }
225
+
212
226
  /**
213
227
  * Mount every world declared by a `vgai.project.json` manifest onto `host
214
228
  * .container`, resolving each world's kind through the `registerAdapter`
@@ -240,6 +254,7 @@ export async function mountGameFromManifest(
240
254
  assertExportedOrInEditor();
241
255
  const manifest = resolveManifest(manifestInput);
242
256
  const entries: Record<string, MountEntry> = { ...opts.entries };
257
+ const nativeDebug: NativeDebugBinding[] = [];
243
258
 
244
259
  for (const world of manifest.roots) {
245
260
  if (entries[world.id] !== undefined) continue; // explicit entry always wins
@@ -250,6 +265,7 @@ export async function mountGameFromManifest(
250
265
  world.entry !== undefined && host.loadEntryModule
251
266
  ? await host.loadEntryModule(world.entry)
252
267
  : undefined;
268
+ nativeDebug.push(...nativeDebugForEntry(world, entryModule));
253
269
  entries[world.id] = await factory(world, { host, entryModule });
254
270
  continue;
255
271
  }
@@ -265,7 +281,7 @@ export async function mountGameFromManifest(
265
281
  );
266
282
  }
267
283
 
268
- return mountManifestRoots({
284
+ const session = await mountManifestRoots({
269
285
  manifest,
270
286
  container: host.container,
271
287
  entries,
@@ -273,4 +289,6 @@ export async function mountGameFromManifest(
273
289
  height: host.height,
274
290
  headless: host.headless,
275
291
  });
292
+ installNativeDebugBindings(session.game, nativeDebug);
293
+ return session;
276
294
  }
@@ -20,8 +20,7 @@
20
20
  // `entries[id].adapter` (the caller's own module graph built it). Threejs is
21
21
  // different: `three`/
22
22
  // `SetupThreeRootAdapter` are already unconditional dependencies of every
23
- // caller of `createGameRuntime`'s legacy path (this same file's sibling,
24
- // immediately below in this directory), so wrapping a `setup` with
23
+ // caller of `createGameRuntime`, so wrapping a `setup` with
25
24
  // `fromSetup` here adds no NEW bundle weight — only pixi/react are avoided.
26
25
 
27
26
  import { assertNever } from '../adapter/adapter-surface';
@@ -275,8 +274,9 @@ function resolveThreeAdapter(
275
274
  if (world.adapter.type === 'ingest') {
276
275
  throw new Error(
277
276
  `mountManifestRoots: world "${world.id}" (three) declares an { ingest } adapter — ` +
278
- "ingest roots require the editor's dev-server-backed mount machinery (an EditorStore " +
279
- "plus in-realm scene capture, see adapter-resolver.ts's resolveIngestThreeAdapter) and are " +
277
+ "captured ingest roots require the editor's dev-server-backed mount machinery (an EditorStore " +
278
+ 'plus in-realm scene capture, see resolveIngestDescriptor + ' +
279
+ 'mountThreeIngestRootFromManifest) and are ' +
280
280
  'not supported by mountManifestRoots (no porting aids, no hosted ingest routes).',
281
281
  );
282
282
  }
@@ -347,7 +347,6 @@ function buildRootMountSpec(
347
347
  zOrder: world.zOrder,
348
348
  pausable: world.pausable,
349
349
  dev: world.dev,
350
- loop: world.loop,
351
350
  };
352
351
 
353
352
  // `entry`'s `kind` is guaranteed to agree with `world.kind` past the guard
@@ -259,6 +259,28 @@ export interface GameContext {
259
259
  playtest?: PlaytestContext | null | undefined;
260
260
  }
261
261
 
262
+ /**
263
+ * Services shared by component-authored roots, independent of their render
264
+ * surface. This is deliberately an explicit pick from {@link GameContext}:
265
+ * adding a subsystem to the imperative Three setup context never silently
266
+ * exposes it to R3F or Canvas components.
267
+ *
268
+ * `input` is nullable because it belongs to the optional `Game` host. Normal
269
+ * vgai project mounts always provide it; bare harnesses and foreign hosts do
270
+ * not. Component code therefore sees the absence in its type instead of a
271
+ * getter that first throws from inside a frame callback.
272
+ *
273
+ * `clock` is surface-specific and intentionally absent here. The imperative
274
+ * and R3F clocks can dispose `THREE.Object3D` debris; a Canvas context narrows
275
+ * that Three-only operation away.
276
+ */
277
+ export type ComponentContextBase = Pick<
278
+ GameContext,
279
+ 'systems' | 'debug' | 'random' | 'game' | 'roots' | 'playtest' | 'registerSystemAdapter'
280
+ > & {
281
+ readonly input: InputManager | null;
282
+ };
283
+
262
284
  /** Return value from a game setup function. */
263
285
  export interface GameCleanup {
264
286
  dispose: () => void;
@@ -79,10 +79,9 @@ export function applyRendererSettings(
79
79
  * reload-only `antialias` option at construction and the live shadow/resolution
80
80
  * settings (via {@link applyRendererSettings}). Absent → registry defaults.
81
81
  *
82
- * `opts` is additive (T6.1 slice 1, D5 §1/§4): omitted
83
- * entirely, construction is byte-identical to before (no `alpha`/
84
- * `preserveDrawingBuffer` keys at all) — the legacy single-canvas host never
85
- * passes it. The roots path passes `alpha:true` for every stacked canvas
82
+ * When `opts` is omitted, construction leaves `alpha` and
83
+ * `preserveDrawingBuffer` at their renderer defaults. The host passes
84
+ * `alpha:true` for every stacked canvas
86
85
  * above the bottom one (so its clear-alpha-0 shows the layer below through
87
86
  * it) and `preserveDrawingBuffer:true` for every stacked canvas (the
88
87
  * recorded capture-tier cost, paid once here rather than re-derived later).
@@ -39,7 +39,7 @@ export const EngineBridge = createContext<EngineBridgeValue | null>(null);
39
39
  * function Player({ speed = 4.5 }) {
40
40
  * const { input } = useGameContext();
41
41
  * const ref = useRef<THREE.Group>(null);
42
- * useFrame((_, dt) => { if (input.isPressed('move_forward')) … });
42
+ * useFrame((_, dt) => { if (input?.isPressed('move_forward')) … });
43
43
  * return <group ref={ref}><PlayerCharacter /></group>;
44
44
  * }
45
45
  * ```