@vgai/engine 0.5.41 → 0.5.44

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 (129) hide show
  1. package/dist/adapter/adapter-module.d.ts +28 -29
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +11 -34
  4. package/dist/adapter/authoring-seam-contract.d.ts +10 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
  6. package/dist/adapter/authoring-seam-contract.js +2 -0
  7. package/dist/adapter/authoring.d.ts +31 -0
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/finders/finder-result.d.ts +2 -2
  10. package/dist/adapter/finders/finder-result.d.ts.map +1 -1
  11. package/dist/adapter/finders/index.d.ts +15 -2
  12. package/dist/adapter/finders/index.d.ts.map +1 -1
  13. package/dist/adapter/finders/index.js +38 -10
  14. package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -1
  15. package/dist/adapter/finders/registry.d.ts +45 -0
  16. package/dist/adapter/finders/registry.d.ts.map +1 -0
  17. package/dist/adapter/finders/registry.js +56 -0
  18. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  19. package/dist/adapter/ingest/scene-capture.js +50 -20
  20. package/dist/adapter/system-adapter.d.ts +3 -1
  21. package/dist/adapter/system-adapter.d.ts.map +1 -1
  22. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  23. package/dist/canvas-react/pixi-react-root-factory.js +21 -11
  24. package/dist/data/vite-plugin-data.d.ts +1 -1
  25. package/dist/data/vite-plugin-data.d.ts.map +1 -1
  26. package/dist/data/vite-plugin-data.js +2 -2
  27. package/dist/ecs/user-data.d.ts +6 -0
  28. package/dist/ecs/user-data.d.ts.map +1 -1
  29. package/dist/ecs/user-data.js +4 -0
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/input/host-pointer.d.ts +18 -0
  34. package/dist/input/host-pointer.d.ts.map +1 -1
  35. package/dist/input/host-pointer.js +54 -5
  36. package/dist/input/input-manager.d.ts +83 -1
  37. package/dist/input/input-manager.d.ts.map +1 -1
  38. package/dist/input/input-manager.js +357 -8
  39. package/dist/manifest/configuration-kinds.d.ts +97 -0
  40. package/dist/manifest/configuration-kinds.d.ts.map +1 -0
  41. package/dist/manifest/configuration-kinds.js +148 -0
  42. package/dist/manifest/index.d.ts +2 -1
  43. package/dist/manifest/index.d.ts.map +1 -1
  44. package/dist/manifest/index.js +4 -3
  45. package/dist/manifest/kind-modules.d.ts +18 -0
  46. package/dist/manifest/kind-modules.d.ts.map +1 -0
  47. package/dist/manifest/kind-modules.js +58 -0
  48. package/dist/manifest/load-file.d.ts +3 -3
  49. package/dist/manifest/load-file.d.ts.map +1 -1
  50. package/dist/manifest/load-file.js +4 -4
  51. package/dist/manifest/load.d.ts +17 -12
  52. package/dist/manifest/load.d.ts.map +1 -1
  53. package/dist/manifest/load.js +29 -19
  54. package/dist/manifest/runtime-environment.d.ts +11 -0
  55. package/dist/manifest/runtime-environment.d.ts.map +1 -0
  56. package/dist/manifest/runtime-environment.js +44 -0
  57. package/dist/manifest/schema.d.ts +10 -9
  58. package/dist/manifest/schema.d.ts.map +1 -1
  59. package/dist/manifest/schema.js +32 -27
  60. package/dist/react/use-data.d.ts +1 -1
  61. package/dist/react/use-data.js +1 -1
  62. package/dist/render/environment-capture.d.ts +10 -1
  63. package/dist/render/environment-capture.d.ts.map +1 -1
  64. package/dist/render/environment-capture.js +25 -0
  65. package/dist/render/ibl-override-material.d.ts +11 -2
  66. package/dist/render/ibl-override-material.d.ts.map +1 -1
  67. package/dist/render/ibl-override-material.js +16 -2
  68. package/dist/render/matcap-texture.d.ts +5 -0
  69. package/dist/render/matcap-texture.d.ts.map +1 -0
  70. package/dist/render/matcap-texture.js +82 -0
  71. package/dist/render/soft-particle-depth.d.ts +14 -8
  72. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  73. package/dist/render/soft-particle-depth.js +37 -14
  74. package/dist/render/viewport-shading.d.ts +11 -1
  75. package/dist/render/viewport-shading.d.ts.map +1 -1
  76. package/dist/render/viewport-shading.js +25 -0
  77. package/dist/runtime/create-runtime.js +6 -5
  78. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  79. package/dist/runtime/debug-bridge.js +17 -3
  80. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  81. package/dist/runtime/mount-manifest.js +11 -2
  82. package/dist/settings/schema.d.ts +99 -0
  83. package/dist/settings/schema.d.ts.map +1 -0
  84. package/dist/settings/schema.js +110 -0
  85. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  86. package/dist/world3d-react/r3f-root-factory.js +34 -36
  87. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  88. package/dist/world3d-react/rapier-physics-bridge.js +21 -6
  89. package/dist-config/config.js +1 -1
  90. package/dist-config/config.js.map +2 -2
  91. package/package.json +2 -1
  92. package/schemas/engine-capabilities.json +4 -4
  93. package/schemas/vgai-project.schema.json +41 -36
  94. package/schemas/vgai-settings.schema.json +112 -0
  95. package/src/adapter/adapter-module.ts +33 -51
  96. package/src/adapter/authoring-seam-contract.ts +2 -0
  97. package/src/adapter/authoring.ts +31 -0
  98. package/src/adapter/finders/finder-result.ts +2 -2
  99. package/src/adapter/finders/index.ts +68 -12
  100. package/src/adapter/finders/prefabs-from-stories.ts +2 -2
  101. package/src/adapter/finders/registry.ts +95 -0
  102. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +4 -4
  103. package/src/adapter/ingest/scene-capture.ts +48 -19
  104. package/src/adapter/system-adapter.ts +3 -1
  105. package/src/canvas-react/pixi-react-root-factory.tsx +21 -11
  106. package/src/data/vite-plugin-data.ts +4 -2
  107. package/src/ecs/user-data.ts +6 -0
  108. package/src/index.ts +1 -1
  109. package/src/input/host-pointer.ts +84 -5
  110. package/src/input/input-manager.ts +404 -7
  111. package/src/manifest/configuration-kinds.ts +241 -0
  112. package/src/manifest/index.ts +4 -3
  113. package/src/manifest/kind-modules.ts +58 -0
  114. package/src/manifest/load-file.ts +11 -5
  115. package/src/manifest/load.ts +53 -22
  116. package/src/manifest/runtime-environment.ts +48 -0
  117. package/src/manifest/schema.ts +39 -33
  118. package/src/react/use-data.ts +1 -1
  119. package/src/render/environment-capture.ts +30 -1
  120. package/src/render/ibl-override-material.ts +32 -2
  121. package/src/render/matcap-texture.ts +92 -0
  122. package/src/render/soft-particle-depth.ts +43 -14
  123. package/src/render/viewport-shading.ts +33 -1
  124. package/src/runtime/create-runtime.ts +6 -5
  125. package/src/runtime/debug-bridge.ts +21 -3
  126. package/src/runtime/mount-manifest.ts +13 -2
  127. package/src/settings/schema.ts +127 -0
  128. package/src/world3d-react/r3f-root-factory.tsx +45 -39
  129. package/src/world3d-react/rapier-physics-bridge.tsx +22 -8
@@ -43,7 +43,14 @@ import {
43
43
  events as pointerEvents,
44
44
  type RootState,
45
45
  } from '@react-three/fiber';
46
- import { type ComponentType, createElement, Fragment, useEffect } from 'react';
46
+ import {
47
+ Component,
48
+ type ComponentType,
49
+ createElement,
50
+ Fragment,
51
+ type PropsWithChildren,
52
+ useEffect,
53
+ } from 'react';
47
54
  import type { MountedThreeRoot, RootAdapter, ThreeHostContext } from '../adapter';
48
55
  import type { SystemAdapters } from '../adapter/system-adapter';
49
56
  import { type RenderVitalsRegistration, registerRenderVitals } from '../dev/register-render-vitals';
@@ -76,6 +83,24 @@ interface R3FEntryModuleExports {
76
83
  readonly default?: ComponentType;
77
84
  }
78
85
 
86
+ /** React errors must reject a mount in Node too, where there is no window
87
+ * error event. This boundary changes no authored content on successful mounts. */
88
+ class MountErrorBoundary extends Component<
89
+ PropsWithChildren<{ onError: (error: Error) => void }>,
90
+ { failed: boolean }
91
+ > {
92
+ override state = { failed: false };
93
+ static getDerivedStateFromError(): { failed: boolean } {
94
+ return { failed: true };
95
+ }
96
+ override componentDidCatch(error: Error): void {
97
+ this.props.onError(error);
98
+ }
99
+ override render() {
100
+ return this.state.failed ? null : this.props.children;
101
+ }
102
+ }
103
+
79
104
  /**
80
105
  * Build the `RootAdapter` for one default-exported R3F world. Mounts through
81
106
  * react-three-fiber, gated entirely by the host's own loop and rendering
@@ -89,29 +114,9 @@ function threeWorldAdapter(id: string, component: ComponentType): RootAdapter {
89
114
  id,
90
115
 
91
116
  async mount(host: ThreeHostContext): Promise<MountedThreeRoot> {
92
- // Headless honesty every `mount()` must work headless: the conformance
93
- // kit runs `mount()` in Node no canvas, no WebGL, no fiber
94
- // reconciler. Guard the ENTIRE fiber mount behind `!host.headless` and
95
- // return a bare, real scene + camera instead (still `instanceof
96
- // host.three.Scene` / `.Camera` — the identity rule holds even here,
97
- // since this uses the host's own `three` instance, not a fresh import).
98
- // Residual: the hierarchy is empty under headless conformance —
99
- // recorded as design O2, not hidden; a react-nil-style headless
100
- // reconciler mount is the known fix, out of scope.
101
- if (host.headless) {
102
- const scene = new host.three.Scene();
103
- const camera = new host.three.PerspectiveCamera();
104
- return {
105
- kind: 'three',
106
- scene,
107
- camera,
108
- drivesOwnLoop: false,
109
- dispose(): void {
110
- /* nothing was ever mounted */
111
- },
112
- };
113
- }
114
-
117
+ // Headless mounts use this same Fiber reconciler and authored tree.
118
+ // Their host supplies an inert draw command; absence of a GPU is not
119
+ // permission to return a successful but empty scene.
115
120
  // Fiber v9 made the THREE catalogue tree-shakeable: `<Canvas>` calls
116
121
  // `extend(THREE)` for you, a bare `createRoot` does NOT — without this,
117
122
  // the FIRST three intrinsic in the tree (`<color>`, `<ambientLight>`,
@@ -208,7 +213,7 @@ function threeWorldAdapter(id: string, component: ComponentType): RootAdapter {
208
213
  // bound to the canvas, no error — and every mesh-level pointer prop
209
214
  // (`onClick`, `onPointerOver`, `onPointerMissed`) is dead. Proven by a
210
215
  // control experiment differing ONLY in this property.
211
- events: pointerEvents,
216
+ ...(host.headless ? {} : { events: pointerEvents }),
212
217
  onCreated: (state) => resolveState(state),
213
218
  });
214
219
 
@@ -231,24 +236,23 @@ function threeWorldAdapter(id: string, component: ComponentType): RootAdapter {
231
236
  createElement(MountEffectsReady, { key: 'vgai-mount-effects-ready' }),
232
237
  );
233
238
  const provided = game ? createElement(WorldProvider, { game }, world) : world;
234
- root.render(provided);
235
239
  // A reconcile-time crash (e.g. a missing `extend` catalogue entry)
236
240
  // surfaces as an uncaught window error and `onCreated` never fires —
237
241
  // without this guard, `mount()` would await `statePromise` FOREVER and
238
242
  // silently wedge every world declared after this one (roots mount
239
243
  // sequentially). Convert that class of failure into a loud mount error.
240
244
  const state = await new Promise<RootState>((resolve, reject) => {
241
- const onError = (event: ErrorEvent) => {
245
+ const fail = (message: string) => {
242
246
  cleanup();
243
247
  root.unmount();
244
248
  gameDebugRegistry?.strip(id);
245
249
  reject(
246
- new Error(
247
- `three world "${id}": fiber crashed before its first commit — ${event.message} ` +
248
- '(mount() fails loudly instead of hanging on onCreated)',
249
- ),
250
+ new Error(`three world "${id}": fiber crashed before its first commit — ${message}`),
250
251
  );
251
252
  };
253
+ const onError = (event: ErrorEvent) => {
254
+ fail(event.message);
255
+ };
252
256
  const timer = setTimeout(() => {
253
257
  cleanup();
254
258
  root.unmount();
@@ -262,9 +266,12 @@ function threeWorldAdapter(id: string, component: ComponentType): RootAdapter {
262
266
  }, 10_000);
263
267
  const cleanup = () => {
264
268
  clearTimeout(timer);
265
- window.removeEventListener('error', onError);
269
+ if (typeof window !== 'undefined') window.removeEventListener('error', onError);
266
270
  };
267
- window.addEventListener('error', onError);
271
+ if (typeof window !== 'undefined') window.addEventListener('error', onError);
272
+ root.render(
273
+ createElement(MountErrorBoundary, { onError: (error) => fail(error.message) }, provided),
274
+ );
268
275
  void Promise.all([statePromise, effectsReady]).then(([s]) => {
269
276
  cleanup();
270
277
  resolve(s);
@@ -301,10 +308,9 @@ function threeWorldAdapter(id: string, component: ComponentType): RootAdapter {
301
308
  // to write. Gate: `devBuildEnabled()` (the ONE owner of "is this a dev
302
309
  // context" — a ship build registers nothing and pays nothing) and a
303
310
  // `Game` shell (the readings are folded out of that game's profiler
304
- // frames). "Not headless" needs no term here: a headless mount returned
305
- // above, before fiber was ever configured.
311
+ // frames). A headless host cannot supply GPU readings.
306
312
  let renderVitals: RenderVitalsRegistration | null = null;
307
- if (host.game && devBuildEnabled()) {
313
+ if (!host.headless && host.game && devBuildEnabled()) {
308
314
  // The readings are folded out of profiler frames, and the profiler is
309
315
  // a flag the editor already sets on play — under the dev gate this is
310
316
  // the same cost arriving a little earlier, not a new one.
@@ -322,11 +328,11 @@ function threeWorldAdapter(id: string, component: ComponentType): RootAdapter {
322
328
  }
323
329
 
324
330
  // Engine-owned render introspection, built over the SAME renderer and
325
- // scene Fiber uses. A design-time stand-in has no context and a
326
- // headless mount returned above, so both honestly omit the capability.
331
+ // scene Fiber uses. A design-time stand-in has no context; a headless
332
+ // mount also honestly omits the GPU capture capability.
327
333
  const rendererContext =
328
334
  typeof host.renderer.getContext === 'function' ? host.renderer.getContext() : undefined;
329
- const frameCaptureContext = frameCaptureContextFor(false, rendererContext);
335
+ const frameCaptureContext = frameCaptureContextFor(host.headless ?? false, rendererContext);
330
336
  const frameCapture = frameCaptureContext
331
337
  ? createWebGLFrameCapture(frameCaptureContext)
332
338
  : null;
@@ -427,17 +427,31 @@ let liveBridgeCount = 0;
427
427
  * silent degrade): a second concurrent bridge mount — a scene that mounted
428
428
  * two, or a second Play instance of a rapier world — is reported the moment
429
429
  * it happens, naming what breaks and until when. */
430
+ /** A contention that resolves within this window is a hand-over, not a state:
431
+ * the editor's design session mounts the NEXT world before it disposes the
432
+ * one on screen (the swap waits for the hand), so every source edit put two
433
+ * bridges on the page for a fraction of a second and printed this warning —
434
+ * which every tester read as instability ("wrap your physics system…",
435
+ * runhuman pass 139). Only a contention that OUTLIVES the hand-over is one. */
436
+ const CONTENTION_GRACE_MS = 2_000;
437
+ let contentionTimer: ReturnType<typeof setTimeout> | null = null;
438
+
430
439
  function warnOnContendedMount(): void {
431
440
  liveBridgeCount += 1;
432
441
  if (liveBridgeCount <= 1) return;
433
- // biome-ignore lint/suspicious/noConsole: the standing-warning channel for a capability the adapter cannot reach — the editor console surfaces it during play
434
- console.warn(
435
- `[rapierPhysicsSystem] ${liveBridgeCount} <RapierPhysicsBridge> mounts are live on this ` +
436
- 'page, but the declared physics slot is one per page (engine modules are shared across ' +
437
- "Play instances). Every instance's `systems.physics` now forwards to the LAST bridge " +
438
- 'mounted, so physics gizmo edits and instruments address that world — not necessarily ' +
439
- 'the one selected until the extra mounts unmount.',
440
- );
442
+ if (contentionTimer !== null) return;
443
+ contentionTimer = setTimeout(() => {
444
+ contentionTimer = null;
445
+ if (liveBridgeCount <= 1) return;
446
+ // biome-ignore lint/suspicious/noConsole: the standing-warning channel for a capability the adapter cannot reach — the editor console surfaces it during play
447
+ console.warn(
448
+ `[rapierPhysicsSystem] ${liveBridgeCount} <RapierPhysicsBridge> mounts are live on this ` +
449
+ 'page, but the declared physics slot is one per page (engine modules are shared across ' +
450
+ "Play instances). Every instance's `systems.physics` now forwards to the LAST bridge " +
451
+ 'mounted, so physics gizmo edits and instruments address that world — not necessarily ' +
452
+ 'the one selected — until the extra mounts unmount.',
453
+ );
454
+ }, CONTENTION_GRACE_MS);
441
455
  }
442
456
 
443
457
  const physicsSlot = createSystemSlot<PhysicsAdapter>({