@vgai/engine 0.5.15 → 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 (174) 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/pixi/authoring.d.ts.map +1 -1
  94. package/dist/pixi/authoring.js +220 -1
  95. package/dist/pixi/ingest.d.ts +2 -1
  96. package/dist/pixi/ingest.d.ts.map +1 -1
  97. package/dist/pixi/ingest.js +4 -2
  98. package/dist/pixi/scene-capture.d.ts +9 -1
  99. package/dist/pixi/scene-capture.d.ts.map +1 -1
  100. package/dist/pixi/scene-capture.js +9 -6
  101. package/dist/render/particles-factory.d.ts +13 -6
  102. package/dist/render/particles-factory.d.ts.map +1 -1
  103. package/dist/render/particles-factory.js +6 -3
  104. package/dist/runtime/create-runtime.d.ts +23 -46
  105. package/dist/runtime/create-runtime.d.ts.map +1 -1
  106. package/dist/runtime/create-runtime.js +22 -67
  107. package/dist/runtime/game.d.ts +29 -71
  108. package/dist/runtime/game.d.ts.map +1 -1
  109. package/dist/runtime/game.js +15 -54
  110. package/dist/runtime/mount-game.d.ts.map +1 -1
  111. package/dist/runtime/mount-game.js +12 -1
  112. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  113. package/dist/runtime/mount-manifest.js +4 -5
  114. package/dist/runtime/types.d.ts +18 -0
  115. package/dist/runtime/types.d.ts.map +1 -1
  116. package/dist/setup/setup-renderer.d.ts +3 -4
  117. package/dist/setup/setup-renderer.d.ts.map +1 -1
  118. package/dist/setup/setup-renderer.js +3 -4
  119. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  120. package/dist/world3d-react/engine-bridge.js +1 -1
  121. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  122. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  123. package/dist/world3d-react/r3f-adapter.js +50 -21
  124. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  125. package/dist/world3d-react/r3f-root-factory.js +4 -0
  126. package/dist/world3d-react/world-context.d.ts +9 -3
  127. package/dist/world3d-react/world-context.d.ts.map +1 -1
  128. package/dist/world3d-react/world-context.js +5 -18
  129. package/package.json +1 -1
  130. package/schemas/engine-capabilities.json +7 -7
  131. package/src/adapter/adapter-module.ts +407 -50
  132. package/src/adapter/authoring-seam-contract.ts +211 -0
  133. package/src/adapter/authoring.ts +242 -50
  134. package/src/adapter/entrypoint-selection-source.ts +159 -0
  135. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
  136. package/src/adapter/first-party-systems.ts +17 -3
  137. package/src/adapter/index.ts +51 -1
  138. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  139. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  140. package/src/adapter/ingest/game-contract.ts +9 -4
  141. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  142. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  143. package/src/adapter/ingest/scene-capture.ts +126 -23
  144. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  145. package/src/adapter/native-debug-module.ts +307 -0
  146. package/src/adapter/object3d-authoring-subject.ts +50 -0
  147. package/src/adapter/root-adapter.ts +21 -56
  148. package/src/adapter/root-seam-contract.ts +56 -0
  149. package/src/adapter/seam-evidence.ts +336 -0
  150. package/src/adapter/setup-three-root-adapter.ts +4 -6
  151. package/src/adapter/system-adapter.ts +6 -1
  152. package/src/adapter/system-seam-contract.ts +121 -0
  153. package/src/animation/xstate-animation-binding.ts +151 -4
  154. package/src/canvas-react/engine-bridge.ts +6 -6
  155. package/src/canvas-react/index.ts +2 -1
  156. package/src/canvas-react/pixi-react-adapter.tsx +55 -34
  157. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  158. package/src/canvas-react/world-context.ts +26 -101
  159. package/src/ecs/user-data.ts +5 -0
  160. package/src/input/input-manager.ts +122 -0
  161. package/src/pixi/authoring.ts +236 -1
  162. package/src/pixi/ingest.ts +9 -2
  163. package/src/pixi/scene-capture.ts +20 -5
  164. package/src/render/particles-factory.ts +16 -6
  165. package/src/runtime/create-runtime.ts +41 -106
  166. package/src/runtime/game.ts +41 -111
  167. package/src/runtime/mount-game.ts +19 -1
  168. package/src/runtime/mount-manifest.ts +4 -5
  169. package/src/runtime/types.ts +22 -0
  170. package/src/setup/setup-renderer.ts +3 -4
  171. package/src/world3d-react/engine-bridge.ts +1 -1
  172. package/src/world3d-react/r3f-adapter.tsx +64 -24
  173. package/src/world3d-react/r3f-root-factory.tsx +4 -0
  174. package/src/world3d-react/world-context.ts +11 -36
@@ -79,12 +79,24 @@ interface PixiLike {
79
79
  Application: { prototype: Record<string, unknown> };
80
80
  }
81
81
 
82
+ export interface SceneCapture2DOptions {
83
+ /**
84
+ * Select the runtime owned by this mount when other Pixi Applications share
85
+ * the page (for example editor prefab previews). Rejected draws still count,
86
+ * but cannot satisfy {@link SceneCapture2DHandle.waitForCapture}.
87
+ */
88
+ accept?: ((runtime: CapturedRuntime2D) => boolean) | undefined;
89
+ }
90
+
82
91
  /**
83
92
  * Install the render trap on `pixiNamespace.Application.prototype.render`. Pass the
84
93
  * host's `pixi.js` namespace so a shared-instance game is trapped. Install once per
85
94
  * ingest session; `uninstall()` on teardown.
86
95
  */
87
- export function installSceneCapture2D(pixiNamespace: unknown): SceneCapture2DHandle {
96
+ export function installSceneCapture2D(
97
+ pixiNamespace: unknown,
98
+ options: SceneCapture2DOptions = {},
99
+ ): SceneCapture2DHandle {
88
100
  const PIXI = pixiNamespace as PixiLike;
89
101
  const proto = PIXI.Application.prototype;
90
102
 
@@ -102,8 +114,11 @@ export function installSceneCapture2D(pixiNamespace: unknown): SceneCapture2DHan
102
114
  drawCount++;
103
115
  const stage = this['stage'];
104
116
  if (!captured && stage) {
105
- captured = { stage, renderer: this['renderer'], app: this };
106
- for (const resolve of waiters.splice(0)) resolve(captured);
117
+ const candidate = { stage, renderer: this['renderer'], app: this };
118
+ if (options.accept?.(candidate) ?? true) {
119
+ captured = candidate;
120
+ for (const resolve of waiters.splice(0)) resolve(captured);
121
+ }
107
122
  }
108
123
  return realRender?.apply(this, args);
109
124
  },
@@ -138,8 +153,8 @@ export function installSceneCapture2D(pixiNamespace: unknown): SceneCapture2DHan
138
153
  reject(
139
154
  new Error(
140
155
  `pixi scene capture timed out after ${timeoutMs}ms of VISIBLE time ` +
141
- `(${Math.round(captureWindow.elapsedHiddenMs())}ms hidden, which is not counted — ` +
142
- 'a hidden tab cannot render) — the game never rendered, or it bundles its own ' +
156
+ `(${Math.round(captureWindow.elapsedHiddenMs())}ms browser-suspended, which is ` +
157
+ 'not counted because no frame can be presented) — the game never rendered, or it bundles its own ' +
143
158
  '(un-shared) copy of pixi.js.',
144
159
  ),
145
160
  );
@@ -13,6 +13,7 @@ import {
13
13
  ApplyForce,
14
14
  ChangeEmitDirection,
15
15
  CircleEmitter,
16
+ type ColorGenerator,
16
17
  ColorGeneratorFromJSON,
17
18
  ColorOverLife,
18
19
  ConeEmitter,
@@ -20,6 +21,7 @@ import {
20
21
  type EmitterShape,
21
22
  ForceOverLife,
22
23
  FrameOverLife,
24
+ type FunctionColorGenerator,
23
25
  GravityForce,
24
26
  GridEmitter,
25
27
  HemisphereEmitter,
@@ -345,9 +347,9 @@ export interface ParticleSystemResult {
345
347
  * This is how a caller with fidelity a plain descriptor cannot express reaches
346
348
  * the SAME factory instead of hand-building its own `new ParticleSystem(...)`:
347
349
  * a foreign-engine carry (e.g. Godot CPUParticles) supplies its own
348
- * `EmitterShape` and an instancing geometry here, and expresses everything
349
- * else — lifecycle, start values, emission, behaviors, material — as descriptor
350
- * JSON.
350
+ * `EmitterShape`, start-colour generator, and instancing geometry here, and
351
+ * expresses everything else — lifecycle, scalar start values, emission,
352
+ * behaviors, material — as descriptor JSON.
351
353
  */
352
354
  export interface ParticleSystemObjects {
353
355
  /**
@@ -356,6 +358,12 @@ export interface ParticleSystemObjects {
356
358
  * present it REPLACES `data.shape`.
357
359
  */
358
360
  shape?: EmitterShape;
361
+ /**
362
+ * A pre-built start-colour generator for a per-particle draw the descriptor's
363
+ * JSON generator vocabulary cannot express. When present it REPLACES
364
+ * `data.startColor`.
365
+ */
366
+ startColor?: ColorGenerator | FunctionColorGenerator;
359
367
  /**
360
368
  * The geometry instanced per particle in `RenderMode.Mesh`. A
361
369
  * `BufferGeometry` is not JSON-serializable, so `renderMode: 'mesh'` requires
@@ -377,8 +385,9 @@ export interface ParticleSystemObjects {
377
385
  * Create a three.quarks ParticleSystem from ParticlesDescriptor JSON data.
378
386
  *
379
387
  * `objects` carries the pieces a JSON descriptor cannot: a plugin emitter
380
- * shape, a mesh-mode instancing geometry, and the default-sprite toggle. It is
381
- * optional and additive — a call with only `data` behaves exactly as before.
388
+ * shape, a start-colour generator, a mesh-mode instancing geometry, and the
389
+ * default-sprite toggle. It is optional and additive — a call with only `data`
390
+ * behaves exactly as before.
382
391
  */
383
392
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: straightforward field-by-field mapping
384
393
  export function createParticleSystemFromData(
@@ -410,7 +419,8 @@ export function createParticleSystemFromData(
410
419
  if (data.startSpeed) params['startSpeed'] = valueGen(data.startSpeed);
411
420
  if (data.startSize) params['startSize'] = valueGen(data.startSize);
412
421
  if (data.startRotation) params['startRotation'] = valueGen(data.startRotation);
413
- if (data.startColor) params['startColor'] = colorGen(data.startColor);
422
+ if (objects?.startColor) params['startColor'] = objects.startColor;
423
+ else if (data.startColor) params['startColor'] = colorGen(data.startColor);
414
424
  if (data.startTileIndex) params['startTileIndex'] = valueGen(data.startTileIndex);
415
425
 
416
426
  // Trail mode requires startLength inside rendererEmitterSettings, not as a top-level param
@@ -31,12 +31,11 @@ import type { PlaytestContext } from './types';
31
31
  * by `createGameRuntime` and every headless test harness
32
32
  * (`test/game-root.test.ts`, `test/frame-order.test.ts`,
33
33
  * `test/game-two-roots.test.ts`) so there is exactly one code path for this
34
- * wiring (T7.1 slice 1, id-generalized in slice 3 for the two-world proof — stage
35
- * 3) — `physics`/`collisions`/`camera`/ `frame` are populated from the mount's
34
+ * wiring. `physics`/`collisions`/`camera`/`frame` are populated from the mount's
36
35
  * first-party `GameContext`/ `MountedSetupThreeRoot` when available, left
37
36
  * `undefined` otherwise (an external adapter's mount has none of these
38
37
  * first-party handles — `frame` in particular is what makes
39
- * `GameInternal.runFrame` (T7.1 slice 2) fall back to a single opaque
38
+ * `GameInternal.runFrame` fall back to a single opaque
40
39
  * `mounted.update` call per substep for such a world).
41
40
  */
42
41
  function firstPartyExtras(
@@ -53,10 +52,8 @@ function firstPartyExtras(
53
52
 
54
53
  /**
55
54
  * Register a three world onto the Game shell. `opts.id` defaults to
56
- * `'main'` — the single-world callers (`createGameRuntime`, slice-1/2 tests)
57
- * are unaffected; a second/third world (T7.1 slice 3's two-world proof, and
58
- * real multi-world manifests later) pass an explicit id. This is the ONE
59
- * registration code path for a three world, whatever its id.
55
+ * `'main'`; multi-root callers pass an explicit id. This is the one
56
+ * registration path for a three world, whatever its id.
60
57
  */
61
58
  export function registerThreeRoot(
62
59
  game: GameInternal,
@@ -104,13 +101,13 @@ export function registerPixiRoot(
104
101
  }
105
102
 
106
103
  /**
107
- * Register a react world onto the Game shell (T6.2 slice 1) — the react
104
+ * Register a React world onto the Game shell — the DOM
108
105
  * analog of {@link registerThreeRoot}/{@link registerPixiRoot}. A react
109
106
  * world has no `frame` hooks (react's own
110
107
  * `createRoot` schedules its commits; `GameInternal.runFrame` correctly
111
108
  * leaves a world with no `frame` untouched by its opaque-`update` fallback
112
109
  * too, since `MountedReactGame` declares no `update`). `adapter` is typed
113
- * as the real {@link ReactRootAdapter} shape (T7.5, same reasoning as
110
+ * as the real {@link ReactRootAdapter} shape (same reasoning as
114
111
  * `registerPixiRoot`'s doc comment above) — `RootInstance.adapter` only
115
112
  * needs `.id`, so this passes through with zero cast.
116
113
  */
@@ -134,12 +131,12 @@ export function registerReactRoot(
134
131
  }
135
132
 
136
133
  /**
137
- * One world to mount in the {@link RootsRuntimeConfig} roots path (T6.1
138
- * slice 1) — the host-facing mirror of `manifest/load.ts`'s
139
- * `ResolvedAdapterRoot` (same `id`/`zOrder`/`pausable`/`loop` fields; the
134
+ * One world to mount in the universal host the host-facing mirror of
135
+ * `manifest/load.ts`'s `ResolvedAdapterRoot` (same identity and presentation
136
+ * fields; the
140
137
  * manifest-to-host translation itself is the editor's job, not this file's).
141
138
  *
142
- * `react` (T6.2 slice 1) mounts as a DOM-root layer `<div>` in the SAME
139
+ * A `dom` root mounts as a DOM layer `<div>` in the same
143
140
  * stack instead of a canvas — see {@link ReactRootMountSpec}/{@link
144
141
  * ReactRootAdapter} below.
145
142
  */
@@ -168,12 +165,6 @@ export interface RootMountSpecBase {
168
165
  * never sees DOM roots at all.
169
166
  */
170
167
  readonly dev?: boolean | undefined;
171
- /** `'gated'` (host-driven, default) or `'self-driven'` (this world drives
172
- * its own loop — D5's "composited, unsynchronized" tier). Carried through
173
- * for parity with `ResolvedAdapterRoot`; T6.1 slice 1 does not yet
174
- * validate it against the mounted adapter's actual `drivesOwnLoop` (that
175
- * cross-check, if ever needed, is T7.6's loop-gate surface). */
176
- readonly loop?: 'gated' | 'self-driven' | undefined;
177
168
  /**
178
169
  * Optional claim predicate for the delegating input router (D5 §2a), over
179
170
  * a point RELATIVE TO THE CONTAINER. Absent means: this world claims only
@@ -199,8 +190,8 @@ export interface PixiRootMountSpec extends RootMountSpecBase {
199
190
  }
200
191
 
201
192
  /**
202
- * A live, mounted react world (T6.2 slice 1) — the react analog of a mounted
203
- * canvas world. React roots render from game state via the T7.4 bridge
193
+ * A live, mounted React world — the DOM analog of a mounted canvas world.
194
+ * React roots render from game state
204
195
  * instead of a per-frame `update`, so this shape carries no `update`/`fixedUpdate`/
205
196
  * `ctx`/`frame` — `drivesOwnLoop` is always `false` (react's `createRoot`
206
197
  * schedules its OWN commits; the host's fixed-step loop never drives it,
@@ -217,10 +208,10 @@ export interface PixiRootMountSpec extends RootMountSpecBase {
217
208
  * `kind: 'dom'` (§7.1-15): a react world has no `observe` BY DESIGN — that
218
209
  * hook is scoped to `useRootObservation` (the ingested/foreign-world case),
219
210
  * not `useWorldState` (`ui/game-state.tsx`), which a react world's own
220
- * mounted tree uses instead — it reads `Game.state` directly (T7.4's actual
221
- * bridge for first-party-observable state), never a per-world `observe`.
211
+ * mounted tree uses instead — it reads `Game.state` directly, never a
212
+ * per-world `observe`.
222
213
  *
223
- * `kind`/`container` (T7.5) satisfy `MountedReactRoot` (`adapter/
214
+ * `kind`/`container` satisfy `MountedReactRoot` (`adapter/
224
215
  * root-adapter.ts`) — `container` is the SAME `DomHostContext.container` the
225
216
  * adapter's `mount` was handed (identity matters, mirroring `threeScene()`/
226
217
  * `pixiStage()`'s "same instance the adapter mounted" contract); every
@@ -236,7 +227,7 @@ export interface MountedReactGame extends MountedReactRoot {
236
227
  * A react-shaped adapter — the structural contract `RootMountSpec`'s
237
228
  * `react` variant requires. Deliberately
238
229
  * NOT tied to a concrete implementer here so an editor-resolved adapter (the
239
- * `default-react` resolver branch, T6.2's editor-side follow-up) can satisfy
230
+ * `default-react` resolver branch) can satisfy
240
231
  * this shape without this file importing react-dom or any editor code.
241
232
  *
242
233
  * A genuine `RootAdapter<'dom'>` refinement (`HostContextFor<'dom'>` =
@@ -328,37 +319,23 @@ export interface RootsRuntimeConfig {
328
319
  *
329
320
  * This is the GENERIC host handle — it has no first-party concepts (no
330
321
  * `GameContext`, no `GameSetupFn`). First-party features (the live `GameContext`,
331
- * warm-restart hot reload) are reached by casting `mounted` to `MountedSetupThreeRoot`
322
+ * warm-restart hot reload) are reached through `game.defaultRoot.mounted`
323
+ * and narrowed to `MountedSetupThreeRoot`
332
324
  * (the editor does this for HMR/physics-sync — those are inherently first-party).
333
325
  *
334
- * `scene`/`camera`/`mounted` alias the Game's `defaultRoot`: it is the first
335
- * three root (else the first
336
- * world) by DECLARATION order, per `Game.defaultRoot`'s existing rule —
337
- * independent of `zOrder`/canvas stacking, which is a rendering-only concern.
326
+ * Root access is explicit through `game.roots`, `game.world(id)`, and
327
+ * `game.defaultRoot`; the session does not project a surface-specific alias.
338
328
  */
339
329
  export interface GameSession {
340
330
  stop(): void;
341
331
  pause(): void;
342
332
  resume(): void;
343
333
  step(): void;
344
- /** Resize every world's render buffer. `pixelRatio` (W2c device preview)
345
- * optionally re-pins the three renderers' DPR in the same pass; omitted,
346
- * the mount-time ratio is left untouched (legacy behavior). */
334
+ /** Resize every world's render buffer. `pixelRatio` optionally re-pins the
335
+ * three renderers' DPR in the same pass. */
347
336
  resize(width: number, height: number, pixelRatio?: number): void;
348
- readonly scene: THREE.Scene;
349
- readonly camera: THREE.PerspectiveCamera;
350
- /** The default world's mounted root, as the honest `MountedRoot` union —
351
- * narrow on `.kind` before reaching for a surface's own members (the same
352
- * discriminant `scene`/`camera` above use). This deliberately does NOT
353
- * claim `MountedThreeRoot`: a roots-path session whose default world is
354
- * canvas or dom returns that world's mount, and the old
355
- * `as MountedThreeRoot` cast made every such value a type-level lie. For a
356
- * first-party three mount, narrow to `'three'` and cast to
357
- * `MountedSetupThreeRoot` for the first-party extras. */
358
- readonly mounted: MountedRoot;
359
- /** The Game root (T6.1 slice 1) — the multi-world entry point
360
- * (`game.roots`/`game.world(id)`/`game.queryByComponent`) for callers
361
- * that need more than the default-world aliases above. */
337
+ /** The multi-root entry point. Root access stays explicit through
338
+ * `game.roots`, `game.world(id)`, and `game.defaultRoot`. */
362
339
  readonly game: Game;
363
340
  }
364
341
 
@@ -374,7 +351,7 @@ export async function createGameRuntime(config: RootsRuntimeConfig): Promise<Gam
374
351
  }
375
352
 
376
353
  // ---------------------------------------------------------------------------
377
- // Worlds path (T6.1 slice 1)
354
+ // Universal root host
378
355
  // ---------------------------------------------------------------------------
379
356
 
380
357
  /** A stand-in `THREE.WebGLRenderer` for headless (`headless:true`) three
@@ -397,7 +374,7 @@ function createHeadlessRendererStub(): THREE.WebGLRenderer {
397
374
 
398
375
  /** One already-mounted world, tracked for disposal + the router. `element`
399
376
  * is the world's stacked surface — an `HTMLCanvasElement` for three/
400
- * canvas, or the DOM-root layer `<div>` for react (T6.2 slice 1) — kept
377
+ * canvas, or the DOM-root layer `<div>` for React — kept
401
378
  * under one field name so `fullCleanup`'s disposal loop stays kind-generic
402
379
  * (`container.removeChild(entry.element)` needs no branch). */
403
380
  interface MountedAdapterRoot {
@@ -477,11 +454,8 @@ async function mountOneThreeRoot(
477
454
  // resize-staleness (the roots-path `resize()` below always calls
478
455
  // `setSize(rw, rh, false)`, so nothing else was ever going to update this
479
456
  // canvas' CSS after mount). `createHostRenderer`'s own default
480
- // (`updateStyle:true`) is intentionally left alone the LEGACY
481
- // single-canvas path (editor play-mode's non-multi-world branch) still
482
- // relies on that construction-time stamp + its own later `updateStyle:true`
483
- // resizes for byte-identical behavior; this fix touches only the
484
- // roots-path canvas, after the fact.
457
+ // (`updateStyle:true`) is intentionally left alone; the host owns CSS layout
458
+ // and reasserts the container-relative size after renderer construction.
485
459
  canvas.style.width = '100%';
486
460
  canvas.style.height = '100%';
487
461
  renderer.setPixelRatio(dpr);
@@ -559,7 +533,7 @@ async function mountOnePixiRoot(
559
533
  }
560
534
 
561
535
  /**
562
- * Mount one react `RootMountSpec` (T6.2 slice 1) — the react sibling of
536
+ * Mount one React `RootMountSpec` — the DOM sibling of
563
537
  * `mountOneThreeRoot`/`mountOnePixiRoot`. Unlike its canvas-backed siblings
564
538
  * this returns NO `routerEntry`: a react world's DOM-root layer participates in
565
539
  * D5's z-order/box stacking (the caller still creates and positions its `<div>`
@@ -592,7 +566,7 @@ async function mountOneReactRoot(
592
566
  // An absolutely-positioned layer with no width/height collapses to zero
593
567
  // content size, so a child's
594
568
  // own position:absolute offsets resolve against a degenerate containing
595
- // block (found by the T6.2 slice-3 e2e — clicks landed outside the game).
569
+ // block; otherwise clicks land outside the game.
596
570
  layer.style.width = '100%';
597
571
  layer.style.height = '100%';
598
572
  const reactHost: DomHostContext = { container: layer, game };
@@ -613,8 +587,8 @@ async function mountOneReactRoot(
613
587
  }
614
588
 
615
589
  /**
616
- * The roots-path implementer behind {@link createGameRuntime} (T6.1 slice
617
- * 1; react roots added T6.2 slice 1). Builds ONE surface per world — a
590
+ * The universal host implementer behind {@link createGameRuntime}. It builds
591
+ * one surface per world — a
618
592
  * canvas for three/canvas, a DOM-root `<div>` layer for react — stacked
619
593
  * per D5 §1, z-order/ties exactly matching
620
594
  * `manifest/load.ts`'s sort, ONE `Game`, and registers every world onto it
@@ -628,16 +602,11 @@ async function mountOneReactRoot(
628
602
 
629
603
  /**
630
604
  * Dev/e2e-only `window.__vgaiScene`/`__vgaiCamera` exposure for the roots
631
- * path's DEFAULT world (E4) — split out of `createRootsGameRuntime` purely
632
- * to keep that function's own cyclomatic complexity down. Mirrors the
633
- * legacy single-world path's identical exposure (above, in this same file),
634
- * using the SAME "first three world, else none" default-world rule the
635
- * `GameSession.scene`/`.camera` getters alias (`Game.defaultRoot`): a
636
- * non-threejs default world (or none at all) publishes neither global,
637
- * exactly like those getters return `undefined` in that case. Returns a
638
- * retraction callback — identity-guarded (a newer session's globals must
639
- * never be clobbered by an older session's stop, same reason the legacy
640
- * path's own cleanup guards it) — that is a no-op when nothing was
605
+ * path's default world — split out of `createRootsGameRuntime` purely
606
+ * to keep that function's own cyclomatic complexity down. It uses the
607
+ * "first three world, else none" rule: a non-Three default world publishes
608
+ * neither global. Returns an identity-guarded retraction callback so an
609
+ * older session's stop cannot clobber a newer session's globals. It is a no-op when nothing was
641
610
  * published (non-DEV build, or non-threejs default world).
642
611
  */
643
612
  function installDefaultRootDevGlobals(game: GameInternal): () => void {
@@ -804,8 +773,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
804
773
  // --- Surface stack (D5 §1): DOM/z-order follows zOrder, ties -> array order
805
774
  // — computed FIRST (bottom -> top) so both the z-index assignment below and
806
775
  // the router's default-claim rule share one definition. A react world's
807
- // DOM-root layer shares this SAME stacking pass (T6.2 slice 1 "one
808
- // stacking model, no special case") even though it is a `<div>`, not a
776
+ // DOM-root layer shares this SAME stacking pass even though it is a `<div>`, not a
809
777
  // canvas, and carries no `hitTest` (the router never sees react entries at
810
778
  // all — see the dispatch loop below). ---
811
779
  // Dev layers sit TOPMOST, above every non-dev root, whatever zOrder either
@@ -862,7 +830,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
862
830
  // too (not just in `mountOneThreeRoot`) means every surface, whatever
863
831
  // kind, starts container-relative from its very first paint, before any
864
832
  // per-kind mount work has even run.
865
- // `contain:layout paint` makes each surface the CONTAINING BLOCK for
833
+ // `contain:layout paint` makes each surface the containing block for
866
834
  // `position:fixed` descendants (and clips overflow to the world's
867
835
  // rectangle): full-screen game UI written the natural way (`fixed;
868
836
  // inset:0`) then fills the WORLD, not the page. Without it, `fixed` UI
@@ -936,17 +904,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
936
904
  started = true;
937
905
  loop.start();
938
906
 
939
- // Expose scene & camera for dev tools / e2e tests — mirrors the legacy
940
- // single-world path's identical dev-only exposure above, generalized to
941
- // the roots path's DEFAULT world (E4): `mountGameFromManifest`/
942
- // `mountManifestRoots` route every caller (including a
943
- // single-threejs-world scaffold project) through THIS path, so a caller
944
- // migrating off the legacy `{canvas, adapter}` call must not silently
945
- // lose `window.__vgaiScene`/`__vgaiCamera` — real e2e/dev tooling
946
- // depends on them
947
- // (`packages/editor/e2e/tests/04-standalone-game.spec.ts`). Split into
948
- // its own top-level helper (with its retraction counterpart below)
949
- // purely to keep this function's own cyclomatic complexity down.
907
+ // Expose the default Three root's scene and camera for dev/e2e tooling.
950
908
  const retractDevGlobals = installDefaultRootDevGlobals(game);
951
909
 
952
910
  // G3/FT-11 — publish `window.__vgaiRender` for the deterministic capture
@@ -986,12 +944,7 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
986
944
 
987
945
  return {
988
946
  stop: fullCleanup,
989
- // D10/T7.6: `Game.play` fans out per-world pausable/loop-gate/audio-gate
990
- // semantics itself now (see the legacy path's identical comment above) —
991
- // a `pausable: false` world (a menu/HUD world) keeps ticking while every
992
- // other world freezes, which the old blind `for (const entry of
993
- // mountedEntries) entry.mounted.setPaused?.(true)` fan-out (with no
994
- // `pausable` check at all) could never express.
947
+ // `Game.play` owns per-world pause, loop-gate, and audio-gate semantics.
995
948
  pause() {
996
949
  game.play.pause();
997
950
  },
@@ -1014,24 +967,6 @@ async function createRootsGameRuntime(config: RootsRuntimeConfig): Promise<GameS
1014
967
  entry.mounted.resize?.(safeWidth, safeHeight);
1015
968
  }
1016
969
  },
1017
- // `GameSession.scene`/`.camera`/`.mounted` alias `Game.defaultRoot` (T6.1
1018
- // slice 1's "first three world, else first world" rule) — a legacy
1019
- // convenience shaped for the threejs-only past. T7.5 narrows the read via
1020
- // the `kind` discriminant instead of a blind `.scene`/`.camera` cast
1021
- // through a nonexistent property (identical behavior to before: still
1022
- // `undefined` for a roots-path session whose default world isn't
1023
- // three — real per-world surface routing for that case is T7.6's).
1024
- get scene() {
1025
- const m = game.defaultRoot.mounted;
1026
- return (m.kind === 'three' ? m.scene : undefined) as THREE.Scene;
1027
- },
1028
- get camera() {
1029
- const m = game.defaultRoot.mounted;
1030
- return (m.kind === 'three' ? m.camera : undefined) as THREE.PerspectiveCamera;
1031
- },
1032
- get mounted() {
1033
- return game.defaultRoot.mounted;
1034
- },
1035
970
  game,
1036
971
  };
1037
972
  }