@vgai/engine 0.3.0 → 0.4.0-canary.20260715.0
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/package.json +8 -2
- package/schemas/engine-api.json +124 -0
- package/schemas/engine-api.md +53 -0
- package/schemas/engine-capabilities.json +124 -0
- package/schemas/inputmap.schema.json +314 -0
- package/schemas/mat.schema.json +286 -0
- package/schemas/prefab.schema.json +10148 -0
- package/schemas/scn2d.schema.json +475 -0
- package/schemas/vgai-game.schema.json +383 -0
- package/schemas/vscn.schema.json +11007 -0
- package/src/adapter/{world-kind.ts → adapter-surface.ts} +6 -6
- package/src/adapter/authoring.ts +67 -0
- package/src/adapter/game-adapter.ts +8 -8
- package/src/adapter/host-context.ts +2 -4
- package/src/adapter/index.ts +3 -1
- package/src/adapter/system-adapter.ts +73 -0
- package/src/adapter/vgai-scene-game-adapter.ts +239 -180
- package/src/animation/xstate-animation-binding.ts +300 -262
- package/src/animation/xstate-animation-meta.ts +50 -13
- package/src/character/cloth-sim.ts +533 -0
- package/src/character/spring-chain.ts +307 -0
- package/src/core/game-loop.ts +41 -3
- package/src/core/seeded-random.ts +161 -0
- package/src/core/system-runner.ts +20 -3
- package/src/core/types.ts +37 -0
- package/src/data/data-asset.ts +1 -1
- package/src/dev/performance-profiler.ts +213 -0
- package/src/dev/webgl-gpu-timer.ts +53 -0
- package/src/ecs/component-manager.ts +45 -12
- package/src/ecs/game-component.ts +95 -11
- package/src/humanoid/bake.operation.ts +326 -0
- package/src/humanoid/body.ts +663 -0
- package/src/humanoid/clips.ts +149 -0
- package/src/humanoid/compose.ts +209 -0
- package/src/humanoid/generate.ts +189 -0
- package/src/humanoid/index.ts +36 -0
- package/src/humanoid/schema.ts +108 -0
- package/src/humanoid/skeleton.ts +345 -0
- package/src/index.ts +5 -5
- package/src/input/input-manager.ts +701 -22
- package/src/input/input-types.ts +5 -2
- package/src/manifest/index.ts +5 -5
- package/src/manifest/load.ts +125 -72
- package/src/manifest/schema.ts +362 -255
- package/src/react/game-state.tsx +135 -32
- package/src/react/root-adapter.tsx +49 -0
- package/src/react/unmanaged-root-detector.ts +66 -0
- package/src/react/use-data.ts +12 -2
- package/src/runtime/create-runtime.ts +100 -282
- package/src/runtime/debug-bridge.ts +483 -0
- package/src/runtime/debug-registry.ts +856 -0
- package/src/runtime/game.ts +342 -93
- package/src/runtime/gameplay-rng-trap.ts +134 -0
- package/src/runtime/input-router.ts +7 -7
- package/src/runtime/mount-game.ts +40 -38
- package/src/runtime/mount-manifest.ts +169 -37
- package/src/runtime/render-control.ts +13 -13
- package/src/runtime/render-seed.ts +1 -1
- package/src/runtime/state-bridge.ts +24 -10
- package/src/runtime/types.ts +110 -28
- package/src/scene/asset-registry.ts +22 -0
- package/src/scene/component-registry.ts +14 -3
- package/src/scene/defaults.ts +1 -0
- package/src/scene/light-camera-factory.ts +11 -3
- package/src/scene/scene-apply.ts +55 -4
- package/src/scene/scene-loader.ts +10 -27
- package/src/scene/scene-types.ts +0 -1
- package/src/scene/schema/index.ts +0 -34
- package/src/scene/schema/light.ts +16 -1
- package/src/scene/schema/material.ts +96 -91
- package/src/scene/schema/scene-file.ts +1 -7
- package/src/scene/user-data.ts +14 -1
- package/src/setup/setup-renderer.ts +1 -1
- package/src/tools/define-tool.ts +50 -11
- package/src/world2d/authoring-2d.ts +17 -1
- package/src/world2d/collision-2d.ts +1 -1
- package/src/world2d/pixi-game-adapter.ts +19 -17
- package/src/world2d/scene2d-loader.ts +1 -0
- package/src/world2d/types.ts +8 -2
- package/dist/adapter/authoring.d.ts +0 -404
- package/dist/adapter/authoring.d.ts.map +0 -1
- package/dist/adapter/authoring.js +0 -22
- package/dist/adapter/colyseus-networking-adapter.d.ts +0 -43
- package/dist/adapter/colyseus-networking-adapter.d.ts.map +0 -1
- package/dist/adapter/colyseus-networking-adapter.js +0 -38
- package/dist/adapter/first-party-systems.d.ts +0 -38
- package/dist/adapter/first-party-systems.d.ts.map +0 -1
- package/dist/adapter/first-party-systems.js +0 -77
- package/dist/adapter/game-adapter.d.ts +0 -133
- package/dist/adapter/game-adapter.d.ts.map +0 -1
- package/dist/adapter/game-adapter.js +0 -10
- package/dist/adapter/host-context.d.ts +0 -73
- package/dist/adapter/host-context.d.ts.map +0 -1
- package/dist/adapter/host-context.js +0 -16
- package/dist/adapter/index.d.ts +0 -22
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js +0 -15
- package/dist/adapter/ingest/game-contract.d.ts +0 -53
- package/dist/adapter/ingest/game-contract.d.ts.map +0 -1
- package/dist/adapter/ingest/game-contract.js +0 -30
- package/dist/adapter/ingest/overlay-applier.d.ts +0 -118
- package/dist/adapter/ingest/overlay-applier.d.ts.map +0 -1
- package/dist/adapter/ingest/overlay-applier.js +0 -132
- package/dist/adapter/ingest/overlay-apply.d.ts +0 -60
- package/dist/adapter/ingest/overlay-apply.d.ts.map +0 -1
- package/dist/adapter/ingest/overlay-apply.js +0 -101
- package/dist/adapter/ingest/overlay-file.d.ts +0 -79
- package/dist/adapter/ingest/overlay-file.d.ts.map +0 -1
- package/dist/adapter/ingest/overlay-file.js +0 -66
- package/dist/adapter/ingest/overlay-report.d.ts +0 -113
- package/dist/adapter/ingest/overlay-report.d.ts.map +0 -1
- package/dist/adapter/ingest/overlay-report.js +0 -103
- package/dist/adapter/ingest/scene-capture.d.ts +0 -97
- package/dist/adapter/ingest/scene-capture.d.ts.map +0 -1
- package/dist/adapter/ingest/scene-capture.js +0 -248
- package/dist/adapter/ingest/upstream-pin.d.ts +0 -47
- package/dist/adapter/ingest/upstream-pin.d.ts.map +0 -1
- package/dist/adapter/ingest/upstream-pin.js +0 -50
- package/dist/adapter/loop-gate-report.d.ts +0 -34
- package/dist/adapter/loop-gate-report.d.ts.map +0 -1
- package/dist/adapter/loop-gate-report.js +0 -37
- package/dist/adapter/rapier-physics-adapter.d.ts +0 -12
- package/dist/adapter/rapier-physics-adapter.d.ts.map +0 -1
- package/dist/adapter/rapier-physics-adapter.js +0 -46
- package/dist/adapter/system-adapter.d.ts +0 -133
- package/dist/adapter/system-adapter.d.ts.map +0 -1
- package/dist/adapter/system-adapter.js +0 -13
- package/dist/adapter/transform.d.ts +0 -18
- package/dist/adapter/transform.d.ts.map +0 -1
- package/dist/adapter/transform.js +0 -1
- package/dist/adapter/vgai-scene-game-adapter.d.ts +0 -116
- package/dist/adapter/vgai-scene-game-adapter.d.ts.map +0 -1
- package/dist/adapter/vgai-scene-game-adapter.js +0 -714
- package/dist/adapter/world-kind.d.ts +0 -30
- package/dist/adapter/world-kind.d.ts.map +0 -1
- package/dist/adapter/world-kind.js +0 -18
- package/dist/ai/navigation.d.ts +0 -64
- package/dist/ai/navigation.d.ts.map +0 -1
- package/dist/ai/navigation.js +0 -133
- package/dist/animation/anim-graph-types.d.ts +0 -25
- package/dist/animation/anim-graph-types.d.ts.map +0 -1
- package/dist/animation/anim-graph-types.js +0 -1
- package/dist/animation/animation-clock.d.ts +0 -213
- package/dist/animation/animation-clock.d.ts.map +0 -1
- package/dist/animation/animation-clock.js +0 -301
- package/dist/animation/blend-node.d.ts +0 -22
- package/dist/animation/blend-node.d.ts.map +0 -1
- package/dist/animation/blend-node.js +0 -97
- package/dist/animation/camera-ownership.d.ts +0 -250
- package/dist/animation/camera-ownership.d.ts.map +0 -1
- package/dist/animation/camera-ownership.js +0 -169
- package/dist/animation/cinematic-cues.d.ts +0 -200
- package/dist/animation/cinematic-cues.d.ts.map +0 -1
- package/dist/animation/cinematic-cues.js +0 -213
- 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 -37
- package/dist/animation/gsap-registration.d.ts +0 -119
- package/dist/animation/gsap-registration.d.ts.map +0 -1
- package/dist/animation/gsap-registration.js +0 -92
- package/dist/animation/theatre-clock-binding.d.ts +0 -89
- package/dist/animation/theatre-clock-binding.d.ts.map +0 -1
- package/dist/animation/theatre-clock-binding.js +0 -29
- package/dist/animation/theatre-director.d.ts +0 -242
- package/dist/animation/theatre-director.d.ts.map +0 -1
- package/dist/animation/theatre-director.js +0 -235
- package/dist/animation/theatre-object-binding.d.ts +0 -305
- package/dist/animation/theatre-object-binding.d.ts.map +0 -1
- package/dist/animation/theatre-object-binding.js +0 -351
- package/dist/animation/xstate-animation-binding.d.ts +0 -111
- package/dist/animation/xstate-animation-binding.d.ts.map +0 -1
- package/dist/animation/xstate-animation-binding.js +0 -307
- package/dist/animation/xstate-animation-meta.d.ts +0 -228
- package/dist/animation/xstate-animation-meta.d.ts.map +0 -1
- package/dist/animation/xstate-animation-meta.js +0 -221
- package/dist/assets.d.ts +0 -26
- package/dist/assets.d.ts.map +0 -1
- package/dist/assets.js +0 -55
- package/dist/audio/index.d.ts +0 -4
- package/dist/audio/index.d.ts.map +0 -1
- package/dist/audio/index.js +0 -24
- package/dist/audio/tone-clock-binding.d.ts +0 -75
- package/dist/audio/tone-clock-binding.d.ts.map +0 -1
- package/dist/audio/tone-clock-binding.js +0 -29
- package/dist/audio/tone-context.d.ts +0 -114
- package/dist/audio/tone-context.d.ts.map +0 -1
- package/dist/audio/tone-context.js +0 -39
- package/dist/audio/tone-offline-render.d.ts +0 -90
- package/dist/audio/tone-offline-render.d.ts.map +0 -1
- package/dist/audio/tone-offline-render.js +0 -56
- package/dist/audio/wav-encode.d.ts +0 -38
- package/dist/audio/wav-encode.d.ts.map +0 -1
- package/dist/audio/wav-encode.js +0 -97
- package/dist/core/game-loop.d.ts +0 -21
- package/dist/core/game-loop.d.ts.map +0 -1
- package/dist/core/game-loop.js +0 -130
- package/dist/core/system-runner.d.ts +0 -120
- package/dist/core/system-runner.d.ts.map +0 -1
- package/dist/core/system-runner.js +0 -286
- package/dist/core/types.d.ts +0 -58
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -25
- package/dist/data/data-asset.d.ts +0 -101
- package/dist/data/data-asset.d.ts.map +0 -1
- package/dist/data/data-asset.js +0 -122
- package/dist/data/data-check-core.d.ts +0 -110
- package/dist/data/data-check-core.d.ts.map +0 -1
- package/dist/data/data-check-core.js +0 -178
- package/dist/data/data-ref.d.ts +0 -79
- package/dist/data/data-ref.d.ts.map +0 -1
- package/dist/data/data-ref.js +0 -130
- package/dist/data/vite-plugin-data.d.ts +0 -79
- package/dist/data/vite-plugin-data.d.ts.map +0 -1
- package/dist/data/vite-plugin-data.js +0 -217
- package/dist/dev/console-bridge.d.ts +0 -22
- package/dist/dev/console-bridge.d.ts.map +0 -1
- package/dist/dev/console-bridge.js +0 -71
- 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/dev/logger.d.ts +0 -30
- package/dist/dev/logger.d.ts.map +0 -1
- package/dist/dev/logger.js +0 -90
- package/dist/ecs/component-manager.d.ts +0 -189
- package/dist/ecs/component-manager.d.ts.map +0 -1
- package/dist/ecs/component-manager.js +0 -676
- package/dist/ecs/game-component.d.ts +0 -97
- package/dist/ecs/game-component.d.ts.map +0 -1
- package/dist/ecs/game-component.js +0 -80
- package/dist/ecs/hmr-swap-report.d.ts +0 -53
- package/dist/ecs/hmr-swap-report.d.ts.map +0 -1
- package/dist/ecs/hmr-swap-report.js +0 -55
- package/dist/index.d.ts +0 -37
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -30
- package/dist/input/input-manager.d.ts +0 -596
- package/dist/input/input-manager.d.ts.map +0 -1
- package/dist/input/input-manager.js +0 -1564
- package/dist/input/input-types.d.ts +0 -171
- package/dist/input/input-types.d.ts.map +0 -1
- package/dist/input/input-types.js +0 -1
- package/dist/input/prompt-labels.d.ts +0 -21
- package/dist/input/prompt-labels.d.ts.map +0 -1
- package/dist/input/prompt-labels.js +0 -121
- package/dist/input/rebind-controller.d.ts +0 -59
- package/dist/input/rebind-controller.d.ts.map +0 -1
- package/dist/input/rebind-controller.js +0 -85
- package/dist/input/schema.d.ts +0 -16
- package/dist/input/schema.d.ts.map +0 -1
- package/dist/input/schema.js +0 -228
- package/dist/loader.d.ts +0 -43
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -59
- package/dist/manifest/index.d.ts +0 -5
- package/dist/manifest/index.d.ts.map +0 -1
- package/dist/manifest/index.js +0 -8
- package/dist/manifest/load-file.d.ts +0 -4
- package/dist/manifest/load-file.d.ts.map +0 -1
- package/dist/manifest/load-file.js +0 -14
- package/dist/manifest/load.d.ts +0 -117
- package/dist/manifest/load.d.ts.map +0 -1
- package/dist/manifest/load.js +0 -255
- package/dist/manifest/schema.d.ts +0 -173
- package/dist/manifest/schema.d.ts.map +0 -1
- package/dist/manifest/schema.js +0 -302
- package/dist/physics/collision-system.d.ts +0 -41
- package/dist/physics/collision-system.d.ts.map +0 -1
- package/dist/physics/collision-system.js +0 -64
- package/dist/physics/physics-registry.d.ts +0 -45
- package/dist/physics/physics-registry.d.ts.map +0 -1
- package/dist/physics/physics-registry.js +0 -65
- package/dist/physics/transform-writer.d.ts +0 -16
- package/dist/physics/transform-writer.d.ts.map +0 -1
- package/dist/physics/transform-writer.js +0 -38
- package/dist/physics/trigger-dispatch.d.ts +0 -24
- package/dist/physics/trigger-dispatch.d.ts.map +0 -1
- package/dist/physics/trigger-dispatch.js +0 -80
- package/dist/react/game-state.d.ts +0 -105
- package/dist/react/game-state.d.ts.map +0 -1
- package/dist/react/game-state.js +0 -144
- package/dist/react/use-data.d.ts +0 -60
- package/dist/react/use-data.d.ts.map +0 -1
- package/dist/react/use-data.js +0 -96
- package/dist/react/use-selection.d.ts +0 -75
- package/dist/react/use-selection.d.ts.map +0 -1
- package/dist/react/use-selection.js +0 -90
- 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/render-batch-system.d.ts +0 -33
- package/dist/render/render-batch-system.d.ts.map +0 -1
- package/dist/render/render-batch-system.js +0 -230
- package/dist/render/render-features.d.ts +0 -47
- package/dist/render/render-features.d.ts.map +0 -1
- package/dist/render/render-features.js +0 -105
- package/dist/render/render-settings.d.ts +0 -36
- package/dist/render/render-settings.d.ts.map +0 -1
- package/dist/render/render-settings.js +0 -51
- package/dist/runtime/create-runtime.d.ts +0 -280
- package/dist/runtime/create-runtime.d.ts.map +0 -1
- package/dist/runtime/create-runtime.js +0 -795
- package/dist/runtime/frame-selector-cache.d.ts +0 -41
- package/dist/runtime/frame-selector-cache.d.ts.map +0 -1
- package/dist/runtime/frame-selector-cache.js +0 -65
- package/dist/runtime/game.d.ts +0 -433
- package/dist/runtime/game.d.ts.map +0 -1
- package/dist/runtime/game.js +0 -569
- package/dist/runtime/input-router.d.ts +0 -78
- package/dist/runtime/input-router.d.ts.map +0 -1
- package/dist/runtime/input-router.js +0 -171
- package/dist/runtime/mount-game.d.ts +0 -129
- package/dist/runtime/mount-game.d.ts.map +0 -1
- package/dist/runtime/mount-game.js +0 -152
- package/dist/runtime/mount-manifest.d.ts +0 -119
- package/dist/runtime/mount-manifest.d.ts.map +0 -1
- package/dist/runtime/mount-manifest.js +0 -204
- package/dist/runtime/render-audio-control.d.ts +0 -110
- package/dist/runtime/render-audio-control.d.ts.map +0 -1
- package/dist/runtime/render-audio-control.js +0 -111
- package/dist/runtime/render-control.d.ts +0 -291
- package/dist/runtime/render-control.d.ts.map +0 -1
- package/dist/runtime/render-control.js +0 -264
- package/dist/runtime/render-seed.d.ts +0 -57
- package/dist/runtime/render-seed.d.ts.map +0 -1
- package/dist/runtime/render-seed.js +0 -74
- package/dist/runtime/scene-ui-bridge.d.ts +0 -69
- package/dist/runtime/scene-ui-bridge.d.ts.map +0 -1
- package/dist/runtime/scene-ui-bridge.js +0 -23
- package/dist/runtime/scene-ui-data.d.ts +0 -13
- package/dist/runtime/scene-ui-data.d.ts.map +0 -1
- package/dist/runtime/scene-ui-data.js +0 -92
- package/dist/runtime/state-bridge.d.ts +0 -47
- package/dist/runtime/state-bridge.d.ts.map +0 -1
- package/dist/runtime/state-bridge.js +0 -53
- package/dist/runtime/types.d.ts +0 -179
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js +0 -1
- package/dist/scene/asset-loaders.d.ts +0 -79
- package/dist/scene/asset-loaders.d.ts.map +0 -1
- package/dist/scene/asset-loaders.js +0 -141
- package/dist/scene/asset-paths.d.ts +0 -11
- package/dist/scene/asset-paths.d.ts.map +0 -1
- package/dist/scene/asset-paths.js +0 -108
- package/dist/scene/asset-ref-check.d.ts +0 -88
- package/dist/scene/asset-ref-check.d.ts.map +0 -1
- package/dist/scene/asset-ref-check.js +0 -214
- package/dist/scene/asset-registry.d.ts +0 -30
- package/dist/scene/asset-registry.d.ts.map +0 -1
- package/dist/scene/asset-registry.js +0 -50
- package/dist/scene/collider-dimensions.d.ts +0 -63
- package/dist/scene/collider-dimensions.d.ts.map +0 -1
- package/dist/scene/collider-dimensions.js +0 -98
- package/dist/scene/component-registry.d.ts +0 -17
- package/dist/scene/component-registry.d.ts.map +0 -1
- package/dist/scene/component-registry.js +0 -28
- package/dist/scene/defaults.d.ts +0 -252
- package/dist/scene/defaults.d.ts.map +0 -1
- package/dist/scene/defaults.js +0 -151
- package/dist/scene/geometries/index.d.ts +0 -8
- package/dist/scene/geometries/index.d.ts.map +0 -1
- package/dist/scene/geometries/index.js +0 -7
- package/dist/scene/geometries/terrain.d.ts +0 -2
- package/dist/scene/geometries/terrain.d.ts.map +0 -1
- package/dist/scene/geometries/terrain.js +0 -37
- package/dist/scene/geometry-registry.d.ts +0 -24
- package/dist/scene/geometry-registry.d.ts.map +0 -1
- package/dist/scene/geometry-registry.js +0 -20
- package/dist/scene/instance-registry.d.ts +0 -36
- package/dist/scene/instance-registry.d.ts.map +0 -1
- package/dist/scene/instance-registry.js +0 -55
- package/dist/scene/instancers/grid.d.ts +0 -2
- package/dist/scene/instancers/grid.d.ts.map +0 -1
- package/dist/scene/instancers/grid.js +0 -36
- package/dist/scene/instancers/index.d.ts +0 -8
- package/dist/scene/instancers/index.d.ts.map +0 -1
- package/dist/scene/instancers/index.js +0 -7
- package/dist/scene/light-camera-factory.d.ts +0 -23
- package/dist/scene/light-camera-factory.d.ts.map +0 -1
- package/dist/scene/light-camera-factory.js +0 -68
- package/dist/scene/material-factory.d.ts +0 -17
- package/dist/scene/material-factory.d.ts.map +0 -1
- package/dist/scene/material-factory.js +0 -197
- package/dist/scene/material-registry.d.ts +0 -40
- package/dist/scene/material-registry.d.ts.map +0 -1
- package/dist/scene/material-registry.js +0 -44
- package/dist/scene/materials/index.d.ts +0 -8
- package/dist/scene/materials/index.d.ts.map +0 -1
- package/dist/scene/materials/index.js +0 -7
- package/dist/scene/materials/water.d.ts +0 -2
- package/dist/scene/materials/water.d.ts.map +0 -1
- package/dist/scene/materials/water.js +0 -54
- package/dist/scene/parse.d.ts +0 -23
- package/dist/scene/parse.d.ts.map +0 -1
- package/dist/scene/parse.js +0 -179
- package/dist/scene/particles-factory.d.ts +0 -33
- package/dist/scene/particles-factory.d.ts.map +0 -1
- package/dist/scene/particles-factory.js +0 -282
- package/dist/scene/scene-apply.d.ts +0 -109
- package/dist/scene/scene-apply.d.ts.map +0 -1
- package/dist/scene/scene-apply.js +0 -326
- package/dist/scene/scene-diff-schema.d.ts +0 -285
- package/dist/scene/scene-diff-schema.d.ts.map +0 -1
- package/dist/scene/scene-diff-schema.js +0 -92
- package/dist/scene/scene-diff-types.d.ts +0 -40
- package/dist/scene/scene-diff-types.d.ts.map +0 -1
- package/dist/scene/scene-diff-types.js +0 -14
- package/dist/scene/scene-loader.d.ts +0 -177
- package/dist/scene/scene-loader.d.ts.map +0 -1
- package/dist/scene/scene-loader.js +0 -1213
- package/dist/scene/scene-query.d.ts +0 -11
- package/dist/scene/scene-query.d.ts.map +0 -1
- package/dist/scene/scene-query.js +0 -63
- package/dist/scene/scene-types.d.ts +0 -10
- package/dist/scene/scene-types.d.ts.map +0 -1
- package/dist/scene/scene-types.js +0 -8
- package/dist/scene/scene-version.d.ts +0 -26
- package/dist/scene/scene-version.d.ts.map +0 -1
- package/dist/scene/scene-version.js +0 -34
- package/dist/scene/schema/animation.d.ts +0 -8
- package/dist/scene/schema/animation.d.ts.map +0 -1
- package/dist/scene/schema/animation.js +0 -42
- package/dist/scene/schema/audio.d.ts +0 -13
- package/dist/scene/schema/audio.d.ts.map +0 -1
- package/dist/scene/schema/audio.js +0 -22
- package/dist/scene/schema/camera.d.ts +0 -28
- package/dist/scene/schema/camera.d.ts.map +0 -1
- package/dist/scene/schema/camera.js +0 -17
- package/dist/scene/schema/collider.d.ts +0 -40
- package/dist/scene/schema/collider.d.ts.map +0 -1
- package/dist/scene/schema/collider.js +0 -55
- package/dist/scene/schema/entity-ref.d.ts +0 -31
- package/dist/scene/schema/entity-ref.d.ts.map +0 -1
- package/dist/scene/schema/entity-ref.js +0 -61
- package/dist/scene/schema/entity.d.ts +0 -1036
- package/dist/scene/schema/entity.d.ts.map +0 -1
- package/dist/scene/schema/entity.js +0 -153
- package/dist/scene/schema/environment.d.ts +0 -425
- package/dist/scene/schema/environment.d.ts.map +0 -1
- package/dist/scene/schema/environment.js +0 -357
- package/dist/scene/schema/index.d.ts +0 -39
- package/dist/scene/schema/index.d.ts.map +0 -1
- package/dist/scene/schema/index.js +0 -20
- package/dist/scene/schema/instances.d.ts +0 -14
- package/dist/scene/schema/instances.d.ts.map +0 -1
- package/dist/scene/schema/instances.js +0 -28
- package/dist/scene/schema/joint.d.ts +0 -23
- package/dist/scene/schema/joint.d.ts.map +0 -1
- package/dist/scene/schema/joint.js +0 -23
- package/dist/scene/schema/light.d.ts +0 -38
- package/dist/scene/schema/light.d.ts.map +0 -1
- package/dist/scene/schema/light.js +0 -26
- package/dist/scene/schema/material.d.ts +0 -199
- package/dist/scene/schema/material.d.ts.map +0 -1
- package/dist/scene/schema/material.js +0 -99
- package/dist/scene/schema/mesh.d.ts +0 -48
- package/dist/scene/schema/mesh.d.ts.map +0 -1
- package/dist/scene/schema/mesh.js +0 -82
- package/dist/scene/schema/particles.d.ts +0 -2178
- package/dist/scene/schema/particles.d.ts.map +0 -1
- package/dist/scene/schema/particles.js +0 -321
- package/dist/scene/schema/physics.d.ts +0 -56
- package/dist/scene/schema/physics.d.ts.map +0 -1
- package/dist/scene/schema/physics.js +0 -37
- package/dist/scene/schema/scene-file.d.ts +0 -304
- package/dist/scene/schema/scene-file.d.ts.map +0 -1
- package/dist/scene/schema/scene-file.js +0 -262
- package/dist/scene/schema/shadow.d.ts +0 -28
- package/dist/scene/schema/shadow.d.ts.map +0 -1
- package/dist/scene/schema/shadow.js +0 -20
- package/dist/scene/schema/spline.d.ts +0 -25
- package/dist/scene/schema/spline.d.ts.map +0 -1
- package/dist/scene/schema/spline.js +0 -15
- package/dist/scene/schema/tuples.d.ts +0 -12
- package/dist/scene/schema/tuples.d.ts.map +0 -1
- package/dist/scene/schema/tuples.js +0 -14
- package/dist/scene/schema/ui.d.ts +0 -880
- package/dist/scene/schema/ui.d.ts.map +0 -1
- package/dist/scene/schema/ui.js +0 -506
- package/dist/scene/user-data.d.ts +0 -171
- package/dist/scene/user-data.d.ts.map +0 -1
- package/dist/scene/user-data.js +0 -137
- package/dist/setup/setup-audio.d.ts +0 -20
- package/dist/setup/setup-audio.d.ts.map +0 -1
- package/dist/setup/setup-audio.js +0 -41
- package/dist/setup/setup-particles.d.ts +0 -17
- package/dist/setup/setup-particles.d.ts.map +0 -1
- package/dist/setup/setup-particles.js +0 -16
- package/dist/setup/setup-physics.d.ts +0 -25
- package/dist/setup/setup-physics.d.ts.map +0 -1
- package/dist/setup/setup-physics.js +0 -51
- package/dist/setup/setup-renderer.d.ts +0 -69
- package/dist/setup/setup-renderer.d.ts.map +0 -1
- package/dist/setup/setup-renderer.js +0 -363
- package/dist/tools/define-tool.d.ts +0 -105
- package/dist/tools/define-tool.d.ts.map +0 -1
- package/dist/tools/define-tool.js +0 -82
- package/dist/world2d/authoring-2d.d.ts +0 -76
- package/dist/world2d/authoring-2d.d.ts.map +0 -1
- package/dist/world2d/authoring-2d.js +0 -176
- package/dist/world2d/capture-to-scene2d.d.ts +0 -5
- package/dist/world2d/capture-to-scene2d.d.ts.map +0 -1
- package/dist/world2d/capture-to-scene2d.js +0 -49
- package/dist/world2d/collision-2d.d.ts +0 -52
- package/dist/world2d/collision-2d.d.ts.map +0 -1
- package/dist/world2d/collision-2d.js +0 -70
- package/dist/world2d/components-2d.d.ts +0 -47
- package/dist/world2d/components-2d.d.ts.map +0 -1
- package/dist/world2d/components-2d.js +0 -77
- package/dist/world2d/index.d.ts +0 -34
- package/dist/world2d/index.d.ts.map +0 -1
- package/dist/world2d/index.js +0 -32
- package/dist/world2d/ingest-iframe-2d.d.ts +0 -82
- package/dist/world2d/ingest-iframe-2d.d.ts.map +0 -1
- package/dist/world2d/ingest-iframe-2d.js +0 -166
- package/dist/world2d/ingest2d.d.ts +0 -97
- package/dist/world2d/ingest2d.d.ts.map +0 -1
- package/dist/world2d/ingest2d.js +0 -40
- package/dist/world2d/physics2d-registry.d.ts +0 -24
- package/dist/world2d/physics2d-registry.d.ts.map +0 -1
- package/dist/world2d/physics2d-registry.js +0 -38
- package/dist/world2d/pixi-game-adapter.d.ts +0 -107
- package/dist/world2d/pixi-game-adapter.d.ts.map +0 -1
- package/dist/world2d/pixi-game-adapter.js +0 -211
- package/dist/world2d/pixi-surface.d.ts +0 -39
- package/dist/world2d/pixi-surface.d.ts.map +0 -1
- package/dist/world2d/pixi-surface.js +0 -47
- package/dist/world2d/scene-capture-2d.d.ts +0 -43
- package/dist/world2d/scene-capture-2d.d.ts.map +0 -1
- package/dist/world2d/scene-capture-2d.js +0 -92
- package/dist/world2d/scene2d-loader.d.ts +0 -27
- package/dist/world2d/scene2d-loader.d.ts.map +0 -1
- package/dist/world2d/scene2d-loader.js +0 -274
- package/dist/world2d/schema/entity2d.d.ts +0 -102
- package/dist/world2d/schema/entity2d.d.ts.map +0 -1
- package/dist/world2d/schema/entity2d.js +0 -119
- package/dist/world2d/schema/physics2d.d.ts +0 -46
- package/dist/world2d/schema/physics2d.d.ts.map +0 -1
- package/dist/world2d/schema/physics2d.js +0 -45
- package/dist/world2d/schema/sprite.d.ts +0 -42
- package/dist/world2d/schema/sprite.d.ts.map +0 -1
- package/dist/world2d/schema/sprite.js +0 -63
- package/dist/world2d/schema/tilemap.d.ts +0 -15
- package/dist/world2d/schema/tilemap.d.ts.map +0 -1
- package/dist/world2d/schema/tilemap.js +0 -19
- package/dist/world2d/schema/tuples2d.d.ts +0 -20
- package/dist/world2d/schema/tuples2d.d.ts.map +0 -1
- package/dist/world2d/schema/tuples2d.js +0 -19
- package/dist/world2d/system-adapters-2d.d.ts +0 -16
- package/dist/world2d/system-adapters-2d.d.ts.map +0 -1
- package/dist/world2d/system-adapters-2d.js +0 -37
- package/dist/world2d/transform-writer-2d.d.ts +0 -16
- package/dist/world2d/transform-writer-2d.d.ts.map +0 -1
- package/dist/world2d/transform-writer-2d.js +0 -22
- package/dist/world2d/types.d.ts +0 -53
- package/dist/world2d/types.d.ts.map +0 -1
- package/dist/world2d/types.js +0 -1
- package/src/runtime/scene-ui-bridge.ts +0 -86
- package/src/runtime/scene-ui-data.ts +0 -119
- package/src/scene/schema/ui.ts +0 -602
|
@@ -38,6 +38,70 @@ function isTextEntryFocused(): boolean {
|
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Thrown by `InputManager.setVirtualAction`/`tapVirtualAction` (Task 1.4,
|
|
43
|
+
* ACCEPTANCE-DRIVER-BUILD-PLAN.md / SYNTHETIC-PLAYER-SPEC.md §3.2) for an
|
|
44
|
+
* action name that was never `registerAction`/`loadMap`-ed. Carries a
|
|
45
|
+
* machine-readable `code` and `data.registered` (every declared action name)
|
|
46
|
+
* rather than requiring a caller to parse the message.
|
|
47
|
+
*/
|
|
48
|
+
export class InputActionError extends Error {
|
|
49
|
+
readonly code = 'INPUT_ACTION_NOT_FOUND' as const;
|
|
50
|
+
readonly data: { registered: string[] };
|
|
51
|
+
|
|
52
|
+
constructor(action: string, method: string, registered: string[]) {
|
|
53
|
+
super(
|
|
54
|
+
`InputManager.${method}: unknown action "${action}". Registered actions: ` +
|
|
55
|
+
(registered.length ? registered.join(', ') : '(none)'),
|
|
56
|
+
);
|
|
57
|
+
this.name = 'InputActionError';
|
|
58
|
+
this.data = { registered };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Thrown by `InputManager.scheduleActionAtTick` (D15/T-D15.5,
|
|
64
|
+
* docs/D15-DETERMINISM-DESIGN.md §2.c) when the named `tick` has already
|
|
65
|
+
* elapsed — scheduling only ever applies to the CURRENT or a future tick,
|
|
66
|
+
* never one already serviced by `poll()`. Carries `data.currentTick` — the
|
|
67
|
+
* NEXT tick `poll()` will service (not the tick that just elapsed) — so a
|
|
68
|
+
* caller can retry by scheduling for `data.currentTick` or later.
|
|
69
|
+
*/
|
|
70
|
+
export class InputTickError extends Error {
|
|
71
|
+
readonly code = 'TICK_ALREADY_PASSED' as const;
|
|
72
|
+
readonly data: { currentTick: number };
|
|
73
|
+
|
|
74
|
+
constructor(tick: number, currentTick: number) {
|
|
75
|
+
super(
|
|
76
|
+
`InputManager.scheduleActionAtTick: tick ${tick} has already passed (next tick to be ` +
|
|
77
|
+
`serviced: ${currentTick}) — scheduling only applies to the current or a future tick`,
|
|
78
|
+
);
|
|
79
|
+
this.name = 'InputTickError';
|
|
80
|
+
this.data = { currentTick };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Shallow equality for a resolved action value (boolean/number/Vector2) —
|
|
85
|
+
* D15/T-D15.5's recording tap uses this to emit DELTAS only (the trace
|
|
86
|
+
* format's "deltas only" line), never a full snapshot every tick. `prev`
|
|
87
|
+
* is `undefined` for an action never recorded before (always "changed"). */
|
|
88
|
+
function actionValuesEqual(
|
|
89
|
+
prev: boolean | number | { x: number; y: number } | undefined,
|
|
90
|
+
next: boolean | number | { x: number; y: number },
|
|
91
|
+
): boolean {
|
|
92
|
+
if (prev === undefined) return false;
|
|
93
|
+
if (typeof prev !== typeof next) return false;
|
|
94
|
+
if (typeof prev === 'object' && typeof next === 'object') {
|
|
95
|
+
return prev.x === next.x && prev.y === next.y;
|
|
96
|
+
}
|
|
97
|
+
return prev === next;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Cap on {@link InputManager}'s post-gate input-trace ring (D15/T-D15.5) —
|
|
101
|
+
* mirrors `debug-registry.ts`'s own `RING_CAP` precedent for the debug event
|
|
102
|
+
* ring: bounds memory for a recording session a caller forgot to stop. */
|
|
103
|
+
const INPUT_TRACE_CAP = 2000;
|
|
104
|
+
|
|
41
105
|
/**
|
|
42
106
|
* Maps raw keyboard/mouse/gamepad events → named actions.
|
|
43
107
|
*
|
|
@@ -296,6 +360,85 @@ export class InputManager {
|
|
|
296
360
|
private static isStickActive(v: Vector2): boolean {
|
|
297
361
|
return Math.hypot(v.x, v.y) > 0.3;
|
|
298
362
|
}
|
|
363
|
+
// Task 1.4 (ACCEPTANCE-DRIVER-BUILD-PLAN.md) — action-level virtual input
|
|
364
|
+
// for a synthetic player, promoted from hollowstone's `VirtualInput`. Held
|
|
365
|
+
// digitals OR into isPressed alongside binding contributions (like
|
|
366
|
+
// touchButtonsDown above); tap queues, then is promoted to "active" for
|
|
367
|
+
// exactly one poll()/endFrame() bracket (see poll()/endFrame() below,
|
|
368
|
+
// mirroring hollowstone's queued/beginFrame contract); scalar/vector2 are
|
|
369
|
+
// level values feeding the same max-magnitude/sum combine every real
|
|
370
|
+
// binding uses (collectScalarContributions/collectVector2Contributions).
|
|
371
|
+
private virtualDigitalHeld = new Set<string>();
|
|
372
|
+
private virtualDigitalTapQueued = new Set<string>();
|
|
373
|
+
private virtualDigitalTapActive = new Set<string>();
|
|
374
|
+
/** pulse-runner friction #4 — the release-edge mirror of
|
|
375
|
+
* `virtualDigitalTapActive`: a direct `setVirtualAction(action, false)` (or
|
|
376
|
+
* `clearVirtualActions()`) on an action that WAS held writes here so
|
|
377
|
+
* `isJustReleased` fires exactly once, mirroring real `onKeyUp`'s
|
|
378
|
+
* immediate `keysJustUp` write (no queue/promotion needed — unlike the
|
|
379
|
+
* press edge, nothing in `poll()` reassigns this set out from under a
|
|
380
|
+
* write that lands before `poll()` runs). Cleared every `endFrame()`,
|
|
381
|
+
* same bracket as every other just-* edge set. */
|
|
382
|
+
private virtualDigitalJustReleased = new Set<string>();
|
|
383
|
+
private virtualScalarValues = new Map<string, number>();
|
|
384
|
+
private virtualVector2Values = new Map<string, Vector2>();
|
|
385
|
+
// D15/T-D15.5 (docs/D15-DETERMINISM-DESIGN.md §2.c) — tick-indexed input
|
|
386
|
+
// scheduling + post-gate recording.
|
|
387
|
+
//
|
|
388
|
+
// `currentTick` is a FALLBACK, self-incrementing counter, used only when a
|
|
389
|
+
// caller never tells `poll()` which tick it's servicing (every existing
|
|
390
|
+
// bare/headless caller — tests, a mount with no `Game` shell behind it —
|
|
391
|
+
// keeps this exact pre-existing behavior, zero regression). The REAL
|
|
392
|
+
// per-world wiring (`vgai-scene-game-adapter.ts`) instead passes the
|
|
393
|
+
// shared `Game`-level tick counter into every `poll(tick)` call — this is
|
|
394
|
+
// deliberate: an InputManager-local counter drifts from the actual game
|
|
395
|
+
// tick for a paused/frozen world (its `poll()` isn't called every game
|
|
396
|
+
// tick, so a local increment-per-call counter undercounts) — see the
|
|
397
|
+
// `lastServicedTick` gap-handling below, which structurally can't drift
|
|
398
|
+
// because it's driven by whatever tick value the CALLER (ultimately the
|
|
399
|
+
// shared `Game` tick) actually supplies.
|
|
400
|
+
private currentTick = 0;
|
|
401
|
+
/** The last tick value `poll()` actually serviced, or `-1` before the
|
|
402
|
+
* first call. Used to detect a GAP (this world's input phase didn't run
|
|
403
|
+
* for one or more intervening ticks — e.g. frozen/paused while other
|
|
404
|
+
* roots kept advancing the shared game tick) so any schedule entries
|
|
405
|
+
* inside that gap can be dropped (with an observable event) instead of
|
|
406
|
+
* silently rotting in {@link scheduledActions} forever. */
|
|
407
|
+
private lastServicedTick = -1;
|
|
408
|
+
/** Pending `scheduleActionAtTick` actuations, grouped by their exact target
|
|
409
|
+
* tick. Applied (and removed) at the START of that tick's `poll()` — see
|
|
410
|
+
* `applyScheduledActionsForTick`. A tick whose input phase is skipped
|
|
411
|
+
* entirely (this world paused/frozen, or a gap between two serviced
|
|
412
|
+
* ticks) never reaches it — its scheduled entries are dropped, each
|
|
413
|
+
* emitting one `'input.schedule.dropped'` debug event (`{tick, action}`)
|
|
414
|
+
* via {@link setDebugEmit}'s sink if one is wired — NOT retried on a
|
|
415
|
+
* later tick (mirrors `clearVirtualActions`-on-gate-close: no stuck
|
|
416
|
+
* actuation ever silently fires late). */
|
|
417
|
+
private scheduledActions = new Map<
|
|
418
|
+
number,
|
|
419
|
+
{ action: string; value: boolean | number | { x: number; y: number } }[]
|
|
420
|
+
>();
|
|
421
|
+
/** Optional sink for the `'input.schedule.dropped'` debug event (`{tick,
|
|
422
|
+
* action}`) emitted whenever a scheduled entry is discarded because its
|
|
423
|
+
* target tick's input phase never ran. Wired by whoever constructs this
|
|
424
|
+
* `InputManager` with access to a `DebugRegistry`
|
|
425
|
+
* (`vgai-scene-game-adapter.ts`, the same seed spot as
|
|
426
|
+
* `setVirtualInputTarget`/`setInputActionsSource`) — `null` (the
|
|
427
|
+
* default) for a bare/headless `InputManager`, in which case a drop
|
|
428
|
+
* stays silent (matching pre-D15/T-D15.5 behavior). */
|
|
429
|
+
private debugEmit: ((event: string, detail?: unknown) => void) | null = null;
|
|
430
|
+
/** D15/T-D15.5 recording toggle — see {@link startInputRecording}. */
|
|
431
|
+
private inputRecording = false;
|
|
432
|
+
/** The post-gate action-delta trace (§2.c's format sketch) — capped so an
|
|
433
|
+
* accidentally-long recording session can't grow this unboundedly. */
|
|
434
|
+
private inputTrace: {
|
|
435
|
+
tick: number;
|
|
436
|
+
actions: Record<string, boolean | number | { x: number; y: number }>;
|
|
437
|
+
}[] = [];
|
|
438
|
+
/** Last value recorded per action, so `recordPostGateTick` only ever
|
|
439
|
+
* appends a DELTA (the format sketch's "deltas only" line), not a full
|
|
440
|
+
* snapshot every tick. Cleared on `startInputRecording()`. */
|
|
441
|
+
private lastTraceSnapshot = new Map<string, boolean | number | { x: number; y: number }>();
|
|
299
442
|
private disposed = false;
|
|
300
443
|
/**
|
|
301
444
|
* When false, all raw input is ignored and held/transient state is cleared.
|
|
@@ -320,6 +463,17 @@ export class InputManager {
|
|
|
320
463
|
private get inputActive(): boolean {
|
|
321
464
|
return this.enabled && this.focused;
|
|
322
465
|
}
|
|
466
|
+
/** #144 — the gate MACHINE input passes (virtual actions and
|
|
467
|
+
* tick-scheduled actuations): the editor's explicit `enabled` flag ONLY.
|
|
468
|
+
* Window focus deliberately does not participate: a synthetic player
|
|
469
|
+
* drives the shared editor tab precisely while the human's focus is
|
|
470
|
+
* elsewhere (their terminal, another window) — the first live in-editor
|
|
471
|
+
* probe run froze mid-suite the moment the owner started typing to their
|
|
472
|
+
* agent, which is the bug this gate split fixes. Real-device input keeps
|
|
473
|
+
* the stricter `inputActive` (enabled AND focused) gate above. */
|
|
474
|
+
private get machineInputActive(): boolean {
|
|
475
|
+
return this.enabled;
|
|
476
|
+
}
|
|
323
477
|
/** F2 — real pointer-lock state (`document.pointerLockElement`), tracked via
|
|
324
478
|
* `pointerlockchange` (see the constructor). */
|
|
325
479
|
private pointerLocked = false;
|
|
@@ -380,12 +534,15 @@ export class InputManager {
|
|
|
380
534
|
this.mouseDeltaY += e.movementY;
|
|
381
535
|
};
|
|
382
536
|
|
|
383
|
-
/** F2 — real window blur: drop focus and flush held state
|
|
384
|
-
*
|
|
385
|
-
*
|
|
537
|
+
/** F2 — real window blur: drop focus and flush held REAL-device state
|
|
538
|
+
* (the browser stops delivering keyup/mouseup while blurred, so a key
|
|
539
|
+
* held at alt-tab time would stick). #144: virtual-action state is
|
|
540
|
+
* deliberately NOT flushed here — a bot's held action survives the human
|
|
541
|
+
* looking away (see `machineInputActive`); only `setEnabled(false)`
|
|
542
|
+
* clears it. */
|
|
386
543
|
private onWindowBlur = () => {
|
|
387
544
|
this.focused = false;
|
|
388
|
-
this.
|
|
545
|
+
this.flushRealHeldState();
|
|
389
546
|
};
|
|
390
547
|
|
|
391
548
|
/** F2 — real window focus regain. No flush needed (there's nothing held to
|
|
@@ -395,14 +552,15 @@ export class InputManager {
|
|
|
395
552
|
this.focused = true;
|
|
396
553
|
};
|
|
397
554
|
|
|
398
|
-
/** Clear
|
|
399
|
-
* `setEnabled(false)`
|
|
400
|
-
* physically held when input is
|
|
401
|
-
* (gamepad axes, touch sticks,
|
|
402
|
-
* flushed here — they are gated
|
|
403
|
-
* checks in `poll()`/the
|
|
404
|
-
* pre-existing
|
|
405
|
-
|
|
555
|
+
/** Clear held/transient REAL-device state (keyboard/mouse/touch-button) —
|
|
556
|
+
* shared by `setEnabled(false)` (via `flushHeldState`) and real blur
|
|
557
|
+
* (`onWindowBlur`), so a key/button physically held when input is
|
|
558
|
+
* suspended doesn't linger. Level values (gamepad axes, touch sticks,
|
|
559
|
+
* injected test axes) are deliberately NOT flushed here — they are gated
|
|
560
|
+
* at READ time instead (see `inputActive` checks in `poll()`/the
|
|
561
|
+
* `collect*Contributions` methods), matching the pre-existing
|
|
562
|
+
* gamepad-axis convention. */
|
|
563
|
+
private flushRealHeldState(): void {
|
|
406
564
|
this.keysDown.clear();
|
|
407
565
|
this.keysJustDown.clear();
|
|
408
566
|
this.keysJustUp.clear();
|
|
@@ -416,6 +574,45 @@ export class InputManager {
|
|
|
416
574
|
this.touchButtonsJustUp.clear();
|
|
417
575
|
}
|
|
418
576
|
|
|
577
|
+
/** The full flush `setEnabled(false)` performs: real-device state AND
|
|
578
|
+
* virtual-action state. Task 1.4's rule ("a bot's held action must not
|
|
579
|
+
* survive input being suspended") now applies only to the editor's
|
|
580
|
+
* explicit gate — #144 moved real window blur onto `flushRealHeldState`
|
|
581
|
+
* alone, so machine input survives the human's focus leaving the tab.
|
|
582
|
+
* No release edge is manufactured here (`manufactureReleaseEdge: false`,
|
|
583
|
+
* the default) — this is a SUSPEND, not a deliberate release, and mirrors
|
|
584
|
+
* `flushRealHeldState`'s own silence (a key physically held at
|
|
585
|
+
* suspend/blur time is wiped with no `keysJustUp`, "flush held state,
|
|
586
|
+
* don't corrupt history", see `poll()`'s doc comment). */
|
|
587
|
+
private flushHeldState(): void {
|
|
588
|
+
this.flushRealHeldState();
|
|
589
|
+
this.clearVirtualActionState();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Shared by the public `clearVirtualActions()` and `flushHeldState()`
|
|
593
|
+
* above — clears every virtual-action store (held digitals, queued/active
|
|
594
|
+
* taps, scalar/vector2 values, the release-edge set itself).
|
|
595
|
+
*
|
|
596
|
+
* `manufactureReleaseEdge` (default `false`): when `true` — only
|
|
597
|
+
* `clearVirtualActions()`'s own deliberate "let go" passes this — every
|
|
598
|
+
* currently-held digital in `virtualDigitalHeld` first gets an
|
|
599
|
+
* `isJustReleased` edge written to `virtualDigitalJustReleased`, mirroring
|
|
600
|
+
* a real key-up (`onKeyUp` → `keysJustUp`). `flushHeldState()` (the
|
|
601
|
+
* editor-gate suspend path) deliberately passes `false` — see its own doc
|
|
602
|
+
* comment for why a suspend must stay silent. */
|
|
603
|
+
private clearVirtualActionState(options?: { manufactureReleaseEdge: boolean }): void {
|
|
604
|
+
if (options?.manufactureReleaseEdge) {
|
|
605
|
+
for (const action of this.virtualDigitalHeld) {
|
|
606
|
+
this.virtualDigitalJustReleased.add(action);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
this.virtualDigitalHeld.clear();
|
|
610
|
+
this.virtualDigitalTapQueued.clear();
|
|
611
|
+
this.virtualDigitalTapActive.clear();
|
|
612
|
+
this.virtualScalarValues.clear();
|
|
613
|
+
this.virtualVector2Values.clear();
|
|
614
|
+
}
|
|
615
|
+
|
|
419
616
|
constructor() {
|
|
420
617
|
window.addEventListener('keydown', this.onKeyDown);
|
|
421
618
|
window.addEventListener('keyup', this.onKeyUp);
|
|
@@ -529,10 +726,56 @@ export class InputManager {
|
|
|
529
726
|
return [...this.actions.keys()];
|
|
530
727
|
}
|
|
531
728
|
|
|
532
|
-
/**
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
729
|
+
/**
|
|
730
|
+
* Poll gamepads and virtual look stick (call at start of frame).
|
|
731
|
+
*
|
|
732
|
+
* `tick` (D15/T-D15.3/.5) — the GAME tick this call services. Pass the
|
|
733
|
+
* live, shared `Game` tick counter here (`vgai-scene-game-adapter.ts` does,
|
|
734
|
+
* via `DebugRegistry.getGameTick()`) so `scheduleActionAtTick`'s numbering
|
|
735
|
+
* never drifts from the actual game tick — the bug an earlier revision of
|
|
736
|
+
* this feature had: an InputManager-LOCAL counter, incremented once per
|
|
737
|
+
* `poll()` call, undercounts for a paused/frozen world (its `poll()` isn't
|
|
738
|
+
* called every game tick while frozen), so a schedule set against "tick
|
|
739
|
+
* 500" could fire at the wrong wall-tick once the world resumed. Omit it
|
|
740
|
+
* (every existing bare/headless caller does) to fall back to the
|
|
741
|
+
* self-incrementing `currentTick` counter — unchanged pre-D15 behavior.
|
|
742
|
+
*/
|
|
743
|
+
poll(tick?: number) {
|
|
744
|
+
const servicedTick = tick !== undefined ? tick : this.currentTick;
|
|
745
|
+
// D15/T-D15.5 — a GAP: one or more ticks strictly between the last tick
|
|
746
|
+
// this InputManager serviced and this one never ran its input phase at
|
|
747
|
+
// all (this world was paused/frozen and skipped straight from tick A to
|
|
748
|
+
// a later tick B > A+1 once stepped/resumed again — the frozen-world
|
|
749
|
+
// scenario objection above). Any entries scheduled for a tick inside
|
|
750
|
+
// that gap can never be honored — drop them (each emitting one
|
|
751
|
+
// `'input.schedule.dropped'` event) rather than leaving them to rot in
|
|
752
|
+
// `scheduledActions` forever. Guarded on `size > 0` so the overwhelmingly
|
|
753
|
+
// common case (nothing ever scheduled) costs nothing, even across a
|
|
754
|
+
// very large gap.
|
|
755
|
+
if (this.scheduledActions.size > 0) {
|
|
756
|
+
// Iterate the (tiny) schedule map, never the gap's tick RANGE — a
|
|
757
|
+
// world resuming after a very long freeze (or one mounting late into
|
|
758
|
+
// a game already millions of ticks in) must not pay O(gap) here.
|
|
759
|
+
// Sorted so drop events still arrive in tick order.
|
|
760
|
+
const gapped = [...this.scheduledActions.keys()]
|
|
761
|
+
.filter((t) => t > this.lastServicedTick && t < servicedTick)
|
|
762
|
+
.sort((a, b) => a - b);
|
|
763
|
+
for (const t of gapped) {
|
|
764
|
+
const skipped = this.scheduledActions.get(t)!;
|
|
765
|
+
this.scheduledActions.delete(t);
|
|
766
|
+
for (const entry of skipped) {
|
|
767
|
+
this.debugEmit?.('input.schedule.dropped', { tick: t, action: entry.action });
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
this.lastServicedTick = servicedTick;
|
|
772
|
+
this.currentTick = servicedTick + 1;
|
|
773
|
+
if (!this.enabled) {
|
|
774
|
+
// Input suspended by the editor's explicit gate (play stopped /
|
|
775
|
+
// game-tab-inactive). #144: this branch no longer covers real window
|
|
776
|
+
// blur — an enabled-but-blurred tick falls through so MACHINE input
|
|
777
|
+
// (virtual actions, scheduled actuations) keeps flowing while the
|
|
778
|
+
// human's focus is elsewhere; real devices are gated further down.
|
|
536
779
|
// Unlike keyboard/mouse, gamepad state isn't DOM-event-driven —
|
|
537
780
|
// `isPressed`/`isJustPressed`/`isJustReleased` read `this.gamepads`
|
|
538
781
|
// (and the edge-tracked sets) directly, not a live poll of `navigator`.
|
|
@@ -544,9 +787,49 @@ export class InputManager {
|
|
|
544
787
|
// resume diffing against genuinely-last-observed hardware state once
|
|
545
788
|
// re-enabled, so there's no phantom edge on re-enable (mirrors the
|
|
546
789
|
// "flush held state, don't corrupt history" intent of setEnabled below).
|
|
790
|
+
// D15/T-D15.5: a tick serviced while gated drops its OWN scheduled
|
|
791
|
+
// entries too (never applied, never retried later) — same honesty line
|
|
792
|
+
// `setVirtualAction`'s own gate already draws — each emitting the same
|
|
793
|
+
// `'input.schedule.dropped'` event the gap-handling above does.
|
|
794
|
+
const gatedDrop = this.scheduledActions.get(servicedTick);
|
|
795
|
+
if (gatedDrop) {
|
|
796
|
+
this.scheduledActions.delete(servicedTick);
|
|
797
|
+
for (const entry of gatedDrop) {
|
|
798
|
+
this.debugEmit?.('input.schedule.dropped', { tick: servicedTick, action: entry.action });
|
|
799
|
+
}
|
|
800
|
+
}
|
|
547
801
|
this.gamepads = [];
|
|
548
802
|
return;
|
|
549
803
|
}
|
|
804
|
+
// Task 1.4 — promote queued tapVirtualAction() calls to this frame's
|
|
805
|
+
// "active" just-pressed set (hollowstone's queued/beginFrame contract,
|
|
806
|
+
// ported onto this engine's existing fixed-step poll()/endFrame()
|
|
807
|
+
// bracket instead of a bespoke beginFrame() — poll() already runs at the
|
|
808
|
+
// start of the fixed step). Cleared in endFrame() below.
|
|
809
|
+
this.virtualDigitalTapActive = this.virtualDigitalTapQueued;
|
|
810
|
+
this.virtualDigitalTapQueued = new Set();
|
|
811
|
+
// D15/T-D15.5 — apply any actuation scheduled for exactly this tick
|
|
812
|
+
// (spec §2.c: "applied at the START of the target tick's input phase",
|
|
813
|
+
// before any gameplay phase reads input — this still runs before every
|
|
814
|
+
// OTHER phase this tick, `input` being the first system phase). Placed
|
|
815
|
+
// AFTER the tap-queue promotion immediately above (not before) so a
|
|
816
|
+
// scheduled digital `true` can add itself to `virtualDigitalTapActive`
|
|
817
|
+
// (see `applyScheduledActionsForTick`) without that promotion's
|
|
818
|
+
// reassignment wiping it out.
|
|
819
|
+
this.applyScheduledActionsForTick(servicedTick);
|
|
820
|
+
|
|
821
|
+
if (!this.focused) {
|
|
822
|
+
// #144 — enabled but blurred: everything ABOVE this line is machine
|
|
823
|
+
// input (tap promotion, scheduled actuations) and stays live; real
|
|
824
|
+
// devices below gate exactly as the old combined branch did (no
|
|
825
|
+
// gamepads presented, edge sets frozen at their last real values).
|
|
826
|
+
// The post-gate trace still records — "what the sim actually
|
|
827
|
+
// consumed" includes machine input delivered while blurred.
|
|
828
|
+
this.gamepads = [];
|
|
829
|
+
this.recordPostGateTick(servicedTick);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
|
|
550
833
|
this.gamepads = navigator.getGamepads ? [...navigator.getGamepads()] : [];
|
|
551
834
|
|
|
552
835
|
// Gamepad button edge detection: diff this frame's pressed set against the
|
|
@@ -593,6 +876,102 @@ export class InputManager {
|
|
|
593
876
|
this.mouseDeltaX += this.lookStickX * scale;
|
|
594
877
|
this.mouseDeltaY += this.lookStickY * scale;
|
|
595
878
|
}
|
|
879
|
+
// D15/T-D15.5 — tap the POST-GATE resolved values for this tick LAST
|
|
880
|
+
// (every binding/virtual/scheduled contribution above has already been
|
|
881
|
+
// folded in): "what the sim actually consumed", the honesty line the
|
|
882
|
+
// design doc draws for record-now/replay-in-SP5 (§2.c).
|
|
883
|
+
this.recordPostGateTick(servicedTick);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* D15/T-D15.5 — apply every actuation `scheduleActionAtTick` queued for
|
|
888
|
+
* exactly `tick`, then forget them (a tick is serviced at most once).
|
|
889
|
+
*
|
|
890
|
+
* Digital `true` gets SPECIAL handling (the objection-1 fix): it promotes
|
|
891
|
+
* the action into this tick's `virtualDigitalTapActive` set directly — the
|
|
892
|
+
* same one-tick "active" pulse `tapVirtualAction` uses — so `isJustPressed`
|
|
893
|
+
* reads true for EXACTLY this tick (false on the next, per `endFrame()`'s
|
|
894
|
+
* clear) while `isPressed` keeps reading true afterward (still held) until
|
|
895
|
+
* a later scheduled/explicit `false` releases it. This direct write is
|
|
896
|
+
* safe here (unlike `applyVirtualActionValue`'s equivalent direct-path
|
|
897
|
+
* case — see its doc comment) specifically because this method only ever
|
|
898
|
+
* runs from inside `poll()`, AFTER `poll()`'s own tap-queue-promotion
|
|
899
|
+
* reassignment of `virtualDigitalTapActive` has already happened for this
|
|
900
|
+
* tick (see `poll()`'s own comment on why it's placed after) — so there's
|
|
901
|
+
* no risk of this write being silently wiped by that reassignment.
|
|
902
|
+
*
|
|
903
|
+
* pulse-runner friction #4: a prior revision of this comment
|
|
904
|
+
* used to say `setVirtualAction`'s plain write "never produces a
|
|
905
|
+
* just-pressed edge at all" and that the edge fix landed ONLY here, for
|
|
906
|
+
* this scheduled path — true at the time, and exactly the gap a blind
|
|
907
|
+
* dogfood run (pulse-runner) hit through the direct `setVirtualAction`/
|
|
908
|
+
* `holdFor` path. `applyVirtualActionValue` now manufactures the same kind
|
|
909
|
+
* of edge for THAT path too (queued instead of written directly, for the
|
|
910
|
+
* timing reason above) — this is no longer the only path that does.
|
|
911
|
+
*
|
|
912
|
+
* A digital `false` here simply releases the hold — no edge is
|
|
913
|
+
* manufactured for it at a SCHEDULED tick (no caller needs a virtual
|
|
914
|
+
* `isJustReleased` fired at an exact future tick today); the direct
|
|
915
|
+
* `setVirtualAction(action, false)`/`clearVirtualActions()` paths DO
|
|
916
|
+
* manufacture one now (see `applyVirtualActionValue`/
|
|
917
|
+
* `clearVirtualActionState`). Scalar/vector2 values apply directly,
|
|
918
|
+
* unchanged.
|
|
919
|
+
*/
|
|
920
|
+
private applyScheduledActionsForTick(tick: number): void {
|
|
921
|
+
const pending = this.scheduledActions.get(tick);
|
|
922
|
+
if (!pending) return;
|
|
923
|
+
this.scheduledActions.delete(tick);
|
|
924
|
+
for (const entry of pending) {
|
|
925
|
+
if (typeof entry.value === 'boolean') {
|
|
926
|
+
if (entry.value) {
|
|
927
|
+
this.virtualDigitalHeld.add(entry.action);
|
|
928
|
+
this.virtualDigitalTapActive.add(entry.action);
|
|
929
|
+
} else {
|
|
930
|
+
this.virtualDigitalHeld.delete(entry.action);
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
this.applyVirtualActionValue(entry.action, entry.value);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** D15/T-D15.5 — append this tick's action-value DELTA to the recording
|
|
939
|
+
* ring, iff a recording is active. Reads every declared digital/scalar/
|
|
940
|
+
* vector2 action through the exact same typed getters gameplay code would
|
|
941
|
+
* (`readAction`), so the trace reflects what a component reading these
|
|
942
|
+
* actions THIS tick actually sees post-gate — never a raw/ungated value.
|
|
943
|
+
* `pointerDelta`/`pointerPosition` actions are skipped (a different,
|
|
944
|
+
* frame-accumulated shape, and not part of the virtual-action/schedule
|
|
945
|
+
* surface this trace exists to prove).
|
|
946
|
+
*
|
|
947
|
+
* Two replay-affecting caveats a future SP5 consumer must know:
|
|
948
|
+
* 1. A gated stretch never calls this at all (`poll()`'s gated branch
|
|
949
|
+
* returns before reaching it) — no entries are recorded for ticks
|
|
950
|
+
* input was suspended. Any change only OBSERVABLE once re-enabled (a
|
|
951
|
+
* delta against `lastTraceSnapshot`) lands as a delta AT the re-enable
|
|
952
|
+
* tick, not at whatever tick it actually happened.
|
|
953
|
+
* 2. This ring is capped (`INPUT_TRACE_CAP`) and evicts via `shift()` once
|
|
954
|
+
* it fills — dropping the OLDEST entry. Because entries are DELTAS,
|
|
955
|
+
* evicting the earliest entries drops the base state later deltas were
|
|
956
|
+
* relative to — this ring is not safe to replay from its start once it
|
|
957
|
+
* has ever been capped. */
|
|
958
|
+
private recordPostGateTick(tick: number): void {
|
|
959
|
+
if (!this.inputRecording) return;
|
|
960
|
+
const deltas: Record<string, boolean | number | { x: number; y: number }> = {};
|
|
961
|
+
let changed = false;
|
|
962
|
+
for (const name of this.actionNames()) {
|
|
963
|
+
const type = this.getActionValueType(name);
|
|
964
|
+
if (type !== 'digital' && type !== 'scalar' && type !== 'vector2') continue;
|
|
965
|
+
const value = this.readAction(name, type);
|
|
966
|
+
const prev = this.lastTraceSnapshot.get(name);
|
|
967
|
+
if (actionValuesEqual(prev, value)) continue;
|
|
968
|
+
deltas[name] = value;
|
|
969
|
+
this.lastTraceSnapshot.set(name, value);
|
|
970
|
+
changed = true;
|
|
971
|
+
}
|
|
972
|
+
if (!changed) return;
|
|
973
|
+
this.inputTrace.push({ tick, actions: deltas });
|
|
974
|
+
if (this.inputTrace.length > INPUT_TRACE_CAP) this.inputTrace.shift();
|
|
596
975
|
}
|
|
597
976
|
|
|
598
977
|
/** Has a `gamepad_axis` binding's raw value crossed its direction+deadzone
|
|
@@ -608,6 +987,11 @@ export class InputManager {
|
|
|
608
987
|
/** Is an action currently held down? */
|
|
609
988
|
isPressed(actionName: string): boolean {
|
|
610
989
|
this.assertValueType(actionName, 'digital', 'isPressed');
|
|
990
|
+
// Task 1.4 — a held virtual action, or a tap promoted to this frame's
|
|
991
|
+
// "active" set, ORs into isPressed alongside binding contributions.
|
|
992
|
+
if (this.virtualDigitalHeld.has(actionName) || this.virtualDigitalTapActive.has(actionName)) {
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
611
995
|
const bindings = this.actions.get(actionName);
|
|
612
996
|
if (!bindings) return false;
|
|
613
997
|
|
|
@@ -644,6 +1028,10 @@ export class InputManager {
|
|
|
644
1028
|
/** Was an action pressed this frame (not held from previous)? */
|
|
645
1029
|
isJustPressed(actionName: string): boolean {
|
|
646
1030
|
this.assertValueType(actionName, 'digital', 'isJustPressed');
|
|
1031
|
+
// Task 1.4 — a tapVirtualAction() queued on a prior frame reads
|
|
1032
|
+
// just-pressed for exactly the one poll()/endFrame() bracket it was
|
|
1033
|
+
// promoted into (see poll()/endFrame() below).
|
|
1034
|
+
if (this.virtualDigitalTapActive.has(actionName)) return true;
|
|
647
1035
|
const bindings = this.actions.get(actionName);
|
|
648
1036
|
if (!bindings) return false;
|
|
649
1037
|
|
|
@@ -688,6 +1076,11 @@ export class InputManager {
|
|
|
688
1076
|
/** Was an action released this frame (held last frame, up now)? */
|
|
689
1077
|
isJustReleased(actionName: string): boolean {
|
|
690
1078
|
this.assertValueType(actionName, 'digital', 'isJustReleased');
|
|
1079
|
+
// pulse-runner friction #4 — mirrors isJustPressed's virtual check above:
|
|
1080
|
+
// a direct setVirtualAction(action, false) or clearVirtualActions() on a
|
|
1081
|
+
// held action manufactures exactly one just-released edge here, matching
|
|
1082
|
+
// a real key-up's keysJustUp.
|
|
1083
|
+
if (this.virtualDigitalJustReleased.has(actionName)) return true;
|
|
691
1084
|
const bindings = this.actions.get(actionName);
|
|
692
1085
|
if (!bindings) return false;
|
|
693
1086
|
|
|
@@ -732,6 +1125,14 @@ export class InputManager {
|
|
|
732
1125
|
* iteration order. */
|
|
733
1126
|
getDigitalSource(actionName: string): ActionValueSource | null {
|
|
734
1127
|
this.assertValueType(actionName, 'digital', 'getDigitalSource');
|
|
1128
|
+
// #144 — mirror isPressed's own pre-gate virtual check: a machine-held
|
|
1129
|
+
// action reports its source even while the window is blurred.
|
|
1130
|
+
if (
|
|
1131
|
+
this.machineInputActive &&
|
|
1132
|
+
(this.virtualDigitalHeld.has(actionName) || this.virtualDigitalTapActive.has(actionName))
|
|
1133
|
+
) {
|
|
1134
|
+
return { bindingType: 'virtual' };
|
|
1135
|
+
}
|
|
735
1136
|
if (!this.inputActive) return null;
|
|
736
1137
|
const bindings = this.actions.get(actionName);
|
|
737
1138
|
if (!bindings) return null;
|
|
@@ -823,10 +1224,18 @@ export class InputManager {
|
|
|
823
1224
|
private collectScalarContributions(
|
|
824
1225
|
actionName: string,
|
|
825
1226
|
): { value: number; source: ActionValueSource }[] {
|
|
826
|
-
if (!this.inputActive) return [];
|
|
827
|
-
const bindings = this.actions.get(actionName);
|
|
828
|
-
if (!bindings) return [];
|
|
829
1227
|
const out: { value: number; source: ActionValueSource }[] = [];
|
|
1228
|
+
// Task 1.4 — a virtual scalar value joins the same max-magnitude combine
|
|
1229
|
+
// real bindings do (computeScalar below). #144: it passes the MACHINE
|
|
1230
|
+
// gate (enabled only), so a bot's held value reads through while the
|
|
1231
|
+
// window is blurred; real bindings below keep the full focus gate.
|
|
1232
|
+
const virtual = this.virtualScalarValues.get(actionName);
|
|
1233
|
+
if (virtual !== undefined && this.machineInputActive) {
|
|
1234
|
+
out.push({ value: virtual, source: { bindingType: 'virtual' } });
|
|
1235
|
+
}
|
|
1236
|
+
if (!this.inputActive) return out;
|
|
1237
|
+
const bindings = this.actions.get(actionName);
|
|
1238
|
+
if (!bindings) return out;
|
|
830
1239
|
for (const binding of bindings) {
|
|
831
1240
|
if (binding.type === 'gamepad_axis') {
|
|
832
1241
|
const dz = binding.deadzone ?? 0.15;
|
|
@@ -904,10 +1313,17 @@ export class InputManager {
|
|
|
904
1313
|
private collectVector2Contributions(
|
|
905
1314
|
actionName: string,
|
|
906
1315
|
): { value: Vector2; source: ActionValueSource }[] {
|
|
907
|
-
if (!this.inputActive) return [];
|
|
908
|
-
const bindings = this.actions.get(actionName);
|
|
909
|
-
if (!bindings) return [];
|
|
910
1316
|
const out: { value: Vector2; source: ActionValueSource }[] = [];
|
|
1317
|
+
// Task 1.4 — a virtual vector2 value joins the same sum-then-clamp
|
|
1318
|
+
// combine real bindings do (computeVector2 below). #144: machine gate
|
|
1319
|
+
// only, same as the scalar collector above.
|
|
1320
|
+
const virtual = this.virtualVector2Values.get(actionName);
|
|
1321
|
+
if (virtual !== undefined && this.machineInputActive) {
|
|
1322
|
+
out.push({ value: virtual, source: { bindingType: 'virtual' } });
|
|
1323
|
+
}
|
|
1324
|
+
if (!this.inputActive) return out;
|
|
1325
|
+
const bindings = this.actions.get(actionName);
|
|
1326
|
+
if (!bindings) return out;
|
|
911
1327
|
for (const binding of bindings) {
|
|
912
1328
|
switch (binding.type) {
|
|
913
1329
|
case 'test_vector2': {
|
|
@@ -1511,6 +1927,14 @@ export class InputManager {
|
|
|
1511
1927
|
// F2 — touch-button edges, same shape as the mouse-button edges above.
|
|
1512
1928
|
this.touchButtonsJustDown.clear();
|
|
1513
1929
|
this.touchButtonsJustUp.clear();
|
|
1930
|
+
// Task 1.4 — a promoted tap is just-pressed for exactly one poll()/
|
|
1931
|
+
// endFrame() bracket.
|
|
1932
|
+
this.virtualDigitalTapActive.clear();
|
|
1933
|
+
// Mirrors the line above for the release edge a direct
|
|
1934
|
+
// `setVirtualAction(action, false)`/`clearVirtualActions()` manufactures
|
|
1935
|
+
// (see `virtualDigitalJustReleased`'s doc comment) — just-released for
|
|
1936
|
+
// exactly one poll()/endFrame() bracket too.
|
|
1937
|
+
this.virtualDigitalJustReleased.clear();
|
|
1514
1938
|
}
|
|
1515
1939
|
|
|
1516
1940
|
/** Inject synthetic key state from touch controls */
|
|
@@ -1579,6 +2003,261 @@ export class InputManager {
|
|
|
1579
2003
|
this.touchStickValues.delete(sourceId);
|
|
1580
2004
|
}
|
|
1581
2005
|
|
|
2006
|
+
// ==========================================================================
|
|
2007
|
+
// Task 1.4 (ACCEPTANCE-DRIVER-BUILD-PLAN.md / SYNTHETIC-PLAYER-SPEC.md §3.2)
|
|
2008
|
+
// — action-level virtual input for a synthetic player. Promoted from
|
|
2009
|
+
// hollowstone's proven `VirtualInput` (`bot/virtual-input.ts`): a bot drives
|
|
2010
|
+
// NAMED ACTIONS (not raw devices), and its contributions join action
|
|
2011
|
+
// resolution at exactly the same points binding contributions do (see
|
|
2012
|
+
// isPressed/isJustPressed and collectScalarContributions/
|
|
2013
|
+
// collectVector2Contributions above). #144 split the gates: virtual input
|
|
2014
|
+
// passes `machineInputActive` (the editor's `enabled` flag only), while
|
|
2015
|
+
// human input keeps the full `inputActive` (enabled AND focused) gate — a
|
|
2016
|
+
// synthetic player must keep playing the shared editor tab while the
|
|
2017
|
+
// human's focus is elsewhere. Unlike every typed getter above (which
|
|
2018
|
+
// treats an unknown action as inert), these THROW on an unknown name — a
|
|
2019
|
+
// synthetic-player caller needs a loud signal that the action it's driving
|
|
2020
|
+
// doesn't exist, not a silently-ignored actuation.
|
|
2021
|
+
// ==========================================================================
|
|
2022
|
+
|
|
2023
|
+
/** Existence check shared by `setVirtualAction`/`tapVirtualAction` — throws
|
|
2024
|
+
* `InputActionError` (`code: 'INPUT_ACTION_NOT_FOUND'`, `data.registered`
|
|
2025
|
+
* = every declared action name) for a name that was never
|
|
2026
|
+
* `registerAction`/`loadMap`-ed. */
|
|
2027
|
+
private assertActionExists(actionName: string, method: string): void {
|
|
2028
|
+
if (!this.actionValueTypes.has(actionName)) {
|
|
2029
|
+
throw new InputActionError(actionName, method, this.actionNames());
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/** Which declared `ActionValueType` a `setVirtualAction` value shape
|
|
2034
|
+
* corresponds to — the type-safety check mirrors every typed getter's
|
|
2035
|
+
* `assertValueType` (F1). `pointerDelta`/`pointerPosition` actions aren't
|
|
2036
|
+
* drivable through `setVirtualAction` (no boolean/number/Vector2 call
|
|
2037
|
+
* shape maps to them) — use `injectPointerDelta`/`injectPointerPosition`. */
|
|
2038
|
+
private static virtualValueCategory(
|
|
2039
|
+
value: boolean | number | { x: number; y: number },
|
|
2040
|
+
): ActionValueType {
|
|
2041
|
+
if (typeof value === 'boolean') return 'digital';
|
|
2042
|
+
if (typeof value === 'number') return 'scalar';
|
|
2043
|
+
return 'vector2';
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
/**
|
|
2047
|
+
* Set a virtual value for `action`, OR'd/summed into the same action reads
|
|
2048
|
+
* humans drive (see the section doc comment above). A `boolean` holds a
|
|
2049
|
+
* digital action pressed until set `false`/cleared; a `number` or `{x,y}`
|
|
2050
|
+
* is a level value combined via the action's own scalar max-magnitude /
|
|
2051
|
+
* vector2 sum-then-clamp rule.
|
|
2052
|
+
*
|
|
2053
|
+
* A digital `false`→`true` transition manufactures a genuine `isJustPressed`
|
|
2054
|
+
* edge, exactly one real key-down does (pulse-runner friction #4 —
|
|
2055
|
+
* previously this write-half only ever held the action pressed with NO
|
|
2056
|
+
* edge at all; every edge-triggered ability driven through this direct
|
|
2057
|
+
* path was unprovable). Re-setting `true` while already held (auto-repeat)
|
|
2058
|
+
* does NOT re-fire it — see {@link applyVirtualActionValue}. The mirror
|
|
2059
|
+
* `true`→`false` transition manufactures an `isJustReleased` edge the same
|
|
2060
|
+
* way a real key-up does.
|
|
2061
|
+
*
|
|
2062
|
+
* Throws `InputActionError` for an unregistered `action`, and the same
|
|
2063
|
+
* valueType-mismatch error every typed getter throws (`assertValueType`)
|
|
2064
|
+
* when `value`'s shape doesn't match the action's declared `valueType`.
|
|
2065
|
+
*
|
|
2066
|
+
* Gated (`setEnabled(false)` — play stopped or editor-gated; #144: window
|
|
2067
|
+
* focus no longer gates machine input): writes NOTHING — matching
|
|
2068
|
+
* `setTouchButton`'s write-gate precedent above — and returns
|
|
2069
|
+
* `{delivered: false, reason}` rather than a silent no-op, so a probe/bot
|
|
2070
|
+
* never mistakes a swallowed actuation for a delivered one.
|
|
2071
|
+
*/
|
|
2072
|
+
setVirtualAction(
|
|
2073
|
+
action: string,
|
|
2074
|
+
value: boolean | number | { x: number; y: number },
|
|
2075
|
+
): { delivered: boolean; reason?: string } {
|
|
2076
|
+
this.assertActionExists(action, 'setVirtualAction');
|
|
2077
|
+
const expected = InputManager.virtualValueCategory(value);
|
|
2078
|
+
this.assertValueType(action, expected, 'setVirtualAction');
|
|
2079
|
+
if (!this.machineInputActive) {
|
|
2080
|
+
return { delivered: false, reason: 'input-gated (input disabled by the editor)' };
|
|
2081
|
+
}
|
|
2082
|
+
this.applyVirtualActionValue(action, value);
|
|
2083
|
+
return { delivered: true };
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/**
|
|
2087
|
+
* The write half of {@link setVirtualAction} — extracted so D15/T-D15.5's
|
|
2088
|
+
* `scheduleActionAtTick` can apply a non-boolean (scalar/vector2) queued
|
|
2089
|
+
* actuation through the exact same code path (no gate check here: the two
|
|
2090
|
+
* callers gate differently — `setVirtualAction` above checks
|
|
2091
|
+
* `inputActive` before calling this; `applyScheduledActionsForTick` is
|
|
2092
|
+
* only ever reached from inside `poll()` AFTER its own gate check has
|
|
2093
|
+
* already returned early on a gated tick). `applyScheduledActionsForTick`
|
|
2094
|
+
* still handles digital `true` itself rather than calling here (its
|
|
2095
|
+
* one-tick edge must land EXACTLY at the scheduled tick — see its own doc
|
|
2096
|
+
* comment) — this method's digital branch is `setVirtualAction`'s direct
|
|
2097
|
+
* path, called at an arbitrary point relative to the poll()/endFrame()
|
|
2098
|
+
* bracket.
|
|
2099
|
+
*
|
|
2100
|
+
* pulse-runner friction #4 — the digital branch used to be a bare held-flag
|
|
2101
|
+
* write with no manufactured edge at all (the comment that used to sit
|
|
2102
|
+
* here read: "A digital value is handled by `applyScheduledActionsForTick`
|
|
2103
|
+
* itself (it needs the extra just-pressed edge a plain held-value write
|
|
2104
|
+
* doesn't produce)" — i.e. the edge fix shipped ONLY for the scheduled
|
|
2105
|
+
* path, never for this one, which is exactly the gap a blind dogfood run
|
|
2106
|
+
* hit: `setVirtualAction('jump', true)` held the action down but
|
|
2107
|
+
* `isJustPressed('jump')` never fired). Fixed here by reusing
|
|
2108
|
+
* `tapVirtualAction`'s exact one-tick-pulse queue mechanism for this
|
|
2109
|
+
* DIRECT case, specifically because this method's caller can land at any
|
|
2110
|
+
* point relative to `poll()`: writing `virtualDigitalTapActive` directly
|
|
2111
|
+
* here (as the scheduled path safely does, from inside `poll()` itself)
|
|
2112
|
+
* would risk being silently wiped by `poll()`'s own unconditional
|
|
2113
|
+
* `virtualDigitalTapActive = virtualDigitalTapQueued` reassignment,
|
|
2114
|
+
* depending on timing. Queueing side-steps that hazard by construction —
|
|
2115
|
+
* the same reason `tapVirtualAction` queues instead of writing directly. A
|
|
2116
|
+
* redundant `true` while already held (`virtualDigitalHeld` already has
|
|
2117
|
+
* `action`) does not queue again, matching a real key's auto-repeat never
|
|
2118
|
+
* re-firing `isJustPressed`. The `false` branch mirrors real `onKeyUp`
|
|
2119
|
+
* instead (immediate write, no queue needed — nothing in `poll()`
|
|
2120
|
+
* reassigns `virtualDigitalJustReleased`), and only fires when the action
|
|
2121
|
+
* was actually held (no released→released re-fire).
|
|
2122
|
+
*/
|
|
2123
|
+
private applyVirtualActionValue(
|
|
2124
|
+
action: string,
|
|
2125
|
+
value: boolean | number | { x: number; y: number },
|
|
2126
|
+
): void {
|
|
2127
|
+
switch (InputManager.virtualValueCategory(value)) {
|
|
2128
|
+
case 'digital':
|
|
2129
|
+
if (value as boolean) {
|
|
2130
|
+
if (!this.virtualDigitalHeld.has(action)) this.virtualDigitalTapQueued.add(action);
|
|
2131
|
+
this.virtualDigitalHeld.add(action);
|
|
2132
|
+
} else {
|
|
2133
|
+
if (this.virtualDigitalHeld.has(action)) this.virtualDigitalJustReleased.add(action);
|
|
2134
|
+
this.virtualDigitalHeld.delete(action);
|
|
2135
|
+
}
|
|
2136
|
+
break;
|
|
2137
|
+
case 'scalar':
|
|
2138
|
+
this.virtualScalarValues.set(action, value as number);
|
|
2139
|
+
break;
|
|
2140
|
+
default:
|
|
2141
|
+
this.virtualVector2Values.set(action, value as { x: number; y: number });
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/**
|
|
2147
|
+
* One-frame press of a `'digital'` action (Task 1.4): queues `action` so it
|
|
2148
|
+
* reads `isJustPressed`/`isPressed` true for EXACTLY the next `poll()`'s
|
|
2149
|
+
* frame — promoted to the "active" set at the top of `poll()`, cleared in
|
|
2150
|
+
* `endFrame()` alongside every other just-pressed edge set (see both
|
|
2151
|
+
* above). Hollowstone's `queued`/`beginFrame` tap contract, ported onto
|
|
2152
|
+
* this engine's existing fixed-step `poll()`/`endFrame()` bracket instead
|
|
2153
|
+
* of a bespoke `beginFrame()` hook.
|
|
2154
|
+
*
|
|
2155
|
+
* Throws the same errors as {@link setVirtualAction} (unknown action;
|
|
2156
|
+
* non-digital valueType). A gated tap does NOT queue — it must not fire on
|
|
2157
|
+
* a later frame once input becomes active again — and returns
|
|
2158
|
+
* `{delivered: false, reason}`.
|
|
2159
|
+
*/
|
|
2160
|
+
tapVirtualAction(action: string): { delivered: boolean; reason?: string } {
|
|
2161
|
+
this.assertActionExists(action, 'tapVirtualAction');
|
|
2162
|
+
this.assertValueType(action, 'digital', 'tapVirtualAction');
|
|
2163
|
+
if (!this.machineInputActive) {
|
|
2164
|
+
return { delivered: false, reason: 'input-gated (input disabled by the editor)' };
|
|
2165
|
+
}
|
|
2166
|
+
this.virtualDigitalTapQueued.add(action);
|
|
2167
|
+
return { delivered: true };
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/** Clear all virtual-action state — held digitals, queued/active taps, and
|
|
2171
|
+
* scalar/vector2 values. `flushHeldState()` (the same flush
|
|
2172
|
+
* `setEnabled(false)`/real blur apply to held keys/mouse/touch buttons)
|
|
2173
|
+
* shares the underlying wipe, so a bot's held action never survives input
|
|
2174
|
+
* being suspended — but only THIS public entry point (a deliberate "let
|
|
2175
|
+
* go", the mirror of `setVirtualAction(action, false)` below) manufactures
|
|
2176
|
+
* an `isJustReleased` edge for whatever was held; the suspend path stays
|
|
2177
|
+
* silent (see `flushHeldState`'s own doc comment). */
|
|
2178
|
+
clearVirtualActions(): void {
|
|
2179
|
+
this.clearVirtualActionState({ manufactureReleaseEdge: true });
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* D15/T-D15.5 (`docs/D15-DETERMINISM-DESIGN.md` §2.c) — schedule a virtual
|
|
2184
|
+
* actuation for exactly `tick`: applied at the START of that tick's
|
|
2185
|
+
* `poll()` (composing with `runTicks` — a schedule for tick 500 fires
|
|
2186
|
+
* exactly once the sim has been driven through tick 500, regardless of
|
|
2187
|
+
* burst size). A digital `true` produces a genuine `isJustPressed` edge
|
|
2188
|
+
* exactly at the target tick (not merely a held value — see
|
|
2189
|
+
* `applyScheduledActionsForTick`).
|
|
2190
|
+
*
|
|
2191
|
+
* Throws `InputActionError` for an unregistered `action` and the same
|
|
2192
|
+
* valueType-mismatch error every typed getter throws (`assertValueType`) —
|
|
2193
|
+
* both checked EAGERLY, at schedule time, not deferred to the target tick.
|
|
2194
|
+
* Throws `InputTickError` (`code: 'TICK_ALREADY_PASSED'`) when `tick` is
|
|
2195
|
+
* strictly less than the tick `poll()` will service NEXT — scheduling only
|
|
2196
|
+
* ever reaches the current or a future tick. A tick whose input phase
|
|
2197
|
+
* never runs (this world paused/frozen at that point, or a multi-tick gap
|
|
2198
|
+
* — see `poll()`) drops its scheduled entries rather than replaying them
|
|
2199
|
+
* late — each drop emits one `'input.schedule.dropped'` debug event
|
|
2200
|
+
* (`{tick, action}`) via {@link setDebugEmit}'s sink, if one is wired.
|
|
2201
|
+
*/
|
|
2202
|
+
scheduleActionAtTick(
|
|
2203
|
+
tick: number,
|
|
2204
|
+
action: string,
|
|
2205
|
+
value: boolean | number | { x: number; y: number },
|
|
2206
|
+
): void {
|
|
2207
|
+
this.assertActionExists(action, 'scheduleActionAtTick');
|
|
2208
|
+
const expected = InputManager.virtualValueCategory(value);
|
|
2209
|
+
this.assertValueType(action, expected, 'scheduleActionAtTick');
|
|
2210
|
+
if (tick < this.currentTick) {
|
|
2211
|
+
throw new InputTickError(tick, this.currentTick);
|
|
2212
|
+
}
|
|
2213
|
+
const pending = this.scheduledActions.get(tick);
|
|
2214
|
+
if (pending) pending.push({ action, value });
|
|
2215
|
+
else this.scheduledActions.set(tick, [{ action, value }]);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/** Wire (or clear, via `null`) the sink `poll()` calls with
|
|
2219
|
+
* `'input.schedule.dropped'` (`{tick, action}`) whenever it discards a
|
|
2220
|
+
* scheduled entry for a tick whose input phase never ran. Called once by
|
|
2221
|
+
* whoever constructs this `InputManager` with a `DebugRegistry` behind it
|
|
2222
|
+
* (`vgai-scene-game-adapter.ts`), the same seed spot as
|
|
2223
|
+
* `setVirtualInputTarget`/`setInputActionsSource` — never called directly
|
|
2224
|
+
* by gameplay code. */
|
|
2225
|
+
setDebugEmit(fn: ((event: string, detail?: unknown) => void) | null): void {
|
|
2226
|
+
this.debugEmit = fn;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
/** D15/T-D15.5 — start (or restart) recording the post-gate action-delta
|
|
2230
|
+
* trace: clears any prior trace/snapshot, so a fresh recording session
|
|
2231
|
+
* never carries over stale deltas from an earlier one. */
|
|
2232
|
+
startInputRecording(): void {
|
|
2233
|
+
this.inputRecording = true;
|
|
2234
|
+
this.inputTrace = [];
|
|
2235
|
+
this.lastTraceSnapshot.clear();
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
/** D15/T-D15.5 — stop recording (the trace accumulated so far stays
|
|
2239
|
+
* readable via {@link getInputTrace} until the next `startInputRecording`
|
|
2240
|
+
* clears it). Idempotent. */
|
|
2241
|
+
stopInputRecording(): void {
|
|
2242
|
+
this.inputRecording = false;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
/** D15/T-D15.5 — whether a recording is currently active. */
|
|
2246
|
+
isInputRecording(): boolean {
|
|
2247
|
+
return this.inputRecording;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
/** D15/T-D15.5 — the recorded trace so far (a defensive copy), versioned
|
|
2251
|
+
* per the design doc's format sketch (§2.c). SP5 (out of scope here) is
|
|
2252
|
+
* the eventual consumer that replays this; this module only ever
|
|
2253
|
+
* RECORDS. */
|
|
2254
|
+
getInputTrace(): {
|
|
2255
|
+
version: 1;
|
|
2256
|
+
ticks: { tick: number; actions: Record<string, boolean | number | { x: number; y: number }> }[];
|
|
2257
|
+
} {
|
|
2258
|
+
return { version: 1, ticks: this.inputTrace.slice() };
|
|
2259
|
+
}
|
|
2260
|
+
|
|
1582
2261
|
/** Request pointer lock for FPS-style mouse control */
|
|
1583
2262
|
requestPointerLock(element: HTMLElement) {
|
|
1584
2263
|
// Idempotent: never stack duplicate click listeners. Rewire if the element
|