@vgai/engine 0.5.22 → 0.5.24
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-seams.d.ts +5 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract-seams.js +1 -0
- package/dist/adapter/ingest/game-contract.d.ts +22 -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/native-debug-module.d.ts +40 -5
- package/dist/adapter/native-debug-module.d.ts.map +1 -1
- package/dist/adapter/native-debug-module.js +36 -9
- 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 +2 -2
- package/dist/adapter/renderer-config.js +2 -2
- 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/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 +5 -6
- package/dist/core/sim-clock.d.ts.map +1 -1
- package/dist/core/sim-clock.js +4 -5
- 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/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 +18 -5
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +33 -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/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-bridge.d.ts +12 -0
- package/dist/runtime/debug-bridge.d.ts.map +1 -1
- package/dist/runtime/debug-bridge.js +4 -0
- package/dist/runtime/debug-registry.d.ts +95 -8
- package/dist/runtime/debug-registry.d.ts.map +1 -1
- package/dist/runtime/debug-registry.js +24 -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 +2 -2
- package/dist/runtime/game-input-seams.d.ts.map +1 -1
- package/dist/runtime/game.d.ts +102 -41
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +80 -54
- 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 +8 -39
- 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 +18 -18
- 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/run-ticks-settled.d.ts +34 -0
- package/dist/runtime/run-ticks-settled.d.ts.map +1 -0
- package/dist/runtime/run-ticks-settled.js +59 -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/r3f-root-factory.d.ts +7 -7
- package/dist/world3d-react/r3f-root-factory.js +7 -7
- package/dist/world3d-react/rapier-physics-bridge.d.ts +9 -8
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +100 -48
- 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-seams.ts +1 -0
- package/src/adapter/ingest/game-contract.ts +22 -3
- package/src/adapter/ingest/scene-capture.ts +190 -39
- package/src/adapter/ingest/visible-capture-window.ts +35 -0
- package/src/adapter/native-debug-module.ts +66 -9
- package/src/adapter/rapier-physics-adapter.ts +76 -20
- package/src/adapter/renderer-config.ts +2 -2
- 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/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 +5 -6
- package/src/core/types.ts +1 -3
- package/src/defaults.ts +6 -14
- 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 +34 -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/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-bridge.ts +16 -0
- package/src/runtime/debug-registry.ts +121 -8
- package/src/runtime/dev-build.ts +2 -2
- package/src/runtime/game-input-seams.ts +2 -2
- package/src/runtime/game.ts +180 -80
- package/src/runtime/mount-game.ts +8 -56
- package/src/runtime/mount-manifest.ts +43 -34
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/run-ticks-settled.ts +72 -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/r3f-root-factory.tsx +7 -7
- package/src/world3d-react/rapier-physics-bridge.tsx +107 -50
- 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/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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHO IS PLAYING, and under what identity — the host's own declaration for one
|
|
3
|
+
* private play run or coordinated team test.
|
|
4
|
+
*
|
|
5
|
+
* Its own module because it is a PUBLISHED path: a project's Colyseus
|
|
6
|
+
* networking adapter (the `colyseus` capability, copied into every game) names
|
|
7
|
+
* this type, so it must live somewhere that survives. The engine never joins a
|
|
8
|
+
* room and never couples this authoring identity to a networking library —
|
|
9
|
+
* games may pass `roomKey` as a Colyseus filter/join option, and that is the
|
|
10
|
+
* whole of the coupling.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runTicksWhenSettled` — the settled-aware tick driver behind BOTH session run-ticks doors
|
|
3
|
+
* (the bridge's `window.__vgai.runTicksSettled`, the editor relay's `run-ticks` case), one
|
|
4
|
+
* implementation with byte-identical semantics across doors, exactly as `runTicks` itself is
|
|
5
|
+
* shared (D17).
|
|
6
|
+
*
|
|
7
|
+
* Why it exists: `Game.runTicks` is deliberately SYNCHRONOUS, but a game may be intentionally
|
|
8
|
+
* BETWEEN worlds when a tick arrives — `reload_current_scene` unmounts the running scene and
|
|
9
|
+
* the fresh one arrives on an ASYNC React commit. A tick driven into that gap runs against
|
|
10
|
+
* nothing, and WHICH tick first runs the fresh world then depends on wall timing between
|
|
11
|
+
* driver calls — measured on the starter-kit fidelity drive: the same script produced 7 or 8
|
|
12
|
+
* post-respawn walked ticks depending on how long the driver idled between ticks. The fidelity
|
|
13
|
+
* contract (docs/MIGRATE-FIDELITY.md) treats a nondeterministic channel as a MECHANISM defect,
|
|
14
|
+
* and this is the mechanism: ticks must not race remounts.
|
|
15
|
+
*
|
|
16
|
+
* So this driver runs the budget ONE tick at a time and, before each tick, waits for every
|
|
17
|
+
* registered world-settled probe ({@link DebugRegistry.registerWorldSettledProbe} — declared by
|
|
18
|
+
* the game through its `debug.settled` entry export) to answer `true`. The wall-clock poll is
|
|
19
|
+
* sound because the OUTCOME no longer depends on timing: however long the commit takes, the
|
|
20
|
+
* next tick is always the fresh world's first. A game that registers no probe gets the old
|
|
21
|
+
* behavior exactly (every wait resolves immediately).
|
|
22
|
+
*
|
|
23
|
+
* The wait is BOUNDED and loud: a remount that never settles is a real defect, and
|
|
24
|
+
* `WORLD_UNSETTLED_TIMEOUT` names it rather than letting the driver hang.
|
|
25
|
+
*/
|
|
26
|
+
import { type DebugRegistry, type RunTicksOptions } from './debug-registry';
|
|
27
|
+
export declare function runTicksWhenSettled(registry: Pick<DebugRegistry, 'worldSettled' | 'getRunTicksTarget'>, n: number, opts?: RunTicksOptions,
|
|
28
|
+
/** Test-only overrides — real callers leave this unset (the same seam
|
|
29
|
+
* convention as fast-forward's `batchTicks`). */
|
|
30
|
+
waits?: {
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
pollMs?: number;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
//# sourceMappingURL=run-ticks-settled.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-ticks-settled.d.ts","sourceRoot":"","sources":["../../src/runtime/run-ticks-settled.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAc,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAQxF,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,cAAc,GAAG,mBAAmB,CAAC,EACnE,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE,eAAe;AACtB;kDACkD;AAClD,KAAK,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runTicksWhenSettled` — the settled-aware tick driver behind BOTH session run-ticks doors
|
|
3
|
+
* (the bridge's `window.__vgai.runTicksSettled`, the editor relay's `run-ticks` case), one
|
|
4
|
+
* implementation with byte-identical semantics across doors, exactly as `runTicks` itself is
|
|
5
|
+
* shared (D17).
|
|
6
|
+
*
|
|
7
|
+
* Why it exists: `Game.runTicks` is deliberately SYNCHRONOUS, but a game may be intentionally
|
|
8
|
+
* BETWEEN worlds when a tick arrives — `reload_current_scene` unmounts the running scene and
|
|
9
|
+
* the fresh one arrives on an ASYNC React commit. A tick driven into that gap runs against
|
|
10
|
+
* nothing, and WHICH tick first runs the fresh world then depends on wall timing between
|
|
11
|
+
* driver calls — measured on the starter-kit fidelity drive: the same script produced 7 or 8
|
|
12
|
+
* post-respawn walked ticks depending on how long the driver idled between ticks. The fidelity
|
|
13
|
+
* contract (docs/MIGRATE-FIDELITY.md) treats a nondeterministic channel as a MECHANISM defect,
|
|
14
|
+
* and this is the mechanism: ticks must not race remounts.
|
|
15
|
+
*
|
|
16
|
+
* So this driver runs the budget ONE tick at a time and, before each tick, waits for every
|
|
17
|
+
* registered world-settled probe ({@link DebugRegistry.registerWorldSettledProbe} — declared by
|
|
18
|
+
* the game through its `debug.settled` entry export) to answer `true`. The wall-clock poll is
|
|
19
|
+
* sound because the OUTCOME no longer depends on timing: however long the commit takes, the
|
|
20
|
+
* next tick is always the fresh world's first. A game that registers no probe gets the old
|
|
21
|
+
* behavior exactly (every wait resolves immediately).
|
|
22
|
+
*
|
|
23
|
+
* The wait is BOUNDED and loud: a remount that never settles is a real defect, and
|
|
24
|
+
* `WORLD_UNSETTLED_TIMEOUT` names it rather than letting the driver hang.
|
|
25
|
+
*/
|
|
26
|
+
import { DebugError } from './debug-registry';
|
|
27
|
+
/** A remount is a couple of React commits on an idle main thread; polling faster buys nothing
|
|
28
|
+
* and slower delays every settled check by the poll period. */
|
|
29
|
+
const SETTLED_POLL_MS = 4;
|
|
30
|
+
/** A commit that hasn't landed after this long is not "in flight", it is stuck. */
|
|
31
|
+
const SETTLED_TIMEOUT_MS = 10_000;
|
|
32
|
+
export async function runTicksWhenSettled(registry, n, opts,
|
|
33
|
+
/** Test-only overrides — real callers leave this unset (the same seam
|
|
34
|
+
* convention as fast-forward's `batchTicks`). */
|
|
35
|
+
waits) {
|
|
36
|
+
const target = registry.getRunTicksTarget();
|
|
37
|
+
if (target === null) {
|
|
38
|
+
throw new DebugError('RUN_TICKS_UNAVAILABLE', 'runTicksWhenSettled: no run-ticks target is wired — no Game has mounted yet');
|
|
39
|
+
}
|
|
40
|
+
const timeoutMs = waits?.timeoutMs ?? SETTLED_TIMEOUT_MS;
|
|
41
|
+
const pollMs = waits?.pollMs ?? SETTLED_POLL_MS;
|
|
42
|
+
const render = opts?.render ?? 'last';
|
|
43
|
+
for (let i = 0; i < n; i++) {
|
|
44
|
+
if (!registry.worldSettled()) {
|
|
45
|
+
const deadline = Date.now() + timeoutMs;
|
|
46
|
+
while (!registry.worldSettled()) {
|
|
47
|
+
if (Date.now() > deadline) {
|
|
48
|
+
throw new DebugError('WORLD_UNSETTLED_TIMEOUT', `runTicksWhenSettled: a world-settled probe still answers false after ${timeoutMs}ms — ` +
|
|
49
|
+
'a scene remount (or a broken probe) is stuck, and driving ticks into it would race it');
|
|
50
|
+
}
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const isFinalTick = i === n - 1;
|
|
55
|
+
// Same per-tick render policy `Game.runTicks` applies across its own loop.
|
|
56
|
+
const tickRender = render === 'all' ? 'all' : render === 'none' ? 'none' : isFinalTick ? 'last' : 'none';
|
|
57
|
+
target.runTicks(1, { render: tickRender });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -46,9 +46,8 @@ export interface GameStateBridge {
|
|
|
46
46
|
export interface GameStateBridgeInternal extends GameStateBridge {
|
|
47
47
|
/** Bump `frameVersion` by one and notify every current subscriber, in
|
|
48
48
|
* subscription order. Subscriber errors are isolated (per-subscriber
|
|
49
|
-
* try/catch, loud `console.error`, never swallowed silently)
|
|
50
|
-
*
|
|
51
|
-
* `update()` calls: one offending subscriber must not stop its siblings
|
|
49
|
+
* try/catch, loud `console.error`, never swallowed silently): one
|
|
50
|
+
* offending subscriber must not stop its siblings
|
|
52
51
|
* from being notified, and must not break the frame loop. */
|
|
53
52
|
bump(): void;
|
|
54
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-bridge.d.ts","sourceRoot":"","sources":["../../src/runtime/state-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B;;;;kEAI8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;6EACyE;IACzE,SAAS,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AAED;;;;;mEAKmE;AACnE,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D
|
|
1
|
+
{"version":3,"file":"state-bridge.d.ts","sourceRoot":"","sources":["../../src/runtime/state-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B;;;;kEAI8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;6EACyE;IACzE,SAAS,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AAED;;;;;mEAKmE;AACnE,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D;;;;kEAI8D;IAC9D,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,uBAAuB,CA+B3D"}
|
|
@@ -54,6 +54,7 @@ export function createStateBridge() {
|
|
|
54
54
|
onFrame();
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
|
+
// biome-ignore lint/suspicious/noConsole: a throwing subscriber must not take the notify loop down, and the console (captured by the editor session ledger) is the engine runtime's one report channel.
|
|
57
58
|
console.error('[state-bridge] subscriber threw:', err);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
+
import { type AudioBusMixer } from '../audio/bus-mixer';
|
|
2
3
|
export interface AudioContext {
|
|
3
4
|
listener: THREE.AudioListener;
|
|
4
5
|
masterGain: GainNode;
|
|
@@ -7,6 +8,17 @@ export interface AudioContext {
|
|
|
7
8
|
sfx: GainNode;
|
|
8
9
|
voice: GainNode;
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* The named-bus mixer the three below are buses OF (`../audio/bus-mixer.ts`).
|
|
13
|
+
*
|
|
14
|
+
* `buses` is a fixed struct because those three names are the ones the engine
|
|
15
|
+
* itself routes; a game with an `ambience`, a `ui` or a per-character bus asks
|
|
16
|
+
* the mixer for it by name — `audio.mixer.bus('ambience', 'master')` mints it
|
|
17
|
+
* under the master gain, `audio.mixer.setDb('ambience', -6)` sets it in
|
|
18
|
+
* decibels — and `audio.mixer.disconnect()` is the ONE teardown for all of
|
|
19
|
+
* them, the three struct members included.
|
|
20
|
+
*/
|
|
21
|
+
mixer: AudioBusMixer;
|
|
10
22
|
/** Call this on first user interaction (click/key) to resume audio context */
|
|
11
23
|
resume: () => void;
|
|
12
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-audio.d.ts","sourceRoot":"","sources":["../../src/setup/setup-audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"setup-audio.d.ts","sourceRoot":"","sources":["../../src/setup/setup-audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC;IAC9B,UAAU,EAAE,QAAQ,CAAC;IACrB,KAAK,EAAE;QACL,KAAK,EAAE,QAAQ,CAAC;QAChB,GAAG,EAAE,QAAQ,CAAC;QACd,KAAK,EAAE,QAAQ,CAAC;KACjB,CAAC;IACF;;;;;;;;;OASG;IACH,KAAK,EAAE,aAAa,CAAC;IACrB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAKD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAkC7D"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
+
import { createAudioBusMixer } from '../audio/bus-mixer';
|
|
3
|
+
/** The bus every other bus routes into, and the node `masterGain` is. */
|
|
4
|
+
const MASTER_BUS = 'master';
|
|
2
5
|
/**
|
|
3
6
|
* Creates the audio listener and bus hierarchy.
|
|
4
7
|
*
|
|
@@ -9,21 +12,16 @@ export function setupAudio(camera) {
|
|
|
9
12
|
const listener = new THREE.AudioListener();
|
|
10
13
|
camera.add(listener);
|
|
11
14
|
const ctx = listener.context;
|
|
12
|
-
const
|
|
13
|
-
masterGain.
|
|
15
|
+
const mixer = createAudioBusMixer({ context: ctx, destination: ctx.destination });
|
|
16
|
+
const masterGain = mixer.bus(MASTER_BUS);
|
|
14
17
|
// Route the Three.js listener through masterGain so that THREE.Audio /
|
|
15
18
|
// THREE.PositionalAudio objects are also silenced by the mute button.
|
|
16
19
|
listener.gain.disconnect();
|
|
17
20
|
listener.gain.connect(masterGain);
|
|
18
|
-
const musicGain =
|
|
21
|
+
const musicGain = mixer.bus('music', MASTER_BUS);
|
|
19
22
|
musicGain.gain.value = 0.7;
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
sfxGain.gain.value = 1.0;
|
|
23
|
-
sfxGain.connect(masterGain);
|
|
24
|
-
const voiceGain = ctx.createGain();
|
|
25
|
-
voiceGain.gain.value = 1.0;
|
|
26
|
-
voiceGain.connect(masterGain);
|
|
23
|
+
const sfxGain = mixer.bus('sfx', MASTER_BUS);
|
|
24
|
+
const voiceGain = mixer.bus('voice', MASTER_BUS);
|
|
27
25
|
return {
|
|
28
26
|
listener,
|
|
29
27
|
masterGain,
|
|
@@ -32,6 +30,7 @@ export function setupAudio(camera) {
|
|
|
32
30
|
sfx: sfxGain,
|
|
33
31
|
voice: voiceGain,
|
|
34
32
|
},
|
|
33
|
+
mixer,
|
|
35
34
|
resume() {
|
|
36
35
|
if (ctx.state === 'suspended') {
|
|
37
36
|
ctx.resume();
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import { EffectComposer } from 'postprocessing';
|
|
1
|
+
import { type EffectComposer } from 'postprocessing';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import type { RenderEnvironment } from '../asset-formats/render-env';
|
|
4
4
|
import { type RenderScope } from '../render/render-settings';
|
|
5
|
-
export interface RendererContext {
|
|
6
|
-
renderer: THREE.WebGLRenderer;
|
|
7
|
-
scene: THREE.Scene;
|
|
8
|
-
camera: THREE.PerspectiveCamera;
|
|
9
|
-
composer: EffectComposer;
|
|
10
|
-
}
|
|
11
5
|
/**
|
|
12
6
|
* Apply the live renderer-level render settings (shadows, resolution scale) from
|
|
13
7
|
* the resolved cascade. `antialias`/`backend` are reload-only (renderer re-init)
|
|
@@ -22,7 +16,7 @@ export declare function applyRendererSettings(renderer: THREE.WebGLRenderer, ren
|
|
|
22
16
|
/**
|
|
23
17
|
* Create ONLY the WebGL renderer. The HOST owns the renderer (see
|
|
24
18
|
* `ThreeHostContext.renderer`); the game/adapter owns the scene/camera/composer it
|
|
25
|
-
* renders into.
|
|
19
|
+
* renders into.
|
|
26
20
|
*
|
|
27
21
|
* `rendering` is the scene-level render scope (`env.rendering`); it seeds the
|
|
28
22
|
* reload-only `antialias` option at construction and the live shadow/resolution
|
|
@@ -39,23 +33,6 @@ export declare function createHostRenderer(canvas: HTMLCanvasElement, width?: nu
|
|
|
39
33
|
alpha?: boolean;
|
|
40
34
|
preserveDrawingBuffer?: boolean;
|
|
41
35
|
}): THREE.WebGLRenderer;
|
|
42
|
-
/**
|
|
43
|
-
* Create the scene, camera, and postprocessing composer that a first-party game
|
|
44
|
-
* renders into, given a host-provided renderer. The GPU-only `composer` is
|
|
45
|
-
* created here; a headless host (no GPU) skips this entirely.
|
|
46
|
-
*/
|
|
47
|
-
export declare function createSceneView(renderer: THREE.WebGLRenderer, width?: number, height?: number): {
|
|
48
|
-
scene: THREE.Scene;
|
|
49
|
-
camera: THREE.PerspectiveCamera;
|
|
50
|
-
composer: EffectComposer;
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Creates the Three.js renderer, scene, camera, and postprocessing pipeline.
|
|
54
|
-
*
|
|
55
|
-
* This is direct Three.js setup — no abstraction. Edit freely. (Now a thin
|
|
56
|
-
* combination of {@link createHostRenderer} + {@link createSceneView}.)
|
|
57
|
-
*/
|
|
58
|
-
export declare function setupRenderer(canvas: HTMLCanvasElement, width?: number, height?: number): RendererContext;
|
|
59
36
|
/**
|
|
60
37
|
* Rebuild composer passes from scene environment settings.
|
|
61
38
|
* Scenes without toneMapping default to ACES (the runtime default).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-renderer.d.ts","sourceRoot":"","sources":["../../src/setup/setup-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,EAQL,cAAc,
|
|
1
|
+
{"version":3,"file":"setup-renderer.d.ts","sourceRoot":"","sources":["../../src/setup/setup-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,EAQL,KAAK,cAAc,EAoBpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,2BAA2B,CAAC;AAEpF;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,SAAS,CAAC,EAAE,WAAW,EACvB,cAAc,SAAuC,GACpD,IAAI,CAKN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,KAAK,SAAoB,EACzB,MAAM,SAAqB,EAC3B,SAAS,CAAC,EAAE,WAAW,EACvB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1D,KAAK,CAAC,aAAa,CAwBrB;AAkBD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,GAAG,CAAC,EAAE,iBAAiB,EACvB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EACvC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACjC,IAAI,CAsVN;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,GAAG,CAAC,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC,GACA,IAAI,CAeN"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference path="../types-n8ao.d.ts" />
|
|
2
2
|
import { N8AOPostPass } from 'n8ao';
|
|
3
|
-
import { BloomEffect, BrightnessContrastEffect, ChromaticAberrationEffect, ColorDepthEffect, DepthOfFieldEffect, DotScreenEffect,
|
|
3
|
+
import { BloomEffect, BrightnessContrastEffect, ChromaticAberrationEffect, ColorDepthEffect, DepthOfFieldEffect, DotScreenEffect, EffectPass, GlitchEffect, GodRaysEffect, GridEffect, HueSaturationEffect, LensDistortionEffect, NoiseEffect, NormalPass, OutlineEffect, PixelationEffect, RenderPass, ScanlineEffect, SepiaEffect, ShockWaveEffect, SMAAEffect, SMAAPreset, SSAOEffect, TiltShiftEffect, VignetteEffect, } from 'postprocessing';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { MotionBlurEffect, SSGIEffect, SSREffect, VelocityDepthNormalPass, } from '../../vendor/realism-effects/dist/index.js';
|
|
6
6
|
import { DEFAULTS } from '../defaults';
|
|
@@ -25,7 +25,7 @@ export function applyRendererSettings(renderer, rendering, basePixelRatio = Math
|
|
|
25
25
|
/**
|
|
26
26
|
* Create ONLY the WebGL renderer. The HOST owns the renderer (see
|
|
27
27
|
* `ThreeHostContext.renderer`); the game/adapter owns the scene/camera/composer it
|
|
28
|
-
* renders into.
|
|
28
|
+
* renders into.
|
|
29
29
|
*
|
|
30
30
|
* `rendering` is the scene-level render scope (`env.rendering`); it seeds the
|
|
31
31
|
* reload-only `antialias` option at construction and the live shadow/resolution
|
|
@@ -63,35 +63,6 @@ export function createHostRenderer(canvas, width = window.innerWidth, height = w
|
|
|
63
63
|
detectKtx2Support(renderer);
|
|
64
64
|
return renderer;
|
|
65
65
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Create the scene, camera, and postprocessing composer that a first-party game
|
|
68
|
-
* renders into, given a host-provided renderer. The GPU-only `composer` is
|
|
69
|
-
* created here; a headless host (no GPU) skips this entirely.
|
|
70
|
-
*/
|
|
71
|
-
export function createSceneView(renderer, width = window.innerWidth, height = window.innerHeight) {
|
|
72
|
-
const safeWidth = Math.max(1, width);
|
|
73
|
-
const safeHeight = Math.max(1, height);
|
|
74
|
-
const scene = new THREE.Scene();
|
|
75
|
-
scene.background = new THREE.Color(0x1a1a2e);
|
|
76
|
-
const camera = new THREE.PerspectiveCamera(DEFAULTS.camera.fov, safeWidth / safeHeight, DEFAULTS.camera.near, DEFAULTS.camera.far);
|
|
77
|
-
camera.position.set(0, 5, 10);
|
|
78
|
-
camera.lookAt(0, 0, 0);
|
|
79
|
-
const composer = new EffectComposer(renderer);
|
|
80
|
-
composer.addPass(new RenderPass(scene, camera));
|
|
81
|
-
composer.addPass(new EffectPass(camera, new BloomEffect({ luminanceThreshold: DEFAULTS.postProcessing.bloom.luminanceThreshold })));
|
|
82
|
-
return { scene, camera, composer };
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Creates the Three.js renderer, scene, camera, and postprocessing pipeline.
|
|
86
|
-
*
|
|
87
|
-
* This is direct Three.js setup — no abstraction. Edit freely. (Now a thin
|
|
88
|
-
* combination of {@link createHostRenderer} + {@link createSceneView}.)
|
|
89
|
-
*/
|
|
90
|
-
export function setupRenderer(canvas, width = window.innerWidth, height = window.innerHeight) {
|
|
91
|
-
const renderer = createHostRenderer(canvas, width, height);
|
|
92
|
-
const { scene, camera, composer } = createSceneView(renderer, width, height);
|
|
93
|
-
return { renderer, scene, camera, composer };
|
|
94
|
-
}
|
|
95
66
|
const toneMappingModes = {
|
|
96
67
|
none: THREE.NoToneMapping,
|
|
97
68
|
linear: THREE.LinearToneMapping,
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
*
|
|
19
19
|
* ## Why this is NOT in `mount-game.ts`
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* The adapter registry module is deliberately dependency-free beyond `three`
|
|
22
|
+
* (already an unconditional engine dependency). `@react-three/fiber` is NOT —
|
|
23
|
+
* it is the PROJECT's dependency, and `world3d-react` is the opt-in module
|
|
24
|
+
* that owns it. Putting this factory beside the registry would make R3F
|
|
25
|
+
* unconditional for every game, including ones with no React at all; instead
|
|
26
|
+
* each project's `main.ts` registers it (`registerAdapter('three',
|
|
27
|
+
* r3fRootFactory)`).
|
|
28
28
|
*
|
|
29
29
|
* ## The mount mechanics (fiber v9, host-gated)
|
|
30
30
|
*
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
*
|
|
19
19
|
* ## Why this is NOT in `mount-game.ts`
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* The adapter registry module is deliberately dependency-free beyond `three`
|
|
22
|
+
* (already an unconditional engine dependency). `@react-three/fiber` is NOT —
|
|
23
|
+
* it is the PROJECT's dependency, and `world3d-react` is the opt-in module
|
|
24
|
+
* that owns it. Putting this factory beside the registry would make R3F
|
|
25
|
+
* unconditional for every game, including ones with no React at all; instead
|
|
26
|
+
* each project's `main.ts` registers it (`registerAdapter('three',
|
|
27
|
+
* r3fRootFactory)`).
|
|
28
28
|
*
|
|
29
29
|
* ## The mount mechanics (fiber v9, host-gated)
|
|
30
30
|
*
|
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* WHY IT EXISTS. `SystemAdapters.physics` is what the editor's transform
|
|
6
6
|
* coordination (`freeze → apply → unfreeze`) and the universal
|
|
7
|
-
* "Colliders drawn" instrument (`@engine/dev/instruments`) read.
|
|
8
|
-
* first-party
|
|
9
|
-
*
|
|
10
|
-
* Rapier runtime at all (see `R3FGameContext`'s doc comment) — it does physics
|
|
11
|
-
* with `@react-three/rapier` INSIDE the fiber tree, where nothing engine-side
|
|
7
|
+
* "Colliders drawn" instrument (`@engine/dev/instruments`) read. An R3F world
|
|
8
|
+
* builds no first-party Rapier runtime at all — it does physics with
|
|
9
|
+
* `@react-three/rapier` INSIDE the fiber tree, where nothing engine-side
|
|
12
10
|
* can see it. Result, before this: the colliders instrument read `null` (an
|
|
13
11
|
* em-dash) and its toggle threw "no physics adapter" in every shipped example
|
|
14
12
|
* and every scaffolded game.
|
|
@@ -51,9 +49,12 @@
|
|
|
51
49
|
* TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key
|
|
52
50
|
* and the slot it set (identity-guarded), so a play stop/start, a hot
|
|
53
51
|
* restart, or a world that unmounts `<Physics>` leaves no stale adapter
|
|
54
|
-
* behind. Because the slot is
|
|
55
|
-
*
|
|
56
|
-
*
|
|
52
|
+
* behind. Because the slot is PAGE-global (see its doc below — an engine
|
|
53
|
+
* module is shared across the editor's Play instances), mount exactly ONE
|
|
54
|
+
* bridge per PAGE: with two — a scene that mounts two, or two Play instances
|
|
55
|
+
* of one rapier world — the later mount wins the slot and the later unmount
|
|
56
|
+
* clears it while the earlier bridge is still live. That state is reported
|
|
57
|
+
* loudly the moment it starts (`warnOnContendedMount`).
|
|
57
58
|
*
|
|
58
59
|
* PHASE ORDERING: none to respect — this component registers pull-only
|
|
59
60
|
* callbacks and runs no per-frame work. Every method it exposes is invoked
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rapier-physics-bridge.d.ts","sourceRoot":"","sources":["../../src/world3d-react/rapier-physics-bridge.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rapier-physics-bridge.d.ts","sourceRoot":"","sources":["../../src/world3d-react/rapier-physics-bridge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAUH,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD;AAkCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,aAAa,EAAE,EAAE,wBAAwB,GAAG,IAAI,CA0QrF;AAkED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD"}
|
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* WHY IT EXISTS. `SystemAdapters.physics` is what the editor's transform
|
|
6
6
|
* coordination (`freeze → apply → unfreeze`) and the universal
|
|
7
|
-
* "Colliders drawn" instrument (`@engine/dev/instruments`) read.
|
|
8
|
-
* first-party
|
|
9
|
-
*
|
|
10
|
-
* Rapier runtime at all (see `R3FGameContext`'s doc comment) — it does physics
|
|
11
|
-
* with `@react-three/rapier` INSIDE the fiber tree, where nothing engine-side
|
|
7
|
+
* "Colliders drawn" instrument (`@engine/dev/instruments`) read. An R3F world
|
|
8
|
+
* builds no first-party Rapier runtime at all — it does physics with
|
|
9
|
+
* `@react-three/rapier` INSIDE the fiber tree, where nothing engine-side
|
|
12
10
|
* can see it. Result, before this: the colliders instrument read `null` (an
|
|
13
11
|
* em-dash) and its toggle threw "no physics adapter" in every shipped example
|
|
14
12
|
* and every scaffolded game.
|
|
@@ -51,9 +49,12 @@
|
|
|
51
49
|
* TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key
|
|
52
50
|
* and the slot it set (identity-guarded), so a play stop/start, a hot
|
|
53
51
|
* restart, or a world that unmounts `<Physics>` leaves no stale adapter
|
|
54
|
-
* behind. Because the slot is
|
|
55
|
-
*
|
|
56
|
-
*
|
|
52
|
+
* behind. Because the slot is PAGE-global (see its doc below — an engine
|
|
53
|
+
* module is shared across the editor's Play instances), mount exactly ONE
|
|
54
|
+
* bridge per PAGE: with two — a scene that mounts two, or two Play instances
|
|
55
|
+
* of one rapier world — the later mount wins the slot and the later unmount
|
|
56
|
+
* clears it while the earlier bridge is still live. That state is reported
|
|
57
|
+
* loudly the moment it starts (`warnOnContendedMount`).
|
|
57
58
|
*
|
|
58
59
|
* PHASE ORDERING: none to respect — this component registers pull-only
|
|
59
60
|
* callbacks and runs no per-frame work. Every method it exposes is invoked
|
|
@@ -64,6 +65,7 @@ import { useRapier } from '@react-three/rapier';
|
|
|
64
65
|
import { useEffect } from 'react';
|
|
65
66
|
import * as THREE from 'three';
|
|
66
67
|
import { createRapierBodyEditing, } from '../adapter/rapier-physics-adapter';
|
|
68
|
+
import { createSystemSlot } from '../adapter/system-slot';
|
|
67
69
|
function lateEntityObject(states, nodeId) {
|
|
68
70
|
let found;
|
|
69
71
|
for (const state of states) {
|
|
@@ -119,6 +121,25 @@ export function RapierPhysicsBridge({ onDebugChange }) {
|
|
|
119
121
|
return undefined;
|
|
120
122
|
return owningBody(object, rigidBodyStates.values());
|
|
121
123
|
};
|
|
124
|
+
/**
|
|
125
|
+
* The same question, with the two negative answers KEPT APART for the
|
|
126
|
+
* editing protocol — see {@link RapierBodyLookup}. This world's search
|
|
127
|
+
* space is the `<RigidBody>` subtrees, so "no object here" really is "not a
|
|
128
|
+
* node of mine", not "a node of mine without physics".
|
|
129
|
+
*/
|
|
130
|
+
const bodyLookup = (nodeId) => {
|
|
131
|
+
const object = lateEntityObject(rigidBodyStates.values(), nodeId);
|
|
132
|
+
if (!object)
|
|
133
|
+
return { kind: 'unresolved' };
|
|
134
|
+
const body = owningBody(object, rigidBodyStates.values());
|
|
135
|
+
return body ? { kind: 'body', body } : { kind: 'no-body' };
|
|
136
|
+
};
|
|
137
|
+
/** Rapier's own name for a shape type, via the enum's reverse mapping —
|
|
138
|
+
* so an unprojected collider reports WHICH shape it is (`TriMesh`,
|
|
139
|
+
* `HeightField`, …) instead of a bare number, and a shape added by a
|
|
140
|
+
* future rapier still names itself. */
|
|
141
|
+
const rapierShapeName = (shapeType) => rapier.ShapeType[shapeType] ??
|
|
142
|
+
`shapeType ${shapeType}`;
|
|
122
143
|
const colliderShape = (collider) => {
|
|
123
144
|
switch (collider.shapeType()) {
|
|
124
145
|
case rapier.ShapeType.Cuboid: {
|
|
@@ -133,8 +154,13 @@ export function RapierPhysicsBridge({ onDebugChange }) {
|
|
|
133
154
|
halfHeight: collider.halfHeight(),
|
|
134
155
|
radius: collider.radius(),
|
|
135
156
|
};
|
|
157
|
+
// Rapier has many more shapes than this seam projects (TriMesh,
|
|
158
|
+
// HeightField, ConvexPolyhedron, Cylinder, Cone, …). The collider is
|
|
159
|
+
// REAL; only our plain-data projection of it is missing, so it is
|
|
160
|
+
// reported as itself rather than dropped — a body whose only collider
|
|
161
|
+
// is a trimesh used to inspect as a body with no colliders at all.
|
|
136
162
|
default:
|
|
137
|
-
return
|
|
163
|
+
return { type: 'unsupported', kind: rapierShapeName(collider.shapeType()) };
|
|
138
164
|
}
|
|
139
165
|
};
|
|
140
166
|
const colliders = (nodeId) => {
|
|
@@ -146,8 +172,6 @@ export function RapierPhysicsBridge({ onDebugChange }) {
|
|
|
146
172
|
if (state.collider.parent()?.handle !== body.handle)
|
|
147
173
|
continue;
|
|
148
174
|
const shape = colliderShape(state.collider);
|
|
149
|
-
if (!shape)
|
|
150
|
-
continue;
|
|
151
175
|
state.object.updateWorldMatrix(true, false);
|
|
152
176
|
const position = state.collider.translation();
|
|
153
177
|
const rotation = state.collider.rotation();
|
|
@@ -182,6 +206,11 @@ export function RapierPhysicsBridge({ onDebugChange }) {
|
|
|
182
206
|
collider.setHalfHeight(shape.halfHeight);
|
|
183
207
|
collider.setRadius(shape.radius);
|
|
184
208
|
break;
|
|
209
|
+
// A shape this seam does not project has no dimensions to write back.
|
|
210
|
+
// `colliders()` reports it so the body's physics is not invisible; that
|
|
211
|
+
// is a READ, and it does not make the shape editable.
|
|
212
|
+
case 'unsupported':
|
|
213
|
+
break;
|
|
185
214
|
}
|
|
186
215
|
};
|
|
187
216
|
const jointType = (type) => {
|
|
@@ -279,17 +308,18 @@ export function RapierPhysicsBridge({ onDebugChange }) {
|
|
|
279
308
|
joint.setAnchor2(value);
|
|
280
309
|
};
|
|
281
310
|
const adapter = {
|
|
282
|
-
...createRapierBodyEditing(
|
|
311
|
+
...createRapierBodyEditing(bodyLookup),
|
|
283
312
|
setDebugDrawEnabled: (enabled) => onDebugChange(enabled),
|
|
284
313
|
colliders,
|
|
285
314
|
previewCollider,
|
|
286
315
|
joints,
|
|
287
316
|
previewJointAnchor,
|
|
288
317
|
};
|
|
289
|
-
|
|
318
|
+
warnOnContendedMount();
|
|
319
|
+
const detach = physicsSlot.attach(adapter);
|
|
290
320
|
return () => {
|
|
291
|
-
|
|
292
|
-
|
|
321
|
+
liveBridgeCount -= 1;
|
|
322
|
+
detach();
|
|
293
323
|
};
|
|
294
324
|
}, [colliderStates, onDebugChange, rapier, rigidBodyStates, world]);
|
|
295
325
|
return null;
|
|
@@ -297,17 +327,61 @@ export function RapierPhysicsBridge({ onDebugChange }) {
|
|
|
297
327
|
// ---------------------------------------------------------------------------
|
|
298
328
|
// The entry-declaration half
|
|
299
329
|
// ---------------------------------------------------------------------------
|
|
300
|
-
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
330
|
+
/**
|
|
331
|
+
* The bridge's slot. Module-scoped on purpose: the entry's `systems`
|
|
332
|
+
* declaration is static, the rapier world is not, and this slot is the seam
|
|
333
|
+
* between the two. Module scope here means one per PAGE, not one per game:
|
|
334
|
+
* this file is an ENGINE module, reached by bare specifier, so the editor's
|
|
335
|
+
* multi-instance Play (which stamps mount ids onto PROJECT-owned specifiers
|
|
336
|
+
* only — `server/project-module-instance.ts`) shares this one slot across
|
|
337
|
+
* every mounted instance. Two live instances of a rapier world therefore
|
|
338
|
+
* contend for it: the LAST bridge to mount wins every instance's declared
|
|
339
|
+
* `systems.physics`, and the loser's detach is dropped by the identity guard.
|
|
340
|
+
* {@link warnOnContendedMount} makes that state loud at the moment it
|
|
341
|
+
* happens; making the slot instance-scoped needs a per-mount rendezvous the
|
|
342
|
+
* context-free declaration (`rapierPhysicsSystem()` at project-module scope)
|
|
343
|
+
* does not yet have.
|
|
344
|
+
*
|
|
345
|
+
* Every member is an ACTION: each one either edits the sim or reads a live
|
|
346
|
+
* Rapier handle, and all of them arrive at gesture rate (a gizmo drag, an
|
|
347
|
+
* instrument command), never from a per-frame loop — so refusing by name is
|
|
348
|
+
* always the right answer, and a fabricated empty never is.
|
|
349
|
+
* `debugDraw`/`contactPoints` are OMITTED, not stubbed, for the anti-shim
|
|
350
|
+
* reason stated at the top of this file; a member the config does not list
|
|
351
|
+
* does not exist on the slot.
|
|
352
|
+
*/
|
|
353
|
+
let liveBridgeCount = 0;
|
|
354
|
+
/** The page-scope contention above, made LOUD instead of silent (never a
|
|
355
|
+
* silent degrade): a second concurrent bridge mount — a scene that mounted
|
|
356
|
+
* two, or a second Play instance of a rapier world — is reported the moment
|
|
357
|
+
* it happens, naming what breaks and until when. */
|
|
358
|
+
function warnOnContendedMount() {
|
|
359
|
+
liveBridgeCount += 1;
|
|
360
|
+
if (liveBridgeCount <= 1)
|
|
361
|
+
return;
|
|
362
|
+
// biome-ignore lint/suspicious/noConsole: the standing-warning channel for a capability the adapter cannot reach — the editor console surfaces it during play
|
|
363
|
+
console.warn(`[rapierPhysicsSystem] ${liveBridgeCount} <RapierPhysicsBridge> mounts are live on this ` +
|
|
364
|
+
'page, but the declared physics slot is one per page (engine modules are shared across ' +
|
|
365
|
+
"Play instances). Every instance's `systems.physics` now forwards to the LAST bridge " +
|
|
366
|
+
'mounted, so physics gizmo edits and instruments address that world — not necessarily ' +
|
|
367
|
+
'the one selected — until the extra mounts unmount.');
|
|
310
368
|
}
|
|
369
|
+
const physicsSlot = createSystemSlot({
|
|
370
|
+
name: 'rapierPhysicsSystem',
|
|
371
|
+
unattachedMessage: 'no <RapierPhysicsBridge> is mounted inside a live <Physics> right now — the world is not ' +
|
|
372
|
+
'playing, or the scene never mounted the bridge.',
|
|
373
|
+
members: {
|
|
374
|
+
ownerOf: { kind: 'action' },
|
|
375
|
+
freeze: { kind: 'action' },
|
|
376
|
+
commit: { kind: 'action' },
|
|
377
|
+
unfreeze: { kind: 'action' },
|
|
378
|
+
setDebugDrawEnabled: { kind: 'action' },
|
|
379
|
+
colliders: { kind: 'action' },
|
|
380
|
+
previewCollider: { kind: 'action' },
|
|
381
|
+
joints: { kind: 'action' },
|
|
382
|
+
previewJointAnchor: { kind: 'action' },
|
|
383
|
+
},
|
|
384
|
+
});
|
|
311
385
|
/**
|
|
312
386
|
* The `systems.physics` slot for a default-exported world — declare it from
|
|
313
387
|
* the entry (`export const systems = { physics: rapierPhysicsSystem() }`) and
|
|
@@ -317,27 +391,5 @@ function requireLiveAdapter() {
|
|
|
317
391
|
* name rather than fabricating.
|
|
318
392
|
*/
|
|
319
393
|
export function rapierPhysicsSystem() {
|
|
320
|
-
|
|
321
|
-
// or fabricated empty answer is the shape the anti-shim rule forbids. The
|
|
322
|
-
// bridge's adapter always carries all five optional members, so the member
|
|
323
|
-
// checks only ever fire against a foreign replacement.
|
|
324
|
-
const requireMember = (member) => {
|
|
325
|
-
const adapter = requireLiveAdapter();
|
|
326
|
-
const value = adapter[member];
|
|
327
|
-
if (typeof value !== 'function') {
|
|
328
|
-
throw new Error(`rapierPhysicsSystem: the live physics adapter has no ${String(member)}().`);
|
|
329
|
-
}
|
|
330
|
-
return value.bind(adapter);
|
|
331
|
-
};
|
|
332
|
-
return {
|
|
333
|
-
ownerOf: (nodeId) => requireLiveAdapter().ownerOf(nodeId),
|
|
334
|
-
freeze: (nodeId) => requireLiveAdapter().freeze(nodeId),
|
|
335
|
-
commit: (nodeId, t) => requireLiveAdapter().commit(nodeId, t),
|
|
336
|
-
unfreeze: (nodeId) => requireLiveAdapter().unfreeze(nodeId),
|
|
337
|
-
setDebugDrawEnabled: (enabled) => requireMember('setDebugDrawEnabled')(enabled),
|
|
338
|
-
colliders: (nodeId) => requireMember('colliders')(nodeId),
|
|
339
|
-
previewCollider: (colliderId, shape) => requireMember('previewCollider')(colliderId, shape),
|
|
340
|
-
joints: (nodeId) => requireMember('joints')(nodeId),
|
|
341
|
-
previewJointAnchor: (jointId, endpoint, anchor) => requireMember('previewJointAnchor')(jointId, endpoint, anchor),
|
|
342
|
-
};
|
|
394
|
+
return physicsSlot.slot;
|
|
343
395
|
}
|