@bitefoundry/retrieviewer 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +126 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +86 -2
  12. package/RELEASING.md +70 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/WINDOWS-COMPANION.md +113 -0
  16. package/braille.mjs +45 -0
  17. package/examples/bridge.py +62 -0
  18. package/examples/embed.mjs +15 -0
  19. package/examples/live.mjs +40 -0
  20. package/examples/plugin.mjs +21 -0
  21. package/integrations/opencode/runtime.mjs +195 -0
  22. package/integrations/opencode/tui.tsx +59 -0
  23. package/integrations/windows-overlay/Overlay.cs +259 -0
  24. package/integrations/windows-overlay/desktop.mjs +87 -0
  25. package/integrations/windows-overlay/protocol.mjs +39 -0
  26. package/integrations/windows-overlay/worker.mjs +60 -0
  27. package/locomotion-core.mjs +21 -0
  28. package/locomotion-puppy.animal +0 -0
  29. package/package.json +131 -4
  30. package/pet-q5-manifest.json +118 -0
  31. package/puppy-clean.mjs +51 -0
  32. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  33. package/puppy-companion-interactions-v3.json +1 -0
  34. package/puppy-companion-life.mjs +182 -0
  35. package/puppy-companion-objects.mjs +30 -0
  36. package/puppy-companion-sprites.mjs +14 -0
  37. package/puppy-continuous.mjs +57 -0
  38. package/puppy-face.mjs +46 -0
  39. package/puppy-frame-predictive.mjs +147 -0
  40. package/puppy-frame-store.mjs +108 -0
  41. package/puppy-mouth-anchor.json +1 -0
  42. package/puppy-mouth-anchor.mjs +33 -0
  43. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  44. package/puppy-paper-actions-v9.json +1 -0
  45. package/puppy-paper-actions.mjs +48 -0
  46. package/puppy-paper-approach-q5.bin.gz +0 -0
  47. package/puppy-paper-approach.json +1 -0
  48. package/puppy-paper-area.mjs +15 -0
  49. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  50. package/puppy-paper-arrival-seat.json +1 -0
  51. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  52. package/puppy-paper-behaviors.json +1 -0
  53. package/puppy-paper-behaviors.mjs +8 -0
  54. package/puppy-paper-contact.mjs +103 -0
  55. package/puppy-paper-director.mjs +21 -0
  56. package/puppy-paper-paw-transport.mjs +76 -0
  57. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  58. package/puppy-paper-rear-gallop.json +1 -0
  59. package/puppy-paper-seat-q5.bin.gz +0 -0
  60. package/puppy-paper-seat.json +14 -0
  61. package/puppy-paper-seat.mjs +13 -0
  62. package/puppy-paper-trick-timing.mjs +58 -0
  63. package/puppy-paper-world.mjs +108 -0
  64. package/puppy-paper.mjs +271 -0
  65. package/puppy-stable-meta.json +1606 -0
  66. package/puppy-stable.mjs +72 -0
  67. package/puppy-stride-meta.json +343 -0
  68. package/puppy-stride-tweens-q5.bin.gz +0 -0
  69. package/puppy-zoomies.mjs +27 -0
  70. package/puppy-zoomies30.mjs +12 -0
  71. package/retrieviewer-cli.mjs +94 -0
  72. package/retrieviewer-config.mjs +49 -0
  73. package/retrieviewer-install.mjs +157 -0
  74. package/retrieviewer-plugin.d.mts +44 -0
  75. package/retrieviewer-plugin.mjs +97 -0
  76. package/retrieviewer-stdio.mjs +38 -0
  77. package/retrieviewer.d.mts +85 -0
  78. package/retrieviewer.mjs +142 -0
  79. package/vendor/procedural-animals/LICENSE +21 -0
  80. package/vendor/procedural-animals/package.json +40 -0
  81. package/vendor/procedural-animals/src/animal.js +146 -0
  82. package/vendor/procedural-animals/src/bake.js +51 -0
  83. package/vendor/procedural-animals/src/build.js +81 -0
  84. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  85. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  86. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  87. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  88. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  89. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  90. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  91. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  92. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  93. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  94. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  95. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  96. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  97. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  98. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  99. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  100. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  101. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  102. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  103. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  104. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  105. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  106. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  107. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  108. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  109. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  110. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  111. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  112. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  113. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  114. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  115. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  116. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  117. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  118. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  119. package/vendor/procedural-animals/src/index.d.ts +98 -0
  120. package/vendor/procedural-animals/src/index.js +29 -0
  121. package/vendor/procedural-animals/src/registry.js +48 -0
  122. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  123. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  124. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  125. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  126. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  127. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  128. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  129. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  130. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  131. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  132. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  133. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  134. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  135. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  136. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  137. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  138. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  139. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  140. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  141. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  142. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  143. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  144. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  145. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  146. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  147. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  148. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  149. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  150. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  151. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  152. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  153. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  154. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  155. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  156. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  157. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  158. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  159. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  160. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  161. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  162. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  163. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  164. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  165. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  166. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  167. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  168. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  169. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  170. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  171. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  172. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  173. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  174. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  175. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  176. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  177. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  178. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  179. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  180. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  181. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  182. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  183. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  184. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  185. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  186. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  187. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  188. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  189. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  190. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  191. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  192. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  193. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  194. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  195. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  196. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  197. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  198. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  199. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  200. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  201. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  202. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  203. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  204. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  205. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  206. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  207. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  208. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  209. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  210. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  211. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  212. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  213. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  214. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  215. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  216. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  217. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  218. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  219. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  220. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  221. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  222. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  223. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  224. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  225. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  226. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  227. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  228. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  229. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  230. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  231. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  232. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  233. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  234. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  235. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  236. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  237. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  238. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  239. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  240. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  241. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  242. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  243. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  244. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  245. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  246. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  247. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  248. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  249. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  250. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  251. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  252. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  253. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  254. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  255. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  256. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  257. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  258. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  259. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  260. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  261. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  262. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  263. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  264. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  265. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  266. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  267. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  268. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  269. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  270. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  271. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  272. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  273. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  274. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  275. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  276. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  277. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  278. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  279. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  280. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  281. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  282. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  283. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  284. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  285. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  286. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  287. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  288. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  289. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  290. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  291. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  292. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  293. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  294. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  295. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  296. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  297. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,315 @@
1
+ // Snake coat: overlapping dorsal scales (keeled on the rattlesnake), wide ventral scutes, head plates;
2
+ // corn snake saddles with dark borders, lateral blotches and the checkerboard belly; rattlesnake
3
+ // diamonds with pale edges, flank blotches, face stripes and the black-and-white "coon tail".
4
+ // Everything is laid out in body coordinates: arc length sigma along the spine from the occiput and
5
+ // the arc u around the body from the dorsal midline, so the pattern wraps the tube like the real one.
6
+ import { SPINE } from './rig.js';
7
+ import { eyeSpec } from './sculpt.js';
8
+ import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
9
+ import { srgb, mix3, MAT, distPolyline } from '../../core/build/coatKit.js';
10
+ import { clamp, smoothstep, mix, fbm3, rng } from '../../core/math/vec.js';
11
+
12
+ // Colour sets (sRGB hex sampled from reference photos in neutral light).
13
+ export const MORPHS = {
14
+ normal: { ground: 0xc9763f, flank: 0xd99a5c, saddle: 0xa8321f, lateral: 0x983a26, border: 0x1b1310, belly: 0xe9e2d2, check: 0x171412, mark: [0.012, 0.009, 0.008] },
15
+ okeetee: { ground: 0xd6843f, flank: 0xe0a45e, saddle: 0xb52f1c, lateral: 0xa23421, border: 0x100b09, belly: 0xece4d2, check: 0x141110, mark: [0.006, 0.004, 0.004], borderW: 2.6 },
16
+ wild: { ground: 0xa68a6c, flank: 0xb89c7c, saddle: 0x8a3a24, lateral: 0x7c3a28, border: 0x1d1512, belly: 0xe4ddcc, check: 0x1a1715, mark: [0.013, 0.01, 0.009] },
17
+ amel: { ground: 0xeb9656, flank: 0xf0b070, saddle: 0xd9482a, lateral: 0xd65a36, border: 0xf2ece0, belly: 0xf3eee4, check: 0xe8a070, mark: [0.86, 0.82, 0.74], borderW: 1.2 },
18
+ anery: { ground: 0xa9a6a2, flank: 0xbdbab4, saddle: 0x4a4847, lateral: 0x55524f, border: 0x151414, belly: 0xe6e4e0, check: 0x161515, mark: [0.01, 0.01, 0.01] },
19
+ juvenile: { ground: 0x9c8672, flank: 0xae9a86, saddle: 0x6e2a1c, lateral: 0x5e2a20, border: 0x140e0c, belly: 0xe6e0d2, check: 0x151211, mark: [0.008, 0.006, 0.006] },
20
+ };
21
+ export const DIAMONDBACK = {
22
+ ground: 0x8a7560, flank: 0x9a846a, diamond: 0x56422f, centre: 0x76604a, edge: 0xcdbd9c, blotch: 0x5e4a38,
23
+ belly: 0xe2d6bc, ringW: 0xe6e0d4, ringB: 0x1a1715, rattle: 0xbfae8c, mark: [0.69, 0.6, 0.45],
24
+ };
25
+
26
+ export function markColorOf(params) {
27
+ if (params.variant === 'rattlesnake') return DIAMONDBACK.mark;
28
+ return (MORPHS[params.coat?.morph] || MORPHS.normal).mark;
29
+ }
30
+
31
+ export function snakeCoat(ctx) {
32
+ const { rig, params, pos, nrm, nV, regionOf, regionNames, weights } = ctx;
33
+ const J = rig.J, B = rig.plan, viper = params.variant === 'rattlesnake';
34
+ const cp = params.coat || {};
35
+ const R = rng(9173 + (cp.seed || 0));
36
+ const AXL = weights.axialLengths;
37
+ const s0 = AXL[1]; // arc of v0 from the nose
38
+ const total = AXL[AXL.length - 1] - s0;
39
+ const sVent = total * (1 - B.tailFrac);
40
+ const hL = B.headLen, W = B.P.headW * B.kH, H = B.P.headH * B.kH;
41
+ const yB = -J.v0[1];
42
+ // axis samples: y and half width at arc sigma
43
+ const axY = (sg) => {
44
+ let i = 1;
45
+ while (i < AXL.length - 2 && sg + s0 > AXL[i + 1]) i++;
46
+ const t = clamp((sg + s0 - AXL[i]) / (AXL[i + 1] - AXL[i]), 0, 1);
47
+ const a = J['v' + (i - 1)], b = J['v' + i];
48
+ return a[1] + (b[1] - a[1]) * t;
49
+ };
50
+ const hwAt = (sg) => B.hw(clamp(sg / total, 0, 1));
51
+
52
+ // ---- colours
53
+ const juv = params.age === 'juvenile';
54
+ const morph = viper ? null : juv && (cp.morph === 'normal' || cp.morph === 'wild') ? MORPHS.juvenile : MORPHS[cp.morph] || MORPHS.normal;
55
+ const warm = cp.warm || 0, light = cp.light || 0;
56
+ const tweak = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.1 * warm + light), c[1] * (1 + 0.02 * warm + light), c[2] * (1 - 0.1 * warm + light)]; };
57
+ const C0 = viper
58
+ ? Object.fromEntries(Object.entries(DIAMONDBACK).filter(([k]) => k !== 'mark').map(([k, v]) => [k, tweak(v)]))
59
+ : Object.fromEntries(Object.entries(morph).filter(([k]) => typeof morph[k] === 'number' && k !== 'borderW').map(([k, v]) => [k, tweak(v)]));
60
+ // individuals range from bright orange to duller, browner / greyer grounds
61
+ const C = C0;
62
+ if (!viper && (cp.morph === 'normal' || cp.morph === 'okeetee')) {
63
+ const d = clamp(cp.dull || 0, 0, 0.7);
64
+ C.ground = mix3(C.ground, srgb(MORPHS.wild.ground), d); C.flank = mix3(C.flank, srgb(MORPHS.wild.flank), d);
65
+ C.saddle = mix3(C.saddle, srgb(0x8e2c1c), d * 0.6);
66
+ }
67
+ if (viper) {
68
+ // wild C. atrox range from pale grey through grey-brown to reddish-brown, with bolder or fainter
69
+ // diamonds (seeded tone: < 0 greyer, > 0 redder; light: paler / darker)
70
+ const tone = clamp(cp.tone || 0, -1, 1), lt = cp.light || 0;
71
+ const tgt = tone < 0 ? srgb(0x8f8c86) : srgb(0x9a6a4e);
72
+ const k = Math.abs(tone) * 0.75;
73
+ for (const key of ['ground', 'flank', 'centre']) C[key] = mix3(C[key], [tgt[0] * (key === 'flank' ? 1.08 : 1), tgt[1] * (key === 'flank' ? 1.08 : 1), tgt[2] * (key === 'flank' ? 1.08 : 1)], k);
74
+ C.diamond = mix3(C.diamond, tone < 0 ? srgb(0x4a4744) : srgb(0x5a3526), k * 0.7);
75
+ for (const key of ['ground', 'flank', 'centre', 'edge']) C[key] = C[key].map((c) => c * (1 + 1.6 * lt));
76
+ C.diamond = mix3(C.diamond, C.ground, clamp(0.3 - 0.3 * ((cp.blotch || 1) - 1) / 0.18, 0, 0.55));
77
+ }
78
+ const bk = cp.blotch || 1;
79
+
80
+ // ---- pattern features (body coordinates)
81
+ const sStart = viper ? 0.045 * total : Math.max(0, 1.5 * hL - (0.5 * (total * (1 - B.tailFrac) - 1.5 * hL)) / 36);
82
+ const feats = []; // { s, a, b, u (centre across), kind, col }
83
+ if (viper) {
84
+ const n = Math.round((cp.count || 1) * 29);
85
+ const sp = (sVent - sStart) / n;
86
+ for (let k = 0; k < n; k++) {
87
+ const s = sStart + (k + 0.5) * sp + (R() - 0.5) * 0.08 * sp;
88
+ feats.push({ s, a: 0.56 * sp, b: 0.9, u: 0, kind: 'diamond' });
89
+ // flank blotches below the lateral corners of the diamonds
90
+ for (const side of [1, -1]) feats.push({ s: s + 0.5 * sp + (R() - 0.5) * 0.1 * sp, a: 0.2 * sp, b: 0.28, u: side * 1.55, kind: 'blotch' });
91
+ }
92
+ } else {
93
+ const n = Math.round((cp.count || 1) * 36);
94
+ const sp = (sVent - sStart) / n;
95
+ for (let k = 0; k < n; k++) {
96
+ const s = sStart + (k + 0.5) * sp + (R() - 0.5) * 0.14 * sp;
97
+ feats.push({ s, a: (0.25 + 0.06 * R()) * sp * bk, b: (1.0 + 0.14 * R()) * bk, u: (R() - 0.5) * 0.12, kind: 'saddle' });
98
+ for (const side of [1, -1]) if (R() < 0.85) feats.push({ s: s + 0.5 * sp + (R() - 0.5) * 0.2 * sp, a: (0.15 + 0.05 * R()) * sp * bk, b: 0.3 + 0.08 * R(), u: side * (1.72 + 0.1 * R()), kind: 'lateral' });
99
+ }
100
+ // tail: saddles continue, closer
101
+ const nt = Math.round(11 * (cp.count || 1));
102
+ const spt = (total * 0.985 - sVent) / nt;
103
+ for (let k = 0; k < nt; k++) feats.push({ s: sVent + (k + 0.5) * spt, a: 0.27 * spt * bk, b: 1.2, u: 0, kind: 'saddle' });
104
+ }
105
+ feats.sort((p, q) => p.s - q.s);
106
+ // belly checks (corn): blocks on alternating sides, scute by scute
107
+ const checks = [];
108
+ const scute = viper ? total / 182 : total / 215;
109
+ if (!viper) {
110
+ for (let s = 0.09 * total; s < sVent; s += scute) {
111
+ if (R() < 0.5) checks.push({ s: s + scute * (0.4 + 0.2 * R()), a: scute * (0.45 + 0.5 * R()), side: R() < 0.5 ? 1 : -1, w: 0.3 + 0.25 * R() });
112
+ }
113
+ }
114
+
115
+ // head and neck features, as 2-D polylines in a projection of the (straight) bind pose:
116
+ // top view (x, z) for the crown and nape, side view (z, y) for the flanks of the head
117
+ const E = eyeSpec(params);
118
+ const eyeC = eyeFrameOf(E, J.v0, 1).c; // left eye centre (x > 0)
119
+ const z0 = J.v0[2];
120
+ const ez = eyeC[2] - z0, ey = eyeC[1]; // eye position along the head, height (world)
121
+ const seg2 = (px, py, P, Wd) => {
122
+ let best = 1e9;
123
+ for (let i = 0; i < P.length - 1; i++) {
124
+ const ax = P[i][0], ay = P[i][1], bx = P[i + 1][0] - ax, by = P[i + 1][1] - ay;
125
+ const t = clamp(((px - ax) * bx + (py - ay) * by) / (bx * bx + by * by || 1e-12), 0, 1);
126
+ const d = Math.hypot(px - ax - bx * t, py - ay - by * t) - (Wd[i] + (Wd[i + 1] - Wd[i]) * t);
127
+ if (d < best) best = d;
128
+ }
129
+ return best;
130
+ };
131
+ const kH = B.kH;
132
+ // corn: spear point (two blotches meeting in a point between the eyes, running back onto the neck),
133
+ // the interocular bar, and the post-ocular stripe through the eye to the jaw angle
134
+ const spearL = [[0.0, z0 + ez + 0.003 * kH], [0.06 * W, z0 + 0.4 * hL], [0.11 * W, z0 + 0.0 * hL], [0.14 * W, z0 - 0.35 * hL], [0.1 * W, z0 - 0.75 * hL]];
135
+ const spearW = [0.0006, 0.0011, 0.0013, 0.0014, 0.001].map((w) => w * kH);
136
+ const barP = [[0, z0 + ez + 0.1 * E.r], [eyeC[0] * 0.85, z0 + ez + 0.4 * E.r]];
137
+ const postP = [[z0 + ez - 1.0 * E.r, ey - 0.3 * E.r], [z0 + 0.2 * hL, 0.45 * H], [z0 + 0.02 * hL, 0.36 * H]];
138
+ // rattlesnake: pale pre-ocular stripe (front of the eye down to the lip) and post-ocular stripe
139
+ // (behind the eye diagonally to the mouth corner), with the dark mask between them
140
+ const preV = [[z0 + ez + 1.7 * E.r, ey + 1.1 * E.r], [z0 + 0.78 * hL, 0.62 * H], [z0 + 0.66 * hL, 0.4 * H]];
141
+ const postV = [[z0 + ez - 0.5 * E.r, ey - 1.5 * E.r], [z0 + 0.3 * hL, 0.34 * H], [z0 + 0.02 * hL, 0.26 * H]];
142
+ const maskV = [[z0 + ez + 0.4 * E.r, ey], [z0 + 0.3 * hL, 0.5 * H], [z0 + 0.05 * hL, 0.42 * H]];
143
+ // fine vertical sutures between the labial scales (corn)
144
+ const labialMark = (x, z, k) => {
145
+ const u = ((z - J.v0[2]) / hL - 0.12) / 0.11; // ~8 labials from the jaw angle to the snout
146
+ if (u < 0 || u > 8) return 1;
147
+ const f = Math.abs(u - Math.round(u));
148
+ return f * 0.11 * hL - 0.00008 * B.kH + (1 - k) * 0;
149
+ };
150
+ // ---- per-vertex outputs
151
+ const comb = new Float32Array(nV * 3), tint = new Float32Array(nV * 4), pattern = new Float32Array(nV).fill(1);
152
+ const mark = new Float32Array(nV).fill(1), furLen = new Float32Array(nV), patternColor = new Float32Array(nV * 4), surf = new Float32Array(nV * 4);
153
+ const bw = ((morph && morph.borderW) || 1) * 0.0007;
154
+ const idx = { body: 0, head: 1, jaw: 2, tongue: 3, fork: 4, fang: 5, rattle: 6 };
155
+ for (let v = 0; v < nV; v++) {
156
+ const x = pos[v * 3], y = pos[v * 3 + 1], z = pos[v * 3 + 2];
157
+ const nx = nrm[v * 3], ny = nrm[v * 3 + 1], nz = nrm[v * 3 + 2];
158
+ const reg = idx[regionNames[regionOf[v]]] ?? 0;
159
+ // comb: scales overlap tail-ward
160
+ let cx = -nx * -nz, cy = -ny * -nz, cz = -1 - nz * -nz;
161
+ const cl = Math.hypot(cx, cy, cz) || 1;
162
+ comb[v * 3] = cx / cl; comb[v * 3 + 1] = cy / cl; comb[v * 3 + 2] = cz / cl;
163
+ const sg = weights.axialS[v] - s0;
164
+ const hw = hwAt(Math.max(0, sg));
165
+ const ya = axY(Math.max(0, sg));
166
+ const th = Math.atan2(x, y - ya); // 0 dorsal, +-pi ventral
167
+ const u = th; // angular coordinate (radians around the body)
168
+ const nzF = fbm3(x * 60, y * 60, z * 60, 3) - 0.5;
169
+ const nLo = fbm3(x * 9, y * 9, z * 9, 3) - 0.5;
170
+ let col, mat = MAT.SCALES, gloss = viper ? 0.28 : 0.55, size = (viper ? 0.0042 : 0.0024) * (hw / (viper ? 0.0265 : 0.0135)) ** 0.35, keel = viper ? 0.85 : 0.05, irid = viper ? -0.15 : -0.4;
171
+ let pat = 1, pcol = null, mk = 1;
172
+ const belly = reg <= 1 && y < 0.1 * hw && ny < -0.75 && sg > -0.2 * hL;
173
+ // ---------------- body (and neck)
174
+ if (reg === 0 || reg === 1) {
175
+ const lat = smoothstep(0.9, 2.2, Math.abs(u)); // 0 dorsal .. 1 low flank
176
+ const t = clamp(sg / total, 0, 1);
177
+ if (viper) {
178
+ col = mix3(C.ground, C.flank, lat);
179
+ if (sg > sVent) {
180
+ // coon tail: alternating rings from the vent to the rattle
181
+ const rl = (total - sVent) / 9.5;
182
+ const q = (sg - sVent) / rl;
183
+ col = C.ringW;
184
+ const f = q - Math.floor(q);
185
+ if (Math.floor(q) % 2 === 1) { pat = -Math.min(f, 1 - f) * rl; pcol = C.ringB; }
186
+ else pat = Math.min(f, 1 - f) * rl;
187
+ pat += nzF * 0.0015;
188
+ }
189
+ } else {
190
+ col = mix3(C.ground, C.flank, lat);
191
+ }
192
+ // dorsal features near sigma
193
+ let lo = 0, hi = feats.length;
194
+ while (lo < hi) { const md = (lo + hi) >> 1; if (feats[md].s < sg - 0.06) lo = md + 1; else hi = md; }
195
+ for (let k = lo; k < feats.length && feats[k].s < sg + 0.06; k++) {
196
+ const F = feats[k];
197
+ if (sg > sVent && viper) break;
198
+ const ds = sg - F.s, du = (u - F.u) * hw; // metres across the surface
199
+ const bb = F.b * hw;
200
+ let d;
201
+ if (F.kind === 'diamond') {
202
+ const L1 = Math.abs(ds) / F.a + Math.abs(du) / bb;
203
+ d = (L1 - 1) * (F.a * bb) / Math.hypot(F.a, bb);
204
+ } else {
205
+ const rr = Math.min(F.a, bb) * 0.55;
206
+ const qx = Math.abs(ds) - (F.a - rr), qy = Math.abs(du) - (bb - rr);
207
+ d = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - rr;
208
+ }
209
+ d += nzF * (viper ? 0.0012 : 0.0016);
210
+ if (d < pat) {
211
+ pat = d;
212
+ if (F.kind === 'diamond') pcol = mix3(C.diamond, C.centre, smoothstep(-0.003, -0.009, d) * 0.8);
213
+ else pcol = F.kind === 'saddle' ? C.saddle : F.kind === 'lateral' ? C.lateral : C.blotch;
214
+ }
215
+ }
216
+ if (viper && sg <= sVent) {
217
+ // pale diamond edges (one scale wide), fading into the ground colour
218
+ col = mix3(C.edge, col, smoothstep(0.0022, 0.0034, pat + 0.0006 * nzF));
219
+ mk = 1;
220
+ } else if (!viper) {
221
+ mk = Math.abs(pat) - bw * (0.8 + 0.4 * (0.5 + nzF));
222
+ }
223
+ // belly
224
+ if (belly || Math.abs(u) > 2.55) {
225
+ const bcol = viper ? C.belly : C.belly;
226
+ const e = belly ? 1 : smoothstep(2.55, 2.75, Math.abs(u));
227
+ col = mix3(col, bcol, e);
228
+ if (e > 0.5) { pat = 1; pcol = null; mk = 1; }
229
+ if (!viper && belly) {
230
+ // checkerboard blocks, and two dark stripes under the tail
231
+ let best = 1;
232
+ for (const c of checks) {
233
+ if (Math.abs(c.s - sg) > scute * 3) continue;
234
+ const qx = Math.abs(sg - c.s) - c.a, xc = c.side * hw * (0.2 + c.w * 0.5), qy = Math.abs(x - xc) - hw * c.w * 0.5;
235
+ const d = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0);
236
+ if (d < best) best = d;
237
+ }
238
+ if (sg > sVent) best = Math.min(best, Math.abs(Math.abs(x) - hw * 0.35) - hw * 0.14);
239
+ if (best < 1) { pat = best + nzF * 0.0006; pcol = C.check; }
240
+ }
241
+ if (belly) { mat = MAT.SCALES; gloss = 0.7; size = scute; keel = -1; irid = -0.3; }
242
+ }
243
+ // head and nape: the scales grow into large head plates over ~1 head length, and their relief
244
+ // fades in the same span, so head and neck are one continuous covering (no seam)
245
+ // (measured straight back from the occiput in the bind pose, identical on the overlapping head
246
+ // and body meshes, so their cross-fade shows no seam)
247
+ const sgG = J.v0[2] - z;
248
+ const hq = smoothstep(-0.1 * hL, 1.2 * hL, sgG); // 0 on the head .. 1 on the body
249
+ if (!belly && sgG < 1.2 * hL) {
250
+ const plate = viper ? 0.0027 * kH : 0.009 * kH;
251
+ size = size + (plate - size) * (1 - hq);
252
+ keel = keel * (viper ? 0.5 + 0.5 * hq : 1);
253
+ irid = mix(viper ? -0.5 : -0.75, irid, hq);
254
+ gloss = mix(viper ? 0.35 : 0.75, gloss, hq);
255
+ }
256
+ const upper = ny > -0.15 && y > 0.35 * hw;
257
+ if (!viper && sgG < 1.3 * hL) {
258
+ // spear point on the crown and nape (top view), with black borders
259
+ let d = 1;
260
+ if (upper) {
261
+ const ax = Math.abs(x);
262
+ d = Math.min(seg2(ax, z, spearL, spearW), seg2(ax, z, barP, [0.0012 * kH, 0.0009 * kH])) + nzF * 0.0005;
263
+ }
264
+ // post-ocular stripe on the side of the head (side view)
265
+ if (Math.abs(x) > 0.2 * W && sgG < 0.1 * hL) d = Math.min(d, seg2(z, y, postP, [0.0009 * kH, 0.0012 * kH, 0.0009 * kH]) + nzF * 0.0004);
266
+ if (d < pat) { pat = d; pcol = C.saddle; mk = Math.abs(d) - bw * 0.9; }
267
+ }
268
+ if (viper && sgG < 0.3 * hL && Math.abs(x) > 0.15 * W) {
269
+ const dm = seg2(z, y, maskV, [0.0022 * kH, 0.0022 * kH, 0.0016 * kH]);
270
+ if (dm < 0) col = mix3(col, C.diamond, 0.75 * smoothstep(0, -0.0015, dm));
271
+ mk = Math.min(mk, seg2(z, y, preV, [0.0011 * kH, 0.0018 * kH, 0.0015 * kH]) + nzF * 0.0004, seg2(z, y, postV, [0.0011 * kH, 0.002 * kH, 0.0016 * kH]) + nzF * 0.0004);
272
+ }
273
+ if (reg === 1 && sgG < 0.02 * hL) {
274
+ const hy = y - J.v0[1];
275
+ if (viper) {
276
+ col = mix3(col, C.edge, 0.8 * smoothstep(yB + 0.46 * H, yB + 0.34 * H, hy));
277
+ } else {
278
+ // labials: belly cream with thin, faint sutures
279
+ if (hy < yB + 0.48 * H) col = mix3(col, C.belly, 0.7 * smoothstep(yB + 0.48 * H, yB + 0.38 * H, hy));
280
+ if (hy < yB + 0.48 * H && Math.abs(x) > 0.3 * W) mk = Math.min(mk, labialMark(x, z, 0.1));
281
+ }
282
+ // mouth: the palate, well inside the lips (no lining shows while the mouth is shut)
283
+ if (ny < -0.6 && hy < yB + 0.4 * H && Math.abs(x) < 0.15 * W && z > J.v0[2] + 0.12 * hL && !belly) { mat = MAT.MOUTH; col = srgb(0x8a5a58); pat = 1; mk = 1; }
284
+ }
285
+ // low-frequency mottling
286
+ col = [col[0] * (1 + 0.1 * nLo), col[1] * (1 + 0.1 * nLo), col[2] * (1 + 0.08 * nLo)];
287
+ void t;
288
+ } else if (reg === 2) {
289
+ // lower jaw: pale labials outside (plates, faint relief), mouth lining well inside
290
+ const hy = y - J.v0[1];
291
+ col = viper ? mix3(C.edge, C.belly, 0.5) : mix3(C.belly, C.flank, 0.3);
292
+ size = viper ? 0.003 * kH : 0.008 * kH;
293
+ keel = 0; gloss = viper ? 0.5 : 0.75; irid = viper ? -0.5 : -0.75;
294
+ if (ny > 0.6 && hy > yB + 0.32 * H && Math.abs(x) < 0.12 * W) { mat = MAT.MOUTH; col = srgb(0x8a5a58); }
295
+ } else if (reg === 3 || reg === 4) {
296
+ mat = MAT.SKIN;
297
+ col = viper ? srgb(0x141212) : reg === 4 ? srgb(0x1a0f0f) : srgb(0x5a1414);
298
+ gloss = 0.85;
299
+ } else if (reg === 5) {
300
+ mat = MAT.KERATIN; col = srgb(0xece4d0); gloss = 0.8;
301
+ } else if (reg === 6) {
302
+ mat = MAT.KERATIN;
303
+ col = mix3(C.rattle, [C.rattle[0] * 0.55, C.rattle[1] * 0.5, C.rattle[2] * 0.45], 0.5 + 0.5 * Math.sin(-z * 900));
304
+ gloss = 0.55;
305
+ }
306
+ tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
307
+ pattern[v] = pat;
308
+ mark[v] = mk;
309
+ const pc = pcol || col;
310
+ patternColor[v * 4] = pc[0]; patternColor[v * 4 + 1] = pc[1]; patternColor[v * 4 + 2] = pc[2]; patternColor[v * 4 + 3] = pcol ? 1 : 0;
311
+ surf[v * 4] = gloss; surf[v * 4 + 1] = size; surf[v * 4 + 2] = keel; surf[v * 4 + 3] = irid;
312
+ }
313
+ void SPINE; void mix;
314
+ return { comb, tint, pattern, mark, furLen, patternColor, surf, stats: { features: feats.length, checks: checks.length } };
315
+ }
@@ -0,0 +1,73 @@
1
+ // Snake: corn snake (Pantherophis guttatus) by default; western diamondback rattlesnake
2
+ // (Crotalus atrox) with `variant: 'rattlesnake'`. Proving species for the snake body plan
3
+ // (core/motion/snake.js, core/rig/snake.js).
4
+ import { snakeRig } from './rig.js';
5
+ import { sculptSnake, eyeSpec } from './sculpt.js';
6
+ import { snakeRegions } from './regions.js';
7
+ import { snakeCoat, markColorOf } from './coat.js';
8
+ import { motion } from './motion.js';
9
+
10
+ const VARIANTS = { corn: 'corn', cornsnake: 'corn', rattlesnake: 'rattlesnake', rattler: 'rattlesnake', diamondback: 'rattlesnake', viper: 'rattlesnake' };
11
+
12
+ export default {
13
+ id: 'snake',
14
+ name: 'Snake',
15
+ latin: 'Pantherophis guttatus',
16
+ group: 'others',
17
+ plan: 'snake',
18
+ covering: 'scales',
19
+ variants: [
20
+ { id: 'corn', name: 'Corn snake', latin: 'Pantherophis guttatus' },
21
+ { id: 'rattlesnake', name: 'Western diamondback rattlesnake', latin: 'Crotalus atrox' },
22
+ ],
23
+
24
+ // Seeded individual. Size: corn adults 0.9-1.5 m (reference 1.2 m), rattlesnake 0.9-1.6 m, males
25
+ // larger with longer tails; hatchlings ~0.3 m with relatively bigger heads and eyes.
26
+ variation(R, o) {
27
+ const variant = VARIANTS[String(o.variant || 'corn').toLowerCase()] || 'corn';
28
+ const viper = variant === 'rattlesnake';
29
+ const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
30
+ const age = o.age || 'adult';
31
+ const juv = age === 'juvenile';
32
+ const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
33
+ const male = sex === 'male';
34
+ const size = (male ? 1.04 : 0.96) * (1 + 0.1 * g()) * (juv ? 0.27 : 1) * (viper && male ? 1.05 : 1);
35
+ const shape = {
36
+ length: 1,
37
+ girth: (1 + 0.07 * g()) * (juv ? 0.92 : 1) * (viper && !male ? 1.04 : 1),
38
+ head: (1 + 0.04 * g()) * (juv ? 1.4 : 1),
39
+ tail: (male ? 1.08 : 0.94) * (1 + 0.04 * g()),
40
+ };
41
+ const r = R();
42
+ const morph = viper ? 'diamondback' : r < 0.52 ? 'normal' : r < 0.68 ? 'okeetee' : r < 0.84 ? 'wild' : r < 0.94 ? 'amel' : 'anery';
43
+ return {
44
+ variant, sex, age, size, shape,
45
+ warps: [],
46
+ minThick: 0.0005,
47
+ rattleSegs: viper ? (juv ? 1 : 5 + Math.floor(R() * 4)) : 0,
48
+ coat: { morph, seed: Math.floor(R() * 1e6), warm: 1.1 * g(), light: 0.1 * g(), count: 1 + 0.1 * g(), dull: Math.max(0, 0.45 * g() + 0.1), blotch: 1 + 0.18 * g(), tone: viper ? 2 * R() - 1 : 0 },
49
+ };
50
+ },
51
+ rig: (params) => snakeRig(params),
52
+ sculpt: (m, rig, params) => sculptSnake(m, rig, params),
53
+ regions: snakeRegions,
54
+ coat: snakeCoat,
55
+ eyeSpecs: (params, rig) => [1, -1].map((side) => ({ side, spec: eyeSpec(params), headOrigin: rig.J.v0, bone: 'head', look: lookOf(params) })),
56
+ motion,
57
+ // render hints may depend on the individual (the mark colour follows the morph)
58
+ render: (params) => ({ markColor: markColorOf(params || {}) }),
59
+ };
60
+
61
+ function lookOf(params) {
62
+ if (params.variant === 'rattlesnake') {
63
+ return { pupil: 'slit', lids: 'none', pupilSize: [0.1, 0.55], iris: { inner: [0.12, 0.1, 0.07], mid: [0.33, 0.27, 0.19], hi: [0.5, 0.42, 0.3], outer: [0.16, 0.13, 0.09] }, sclera: { color: [0.2, 0.17, 0.12], visible: false }, lidColor: [0.2, 0.17, 0.13] };
64
+ }
65
+ const amel = params.coat?.morph === 'amel';
66
+ return {
67
+ pupil: 'round', lids: 'none', pupilSize: [0.34, 0.5],
68
+ iris: amel
69
+ ? { inner: [0.35, 0.02, 0.03], mid: [0.62, 0.06, 0.07], hi: [0.8, 0.2, 0.16], outer: [0.4, 0.03, 0.04] }
70
+ : { inner: [0.2, 0.06, 0.02], mid: [0.62, 0.22, 0.06], hi: [0.82, 0.42, 0.14], outer: [0.16, 0.05, 0.02] },
71
+ sclera: { color: [0.3, 0.12, 0.05], visible: false }, lidColor: [0.3, 0.12, 0.05],
72
+ };
73
+ }
@@ -0,0 +1,51 @@
1
+ // Snake motion data for the snake engine (schema: src/core/motion/snake.js). Numbers are for the
2
+ // reference individual (params.size = 1, total length 1.2 m); the engine scales lengths by size and
3
+ // speeds by sqrt(size).
4
+ //
5
+ // Lateral undulation (Jayne 1986, Hu et al. 2009, Fu et al. 2022): every point follows the
6
+ // head's path; 1.5-2.5 waves on the body; wavelength 0.35-0.45 of the total length, amplitude
7
+ // 0.06-0.10 of it, shrinking at a slow crawl; cruise 0.1-0.3 m/s, sprint ~0.6-0.8 m/s.
8
+ // Strike (Penning et al. 2016): peak head speed ~2.7-3 m/s, ~190 m/s^2, 50-90 ms to the target.
9
+ export const motion = {
10
+ maxSpeed: 0.7,
11
+ accel: 0.9,
12
+ decel: 1.6,
13
+ turnRate: 2.2,
14
+ minTurnRadius: 0.1, // x total length
15
+ gears: { crawl: 0.07, slither: 0.24, dash: 0.6 },
16
+ // [name, speed (m/s), wavelength (x TL), amplitude (x TL), head lift (m), head yaw stabilisation 0..1]
17
+ gaits: [
18
+ { name: 'crawl', v: 0, wave: 0.5, amp: 0.028, headLift: 0.012, stab: 0.75 },
19
+ { name: 'crawl', v: 0.1, wave: 0.46, amp: 0.05, headLift: 0.012, stab: 0.7 },
20
+ { name: 'slither', v: 0.16, wave: 0.43, amp: 0.066, headLift: 0.009, stab: 0.65 },
21
+ { name: 'slither', v: 0.35, wave: 0.4, amp: 0.078, headLift: 0.006, stab: 0.6 },
22
+ { name: 'dash', v: 0.45, wave: 0.37, amp: 0.085, headLift: 0.004, stab: 0.55 },
23
+ { name: 'dash', v: 0.8, wave: 0.35, amp: 0.09, headLift: 0.003, stab: 0.5 },
24
+ ],
25
+ body: { neck: 0.12, forebody: 0.3 }, // x TL: neck (raised when looking), strike forebody
26
+ tongue: { rate: 0.7, length: 0.6, flicks: [3, 6], freq: 7.5 },
27
+ breath: { rate: 0.28, roll: 0.03 },
28
+ actions: {
29
+ strike: { reach: 0.33, speed: 2.8, gape: 75, coilHeight: 0.07, rise: 0.12 },
30
+ coil: { pitch: 0.88, speed: 0.34 },
31
+ sit: { raise: 0.13 },
32
+ eat: { swallow: 3.2 },
33
+ drink: { pumps: 4 },
34
+ },
35
+ variants: {
36
+ // heavy-bodied viper: slower, stiffer undulation, big gape, fangs, rattle
37
+ // C. atrox: heavy and slow; slow travel is nearly straight (rectilinear-looking, 1-5 cm/s),
38
+ // a fast escape tops out ~0.35 m/s; the rattle is carried just off the ground while moving
39
+ rattlesnake: {
40
+ maxSpeed: 0.38, gears: { crawl: 0.04, slither: 0.16, dash: 0.34 }, tailCarry: 0.012,
41
+ gaits: [
42
+ { name: 'crawl', v: 0, wave: 0.6, amp: 0.012, headLift: 0.006, stab: 0.85 },
43
+ { name: 'crawl', v: 0.06, wave: 0.55, amp: 0.022, headLift: 0.008, stab: 0.8 },
44
+ { name: 'slither', v: 0.1, wave: 0.46, amp: 0.05, headLift: 0.008, stab: 0.7 },
45
+ { name: 'slither', v: 0.22, wave: 0.42, amp: 0.062, headLift: 0.006, stab: 0.65 },
46
+ { name: 'dash', v: 0.28, wave: 0.4, amp: 0.07, headLift: 0.004, stab: 0.6 },
47
+ { name: 'dash', v: 0.38, wave: 0.38, amp: 0.075, headLift: 0.004, stab: 0.55 },
48
+ ],
49
+ tongue: { length: 0.85, amp: 0.55 }, strike: { gape: 115, reach: 0.36, coilHeight: 0.1 }, rattle: true },
50
+ },
51
+ };
@@ -0,0 +1,51 @@
1
+ // Meshing plan: the long body at ~7 % of the max half width, the head at half that (overlapping the
2
+ // body across the neck and cross-faded), the lower jaw, tongue, fangs and rattle as rigid regions.
3
+ // Thin parts (tongue, fangs) are dropped at the low and crowd tiers.
4
+ import { SPINE } from './rig.js';
5
+ import { smoothstep } from '../../core/math/vec.js';
6
+
7
+ export function snakeRegions(rig, params, Q) {
8
+ const J = rig.J, B = rig.plan, P = B.P, r = Q.res;
9
+ let hwMax = 0;
10
+ for (let i = 0; i <= 40; i++) hwMax = Math.max(hwMax, B.hw(i / 40));
11
+ const hL = B.headLen, W = P.headW * B.kH, H = P.headH * B.kH;
12
+ const hBody = 0.078 * hwMax * r;
13
+ const hHead = Math.min(hBody * 0.5, 0.012 * hL * r);
14
+ const zCut = -0.35 * hL, band = Math.max(0.0025, 1.5 * hBody);
15
+ const zTail = J['v' + SPINE][2];
16
+ const pad = 3 * hBody;
17
+ const y0 = J.v0[1];
18
+ const jobs = [
19
+ [{ name: 'body', part: 'body', bmin: [-hwMax - pad, -pad, zTail - pad - 0.01], bmax: [hwMax + pad, 2.2 * hwMax + pad, zCut + band + pad], h: hBody, F: 4, clip: (x, y, z) => z < zCut + band }],
20
+ ];
21
+ const head = [
22
+ { name: 'head', part: 'body', bmin: [-0.7 * W, -pad, zCut - band - 0.004], bmax: [0.7 * W, y0 + 1.2 * H, hL * 1.12], h: hHead, F: 5, clip: (x, y, z) => z > zCut - band },
23
+ { name: 'jaw', part: 'jaw', bmin: [-0.6 * W, -0.003, -0.1 * hL], bmax: [0.6 * W, y0 + 0.2 * H, hL * 1.05], h: hHead, F: 5, rigidBone: 'jaw' },
24
+ ];
25
+ if (r < 2.5) {
26
+ const tr = 0.0012 * B.kH;
27
+ const hT = Math.min(0.00022 * B.kH * r, hHead);
28
+ head.push(
29
+ { name: 'tongue', part: 'tongue', bmin: [-tr * 2, J.tongueBase[1] - tr * 2, J.tongueBase[2] - tr * 2], bmax: [tr * 2, J.tongueBase[1] + tr * 2, J.tongueFork[2] + tr * 3], h: hT, F: 4, rigidBone: 'tongue' },
30
+ { name: 'fork', part: 'fork', bmin: [-0.3 * hL, J.tongueFork[1] - tr * 2, J.tongueFork[2] - tr * 2], bmax: [0.3 * hL, J.tongueFork[1] + tr * 2, J.tongueTip[2] + tr * 2], h: hT, F: 4, rigidBone: 'tongueTip' },
31
+ );
32
+ if (params.variant === 'rattlesnake') {
33
+ head.push({ name: 'fang', part: 'fang', bmin: [-0.4 * W, J.fangTip[1] - 0.004, J.fangTip[2] - 0.004], bmax: [0.4 * W, J.fangBase[1] + 0.003, J.fangBase[2] + 0.003], h: hT, F: 4, rigidBone: 'fang' });
34
+ }
35
+ }
36
+ jobs.push(head);
37
+ if (P.rattle) {
38
+ const b = J['v' + SPINE];
39
+ const L = P.rattle.len * 1.3 * (params.shape?.girth ?? 1);
40
+ const w = B.hw(1) * 2;
41
+ jobs[0].push({ name: 'rattle', part: 'rattle', bmin: [-w, b[1] - w, b[2] - L - 0.01], bmax: [w, b[1] + w, b[2] + 0.01], h: Math.min(hBody * 0.5, 0.00035 * r), F: 4, rigidBone: 'spine' + (SPINE - 1) });
42
+ }
43
+ return {
44
+ jobs,
45
+ fade(x, y, z, name) {
46
+ if (name === 'body') return 1 - smoothstep(zCut - band, zCut + band, z);
47
+ if (name === 'head') return smoothstep(zCut - band, zCut + band, z);
48
+ return 1;
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,108 @@
1
+ // Snake skeleton in bind pose (metres): lying straight along -Z, belly on y = 0, head at +Z.
2
+ // Two builds share the code: the corn snake (default) and the western diamondback rattlesnake
3
+ // (variant). Numbers: total length 1.2 m (reference adults), corn head 3.4 cm and
4
+ // max body width 2.7 cm, rattlesnake head 5.2 cm (3.8 cm wide) and max body width 5.2 cm;
5
+ // tail 17 % (corn) / 7.5 % (rattlesnake) of the total length.
6
+ import { buildRig } from '../../core/rig/rig.js';
7
+ import { snakeSpine, snakeBones, snakeAxial, snakeSegments } from '../../core/rig/snake.js';
8
+
9
+ export const SPINE = 50; // spine bones (each ~4-5 vertebrae)
10
+
11
+ // Per-variant proportions (reference individual). hw = half width of the body (m) at arc fraction t
12
+ // (0 = occiput, 1 = tail tip); flat = axis height / half width (loaf cross-section: flat belly).
13
+ export const PROFILES = {
14
+ corn: {
15
+ TL: 1.2, headLen: 0.034, headW: 0.0185, headH: 0.0112, tail: 0.17, flat: 0.74,
16
+ hw: [[0, 0.0071], [0.05, 0.0076], [0.22, 0.0112], [0.45, 0.0135], [0.66, 0.0128], [0.8, 0.0098], [0.88, 0.0068], [0.95, 0.0036], [1, 0.0011]],
17
+ eye: { r: 0.0026, at: [0.006, 0.0027, 0.0205], yaw: 1.05, pitch: 0.2 },
18
+ },
19
+ rattlesnake: {
20
+ TL: 1.2, headLen: 0.052, headW: 0.038, headH: 0.02, tail: 0.075, flat: 0.64,
21
+ hw: [[0, 0.0092], [0.04, 0.0105], [0.2, 0.019], [0.46, 0.0265], [0.7, 0.0238], [0.86, 0.016], [0.92, 0.0112], [0.97, 0.0078], [1, 0.0062]],
22
+ eye: { r: 0.0029, at: [0.0133, 0.0052, 0.032], yaw: 1.0, pitch: 0.22 },
23
+ rattle: { len: 0.05, segs: 7 },
24
+ },
25
+ };
26
+
27
+ // monotone cubic (Fritsch-Carlson) through [[t, v]...]
28
+ export function pchip(keys) {
29
+ const n = keys.length, x = keys.map((k) => k[0]), y = keys.map((k) => k[1]);
30
+ const d = [], m = new Array(n).fill(0);
31
+ for (let i = 0; i < n - 1; i++) d.push((y[i + 1] - y[i]) / (x[i + 1] - x[i]));
32
+ m[0] = d[0]; m[n - 1] = d[n - 2];
33
+ for (let i = 1; i < n - 1; i++) m[i] = d[i - 1] * d[i] <= 0 ? 0 : (d[i - 1] + d[i]) / 2;
34
+ for (let i = 0; i < n - 1; i++) {
35
+ if (d[i] === 0) { m[i] = m[i + 1] = 0; continue; }
36
+ const a = m[i] / d[i], b = m[i + 1] / d[i], s = a * a + b * b;
37
+ if (s > 9) { const t = 3 / Math.sqrt(s); m[i] = t * a * d[i]; m[i + 1] = t * b * d[i]; }
38
+ }
39
+ return (t) => {
40
+ if (t <= x[0]) return y[0];
41
+ if (t >= x[n - 1]) return y[n - 1];
42
+ let i = 0;
43
+ while (i < n - 2 && t > x[i + 1]) i++;
44
+ const h = x[i + 1] - x[i], u = (t - x[i]) / h;
45
+ const h00 = 2 * u * u * u - 3 * u * u + 1, h10 = u * u * u - 2 * u * u + u, h01 = -2 * u * u * u + 3 * u * u, h11 = u * u * u - u * u;
46
+ return h00 * y[i] + h10 * h * m[i] + h01 * y[i + 1] + h11 * h * m[i + 1];
47
+ };
48
+ }
49
+
50
+ // Individual body plan from the variant profile and the seeded proportions in params:
51
+ // params.shape = { length, girth, head, tail } multipliers (1 = reference).
52
+ export function bodyPlan(params) {
53
+ const P = PROFILES[params.variant] || PROFILES.corn;
54
+ const sh = params.shape || {};
55
+ const kL = sh.length ?? 1, kG = sh.girth ?? 1, kH = sh.head ?? 1, kT = sh.tail ?? 1;
56
+ const headLen = P.headLen * kH;
57
+ const bodyLen = P.TL * kL - headLen;
58
+ const tailFrac = Math.min(0.3, (P.tail * kT * P.TL * kL) / bodyLen);
59
+ // re-map the profile's tail portion to this individual's tail fraction
60
+ const refTail = (P.tail * P.TL) / (P.TL - P.headLen);
61
+ const remap = (t) => (t <= 1 - tailFrac ? (t / (1 - tailFrac)) * (1 - refTail) : 1 - refTail + ((t - (1 - tailFrac)) / tailFrac) * refTail);
62
+ const base = pchip(P.hw);
63
+ const neckK = kH; // a bigger head comes with a slightly thicker neck
64
+ const hw = (t) => {
65
+ const r = base(remap(t));
66
+ const neck = 1 - Math.min(1, t / 0.1);
67
+ return r * (kG * (1 - neck) + (0.5 * kG + 0.5 * neckK) * neck);
68
+ };
69
+ return { P, headLen, bodyLen, tailFrac, hw, flat: P.flat, kH };
70
+ }
71
+
72
+ export function snakeRig(params) {
73
+ const B = bodyPlan(params);
74
+ const P = B.P;
75
+ const J = {};
76
+ const lens = snakeSegments(SPINE, B.bodyLen, { tipShrink: P.rattle ? 0.8 : 0.5, tipBones: 10 });
77
+ snakeSpine(J, 0, lens, (s, t) => B.hw(t) * B.flat);
78
+ const y0 = J.v0[1];
79
+ const kH = B.kH;
80
+ const hL = B.headLen, hH = P.headH * kH;
81
+ // head landmarks (head-local: origin v0, +Z forward). The jaw's underside rests on y = 0.
82
+ // heights are from the belly plane (the jaw's underside rests on y = 0)
83
+ J.nose = [0, 0.56 * hH, hL];
84
+ J.jawHinge = [0, 0.34 * hH, 0.06 * hL];
85
+ J.jawTip = [0, 0.16 * hH, 0.9 * hL];
86
+ // tongue lies in its sheath in the floor of the mouth, tips just behind the chin
87
+ J.tongueBase = [0, 0.2 * hH, 0.18 * hL];
88
+ J.tongueFork = [0, 0.2 * hH, 0.62 * hL];
89
+ J.tongueTip = [0, 0.2 * hH, 0.84 * hL];
90
+ // fangs hinge at the front of the upper jaw, folded back along the palate
91
+ J.fangBase = [0, 0.38 * hH, 0.72 * hL];
92
+ J.fangTip = [0, 0.32 * hH, 0.55 * hL];
93
+ const rig = buildRig({
94
+ joints: J,
95
+ unit: (B.headLen + B.bodyLen) / 1.3,
96
+ headOrigin: J.v0,
97
+ bones: snakeBones(SPINE, { neck: 4, tail: Math.round(B.tailFrac * SPINE) }),
98
+ axial: {
99
+ ...snakeAxial(SPINE),
100
+ // the skull is rigid right up to the occiput; the neck blends in just behind it
101
+ blend: { 1: [-0.004 * kH, 0.006 * kH] },
102
+ },
103
+ limbs: {},
104
+ appendages: [],
105
+ });
106
+ rig.plan = B;
107
+ return rig;
108
+ }