@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
@@ -61,6 +61,6 @@ try{
61
61
  const {createPet}=await import('../../retrieviewer.mjs');const pet=await createPet();
62
62
  if(stopping)pet.dispose();else{
63
63
  pump=createFramePump({pet,send:write});if(pendingViewport)pump.viewport(pendingViewport);
64
- write({type:'loaded'});timer=setInterval(()=>{try{pump.tick();}catch(error){write({type:'error',message:error.message});stop();}},1000/spec.fps);
64
+ write({type:'loaded',ink:process.env.RETRIEVIEWER_INK??'cream'});timer=setInterval(()=>{try{pump.tick();}catch(error){write({type:'error',message:error.message});stop();}},1000/spec.fps);
65
65
  }
66
66
  }catch(error){sendParent({error:error.message});await stop();}
package/package.json CHANGED
@@ -1,10 +1,28 @@
1
1
  {
2
2
  "name": "@bitefoundry/retrieviewer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "A fluffy Braille desktop pet for Windows and Linux X11, with experimental macOS support and an embeddable TUI engine.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
- "homepage": "https://github.com/BiteFoundry",
7
- "keywords": ["tui", "braille", "pet", "retriever", "opencode", "desktop-pet", "windows", "linux", "macos", "overlay"],
6
+ "homepage": "https://bitefoundry.github.io/retrieviewer/",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/BiteFoundry/retrieviewer.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/BiteFoundry/retrieviewer/issues"
13
+ },
14
+ "keywords": [
15
+ "tui",
16
+ "braille",
17
+ "pet",
18
+ "retriever",
19
+ "opencode",
20
+ "desktop-pet",
21
+ "windows",
22
+ "linux",
23
+ "macos",
24
+ "overlay"
25
+ ],
8
26
  "publishConfig": {
9
27
  "access": "public",
10
28
  "registry": "https://registry.npmjs.org/",
@@ -64,16 +82,17 @@
64
82
  "DESKTOP-PLATFORMS.md",
65
83
  "WINDOWS-COMPANION.md",
66
84
  "INSTALLING.md",
85
+ "PREREQUISITES.md",
86
+ "README.ko.md",
67
87
  "PLUGIN.md",
68
88
  "EMBEDDING.md",
69
89
  "PUPPY-MEMORY.md",
70
90
  "PUPPY-Q5-COMPARISON.md",
91
+ "COMFORT-ART.md",
71
92
  "examples/embed.mjs",
72
93
  "examples/live.mjs",
73
94
  "examples/plugin.mjs",
74
95
  "examples/bridge.py",
75
- "locomotion-core.mjs",
76
- "locomotion-puppy.animal",
77
96
  "braille.mjs",
78
97
  "puppy-frame-store.mjs",
79
98
  "puppy-frame-predictive.mjs",
@@ -91,6 +110,7 @@
91
110
  "puppy-companion-life.mjs",
92
111
  "puppy-companion-objects.mjs",
93
112
  "puppy-companion-sprites.mjs",
113
+ "puppy-comfort-sprites.mjs",
94
114
  "puppy-mouth-anchor.mjs",
95
115
  "puppy-mouth-anchor.json",
96
116
  "puppy-clean.mjs",
@@ -116,24 +136,25 @@
116
136
  "puppy-paper-actions-v9.json",
117
137
  "puppy-companion-interactions-v3-q5.bin.gz",
118
138
  "puppy-companion-interactions-v3.json",
119
- "vendor/procedural-animals/src",
120
- "vendor/procedural-animals/LICENSE",
121
- "vendor/procedural-animals/package.json"
139
+ "puppy-companion-comfort-q5.bin.gz",
140
+ "puppy-companion-comfort.json",
141
+ "vendor/procedural-animals/LICENSE"
122
142
  ],
123
143
  "scripts": {
124
- "start": "node server.mjs",
125
- "terminal": "node terminal.mjs",
126
- "pet": "node puppy-terminal.mjs",
144
+ "start": "node tools/preview-site.mjs",
145
+ "build:site": "node tools/build-site.mjs",
127
146
  "pet:companion": "node examples/live.mjs",
128
147
  "pet:desktop": "node retrieviewer-cli.mjs desktop",
129
148
  "test": "node --test \"*.test.mjs\"",
130
- "test:locomotion": "node check-locomotion.mjs",
131
149
  "test:package": "node check-pet-package.mjs",
132
150
  "release:check": "node tools/check-release.mjs",
133
151
  "prepublishOnly": "npm run release:check"
134
152
  },
135
153
  "dependencies": {
136
- "jsonc-parser": "3.3.1",
154
+ "jsonc-parser": "3.3.1"
155
+ },
156
+ "devDependencies": {
157
+ "playwright": "1.64.1",
137
158
  "three": "0.186.1"
138
159
  }
139
160
  }
@@ -0,0 +1,33 @@
1
+ import {grayFrameReader,isGrayFrameSource} from './puppy-frame-store.mjs';
2
+
3
+ // A handful of authored keys, not an expanded 30fps film or a live image morph.
4
+ // First/last keys are the exact shared seated source; all keys use one scale.
5
+ export function comfortFrameIndex(progress,info){
6
+ if(!Number.isFinite(progress)||progress<0||progress>1)throw Error('Invalid comfort progress');
7
+ const seconds=progress*6;
8
+ if(seconds<.12||progress===1)return 0;
9
+ if(seconds<.65)return seconds<.3?1:2;
10
+ if(seconds>5.15)return seconds<5.45?7:seconds<5.8?8:0;
11
+ const loop=[3,4,3,5,3,6,3,5,3,4,3,6];
12
+ return loop[Math.floor((seconds-.65)/.28)%loop.length];
13
+ }
14
+
15
+ export function createComfortSpriteSampler(bytes,info,output){
16
+ if(!isGrayFrameSource(bytes)||![1,2].includes(info?.version)||info.width!==320||info.height!==320||info.frames!==(info.version===2?13:9))throw Error('Invalid comfort sprites');
17
+ if(output!==undefined&&(!(output instanceof Uint8ClampedArray)||output.length!==320*320*4))throw Error('Invalid comfort output');
18
+ const frames=grayFrameReader(bytes,320,320,info.frames);let rgba=output??null;
19
+ if(rgba)for(let i=3;i<rgba.length;i+=4)rgba[i]=255;
20
+ return pose=>{
21
+ if(!['tug','sleep'].includes(pose?.clip))throw Error('Invalid comfort clip');
22
+ if(!rgba){rgba=new Uint8ClampedArray(320*320*4);for(let i=3;i<rgba.length;i+=4)rgba[i]=255;}
23
+ const index=pose.clip==='sleep'?info.sleep?.[pose.variant]:comfortFrameIndex(pose.progress,info);
24
+ if(!Number.isInteger(index))throw Error('Invalid sleep variant');
25
+ const frame=frames.getFrame(index),base=pose.base,blend=pose.blend??1;
26
+ if(!Number.isFinite(blend)||blend<0||blend>1)throw Error('Invalid sleep blend');
27
+ for(let i=0,j=0;i<frame.length;i++,j+=4){
28
+ const v=frame[pose.right?Math.floor(i/320)*320+319-i%320:i];
29
+ rgba[j]=rgba[j+1]=rgba[j+2]=base&&blend<1?Math.round(base[j]+(v-base[j])*blend):v;
30
+ }
31
+ return rgba;
32
+ };
33
+ }
@@ -0,0 +1,140 @@
1
+ {
2
+ "version": 2,
3
+ "width": 320,
4
+ "height": 320,
5
+ "frames": 13,
6
+ "clips": [
7
+ "tug",
8
+ "sleep"
9
+ ],
10
+ "gzipBytes": 129148,
11
+ "residentPixelBytes": 341139,
12
+ "sourceSheetSha256": "10fde9dacca5cb45acd68f387e7ed3bcf88cd72cf0f9c4220d31d819db91731e",
13
+ "neutralSha256": "19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a",
14
+ "encodedSha256": "35327cc103b1f7ac2ff1b09ed98658fd8ff1dcef322de9c9b18071a4358b7b87",
15
+ "method": "Built-in imagegen authored tug keys. One fixed scale, ground/center translation only. Exact existing Q5 seat first/last. No expanded tween film or live body deformation.",
16
+ "registration": {
17
+ "sheetSize": [
18
+ 1774,
19
+ 887
20
+ ],
21
+ "targetBounds": [
22
+ 41,
23
+ 92,
24
+ 231,
25
+ 301
26
+ ],
27
+ "registration": [
28
+ {
29
+ "cell": 0,
30
+ "scale": 0.5663956639566395,
31
+ "dx": 32,
32
+ "dy": 69
33
+ },
34
+ {
35
+ "cell": 1,
36
+ "scale": 0.5663956639566395,
37
+ "dx": 39,
38
+ "dy": 69
39
+ },
40
+ {
41
+ "cell": 2,
42
+ "scale": 0.5663956639566395,
43
+ "dx": 39,
44
+ "dy": 69
45
+ },
46
+ {
47
+ "cell": 3,
48
+ "scale": 0.5663956639566395,
49
+ "dx": 35,
50
+ "dy": 68
51
+ },
52
+ {
53
+ "cell": 4,
54
+ "scale": 0.5663956639566395,
55
+ "dx": 33,
56
+ "dy": 74
57
+ },
58
+ {
59
+ "cell": 5,
60
+ "scale": 0.5663956639566395,
61
+ "dx": 39,
62
+ "dy": 74
63
+ },
64
+ {
65
+ "cell": 6,
66
+ "scale": 0.5663956639566395,
67
+ "dx": 39,
68
+ "dy": 74
69
+ },
70
+ {
71
+ "cell": 7,
72
+ "scale": 0.5663956639566395,
73
+ "dx": 36,
74
+ "dy": 74
75
+ }
76
+ ]
77
+ },
78
+ "sleep": {
79
+ "side": 9,
80
+ "belly": 10,
81
+ "curl": 11,
82
+ "chin": 12
83
+ },
84
+ "sleepRegistration": [
85
+ {
86
+ "key": "side",
87
+ "sourceBounds": [
88
+ 26,
89
+ 461,
90
+ 1234,
91
+ 873
92
+ ],
93
+ "width": 254,
94
+ "scale": 0.21026490066225165,
95
+ "ground": 300
96
+ },
97
+ {
98
+ "key": "belly",
99
+ "sourceBounds": [
100
+ 28,
101
+ 413,
102
+ 1239,
103
+ 881
104
+ ],
105
+ "width": 224,
106
+ "scale": 0.18497109826589594,
107
+ "ground": 300
108
+ },
109
+ {
110
+ "key": "curl",
111
+ "sourceBounds": [
112
+ 44,
113
+ 373,
114
+ 1213,
115
+ 992
116
+ ],
117
+ "width": 216,
118
+ "scale": 0.1847733105218135,
119
+ "ground": 300
120
+ },
121
+ {
122
+ "key": "chin",
123
+ "sourceBounds": [
124
+ 116,
125
+ 435,
126
+ 1218,
127
+ 953
128
+ ],
129
+ "width": 198,
130
+ "scale": 0.17967332123411978,
131
+ "ground": 300
132
+ }
133
+ ],
134
+ "sleepSourceHashes": {
135
+ "side": "85c567eb0958a213ee5b93d0a8592639489f5f29dded35e0d1d22e920ed1fe9f",
136
+ "belly": "e1967d9156a119d04e25fd6428fe5dcdbd2156c5e4eba0347217847874006109",
137
+ "curl": "10c9a85701af3587c48c1d8158972a07026e79a18a2375d86108538eda05aae7",
138
+ "chin": "e971eaa6861b597f0dba3a487403cee5f120cba0a9f9584d831b5634d096dee7"
139
+ }
140
+ }
@@ -11,16 +11,18 @@ const BALL_TARGET={x:0,z:-4};
11
11
  const OFFER={x:0,z:.3};
12
12
  // Hand-fed treat marker, relative to the puppy's current root (no travel for treats).
13
13
  const TREAT_OFFSET={z:.08,height:.25};
14
- const COOLDOWN={ball:6,treat:15};
14
+ const COOLDOWN={ball:6,treat:15,tug:20};
15
15
  const FLIGHT=.65,BALL_PEAK=.75,CARRY_HEIGHT=.19,SETTLE_FALLBACK=.6,MAX_DT=.25,ARRIVE=.05;
16
16
  const LABEL={
17
17
  start:'기다리는 중',pause:'잠시 느긋하게 둘러보는 중',lie:'엎드리는 중',rest:'엎드려 쉬는 중',recover:'앉아서 기운 차리는 중',
18
18
  sit:'앉는 중',watch:'앉아서 지켜보는 중',look:'고개를 돌려 살피는 중',observe:'가만히 주변 관찰 중',
19
19
  stroll:'옆으로 살짝 걸어가는 중',sniff:'잠깐 멈춰 주변을 살피는 중',dash:'저 멀리 달려가는 중',far:'멀리서 둘러보는 중',home:'제자리로 돌아오는 중',
20
+ patrol:'주변을 왕복 산책하는 중',scan:'양쪽을 번갈아 살피는 중',roll:'느긋하게 뒹구는 중',play:'신나서 한 바퀴 도는 중',checkin:'가까이 와서 함께 쉬는 중',
21
+ belly:'배를 보이고 발라당 쉬는 중',sleep:'새근새근 잠자는 중',tug:'장난감을 물고 당기는 중',
20
22
  fetch:'공을 쫓아가는 중',pickup:'공을 무는 중',carry:'공을 물고 돌아오는 중',drop:'공을 내려놓는 중',offer:'공을 내려놓고 기다리는 중',
21
23
  treatSit:'간식 앞에 앉는 중',chew:'간식 먹는 중'
22
24
  };
23
- const PENDING_LABEL={treat:'곧 간식'};
25
+ const PENDING_LABEL={treat:'곧 간식',tug:'곧 터그놀이'};
24
26
  // Baked interaction clips, played only while holding at the shared seated endpoint.
25
27
  const GESTURE={'ball-pickup':1.4,'ball-drop':1.2,'treat-eat':2.4};
26
28
  const clamp=(v,a,b)=>Math.min(b,Math.max(a,v));
@@ -37,9 +39,10 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
37
39
  if(!Number.isFinite(energy)||energy<0||energy>1)throw RangeError('energy must be within 0..1');
38
40
 
39
41
  const rand=mulberry32(seed),range=(a,b)=>a+(b-a)*rand(),side=()=>rand()<.5?'left':'right';
40
- const needs={energy,social:.5,curiosity:.5};
41
- const cooldowns={ball:0,treat:0};
42
- let clock=0,decisions=0,lastEpisode=null;
42
+ const needs={energy,social:.5,curiosity:.5,drowsiness:0};
43
+ const cooldowns={ball:0,treat:0,tug:0};
44
+ const episodeCooldowns={roll:0,play:0,belly:0,sleep:0};
45
+ let clock=0,decisions=0,lastEpisode=null,quiet=0;
43
46
  let queue=[],hold=0,holdIntent=null,waiting=null,gesture=null;
44
47
  let intent='start',label=LABEL.start,pending=null,active=null,ball=null,treat=null;
45
48
 
@@ -51,25 +54,47 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
51
54
  // Treats accept any seat variant at the front-seated endpoint: the hand comes to the puppy.
52
55
  const seatedAnyVariant=()=>{const s=actor.sample(clock);return actor.state==='idle'&&s.seatProgress===1&&s.view==='approach';};
53
56
  const fx=fn=>({fx:fn});
54
- const home=()=>cmd('go',[HOME.x,HOME.z],'home');
57
+ const home=()=>cmd('go',[HOME.x,HOME.z],'home',()=>{const s=actor.sample(clock);return Math.hypot(s.x-HOME.x,s.z-HOME.z)>ARRIVE;});
58
+ const sit=step=>cmd('sit',[],step,()=>!seatedAnyVariant());
59
+ const lie=()=>cmd('lie',[],'lie',()=>actor.state!=='down');
60
+ const walkTo=x=>cmd('walk',()=>{const dx=x-actor.sample(clock).x;return [dx<0?'left':'right',Math.abs(dx)];},'patrol',()=>Math.abs(x-actor.sample(clock).x)>.002);
55
61
 
56
62
  // Each episode is a short coherent plan; a pause always leads in so autonomy never stalls.
57
63
  const EPISODES={
58
- rest:()=>[cmd('lie',[],'lie'),wait(range(10,18),'rest'),cmd('sit',[],'recover'),wait(range(3,6),'recover')],
59
- watch:()=>[cmd('sit',[],'sit'),cmd('look',[side()],'look'),wait(range(3,9),'watch'),fx(()=>{needs.social-=.3;needs.curiosity+=.1;})],
60
- observe:()=>[cmd('look',[side()],'look'),wait(range(3,9),'observe'),fx(()=>{needs.curiosity-=.15;})],
64
+ rest:()=>[lie(),wait(range(10,18),'rest'),sit('recover'),wait(range(3,6),'recover')],
65
+ watch:()=>[sit('sit'),cmd('look',[side()],'look'),wait(range(3,9),'watch'),fx(()=>{needs.social-=.3;needs.curiosity+=.1;})],
66
+ observe:()=>[sit('sit'),cmd('look',[side()],'look'),wait(range(3,9),'observe'),fx(()=>{needs.curiosity-=.15;})],
61
67
  stroll:()=>[cmd('walk',[side(),range(.08,.18)],'stroll'),wait(range(2,4),'sniff'),home(),fx(()=>{needs.energy-=.08;needs.curiosity-=.2;})],
62
- dash:()=>[cmd('go',[(rand()*2-1)*.18,-range(3,8)],'dash'),wait(range(1.5,3),'far'),home(),fx(()=>{needs.energy-=.3;needs.curiosity-=.4;})]
68
+ dash:()=>[cmd('go',[(rand()*2-1)*.18,-range(3,8)],'dash'),wait(range(1.5,3),'far'),home(),fx(()=>{needs.energy-=.3;needs.curiosity-=.4;})],
69
+ scan:()=>[sit('sit'),cmd('look',['left'],'scan'),wait(range(.5,1.5),'scan'),cmd('look',['right'],'scan'),wait(range(2,4),'observe'),fx(()=>{needs.curiosity-=.25;})],
70
+ patrol:()=>{const x=actor.sample(clock).x,d=range(.16,.3)*(rand()<.5?-1:1);return [walkTo(x+d),wait(range(.6,1.2),'sniff'),walkTo(x-d),wait(range(.6,1.2),'sniff'),walkTo(x),wait(range(1,3),'observe'),fx(()=>{needs.energy-=.1;needs.curiosity-=.25;})];},
71
+ roll:()=>[lie(),wait(range(.7,1.5),'rest'),cmd('roll',[],'roll'),wait(range(2,5),'watch'),fx(()=>{needs.energy-=.08;needs.curiosity-=.15;})],
72
+ checkin:()=>[home(),sit('sit'),cmd('look',[side()],'look'),wait(range(5,10),'checkin'),fx(()=>{needs.social-=.4;})],
73
+ play:()=>[cmd('spin',[],'play'),wait(range(1,2),'pause'),sit('sit'),wait(range(2,4),'watch'),fx(()=>{needs.energy-=.12;needs.curiosity-=.2;})],
74
+ belly:()=>[cmd('belly',[range(5,9)],'belly'),wait(range(2,4),'watch'),fx(()=>{needs.energy+=.08;needs.curiosity-=.15;})],
75
+ sleep:()=>[cmd('sleep',[range(65,110),['side','curl','chin','belly'][Math.floor(rand()*4)],rand()<.5],'sleep'),wait(range(2,4),'recover'),fx(()=>{needs.energy+=.3;})],
63
76
  };
64
77
  const weights=()=>{
65
78
  const {energy:e,social:s,curiosity:c}=needs;
66
- return {rest:e<.35?6:(1-e)*1.2,watch:.8+s,observe:.6+c*.6,stroll:1.2*c*e,dash:e>.55?1.2*e*c:0};
79
+ return {rest:e<.35?6:(1-e)*1.2,watch:.8+s,observe:.6+c*.6,stroll:1.2*c*e,dash:e>.55?1.2*e*c:0,
80
+ scan:.35+c*.4,patrol:e>.25?.7*c*e:0,checkin:.25+s*.7,
81
+ roll:episodeCooldowns.roll===0&&e>.45?.35*e:0,
82
+ play:episodeCooldowns.play===0&&e>.6&&typeof actor.spin==='function'?.25*e*c:0,
83
+ belly:episodeCooldowns.belly===0&&typeof actor.belly==='function'?.35:0,
84
+ sleep:episodeCooldowns.sleep===0&&typeof actor.sleep==='function'?(e<.4?3:.2):0};
67
85
  };
68
86
  function decide(){
69
87
  const w=weights();if(lastEpisode)w[lastEpisode]=0;
70
88
  const kinds=Object.keys(w),total=kinds.reduce((sum,k)=>sum+w[k],0);
71
89
  let pick=rand()*total,kind=kinds.find(k=>(pick-=w[k])<0)??'observe';
90
+ // Quiet-time sleep pressure is independent of energy restored by sitting.
91
+ // Wait for the current coherent episode to end; never yank a running puppy.
92
+ if(quiet>=120&&needs.drowsiness>=.35&&episodeCooldowns.sleep===0&&lastEpisode!=='sleep'&&typeof actor.sleep==='function')kind='sleep';
72
93
  lastEpisode=kind;decisions++;
94
+ if(kind==='roll')episodeCooldowns.roll=90;
95
+ if(kind==='play')episodeCooldowns.play=120;
96
+ if(kind==='belly')episodeCooldowns.belly=80;
97
+ if(kind==='sleep')episodeCooldowns.sleep=100;
73
98
  queue=[wait(range(3,9),'pause'),...EPISODES[kind]()];
74
99
  }
75
100
 
@@ -88,16 +113,22 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
88
113
  fx(()=>{treat=true;}),cmd('sit',[],'treatSit',()=>!seatedAnyVariant()),
89
114
  play('treat-eat','chew'),fx(()=>{treat=null;needs.energy+=.25;needs.social-=.3;})
90
115
  ];
116
+ const TUG_STEPS=()=>[
117
+ cmd('sit',[],'tug',unseated),cmd('tug',[6],'tug'),wait(.5,'watch'),fx(()=>{needs.energy-=.12;needs.social-=.4;})
118
+ ];
91
119
 
92
120
  function startInteraction(){
93
121
  active=pending;pending=null;decisions++;hold=0;
94
- queue=active==='ball'?BALL_STEPS():TREAT_STEPS();
122
+ queue=active==='ball'?BALL_STEPS():active==='tug'?TUG_STEPS():TREAT_STEPS();
95
123
  }
96
124
  function finishInteraction(){cooldowns[active]=COOLDOWN[active];active=null;}
97
125
 
98
126
  function drift(dt){
99
- const resting=holdIntent==='rest'||holdIntent==='recover';
127
+ const resting=holdIntent==='rest'||holdIntent==='recover'||actor.state==='sleep'||intent==='belly';
100
128
  needs.energy+=resting?.04*dt:-.004*dt;
129
+ const asleep=actor.state==='sleep';
130
+ needs.drowsiness+=asleep?-dt/60:dt/240;
131
+ quiet=active||pending||asleep?0:quiet+dt;
101
132
  if(!active){needs.curiosity+=.01*dt;needs.social+=.008*dt;}
102
133
  for(const k in needs)needs[k]=clamp(needs[k],0,1);
103
134
  }
@@ -106,6 +137,7 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
106
137
  if(typeof dt!=='number'||!Number.isFinite(dt)||dt<0)throw RangeError('dt must be a finite non-negative number');
107
138
  dt=Math.min(dt,MAX_DT);clock+=dt;
108
139
  for(const k in cooldowns)cooldowns[k]=Math.max(0,cooldowns[k]-dt);
140
+ for(const k in episodeCooldowns)episodeCooldowns[k]=Math.max(0,episodeCooldowns[k]-dt);
109
141
  drift(dt);
110
142
  const safe=SAFE.has(actor.state);
111
143
  if(waiting){
@@ -133,15 +165,22 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
133
165
  if(step.when&&!step.when())continue;
134
166
  intent=step.intent;label=step.label;
135
167
  if(step.hold!==undefined){hold=step.hold;holdIntent=step.intent;if(step.gesture)gesture={clip:step.gesture,duration:step.hold,elapsed:0};return;}
136
- if(actor[step.cmd](...step.args)!==false)waiting={age:0,left:false};
168
+ if(actor[step.cmd](...(typeof step.args==='function'?step.args():step.args))!==false)waiting={age:0,left:false};
137
169
  return;
138
170
  }
139
171
  }
140
172
 
141
173
  function interact(kind){
142
- if(kind!=='ball'&&kind!=='treat')return false;
174
+ if(!['ball','treat','tug'].includes(kind))return false;
175
+ if(kind==='tug'&&typeof actor.tug!=='function')return false;
143
176
  if(pending||active||cooldowns[kind]>0)return false;
144
- if(kind==='treat'){pending=kind;return true;}
177
+ quiet=0;
178
+ if(kind==='treat'||kind==='tug'){
179
+ pending=kind;
180
+ // The pet wakes through its authored recovery, not after a long sleep timer.
181
+ if(['sleep','belly'].includes(actor.sample(clock).state))actor.sit();
182
+ return true;
183
+ }
145
184
  // Ball: launch now and drop obsolete controller plans; the actor's own state is left to chase.
146
185
  active='ball';decisions++;ball={mode:'flight',start:clock};
147
186
  queue=BALL_STEPS();hold=0;holdIntent=null;gesture=null;waiting=null;
@@ -172,8 +211,9 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
172
211
  get report(){
173
212
  const interaction=active??pending;
174
213
  return {
175
- intent,label:pending?`${label} · ${PENDING_LABEL[pending]}`:label,interaction,busy:interaction!==null,
176
- ball:ballReport(),treat:treatReport(),energy:needs.energy,decisions,cooldowns:{...cooldowns},
214
+ intent,label:pending?`${label} · ${PENDING_LABEL[pending]}`:label,episode:lastEpisode,interaction,busy:interaction!==null,
215
+ ball:ballReport(),treat:treatReport(),tug:actor.state==='tug'?{progress:actor.sample(clock).comfortPose?.progress??0,grip:1}:null,
216
+ snore:actor.state==='sleep'?((clock%3.6)/3.6):null,energy:needs.energy,drowsiness:needs.drowsiness,decisions,cooldowns:{...cooldowns},
177
217
  // Local hold clock only: progress freezes between updates; null outside the stationary hold.
178
218
  gesture:gesture&&SAFE.has(actor.state)?{clip:gesture.clip,progress:clamp(gesture.elapsed/gesture.duration,0,1)}:null
179
219
  };
@@ -7,6 +7,18 @@ export function paintCompanionObjects(pixels,width,height,projection,report,stat
7
7
  // Interaction drawings already include the toy/food. Never paint a second
8
8
  // floating ball over the baked mouth, or a biscuit through the puppy's chest.
9
9
  if(report.gesture)return pixels;
10
+ // A quiet, tiny rising "z" suggests snoring. The sleeping dog itself remains
11
+ // the original eyes-closed source pose: no chest/body zoom or squashing.
12
+ if(Number.isFinite(report.snore)&&state.state==='sleep'){
13
+ const placement=projection.placement(state.x,state.z),side=height*placement.size,sideX=side/placement.pixelAspect;
14
+ const left=width*placement.x-sideX/2,top=height*placement.y-300/320*side;
15
+ const cycle=report.snore,tone=Math.round(140*Math.sin(Math.PI*cycle)),unit=Math.max(1,Math.round(side/160));
16
+ const cx=Math.round(left+220/320*sideX),cy=Math.round(top+(210-16*cycle)/320*side);
17
+ if(tone>12)for(const [gx,gy] of [[0,0],[1,0],[2,0],[1,1],[0,2],[1,2],[2,2]])for(let dy=0;dy<unit;dy++)for(let dx=0;dx<unit;dx++){
18
+ const x=cx+gx*unit+dx,y=cy+gy*unit+dy;if(x<0||x>=width||y<0||y>=height)continue;
19
+ const j=(y*width+x)*4;pixels[j]=pixels[j+1]=pixels[j+2]=Math.max(pixels[j],tone);
20
+ }
21
+ }
10
22
  if(report.ball?.carried){if(mouth&&[mouth.x,mouth.y,mouth.rx,mouth.ry].every(Number.isFinite)&&mouth.rx>0&&mouth.ry>0)objects.push({kind:'ball',screen:mouth});}
11
23
  else if(report.ball)objects.push({...report.ball,radius:.035,kind:'ball'});
12
24
  if(report.treat)objects.push({...report.treat,height:report.treat.height??.018,radius:.022,kind:'treat'});
@@ -1,11 +1,13 @@
1
1
  import {grayFrameReader,isGrayFrameSource} from './puppy-frame-store.mjs';
2
2
  // Prebaked interaction samples; the original seat is the exact shared endpoint.
3
3
  // Pure sampling, no live image morph, root motion, camera or pose-size fitting.
4
- export function createCompanionSpriteSampler(bytes,info){
4
+ export function createCompanionSpriteSampler(bytes,info,output){
5
5
  const n=320*320;
6
6
  if(!isGrayFrameSource(bytes)||info?.width!==320||info.height!==320||info.samples!==9||!Number.isInteger(info.pairs)||info.pairs<1||info.pairs>128||bytes.byteLength!==info.pairs*9*n)throw Error('Invalid companion sprite cache');
7
7
  for(const key of ['treat-eat','ball-pickup','ball-drop'])if(!Array.isArray(info.clips?.[key])||!info.clips[key].length||!info.clips[key].every(p=>Number.isInteger(p)&&p>=0&&p<info.pairs))throw Error('Invalid companion sprite clip');
8
- const frames=grayFrameReader(bytes,320,320,info.pairs*9),rgba=new Uint8ClampedArray(n*4);for(let i=3;i<rgba.length;i+=4)rgba[i]=255;
8
+ const frames=grayFrameReader(bytes,320,320,info.pairs*9),rgba=output??new Uint8ClampedArray(n*4);
9
+ if(!(rgba instanceof Uint8ClampedArray)||rgba.length!==n*4)throw Error('Invalid sprite output');
10
+ for(let i=3;i<rgba.length;i+=4)rgba[i]=255;
9
11
  return({clip,progress})=>{
10
12
  const list=info.clips[clip];if(!list||!Number.isFinite(progress)||progress<0||progress>1)throw Error('Invalid companion sprite pose');
11
13
  const q=progress*list.length,index=Math.min(list.length-1,Math.floor(q)),s=Math.round((q-index)*8),frame=frames.getFrame(list[index]*9+s);
@@ -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);
@@ -37,11 +37,13 @@ export function paperActionPose(name,progress,info){
37
37
  if(!Number.isFinite(blend))throw Error('Invalid paper action: blend');
38
38
  return{pair:list[index],blend};
39
39
  }
40
- export function createPaperActionSampler(bytes,info){
40
+ export function createPaperActionSampler(bytes,info,output){
41
41
  if(!isGrayFrameSource(bytes))throw Error('Invalid action cache: bytes');
42
42
  validatePaperActionInfo(info,bytes.byteLength);
43
43
  const stride=info.width*info.height,pairs=info.pairs,frames=grayFrameReader(bytes,info.width,info.height,pairs*9);
44
- const out=new Uint8ClampedArray(stride*4);for(let i=3;i<out.length;i+=4)out[i]=255;
44
+ const out=output??new Uint8ClampedArray(stride*4);
45
+ if(!(out instanceof Uint8ClampedArray)||out.length!==stride*4)throw Error('Invalid action output');
46
+ for(let i=3;i<out.length;i+=4)out[i]=255;
45
47
  return state=>{const p=paperActionPose(state.actionName,state.actionProgress,info),sample=Math.round(p.blend*8);
46
48
  if(p.pair>=pairs||sample<0||sample>8)throw Error('Invalid paper action: sample out of range');
47
49
  const frame=frames.getFrame(p.pair*9+sample);for(let i=0,j=0;i<stride;i++,j+=4)out[j]=out[j+1]=out[j+2]=frame[i];return out;};
@@ -5,4 +5,4 @@ export function paperBehaviorPose(state,info){
5
5
  const q=Math.min(list.length-1e-9,progress*list.length),i=Math.floor(q),blend=Math.round((q-i)*8)/8;
6
6
  return{pair:list[i],blend};
7
7
  }
8
- export function createPaperBehaviorSampler(bytes,info){const sample=createRearSampler(bytes,info);return state=>{const p=paperBehaviorPose(state,info);return sample(p.pair,p.blend);};}
8
+ export function createPaperBehaviorSampler(bytes,info,output){const sample=createRearSampler(bytes,info,output);return state=>{const p=paperBehaviorPose(state,info);return sample(p.pair,p.blend);};}
@@ -91,9 +91,11 @@ export function footSourcePoint(x,y,pins){
91
91
  return chosen?{x:x-chosen.dx*best,y:y-chosen.dy*best}:{x,y};
92
92
  }
93
93
 
94
- export function createRearSampler(bytes,info){
94
+ export function createRearSampler(bytes,info,output){
95
95
  if(!isGrayFrameSource(bytes)||info.pairs!==18||info.samples!==9||bytes.byteLength!==18*9*320*320)throw Error('Invalid rear sprite cache');
96
- const frames=grayFrameReader(bytes,320,320,18*9),out=new Uint8ClampedArray(320*320*4);for(let i=3;i<out.length;i+=4)out[i]=255;
96
+ const frames=grayFrameReader(bytes,320,320,18*9),out=output??new Uint8ClampedArray(320*320*4);
97
+ if(!(out instanceof Uint8ClampedArray)||out.length!==320*320*4)throw Error('Invalid rear output');
98
+ for(let i=3;i<out.length;i+=4)out[i]=255;
97
99
  return(pair,blend)=>{
98
100
  if(!Number.isInteger(pair)||pair<0||pair>=18||!Number.isFinite(blend)||blend<0||blend>1)throw Error('Invalid rear pose');
99
101
  const frame=frames.getFrame(pair*9+Math.round(blend*8));
@@ -1,9 +1,11 @@
1
1
  import {grayFrameReader,isGrayFrameSource} from './puppy-frame-store.mjs';
2
2
  // Exact cached samples; no live crossfade. Reverse the same path to stand up.
3
- export function createPaperSeatSampler(bytes,info){
3
+ export function createPaperSeatSampler(bytes,info,output){
4
4
  const stride=320*320;
5
5
  if(!isGrayFrameSource(bytes)||info.width!==320||info.height!==320||info.pairs!==3||info.samples!==17||bytes.byteLength!==3*17*stride)throw Error('Invalid paper sitting cache');
6
- const frames=grayFrameReader(bytes,320,320,3*17),out=new Uint8ClampedArray(stride*4);for(let i=3;i<out.length;i+=4)out[i]=255;
6
+ const frames=grayFrameReader(bytes,320,320,3*17),out=output??new Uint8ClampedArray(stride*4);
7
+ if(!(out instanceof Uint8ClampedArray)||out.length!==stride*4)throw Error('Invalid seat output');
8
+ for(let i=3;i<out.length;i+=4)out[i]=255;
7
9
  return progress=>{
8
10
  if(!Number.isFinite(progress)||progress<0||progress>1)throw Error('Invalid paper sitting progress');
9
11
  const q=progress*3,pair=Math.min(2,Math.floor(q)),sample=Math.round((q-pair)*16),frame=frames.getFrame(pair*17+sample);
@@ -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});