@bitefoundry/retrieviewer 0.0.3 → 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 (254) 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 +6 -1
  6. package/PLUGIN.md +13 -10
  7. package/PREREQUISITES.md +95 -0
  8. package/PUPPY-MEMORY.md +102 -1
  9. package/README.ko.md +110 -0
  10. package/README.md +117 -59
  11. package/RELEASING.md +23 -12
  12. package/THIRD-PARTY-NOTICES.md +5 -3
  13. package/braille.mjs +35 -9
  14. package/examples/live.mjs +2 -2
  15. package/integrations/linux-overlay/overlay.py +2 -2
  16. package/integrations/macos-overlay/overlay.jxa +8 -6
  17. package/integrations/opencode/runtime.mjs +4 -4
  18. package/integrations/opencode/tui.tsx +1 -1
  19. package/integrations/windows-overlay/Overlay.cs +4 -4
  20. package/integrations/windows-overlay/protocol.mjs +11 -7
  21. package/package.json +34 -13
  22. package/puppy-comfort-sprites.mjs +25 -0
  23. package/puppy-companion-comfort-q5.bin.gz +0 -0
  24. package/puppy-companion-comfort.json +77 -0
  25. package/puppy-companion-life.mjs +49 -16
  26. package/puppy-companion-objects.mjs +12 -0
  27. package/puppy-frame-predictive.mjs +15 -9
  28. package/puppy-frame-store.mjs +2 -2
  29. package/puppy-paper-world.mjs +14 -0
  30. package/puppy-paper.mjs +65 -14
  31. package/retrieviewer-plugin.d.mts +1 -1
  32. package/retrieviewer-plugin.mjs +1 -1
  33. package/retrieviewer.d.mts +10 -4
  34. package/retrieviewer.mjs +20 -14
  35. package/locomotion-core.mjs +0 -21
  36. package/locomotion-puppy.animal +0 -0
  37. package/vendor/procedural-animals/package.json +0 -40
  38. package/vendor/procedural-animals/src/animal.js +0 -146
  39. package/vendor/procedural-animals/src/bake.js +0 -51
  40. package/vendor/procedural-animals/src/build.js +0 -81
  41. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  42. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  43. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  44. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  45. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  46. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  47. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  48. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  49. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  50. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  51. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  52. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  53. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  54. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  55. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  56. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  57. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  58. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  59. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  60. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  61. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  62. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  63. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  64. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  65. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  66. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  67. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  68. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  69. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  70. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  71. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  72. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  73. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  74. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  75. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  76. package/vendor/procedural-animals/src/index.d.ts +0 -98
  77. package/vendor/procedural-animals/src/index.js +0 -29
  78. package/vendor/procedural-animals/src/registry.js +0 -48
  79. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  80. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  81. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  82. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  83. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  84. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  85. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  86. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  87. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  88. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  89. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  90. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  91. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  92. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  93. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  94. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  95. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  96. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  97. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  98. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  99. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  100. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  101. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  102. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  103. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  104. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  105. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  106. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  107. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  108. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  109. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  110. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  111. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  112. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  113. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  114. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  115. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  116. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  117. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  118. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  119. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  120. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  121. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  122. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  123. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  124. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  125. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  126. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  127. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  128. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  129. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  130. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  131. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  132. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  133. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  134. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  135. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  136. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  137. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  138. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  139. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  140. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  141. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  142. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  143. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  144. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  145. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  146. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  147. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  148. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  149. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  150. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  151. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  152. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  153. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  154. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  155. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  156. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  157. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  158. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  159. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  160. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  161. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  162. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  163. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  164. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  165. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  166. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  167. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  168. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  169. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  170. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  171. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  172. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  173. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  174. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  175. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  176. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  177. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  178. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  179. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  180. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  181. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  182. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  183. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  184. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  185. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  186. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  187. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  188. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  189. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  190. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  191. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  192. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  193. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  194. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  195. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  196. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  197. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  198. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  199. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  200. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  201. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  202. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  203. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  204. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  205. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  206. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  207. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  208. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  209. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  210. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  211. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  212. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  213. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  214. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  215. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  216. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  217. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  218. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  219. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  220. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  221. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  222. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  223. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  224. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  225. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  226. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  227. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  228. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  229. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  230. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  231. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  232. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  233. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  234. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  235. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  236. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  237. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  238. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  239. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  240. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  241. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  242. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  243. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  244. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  245. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  246. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  247. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  248. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  249. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  250. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  251. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  252. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  253. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  254. 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
- };