@vgai/engine 0.5.22 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/README.md +6 -3
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +2 -4
  4. package/dist/adapter/adapter-surface.d.ts +1 -1
  5. package/dist/adapter/adapter-surface.js +1 -1
  6. package/dist/adapter/authoring.d.ts +19 -21
  7. package/dist/adapter/authoring.d.ts.map +1 -1
  8. package/dist/adapter/authoring.js +2 -2
  9. package/dist/adapter/binding.d.ts +341 -0
  10. package/dist/adapter/binding.d.ts.map +1 -0
  11. package/dist/adapter/binding.js +148 -0
  12. package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -1
  13. package/dist/adapter/colyseus-networking-adapter.js +23 -4
  14. package/dist/adapter/entrypoint-selection-readers.d.ts +24 -0
  15. package/dist/adapter/entrypoint-selection-readers.d.ts.map +1 -0
  16. package/dist/adapter/entrypoint-selection-readers.js +63 -0
  17. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -1
  18. package/dist/adapter/entrypoint-selection-source.js +1 -44
  19. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  20. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +1 -52
  21. package/dist/adapter/host-context.d.ts +46 -15
  22. package/dist/adapter/host-context.d.ts.map +1 -1
  23. package/dist/adapter/host-context.js +2 -9
  24. package/dist/adapter/index.d.ts +11 -12
  25. package/dist/adapter/index.d.ts.map +1 -1
  26. package/dist/adapter/index.js +9 -10
  27. package/dist/adapter/ingest/game-contract-seams.d.ts +5 -0
  28. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -1
  29. package/dist/adapter/ingest/game-contract-seams.js +1 -0
  30. package/dist/adapter/ingest/game-contract.d.ts +22 -3
  31. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  32. package/dist/adapter/ingest/game-contract.js +0 -3
  33. package/dist/adapter/ingest/scene-capture.d.ts +19 -23
  34. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  35. package/dist/adapter/ingest/scene-capture.js +143 -17
  36. package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
  37. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  38. package/dist/adapter/native-debug-module.d.ts +40 -5
  39. package/dist/adapter/native-debug-module.d.ts.map +1 -1
  40. package/dist/adapter/native-debug-module.js +36 -9
  41. package/dist/adapter/rapier-physics-adapter.d.ts +31 -1
  42. package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -1
  43. package/dist/adapter/rapier-physics-adapter.js +50 -36
  44. package/dist/adapter/renderer-config.d.ts +2 -2
  45. package/dist/adapter/renderer-config.js +2 -2
  46. package/dist/adapter/root-adapter.d.ts +7 -7
  47. package/dist/adapter/root-adapter.js +4 -4
  48. package/dist/adapter/system-adapter.d.ts +45 -7
  49. package/dist/adapter/system-adapter.d.ts.map +1 -1
  50. package/dist/adapter/system-seam-contract.d.ts +3 -3
  51. package/dist/adapter/system-seam-contract.js +3 -3
  52. package/dist/adapter/system-slot.d.ts +168 -0
  53. package/dist/adapter/system-slot.d.ts.map +1 -0
  54. package/dist/adapter/system-slot.js +229 -0
  55. package/dist/adapter/transform.d.ts +11 -1
  56. package/dist/adapter/transform.d.ts.map +1 -1
  57. package/dist/ai/navigation.d.ts +17 -1
  58. package/dist/ai/navigation.d.ts.map +1 -1
  59. package/dist/ai/navigation.js +26 -5
  60. package/dist/animation/anim-graph-types.d.ts +11 -3
  61. package/dist/animation/anim-graph-types.d.ts.map +1 -1
  62. package/dist/animation/theatre-clock-binding.d.ts +4 -4
  63. package/dist/animation/theatre-clock-binding.js +4 -4
  64. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  65. package/dist/animation/xstate-animation-binding.js +2 -2
  66. package/dist/animation/xstate-animation-meta.d.ts +13 -0
  67. package/dist/animation/xstate-animation-meta.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-meta.js +14 -0
  69. package/dist/asset-formats/index.d.ts +4 -7
  70. package/dist/asset-formats/index.d.ts.map +1 -1
  71. package/dist/asset-formats/index.js +4 -7
  72. package/dist/asset-formats/render-env.d.ts +5 -8
  73. package/dist/asset-formats/render-env.d.ts.map +1 -1
  74. package/dist/asset-formats/render-env.js +3 -6
  75. package/dist/asset-parse-error.d.ts +2 -5
  76. package/dist/asset-parse-error.d.ts.map +1 -1
  77. package/dist/asset-parse-error.js +2 -5
  78. package/dist/audio/bus-mixer.d.ts +101 -0
  79. package/dist/audio/bus-mixer.d.ts.map +1 -0
  80. package/dist/audio/bus-mixer.js +115 -0
  81. package/dist/core/frame-pacing.d.ts +1 -1
  82. package/dist/core/frame-pacing.js +2 -2
  83. package/dist/core/game-loop.d.ts +1 -3
  84. package/dist/core/game-loop.d.ts.map +1 -1
  85. package/dist/core/game-loop.js +1 -3
  86. package/dist/core/seeded-random.d.ts +1 -1
  87. package/dist/core/sim-clock.d.ts +5 -6
  88. package/dist/core/sim-clock.d.ts.map +1 -1
  89. package/dist/core/sim-clock.js +4 -5
  90. package/dist/core/types.d.ts +1 -3
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/defaults.d.ts +6 -14
  93. package/dist/defaults.d.ts.map +1 -1
  94. package/dist/defaults.js +6 -14
  95. package/dist/dev/register-render-vitals.d.ts +1 -4
  96. package/dist/dev/register-render-vitals.d.ts.map +1 -1
  97. package/dist/dev/register-render-vitals.js +1 -4
  98. package/dist/dev/render-debug-adapter.d.ts +2 -3
  99. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  100. package/dist/dev/render-debug-adapter.js +2 -3
  101. package/dist/dev/render-vitals.d.ts +3 -3
  102. package/dist/dev/render-vitals.js +3 -3
  103. package/dist/dev/webgl-frame-capture.d.ts +1 -1
  104. package/dist/dev/webgl-frame-capture.js +1 -1
  105. package/dist/index.d.ts +2 -5
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -4
  108. package/dist/input/host-pointer.d.ts +122 -0
  109. package/dist/input/host-pointer.d.ts.map +1 -0
  110. package/dist/input/host-pointer.js +160 -0
  111. package/dist/input/input-manager.d.ts +18 -5
  112. package/dist/input/input-manager.d.ts.map +1 -1
  113. package/dist/input/input-manager.js +33 -13
  114. package/dist/input/rebind-controller.d.ts.map +1 -1
  115. package/dist/input/rebind-controller.js +6 -8
  116. package/dist/input/schema.d.ts.map +1 -1
  117. package/dist/input/schema.js +56 -28
  118. package/dist/manifest/filename.d.ts.map +1 -1
  119. package/dist/manifest/filename.js +8 -12
  120. package/dist/manifest/load.d.ts +10 -10
  121. package/dist/manifest/load.d.ts.map +1 -1
  122. package/dist/manifest/load.js +9 -6
  123. package/dist/manifest/schema.d.ts +17 -21
  124. package/dist/manifest/schema.d.ts.map +1 -1
  125. package/dist/manifest/schema.js +45 -35
  126. package/dist/physics/collider-dimensions.d.ts +8 -12
  127. package/dist/physics/collider-dimensions.d.ts.map +1 -1
  128. package/dist/physics/collider-dimensions.js +8 -12
  129. package/dist/pixi/scene-capture.d.ts +11 -13
  130. package/dist/pixi/scene-capture.d.ts.map +1 -1
  131. package/dist/react/world-state.d.ts +1 -1
  132. package/dist/react/world-state.d.ts.map +1 -1
  133. package/dist/render/light-camera-factory.d.ts +5 -7
  134. package/dist/render/light-camera-factory.d.ts.map +1 -1
  135. package/dist/render/light-camera-factory.js +8 -11
  136. package/dist/render/material-factory.js +1 -1
  137. package/dist/render/render-batch-system.d.ts.map +1 -1
  138. package/dist/render/render-features.d.ts +17 -6
  139. package/dist/render/render-features.d.ts.map +1 -1
  140. package/dist/render/render-features.js +0 -28
  141. package/dist/runtime/create-runtime.d.ts +34 -25
  142. package/dist/runtime/create-runtime.d.ts.map +1 -1
  143. package/dist/runtime/create-runtime.js +218 -93
  144. package/dist/runtime/debug-bridge.d.ts +12 -0
  145. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  146. package/dist/runtime/debug-bridge.js +4 -0
  147. package/dist/runtime/debug-registry.d.ts +95 -8
  148. package/dist/runtime/debug-registry.d.ts.map +1 -1
  149. package/dist/runtime/debug-registry.js +24 -3
  150. package/dist/runtime/dev-build.d.ts +2 -2
  151. package/dist/runtime/dev-build.js +2 -2
  152. package/dist/runtime/game-input-seams.d.ts +2 -2
  153. package/dist/runtime/game-input-seams.d.ts.map +1 -1
  154. package/dist/runtime/game.d.ts +102 -41
  155. package/dist/runtime/game.d.ts.map +1 -1
  156. package/dist/runtime/game.js +80 -54
  157. package/dist/runtime/mount-game.d.ts +0 -16
  158. package/dist/runtime/mount-game.d.ts.map +1 -1
  159. package/dist/runtime/mount-game.js +8 -39
  160. package/dist/runtime/mount-manifest.d.ts +22 -14
  161. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  162. package/dist/runtime/mount-manifest.js +18 -18
  163. package/dist/runtime/playtest.d.ts +22 -0
  164. package/dist/runtime/playtest.d.ts.map +1 -0
  165. package/dist/runtime/playtest.js +12 -0
  166. package/dist/runtime/run-ticks-settled.d.ts +34 -0
  167. package/dist/runtime/run-ticks-settled.d.ts.map +1 -0
  168. package/dist/runtime/run-ticks-settled.js +59 -0
  169. package/dist/runtime/state-bridge.d.ts +2 -3
  170. package/dist/runtime/state-bridge.d.ts.map +1 -1
  171. package/dist/runtime/state-bridge.js +1 -0
  172. package/dist/setup/setup-audio.d.ts +12 -0
  173. package/dist/setup/setup-audio.d.ts.map +1 -1
  174. package/dist/setup/setup-audio.js +9 -10
  175. package/dist/setup/setup-renderer.d.ts +2 -25
  176. package/dist/setup/setup-renderer.d.ts.map +1 -1
  177. package/dist/setup/setup-renderer.js +2 -31
  178. package/dist/world3d-react/r3f-root-factory.d.ts +7 -7
  179. package/dist/world3d-react/r3f-root-factory.js +7 -7
  180. package/dist/world3d-react/rapier-physics-bridge.d.ts +9 -8
  181. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  182. package/dist/world3d-react/rapier-physics-bridge.js +100 -48
  183. package/package.json +1 -1
  184. package/schemas/engine-capabilities.json +16 -14
  185. package/schemas/vgai-project.schema.json +35 -26
  186. package/src/adapter/adapter-module.ts +2 -4
  187. package/src/adapter/adapter-surface.ts +1 -1
  188. package/src/adapter/authoring.ts +19 -21
  189. package/src/adapter/binding.ts +468 -0
  190. package/src/adapter/colyseus-networking-adapter.ts +20 -5
  191. package/src/adapter/entrypoint-selection-readers.ts +66 -0
  192. package/src/adapter/entrypoint-selection-source.ts +1 -40
  193. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
  194. package/src/adapter/host-context.ts +45 -15
  195. package/src/adapter/index.ts +23 -11
  196. package/src/adapter/ingest/game-contract-seams.ts +1 -0
  197. package/src/adapter/ingest/game-contract.ts +22 -3
  198. package/src/adapter/ingest/scene-capture.ts +190 -39
  199. package/src/adapter/ingest/visible-capture-window.ts +35 -0
  200. package/src/adapter/native-debug-module.ts +66 -9
  201. package/src/adapter/rapier-physics-adapter.ts +76 -20
  202. package/src/adapter/renderer-config.ts +2 -2
  203. package/src/adapter/root-adapter.ts +7 -7
  204. package/src/adapter/system-adapter.ts +43 -8
  205. package/src/adapter/system-seam-contract.ts +3 -3
  206. package/src/adapter/system-slot.ts +291 -0
  207. package/src/adapter/transform.ts +18 -1
  208. package/src/ai/navigation.ts +30 -3
  209. package/src/animation/anim-graph-types.ts +11 -3
  210. package/src/animation/theatre-clock-binding.ts +4 -4
  211. package/src/animation/xstate-animation-binding.ts +2 -2
  212. package/src/animation/xstate-animation-meta.ts +16 -0
  213. package/src/asset-formats/index.ts +4 -7
  214. package/src/asset-formats/render-env.ts +3 -6
  215. package/src/asset-parse-error.ts +2 -5
  216. package/src/audio/bus-mixer.ts +161 -0
  217. package/src/core/frame-pacing.ts +2 -2
  218. package/src/core/game-loop.ts +1 -3
  219. package/src/core/seeded-random.ts +1 -1
  220. package/src/core/sim-clock.ts +5 -6
  221. package/src/core/types.ts +1 -3
  222. package/src/defaults.ts +6 -14
  223. package/src/dev/register-render-vitals.ts +1 -4
  224. package/src/dev/render-debug-adapter.ts +2 -3
  225. package/src/dev/render-vitals.ts +3 -3
  226. package/src/dev/webgl-frame-capture.ts +1 -1
  227. package/src/index.ts +2 -5
  228. package/src/input/host-pointer.ts +230 -0
  229. package/src/input/input-manager.ts +34 -13
  230. package/src/input/input-types.ts +1 -1
  231. package/src/input/rebind-controller.ts +6 -8
  232. package/src/input/schema.ts +216 -188
  233. package/src/manifest/filename.ts +8 -12
  234. package/src/manifest/load.ts +17 -13
  235. package/src/manifest/schema.ts +55 -47
  236. package/src/physics/collider-dimensions.ts +8 -12
  237. package/src/pixi/scene-capture.ts +12 -15
  238. package/src/react/world-state.tsx +1 -1
  239. package/src/render/light-camera-factory.ts +8 -11
  240. package/src/render/material-factory.ts +1 -1
  241. package/src/render/render-batch-system.ts +14 -6
  242. package/src/render/render-features.ts +17 -35
  243. package/src/runtime/create-runtime.ts +270 -108
  244. package/src/runtime/debug-bridge.ts +16 -0
  245. package/src/runtime/debug-registry.ts +121 -8
  246. package/src/runtime/dev-build.ts +2 -2
  247. package/src/runtime/game-input-seams.ts +2 -2
  248. package/src/runtime/game.ts +180 -80
  249. package/src/runtime/mount-game.ts +8 -56
  250. package/src/runtime/mount-manifest.ts +43 -34
  251. package/src/runtime/playtest.ts +22 -0
  252. package/src/runtime/run-ticks-settled.ts +72 -0
  253. package/src/runtime/state-bridge.ts +3 -3
  254. package/src/setup/setup-audio.ts +21 -11
  255. package/src/setup/setup-renderer.ts +2 -61
  256. package/src/world3d-react/r3f-root-factory.tsx +7 -7
  257. package/src/world3d-react/rapier-physics-bridge.tsx +107 -50
  258. package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
  259. package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
  260. package/dist/adapter/setup-three-root-adapter.js +0 -908
  261. package/dist/animation/clip-map.d.ts +0 -12
  262. package/dist/animation/clip-map.d.ts.map +0 -1
  263. package/dist/animation/clip-map.js +0 -31
  264. package/dist/asset-registry.d.ts +0 -38
  265. package/dist/asset-registry.d.ts.map +0 -1
  266. package/dist/asset-registry.js +0 -66
  267. package/dist/canvas-react/engine-bridge.d.ts +0 -45
  268. package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
  269. package/dist/canvas-react/engine-bridge.js +0 -45
  270. package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
  271. package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
  272. package/dist/canvas-react/pixi-react-adapter.js +0 -294
  273. package/dist/canvas-react/world-context.d.ts +0 -98
  274. package/dist/canvas-react/world-context.d.ts.map +0 -1
  275. package/dist/canvas-react/world-context.js +0 -173
  276. package/dist/dev/debug-draw.d.ts +0 -24
  277. package/dist/dev/debug-draw.d.ts.map +0 -1
  278. package/dist/dev/debug-draw.js +0 -73
  279. package/dist/render/auto-batcher.d.ts +0 -34
  280. package/dist/render/auto-batcher.d.ts.map +0 -1
  281. package/dist/render/auto-batcher.js +0 -140
  282. package/dist/render/lod.d.ts +0 -13
  283. package/dist/render/lod.d.ts.map +0 -1
  284. package/dist/render/lod.js +0 -16
  285. package/dist/runtime/types.d.ts +0 -303
  286. package/dist/runtime/types.d.ts.map +0 -1
  287. package/dist/runtime/types.js +0 -1
  288. package/dist/world3d-react/engine-bridge.d.ts +0 -47
  289. package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
  290. package/dist/world3d-react/engine-bridge.js +0 -73
  291. package/dist/world3d-react/r3f-adapter.d.ts +0 -58
  292. package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
  293. package/dist/world3d-react/r3f-adapter.js +0 -634
  294. package/dist/world3d-react/world-context.d.ts +0 -182
  295. package/dist/world3d-react/world-context.d.ts.map +0 -1
  296. package/dist/world3d-react/world-context.js +0 -235
  297. package/src/adapter/setup-three-root-adapter.ts +0 -1032
  298. package/src/animation/clip-map.ts +0 -34
  299. package/src/asset-registry.ts +0 -89
  300. package/src/dev/debug-draw.ts +0 -80
  301. package/src/render/auto-batcher.ts +0 -168
  302. package/src/render/lod.ts +0 -17
  303. package/src/runtime/types.ts +0 -328
@@ -184,10 +184,16 @@ const INPUT_TRACE_CAP = 2000;
184
184
  * controls. `setTouchButton(sourceId, pressed)` feeds a digital action
185
185
  * (edge-tracked like `mouse_button`); `setTouchStick(sourceId, value)`
186
186
  * feeds a vector2 action (deadzone-rescaled like a gamepad stick). These
187
- * are the injectable entry points a real on-screen touch UI's own
188
- * touchstart/touchmove/touchend handlers would callthat DOM wiring is
189
- * a thin adapter at the edge (browser-verified separately, e.g. F4/e2e);
190
- * the mapping logic here is unit-tested headlessly with no DOM.
187
+ * are the injectable entry points an on-screen touch UI calls, and they
188
+ * are the UPPER half of the touch story. The LOWER half the DOM
189
+ * pointer stream itself, as dense finger indices mapped into the game's
190
+ * own design space and gated on `isInputActive()` is
191
+ * `input/host-pointer.ts` (`createHostPointer`), beside this file; a
192
+ * control drains its `takeTouches()` and calls the setters above.
193
+ * What is deliberately still NOT here is the control layer in between:
194
+ * which rect owns which finger, and how a knob's offset becomes a stick
195
+ * vector, is the shape of the UI drawing the control, so it belongs to
196
+ * that UI and there is no first-party one to generalize from yet.
191
197
  * - Focus gating — `blur`/`focus` on `window` (guarded, real DOM only) now
192
198
  * drop input the same way `setEnabled(false)` does (flushing held
193
199
  * key/mouse/touch-button state) without touching the `enabled` flag the
@@ -428,7 +434,7 @@ export class InputManager {
428
434
  // caller never tells `poll()` which tick it's servicing (every existing
429
435
  // bare/headless caller — tests, a mount with no `Game` shell behind it —
430
436
  // keeps this exact pre-existing behavior, zero regression). The REAL
431
- // per-world wiring (`setup-three-root-adapter.ts`) instead passes the
437
+ // per-world wiring (`world3d-react/r3f-root-factory.tsx`) instead passes the
432
438
  // shared `Game`-level tick counter into every `poll(tick)` call — this is
433
439
  // deliberate: an InputManager-local counter drifts from the actual game
434
440
  // tick for a paused/frozen world (its `poll()` isn't called every game
@@ -461,7 +467,7 @@ export class InputManager {
461
467
  * action}`) emitted whenever a scheduled entry is discarded because its
462
468
  * target tick's input phase never ran. Wired by whoever constructs this
463
469
  * `InputManager` with access to a `DebugRegistry`
464
- * (`setup-three-root-adapter.ts`, the same seed spot as
470
+ * (`world3d-react/r3f-root-factory.tsx`, the same seed spot as
465
471
  * `setVirtualInputTarget`/`setInputActionsSource`) — `null` (the
466
472
  * default) for a bare/headless `InputManager`, in which case a drop
467
473
  * stays silent (matching pre-D15/T-D15.5 behavior). */
@@ -787,8 +793,8 @@ export class InputManager {
787
793
  }
788
794
  }
789
795
 
790
- /** The names of all registered actions (r3f `world-context` enumerates these
791
- * for its input-binding projection). */
796
+ /** The names of all registered actions (the `game.input.actions` debug seam
797
+ * enumerates these — `runtime/game-input-seams.ts`). */
792
798
  actionNames(): string[] {
793
799
  return [...this.actions.keys()];
794
800
  }
@@ -797,7 +803,7 @@ export class InputManager {
797
803
  * Poll gamepads and virtual look stick (call at start of frame).
798
804
  *
799
805
  * `tick` (D15/T-D15.3/.5) — the GAME tick this call services. Pass the
800
- * live, shared `Game` tick counter here (`setup-three-root-adapter.ts` does,
806
+ * live, shared `Game` tick counter here (`world3d-react/r3f-root-factory.tsx` does,
801
807
  * via `DebugRegistry.getGameTick()`) so `scheduleActionAtTick`'s numbering
802
808
  * never drifts from the actual game tick — the bug an earlier revision of
803
809
  * this feature had: an InputManager-LOCAL counter, incremented once per
@@ -1691,6 +1697,22 @@ export class InputManager {
1691
1697
  return this.focused;
1692
1698
  }
1693
1699
 
1700
+ /**
1701
+ * The gate every REAL-DEVICE listener here passes: {@link isEnabled} AND
1702
+ * {@link isFocused}. Public because the DOM edge this class deliberately
1703
+ * does not own — `host-pointer.ts`, the pointer stream behind the touch
1704
+ * backends — must pass the SAME gate, and a caller writing
1705
+ * `isEnabled() && isFocused()` out for itself is how a looser one drifts in
1706
+ * (a pointer adapter that checked only `isEnabled` kept updating position
1707
+ * for a blurred tab). Read it; do not recompose it.
1708
+ *
1709
+ * Machine input (`setVirtualAction` and the tick-scheduled actuations) is
1710
+ * deliberately NOT this gate — see `machineInputActive`.
1711
+ */
1712
+ isInputActive(): boolean {
1713
+ return this.inputActive;
1714
+ }
1715
+
1694
1716
  /** Whether the pointer is currently locked to {@link requestPointerLock}'s
1695
1717
  * element (F2, spec §12 "pointer lock") — tracked via the real
1696
1718
  * `pointerlockchange` event. */
@@ -2167,12 +2189,11 @@ export class InputManager {
2167
2189
  }
2168
2190
 
2169
2191
  // ==========================================================================
2170
- // Task 1.4 — action-level virtual input for a synthetic player. Promoted from
2171
- // hollowstone's proven `VirtualInput` (`bot/virtual-input.ts`): a bot drives
2192
+ // Action-level virtual input for a synthetic player: a bot drives
2172
2193
  // NAMED ACTIONS (not raw devices), and its contributions join action
2173
2194
  // resolution at exactly the same points binding contributions do (see
2174
2195
  // isPressed/isJustPressed and collectScalarContributions/
2175
- // collectVector2Contributions above). #144 split the gates: virtual input
2196
+ // collectVector2Contributions above). The gates are split: virtual input
2176
2197
  // passes `machineInputActive` (the editor's `enabled` flag only), while
2177
2198
  // human input keeps the full `inputActive` (enabled AND focused) gate — a
2178
2199
  // synthetic player must keep playing the shared editor tab while the
@@ -2380,7 +2401,7 @@ export class InputManager {
2380
2401
  * `'input.schedule.dropped'` (`{tick, action}`) whenever it discards a
2381
2402
  * scheduled entry for a tick whose input phase never ran. Called once by
2382
2403
  * whoever constructs this `InputManager` with a `DebugRegistry` behind it
2383
- * (`setup-three-root-adapter.ts`), the same seed spot as
2404
+ * (`world3d-react/r3f-root-factory.tsx`), the same seed spot as
2384
2405
  * `setVirtualInputTarget`/`setInputActionsSource` — never called directly
2385
2406
  * by gameplay code. */
2386
2407
  setDebugEmit(fn: ((event: string, detail?: unknown) => void) | null): void {
@@ -128,7 +128,7 @@ export interface InputMapFile {
128
128
  // types. See input-manager.ts's class doc comment for the full design note;
129
129
  // these are the plain data shapes shared between `InputManager`'s rebinding
130
130
  // API, the framework-free `RebindController` (rebind-controller.ts), and any
131
- // UI (e.g. the template's `RebindPanel.tsx`) that surfaces them.
131
+ // UI that surfaces them.
132
132
  // =============================================================================
133
133
 
134
134
  /**
@@ -1,11 +1,9 @@
1
- // F3 (spec §12 "Add Rebinding and Prompts") — a framework-free rebinding
2
- // STATE MACHINE over an `InputManager`. Deliberately has no dependency on
3
- // React (or any UI library): "listen for the next input, surface a conflict,
4
- // let the caller decide" is the same flow whether the caller is the
5
- // template's `RebindPanel.tsx` (ordinary React, see that file) or any other
6
- // UI/CLI. Keeping the logic here means it's unit-testable with no DOM/JSX at
7
- // all, and the React component becomes a thin, mechanically-verifiable
8
- // wrapper (see the template component's own doc comment).
1
+ // A framework-free rebinding STATE MACHINE over an `InputManager`.
2
+ // Deliberately has no dependency on React (or any UI library): "listen for the
3
+ // next input, surface a conflict, let the caller decide" is the same flow
4
+ // whether the caller is a React panel or a CLI. Keeping the logic here makes
5
+ // it unit-testable with no DOM/JSX at all, and leaves any UI a thin,
6
+ // mechanically-verifiable wrapper.
9
7
  //
10
8
  // Flow: `listen(actionName)` -> caller captures the next raw input event
11
9
  // itself (a keydown, a gamepad poll, a touch tap — capture is inherently
@@ -37,62 +37,74 @@
37
37
  import { z } from 'zod';
38
38
  import type { InputAction, InputBinding, InputMapFile } from './input-types';
39
39
 
40
- const KeyBindingSchema = z.object({
41
- type: z.literal('key').describe('Binding kind: a keyboard key (KeyboardEvent.code)'),
42
- code: z.string().describe('KeyboardEvent.code value, e.g. "KeyW", "Space", "ArrowUp"'),
43
- });
40
+ const KeyBindingSchema = z
41
+ .object({
42
+ type: z.literal('key').describe('Binding kind: a keyboard key (KeyboardEvent.code)'),
43
+ code: z.string().describe('KeyboardEvent.code value, e.g. "KeyW", "Space", "ArrowUp"'),
44
+ })
45
+ .strict();
44
46
 
45
- const MouseButtonBindingSchema = z.object({
46
- type: z.literal('mouse_button').describe('Binding kind: a mouse button'),
47
- button: z.number().describe('MouseEvent.button index (0 = left, 1 = middle, 2 = right)'),
48
- });
47
+ const MouseButtonBindingSchema = z
48
+ .object({
49
+ type: z.literal('mouse_button').describe('Binding kind: a mouse button'),
50
+ button: z.number().describe('MouseEvent.button index (0 = left, 1 = middle, 2 = right)'),
51
+ })
52
+ .strict();
49
53
 
50
- const MouseMoveBindingSchema = z.object({
51
- type: z
52
- .literal('mouse_move')
53
- .describe(
54
- 'Binding kind: raw mouse movement (F2, spec §12 "Complete Device Backends"). Feeds a ' +
55
- "'pointerDelta'-valueType action with the raw accumulated mouse delta (no deadzone), and " +
56
- "can also feed a 'vector2'-valueType action (the same delta, radial-deadzoned + unit-" +
57
- 'circle-clamped via `deadzone`).',
58
- ),
59
- deadzone: z
60
- .number()
61
- .optional()
62
- .describe(
63
- "Radial deadzone magnitude applied only when this binding feeds a 'vector2'-valueType " +
64
- "action (default 0 no deadzone; ignored for 'pointerDelta' reads, which are never " +
65
- 'deadzoned).',
66
- ),
67
- });
54
+ const MouseMoveBindingSchema = z
55
+ .object({
56
+ type: z
57
+ .literal('mouse_move')
58
+ .describe(
59
+ 'Binding kind: raw mouse movement (F2, spec §12 "Complete Device Backends"). Feeds a ' +
60
+ "'pointerDelta'-valueType action with the raw accumulated mouse delta (no deadzone), and " +
61
+ "can also feed a 'vector2'-valueType action (the same delta, radial-deadzoned + unit-" +
62
+ 'circle-clamped via `deadzone`).',
63
+ ),
64
+ deadzone: z
65
+ .number()
66
+ .optional()
67
+ .describe(
68
+ "Radial deadzone magnitude applied only when this binding feeds a 'vector2'-valueType " +
69
+ "action (default 0 — no deadzone; ignored for 'pointerDelta' reads, which are never " +
70
+ 'deadzoned).',
71
+ ),
72
+ })
73
+ .strict();
68
74
 
69
- const GamepadButtonBindingSchema = z.object({
70
- type: z.literal('gamepad_button').describe('Binding kind: a gamepad face/shoulder button'),
71
- button: z.number().describe('Standard Gamepad API button index'),
72
- });
75
+ const GamepadButtonBindingSchema = z
76
+ .object({
77
+ type: z.literal('gamepad_button').describe('Binding kind: a gamepad face/shoulder button'),
78
+ button: z.number().describe('Standard Gamepad API button index'),
79
+ })
80
+ .strict();
73
81
 
74
- const GamepadAxisBindingSchema = z.object({
75
- type: z.literal('gamepad_axis').describe('Binding kind: a single thresholded gamepad axis'),
76
- axis: z.number().describe('Standard Gamepad API axis index'),
77
- direction: z
78
- .enum(['positive', 'negative'])
79
- .describe('Which side of the deadzone counts as "pressed"'),
80
- deadzone: z.number().optional().describe('Deadzone magnitude (default 0.15 if omitted)'),
81
- });
82
+ const GamepadAxisBindingSchema = z
83
+ .object({
84
+ type: z.literal('gamepad_axis').describe('Binding kind: a single thresholded gamepad axis'),
85
+ axis: z.number().describe('Standard Gamepad API axis index'),
86
+ direction: z
87
+ .enum(['positive', 'negative'])
88
+ .describe('Which side of the deadzone counts as "pressed"'),
89
+ deadzone: z.number().optional().describe('Deadzone magnitude (default 0.15 if omitted)'),
90
+ })
91
+ .strict();
82
92
 
83
- const GamepadAxisPairBindingSchema = z.object({
84
- type: z
85
- .literal('gamepad_axis_pair')
86
- .describe(
87
- 'Binding kind: a coupled (x, y) gamepad axis pair, e.g. a stick used as a 2D vector ' +
88
- '(look/aim/movement) (F2, spec §12 "Complete Device Backends"). Feeds a ' +
89
- "'vector2'-valueType action: (xAxis, yAxis) read together, radial-deadzoned + unit-" +
90
- 'circle-clamped the same way a real analog stick is.',
91
- ),
92
- xAxis: z.number().describe('Standard Gamepad API axis index for the X component'),
93
- yAxis: z.number().describe('Standard Gamepad API axis index for the Y component'),
94
- deadzone: z.number().optional().describe('Deadzone magnitude (default 0.15 if omitted)'),
95
- });
93
+ const GamepadAxisPairBindingSchema = z
94
+ .object({
95
+ type: z
96
+ .literal('gamepad_axis_pair')
97
+ .describe(
98
+ 'Binding kind: a coupled (x, y) gamepad axis pair, e.g. a stick used as a 2D vector ' +
99
+ '(look/aim/movement) (F2, spec §12 "Complete Device Backends"). Feeds a ' +
100
+ "'vector2'-valueType action: (xAxis, yAxis) read together, radial-deadzoned + unit-" +
101
+ 'circle-clamped the same way a real analog stick is.',
102
+ ),
103
+ xAxis: z.number().describe('Standard Gamepad API axis index for the X component'),
104
+ yAxis: z.number().describe('Standard Gamepad API axis index for the Y component'),
105
+ deadzone: z.number().optional().describe('Deadzone magnitude (default 0.15 if omitted)'),
106
+ })
107
+ .strict();
96
108
 
97
109
  // ---------------------------------------------------------------------------
98
110
  // F1 (spec §12) — injected test input bindings. LIVE (real runtime readers in
@@ -102,78 +114,86 @@ const GamepadAxisPairBindingSchema = z.object({
102
114
  // aggregation path these exercise today.
103
115
  // ---------------------------------------------------------------------------
104
116
 
105
- const TestAxisBindingSchema = z.object({
106
- type: z
107
- .literal('test_axis')
108
- .describe(
109
- "Binding kind: an injected synthetic scalar value for a 'scalar'-valueType action " +
110
- '(InputManager.injectAxis/getScalar) the "injected test input" backend named in F2, ' +
111
- "built in F1 as the typed scalar value model's testable source.",
112
- ),
113
- sourceId: z
114
- .string()
115
- .describe('Names the injected test source this binding reads (InputManager.injectAxis).'),
116
- deadzone: z
117
- .number()
118
- .optional()
119
- .describe(
120
- '1D deadzone magnitude applied to the injected raw value before combine (default 0 — no ' +
121
- 'deadzone). Values below it read as 0; values at/above it rescale from 0 (at the ' +
122
- 'deadzone edge) to ±1 (at |raw| = 1), sign preserved.',
123
- ),
124
- });
117
+ const TestAxisBindingSchema = z
118
+ .object({
119
+ type: z
120
+ .literal('test_axis')
121
+ .describe(
122
+ "Binding kind: an injected synthetic scalar value for a 'scalar'-valueType action " +
123
+ '(InputManager.injectAxis/getScalar) — the "injected test input" backend named in F2, ' +
124
+ "built in F1 as the typed scalar value model's testable source.",
125
+ ),
126
+ sourceId: z
127
+ .string()
128
+ .describe('Names the injected test source this binding reads (InputManager.injectAxis).'),
129
+ deadzone: z
130
+ .number()
131
+ .optional()
132
+ .describe(
133
+ '1D deadzone magnitude applied to the injected raw value before combine (default 0 no ' +
134
+ 'deadzone). Values below it read as 0; values at/above it rescale from 0 (at the ' +
135
+ 'deadzone edge) to ±1 (at |raw| = 1), sign preserved.',
136
+ ),
137
+ })
138
+ .strict();
125
139
 
126
- const TestVector2BindingSchema = z.object({
127
- type: z
128
- .literal('test_vector2')
129
- .describe(
130
- "Binding kind: an injected synthetic {x,y} value for a 'vector2'-valueType action " +
131
- '(InputManager.injectVector2/getVector2) the "injected test input" backend named in ' +
132
- "F2, built in F1 as the typed Vector2 value model's testable source.",
133
- ),
134
- sourceId: z
135
- .string()
136
- .describe('Names the injected test source this binding reads (InputManager.injectVector2).'),
137
- deadzone: z
138
- .number()
139
- .optional()
140
- .describe(
141
- 'Radial deadzone magnitude applied to the injected raw {x,y} before combine (default 0 ' +
142
- ' no deadzone). Vectors whose magnitude is below it read as {0,0}; at/above it, ' +
143
- 'direction is preserved and magnitude rescales the same way the scalar deadzone does, ' +
144
- 'then the result is clamped to the unit circle (magnitude <= 1).',
145
- ),
146
- });
140
+ const TestVector2BindingSchema = z
141
+ .object({
142
+ type: z
143
+ .literal('test_vector2')
144
+ .describe(
145
+ "Binding kind: an injected synthetic {x,y} value for a 'vector2'-valueType action " +
146
+ '(InputManager.injectVector2/getVector2) the "injected test input" backend named in ' +
147
+ "F2, built in F1 as the typed Vector2 value model's testable source.",
148
+ ),
149
+ sourceId: z
150
+ .string()
151
+ .describe('Names the injected test source this binding reads (InputManager.injectVector2).'),
152
+ deadzone: z
153
+ .number()
154
+ .optional()
155
+ .describe(
156
+ 'Radial deadzone magnitude applied to the injected raw {x,y} before combine (default 0 ' +
157
+ ' no deadzone). Vectors whose magnitude is below it read as {0,0}; at/above it, ' +
158
+ 'direction is preserved and magnitude rescales the same way the scalar deadzone does, ' +
159
+ 'then the result is clamped to the unit circle (magnitude <= 1).',
160
+ ),
161
+ })
162
+ .strict();
147
163
 
148
- const TestPointerDeltaBindingSchema = z.object({
149
- type: z
150
- .literal('test_pointer_delta')
151
- .describe(
152
- "Binding kind: an injected synthetic per-frame {x,y} delta for a 'pointerDelta'-" +
153
- 'valueType action (InputManager.injectPointerDelta/getPointerDelta) the "injected ' +
154
- 'test input" backend named in F2. No deadzone (deltas are not deadzoned).',
155
- ),
156
- sourceId: z
157
- .string()
158
- .describe(
159
- 'Names the injected test source this binding reads (InputManager.injectPointerDelta).',
160
- ),
161
- });
164
+ const TestPointerDeltaBindingSchema = z
165
+ .object({
166
+ type: z
167
+ .literal('test_pointer_delta')
168
+ .describe(
169
+ "Binding kind: an injected synthetic per-frame {x,y} delta for a 'pointerDelta'-" +
170
+ 'valueType action (InputManager.injectPointerDelta/getPointerDelta) the "injected ' +
171
+ 'test input" backend named in F2. No deadzone (deltas are not deadzoned).',
172
+ ),
173
+ sourceId: z
174
+ .string()
175
+ .describe(
176
+ 'Names the injected test source this binding reads (InputManager.injectPointerDelta).',
177
+ ),
178
+ })
179
+ .strict();
162
180
 
163
- const TestPointerPositionBindingSchema = z.object({
164
- type: z
165
- .literal('test_pointer_position')
166
- .describe(
167
- "Binding kind: an injected synthetic absolute {x,y} position for a 'pointerPosition'-" +
168
- 'valueType action (InputManager.injectPointerPosition/getPointerPosition) the ' +
169
- '"injected test input" backend named in F2.',
170
- ),
171
- sourceId: z
172
- .string()
173
- .describe(
174
- 'Names the injected test source this binding reads (InputManager.injectPointerPosition).',
175
- ),
176
- });
181
+ const TestPointerPositionBindingSchema = z
182
+ .object({
183
+ type: z
184
+ .literal('test_pointer_position')
185
+ .describe(
186
+ "Binding kind: an injected synthetic absolute {x,y} position for a 'pointerPosition'-" +
187
+ 'valueType action (InputManager.injectPointerPosition/getPointerPosition) the ' +
188
+ '"injected test input" backend named in F2.',
189
+ ),
190
+ sourceId: z
191
+ .string()
192
+ .describe(
193
+ 'Names the injected test source this binding reads (InputManager.injectPointerPosition).',
194
+ ),
195
+ })
196
+ .strict();
177
197
 
178
198
  // ---------------------------------------------------------------------------
179
199
  // F2 (spec §12 "Complete Device Backends") — touch/virtual-control bindings.
@@ -185,44 +205,48 @@ const TestPointerPositionBindingSchema = z.object({
185
205
  // thin adapter at the edge, out of scope here — see input-manager.ts).
186
206
  // ---------------------------------------------------------------------------
187
207
 
188
- const TouchButtonBindingSchema = z.object({
189
- type: z
190
- .literal('touch_button')
191
- .describe(
192
- "Binding kind: a virtual on-screen button for a 'digital' action " +
193
- '(InputManager.setTouchButton/isPressed) edge-tracked (isJustPressed/isJustReleased) ' +
194
- 'the same way a real mouse/keyboard button is.',
195
- ),
196
- sourceId: z
197
- .string()
198
- .describe(
199
- 'Names the virtual control zone this binding reads (InputManager.setTouchButton) — a ' +
200
- 'touch-UI-chosen id, not a real device index.',
201
- ),
202
- });
208
+ const TouchButtonBindingSchema = z
209
+ .object({
210
+ type: z
211
+ .literal('touch_button')
212
+ .describe(
213
+ "Binding kind: a virtual on-screen button for a 'digital' action " +
214
+ '(InputManager.setTouchButton/isPressed) edge-tracked (isJustPressed/isJustReleased) ' +
215
+ 'the same way a real mouse/keyboard button is.',
216
+ ),
217
+ sourceId: z
218
+ .string()
219
+ .describe(
220
+ 'Names the virtual control zone this binding reads (InputManager.setTouchButton) — a ' +
221
+ 'touch-UI-chosen id, not a real device index.',
222
+ ),
223
+ })
224
+ .strict();
203
225
 
204
- const TouchStickBindingSchema = z.object({
205
- type: z
206
- .literal('touch_stick')
207
- .describe(
208
- "Binding kind: a virtual on-screen analog stick for a 'vector2' action " +
209
- '(InputManager.setTouchStick/getVector2) radial-deadzoned + unit-circle-clamped the ' +
210
- 'same way a real gamepad stick is.',
211
- ),
212
- sourceId: z
213
- .string()
214
- .describe(
215
- 'Names the virtual control zone this binding reads (InputManager.setTouchStick) — a ' +
216
- 'touch-UI-chosen id, not a real device index.',
217
- ),
218
- deadzone: z
219
- .number()
220
- .optional()
221
- .describe(
222
- 'Radial deadzone magnitude applied to the raw {x,y} before combine (default 0 — no ' +
223
- 'deadzone).',
224
- ),
225
- });
226
+ const TouchStickBindingSchema = z
227
+ .object({
228
+ type: z
229
+ .literal('touch_stick')
230
+ .describe(
231
+ "Binding kind: a virtual on-screen analog stick for a 'vector2' action " +
232
+ '(InputManager.setTouchStick/getVector2) radial-deadzoned + unit-circle-clamped the ' +
233
+ 'same way a real gamepad stick is.',
234
+ ),
235
+ sourceId: z
236
+ .string()
237
+ .describe(
238
+ 'Names the virtual control zone this binding reads (InputManager.setTouchStick) — a ' +
239
+ 'touch-UI-chosen id, not a real device index.',
240
+ ),
241
+ deadzone: z
242
+ .number()
243
+ .optional()
244
+ .describe(
245
+ 'Radial deadzone magnitude applied to the raw {x,y} before combine (default 0 — no ' +
246
+ 'deadzone).',
247
+ ),
248
+ })
249
+ .strict();
226
250
 
227
251
  /** A single input binding. Every kind listed here is LIVE — F2 (spec §12) closed the last two
228
252
  * dead stubs (`mouse_move`/`gamepad_axis_pair`) by giving both real `InputManager` readers. */
@@ -242,31 +266,33 @@ export const InputBindingSchema: z.ZodType<InputBinding> = z.discriminatedUnion(
242
266
  ]) as z.ZodType<InputBinding>;
243
267
 
244
268
  /** An action has a name (the record key) and one or more bindings. */
245
- export const InputActionSchema: z.ZodType<InputAction> = z.object({
246
- valueType: z
247
- .enum(['digital', 'scalar', 'vector2', 'pointerDelta', 'pointerPosition'])
248
- .default('digital')
249
- .describe(
250
- "This action's declared value shape (F1, spec §12). 'digital' (boolean, with " +
251
- 'pressed/justPressed/justReleased edges) is the default when omitted, matching every ' +
252
- "action authored before F1. 'scalar' is a single deadzone-rescaled float axis. " +
253
- "'vector2' is a radial-deadzoned, unit-circle-normalized {x,y}. 'pointerDelta' is a " +
254
- "per-frame {x,y} movement delta, summed across sources. 'pointerPosition' is an " +
255
- 'absolute {x,y}, last-write-wins across sources. InputManager enforces this at read ' +
256
- 'time: reading an action through the wrong typed getter (e.g. getScalar on a ' +
257
- "'digital' action) throws.",
258
- ),
259
- bindings: z
260
- .array(InputBindingSchema)
261
- .describe(
262
- 'Bindings that all trigger/feed this action. Digital actions combine via OR (any one ' +
263
- 'binding satisfied is enough). Scalar actions combine via max-magnitude across ' +
264
- 'contributing bindings (ties keep the first-listed binding). Vector2 actions combine ' +
265
- 'by summing contributing {x,y} values then clamping to the unit circle. PointerDelta ' +
266
- 'actions sum contributing deltas. PointerPosition actions use the most-recently-' +
267
- 'written contributing source (last-write-wins).',
268
- ),
269
- });
269
+ export const InputActionSchema: z.ZodType<InputAction> = z
270
+ .object({
271
+ valueType: z
272
+ .enum(['digital', 'scalar', 'vector2', 'pointerDelta', 'pointerPosition'])
273
+ .default('digital')
274
+ .describe(
275
+ "This action's declared value shape (F1, spec §12). 'digital' (boolean, with " +
276
+ 'pressed/justPressed/justReleased edges) is the default when omitted, matching every ' +
277
+ "action authored before F1. 'scalar' is a single deadzone-rescaled float axis. " +
278
+ "'vector2' is a radial-deadzoned, unit-circle-normalized {x,y}. 'pointerDelta' is a " +
279
+ "per-frame {x,y} movement delta, summed across sources. 'pointerPosition' is an " +
280
+ 'absolute {x,y}, last-write-wins across sources. InputManager enforces this at read ' +
281
+ 'time: reading an action through the wrong typed getter (e.g. getScalar on a ' +
282
+ "'digital' action) throws.",
283
+ ),
284
+ bindings: z
285
+ .array(InputBindingSchema)
286
+ .describe(
287
+ 'Bindings that all trigger/feed this action. Digital actions combine via OR (any one ' +
288
+ 'binding satisfied is enough). Scalar actions combine via max-magnitude across ' +
289
+ 'contributing bindings (ties keep the first-listed binding). Vector2 actions combine ' +
290
+ 'by summing contributing {x,y} values then clamping to the unit circle. PointerDelta ' +
291
+ 'actions sum contributing deltas. PointerPosition actions use the most-recently-' +
292
+ 'written contributing source (last-write-wins).',
293
+ ),
294
+ })
295
+ .strict();
270
296
 
271
297
  /**
272
298
  * The `.inputmap.json` file format (`InputManager.loadMap`).
@@ -275,9 +301,11 @@ export const InputActionSchema: z.ZodType<InputAction> = z.object({
275
301
  * same convention `2d.version`/`Scene2DSchema` used before T2.3 gave the 3D
276
302
  * scene version axis a real reader; see `schema-consumption-map.ts`).
277
303
  */
278
- export const InputMapFileSchema: z.ZodType<InputMapFile> = z.object({
279
- version: z.number().describe('Input map file format version (RESERVED — no range check yet)'),
280
- actions: z
281
- .record(z.string(), InputActionSchema)
282
- .describe('Named actions, each with a list of bindings (InputManager.loadMap)'),
283
- }) as z.ZodType<InputMapFile>;
304
+ export const InputMapFileSchema: z.ZodType<InputMapFile> = z
305
+ .object({
306
+ version: z.number().describe('Input map file format version (RESERVED — no range check yet)'),
307
+ actions: z
308
+ .record(z.string(), InputActionSchema)
309
+ .describe('Named actions, each with a list of bindings (InputManager.loadMap)'),
310
+ })
311
+ .strict() as z.ZodType<InputMapFile>;