@bitefoundry/retrieviewer 0.0.0-stage → 0.0.2

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 (297) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +126 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +86 -2
  12. package/RELEASING.md +70 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/WINDOWS-COMPANION.md +113 -0
  16. package/braille.mjs +45 -0
  17. package/examples/bridge.py +62 -0
  18. package/examples/embed.mjs +15 -0
  19. package/examples/live.mjs +40 -0
  20. package/examples/plugin.mjs +21 -0
  21. package/integrations/opencode/runtime.mjs +195 -0
  22. package/integrations/opencode/tui.tsx +59 -0
  23. package/integrations/windows-overlay/Overlay.cs +259 -0
  24. package/integrations/windows-overlay/desktop.mjs +87 -0
  25. package/integrations/windows-overlay/protocol.mjs +39 -0
  26. package/integrations/windows-overlay/worker.mjs +60 -0
  27. package/locomotion-core.mjs +21 -0
  28. package/locomotion-puppy.animal +0 -0
  29. package/package.json +131 -4
  30. package/pet-q5-manifest.json +118 -0
  31. package/puppy-clean.mjs +51 -0
  32. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  33. package/puppy-companion-interactions-v3.json +1 -0
  34. package/puppy-companion-life.mjs +182 -0
  35. package/puppy-companion-objects.mjs +30 -0
  36. package/puppy-companion-sprites.mjs +14 -0
  37. package/puppy-continuous.mjs +57 -0
  38. package/puppy-face.mjs +46 -0
  39. package/puppy-frame-predictive.mjs +147 -0
  40. package/puppy-frame-store.mjs +108 -0
  41. package/puppy-mouth-anchor.json +1 -0
  42. package/puppy-mouth-anchor.mjs +33 -0
  43. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  44. package/puppy-paper-actions-v9.json +1 -0
  45. package/puppy-paper-actions.mjs +48 -0
  46. package/puppy-paper-approach-q5.bin.gz +0 -0
  47. package/puppy-paper-approach.json +1 -0
  48. package/puppy-paper-area.mjs +15 -0
  49. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  50. package/puppy-paper-arrival-seat.json +1 -0
  51. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  52. package/puppy-paper-behaviors.json +1 -0
  53. package/puppy-paper-behaviors.mjs +8 -0
  54. package/puppy-paper-contact.mjs +103 -0
  55. package/puppy-paper-director.mjs +21 -0
  56. package/puppy-paper-paw-transport.mjs +76 -0
  57. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  58. package/puppy-paper-rear-gallop.json +1 -0
  59. package/puppy-paper-seat-q5.bin.gz +0 -0
  60. package/puppy-paper-seat.json +14 -0
  61. package/puppy-paper-seat.mjs +13 -0
  62. package/puppy-paper-trick-timing.mjs +58 -0
  63. package/puppy-paper-world.mjs +108 -0
  64. package/puppy-paper.mjs +271 -0
  65. package/puppy-stable-meta.json +1606 -0
  66. package/puppy-stable.mjs +72 -0
  67. package/puppy-stride-meta.json +343 -0
  68. package/puppy-stride-tweens-q5.bin.gz +0 -0
  69. package/puppy-zoomies.mjs +27 -0
  70. package/puppy-zoomies30.mjs +12 -0
  71. package/retrieviewer-cli.mjs +94 -0
  72. package/retrieviewer-config.mjs +49 -0
  73. package/retrieviewer-install.mjs +157 -0
  74. package/retrieviewer-plugin.d.mts +44 -0
  75. package/retrieviewer-plugin.mjs +97 -0
  76. package/retrieviewer-stdio.mjs +38 -0
  77. package/retrieviewer.d.mts +85 -0
  78. package/retrieviewer.mjs +142 -0
  79. package/vendor/procedural-animals/LICENSE +21 -0
  80. package/vendor/procedural-animals/package.json +40 -0
  81. package/vendor/procedural-animals/src/animal.js +146 -0
  82. package/vendor/procedural-animals/src/bake.js +51 -0
  83. package/vendor/procedural-animals/src/build.js +81 -0
  84. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  85. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  86. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  87. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  88. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  89. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  90. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  91. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  92. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  93. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  94. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  95. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  96. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  97. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  98. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  99. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  100. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  101. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  102. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  103. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  104. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  105. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  106. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  107. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  108. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  109. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  110. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  111. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  112. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  113. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  114. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  115. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  116. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  117. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  118. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  119. package/vendor/procedural-animals/src/index.d.ts +98 -0
  120. package/vendor/procedural-animals/src/index.js +29 -0
  121. package/vendor/procedural-animals/src/registry.js +48 -0
  122. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  123. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  124. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  125. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  126. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  127. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  128. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  129. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  130. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  131. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  132. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  133. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  134. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  135. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  136. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  137. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  138. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  139. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  140. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  141. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  142. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  143. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  144. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  145. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  146. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  147. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  148. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  149. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  150. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  151. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  152. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  153. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  154. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  155. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  156. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  157. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  158. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  159. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  160. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  161. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  162. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  163. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  164. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  165. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  166. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  167. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  168. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  169. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  170. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  171. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  172. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  173. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  174. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  175. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  176. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  177. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  178. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  179. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  180. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  181. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  182. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  183. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  184. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  185. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  186. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  187. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  188. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  189. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  190. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  191. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  192. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  193. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  194. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  195. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  196. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  197. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  198. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  199. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  200. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  201. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  202. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  203. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  204. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  205. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  206. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  207. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  208. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  209. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  210. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  211. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  212. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  213. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  214. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  215. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  216. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  217. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  218. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  219. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  220. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  221. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  222. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  223. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  224. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  225. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  226. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  227. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  228. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  229. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  230. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  231. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  232. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  233. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  234. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  235. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  236. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  237. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  238. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  239. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  240. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  241. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  242. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  243. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  244. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  245. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  246. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  247. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  248. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  249. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  250. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  251. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  252. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  253. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  254. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  255. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  256. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  257. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  258. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  259. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  260. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  261. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  262. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  263. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  264. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  265. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  266. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  267. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  268. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  269. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  270. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  271. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  272. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  273. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  274. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  275. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  276. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  277. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  278. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  279. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  280. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  281. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  282. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  283. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  284. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  285. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  286. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  287. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  288. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  289. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  290. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  291. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  292. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  293. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  294. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  295. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  296. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  297. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,800 @@
1
+ // Action layer for the quadruped engine.
2
+ //
3
+ // Every frame the layer fills a posture parameter set P (createPostureParams) that the engine reads
4
+ // on top of the locomotion. Actions are time-parameterised and blend in / out with weights:
5
+ //
6
+ // postures sit, lie, sleep, death replace the locomotion until 'stand' (or until the animal is
7
+ // asked to move: it stands up first). Cross-fade between them.
8
+ // one-shots jump, attack, hit, eat, blend over whatever runs underneath (an attack can be
9
+ // drink played while galloping), fade in and out.
10
+ // idle automatic when standing with nothing else running: weight shifts with occasional
11
+ // re-steps, looking around, ear twitches, tail flicks (blinks: the eye rig).
12
+ //
13
+ // play(name, opts) returns a Promise resolved when the action ends (postures: when the transition
14
+ // into the pose is complete; looping actions: when stopped). opts: { target (Vector3), direction
15
+ // (Vector3, hit: direction the blow travels), loop, speed (time scale) }. The promise resolves with
16
+ // how the action ended: 'done' | 'interrupted' | 'stopped' | 'refused'.
17
+ //
18
+ // Arbitration (the same rule in every body plan):
19
+ // - a new one-shot interrupts the running one-shots: they fade out over their own fade and resolve
20
+ // 'interrupted' (the new one fades in over the same time: a cross-fade);
21
+ // - a one-shot committed to the air (a jump or pounce between push-off and touchdown) cannot be cut:
22
+ // while it runs a new one-shot is refused, except `hit`, which layers over it;
23
+ // - a posture cross-fades from the current posture and ends the running one-shots (except a hit
24
+ // flinch); jump / attack (and hook actions with needsStand) stand the animal up first;
25
+ // - stop(name) ends that action (a posture: the animal stands up); stop() ends every one-shot and
26
+ // posture except death (only play('stand') or stop('death') revives), and never cuts a jump in the
27
+ // air (it lands first);
28
+ // - dead: every one-shot except hit is refused; moving does not stand a dead animal up.
29
+ // Hook one-shots may set overlay: true (a tail swish, a vocalisation): they neither interrupt nor
30
+ // are interrupted by other one-shots.
31
+ //
32
+ // Species tuning: species.motion.actions = {
33
+ // attack: { style: 'bite-lunge' | 'pounce' | 'charge' | 'headbutt' | 'kick', kick: 'double' | 'rear',
34
+ // kickReach: [back, up] leg lengths ([1, 0.45]) } (play('attack', { kick: 'rear' }) picks
35
+ // the kick per call),
36
+ // eat: { style: 'tear' | 'graze', dropF, dropH, ahead: mouth ahead of the forefeet (leg lengths, 0.5) },
37
+ // drink: { dropF, dropH, ahead },
38
+ // jump: { height (m), distance (m), land: [front, hind] landing crouch, give: landing sink
39
+ // speed handed to the girdle springs (x the body's, 1) }, sleep: { style: 'curl' | 'lateral' },
40
+ // sit / lie / death: { dropF, dropH, ... } overrides of the pose values below,
41
+ // lie / sleep: { down, up: 'front' | 'hind' } which end kneels first / gets up first,
42
+ // idle: { shift: weight-shift amount (1), restep: false disables idle re-steps },
43
+ // fold: { front: { z, k, beta } } forelegs folded under the chest when lying (hoofed animals kneel) }
44
+ //
45
+ // ------------------------------------------------------------------------------------------------
46
+ // Species hooks: species.motion.hooks = { actions, update, pose } (all optional). The one place a
47
+ // species adds behaviour to the quadruped engine; everything else is data.
48
+ //
49
+ // actions: { name: def } extra actions, or replacements of built-ins of the same name. def =
50
+ // { kind: 'oneshot' | 'posture',
51
+ // start(inst, layer), update(inst, dt, layer) -> true when done, apply(P, w, inst, layer),
52
+ // onLand?(inst, layer) (one-shots; apply is called with the faded weight)
53
+ // fadeIn, fadeOut, apply(P, w, inst, layer) (postures: held until 'stand')
54
+ // fade?: s (one-shot fade in / out, 0.12), airborne?: true (it calls engine.takeOff: no second
55
+ // jump while in the air, fast fade-out), needsStand?: true (a sitting / lying animal stands up
56
+ // first, like jump and attack) }
57
+ // inst: { name, t, w, dur, opts (play options), tune (species.motion.actions[name]), side, ... }
58
+ // The built-in definitions are exported (ONESHOTS, POSTURES) so a species can wrap them.
59
+ // update(P, dt, layer) every frame after the built-in postures and one-shots have filled P and before
60
+ // finished actions are resolved: body language and style on top of everything (tail by mood,
61
+ // ears, hackles P.furRaise, a walk's head nod, dozing, idle variants that layer.play() actions).
62
+ // It may also steer (engine.wantSpeed / wantHeading). layer: { engine, idle: { w, stepT },
63
+ // postures: [inst], oneshots: [inst], play(name, opts) }.
64
+ // pose(engine, dt) after the engine has written every bone (legs, head, ears, tail and the species'
65
+ // extra bones): direct bone edits (a twitching snout, independent ear swivels). engine.bone[name]
66
+ // .matrixWorld is world space; keep the change continuous from frame to frame.
67
+ //
68
+ // Units in P: angles in radians; dropF / dropH are fractions of the girdle height; posture lengths
69
+ // (fwd, side, lift, headRaise, neckReach, headLocal) are in engine units, multiplied by cfg.k = size x
70
+ // unit (the individual's size relative to the cheetah): a hook that means species metres divides by
71
+ // cfg.unit. World points (headPos, legs[i].target) are metres. Helpers: mx(P, key, value, w) blends
72
+ // P[key] toward value with weight w; mxLeg(P, i, key, value, w), mxLegs(P, key, front, hind, w);
73
+ // mouthGroundTarget(engine, out, pitch, levelFn). Engine state a hook may read: speed, heading, pos,
74
+ // phase, gait, legs[i] ({ state, plant, contact, def }), fr (body frames), cfg, params (the
75
+ // individual's build params), rand(), input, air.
76
+ import * as THREE from 'three';
77
+ import { clamp, lerp, smooth, envelope, DEG, tv, angDiff } from './common.js';
78
+
79
+ const LEG_FIELDS = ['fold', 'sit', 'side', 'tuck', 'reach'];
80
+ const KICK_REACH = [1.0, 0.45];
81
+
82
+ export function createPostureParams() {
83
+ return {
84
+ gaitW: 1, legGait: 1, holdFeet: 0, bodyOmega: 1, speedScale: 1,
85
+ frontH: 0, frontHW: 0, // absolute shoulder-joint height above the ground (m) and its weight
86
+ dropF: 0, dropH: 0, pitch: 0, roll: 0, flex: 0, bend: 0, lift: 0, fwd: 0, side: 0, groundW: 0,
87
+ headW: 0, headPos: new THREE.Vector3(), headLocalW: 0, headLocal: new THREE.Vector3(), headReach: 0,
88
+ headRaise: 0, neckReach: 0, headPitch: 0, headYaw: 0, headRoll: 0, headLimp: 0, headOmega: 1,
89
+ look: null, lookW: 1, jaw: 0, jawOmega: 12,
90
+ legs: [0, 1, 2, 3].map(() => ({ fold: 0, sit: 0, side: 0, spread: 0, tuck: 0, extend: 0, reach: 0, target: new THREE.Vector3() })),
91
+ tailLift: 0, tailGround: 0, tailSide: 0, tailCurl: 0, tailWag: 0, tailIdle: 1, tailStiff: 1,
92
+ earFlat: 0, earTwitch: 1, earSwivel: 0, eyelid: 0, breathRate: 1, breathAmp: 1, furRaise: 0,
93
+ };
94
+ }
95
+
96
+ function resetParams(P) {
97
+ P.gaitW = 1; P.legGait = 1; P.holdFeet = 0; P.bodyOmega = 1; P.speedScale = 1; P.frontH = 0; P.frontHW = 0;
98
+ P.dropF = P.dropH = P.pitch = P.roll = P.flex = P.bend = P.lift = P.fwd = P.side = P.groundW = 0;
99
+ P.headW = P.headLocalW = P.headReach = P.headRaise = P.neckReach = P.headPitch = P.headYaw = P.headRoll = P.headLimp = 0;
100
+ P.headOmega = 1; P.look = null; P.lookW = 1; P.jaw = 0; P.jawOmega = 12;
101
+ for (const l of P.legs) { l.fold = l.sit = l.side = l.spread = l.tuck = l.extend = l.reach = 0; }
102
+ P.tailLift = P.tailGround = P.tailSide = P.tailCurl = P.tailWag = 0; P.tailIdle = 1; P.tailStiff = 1;
103
+ P.earFlat = 0; P.earTwitch = 1; P.earSwivel = 0; P.eyelid = 0; P.breathRate = 1; P.breathAmp = 1; P.furRaise = 0;
104
+ }
105
+
106
+ // P.key = lerp(P.key, value, w)
107
+ const mx = (P, k, v, w) => { P[k] += (v - P[k]) * w; };
108
+ const mxLeg = (P, i, k, v, w) => { const l = P.legs[i]; l[k] += (v - l[k]) * w; };
109
+ const mxLegs = (P, k, vF, vH, w) => { for (let i = 0; i < 4; i++) mxLeg(P, i, k, i < 2 ? vF : vH, w); };
110
+
111
+ // ------------------------------------------------------------------------------------------------
112
+ // Posture poses: value sets applied with the posture weight (a function of time for sequences)
113
+ // ------------------------------------------------------------------------------------------------
114
+ const POSTURES = {
115
+ // haunches down, forelegs straight, tail on the ground curled to one side
116
+ sit: {
117
+ fadeIn: 1.0, fadeOut: 0.8,
118
+ apply(P, w, inst, L) {
119
+ const o = inst.tune;
120
+ const wH = smooth(0, 0.7, w), wF = smooth(0.25, 1, w);
121
+ mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
122
+ mx(P, 'dropH', o.dropH ?? 1.0, wH); mx(P, 'dropF', o.dropF ?? -0.04, wF);
123
+ // forelegs straight: the shoulders sit at the straight foreleg's height (metacarpus near
124
+ // vertical, radius + humerus at 96 % of their length: just inside the IK's soft reach limit)
125
+ const Lf = L.engine.cfg.legs[0];
126
+ mx(P, 'frontH', o.frontH ?? Lf.M.y + Lf.Lmc * Math.cos(0.6 * Lf.a0) + 0.955 * (Lf.Lh + Lf.Lr), wF); mx(P, 'frontHW', 1, wF);
127
+ mxLegs(P, 'sit', 1, 1, w);
128
+ mx(P, 'tailGround', 1, wH); mx(P, 'tailSide', inst.side * 1.3, wH);
129
+ mx(P, 'headRaise', 0.02, w); mx(P, 'groundW', 1, w); // (full ground fit: the rump rests on the ground)
130
+ },
131
+ },
132
+ // sternal: legs folded, belly down, head up
133
+ lie: {
134
+ fadeIn: 1.3, fadeOut: 1.0,
135
+ apply(P, w, inst) {
136
+ const o = inst.tune;
137
+ let wH = smooth(0.1, 0.9, w), wF = smooth(0, 0.8, w), fF = smooth(0, 0.6, w), fH = fF;
138
+ if (o.down || o.up) {
139
+ // which end goes down / gets up first (tune.down, tune.up: 'front' | 'hind'); horses kneel
140
+ // and rise forelegs first, cattle kneel first but rise hind first. Followed by a spring so a
141
+ // reversal half way through does not jump. (The spring lags the posture weight: the folded
142
+ // legs release behind the rising girdle, which a staging without the lag outran - the foreleg
143
+ // folded up into a Z and its elbow flipped over. The posture therefore ends, and is reported
144
+ // reached, only once these springs have settled: see stageSettled.)
145
+ const lead = inst.target > 0 ? o.down : o.up;
146
+ const a = inst.target > 0 ? smooth(0, 0.6, w) : smooth(0.35, 1, w), b = inst.target > 0 ? smooth(0.35, 1, w) : smooth(0, 0.65, w);
147
+ const tF = lead === 'front' ? a : lead === 'hind' ? b : wF, tH = lead === 'front' ? b : lead === 'hind' ? a : wH;
148
+ const dt = clamp(inst.t - (inst.tk ?? inst.t), 0, 0.1);
149
+ inst.tk = inst.t;
150
+ if (inst.kF === undefined) { inst.kF = tF; inst.kH = tH; inst.vF = 0; inst.vH = 0; }
151
+ const om = 9, st = (x, v, g) => { const a2 = om * om * (g - x) - 2 * om * v; return v + a2 * dt; };
152
+ inst.vF = st(inst.kF, inst.vF, tF); inst.kF += inst.vF * dt;
153
+ inst.vH = st(inst.kH, inst.vH, tH); inst.kH += inst.vH * dt;
154
+ // (held at the ends: a clamped weight keeps no speed, or it stuck there and then kicked)
155
+ if (inst.kF < 0 || inst.kF > 1) { inst.kF = clamp(inst.kF, 0, 1); inst.vF = 0; }
156
+ if (inst.kH < 0 || inst.kH > 1) { inst.kH = clamp(inst.kH, 0, 1); inst.vH = 0; }
157
+ wF = inst.kF; wH = inst.kH; fF = smooth(0, 0.8, wF); fH = smooth(0, 0.8, wH);
158
+ }
159
+ mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
160
+ mx(P, 'dropF', o.dropF ?? 1, wF); mx(P, 'dropH', o.dropH ?? 1, wH);
161
+ for (let i = 0; i < 4; i++) mxLeg(P, i, 'fold', 1, i < 2 ? fF : fH);
162
+ mx(P, 'groundW', 1, w);
163
+ mx(P, 'tailGround', 1, wH); mx(P, 'tailSide', inst.side * 1.0, wH);
164
+ mx(P, 'headRaise', -0.03, w); mx(P, 'breathRate', 0.8, w);
165
+ },
166
+ },
167
+ // lie, then curl (or roll onto the side), head down, eyes closed, slow breathing
168
+ sleep: {
169
+ fadeIn: 1.3, fadeOut: 1.2,
170
+ apply(P, w, inst, L) {
171
+ POSTURES.lie.apply(P, w, inst, L);
172
+ const c = smooth(1.0, 3.0, inst.t) * w;
173
+ const side = inst.side;
174
+ const e = L.engine, cfg = e.cfg;
175
+ if (inst.tune.style === 'lateral') {
176
+ mx(P, 'roll', side * 1.35, c);
177
+ mxLegs(P, 'side', 1, 1, c); mxLegs(P, 'spread', 0.3, 0.3, c);
178
+ mxLegs(P, 'fold', 0, 0, c);
179
+ mx(P, 'bend', side * 0.15, c);
180
+ } else {
181
+ mx(P, 'roll', side * 0.35, c);
182
+ mx(P, 'bend', side * 0.9, c);
183
+ // the tail wraps tightly round the rump and runs forward along the flank on the inside of
184
+ // the curl (tailSide swept it out sideways from the rump instead)
185
+ mx(P, 'tailSide', 0, c); mx(P, 'tailCurl', side * 3.0, c);
186
+ }
187
+ // head resting on the ground beside the forepaws (turned toward the curl)
188
+ const fr = e.fr, s = cfg.k;
189
+ const lat = inst.tune.style === 'lateral';
190
+ const hp = tv().copy(fr.neckBase).addScaledVector(fr.F, cfg.neckLen * (lat ? 0.75 : 0.45)).addScaledVector(fr.Lf, side * cfg.neckLen * (lat ? 0.1 : 0.9));
191
+ // (curled: the head rests on the forepaws, a little above the ground: the neck slopes instead of
192
+ // hanging straight down from the withers with the skull levered 100 deg against it)
193
+ hp.y = e.terrainH(hp.x, hp.z) + (lat ? 0.085 : 0.12) * s;
194
+ P.headPos.lerp(hp, c / Math.max(1e-3, P.headW + c)); mx(P, 'headW', 1, c);
195
+ mx(P, 'headYaw', side * (lat ? 0.1 : 1.1), c); mx(P, 'headPitch', lat ? 0.2 : 0.35, c);
196
+ mx(P, 'headRoll', side * (inst.tune.style === 'lateral' ? 1.2 : 0.5), c); mx(P, 'headLimp', 0.3, c);
197
+ mx(P, 'eyelid', 1, smooth(1.5, 3.2, inst.t) * w);
198
+ mx(P, 'breathRate', 0.45, c); mx(P, 'breathAmp', 1.4, c);
199
+ mx(P, 'earFlat', 0.25, c); mx(P, 'earTwitch', 0.35, c); mx(P, 'tailIdle', 0.1, c);
200
+ mx(P, 'lookW', 0, c);
201
+ },
202
+ },
203
+ // legs buckle, falls on its side, settles on the terrain; stays down until 'stand'
204
+ death: {
205
+ fadeIn: 0.25, fadeOut: 1.6,
206
+ apply(P, w, inst, L) {
207
+ const t = inst.t, side = inst.side, e = L.engine, cfg = e.cfg, s = cfg.k;
208
+ const buckle = smooth(0, 0.35, t);
209
+ const fall = clamp((t - 0.3) / 0.55, 0, 1);
210
+ const fallE = fall * fall; // accelerating topple
211
+ const settle = fall >= 1 ? Math.exp(-(t - 0.85) * 6) * Math.sin((t - 0.85) * 18) * 0.06 : 0;
212
+ // killed on the move: the body carries its momentum (it brakes like the living animal) and the
213
+ // legs keep striding under it while it buckles, folding once it is down (no dragged feet)
214
+ const mv = e.air.active ? 0 : smooth(0.08, 0.45, e.speed / cfg.sk) * (1 - smooth(0.3, 0.7, t));
215
+ mx(P, 'gaitW', 0, w * (1 - mv)); mx(P, 'legGait', 0, w * (1 - mv)); mx(P, 'bodyOmega', 1.6, w);
216
+ const kb = 1 - mv; // (the body does not buckle while the legs still stride under it)
217
+ mx(P, 'dropF', lerp(0.55 * buckle * kb, 1, fallE), w); mx(P, 'dropH', lerp(0.45 * buckle * kb, 1, fallE), w);
218
+ mx(P, 'roll', side * (1.42 * fallE - settle), w);
219
+ mx(P, 'groundW', fallE, w);
220
+ mxLegs(P, 'fold', 0.6 * buckle * (1 - fallE), 0.6 * buckle * (1 - fallE), w);
221
+ mxLegs(P, 'side', fallE, fallE, w); mxLegs(P, 'spread', smooth(0.8, 1.6, t), smooth(0.8, 1.6, t), w);
222
+ // head and neck lie on the terrain
223
+ const fr = e.fr;
224
+ const hp = tv().copy(fr.neckBase).addScaledVector(fr.F, cfg.neckLen * 0.8);
225
+ hp.y = e.terrainH(hp.x, hp.z) + 0.092 * s; // occiput height with the skull on its side (half-width ~7 cm + cheek)
226
+ const hw = smooth(0.35, 1.0, t);
227
+ P.headPos.lerp(hp, hw / Math.max(1e-3, P.headW + hw)); mx(P, 'headW', 1, hw * w);
228
+ mx(P, 'headRoll', side * 1.3, hw * w); mx(P, 'headPitch', 0.15, hw * w); mx(P, 'headLimp', 0.5, hw * w);
229
+ mx(P, 'headOmega', 1.8, w);
230
+ mx(P, 'jaw', 0.1, hw * w); mx(P, 'eyelid', 0.55, smooth(0.2, 1.2, t) * w);
231
+ mx(P, 'tailGround', 1, w); mx(P, 'tailIdle', 0, w); mx(P, 'tailSide', side * 0.6, w); mx(P, 'tailStiff', 0.5, w);
232
+ mx(P, 'breathAmp', 0, w); mx(P, 'breathRate', 0, w);
233
+ mx(P, 'earFlat', 0.4, w); mx(P, 'earTwitch', 0, w); mx(P, 'lookW', 0, w);
234
+ },
235
+ },
236
+ };
237
+
238
+ // ------------------------------------------------------------------------------------------------
239
+ // One-shot actions
240
+ // ------------------------------------------------------------------------------------------------
241
+ function mouthGroundTarget(e, out, pitchA, levelFn, ahead) {
242
+ // occiput position that puts the mouth on the ground (or water) ahead of the forefeet
243
+ const cfg = e.cfg, fr = e.fr;
244
+ const g = tv();
245
+ const FL = e.legs[0], FR = e.legs[1];
246
+ // head frame: yaw = heading, pitch = pitchA (nose down); the lower of mouth and nose touches
247
+ const cp = Math.cos(pitchA), sp = Math.sin(pitchA);
248
+ const qm = cfg.mouthLocal, qn = cfg.noseLocal;
249
+ const ym = qm.y * cp - qm.z * sp, yn = qn.y * cp - qn.z * sp;
250
+ const zm = qm.y * sp + qm.z * cp, zn = qn.y * sp + qn.z * cp;
251
+ // the lower of mouth and nose touches; blended over 1 cm of height difference so the target does
252
+ // not jump from one point to the other as the head pitches (tearing jerks)
253
+ const k = smooth(-0.01 * cfg.k, 0.01 * cfg.k, ym - yn); // 0: mouth lower, 1: nose lower
254
+ const x = lerp(qm.x, qn.x, k), y = lerp(ym, yn, k), z = lerp(zm, zn, k);
255
+ // the mouth `ahead` leg lengths ahead of the forefeet (0.5; nearer, the neck had to fold back under
256
+ // itself to reach it and the head bent 80-140 deg against the neck; a short neck on long legs
257
+ // (wolf) needs more, or the neck hangs vertically with the head levered against it)
258
+ g.copy(FL.plant).add(FR.plant).multiplyScalar(0.5).addScaledVector(fr.F, (ahead ?? 0.5) * cfg.legLen);
259
+ g.y = levelFn(g.x, g.z);
260
+ const sh = Math.sin(e.heading), ch = Math.cos(e.heading);
261
+ out.set(g.x - (x * ch + z * sh), g.y - y + 0.015 * cfg.k, g.z - (-x * sh + z * ch));
262
+ return out;
263
+ }
264
+
265
+ const ONESHOTS = {
266
+ jump: {
267
+ start(inst, L) {
268
+ const e = L.engine, cfg = e.cfg, o = inst.tune;
269
+ inst.run = smooth(1.5, 5, e.speed / cfg.sk);
270
+ inst.tc = lerp(0.3, 0.1, inst.run) * cfg.sk;
271
+ const h = (o.height ?? 0.6 * cfg.unit) * cfg.s * lerp(1, 0.7, inst.run);
272
+ inst.vy = Math.sqrt(2 * 9.81 * h);
273
+ let T = (2 * inst.vy) / 9.81;
274
+ let dist = (o.distance ?? 2.5 * cfg.unit) * cfg.s;
275
+ const target = inst.opts.target;
276
+ if (target) dist = clamp(Math.hypot(target.x - e.pos.x, target.z - e.pos.z), 0.3 * cfg.k, dist * 2);
277
+ // (a target the run-up would overshoot: a lower, shorter flight, down to 0.35x the height)
278
+ if (target && e.speed * T > dist) { T = Math.max(Math.sqrt(0.35) * T, dist / e.speed); inst.vy = 4.905 * T; }
279
+ // the flight covers at least the species' jump distance (or the target's): a run-up slower than
280
+ // that is sped up in the push-off, a faster one carries its speed (a longer, flatter running
281
+ // jump). (It was lerp(distance / T, speed, run): from a trot the jump fell short of a standing
282
+ // jump, and from a canter on the distance was ignored.)
283
+ inst.vh = Math.max(e.speed, dist / T);
284
+ // push-off: the crouched body extends and gains its launch speed over tp before it leaves the
285
+ // ground (an instant launch from the crouch yanked every leg in one frame)
286
+ inst.tp = lerp(0.14, 0.08, inst.run) * cfg.sk;
287
+ inst.v0 = e.speed;
288
+ inst.phase = 'crouch';
289
+ inst.dur = Infinity;
290
+ },
291
+ update(inst, dt, L) {
292
+ const e = L.engine;
293
+ if (inst.phase === 'crouch' && inst.t >= inst.tc) inst.phase = 'push';
294
+ if (inst.phase === 'push') {
295
+ const x = clamp((inst.t - inst.tc) / inst.tp, 0, 1);
296
+ e.speedOverride = lerp(inst.v0, inst.vh, x * x * (3 - 2 * x));
297
+ if (x >= 1) { e.speedOverride = null; inst.phase = 'air'; inst.tAir = inst.t; e.takeOff(inst.vy, inst.vh); }
298
+ }
299
+ if (inst.phase === 'land' && inst.t - inst.tLand > 0.45) return true;
300
+ if (inst.phase === 'air' && inst.t - inst.tAir > 4) { e.touchDown(); }
301
+ return false;
302
+ },
303
+ onLand(inst) { inst.phase = 'land'; inst.tLand = inst.t; },
304
+ apply(P, w, inst, L) {
305
+ const e = L.engine;
306
+ if (inst.phase === 'crouch' || inst.phase === 'push') {
307
+ const c = smooth(0, inst.tc, inst.t) * (1 - inst.run * 0.6);
308
+ // push: from the crouch to an extended body (dropF / dropH < 0 raise the girdles)
309
+ const x = inst.phase === 'push' ? smooth(0, 1, (inst.t - inst.tc) / inst.tp) : 0;
310
+ mx(P, 'dropF', lerp(0.35, -0.1, x), c); mx(P, 'dropH', lerp(0.45, -0.12, x), c); mx(P, 'pitch', lerp(-0.05, 0.12, x), c);
311
+ mx(P, 'bodyOmega', lerp(1, 2.2, x), c);
312
+ // standing: the planted feet stay planted while it pushes off (holdFeet: no catch-up steps
313
+ // either, the legs extend over them); from a run the gait steps on until take-off (held
314
+ // at c (1 - run), a canter's push-off dragged a bear's planted forefoot 45 cm behind)
315
+ if (inst.phase === 'push') { const hold = c * (1 - smooth(0, 0.3, inst.run)); mx(P, 'legGait', 0, hold); mx(P, 'holdFeet', 1, hold); }
316
+ mx(P, 'earFlat', 0.3, c);
317
+ } else if (inst.phase === 'air') {
318
+ const T = (2 * inst.vy) / 9.81, x = clamp((inst.t - inst.tAir) / T, 0, 1);
319
+ const k0 = smooth(0, 0.12, inst.t - inst.tAir);
320
+ // (legGait continues from the push-off, where it was already held down)
321
+ mx(P, 'gaitW', 0, k0); mx(P, 'legGait', 0, Math.max(k0, (1 - inst.run * 0.6) * (1 - smooth(0, 0.3, inst.run))));
322
+ const ext = x < 0.25 ? 1 - smooth(0, 0.25, x) * 0.9 : x < 0.65 ? 0.1 : 0.1 + smooth(0.65, 1, x) * 0.7;
323
+ mxLegs(P, 'tuck', 1, 1, k0);
324
+ for (const l of P.legs) l.extend = ext;
325
+ // body pitch in flight, continuous with the push-off's (0.12 at take-off) and held into the
326
+ // landing (inst.pitchAir): a step in P.pitch moves the girdle-height correction of the body
327
+ // frame, which dropped a horse 3 cm in the touchdown frame (and lifted it at take-off)
328
+ const pf = lerp(0.22, -0.18, smooth(0.1, 0.95, x)) * (1 - inst.run * 0.5);
329
+ inst.pitchAir = lerp(0.12 * (1 - inst.run * 0.6), pf, smooth(0, 0.2, x));
330
+ mx(P, 'pitch', inst.pitchAir, 1);
331
+ mx(P, 'tailLift', 0.25, 1); mx(P, 'headPitch', lerp(-0.1, 0.15, x), 1);
332
+ } else {
333
+ const x = (inst.t - inst.tLand) / 0.45;
334
+ const a = Math.sin(Math.PI * Math.min(1, x * 1.6)) * (1 - x);
335
+ const ld = inst.tune.land || [0.3, 0.25]; // landing crouch [front, hind] (jump tuning: long-legged runners land straighter)
336
+ mx(P, 'dropF', ld[0], a); mx(P, 'dropH', ld[1], a);
337
+ const k = 1 - smooth(0, 0.35, x);
338
+ mxLegs(P, 'tuck', 1, 1, k);
339
+ for (const l of P.legs) l.extend = lerp(l.extend, 0.8, k);
340
+ mx(P, 'legGait', 0, k); mx(P, 'gaitW', 0, k);
341
+ // (the flight's pitch eases out over the landing)
342
+ mx(P, 'pitch', (inst.pitchAir || 0) * (1 - smooth(0, 0.6, x)), 1);
343
+ }
344
+ void e;
345
+ },
346
+ },
347
+
348
+ attack: {
349
+ start(inst, L) {
350
+ const e = L.engine, o = inst.tune;
351
+ inst.style = o.style || 'bite-lunge';
352
+ inst.kick = inst.opts.kick || o.kick || 'double'; // play('attack', { kick: 'rear' }) picks per call
353
+ inst.run = smooth(1.5, 4, e.speed / e.cfg.sk);
354
+ if (inst.style === 'pounce' && inst.run > 0.5) inst.style = 'bite-lunge';
355
+ inst.dur = { 'bite-lunge': 0.95, pounce: 1.8, charge: 1.1, headbutt: 1.1, kick: inst.kick === 'rear' ? 1.8 : 1.2 }[inst.style] || 1;
356
+ inst.hitAt = { 'bite-lunge': 0.5, pounce: 0, charge: 0.55, headbutt: 0.55, kick: inst.kick === 'rear' ? 1.05 : 0.5 }[inst.style];
357
+ inst.hit = false;
358
+ inst.target = inst.opts.target ? inst.opts.target.clone() : null;
359
+ if (inst.style === 'pounce') inst.jumped = false;
360
+ },
361
+ update(inst, dt, L) {
362
+ const e = L.engine, cfg = e.cfg;
363
+ if (inst.style === 'pounce' && !inst.jumped && inst.t >= 0.6) {
364
+ inst.jumped = true;
365
+ const h = 0.3 * cfg.k, vy = Math.sqrt(2 * 9.81 * h), T = 2 * vy / 9.81;
366
+ let dist = 1.6 * cfg.k;
367
+ if (inst.target) dist = clamp(Math.hypot(inst.target.x - e.pos.x, inst.target.z - e.pos.z) - 0.35 * cfg.legLen, 0.4 * cfg.k, 3.5 * cfg.k);
368
+ inst.T = T;
369
+ e.takeOff(vy, Math.max(e.speed, dist / T));
370
+ }
371
+ if (inst.style === 'pounce' && inst.landT !== undefined && inst.t - inst.landT > 0.7) return true;
372
+ if (inst.style === 'pounce' && inst.jumped && inst.landT === undefined && inst.t > 4) e.touchDown();
373
+ if (!inst.hit && inst.hitAt && inst.t >= inst.hitAt) this.strike(inst, L);
374
+ return inst.style !== 'pounce' && inst.t >= inst.dur;
375
+ },
376
+ onLand(inst, L) {
377
+ if (inst.style !== 'pounce') return;
378
+ inst.landT = inst.t;
379
+ L.engine.speed *= 0.25; // pins the prey: no run-out
380
+ this.strike(inst, L);
381
+ },
382
+ strike(inst, L) {
383
+ const e = L.engine;
384
+ inst.hit = true;
385
+ let p;
386
+ if (inst.style === 'kick') p = inst.kick === 'rear' ? e.legs[0].contact.clone() : e.legs[2].contact.clone().lerp(e.legs[3].contact, 0.5);
387
+ else p = new THREE.Vector3().copy(e.cfg.mouthLocal).applyQuaternion(e.headPose.quaternion).add(e.headPose.position);
388
+ const dir = inst.target ? inst.target.clone().sub(e.headPose.position).setY(0).normalize() : e.forward(e.heading, new THREE.Vector3());
389
+ if (inst.style === 'kick' && inst.kick !== 'rear') dir.negate();
390
+ e.emit('attackHit', { position: p, direction: dir, style: inst.style });
391
+ },
392
+ apply(P, w, inst, L) {
393
+ const e = L.engine, cfg = e.cfg, fr = e.fr, s = cfg.k, t = inst.t, run = inst.run, still = 1 - run;
394
+ const lookAt = inst.target;
395
+ if (lookAt) { P.look = lookAt; mx(P, 'lookW', 1, w); }
396
+ mx(P, 'earFlat', 1, w * smooth(0, 0.15, t)); mx(P, 'earTwitch', 0, w);
397
+ const reachF = (x, amount, fwd, up) => {
398
+ // forepaws strike ahead (toward the target when given)
399
+ for (let i = 0; i < 2; i++) {
400
+ const leg = e.legs[i], Ld = leg.def, pl = P.legs[i];
401
+ const tp = tv();
402
+ if (lookAt) tp.copy(lookAt).addScaledVector(fr.Lf, Ld.s * Ld.cx * 1.2);
403
+ else e.bodyToWorld(Ld.cx * Ld.s * 1.2, 0, cfg.zS + fwd * Ld.len, e.heading, e.pos, tp);
404
+ tp.y = Math.max(tp.y, e.terrainH(tp.x, tp.z)) + up * Ld.len;
405
+ const k = amount * x;
406
+ pl.target.lerp(tp, k / Math.max(1e-3, pl.reach + k));
407
+ mxLeg(P, i, 'reach', 1, k);
408
+ }
409
+ };
410
+ if (inst.style === 'bite-lunge') {
411
+ const crouch = smooth(0, 0.3, t) * (1 - smooth(0.32, 0.45, t)) * still;
412
+ const lunge = smooth(0.3, 0.48, t) * (1 - smooth(0.6, 0.95, t));
413
+ mx(P, 'dropF', 0.35, crouch * w); mx(P, 'dropH', 0.3, crouch * w); mx(P, 'fwd', -0.06, crouch * w);
414
+ mx(P, 'fwd', 0.22 * (1 - run * 0.5), lunge * w); mx(P, 'neckReach', 0.1, lunge * w); mx(P, 'headRaise', -0.03, lunge * w);
415
+ mx(P, 'jaw', 0.65, w * smooth(0.25, 0.42, t) * (1 - smooth(0.46, 0.52, t)));
416
+ mx(P, 'jawOmega', 30, w);
417
+ mx(P, 'gaitW', 1 - 0.7 * still, w * (crouch + lunge > 0 ? 1 : 0));
418
+ // (tune.paws: false for canids, whose forefeet stay planted while the head and neck drive the
419
+ // bite; felids and rodents reach and grab with the forepaws)
420
+ if (inst.tune.paws === false) {
421
+ const snap = smooth(0.55, 0.7, t) * (1 - smooth(0.75, 0.95, t));
422
+ mx(P, 'neckReach', 0.17, lunge * w); mx(P, 'headRaise', -0.07, lunge * w); mx(P, 'headPitch', 0.18, lunge * w);
423
+ mx(P, 'dropF', 0.18, lunge * w * still); mx(P, 'fwd', -0.05, snap * w * still);
424
+ } else if (still > 0.2) reachF(lunge * w, 0.8 * still, 0.55, 0.35);
425
+ } else if (inst.style === 'pounce') {
426
+ const crouch = smooth(0, 0.35, t) * (inst.jumped ? 0 : 1);
427
+ const wig = inst.jumped ? 0 : Math.sin(t * 28) * smooth(0.25, 0.4, t) * 0.02;
428
+ mx(P, 'dropF', 0.55, crouch * w); mx(P, 'dropH', 0.4, crouch * w); mx(P, 'side', wig, w);
429
+ mx(P, 'headRaise', -0.04, crouch * w); mx(P, 'tailLift', -0.2, crouch * w);
430
+ if (inst.jumped && inst.landT === undefined) {
431
+ const x = clamp((t - 0.6) / (inst.T || 0.5), 0, 1);
432
+ const k0 = smooth(0, 0.12, t - 0.6) * w;
433
+ mx(P, 'gaitW', 0, k0); mx(P, 'legGait', 0, k0);
434
+ mxLeg(P, 2, 'tuck', 1, k0); mxLeg(P, 3, 'tuck', 1, k0);
435
+ P.legs[2].extend = P.legs[3].extend = 1 - smooth(0, 0.4, x);
436
+ reachF(k0, 1, 0.6, lerp(0.35, 0.05, x));
437
+ // (flight pitch, held into the landing: see the jump)
438
+ inst.pitchAir = lerp(0.2, -0.15, x);
439
+ mx(P, 'pitch', inst.pitchAir, w); mx(P, 'jaw', 0.5, w * smooth(0.3, 0.8, x));
440
+ } else if (inst.landT !== undefined) {
441
+ const x = (t - inst.landT) / 0.7;
442
+ const k = 1 - smooth(0.3, 1, x);
443
+ mx(P, 'pitch', (inst.pitchAir || 0) * (1 - smooth(0, 0.4, x)), w);
444
+ reachF(k * w, 1, 0.55, 0);
445
+ mx(P, 'dropF', 0.5, k * w); mx(P, 'dropH', 0.2, k * w);
446
+ mx(P, 'jaw', 0.55 * (1 - smooth(0, 0.15, x)), w); mx(P, 'jawOmega', 30, w);
447
+ mx(P, 'headRaise', -0.06, k * w); mx(P, 'neckReach', 0.06, k * w);
448
+ }
449
+ } else if (inst.style === 'charge' || inst.style === 'headbutt') {
450
+ const low = smooth(0, 0.35, t) * (1 - smooth(0.7, 1.1, t));
451
+ const lunge = smooth(0.35, 0.55, t) * (1 - smooth(0.6, 1.0, t));
452
+ mx(P, 'headPitch', 0.7, low * w); mx(P, 'headRaise', -0.12, low * w); mx(P, 'dropF', 0.2, low * w * still);
453
+ mx(P, 'fwd', 0.3, lunge * w); mx(P, 'neckReach', 0.08, lunge * w);
454
+ mx(P, 'fwd', -0.08, low * (1 - lunge) * w * still);
455
+ } else if (inst.style === 'kick') {
456
+ if (inst.kick === 'rear') {
457
+ const up = smooth(0.1, 0.7, t) * (1 - smooth(1.2, 1.75, t));
458
+ const strike = smooth(0.7, 1.05, t) * (1 - smooth(1.15, 1.65, t));
459
+ mx(P, 'dropF', -1.4, up * w); mx(P, 'dropH', 0.25, up * w); mx(P, 'legGait', 0, up * w); mx(P, 'gaitW', 0, up * w);
460
+ mxLegs(P, 'tuck', 1, 0, up * w);
461
+ for (let i = 0; i < 2; i++) P.legs[i].extend = lerp(0.1, 1, strike);
462
+ mx(P, 'headRaise', 0.1, up * w);
463
+ } else {
464
+ const buck = smooth(0.15, 0.4, t) * (1 - smooth(0.6, 1.1, t));
465
+ const kick = smooth(0.35, 0.5, t) * (1 - smooth(0.6, 0.9, t));
466
+ mx(P, 'dropF', 0.15, buck * w); mx(P, 'dropH', -0.25, buck * w); mx(P, 'headPitch', 0.4, buck * w);
467
+ for (let i = 2; i < 4; i++) {
468
+ const leg = e.legs[i], Ld = leg.def, pl = P.legs[i];
469
+ // (tune.kickReach: [back, up] in leg lengths behind the hip / above the ground)
470
+ const kr = inst.tune.kickReach || KICK_REACH;
471
+ const tp = e.bodyToWorld(Ld.cx * Ld.s, 0, cfg.zH - Ld.len * kr[0], e.heading, e.pos, tv());
472
+ tp.y = e.terrainH(tp.x, tp.z) + Ld.len * kr[1];
473
+ pl.target.copy(tp);
474
+ mxLeg(P, i, 'reach', 1, kick * w);
475
+ }
476
+ mx(P, 'legGait', 0.4, buck * w);
477
+ }
478
+ }
479
+ void s;
480
+ },
481
+ },
482
+
483
+ hit: {
484
+ start(inst, L) {
485
+ const e = L.engine;
486
+ const d = inst.opts.direction ? inst.opts.direction.clone().setY(0) : e.forward(e.heading, new THREE.Vector3()).negate();
487
+ if (d.lengthSq() < 1e-6) d.set(0, 0, -1);
488
+ d.normalize();
489
+ inst.dir = d;
490
+ inst.dur = 0.75;
491
+ // body-local components: + left, + forward
492
+ inst.lx = d.dot(e.left(e.heading, new THREE.Vector3()));
493
+ inst.lz = d.dot(e.forward(e.heading, new THREE.Vector3()));
494
+ e.push.addScaledVector(d, 0.9 * e.cfg.sk * (inst.opts.strength ?? 1));
495
+ },
496
+ update(inst) { return inst.t >= inst.dur; },
497
+ apply(P, w, inst) {
498
+ const t = inst.t;
499
+ // recoil: peak at ~0.12 s, a held 35 % until 0.3 s, then decays (continuous: the old
500
+ // sin-then-exp form jumped from 0 to 0.35 at t = 0.3 s)
501
+ const f = smooth(0, 0.12, t) * (t < 0.12 ? 1 : 0.35 + 0.65 * Math.exp(-(t - 0.12) / 0.06)) * Math.exp(-Math.max(0, t - 0.3) * 6);
502
+ const k = f * w;
503
+ mx(P, 'roll', inst.lx * 0.16, k); mx(P, 'bend', -inst.lx * 0.25, k);
504
+ mx(P, 'pitch', inst.lz * 0.08, k); mx(P, 'dropF', 0.18, k); mx(P, 'dropH', 0.12, k);
505
+ P.headLocal.set(inst.lx * 0.06, 0.02, inst.lz * 0.06 - 0.03); mx(P, 'headLocalW', 1, k);
506
+ mx(P, 'headYaw', -inst.lx * 0.35, k); mx(P, 'headPitch', -0.15, k);
507
+ mx(P, 'earFlat', 1, w * (1 - smooth(0.4, 0.75, t))); mx(P, 'eyelid', 0.7, k);
508
+ mx(P, 'jaw', 0.18, k); mx(P, 'tailLift', -0.15, k);
509
+ },
510
+ },
511
+
512
+ eat: {
513
+ start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 6); inst.fade = 0.8; },
514
+ update(inst) { return inst.t >= inst.dur; },
515
+ apply(P, w, inst, L) { applyGround(P, w, inst, L, 'eat'); },
516
+ },
517
+ drink: {
518
+ start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 5); inst.fade = 0.8; },
519
+ update(inst) { return inst.t >= inst.dur; },
520
+ apply(P, w, inst, L) { applyGround(P, w, inst, L, 'drink'); },
521
+ },
522
+ };
523
+
524
+ function applyGround(P, w, inst, L, kind) {
525
+ const e = L.engine, t = inst.t, o = inst.tune;
526
+ const water = e.water;
527
+ const level = kind === 'drink' && water ? (x, z) => { const wl = water(x, z); const g = e.terrainH(x, z); return Number.isFinite(wl) ? Math.max(g, wl) : g; } : (x, z) => e.terrainH(x, z);
528
+ let pitchA = o.pitch ?? 1.3; // nose down, roughly in line with the lowered neck (tune.pitch: rad from the head's bind pose)
529
+ let bob = 0, jaw = 0, yaw = 0;
530
+ if (kind === 'eat') {
531
+ if ((o.style || 'tear') === 'graze') {
532
+ // grazing: bite, a small pull, chew with the head down
533
+ const c = (t * 0.9) % 1;
534
+ bob = -0.02 * Math.sin(Math.PI * smooth(0, 0.25, c));
535
+ jaw = 0.12 + 0.1 * Math.sin(t * 9.5);
536
+ yaw = 0.12 * Math.sin(t * 0.7);
537
+ } else {
538
+ // predators: tearing jerks (pull up and back, twist), jaw clamps, then reset
539
+ const c = (t * 0.75) % 1;
540
+ const jerk = smooth(0.35, 0.5, c) * (1 - smooth(0.6, 0.95, c));
541
+ bob = 0.07 * jerk;
542
+ pitchA -= 0.35 * jerk;
543
+ jaw = 0.05 + 0.3 * smooth(0, 0.15, c) * (1 - smooth(0.25, 0.4, c));
544
+ yaw = 0.25 * jerk * Math.sin(Math.floor(t * 0.75) * 2.3);
545
+ }
546
+ } else {
547
+ // lapping
548
+ jaw = 0.1 + 0.08 * Math.max(0, Math.sin(t * 3 * 6.283));
549
+ bob = 0.006 * Math.sin(t * 3 * 6.283);
550
+ pitchA = o.pitch ?? 1.3;
551
+ }
552
+ const hp = mouthGroundTarget(e, tv(), pitchA, level, o.ahead);
553
+ hp.y += bob * e.cfg.k;
554
+ P.headPos.copy(hp);
555
+ const k = w;
556
+ mx(P, 'headW', 1, k); mx(P, 'headReach', 1, k); mx(P, 'headPitch', pitchA, k); mx(P, 'headYaw', yaw, k);
557
+ mx(P, 'lookW', 0, k); mx(P, 'headOmega', 2.2, k);
558
+ mx(P, 'jaw', jaw, k); mx(P, 'jawOmega', 25, k);
559
+ mx(P, 'gaitW', 0, k);
560
+ mx(P, 'dropF', o.dropF ?? 0.1, k); mx(P, 'dropH', o.dropH ?? 0, k);
561
+ mx(P, 'earTwitch', 1.5, k);
562
+ mx(P, 'speedScale', 0, k);
563
+ void L;
564
+ }
565
+
566
+ // in-place filter (no per-frame allocation)
567
+ function compact(arr, keep) {
568
+ let j = 0;
569
+ for (let i = 0; i < arr.length; i++) if (keep(arr[i])) arr[j++] = arr[i];
570
+ arr.length = j;
571
+ }
572
+ // a staged kneel (POSTURES.lie with tune.down / up) has settled when its fore / hind springs have
573
+ // reached the posture's end (1 lying, 0 standing) and stopped: the posture ends, and is reported
574
+ // reached, only then (released earlier, the springs' last few per cent of the lying drop and of the
575
+ // folded legs vanished in one frame: a pop of the forelegs as a cow finished getting up)
576
+ const stageSettled = (p) => p.kF === undefined || ((p.target > 0 ? 2 - p.kF - p.kH : p.kF + p.kH) < 2e-3 && Math.abs(p.vF) + Math.abs(p.vH) < 2e-2);
577
+ const keepPosture = (p) => p.target > 0 || p.w > 0 || !stageSettled(p);
578
+ const keepOneshot = (a) => !(a.stopping && a.w <= 0);
579
+ const isHolding = (p) => p.target > 0 || p.w > 0.02;
580
+ const isRunning = (a) => !a.stopping;
581
+ const isTargeted = (p) => p.target > 0;
582
+ const isBusy = (a) => !a.stopping && a.name !== 'hit';
583
+ const isAirborneAction = (a) => a.name === 'jump' || a.name === 'attack' || !!a.airborne;
584
+ const isDead = (p) => p.name === 'death' && p.target > 0;
585
+
586
+ // ------------------------------------------------------------------------------------------------
587
+ export class ActionLayer {
588
+ constructor(engine) {
589
+ this.engine = engine;
590
+ this.tuning = engine.cfg.actions || {};
591
+ this.names = ['idle', 'jump', 'sit', 'lie', 'sleep', 'eat', 'drink', 'attack', 'hit', 'death', 'stand'];
592
+ // species actions (species.motion.hooks.actions): { name: { kind: 'oneshot' | 'posture', airborne?,
593
+ // needsStand?, fade?, start, update, apply, onLand } }; they may replace built-ins of the same name
594
+ this.hooks = engine.hooks || {};
595
+ this.oneshotDefs = { ...ONESHOTS };
596
+ this.postureDefs = { ...POSTURES };
597
+ for (const [n, d] of Object.entries(this.hooks.actions || {})) {
598
+ if (d.kind === 'posture') this.postureDefs[n] = d; else this.oneshotDefs[n] = d;
599
+ if (!this.names.includes(n)) this.names.push(n);
600
+ }
601
+ this.postures = []; // { name, t, w, target, fadeIn, fadeOut, resolve, side }
602
+ this.oneshots = []; // { name, t, dur, w, stopping, resolve }
603
+ this.posture = 'stand';
604
+ this.standReq = null;
605
+ this.idle = { t: 0, lookT: 0, look: new THREE.Vector3(), hasLook: false, stepT: 4, w: 0, shiftPh: 0 };
606
+ }
607
+
608
+ holdsStill() {
609
+ return this.postures.some(isHolding);
610
+ }
611
+ dead() { return this.postures.some(isDead); }
612
+ // automatic stand-up (the animal is asked to move): never revives a dead animal
613
+ requestStand() { if (!this.standReq && !this.dead()) this.play('stand'); }
614
+ // a one-shot committed to the air (between push-off and touchdown): not interrupted, not stopped
615
+ committed(a) {
616
+ const e = this.engine;
617
+ return !a.stopping && ((e.air.active && isAirborneAction(a)) || (a.name === 'jump' && a.phase === 'push'));
618
+ }
619
+ // end a one-shot: it fades out, then its promise resolves with the reason
620
+ end(a, reason) {
621
+ if (a.stopping) return;
622
+ a.stopping = true; a.reason = reason;
623
+ this.oneshotDefs[a.name]?.end?.(a, this);
624
+ }
625
+ current() {
626
+ const o = this.oneshots.find(isRunning);
627
+ if (o) return o.name;
628
+ const p = this.postures.find(isTargeted);
629
+ return p ? p.name : this.standReq ? 'stand' : null;
630
+ }
631
+ clear() {
632
+ for (const a of [...this.postures, ...this.oneshots]) { a.resolve?.('stopped'); a.resolveIn?.('stopped'); }
633
+ this.postures.length = 0; this.oneshots.length = 0;
634
+ }
635
+
636
+ play(name, opts = {}) {
637
+ const e = this.engine;
638
+ if (name === 'idle') name = 'stand';
639
+ if (name === 'stand') {
640
+ if (!this.postures.length) return Promise.resolve('done');
641
+ for (const p of this.postures) { if (p.target > 0) { p.target = 0; p.resolveIn?.('interrupted'); } }
642
+ e.emit('actionStart', { name: 'stand' });
643
+ if (this.standReq) return this.standReq.promise;
644
+ let resolve;
645
+ const promise = new Promise((r) => { resolve = r; });
646
+ this.standReq = { promise, resolve };
647
+ return promise;
648
+ }
649
+ if (this.postureDefs[name]) {
650
+ const cur = this.postures.find((p) => p.target > 0);
651
+ if (cur && cur.name === 'death' && name !== 'death') return Promise.resolve('refused');
652
+ if (cur && cur.name === name) return cur.promiseIn;
653
+ // (in the air only death is taken: the jump lands first, the body collapses as it comes down)
654
+ if (name !== 'death' && this.oneshots.some((a) => this.committed(a))) return Promise.resolve('refused');
655
+ for (const p of this.postures) { if (p.target > 0) { p.target = 0; p.resolveIn?.('interrupted'); } }
656
+ if (this.standReq) { this.standReq.resolve('interrupted'); this.standReq = null; }
657
+ // one-shots other than hit end when the animal lies down
658
+ for (const a of this.oneshots) if (a.name !== 'hit' && !this.committed(a) && !this.oneshotDefs[a.name]?.overlay) this.end(a, 'interrupted');
659
+ const def = this.postureDefs[name];
660
+ let side = opts.side ?? (e.rand() < 0.5 ? -1 : 1);
661
+ if (opts.direction && name === 'death') {
662
+ // fall away from the blow
663
+ const d = opts.direction;
664
+ side = (d.x * Math.cos(e.heading) - d.z * Math.sin(e.heading)) > 0 ? -1 : 1;
665
+ }
666
+ // continue a lying curl side when going lie -> sleep
667
+ const prev = this.postures.find((p) => p.name === 'lie' || p.name === 'sleep');
668
+ if (prev && opts.side === undefined) side = prev.side;
669
+ const inst = { name, t: 0, w: 0, target: 1, fadeIn: def.fadeIn / (opts.speed || 1), fadeOut: def.fadeOut, side, opts, tune: this.tuning[name] || {}, reached: false };
670
+ // a posture of the same family keeps its time (lie -> sleep keeps lying)
671
+ if (prev && name === 'sleep') {
672
+ inst.w = prev.w; prev.w = 0; prev.target = 0; this.postures = this.postures.filter((p) => p !== prev);
673
+ // (the staged kneel carries on: its springs' state, see POSTURES.lie)
674
+ if (prev.kF !== undefined) { inst.kF = prev.kF; inst.kH = prev.kH; inst.vF = prev.vF; inst.vH = prev.vH; }
675
+ }
676
+ inst.promiseIn = new Promise((r) => { inst.resolveIn = r; });
677
+ this.postures.push(inst);
678
+ e.emit('actionStart', { name });
679
+ if (name === 'death') e.emit('death', { position: e.pos.clone() });
680
+ return inst.promiseIn;
681
+ }
682
+ const def = this.oneshotDefs[name];
683
+ if (!def) return Promise.reject(new Error(`procedural-animals: unknown action "${name}"`));
684
+ if (this.dead() && name !== 'hit') return Promise.resolve('refused');
685
+ const leaves = name === 'jump' || name === 'attack' || !!def.airborne || !!def.needsStand;
686
+ if (this.holdsStill() && leaves) return this.play('stand').then((r) => (r === 'refused' ? r : this.play(name, opts)));
687
+ // arbitration: a new one-shot interrupts the running ones (overlays excepted); nothing but a hit
688
+ // is taken while a one-shot is committed to the air
689
+ if (this.oneshots.some((a) => this.committed(a))) { if (name !== 'hit') return Promise.resolve('refused'); }
690
+ else if (!def.overlay) for (const a of this.oneshots) if (!this.oneshotDefs[a.name]?.overlay || a.name === name) this.end(a, 'interrupted');
691
+ for (const a of this.oneshots) if (a.name === name) this.end(a, 'interrupted');
692
+ const inst = { name, t: 0, w: 0, dur: 1, stopping: false, opts, tune: this.tuning[name] || {}, fade: def.fade ?? 0.12, speed: opts.speed || 1, airborne: !!def.airborne };
693
+ def.start(inst, this);
694
+ inst.promise = new Promise((r) => { inst.resolve = r; });
695
+ this.oneshots.push(inst);
696
+ e.emit('actionStart', { name });
697
+ return inst.promise;
698
+ }
699
+
700
+ stop(name) {
701
+ for (const a of this.oneshots) if ((!name || a.name === name) && !this.committed(a)) this.end(a, 'stopped');
702
+ if (!name || this.postureDefs[name]) {
703
+ if (this.postures.some((p) => p.target > 0 && (name ? p.name === name : p.name !== 'death'))) this.play('stand');
704
+ }
705
+ }
706
+
707
+ onLand() {
708
+ for (const a of this.oneshots) if (this.oneshotDefs[a.name].onLand) this.oneshotDefs[a.name].onLand(a, this);
709
+ }
710
+
711
+ update(dt) {
712
+ const e = this.engine, P = e.P;
713
+ resetParams(P);
714
+ // ---- postures
715
+ for (const p of this.postures) {
716
+ p.t += dt;
717
+ const rate = p.target > p.w ? 1 / p.fadeIn : 1 / p.fadeOut;
718
+ p.w = clamp(p.w + clamp(p.target - p.w, -rate * dt, rate * dt), 0, 1);
719
+ if (!p.reached && p.target > 0 && p.w >= 1 && (p.name !== 'death' || p.t > 1.6) && (p.name !== 'sleep' || p.t > 3.2) && stageSettled(p)) { p.reached = true; p.resolveIn('done'); e.emit('actionEnd', { name: p.name }); }
720
+ }
721
+ compact(this.postures, keepPosture);
722
+ const active = this.postures.find(isTargeted);
723
+ this.posture = active ? (active.name === 'death' ? 'dead' : active.name) : this.postures.length ? 'standing-up' : 'stand';
724
+ if (this.standReq && !this.postures.length) { this.standReq.resolve('done'); this.standReq = null; e.emit('actionEnd', { name: 'stand' }); }
725
+
726
+ // ---- idle (automatic when standing still with nothing else running)
727
+ const still = e.speed < 0.05 * e.cfg.sk && !e.input.follow;
728
+ const idleTarget = still && !this.postures.length && !this.oneshots.some(isBusy) ? 1 : 0;
729
+ const id = this.idle;
730
+ id.w += clamp(idleTarget - id.w, -dt * 1.5, dt * 0.7);
731
+ this.applyIdle(P, id.w, dt);
732
+
733
+ // ---- postures (smoothstepped weights)
734
+ for (const p of this.postures) {
735
+ const w = p.w * p.w * (3 - 2 * p.w);
736
+ this.postureDefs[p.name].apply(P, w, p, this);
737
+ }
738
+ // ---- one-shots
739
+ const wantsToMove = e.wantSpeed > 0.05 || !!e.input.follow;
740
+ for (const a of this.oneshots) {
741
+ if (wantsToMove && (a.name === 'eat' || a.name === 'drink')) this.end(a, 'interrupted');
742
+ a.t += dt * a.speed;
743
+ const def = this.oneshotDefs[a.name];
744
+ if (!a.stopping && def.update(a, dt * a.speed, this)) this.end(a, 'done');
745
+ // (a jump that has landed ends fast; one interrupted early fades out like any other)
746
+ const fadeIn = a.fade, fadeOut = (a.name === 'jump' || a.airborne) && a.reason === 'done' ? 0.05 : a.fade;
747
+ if (a.stopping) a.w = Math.max(0, a.w - dt / fadeOut);
748
+ else a.w = Math.min(1, a.w + dt / fadeIn);
749
+ const w = a.w * a.w * (3 - 2 * a.w);
750
+ if (w > 0) def.apply(P, w, a, this);
751
+ }
752
+ // species hook: style / behaviour layer on top of everything (may also steer e.wantSpeed / wantHeading)
753
+ if (this.hooks.update) this.hooks.update(P, dt, this);
754
+ for (const a of this.oneshots) {
755
+ if (a.stopping && a.w <= 0) { a.resolve(a.reason || 'done'); e.emit('actionEnd', { name: a.name, reason: a.reason || 'done' }); }
756
+ }
757
+ compact(this.oneshots, keepOneshot);
758
+ if (e.air.active && !this.oneshots.some(isAirborneAction)) e.touchDown();
759
+ }
760
+
761
+ applyIdle(P, w, dt) {
762
+ const e = this.engine, id = this.idle, cfg = e.cfg;
763
+ // offsets from idle re-steps relax back once the animal moves
764
+ if (w < 0.5) { for (const leg of e.legs) leg.idleOff.multiplyScalar(Math.exp(-dt * 4)); }
765
+ if (w <= 0.001) { id.hasLook = false; return; }
766
+ id.t += dt;
767
+ // weight shift: slow sway of the body over the feet
768
+ id.shiftPh += dt * 0.23;
769
+ const sh = Math.sin(id.shiftPh * 6.283) * 0.6 + Math.sin(id.shiftPh * 6.283 * 0.37 + 1.1) * 0.4;
770
+ const it = this.tuning.idle || {}, ka = it.shift ?? 1;
771
+ mx(P, 'side', 0.012 * sh * ka, w); mx(P, 'roll', -0.02 * sh * ka, w); mx(P, 'dropH', (0.02 + 0.015 * sh) * ka, w);
772
+ // occasional re-step: nudge one foot's neutral position, the gait engine steps it
773
+ id.stepT -= dt;
774
+ if (id.stepT <= 0 && w > 0.9 && it.restep !== false) {
775
+ id.stepT = 5 + e.rand() * 9;
776
+ const leg = e.legs[Math.floor(e.rand() * 4)];
777
+ const s = cfg.k;
778
+ if (leg.idleOff.lengthSq() > 1e-6) leg.idleOff.set(0, 0, 0);
779
+ else leg.idleOff.set((e.rand() - 0.5) * 0.05 * s, 0, (e.rand() < 0.5 ? -1 : 1) * (0.09 + e.rand() * 0.03) * s);
780
+ }
781
+ // look around at random points (unless the game asked the animal to look at something)
782
+ if (!e.input.look) {
783
+ id.lookT -= dt;
784
+ if (id.lookT <= 0) {
785
+ id.lookT = 1.4 + e.rand() * 2.8;
786
+ if (e.rand() < 0.3) id.hasLook = false;
787
+ else {
788
+ const a = e.heading + (e.rand() - 0.5) * 2.4;
789
+ const d = (6 + e.rand() * 10) * cfg.k;
790
+ const px = e.pos.x + Math.sin(a) * d, pz = e.pos.z + Math.cos(a) * d;
791
+ id.look.set(px, e.terrainH(px, pz) + (0.1 + e.rand() * 0.9) * cfg.k, pz);
792
+ id.hasLook = true;
793
+ }
794
+ }
795
+ if (id.hasLook) { P.look = id.look; P.lookW = w; }
796
+ }
797
+ }
798
+ }
799
+
800
+ export { angDiff, DEG, lerp, envelope, LEG_FIELDS, mx, mxLeg, mxLegs, mouthGroundTarget, ONESHOTS, POSTURES };