@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
|
@@ -195,6 +195,41 @@ export interface VisibleCaptureWindow {
|
|
|
195
195
|
cancel(): void;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* How a caller configures ONE such wait — the options both `waitForCapture`s
|
|
200
|
+
* take (`adapter/ingest/scene-capture.ts` on the three surface,
|
|
201
|
+
* `pixi/scene-capture.ts` on the canvas surface). Passing a bare number
|
|
202
|
+
* instead is `{ timeoutMs }`, the shape every existing caller uses.
|
|
203
|
+
*
|
|
204
|
+
* It lives HERE, with the window, rather than once per lane, because every
|
|
205
|
+
* field is a parameter of the mechanism above and none of them is a parameter
|
|
206
|
+
* of a surface: `timeoutMs` is {@link startVisibleCaptureWindow}'s `budgetMs`,
|
|
207
|
+
* `visibility` is its {@link VisibilityClock}, and `onWait` hands out the
|
|
208
|
+
* {@link VisibleCaptureWindow} it returns. The two lanes had identical copies
|
|
209
|
+
* of this — which is what a shape with no lane-specific field looks like when
|
|
210
|
+
* it is declared per lane — while both already imported those three names from
|
|
211
|
+
* this module. Genuinely per-surface shapes (`CapturedRuntime` vs
|
|
212
|
+
* `CapturedRuntime2D`: a three scene/renderer against a Pixi stage/app) stay in
|
|
213
|
+
* their own lanes and keep their own names.
|
|
214
|
+
*/
|
|
215
|
+
export interface CaptureWaitOptions {
|
|
216
|
+
/** The capture window, in VISIBLE milliseconds (default 10s). */
|
|
217
|
+
timeoutMs?: number | undefined;
|
|
218
|
+
/** Injected in tests; defaults to the document's own visibility. */
|
|
219
|
+
visibility?: VisibilityClock | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* Called with a LIVE view of the wait when it begins, and with `null` the
|
|
222
|
+
* moment it ends (captured, expired, or the window was cancelled).
|
|
223
|
+
*
|
|
224
|
+
* A wait parked on a hidden tab is otherwise indistinguishable from a hung
|
|
225
|
+
* mount: nothing renders, nothing fails, and every door reports silence.
|
|
226
|
+
* This is the seam the editor publishes to `vgai status` so the answer is
|
|
227
|
+
* "waiting for the first visible frame — the tab is hidden", not a countdown
|
|
228
|
+
* that is not running.
|
|
229
|
+
*/
|
|
230
|
+
onWait?: ((wait: VisibleCaptureWindow | null) => void) | undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
198
233
|
/**
|
|
199
234
|
* Start a window that calls `onExpire` after `budgetMs` of VISIBLE time.
|
|
200
235
|
* While browser frame presentation is suspended the timer is disarmed entirely
|
|
@@ -23,6 +23,7 @@ import type { Game } from '../runtime/game';
|
|
|
23
23
|
import {
|
|
24
24
|
CONTRACT_SYSTEM_SLOTS,
|
|
25
25
|
type ContractSurface,
|
|
26
|
+
type ContractSystemEmptySlot,
|
|
26
27
|
projectContractSystemAdapters,
|
|
27
28
|
} from './ingest/contract-system-adapters';
|
|
28
29
|
import type { VgaiGameSystemAdapters } from './ingest/game-contract';
|
|
@@ -76,6 +77,15 @@ export interface NativeDebugModule {
|
|
|
76
77
|
readonly events?: {
|
|
77
78
|
readonly subscribe: (listener: (event: string, detail?: unknown) => void) => () => void;
|
|
78
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* `false` while the app is intentionally BETWEEN worlds — a scene remount in flight (a
|
|
82
|
+
* `reload_current_scene`-style restart unmounts the running scene and the fresh one arrives
|
|
83
|
+
* on an ASYNC React commit). Session tick drivers (`runTicksWhenSettled`, behind both
|
|
84
|
+
* run-ticks doors) wait for `true` before each tick, so which tick first runs the fresh
|
|
85
|
+
* world is a function of the sim, never of wall timing between driver calls. Omitted =
|
|
86
|
+
* always settled.
|
|
87
|
+
*/
|
|
88
|
+
readonly settled?: () => boolean;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
type NativeInputBinding = NonNullable<NativeDebugModule['input']>;
|
|
@@ -176,7 +186,11 @@ export function nativeDebugBindingFromEntryModule(
|
|
|
176
186
|
throw new Error(`Root "${rootId}" exports \`debug\`, but it is not an object.`);
|
|
177
187
|
}
|
|
178
188
|
const raw = entryModule['debug'];
|
|
179
|
-
rejectUnknownKeys(
|
|
189
|
+
rejectUnknownKeys(
|
|
190
|
+
raw,
|
|
191
|
+
['commands', 'state', 'input', 'events', 'settled'],
|
|
192
|
+
`Root "${rootId}" debug export`,
|
|
193
|
+
);
|
|
180
194
|
|
|
181
195
|
// A thunk table defers to install time (see {@link NativeTable}); a plain
|
|
182
196
|
// record validates here, where the error can still name the export.
|
|
@@ -197,7 +211,11 @@ export function nativeDebugBindingFromEntryModule(
|
|
|
197
211
|
const state = parseTable(raw['state'], 'debug.state');
|
|
198
212
|
const input = raw['input'] === undefined ? undefined : parseInput(raw['input']);
|
|
199
213
|
const events = raw['events'] === undefined ? undefined : parseEvents(raw['events']);
|
|
200
|
-
|
|
214
|
+
const settled = raw['settled'];
|
|
215
|
+
if (settled !== undefined && typeof settled !== 'function') {
|
|
216
|
+
throw new Error('debug.settled must be a function returning a boolean.');
|
|
217
|
+
}
|
|
218
|
+
if (!commands && !state && !input && !events && !settled) {
|
|
201
219
|
throw new Error(`Root "${rootId}" exports an empty \`debug\` object.`);
|
|
202
220
|
}
|
|
203
221
|
return {
|
|
@@ -207,6 +225,7 @@ export function nativeDebugBindingFromEntryModule(
|
|
|
207
225
|
...(state ? { state } : {}),
|
|
208
226
|
...(input ? { input } : {}),
|
|
209
227
|
...(events ? { events } : {}),
|
|
228
|
+
...(settled ? { settled: settled as () => boolean } : {}),
|
|
210
229
|
},
|
|
211
230
|
};
|
|
212
231
|
}
|
|
@@ -417,6 +436,9 @@ export function installNativeDebugBindings(
|
|
|
417
436
|
// subscription's disposer is deliberately dropped: it lives exactly as
|
|
418
437
|
// long as this Game's registry does.
|
|
419
438
|
binding.debug.events?.subscribe((event, detail) => root.emit(event, detail));
|
|
439
|
+
// The app's own between-worlds truth → the session's tick gate
|
|
440
|
+
// (`runTicksWhenSettled`). Same lifetime rule as the event subscription.
|
|
441
|
+
if (binding.debug.settled) registry.registerWorldSettledProbe(binding.debug.settled);
|
|
420
442
|
}
|
|
421
443
|
}
|
|
422
444
|
|
|
@@ -447,6 +469,18 @@ export function installNativeDebugBindings(
|
|
|
447
469
|
export interface NativeSystemsBinding {
|
|
448
470
|
readonly rootId: string;
|
|
449
471
|
readonly slots: Readonly<Partial<SystemAdapters>>;
|
|
472
|
+
/**
|
|
473
|
+
* The slots this root's `systems` table answered with `absent(reason)` — the
|
|
474
|
+
* positive absences, in the game's own words.
|
|
475
|
+
*
|
|
476
|
+
* They are carried rather than dropped because they are the ONLY thing that
|
|
477
|
+
* can tell a coverage reader "this game has no networking" apart from "nobody
|
|
478
|
+
* ever looked". Nothing is installed for them (that would be the stub adapter
|
|
479
|
+
* `absent()`'s own comment forbids); they exist to be READ — through
|
|
480
|
+
* `binding.observation.entrySystems`, which is the product door the coverage
|
|
481
|
+
* table renders from.
|
|
482
|
+
*/
|
|
483
|
+
readonly absent: readonly ContractSystemEmptySlot[];
|
|
450
484
|
}
|
|
451
485
|
|
|
452
486
|
/**
|
|
@@ -478,13 +512,27 @@ export function nativeSystemsBindingFromEntryModule(
|
|
|
478
512
|
if (Object.keys(projection.bound).length === 0 && projection.empty.length === 0) {
|
|
479
513
|
throw new Error(`Root "${rootId}" exports an empty \`systems\` object.`);
|
|
480
514
|
}
|
|
481
|
-
return { rootId, slots: projection.bound };
|
|
515
|
+
return { rootId, slots: projection.bound, absent: projection.empty };
|
|
482
516
|
}
|
|
483
517
|
|
|
484
|
-
/**
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
518
|
+
/**
|
|
519
|
+
* Install every declared binding onto the mounted Game's game-scoped slot
|
|
520
|
+
* table (`Game.installDeclaredSystemAdapters`), where the ordinary
|
|
521
|
+
* `game.systemAdapters` merge picks them up ahead of any lingering component
|
|
522
|
+
* registration for the same root.
|
|
523
|
+
*
|
|
524
|
+
* A root's ABSENCES travel the same call and install NOTHING — they are
|
|
525
|
+
* recorded, never bound, because the whole point of `absent()` is that no
|
|
526
|
+
* adapter exists to bind (see its comment: a marker, never a stub). They are
|
|
527
|
+
* recorded HERE rather than left on the binding alone so the answer is
|
|
528
|
+
* game-scoped, matching the registry every editor panel already reads: a
|
|
529
|
+
* `SystemAdapters` slot is filled by whichever root builds it, so "does this
|
|
530
|
+
* GAME have physics" cannot be answered one root at a time.
|
|
531
|
+
*
|
|
532
|
+
* A root that declares only absences therefore still calls through — skipping
|
|
533
|
+
* it on an empty `slots` map is what would drop exactly the games whose whole
|
|
534
|
+
* declaration is "I have none of these".
|
|
535
|
+
*/
|
|
488
536
|
export function installNativeSystemsBindings(
|
|
489
537
|
game: Game,
|
|
490
538
|
bindings: readonly NativeSystemsBinding[],
|
|
@@ -498,7 +546,16 @@ export function installNativeSystemsBindings(
|
|
|
498
546
|
);
|
|
499
547
|
}
|
|
500
548
|
for (const binding of bindings) {
|
|
501
|
-
if (Object.keys(binding.slots).length === 0) continue;
|
|
502
|
-
install.call(
|
|
549
|
+
if (Object.keys(binding.slots).length === 0 && binding.absent.length === 0) continue;
|
|
550
|
+
install.call(
|
|
551
|
+
game,
|
|
552
|
+
binding.rootId,
|
|
553
|
+
binding.slots,
|
|
554
|
+
binding.absent.map((slot) => ({
|
|
555
|
+
rootId: binding.rootId,
|
|
556
|
+
slot: slot.slot,
|
|
557
|
+
reason: slot.evidence,
|
|
558
|
+
})),
|
|
559
|
+
);
|
|
503
560
|
}
|
|
504
561
|
}
|
|
@@ -29,6 +29,15 @@ import type { Transform, TransformOwner } from './transform';
|
|
|
29
29
|
* `@react-three/rapier` pins for R3F worlds (0.19). Their `RigidBody` classes
|
|
30
30
|
* are unrelated NOMINAL types, so a shared helper that named either one could
|
|
31
31
|
* not serve both callers.
|
|
32
|
+
*
|
|
33
|
+
* FLIP CONDITION: `@react-three/rapier` pins its rapier EXACTLY (`0.19.2` as
|
|
34
|
+
* of its 2.2.0 — check `npm view @react-three/rapier dependencies`), so an
|
|
35
|
+
* override or engine bump to any OTHER rapier version forks the wasm instance
|
|
36
|
+
* that library initializes against and is refused. This structural split
|
|
37
|
+
* collapses only when the engine's own `@dimforge/rapier3d-compat` and
|
|
38
|
+
* `@react-three/rapier`'s exact pin agree on one version — at that point the
|
|
39
|
+
* two `RigidBody` types are one nominal type again and this interface can
|
|
40
|
+
* name it directly.
|
|
32
41
|
*/
|
|
33
42
|
export interface RapierEditableBody {
|
|
34
43
|
isFixed(): boolean;
|
|
@@ -53,28 +62,66 @@ export interface RapierEditableBody {
|
|
|
53
62
|
* answered differently by each Rapier owner, and a caller that cannot answer
|
|
54
63
|
* one honestly omits it.
|
|
55
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* What asking this Rapier owner about a node id actually yields.
|
|
67
|
+
*
|
|
68
|
+
* THE TWO NEGATIVE ANSWERS ARE DIFFERENT FACTS, and collapsing them into one
|
|
69
|
+
* `undefined` is what let `ownerOf` answer `'editor'` — "the editor drives this
|
|
70
|
+
* node's transform" — for an id this world has never heard of. `'no-body'` is a
|
|
71
|
+
* real measurement about a real node; `'unresolved'` is this implementer saying
|
|
72
|
+
* it cannot answer at all.
|
|
73
|
+
*/
|
|
74
|
+
export type RapierBodyLookup =
|
|
75
|
+
| { readonly kind: 'body'; readonly body: RapierEditableBody }
|
|
76
|
+
/** The node exists here and simply has no physics body. */
|
|
77
|
+
| { readonly kind: 'no-body' }
|
|
78
|
+
/** This world does not know this node id. */
|
|
79
|
+
| { readonly kind: 'unresolved' };
|
|
80
|
+
|
|
81
|
+
/** The refusal sentence for a protocol verb aimed at a node this world has
|
|
82
|
+
* never heard of — named once so every verb says the same thing. */
|
|
83
|
+
function unresolvedNodeRefusal(operation: string, nodeId: string): string {
|
|
84
|
+
return (
|
|
85
|
+
`[RapierPhysicsAdapter] ${operation}: this world does not know node id "${nodeId}", so ` +
|
|
86
|
+
'there is no body to drive. Refusing rather than reporting a completed write — gate on ' +
|
|
87
|
+
"ownerOf(id) !== 'unresolved' before driving physics from a game-scoped adapter."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
56
91
|
export function createRapierBodyEditing(
|
|
57
|
-
|
|
92
|
+
lookup: (nodeId: string) => RapierBodyLookup,
|
|
58
93
|
): Pick<PhysicsAdapter, 'ownerOf' | 'freeze' | 'commit' | 'unfreeze'> {
|
|
59
94
|
/** Saved body types while frozen, so unfreeze can restore them. */
|
|
60
95
|
const frozen = new Map<RapierEditableBody, number>();
|
|
61
96
|
|
|
97
|
+
/** The body for a verb that must actually reach one, or `null` when this node
|
|
98
|
+
* genuinely has no physics. THROWS for an unknown id: the three verbs below
|
|
99
|
+
* return `void`, so a silent return is indistinguishable from a completed
|
|
100
|
+
* write, and `commit` in particular is a WRITE the caller believes landed. */
|
|
101
|
+
const bodyForWrite = (operation: string, nodeId: string): RapierEditableBody | null => {
|
|
102
|
+
const answer = lookup(nodeId);
|
|
103
|
+
if (answer.kind === 'unresolved') throw new Error(unresolvedNodeRefusal(operation, nodeId));
|
|
104
|
+
return answer.kind === 'body' ? answer.body : null;
|
|
105
|
+
};
|
|
106
|
+
|
|
62
107
|
return {
|
|
63
108
|
ownerOf(nodeId: string): TransformOwner {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
// A
|
|
67
|
-
|
|
109
|
+
const answer = lookup(nodeId);
|
|
110
|
+
if (answer.kind === 'unresolved') return 'unresolved';
|
|
111
|
+
// A node with no body, and a FIXED body (which never moves on its own),
|
|
112
|
+
// are both transforms the editor drives.
|
|
113
|
+
if (answer.kind === 'no-body') return 'editor';
|
|
114
|
+
return answer.body.isFixed() ? 'editor' : 'physics';
|
|
68
115
|
},
|
|
69
116
|
freeze(nodeId: string): void {
|
|
70
|
-
const body =
|
|
117
|
+
const body = bodyForWrite('freeze', nodeId);
|
|
71
118
|
if (!body || frozen.has(body)) return;
|
|
72
119
|
frozen.set(body, body.bodyType());
|
|
73
120
|
// Kinematic-position: the body stops simulating but tracks the pose we set.
|
|
74
121
|
body.setBodyType(2 /* KinematicPositionBased */, true);
|
|
75
122
|
},
|
|
76
123
|
commit(nodeId: string, t: Transform): void {
|
|
77
|
-
const body =
|
|
124
|
+
const body = bodyForWrite('commit', nodeId);
|
|
78
125
|
if (!body) return;
|
|
79
126
|
body.setTranslation({ x: t.position[0], y: t.position[1], z: t.position[2] }, true);
|
|
80
127
|
body.setRotation(
|
|
@@ -83,7 +130,7 @@ export function createRapierBodyEditing(
|
|
|
83
130
|
);
|
|
84
131
|
},
|
|
85
132
|
unfreeze(nodeId: string): void {
|
|
86
|
-
const body =
|
|
133
|
+
const body = bodyForWrite('unfreeze', nodeId);
|
|
87
134
|
if (!body) return;
|
|
88
135
|
const prev = frozen.get(body);
|
|
89
136
|
if (prev !== undefined) {
|
|
@@ -149,22 +196,31 @@ export function createRapierPhysicsAdapter(
|
|
|
149
196
|
return contactBuf.subarray(0, contactCount * 3);
|
|
150
197
|
};
|
|
151
198
|
|
|
152
|
-
/** The one place this adapter crosses from node id into Three's vocabulary
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
199
|
+
/** The one place this adapter crosses from node id into Three's vocabulary —
|
|
200
|
+
* and the one place that can tell an UNKNOWN node from one with no body. */
|
|
201
|
+
const lookup = (nodeId: string): RapierBodyLookup => {
|
|
202
|
+
const object = resolve(nodeId);
|
|
203
|
+
if (!object) return { kind: 'unresolved' };
|
|
204
|
+
const body = registry.get(object)?.body;
|
|
205
|
+
return body ? { kind: 'body', body } : { kind: 'no-body' };
|
|
156
206
|
};
|
|
157
207
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
208
|
+
const adapter: PhysicsAdapter = { ...createRapierBodyEditing(lookup) };
|
|
209
|
+
// The debug-draw and contact capabilities exist only where a physics CONTEXT
|
|
210
|
+
// does. They used to be attached unconditionally and then no-op internally:
|
|
211
|
+
// `setDebugDrawEnabled(true)` returned as if it had enabled something, and
|
|
212
|
+
// `contactPoints()` returned an empty view — "we looked and there are no
|
|
213
|
+
// contacts" — for a world with no simulation at all. Presence mirrors the
|
|
214
|
+
// capability, so `physics-debug.ts`'s existing `?.()` calls now see absence.
|
|
215
|
+
if (physics) {
|
|
216
|
+
adapter.debugDraw = () => physics.debugMesh;
|
|
217
|
+
adapter.setDebugDrawEnabled = (enabled: boolean): void => {
|
|
163
218
|
// Same two fields the in-game KeyP toggle flips — the adapter's render
|
|
164
219
|
// system reads `debugEnabled` and feeds `debugRender()` into the mesh.
|
|
165
220
|
physics.debugEnabled = enabled;
|
|
166
221
|
physics.debugMesh.visible = enabled;
|
|
167
|
-
}
|
|
168
|
-
contactPoints
|
|
169
|
-
}
|
|
222
|
+
};
|
|
223
|
+
adapter.contactPoints = collectContactPoints;
|
|
224
|
+
}
|
|
225
|
+
return adapter;
|
|
170
226
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* It lives in the ADAPTER SEAM, not in a surface entry, because `WorldRendererConfig` IS part of
|
|
5
5
|
* the root contract: `MountedThreeRoot.rendererConfig` reports it (`./root-adapter.ts`) and
|
|
6
6
|
* `./root-seam-contract.ts` already lists it as a contract field. The seam defines the shape; the
|
|
7
|
-
* surface entries and the editor CONFORM to it — `world3d-react/r3f-
|
|
7
|
+
* surface entries and the editor CONFORM to it — `world3d-react/r3f-root-factory.tsx` is the declarer,
|
|
8
8
|
* `runtime/create-runtime.ts` the host, `editor/src/components/ViewportPanel.tsx` the editor's
|
|
9
9
|
* applier. Do NOT move it back under `world3d-react/`: nothing here touches react, and a core
|
|
10
10
|
* adapter file reaching into a react entry is exactly what `react-core-import-ban.test.ts` forbids
|
|
@@ -90,7 +90,7 @@ export interface WorldRendererConfig {
|
|
|
90
90
|
* Apply `config` to `renderer`, returning the restore function that puts back what was there.
|
|
91
91
|
*
|
|
92
92
|
* `three` is passed in rather than imported for values so the enum constants come from the HOST's
|
|
93
|
-
* three instance — the same identity rule `r3f-
|
|
93
|
+
* three instance — the same identity rule `r3f-root-factory.tsx` follows for the scene and camera.
|
|
94
94
|
*/
|
|
95
95
|
export function applyWorldRendererConfig(
|
|
96
96
|
three: typeof THREE,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RootAdapter — the host ⇄ runtime contract. A ROOT is *anything that mounts*.
|
|
3
3
|
*
|
|
4
|
-
* This is the interface the host depends on.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* This is the interface the host depends on. First-party content and
|
|
5
|
+
* unmodified external games are peer root adapters. A game is the
|
|
6
|
+
* manifest's complete roots array, while each adapter owns exactly one
|
|
7
|
+
* root.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Container } from 'pixi.js';
|
|
@@ -136,8 +136,8 @@ export interface MountedReactRoot extends MountedRootBase {
|
|
|
136
136
|
/** Every kind of live, mounted world. */
|
|
137
137
|
export type MountedRoot = MountedThreeRoot | MountedPixiRoot | MountedReactRoot;
|
|
138
138
|
|
|
139
|
-
/** Map a {@link AdapterSurface} to its mounted-world shape
|
|
140
|
-
*
|
|
139
|
+
/** Map a {@link AdapterSurface} to its mounted-world shape — lets generic
|
|
140
|
+
* code over
|
|
141
141
|
* `K extends AdapterSurface` name the right surface without a manual union. */
|
|
142
142
|
export type MountedRootFor<K extends AdapterSurface> = K extends 'three'
|
|
143
143
|
? MountedThreeRoot
|
|
@@ -174,7 +174,7 @@ export interface RootAdapter<K extends AdapterSurface = 'three'> {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
* One surface tag paired with an adapter typed FOR that surface
|
|
177
|
+
* One surface tag paired with an adapter typed FOR that surface.
|
|
178
178
|
*
|
|
179
179
|
* A resolver that turns a manifest root into a mountable
|
|
180
180
|
* adapter handles every surface in one function, so its return type has to
|
|
@@ -20,7 +20,19 @@ import type { Transform, TransformOwner } from './transform';
|
|
|
20
20
|
export type PhysicsColliderShape =
|
|
21
21
|
| { readonly type: 'cuboid'; readonly halfExtents: readonly [number, number, number] }
|
|
22
22
|
| { readonly type: 'ball'; readonly radius: number }
|
|
23
|
-
| { readonly type: 'capsule'; readonly halfHeight: number; readonly radius: number }
|
|
23
|
+
| { readonly type: 'capsule'; readonly halfHeight: number; readonly radius: number }
|
|
24
|
+
/**
|
|
25
|
+
* A collider this seam has no plain-data projection for — a trimesh,
|
|
26
|
+
* heightfield, convex hull, cylinder, cone, …
|
|
27
|
+
*
|
|
28
|
+
* IT IS STILL A COLLIDER, and the body still has it. Implementations used to
|
|
29
|
+
* drop these from `colliders()` entirely, so a body whose only collider was a
|
|
30
|
+
* trimesh inspected as a body with NO physics at all. `kind` is the native
|
|
31
|
+
* shape's own name, so the inspector can say WHICH shape it cannot draw
|
|
32
|
+
* rather than implying nothing is there. Nothing may synthesize dimensions
|
|
33
|
+
* for one, and `previewCollider` refuses it.
|
|
34
|
+
*/
|
|
35
|
+
| { readonly type: 'unsupported'; readonly kind: string };
|
|
24
36
|
|
|
25
37
|
/** Plain-data projection of one native 3D physics collider. The owning
|
|
26
38
|
* physics implementation keeps the actual collider; editor authoring only
|
|
@@ -249,12 +261,28 @@ export interface NetworkingAdapter {
|
|
|
249
261
|
authority(nodeId: string): 'local' | 'remote' | 'server';
|
|
250
262
|
/** Remote/server-authoritative nodes → inspect-only in the editor. */
|
|
251
263
|
editable(nodeId: string): boolean;
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Optional capability: current connection lifecycle state.
|
|
266
|
+
*
|
|
267
|
+
* OPTIONAL for the same reason every other member here is — `'disconnected'`
|
|
268
|
+
* is a POSITIVE CLAIM about the link, and an implementer that never taught
|
|
269
|
+
* this adapter to read connection state has not made it. Absent ⇒ the
|
|
270
|
+
* inspector marks the section unsupported; it does not report a game as
|
|
271
|
+
* disconnected on the strength of nobody having looked.
|
|
272
|
+
*/
|
|
273
|
+
getConnectionState?(): ConnectionState;
|
|
274
|
+
/**
|
|
275
|
+
* Optional capability: the active room's identity, or `null` when connected
|
|
276
|
+
* to no room. `null` is the ANSWER "there is no room", so absence of the
|
|
277
|
+
* accessor — "nothing here can tell you" — has to be a different thing.
|
|
278
|
+
*/
|
|
279
|
+
getRoomInfo?(): RoomInfo | null;
|
|
280
|
+
/**
|
|
281
|
+
* Optional capability: replication activity snapshot, for a live network
|
|
282
|
+
* inspector panel. Zeroes are a measurement, not a default; an adapter with
|
|
283
|
+
* no counters omits this rather than reporting a quiet link.
|
|
284
|
+
*/
|
|
285
|
+
getReplicationStats?(): ReplicationStats;
|
|
258
286
|
/** Subscribe to changes in connection state / room / replication stats. */
|
|
259
287
|
subscribe(cb: () => void): Unsubscribe;
|
|
260
288
|
/** Optional capability: one plain-data (JSON-safe) snapshot of the
|
|
@@ -281,7 +309,7 @@ export interface NetworkingAdapter {
|
|
|
281
309
|
* (e.g. read off its own room state). The editor consumes this for display —
|
|
282
310
|
* a seat label — ONLY when a real implementer provides it, falling back to a
|
|
283
311
|
* generic label otherwise. */
|
|
284
|
-
getPlayerIdentity?(): NetPlayerIdentity;
|
|
312
|
+
getPlayerIdentity?(): NetPlayerIdentity | undefined;
|
|
285
313
|
/** Optional capability, PAIRED with {@link getPlayerIdentity}: set the local
|
|
286
314
|
* player's identity through the game's OWN multiplayer mechanism. The editor
|
|
287
315
|
* renders an editable name field ONLY when a real implementer provides this
|
|
@@ -346,6 +374,13 @@ export interface NavCrowdAgentState {
|
|
|
346
374
|
*/
|
|
347
375
|
export interface NavigationAdapter {
|
|
348
376
|
hasNavMesh(): boolean;
|
|
377
|
+
/**
|
|
378
|
+
* `[]` means SEARCHED AND FOUND NO ROUTE — a fact about the level. An
|
|
379
|
+
* implementer with no navmesh to search, or whose query FAILED, THROWS
|
|
380
|
+
* instead; those are facts about the implementer, and collapsing all three
|
|
381
|
+
* into `[]` tells a caller the level is impassable when nobody looked.
|
|
382
|
+
* Gate on {@link hasNavMesh} where the first case is reachable.
|
|
383
|
+
*/
|
|
349
384
|
findPath(start: NavPoint, end: NavPoint): NavPoint[];
|
|
350
385
|
debugMesh(scene: THREE.Scene): THREE.Object3D | null;
|
|
351
386
|
/** (Re)build the navmesh from source meshes. Synchronous in the blessed
|
|
@@ -53,9 +53,9 @@ export const NETWORKING_ADAPTER_SHAPE = defineSeamShape<NetworkingAdapter>()({
|
|
|
53
53
|
networkId: { optional: false, kind: 'function', required: 'operation' },
|
|
54
54
|
authority: { optional: false, kind: 'function', required: 'operation' },
|
|
55
55
|
editable: { optional: false, kind: 'function', required: 'operation' },
|
|
56
|
-
getConnectionState: { optional:
|
|
57
|
-
getRoomInfo: { optional:
|
|
58
|
-
getReplicationStats: { optional:
|
|
56
|
+
getConnectionState: { optional: true, kind: 'function', required: 'operation' },
|
|
57
|
+
getRoomInfo: { optional: true, kind: 'function', required: 'operation' },
|
|
58
|
+
getReplicationStats: { optional: true, kind: 'function', required: 'operation' },
|
|
59
59
|
subscribe: { optional: false, kind: 'function', required: 'effect' },
|
|
60
60
|
getStateSnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
61
61
|
messageEvents: { optional: true, kind: 'function', required: 'operation' },
|