@bitefoundry/retrieviewer 0.0.3 → 0.0.5

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 (263) hide show
  1. package/ASSET-PROVENANCE.md +11 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +95 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/INSTALLING.md +4 -0
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +116 -0
  11. package/README.md +121 -59
  12. package/RELEASING.md +33 -13
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/linux-overlay/overlay.py +8 -2
  17. package/integrations/macos-overlay/overlay.jxa +9 -7
  18. package/integrations/opencode/runtime.mjs +4 -4
  19. package/integrations/opencode/tui.tsx +1 -1
  20. package/integrations/windows-overlay/Overlay.cs +7 -6
  21. package/integrations/windows-overlay/desktop.mjs +2 -1
  22. package/integrations/windows-overlay/protocol.mjs +13 -7
  23. package/integrations/windows-overlay/worker.mjs +1 -1
  24. package/package.json +34 -13
  25. package/puppy-comfort-sprites.mjs +33 -0
  26. package/puppy-companion-comfort-q5.bin.gz +0 -0
  27. package/puppy-companion-comfort.json +140 -0
  28. package/puppy-companion-life.mjs +58 -18
  29. package/puppy-companion-objects.mjs +12 -0
  30. package/puppy-companion-sprites.mjs +4 -2
  31. package/puppy-frame-predictive.mjs +15 -9
  32. package/puppy-frame-store.mjs +2 -2
  33. package/puppy-paper-actions.mjs +4 -2
  34. package/puppy-paper-behaviors.mjs +1 -1
  35. package/puppy-paper-contact.mjs +4 -2
  36. package/puppy-paper-seat.mjs +4 -2
  37. package/puppy-paper-world.mjs +14 -0
  38. package/puppy-paper.mjs +79 -21
  39. package/retrieviewer-cli.mjs +8 -6
  40. package/retrieviewer-plugin.d.mts +1 -1
  41. package/retrieviewer-plugin.mjs +1 -1
  42. package/retrieviewer.d.mts +12 -4
  43. package/retrieviewer.mjs +20 -14
  44. package/locomotion-core.mjs +0 -21
  45. package/locomotion-puppy.animal +0 -0
  46. package/vendor/procedural-animals/package.json +0 -40
  47. package/vendor/procedural-animals/src/animal.js +0 -146
  48. package/vendor/procedural-animals/src/bake.js +0 -51
  49. package/vendor/procedural-animals/src/build.js +0 -81
  50. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  51. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  52. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  53. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  54. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  55. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  56. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  57. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  58. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  59. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  60. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  61. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  62. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  63. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  64. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  65. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  66. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  67. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  68. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  69. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  70. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  71. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  72. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  73. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  74. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  75. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  76. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  77. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  79. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  80. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  81. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  82. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  83. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  84. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  85. package/vendor/procedural-animals/src/index.d.ts +0 -98
  86. package/vendor/procedural-animals/src/index.js +0 -29
  87. package/vendor/procedural-animals/src/registry.js +0 -48
  88. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  89. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  90. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  91. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  92. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  94. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  95. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  96. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  97. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  98. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  99. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  100. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  101. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  102. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  103. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  104. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  105. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  106. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  107. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  108. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  109. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  110. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  111. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  112. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  113. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  114. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  115. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  116. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  117. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  118. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  119. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  120. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  121. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  122. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  123. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  124. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  125. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  126. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  127. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  128. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  129. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  130. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  131. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  132. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  133. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  134. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  135. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  136. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  137. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  138. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  139. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  140. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  141. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  142. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  143. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  144. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  145. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  146. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  147. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  148. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  149. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  150. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  151. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  152. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  153. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  154. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  155. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  156. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  157. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  158. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  159. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  160. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  161. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  162. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  163. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  164. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  165. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  166. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  167. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  168. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  169. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  170. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  171. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  172. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  173. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  174. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  175. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  176. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  177. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  178. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  179. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  180. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  181. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  182. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  183. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  184. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  185. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  186. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  187. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  188. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  189. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  190. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  191. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  192. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  193. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  194. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  195. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  196. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  197. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  198. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  199. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  200. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  201. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  202. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  203. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  204. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  205. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  206. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  207. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  208. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  209. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  210. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  211. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  212. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  213. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  214. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  215. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  216. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  217. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  218. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  219. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  220. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  221. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  222. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  223. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  224. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  225. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  226. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  227. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  228. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  229. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  230. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  231. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  232. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  233. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  234. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  235. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  236. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  237. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  238. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  239. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  240. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  241. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  242. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  243. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  244. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  245. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  246. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  247. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  248. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  249. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  250. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  251. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  252. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  253. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  254. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  255. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  256. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  257. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  258. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  259. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  260. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  261. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  262. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  263. package/vendor/procedural-animals/src/worker.js +0 -29
package/puppy-paper.mjs CHANGED
@@ -8,13 +8,31 @@ import {createPaperAreaSampler} from './puppy-paper-area.mjs';
8
8
  import {PAPER_ACTION_DURATIONS,createPaperActionSampler,paperActionPose} from './puppy-paper-actions.mjs';
9
9
  import {createPawTransport} from './puppy-paper-paw-transport.mjs';
10
10
  import {createCompanionSpriteSampler} from './puppy-companion-sprites.mjs';
11
+ import {createComfortSpriteSampler} from './puppy-comfort-sprites.mjs';
11
12
  import {mouthSourceFrame,mouthAnchor,projectMouth} from './puppy-mouth-anchor.mjs';
13
+ import {trickClipEdges} from './puppy-paper-trick-timing.mjs';
12
14
 
13
15
  // Fixed viewer: slightly closer/wider lens gives depth without changing the art.
14
16
  export {PAPER_CAMERA} from './puppy-paper-world.mjs';
15
17
 
18
+ // Comfort poses reuse existing clips only. Belly holds the calibrated roll-down
19
+ // source exactly at the start of the edge leaving `key` (paws-up belly), easing
20
+ // in/out with zero speed at the hold. `hold` defaults to the v9 keyOrder/edge
21
+ // boundary of roll7; createPaperComfortTimings re-derives it from action info.
22
+ export const PAPER_COMFORT_LIMITS=Object.freeze({belly:30,sleep:120,tug:30});
23
+ export const PAPER_COMFORT_TIMINGS=Object.freeze({belly:Object.freeze({key:'roll7',hold:.4449823183425605,enter:1.3,exit:1.7}),tugExit:.35});
24
+ export function createPaperComfortTimings(actionInfo,{key=PAPER_COMFORT_TIMINGS.belly.key}={}){
25
+ const {keys,bounds}=trickClipEdges('roll-down',actionInfo),i=keys.findIndex(k=>k[0]===key);
26
+ if(i<=0)throw Error(`Invalid comfort timing: no roll-down edge leaves ${key}`);
27
+ return Object.freeze({...PAPER_COMFORT_TIMINGS,belly:Object.freeze({...PAPER_COMFORT_TIMINGS.belly,key,hold:bounds[i]})});
28
+ }
29
+ const comfortDuration=(name,seconds)=>{if(typeof seconds!=='number'||!Number.isFinite(seconds)||seconds<=0||seconds>PAPER_COMFORT_LIMITS[name])throw Error(`Invalid ${name} duration`);};
30
+ const comfortEase=u=>u<=0?0:u>=1?1:u*u*(3-2*u);
31
+
16
32
  // Original artwork only. Physics supplies ground travel; no procedural dog is drawn.
17
- export function createPaperActor(animal,info,{manualTricks=false,trickTimings=null,walkTransitions=false}={}){
33
+ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=null,walkTransitions=false,comfortTimings=PAPER_COMFORT_TIMINGS}={}){
34
+ const bellyTiming=comfortTimings?.belly;
35
+ if(!bellyTiming||![bellyTiming.hold,bellyTiming.enter,bellyTiming.exit,comfortTimings.tugExit].every(Number.isFinite)||bellyTiming.hold<=0||bellyTiming.hold>=1||bellyTiming.enter<=0||bellyTiming.exit<=0||comfortTimings.tugExit<=0)throw Error('Invalid comfort timing');
18
36
  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
37
  let held=null,waypoints=[];
20
38
  let preparationRate=1;
@@ -22,6 +40,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
22
40
  let straight=false,seatProgress=0,seatVariant='default',travel=null,approachPlan=null,travelAge=0,travelDistance=0,motionSpeed=0,motionStage='rest',completed=false;
23
41
  let watchSide='left',restProgress=0,idleHold=2.5;
24
42
  let pendingAction=null,activeAction=null,actionDone=null,deferred=null,movementMode='run',stayDown=false,manualDown=false,spinReady=false;
43
+ let comfort=null,comfortSeconds=0,sleepLook=null,nextSleepLook={variant:'side',right:false};
25
44
  const director=createPaperDirector();
26
45
  const points=[[.12,PAPER_GROUND.away]];
27
46
  const enter=s=>{state=s;age=0;};
@@ -33,13 +52,31 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
33
52
  const turnSeconds=()=>snappyPreparation()?.22:.85;
34
53
  const launchSeconds=()=>snappyPreparation()?.10:.25;
35
54
  const dispatch=(fn,{keepDown=false}={})=>{
36
- if(activeAction||(movementMode==='walk'&&state==='run')){route=false;deferred=()=>dispatch(fn,{keepDown});return true;}
55
+ if(activeAction||state==='tug'||(movementMode==='walk'&&state==='run')){route=false;deferred=()=>dispatch(fn,{keepDown});hurryComfort();return true;}
37
56
  // The calibrated trick down pose has its own byte-shared recovery; never
38
57
  // jump back into the older/larger rest illustration on another command.
39
58
  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
59
  return fn();
41
60
  };
42
61
  const drain=()=>{const fn=deferred;deferred=null;if(fn){fn();return true;}return false;};
62
+ // Belly: 0 -> hold eased to rest, held exact source sample, eased hold -> 1.
63
+ // Queued commands cut the remaining hold only; the roll always finishes at seat.
64
+ const bellyAction=seconds=>{
65
+ const {hold:H,enter:a,exit:b}=bellyTiming,t={comfort:'belly',hold:[a,a+seconds],duration:a+seconds+b};
66
+ t.progressAt=age=>{if(!Number.isFinite(age))throw Error('Invalid comfort age');return age<t.hold[0]?H*comfortEase(age/a):age<t.hold[1]?H:H+(1-H)*comfortEase((age-t.hold[1])/b);};
67
+ return t;
68
+ };
69
+ const startBelly=seconds=>{
70
+ manualDown=false;stayDown=false;restProgress=0;comfort=null;
71
+ const timing=bellyAction(seconds);activeAction={name:'roll-down',duration:timing.duration,timing,comfort:'belly'};actionDone=()=>finishTrick('roll');stop();enter('action');
72
+ if(deferred)hurryComfort();
73
+ };
74
+ const tugProgress=()=>{if(!comfort||comfort.kind!=='tug')return 0;const h=comfort.hurry;return h?(h.length>0?Math.min(1,h.progress+(1-h.progress)*(age-h.age)/h.length):1):Math.min(1,age/comfort.seconds);};
75
+ function hurryComfort(){
76
+ const t=activeAction?.timing;
77
+ if(t?.comfort==='belly'&&age<t.hold[1]){t.hold[1]=Math.max(t.hold[0],age);t.duration=activeAction.duration=t.hold[1]+bellyTiming.exit;}
78
+ else if(state==='tug'&&comfort&&!comfort.hurry){const progress=tugProgress();comfort.hurry={progress,age,length:Math.min(comfortTimings.tugExit,(1-progress)*comfort.seconds)};}
79
+ }
43
80
  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
81
  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
82
  const issue=(skipEntry=false)=>{
@@ -61,6 +98,9 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
61
98
  if(walkTransitions&&name==='spin'&&!spinReady){spinReady=true;enter('launch');return;}
62
99
  pendingAction=null;
63
100
  if(name==='sit'){enter('idle');return;}
101
+ if(name==='belly'){const seconds=comfortSeconds;playAction('lie-down',()=>startBelly(seconds),.65);return;}
102
+ if(name==='sleep'){stayDown=false;sleepLook={...nextSleepLook};comfort={kind:'sleep',seconds:comfortSeconds};enter('recline');return;}
103
+ if(name==='tug'){stop();comfort={kind:'tug',seconds:comfortSeconds};enter('tug');return;}
64
104
  if(name==='lie'){
65
105
  if(manualTricks){playAction('lie-down',()=>{manualDown=true;enter('down');drain();},.65);}
66
106
  else{stayDown=true;enter('recline');}
@@ -73,7 +113,9 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
73
113
  preparationRate=1;
74
114
  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
115
  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();
116
+ if(name==='belly'&&state==='down'&&(manualDown||restProgress===.25)){route=false;target=null;waypoints=[];pendingAction=null;startBelly(comfortSeconds);return true;}
117
+ if(name==='sleep'&&state==='sleep'&&comfort?.kind==='sleep'){comfort.seconds=age+comfortSeconds;return true;}
118
+ comfort=null;route=false;target=null;waypoints=[];pendingAction=name;spinReady=false;completed=false;stayDown=false;movementMode='run';stop();
77
119
  if(restProgress>0||state==='recline'||state==='wake')enter('wake');
78
120
  else if(['watch','view-turn','turn','arrival-exit'].includes(state)){/* reach a shared endpoint */}
79
121
  else if(['run','finish','launch','settle'].includes(state))enter('finish');
@@ -87,7 +129,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
87
129
  if(![x,z].every(Number.isFinite))throw Error('Invalid paper target');
88
130
  preparationRate=urgent?3:1;
89
131
  target=paperGroundTarget(x,z);
90
- pendingAction=null;stayDown=false;movementMode=walking?'walk':'run';
132
+ pendingAction=null;stayDown=false;comfort=null;movementMode=walking?'walk':'run';
91
133
  const dz=target[1]-animal.position.z,dx=target[0]-animal.position.x;
92
134
  waypoints=[];
93
135
  // Do not drag a straight rear/front drawing diagonally across the floor.
@@ -116,10 +158,14 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
116
158
  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
159
  look(side='left'){if(state!=='idle'||seatProgress!==1)return false;route=false;watchSide=side==='right'?'right':'left';enter('watch');return true;},
118
160
  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');});},
161
+ // Comfort poses: bounded finite seconds; same dispatch safety as tricks.
162
+ belly(seconds=6){comfortDuration('belly',seconds);return dispatch(()=>{comfortSeconds=seconds;return requestAction('belly');},{keepDown:true});},
163
+ sleep(seconds=25,variant='side',right=false){comfortDuration('sleep',seconds);if(!['side','belly','curl','chin'].includes(variant)||typeof right!=='boolean')throw Error('Invalid sleep pose');return dispatch(()=>{comfortSeconds=seconds;nextSleepLook={variant,right};return requestAction('sleep');});},
164
+ tug(seconds=6){comfortDuration('tug',seconds);return dispatch(()=>{comfortSeconds=seconds;return requestAction('tug');});},
165
+ rest(){return dispatch(()=>{route=false;target=null;waypoints=[];pendingAction=null;stayDown=false;comfort=null;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
166
  update(dt){
121
167
  if(!Number.isFinite(dt)||dt<0||dt>.1)throw Error('Invalid paper step');
122
- const before=animal.position.clone();
168
+ const beforeX=animal.position.x,beforeZ=animal.position.z;
123
169
  if(state==='run'&&travel&&target){
124
170
  travelAge+=dt;const p=travel.plan.at(travelAge),ratio=travel.plan.distance?p.distance/travel.plan.distance:1;
125
171
  travelDistance=p.distance;motionSpeed=p.speed;motionStage=p.stage;
@@ -130,7 +176,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
130
176
  animal.update(dt);
131
177
  const prepareDt=dt*preparationRate;
132
178
  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);
179
+ const distance=Math.hypot(animal.position.x-beforeX,animal.position.z-beforeZ);
134
180
  director.update(dt,state==='run',distance);
135
181
  if(state==='run'){
136
182
  phase+=distance/travel.stride;
@@ -151,7 +197,9 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
151
197
  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
198
  else if(state==='arrival-exit'&&age>=.22){seatVariant='default';prepareDirection();}
153
199
  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');}
200
+ else if(state==='recline'){const end=stayDown?.25:1;restProgress=Math.min(end,restProgress+dt/1.8);if(restProgress===end)enter(stayDown?'down':comfort?.kind==='sleep'?'sleep':'nap');}
201
+ else if(state==='sleep'&&age>=comfort.seconds){comfort=null;enter('wake');}
202
+ else if(state==='tug'&&tugProgress()>=1){comfort=null;enter('idle');drain();}
155
203
  else if(state==='nap'&&age>=5&&!stayDown){enter('wake');}
156
204
  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
205
  else if(state==='idle'&&route&&age>=idleHold)nextRoute();
@@ -161,7 +209,9 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
161
209
  if(![12,24,30].includes(poseFps))throw Error('Invalid pose cadence');
162
210
  let pair=20,blend=0,kind=state,rearPair=null,approachPair=null;
163
211
  let seat=state==='sit-down'||state==='rise'||seatProgress>0?Math.round(seatProgress*48)/48:null;
164
- let behavior=null,behaviorProgress=0;
212
+ if(restProgress===0&&state!=='recline')sleepLook=null;
213
+ let behavior=null,behaviorProgress=0,comfortPose=state==='tug'?{clip:'tug',progress:tugProgress()}:sleepLook&&restProgress>.72?{clip:'sleep',...sleepLook,blend:comfortEase((restProgress-.72)/.28)}:null;
214
+ if(activeAction?.comfort)kind=activeAction.comfort;
165
215
  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
216
  if(manualDown&&state==='down'){actionName='lie-down';actionProgress=1;}
167
217
  if(walkTransitions&&state==='view-turn'&&turnView==='front'){
@@ -192,23 +242,30 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
192
242
  // Deliberate held source poses: no live frame blending or face/body deformation.
193
243
  blend=Math.round(blend*16)/16;
194
244
  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};
245
+ 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;comfortPose=held.comfortPose;}else held={key,pair,blend,rearPair,approachPair,seat,behavior,behaviorProgress,actionName,actionProgress,comfortPose,posePhase:phase};
246
+ 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,...(comfortPose?{comfortPose:{...comfortPose}}:{})};
197
247
  },get state(){return state;}
198
248
  };
199
249
  }
200
250
 
201
- export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,contacts=true,pawTransport=false}={}){
251
+ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,comfort,contacts=true,pawTransport=false}={}){
202
252
  if(![width,height].every(Number.isInteger)||width<2||height<2||width*height>1_000_000)throw Error('Invalid paper grid');
203
253
  const tween=createCachedTween(cache,info),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
204
254
  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;
255
+ // Only one authored source is drawn at a time. Small samplers borrow a view of
256
+ // the action-sized scratch. A sleep blend reads each base pixel before writing
257
+ // that same pixel, so it needs no second image or per-frame temporary buffer.
258
+ const actionScratch=new Uint8ClampedArray(Math.max(320*320,(actions?.info.width??320)*(actions?.info.height??320))*4);
259
+ const spriteScratch=actionScratch.subarray(0,320*320*4);
260
+ for(let i=3;i<actionScratch.length;i+=4)actionScratch[i]=255;
261
+ const rearSample=rear?createRearSampler(rear.bytes,rear.info,spriteScratch):null,lock=createPaperFootLock({maxImageStretch:pawTransport?1.45:Infinity});
262
+ const approachSample=approach?createRearSampler(approach.bytes,approach.info,spriteScratch):null;
263
+ const seatSample=seat?createPaperSeatSampler(seat.bytes,seat.info,spriteScratch):null;
264
+ const arrivalSeatSample=arrivalSeat?createPaperSeatSampler(arrivalSeat.bytes,arrivalSeat.info,spriteScratch):null;
265
+ const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info,spriteScratch):null;
266
+ const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info,spriteScratch):null;
267
+ const comfortSample=comfort?createComfortSpriteSampler(comfort.bytes,comfort.info,spriteScratch):null;
268
+ const actionSample=actions?createPaperActionSampler(actions.bytes,actions.info,actionScratch.subarray(0,actions.info.width*actions.info.height*4)):null,actionArea=actions?createPaperAreaSampler(actions.info.width):null;
212
269
  const walkEpisodes=actions?createPaperSupportEpisodes(actions.info.feet,actions.info.clips.walk,9):null;
213
270
  const sourceEpisodes=rear?{
214
271
  front:createPaperSupportEpisodes(rear.info.frontFeet,[0,1,2,3,4,5,6,7],17),
@@ -222,10 +279,11 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
222
279
  const isApproach=state.approachPair!==null&&state.approachPair!==undefined&&approachSample;
223
280
  const isSeat=state.seatProgress!==null&&state.seatProgress!==undefined&&seatSample;
224
281
  if(!Number.isFinite(pixelAspect)||pixelAspect<.1||pixelAspect>10)throw Error('Invalid dot pixel aspect');
225
- const isInteraction=state.interactionPose&&interactionSample;
282
+ const isComfort=state.comfortPose&&comfortSample;
283
+ const isInteraction=isComfort||state.interactionPose&&interactionSample;
226
284
  const isBehavior=!isInteraction&&state.behavior&&behaviorSample;
227
285
  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;
286
+ const image=isComfort?comfortSample({...state.comfortPose,...state.comfortPose.clip==='sleep'&&state.comfortPose.blend<1&&behaviorSample?{base:behaviorSample({behavior:'rest',behaviorProgress:state.behaviorProgress})}:{}}):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
287
  // Quantize source lookup only, never the projected rectangle. Rounding side,
230
288
  // left and top first introduced whole-body scale/position jumps at thresholds.
231
289
  const left=width*x-sideX/2,floor=isInteraction||isAction||isBehavior||isSeat||isRear||isApproach?0:cleanGroundOffset(state,renderMeta),top=height*y-(300-floor)/320*side;
@@ -13,7 +13,7 @@ Usage:
13
13
  retrieviewer doctor [--agent ID] [--json]
14
14
  retrieviewer agents [--json]
15
15
  retrieviewer preview [--original]
16
- retrieviewer desktop
16
+ retrieviewer desktop [--ink cream|gold|dark]
17
17
  retrieviewer desktop-status [--json]
18
18
  retrieviewer desktop-stop
19
19
  retrieviewer desktop-clean --yes
@@ -32,15 +32,17 @@ export function parseArguments(args){
32
32
  const [command='help',...rest]=args,options={};
33
33
  for(let i=0;i<rest.length;i++){
34
34
  const token=rest[i];
35
- if(token==='--agent'){
36
- if(options.agent||!rest[i+1]||rest[i+1].startsWith('-'))throw Error('--agent requires one ID');
37
- options.agent=rest[++i];
35
+ if(token==='--agent'||token==='--ink'){
36
+ const key=token.slice(2);
37
+ if(options[key]||!rest[i+1]||rest[i+1].startsWith('-'))throw Error(token+' requires one value');
38
+ options[key]=rest[++i];
38
39
  }else if(['--yes','--json','--dry-run','--original'].includes(token))options[token.slice(2)]=true;
39
40
  else throw Error('Unknown argument: '+token);
40
41
  }
41
42
  if(options.agent&&!AGENTS.some(a=>a.id===options.agent))throw Error('Unknown agent: '+options.agent);
43
+ if(options.ink&&!['cream','gold','dark'].includes(options.ink))throw Error('--ink must be cream, gold or dark');
42
44
  if(!['help','--help','-h','enable','disable','disconnect','status','doctor','agents','preview','desktop','desktop-status','desktop-stop','desktop-clean','desktop-remove'].includes(command))throw Error('Unknown command: '+command);
43
- const allowed={enable:['agent','yes','json','dry-run'],disconnect:['agent','yes','json','dry-run'],disable:['agent','json'],status:['agent','json'],doctor:['agent','json'],agents:['json'],preview:['original'],'desktop-status':['json'],'desktop-clean':['yes'],'desktop-remove':['yes']};
45
+ const allowed={desktop:['ink'],enable:['agent','yes','json','dry-run'],disconnect:['agent','yes','json','dry-run'],disable:['agent','json'],status:['agent','json'],doctor:['agent','json'],agents:['json'],preview:['original'],'desktop-status':['json'],'desktop-clean':['yes'],'desktop-remove':['yes']};
44
46
  for(const key of Object.keys(options))if(!(allowed[command]??[]).includes(key))throw Error(`--${key} is not supported by ${command}`);
45
47
  return {command,options};
46
48
  }
@@ -51,7 +53,7 @@ export async function runCli(args,{input=process.stdin,output=process.stdout,err
51
53
  if(['desktop-clean','desktop-remove'].includes(command)&&!options.yes)throw Error('Use '+command+' --yes to remove only the companion cache.');
52
54
  const api=desktop??await import('./integrations/windows-overlay/desktop.mjs');
53
55
  const action={'desktop':'startDesktop','desktop-status':'desktopStatus','desktop-stop':'stopDesktop','desktop-clean':'cleanDesktop','desktop-remove':'removeDesktop'}[command];
54
- emit(await api[action]());return 0;
56
+ emit(await api[action](options.ink?{ink:options.ink}:{}));return 0;
55
57
  }
56
58
  if(command==='preview'){await import('./examples/live.mjs');return 0;}
57
59
  if(['status','doctor','agents'].includes(command)){
@@ -36,7 +36,7 @@ export interface PetPlugin {
36
36
  /** Host-owned loop: advance seconds then render. False while hidden. Throws while running. */
37
37
  tick(dtSeconds: number): boolean;
38
38
  /** False when busy or cooling down. */
39
- interact(kind: 'ball' | 'treat'): boolean;
39
+ interact(kind: 'ball' | 'treat' | 'tug'): boolean;
40
40
  readonly running: boolean;
41
41
  readonly disposed: boolean;
42
42
  dispose(): void;
@@ -82,7 +82,7 @@ export async function createPetPlugin({host,petOptions,fps=30,scheduler}={}){
82
82
  if(typeof dtSeconds!=='number'||!Number.isFinite(dtSeconds)||dtSeconds<0)throw RangeError('dtSeconds must be finite non-negative seconds');
83
83
  try{return draw(dtSeconds);}catch(error){halt();throw error;}
84
84
  },
85
- // 'ball' | 'treat'. False when busy or cooling down; shows on the next frame.
85
+ // 'ball' | 'treat' | 'tug'. False when busy or cooling down; shows on the next frame.
86
86
  interact(kind){alive();return pet.interact(kind);},
87
87
  get running(){return running;},
88
88
  get disposed(){return disposed;},
@@ -35,16 +35,24 @@ export interface PetState {
35
35
  [key: string]: unknown;
36
36
  }
37
37
  export interface LifeReport {
38
+ /** Last selected autonomous episode; null before the first decision. */
39
+ episode: string | null;
38
40
  intent: string;
39
41
  label: string;
40
- interaction: 'ball' | 'treat' | null;
42
+ interaction: 'ball' | 'treat' | 'tug' | null;
41
43
  busy: boolean;
42
44
  energy: number;
45
+ /** Awake-time sleep pressure; ordinary sitting does not erase it. */
46
+ drowsiness: number;
43
47
  decisions: number;
44
- cooldowns: {ball: number; treat: number};
48
+ cooldowns: {ball: number; treat: number; tug: number};
45
49
  gesture: {clip: string; progress: number} | null;
46
50
  ball: {x: number; z: number; height: number; carried: boolean} | null;
47
51
  treat: {x: number; z: number; height: number} | null;
52
+ /** Tug-of-war state while a tug interaction is active. */
53
+ tug: {progress: number; grip: number} | null;
54
+ /** Silent rising sleep cue, normalized cycle progress; no audio process. */
55
+ snore: number | null;
48
56
  }
49
57
  export interface PetFrame {
50
58
  artProfile: 'original' | 'q5';
@@ -70,7 +78,7 @@ export interface Pet {
70
78
  step(dt: number): number;
71
79
  frame(options: FrameOptions): PetFrame;
72
80
  /** False when busy or cooling down. */
73
- interact(kind: 'ball' | 'treat'): boolean;
81
+ interact(kind: 'ball' | 'treat' | 'tug'): boolean;
74
82
  readonly disposed: boolean;
75
83
  dispose(): void;
76
84
  }
@@ -78,7 +86,7 @@ export function createPet(options?: PetOptions): Promise<Pet>;
78
86
  export function defaultLoadAsset(name: string): Promise<ArrayBuffer | Uint8Array>;
79
87
  export const STEP_SECONDS: number;
80
88
  export const MAX_CATCH_UP_SECONDS: number;
81
- export const INTERACTIONS: readonly ['ball', 'treat'];
89
+ export const INTERACTIONS: readonly ['ball', 'treat', 'tug'];
82
90
  export const ART_PROFILES: readonly ['original', 'q5'];
83
91
  export const DEFAULT_ART_PROFILE: 'q5';
84
92
  export function getPetAssetFiles(artProfile?: 'original' | 'q5'): readonly string[];
package/retrieviewer.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import {makeLabAnimal} from './locomotion-core.mjs';
2
- import {createPaperActor,createPaperRenderer} from './puppy-paper.mjs';
3
- import {createPaperProjection} from './puppy-paper-world.mjs';
1
+ import {createPaperActor,createPaperRenderer,createPaperComfortTimings} from './puppy-paper.mjs';
2
+ import {createPaperProjection,createPaperBody} from './puppy-paper-world.mjs';
4
3
  import {rgbaToBraille} from './braille.mjs';
5
4
  import {createSnappyTrickTimings} from './puppy-paper-trick-timing.mjs';
6
5
  import {createCompanionLife} from './puppy-companion-life.mjs';
@@ -11,24 +10,25 @@ import {readPackedFrames} from './puppy-frame-store.mjs';
11
10
  // or listener side effects: the host owns the loop and calls step() then frame().
12
11
  export const STEP_SECONDS=1/60;
13
12
  export const MAX_CATCH_UP_SECONDS=.1;
14
- export const INTERACTIONS=Object.freeze(['ball','treat']);
13
+ export const INTERACTIONS=Object.freeze(['ball','treat','tug']);
15
14
  // Approved 32-level art ships by default; preserved original art is optional.
16
15
  export const DEFAULT_ART_PROFILE='q5';
17
16
  export const ART_PROFILES=Object.freeze(['original','q5']);
18
17
  // Current companion runtime: v9 tricks + interactions-v3, same as puppy-paper.html.
19
18
  export const PET_ASSETS=Object.freeze({
19
+ // Kept as a legacy identifier only; createPet/getPetAssetFiles no longer load it.
20
20
  animal:'locomotion-puppy.animal',
21
21
  stride:'puppy-stride-tweens-q5.bin.gz',strideInfo:'puppy-stride-meta.json',stableInfo:'puppy-stable-meta.json',
22
22
  rear:'puppy-paper-rear-gallop',approach:'puppy-paper-approach',seat:'puppy-paper-seat',arrivalSeat:'puppy-paper-arrival-seat',
23
- behaviors:'puppy-paper-behaviors',actions:'puppy-paper-actions-v9',interactions:'puppy-companion-interactions-v3',
23
+ behaviors:'puppy-paper-behaviors',actions:'puppy-paper-actions-v9',interactions:'puppy-companion-interactions-v3',comfort:'puppy-companion-comfort',
24
24
  });
25
- const BUNDLES=['rear','approach','seat','arrivalSeat','behaviors','actions','interactions'];
25
+ const BUNDLES=['rear','approach','seat','arrivalSeat','behaviors','actions','interactions','comfort'];
26
26
  const frameFile=(base,profile)=>base+(profile==='q5'?'-q5.bin.gz':'-compact.bin.gz');
27
27
  /** Exact files a host/bundler must copy, including metadata. Never loads assets. */
28
28
  export function getPetAssetFiles(artProfile=DEFAULT_ART_PROFILE){
29
29
  if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
30
30
  return Object.freeze([PET_ASSETS.strideInfo,PET_ASSETS.stableInfo,frameFile('puppy-stride-tweens',artProfile),
31
- ...BUNDLES.flatMap(key=>[PET_ASSETS[key]+'.json',frameFile(PET_ASSETS[key],artProfile)]),PET_ASSETS.animal]);
31
+ ...BUNDLES.flatMap(key=>[PET_ASSETS[key]+'.json',frameFile(PET_ASSETS[key],artProfile)])]);
32
32
  }
33
33
 
34
34
  // Relative to this module, never the process cwd: file: URLs in Node, HTTP in browsers.
@@ -62,13 +62,19 @@ const sharedAssets=new Map();
62
62
  async function loadPetAssets(loadAsset,artProfile){
63
63
  const load=async name=>bytesOf(name,await loadAsset(name));
64
64
  const json=async name=>JSON.parse(new TextDecoder().decode(await load(name)));
65
- const frames=async name=>readPackedFrames(await gunzip(await load(name)));
65
+ const frames=async (name,options)=>readPackedFrames(await gunzip(await load(name)),options);
66
66
  // One family at a time: never inflate all eight legacy-sized buffers together.
67
67
  const data={assets:{}};
68
68
  data.info=await json(PET_ASSETS.strideInfo);data.meta=await json(PET_ASSETS.stableInfo);
69
69
  data.cache=await frames(frameFile('puppy-stride-tweens',artProfile));
70
- for(const key of BUNDLES){const base=PET_ASSETS[key];data.assets[key]={info:await json(base+'.json'),bytes:await frames(frameFile(base,artProfile))};}
71
- data.baked=await load(PET_ASSETS.animal);
70
+ for(const key of BUNDLES){
71
+ const base=PET_ASSETS[key],info=await json(base+'.json');
72
+ // Old interaction sheets include preparation frames no runtime clip can reach.
73
+ // Keep exact active samples only, leaving room for small new gestures without
74
+ // keeping another multi-megabyte art family resident. Original RVF1 unchanged.
75
+ const options=key==='interactions'&&artProfile==='q5'?{frameIndices:[...new Set(Object.values(info.clips).flatMap(list=>list.flatMap(p=>Array.from({length:info.samples},(_,s)=>p*info.samples+s))))]}:undefined;
76
+ data.assets[key]={info,bytes:await frames(frameFile(base,artProfile),options)};
77
+ }
72
78
  return data;
73
79
  }
74
80
  async function acquireAssets(loadAsset,artProfile){
@@ -90,12 +96,12 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
90
96
  if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
91
97
  let lease=await acquireAssets(loadAsset,artProfile),data=lease.data;
92
98
  const release=lease.release;lease=null;
93
- let {cache,info,meta,baked}=data;const assets={...data.assets};data=null;
99
+ let {cache,info,meta}=data;const assets={...data.assets};data=null;
94
100
  let animal,actor,life;
95
101
  try{
96
102
  if(!assets.actions.info.clips?.['lie-down']||!assets.actions.info.clips?.['lie-up'])throw Error('v9 action cache lacks lie-down/lie-up clips');
97
- animal=await makeLabAnimal({baked:baked.buffer.slice(baked.byteOffset,baked.byteOffset+baked.byteLength)});baked=null;
98
- actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),walkTransitions:true});
103
+ animal=createPaperBody();
104
+ actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),comfortTimings:createPaperComfortTimings(assets.actions.info),walkTransitions:true});
99
105
  life=createCompanionLife(actor,{seed,...energy===undefined?{}:{energy}});
100
106
  }catch(e){animal?.dispose?.();release();throw e;}
101
107
  let paint=null,paintKey='',time=0,acc=0,disposed=false;
@@ -127,7 +133,7 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
127
133
  const {text}=rgbaToBraille(pixels,width,height,{method:'stable',brightness:1.18,contrast:1.1});
128
134
  return {text,columns,rows,pixels,state,report,placement,projection,artProfile,mouth:paint.lastMouth,contacts:paint.report()};
129
135
  },
130
- // 'ball' | 'treat'. False when busy or cooling down; unknown kinds throw.
136
+ // 'ball' | 'treat' | 'tug'. False when busy or cooling down; unknown kinds throw.
131
137
  interact(kind){alive();if(!INTERACTIONS.includes(kind))throw TypeError(`Unknown interaction: ${kind}`);return life.interact(kind);},
132
138
  get disposed(){return disposed;},
133
139
  dispose(){
@@ -1,21 +0,0 @@
1
- import * as THREE from 'three';
2
- import {createAnimal} from './vendor/procedural-animals/src/index.js';
3
- export const LAB_OPTIONS={variant:'retriever',age:'juvenile',seed:2,quality:'low',worker:false,overrides:{colour:'yellow',stanceSoftMargin:.005}};
4
- export async function makeLabAnimal(extra={}){
5
- return createAnimal('dog',{...LAB_OPTIONS,...extra,overrides:{...LAB_OPTIONS.overrides,...extra.overrides}});
6
- }
7
- // Never translate the mesh externally. The gait engine owns root motion + IK.
8
- export function makeLabController(animal){
9
- let target=null,speed=animal.gears.walk;
10
- return {get target(){return target;},get speed(){return speed;},setSpeed(kind){
11
- if(!['walk','trot','gallop'].includes(kind))throw Error('Unknown gait');
12
- speed=animal.gears[kind];if(target)animal.moveTo(target,{speed});
13
- },go(x,z){
14
- if(![x,z].every(Number.isFinite))throw Error('Invalid ground target');
15
- target=new THREE.Vector3(Math.max(-.8,Math.min(.8,x)),0,Math.max(-1.7,Math.min(.3,z)));
16
- animal.stopAction();animal.moveTo(target,{speed});
17
- },stop(){target=null;animal.stop();},update(dt){
18
- if(!Number.isFinite(dt)||dt<0||dt>.1)throw Error('Invalid lab step');animal.update(dt);
19
- if(target&&animal.position.distanceTo(target)<.025&&animal.speed<.03){target=null;animal.stop();}
20
- }};
21
- }
Binary file
@@ -1,40 +0,0 @@
1
- {
2
- "name": "procedural-animals",
3
- "version": "0.1.0",
4
- "description": "24 fully procedural animals for three.js: SDF-sculpted bodies, dual-quaternion skinning, GPU fur, feathers and scales, IK locomotion and actions. No models, no textures, no downloads.",
5
- "license": "MIT",
6
- "author": "Majid Manzarpour",
7
- "homepage": "https://majidmanzarpour.github.io/threejs-procedural-animals/",
8
- "repository": { "type": "git", "url": "git+https://github.com/majidmanzarpour/threejs-procedural-animals.git" },
9
- "bugs": { "url": "https://github.com/majidmanzarpour/threejs-procedural-animals/issues" },
10
- "type": "module",
11
- "main": "./src/index.js",
12
- "module": "./src/index.js",
13
- "types": "./src/index.d.ts",
14
- "exports": {
15
- ".": { "types": "./src/index.d.ts", "default": "./src/index.js" },
16
- "./species/*": "./src/species/*/index.js",
17
- "./worker": "./src/worker.js",
18
- "./bake": "./src/bake.js",
19
- "./package.json": "./package.json"
20
- },
21
- "bin": { "procedural-animals-bake": "./tools/bake.mjs" },
22
- "sideEffects": ["./src/worker.js"],
23
- "files": [
24
- "src/**/*.js",
25
- "src/index.d.ts",
26
- "tools/bake.mjs",
27
- "README.md",
28
- "LICENSE",
29
- "llms.txt"
30
- ],
31
- "engines": { "node": ">=18" },
32
- "peerDependencies": { "three": ">=0.160.0" },
33
- "devDependencies": { "esbuild": "^0.28.2", "playwright-core": "1.56", "three": "^0.186.0" },
34
- "scripts": {
35
- "build:showcase": "node showcase/build.mjs",
36
- "build:pages": "node showcase/build.mjs --pages",
37
- "test": "node tools/test.mjs"
38
- },
39
- "keywords": ["three", "threejs", "procedural", "animals", "animation", "ik", "fur", "sdf", "creatures", "gamedev", "webgl"]
40
- }
@@ -1,146 +0,0 @@
1
- // The public Animal: build data + render object + motion engine behind a small game-facing API.
2
- import * as THREE from 'three';
3
- import { createAnimalObject } from './core/render/animalObject.js';
4
- import { createMotion } from './core/motion/index.js';
5
-
6
- const _v = new THREE.Vector3();
7
-
8
- export class Animal {
9
- constructor(species, data, opts = {}) {
10
- this.species = species;
11
- this.id = species.id;
12
- this.data = data;
13
- this.seed = data.seed;
14
- this.params = data.params;
15
- this.quality = opts.quality || data.quality;
16
- const ground = opts.ground || (() => 0);
17
- this.ground = ground;
18
- this.water = opts.water || null;
19
- this.render = createAnimalObject(data, species, { quality: this.quality, castShadow: opts.castShadow, receiveShadow: opts.receiveShadow });
20
- /** Add this to your scene. Keep its own transform at identity: the animal moves by itself. */
21
- this.object = this.render.group;
22
- this.object.userData.animal = this;
23
- this._listeners = new Map();
24
- this.motion = createMotion(species.plan, {
25
- species, data, skeleton: this.render.skeleton, ground, water: this.water, perches: opts.perches,
26
- position: opts.position, heading: opts.heading ?? 0,
27
- emit: (type, payload) => this._emit(type, payload),
28
- });
29
- this.attachments = {};
30
- this._attachDefs = [];
31
- for (const [name, def] of Object.entries(this.motion.attachmentPoints || {})) {
32
- const o = new THREE.Object3D();
33
- o.name = name;
34
- o.matrixAutoUpdate = false;
35
- this.object.add(o);
36
- this.attachments[name] = o;
37
- this._attachDefs.push({ o, ...def });
38
- }
39
- this.stats = { vertices: data.nV, triangles: data.index.length / 3, bones: data.bones.length, buildMs: data.stats?.buildMs || 0, updateMs: 0 };
40
- this.time = 0;
41
- this._lastDt = 1 / 60;
42
- this.update(0);
43
- }
44
-
45
- // ---------------------------------------------------------------- control
46
- /** Locomotion: the animal moves itself. speed in m/s, heading in radians (0 = +Z). */
47
- move({ speed = 0, heading, climb = 0, gait } = {}) {
48
- const inp = this.motion.input;
49
- inp.follow = null;
50
- inp.target = null;
51
- inp.speed = Math.max(0, speed);
52
- if (heading !== undefined) inp.heading = heading;
53
- inp.climb = climb;
54
- inp.gait = gait || null;
55
- return this;
56
- }
57
- stop() { return this.move({ speed: 0 }); }
58
- /** Walk / run to a point and stop there. */
59
- moveTo(point, { speed = 3 } = {}) {
60
- const inp = this.motion.input;
61
- inp.follow = null;
62
- inp.target = point ? new THREE.Vector3().copy(point) : null;
63
- inp.targetSpeed = speed;
64
- return this;
65
- }
66
- /** Match your own physics body: the animal is placed at `position` and animates for `velocity`. */
67
- follow(position, velocity, { heading } = {}) {
68
- const inp = this.motion.input;
69
- inp.follow = inp.follow || { position: new THREE.Vector3(), velocity: new THREE.Vector3(), heading: null };
70
- inp.follow.position.copy(position);
71
- inp.follow.velocity.copy(velocity || _v.set(0, 0, 0));
72
- inp.follow.heading = heading ?? null;
73
- return this;
74
- }
75
- /** Look at a world point (Vector3) or stop looking (null). The animal keeps tracking the point you pass. */
76
- lookAt(target) {
77
- this.motion.input.look = target ? (target.isVector3 ? target : new THREE.Vector3().copy(target)) : null;
78
- return this;
79
- }
80
- /**
81
- * Play an action ('jump', 'sit', 'lie', 'sleep', 'eat', 'drink', 'attack', 'hit', 'death', 'stand', ...).
82
- * A new one-shot interrupts the running one; postures cross-fade.
83
- * Resolves with 'done' | 'interrupted' | 'stopped' | 'refused'.
84
- */
85
- play(name, opts = {}) { return this.motion.play(name, opts); }
86
- /** End an action (a posture: stand up), or every action but death when no name is given. */
87
- stopAction(name) { this.motion.stop(name); return this; }
88
- get actions() { return this.motion.actions; }
89
- get gaits() { return this.motion.gaits; }
90
- /** Named speeds for UIs ({ walk, trot, ... } in m/s, scaled to this individual's size). */
91
- get gears() { return this.motion.gears || {}; }
92
-
93
- // ---------------------------------------------------------------- events
94
- on(type, fn) {
95
- if (!this._listeners.has(type)) this._listeners.set(type, new Set());
96
- this._listeners.get(type).add(fn);
97
- return () => this.off(type, fn);
98
- }
99
- off(type, fn) { this._listeners.get(type)?.delete(fn); return this; }
100
- _emit(type, payload) {
101
- const set = this._listeners.get(type);
102
- if (set) for (const fn of set) fn({ type, animal: this, ...payload });
103
- }
104
-
105
- // ---------------------------------------------------------------- state
106
- get position() { return this.motion.state.position; }
107
- get heading() { return this.motion.state.heading; }
108
- get speed() { return this.motion.state.speed; }
109
- get velocity() { return this.motion.state.velocity; }
110
- get state() { return this.motion.state; }
111
-
112
- // ---------------------------------------------------------------- frame
113
- /** Call once per frame with the frame time in seconds. */
114
- update(dt) {
115
- const t0 = now();
116
- if (!(dt >= 0) || !Number.isFinite(dt)) dt = 0;
117
- dt = Math.min(dt, 0.1);
118
- this.time += dt;
119
- this.motion.update(dt);
120
- this.render.update({ dt, time: this.time, motion: this.motion });
121
- for (const a of this._attachDefs) {
122
- const b = this.render.skeleton.byName[a.bone];
123
- if (!b) continue;
124
- a.o.matrix.multiplyMatrices(b.matrixWorld, a.local);
125
- a.o.matrixWorld.multiplyMatrices(this.object.matrixWorld, a.o.matrix);
126
- a.o.matrixWorldNeedsUpdate = false;
127
- }
128
- this.stats.updateMs = this.stats.updateMs * 0.9 + (now() - t0) * 0.1;
129
- return this;
130
- }
131
-
132
- setQuality(q) { this.render.setQuality?.(q); this.quality = q; return this; }
133
- /** Motion level of detail: 0 full, 1 reduced secondary motion, 2 crowd (half-rate IK). */
134
- setLod(level) { this.motion.setLod?.(level); this.lod = level; return this; }
135
- setDebug(mode, on) { this.render.setDebug?.(mode, on); return this; }
136
- /** Show or hide the coverings (fur shells and silhouette fins); the base still reads as fur. */
137
- setCoverings(on) { this.render.setCoverings?.(on); return this; }
138
-
139
- dispose() {
140
- this.render.dispose();
141
- this.motion.dispose?.();
142
- this._listeners.clear();
143
- }
144
- }
145
-
146
- const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());