lecodes-sdk 0.19.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.
Files changed (367) hide show
  1. package/README.md +114 -0
  2. package/dist/global.d.ts +503 -0
  3. package/dist/host.d.ts +61 -0
  4. package/dist/inject.js +4730 -0
  5. package/dist/types/animate/animate.d.ts +20 -0
  6. package/dist/types/animate/bezier.d.ts +2 -0
  7. package/dist/types/animate/easings.d.ts +21 -0
  8. package/dist/types/canvas/Canvas.d.ts +168 -0
  9. package/dist/types/core/Aspect.d.ts +171 -0
  10. package/dist/types/core/InspectorUI.d.ts +88 -0
  11. package/dist/types/core/StateMachine.d.ts +81 -0
  12. package/dist/types/core/color.d.ts +13 -0
  13. package/dist/types/core/compWrite.d.ts +11 -0
  14. package/dist/types/core/events.d.ts +11 -0
  15. package/dist/types/core/fields.d.ts +58 -0
  16. package/dist/types/core/registry.d.ts +7 -0
  17. package/dist/types/core/signals.d.ts +29 -0
  18. package/dist/types/core/time.d.ts +31 -0
  19. package/dist/types/g2/Camera2D.d.ts +19 -0
  20. package/dist/types/g2/CharacterController2D.d.ts +134 -0
  21. package/dist/types/g2/Node2D.d.ts +91 -0
  22. package/dist/types/g2/OneWay2D.d.ts +24 -0
  23. package/dist/types/g2/Physics2D.d.ts +147 -0
  24. package/dist/types/g2/Scene2D.d.ts +72 -0
  25. package/dist/types/g2/Shape2D.d.ts +58 -0
  26. package/dist/types/g2/Sprite.d.ts +51 -0
  27. package/dist/types/g2/SpriteAnimation.d.ts +50 -0
  28. package/dist/types/g2/SpriteSheet.d.ts +69 -0
  29. package/dist/types/g2/Texture2D.d.ts +16 -0
  30. package/dist/types/g2/Tilemap.d.ts +20 -0
  31. package/dist/types/g2/Tileset.d.ts +32 -0
  32. package/dist/types/g2/Trigger2D.d.ts +20 -0
  33. package/dist/types/g2/autotile.d.ts +133 -0
  34. package/dist/types/g2/cells.d.ts +14 -0
  35. package/dist/types/g2/defineScene2d.d.ts +141 -0
  36. package/dist/types/g2/groups2d.d.ts +16 -0
  37. package/dist/types/g2/loop.d.ts +4 -0
  38. package/dist/types/g2/scenarios2d.d.ts +30 -0
  39. package/dist/types/g2/touch.d.ts +2 -0
  40. package/dist/types/gl/Camera.d.ts +79 -0
  41. package/dist/types/gl/CameraPlace.d.ts +26 -0
  42. package/dist/types/gl/CharacterController.d.ts +98 -0
  43. package/dist/types/gl/Gearbox.d.ts +86 -0
  44. package/dist/types/gl/Geometry.d.ts +39 -0
  45. package/dist/types/gl/IK.d.ts +53 -0
  46. package/dist/types/gl/InstancedMesh.d.ts +46 -0
  47. package/dist/types/gl/Light.d.ts +73 -0
  48. package/dist/types/gl/Lightmap.d.ts +85 -0
  49. package/dist/types/gl/Locomotion.d.ts +279 -0
  50. package/dist/types/gl/Material.d.ts +100 -0
  51. package/dist/types/gl/Mesh.d.ts +33 -0
  52. package/dist/types/gl/Model.d.ts +53 -0
  53. package/dist/types/gl/NavAgent.d.ts +99 -0
  54. package/dist/types/gl/NavMesh.d.ts +169 -0
  55. package/dist/types/gl/Node.d.ts +99 -0
  56. package/dist/types/gl/Noise.d.ts +20 -0
  57. package/dist/types/gl/Particles.d.ts +277 -0
  58. package/dist/types/gl/Physics.d.ts +91 -0
  59. package/dist/types/gl/Plane.d.ts +19 -0
  60. package/dist/types/gl/Ragdoll.d.ts +86 -0
  61. package/dist/types/gl/Ray.d.ts +8 -0
  62. package/dist/types/gl/Scene.d.ts +221 -0
  63. package/dist/types/gl/Shape.d.ts +122 -0
  64. package/dist/types/gl/Terrain.d.ts +280 -0
  65. package/dist/types/gl/Texture.d.ts +27 -0
  66. package/dist/types/gl/Trigger.d.ts +10 -0
  67. package/dist/types/gl/Vehicle.d.ts +191 -0
  68. package/dist/types/gl/Wheel.d.ts +95 -0
  69. package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
  70. package/dist/types/gl/animation/Animator.d.ts +219 -0
  71. package/dist/types/gl/animation/Layer.d.ts +31 -0
  72. package/dist/types/gl/animation/Loop.d.ts +17 -0
  73. package/dist/types/gl/animation/Playback.d.ts +36 -0
  74. package/dist/types/gl/animation/core.d.ts +367 -0
  75. package/dist/types/gl/controls.d.ts +19 -0
  76. package/dist/types/gl/physicsEvents.d.ts +1 -0
  77. package/dist/types/gl/scenarios.d.ts +98 -0
  78. package/dist/types/gl/state.d.ts +5 -0
  79. package/dist/types/gl/terrainMesh.d.ts +46 -0
  80. package/dist/types/gl/touch.d.ts +2 -0
  81. package/dist/types/inject.d.ts +123 -0
  82. package/dist/types/math/Mathf.d.ts +39 -0
  83. package/dist/types/math/mat4.d.ts +66 -0
  84. package/dist/types/math/quat.d.ts +53 -0
  85. package/dist/types/math/vec.d.ts +129 -0
  86. package/dist/types/net/codec.d.ts +25 -0
  87. package/dist/types/net/core.d.ts +106 -0
  88. package/dist/types/net/index.d.ts +80 -0
  89. package/dist/types/net/replication.d.ts +101 -0
  90. package/dist/types/plugins/camera.d.ts +25 -0
  91. package/dist/types/plugins/geolocation.d.ts +39 -0
  92. package/dist/types/plugins/oauth.d.ts +25 -0
  93. package/dist/types/plugins/permission.d.ts +1 -0
  94. package/dist/types/plugins/push.d.ts +46 -0
  95. package/dist/types/plugins/qr.d.ts +21 -0
  96. package/dist/types/plugins/service.d.ts +33 -0
  97. package/dist/types/runtime/app.d.ts +67 -0
  98. package/dist/types/runtime/appEvents.d.ts +7 -0
  99. package/dist/types/runtime/channel.d.ts +1 -0
  100. package/dist/types/runtime/clipboard.d.ts +7 -0
  101. package/dist/types/runtime/datetime.d.ts +80 -0
  102. package/dist/types/runtime/device.d.ts +126 -0
  103. package/dist/types/runtime/fetch.d.ts +31 -0
  104. package/dist/types/runtime/files.d.ts +33 -0
  105. package/dist/types/runtime/input.d.ts +121 -0
  106. package/dist/types/runtime/media.d.ts +46 -0
  107. package/dist/types/runtime/misc.d.ts +11 -0
  108. package/dist/types/runtime/net.d.ts +15 -0
  109. package/dist/types/runtime/rpc.d.ts +44 -0
  110. package/dist/types/runtime/service.d.ts +1 -0
  111. package/dist/types/runtime/share.d.ts +2 -0
  112. package/dist/types/runtime/storage.d.ts +5 -0
  113. package/dist/types/runtime/touch.d.ts +56 -0
  114. package/dist/types/scene/defineScene.d.ts +296 -0
  115. package/dist/types/scene/editorPlugins.d.ts +82 -0
  116. package/dist/types/scene/gizmos.d.ts +56 -0
  117. package/dist/types/scene/grammar.d.ts +56 -0
  118. package/dist/types/scene/level.d.ts +98 -0
  119. package/dist/types/scene/material.d.ts +76 -0
  120. package/dist/types/server/auth/appConfig.d.ts +16 -0
  121. package/dist/types/server/auth/global.d.ts +56 -0
  122. package/dist/types/server/auth/models.d.ts +164 -0
  123. package/dist/types/server/auth/types.d.ts +56 -0
  124. package/dist/types/server/channel.d.ts +28 -0
  125. package/dist/types/server/context.d.ts +27 -0
  126. package/dist/types/server/db/defineDb.d.ts +36 -0
  127. package/dist/types/server/db/fields.d.ts +146 -0
  128. package/dist/types/server/db/index.d.ts +6 -0
  129. package/dist/types/server/db/marci/query.d.ts +326 -0
  130. package/dist/types/server/db/types.d.ts +163 -0
  131. package/dist/types/server/errors.d.ts +8 -0
  132. package/dist/types/server/inject.d.ts +5 -0
  133. package/dist/types/ui/NativeView.d.ts +38 -0
  134. package/dist/types/ui/UI.d.ts +27 -0
  135. package/dist/types/ui/UIBottomSheet.d.ts +71 -0
  136. package/dist/types/ui/UIButton.d.ts +30 -0
  137. package/dist/types/ui/UIContainer.d.ts +33 -0
  138. package/dist/types/ui/UIImage.d.ts +35 -0
  139. package/dist/types/ui/UIInput.d.ts +110 -0
  140. package/dist/types/ui/UIModal.d.ts +70 -0
  141. package/dist/types/ui/UINode.d.ts +363 -0
  142. package/dist/types/ui/UIPager.d.ts +123 -0
  143. package/dist/types/ui/UIPopover.d.ts +39 -0
  144. package/dist/types/ui/UIScreen.d.ts +57 -0
  145. package/dist/types/ui/UIScrollable.d.ts +40 -0
  146. package/dist/types/ui/UISpacer.d.ts +9 -0
  147. package/dist/types/ui/UITabs.d.ts +96 -0
  148. package/dist/types/ui/UIText.d.ts +19 -0
  149. package/dist/types/ui/UIVideo.d.ts +28 -0
  150. package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
  151. package/dist/types/ui/UIWidget.d.ts +65 -0
  152. package/dist/types/ui/fonts.d.ts +9 -0
  153. package/dist/types/ui/presentable.d.ts +68 -0
  154. package/dist/types/ui/router.d.ts +40 -0
  155. package/dist/types/ui/theme.d.ts +26 -0
  156. package/dist/types.json +1 -0
  157. package/package.json +46 -0
  158. package/prompts/2d.md +395 -0
  159. package/prompts/3d-scene-files.md +113 -0
  160. package/prompts/3d-scene.md +119 -0
  161. package/prompts/3d.md +303 -0
  162. package/prompts/README.md +142 -0
  163. package/prompts/ar.md +109 -0
  164. package/prompts/canvas.md +27 -0
  165. package/prompts/compose.ts +46 -0
  166. package/prompts/concept.md +49 -0
  167. package/prompts/core-design.md +75 -0
  168. package/prompts/core.md +264 -0
  169. package/prompts/design.md +218 -0
  170. package/prompts/dist/2d-game.md +1530 -0
  171. package/prompts/dist/3d-app.md +1672 -0
  172. package/prompts/dist/ar-app.md +1548 -0
  173. package/prompts/dist/concept.md +49 -0
  174. package/prompts/dist/design.md +498 -0
  175. package/prompts/dist/ui-app.md +1134 -0
  176. package/prompts/index.md +37 -0
  177. package/prompts/intake-prompt.md +33 -0
  178. package/prompts/namer-prompt.md +12 -0
  179. package/prompts/router-prompt.md +50 -0
  180. package/prompts/select.ts +111 -0
  181. package/prompts/ui-design.md +203 -0
  182. package/prompts/ui.md +803 -0
  183. package/src/animate/animate.ts +238 -0
  184. package/src/animate/bezier.ts +138 -0
  185. package/src/animate/easings.ts +126 -0
  186. package/src/bridges.d.ts +1345 -0
  187. package/src/canvas/Canvas.ts +305 -0
  188. package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
  189. package/src/compile/__tests__/assetMacro.test.ts +74 -0
  190. package/src/compile/__tests__/assetName.test.ts +55 -0
  191. package/src/compile/__tests__/compile.test.ts +287 -0
  192. package/src/compile/__tests__/detectEntry.test.ts +132 -0
  193. package/src/compile/__tests__/fontMacro.test.ts +199 -0
  194. package/src/compile/aspectMacro.ts +86 -0
  195. package/src/compile/assetIconMacro.ts +384 -0
  196. package/src/compile/assetMacro.ts +146 -0
  197. package/src/compile/assetName.ts +50 -0
  198. package/src/compile/bundler.ts +287 -0
  199. package/src/compile/compileProject.ts +127 -0
  200. package/src/compile/detectEntry.ts +128 -0
  201. package/src/compile/fontMacro.ts +459 -0
  202. package/src/compile/fontRegistry.ts +78 -0
  203. package/src/compile/header.ts +67 -0
  204. package/src/compile/index.ts +100 -0
  205. package/src/compile/libraryImports.ts +52 -0
  206. package/src/compile/liteMaterial.ts +247 -0
  207. package/src/compile/sceneEditor.ts +88 -0
  208. package/src/compile/serverSplit.ts +233 -0
  209. package/src/compile/serverTypes.ts +227 -0
  210. package/src/compile/sfnt.ts +98 -0
  211. package/src/compile/shaderSchema.ts +202 -0
  212. package/src/compile/shaderTargets.ts +77 -0
  213. package/src/compile/sourcemap.ts +25 -0
  214. package/src/core/Aspect.ts +568 -0
  215. package/src/core/InspectorUI.ts +212 -0
  216. package/src/core/StateMachine.ts +308 -0
  217. package/src/core/__tests__/stateMachine.test.ts +132 -0
  218. package/src/core/color.ts +66 -0
  219. package/src/core/compWrite.ts +42 -0
  220. package/src/core/events.ts +38 -0
  221. package/src/core/fields.ts +120 -0
  222. package/src/core/registry.ts +23 -0
  223. package/src/core/signals.ts +277 -0
  224. package/src/core/time.ts +84 -0
  225. package/src/g2/Camera2D.ts +40 -0
  226. package/src/g2/CharacterController2D.ts +276 -0
  227. package/src/g2/Node2D.ts +267 -0
  228. package/src/g2/OneWay2D.ts +66 -0
  229. package/src/g2/Physics2D.ts +346 -0
  230. package/src/g2/Scene2D.ts +209 -0
  231. package/src/g2/Shape2D.ts +259 -0
  232. package/src/g2/Sprite.ts +89 -0
  233. package/src/g2/SpriteAnimation.ts +171 -0
  234. package/src/g2/SpriteSheet.ts +166 -0
  235. package/src/g2/Texture2D.ts +47 -0
  236. package/src/g2/Tilemap.ts +41 -0
  237. package/src/g2/Tileset.ts +71 -0
  238. package/src/g2/Trigger2D.ts +77 -0
  239. package/src/g2/autotile.ts +433 -0
  240. package/src/g2/cells.ts +91 -0
  241. package/src/g2/defineScene2d.ts +381 -0
  242. package/src/g2/groups2d.ts +106 -0
  243. package/src/g2/loop.ts +50 -0
  244. package/src/g2/scenarios2d.ts +69 -0
  245. package/src/g2/touch.ts +83 -0
  246. package/src/gl/Camera.ts +160 -0
  247. package/src/gl/CameraPlace.ts +52 -0
  248. package/src/gl/CharacterController.ts +238 -0
  249. package/src/gl/Gearbox.ts +212 -0
  250. package/src/gl/Geometry.ts +279 -0
  251. package/src/gl/IK.ts +193 -0
  252. package/src/gl/InstancedMesh.ts +132 -0
  253. package/src/gl/Light.ts +135 -0
  254. package/src/gl/Lightmap.ts +402 -0
  255. package/src/gl/Locomotion.ts +498 -0
  256. package/src/gl/Material.ts +279 -0
  257. package/src/gl/Mesh.ts +83 -0
  258. package/src/gl/Model.ts +124 -0
  259. package/src/gl/NavAgent.ts +337 -0
  260. package/src/gl/NavMesh.ts +397 -0
  261. package/src/gl/Node.ts +350 -0
  262. package/src/gl/Noise.ts +30 -0
  263. package/src/gl/Particles.ts +676 -0
  264. package/src/gl/Physics.ts +222 -0
  265. package/src/gl/Plane.ts +53 -0
  266. package/src/gl/Ragdoll.ts +270 -0
  267. package/src/gl/Ray.ts +16 -0
  268. package/src/gl/Scene.ts +505 -0
  269. package/src/gl/Shape.ts +409 -0
  270. package/src/gl/Terrain.ts +1092 -0
  271. package/src/gl/Texture.ts +63 -0
  272. package/src/gl/Trigger.ts +45 -0
  273. package/src/gl/Vehicle.ts +473 -0
  274. package/src/gl/Wheel.ts +240 -0
  275. package/src/gl/animation/AnimationClip.ts +204 -0
  276. package/src/gl/animation/Animator.ts +329 -0
  277. package/src/gl/animation/Layer.ts +39 -0
  278. package/src/gl/animation/Loop.ts +31 -0
  279. package/src/gl/animation/Playback.ts +52 -0
  280. package/src/gl/animation/core.ts +670 -0
  281. package/src/gl/controls.ts +95 -0
  282. package/src/gl/physicsEvents.ts +20 -0
  283. package/src/gl/scenarios.ts +291 -0
  284. package/src/gl/state.ts +6 -0
  285. package/src/gl/terrainMesh.ts +219 -0
  286. package/src/gl/touch.ts +68 -0
  287. package/src/host.d.ts +61 -0
  288. package/src/inject.ts +203 -0
  289. package/src/math/Mathf.ts +118 -0
  290. package/src/math/mat4.ts +278 -0
  291. package/src/math/quat.ts +232 -0
  292. package/src/math/vec.ts +255 -0
  293. package/src/net/codec.ts +119 -0
  294. package/src/net/core.ts +384 -0
  295. package/src/net/index.ts +181 -0
  296. package/src/net/replication.ts +622 -0
  297. package/src/plugins/camera.ts +81 -0
  298. package/src/plugins/geolocation.ts +123 -0
  299. package/src/plugins/oauth.ts +61 -0
  300. package/src/plugins/permission.ts +7 -0
  301. package/src/plugins/push.ts +132 -0
  302. package/src/plugins/qr.ts +73 -0
  303. package/src/plugins/service.ts +47 -0
  304. package/src/runtime/app.ts +101 -0
  305. package/src/runtime/appEvents.ts +54 -0
  306. package/src/runtime/channel.ts +61 -0
  307. package/src/runtime/clipboard.ts +20 -0
  308. package/src/runtime/datetime.ts +329 -0
  309. package/src/runtime/device.ts +293 -0
  310. package/src/runtime/fetch.ts +77 -0
  311. package/src/runtime/files.ts +108 -0
  312. package/src/runtime/input.ts +175 -0
  313. package/src/runtime/media.ts +111 -0
  314. package/src/runtime/misc.ts +16 -0
  315. package/src/runtime/net.ts +36 -0
  316. package/src/runtime/rpc.ts +218 -0
  317. package/src/runtime/service.ts +83 -0
  318. package/src/runtime/share.ts +9 -0
  319. package/src/runtime/storage.ts +13 -0
  320. package/src/runtime/touch.ts +76 -0
  321. package/src/scene/defineScene.ts +1396 -0
  322. package/src/scene/editorPlugins.ts +110 -0
  323. package/src/scene/gizmos.ts +148 -0
  324. package/src/scene/grammar.ts +120 -0
  325. package/src/scene/level.ts +296 -0
  326. package/src/scene/material.ts +188 -0
  327. package/src/server/auth/appConfig.ts +12 -0
  328. package/src/server/auth/global.ts +80 -0
  329. package/src/server/auth/host.ts +318 -0
  330. package/src/server/auth/models.ts +83 -0
  331. package/src/server/auth/types.ts +50 -0
  332. package/src/server/channel.ts +56 -0
  333. package/src/server/context.ts +36 -0
  334. package/src/server/db/defineDb.ts +237 -0
  335. package/src/server/db/fields.ts +132 -0
  336. package/src/server/db/httpTransport.ts +93 -0
  337. package/src/server/db/index.ts +7 -0
  338. package/src/server/db/marci/query.ts +412 -0
  339. package/src/server/db/types.ts +202 -0
  340. package/src/server/errors.ts +12 -0
  341. package/src/server/host.ts +74 -0
  342. package/src/server/inject.ts +13 -0
  343. package/src/server/runtime.ts +133 -0
  344. package/src/server/validate.ts +87 -0
  345. package/src/ui/NativeView.ts +142 -0
  346. package/src/ui/UI.ts +39 -0
  347. package/src/ui/UIBottomSheet.ts +139 -0
  348. package/src/ui/UIButton.ts +101 -0
  349. package/src/ui/UIContainer.ts +60 -0
  350. package/src/ui/UIImage.ts +83 -0
  351. package/src/ui/UIInput.ts +185 -0
  352. package/src/ui/UIModal.ts +139 -0
  353. package/src/ui/UINode.ts +830 -0
  354. package/src/ui/UIPager.ts +362 -0
  355. package/src/ui/UIPopover.ts +100 -0
  356. package/src/ui/UIScreen.ts +123 -0
  357. package/src/ui/UIScrollable.ts +87 -0
  358. package/src/ui/UISpacer.ts +14 -0
  359. package/src/ui/UITabs.ts +236 -0
  360. package/src/ui/UIText.ts +51 -0
  361. package/src/ui/UIVideo.ts +88 -0
  362. package/src/ui/UIVirtualizedList.ts +241 -0
  363. package/src/ui/UIWidget.ts +127 -0
  364. package/src/ui/fonts.ts +13 -0
  365. package/src/ui/presentable.ts +117 -0
  366. package/src/ui/router.ts +132 -0
  367. package/src/ui/theme.ts +84 -0
@@ -0,0 +1,498 @@
1
+ // Locomotion — a character's movement, as an aspect on its node (docs/animation-v2-plan.md §2.8).
2
+ //
3
+ // The GAME states an intent; the ENGINE owns everything that follows. Two intents, and they have
4
+ // different lifetimes on purpose:
5
+ //
6
+ // the DIRECTION (and gait) — where to go, how fast to want to go there;
7
+ // the FACING — where to look. It outlives the direction: letting go of the key does not mean
8
+ // "never mind", it means "stop moving", so the body still owes that heading and turns to it
9
+ // on the spot.
10
+ //
11
+ // From those the engine springs a desired velocity into a simulated one, predicts the path ahead, and
12
+ // picks what the animator's base layer shows — a start, the gait loop, a turn, a stop, a turn on the
13
+ // spot. What actually MOVES the body is `displacement`: by default the clip's own recorded motion,
14
+ // adjusted toward the simulation by a clamped amount. The simulation itself never reads the animation,
15
+ // so nothing the character does can feed back into what it plays.
16
+ //
17
+ // const player = new Node().aspect(CharacterController).aspect(Locomotion, {
18
+ // animator: model.anim,
19
+ // set: { idle: 'Idle', gaits: [ 'Walk', 'Run' ], starts, stops, turns, spins },
20
+ // })
21
+ // // each frame, in an aspect ordered BEFORE this one:
22
+ // held ? player.loco.move(direction, { gait: running ? 'run' : 'walk' }) : player.loco.move(null)
23
+ // player.loco.state // 'idle' | 'start' | 'move' | 'turn' | 'stop' | 'spin'
24
+ // player.loco.speed // m/s the body actually travels at
25
+ //
26
+ // The clip SET is the pack's names; the engine measures what each clip does itself (how far it turns,
27
+ // how fast the gait moves, where its foot contacts fall), so nothing here is a hand-measured table.
28
+ // Give `angle` / `speed` only to override a measurement.
29
+
30
+ import { Aspect } from "../core/Aspect"
31
+ import { Vec3, cx, cz, type Vec3Like } from "../math/vec"
32
+ import { Animator, type FeetOptions, type WarpOptions } from "./animation/Animator"
33
+ import { CharacterController } from "./CharacterController"
34
+ import type { Node } from "./Node"
35
+
36
+ /** How fast the character wants to go: the gait picks its clips and its speed. */
37
+ export type Gait = "walk" | "run" | "sprint"
38
+ /** What the engine shows: the idle, a start, the gait loop, a turn while moving, a stop, or a turn on the spot. */
39
+ export type LocomotionState = "idle" | "start" | "move" | "turn" | "stop" | "spin"
40
+ /** Who moves the body:
41
+ * `'hybrid'` (the default) — the clip's own recorded motion moves it; while the gait loop shows it
42
+ * is ADJUSTED toward what the simulation wants by at most `tuning.adjust` metres a second, and a
43
+ * start / turn / stop / spin moves it alone. The recording keeps the feet honest, the clamp keeps
44
+ * the player in charge of the arcs. What a pack authored with root motion wants.
45
+ * `'code'` — the simulation moves it and the animation has to be warped to follow (stride warping);
46
+ * for packs recorded in place.
47
+ * `'data'` — the clips' root motion moves it and nothing adjusts it; the simulation only predicts. */
48
+ export type Displacement = "hybrid" | "code" | "data"
49
+
50
+ /** One clip of the set: its name, or the name with what the clip itself cannot say — `gait` (which
51
+ * gait a transition belongs to: a walking body plays the walking starts, stops and turns; left out =
52
+ * any gait) — and overrides for what the engine would otherwise measure: `angle` (degrees the clip
53
+ * turns the body, + = left) for a start / turn / spin, `speed` (m/s) for a gait clip. */
54
+ export type LocomotionClip = string | { clip: string, gait?: Gait, angle?: number, speed?: number }
55
+
56
+ /** The clips the selector may show. Everything but `idle` is a list; an empty one simply means the
57
+ * engine never shows that kind (a pack without turns steers through them instead). */
58
+ export type LocomotionSet = {
59
+ /** standing still */
60
+ idle?: LocomotionClip
61
+ /** the gait loop's members, slowest first — walk, run, sprint. Blended by the simulated speed. */
62
+ gaits?: LocomotionClip[]
63
+ /** standing → moving, one per direction (the engine picks by the turn each one makes) */
64
+ starts?: LocomotionClip[]
65
+ /** moving → standing */
66
+ stops?: LocomotionClip[]
67
+ /** turns taken while moving */
68
+ turns?: LocomotionClip[]
69
+ /** turns on the spot — what a facing still owed by a standing body plays */
70
+ spins?: LocomotionClip[]
71
+ }
72
+
73
+ /** The tuning. Times are seconds, speeds m/s, angles degrees. */
74
+ export type LocomotionTuning = {
75
+ /** How briskly the simulated speed reaches the wanted one — the halflife of the difference, per
76
+ * gait (default 0.27 walking, 0.2 running). Smaller = more responsive, larger = heavier. */
77
+ responseWalk?: number
78
+ responseRun?: number
79
+ /** How briskly the body brakes when nothing is asked for (default 0.15). Braking is not
80
+ * acceleration backwards: a body plants and stops in far less than it takes to reach a run, and
81
+ * with the gait's own number the tail of the curve is a long, visible glide. */
82
+ responseStop?: number
83
+ /** The same for the facing (default 0.15). */
84
+ responseFacing?: number
85
+ /** Top speed per gait. Left out (the default), each gait travels at the speed its own clip was
86
+ * recorded at — which is what keeps the feet from sliding. */
87
+ speeds?: Partial<Record<Gait, number>>
88
+ /** The step warp's split when a gait is asked for at a speed that is not its clip's own: the
89
+ * STRIDE (spatial squash of the leg's reach) gets ratio^strideShare, clamped to `strideBand`,
90
+ * and the cadence takes all the rest — the asked speed is always delivered exactly.
91
+ * 0 = all cadence (slow motion), 1 = all stride (within the band). Default 0.5. */
92
+ strideShare?: number
93
+ /** How far the stride's share may squash or stretch the legs' reach (default [0.85, 1.15]) —
94
+ * past it the warp re-bends legs the clip authored straight. */
95
+ strideBand?: [number, number]
96
+ /** PIN the step warp's stride ratio (a tuning bench's knob): the step width is held at exactly
97
+ * this, whatever speed is asked — the body's speed falls out of stride × cadence instead of
98
+ * driving the split. Leave out (or 0) for automatic. */
99
+ warpStride?: number
100
+ /** PIN the step warp's cadence ratio the same way — the animation's pace held at exactly this.
101
+ * With both pinned, stride and pace are fully independent. Leave out (or 0) for automatic. */
102
+ warpCadence?: number
103
+ /** Cap on how fast the body may turn by STEERING, deg/s (default: uncapped) — the arc it walks when
104
+ * the heading is off by less than `turnMin`. A start / turn / spin clip owns the facing while it
105
+ * turns: the body then turns exactly as much as the (warped) animation does. */
106
+ turnRate?: number
107
+ /** Heading error that plays a turn clip instead of steering through it (default 60°). Which turn
108
+ * clip is not a threshold: every one is entered where the gait phase allows nearest to its pivot,
109
+ * and the one whose turn LEFT from there best answers the error wins. */
110
+ turnMin?: number
111
+ /** Facing still owed by a standing body that turns it on the spot (default 30°). Less than this it
112
+ * accepts — a standing body never rotates by code, that is the one thing no tuning makes look right. */
113
+ spinMin?: number
114
+ /** Transition seconds between clips (default 0.2), and into the idle after a stop (default 0.3). */
115
+ blend?: number
116
+ stopBlend?: number
117
+ /** How far a start / turn / spin clip's recorded turn may be SCALED so it delivers exactly the heading
118
+ * asked for, as a fraction (default 0.3 = ×0.7 … ×1.3). What lies beyond the clamp is steered once
119
+ * the body moves. 0 = the recording is law. */
120
+ yawWarp?: number
121
+ /** A start or a turn hands over to the gait loop at the first foot-down after its turn is delivered
122
+ * at which its pose is within this many metres of the loop's — the engine's baked fit curve —
123
+ * instead of at its end (default 0.15). 0 = at the end. */
124
+ exitTolerance?: number
125
+ /** A stop asked to move again resumes the gait loop while the body still travels at this fraction of
126
+ * the gait's speed or more (default 0.5); slower, it starts afresh. */
127
+ resumeSpeed?: number
128
+ /** …and then let it play this many more FOOT-DOWNS before handing over (default 0). `exitTolerance`
129
+ * answers "when has this pose become the walk's", which is the earliest honest moment and not always
130
+ * the best-looking one — a start that has just found the gait still reads as a start for another step
131
+ * or two. 1 or 2 gives a take room to finish its thought; a take that runs out hands over at its own
132
+ * end regardless, so nothing can hang. */
133
+ exitSteps?: number
134
+ /** How much braking room, in metres, to trade for each second of a stop take's lead-in that would
135
+ * otherwise be skipped (default 0). A stop cannot be rewound — the variants are one cycle long, so
136
+ * no earlier frame has the same foot down — so the only way to begin a stop nearer its own start is
137
+ * to prefer the VARIANT that starts there for the foot the body happens to be on. Raise it (0.5…1.5)
138
+ * to see the whole braking gesture; leave it at 0 to land exactly where the body should stop. */
139
+ stopEarly?: number
140
+ /** How much braking room, in metres, one extra FOOT-DOWN in the stop is worth (default 0). A stop
141
+ * take is one cycle long, so wanting another step means wanting a LONGER variant, and a longer one
142
+ * lands the body further along than it asked to stop — the trade is this number. Around 1 buys a
143
+ * second step on a pack like Mobility Pro; 0 lands exactly where the body should stop. */
144
+ stopSteps?: number
145
+ /** How far a stop take's pose may be from the one showing, in metres, for it to be entered BEFORE
146
+ * its own first foot-down (default 0.25). Ahead of that footfall a take's phase curve is an
147
+ * extrapolation: it can match the body's phase arithmetically with the legs at a different stage
148
+ * of the stride, which is why the pose is asked directly. 0 = never enter a take early — the body
149
+ * then takes whatever later entry matches its foot, which on a walk can be three quarters of the
150
+ * way through a take, on top of one of its plants. */
151
+ stopFit?: number
152
+ /** What a metre of POSE distance at the entry is worth against a metre of braking room when the
153
+ * variants are ranked (default 1). Both are metres and the feet pay both: room the body has not
154
+ * got is taken out of the root while a foot is planted, and a pose the transition has to hide is
155
+ * dragged out of that same foot over the blend. 0 = rank by braking room alone, which picks a
156
+ * take by where it ENDS and lets it start from any pose at all. */
157
+ stopPose?: number
158
+ /** Input magnitude below which a stick counts as released (default 0.2). */
159
+ deadZone?: number
160
+ /** `displacement: 'hybrid'`: how far, m/s, the body may be pushed off what the animation recorded,
161
+ * toward what the simulation wants, while the gait loop shows (default 0.5) — a start, turn, stop
162
+ * or spin moves the body alone, since pushing it there is its planted foot skating. 0 = the
163
+ * recording is law; large = the code is law — responsive, and the feet start lying. */
164
+ adjust?: number
165
+ }
166
+
167
+ /** One stop take as the selector weighed it, from `stopReport()`. A stop is chosen for the foot the
168
+ * body stands on and the room it needs: `phaseOk` and `afterFirstStep` are the two pass/fail rules,
169
+ * `score` ranks whatever passed (lower wins), and `chosen` marks the one that plays. */
170
+ export type StopCandidate = {
171
+ /** the take's clip name */
172
+ clip: string
173
+ /** seconds into it the entry would be — near its end means its braking gesture is skipped */
174
+ at: number
175
+ /** metres it still travels from that entry */
176
+ room: number
177
+ /** metres this body needs to come to rest (the same for every row) */
178
+ need: number
179
+ /** seconds from the entry to its next foot-down */
180
+ wait: number
181
+ /** foot-downs left after the entry: 0 = it plants where it stands */
182
+ steps: number
183
+ /** does it have a frame at the body's own cycle phase (the right foot down)? */
184
+ phaseOk: boolean
185
+ /** is that frame at or after its own first foot-down? Ahead of it the phase curve is an
186
+ * extrapolation — it can match arithmetically at the wrong stage of the stride */
187
+ afterFirstStep: boolean
188
+ /** …and it is ahead of it, but its POSE matched what is showing closely enough to be let in
189
+ * anyway (`fit` under `tuning.stopFit`) */
190
+ earlyByPose: boolean
191
+ /** the one that is playing */
192
+ chosen: boolean
193
+ /** |room − need| + the pose distance + how late in the take (lower wins) */
194
+ score: number
195
+ /** how far this entry's pose is from the one showing, in the fit measure's metres (-1 = not
196
+ * measured). Under `tuning.stopFit` an entry ahead of the take's first foot-down is allowed. */
197
+ fit: number
198
+ }
199
+
200
+ const STOP_ROW = 9
201
+ const STOP_ROWS = 8
202
+ const IN = { DIR_X: 0, DIR_Z: 1, MAG: 2, FACE_X: 3, FACE_Z: 4, GAIT: 5, COUNT: 6 }
203
+ const OUT = { STATE: 0, SPEED: 1, VEL_X: 2, VEL_Y: 3, VEL_Z: 4, YAW: 5, YAW_RATE: 6, PHASE: 7, SEQ: 8, PRED: 9, YAW_LEFT: 21, CLIP_TIME: 22, EXIT_AT: 23, STOP_OFF: 24, STOP_STEPS: 25, STOP_WAIT: 26, COUNT: 27 }
204
+ const P = {
205
+ HALFLIFE_WALK: 0, HALFLIFE_RUN: 1, HALFLIFE_FACING: 2, SPEED_WALK: 3, SPEED_RUN: 4, SPEED_SPRINT: 5,
206
+ DEADZONE: 6, TURN_MIN: 7, TURN_BIG: 8, BLEND: 9, STOP_BLEND: 10, TAP: 11, RESUME: 12, PREDICT: 13,
207
+ DISPLACEMENT: 14, MODE: 15, MATCH_INTERVAL: 16, MATCH_BLEND: 17, SPIN_MIN: 18, TURN_RATE: 19,
208
+ HALFLIFE_STOP: 20, ADJUST: 21, YAW_WARP: 22, EXIT_TOL: 23, RESUME_SPEED: 24, EXIT_STEPS: 25, STOP_EARLY: 26, STOP_STEPS: 27, STOP_FIT: 28, STOP_POSE: 29,
209
+ STRIDE_SHARE: 30, STRIDE_MIN: 31, STRIDE_MAX: 32, WARP_STRIDE: 33, WARP_RATE: 34, COUNT: 35,
210
+ }
211
+ const KIND = { IDLE: 0, GAIT: 1, START: 2, STOP: 3, TURN: 4, MATCH: 5, SPIN: 6 }
212
+ const STATES: LocomotionState[] = [ "idle", "start", "move", "turn", "stop", "spin" ]
213
+ const GAITS: Gait[] = [ "walk", "run", "sprint" ]
214
+ const RAD2DEG = 180 / Math.PI
215
+
216
+ export type LocomotionEvents = {
217
+ /** What the selector shows changed. */
218
+ state: (state: LocomotionState) => void
219
+ }
220
+
221
+ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
222
+ static readonly aspect = "loco"
223
+
224
+ /** The animator whose clips are shown. Default: the first one on this node or under it (a Model
225
+ * child), which is the usual character — a capsule node with the model inside. */
226
+ animator?: Animator
227
+ /** The clips the selector may show. */
228
+ set: LocomotionSet = {}
229
+ /** Who moves the body (see `Displacement`). */
230
+ displacement: Displacement = "hybrid"
231
+ /** The feel (see `LocomotionTuning`). */
232
+ tuning: LocomotionTuning = {}
233
+ /** Fit the POSE to what the body does — stride and orientation warping (see `Animator.warp`, which
234
+ * this simply sets). The simulation feeds the engine the body's motion every frame, so here it
235
+ * only has to be turned on: `warp: true`, or `{ stride: [0.7, 1.4], orientation: 45 }`. Off by
236
+ * default — a pack whose gaits already cover the speeds it is played at does not need it. */
237
+ warp?: WarpOptions | boolean
238
+ /** The FEET (see `Animator.feet`, which this simply sets). Default: the foot lock on, ground IK off
239
+ * — a planted foot stays where it landed through every transition the selector makes, which is the
240
+ * last of the slide once the recording moves the body. `true` = lock + ground IK (stairs, slopes);
241
+ * `false` = neither; or the options themselves. */
242
+ feet?: FeetOptions | boolean
243
+
244
+ private _loco = 0
245
+ private _dir = new Vec3(0, 0, 0)
246
+ private _face = new Vec3(0, 0, 1)
247
+ private _mag = 0
248
+ private _gait: Gait = "walk"
249
+ private _seq = -1
250
+ private readonly _in = new Float32Array(IN.COUNT)
251
+ private readonly _out = new Float32Array(OUT.COUNT)
252
+ private readonly _names = new Map<number, string>()
253
+ private _report?: Float32Array
254
+
255
+ private get anim(): Animator | undefined {
256
+ if (this.animator) return this.animator
257
+ let found = this.node.get(Animator)
258
+ if (!found) this.node.traverse((n) => { found ??= n.get(Animator) })
259
+ return this.animator = found
260
+ }
261
+ private get controller(): CharacterController | undefined {
262
+ for (let n: Node | null = this.node; n; n = n.parent) { const c = n.get(CharacterController); if (c) return c }
263
+ return undefined
264
+ }
265
+
266
+ protected onAttach(): void { this.rebuild() }
267
+ protected onDetach(): void {
268
+ if (this._loco) { _creator.locoDestroy(this._loco); this._loco = 0 }
269
+ }
270
+
271
+ /** Re-read the set and the tuning — after changing either at runtime (a weapon that changes the
272
+ * walk, a slowed-down character). */
273
+ rebuild(): void {
274
+ const anim = this.anim
275
+ if (!anim || !_creator.locoCreate) return
276
+ if (!this._loco) {
277
+ const first = this.set.idle ?? (this.set.gaits ?? [])[0] // binding a clip is what creates the native animator
278
+ if (first !== undefined) anim._slot(name(first))
279
+ if (!anim._id) return
280
+ this._loco = _creator.locoCreate(anim._id)
281
+ if (!this._loco) return
282
+ }
283
+ _creator.locoClearSet(this._loco)
284
+ this._names.clear()
285
+ this.enter(this.set.idle, KIND.IDLE)
286
+ for (const c of this.set.gaits ?? []) this.enter(c, KIND.GAIT)
287
+ for (const c of this.set.starts ?? []) this.enter(c, KIND.START)
288
+ for (const c of this.set.stops ?? []) this.enter(c, KIND.STOP)
289
+ for (const c of this.set.turns ?? []) this.enter(c, KIND.TURN)
290
+ for (const c of this.set.spins ?? []) this.enter(c, KIND.SPIN)
291
+ if (this.warp !== undefined) anim.warp = this.warp
292
+ anim.feet = this.feet === undefined ? { lock: true } : this.feet === true ? { lock: true, ik: true } : this.feet === false ? { lock: false, ik: false } : this.feet
293
+ this.pushTuning()
294
+ }
295
+
296
+ /** Register one clip of the set: the engine measures the clip's own turn / speed unless given. */
297
+ private enter(clip: LocomotionClip | undefined, kind: number): void {
298
+ if (clip === undefined) return
299
+ const anim = this.anim
300
+ if (!anim) return
301
+ const n = name(clip)
302
+ const slot = anim._slot(n)
303
+ if (slot < 0) { console.warn(`Locomotion: no clip '${n}' on ${this.node.name || "the character"}`); return }
304
+ this._names.set(slot, n)
305
+ const o = typeof clip === "string" ? undefined : clip
306
+ const measured = anim.curves(n)
307
+ const angle = o?.angle ?? (measured ? measured.turn * RAD2DEG : 0)
308
+ const gait = kind === KIND.GAIT ? -1 : o?.gait ? GAITS.indexOf(o.gait) : -1
309
+ _creator.locoSetEntry(this._loco, slot, kind, angle, o?.speed ?? 0, gait)
310
+ }
311
+
312
+ /** Why the stop that is playing is the one that plays: every candidate the selector weighed, in
313
+ * the order it weighed them. Read it when a stop looks wrong — the answer is usually that the
314
+ * take that would have looked right was rejected by one of the two rules, not that the score
315
+ * picked badly. Empty until a stop has been chosen (and on hosts without the debug read). */
316
+ stopReport(): StopCandidate[] {
317
+ if (!this._loco || !_creator.locoStopReport) return []
318
+ const buf = this._report ??= new Float32Array(STOP_ROWS * STOP_ROW)
319
+ const rows = _creator.locoStopReport(this._loco, buf)
320
+ const out: StopCandidate[] = []
321
+ for (let i = 0; i < rows; i++) {
322
+ const r = i * STOP_ROW
323
+ const flags = buf[r + 6]
324
+ out.push({
325
+ clip: this._names.get(buf[r]) ?? `slot ${buf[r]}`,
326
+ at: buf[r + 1], room: buf[r + 2], need: buf[r + 3], wait: buf[r + 4], steps: buf[r + 5], fit: buf[r + 8],
327
+ phaseOk: (flags & 1) !== 0, afterFirstStep: (flags & 2) !== 0, chosen: (flags & 4) !== 0,
328
+ earlyByPose: (flags & 8) !== 0,
329
+ score: buf[r + 7],
330
+ })
331
+ }
332
+ return out
333
+ }
334
+
335
+ /** Re-push the TUNING alone — the numbers, not the clip set. What a live tweak wants (a debug
336
+ * slider, a character that gets heavier when hurt): `rebuild()` also re-measures the set, which
337
+ * mid-clip is more than a changed number asks for. */
338
+ retune(): void { this.pushTuning() }
339
+
340
+ private pushTuning(): void {
341
+ if (!this._loco) return
342
+ const p = new Float32Array(P.COUNT)
343
+ _creator.locoDefaults(p)
344
+ const t = this.tuning
345
+ if (t.responseWalk !== undefined) p[P.HALFLIFE_WALK] = t.responseWalk
346
+ if (t.responseRun !== undefined) p[P.HALFLIFE_RUN] = t.responseRun
347
+ if (t.responseStop !== undefined) p[P.HALFLIFE_STOP] = t.responseStop
348
+ if (t.responseFacing !== undefined) p[P.HALFLIFE_FACING] = t.responseFacing
349
+ if (t.speeds?.walk !== undefined) p[P.SPEED_WALK] = t.speeds.walk
350
+ if (t.speeds?.run !== undefined) p[P.SPEED_RUN] = t.speeds.run
351
+ if (t.speeds?.sprint !== undefined) p[P.SPEED_SPRINT] = t.speeds.sprint
352
+ if (t.turnRate !== undefined) p[P.TURN_RATE] = t.turnRate
353
+ if (t.turnMin !== undefined) p[P.TURN_MIN] = t.turnMin
354
+ if (t.spinMin !== undefined) p[P.SPIN_MIN] = t.spinMin
355
+ if (t.blend !== undefined) p[P.BLEND] = t.blend
356
+ if (t.stopBlend !== undefined) p[P.STOP_BLEND] = t.stopBlend
357
+ if (t.yawWarp !== undefined) p[P.YAW_WARP] = t.yawWarp
358
+ if (t.exitTolerance !== undefined) p[P.EXIT_TOL] = t.exitTolerance
359
+ if (t.resumeSpeed !== undefined) p[P.RESUME_SPEED] = t.resumeSpeed
360
+ if (t.exitSteps !== undefined) p[P.EXIT_STEPS] = t.exitSteps
361
+ if (t.stopEarly !== undefined) p[P.STOP_EARLY] = t.stopEarly
362
+ if (t.stopSteps !== undefined) p[P.STOP_STEPS] = t.stopSteps
363
+ if (t.stopFit !== undefined) p[P.STOP_FIT] = t.stopFit
364
+ if (t.stopPose !== undefined) p[P.STOP_POSE] = t.stopPose
365
+ if (t.strideShare !== undefined) p[P.STRIDE_SHARE] = t.strideShare
366
+ if (t.strideBand !== undefined) { p[P.STRIDE_MIN] = t.strideBand[0]; p[P.STRIDE_MAX] = t.strideBand[1] }
367
+ if (t.warpStride !== undefined) p[P.WARP_STRIDE] = t.warpStride
368
+ if (t.warpCadence !== undefined) p[P.WARP_RATE] = t.warpCadence
369
+ if (t.deadZone !== undefined) p[P.DEADZONE] = t.deadZone
370
+ if (t.adjust !== undefined) p[P.ADJUST] = t.adjust
371
+ p[P.DISPLACEMENT] = this.displacement === "data" ? 1 : this.displacement === "hybrid" ? 2 : 0
372
+ _creator.locoSetParams(this._loco, p)
373
+ }
374
+
375
+ // ---- the intent --------------------------------------------------------------------------------
376
+
377
+ /** State the movement intent for this frame: a world direction (its length is the stick's pull,
378
+ * clamped to 1) and the gait. `null` = no movement — the body stops, but it keeps facing where it
379
+ * was last asked to, and turns there on the spot if it still owes the turn.
380
+ * `face` states a facing of its own (aiming, strafing, a camera-relative shooter). */
381
+ move(direction: Vec3Like | null | undefined, options: { gait?: Gait, face?: Vec3Like } = {}): void {
382
+ const dx = direction ? cx(direction) : 0, dz = direction ? cz(direction) : 0
383
+ const len = Math.hypot(dx, dz)
384
+ if (len > 1e-4) {
385
+ this._dir = new Vec3(dx / len, 0, dz / len)
386
+ this._mag = Math.min(len, 1)
387
+ this._face = this._dir
388
+ } else this._mag = 0
389
+ if (options.face) {
390
+ const fx = cx(options.face), fz = cz(options.face), fl = Math.hypot(fx, fz)
391
+ if (fl > 1e-4) this._face = new Vec3(fx / fl, 0, fz / fl)
392
+ }
393
+ if (options.gait) this._gait = options.gait
394
+ }
395
+
396
+ /** Face this way without moving — the facing intent on its own (`Vec3`, or a heading in degrees,
397
+ * 0 = +Z, + = toward +X). A standing body turns to it on the spot. */
398
+ face(direction: Vec3Like | number): void {
399
+ if (typeof direction === "number") {
400
+ const a = direction / RAD2DEG
401
+ this._face = new Vec3(Math.sin(a), 0, Math.cos(a))
402
+ return
403
+ }
404
+ const fx = cx(direction), fz = cz(direction), fl = Math.hypot(fx, fz)
405
+ if (fl > 1e-4) this._face = new Vec3(fx / fl, 0, fz / fl)
406
+ }
407
+
408
+ /** The gait the intent asks for. */
409
+ get gait(): Gait { return this._gait }
410
+ set gait(g: Gait) { this._gait = g }
411
+ /** Is a movement intent standing right now? (The debug answer to "is a key held".) */
412
+ get moving(): boolean { return this._mag > (this.tuning.deadZone ?? 0.2) }
413
+ /** The direction the intent points, world. */
414
+ get direction(): Vec3 { return this._dir }
415
+ /** The heading the intent asks the body to face, degrees (0 = +Z, + = toward +X). */
416
+ get heading(): number { return Math.atan2(this._face.x, this._face.z) * RAD2DEG }
417
+ /** The speed the intent asks for, m/s — it jumps the moment a key goes down or the gait changes.
418
+ * `speed` is what the body has made of it. */
419
+ get intentSpeed(): number {
420
+ if (!this.moving) return 0
421
+ const t = this.tuning.speeds
422
+ const named = t ? t[this._gait] : undefined
423
+ if (named !== undefined) return named * this._mag
424
+ const clip = (this.set.gaits ?? [])[GAITS.indexOf(this._gait)]
425
+ const measured = clip !== undefined ? this.anim?.curves(name(clip))?.speed : undefined
426
+ return (measured ?? 0) * this._mag
427
+ }
428
+
429
+ // ---- what came of it ---------------------------------------------------------------------------
430
+
431
+ /** What the selector shows. */
432
+ get state(): LocomotionState { return STATES[this._out[OUT.STATE]] ?? "idle" }
433
+ /** The body's speed, m/s — the simulation's, which is what the animation is fitted to. */
434
+ get speed(): number { return this._out[OUT.SPEED] }
435
+ /** The body's velocity, world m/s. */
436
+ get velocity(): Vec3 { return new Vec3(this._out[OUT.VEL_X], this._out[OUT.VEL_Y], this._out[OUT.VEL_Z]) }
437
+ /** Where the body faces, degrees (0 = +Z, + = toward +X) — the node's yaw, which this drives. */
438
+ get facing(): number { return this._out[OUT.YAW] * RAD2DEG }
439
+ /** Where the base layer is in the gait cycle, 0–1 (-1 when what shows has no cycle). */
440
+ get phase(): number { return this._out[OUT.PHASE] }
441
+ /** How far the STOP that is showing was entered from the phase the body was on, in cycles, signed —
442
+ * measured at the switch. 0 while no stop shows; ±0.5 would be the other foot on the ground. */
443
+ get stopFootOffset(): number { return this._out[OUT.STOP_OFF] }
444
+
445
+ /** Foot-downs left in the stop that is showing, counted from the frame it was entered at. 0 means it
446
+ * was entered past its own last footfall — the body will not step, it will only settle. -1 when no
447
+ * stop shows. The number behind "he did not take a step, he just pulled the foot back". */
448
+ get stopStepsLeft(): number { return this._out[OUT.STOP_STEPS] }
449
+
450
+ /** Seconds from the frame the shown stop was entered at to its next foot-down. Large means it was
451
+ * entered just AFTER a footfall: the leg is planted and then held still until the one after, which
452
+ * reads as a foot that sticks. -1 when no stop shows. */
453
+ get stopStepWait(): number { return this._out[OUT.STOP_WAIT] }
454
+
455
+ /** Degrees the clip showing still turns the body by (a start / turn / spin, after its warp); 0 for
456
+ * the loop and the idle — the heading the engine has committed to but not yet delivered. */
457
+ get yawLeft(): number { return this._out[OUT.YAW_LEFT] * RAD2DEG }
458
+ /** The shown transition's clip time and the time it hands over at, seconds (-1 for the loop / idle;
459
+ * `exitAt` -1 = at the clip's end). A debug readout. */
460
+ get clipTime(): number { return this._out[OUT.CLIP_TIME] }
461
+ get exitAt(): number { return this._out[OUT.EXIT_AT] }
462
+ /** Where the body will be in 0.2 / 0.4 / 0.7 s if the intent holds — the path the selector judges
463
+ * a turn by, and a debug overlay's dots. */
464
+ get trajectory(): Vec3[] {
465
+ const out: Vec3[] = []
466
+ for (let i = 0; i < 3; i++) out.push(new Vec3(this._out[OUT.PRED + i * 4], 0, this._out[OUT.PRED + i * 4 + 1]))
467
+ return out
468
+ }
469
+
470
+ // ---- the frame ---------------------------------------------------------------------------------
471
+
472
+ protected updateBefore(dt: number): void {
473
+ if (!this._loco) return
474
+ const i = this._in
475
+ i[IN.DIR_X] = this._dir.x; i[IN.DIR_Z] = this._dir.z; i[IN.MAG] = this._mag
476
+ i[IN.FACE_X] = this._face.x; i[IN.FACE_Z] = this._face.z
477
+ i[IN.GAIT] = Math.max(0, GAITS.indexOf(this._gait))
478
+ _creator.locoSetInput(this._loco, i)
479
+ _creator.locoUpdate(this._loco, dt)
480
+ _creator.locoRead(this._loco, this._out)
481
+ if (this.displacement !== "data") this.node.eulerAngles = [ 0, this._out[OUT.YAW] * RAD2DEG, 0 ] // data: the root motion turns the node itself
482
+ const seq = this._out[OUT.SEQ]
483
+ if (seq !== this._seq) { this._seq = seq; this.emit("state", this.state) }
484
+ }
485
+
486
+ protected updateFixed(dt: number): void {
487
+ if (!this._loco) return
488
+ const p = this.node.position
489
+ _creator.locoStep(this._loco, dt, p.x, p.y, p.z)
490
+ if (this.displacement === "data") return // the engine hands the root motion to the character itself
491
+ _creator.locoRead(this._loco, this._out)
492
+ const c = this.controller
493
+ if (c) c.move(this._out[OUT.VEL_X], this._out[OUT.VEL_Z])
494
+ else this.node.position = [ p.x + this._out[OUT.VEL_X] * dt, p.y, p.z + this._out[OUT.VEL_Z] * dt ]
495
+ }
496
+ }
497
+
498
+ const name = (clip: LocomotionClip): string => typeof clip === "string" ? clip : clip.clip