@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
@@ -23,12 +23,14 @@ import {
23
23
  advance,
24
24
  createRoot,
25
25
  extend,
26
+ flushSync,
26
27
  events as pointerEvents,
27
28
  type RootState,
28
29
  useFrame,
29
30
  } from '@react-three/fiber';
30
31
  import { createElement, Fragment, type ReactNode, useEffect, useMemo } from 'react';
31
32
  import type { MountedThreeRoot, RootAdapter, ThreeHostContext } from '../adapter';
33
+ import type { SystemAdapters } from '../adapter/system-adapter';
32
34
  import { type RenderVitalsRegistration, registerRenderVitals } from '../dev/register-render-vitals';
33
35
  import {
34
36
  createRenderDebugAdapter,
@@ -40,6 +42,7 @@ import { RENDER_SUBMIT_PHASE } from '../dev/render-vitals';
40
42
  import { createWebGLFrameCapture } from '../dev/webgl-frame-capture';
41
43
  import { WorldProvider } from '../react/world-state';
42
44
  import { createSoftParticleDepthPass } from '../render/soft-particle-depth';
45
+ import { getDebugRegistry } from '../runtime/debug-registry';
43
46
  import { devLayersEnabled } from '../runtime/dev-layers';
44
47
  import { EngineBridge, type EngineBridgeValue } from './engine-bridge';
45
48
  import { applyWorldRendererConfig, type WorldRendererConfig } from './renderer-config';
@@ -99,6 +102,13 @@ export interface CreateR3FAdapterOptions {
99
102
  * actions). A missing/invalid map degrades loudly without failing the
100
103
  * mount — see `world-context.ts`. */
101
104
  readonly inputMapPath?: string | null | undefined;
105
+ /**
106
+ * Transitional compatibility for explicit pre-native adapters. The
107
+ * default-export root factory always passes `false`: ordinary R3F trees get
108
+ * no vgai context/provider or private scheduler. Remove this switch with
109
+ * the last explicit legacy adapter migration.
110
+ */
111
+ readonly engineRuntime?: boolean | undefined;
102
112
  /** The colour pipeline this world was AUTHORED for, applied to the host's renderer for the life
103
113
  * of the mount and restored on dispose. Omit it (every world here does) to keep the host's own
104
114
  * defaults; declare it when the world's colours were picked against a different engine's
@@ -113,7 +123,7 @@ export interface CreateR3FAdapterOptions {
113
123
  * second `requestAnimationFrame` loop (design §1.C).
114
124
  */
115
125
  export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter {
116
- const { id, content, inputMapPath, renderer: rendererConfig } = options;
126
+ const { id, content, inputMapPath, engineRuntime = true, renderer: rendererConfig } = options;
117
127
 
118
128
  return {
119
129
  id,
@@ -171,15 +181,14 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
171
181
  // for one is a compile error rather than anything that can happen at
172
182
  // runtime. `scene`/`camera` are backfilled right after fiber's first
173
183
  // commit resolves them, before any tick can run.
174
- const runtime = createR3FRootContext(host, { id, inputMapPath });
175
- const { systems } = runtime;
184
+ const runtime = engineRuntime ? createR3FRootContext(host, { id, inputMapPath }) : null;
176
185
  // `ctx` rides the bridge so ordinary components can reach engine
177
186
  // services through `useGameContext()`.
178
- const bridge: EngineBridgeValue = { ctx: runtime.ctx };
187
+ const bridge: EngineBridgeValue | null = runtime ? { ctx: runtime.ctx } : null;
179
188
  // Actions must exist before any component's init()/first frame reads
180
189
  // them — wait for the (never-rejecting) input-map load before the first
181
190
  // commit.
182
- await runtime.inputMapReady;
191
+ await runtime?.inputMapReady;
183
192
 
184
193
  // The world's own colour pipeline. Applied AFTER `root.configure()` below — see the comment
185
194
  // at its call site for why "before" is the version that silently does nothing.
@@ -199,6 +208,19 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
199
208
  const statePromise = new Promise<RootState>((resolve) => {
200
209
  resolveState = resolve;
201
210
  });
211
+ let resolveEffectsReady!: () => void;
212
+ const effectsReady = new Promise<void>((resolve) => {
213
+ resolveEffectsReady = resolve;
214
+ });
215
+ // Rendered after the game's content. React runs passive effects in tree
216
+ // order, so when this fires every useDebugProvider/useDebugCommand in
217
+ // the initial tree has registered. A Play acknowledgement is a claim
218
+ // that the mounted game's command surface exists, not merely that Fiber
219
+ // allocated a scene.
220
+ function MountEffectsReady(): null {
221
+ useEffect(resolveEffectsReady, []);
222
+ return null;
223
+ }
202
224
 
203
225
  // `root.configure()` is ASYNC in fiber v9 (`Promise<ReconcilerRoot>`)
204
226
  // — the design doc's §1.C sketch shows it called synchronously; the
@@ -298,21 +320,24 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
298
320
  // until this landed. `host.game` is genuinely absent in bare/headless
299
321
  // hosts, so the provider is conditional and those hooks stay the inert
300
322
  // no-ops they already document themselves to be.
301
- const game = runtime.ctx.game;
323
+ const game = runtime?.ctx.game;
302
324
  // `SoftParticleDepth` trails the world's own tree deliberately — see its
303
325
  // docblock for why the subscription must land last.
304
326
  const world = createElement(
305
327
  Fragment,
306
328
  null,
307
329
  content,
308
- createElement(SoftParticleDepth, { key: 'vgai-soft-particle-depth' }),
330
+ runtime ? createElement(SoftParticleDepth, { key: 'vgai-soft-particle-depth' }) : null,
331
+ createElement(MountEffectsReady, { key: 'vgai-mount-effects-ready' }),
309
332
  );
310
333
  root.render(
311
- createElement(
312
- EngineBridge.Provider,
313
- { value: bridge },
314
- game ? createElement(WorldProvider, { game }, world) : world,
315
- ),
334
+ bridge
335
+ ? createElement(
336
+ EngineBridge.Provider,
337
+ { value: bridge },
338
+ game ? createElement(WorldProvider, { game }, world) : world,
339
+ )
340
+ : world,
316
341
  );
317
342
  // A reconcile-time crash (e.g. a missing `extend` catalogue entry)
318
343
  // surfaces as an uncaught window error and `onCreated` never fires —
@@ -323,7 +348,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
323
348
  const onError = (event: ErrorEvent) => {
324
349
  cleanup();
325
350
  root.unmount();
326
- runtime.dispose();
351
+ runtime?.dispose();
327
352
  restoreRendererConfig();
328
353
  reject(
329
354
  new Error(
@@ -335,7 +360,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
335
360
  const timer = setTimeout(() => {
336
361
  cleanup();
337
362
  root.unmount();
338
- runtime.dispose();
363
+ runtime?.dispose();
339
364
  restoreRendererConfig();
340
365
  reject(
341
366
  new Error(
@@ -349,7 +374,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
349
374
  window.removeEventListener('error', onError);
350
375
  };
351
376
  window.addEventListener('error', onError);
352
- void statePromise.then((s) => {
377
+ void Promise.all([statePromise, effectsReady]).then(([s]) => {
353
378
  cleanup();
354
379
  resolve(s);
355
380
  });
@@ -369,7 +394,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
369
394
  // Backfill the component runtime's ctx with fiber's real scene/camera
370
395
  // (W3 — see the HONESTY NOTE above). This happens before the first
371
396
  // `update(dt)` tick, so no component ever observes them missing.
372
- runtime.setSceneCamera(state.scene, state.camera);
397
+ runtime?.setSceneCamera(state.scene, state.camera);
373
398
 
374
399
  // THE STORE IS THE TRUTH, NOT THE `onCreated` SNAPSHOT.
375
400
  //
@@ -395,6 +420,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
395
420
  // `RootState` for precisely this reason, so reading it per frame is the
396
421
  // supported way to see the live root. It is a plain map read.
397
422
  const live = (): RootState => state.get();
423
+ const gameDebugRegistry = host.game ? getDebugRegistry(host.game) : null;
398
424
 
399
425
  // --- Live render vitals (issue #1504), for an R3F world -----------------
400
426
  // The SAME door the classic adapter seeds
@@ -434,8 +460,11 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
434
460
  // sets on play — under the dev gate this is the same cost arriving a
435
461
  // little earlier, not a new one.
436
462
  host.game.profiler.enabled = true;
463
+ if (!gameDebugRegistry) {
464
+ throw new Error('r3f-adapter: mounted Game has no debug registry.');
465
+ }
437
466
  renderVitals = registerRenderVitals({
438
- registry: runtime.debugRegistry,
467
+ registry: gameDebugRegistry,
439
468
  worldId: id,
440
469
  profiler: host.game.profiler,
441
470
  scene: live().scene,
@@ -461,7 +490,12 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
461
490
  memory: () => collectRenderMemory(live().scene, host.renderer.info),
462
491
  })
463
492
  : null;
464
- if (renderDebugWiring) runtime.systemAdapters.renderDebug = renderDebugWiring.adapter;
493
+ // The session debug registry is host instrumentation, not component
494
+ // context. Publishing the existing adapter here lets coverage and
495
+ // editor panels see the same door without wrapping the R3F tree.
496
+ const systemAdapters: SystemAdapters =
497
+ runtime?.systemAdapters ?? (gameDebugRegistry ? { debug: gameDebugRegistry.adapter } : {});
498
+ if (renderDebugWiring) systemAdapters.renderDebug = renderDebugWiring.adapter;
465
499
 
466
500
  // The engine drives every `useFrame` through the mounted world's
467
501
  // `update(dt)` hook, never off a raw host-loop callback. That is the
@@ -490,7 +524,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
490
524
  // resumed frame as one giant `useFrame` delta — the exact "time passed
491
525
  // while frozen" illusion acceptance gate 2 forbids.
492
526
  let elapsed = 0;
493
- /** The camera `ctx` was last backfilled with — see `update()`. */
527
+ /** The legacy component ctx camera last backfilled by `update()`. */
494
528
  let backfilledCamera = state.camera;
495
529
 
496
530
  return {
@@ -524,7 +558,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
524
558
  // WebGL2 context exists, `renderDebug` are engine-seeded. Game code
525
559
  // adds its own capabilities via `ctx.registerSystemAdapter` exactly
526
560
  // as in a classic world.
527
- systems: runtime.systemAdapters,
561
+ systems: systemAdapters,
528
562
  update(dt: number): void {
529
563
  elapsed += dt;
530
564
  const current = live();
@@ -533,14 +567,14 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
533
567
  // component reading one while the frame uses the other is the
534
568
  // silent kind of wrong. Identity-gated, so this is one comparison
535
569
  // per frame in the overwhelmingly common no-swap case.
536
- if (current.camera !== backfilledCamera) {
570
+ if (runtime && current.camera !== backfilledCamera) {
537
571
  backfilledCamera = current.camera;
538
572
  runtime.setSceneCamera(current.scene, current.camera);
539
573
  }
540
574
  // Engine phases FIRST (registered systems mutate transforms),
541
575
  // then fiber's advance (useFrame callbacks + the actual render see
542
576
  // the fresh state). Both are host-gated: neither runs while frozen.
543
- systems.run(dt);
577
+ runtime?.systems.run(dt);
544
578
  renderDebugWiring?.beforeRender();
545
579
  try {
546
580
  const profiler = host.game?.profiler;
@@ -591,7 +625,13 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
591
625
  // Reject/restore a pending capture while its renderables still
592
626
  // exist, then unmount component effects and strip runtime state.
593
627
  renderDebugWiring?.dispose();
594
- root.unmount();
628
+ // The editor audits and disposes the mounted game's window/document realm immediately
629
+ // after `session.stop()`. Fiber's ordinary `unmount()` submits a concurrent reconciler
630
+ // update, so component effect cleanups could still be pending at that boundary and the
631
+ // audit would truthfully report/reclaim listeners the world was about to remove itself.
632
+ // Force the unmount COMMIT (not fiber's deliberately delayed renderer/context disposal)
633
+ // to finish here: a stopped root owns no live component lifecycle beyond this method.
634
+ flushSync(() => root.unmount());
595
635
  // The vitals fold owns two `profiler.subscribe` registrations, and
596
636
  // `runtime.dispose()`'s `strip(id)` removes the REGISTRATIONS only —
597
637
  // this is the one path that ends the subscriptions, exactly as the
@@ -599,7 +639,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
599
639
  // subscription can never outlive the door it feeds.
600
640
  renderVitals?.dispose();
601
641
  renderVitals = null;
602
- runtime.dispose();
642
+ runtime?.dispose();
603
643
  restoreRendererConfig();
604
644
  },
605
645
  };
@@ -69,6 +69,10 @@ export function resolveR3FEntryAdapter(entryModule: unknown, rootId: string): Ro
69
69
  return createR3FAdapter({
70
70
  id: rootId,
71
71
  content: createElement(mod.default),
72
+ // A default-exported R3F component is an ordinary R3F app. The host
73
+ // controls Fiber's native `frameloop: 'never'` scheduler; vgai runtime
74
+ // context, systems and state providers never enter its React tree.
75
+ engineRuntime: false,
72
76
  });
73
77
  }
74
78
  return null;
@@ -13,12 +13,15 @@
13
13
  * `ctx.debug = registry.forRoot(id)`, so a component's
14
14
  * effect can `ctx.debug.registerStateProvider(...)` and be visible
15
15
  * to `vgai eval`/the editor's debug panels — previously a silent no-op.
16
- * 2. **`ctx.input` is the game-owned `InputManager`** (`host.game.input`
16
+ * 2. **`ctx.input` is the game-owned `InputManager` when a Game hosts the
17
+ * root** (`host.game.input` —
17
18
  * the same instance the classic adapter uses when hosted), and this
18
19
  * module registers the SAME per-world debug-registry seams the classic
19
20
  * adapter registers (`setVirtualInputTarget`/`setInputActionsSource`/
20
21
  * `setInputTraceSource`), which is what makes `game.input.*` (the bot
21
22
  * input doctrine) work instead of throwing `DEBUG_INPUT_UNAVAILABLE`.
23
+ * A bare/foreign mount receives `null`, visibly in the type, rather than
24
+ * a getter that throws later inside a frame callback.
22
25
  * The project's input map is loaded through `Game.loadInputMap` (the
23
26
  * game-owned, load-once path every root shares) with the SAME default
24
27
  * path the classic adapter uses. A missing/unparseable map degrades
@@ -49,7 +52,7 @@ import {
49
52
  getDebugRegistry,
50
53
  } from '../runtime/debug-registry';
51
54
  import { disposeDebrisSubtree } from '../runtime/game';
52
- import type { GameContext } from '../runtime/types';
55
+ import type { ComponentContextBase, GameContext } from '../runtime/types';
53
56
 
54
57
  /** The classic adapter's default input-map path — the conventional location
55
58
  * every scaffolded project ships (`public/inputmaps/default.inputmap.json`). */
@@ -90,22 +93,11 @@ export const DEFAULT_INPUT_MAP_PATH = '/inputmaps/default.inputmap.json';
90
93
  * exporting `setup(ctx): Promise<GameCleanup>`, which `SetupThreeRootAdapter`
91
94
  * mounts with the complete {@link GameContext}.
92
95
  */
93
- export type R3FGameContext = Pick<
94
- GameContext,
95
- | 'scene'
96
- | 'camera'
97
- | 'sceneIndex'
98
- | 'input'
99
- | 'assets'
100
- | 'systems'
101
- | 'clock'
102
- | 'debug'
103
- | 'random'
104
- | 'game'
105
- | 'roots'
106
- | 'playtest'
107
- | 'registerSystemAdapter'
108
- >;
96
+ export type R3FGameContext = ComponentContextBase &
97
+ Pick<GameContext, 'scene' | 'sceneIndex' | 'assets' | 'clock'> & {
98
+ /** Fiber may install either a perspective or orthographic camera. */
99
+ camera: THREE.Camera;
100
+ };
109
101
 
110
102
  export interface R3FRootContextOptions {
111
103
  /** World id — provenance for every debug-registry registration (matches the
@@ -185,6 +177,7 @@ export function createR3FRootContext(
185
177
  const ctxRaw: Record<string, unknown> = {
186
178
  systems,
187
179
  assets: host.assets,
180
+ input: host.game?.input ?? null,
188
181
  registerSystemAdapter: (kind: keyof SystemAdapters, adapter: SystemAdapters[typeof kind]) => {
189
182
  // biome-ignore lint/suspicious/noExplicitAny: same per-key record write the classic adapter performs; correct by construction
190
183
  (systemAdapters as any)[kind] = adapter;
@@ -232,7 +225,6 @@ export function createR3FRootContext(
232
225
  if (host.game) {
233
226
  const game = host.game;
234
227
  const input = game.input;
235
- ctxRaw['input'] = input;
236
228
  debugRegistry.setInputActionsSource(id, () =>
237
229
  input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
238
230
  );
@@ -274,23 +266,6 @@ export function createR3FRootContext(
274
266
  );
275
267
  });
276
268
  }
277
- } else {
278
- // `input` IS a field of every R3F world's ctx — it is game-owned, and this
279
- // particular mount has no Game to own it (a bare harness or a foreign
280
- // host). A throwing getter names that at the read, instead of handing back
281
- // `undefined` that breaks deep inside a frame callback. Non-enumerable so
282
- // spreads/serialization of the ctx never trip it.
283
- Object.defineProperty(ctxRaw, 'input', {
284
- configurable: true,
285
- enumerable: false,
286
- get(): never {
287
- throw new Error(
288
- `R3F world "${id}": ctx.input is game-owned and this mount has no Game host ` +
289
- '(host.game is absent — bare harness/foreign host); mount through the vgai host ' +
290
- 'to get the shared InputManager.',
291
- );
292
- },
293
- });
294
269
  }
295
270
 
296
271
  return {