@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,521 +0,0 @@
1
- // Dog behaviour layer for the quadruped engine (species.motion.hooks, see core/motion/actions.js):
2
- //
3
- // update(P, dt, layer) every frame over the action layer: mood (happy / alert / angry / afraid) from
4
- // what the dog is doing, which sets the tail wag (rate and amplitude), the tail
5
- // carriage (raised stiff in an attack, tucked when hit), the ears, hackles and
6
- // the snarl; panting after running (jaw, tongue); the head tilt; the curled
7
- // sleep (tail over the nose); automatic idle variants (head tilt, sniffing,
8
- // pawing the ground, play bow, a happy wag burst)
9
- // pose(engine, dt) after the engine's solve: the wag itself (any rate: 2-3 Hz relaxed, 4-6 Hz
10
- // excited, whip-like from the root, biased to the dog's right when happy,
11
- // Quaranta et al. 2007), the tongue sliding out to pant and lapping when
12
- // drinking, and the soft drop ears of retrievers (and pups / button ears)
13
- // swinging as pendulums driven by the head's acceleration and gravity
14
- // actions greet (wiggling, fast-wagging welcome), playbow, sniff, dig (pawing the
15
- // ground), hit (yelp and cower, wraps the built-in flinch)
16
- //
17
- // Distances are dog metres at the reference size x cfg.s; posture offsets that the engine multiplies by
18
- // cfg.k (fwd, headRaise, neckReach) are divided by cfg.unit.
19
- import * as THREE from 'three';
20
- import { clamp, lerp, smooth, TAU, tv, tq, tqa } from '../../core/motion/common.js';
21
- import { mx, mxLeg, ONESHOTS, POSTURES, mouthGroundTarget } from '../../core/motion/actions.js';
22
-
23
- const sm = (w) => w * w * (3 - 2 * w);
24
- const _q = new THREE.Quaternion(), _q2 = new THREE.Quaternion(), _s = new THREE.Vector3(1, 1, 1);
25
- const _p = new THREE.Vector3(), _d = new THREE.Vector3(), _a = new THREE.Vector3(), _lat = new THREE.Vector3();
26
- const _hx = new THREE.Vector3(), _hz = new THREE.Vector3(), _g = new THREE.Vector3(), _qi = new THREE.Quaternion();
27
- const TAIL_W = [0.5, 0.45, 0.4, 0.36, 0.32, 0.29, 0.27, 0.25]; // share of the wag per tail segment, root -> tip
28
- const busy = (L) => L.postures.length > 0 || L.oneshots.some((a) => !a.stopping);
29
-
30
- // tail carriage offsets per variant (rad): raised stiff in an attack, tucked when afraid
31
- const TAIL_MOOD = { shepherd: { up: 1.25, down: -1.0 }, retriever: { up: 1.0, down: -1.3 }, terrier: { up: 0.3, down: -2.2 } };
32
- // roll of the curled sleep onto the outer hip (rad; the core's is 0.35). The terrier lies further over:
33
- // its chest rests on the ground with the shoulders so low that the elbow on the inside of the curl
34
- // sank 6 mm below its skin's clearance at 0.35 and had to be swung out of the ground (measured:
35
- // at 0.45 both elbows stay clear unswung on seeds 1-4, the lowest 1.4 mm above it)
36
- const CURL_ROLL = { terrier: 0.45 };
37
-
38
- function B(e) {
39
- if (!e._dog) {
40
- const v = e.params.variant || 'shepherd';
41
- const earType = e.params.earType || 'prick';
42
- e._dog = {
43
- variant: v, mood: TAIL_MOOD[v] || TAIL_MOOD.shepherd,
44
- happy: 0.4 + 0.3 * e.rand(), wagPh: 0, wagAmp: 0, wagAmpV: 0, wagFreq: 2.4, wagBias: 0, wagOn: 0,
45
- tongue: 0, tongueV: 0, lap: 0, lapW: 0, pant: 0,
46
- tilt: null, idleT: 4 + e.rand() * 6,
47
- swing: earType === 'drop' ? 1 : earType === 'button' ? 0.45 : 0,
48
- ear: [{ f: 0, vf: 0, s: 0, vs: 0 }, { f: 0, vf: 0, s: 0, vs: 0 }],
49
- hp: new THREE.Vector3(), hv: new THREE.Vector3(), ha: new THREE.Vector3(), hInit: 0,
50
- };
51
- }
52
- return e._dog;
53
- }
54
-
55
- // ------------------------------------------------------------------------------------------------
56
- // update: mood, tail, ears, panting, head tilt, idle variants
57
- function update(P, dt, L) {
58
- const e = L.engine, b = B(e), cfg = e.cfg;
59
- const vn = e.speed / cfg.sq;
60
- const moving = smooth(0.05, 0.8, vn) * P.gaitW;
61
- let attack = 0, hit = 0, greet = 0, bow = 0, eat = 0, drink = 0, sniff = 0;
62
- for (const a of L.oneshots) {
63
- const w = sm(a.w);
64
- if (w <= 0) continue;
65
- if (a.name === 'attack') {
66
- attack = Math.max(attack, w);
67
- const t = a.t;
68
- // threat -> lunge: hackles up, tail raised stiff, lips drawn back (the jaw opens a little on the
69
- // crouch so the canines show), a growl
70
- const snarl = smooth(0.0, 0.12, t) * (1 - smooth(0.24, 0.32, t));
71
- P.jaw = Math.max(P.jaw, 0.16 * snarl * w);
72
- mx(P, 'headRaise', -0.03 / cfg.unit, snarl * w);
73
- if (!a._voc) { a._voc = true; e.emit('vocalize', { kind: 'snarl' }); }
74
- } else if (a.name === 'hit') hit = Math.max(hit, w * (1 - smooth(0.9, 1.5, a.t)));
75
- else if (a.name === 'greet') greet = Math.max(greet, w);
76
- else if (a.name === 'playbow') bow = Math.max(bow, w);
77
- else if (a.name === 'eat') eat = Math.max(eat, w);
78
- else if (a.name === 'drink') { drink = Math.max(drink, w); b.lapT = a.t; }
79
- else if (a.name === 'sniff' || a.name === 'dig') sniff = Math.max(sniff, w);
80
- }
81
- let asleep = 0, dead = 0, lying = 0, sitting = 0;
82
- for (const p of L.postures) {
83
- const w = sm(clamp(p.w, 0, 1));
84
- if (p.name === 'sleep') asleep = Math.max(asleep, w * smooth(1.0, 3.0, p.t));
85
- if (p.name === 'death') dead = Math.max(dead, w);
86
- if (p.name === 'lie' || p.name === 'sleep') lying = Math.max(lying, w);
87
- if (p.name === 'sit') sitting = Math.max(sitting, w);
88
- // curled sleep: the tail is drawn round over the nose and forepaws
89
- if (p.name === 'sleep' && p.tune.style !== 'lateral') {
90
- const c = w * smooth(1.2, 3.2, p.t);
91
- mx(P, 'tailSide', p.side * 3.0, c); mx(P, 'tailStiff', 0.7, c); mx(P, 'bend', p.side * 1.35, c); mx(P, 'headYaw', p.side * 0.9, c); mx(P, 'headRoll', p.side * 0.3, c);
92
- if (CURL_ROLL[b.variant]) mx(P, 'roll', p.side * CURL_ROLL[b.variant], c);
93
- }
94
- // (the head lies on its side on the cheek and the lower ear: the occiput rests higher than the core's)
95
- if (p.name === 'death') { const c = w * smooth(0.3, 1.0, p.t); mx(P, 'earFlat', 1.1, c); mx(P, 'earTwitch', 0, c); P.headPos.y += (0.035 + 0.1 * Math.max(0, (e.params.headScale || 1) * (e.params.headW || 1) - 1)) * cfg.s * c; }
96
- if (p.name === 'sleep') { const c = w * smooth(1.0, 3.0, p.t); P.headPos.y += (0.03 + 0.22 * Math.max(0, (e.params.headScale || 1) * (e.params.headW || 1) - 1)) * cfg.s * c; mx(P, 'headPitch', 0.55, c); }
97
- }
98
-
99
- // eating: the mouth works just above the food (the jaw opens below the closed-mouth target)
100
- if (eat > 0) { P.headPos.y += 0.02 * cfg.s * eat; P.jaw = Math.min(P.jaw, 0.22); }
101
-
102
- // ---- mood: happiness rises when greeting / playing, fades slowly; fear and anger override it
103
- b.happy = clamp(b.happy + dt * (0.5 * Math.max(greet, bow) - 0.012) - dt * 0.6 * (attack + hit), 0.15, 1);
104
-
105
- // ---- tail carriage by mood
106
- mx(P, 'tailLift', b.mood.up, attack); mx(P, 'tailStiff', 2.0, attack); mx(P, 'tailIdle', 0.2, attack);
107
- mx(P, 'tailLift', b.mood.down, hit); mx(P, 'tailStiff', 1.6, hit); mx(P, 'tailIdle', 0, hit);
108
- // happy: carried a little higher (never above the back in a shepherd)
109
- const up = Math.max(greet, bow) * (b.variant === 'terrier' ? 0.1 : 0.55);
110
- if (up > 0) P.tailLift += up;
111
- P.furRaise = Math.max(P.furRaise || 0, attack * 0.8);
112
-
113
- // ---- wag: target amplitude (rad at the root segment) and rate
114
- let amp = 0, freq = 2.4;
115
- const idleW = L.idle.w;
116
- // relaxed: a loose wag while standing about, faster and wider the happier the dog is
117
- amp = idleW * (0.1 + 0.35 * b.happy) * (b.wagOn > 0 ? 1 : 0.35);
118
- freq = lerp(2.0, 3.0, b.happy);
119
- // on the move: a small wag at walk and trot, none at the gallop
120
- amp = Math.max(amp, moving * (1 - smooth(1.5, 4, vn)) * 0.12 * b.happy);
121
- // sitting: the tail sweeps the ground
122
- amp = Math.max(amp, sitting * 0.25 * b.happy);
123
- // greeting / play bow: fast, wide, whole-rear wag
124
- if (greet + bow > 0) { amp = lerp(amp, 0.62, Math.max(greet, bow)); freq = lerp(freq, 5.2, Math.max(greet, bow)); }
125
- if (sniff > 0) { amp = lerp(amp, 0.18, sniff); freq = lerp(freq, 3.2, sniff); }
126
- amp *= (1 - attack) * (1 - hit) * (1 - asleep) * (1 - dead) * (1 - 0.8 * lying) * (1 - 0.6 * eat);
127
- // attack: the raised tail quivers ("flagging")
128
- amp += attack * 0.05; if (attack > 0.3) freq = lerp(freq, 7, attack);
129
- b.wagAmpT = amp; b.wagFreqT = freq;
130
- // occasional bursts of wagging while idle
131
- b.wagOn -= dt;
132
- if (b.wagOn < -3 - 6 * (1 - b.happy) && idleW > 0.5) b.wagOn = 1.5 + 3 * e.rand();
133
-
134
- // ---- panting after running (the engine's heat), jaw wider, tongue out
135
- const hot = smooth(0.12, 0.45, e.heat) * (1 - smooth(5, 8, vn));
136
- b.pant = hot * (1 - attack) * (1 - asleep) * (1 - dead) * (1 - eat) * (1 - drink) * (1 - sniff);
137
- if (b.pant > 0.01) { P.jaw += 0.13 * b.pant; mx(P, 'breathAmp', 1.5, b.pant); }
138
- // tongue: panting, a happy greeting, lapping
139
- b.tongueT = Math.max(b.pant, 0.35 * greet * b.happy, 0.25 * bow);
140
- b.lapW = drink;
141
-
142
- // ---- ears
143
- // alert (a look target or sniffing): pricked forward; greeting: laid softly back; attack: core flattens
144
- if (e.input.look && attack < 0.01) mx(P, 'earFlat', -0.2, clamp(P.lookW, 0, 1));
145
- mx(P, 'earFlat', 0.45, greet); mx(P, 'earFlat', -0.15, bow + sniff);
146
- mx(P, 'earFlat', 1.1, hit);
147
-
148
- // ---- head tilt (idle variant): the head rolls 20-25 deg to one side, ears pricked, sometimes twice
149
- const tl = b.tilt;
150
- if (tl) {
151
- tl.t += dt;
152
- const k = smooth(0, 0.35, tl.t) * (1 - smooth(tl.dur, tl.dur + 0.4, tl.t)) * idleW;
153
- mx(P, 'headRoll', tl.side * 0.42, k); mx(P, 'headYaw', -tl.side * 0.12, k); mx(P, 'headPitch', -0.08, k);
154
- mx(P, 'earFlat', -0.25, k);
155
- if (tl.t > tl.dur + 0.4) b.tilt = tl.again ? { t: -0.2, dur: 0.8 + 0.6 * e.rand(), side: -tl.side, again: false } : null;
156
- if (idleW < 0.3) b.tilt = null;
157
- }
158
-
159
- // ---- automatic idle variants (standing still, nothing else running, nobody steering the gaze)
160
- if (e.idleVariants !== false && idleW > 0.98 && !busy(L) && !e.input.look && !e.input.follow && !b.tilt) {
161
- b.idleT -= dt;
162
- if (b.idleT <= 0) {
163
- b.idleT = 5 + e.rand() * 9;
164
- const r = e.rand();
165
- if (r < 0.34) b.tilt = { t: 0, dur: 1.0 + 1.2 * e.rand(), side: e.rand() < 0.5 ? -1 : 1, again: e.rand() < 0.4 };
166
- else if (r < 0.54) L.play('sniff', { duration: 2.5 + 2 * e.rand() });
167
- else if (r < 0.66) L.play('playbow');
168
- else if (r < 0.76) L.play('dig', { duration: 1.8 + e.rand() });
169
- else b.wagOn = 2 + 2 * e.rand(), b.happy = Math.min(1, b.happy + 0.2);
170
- }
171
- } else if (busy(L)) b.idleT = Math.max(b.idleT, 3);
172
- }
173
-
174
- // ------------------------------------------------------------------------------------------------
175
- // pose: wag, tongue, swinging ears (after the engine wrote every bone)
176
- function pose(e, dt) {
177
- const b = B(e), cfg = e.cfg;
178
- // wag state (critically damped amplitude, soft rate changes)
179
- const om = 7;
180
- b.wagAmpV += (om * om * ((b.wagAmpT || 0) - b.wagAmp) - 2 * om * b.wagAmpV) * dt;
181
- b.wagAmp += b.wagAmpV * dt;
182
- b.wagFreq += ((b.wagFreqT || 2.4) - b.wagFreq) * (1 - Math.exp(-dt * 3));
183
- b.wagPh = (b.wagPh + dt * TAU * b.wagFreq) % (TAU * 1000);
184
- // right-biased when positive (the dog's right = -x: negative yaw), smoothed (no steps)
185
- const biasT = -0.18 * b.happy * Math.max(0, b.wagAmp);
186
- b.biasV = (b.biasV || 0) + (om * om * (biasT - b.wagBias) - 2 * om * (b.biasV || 0)) * dt;
187
- b.wagBias += b.biasV * dt;
188
- const A = b.wagAmp;
189
- const lod = e.lod;
190
- // (crowds: only a real wag, not the relaxed idle sway)
191
- const minA = lod >= 2 ? 0.12 : 1e-4;
192
- if ((Math.abs(A) > minA || Math.abs(b.wagBias) > minA) && e.tail && e.tail.n) wagTail(e, b, A);
193
- // tongue (not in crowds)
194
- if (lod < 2) { clearJaw(e, b, dt); tongue(e, b, dt); }
195
- if (b.swing > 0 && lod < 2) swingEars(e, b, dt);
196
- void cfg;
197
- }
198
-
199
- // rotate the tail chain from the root: each segment turns about its own dorsal axis (lateral bending)
200
- function wagTail(e, b, A) {
201
- const n = e.tail.n, T = e.cfg.tail;
202
- const rad0 = T.radius[0] * e.cfg.s, rad1 = T.radius[1] * e.cfg.s;
203
- _lat.set(1, 0, 0).applyQuaternion(e.fr.qPel);
204
- _q2.identity(); // cumulative rotation
205
- let px = 0, py = 0, pz = 0; // accumulated new head position
206
- for (let i = 0; i < n; i++) {
207
- const bone = e.bone['tail' + i];
208
- if (!bone) return;
209
- const m = bone.matrixWorld;
210
- _p.setFromMatrixPosition(m);
211
- _q.setFromRotationMatrix(m);
212
- if (i === 0) { px = _p.x; py = _p.y; pz = _p.z; }
213
- _d.set(0, 1, 0).applyQuaternion(_q); // original direction
214
- // the segment's dorsal axis: lat x dir, plus the pelvis' up axis orthogonal to the segment (never
215
- // zero together, and both point the same way: continuous for a hanging, level, erect or curled tail)
216
- const ax = tv().crossVectors(_lat, _d);
217
- const upx = tv(0, 1, 0).applyQuaternion(e.fr.qPel);
218
- ax.add(upx.addScaledVector(_d, -upx.dot(_d))).normalize();
219
- const a = (A * Math.sin(b.wagPh - 0.38 * i) + b.wagBias) * TAIL_W[Math.min(i, TAIL_W.length - 1)];
220
- _q2.multiply(tqa(ax, a));
221
- const len = e.tail.len[i];
222
- const newQ = tqa(ax, 0).copy(_q2).multiply(_q);
223
- const dn = tv(0, 1, 0).applyQuaternion(newQ);
224
- // the wag never swings a segment into the ground (a tail lying on it when sitting or lying):
225
- // raise the segment's slope just enough, keeping its heading; the rest of the tail follows
226
- const floor = e.terrainH(px + dn.x * len, pz + dn.z * len) + lerp(rad0, rad1, (i + 1) / n);
227
- if (py + dn.y * len < floor) {
228
- const dy = clamp((floor - py) / len, -1, 1), hl = Math.hypot(dn.x, dn.z) || 1, c = Math.sqrt(1 - dy * dy);
229
- const d2 = tv(dn.x / hl * c, dy, dn.z / hl * c);
230
- const fix = tq().setFromUnitVectors(dn, d2);
231
- newQ.premultiply(fix); _q2.premultiply(fix);
232
- dn.copy(d2);
233
- }
234
- m.compose(tv(px, py, pz), newQ, _s);
235
- px += dn.x * len; py += dn.y * len; pz += dn.z * len;
236
- }
237
- }
238
-
239
- // the lower jaw never enters the ground (the core clears the closed mouth; an open jaw reaches lower
240
- // when tearing food or panting with the head down): close it about the hinge just enough, springy
241
- function clearJaw(e, b, dt) {
242
- const jaw = e.bone.jaw;
243
- if (!jaw) return;
244
- const s = e.cfg.s, m = jaw.matrixWorld;
245
- _p.setFromMatrixPosition(m);
246
- _q.setFromRotationMatrix(m);
247
- _d.set(0, 1, 0).applyQuaternion(_q);
248
- const len = b.jawLen || (b.jawLen = e.J.jawHinge.distanceTo(e.J.jawTip));
249
- let need = 0;
250
- // chin (under the jaw tip) and the middle of the mandible's lower edge
251
- for (const [t, r] of [[1, 0.012], [0.55, 0.02]]) {
252
- const pt = tv().copy(_p).addScaledVector(_d, len * t);
253
- const lack = e.terrainH(pt.x, pt.z) + r * s - pt.y;
254
- if (lack > 0) need = Math.max(need, Math.asin(clamp(lack / (len * t), 0, 1)));
255
- }
256
- // rate-limited release (no snap back when the head lifts)
257
- b.jawClr = Math.max(need, (b.jawClr || 0) - dt * 2);
258
- if (b.jawClr < 1e-4) return;
259
- _hx.set(1, 0, 0).applyQuaternion(_q);
260
- // (closing = rotating the jaw's +Y up about its lateral axis: toward the skull)
261
- _q.premultiply(tqa(_hx, -b.jawClr));
262
- m.compose(_p, _q, _s);
263
- // the tongue rides on the jaw: re-derive it from the corrected jaw
264
- const tb = e.bone.tongue;
265
- if (tb) for (const x of e.extraBones) if (x.bone === tb) tb.matrixWorld.multiplyMatrices(m, x.rel);
266
- }
267
-
268
- // tongue: slides out over the lower incisors and hangs (panting), or ladles in and out (lapping)
269
- function tongue(e, b, dt) {
270
- const bone = e.bone.tongue;
271
- if (!bone) return;
272
- // lapping: ~3 laps / s in phase with the built-in drink's jaw (tongue curled back into a ladle)
273
- let tgt = b.tongueT || 0, droop = 0.55;
274
- if (b.lapW > 0.01) {
275
- const lap = Math.max(0, Math.sin((b.lapT || 0) * 3 * TAU));
276
- tgt = lerp(tgt, 0.3 + 0.45 * lap, b.lapW);
277
- droop = lerp(droop, 0.15 + 0.35 * lap, b.lapW);
278
- }
279
- const om = b.lapW > 0.01 ? 30 : 9;
280
- b.tongueV += (om * om * (tgt - b.tongue) - 2 * om * b.tongueV) * dt;
281
- b.tongue += b.tongueV * dt;
282
- const out = clamp(b.tongue, 0, 1.2);
283
- if (out < 1e-3) return;
284
- const s = e.cfg.s;
285
- const m = bone.matrixWorld;
286
- _p.setFromMatrixPosition(m);
287
- _q.setFromRotationMatrix(m);
288
- _d.set(0, 1, 0).applyQuaternion(_q);
289
- _hx.set(1, 0, 0).applyQuaternion(_q);
290
- // slide forward, then droop about the lower incisors; the hanging part bobs with the breath
291
- const slide = 0.058 * s * out;
292
- const pivotAlong = 0.11 * s;
293
- const ang = droop * smooth(0, 0.6, out) * (1 + 0.12 * Math.sin(e.breath));
294
- const base0 = tv().copy(_p), pivot = tv().copy(_p).addScaledVector(_d, pivotAlong);
295
- // (the tip never enters the ground: the tongue first slides back in just enough (the tip's height is
296
- // linear in the slide, well conditioned while the tongue points down, as when lapping with the head
297
- // low), then the droop is reduced, down to curling the tongue up; both by bisection, continuous in
298
- // the pose. Reducing only the droop near the bottom of its arc swung the tongue ~0.6 rad in 10 ms.)
299
- const len = e.J.tongueBase.distanceTo(e.J.tongueTip), p0 = tv();
300
- const tipY = (a, sl) => {
301
- const R = tqa(_hx, a);
302
- p0.copy(base0).addScaledVector(_d, sl);
303
- const base = tv().subVectors(p0, pivot).applyQuaternion(R).add(pivot);
304
- const tip = tv().copy(_d).applyQuaternion(R).multiplyScalar(len).add(base);
305
- return tip.y - (e.terrainH(tip.x, tip.z) + 0.004 * s); // (it may dip into water: lapping)
306
- };
307
- let A = ang, sl = slide;
308
- if (tipY(A, sl) < 0) {
309
- if (tipY(A, 0) >= 0) {
310
- // the longest feasible slide
311
- let lo = 0, hi = sl;
312
- for (let i = 0; i < 14; i++) { const mid = (lo + hi) / 2; if (tipY(A, mid) >= 0) lo = mid; else hi = mid; }
313
- sl = lo;
314
- } else {
315
- // fully in, and the largest feasible droop below A: scan down, then refine between the last two samples
316
- sl = 0;
317
- let hi = A, lo = A;
318
- const step = (A + 0.9) / 24;
319
- for (let i = 1; i <= 24; i++) { lo = A - step * i; if (tipY(lo, 0) >= 0) break; hi = lo; }
320
- for (let i = 0; i < 12; i++) { const mid = (lo + hi) / 2; if (tipY(mid, 0) >= 0) lo = mid; else hi = mid; }
321
- A = lo;
322
- }
323
- }
324
- p0.copy(base0).addScaledVector(_d, sl);
325
- A *= smooth(0, 0.25, out); // (retracted: rigid on the jaw, whatever the clamp says)
326
- const R = tqa(_hx, A);
327
- const rel = tv().subVectors(p0, pivot).applyQuaternion(R);
328
- _p.copy(pivot).add(rel);
329
- _q.premultiply(R);
330
- m.compose(_p, _q, _s);
331
- }
332
-
333
- // soft drop ears: two damped pendulum angles per ear in the head frame (fore-aft about the head's
334
- // lateral axis, outward about its long axis), driven by the head's acceleration and by gravity
335
- function swingEars(e, b, dt) {
336
- if (!(dt > 1e-5)) return;
337
- const hp = e.headPose.position, hq = e.headPose.quaternion;
338
- if (b.hInit < 2) {
339
- if (b.hInit === 1) b.hv.subVectors(hp, b.hp).multiplyScalar(1 / dt);
340
- b.hp.copy(hp); b.hInit++;
341
- return;
342
- }
343
- const v = tv().subVectors(hp, b.hp).multiplyScalar(1 / dt);
344
- const acc = tv().subVectors(v, b.hv).multiplyScalar(1 / dt);
345
- b.hp.copy(hp); b.hv.copy(v);
346
- // low-pass the acceleration (finite differences of a stepped body are noisy)
347
- b.ha.lerp(acc.clampLength(0, 60), 1 - Math.exp(-dt * 30));
348
- _qi.copy(hq).invert();
349
- _a.copy(b.ha).applyQuaternion(_qi); // head-local acceleration
350
- _g.set(0, -1, 0).applyQuaternion(_qi); // gravity direction in the head frame
351
- const s = e.cfg.s;
352
- const lenE = 0.09 * s;
353
- const om = 13 / Math.sqrt(s), z = 0.28;
354
- const K = b.swing;
355
- _hx.set(1, 0, 0).applyQuaternion(hq); _hz.set(0, 0, 1).applyQuaternion(hq);
356
- const sub = Math.max(1, Math.ceil(dt / (1 / 120))), h = dt / sub;
357
- for (let k = 0; k < 2; k++) {
358
- const E = b.ear[k], side = k === 0 ? 1 : -1;
359
- // equilibrium: hang along gravity (a lowered or tilted head swings the flaps), outward flop when the
360
- // head drops fast (trot bounce)
361
- const tf = -0.8 * Math.asin(clamp(_g.z, -1, 1));
362
- let ts = 0.8 * Math.asin(clamp(_g.x, -1, 1));
363
- for (let i = 0; i < sub; i++) {
364
- const af = om * om * (tf - E.f) - 2 * z * om * E.vf + (_a.z / lenE) * 0.9;
365
- const as = om * om * (ts - E.s) - 2 * z * om * E.vs - (_a.x / lenE) * 0.9 - side * Math.min(0, _a.y) / lenE * 0.35;
366
- E.vf += af * h; E.f += E.vf * h;
367
- E.vs += as * h; E.s += E.vs * h;
368
- // the flap cannot swing into the cheek; limits
369
- if (side * E.s < -0.04) { E.s = -0.04 * side; if (side * E.vs < 0) E.vs = 0; }
370
- E.f = clamp(E.f, -0.55, 0.55); E.s = clamp(E.s, -0.65, 0.65);
371
- }
372
- const bone = e.bone[k === 0 ? 'earL' : 'earR'];
373
- if (!bone) continue;
374
- const m = bone.matrixWorld;
375
- _p.setFromMatrixPosition(m);
376
- _q.setFromRotationMatrix(m);
377
- // the flap never swings into the ground (a head lowered or lying on its side): keep the largest
378
- // share of the swing that leaves its tip and its broad middle clear (the core cleared the unswung ear)
379
- const L = b.earLen || (b.earLen = e.J.earBaseL.distanceTo(e.J.earTipL));
380
- const clear = (f) => {
381
- const q = tqa(_hz, E.s * K * f).multiply(tqa(_hx, E.f * K * f)).multiply(_q);
382
- const dy = tv(0, 1, 0).applyQuaternion(q);
383
- let worst = 1;
384
- for (const t of [1, 0.6]) {
385
- const pt = tv().copy(_p).addScaledVector(dy, L * t);
386
- worst = Math.min(worst, pt.y - (e.terrainH(pt.x, pt.z) + 0.012 * s));
387
- }
388
- return worst;
389
- };
390
- let f = 1;
391
- if (clear(1) < 0) {
392
- let lo = 0, hi = 1;
393
- for (let i = 0; i < 10; i++) { const mid = (lo + hi) / 2; if (clear(mid) >= 0) lo = mid; else hi = mid; }
394
- f = lo;
395
- E.vf *= 0.5; E.vs *= 0.5;
396
- }
397
- _q.premultiply(tqa(_hx, E.f * K * f)).premultiply(tqa(_hz, E.s * K * f));
398
- m.compose(_p, _q, _s);
399
- }
400
- }
401
-
402
- // ------------------------------------------------------------------------------------------------
403
- // actions
404
-
405
- // greeting: fast wide wag that swings the whole rear, body wiggles, ears back, head a little low
406
- const greet = {
407
- kind: 'oneshot', fade: 0.3,
408
- start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 3); L.engine.emit('vocalize', { kind: 'whine' }); },
409
- update(inst) { return inst.t >= inst.dur; },
410
- apply(P, w, inst, L) {
411
- const e = L.engine, t = inst.t, b = B(e);
412
- const k = w * smooth(0, 0.4, t);
413
- // the rear swings with the wag ("wiggle"): a lateral spine bend in phase with the tail
414
- mx(P, 'bend', 0.14 * Math.sin(b.wagPh + 0.6), k);
415
- mx(P, 'roll', 0.03 * Math.sin(b.wagPh), k);
416
- mx(P, 'headRaise', -0.015 / e.cfg.unit, k); mx(P, 'headPitch', -0.1, k);
417
- // small bounces on the forelegs
418
- mx(P, 'dropF', 0.06 + 0.05 * Math.sin(t * TAU * 2.2), k);
419
- mx(P, 'eyelid', 0.2, k);
420
- },
421
- };
422
-
423
- // play bow: forequarters down on the elbows, forelegs stretched forward, rear up, tail up and wagging
424
- const playbow = {
425
- kind: 'oneshot', fade: 0.3, needsStand: true,
426
- start(inst, L) {
427
- inst.dur = inst.opts.duration ?? 2.4;
428
- const e = L.engine;
429
- if (e.rand() < 0.5) e.emit('vocalize', { kind: 'bark' });
430
- },
431
- update(inst) { return inst.t >= inst.dur; },
432
- apply(P, w, inst, L) {
433
- const e = L.engine, t = inst.t;
434
- const down = w * smooth(0.0, 0.5, t) * (1 - smooth(inst.dur - 0.5, inst.dur, t));
435
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
436
- // the forelegs fold forward along the ground as in the sphinx (the core's lying fold), the chest
437
- // drops to the elbows while the hind legs stay straight, rear up
438
- mx(P, 'dropF', 0.88, down); mx(P, 'dropH', -0.06, down);
439
- for (let i = 0; i < 2; i++) mxLeg(P, i, 'fold', 1, smooth(0.1, 0.8, down));
440
- mx(P, 'headRaise', 0.03 / e.cfg.unit, down); mx(P, 'headPitch', -0.3, down); mx(P, 'neckReach', 0.02 / e.cfg.unit, down);
441
- mx(P, 'lookW', 0, down);
442
- },
443
- };
444
-
445
- // sniffing: nose to the ground ahead, sweeping side to side
446
- const sniff = {
447
- kind: 'oneshot', fade: 0.5,
448
- start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 4); inst.ph = L.engine.rand() * 6; },
449
- update(inst) { return inst.t >= inst.dur; },
450
- apply(P, w, inst, L) {
451
- const e = L.engine, t = inst.t, cfg = e.cfg;
452
- const pitchA = 0.82;
453
- const hp = mouthGroundTarget(e, tv(), pitchA, (x, z) => e.terrainH(x, z), 0.95);
454
- // side to side sweeps, the nose just above the ground; quick sniff bobs
455
- const sweep = Math.sin(t * 1.3 + inst.ph) * 0.8 + Math.sin(t * 0.47 + 2 * inst.ph) * 0.2;
456
- hp.addScaledVector(e.left(e.heading, tv()), 0.07 * cfg.s * sweep);
457
- hp.y += (0.012 + 0.006 * Math.sin(t * TAU * 5)) * cfg.s;
458
- P.headPos.copy(hp);
459
- mx(P, 'headW', 1, w); mx(P, 'headReach', 1, w); mx(P, 'headPitch', pitchA, w); mx(P, 'headYaw', 0.3 * sweep, w);
460
- mx(P, 'lookW', 0, w); mx(P, 'headOmega', 1.6, w);
461
- mx(P, 'gaitW', 0, w); mx(P, 'dropF', 0.18, w);
462
- mx(P, 'speedScale', 0, w);
463
- },
464
- };
465
-
466
- // pawing the ground: head down over the spot, the forepaws scrape it alternately, back under the chest
467
- const dig = {
468
- kind: 'oneshot', fade: 0.3, needsStand: true,
469
- start(inst, L) {
470
- inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 2.5);
471
- const e = L.engine;
472
- inst.base = [0, 1].map((i) => new THREE.Vector3().copy(e.legs[i].contact));
473
- inst.fwd = e.forward(e.heading, new THREE.Vector3());
474
- },
475
- update(inst) { return inst.t >= inst.dur; },
476
- apply(P, w, inst, L) {
477
- const e = L.engine, t = inst.t, cfg = e.cfg;
478
- const k = w * smooth(0, 0.3, t);
479
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
480
- mx(P, 'dropF', 0.3, k); mx(P, 'dropH', 0.05, k);
481
- mx(P, 'headPitch', 0.75, k); mx(P, 'headRaise', -0.06 / cfg.unit, k); mx(P, 'lookW', 0, k);
482
- mx(P, 'earFlat', -0.1, k);
483
- const env = smooth(0.15, 0.4, t) * (1 - smooth(inst.dur - 0.35, inst.dur, t));
484
- for (let i = 0; i < 2; i++) {
485
- const leg = e.legs[i], len = leg.def.len;
486
- // one stroke: reach forward high, press down, drag back under the chest, lift (3 Hz, legs alternate)
487
- const ph = (t * 3.0 + i * 0.5) % 1;
488
- const u = ph < 0.55 ? 0.28 - 0.4 * smooth(0, 0.55, ph) : -0.12 + 0.4 * smooth(0.55, 1, ph);
489
- const hgt = ph < 0.55 ? 0.03 : 0.03 + 0.08 * Math.sin(Math.PI * (ph - 0.55) / 0.45);
490
- const tp = tv().copy(inst.base[i]).addScaledVector(inst.fwd, u * len);
491
- tp.y = e.terrainH(tp.x, tp.z) + hgt * len;
492
- // the planted foot stays where it stood outside the strokes
493
- tp.lerp(inst.base[i], 1 - env);
494
- const pl = P.legs[i];
495
- pl.target.copy(tp);
496
- mxLeg(P, i, 'reach', 1, w);
497
- }
498
- if (Math.floor(t * 3) !== inst.lastStroke) { inst.lastStroke = Math.floor(t * 3); }
499
- },
500
- };
501
-
502
- // hit: the built-in flinch, then a yelp and a cower (body low, tail tucked, ears flat, head down)
503
- const hit = {
504
- kind: 'oneshot', fade: 0.12,
505
- start(inst, L) { ONESHOTS.hit.start(inst, L); inst.dur = 1.6; L.engine.emit('vocalize', { kind: 'yelp' }); },
506
- update(inst) { return inst.t >= inst.dur; },
507
- apply(P, w, inst, L) {
508
- ONESHOTS.hit.apply(P, w, inst, L);
509
- const t = inst.t, e = L.engine;
510
- const c = w * smooth(0.12, 0.4, t) * (1 - smooth(1.0, 1.6, t));
511
- mx(P, 'dropF', 0.3, c); mx(P, 'dropH', 0.42, c);
512
- mx(P, 'headRaise', -0.035 / e.cfg.unit, c); mx(P, 'headPitch', 0.25, c);
513
- mx(P, 'eyelid', 0.35, c); mx(P, 'lookW', 0.3, c);
514
- },
515
- };
516
-
517
- // the curled sleep: the core's, unrolling a little more slowly when the dog gets up (the forelegs
518
- // come out from under the curled body)
519
- const sleep = { ...POSTURES.sleep, kind: 'posture', fadeOut: 1.6 };
520
-
521
- export const dogHooks = { update, pose, actions: { greet, playbow, sniff, dig, hit, sleep } };