@vgai/engine 0.5.21 → 0.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +2 -4
- package/dist/adapter/adapter-surface.d.ts +1 -1
- package/dist/adapter/adapter-surface.js +1 -1
- package/dist/adapter/authoring.d.ts +19 -21
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -2
- package/dist/adapter/binding.d.ts +341 -0
- package/dist/adapter/binding.d.ts.map +1 -0
- package/dist/adapter/binding.js +148 -0
- package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -1
- package/dist/adapter/colyseus-networking-adapter.js +23 -4
- package/dist/adapter/entrypoint-selection-readers.d.ts +24 -0
- package/dist/adapter/entrypoint-selection-readers.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-readers.js +63 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -1
- package/dist/adapter/entrypoint-selection-source.js +1 -44
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +1 -52
- package/dist/adapter/host-context.d.ts +46 -15
- package/dist/adapter/host-context.d.ts.map +1 -1
- package/dist/adapter/host-context.js +2 -9
- package/dist/adapter/index.d.ts +11 -12
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +9 -10
- package/dist/adapter/ingest/game-contract.d.ts +0 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +0 -3
- package/dist/adapter/ingest/scene-capture.d.ts +19 -23
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +143 -17
- package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/manifest-interpreter.d.ts +59 -0
- package/dist/adapter/manifest-interpreter.d.ts.map +1 -0
- package/dist/adapter/manifest-interpreter.js +50 -0
- package/dist/adapter/native-debug-module.d.ts +71 -3
- package/dist/adapter/native-debug-module.d.ts.map +1 -1
- package/dist/adapter/native-debug-module.js +146 -10
- package/dist/adapter/rapier-physics-adapter.d.ts +31 -1
- package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -1
- package/dist/adapter/rapier-physics-adapter.js +50 -36
- package/dist/adapter/renderer-config.d.ts +3 -3
- package/dist/adapter/renderer-config.js +3 -3
- package/dist/adapter/root-adapter.d.ts +7 -7
- package/dist/adapter/root-adapter.js +4 -4
- package/dist/adapter/system-adapter.d.ts +45 -7
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +3 -3
- package/dist/adapter/system-seam-contract.js +3 -3
- package/dist/adapter/system-slot.d.ts +168 -0
- package/dist/adapter/system-slot.d.ts.map +1 -0
- package/dist/adapter/system-slot.js +229 -0
- package/dist/adapter/transform.d.ts +11 -1
- package/dist/adapter/transform.d.ts.map +1 -1
- package/dist/ai/navigation.d.ts +17 -1
- package/dist/ai/navigation.d.ts.map +1 -1
- package/dist/ai/navigation.js +26 -5
- package/dist/animation/anim-graph-types.d.ts +11 -3
- package/dist/animation/anim-graph-types.d.ts.map +1 -1
- package/dist/animation/theatre-clock-binding.d.ts +4 -4
- package/dist/animation/theatre-clock-binding.js +4 -4
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +2 -2
- package/dist/animation/xstate-animation-meta.d.ts +13 -0
- package/dist/animation/xstate-animation-meta.d.ts.map +1 -1
- package/dist/animation/xstate-animation-meta.js +14 -0
- package/dist/asset-formats/index.d.ts +4 -7
- package/dist/asset-formats/index.d.ts.map +1 -1
- package/dist/asset-formats/index.js +4 -7
- package/dist/asset-formats/render-env.d.ts +5 -8
- package/dist/asset-formats/render-env.d.ts.map +1 -1
- package/dist/asset-formats/render-env.js +3 -6
- package/dist/asset-parse-error.d.ts +2 -5
- package/dist/asset-parse-error.d.ts.map +1 -1
- package/dist/asset-parse-error.js +2 -5
- package/dist/audio/bus-mixer.d.ts +101 -0
- package/dist/audio/bus-mixer.d.ts.map +1 -0
- package/dist/audio/bus-mixer.js +115 -0
- package/dist/canvas-react/index.d.ts +5 -12
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +5 -12
- package/dist/canvas-react/pixi-react-root-factory.d.ts +34 -21
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +274 -36
- package/dist/core/countdown-timer.d.ts +118 -0
- package/dist/core/countdown-timer.d.ts.map +1 -0
- package/dist/core/countdown-timer.js +137 -0
- package/dist/core/deferred-commands.d.ts +127 -0
- package/dist/core/deferred-commands.d.ts.map +1 -0
- package/dist/core/deferred-commands.js +132 -0
- package/dist/core/frame-pacing.d.ts +1 -1
- package/dist/core/frame-pacing.js +2 -2
- package/dist/core/game-loop.d.ts +1 -3
- package/dist/core/game-loop.d.ts.map +1 -1
- package/dist/core/game-loop.js +1 -3
- package/dist/core/seeded-random.d.ts +1 -1
- package/dist/core/sim-clock.d.ts +19 -8
- package/dist/core/sim-clock.d.ts.map +1 -1
- package/dist/core/sim-clock.js +18 -7
- package/dist/core/types.d.ts +1 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/defaults.d.ts +6 -14
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +6 -14
- package/dist/dev/instruments.d.ts +38 -11
- package/dist/dev/instruments.d.ts.map +1 -1
- package/dist/dev/instruments.js +81 -11
- package/dist/dev/register-render-vitals.d.ts +1 -4
- package/dist/dev/register-render-vitals.d.ts.map +1 -1
- package/dist/dev/register-render-vitals.js +1 -4
- package/dist/dev/render-debug-adapter.d.ts +2 -3
- package/dist/dev/render-debug-adapter.d.ts.map +1 -1
- package/dist/dev/render-debug-adapter.js +2 -3
- package/dist/dev/render-vitals.d.ts +3 -3
- package/dist/dev/render-vitals.js +3 -3
- package/dist/dev/webgl-frame-capture.d.ts +1 -1
- package/dist/dev/webgl-frame-capture.js +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/input/host-pointer.d.ts +122 -0
- package/dist/input/host-pointer.d.ts.map +1 -0
- package/dist/input/host-pointer.js +160 -0
- package/dist/input/input-manager.d.ts +33 -5
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +48 -13
- package/dist/input/rebind-controller.d.ts.map +1 -1
- package/dist/input/rebind-controller.js +6 -8
- package/dist/input/schema.d.ts.map +1 -1
- package/dist/input/schema.js +56 -28
- package/dist/manifest/filename.d.ts.map +1 -1
- package/dist/manifest/filename.js +8 -12
- package/dist/manifest/load.d.ts +10 -10
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +9 -6
- package/dist/manifest/schema.d.ts +17 -21
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +45 -35
- package/dist/physics/collider-dimensions.d.ts +8 -12
- package/dist/physics/collider-dimensions.d.ts.map +1 -1
- package/dist/physics/collider-dimensions.js +8 -12
- package/dist/pixi/scene-capture.d.ts +11 -13
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/react/world-state.d.ts +1 -1
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/render/directional-shadow-fit.d.ts +88 -0
- package/dist/render/directional-shadow-fit.d.ts.map +1 -0
- package/dist/render/directional-shadow-fit.js +133 -0
- package/dist/render/environment-capture.d.ts +68 -0
- package/dist/render/environment-capture.d.ts.map +1 -0
- package/dist/render/environment-capture.js +90 -0
- package/dist/render/ibl-override-material.d.ts +97 -0
- package/dist/render/ibl-override-material.d.ts.map +1 -0
- package/dist/render/ibl-override-material.js +114 -0
- package/dist/render/light-camera-factory.d.ts +5 -7
- package/dist/render/light-camera-factory.d.ts.map +1 -1
- package/dist/render/light-camera-factory.js +8 -11
- package/dist/render/material-factory.js +1 -1
- package/dist/render/render-batch-system.d.ts.map +1 -1
- package/dist/render/render-features.d.ts +17 -6
- package/dist/render/render-features.d.ts.map +1 -1
- package/dist/render/render-features.js +0 -28
- package/dist/runtime/create-runtime.d.ts +34 -25
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +218 -93
- package/dist/runtime/debug-registry.d.ts +77 -8
- package/dist/runtime/debug-registry.d.ts.map +1 -1
- package/dist/runtime/debug-registry.js +3 -3
- package/dist/runtime/dev-build.d.ts +2 -2
- package/dist/runtime/dev-build.js +2 -2
- package/dist/runtime/game-input-seams.d.ts +51 -0
- package/dist/runtime/game-input-seams.d.ts.map +1 -0
- package/dist/runtime/game-input-seams.js +86 -0
- package/dist/runtime/game.d.ts +104 -40
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +119 -61
- package/dist/runtime/mount-game.d.ts +0 -16
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +26 -41
- package/dist/runtime/mount-manifest.d.ts +22 -14
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +22 -20
- package/dist/runtime/playtest.d.ts +22 -0
- package/dist/runtime/playtest.d.ts.map +1 -0
- package/dist/runtime/playtest.js +12 -0
- package/dist/runtime/state-bridge.d.ts +2 -3
- package/dist/runtime/state-bridge.d.ts.map +1 -1
- package/dist/runtime/state-bridge.js +1 -0
- package/dist/setup/setup-audio.d.ts +12 -0
- package/dist/setup/setup-audio.d.ts.map +1 -1
- package/dist/setup/setup-audio.js +9 -10
- package/dist/setup/setup-renderer.d.ts +2 -25
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +2 -31
- package/dist/world3d-react/index.d.ts +15 -25
- package/dist/world3d-react/index.d.ts.map +1 -1
- package/dist/world3d-react/index.js +15 -25
- package/dist/world3d-react/r3f-root-factory.d.ts +28 -34
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +403 -57
- package/dist/world3d-react/rapier-physics-bridge.d.ts +30 -17
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +143 -58
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +16 -14
- package/schemas/vgai-project.schema.json +35 -26
- package/src/adapter/adapter-module.ts +2 -4
- package/src/adapter/adapter-surface.ts +1 -1
- package/src/adapter/authoring.ts +19 -21
- package/src/adapter/binding.ts +468 -0
- package/src/adapter/colyseus-networking-adapter.ts +20 -5
- package/src/adapter/entrypoint-selection-readers.ts +66 -0
- package/src/adapter/entrypoint-selection-source.ts +1 -40
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
- package/src/adapter/host-context.ts +45 -15
- package/src/adapter/index.ts +23 -11
- package/src/adapter/ingest/game-contract.ts +0 -3
- package/src/adapter/ingest/scene-capture.ts +190 -39
- package/src/adapter/ingest/visible-capture-window.ts +35 -0
- package/src/adapter/manifest-interpreter.ts +64 -0
- package/src/adapter/native-debug-module.ts +248 -15
- package/src/adapter/rapier-physics-adapter.ts +76 -20
- package/src/adapter/renderer-config.ts +3 -3
- package/src/adapter/root-adapter.ts +7 -7
- package/src/adapter/system-adapter.ts +43 -8
- package/src/adapter/system-seam-contract.ts +3 -3
- package/src/adapter/system-slot.ts +291 -0
- package/src/adapter/transform.ts +18 -1
- package/src/ai/navigation.ts +30 -3
- package/src/animation/anim-graph-types.ts +11 -3
- package/src/animation/theatre-clock-binding.ts +4 -4
- package/src/animation/xstate-animation-binding.ts +2 -2
- package/src/animation/xstate-animation-meta.ts +16 -0
- package/src/asset-formats/index.ts +4 -7
- package/src/asset-formats/render-env.ts +3 -6
- package/src/asset-parse-error.ts +2 -5
- package/src/audio/bus-mixer.ts +161 -0
- package/src/canvas-react/index.ts +5 -21
- package/src/canvas-react/pixi-react-root-factory.tsx +301 -39
- package/src/core/countdown-timer.ts +188 -0
- package/src/core/deferred-commands.ts +174 -0
- package/src/core/frame-pacing.ts +2 -2
- package/src/core/game-loop.ts +1 -3
- package/src/core/seeded-random.ts +1 -1
- package/src/core/sim-clock.ts +19 -8
- package/src/core/types.ts +1 -3
- package/src/defaults.ts +6 -14
- package/src/dev/instruments.ts +108 -11
- package/src/dev/register-render-vitals.ts +1 -4
- package/src/dev/render-debug-adapter.ts +2 -3
- package/src/dev/render-vitals.ts +3 -3
- package/src/dev/webgl-frame-capture.ts +1 -1
- package/src/index.ts +2 -5
- package/src/input/host-pointer.ts +230 -0
- package/src/input/input-manager.ts +49 -13
- package/src/input/input-types.ts +1 -1
- package/src/input/rebind-controller.ts +6 -8
- package/src/input/schema.ts +216 -188
- package/src/manifest/filename.ts +8 -12
- package/src/manifest/load.ts +17 -13
- package/src/manifest/schema.ts +55 -47
- package/src/physics/collider-dimensions.ts +8 -12
- package/src/pixi/scene-capture.ts +12 -15
- package/src/react/world-state.tsx +1 -1
- package/src/render/directional-shadow-fit.ts +156 -0
- package/src/render/environment-capture.ts +102 -0
- package/src/render/ibl-override-material.ts +170 -0
- package/src/render/light-camera-factory.ts +8 -11
- package/src/render/material-factory.ts +1 -1
- package/src/render/render-batch-system.ts +14 -6
- package/src/render/render-features.ts +17 -35
- package/src/runtime/create-runtime.ts +270 -108
- package/src/runtime/debug-registry.ts +84 -8
- package/src/runtime/dev-build.ts +2 -2
- package/src/runtime/game-input-seams.ts +108 -0
- package/src/runtime/game.ts +231 -85
- package/src/runtime/mount-game.ts +31 -57
- package/src/runtime/mount-manifest.ts +47 -36
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/state-bridge.ts +3 -3
- package/src/setup/setup-audio.ts +21 -11
- package/src/setup/setup-renderer.ts +2 -61
- package/src/world3d-react/index.ts +15 -31
- package/src/world3d-react/r3f-root-factory.tsx +451 -60
- package/src/world3d-react/rapier-physics-bridge.tsx +154 -58
- package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
- package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
- package/dist/adapter/setup-three-root-adapter.js +0 -908
- package/dist/animation/clip-map.d.ts +0 -12
- package/dist/animation/clip-map.d.ts.map +0 -1
- package/dist/animation/clip-map.js +0 -31
- package/dist/asset-registry.d.ts +0 -38
- package/dist/asset-registry.d.ts.map +0 -1
- package/dist/asset-registry.js +0 -66
- package/dist/canvas-react/engine-bridge.d.ts +0 -45
- package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
- package/dist/canvas-react/engine-bridge.js +0 -45
- package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
- package/dist/canvas-react/pixi-react-adapter.js +0 -294
- package/dist/canvas-react/world-context.d.ts +0 -98
- package/dist/canvas-react/world-context.d.ts.map +0 -1
- package/dist/canvas-react/world-context.js +0 -173
- package/dist/dev/debug-draw.d.ts +0 -24
- package/dist/dev/debug-draw.d.ts.map +0 -1
- package/dist/dev/debug-draw.js +0 -73
- package/dist/render/auto-batcher.d.ts +0 -34
- package/dist/render/auto-batcher.d.ts.map +0 -1
- package/dist/render/auto-batcher.js +0 -140
- package/dist/render/lod.d.ts +0 -13
- package/dist/render/lod.d.ts.map +0 -1
- package/dist/render/lod.js +0 -16
- package/dist/runtime/types.d.ts +0 -303
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js +0 -1
- package/dist/world3d-react/engine-bridge.d.ts +0 -47
- package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
- package/dist/world3d-react/engine-bridge.js +0 -73
- package/dist/world3d-react/r3f-adapter.d.ts +0 -58
- package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
- package/dist/world3d-react/r3f-adapter.js +0 -634
- package/dist/world3d-react/world-context.d.ts +0 -182
- package/dist/world3d-react/world-context.d.ts.map +0 -1
- package/dist/world3d-react/world-context.js +0 -235
- package/src/adapter/setup-three-root-adapter.ts +0 -1032
- package/src/animation/clip-map.ts +0 -34
- package/src/asset-registry.ts +0 -89
- package/src/canvas-react/engine-bridge.ts +0 -59
- package/src/canvas-react/pixi-react-adapter.tsx +0 -356
- package/src/canvas-react/world-context.ts +0 -253
- package/src/dev/debug-draw.ts +0 -80
- package/src/render/auto-batcher.ts +0 -168
- package/src/render/lod.ts +0 -17
- package/src/runtime/types.ts +0 -328
- package/src/world3d-react/engine-bridge.ts +0 -86
- package/src/world3d-react/r3f-adapter.tsx +0 -717
- package/src/world3d-react/world-context.ts +0 -358
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Countdown` — a restartable interval the CALLER steps, with the two
|
|
3
|
+
* behaviours under a frame hitch that decide whether a game's event count is
|
|
4
|
+
* right.
|
|
5
|
+
*
|
|
6
|
+
* A respawn delay, a weapon cooldown, a wave spawner, a grace window: each is
|
|
7
|
+
* "count down from N seconds, tell me, maybe go again, and let me restart or
|
|
8
|
+
* stop you at any point". `core/sim-clock.ts` schedules a one-shot at an
|
|
9
|
+
* ABSOLUTE sim time — the right shape for "3 seconds from now, once" and the
|
|
10
|
+
* wrong one for a live interval that game code re-arms, reads
|
|
11
|
+
* ({@link Countdown.remaining} is what a HUD ring draws) and cancels.
|
|
12
|
+
*
|
|
13
|
+
* That gap used to be papered over in `sim-clock.ts`'s own header, which said
|
|
14
|
+
* an `after` re-arming itself was "three lines the game owns". Those three
|
|
15
|
+
* lines are wrong in a way nobody notices: re-arming schedules the next fire
|
|
16
|
+
* from the moment the callback RAN, so every long frame permanently lengthens
|
|
17
|
+
* the interval, and a game that hitches ten times has silently slowed its
|
|
18
|
+
* spawner. That is the drift {@link Countdown} exists to not have.
|
|
19
|
+
*
|
|
20
|
+
* ## It owns no clock, and that is the requirement rather than a shortcut
|
|
21
|
+
*
|
|
22
|
+
* There is no `setTimeout`, no `setInterval`, no `requestAnimationFrame` and no
|
|
23
|
+
* subscription anywhere in this module. A countdown advances only inside
|
|
24
|
+
* {@link Countdown.advance}, from seconds the caller already has — the fixed
|
|
25
|
+
* loop's own dt. This repo runs a fixed-step sim with display-rate
|
|
26
|
+
* presentation and can step a game deterministically; a countdown on wall-clock
|
|
27
|
+
* `setTimeout` would be a second, undeterministic timeline that no care
|
|
28
|
+
* downstream could re-sync. A countdown nobody advances does not fire —
|
|
29
|
+
* visibly, rather than drifting.
|
|
30
|
+
*
|
|
31
|
+
* ## The frame-hitch contract, which is the whole reason this is a module
|
|
32
|
+
*
|
|
33
|
+
* Both rules are about keeping the EVENT COUNT correct when `dt` is larger than
|
|
34
|
+
* the period, which is exactly when a game is already in trouble:
|
|
35
|
+
*
|
|
36
|
+
* - **Overshoot carries into the next period.** A 0.5 s repeating countdown
|
|
37
|
+
* advanced by 0.6 fires once and has 0.4 s left, not 0.5. Resetting to the
|
|
38
|
+
* full period instead would make one slow frame lengthen every interval
|
|
39
|
+
* after it — the drift above, one layer down.
|
|
40
|
+
* - **A single large step fires MORE THAN ONCE.** A 0.1 s countdown advanced
|
|
41
|
+
* by 0.35 fires three times and has 0.05 s left. Dropping the extras would
|
|
42
|
+
* silently slow a repeating countdown down whenever the frame was long, so a
|
|
43
|
+
* wave that should have spawned 3 enemies spawns 1 and the difficulty curve
|
|
44
|
+
* quietly depends on frame rate.
|
|
45
|
+
*
|
|
46
|
+
* Two consequences a caller must know about, both deliberate: `onElapsed` can
|
|
47
|
+
* be invoked several times within one `advance` call, and it runs INLINE
|
|
48
|
+
* (synchronously, inside `advance`) rather than on a microtask — so the caller
|
|
49
|
+
* always knows exactly which point of its frame the callback ran at.
|
|
50
|
+
*
|
|
51
|
+
* A one-shot countdown STOPS ITSELF BEFORE invoking `onElapsed`, so a callback
|
|
52
|
+
* that calls {@link Countdown.start} re-arms rather than fighting the stop.
|
|
53
|
+
* A callback that calls {@link Countdown.stop} ends the multi-fire loop
|
|
54
|
+
* immediately, which is what makes "fire once then disarm from inside the
|
|
55
|
+
* callback" work.
|
|
56
|
+
*
|
|
57
|
+
* ## Resource ownership
|
|
58
|
+
*
|
|
59
|
+
* **Owner:** whoever calls {@link createCountdown}. A countdown holds two
|
|
60
|
+
* numbers, a boolean and the `onElapsed` reference; it registers nothing,
|
|
61
|
+
* subscribes to nothing, and this module has no module-level state — so there
|
|
62
|
+
* is deliberately no `dispose()` and dropping the countdown drops everything it
|
|
63
|
+
* had. **Sharers:** none; a countdown is not shareable, because whoever
|
|
64
|
+
* advances it decides its timeline. **Teardown:** what DOES need unregistering
|
|
65
|
+
* is the countdown's slot in whatever set the owner advances each frame, and
|
|
66
|
+
* that is the owner's — the same way `SceneTree.addTimer` hands back a remover.
|
|
67
|
+
*/
|
|
68
|
+
function assertDuration(value) {
|
|
69
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
70
|
+
throw new RangeError(`createCountdown: duration must be a finite, positive number of seconds, got ${String(value)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build a countdown.
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* const wave = createCountdown({ duration: 8, repeats: true, autostart: true, onElapsed: spawnWave });
|
|
78
|
+
* useFrame((_, dt) => wave.advance(dt)); // the caller owns the step
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function createCountdown(options) {
|
|
82
|
+
const onElapsed = options.onElapsed;
|
|
83
|
+
const repeats = options.repeats === true;
|
|
84
|
+
let duration = options.duration ?? 1;
|
|
85
|
+
let remaining = 0;
|
|
86
|
+
let running = false;
|
|
87
|
+
assertDuration(duration);
|
|
88
|
+
const countdown = {
|
|
89
|
+
get duration() {
|
|
90
|
+
return duration;
|
|
91
|
+
},
|
|
92
|
+
get repeats() {
|
|
93
|
+
return repeats;
|
|
94
|
+
},
|
|
95
|
+
get remaining() {
|
|
96
|
+
return running ? remaining : 0;
|
|
97
|
+
},
|
|
98
|
+
get running() {
|
|
99
|
+
return running;
|
|
100
|
+
},
|
|
101
|
+
start(newDuration) {
|
|
102
|
+
if (newDuration !== undefined) {
|
|
103
|
+
assertDuration(newDuration);
|
|
104
|
+
duration = newDuration;
|
|
105
|
+
}
|
|
106
|
+
remaining = duration;
|
|
107
|
+
running = true;
|
|
108
|
+
},
|
|
109
|
+
stop() {
|
|
110
|
+
running = false;
|
|
111
|
+
remaining = 0;
|
|
112
|
+
},
|
|
113
|
+
advance(dt) {
|
|
114
|
+
if (!running)
|
|
115
|
+
return;
|
|
116
|
+
remaining -= dt;
|
|
117
|
+
// The loop is the multi-fire rule; `running` in the condition is what
|
|
118
|
+
// lets a callback's own stop() end it. Module header, frame-hitch.
|
|
119
|
+
while (running && remaining <= 0) {
|
|
120
|
+
if (!repeats) {
|
|
121
|
+
// Disarm BEFORE invoking, so a callback that calls start() re-arms
|
|
122
|
+
// instead of being immediately stopped by this branch.
|
|
123
|
+
running = false;
|
|
124
|
+
remaining = 0;
|
|
125
|
+
onElapsed();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Carry the overshoot rather than resetting to the full period.
|
|
129
|
+
remaining += duration;
|
|
130
|
+
onElapsed();
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
if (options.autostart === true)
|
|
135
|
+
countdown.start();
|
|
136
|
+
return countdown;
|
|
137
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `DeferredCommands` — work the caller runs at ONE point it chooses, plus the
|
|
3
|
+
* subject-keyed disposal that has to land after it.
|
|
4
|
+
*
|
|
5
|
+
* The hazard is not exotic and it is not a foreign engine's: it is what a
|
|
6
|
+
* Rapier world does to anything that mutates it from inside a callback. Disable
|
|
7
|
+
* a collider while the solver is iterating, or free the node a collision
|
|
8
|
+
* handler was dispatched ON, and the failure is either a corrupt step or
|
|
9
|
+
* Rapier's own "recursive use of an object ... unsafe aliasing in rust". The
|
|
10
|
+
* remedy is always the same shape — write down WHAT to do, do it once the frame
|
|
11
|
+
* is out of anybody's way — and every caller that needs it was previously
|
|
12
|
+
* hand-rolling an array.
|
|
13
|
+
*
|
|
14
|
+
* ## The one point is the CALLER's, deliberately
|
|
15
|
+
*
|
|
16
|
+
* There is no `queueMicrotask`, no `setTimeout(0)`, no `requestAnimationFrame`
|
|
17
|
+
* and no subscription anywhere in this module. Whoever builds the queue decides
|
|
18
|
+
* where {@link DeferredCommands.drain} runs — after physics, at the tail of a
|
|
19
|
+
* world's update, wherever the frame's quiet moment actually is. A queue nobody
|
|
20
|
+
* drains simply does not run its work, which is a visible failure rather than a
|
|
21
|
+
* silent reordering onto a timeline the game does not control.
|
|
22
|
+
*
|
|
23
|
+
* ## This is NOT `SimClock`, and `after(0)` is not a substitute
|
|
24
|
+
*
|
|
25
|
+
* `core/sim-clock.ts` schedules on SIM TIME: `after(0, fn)` scheduled mid-frame
|
|
26
|
+
* does land in that frame's tail flush (`runtime/game.ts` calls `flush(simT)`
|
|
27
|
+
* after every phase of every world), so the resemblance is real and worth
|
|
28
|
+
* naming. Three things make it the wrong tool for deferral:
|
|
29
|
+
*
|
|
30
|
+
* - **A paused world never flushes.** `flush` is inside `runFrameImpl`'s
|
|
31
|
+
* `advanced` guard, so a game that pauses between the defer and the drain
|
|
32
|
+
* holds the work forever. End-of-frame deferral has to run on the frame,
|
|
33
|
+
* not on the clock.
|
|
34
|
+
* - **No subject identity.** Deferring destruction needs "this object, once",
|
|
35
|
+
* and needs the two questions that fall out of it —
|
|
36
|
+
* {@link DeferredCommands.isDisposePending} (a handler must be able to skip
|
|
37
|
+
* a subject that is already going away) and
|
|
38
|
+
* {@link DeferredCommands.wasDisposed} (an owner sweeping its own indexes
|
|
39
|
+
* after the drain). A time-ordered timer set answers neither.
|
|
40
|
+
* - **No same-drain re-entry.** A sim timer scheduled during a flush is
|
|
41
|
+
* deliberately never due in that flush; a deferred command that defers one
|
|
42
|
+
* more command must still land this frame, or the second write arrives a
|
|
43
|
+
* frame after the first and the two are no longer atomic.
|
|
44
|
+
*
|
|
45
|
+
* ## Ordering, and why disposal goes last
|
|
46
|
+
*
|
|
47
|
+
* {@link DeferredCommands.drain} runs every deferred command first, re-draining
|
|
48
|
+
* until the queue is empty, and only then disposes every pending subject. That
|
|
49
|
+
* order is not cosmetic: a deferred write to a subject that is ALSO queued for
|
|
50
|
+
* disposal must still happen — it is what the caller asked for — and disposing
|
|
51
|
+
* first would make that write operate on a freed handle. A subject queued twice
|
|
52
|
+
* is disposed once, and the FIRST thunk wins (every caller for one subject
|
|
53
|
+
* passes the same one).
|
|
54
|
+
*
|
|
55
|
+
* The re-drain is bounded by {@link MAX_DRAIN_PASSES}. A command that defers
|
|
56
|
+
* itself forever is a bug in the caller, and a bounded loop names it in a
|
|
57
|
+
* millisecond instead of hanging the frame.
|
|
58
|
+
*
|
|
59
|
+
* ## Why disposal carries its own thunk
|
|
60
|
+
*
|
|
61
|
+
* Disposal is the one thing here that is not surface-neutral. A `PIXI.Container`
|
|
62
|
+
* ends with `removeFromParent()` + `destroy({ children })`; a `THREE.Object3D`
|
|
63
|
+
* ends by detaching and disposing the geometries and materials underneath it;
|
|
64
|
+
* a physics-backed entity has a Rapier body to pull first. This queue does none
|
|
65
|
+
* of that — it takes the thunk from whoever owns the surface and runs it at the
|
|
66
|
+
* drain. So the ORDERING lives here once and the surface knowledge stays where
|
|
67
|
+
* the surface is, which is what lets one queue serve a 2D world and a 3D one.
|
|
68
|
+
*
|
|
69
|
+
* ## Resource ownership
|
|
70
|
+
*
|
|
71
|
+
* **Owner:** whoever calls {@link createDeferredCommands}, and that owner is
|
|
72
|
+
* also the only caller of `drain()` — a queue is per-DRAINER, not per-process,
|
|
73
|
+
* so two mounted games are two queues and neither can see the other's work.
|
|
74
|
+
* This module registers nothing anywhere: there is no game-scoped slot (unlike
|
|
75
|
+
* `sim-clock.ts`, which needs one because every world on a Game shares one
|
|
76
|
+
* clock) and no module-level state at all.
|
|
77
|
+
* **Sharers:** the queued subjects, by reference, and only until the drain that
|
|
78
|
+
* disposes them.
|
|
79
|
+
* **Teardown:** dropping the queue. There is deliberately no `dispose()` —
|
|
80
|
+
* nothing here holds a handle, a listener or a timer, and a queue dropped with
|
|
81
|
+
* work still in it has simply not run that work, which is what discarding a
|
|
82
|
+
* frame means.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* How many times {@link DeferredCommands.drain} re-drains a queue that keeps
|
|
86
|
+
* refilling itself before declaring the caller's own logic non-terminating.
|
|
87
|
+
*
|
|
88
|
+
* A bound that correct code cannot reach (a deferred command that defers one
|
|
89
|
+
* more, ten deep) while catching the infinite case immediately.
|
|
90
|
+
*/
|
|
91
|
+
export declare const MAX_DRAIN_PASSES = 16;
|
|
92
|
+
/** The queue. Build one per drainer with {@link createDeferredCommands}. */
|
|
93
|
+
export interface DeferredCommands {
|
|
94
|
+
/**
|
|
95
|
+
* Run `command` at the next {@link DeferredCommands.drain}.
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* // inside a collision handler, where the solver still owns the world:
|
|
99
|
+
* frame.defer(() => collider.setSensor(true));
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
defer(command: () => void): void;
|
|
103
|
+
/**
|
|
104
|
+
* Dispose `subject` at the next drain, after every deferred command has run.
|
|
105
|
+
* Queuing the same subject twice disposes it once — the first `dispose`
|
|
106
|
+
* thunk wins.
|
|
107
|
+
*/
|
|
108
|
+
deferDispose(subject: object, dispose: () => void): void;
|
|
109
|
+
/**
|
|
110
|
+
* Is `subject` queued for disposal and not yet disposed? Read by a handler
|
|
111
|
+
* that must skip a subject already on its way out.
|
|
112
|
+
*/
|
|
113
|
+
isDisposePending(subject: object): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Has THIS queue already disposed `subject`? Read after a drain by an owner
|
|
116
|
+
* sweeping its own indexes (a group, a registry, a spatial bucket) — "am I
|
|
117
|
+
* destroyed" is a surface-specific question that a plain `Object3D` has no
|
|
118
|
+
* answer to, so the queue is asked instead.
|
|
119
|
+
*/
|
|
120
|
+
wasDisposed(subject: object): boolean;
|
|
121
|
+
/** Run every deferred command (re-draining), then dispose every pending
|
|
122
|
+
* subject. Called once per frame, by the owner, at the point it chose. */
|
|
123
|
+
drain(): void;
|
|
124
|
+
}
|
|
125
|
+
/** Build one queue. See the module header for who owns it and who drains it. */
|
|
126
|
+
export declare function createDeferredCommands(): DeferredCommands;
|
|
127
|
+
//# sourceMappingURL=deferred-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-commands.d.ts","sourceRoot":"","sources":["../../src/core/deferred-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkFG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACzD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3C;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC;+EAC2E;IAC3E,KAAK,IAAI,IAAI,CAAC;CACf;AAED,gFAAgF;AAChF,wBAAgB,sBAAsB,IAAI,gBAAgB,CA6CzD"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `DeferredCommands` — work the caller runs at ONE point it chooses, plus the
|
|
3
|
+
* subject-keyed disposal that has to land after it.
|
|
4
|
+
*
|
|
5
|
+
* The hazard is not exotic and it is not a foreign engine's: it is what a
|
|
6
|
+
* Rapier world does to anything that mutates it from inside a callback. Disable
|
|
7
|
+
* a collider while the solver is iterating, or free the node a collision
|
|
8
|
+
* handler was dispatched ON, and the failure is either a corrupt step or
|
|
9
|
+
* Rapier's own "recursive use of an object ... unsafe aliasing in rust". The
|
|
10
|
+
* remedy is always the same shape — write down WHAT to do, do it once the frame
|
|
11
|
+
* is out of anybody's way — and every caller that needs it was previously
|
|
12
|
+
* hand-rolling an array.
|
|
13
|
+
*
|
|
14
|
+
* ## The one point is the CALLER's, deliberately
|
|
15
|
+
*
|
|
16
|
+
* There is no `queueMicrotask`, no `setTimeout(0)`, no `requestAnimationFrame`
|
|
17
|
+
* and no subscription anywhere in this module. Whoever builds the queue decides
|
|
18
|
+
* where {@link DeferredCommands.drain} runs — after physics, at the tail of a
|
|
19
|
+
* world's update, wherever the frame's quiet moment actually is. A queue nobody
|
|
20
|
+
* drains simply does not run its work, which is a visible failure rather than a
|
|
21
|
+
* silent reordering onto a timeline the game does not control.
|
|
22
|
+
*
|
|
23
|
+
* ## This is NOT `SimClock`, and `after(0)` is not a substitute
|
|
24
|
+
*
|
|
25
|
+
* `core/sim-clock.ts` schedules on SIM TIME: `after(0, fn)` scheduled mid-frame
|
|
26
|
+
* does land in that frame's tail flush (`runtime/game.ts` calls `flush(simT)`
|
|
27
|
+
* after every phase of every world), so the resemblance is real and worth
|
|
28
|
+
* naming. Three things make it the wrong tool for deferral:
|
|
29
|
+
*
|
|
30
|
+
* - **A paused world never flushes.** `flush` is inside `runFrameImpl`'s
|
|
31
|
+
* `advanced` guard, so a game that pauses between the defer and the drain
|
|
32
|
+
* holds the work forever. End-of-frame deferral has to run on the frame,
|
|
33
|
+
* not on the clock.
|
|
34
|
+
* - **No subject identity.** Deferring destruction needs "this object, once",
|
|
35
|
+
* and needs the two questions that fall out of it —
|
|
36
|
+
* {@link DeferredCommands.isDisposePending} (a handler must be able to skip
|
|
37
|
+
* a subject that is already going away) and
|
|
38
|
+
* {@link DeferredCommands.wasDisposed} (an owner sweeping its own indexes
|
|
39
|
+
* after the drain). A time-ordered timer set answers neither.
|
|
40
|
+
* - **No same-drain re-entry.** A sim timer scheduled during a flush is
|
|
41
|
+
* deliberately never due in that flush; a deferred command that defers one
|
|
42
|
+
* more command must still land this frame, or the second write arrives a
|
|
43
|
+
* frame after the first and the two are no longer atomic.
|
|
44
|
+
*
|
|
45
|
+
* ## Ordering, and why disposal goes last
|
|
46
|
+
*
|
|
47
|
+
* {@link DeferredCommands.drain} runs every deferred command first, re-draining
|
|
48
|
+
* until the queue is empty, and only then disposes every pending subject. That
|
|
49
|
+
* order is not cosmetic: a deferred write to a subject that is ALSO queued for
|
|
50
|
+
* disposal must still happen — it is what the caller asked for — and disposing
|
|
51
|
+
* first would make that write operate on a freed handle. A subject queued twice
|
|
52
|
+
* is disposed once, and the FIRST thunk wins (every caller for one subject
|
|
53
|
+
* passes the same one).
|
|
54
|
+
*
|
|
55
|
+
* The re-drain is bounded by {@link MAX_DRAIN_PASSES}. A command that defers
|
|
56
|
+
* itself forever is a bug in the caller, and a bounded loop names it in a
|
|
57
|
+
* millisecond instead of hanging the frame.
|
|
58
|
+
*
|
|
59
|
+
* ## Why disposal carries its own thunk
|
|
60
|
+
*
|
|
61
|
+
* Disposal is the one thing here that is not surface-neutral. A `PIXI.Container`
|
|
62
|
+
* ends with `removeFromParent()` + `destroy({ children })`; a `THREE.Object3D`
|
|
63
|
+
* ends by detaching and disposing the geometries and materials underneath it;
|
|
64
|
+
* a physics-backed entity has a Rapier body to pull first. This queue does none
|
|
65
|
+
* of that — it takes the thunk from whoever owns the surface and runs it at the
|
|
66
|
+
* drain. So the ORDERING lives here once and the surface knowledge stays where
|
|
67
|
+
* the surface is, which is what lets one queue serve a 2D world and a 3D one.
|
|
68
|
+
*
|
|
69
|
+
* ## Resource ownership
|
|
70
|
+
*
|
|
71
|
+
* **Owner:** whoever calls {@link createDeferredCommands}, and that owner is
|
|
72
|
+
* also the only caller of `drain()` — a queue is per-DRAINER, not per-process,
|
|
73
|
+
* so two mounted games are two queues and neither can see the other's work.
|
|
74
|
+
* This module registers nothing anywhere: there is no game-scoped slot (unlike
|
|
75
|
+
* `sim-clock.ts`, which needs one because every world on a Game shares one
|
|
76
|
+
* clock) and no module-level state at all.
|
|
77
|
+
* **Sharers:** the queued subjects, by reference, and only until the drain that
|
|
78
|
+
* disposes them.
|
|
79
|
+
* **Teardown:** dropping the queue. There is deliberately no `dispose()` —
|
|
80
|
+
* nothing here holds a handle, a listener or a timer, and a queue dropped with
|
|
81
|
+
* work still in it has simply not run that work, which is what discarding a
|
|
82
|
+
* frame means.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* How many times {@link DeferredCommands.drain} re-drains a queue that keeps
|
|
86
|
+
* refilling itself before declaring the caller's own logic non-terminating.
|
|
87
|
+
*
|
|
88
|
+
* A bound that correct code cannot reach (a deferred command that defers one
|
|
89
|
+
* more, ten deep) while catching the infinite case immediately.
|
|
90
|
+
*/
|
|
91
|
+
export const MAX_DRAIN_PASSES = 16;
|
|
92
|
+
/** Build one queue. See the module header for who owns it and who drains it. */
|
|
93
|
+
export function createDeferredCommands() {
|
|
94
|
+
let commands = [];
|
|
95
|
+
const doomed = new Map();
|
|
96
|
+
const disposed = new WeakSet();
|
|
97
|
+
return {
|
|
98
|
+
defer(command) {
|
|
99
|
+
commands.push(command);
|
|
100
|
+
},
|
|
101
|
+
deferDispose(subject, dispose) {
|
|
102
|
+
if (!doomed.has(subject))
|
|
103
|
+
doomed.set(subject, dispose);
|
|
104
|
+
},
|
|
105
|
+
isDisposePending(subject) {
|
|
106
|
+
return doomed.has(subject);
|
|
107
|
+
},
|
|
108
|
+
wasDisposed(subject) {
|
|
109
|
+
return disposed.has(subject);
|
|
110
|
+
},
|
|
111
|
+
drain() {
|
|
112
|
+
let passes = 0;
|
|
113
|
+
while (commands.length > 0) {
|
|
114
|
+
if (++passes > MAX_DRAIN_PASSES) {
|
|
115
|
+
throw new Error(`DeferredCommands.drain: still queueing more work after ${MAX_DRAIN_PASSES} passes. ` +
|
|
116
|
+
'Something passed to defer() defers itself, so this drain would never terminate.');
|
|
117
|
+
}
|
|
118
|
+
const batch = commands;
|
|
119
|
+
commands = [];
|
|
120
|
+
for (const command of batch)
|
|
121
|
+
command();
|
|
122
|
+
}
|
|
123
|
+
// Subjects last, and only after every deferred command has run — see the
|
|
124
|
+
// module header's ordering section.
|
|
125
|
+
for (const [subject, dispose] of doomed) {
|
|
126
|
+
dispose();
|
|
127
|
+
disposed.add(subject);
|
|
128
|
+
}
|
|
129
|
+
doomed.clear();
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Frame pacing — the accumulator/alpha arithmetic of the fixed-timestep loop,
|
|
3
|
-
* extracted PURE
|
|
3
|
+
* extracted PURE.
|
|
4
4
|
*
|
|
5
5
|
* `core/game-loop.ts` owns the browser side (rAF, `visibilitychange`,
|
|
6
6
|
* `performance.now`); this module owns the arithmetic, so the interesting
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Frame pacing — the accumulator/alpha arithmetic of the fixed-timestep loop,
|
|
3
|
-
* extracted PURE
|
|
3
|
+
* extracted PURE.
|
|
4
4
|
*
|
|
5
5
|
* `core/game-loop.ts` owns the browser side (rAF, `visibilitychange`,
|
|
6
6
|
* `performance.now`); this module owns the arithmetic, so the interesting
|
|
@@ -72,7 +72,7 @@ export function paceFrame(accumulator, rawDt, timeScale, limits) {
|
|
|
72
72
|
// Also the accumulator's hard ceiling (see the spiral-of-death clamp below).
|
|
73
73
|
const maxAccumulator = fixedDt * maxSubSteps;
|
|
74
74
|
// Clamp large frame gaps (a slow frame, a debugger pause) BEFORE scaling by
|
|
75
|
-
// timeScale
|
|
75
|
+
// timeScale.
|
|
76
76
|
const displayDt = Math.min(rawDt, maxAccumulator) * timeScale;
|
|
77
77
|
let next = accumulator + displayDt;
|
|
78
78
|
// Spiral-of-death guard: bound the accumulator regardless of timeScale or
|
package/dist/core/game-loop.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { GameLoopConfig, GameLoopLiveness } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Fixed-timestep game loop with accumulator pattern — SIM at a fixed rate,
|
|
4
|
-
* PRESENTATION at the display's rate
|
|
5
|
-
* flipped the former D1 "render is fixed-rate too" decision this comment used
|
|
6
|
-
* to state).
|
|
4
|
+
* PRESENTATION at the display's rate.
|
|
7
5
|
*
|
|
8
6
|
* Per real (rAF) frame:
|
|
9
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-loop.d.ts","sourceRoot":"","sources":["../../src/core/game-loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQhE
|
|
1
|
+
{"version":3,"file":"game-loop.d.ts","sourceRoot":"","sources":["../../src/core/game-loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc;;;;IAuIjD;;;;;;;;;;;;OAYG;uBACa,gBAAgB;eASX,MAAM;IAe3B;;;;;;;OAOG;;EAKN"}
|
package/dist/core/game-loop.js
CHANGED
|
@@ -6,9 +6,7 @@ import { clampTimeScale, paceFrame, TIME_SCALE_RANGE } from './frame-pacing';
|
|
|
6
6
|
const LOOP_STARVATION_MS = 1_000;
|
|
7
7
|
/**
|
|
8
8
|
* Fixed-timestep game loop with accumulator pattern — SIM at a fixed rate,
|
|
9
|
-
* PRESENTATION at the display's rate
|
|
10
|
-
* flipped the former D1 "render is fixed-rate too" decision this comment used
|
|
11
|
-
* to state).
|
|
9
|
+
* PRESENTATION at the display's rate.
|
|
12
10
|
*
|
|
13
11
|
* Per real (rAF) frame:
|
|
14
12
|
*
|
|
@@ -36,7 +36,7 @@ export declare const GAMEPLAY_STREAM = "gameplay";
|
|
|
36
36
|
* `determinism.seededRandom` is declared, per T4.1's "no dead field" rule). */
|
|
37
37
|
export declare const DEFAULT_SEEDED_RANDOM_SEED = 2654435769;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* A game-scoped random surface.
|
|
40
40
|
* Callable (draws from the `'gameplay'` stream), plus:
|
|
41
41
|
* - `stream(name)` — an independent named generator, `[0, 1)` floats, same
|
|
42
42
|
* call signature as `Math.random`/the bare `SeededRandom` call itself.
|
package/dist/core/sim-clock.d.ts
CHANGED
|
@@ -93,10 +93,9 @@
|
|
|
93
93
|
* controller cancels everything a `setup()` scheduled:
|
|
94
94
|
*
|
|
95
95
|
* ```ts
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* return { dispose: () => ac.abort() }; // cancels timers AND listeners
|
|
96
|
+
* const ac = new AbortController();
|
|
97
|
+
* clock.after(3, () => spawnWave(), { signal: ac.signal });
|
|
98
|
+
* // on dispose: ac.abort() — cancels timers AND listeners
|
|
100
99
|
* }
|
|
101
100
|
* ```
|
|
102
101
|
*
|
|
@@ -105,8 +104,20 @@
|
|
|
105
104
|
*
|
|
106
105
|
* ## Deliberately absent
|
|
107
106
|
*
|
|
108
|
-
* - **No `every`/`repeat`/interval
|
|
109
|
-
* re-arming itself
|
|
107
|
+
* - **No `every`/`repeat`/interval — that is `core/countdown-timer.ts`.**
|
|
108
|
+
* This header used to say an `after` re-arming itself was "three lines the
|
|
109
|
+
* game owns"; it is three lines that DRIFT, because re-arming schedules the
|
|
110
|
+
* next fire from the moment the callback ran, so every long frame
|
|
111
|
+
* permanently lengthens the interval. A repeating interval needs
|
|
112
|
+
* overshoot carry and multi-fire on a big step to keep its event count
|
|
113
|
+
* right, and it is stepped by its owner rather than by the clock. Both
|
|
114
|
+
* belong to a countdown object, not to a sim-time one-shot.
|
|
115
|
+
* - **No end-of-frame command queue — that is
|
|
116
|
+
* `core/deferred-commands.ts`.** `after(0, fn)` looks like one (the flush
|
|
117
|
+
* below does run at the tail of the frame that scheduled it) and is not:
|
|
118
|
+
* it holds the work forever across a pause, it has no subject identity, and
|
|
119
|
+
* a command it schedules during a flush waits a whole extra frame. See that
|
|
120
|
+
* module's header for all three.
|
|
110
121
|
* - **No fiber kernel, no coroutine emulation, no `task.spawn`.** Calling an
|
|
111
122
|
* `async function` *is* `task.spawn` — the language already has it.
|
|
112
123
|
* - **No wall clock.** This module reads no system timer and creates no
|
|
@@ -152,7 +163,7 @@ export interface SimClock {
|
|
|
152
163
|
/**
|
|
153
164
|
* The runtime-facing half — NOT for game code. Split off the public
|
|
154
165
|
* {@link SimClock} the same way `runtime/game.ts` splits `GameInternal` off
|
|
155
|
-
* `Game`:
|
|
166
|
+
* `Game`: game code only ever sees the public `SimClock`, so it cannot
|
|
156
167
|
* reach `flush`/`dispose`.
|
|
157
168
|
*/
|
|
158
169
|
export interface SimClockInternal extends SimClock {
|
|
@@ -193,7 +204,7 @@ export declare function registerSimClock(owner: object, clock: SimClockInternal)
|
|
|
193
204
|
* Returns the INTERNAL view because this registry is engine-only — but a
|
|
194
205
|
* caller that RESOLVES a clock here is not its owner and must not call
|
|
195
206
|
* `dispose()` on it (module header, ownership). Game code never reaches it at
|
|
196
|
-
* all:
|
|
207
|
+
* all: it only ever sees the public {@link SimClock}, which has
|
|
197
208
|
* neither `flush` nor `dispose`, the same way `Game` hides `GameInternal`. */
|
|
198
209
|
export declare function getSimClock(owner: object): SimClockInternal | null;
|
|
199
210
|
//# sourceMappingURL=sim-clock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sim-clock.d.ts","sourceRoot":"","sources":["../../src/core/sim-clock.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sim-clock.d.ts","sourceRoot":"","sources":["../../src/core/sim-clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4HG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAGpC,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,wEAAwE;IACxE,GAAG,IAAI,MAAM,CAAC;IACd,2EAA2E;IAC3E,OAAO,IAAI,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,cAAc,CAAC;IAClF;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC;CACpE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD;;;;;OAKG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;CACjD;AAyBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,gBAAgB,CAiJzE;AAWD,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE7E;AAED;;;;;;;;+EAQ+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAElE"}
|
package/dist/core/sim-clock.js
CHANGED
|
@@ -93,10 +93,9 @@
|
|
|
93
93
|
* controller cancels everything a `setup()` scheduled:
|
|
94
94
|
*
|
|
95
95
|
* ```ts
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* return { dispose: () => ac.abort() }; // cancels timers AND listeners
|
|
96
|
+
* const ac = new AbortController();
|
|
97
|
+
* clock.after(3, () => spawnWave(), { signal: ac.signal });
|
|
98
|
+
* // on dispose: ac.abort() — cancels timers AND listeners
|
|
100
99
|
* }
|
|
101
100
|
* ```
|
|
102
101
|
*
|
|
@@ -105,8 +104,20 @@
|
|
|
105
104
|
*
|
|
106
105
|
* ## Deliberately absent
|
|
107
106
|
*
|
|
108
|
-
* - **No `every`/`repeat`/interval
|
|
109
|
-
* re-arming itself
|
|
107
|
+
* - **No `every`/`repeat`/interval — that is `core/countdown-timer.ts`.**
|
|
108
|
+
* This header used to say an `after` re-arming itself was "three lines the
|
|
109
|
+
* game owns"; it is three lines that DRIFT, because re-arming schedules the
|
|
110
|
+
* next fire from the moment the callback ran, so every long frame
|
|
111
|
+
* permanently lengthens the interval. A repeating interval needs
|
|
112
|
+
* overshoot carry and multi-fire on a big step to keep its event count
|
|
113
|
+
* right, and it is stepped by its owner rather than by the clock. Both
|
|
114
|
+
* belong to a countdown object, not to a sim-time one-shot.
|
|
115
|
+
* - **No end-of-frame command queue — that is
|
|
116
|
+
* `core/deferred-commands.ts`.** `after(0, fn)` looks like one (the flush
|
|
117
|
+
* below does run at the tail of the frame that scheduled it) and is not:
|
|
118
|
+
* it holds the work forever across a pause, it has no subject identity, and
|
|
119
|
+
* a command it schedules during a flush waits a whole extra frame. See that
|
|
120
|
+
* module's header for all three.
|
|
110
121
|
* - **No fiber kernel, no coroutine emulation, no `task.spawn`.** Calling an
|
|
111
122
|
* `async function` *is* `task.spawn` — the language already has it.
|
|
112
123
|
* - **No wall clock.** This module reads no system timer and creates no
|
|
@@ -285,7 +296,7 @@ export function registerSimClock(owner, clock) {
|
|
|
285
296
|
* Returns the INTERNAL view because this registry is engine-only — but a
|
|
286
297
|
* caller that RESOLVES a clock here is not its owner and must not call
|
|
287
298
|
* `dispose()` on it (module header, ownership). Game code never reaches it at
|
|
288
|
-
* all:
|
|
299
|
+
* all: it only ever sees the public {@link SimClock}, which has
|
|
289
300
|
* neither `flush` nor `dispose`, the same way `Game` hides `GameInternal`. */
|
|
290
301
|
export function getSimClock(owner) {
|
|
291
302
|
return clockByOwner.get(owner) ?? null;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -56,9 +56,7 @@ export interface GameLoopConfig {
|
|
|
56
56
|
*/
|
|
57
57
|
update: (dt: number) => void;
|
|
58
58
|
/**
|
|
59
|
-
* Called EXACTLY ONCE per real (rAF) frame — the PRESENTATION half
|
|
60
|
-
* (WO-P13, `docs/WORK.md` §2 P13; this is the callback whose absence the
|
|
61
|
-
* former D1 decision recorded).
|
|
59
|
+
* Called EXACTLY ONCE per real (rAF) frame — the PRESENTATION half.
|
|
62
60
|
*
|
|
63
61
|
* - `alpha` — `accumulator / fixedDt` after this frame's substeps were
|
|
64
62
|
* consumed, in `[0, 1]`: how far presentation sits past the last
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;CASd,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAE7E,+BAA+B;AAC/B,eAAO,MAAM,WAAW,EAAE,eAAe,EASxC,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,QAAQ,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACvE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACtE;AAED,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,KAAK,EAAE,eAAe,CAAC;IACvB,4EAA4E;IAC5E,IAAI,CAAC,IAAI,IAAI,CAAC;IACd,uBAAuB;IACvB,MAAM,EAAE,QAAQ,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;CASd,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAE7E,+BAA+B;AAC/B,eAAO,MAAM,WAAW,EAAE,eAAe,EASxC,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,QAAQ,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACvE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACtE;AAED,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,KAAK,EAAE,eAAe,CAAC;IACvB,4EAA4E;IAC5E,IAAI,CAAC,IAAI,IAAI,CAAC;IACd,uBAAuB;IACvB,MAAM,EAAE,QAAQ,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAAC"}
|
package/dist/defaults.d.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single source of truth for default values the engine's factories read.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* — wire the group to the factory that reads it, or delete it if truly unused —
|
|
11
|
-
* and there is no surviving factory to wire these to: `transform` restated
|
|
12
|
-
* THREE's own Object3D constructor values, `entity.locked`/ `pivot` were
|
|
13
|
-
* descriptor-only fields, and `fog`/`environment.envMapIntensity`/ `shadow` were
|
|
14
|
-
* the `.vscn` `environment` block that `render-env.ts` explicitly did NOT carry
|
|
15
|
-
* forward. Keeping them would have been exactly the dead documentation T1.13
|
|
16
|
-
* created this guard to prevent.
|
|
17
|
-
*
|
|
4
|
+
* A group belongs here only while a FACTORY reads it. A default with no
|
|
5
|
+
* reader is dead documentation: it looks authoritative, nothing consults it,
|
|
6
|
+
* and it drifts from the value the code actually uses. Restating a library's
|
|
7
|
+
* own constructor values (THREE's `Object3D` transform, say) is the same
|
|
8
|
+
* mistake in a different shape — the library is already the source of truth.
|
|
9
|
+
* So when a factory goes, its group goes with it.
|
|
18
10
|
*/
|
|
19
11
|
export declare const DEFAULTS: {
|
|
20
12
|
readonly material: {
|
package/dist/defaults.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gDAkB0B,CAAC,MAAM,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;kCA6BjB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6E5D,CAAC"}
|