@bitefoundry/retrieviewer 0.0.2 → 0.0.4

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 (261) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +75 -0
  6. package/INSTALLING.md +8 -5
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +110 -0
  11. package/README.md +119 -58
  12. package/RELEASING.md +27 -14
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/desktop/platform.mjs +27 -0
  17. package/integrations/linux-overlay/README.md +83 -0
  18. package/integrations/linux-overlay/overlay.py +1094 -0
  19. package/integrations/macos-overlay/README.md +83 -0
  20. package/integrations/macos-overlay/overlay.jxa +639 -0
  21. package/integrations/opencode/runtime.mjs +4 -4
  22. package/integrations/opencode/tui.tsx +1 -1
  23. package/integrations/windows-overlay/Overlay.cs +4 -4
  24. package/integrations/windows-overlay/desktop.mjs +40 -8
  25. package/integrations/windows-overlay/protocol.mjs +11 -7
  26. package/integrations/windows-overlay/worker.mjs +13 -7
  27. package/package.json +41 -14
  28. package/puppy-comfort-sprites.mjs +25 -0
  29. package/puppy-companion-comfort-q5.bin.gz +0 -0
  30. package/puppy-companion-comfort.json +77 -0
  31. package/puppy-companion-life.mjs +49 -16
  32. package/puppy-companion-objects.mjs +12 -0
  33. package/puppy-frame-predictive.mjs +15 -9
  34. package/puppy-frame-store.mjs +2 -2
  35. package/puppy-paper-world.mjs +14 -0
  36. package/puppy-paper.mjs +65 -14
  37. package/retrieviewer-cli.mjs +10 -3
  38. package/retrieviewer-plugin.d.mts +1 -1
  39. package/retrieviewer-plugin.mjs +1 -1
  40. package/retrieviewer.d.mts +10 -4
  41. package/retrieviewer.mjs +20 -14
  42. package/locomotion-core.mjs +0 -21
  43. package/locomotion-puppy.animal +0 -0
  44. package/vendor/procedural-animals/package.json +0 -40
  45. package/vendor/procedural-animals/src/animal.js +0 -146
  46. package/vendor/procedural-animals/src/bake.js +0 -51
  47. package/vendor/procedural-animals/src/build.js +0 -81
  48. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  49. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  50. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  51. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  52. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  53. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  54. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  55. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  56. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  57. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  58. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  59. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  60. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  61. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  62. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  63. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  64. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  65. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  66. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  67. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  68. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  69. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  70. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  71. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  72. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  73. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  74. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  75. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  76. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  77. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  79. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  80. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  81. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  82. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  83. package/vendor/procedural-animals/src/index.d.ts +0 -98
  84. package/vendor/procedural-animals/src/index.js +0 -29
  85. package/vendor/procedural-animals/src/registry.js +0 -48
  86. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  87. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  88. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  89. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  90. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  91. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  92. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  94. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  95. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  96. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  97. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  98. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  99. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  100. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  101. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  102. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  103. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  104. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  105. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  106. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  107. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  108. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  109. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  110. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  111. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  112. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  113. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  114. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  115. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  116. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  117. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  118. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  119. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  120. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  121. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  122. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  123. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  124. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  125. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  126. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  127. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  128. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  129. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  130. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  131. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  132. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  133. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  134. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  135. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  136. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  137. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  138. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  139. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  140. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  141. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  142. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  143. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  144. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  145. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  146. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  147. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  148. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  149. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  150. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  151. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  152. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  153. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  154. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  155. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  156. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  157. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  158. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  159. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  160. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  161. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  162. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  163. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  164. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  165. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  166. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  167. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  168. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  169. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  170. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  171. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  172. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  173. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  174. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  175. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  176. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  177. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  178. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  179. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  180. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  181. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  182. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  183. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  184. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  185. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  186. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  187. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  188. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  189. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  190. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  191. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  192. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  193. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  194. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  195. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  196. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  197. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  198. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  199. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  200. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  201. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  202. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  203. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  204. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  205. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  206. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  207. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  208. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  209. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  210. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  211. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  212. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  213. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  214. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  215. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  216. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  217. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  218. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  219. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  220. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  221. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  222. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  223. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  224. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  225. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  226. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  227. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  228. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  229. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  230. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  231. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  232. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  233. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  234. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  235. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  236. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  237. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  238. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  239. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  240. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  241. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  242. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  243. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  244. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  245. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  246. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  247. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  248. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  249. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  250. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  251. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  252. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  253. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  254. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  255. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  256. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  257. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  258. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  259. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  260. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  261. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,605 +0,0 @@
1
- // Rabbit behaviour layer for the quadruped engine (species.motion.hooks, see core/motion/actions.js):
2
- //
3
- // pose(engine, dt) after the engine's solve: nose twitch (the snout bone: nose leather and
4
- // split upper lip), independent ear swivels (each pinna turns on its own
5
- // toward "sounds"), ears kept out of the ground (lying, dead)
6
- // update(P, dt, layer) every frame over the action layer: the hopping style (the crouched rest
7
- // stance opens up: hips rise, heels lift, head reaches forward), nose
8
- // twitch rate, the zig-zag escape steering, automatic idle variants
9
- // (grooming, periscope, thumping, ear scans)
10
- // actions hop (single hop), jump, binky, groom, periscope, thump, flee (zig-zag
11
- // escape), attack (boxing: rears up, forepaw strikes, lunge-bite), hit
12
- // (startle, then often an alarm thump)
13
- //
14
- // All distances are rabbit metres at the reference size, multiplied by cfg.s; posture offsets that
15
- // the engine multiplies by cfg.k (fwd, side, headRaise, neckReach) are divided by cfg.unit.
16
- import * as THREE from 'three';
17
- import { clamp, lerp, smooth, smin, TAU, tv, tqa, angDiff } from '../../core/motion/common.js';
18
- import { mx, mxLeg, ONESHOTS, POSTURES } from '../../core/motion/actions.js';
19
-
20
- // The loaf, made from the half-extended bind pose (rig.js):
21
- // LOAF_DROP hip drop below the bind (m at size 1; a little less than the sculpt's rear lift: the rump
22
- // rounds under, LOAF_FLEX, and rests just clear of the ground)
23
- // LOAF_FWD forward shift of the body (m at size 1), cancelling the pitch-back of the girdles
24
- // LOAF_FLEX spine flexion: the back rounded over the loins
25
- const LOAF_DROP = 0.031;
26
- const LOAF_FWD = 0.034;
27
- const LOAF_FLEX = 0.15;
28
-
29
- const _q = new THREE.Quaternion(), _s = new THREE.Vector3(1, 1, 1);
30
- const PI = Math.PI;
31
-
32
- // per-engine behaviour state
33
- function B(e) {
34
- if (!e._rb) {
35
- e._rb = {
36
- twitchPh: 0, twitchRate: 2.2, twitchAmp: 1, twitchT: 0,
37
- ear: [{ sw: 0, target: 0, tNext: 0.5, v: 0 }, { sw: 0, target: 0, tNext: 1.3, v: 0 }],
38
- earLift: [0, 0], earLen: [undefined, undefined], earNear: [true, true],
39
- idleT: 7 + e.rand() * 6, thumpAfter: 0, flee: null, scan: 1,
40
- };
41
- }
42
- return e._rb;
43
- }
44
- const busy = (L) => L.postures.length > 0 || L.oneshots.some((a) => !a.stopping);
45
-
46
- // ------------------------------------------------------------------------------------------------
47
- // pose: snout twitch and ear swivels (runs after the engine wrote the head, ears and extra bones)
48
- function pose(e, dt) {
49
- const b = B(e), P = e.P, cfg = e.cfg;
50
- const sn = e.bone.snout;
51
- const awake = 1 - P.eyelid;
52
- // nose twitch: small fast up-down wiggles in bursts; faster when moving / alert, none asleep
53
- b.twitchPh += dt * TAU * b.twitchRate;
54
- const tw = Math.pow(Math.max(0, Math.sin(b.twitchPh)), 2) * b.twitchAmp * awake * (e.lod >= 2 ? 0 : 1);
55
- if (sn && tw > 1e-4) {
56
- const base = tv().setFromMatrixPosition(sn.matrixWorld);
57
- _q.setFromRotationMatrix(sn.matrixWorld);
58
- const lat = tv(1, 0, 0).applyQuaternion(_q);
59
- const back = tv(0, 1, 0).applyQuaternion(_q).multiplyScalar(-0.0012 * tw * cfg.s);
60
- sn.matrixWorld.compose(base.add(back), tqa(lat, -0.2 * tw).multiply(_q), _s);
61
- }
62
- if (!cfg.hasEars) return;
63
- const lop = !!e.params.lop;
64
- const still = 1 - smooth(0.5, 3, e.speed / cfg.sq);
65
- for (let k = 0; k < 2; k++) {
66
- const E = b.ear[k], S = k === 0 ? 'L' : 'R', s = k === 0 ? 1 : -1;
67
- const bone = k === 0 ? e.bone.earL : e.bone.earR;
68
- if (!bone) continue;
69
- // a new "sound" every 1-4 s: mostly outward / backward, sometimes straight ahead
70
- E.tNext -= dt;
71
- if (E.tNext <= 0) {
72
- E.tNext = (1 + e.rand() * 3) / (0.6 + 0.4 * b.scan);
73
- E.target = e.rand() < 0.3 ? 0 : (e.rand() - 0.3) * 1.0 * s;
74
- }
75
- const want = E.target * still * (1 - P.earFlat) * awake * (lop ? 0.12 : 1) * b.scan;
76
- const om = 13; // critically damped swivel
77
- E.v += (om * om * (want - E.sw) - 2 * om * E.v) * dt;
78
- E.sw += E.v * dt;
79
- // (cheap exit: nothing to add to the engine's ear this frame)
80
- if (Math.abs(E.sw) < 1e-4 && b.earLift[k] < 1e-4 && !b.earNear[k]) continue;
81
- const base = tv().setFromMatrixPosition(bone.matrixWorld);
82
- _q.setFromRotationMatrix(bone.matrixWorld);
83
- const ax = tv(0, 1, 0).applyQuaternion(_q);
84
- _q.premultiply(tqa(ax, E.sw));
85
- // keep the long pinna out of the ground (lying on the side, dead, grazing): swing it up about
86
- // its base just enough, smoothly (a spring on the correction angle)
87
- const len = b.earLen[k] ?? (b.earLen[k] = e.J['earBase' + S].distanceTo(e.J['earTip' + S]));
88
- const dir = tv(0, 1, 0).applyQuaternion(_q);
89
- // sample the pinna at its tip and at both edges of its broad middle (it lies flat when the head
90
- // rests on its side); only when the ear base is within reach of the ground
91
- const latE = tv(1, 0, 0).applyQuaternion(_q);
92
- let need = 0;
93
- b.earNear[k] = base.y - e.terrainH(base.x, base.z) < len + 0.03 * cfg.s;
94
- for (let j = 0; j < 3 && b.earNear[k]; j++) {
95
- const pt = tv().copy(base).addScaledVector(dir, len * (j === 0 ? 1 : 0.6));
96
- if (j) pt.addScaledVector(latE, (j === 1 ? 1 : -1) * 0.02 * cfg.s);
97
- const floor = e.terrainH(pt.x, pt.z) + 0.014 * cfg.s;
98
- if (pt.y < floor) need = Math.max(need, Math.asin(clamp((floor - pt.y) / pt.distanceTo(base), 0, 1)) * 1.15);
99
- }
100
- b.earLift[k] += (need - b.earLift[k]) * (1 - Math.exp(-dt * 25));
101
- if (b.earLift[k] > 1e-4) {
102
- const axis = tv().crossVectors(dir, tv(0, 1, 0));
103
- if (axis.lengthSq() > 1e-8) _q.premultiply(tqa(axis.normalize(), b.earLift[k]));
104
- }
105
- bone.matrixWorld.compose(base, _q, _s);
106
- }
107
- }
108
-
109
- // ------------------------------------------------------------------------------------------------
110
- // update: locomotion style and idle life, over the action layer's parameters
111
- function update(P, dt, L) {
112
- const e = L.engine, b = B(e), cfg = e.cfg;
113
- const vn = e.speed / cfg.sq;
114
- // the scut stays pressed to the rump in every posture (never laid on the ground like a long tail)
115
- P.tailGround = 0; P.tailSide = 0;
116
- // the loaf: at rest the hips sit down onto the folded hind legs (the bind pose stands with them
117
- // half extended). Offsets the actions add are relative to the loaf; postures that rest the body on
118
- // the ground (lie, sleep, death: groundW) place it themselves.
119
- const hipRest = cfg.pelvis.hh - (cfg.pelvis.y - cfg.yH);
120
- // (an action that lowers the hips itself, dropH 0..1 toward the resting height, takes the loaf's share
121
- // with it: dropH 1 is the rump on the ground either way. The rabbit's own actions are tuned relative
122
- // to the loaf: 0.1 of dropH ~ 6 mm)
123
- const loaf = (1 - clamp(P.dropH, 0, 1)) * (1 - P.groundW);
124
- P.dropH += (LOAF_DROP * cfg.s) / Math.max(1e-4, cfg.yH - hipRest) * loaf;
125
- // ... the body pitches back about its ground-level origin as the rump sits down, which carried the
126
- // shoulders ~35 mm back over the planted forefeet (forelegs slanting forward): shift it forward again
127
- // (only standing still: in motion the gait places the feet under the girdles itself; eased in and
128
- // out so starting and stopping do not jerk the body over its feet)
129
- b.sitting = (b.sitting ?? 1) + (loaf * (1 - smooth(0.0, 0.08, vn) * P.gaitW) - (b.sitting ?? 1)) * (1 - Math.exp(-dt * 4));
130
- const sitting = b.sitting;
131
- P.fwd += (LOAF_FWD / cfg.unit) * sitting;
132
- // ... and the back rounds over the loins, the rump tucked under (the egg-shaped loaf)
133
- P.flex += LOAF_FLEX * Math.min(sitting, loaf);
134
- if (!b.seated) {
135
- // a new rabbit starts in its loaf (the engine's girdle springs start at the bind heights)
136
- b.seated = true;
137
- const dy = LOAF_DROP * cfg.s;
138
- e.hipY.reset(e.hipY.x - dy);
139
- e.height.reset(e.height.x - dy * cfg.zS / (cfg.zS - cfg.zH));
140
- e.pitch.reset(e.pitch.x + Math.atan2(dy, cfg.zS - cfg.zH));
141
- }
142
- // lying on the side (dead, a flop): the long ears fold back along the neck
143
- const lying = smooth(0.3, 1.1, Math.abs(P.roll));
144
- P.earFlat = Math.max(P.earFlat, lying);
145
- // (the short neck hardly twists: the head rolls over with the body as it goes down, carried by the
146
- // chest rather than held level, and rests on its side; the skull turning on the atlas stretched
147
- // the skin of the short neck)
148
- const dying = L.postures.reduce((m, p) => (p.name === 'death' ? Math.max(m, p.w) : m), 0);
149
- const tip = Math.max(smooth(0.4, 1.0, Math.abs(P.roll)), dying);
150
- P.headLimp = lerp(P.headLimp, 1, tip);
151
- P.headRoll *= 1 - tip;
152
- P.headYaw *= 1 - 0.7 * tip;
153
- // ... and the head rests on the lower ear's root, a little higher than a short-eared head would
154
- if (P.headW > 0) P.headPos.y += 0.014 * cfg.s * lying;
155
- P.headW *= 1 - 0.5 * tip;
156
- // hopping: the crouched rest stance opens up. The hips rise (the hind limb unfolds, the heels leave
157
- // the ground: plantigrade at rest, on the toes in motion), the back lengthens and the head reaches
158
- // forward and down; at escape speed the ears go back along the neck (ears table).
159
- const run = smooth(0.08, 1.6, vn) * P.gaitW;
160
- const fast = smooth(1.5, 6, vn) * P.gaitW;
161
- P.dropH -= 0.25 * run + 0.08 * fast + 0.05 * smooth(0, 0.15, vn) * P.gaitW; // (any step lifts the rump off the ground a little: uneven ground)
162
- // the chest drops so the short forelegs can reach forward and back (they are nearly straight at rest)
163
- P.dropF += 0.45 * run;
164
- P.neckReach += (0.004 * run + 0.003 * fast) / cfg.unit;
165
- // the head leads a turn only a little (the short neck: the engine's lead of the gaze into a turn
166
- // twisted the skull on the atlas in the zig-zag escape)
167
- P.headYaw -= 0.75 * clamp(e.yawRate * 0.22, -0.4, 0.4);
168
- P.headRaise -= (0.004 * run + 0.002 * fast) / cfg.unit;
169
- // nose twitch rate: calm 1.5-3 Hz in bursts with pauses, faster when moving
170
- b.twitchT -= dt;
171
- if (b.twitchT <= 0) { b.twitchT = 0.8 + e.rand() * 2.5; b.twitchAmp = e.rand() < 0.2 ? 0 : 0.6 + 0.5 * e.rand(); b.twitchRate = 1.6 + e.rand() * 1.4; }
172
- b.twitchRate += (lerp(b.twitchRate, 4.5, run) - b.twitchRate) * 0.1;
173
- b.scan = lerp(1, 1.6, P.lookW > 0 && P.look ? 0.5 : 0);
174
- // zig-zag escape steering (the flee action)
175
- const f = b.flee;
176
- if (f) {
177
- f.t += dt;
178
- if (f.t >= f.dur) b.flee = null;
179
- else {
180
- f.next -= dt;
181
- if (f.next <= 0) { f.side = -f.side; f.next = 0.3 + e.rand() * 0.35; }
182
- e.wantSpeed = Math.max(e.wantSpeed, f.speed * cfg.sq * smooth(0, 0.3, f.t));
183
- e.wantHeading = f.h0 + f.side * f.amp * smooth(0.2, 0.6, f.t);
184
- }
185
- }
186
- // any other action interrupts grazing / drinking
187
- if (L.oneshots.some((a) => !a.stopping && a.name !== 'eat' && a.name !== 'drink' && a.name !== 'hit')) for (const a of L.oneshots) if (a.name === 'eat' || a.name === 'drink') a.stopping = true;
188
- // alarm thump after being startled
189
- if (b.thumpAfter > 0) {
190
- b.thumpAfter -= dt;
191
- if (b.thumpAfter <= 0) {
192
- if (!busy(L) && e.speed < 0.05 && e.legs.every((l) => l.state === 'stance')) L.play('thump');
193
- else if (!busy(L)) b.thumpAfter = 0.1; // wait until it stands still on all four
194
- }
195
- }
196
- // automatic idle variants (standing still, nothing else running, nobody steering the gaze)
197
- if (e.idleVariants !== false && L.idle.w > 0.98 && !busy(L) && !e.input.look && !e.input.follow) {
198
- b.idleT -= dt;
199
- if (b.idleT <= 0) {
200
- b.idleT = 6 + e.rand() * 9;
201
- const r = e.rand();
202
- if (r < 0.35) L.play('groom');
203
- else if (r < 0.6) L.play('periscope');
204
- else if (r < 0.7) L.play('thump');
205
- else b.twitchAmp = 1.2; // a sniffing burst
206
- }
207
- } else if (busy(L)) b.idleT = Math.max(b.idleT, 3);
208
- }
209
-
210
- // ------------------------------------------------------------------------------------------------
211
- // hops and jumps: a real ballistic flight (engine.takeOff) with scripted foot paths in world space.
212
- // Each foot leaves from where it stood (A) and lands where the stance after the hop will put it (B),
213
- // so handing the feet back to the gait at the end is continuous. Hind feet push off together, trail
214
- // extended behind, swing forward outside the forelegs and land together just after the forefeet.
215
- function hopDef(o) {
216
- return {
217
- kind: 'oneshot', airborne: true, fade: 0.1,
218
- start(inst, L) {
219
- const e = L.engine, cfg = e.cfg, s = cfg.s;
220
- inst.run = smooth(0.8, 2.5, e.speed / cfg.sq);
221
- inst.tc = lerp(o.crouch, 0.16, inst.run) * cfg.sq;
222
- const h = (inst.opts.height ?? o.height) * s * lerp(1, 0.6, inst.run);
223
- inst.vy = Math.sqrt(2 * 9.81 * h);
224
- inst.T = (2 * inst.vy) / 9.81;
225
- let dist = (inst.opts.distance ?? o.distance) * s;
226
- if (inst.opts.target) { const t = inst.opts.target; dist = clamp(Math.hypot(t.x - e.pos.x, t.z - e.pos.z), 0.05 * s, dist * 2.5); }
227
- inst.vh = Math.max(e.speed, lerp(dist / inst.T, e.speed, inst.run));
228
- inst.tp = Math.min(0.08 * cfg.sq, inst.tc * 0.6);
229
- inst.LD = lerp(0.42, 0.2, inst.run) * cfg.sq; // landing hand-back time
230
- inst.L0 = (inst.vy * inst.tp) / 2;
231
- inst.phase = 'crouch';
232
- inst.dur = Infinity;
233
- inst.side = inst.opts.side ?? (e.rand() < 0.5 ? -1 : 1);
234
- inst.A = [0, 1, 2, 3].map(() => new THREE.Vector3());
235
- inst.Bp = [0, 1, 2, 3].map(() => new THREE.Vector3());
236
- inst.end = [0, 1, 2, 3].map(() => new THREE.Vector3());
237
- inst.last = [0, 1, 2, 3].map(() => new THREE.Vector3());
238
- },
239
- update(inst, dt, L) {
240
- inst.dt = dt;
241
- const e = L.engine, cfg = e.cfg;
242
- if (inst.phase === 'crouch' && inst.t >= inst.tc) {
243
- inst.phase = 'air'; inst.tAir = inst.t;
244
- // the push's height so far (last frame): the engine's flight adds this frame's rise
245
- { const u = Math.max(0, inst.t - dt - (inst.tc - inst.tp)); inst.L0 = u <= inst.tp ? (inst.vy * u * u) / (2 * inst.tp) : inst.vy * (inst.tp / 2 + u - inst.tp); }
246
- e.takeOff(inst.vy, inst.vh);
247
- if (inst.run > 0.5) e.phase = ((-e.gait.f * inst.T) % 1 + 1) % 1;
248
- // landing: body ground point after the flight (heading is held in the air)
249
- const land = tv().copy(e.pos).addScaledVector(e.forward(e.heading, tv()), inst.vh * inst.T);
250
- for (let i = 0; i < 4; i++) {
251
- const Ld = e.legs[i].def;
252
- inst.A[i].copy(e.legs[i].contact);
253
- if (i === 0) { inst.P0 = e.pos.clone(); inst.Pl = land.clone(); inst.F = e.forward(e.heading, new THREE.Vector3()); }
254
- e.bodyToWorld(Ld.cx * Ld.s, 0, Ld.cz, e.heading, land, inst.Bp[i]);
255
- if (inst.run > 0.5) {
256
- // running: land in step with the stride (hind feet plant together as it lands), so the
257
- // gait carries on without hurried catch-up swings
258
- const g = e.gait, half = Math.min(e.speed * g.D / g.f * 0.5, Ld.leadMax);
259
- if (!Ld.front) inst.Bp[i].addScaledVector(e.forward(e.heading, tv()), half);
260
- }
261
- inst.Bp[i].y = e.terrainH(inst.Bp[i].x, inst.Bp[i].z);
262
- }
263
- }
264
- if (inst.phase === 'land' && inst.t - inst.tLand > inst.LD) return true;
265
- if (inst.phase === 'air' && inst.t - inst.tAir > 3) e.touchDown();
266
- return false;
267
- },
268
- onLand(inst, L) {
269
- inst.phase = 'land'; inst.tLand = inst.t;
270
- const e = L.engine;
271
- if (inst.run < 0.5) e.speed *= 0.3; // a single hop stops; a running hop runs on
272
- // the legs take the landing (the crouching rump rides millimetres above the ground)
273
- e.hipY.v *= 0.25; e.shY.v *= 0.5;
274
- for (let i = 0; i < 4; i++) inst.end[i].copy(inst.last[i]);
275
- },
276
- apply(P, w, inst, L) {
277
- const e = L.engine, cfg = e.cfg, s = cfg.s, t = inst.t;
278
- if (inst.phase === 'crouch') {
279
- const c = smooth(0, inst.tc, t) * (1 - inst.run * 0.7) * w;
280
- mx(P, 'dropF', 0.2, c); mx(P, 'dropH', 0.03 + 0.1 * o.crouchH, c); mx(P, 'earFlat', 0.25, c);
281
- // the feet are taken over by the scripted paths already while they still stand (target =
282
- // where they are), so nothing jumps at take-off
283
- const r = smooth(lerp(0.2, 0, inst.run) * inst.tc, 0.9 * inst.tc, t) * w;
284
- const u0 = clamp(t - (inst.tc - inst.tp), 0, inst.tp), push = (inst.vy * u0 * u0) / (2 * inst.tp);
285
- for (let i = 0; i < 4; i++) { P.legs[i].target.copy(e.legs[i].contact); if (i < 2) P.legs[i].target.y += push * o.follow[0]; mxLeg(P, i, 'reach', 1, r); }
286
- // push-off: the forequarters rise first (P.pitch is applied unfiltered: ramp it here)
287
- mx(P, 'pitch', 0.22 * o.pitch * (1 - 0.5 * inst.run), r);
288
- mx(P, 'gaitW', 0, r); mx(P, 'legGait', 0, r);
289
- // push: the body accelerates up (and forward) over the last few frames, so the ballistic
290
- // take-off does not start with a velocity step
291
- const u = clamp(t - (inst.tc - inst.tp), 0, inst.tp);
292
- P.lift += (inst.vy * u * u) / (2 * inst.tp) * w;
293
- if (inst.run < 0.5 && u > 0) e.speed = Math.max(e.speed, inst.vh * (u / inst.tp));
294
- return;
295
- }
296
- // the push's height is carried through the flight and given back while landing
297
- P.lift += inst.L0 * w * (inst.phase === 'land' ? 1 - smooth(0, 0.7 * inst.LD, t - inst.tLand) : 1);
298
- const x = (t - inst.tAir + (inst.dt || 1 / 60)) / inst.T; // after this frame's step
299
- const k0 = w;
300
- let hand = 0; // 0 = scripted foot paths, 1 = back to the gait's own contacts
301
- if (inst.phase === 'land') hand = smooth(0.0, 0.7 * inst.LD, t - inst.tLand);
302
- const lg = inst.phase === 'land' ? 1 - smooth(0.3 * inst.LD, 0.95 * inst.LD, t - inst.tLand) : 1;
303
- mx(P, 'gaitW', 0, k0 * lg); mx(P, 'legGait', 0, k0 * lg);
304
- for (let i = 0; i < 4; i++) {
305
- const pl = P.legs[i];
306
- const p = inst.phase === 'land' ? landTarget(inst, e, i, hand) : pathAt(inst, e, i, x, flightLift(inst, e) + inst.L0 * (1 - smooth(0.55, 0.95, x)));
307
- pl.target.copy(p);
308
- if (inst.phase === 'air') inst.last[i].copy(p);
309
- mxLeg(P, i, 'reach', 1, k0 * lg);
310
- }
311
- // body: nose up at take-off, level in flight, nose down to land on the forefeet
312
- const xp = clamp(x, 0, 1);
313
- if (inst.phase === 'air') {
314
- mx(P, 'pitch', lerp(0.22, -0.2, smooth(0.05, 0.95, xp)) * o.pitch * (1 - 0.5 * inst.run), k0);
315
- mx(P, 'earFlat', 0.45, k0); mx(P, 'tailLift', 0.5, k0);
316
- if (o.twist) {
317
- // binky: the body twists in mid-air and the hind end flicks to the side
318
- const tw = Math.sin(PI * xp) * inst.side;
319
- mx(P, 'bend', 0.45 * tw, k0); mx(P, 'roll', 0.3 * Math.sin(TAU * xp) * inst.side, k0);
320
- mx(P, 'headYaw', -0.4 * tw, k0);
321
- }
322
- } else {
323
- // lands on extended legs, then settles back into the crouch (the rump sits millimetres above
324
- // the ground at rest, so the landing is absorbed by the legs, not by dropping the body)
325
- const xl = (t - inst.tLand) / inst.LD;
326
- const a = Math.sin(PI * Math.min(1, xl * 1.5)) * (1 - xl) * w;
327
- mx(P, 'dropF', 0.2, a);
328
- mx(P, 'dropH', -0.09 * (1 - smooth(0, 1, xl)), w);
329
- }
330
- void s;
331
- },
332
- };
333
- // after landing: the feet ride with the body (a running landing carries on) and blend into the
334
- // gait's own contacts
335
- function landTarget(inst, e, i, hand) {
336
- const p = tv().copy(inst.end[i]).sub(inst.Pl).add(e.pos).addScaledVector(inst.F, e.speed * (inst.dt || 1 / 60));
337
- p.y = inst.end[i].y;
338
- return p.lerp(e.legs[i].contact, hand);
339
- }
340
- // body height in flight as the engine will have it after this frame's simulation step (the action
341
- // layer runs before the step, so e.lift is one frame old)
342
- function flightLift(inst, e) {
343
- if (!e.air.active) return 0;
344
- const ta = e.air.t + (inst.dt || 1 / 60);
345
- return Math.max(0, inst.vy * ta - 4.905 * ta * ta);
346
- }
347
- // foot path in world space at flight fraction x (body lift `lift` above the ground)
348
- function pathAt(inst, e, i, x, lift) {
349
- const front = i < 2, s = e.cfg.s;
350
- const A = inst.A[i], Bq = inst.Bp[i];
351
- if (inst.run > 0.5) {
352
- // running: the feet keep cycling as in the stride, carried up with the body
353
- const c = tv().copy(e.legs[i].contact);
354
- c.y += lift * (front ? o.follow[0] : o.follow[1] * smooth(0, 0.3, x));
355
- // ... but never folded up into the girdle (the leg chain would flip through its minimum length)
356
- const Ld = e.legs[i].def, gy = (front ? e.fr.shC.y : e.fr.hipC.y) - (front ? 0.62 : 0.55) * Ld.len;
357
- c.y = smin(c.y, gy, 0.05 * e.cfg.k);
358
- return c;
359
- }
360
- // feet are carried with the body (ground point Pb, known in advance: constant horizontal speed)
361
- // and move from their take-off to their landing offset; the hind feet first stay where they
362
- // pushed off (the extended phase) and are then swung forward under the body
363
- const xb = clamp(x, 0, 1);
364
- const Pb = tv().copy(inst.P0).lerp(inst.Pl, xb);
365
- const u = front ? smooth(0.0, 0.9, x) : smooth(0.3, 1.0, x);
366
- const rel = tv().subVectors(A, inst.P0).lerp(tv().subVectors(Bq, inst.Pl), u);
367
- const p = tv().copy(Pb).add(rel);
368
- if (front) p.addScaledVector(inst.F, o.reachF * s * Math.sin(PI * u));
369
- else p.lerp(tv().copy(A), 1 - smooth(0.08, 0.5, x));
370
- const arc = Math.sin(PI * clamp(u, 0, 1)) * (front ? o.arcF : o.arcH) * s;
371
- // forefeet leave with the body; the hind feet push until the body has left, then follow
372
- p.y = lerp(A.y, Bq.y, u) + lift * (front ? o.follow[0] : o.follow[1] * smooth(0, 0.3, x)) + arc;
373
- if (o.twist && !front) {
374
- const Ld = e.legs[i].def;
375
- const lat = e.left(e.heading, tv());
376
- p.addScaledVector(lat, inst.side * 0.05 * s * Math.sin(PI * clamp(x, 0, 1)) * (Ld.s > 0 ? 1 : 0.8));
377
- }
378
- return p;
379
- }
380
- }
381
-
382
- // ------------------------------------------------------------------------------------------------
383
- // posture helpers
384
- const raiseFront = (P, w, amount, pitch = 0) => { mx(P, 'dropF', -amount, w); if (pitch) mx(P, 'pitch', pitch, w); };
385
-
386
- // grooming: sits up and washes the face with both forepaws, licking them between strokes
387
- const groom = {
388
- kind: 'oneshot', fade: 0.35,
389
- start(inst) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 4.2); },
390
- update(inst) { return inst.t >= inst.dur; },
391
- apply(P, w, inst, L) {
392
- const e = L.engine, cfg = e.cfg, s = cfg.s, t = inst.t;
393
- const up = w * smooth(0, 0.5, t);
394
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
395
- raiseFront(P, up, 0.35, 0.02); mx(P, 'dropH', -0.02, up);
396
- // the forepaws are held up under the chin and the face is rubbed down over them (the head does
397
- // the wiping: nose down to lick the paws, then the cheeks drawn over them)
398
- const wipe = 0.5 - 0.5 * Math.cos(t * TAU * 1.1);
399
- mx(P, 'headPitch', 0.9 + 0.3 * wipe, up); mx(P, 'headRaise', -0.03 / cfg.unit, up); mx(P, 'lookW', 0, up);
400
- mx(P, 'earFlat', 0.15, up);
401
- const fwd = e.forward(e.heading, tv()), lat = e.left(e.heading, tv());
402
- const pawIn = up * smooth(0.25, 0.7, t);
403
- for (let i = 0; i < 2; i++) {
404
- const sd = i === 0 ? 1 : -1;
405
- const tp = tv().copy(e.fr.shC).addScaledVector(fwd, (0.05 - 0.008 * wipe) * s).addScaledVector(lat, sd * 0.012 * s);
406
- tp.y += (-0.012 + 0.008 * wipe) * s;
407
- P.legs[i].target.lerp(tp, pawIn / Math.max(1e-3, P.legs[i].reach + pawIn));
408
- mxLeg(P, i, 'reach', 1, pawIn);
409
- }
410
- mx(P, 'jaw', 0.14 * Math.max(0, Math.sin(t * TAU * 2.2)) * (1 - wipe), up); mx(P, 'jawOmega', 25, up);
411
- },
412
- };
413
-
414
- // periscope: rises on the hind feet (metatarsi flat), forelegs dangling, head high, looking far
415
- const periscope = {
416
- kind: 'oneshot', fade: 0.45,
417
- start(inst, L) {
418
- inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 3.2);
419
- const e = L.engine;
420
- const a = e.heading + (e.rand() - 0.5) * 1.6, d = 8 * e.cfg.s;
421
- inst.look = inst.opts.target ? inst.opts.target.clone() : new THREE.Vector3(e.pos.x + Math.sin(a) * d, e.pos.y + 0.35 * e.cfg.s, e.pos.z + Math.cos(a) * d);
422
- },
423
- update(inst) { return inst.t >= inst.dur; },
424
- apply(P, w, inst, L) {
425
- const e = L.engine, t = inst.t;
426
- const up = w * smooth(0, 0.6, t);
427
- // the body comes down first, then the forepaws reach for the ground (handed back to their plants
428
- // while the chest was still up, they hung in the air out of reach)
429
- const body = up * (1 - smooth(inst.dur - 0.8, inst.dur - 0.15, t));
430
- const legs = Math.min(up, w * smooth(0.004 * e.cfg.k, 0.03 * e.cfg.k, e.shY.x) + body);
431
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
432
- raiseFront(P, body, 2.3, 0.1);
433
- mx(P, 'dropH', -0.8, body);
434
- for (let i = 0; i < 2; i++) { mxLeg(P, i, 'tuck', 1, legs); P.legs[i].extend = 0.35; }
435
- mx(P, 'headRaise', 0.012 / e.cfg.unit, up); mx(P, 'headPitch', -0.15, up);
436
- if (!e.input.look) { P.look = inst.look; mx(P, 'lookW', 1, up); }
437
- mx(P, 'earFlat', 0, up);
438
- mx(P, 'breathRate', 1.5, up);
439
- },
440
- };
441
-
442
- // thump: alert, then slams both hind feet on the ground (twice), scut up
443
- const THUMPS = [0.45, 0.95];
444
- const thump = {
445
- kind: 'oneshot', fade: 0.2,
446
- start(inst) { inst.dur = 1.45; inst.hit = [false, false]; },
447
- update(inst, dt, L) {
448
- const e = L.engine;
449
- THUMPS.forEach((tt, j) => {
450
- if (!inst.hit[j] && inst.t >= tt + 0.12) {
451
- inst.hit[j] = true;
452
- for (const i of [2, 3]) e.emit('footstep', { foot: e.legs[i].def.key, position: e.legs[i].plant.clone(), speed: 0, strength: 1, thump: true });
453
- }
454
- });
455
- return inst.t >= inst.dur;
456
- },
457
- apply(P, w, inst, L) {
458
- const e = L.engine, s = e.cfg.s, t = inst.t;
459
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
460
- mx(P, 'dropF', -0.2, w); mx(P, 'tailLift', 0.9, w); mx(P, 'headRaise', 0.008 / e.cfg.unit, w);
461
- let lift = 0;
462
- for (const tt of THUMPS) lift = Math.max(lift, smooth(tt - 0.14, tt, t) * (1 - smooth(tt + 0.04, tt + 0.12, t)));
463
- const k = w * smooth(0.15, 0.35, t) * (1 - smooth(1.1, 1.35, t));
464
- for (const i of [2, 3]) {
465
- const leg = e.legs[i];
466
- const tp = tv().copy(leg.plant);
467
- tp.y += 0.022 * s * lift;
468
- tp.addScaledVector(e.forward(e.heading, tv()), -0.01 * s * lift);
469
- P.legs[i].target.copy(tp);
470
- mxLeg(P, i, 'reach', 1, k);
471
- }
472
- mx(P, 'pitch', 0.05 * lift, w);
473
- },
474
- };
475
-
476
- // sit (posture): the rabbit's resting stance is already a crouch, so 'sit' is the alert upright
477
- // sit on the haunches: forequarters raised on straight forelegs, head high, ears up
478
- const sit = {
479
- kind: 'posture', fadeIn: 0.7, fadeOut: 0.6,
480
- apply(P, w, inst, L) {
481
- const e = L.engine;
482
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
483
- raiseFront(P, w, 0.9, 0.04);
484
- mx(P, 'dropH', 0.015, w);
485
- mxLeg(P, 0, 'sit', 1, w); mxLeg(P, 1, 'sit', 1, w);
486
- mx(P, 'headRaise', 0.01 / e.cfg.unit, w); mx(P, 'headPitch', -0.08, w);
487
- mx(P, 'earFlat', 0, w);
488
- },
489
- };
490
-
491
- // sleep (posture): rabbits doze in the loaf (not curled like a cat): lying sternal, head sunk
492
- // between the shoulders, ears laid back along the back, eyes closed, nose twitching stops
493
- const sleep = {
494
- kind: 'posture', fadeIn: 1.3, fadeOut: 1.0,
495
- apply(P, w, inst, L) {
496
- POSTURES.lie.apply(P, w, inst, L);
497
- const c = smooth(0.8, 2.5, inst.t) * w;
498
- mx(P, 'headRaise', -0.012 / L.engine.cfg.unit, c); mx(P, 'headPitch', 0.25, c);
499
- mx(P, 'earFlat', 0.85, c); mx(P, 'earTwitch', 0.3, c);
500
- mx(P, 'eyelid', 1, smooth(1.5, 3, inst.t) * w);
501
- mx(P, 'breathRate', 0.5, c); mx(P, 'breathAmp', 1.3, c);
502
- mx(P, 'lookW', 0, c); mx(P, 'tailIdle', 0.1, c);
503
- },
504
- };
505
-
506
- // flee: bolts with sharp zig-zag turns (45-90 degrees), ears back, white scut raised
507
- const flee = {
508
- kind: 'oneshot', fade: 0.2,
509
- start(inst, L) {
510
- const e = L.engine, b = B(e);
511
- inst.dur = inst.opts.duration ?? 3;
512
- let h0 = e.heading;
513
- if (inst.opts.from) h0 = Math.atan2(e.pos.x - inst.opts.from.x, e.pos.z - inst.opts.from.z);
514
- if (inst.opts.heading !== undefined) h0 = inst.opts.heading;
515
- b.flee = { t: 0, dur: inst.dur, h0, side: e.rand() < 0.5 ? -1 : 1, next: 0.35, amp: 0.7, speed: inst.opts.speed ?? (e.cfg.gears.bound || 3 * e.cfg.sq) / e.cfg.sq };
516
- },
517
- update(inst, dt, L) { if (inst.t >= inst.dur) { B(L.engine).flee = null; return true; } return false; },
518
- apply(P, w) { mx(P, 'earFlat', 0.8, w); mx(P, 'tailLift', 0.6, w); },
519
- };
520
-
521
- // attack: territorial boxing. Rears up, strikes with alternating forepaws, then lunges and bites.
522
- const attack = {
523
- kind: 'oneshot', fade: 0.15, needsStand: true,
524
- start(inst, L) {
525
- const e = L.engine;
526
- inst.dur = 1.55; inst.hit = false;
527
- inst.run = smooth(0.8, 2.5, e.speed / e.cfg.sq);
528
- inst.target = inst.opts.target ? inst.opts.target.clone() : null;
529
- },
530
- update(inst, dt, L) {
531
- const e = L.engine;
532
- if (!inst.hit && inst.t >= 0.5) {
533
- inst.hit = true;
534
- const p = e.legs[0].contact.clone().lerp(e.legs[1].contact, 0.5);
535
- const dir = inst.target ? inst.target.clone().sub(e.pos).setY(0).normalize() : e.forward(e.heading, new THREE.Vector3());
536
- e.emit('attackHit', { position: p, direction: dir, style: 'box' });
537
- }
538
- if (inst.hit && !inst.bit && inst.t >= 1.12) {
539
- inst.bit = true;
540
- const p = new THREE.Vector3().copy(e.cfg.mouthLocal).applyQuaternion(e.headPose.quaternion).add(e.headPose.position);
541
- e.emit('attackHit', { position: p, direction: e.forward(e.heading, new THREE.Vector3()), style: 'bite' });
542
- }
543
- return inst.t >= inst.dur;
544
- },
545
- apply(P, w, inst, L) {
546
- const e = L.engine, cfg = e.cfg, s = cfg.s, t = inst.t, still = 1 - inst.run;
547
- if (inst.target) { P.look = inst.target; mx(P, 'lookW', 1, w); }
548
- mx(P, 'earFlat', 1, w * smooth(0, 0.12, t)); mx(P, 'earTwitch', 0, w);
549
- const rear = smooth(0.0, 0.28, t) * (1 - smooth(0.8, 1.15, t)) * w * still;
550
- const lunge = smooth(0.95, 1.12, t) * (1 - smooth(1.2, 1.5, t)) * w;
551
- mx(P, 'gaitW', 0, rear); mx(P, 'legGait', 0, rear);
552
- raiseFront(P, rear, 1.5, 0.05); mx(P, 'dropH', -0.25, rear);
553
- for (let i = 0; i < 2; i++) { mxLeg(P, i, 'tuck', 1, rear); P.legs[i].extend = 0.25; }
554
- // forepaw strikes, alternating, toward the opponent's face
555
- const fwd = e.forward(e.heading, tv()), lat = e.left(e.heading, tv());
556
- const sh = tv().copy(e.fr.shC);
557
- for (let i = 0; i < 2; i++) {
558
- const t0 = i === 0 ? 0.35 : 0.55;
559
- const strike = Math.pow(Math.sin(PI * clamp((t - t0) / 0.24, 0, 1)), 2) * rear;
560
- const tp = tv().copy(sh).addScaledVector(fwd, 0.1 * s).addScaledVector(lat, (i === 0 ? 1 : -1) * 0.012 * s);
561
- tp.y += 0.01 * s;
562
- if (inst.target) tp.lerp(tv().copy(inst.target).setY(Math.max(inst.target.y, tp.y)), 0.2);
563
- P.legs[i].target.lerp(tp, strike / Math.max(1e-3, P.legs[i].reach + strike));
564
- mxLeg(P, i, 'reach', 1, strike);
565
- }
566
- // lunge and bite
567
- mx(P, 'fwd', 0.03 * s / cfg.unit, lunge); mx(P, 'neckReach', 0.012 * s / cfg.unit, lunge);
568
- mx(P, 'jaw', 0.5, w * smooth(0.98, 1.08, t) * (1 - smooth(1.12, 1.2, t))); mx(P, 'jawOmega', 30, w);
569
- },
570
- };
571
-
572
- // hit: the engine's stagger, then (often) an alarm thump
573
- const hit = {
574
- ...ONESHOTS.hit,
575
- kind: 'oneshot',
576
- start(inst, L) {
577
- // a light animal: the blow shoves it less (fewer recovery steps); often an alarm thump after
578
- inst.opts = { ...inst.opts, strength: (inst.opts.strength ?? 1) * 0.5 };
579
- ONESHOTS.hit.start(inst, L);
580
- if (L.engine.rand() < 0.6) B(L.engine).thumpAfter = 1.0;
581
- },
582
- // the engine's flinch with a continuous envelope (sharp rise, exponential settle), ears flat
583
- apply(P, w, inst) {
584
- const t = inst.t;
585
- const k = smooth(0, 0.09, t) * Math.exp(-Math.max(0, t - 0.09) * 5.5) * w;
586
- mx(P, 'roll', inst.lx * 0.16, k); mx(P, 'bend', -inst.lx * 0.25, k);
587
- mx(P, 'pitch', inst.lz * 0.08, k); mx(P, 'dropF', 0.18, k); mx(P, 'dropH', 0.012, k);
588
- P.headLocal.set(inst.lx * 0.06, 0.02, inst.lz * 0.06 - 0.03); mx(P, 'headLocalW', 1, k);
589
- mx(P, 'headYaw', -inst.lx * 0.35, k); mx(P, 'headPitch', -0.15, k);
590
- mx(P, 'earFlat', 1, w * (1 - smooth(0.4, 0.75, t))); mx(P, 'eyelid', 0.7, k);
591
- mx(P, 'tailLift', 0.6, k);
592
- },
593
- };
594
-
595
- export const hooks = {
596
- pose,
597
- update,
598
- actions: {
599
- hop: hopDef({ height: 0.08, distance: 0.35, crouch: 0.12, crouchH: 0, arcF: 0.02, arcH: 0.02, reachF: 0.03, follow: [0.8, 0.55], pitch: 1, twist: false }),
600
- jump: hopDef({ height: 0.4, distance: 0.7, crouch: 0.2, crouchH: 0.2, arcF: 0.03, arcH: 0.03, reachF: 0.03, follow: [0.93, 0.8], pitch: 1.3, twist: false }),
601
- binky: hopDef({ height: 0.22, distance: 0.12, crouch: 0.14, crouchH: 0.1, arcF: 0.03, arcH: 0.03, reachF: 0.01, follow: [0.9, 0.75], pitch: 0.4, twist: true }),
602
- groom, periscope, thump, flee, attack, hit, sit, sleep,
603
- death: { ...POSTURES.death, kind: 'posture', fadeOut: 2.0 },
604
- },
605
- };