@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.
- package/dist/adapter/adapter-module.d.ts +28 -29
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +11 -34
- package/dist/adapter/authoring-seam-contract.d.ts +10 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
- package/dist/adapter/authoring-seam-contract.js +2 -0
- package/dist/adapter/authoring.d.ts +31 -0
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/finders/finder-result.d.ts +2 -2
- package/dist/adapter/finders/finder-result.d.ts.map +1 -1
- package/dist/adapter/finders/index.d.ts +15 -2
- package/dist/adapter/finders/index.d.ts.map +1 -1
- package/dist/adapter/finders/index.js +38 -10
- package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -1
- package/dist/adapter/finders/registry.d.ts +45 -0
- package/dist/adapter/finders/registry.d.ts.map +1 -0
- package/dist/adapter/finders/registry.js +56 -0
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +50 -20
- package/dist/adapter/system-adapter.d.ts +3 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +21 -11
- package/dist/data/vite-plugin-data.d.ts +1 -1
- package/dist/data/vite-plugin-data.d.ts.map +1 -1
- package/dist/data/vite-plugin-data.js +2 -2
- package/dist/ecs/user-data.d.ts +6 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/input/host-pointer.d.ts +18 -0
- package/dist/input/host-pointer.d.ts.map +1 -1
- package/dist/input/host-pointer.js +54 -5
- package/dist/input/input-manager.d.ts +83 -1
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +357 -8
- package/dist/manifest/configuration-kinds.d.ts +97 -0
- package/dist/manifest/configuration-kinds.d.ts.map +1 -0
- package/dist/manifest/configuration-kinds.js +148 -0
- package/dist/manifest/index.d.ts +2 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +4 -3
- package/dist/manifest/kind-modules.d.ts +18 -0
- package/dist/manifest/kind-modules.d.ts.map +1 -0
- package/dist/manifest/kind-modules.js +58 -0
- package/dist/manifest/load-file.d.ts +3 -3
- package/dist/manifest/load-file.d.ts.map +1 -1
- package/dist/manifest/load-file.js +4 -4
- package/dist/manifest/load.d.ts +17 -12
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +29 -19
- package/dist/manifest/runtime-environment.d.ts +11 -0
- package/dist/manifest/runtime-environment.d.ts.map +1 -0
- package/dist/manifest/runtime-environment.js +44 -0
- package/dist/manifest/schema.d.ts +10 -9
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +32 -27
- package/dist/react/use-data.d.ts +1 -1
- package/dist/react/use-data.js +1 -1
- package/dist/render/environment-capture.d.ts +10 -1
- package/dist/render/environment-capture.d.ts.map +1 -1
- package/dist/render/environment-capture.js +25 -0
- package/dist/render/ibl-override-material.d.ts +11 -2
- package/dist/render/ibl-override-material.d.ts.map +1 -1
- package/dist/render/ibl-override-material.js +16 -2
- package/dist/render/matcap-texture.d.ts +5 -0
- package/dist/render/matcap-texture.d.ts.map +1 -0
- package/dist/render/matcap-texture.js +82 -0
- package/dist/render/soft-particle-depth.d.ts +14 -8
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +37 -14
- package/dist/render/viewport-shading.d.ts +11 -1
- package/dist/render/viewport-shading.d.ts.map +1 -1
- package/dist/render/viewport-shading.js +25 -0
- package/dist/runtime/create-runtime.js +6 -5
- package/dist/runtime/debug-bridge.d.ts.map +1 -1
- package/dist/runtime/debug-bridge.js +17 -3
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +11 -2
- package/dist/settings/schema.d.ts +99 -0
- package/dist/settings/schema.d.ts.map +1 -0
- package/dist/settings/schema.js +110 -0
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +34 -36
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +21 -6
- package/dist-config/config.js +1 -1
- package/dist-config/config.js.map +2 -2
- package/package.json +2 -1
- package/schemas/engine-capabilities.json +4 -4
- package/schemas/vgai-project.schema.json +41 -36
- package/schemas/vgai-settings.schema.json +112 -0
- package/src/adapter/adapter-module.ts +33 -51
- package/src/adapter/authoring-seam-contract.ts +2 -0
- package/src/adapter/authoring.ts +31 -0
- package/src/adapter/finders/finder-result.ts +2 -2
- package/src/adapter/finders/index.ts +68 -12
- package/src/adapter/finders/prefabs-from-stories.ts +2 -2
- package/src/adapter/finders/registry.ts +95 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +4 -4
- package/src/adapter/ingest/scene-capture.ts +48 -19
- package/src/adapter/system-adapter.ts +3 -1
- package/src/canvas-react/pixi-react-root-factory.tsx +21 -11
- package/src/data/vite-plugin-data.ts +4 -2
- package/src/ecs/user-data.ts +6 -0
- package/src/index.ts +1 -1
- package/src/input/host-pointer.ts +84 -5
- package/src/input/input-manager.ts +404 -7
- package/src/manifest/configuration-kinds.ts +241 -0
- package/src/manifest/index.ts +4 -3
- package/src/manifest/kind-modules.ts +58 -0
- package/src/manifest/load-file.ts +11 -5
- package/src/manifest/load.ts +53 -22
- package/src/manifest/runtime-environment.ts +48 -0
- package/src/manifest/schema.ts +39 -33
- package/src/react/use-data.ts +1 -1
- package/src/render/environment-capture.ts +30 -1
- package/src/render/ibl-override-material.ts +32 -2
- package/src/render/matcap-texture.ts +92 -0
- package/src/render/soft-particle-depth.ts +43 -14
- package/src/render/viewport-shading.ts +33 -1
- package/src/runtime/create-runtime.ts +6 -5
- package/src/runtime/debug-bridge.ts +21 -3
- package/src/runtime/mount-manifest.ts +13 -2
- package/src/settings/schema.ts +127 -0
- package/src/world3d-react/r3f-root-factory.tsx +45 -39
- 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 {
|
|
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
|
|
93
|
-
//
|
|
94
|
-
//
|
|
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
|
|
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).
|
|
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
|
|
326
|
-
//
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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>({
|