@bitefoundry/retrieviewer 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +75 -0
  6. package/INSTALLING.md +8 -5
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +110 -0
  11. package/README.md +119 -58
  12. package/RELEASING.md +27 -14
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/desktop/platform.mjs +27 -0
  17. package/integrations/linux-overlay/README.md +83 -0
  18. package/integrations/linux-overlay/overlay.py +1094 -0
  19. package/integrations/macos-overlay/README.md +83 -0
  20. package/integrations/macos-overlay/overlay.jxa +639 -0
  21. package/integrations/opencode/runtime.mjs +4 -4
  22. package/integrations/opencode/tui.tsx +1 -1
  23. package/integrations/windows-overlay/Overlay.cs +4 -4
  24. package/integrations/windows-overlay/desktop.mjs +40 -8
  25. package/integrations/windows-overlay/protocol.mjs +11 -7
  26. package/integrations/windows-overlay/worker.mjs +13 -7
  27. package/package.json +41 -14
  28. package/puppy-comfort-sprites.mjs +25 -0
  29. package/puppy-companion-comfort-q5.bin.gz +0 -0
  30. package/puppy-companion-comfort.json +77 -0
  31. package/puppy-companion-life.mjs +49 -16
  32. package/puppy-companion-objects.mjs +12 -0
  33. package/puppy-frame-predictive.mjs +15 -9
  34. package/puppy-frame-store.mjs +2 -2
  35. package/puppy-paper-world.mjs +14 -0
  36. package/puppy-paper.mjs +65 -14
  37. package/retrieviewer-cli.mjs +10 -3
  38. package/retrieviewer-plugin.d.mts +1 -1
  39. package/retrieviewer-plugin.mjs +1 -1
  40. package/retrieviewer.d.mts +10 -4
  41. package/retrieviewer.mjs +20 -14
  42. package/locomotion-core.mjs +0 -21
  43. package/locomotion-puppy.animal +0 -0
  44. package/vendor/procedural-animals/package.json +0 -40
  45. package/vendor/procedural-animals/src/animal.js +0 -146
  46. package/vendor/procedural-animals/src/bake.js +0 -51
  47. package/vendor/procedural-animals/src/build.js +0 -81
  48. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  49. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  50. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  51. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  52. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  53. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  54. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  55. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  56. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  57. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  58. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  59. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  60. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  61. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  62. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  63. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  64. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  65. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  66. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  67. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  68. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  69. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  70. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  71. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  72. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  73. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  74. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  75. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  76. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  77. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  79. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  80. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  81. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  82. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  83. package/vendor/procedural-animals/src/index.d.ts +0 -98
  84. package/vendor/procedural-animals/src/index.js +0 -29
  85. package/vendor/procedural-animals/src/registry.js +0 -48
  86. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  87. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  88. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  89. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  90. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  91. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  92. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  94. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  95. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  96. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  97. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  98. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  99. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  100. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  101. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  102. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  103. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  104. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  105. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  106. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  107. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  108. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  109. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  110. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  111. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  112. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  113. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  114. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  115. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  116. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  117. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  118. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  119. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  120. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  121. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  122. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  123. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  124. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  125. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  126. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  127. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  128. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  129. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  130. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  131. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  132. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  133. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  134. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  135. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  136. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  137. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  138. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  139. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  140. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  141. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  142. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  143. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  144. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  145. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  146. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  147. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  148. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  149. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  150. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  151. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  152. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  153. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  154. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  155. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  156. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  157. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  158. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  159. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  160. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  161. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  162. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  163. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  164. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  165. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  166. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  167. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  168. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  169. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  170. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  171. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  172. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  173. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  174. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  175. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  176. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  177. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  178. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  179. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  180. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  181. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  182. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  183. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  184. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  185. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  186. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  187. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  188. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  189. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  190. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  191. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  192. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  193. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  194. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  195. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  196. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  197. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  198. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  199. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  200. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  201. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  202. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  203. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  204. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  205. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  206. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  207. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  208. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  209. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  210. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  211. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  212. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  213. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  214. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  215. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  216. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  217. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  218. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  219. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  220. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  221. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  222. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  223. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  224. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  225. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  226. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  227. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  228. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  229. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  230. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  231. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  232. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  233. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  234. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  235. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  236. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  237. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  238. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  239. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  240. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  241. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  242. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  243. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  244. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  245. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  246. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  247. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  248. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  249. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  250. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  251. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  252. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  253. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  254. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  255. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  256. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  257. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  258. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  259. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  260. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  261. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -80,7 +80,7 @@ function unfilterRow(type,src,at,cur,prev,w){
80
80
 
81
81
  // Validate and unfilter. Only the returned typed arrays survive; `bytes` is
82
82
  // not referenced by the store built from them.
83
- function decode(bytes){
83
+ function decode(bytes,{frameIndices}={}){
84
84
  if(!(bytes instanceof Uint8Array))fail('bytes');
85
85
  if(!isPredictiveFrames(bytes)||bytes.length<HEADER)fail('magic');
86
86
  const view=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength),u32=at=>view.getUint32(at,true);
@@ -89,8 +89,13 @@ function decode(bytes){
89
89
  if(unique<1||unique>frameCount)fail('unique count');
90
90
  const data=HEADER+4*frameCount+RECORD*unique;
91
91
  if(bytes.length<data)fail('truncated');
92
- const map=new Uint32Array(frameCount),used=new Uint8Array(unique);
93
- for(let f=0;f<frameCount;f++){const u=u32(HEADER+4*f);if(u>=unique)fail('map');map[f]=u;used[u]=1;}
92
+ const retained=frameIndices===undefined?null:new Uint8Array(frameCount);
93
+ if(retained){
94
+ if(!Array.isArray(frameIndices)||!frameIndices.length)fail('retained frames');
95
+ for(const f of frameIndices){if(!Number.isInteger(f)||f<0||f>=frameCount)fail('retained frame index');retained[f]=1;}
96
+ }
97
+ const map=new Uint32Array(frameCount),used=new Uint8Array(unique),needed=retained?new Uint8Array(unique):null;
98
+ for(let f=0;f<frameCount;f++){const u=u32(HEADER+4*f);if(u>=unique)fail('map');map[f]=u;used[u]=1;if(retained?.[f])needed[u]=1;}
94
99
  if(used.includes(0))fail('unreferenced frame');
95
100
  // x,y,w,h,first pixel index in the packed stream
96
101
  const rects=new Float64Array(unique*5);let total=0,pixels=0;
@@ -98,7 +103,7 @@ function decode(bytes){
98
103
  const at=HEADER+4*frameCount+RECORD*u,x=u32(at),y=u32(at+4),w=u32(at+8),h=u32(at+12),offset=u32(at+16),length=u32(at+20);
99
104
  if((w===0)!==(h===0)||(w===0&&(x||y))||x+w>width||y+h>height)fail('rectangle');
100
105
  if(length!==h*(w+1)||offset!==total)fail('offset');
101
- rects.set([x,y,w,h,pixels],u*5);total+=length;pixels+=w*h;
106
+ rects.set([x,y,w,h,pixels],u*5);total+=length;if(!needed||needed[u])pixels+=w*h;
102
107
  }
103
108
  if(bytes.length!==data+total)fail('pixel length');
104
109
  if(pixels*BITS>0xffffffff)fail('pixel count');
@@ -109,23 +114,24 @@ function decode(bytes){
109
114
  for(let y=0;y<h;y++){
110
115
  unfilterRow(bytes[at],bytes,at+1,cur,prev,w);at+=w+1;
111
116
  for(let x=0;x<w;x++){
112
- const q=cur[x];if(q>31)fail('quantized value');
117
+ const q=cur[x];if(q>31)fail('quantized value');if(needed&&!needed[u])continue;
113
118
  const i=pos>>>3,s=pos&7;packed[i]|=(q<<s)&255;if(s>3)packed[i+1]|=q>>(8-s);pos+=BITS;
114
119
  }
115
120
  const t=prev;prev=cur;cur=t;
116
121
  }
117
122
  }
118
- return{width,height,frameCount,unique,map,rects,packed};
123
+ return{width,height,frameCount,unique,map,rects,packed,retained};
119
124
  }
120
125
 
121
- function createStore({width,height,frameCount,unique,map,rects,packed}){
126
+ function createStore({width,height,frameCount,unique,map,rects,packed,retained}){
122
127
  const n=width*height,slots=[{unique:-1,frame:null},{unique:-1,frame:null}];let decodes=0,hits=0;
123
128
  return Object.freeze({
124
129
  width,height,frameCount,uniqueCount:unique,byteLength:n*frameCount,residentPixelBytes:packed.byteLength,
125
- get residentBytes(){return packed.byteLength+map.byteLength+rects.byteLength+slots.reduce((s,e)=>s+(e.frame?.byteLength??0),0);},
130
+ get residentBytes(){return packed.byteLength+map.byteLength+rects.byteLength+(retained?.byteLength??0)+slots.reduce((s,e)=>s+(e.frame?.byteLength??0),0);},
126
131
  stats(){return{decodes,hits,cachedFrames:slots.filter(e=>e.frame).length};},
127
132
  getFrame(index){
128
133
  if(!Number.isInteger(index)||index<0||index>=frameCount)fail('frame index');
134
+ if(retained&&!retained[index])fail('frame was not retained');
129
135
  const u=map[index];
130
136
  if(slots[0].unique===u){hits++;return slots[0].frame;}
131
137
  if(slots[1].unique===u){hits++;slots.reverse();return slots[0].frame;}
@@ -144,4 +150,4 @@ function createStore({width,height,frameCount,unique,map,rects,packed}){
144
150
 
145
151
  // Parse, validate and unpack an RVQ5 store. A returned frame is valid until two
146
152
  // other unique frames have been requested.
147
- export function readPredictiveFrames(bytes){return createStore(decode(bytes));}
153
+ export function readPredictiveFrames(bytes,options){return createStore(decode(bytes,options));}
@@ -52,9 +52,9 @@ function checkShape(width,height,frameCount){
52
52
  // resident (crops only) and reconstructs at most two full frames at a time.
53
53
  // A returned frame is valid until two other unique frames have been requested.
54
54
  // An 'RVQ5' store (lossy 32-level comparison codec) is delegated and branded.
55
- export function readPackedFrames(bytes){
55
+ export function readPackedFrames(bytes,options){
56
56
  if(!(bytes instanceof Uint8Array))throw Error('Invalid packed frames: bytes');
57
- if(isPredictiveFrames(bytes)){const store=readPredictiveFrames(bytes);stores.add(store);return store;}
57
+ if(isPredictiveFrames(bytes)){const store=readPredictiveFrames(bytes,options);stores.add(store);return store;}
58
58
  if(bytes.length<HEADER||MAGIC.some((v,i)=>bytes[i]!==v))throw Error('Invalid packed frames: magic');
59
59
  const view=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength),u32=at=>view.getUint32(at,true);
60
60
  const width=u32(4),height=u32(8),frameCount=u32(12),unique=u32(16);
@@ -1,6 +1,20 @@
1
1
  // Design calibration, not a measurement of the user's current head position.
2
2
  // Sources: OpenCV pinhole model; PsychoPy viewtools physical-screen frustums.
3
3
  const diagonal=.0254*32;
4
+ // The paper actor already integrates its own calibrated travel profile and foot
5
+ // contacts. Its old 3D Animal.follow() merely copied that root's x/z, then also
6
+ // updated an invisible skinned mesh, IK, fur and materials. Keep just the follow
7
+ // contract here; speed/stride/projection remain owned by the existing planner.
8
+ export function createPaperBody(){
9
+ const position={x:0,y:0,z:0};let target=null,disposed=false;
10
+ const alive=()=>{if(disposed)throw Error('Paper body is disposed');};
11
+ return{
12
+ position,
13
+ follow(point){alive();if(!point||![point.x,point.y,point.z].every(Number.isFinite))throw Error('Invalid paper body position');target={x:point.x,y:point.y,z:point.z};},
14
+ update(dt){alive();if(!Number.isFinite(dt)||dt<0||dt>.1)throw Error('Invalid paper body step');if(target){position.x=target.x;position.z=target.z;}},
15
+ dispose(){disposed=true;target=null;},
16
+ };
17
+ }
4
18
  export const PAPER_SCREEN=Object.freeze({widthMeters:diagonal*16/Math.hypot(16,9),heightMeters:diagonal*9/Math.hypot(16,9),viewDistance:.70});
5
19
  const eyeHeight=.45,eyeZ=1.65,pitch=9*Math.PI/180;
6
20
  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});
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;
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;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){comfortDuration('sleep',seconds);return dispatch(()=>{comfortSeconds=seconds;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,8 @@ 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
+ let behavior=null,behaviorProgress=0,comfortPose=state==='tug'?{clip:'tug',progress:tugProgress()}:null;
213
+ if(activeAction?.comfort)kind=activeAction.comfort;
165
214
  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
215
  if(manualDown&&state==='down'){actionName='lie-down';actionProgress=1;}
167
216
  if(walkTransitions&&state==='view-turn'&&turnView==='front'){
@@ -192,13 +241,13 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
192
241
  // Deliberate held source poses: no live frame blending or face/body deformation.
193
242
  blend=Math.round(blend*16)/16;
194
243
  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};
244
+ 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};
245
+ 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
246
  },get state(){return state;}
198
247
  };
199
248
  }
200
249
 
201
- export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,contacts=true,pawTransport=false}={}){
250
+ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,comfort,contacts=true,pawTransport=false}={}){
202
251
  if(![width,height].every(Number.isInteger)||width<2||height<2||width*height>1_000_000)throw Error('Invalid paper grid');
203
252
  const tween=createCachedTween(cache,info),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
204
253
  const out=new Uint8ClampedArray(width*height*4),area=createPaperAreaSampler();
@@ -208,6 +257,7 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
208
257
  const arrivalSeatSample=arrivalSeat?createPaperSeatSampler(arrivalSeat.bytes,arrivalSeat.info):null;
209
258
  const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info):null;
210
259
  const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info):null;
260
+ const comfortSample=comfort?createComfortSpriteSampler(comfort.bytes,comfort.info):null;
211
261
  const actionSample=actions?createPaperActionSampler(actions.bytes,actions.info):null,actionArea=actions?createPaperAreaSampler(actions.info.width):null;
212
262
  const walkEpisodes=actions?createPaperSupportEpisodes(actions.info.feet,actions.info.clips.walk,9):null;
213
263
  const sourceEpisodes=rear?{
@@ -222,10 +272,11 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
222
272
  const isApproach=state.approachPair!==null&&state.approachPair!==undefined&&approachSample;
223
273
  const isSeat=state.seatProgress!==null&&state.seatProgress!==undefined&&seatSample;
224
274
  if(!Number.isFinite(pixelAspect)||pixelAspect<.1||pixelAspect>10)throw Error('Invalid dot pixel aspect');
225
- const isInteraction=state.interactionPose&&interactionSample;
275
+ const isComfort=state.comfortPose&&comfortSample;
276
+ const isInteraction=isComfort||state.interactionPose&&interactionSample;
226
277
  const isBehavior=!isInteraction&&state.behavior&&behaviorSample;
227
278
  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;
279
+ const image=isComfort?comfortSample(state.comfortPose):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
280
  // Quantize source lookup only, never the projected rectangle. Rounding side,
230
281
  // left and top first introduced whole-body scale/position jumps at thresholds.
231
282
  const left=width*x-sideX/2,floor=isInteraction||isAction||isBehavior||isSeat||isRear||isApproach?0:cleanGroundOffset(state,renderMeta),top=height*y-(300-floor)/320*side;
@@ -23,8 +23,10 @@ Enable opens a first-run selection/confirmation wizard on a real terminal.
23
23
  Non-interactive changes require --agent and --yes. Unsupported native renderers
24
24
  are refused: MCP connectivity alone is never treated as in-screen rendering.
25
25
  No command changes credentials, shell profiles or agent executables.
26
- Desktop is a separate Windows overlay, NOT a Codex plugin. Choose a window once;
26
+ Desktop is a separate native overlay, NOT a Codex plugin. Choose a window once;
27
27
  use Codex normally. Exit from its tray icon or desktop-stop. No auto-start is added.
28
+ Windows is verified; macOS and Linux X11 adapters are experimental. Native Wayland
29
+ app tracking is not supported. Linux targets AlmaLinux8/GLIBC2.28, system Python3.6+.
28
30
  `;
29
31
  export function parseArguments(args){
30
32
  const [command='help',...rest]=args,options={};
@@ -88,7 +90,12 @@ export async function runCli(args,{input=process.stdin,output=process.stdout,err
88
90
  }
89
91
 
90
92
  // CLI is deliberately a separate module from the side-effect-free library.
91
- import {pathToFileURL} from 'node:url';
92
- if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href){
93
+ import {fileURLToPath} from 'node:url';
94
+ import {realpathSync} from 'node:fs';
95
+ // npm global bins are symlinks on Unix. import.meta resolves the real module,
96
+ // whereas argv[1] keeps the bin link; comparing those raw URLs silently did nothing.
97
+ let main=false;
98
+ try{main=!!process.argv[1]&&realpathSync(process.argv[1])===realpathSync(fileURLToPath(import.meta.url));}catch{}
99
+ if(main){
93
100
  try{process.exitCode=await runCli(process.argv.slice(2));}catch(e){process.stderr.write('Retrieviewer: '+e.message+'\n');process.exitCode=1;}
94
101
  }
@@ -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,22 @@ 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;
43
45
  decisions: number;
44
- cooldowns: {ball: number; treat: number};
46
+ cooldowns: {ball: number; treat: number; tug: number};
45
47
  gesture: {clip: string; progress: number} | null;
46
48
  ball: {x: number; z: number; height: number; carried: boolean} | null;
47
49
  treat: {x: number; z: number; height: number} | null;
50
+ /** Tug-of-war state while a tug interaction is active. */
51
+ tug: {progress: number; grip: number} | null;
52
+ /** Silent rising sleep cue, normalized cycle progress; no audio process. */
53
+ snore: number | null;
48
54
  }
49
55
  export interface PetFrame {
50
56
  artProfile: 'original' | 'q5';
@@ -70,7 +76,7 @@ export interface Pet {
70
76
  step(dt: number): number;
71
77
  frame(options: FrameOptions): PetFrame;
72
78
  /** False when busy or cooling down. */
73
- interact(kind: 'ball' | 'treat'): boolean;
79
+ interact(kind: 'ball' | 'treat' | 'tug'): boolean;
74
80
  readonly disposed: boolean;
75
81
  dispose(): void;
76
82
  }
@@ -78,7 +84,7 @@ export function createPet(options?: PetOptions): Promise<Pet>;
78
84
  export function defaultLoadAsset(name: string): Promise<ArrayBuffer | Uint8Array>;
79
85
  export const STEP_SECONDS: number;
80
86
  export const MAX_CATCH_UP_SECONDS: number;
81
- export const INTERACTIONS: readonly ['ball', 'treat'];
87
+ export const INTERACTIONS: readonly ['ball', 'treat', 'tug'];
82
88
  export const ART_PROFILES: readonly ['original', 'q5'];
83
89
  export const DEFAULT_ART_PROFILE: 'q5';
84
90
  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
- }