@vgai/engine 0.5.21 → 0.5.23
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/README.md +6 -3
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +2 -4
- package/dist/adapter/adapter-surface.d.ts +1 -1
- package/dist/adapter/adapter-surface.js +1 -1
- package/dist/adapter/authoring.d.ts +19 -21
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -2
- package/dist/adapter/binding.d.ts +341 -0
- package/dist/adapter/binding.d.ts.map +1 -0
- package/dist/adapter/binding.js +148 -0
- package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -1
- package/dist/adapter/colyseus-networking-adapter.js +23 -4
- package/dist/adapter/entrypoint-selection-readers.d.ts +24 -0
- package/dist/adapter/entrypoint-selection-readers.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-readers.js +63 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -1
- package/dist/adapter/entrypoint-selection-source.js +1 -44
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +1 -52
- package/dist/adapter/host-context.d.ts +46 -15
- package/dist/adapter/host-context.d.ts.map +1 -1
- package/dist/adapter/host-context.js +2 -9
- package/dist/adapter/index.d.ts +11 -12
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +9 -10
- package/dist/adapter/ingest/game-contract.d.ts +0 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +0 -3
- package/dist/adapter/ingest/scene-capture.d.ts +19 -23
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +143 -17
- package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/manifest-interpreter.d.ts +59 -0
- package/dist/adapter/manifest-interpreter.d.ts.map +1 -0
- package/dist/adapter/manifest-interpreter.js +50 -0
- package/dist/adapter/native-debug-module.d.ts +71 -3
- package/dist/adapter/native-debug-module.d.ts.map +1 -1
- package/dist/adapter/native-debug-module.js +146 -10
- package/dist/adapter/rapier-physics-adapter.d.ts +31 -1
- package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -1
- package/dist/adapter/rapier-physics-adapter.js +50 -36
- package/dist/adapter/renderer-config.d.ts +3 -3
- package/dist/adapter/renderer-config.js +3 -3
- package/dist/adapter/root-adapter.d.ts +7 -7
- package/dist/adapter/root-adapter.js +4 -4
- package/dist/adapter/system-adapter.d.ts +45 -7
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +3 -3
- package/dist/adapter/system-seam-contract.js +3 -3
- package/dist/adapter/system-slot.d.ts +168 -0
- package/dist/adapter/system-slot.d.ts.map +1 -0
- package/dist/adapter/system-slot.js +229 -0
- package/dist/adapter/transform.d.ts +11 -1
- package/dist/adapter/transform.d.ts.map +1 -1
- package/dist/ai/navigation.d.ts +17 -1
- package/dist/ai/navigation.d.ts.map +1 -1
- package/dist/ai/navigation.js +26 -5
- package/dist/animation/anim-graph-types.d.ts +11 -3
- package/dist/animation/anim-graph-types.d.ts.map +1 -1
- package/dist/animation/theatre-clock-binding.d.ts +4 -4
- package/dist/animation/theatre-clock-binding.js +4 -4
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +2 -2
- package/dist/animation/xstate-animation-meta.d.ts +13 -0
- package/dist/animation/xstate-animation-meta.d.ts.map +1 -1
- package/dist/animation/xstate-animation-meta.js +14 -0
- package/dist/asset-formats/index.d.ts +4 -7
- package/dist/asset-formats/index.d.ts.map +1 -1
- package/dist/asset-formats/index.js +4 -7
- package/dist/asset-formats/render-env.d.ts +5 -8
- package/dist/asset-formats/render-env.d.ts.map +1 -1
- package/dist/asset-formats/render-env.js +3 -6
- package/dist/asset-parse-error.d.ts +2 -5
- package/dist/asset-parse-error.d.ts.map +1 -1
- package/dist/asset-parse-error.js +2 -5
- package/dist/audio/bus-mixer.d.ts +101 -0
- package/dist/audio/bus-mixer.d.ts.map +1 -0
- package/dist/audio/bus-mixer.js +115 -0
- package/dist/canvas-react/index.d.ts +5 -12
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +5 -12
- package/dist/canvas-react/pixi-react-root-factory.d.ts +34 -21
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +274 -36
- package/dist/core/countdown-timer.d.ts +118 -0
- package/dist/core/countdown-timer.d.ts.map +1 -0
- package/dist/core/countdown-timer.js +137 -0
- package/dist/core/deferred-commands.d.ts +127 -0
- package/dist/core/deferred-commands.d.ts.map +1 -0
- package/dist/core/deferred-commands.js +132 -0
- package/dist/core/frame-pacing.d.ts +1 -1
- package/dist/core/frame-pacing.js +2 -2
- package/dist/core/game-loop.d.ts +1 -3
- package/dist/core/game-loop.d.ts.map +1 -1
- package/dist/core/game-loop.js +1 -3
- package/dist/core/seeded-random.d.ts +1 -1
- package/dist/core/sim-clock.d.ts +19 -8
- package/dist/core/sim-clock.d.ts.map +1 -1
- package/dist/core/sim-clock.js +18 -7
- package/dist/core/types.d.ts +1 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/defaults.d.ts +6 -14
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +6 -14
- package/dist/dev/instruments.d.ts +38 -11
- package/dist/dev/instruments.d.ts.map +1 -1
- package/dist/dev/instruments.js +81 -11
- package/dist/dev/register-render-vitals.d.ts +1 -4
- package/dist/dev/register-render-vitals.d.ts.map +1 -1
- package/dist/dev/register-render-vitals.js +1 -4
- package/dist/dev/render-debug-adapter.d.ts +2 -3
- package/dist/dev/render-debug-adapter.d.ts.map +1 -1
- package/dist/dev/render-debug-adapter.js +2 -3
- package/dist/dev/render-vitals.d.ts +3 -3
- package/dist/dev/render-vitals.js +3 -3
- package/dist/dev/webgl-frame-capture.d.ts +1 -1
- package/dist/dev/webgl-frame-capture.js +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/input/host-pointer.d.ts +122 -0
- package/dist/input/host-pointer.d.ts.map +1 -0
- package/dist/input/host-pointer.js +160 -0
- package/dist/input/input-manager.d.ts +33 -5
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +48 -13
- package/dist/input/rebind-controller.d.ts.map +1 -1
- package/dist/input/rebind-controller.js +6 -8
- package/dist/input/schema.d.ts.map +1 -1
- package/dist/input/schema.js +56 -28
- package/dist/manifest/filename.d.ts.map +1 -1
- package/dist/manifest/filename.js +8 -12
- package/dist/manifest/load.d.ts +10 -10
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +9 -6
- package/dist/manifest/schema.d.ts +17 -21
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +45 -35
- package/dist/physics/collider-dimensions.d.ts +8 -12
- package/dist/physics/collider-dimensions.d.ts.map +1 -1
- package/dist/physics/collider-dimensions.js +8 -12
- package/dist/pixi/scene-capture.d.ts +11 -13
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/react/world-state.d.ts +1 -1
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/render/directional-shadow-fit.d.ts +88 -0
- package/dist/render/directional-shadow-fit.d.ts.map +1 -0
- package/dist/render/directional-shadow-fit.js +133 -0
- package/dist/render/environment-capture.d.ts +68 -0
- package/dist/render/environment-capture.d.ts.map +1 -0
- package/dist/render/environment-capture.js +90 -0
- package/dist/render/ibl-override-material.d.ts +97 -0
- package/dist/render/ibl-override-material.d.ts.map +1 -0
- package/dist/render/ibl-override-material.js +114 -0
- package/dist/render/light-camera-factory.d.ts +5 -7
- package/dist/render/light-camera-factory.d.ts.map +1 -1
- package/dist/render/light-camera-factory.js +8 -11
- package/dist/render/material-factory.js +1 -1
- package/dist/render/render-batch-system.d.ts.map +1 -1
- package/dist/render/render-features.d.ts +17 -6
- package/dist/render/render-features.d.ts.map +1 -1
- package/dist/render/render-features.js +0 -28
- package/dist/runtime/create-runtime.d.ts +34 -25
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +218 -93
- package/dist/runtime/debug-registry.d.ts +77 -8
- package/dist/runtime/debug-registry.d.ts.map +1 -1
- package/dist/runtime/debug-registry.js +3 -3
- package/dist/runtime/dev-build.d.ts +2 -2
- package/dist/runtime/dev-build.js +2 -2
- package/dist/runtime/game-input-seams.d.ts +51 -0
- package/dist/runtime/game-input-seams.d.ts.map +1 -0
- package/dist/runtime/game-input-seams.js +86 -0
- package/dist/runtime/game.d.ts +104 -40
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +119 -61
- package/dist/runtime/mount-game.d.ts +0 -16
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +26 -41
- package/dist/runtime/mount-manifest.d.ts +22 -14
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +22 -20
- package/dist/runtime/playtest.d.ts +22 -0
- package/dist/runtime/playtest.d.ts.map +1 -0
- package/dist/runtime/playtest.js +12 -0
- package/dist/runtime/state-bridge.d.ts +2 -3
- package/dist/runtime/state-bridge.d.ts.map +1 -1
- package/dist/runtime/state-bridge.js +1 -0
- package/dist/setup/setup-audio.d.ts +12 -0
- package/dist/setup/setup-audio.d.ts.map +1 -1
- package/dist/setup/setup-audio.js +9 -10
- package/dist/setup/setup-renderer.d.ts +2 -25
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +2 -31
- package/dist/world3d-react/index.d.ts +15 -25
- package/dist/world3d-react/index.d.ts.map +1 -1
- package/dist/world3d-react/index.js +15 -25
- package/dist/world3d-react/r3f-root-factory.d.ts +28 -34
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +403 -57
- package/dist/world3d-react/rapier-physics-bridge.d.ts +30 -17
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +143 -58
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +16 -14
- package/schemas/vgai-project.schema.json +35 -26
- package/src/adapter/adapter-module.ts +2 -4
- package/src/adapter/adapter-surface.ts +1 -1
- package/src/adapter/authoring.ts +19 -21
- package/src/adapter/binding.ts +468 -0
- package/src/adapter/colyseus-networking-adapter.ts +20 -5
- package/src/adapter/entrypoint-selection-readers.ts +66 -0
- package/src/adapter/entrypoint-selection-source.ts +1 -40
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
- package/src/adapter/host-context.ts +45 -15
- package/src/adapter/index.ts +23 -11
- package/src/adapter/ingest/game-contract.ts +0 -3
- package/src/adapter/ingest/scene-capture.ts +190 -39
- package/src/adapter/ingest/visible-capture-window.ts +35 -0
- package/src/adapter/manifest-interpreter.ts +64 -0
- package/src/adapter/native-debug-module.ts +248 -15
- package/src/adapter/rapier-physics-adapter.ts +76 -20
- package/src/adapter/renderer-config.ts +3 -3
- package/src/adapter/root-adapter.ts +7 -7
- package/src/adapter/system-adapter.ts +43 -8
- package/src/adapter/system-seam-contract.ts +3 -3
- package/src/adapter/system-slot.ts +291 -0
- package/src/adapter/transform.ts +18 -1
- package/src/ai/navigation.ts +30 -3
- package/src/animation/anim-graph-types.ts +11 -3
- package/src/animation/theatre-clock-binding.ts +4 -4
- package/src/animation/xstate-animation-binding.ts +2 -2
- package/src/animation/xstate-animation-meta.ts +16 -0
- package/src/asset-formats/index.ts +4 -7
- package/src/asset-formats/render-env.ts +3 -6
- package/src/asset-parse-error.ts +2 -5
- package/src/audio/bus-mixer.ts +161 -0
- package/src/canvas-react/index.ts +5 -21
- package/src/canvas-react/pixi-react-root-factory.tsx +301 -39
- package/src/core/countdown-timer.ts +188 -0
- package/src/core/deferred-commands.ts +174 -0
- package/src/core/frame-pacing.ts +2 -2
- package/src/core/game-loop.ts +1 -3
- package/src/core/seeded-random.ts +1 -1
- package/src/core/sim-clock.ts +19 -8
- package/src/core/types.ts +1 -3
- package/src/defaults.ts +6 -14
- package/src/dev/instruments.ts +108 -11
- package/src/dev/register-render-vitals.ts +1 -4
- package/src/dev/render-debug-adapter.ts +2 -3
- package/src/dev/render-vitals.ts +3 -3
- package/src/dev/webgl-frame-capture.ts +1 -1
- package/src/index.ts +2 -5
- package/src/input/host-pointer.ts +230 -0
- package/src/input/input-manager.ts +49 -13
- package/src/input/input-types.ts +1 -1
- package/src/input/rebind-controller.ts +6 -8
- package/src/input/schema.ts +216 -188
- package/src/manifest/filename.ts +8 -12
- package/src/manifest/load.ts +17 -13
- package/src/manifest/schema.ts +55 -47
- package/src/physics/collider-dimensions.ts +8 -12
- package/src/pixi/scene-capture.ts +12 -15
- package/src/react/world-state.tsx +1 -1
- package/src/render/directional-shadow-fit.ts +156 -0
- package/src/render/environment-capture.ts +102 -0
- package/src/render/ibl-override-material.ts +170 -0
- package/src/render/light-camera-factory.ts +8 -11
- package/src/render/material-factory.ts +1 -1
- package/src/render/render-batch-system.ts +14 -6
- package/src/render/render-features.ts +17 -35
- package/src/runtime/create-runtime.ts +270 -108
- package/src/runtime/debug-registry.ts +84 -8
- package/src/runtime/dev-build.ts +2 -2
- package/src/runtime/game-input-seams.ts +108 -0
- package/src/runtime/game.ts +231 -85
- package/src/runtime/mount-game.ts +31 -57
- package/src/runtime/mount-manifest.ts +47 -36
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/state-bridge.ts +3 -3
- package/src/setup/setup-audio.ts +21 -11
- package/src/setup/setup-renderer.ts +2 -61
- package/src/world3d-react/index.ts +15 -31
- package/src/world3d-react/r3f-root-factory.tsx +451 -60
- package/src/world3d-react/rapier-physics-bridge.tsx +154 -58
- package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
- package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
- package/dist/adapter/setup-three-root-adapter.js +0 -908
- package/dist/animation/clip-map.d.ts +0 -12
- package/dist/animation/clip-map.d.ts.map +0 -1
- package/dist/animation/clip-map.js +0 -31
- package/dist/asset-registry.d.ts +0 -38
- package/dist/asset-registry.d.ts.map +0 -1
- package/dist/asset-registry.js +0 -66
- package/dist/canvas-react/engine-bridge.d.ts +0 -45
- package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
- package/dist/canvas-react/engine-bridge.js +0 -45
- package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
- package/dist/canvas-react/pixi-react-adapter.js +0 -294
- package/dist/canvas-react/world-context.d.ts +0 -98
- package/dist/canvas-react/world-context.d.ts.map +0 -1
- package/dist/canvas-react/world-context.js +0 -173
- package/dist/dev/debug-draw.d.ts +0 -24
- package/dist/dev/debug-draw.d.ts.map +0 -1
- package/dist/dev/debug-draw.js +0 -73
- package/dist/render/auto-batcher.d.ts +0 -34
- package/dist/render/auto-batcher.d.ts.map +0 -1
- package/dist/render/auto-batcher.js +0 -140
- package/dist/render/lod.d.ts +0 -13
- package/dist/render/lod.d.ts.map +0 -1
- package/dist/render/lod.js +0 -16
- package/dist/runtime/types.d.ts +0 -303
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js +0 -1
- package/dist/world3d-react/engine-bridge.d.ts +0 -47
- package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
- package/dist/world3d-react/engine-bridge.js +0 -73
- package/dist/world3d-react/r3f-adapter.d.ts +0 -58
- package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
- package/dist/world3d-react/r3f-adapter.js +0 -634
- package/dist/world3d-react/world-context.d.ts +0 -182
- package/dist/world3d-react/world-context.d.ts.map +0 -1
- package/dist/world3d-react/world-context.js +0 -235
- package/src/adapter/setup-three-root-adapter.ts +0 -1032
- package/src/animation/clip-map.ts +0 -34
- package/src/asset-registry.ts +0 -89
- package/src/canvas-react/engine-bridge.ts +0 -59
- package/src/canvas-react/pixi-react-adapter.tsx +0 -356
- package/src/canvas-react/world-context.ts +0 -253
- package/src/dev/debug-draw.ts +0 -80
- package/src/render/auto-batcher.ts +0 -168
- package/src/render/lod.ts +0 -17
- package/src/runtime/types.ts +0 -328
- package/src/world3d-react/engine-bridge.ts +0 -86
- package/src/world3d-react/r3f-adapter.tsx +0 -717
- package/src/world3d-react/world-context.ts +0 -358
package/dist/runtime/game.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createDebugRegistry, DebugError, registerDebugRegistry, } from './debug
|
|
|
16
16
|
import { createGameplayRngTrap, registerGameplayRngTrapControl } from './gameplay-rng-trap';
|
|
17
17
|
import { createStateBridge } from './state-bridge';
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* The tail of `PHASE_ORDER` that presents rather than simulates, and
|
|
20
20
|
* therefore runs once per DISPLAY frame (`runRenderFrameImpl`) rather than
|
|
21
21
|
* once per fixed substep. The same two phases `runTicks`' `skipRenderPhases`
|
|
22
22
|
* fast-forward skips and `render-control.ts`'s `renderOnce()` drives — one
|
|
@@ -31,7 +31,7 @@ const DISPLAY_RATE_PHASES = PHASE_ORDER.filter((phase) => phase === SystemPhase.
|
|
|
31
31
|
* slice or later ones) gets identical throw behavior.
|
|
32
32
|
*/
|
|
33
33
|
export function createRootInstance(init) {
|
|
34
|
-
const { id, kind, pausable = true, adapter, mounted, scene, stage, container, physics, collisions, physics2d, camera, frame, } = init;
|
|
34
|
+
const { id, kind, pausable = true, adapter, mounted, scene, stage, container, physics, collisions, physics2d, camera, frame, binding = null, } = init;
|
|
35
35
|
// A three world with no scene has nothing for `threeScene()` to return —
|
|
36
36
|
// fail loudly HERE, at construction, rather than letting `threeScene()`
|
|
37
37
|
// throw its generic "not a three world" message later for a world whose
|
|
@@ -64,11 +64,11 @@ export function createRootInstance(init) {
|
|
|
64
64
|
// read, so this wraps `mounted.dispose` in place (mutating the SAME mount
|
|
65
65
|
// object every holder of `mounted` shares — calling `mounted.dispose()`
|
|
66
66
|
// directly, exactly like calling `world.mounted.dispose()`, trips this)
|
|
67
|
-
// to flip a
|
|
68
|
-
// once
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
67
|
+
// to flip a flag this instance exposes as `disposed`, and wraps the frame
|
|
68
|
+
// hooks so they silently no-op once it is set. `runFrame` reads `disposed`
|
|
69
|
+
// and skips the world outright, which is what also covers the OPAQUE
|
|
70
|
+
// `mounted.update` fallback — the path every three root takes now that a
|
|
71
|
+
// mount is a plain `RootAdapter` with no phase-partitioned entry point.
|
|
72
72
|
let disposed = false;
|
|
73
73
|
const originalDispose = mounted.dispose.bind(mounted);
|
|
74
74
|
mounted.dispose = () => {
|
|
@@ -100,6 +100,10 @@ export function createRootInstance(init) {
|
|
|
100
100
|
physics2d,
|
|
101
101
|
camera,
|
|
102
102
|
frame: guardedFrame,
|
|
103
|
+
binding,
|
|
104
|
+
get disposed() {
|
|
105
|
+
return disposed;
|
|
106
|
+
},
|
|
103
107
|
threeScene() {
|
|
104
108
|
if (kind !== 'three' || !scene) {
|
|
105
109
|
throw new Error(`RootInstance "${id}" (kind: ${kind}): threeScene() requested but this world is not ` +
|
|
@@ -130,22 +134,6 @@ export function createRootInstance(init) {
|
|
|
130
134
|
},
|
|
131
135
|
};
|
|
132
136
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Type guard for whether a `MountedThreeRoot` is a first-party
|
|
135
|
-
* `SetupThreeRootAdapter` mount (has a live `GameContext` at `.ctx`). Used to
|
|
136
|
-
* decide whether a world's `physics`/`collisions`/`camera` can be populated
|
|
137
|
-
* from it — an external
|
|
138
|
-
* adapter's mount has none of these first-party handles.
|
|
139
|
-
*
|
|
140
|
-
* Checks the `firstParty: true` brand (checklist item 1), NOT `'ctx' in
|
|
141
|
-
* mounted` — a foreign mount may carry an unrelated `ctx` key, so a
|
|
142
|
-
* structural `'ctx' in mounted` check would misfire. The probe is a plain property read, not an
|
|
143
|
-
* `instanceof`/value import of `setup-three-root-adapter.ts` — the
|
|
144
|
-
* `MountedSetupThreeRoot` import above stays type-only.
|
|
145
|
-
*/
|
|
146
|
-
export function isFirstPartyMounted(mounted) {
|
|
147
|
-
return mounted.firstParty === true;
|
|
148
|
-
}
|
|
149
137
|
/**
|
|
150
138
|
* Read an owner's Rapier handles, treating an absent pair as "this owner holds
|
|
151
139
|
* no rigid bodies" — the honest reading for a world (R3F, or a bare
|
|
@@ -162,15 +150,14 @@ function readRapierHandles(owner) {
|
|
|
162
150
|
* only *when*; this is the *what*, and it lives here because it is the runtime
|
|
163
151
|
* that knows about Rapier and shared geometry.
|
|
164
152
|
*
|
|
165
|
-
* The order mirrors `adapter/
|
|
153
|
+
* The order mirrors `adapter/world3d-react/r3f-root-factory.tsx`'s world teardown for
|
|
166
154
|
* ONE subtree. Within the physics step,
|
|
167
155
|
* `rapierWorld.removeRigidBody(body)` comes BEFORE `physics.remove(node)`: the
|
|
168
156
|
* registry's `remove()` only drops index entries, so reversing the two leaks
|
|
169
157
|
* the Rapier body.
|
|
170
158
|
*
|
|
171
|
-
* `owners` is every world that could own part of the subtree
|
|
172
|
-
*
|
|
173
|
-
* adapter mount passes its own ctx. `physics.get` is a no-op for a node the
|
|
159
|
+
* `owners` is every world that could own part of the subtree. `physics.get`
|
|
160
|
+
* is a no-op for a node the
|
|
174
161
|
* owner does not own, so offering the subtree to each is safe.
|
|
175
162
|
*
|
|
176
163
|
* Safe on an object already removed or already disposed: `removeFromParent` on
|
|
@@ -212,13 +199,17 @@ export function disposeDebrisSubtree(obj, owners) {
|
|
|
212
199
|
}
|
|
213
200
|
}
|
|
214
201
|
/**
|
|
215
|
-
* The {@link DebrisOwner}s one registered world contributes
|
|
216
|
-
*
|
|
202
|
+
* The {@link DebrisOwner}s one registered world contributes. Written as a
|
|
203
|
+
* standalone function
|
|
217
204
|
* so `createGame`'s clock disposer is one line and the "which mounts count"
|
|
218
205
|
* rule has exactly one home.
|
|
219
206
|
*/
|
|
220
|
-
function debrisOwnersOf(
|
|
221
|
-
|
|
207
|
+
function debrisOwnersOf(_world) {
|
|
208
|
+
// No mount carries first-party Rapier handles: physics is a declared
|
|
209
|
+
// system built inside the world's own tree, and the library owns its
|
|
210
|
+
// bodies' lifecycles there. The owner list stays as the disposal seam's
|
|
211
|
+
// shape; today it is always empty.
|
|
212
|
+
return [];
|
|
222
213
|
}
|
|
223
214
|
/**
|
|
224
215
|
* Construct the (host-internal) Game shell. Callers: `createGameRuntime`
|
|
@@ -233,7 +224,7 @@ export function createGame(opts) {
|
|
|
233
224
|
const input = new InputManager();
|
|
234
225
|
const stateBridge = createStateBridge();
|
|
235
226
|
// D15 (T-D15.1) — the game-scoped seeded-random surface every world's
|
|
236
|
-
// `ctx.random` aliases (see `
|
|
227
|
+
// `ctx.random` aliases (see `world3d-react/r3f-root-factory.tsx`'s `ctx.random =
|
|
237
228
|
// ...`, wired the same way `ctx.debug` is just below). Constructed
|
|
238
229
|
// unconditionally (cheap — a handful of closures) regardless of whether
|
|
239
230
|
// this project ever declares `determinism.seededRandom`; only the BOOT
|
|
@@ -266,7 +257,7 @@ export function createGame(opts) {
|
|
|
266
257
|
// the CURRENT values.
|
|
267
258
|
let tick = 0;
|
|
268
259
|
let simT = 0;
|
|
269
|
-
//
|
|
260
|
+
// The display-rate half. `renderAlpha` is the last alpha
|
|
270
261
|
// `runRenderFrameImpl` presented at (0 until a host drives one); the set is
|
|
271
262
|
// the `RenderStepped`-shaped registry `Game.onRenderStep` feeds. Deliberately
|
|
272
263
|
// NOT beside `tick`/`simT` in meaning: neither of these ever advances sim
|
|
@@ -282,7 +273,7 @@ export function createGame(opts) {
|
|
|
282
273
|
//
|
|
283
274
|
// The disposer is supplied HERE rather than inside the clock because
|
|
284
275
|
// `core/sim-clock.ts` deliberately knows nothing about Rapier or shared
|
|
285
|
-
// geometry. It mirrors `
|
|
276
|
+
// geometry. It mirrors `world3d-react/r3f-root-factory.tsx`'s world teardown
|
|
286
277
|
// ordering for ONE subtree.
|
|
287
278
|
const simClock = createSimClock({
|
|
288
279
|
// Every mount that could own part of the subtree is offered it:
|
|
@@ -335,7 +326,17 @@ export function createGame(opts) {
|
|
|
335
326
|
// above's "once per game instance" idiom.
|
|
336
327
|
const warnedSystemAdapterKeys = new Set();
|
|
337
328
|
const systemAdapterListeners = new Set();
|
|
338
|
-
|
|
329
|
+
/** Per-root slots installed by `installDeclaredSystemAdapters` — the native
|
|
330
|
+
* module surface's static `systems` declaration. Merged BEFORE the same
|
|
331
|
+
* root's live `mounted.systems` below, so during migration a lingering
|
|
332
|
+
* component registration for a declared slot is shadowed (with the
|
|
333
|
+
* standard warning) rather than silently winning by running later. */
|
|
334
|
+
const declaredSystemAdapters = new Map();
|
|
335
|
+
/** Per-root POSITIVE ABSENCES from the same declaration — `absent(reason)`
|
|
336
|
+
* slots. Nothing binds them (there is nothing to bind); they are kept so a
|
|
337
|
+
* reader can tell an answered absence from an unasked question. */
|
|
338
|
+
const declaredAbsences = new Map();
|
|
339
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one cohesive merge-with-collision-report walk (per-world × per-source × per-key); splitting the collision-warn branch out would obscure that it's part of the same pass, not reduce real complexity
|
|
339
340
|
function computeSystemAdapters() {
|
|
340
341
|
// Debug is game-scoped: React hooks, probes, and the built-in time
|
|
341
342
|
// provider all register with the one registry created above, regardless
|
|
@@ -345,10 +346,7 @@ export function createGame(opts) {
|
|
|
345
346
|
// intentional shared registration it is.
|
|
346
347
|
const result = { debug: debugRegistry.adapter };
|
|
347
348
|
const ownerRootId = new Map([['debug', '(game)']]);
|
|
348
|
-
|
|
349
|
-
const adapters = world.mounted.systems;
|
|
350
|
-
if (!adapters)
|
|
351
|
-
continue;
|
|
349
|
+
const merge = (owner, adapters, quietKeys) => {
|
|
352
350
|
for (const key of Object.keys(adapters)) {
|
|
353
351
|
if (adapters[key] === undefined)
|
|
354
352
|
continue;
|
|
@@ -360,18 +358,35 @@ export function createGame(opts) {
|
|
|
360
358
|
// collision, so it must never warn.
|
|
361
359
|
if (result[key] === adapters[key])
|
|
362
360
|
continue;
|
|
361
|
+
// A world's own DECLARED slot overriding that same world's
|
|
362
|
+
// substrate-seeded default (the three lane pre-seeds physics/audio/
|
|
363
|
+
// renderDebug into `mounted.systems`) is the door working as
|
|
364
|
+
// intended, not a collision — same silence `ctx.
|
|
365
|
+
// registerSystemAdapter` gives the same override. Cross-root
|
|
366
|
+
// collisions still warn.
|
|
367
|
+
if (quietKeys?.has(key))
|
|
368
|
+
continue;
|
|
363
369
|
if (!warnedSystemAdapterKeys.has(key)) {
|
|
364
370
|
warnedSystemAdapterKeys.add(key);
|
|
365
371
|
// biome-ignore lint/suspicious/noConsole: structured, greppable — mirrors this file's own reportGateShortfallOnce's deliberate direct console.warn just above
|
|
366
|
-
console.warn(`[game] systemAdapters: "${key}" is registered by both
|
|
367
|
-
|
|
372
|
+
console.warn(`[game] systemAdapters: "${key}" is registered by both ${existingOwner} and ` +
|
|
373
|
+
`${owner} — the FIRST registration (${existingOwner}) wins; the later ` +
|
|
368
374
|
'one is shadowed (game-scoped system adapters).');
|
|
369
375
|
}
|
|
370
376
|
continue;
|
|
371
377
|
}
|
|
372
378
|
// biome-ignore lint/suspicious/noExplicitAny: SystemAdapters is a plain optional-field record; the per-key copy is correct by construction (same key on both sides), just not expressible without a cast
|
|
373
379
|
result[key] = adapters[key];
|
|
374
|
-
ownerRootId.set(key,
|
|
380
|
+
ownerRootId.set(key, owner);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
for (const world of roots) {
|
|
384
|
+
const declared = declaredSystemAdapters.get(world.id);
|
|
385
|
+
if (declared)
|
|
386
|
+
merge(`world "${world.id}" (declared systems export)`, declared);
|
|
387
|
+
const adapters = world.mounted.systems;
|
|
388
|
+
if (adapters) {
|
|
389
|
+
merge(`world "${world.id}"`, adapters, declared ? new Set(Object.keys(declared)) : undefined);
|
|
375
390
|
}
|
|
376
391
|
}
|
|
377
392
|
return result;
|
|
@@ -405,6 +420,14 @@ export function createGame(opts) {
|
|
|
405
420
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: fans out TWO independent capability gates (loop, audio) with the same "call it, else report once" shape per world — splitting the two gates into separate loops would duplicate the fan-out, not reduce real complexity
|
|
406
421
|
function setRootGates(next) {
|
|
407
422
|
for (const world of roots) {
|
|
423
|
+
// A disposed world stays in `roots` (there is no `unregisterRoot`), and
|
|
424
|
+
// its adapter's `setPaused`/`systems.audio` reach a renderer, ticker or
|
|
425
|
+
// audio graph that `dispose()` already released — the same reason
|
|
426
|
+
// `runFrame`/`runRenderFrame`/`runTicks` skip it. Without this, a
|
|
427
|
+
// pause/resume after a partial teardown calls into freed resources, and
|
|
428
|
+
// the audio leg re-mutes a context that no longer exists.
|
|
429
|
+
if (world.disposed)
|
|
430
|
+
continue;
|
|
408
431
|
if (!world.pausable)
|
|
409
432
|
continue; // pausable:false roots are untouched by design
|
|
410
433
|
if (world.mounted.drivesOwnLoop) {
|
|
@@ -435,14 +458,14 @@ export function createGame(opts) {
|
|
|
435
458
|
if (rngTrapEnabled)
|
|
436
459
|
rngTrap.enable();
|
|
437
460
|
profiler.beginFrame();
|
|
438
|
-
//
|
|
461
|
+
// The render-phase skip. Two callers set it, for the
|
|
439
462
|
// same reason — this substep is not the thing that paints. `runTicks`'s
|
|
440
463
|
// `render: 'none'|'last'` fast-forward sets it on every tick it doesn't
|
|
441
464
|
// want to paint (see `runTicks`'s doc comment on `GameInternal`), and a
|
|
442
465
|
// DISPLAY-RATE host sets it on every substep because `runRenderFrame`
|
|
443
|
-
// paints once per real frame instead. Unlike `onlyFrozen` below it is
|
|
444
|
-
//
|
|
445
|
-
// signature, since an external host is
|
|
466
|
+
// paints once per real frame instead. Unlike `onlyFrozen` below it is not
|
|
467
|
+
// internal-only: it is part of the public `GameInternal.runFrame`
|
|
468
|
+
// signature, since an external host is one of those two callers.
|
|
446
469
|
const skipRenderPhases = frameOpts?.skipRenderPhases ?? false;
|
|
447
470
|
// `onlyFrozen` is internal-only (not part of the public `GameInternal.runFrame`
|
|
448
471
|
// signature — no external caller sets it) — `Game.play.step()` below is the
|
|
@@ -460,6 +483,8 @@ export function createGame(opts) {
|
|
|
460
483
|
inputFrameActive = false;
|
|
461
484
|
for (let i = 0; i < n; i++) {
|
|
462
485
|
const world = roots[i];
|
|
486
|
+
if (world.disposed)
|
|
487
|
+
continue;
|
|
463
488
|
if (onlyFrozen) {
|
|
464
489
|
if (paused && world.pausable && !world.mounted.drivesOwnLoop) {
|
|
465
490
|
inputFrameActive = true;
|
|
@@ -487,6 +512,8 @@ export function createGame(opts) {
|
|
|
487
512
|
if (!onlyFrozen) {
|
|
488
513
|
for (let i = 0; i < n; i++) {
|
|
489
514
|
const world = roots[i];
|
|
515
|
+
if (world.disposed)
|
|
516
|
+
continue;
|
|
490
517
|
if (!world.mounted.drivesOwnLoop)
|
|
491
518
|
continue;
|
|
492
519
|
if (!paused || !world.pausable || !world.mounted.setPaused) {
|
|
@@ -512,6 +539,8 @@ export function createGame(opts) {
|
|
|
512
539
|
systems.runPhase(phase, dt);
|
|
513
540
|
for (let i = 0; i < n; i++) {
|
|
514
541
|
const world = roots[i];
|
|
542
|
+
if (world.disposed)
|
|
543
|
+
continue;
|
|
515
544
|
if (world.mounted.drivesOwnLoop)
|
|
516
545
|
continue;
|
|
517
546
|
if (!(paused && world.pausable))
|
|
@@ -528,6 +557,8 @@ export function createGame(opts) {
|
|
|
528
557
|
}
|
|
529
558
|
for (let i = 0; i < n; i++) {
|
|
530
559
|
const world = roots[i];
|
|
560
|
+
if (world.disposed)
|
|
561
|
+
continue;
|
|
531
562
|
if (world.mounted.drivesOwnLoop)
|
|
532
563
|
continue;
|
|
533
564
|
if (!(paused && world.pausable))
|
|
@@ -563,6 +594,8 @@ export function createGame(opts) {
|
|
|
563
594
|
systems.runPhase(phase, dt);
|
|
564
595
|
for (let i = 0; i < n; i++) {
|
|
565
596
|
const world = roots[i];
|
|
597
|
+
if (world.disposed)
|
|
598
|
+
continue;
|
|
566
599
|
if (world.mounted.drivesOwnLoop)
|
|
567
600
|
continue;
|
|
568
601
|
// D10/T7.6: a `pausable` world under an active pause
|
|
@@ -590,6 +623,8 @@ export function createGame(opts) {
|
|
|
590
623
|
}
|
|
591
624
|
for (let i = 0; i < n; i++) {
|
|
592
625
|
const world = roots[i];
|
|
626
|
+
if (world.disposed)
|
|
627
|
+
continue;
|
|
593
628
|
if (world.mounted.drivesOwnLoop)
|
|
594
629
|
continue;
|
|
595
630
|
// A fully-frozen world gets no `endFrame`/opaque-`update` call either —
|
|
@@ -645,7 +680,7 @@ export function createGame(opts) {
|
|
|
645
680
|
rngTrap.disable();
|
|
646
681
|
}
|
|
647
682
|
/**
|
|
648
|
-
*
|
|
683
|
+
* One display frame's PRESENTATION pass. See
|
|
649
684
|
* `GameInternal.runRenderFrame`'s doc comment for the contract; this is the
|
|
650
685
|
* `preRender`+`render` slice of `runFrameImpl`'s phase loop, lifted out and
|
|
651
686
|
* driven by the loop's own per-real-frame callback instead of by the substep
|
|
@@ -693,6 +728,8 @@ export function createGame(opts) {
|
|
|
693
728
|
systems.runPhase(phase, displayDt);
|
|
694
729
|
for (let i = 0; i < n; i++) {
|
|
695
730
|
const world = roots[i];
|
|
731
|
+
if (world.disposed)
|
|
732
|
+
continue;
|
|
696
733
|
if (world.mounted.drivesOwnLoop)
|
|
697
734
|
continue;
|
|
698
735
|
// D10/T7.6, carried over verbatim in substance: a frozen world still
|
|
@@ -741,6 +778,26 @@ export function createGame(opts) {
|
|
|
741
778
|
for (const listener of systemAdapterListeners)
|
|
742
779
|
listener();
|
|
743
780
|
},
|
|
781
|
+
get declaredSystemAbsences() {
|
|
782
|
+
return [...declaredAbsences.values()].flat();
|
|
783
|
+
},
|
|
784
|
+
installDeclaredSystemAdapters(rootId, slots, absent = []) {
|
|
785
|
+
if (!roots.some((w) => w.id === rootId)) {
|
|
786
|
+
throw new Error(`installDeclaredSystemAdapters: no mounted root is named "${rootId}" ` +
|
|
787
|
+
`(mounted: ${roots.map((w) => `"${w.id}"`).join(', ') || 'none'}).`);
|
|
788
|
+
}
|
|
789
|
+
if (declaredSystemAdapters.has(rootId)) {
|
|
790
|
+
throw new Error(`installDeclaredSystemAdapters: root "${rootId}" already installed its declared ` +
|
|
791
|
+
'systems — a declaration is static and installs exactly once per mount.');
|
|
792
|
+
}
|
|
793
|
+
declaredSystemAdapters.set(rootId, slots);
|
|
794
|
+
// ALWAYS set, never merge: a re-install (an HMR of the entry's `systems`
|
|
795
|
+
// table) that dropped an `absent()` marker must also drop the recorded
|
|
796
|
+
// absence, or the coverage report keeps printing a game statement the
|
|
797
|
+
// game no longer makes.
|
|
798
|
+
declaredAbsences.set(rootId, absent);
|
|
799
|
+
gameInternal.notifySystemAdaptersChanged();
|
|
800
|
+
},
|
|
744
801
|
input,
|
|
745
802
|
state: stateBridge,
|
|
746
803
|
play: {
|
|
@@ -769,6 +826,11 @@ export function createGame(opts) {
|
|
|
769
826
|
// not paused, nothing here runs — step() is a whole-call no-op.
|
|
770
827
|
if (paused) {
|
|
771
828
|
for (const world of roots) {
|
|
829
|
+
// Same disposed-world guard `runFrameImpl` (and `setRootGates`)
|
|
830
|
+
// make: a disposed world's `mounted.step` drives a loop whose
|
|
831
|
+
// resources are already freed.
|
|
832
|
+
if (world.disposed)
|
|
833
|
+
continue;
|
|
772
834
|
if (!world.pausable || !world.mounted.drivesOwnLoop)
|
|
773
835
|
continue;
|
|
774
836
|
if (!world.mounted.setPaused)
|
|
@@ -794,8 +856,8 @@ export function createGame(opts) {
|
|
|
794
856
|
// `endFrame` called once per registration) and double-dispose-wrapped
|
|
795
857
|
// (`createRootInstance` wraps `mounted.dispose` in place — a second
|
|
796
858
|
// wrap would flip `disposed` and call through on ITS OWN wrapped
|
|
797
|
-
// `originalDispose`, which
|
|
798
|
-
//
|
|
859
|
+
// `originalDispose`, which would be harmless only if the adapter's
|
|
860
|
+
// dispose happened to be idempotent; a foreign adapter
|
|
799
861
|
// has no such guarantee). Reject it outright instead.
|
|
800
862
|
if (roots.some((w) => w.mounted === world.mounted)) {
|
|
801
863
|
throw new Error(`Game.registerRoot: world "${world.id}" shares its \`mounted\` object with an ` +
|
|
@@ -805,14 +867,14 @@ export function createGame(opts) {
|
|
|
805
867
|
roots.push(world);
|
|
806
868
|
gameInternal.notifySystemAdaptersChanged();
|
|
807
869
|
// Notify state-bridge subscribers THE INSTANT the world list changes —
|
|
808
|
-
// not just at the next completed `runFrame`.
|
|
809
|
-
//
|
|
810
|
-
//
|
|
870
|
+
// not just at the next completed `runFrame`. Otherwise a UI that lists
|
|
871
|
+
// worlds hangs on an empty list: `mountAllRootSpecs`
|
|
872
|
+
// (`create-runtime.ts`) mounts roots
|
|
811
873
|
// SEQUENTIALLY, and a react world's `adapter.mount()`
|
|
812
874
|
// (`resolveDefaultReactAdapter`/`mountOneReactRoot`) renders its tree —
|
|
813
875
|
// synchronously in some React builds, but React 19's concurrent
|
|
814
876
|
// renderer does NOT guarantee a synchronous first commit (see
|
|
815
|
-
// `r3f-
|
|
877
|
+
// `r3f-root-factory.tsx`'s own doc comment on `onCreated`) — BEFORE the
|
|
816
878
|
// caller calls `registerRoot` for that very world. A `useWorldState`
|
|
817
879
|
// selector reading `g.roots` can therefore render for the first time
|
|
818
880
|
// while `roots` is still missing entries that register moments later.
|
|
@@ -856,15 +918,11 @@ export function createGame(opts) {
|
|
|
856
918
|
// world mount), eroding the signal for a genuinely un-observable
|
|
857
919
|
// ingested world.
|
|
858
920
|
// A HOST-DRIVEN mount (`drivesOwnLoop: false`, notably the R3F adapter)
|
|
859
|
-
// is observable through `Game.state`
|
|
860
|
-
//
|
|
861
|
-
// `runFrame`, so the frame-versioned bridge covers it. Requiring a
|
|
921
|
+
// is observable through `Game.state` when it ticks inside
|
|
922
|
+
// `runFrame` — the frame-versioned bridge covers it. Requiring a
|
|
862
923
|
// foreign-world `observe` bridge there produces a false warning while
|
|
863
924
|
// the canonical bridge is already live.
|
|
864
|
-
if (!
|
|
865
|
-
world.mounted.drivesOwnLoop &&
|
|
866
|
-
world.kind !== 'dom' &&
|
|
867
|
-
!world.mounted.observe) {
|
|
925
|
+
if (world.mounted.drivesOwnLoop && world.kind !== 'dom' && !world.mounted.observe) {
|
|
868
926
|
console.warn(`[game] world "${world.id}" (kind: ${world.kind}, adapter: "${world.adapter.id}"): ` +
|
|
869
927
|
'no state bridge — this mounted game has no `observe` (RootStateObserver); ' +
|
|
870
928
|
'react HUDs/useRootObservation cannot subscribe to its state.');
|
|
@@ -952,7 +1010,7 @@ export function createGame(opts) {
|
|
|
952
1010
|
// root's `mounted.dispose()`, whereas a per-root teardown may be ending
|
|
953
1011
|
// just one sub-session while sibling roots keep running (see the
|
|
954
1012
|
// disposed-world guard in `runFrame`). It used to be disposed from
|
|
955
|
-
// `
|
|
1013
|
+
// `world3d-react/r3f-root-factory.tsx`'s teardown, which meant disposing one of
|
|
956
1014
|
// two three roots froze `now()` for the whole Game, rejected the other
|
|
957
1015
|
// world's pending `delay`s and turned its `after()` calls into silent
|
|
958
1016
|
// no-ops. Note the asymmetry that gives the bug away: `seededRandom` and
|
|
@@ -80,22 +80,6 @@ export declare function isAdapterRegistered(kind: string): boolean;
|
|
|
80
80
|
* registry without cross-test leakage.
|
|
81
81
|
*/
|
|
82
82
|
export declare function __clearAdapterSurfaceRegistryForTests(): void;
|
|
83
|
-
/**
|
|
84
|
-
* A ready-to-register {@link AdapterSurfaceFactory} for `kind: 'three'` roots
|
|
85
|
-
* that follow the first-party entry-module convention (`export const
|
|
86
|
-
* adapter` and/or `export async function setup`). Threejs is safe to
|
|
87
|
-
* ship as an exported convenience (unlike canvas/react) because `three`/
|
|
88
|
-
* `SetupThreeRootAdapter` are already unconditional engine dependencies —
|
|
89
|
-
* see `mount-manifest.ts`'s header comment. Still never auto-registered:
|
|
90
|
-
* the registry itself stays zero-policy (see this file's header comment) —
|
|
91
|
-
* a caller opts in with `registerAdapter('three', defaultThreeAdapterFactory)`.
|
|
92
|
-
*
|
|
93
|
-
* Resolution mirrors `mount-manifest.ts`'s own `resolveThreeAdapter` branch
|
|
94
|
-
* order: an entry module's `adapter` export wins outright; else its `setup`
|
|
95
|
-
* export (wrapping via `fromSetup` happens inside `mountManifestRoots`
|
|
96
|
-
* itself — this factory just returns the `MountEntry` shape).
|
|
97
|
-
*/
|
|
98
|
-
export declare const defaultThreeAdapterFactory: AdapterSurfaceFactory;
|
|
99
83
|
export interface MountGameOptions {
|
|
100
84
|
/** Explicit per-world entries, same shape `mountManifestRoots` accepts —
|
|
101
85
|
* ALWAYS wins over a registered kind factory for that world id (backward
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-game.d.ts","sourceRoot":"","sources":["../../src/runtime/mount-game.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount-game.d.ts","sourceRoot":"","sources":["../../src/runtime/mount-game.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,KAAK,UAAU,EAAuC,MAAM,kBAAkB,CAAC;AAOxF;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;6DACyD;IACzD,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD;;;mFAG+E;IAC/E,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC;gEAC4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED,+EAA+E;AAC/E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,KAAK,EAAE,mBAAmB,EAC1B,GAAG,EAAE,4BAA4B,KAC9B,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAQtC;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAUlF;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,qCAAqC,IAAI,IAAI,CAE5D;AAMD,MAAM,WAAW,gBAAgB;IAC/B;;;kEAG8D;IAC9D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC;CACrE;AA+BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,OAAO,EACtB,IAAI,EAAE,YAAY,EAClB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAqDtB"}
|
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
// callers register, but it registers NONE itself. In particular there is no
|
|
13
13
|
// built-in 'canvas'/'react' registration here (that would require this file
|
|
14
14
|
// to value-import `pixi.js`/`react`/`react-dom`, exactly what `mount-manifest
|
|
15
|
-
// .ts` forbids); a project that has a canvas or react world MUST
|
|
16
|
-
// factory for that kind itself —
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
15
|
+
// .ts` forbids); a project that has a three, canvas, or react world MUST
|
|
16
|
+
// register a factory for that kind itself — the factories live with the
|
|
17
|
+
// modules that own their dependencies (`r3fRootFactory` in
|
|
18
|
+
// `@engine/world3d-react`, its Pixi twin in `@engine/canvas-react`), never
|
|
19
|
+
// wired up here automatically.
|
|
20
|
+
import { declaredRoots } from '../adapter/manifest-interpreter';
|
|
21
|
+
import { installNativeDebugBindings, installNativeSystemsBindings, nativeDebugBindingFromEntryModule, nativeSystemsBindingFromEntryModule, } from '../adapter/native-debug-module';
|
|
22
|
+
import { publishDevInstruments } from '../dev/instruments';
|
|
20
23
|
import { mountManifestRoots, resolveManifest } from './mount-manifest';
|
|
21
24
|
import { assertExportedOrInEditor } from './unexported-game-trap';
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
@@ -61,44 +64,12 @@ export function isAdapterRegistered(kind) {
|
|
|
61
64
|
export function __clearAdapterSurfaceRegistryForTests() {
|
|
62
65
|
registry.clear();
|
|
63
66
|
}
|
|
64
|
-
/**
|
|
65
|
-
* A ready-to-register {@link AdapterSurfaceFactory} for `kind: 'three'` roots
|
|
66
|
-
* that follow the first-party entry-module convention (`export const
|
|
67
|
-
* adapter` and/or `export async function setup`). Threejs is safe to
|
|
68
|
-
* ship as an exported convenience (unlike canvas/react) because `three`/
|
|
69
|
-
* `SetupThreeRootAdapter` are already unconditional engine dependencies —
|
|
70
|
-
* see `mount-manifest.ts`'s header comment. Still never auto-registered:
|
|
71
|
-
* the registry itself stays zero-policy (see this file's header comment) —
|
|
72
|
-
* a caller opts in with `registerAdapter('three', defaultThreeAdapterFactory)`.
|
|
73
|
-
*
|
|
74
|
-
* Resolution mirrors `mount-manifest.ts`'s own `resolveThreeAdapter` branch
|
|
75
|
-
* order: an entry module's `adapter` export wins outright; else its `setup`
|
|
76
|
-
* export (wrapping via `fromSetup` happens inside `mountManifestRoots`
|
|
77
|
-
* itself — this factory just returns the `MountEntry` shape).
|
|
78
|
-
*/
|
|
79
|
-
export const defaultThreeAdapterFactory = (world, ctx) => {
|
|
80
|
-
const mod = ctx.entryModule;
|
|
81
|
-
if (mod?.adapter)
|
|
82
|
-
return { kind: 'three', adapter: mod.adapter };
|
|
83
|
-
if (mod?.setup)
|
|
84
|
-
return { kind: 'three', setup: mod.setup };
|
|
85
|
-
if (world.entry !== undefined) {
|
|
86
|
-
throw new Error(`defaultThreeAdapterFactory: entry module "${world.entry}" for world "${world.id}" exports ` +
|
|
87
|
-
'neither `adapter` nor `setup` — every first-party entry module exports at least one ' +
|
|
88
|
-
'(export one from the declared entry module).');
|
|
89
|
-
}
|
|
90
|
-
throw new Error(`defaultThreeAdapterFactory: world "${world.id}" declares no \`entry\` — a three root is ` +
|
|
91
|
-
'authored as a TSX/R3F world module; point `entry` at it.');
|
|
92
|
-
};
|
|
93
67
|
/** True for the one shape `mountManifestRoots` can ALREADY mount with zero
|
|
94
68
|
* `entries[id]` at all.
|
|
95
69
|
*
|
|
96
|
-
*
|
|
97
|
-
* `default`-adapter, SCENE-driven, no-`entry` three world, which
|
|
98
|
-
* `resolveThreeAdapter`'s scene branch could mount from the manifest alone —
|
|
99
|
-
* died with the `.vscn.json` format. `load.ts` now rejects a three root with no
|
|
70
|
+
* NOTHING is self-sufficient: `load.ts` rejects a three root with no
|
|
100
71
|
* `entry` outright, so every world needs a registered factory or an explicit
|
|
101
|
-
* entry
|
|
72
|
+
* entry. This predicate is retained so the loud-degrade error stays the
|
|
102
73
|
* documented outcome rather than a silent skip. */
|
|
103
74
|
function worldIsSelfSufficient(_world) {
|
|
104
75
|
return false;
|
|
@@ -109,6 +80,12 @@ function nativeDebugForEntry(world, entryModule) {
|
|
|
109
80
|
const binding = nativeDebugBindingFromEntryModule(world.id, entryModule);
|
|
110
81
|
return binding ? [binding] : [];
|
|
111
82
|
}
|
|
83
|
+
function nativeSystemsForEntry(world, entryModule) {
|
|
84
|
+
if (entryModule === undefined || world.adapter.type !== 'builtin')
|
|
85
|
+
return [];
|
|
86
|
+
const binding = nativeSystemsBindingFromEntryModule(world.id, entryModule, world.surface);
|
|
87
|
+
return binding ? [binding] : [];
|
|
88
|
+
}
|
|
112
89
|
/**
|
|
113
90
|
* Mount every world declared by a `vgai.project.json` manifest onto `host
|
|
114
91
|
* .container`, resolving each world's kind through the `registerAdapter`
|
|
@@ -137,7 +114,8 @@ export async function mountGameFromManifest(manifestInput, host, opts = {}) {
|
|
|
137
114
|
const manifest = resolveManifest(manifestInput);
|
|
138
115
|
const entries = { ...opts.entries };
|
|
139
116
|
const nativeDebug = [];
|
|
140
|
-
|
|
117
|
+
const nativeSystems = [];
|
|
118
|
+
for (const world of declaredRoots(manifest)) {
|
|
141
119
|
if (entries[world.id] !== undefined)
|
|
142
120
|
continue; // explicit entry always wins
|
|
143
121
|
const factory = registry.get(world.adapter.identity);
|
|
@@ -146,6 +124,7 @@ export async function mountGameFromManifest(manifestInput, host, opts = {}) {
|
|
|
146
124
|
? await host.loadEntryModule(world.entry)
|
|
147
125
|
: undefined;
|
|
148
126
|
nativeDebug.push(...nativeDebugForEntry(world, entryModule));
|
|
127
|
+
nativeSystems.push(...nativeSystemsForEntry(world, entryModule));
|
|
149
128
|
entries[world.id] = await factory(world, { host, entryModule });
|
|
150
129
|
continue;
|
|
151
130
|
}
|
|
@@ -154,7 +133,7 @@ export async function mountGameFromManifest(manifestInput, host, opts = {}) {
|
|
|
154
133
|
throw new Error(`mountGameFromManifest: root "${world.id}" (adapter "${world.adapter.identity}") has no registered ` +
|
|
155
134
|
`adapter factory and no explicit entries["${world.id}"] — call ` +
|
|
156
135
|
`registerAdapter("${world.adapter.identity}", factory) before mounting (see ` +
|
|
157
|
-
'`
|
|
136
|
+
'`r3fRootFactory` in `@vgai/engine/world3d-react` for the worked three factory), or pass ' +
|
|
158
137
|
`opts.entries["${world.id}"] directly (see mount-manifest.ts's \`MountEntry\`).`);
|
|
159
138
|
}
|
|
160
139
|
const session = await mountManifestRoots({
|
|
@@ -166,5 +145,11 @@ export async function mountGameFromManifest(manifestInput, host, opts = {}) {
|
|
|
166
145
|
headless: host.headless,
|
|
167
146
|
});
|
|
168
147
|
installNativeDebugBindings(session.game, nativeDebug);
|
|
148
|
+
installNativeSystemsBindings(session.game, nativeSystems);
|
|
149
|
+
// The universal instruments are host furniture on EVERY mount path — the
|
|
150
|
+
// editor installs them in its own bindings pass; the standalone composer
|
|
151
|
+
// installs them here. The disposer is deliberately dropped: the set's one
|
|
152
|
+
// resource lives on this Game and dies with it.
|
|
153
|
+
publishDevInstruments(session.game);
|
|
169
154
|
return session;
|
|
170
155
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { RootDeclaration } from '../adapter/binding';
|
|
1
2
|
import type { RootAdapter } from '../adapter/root-adapter';
|
|
2
3
|
import { type ResolvedGameManifest } from '../manifest/load';
|
|
3
4
|
import { type GameSession } from './create-runtime';
|
|
4
5
|
import { type DebugBridgeWindowTarget } from './debug-bridge';
|
|
5
|
-
import type {
|
|
6
|
+
import type { PlaytestContext } from './playtest';
|
|
6
7
|
/**
|
|
7
8
|
* A caller-supplied entry for a `kind: 'three'` world — two ways to satisfy
|
|
8
9
|
* it, matching the two shapes `resolveDefaultThreeAdapter` supports (an
|
|
@@ -11,20 +12,27 @@ import type { GameSetupFn, PlaytestContext } from './types';
|
|
|
11
12
|
* - `{ adapter }` — a fully-constructed {@link RootAdapter} (first-party or
|
|
12
13
|
* not). Always wins if present, regardless of the world's `entry` field —
|
|
13
14
|
* full caller control, including for a `{ module }`-adapter world
|
|
14
|
-
* (mountManifestRoots cannot import an arbitrary module path itself
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
* `SetupThreeRootAdapter` here, mirroring `resolveDefaultThreeAdapter`'s
|
|
19
|
-
* entry branch exactly.
|
|
20
|
-
*
|
|
21
|
-
* The former third shape, `{ componentRegistry }` for a `scene`-declaring
|
|
22
|
-
* world, went with the `.vscn.json` format in WO-8.
|
|
15
|
+
* (mountManifestRoots cannot import an arbitrary module path itself —
|
|
16
|
+
* for a default-exported world, build the adapter with
|
|
17
|
+
* `resolveR3FEntryAdapter(entryModule, worldId)` from
|
|
18
|
+
* `@vgai/engine/world3d-react` and pass it here).
|
|
23
19
|
*/
|
|
24
|
-
export interface ThreeMountEntry {
|
|
20
|
+
export interface ThreeMountEntry extends MountEntryDeclaration {
|
|
25
21
|
readonly kind: 'three';
|
|
26
22
|
readonly adapter?: RootAdapter | undefined;
|
|
27
|
-
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The half of a root's binding declaration a CALLER holds — everything in
|
|
26
|
+
* `adapter/binding.ts`'s {@link RootDeclaration} except `root`, which this
|
|
27
|
+
* file already has from the manifest it just resolved.
|
|
28
|
+
*
|
|
29
|
+
* Optional throughout, and absent is the honest state for a caller who built
|
|
30
|
+
* an adapter by hand out of its own module graph: there is no entry namespace
|
|
31
|
+
* to report a static surface for, so the root registers with `binding: null`
|
|
32
|
+
* rather than one describing a module nobody loaded.
|
|
33
|
+
*/
|
|
34
|
+
export interface MountEntryDeclaration {
|
|
35
|
+
readonly declaration?: Omit<RootDeclaration, 'root'> | undefined;
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
* A caller-supplied entry for a `kind: 'canvas'` world — ALWAYS a
|
|
@@ -34,7 +42,7 @@ export interface ThreeMountEntry {
|
|
|
34
42
|
* `pixi.js`, which it deliberately never does (see this file's header
|
|
35
43
|
* comment).
|
|
36
44
|
*/
|
|
37
|
-
export interface PixiMountEntry {
|
|
45
|
+
export interface PixiMountEntry extends MountEntryDeclaration {
|
|
38
46
|
readonly kind: 'canvas';
|
|
39
47
|
readonly adapter: RootAdapter<'canvas'>;
|
|
40
48
|
}
|
|
@@ -51,7 +59,7 @@ export interface PixiMountEntry {
|
|
|
51
59
|
* itself (see this file's header comment) — building the adapter is
|
|
52
60
|
* entirely the caller's job.
|
|
53
61
|
*/
|
|
54
|
-
export interface ReactMountEntry {
|
|
62
|
+
export interface ReactMountEntry extends MountEntryDeclaration {
|
|
55
63
|
readonly kind: 'dom';
|
|
56
64
|
readonly adapter: RootAdapter<'dom'>;
|
|
57
65
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-manifest.d.ts","sourceRoot":"","sources":["../../src/runtime/mount-manifest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount-manifest.d.ts","sourceRoot":"","sources":["../../src/runtime/mount-manifest.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAqB,KAAK,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EAAE,KAAK,uBAAuB,EAA2B,MAAM,gBAAgB,CAAC;AAGvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAOlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAgB,SAAQ,qBAAqB;IAC5D,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC5C;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CAClE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAe,SAAQ,qBAAqB;IAC3D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAgB,SAAQ,qBAAqB;IAC5D,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;CACtC;AAED,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,cAAc,GAAG,eAAe,CAAC;AAM5E,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;6DACyD;IACzD,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC;;qEAEiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC;IACpE;8EAC0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC;gEAC4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EACjB;QACE,QAAQ,CAAC,GAAG,CAAC,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAC;QACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;KACvD,GACD,SAAS,CAAC;IACd;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACvD;;;kFAG8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAC5D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC,aAAa,CAAC,CAAC;IAC1D,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,GAAG,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACvD,GAAG,MAAM,GAAG,SAAS,CAIrB;AAkBD,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC;AA8BjD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,CAGlE;AAiID;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAuFzF"}
|