@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,58 @@
1
+ import {validatePaperActionInfo} from './puppy-paper-actions.mjs';
2
+
3
+ // Art-directed "snappy" trick timing (a staging choice, not measured animal
4
+ // behaviour): short windup, fast active rotation, brief settle. Pure helper that
5
+ // maps action age (s) to the clip progress paperActionPose already consumes.
6
+ // Phase boundaries sit exactly on v5 weighted edge boundaries, located by key
7
+ // name: windup ends after the first edge arriving at `enter`; settle starts at
8
+ // the last edge leaving `exit`. seconds = [windup, active, settle].
9
+ export const SNAPPY_TRICK_PROFILES=Object.freeze({
10
+ spin:Object.freeze({enter:'spin0',exit:'spin15',seconds:Object.freeze([.1,.72,.14])}),
11
+ roll:Object.freeze({enter:'bridge6',exit:'bridge6',seconds:Object.freeze([.56,.86,.62])}),
12
+ 'roll-down':Object.freeze({enter:'bridge6',exit:'bridge6',seconds:Object.freeze([.32,.86,.62])}),
13
+ });
14
+ export const TRICK_PHASES=Object.freeze(['windup','active','settle']);
15
+ const fail=m=>{throw Error(`Invalid trick timing: ${m}`);};
16
+
17
+ // Clip edges as key names plus cumulative weighted progress at every edge boundary.
18
+ export function trickClipEdges(name,info){
19
+ validatePaperActionInfo(info);
20
+ const list=info.clips[name],weights=info.weights?.[name]??list.map(()=>1),{keyOrder,edges}=info;
21
+ if(!Array.isArray(keyOrder)||!Array.isArray(edges)||edges.length<info.pairs)fail('missing keyOrder/edges');
22
+ const keys=list.map(p=>{const e=edges[p];if(!Array.isArray(e)||e.length!==2||!e.every(k=>Number.isInteger(k)&&typeof keyOrder[k]==='string'))fail(`edge ${p}`);return[keyOrder[e[0]],keyOrder[e[1]]];});
23
+ for(let i=1;i<keys.length;i++)if(edges[list[i-1]][1]!==edges[list[i]][0])fail(`${name} chain breaks at edge ${i}`);
24
+ const total=weights.reduce((a,b)=>a+b,0),bounds=[0];let sum=0;
25
+ for(let i=0;i<weights.length-1;i++){sum+=weights[i];bounds.push(sum/total);}bounds.push(1);
26
+ return{list,keys,bounds};
27
+ }
28
+
29
+ export function createTrickTiming(name,info,profile=SNAPPY_TRICK_PROFILES[name]){
30
+ if(!profile||typeof profile.enter!=='string'||typeof profile.exit!=='string')fail(`profile for ${name}`);
31
+ const s=profile.seconds;if(!Array.isArray(s)||s.length!==3||!s.every(v=>typeof v==='number'&&Number.isFinite(v)&&v>0))fail('seconds');
32
+ const {list,keys,bounds}=trickClipEdges(name,info);
33
+ const first=keys.findIndex(k=>k[1]===profile.enter),last=keys.findLastIndex(k=>k[0]===profile.exit);
34
+ if(first<0)fail(`no edge arrives at ${profile.enter}`);if(last<0)fail(`no edge leaves ${profile.exit}`);
35
+ // Edge index ranges [from,to] per phase; every phase owns at least one edge.
36
+ const ranges=[[0,first],[first+1,last-1],[last,list.length-1]];
37
+ if(!ranges.every(([a,b])=>a<=b))fail(`${name} phases need at least one edge each`);
38
+ const T=[0,s[0],s[0]+s[1],s[0]+s[1]+s[2]],P=[0,bounds[first+1],bounds[last],1];
39
+ // Monotone cubic Hermite (Fritsch-Butland): zero speed at both ends, harmonic
40
+ // mean of adjacent secants inside, so progress is C1 across phase boundaries.
41
+ const d=[0,1,2].map(i=>(P[i+1]-P[i])/(T[i+1]-T[i])),m=[0,2*d[0]*d[1]/(d[0]+d[1]),2*d[1]*d[2]/(d[1]+d[2]),0];
42
+ const duration=T[3];
43
+ const progressAt=age=>{
44
+ if(typeof age!=='number'||!Number.isFinite(age))fail('age');
45
+ if(age<=0)return 0;if(age>=duration)return 1;
46
+ const i=age<T[1]?0:age<T[2]?1:2,h=T[i+1]-T[i],u=(age-T[i])/h,u2=u*u,u3=u2*u;
47
+ const p=(2*u3-3*u2+1)*P[i]+(u3-2*u2+u)*h*m[i]+(-2*u3+3*u2)*P[i+1]+(u3-u2)*h*m[i+1];
48
+ return Math.max(P[i],Math.min(P[i+1],p));
49
+ };
50
+ const phases=Object.freeze(TRICK_PHASES.map((phase,i)=>Object.freeze({phase,start:T[i],end:T[i+1],progressStart:P[i],progressEnd:P[i+1],
51
+ firstEdge:ranges[i][0],lastEdge:ranges[i][1],fromKey:keys[ranges[i][0]][0],toKey:keys[ranges[i][1]][1]})));
52
+ return Object.freeze({name,duration,phases,progressAt});
53
+ }
54
+
55
+ // Timings for every profiled clip present in this action cache (e.g. v5 info).
56
+ export function createSnappyTrickTimings(info,profiles=SNAPPY_TRICK_PROFILES){
57
+ return Object.freeze(Object.fromEntries(Object.keys(profiles).filter(n=>Object.prototype.hasOwnProperty.call(info?.clips??{},n)).map(n=>[n,createTrickTiming(n,info,profiles[n])])));
58
+ }
@@ -0,0 +1,108 @@
1
+ // Design calibration, not a measurement of the user's current head position.
2
+ // Sources: OpenCV pinhole model; PsychoPy viewtools physical-screen frustums.
3
+ const diagonal=.0254*32;
4
+ export const PAPER_SCREEN=Object.freeze({widthMeters:diagonal*16/Math.hypot(16,9),heightMeters:diagonal*9/Math.hypot(16,9),viewDistance:.70});
5
+ const eyeHeight=.45,eyeZ=1.65,pitch=9*Math.PI/180;
6
+ export const PAPER_CAMERA=Object.freeze({fov:2*Math.atan(PAPER_SCREEN.heightMeters/(2*PAPER_SCREEN.viewDistance))*180/Math.PI,aspect:16/9,position:Object.freeze([0,eyeHeight,eyeZ]),target:Object.freeze([0,.12,eyeZ-(eyeHeight-.12)/Math.tan(pitch)]),canvasMeters:.64});
7
+ // Bryce & Williams 2017, Table 1/S2: adult retriever shoulder .60m;
8
+ // walk/trot/gallop (stride m, frequency Hz) = (.90,1.38)/(1.36,2.07)/(2.26,2.64).
9
+ // .35m puppy shoulder is a design assumption, NOT a measured juvenile dataset.
10
+ // Dynamic similarity: length *= lambda, frequency /= sqrt(lambda).
11
+ export const PAPER_BODY=Object.freeze({shoulderMeters:.35,referenceShoulderMeters:.60});
12
+ export const PAPER_LIMB=Object.freeze({length:PAPER_BODY.shoulderMeters,stanceHeight:PAPER_BODY.shoulderMeters*5/7});
13
+ const bodyRatio=PAPER_BODY.shoulderMeters/PAPER_BODY.referenceShoulderMeters;
14
+ const gait=(stride,frequency)=>Object.freeze({stride:stride*bodyRatio,frequency:frequency/Math.sqrt(bodyRatio),speed:stride*frequency*Math.sqrt(bodyRatio)});
15
+ const run=gait(2.26,2.64);
16
+ export const PAPER_GAITS=Object.freeze({front:run,rear:run,approach:run,trot:gait(1.36,2.07),walk:gait(.90,1.38)});
17
+ export const PAPER_DYNAMICS=Object.freeze({acceleration:4,deceleration:5,walkAcceleration:2,walkDeceleration:3});
18
+ // World distances, not a second screen-space zoom. Keep distant targets valid
19
+ // all the way to the horizon; never clamp the pet to a readable thumbnail size.
20
+ export const PAPER_GROUND=Object.freeze({near:.2,far:-200,away:-8});
21
+ const dz=PAPER_CAMERA.position[2]-PAPER_CAMERA.target[2],dy=PAPER_CAMERA.position[1]-PAPER_CAMERA.target[1],length=Math.hypot(dz,dy),c=dz/length,s=dy/length;
22
+ export function paperCameraPoint(x,y,z){return{x,y:c*(y-eyeHeight)-s*(z-eyeZ),depth:-s*(y-eyeHeight)-c*(z-eyeZ)};}
23
+ export function paperGroundTarget(x,z){
24
+ if(![x,z].every(Number.isFinite))throw Error('Invalid paper target');
25
+ z=Math.max(PAPER_GROUND.far,Math.min(PAPER_GROUND.near,z));
26
+ const halfWidth=Math.max(.72,paperCameraPoint(0,0,z).depth*PAPER_SCREEN.widthMeters/(2*PAPER_SCREEN.viewDistance)-PAPER_CAMERA.canvasMeters/2);
27
+ return[Math.max(-halfWidth,Math.min(halfWidth,x)),z];
28
+ }
29
+
30
+ // Fit one virtual 32" 16:9 screen INSIDE the actual text rectangle, not behind
31
+ // headers/controls. Its fixed camera/frustum survives viewport and UI resizing.
32
+ export function createPaperProjection({viewportWidth,viewportHeight,rect,width,height}){
33
+ if(![viewportWidth,viewportHeight,rect?.left,rect?.top,rect?.width,rect?.height,width,height].every(Number.isFinite)||Math.min(viewportWidth,viewportHeight,rect.width,rect.height,width,height)<=0)throw Error('Invalid paper viewport');
34
+ const pixelsPerMeter=Math.min(rect.width/PAPER_SCREEN.widthMeters,rect.height/PAPER_SCREEN.heightMeters),f=PAPER_SCREEN.viewDistance*pixelsPerMeter;
35
+ const cx=rect.width/2,cy=rect.height/2;
36
+ return{
37
+ project(x,y,z){const p=paperCameraPoint(x,y,z);if(p.depth<=.02)throw Error('Paper point behind camera');return{x:(cx+f*p.x/p.depth)/rect.width,y:(cy-f*p.y/p.depth)/rect.height,depth:p.depth};},
38
+ placement(x,z){const p=this.project(x,0,z),sourceSize=f*PAPER_CAMERA.canvasMeters/p.depth;return{x:p.x,y:p.y,size:sourceSize/rect.height,pixelAspect:(rect.width/width)/(rect.height/height),depth:p.depth,
39
+ groundFromPixel:(px,py)=>this.ground(px/width,py/height),pixelFromGround:point=>{const q=this.project(point.x,0,point.z);return{x:q.x*width,y:q.y*height};}};},
40
+ ground(u,v){
41
+ if(![u,v].every(Number.isFinite))return null;
42
+ const rx=(u*rect.width-cx)/f,ry=-(v*rect.height-cy)/f;
43
+ const vy=c*ry-s,vz=-s*ry-c;if(vy>=-1e-8)return null;
44
+ const t=-PAPER_CAMERA.position[1]/vy;return{x:rx*t,z:eyeZ+vz*t};
45
+ },
46
+ focalPixels:f,pixelsPerMeter,principalPoint:{x:cx,y:cy}
47
+ };
48
+ }
49
+
50
+ // Exact trapezoidal/triangular speed profile. Rendering and gait consume the
51
+ // integrated arc length, never a second easing curve or screen-space velocity.
52
+ export function planPaperTravel(distance,{speed=.35,acceleration=.7,deceleration=.5,startSpeed=0,endSpeed=0}={}){
53
+ if(![distance,speed,acceleration,deceleration,startSpeed,endSpeed].every(Number.isFinite)||distance<0||Math.min(speed,acceleration,deceleration)<=0||Math.min(startSpeed,endSpeed)<0||Math.max(startSpeed,endSpeed)>speed)throw Error('Invalid paper travel');
54
+ const peak=Math.min(speed,Math.sqrt((2*distance+startSpeed**2/acceleration+endSpeed**2/deceleration)/(1/acceleration+1/deceleration)));
55
+ if(peak+1e-9<Math.max(startSpeed,endSpeed))throw Error('Insufficient braking distance');
56
+ const ta=(peak-startSpeed)/acceleration,td=(peak-endSpeed)/deceleration,da=(peak+startSpeed)*ta/2,dd=(peak+endSpeed)*td/2,dc=Math.max(0,distance-da-dd),tc=peak?dc/peak:0,duration=ta+tc+td;
57
+ return{distance,duration,peak,brakingDistance:dd,acceleration,deceleration,
58
+ at(time){
59
+ if(!Number.isFinite(time))throw Error('Invalid travel time');const t=Math.max(0,time);
60
+ if(t>=duration)return{distance,speed:endSpeed,stage:'arrived',done:true};
61
+ if(t<ta)return{distance:startSpeed*t+.5*acceleration*t*t,speed:startSpeed+acceleration*t,stage:'accelerating',done:false};
62
+ if(t<ta+tc)return{distance:da+peak*(t-ta),speed:peak,stage:'cruise',done:false};
63
+ const r=duration-t;return{distance:distance-endSpeed*r-.5*deceleration*r*r,speed:endSpeed+deceleration*r,stage:'braking',done:false};
64
+ }
65
+ };
66
+ }
67
+
68
+ export function planPaperApproach(distance){
69
+ const runStride=PAPER_GAITS.approach.stride,walkStride=PAPER_GAITS.walk.stride,runDistance=Math.max(0,Math.floor((distance-1.1)/runStride))*runStride,entry=Math.min(.12,distance*.2),walkCycles=Math.max(0,Math.floor((distance-runDistance-entry-.25)/walkStride)),walkDistance=walkCycles*walkStride,brakeStart=runDistance+entry+walkDistance;
70
+ return{runStride,walkStride,runDistance,entry,walkDistance,brakeStart,at(d){
71
+ if(runDistance>0&&d<runDistance)return{clip:'run',progress:d/runStride};
72
+ if(d<runDistance+entry)return{clip:'walk-enter',progress:(d-runDistance)/(entry||1)};
73
+ if(d<brakeStart)return{clip:'walk',progress:(d-runDistance-entry)/walkStride};
74
+ return{clip:'brake',progress:Math.max(0,Math.min(1,(d-brakeStart)/Math.max(1e-9,distance-brakeStart)))};
75
+ }};
76
+ }
77
+
78
+ export function planPaperApproachTravel(distance){
79
+ const geometry=planPaperApproach(distance),walkSpeed=PAPER_GAITS.walk.speed;
80
+ if(!distance)return planPaperTravel(0,{speed:walkSpeed});
81
+ const preDistance=geometry.brakeStart-geometry.runDistance,brakeDistance=distance-geometry.brakeStart;
82
+ const entrySpeed=geometry.runDistance?walkSpeed:Math.min(walkSpeed,Math.sqrt(2*PAPER_DYNAMICS.walkAcceleration*preDistance));
83
+ const segments=[];
84
+ if(geometry.runDistance)segments.push(planPaperTravel(geometry.runDistance,{speed:PAPER_GAITS.approach.speed,...PAPER_DYNAMICS,endSpeed:walkSpeed}));
85
+ segments.push(planPaperTravel(preDistance,{speed:walkSpeed,startSpeed:geometry.runDistance?walkSpeed:0,endSpeed:entrySpeed,acceleration:PAPER_DYNAMICS.walkAcceleration,deceleration:PAPER_DYNAMICS.walkDeceleration}));
86
+ // The entire gathering clip is actual deceleration, not an illustration of
87
+ // braking while the root continues to cruise until its very last centimeters.
88
+ segments.push(planPaperTravel(brakeDistance,{speed:entrySpeed,startSpeed:entrySpeed,acceleration:PAPER_DYNAMICS.walkAcceleration,deceleration:entrySpeed**2/(2*brakeDistance)}));
89
+ const duration=segments.reduce((a,p)=>a+p.duration,0);
90
+ return{distance,duration,peak:Math.max(...segments.map(p=>p.peak)),brakingDistance:brakeDistance,at(t){if(!Number.isFinite(t))throw Error('Invalid approach time');if(t>=duration)return{distance,speed:0,stage:'arrived',done:true};let offset=0;for(let i=0;i<segments.length;i++){const p=segments[i];if(t<p.duration||i===segments.length-1){const s=p.at(t);return{...s,distance:offset+s.distance};}t-=p.duration;offset+=p.distance;}}};
91
+ }
92
+
93
+ // Finish at a shared cycle endpoint without animating extra strides in place.
94
+ // Short paths use shorter strides and a correspondingly lower speed ceiling.
95
+ export function planPaperRun(distance){
96
+ const cycles=Math.max(1,Math.ceil(distance/run.stride)),stride=distance/cycles||run.stride;
97
+ return{stride,cycles,plan:planPaperTravel(distance,{speed:Math.min(run.speed,stride*run.frequency),...PAPER_DYNAMICS})};
98
+ }
99
+ export function planPaperWalk(distance){
100
+ const gait=PAPER_GAITS.walk,cycles=Math.max(1,Math.ceil(distance/gait.stride)),stride=distance/cycles||gait.stride;
101
+ return{stride,cycles,plan:planPaperTravel(distance,{speed:Math.min(gait.speed,stride*gait.frequency),acceleration:PAPER_DYNAMICS.walkAcceleration,deceleration:PAPER_DYNAMICS.walkDeceleration})};
102
+ }
103
+ export function paperWalkTarget(x,z,direction,distance=1.05){
104
+ if(![x,z,direction,distance].every(Number.isFinite)||![-1,1].includes(direction)||distance<=0)throw Error('Invalid walk target');
105
+ const radius=Math.max(.1,paperCameraPoint(0,0,z).depth*PAPER_SCREEN.widthMeters/(2*PAPER_SCREEN.viewDistance)-.44);
106
+ const next=direction>0?Math.max(x,Math.min(radius,x+distance)):Math.min(x,Math.max(-radius,x-distance));
107
+ return paperGroundTarget(next,z);
108
+ }
@@ -0,0 +1,271 @@
1
+ import {createCachedTween,retimeRun,cleanGroundOffset} from './puppy-clean.mjs';
2
+ import {createPaperFootLock,footSourcePoint,createRearSampler,createPaperSupportEpisodes} from './puppy-paper-contact.mjs';
3
+ import {createPaperSeatSampler} from './puppy-paper-seat.mjs';
4
+ import {planPaperRun,planPaperWalk,paperWalkTarget,planPaperApproach,planPaperApproachTravel,PAPER_GAITS,PAPER_GROUND,paperGroundTarget} from './puppy-paper-world.mjs';
5
+ import {createPaperDirector} from './puppy-paper-director.mjs';
6
+ import {createPaperBehaviorSampler,paperBehaviorPose} from './puppy-paper-behaviors.mjs';
7
+ import {createPaperAreaSampler} from './puppy-paper-area.mjs';
8
+ import {PAPER_ACTION_DURATIONS,createPaperActionSampler,paperActionPose} from './puppy-paper-actions.mjs';
9
+ import {createPawTransport} from './puppy-paper-paw-transport.mjs';
10
+ import {createCompanionSpriteSampler} from './puppy-companion-sprites.mjs';
11
+ import {mouthSourceFrame,mouthAnchor,projectMouth} from './puppy-mouth-anchor.mjs';
12
+
13
+ // Fixed viewer: slightly closer/wider lens gives depth without changing the art.
14
+ export {PAPER_CAMERA} from './puppy-paper-world.mjs';
15
+
16
+ // Original artwork only. Physics supplies ground travel; no procedural dog is drawn.
17
+ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=null,walkTransitions=false}={}){
18
+ let state='idle',age=0,phase=0,flip=false,nextFlip=false,target=null,route=false,routeIndex=0,view='front',nextView='front',turnView='front',turnFlip=false;
19
+ let held=null,waypoints=[];
20
+ let preparationRate=1;
21
+ let arrived=false;
22
+ let straight=false,seatProgress=0,seatVariant='default',travel=null,approachPlan=null,travelAge=0,travelDistance=0,motionSpeed=0,motionStage='rest',completed=false;
23
+ let watchSide='left',restProgress=0,idleHold=2.5;
24
+ let pendingAction=null,activeAction=null,actionDone=null,deferred=null,movementMode='run',stayDown=false,manualDown=false,spinReady=false;
25
+ const director=createPaperDirector();
26
+ const points=[[.12,PAPER_GROUND.away]];
27
+ const enter=s=>{state=s;age=0;};
28
+ const nextRoute=()=>{const choice=director.choose({x:animal.position.x,z:animal.position.z,seated:view==='approach'&&seatProgress===1});idleHold=choice.holdSeconds;if(choice.kind==='watch'){watchSide=choice.side;enter('watch');}else if(choice.kind==='rest')enter('recline');else if(choice.kind==='come')go(animal.position.x,.2,{direct:true});else go(...choice.target);};
29
+ const settle=()=>{if(view==='approach'&&completed)seatVariant='arrival';enter(view==='approach'?'sit-down':'settle');};
30
+ const stop=()=>{animal.follow({x:animal.position.x,y:0,z:animal.position.z},{x:0,y:0,z:0},{heading:0});motionSpeed=0;};
31
+ const snappyPreparation=()=>!!trickTimings?.[pendingAction];
32
+ const viewTurnSeconds=()=>snappyPreparation()?.24:walkTransitions&&movementMode==='walk'?.4:1;
33
+ const turnSeconds=()=>snappyPreparation()?.22:.85;
34
+ const launchSeconds=()=>snappyPreparation()?.10:.25;
35
+ const dispatch=(fn,{keepDown=false}={})=>{
36
+ if(activeAction||(movementMode==='walk'&&state==='run')){route=false;deferred=()=>dispatch(fn,{keepDown});return true;}
37
+ // The calibrated trick down pose has its own byte-shared recovery; never
38
+ // jump back into the older/larger rest illustration on another command.
39
+ if(manualDown&&!keepDown){route=false;deferred=fn;playAction('lie-up',()=>{manualDown=false;view='approach';seatProgress=1;seatVariant='default';enter('idle');drain();},.55);return true;}
40
+ return fn();
41
+ };
42
+ const drain=()=>{const fn=deferred;deferred=null;if(fn){fn();return true;}return false;};
43
+ const playAction=(name,done,duration=PAPER_ACTION_DURATIONS[name])=>{const timing=trickTimings?.[name];duration=timing?.duration??duration;if(!Number.isFinite(duration)||duration<=0)throw Error('Invalid paper action duration');activeAction={name,duration,timing};actionDone=done;stop();enter('action');};
44
+ const finishTrick=name=>{if(name!=='spin'){view='approach';flip=false;seatProgress=1;seatVariant='default';enter('idle');}else{view='front';flip=false;seatProgress=0;enter('settle');}drain();};
45
+ const issue=(skipEntry=false)=>{
46
+ if(movementMode==='walk'&&!skipEntry){playAction('walk-enter',()=>issue(true));return;}
47
+ preparationRate=1;arrived=false;completed=false;travelAge=0;travelDistance=0;const x=animal.position.x,z=animal.position.z;if(straight&&view==='approach')target[0]=x;
48
+ const dx=target[0]-x,dz=target[1]-z,distance=Math.hypot(dx,dz),run=movementMode==='walk'?planPaperWalk(distance):planPaperRun(distance);phase=Math.ceil(phase);
49
+ travel={x,z,dx,dz,heading:Math.atan2(dx,dz),stride:view==='approach'?PAPER_GAITS.approach.stride:run.stride,plan:view==='approach'?planPaperApproachTravel(distance):run.plan};approachPlan=view==='approach'?planPaperApproach(distance):null;stop();enter('run');
50
+ };
51
+ const prepareAction=()=>{
52
+ const name=pendingAction,desired=name==='spin'?'front':'approach';
53
+ if(restProgress>0){enter('wake');return;}
54
+ if(name==='spin'&&seatProgress>0){enter('rise');return;}
55
+ if(flip&&view!=='front'){turnView='front';enter('view-turn');return;}
56
+ if(flip){turnFlip=false;enter('turn');return;}
57
+ if(view!==desired){turnView=view==='front'?desired:'front';enter('view-turn');return;}
58
+ if(name!=='spin'&&seatProgress<1){enter('sit-down');return;}
59
+ // Spin's own cache starts at key24; prepare it once, only AFTER the standing
60
+ // facing change. Do not crouch before a standing turn and then crouch again.
61
+ if(walkTransitions&&name==='spin'&&!spinReady){spinReady=true;enter('launch');return;}
62
+ pendingAction=null;
63
+ if(name==='sit'){enter('idle');return;}
64
+ if(name==='lie'){
65
+ if(manualTricks){playAction('lie-down',()=>{manualDown=true;enter('down');drain();},.65);}
66
+ else{stayDown=true;enter('recline');}
67
+ return;
68
+ }
69
+ playAction(name,()=>finishTrick(name));
70
+ };
71
+ const prepareDirection=()=>{if(pendingAction){prepareAction();return;}if(!target){settle();return;}if(nextView!==view){turnView=view==='front'?nextView:'front';enter('view-turn');}else if(view==='front'&&nextFlip!==flip){turnFlip=nextFlip;enter('turn');}else issue();};
72
+ const requestAction=name=>{
73
+ preparationRate=1;
74
+ if(name==='roll'&&manualDown&&state==='down'){route=false;target=null;waypoints=[];pendingAction=null;stayDown=false;manualDown=false;restProgress=0;playAction('roll-down',()=>finishTrick('roll'));return true;}
75
+ if(name==='roll'&&state==='down'&&restProgress===.25){route=false;target=null;waypoints=[];pendingAction=null;stayDown=false;restProgress=0;playAction('roll-down',()=>finishTrick('roll'));return true;}
76
+ route=false;target=null;waypoints=[];pendingAction=name;spinReady=false;completed=false;stayDown=false;movementMode='run';stop();
77
+ if(restProgress>0||state==='recline'||state==='wake')enter('wake');
78
+ else if(['watch','view-turn','turn','arrival-exit'].includes(state)){/* reach a shared endpoint */}
79
+ else if(['run','finish','launch','settle'].includes(state))enter('finish');
80
+ // Only the spin owns a crouched endpoint. Ordinary commands must not borrow
81
+ // its old launch (neutral -> key24) before doing their actual preparation.
82
+ else if(state==='idle'&&seatProgress===0&&view==='front'&&!walkTransitions)enter('launch');else prepareDirection();
83
+ return true;
84
+ };
85
+ const faceTarget=direct=>{nextFlip=target[0]<animal.position.x;const dz=target[1]-animal.position.z,dx=target[0]-animal.position.x;straight=direct;nextView=direct?'approach':Math.abs(dz)>.16&&Math.abs(dz)>Math.abs(dx)*.7?(dz<0?'rear':'approach'):'front';};
86
+ function go(x,z,{direct=false,walking=false,urgent=false}={}){
87
+ if(![x,z].every(Number.isFinite))throw Error('Invalid paper target');
88
+ preparationRate=urgent?3:1;
89
+ target=paperGroundTarget(x,z);
90
+ pendingAction=null;stayDown=false;movementMode=walking?'walk':'run';
91
+ const dz=target[1]-animal.position.z,dx=target[0]-animal.position.x;
92
+ waypoints=[];
93
+ // Do not drag a straight rear/front drawing diagonally across the floor.
94
+ // Depart in depth then traverse; on return traverse first while still far.
95
+ // Each corner stops and uses the existing shared turn art, never camera yaw.
96
+ if(!direct&&Math.abs(dx)>.1&&Math.abs(dz)>.16){waypoints=[target];target=dz<0?[animal.position.x,target[1]]:[target[0],animal.position.z];}
97
+ faceTarget(direct);
98
+ completed=false;stop();
99
+ if(restProgress>0||state==='recline'||state==='wake')enter('wake');
100
+ else if(state==='watch'){/* Return head to its shared neutral endpoint first. */}
101
+ else if(state==='arrival-exit'){/* Finish the shared return bridge first. */}
102
+ else if(seatProgress>0||state==='sit-down'||state==='rise')enter('rise');
103
+ else if(state!=='view-turn'&&state!=='turn'){
104
+ if(walkTransitions&&state==='idle')prepareDirection();
105
+ else enter(state==='idle'?'launch':'finish');
106
+ }
107
+ }
108
+ return{
109
+ go(x,z){if(![x,z].every(Number.isFinite))throw Error('Invalid paper target');return dispatch(()=>{route=false;go(x,z);});},
110
+ // Eager attention changes preparation cadence only. Existing dispatch keeps
111
+ // actions/walks at safe endpoints; run physics and distance gait are unchanged.
112
+ chase(x,z){if(![x,z].every(Number.isFinite))throw Error('Invalid paper target');return dispatch(()=>{route=false;go(x,z,{urgent:true});});},
113
+ come(){return dispatch(()=>{route=false;if(state==='idle'&&seatProgress===1&&Math.abs(animal.position.z-.2)<.002)return;go(animal.position.x,.2,{direct:true});});},
114
+ roam(){return dispatch(()=>{route=true;routeIndex=0;go(...points[routeIndex]);});},
115
+ sit(){return dispatch(()=>requestAction('sit'));},spin(){return dispatch(()=>requestAction('spin'));},roll(){return dispatch(()=>requestAction('roll'),{keepDown:true});},
116
+ walk(side,distance=1.05){if(!['left','right'].includes(side)||!Number.isFinite(distance)||distance<=0)throw Error('Invalid walk command');return dispatch(()=>{const [x,z]=paperWalkTarget(animal.position.x,animal.position.z,side==='left'?-1:1,distance);if(Math.abs(x-animal.position.x)<1e-6)return false;route=false;go(x,z,{walking:true});return true;});},
117
+ look(side='left'){if(state!=='idle'||seatProgress!==1)return false;route=false;watchSide=side==='right'?'right':'left';enter('watch');return true;},
118
+ lie(){const name=manualDown||activeAction?.name==='lie-down'||restProgress>0?'sit':'lie';return dispatch(()=>requestAction(name));},
119
+ rest(){return dispatch(()=>{route=false;target=null;waypoints=[];pendingAction=null;stayDown=false;stop();if(restProgress>0){enter('wake');}else if(state==='watch'||state==='arrival-exit'){/* finish shared return */}else if(seatProgress>0||state==='rise'||state==='sit-down')enter('sit-down');else if(!['idle','view-turn','turn'].includes(state))enter('finish');});},
120
+ update(dt){
121
+ if(!Number.isFinite(dt)||dt<0||dt>.1)throw Error('Invalid paper step');
122
+ const before=animal.position.clone();
123
+ if(state==='run'&&travel&&target){
124
+ travelAge+=dt;const p=travel.plan.at(travelAge),ratio=travel.plan.distance?p.distance/travel.plan.distance:1;
125
+ travelDistance=p.distance;motionSpeed=p.speed;motionStage=p.stage;
126
+ const gain=travel.plan.distance?p.speed/travel.plan.distance:0;
127
+ animal.follow({x:travel.x+travel.dx*ratio,y:0,z:travel.z+travel.dz*ratio},{x:travel.dx*gain,y:0,z:travel.dz*gain},{heading:travel.heading});
128
+ arrived=p.done;
129
+ }
130
+ animal.update(dt);
131
+ const prepareDt=dt*preparationRate;
132
+ age+=['view-turn','turn','rise','watch','arrival-exit','launch','wake'].includes(state)?prepareDt:dt;
133
+ const distance=Math.hypot(animal.position.x-before.x,animal.position.z-before.z);
134
+ director.update(dt,state==='run',distance);
135
+ if(state==='run'){
136
+ phase+=distance/travel.stride;
137
+ if(target&&arrived){completed=true;stop();target=null;if(movementMode==='walk')playAction('walk-exit',()=>{movementMode='run';enter(walkTransitions?'idle':'settle');drain();});else enter('finish');}
138
+ }else if(state==='action'&&age>=activeAction.duration){
139
+ const done=actionDone;activeAction=null;actionDone=null;done();
140
+ }else if(state==='launch'&&age>=launchSeconds()){
141
+ phase=Math.ceil(phase);prepareDirection();
142
+ }else if(state==='finish'&&age>=.22){
143
+ phase=Math.ceil(phase);
144
+ if(!target&&waypoints.length){target=waypoints.shift();faceTarget(false);}
145
+ if(target&&Math.hypot(target[0]-animal.position.x,target[1]-animal.position.z)>.12){
146
+ prepareDirection();
147
+ }else if(pendingAction)prepareDirection();else settle();
148
+ }else if(state==='view-turn'&&age>=viewTurnSeconds()){view=turnView;prepareDirection();}
149
+ else if(state==='turn'&&age>=turnSeconds()){flip=turnFlip;prepareDirection();}
150
+ else if(state==='sit-down'){seatProgress=Math.min(1,seatProgress+dt/(snappyPreparation()?.35:.95));if(seatProgress===1){seatVariant='default';enter('idle');if(pendingAction)prepareDirection();}}
151
+ else if(state==='rise'){seatProgress=Math.max(0,seatProgress-prepareDt/(snappyPreparation()?.28:.65));if(seatProgress===0){if(seatVariant==='arrival')enter('arrival-exit');else prepareDirection();}}
152
+ else if(state==='arrival-exit'&&age>=.22){seatVariant='default';prepareDirection();}
153
+ else if(state==='watch'&&age>=1.4){if(pendingAction)prepareDirection();else if(target)enter('rise');else enter('idle');}
154
+ else if(state==='recline'){const end=stayDown?.25:1;restProgress=Math.min(end,restProgress+dt/1.8);if(restProgress===end)enter(stayDown?'down':'nap');}
155
+ else if(state==='nap'&&age>=5&&!stayDown){enter('wake');}
156
+ else if(state==='wake'){restProgress=Math.max(0,restProgress-prepareDt/1.2);if(restProgress===0){if(pendingAction)prepareDirection();else if(target)enter('rise');else enter('idle');}}
157
+ else if(state==='idle'&&route&&age>=idleHold)nextRoute();
158
+ else if(state==='settle'&&age>=.35)enter('idle');
159
+ },
160
+ sample(clock,{poseFps=24}={}){
161
+ if(![12,24,30].includes(poseFps))throw Error('Invalid pose cadence');
162
+ let pair=20,blend=0,kind=state,rearPair=null,approachPair=null;
163
+ let seat=state==='sit-down'||state==='rise'||seatProgress>0?Math.round(seatProgress*48)/48:null;
164
+ let behavior=null,behaviorProgress=0;
165
+ let actionName=activeAction?.name??(state==='run'&&movementMode==='walk'?'walk':null),actionProgress=activeAction?(activeAction.timing?activeAction.timing.progressAt(age):Math.min(1,age/activeAction.duration)):phase;
166
+ if(manualDown&&state==='down'){actionName='lie-down';actionProgress=1;}
167
+ if(walkTransitions&&state==='view-turn'&&turnView==='front'){
168
+ actionName=view==='approach'?'walk-face-front':'walk-face-rear';actionProgress=Math.min(1,age/viewTurnSeconds());
169
+ }
170
+ // This clip is a calibrated standing half-turn, not walk-specific motion.
171
+ // The old turn's intro/outro added an unrelated squat to every reversal.
172
+ if(walkTransitions&&state==='turn'){actionName='walk-turn';actionProgress=Math.min(1,age/turnSeconds());}
173
+ if(view==='approach'&&approachPlan&&(state==='run'||(state==='finish'&&completed))){const p=approachPlan.at(travelDistance);if(state==='finish'){behavior='brake';behaviorProgress=1;}else if(p.clip!=='run'){behavior=p.clip;behaviorProgress=p.progress;}}
174
+ if(state==='arrival-exit'){behavior='finish';behaviorProgress=Math.min(1,age/.22);}
175
+ if(state==='watch'){behavior=`watch-${watchSide}`;behaviorProgress=Math.min(1,age/1.4);}
176
+ if(restProgress>0||state==='recline'||state==='wake'||state==='nap'){behavior='rest';behaviorProgress=restProgress;}
177
+ if(state==='run'||state==='finish'){
178
+ const p=state==='finish'?phase+(Math.ceil(phase)-phase)*Math.min(1,age/.22):phase;
179
+ const q=view!=='front'?(p%1)*8:retimeRun((p%1)*20,info.run_timing);pair=Math.floor(q)%info.run_sequence.length;blend=q-Math.floor(q);if(view==='rear')rearPair=5+pair;if(view==='approach')approachPair=5+pair;
180
+ }else if(state==='view-turn'){
181
+ const q=Math.min(4.999999,age/viewTurnSeconds()*5),i=Math.floor(q);if(view==='approach'||turnView==='approach')approachPair=(view==='front'?0:13)+i;else rearPair=(view==='front'?0:13)+i;blend=q-i;
182
+ }else if(state==='turn'){
183
+ const t=Math.min(1,age/turnSeconds());
184
+ if(t<.15){pair=flip?43:41;blend=t/.15;}
185
+ else if(t<.85){const q=(t-.15)/.7*11,i=Math.min(10,Math.floor(q));pair=flip?40-i:30+i;blend=flip?1-(q-i):q-i;}
186
+ else{pair=flip?44:42;blend=(t-.85)/.15;}
187
+ }else if(state==='launch'){pair=45;blend=Math.min(1,age/launchSeconds());if(view==='rear'){rearPair=5;blend=0;}}
188
+ else if(state==='settle'){pair=46;blend=Math.min(1,age/.35);}
189
+ else{const q=Math.floor((walkTransitions&&state==='idle'?age:clock)*poseFps)/poseFps*6;pair=20+Math.floor(q)%10;blend=q-Math.floor(q);}
190
+ if(view==='rear'&&(state==='idle'||state==='settle'||state==='turn')){rearPair=5;blend=0;}
191
+ if(view==='approach'&&(state==='idle'||state==='settle'||state==='turn'||state==='launch')){approachPair=5;blend=0;}
192
+ // Deliberate held source poses: no live frame blending or face/body deformation.
193
+ blend=Math.round(blend*16)/16;
194
+ const key=`${state}/${actionName}/${view}/${turnView}/${poseFps}/${Math.floor(clock*poseFps)}`;
195
+ if(held?.key===key){pair=held.pair;blend=held.blend;rearPair=held.rearPair;approachPair=held.approachPair;seat=held.seat;behavior=held.behavior;behaviorProgress=held.behaviorProgress;actionName=held.actionName;actionProgress=held.actionProgress;}else held={key,pair,blend,rearPair,approachPair,seat,behavior,behaviorProgress,actionName,actionProgress,posePhase:phase};
196
+ return{state:state==='run'||state==='finish'?'run':kind,pair,blend,rearPair,approachPair,seatProgress:seat,seatVariant,behavior,behaviorProgress,actionName,actionProgress,queued:!!deferred,view,straight,flip:state==='turn'&&!walkTransitions?false:flip,phase,posePhase:held.posePhase,stride:travel?.stride??PAPER_GAITS.front.stride,heading:travel?.heading??0,speed:motionSpeed,motionStage,travelDistance,energy:director.report.energy,x:animal.position.x,z:animal.position.z};
197
+ },get state(){return state;}
198
+ };
199
+ }
200
+
201
+ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,contacts=true,pawTransport=false}={}){
202
+ if(![width,height].every(Number.isInteger)||width<2||height<2||width*height>1_000_000)throw Error('Invalid paper grid');
203
+ const tween=createCachedTween(cache,info),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
204
+ const out=new Uint8ClampedArray(width*height*4),area=createPaperAreaSampler();
205
+ const rearSample=rear?createRearSampler(rear.bytes,rear.info):null,lock=createPaperFootLock({maxImageStretch:pawTransport?1.45:Infinity});
206
+ const approachSample=approach?createRearSampler(approach.bytes,approach.info):null;
207
+ const seatSample=seat?createPaperSeatSampler(seat.bytes,seat.info):null;
208
+ const arrivalSeatSample=arrivalSeat?createPaperSeatSampler(arrivalSeat.bytes,arrivalSeat.info):null;
209
+ const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info):null;
210
+ const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info):null;
211
+ const actionSample=actions?createPaperActionSampler(actions.bytes,actions.info):null,actionArea=actions?createPaperAreaSampler(actions.info.width):null;
212
+ const walkEpisodes=actions?createPaperSupportEpisodes(actions.info.feet,actions.info.clips.walk,9):null;
213
+ const sourceEpisodes=rear?{
214
+ front:createPaperSupportEpisodes(rear.info.frontFeet,[0,1,2,3,4,5,6,7],17),
215
+ rear:createPaperSupportEpisodes(rear.info.rearFeet,[5,6,7,8,9,10,11,12],9),
216
+ approach:approach?createPaperSupportEpisodes(approach.info.feet,[5,6,7,8,9,10,11,12],9):null,
217
+ }:null;
218
+ const behaviorEpisodes=behaviors?Object.fromEntries(Object.entries(behaviors.info.clips).map(([name,indices])=>[name,createPaperSupportEpisodes(behaviors.info.feet,indices,9,{cyclic:name==='walk'})])):{};
219
+ const render=(state,{x=.5,y=.85,size=.85,pixelAspect=1,contactEnabled=contacts,groundFromPixel,pixelFromGround}={})=>{
220
+ if(![x,y,size].every(Number.isFinite)||size<=0||size>3)throw Error('Invalid paper placement');
221
+ const isRear=state.rearPair!==null&&state.rearPair!==undefined&&rearSample;
222
+ const isApproach=state.approachPair!==null&&state.approachPair!==undefined&&approachSample;
223
+ const isSeat=state.seatProgress!==null&&state.seatProgress!==undefined&&seatSample;
224
+ if(!Number.isFinite(pixelAspect)||pixelAspect<.1||pixelAspect>10)throw Error('Invalid dot pixel aspect');
225
+ const isInteraction=state.interactionPose&&interactionSample;
226
+ const isBehavior=!isInteraction&&state.behavior&&behaviorSample;
227
+ const isAction=!isInteraction&&state.actionName&&actionSample,sourceN=isAction?actions.info.width:320,originX=(sourceN-320)/2,originY=(sourceN/320-1)*300;
228
+ const image=isInteraction?interactionSample(state.interactionPose):isAction?actionSample(state):isBehavior?behaviorSample(state):isSeat?(state.seatVariant==='arrival'&&arrivalSeatSample?arrivalSeatSample:seatSample)(state.seatProgress):isApproach?approachSample(state.approachPair,state.blend):isRear?rearSample(state.rearPair,state.blend):tween(state),side=height*size,sideX=side/pixelAspect;
229
+ // Quantize source lookup only, never the projected rectangle. Rounding side,
230
+ // left and top first introduced whole-body scale/position jumps at thresholds.
231
+ const left=width*x-sideX/2,floor=isInteraction||isAction||isBehavior||isSeat||isRear||isApproach?0:cleanGroundOffset(state,renderMeta),top=height*y-(300-floor)/320*side;
232
+ let feet=[];
233
+ if(contactEnabled&&isAction&&state.actionName==='walk'){
234
+ const p=paperActionPose('walk',state.actionProgress,actions.info);feet=walkEpisodes(actions.info.feet[p.pair][Math.round(p.blend*8)],p.pair,p.blend,state.actionProgress);
235
+ if(state.flip)feet=feet.map(p=>({...p,x:319-p.x}));
236
+ }else if(contactEnabled&&rear&&state.state==='run'&&!isAction){
237
+ const b=isBehavior?paperBehaviorPose(state,behaviors.info):null;
238
+ feet=isBehavior?behaviors.info.feet[b.pair][Math.round(b.blend*8)]:isApproach?approach.info.feet[state.approachPair][Math.round(state.blend*8)]:isRear?rear.info.rearFeet[state.rearPair][Math.round(state.blend*8)]:rear.info.frontFeet[state.pair]?.[Math.round(state.blend*16)]??[];
239
+ feet=isBehavior?behaviorEpisodes[state.behavior](feet,b.pair,b.blend,state.behaviorProgress):isApproach?sourceEpisodes.approach(feet,state.approachPair,state.blend,state.posePhase??state.phase??0):isRear?sourceEpisodes.rear(feet,state.rearPair,state.blend,state.posePhase??state.phase??0):sourceEpisodes.front(feet,state.pair,state.blend,state.posePhase??state.phase??0);
240
+ if(state.flip)feet=feet.map(p=>({...p,x:319-p.x}));
241
+ }
242
+ const pins=lock.update(feet,{left,top,side,sideX,groundFromPixel,pixelFromGround,root:{x:state.x,z:state.z,heading:state.heading??0}},`${state.view??'front'}/${state.flip}/${state.state==='run'}/${state.actionName??state.behavior??'gallop'}`);render.lastPins=pins;
243
+ const transport=pawTransport?createPawTransport(feet,pins,{opaque:(x,y)=>{
244
+ const rawX=x+originX,rawY=y+originY;if(rawX<0||rawX>=sourceN||rawY<0||rawY>=sourceN)return false;
245
+ let sx=Math.floor(rawX);if(state.flip)sx=sourceN-1-sx;
246
+ return image[(Math.floor(rawY)*sourceN+sx)*4]>16;
247
+ }}):null;render.lastTransport=transport;
248
+ const minify=side<320||sideX<320,filter=isAction?actionArea:area;if(minify)filter.prepare(image);
249
+ out.fill(0);for(let i=3;i<out.length;i+=4)out[i]=255;
250
+ render.lastPlacement={left,top,side,sideX};
251
+ // Measured drawn mouth of the EXACT sample above (same priority, quantization,
252
+ // placement and mirror). Side/rear/action/interaction stay null: unsupported.
253
+ const mouthFamily=isInteraction||isAction?null:isBehavior?'behaviors':isSeat?(state.seatVariant==='arrival'&&arrivalSeatSample?'arrivalSeat':'seat'):isApproach?'approach':null;
254
+ const mouthInfo={behaviors:behaviors?.info,seat:seat?.info,arrivalSeat:arrivalSeat?.info,approach:approach?.info}[mouthFamily];
255
+ const mouthFrame=mouthFamily?mouthSourceFrame(mouthFamily,state,mouthInfo):null;
256
+ render.lastMouth=projectMouth(mouthFamily?mouthAnchor(mouthFamily,mouthFrame,mouthInfo?.sha256):null,{left,top,side,sideX},state.flip,{family:mouthFamily,frame:mouthFrame});
257
+ const drawTop=top-originY/320*side,bottom=top+Math.max(sourceN-originY,...pins.map(p=>p.target.y+20))/320*side;
258
+ const drawLeft=left+Math.min(-originX,...pins.map(p=>p.target.x-20))/320*sideX,drawRight=left+Math.max(sourceN-originX,...pins.map(p=>p.target.x+20))/320*sideX;
259
+ for(let dy=Math.max(0,minify?Math.floor(drawTop):Math.ceil(drawTop-.5));dy<Math.min(height,minify?Math.ceil(bottom):Math.ceil(bottom-.5));dy++)for(let dx=Math.max(0,minify?Math.floor(drawLeft):Math.ceil(drawLeft-.5));dx<Math.min(width,minify?Math.ceil(drawRight):Math.ceil(drawRight-.5));dx++){
260
+ const px=(dx+.5-left)/sideX*320,py=(dy+.5-top)/side*320;
261
+ const warped=transport?transport(px,py):footSourcePoint(px,py,pins);if(!warped)continue;
262
+ const rawX=warped.x+originX,rawY=warped.y+originY;
263
+ let sx=Math.max(0,Math.min(sourceN-1,Math.floor(rawX)));if(state.flip)sx=sourceN-1-sx;
264
+ const sy=Math.max(0,Math.min(sourceN-1,Math.floor(rawY))),j=(dy*width+dx)*4,k=(sy*sourceN+sx)*4;
265
+ // Nearest-neighbor source sampling. The authored grayscale/fur remains intact.
266
+ out[j]=out[j+1]=out[j+2]=minify?filter.sample(state.flip?sourceN-rawX:rawX,rawY,320/sideX,320/side):(rawY<0||rawY>=sourceN||rawX<0||rawX>=sourceN?0:image[k]);
267
+ }
268
+ return out;
269
+ };
270
+ render.report=()=>lock.report();render.lastPins=[];render.lastMouth=null;return render;
271
+ }