@bitefoundry/retrieviewer 0.0.3 → 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.
- package/ASSET-PROVENANCE.md +6 -0
- package/ASSETS-LICENSE.md +2 -1
- package/ATTRIBUTION.md +6 -1
- package/COMFORT-ART.md +65 -0
- package/DESKTOP-PLATFORMS.md +6 -1
- package/PLUGIN.md +13 -10
- package/PREREQUISITES.md +95 -0
- package/PUPPY-MEMORY.md +102 -1
- package/README.ko.md +110 -0
- package/README.md +117 -59
- package/RELEASING.md +23 -12
- package/THIRD-PARTY-NOTICES.md +5 -3
- package/braille.mjs +35 -9
- package/examples/live.mjs +2 -2
- package/integrations/linux-overlay/overlay.py +2 -2
- package/integrations/macos-overlay/overlay.jxa +8 -6
- package/integrations/opencode/runtime.mjs +4 -4
- package/integrations/opencode/tui.tsx +1 -1
- package/integrations/windows-overlay/Overlay.cs +4 -4
- package/integrations/windows-overlay/protocol.mjs +11 -7
- package/package.json +34 -13
- package/puppy-comfort-sprites.mjs +25 -0
- package/puppy-companion-comfort-q5.bin.gz +0 -0
- package/puppy-companion-comfort.json +77 -0
- package/puppy-companion-life.mjs +49 -16
- package/puppy-companion-objects.mjs +12 -0
- package/puppy-frame-predictive.mjs +15 -9
- package/puppy-frame-store.mjs +2 -2
- package/puppy-paper-world.mjs +14 -0
- package/puppy-paper.mjs +65 -14
- package/retrieviewer-plugin.d.mts +1 -1
- package/retrieviewer-plugin.mjs +1 -1
- package/retrieviewer.d.mts +10 -4
- package/retrieviewer.mjs +20 -14
- package/locomotion-core.mjs +0 -21
- package/locomotion-puppy.animal +0 -0
- package/vendor/procedural-animals/package.json +0 -40
- package/vendor/procedural-animals/src/animal.js +0 -146
- package/vendor/procedural-animals/src/bake.js +0 -51
- package/vendor/procedural-animals/src/build.js +0 -81
- package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
- package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
- package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
- package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
- package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
- package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
- package/vendor/procedural-animals/src/core/build/seams.js +0 -183
- package/vendor/procedural-animals/src/core/build/warp.js +0 -112
- package/vendor/procedural-animals/src/core/build/weights.js +0 -393
- package/vendor/procedural-animals/src/core/math/vec.js +0 -81
- package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
- package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
- package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
- package/vendor/procedural-animals/src/core/motion/common.js +0 -158
- package/vendor/procedural-animals/src/core/motion/index.js +0 -22
- package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
- package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
- package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
- package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
- package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
- package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
- package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
- package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
- package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
- package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
- package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
- package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
- package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
- package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
- package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
- package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
- package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
- package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
- package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
- package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
- package/vendor/procedural-animals/src/index.d.ts +0 -98
- package/vendor/procedural-animals/src/index.js +0 -29
- package/vendor/procedural-animals/src/registry.js +0 -48
- package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
- package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
- package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
- package/vendor/procedural-animals/src/species/bear/index.js +0 -139
- package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
- package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
- package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
- package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
- package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
- package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
- package/vendor/procedural-animals/src/species/boar/index.js +0 -110
- package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
- package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
- package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
- package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
- package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
- package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
- package/vendor/procedural-animals/src/species/cat/index.js +0 -211
- package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
- package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
- package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
- package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
- package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
- package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
- package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
- package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
- package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
- package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
- package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
- package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
- package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
- package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
- package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
- package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
- package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
- package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
- package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
- package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
- package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
- package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
- package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
- package/vendor/procedural-animals/src/species/cow/index.js +0 -118
- package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
- package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
- package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
- package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
- package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
- package/vendor/procedural-animals/src/species/crow/index.js +0 -70
- package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
- package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
- package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
- package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
- package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
- package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
- package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
- package/vendor/procedural-animals/src/species/deer/index.js +0 -106
- package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
- package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
- package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
- package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
- package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
- package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
- package/vendor/procedural-animals/src/species/dog/index.js +0 -144
- package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
- package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
- package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
- package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
- package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
- package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
- package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
- package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
- package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
- package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
- package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
- package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
- package/vendor/procedural-animals/src/species/fish/index.js +0 -88
- package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
- package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
- package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
- package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
- package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
- package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
- package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
- package/vendor/procedural-animals/src/species/fox/index.js +0 -120
- package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
- package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
- package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
- package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
- package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
- package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
- package/vendor/procedural-animals/src/species/frog/index.js +0 -97
- package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
- package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
- package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
- package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
- package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
- package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
- package/vendor/procedural-animals/src/species/goat/index.js +0 -149
- package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
- package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
- package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
- package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
- package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
- package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
- package/vendor/procedural-animals/src/species/horse/index.js +0 -80
- package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
- package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
- package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
- package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
- package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
- package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
- package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
- package/vendor/procedural-animals/src/species/lion/index.js +0 -111
- package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
- package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
- package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
- package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
- package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
- package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
- package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
- package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
- package/vendor/procedural-animals/src/species/pig/index.js +0 -161
- package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
- package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
- package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
- package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
- package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
- package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
- package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
- package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
- package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
- package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
- package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
- package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
- package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
- package/vendor/procedural-animals/src/species/rat/index.js +0 -111
- package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
- package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
- package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
- package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
- package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
- package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
- package/vendor/procedural-animals/src/species/shark/index.js +0 -104
- package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
- package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
- package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
- package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
- package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
- package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
- package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
- package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
- package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
- package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
- package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
- package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
- package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
- package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
- package/vendor/procedural-animals/src/species/snake/index.js +0 -73
- package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
- package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
- package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
- package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
- package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
- package/vendor/procedural-animals/src/species/spider/index.js +0 -78
- package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
- package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
- package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
- package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
- package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
- package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
- package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
- package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
- package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
- package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
- package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
- package/vendor/procedural-animals/src/worker.js +0 -29
|
@@ -0,0 +1,25 @@
|
|
|
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){
|
|
16
|
+
if(!isGrayFrameSource(bytes)||info?.version!==1||info.width!==320||info.height!==320||info.frames!==9)throw Error('Invalid comfort sprites');
|
|
17
|
+
const frames=grayFrameReader(bytes,320,320,info.frames);let rgba=null;
|
|
18
|
+
return pose=>{
|
|
19
|
+
if(pose?.clip!=='tug')throw Error('Invalid comfort clip');
|
|
20
|
+
if(!rgba){rgba=new Uint8ClampedArray(320*320*4);for(let i=3;i<rgba.length;i+=4)rgba[i]=255;}
|
|
21
|
+
const index=comfortFrameIndex(pose.progress,info),frame=frames.getFrame(index);
|
|
22
|
+
for(let i=0,j=0;i<frame.length;i++,j+=4)rgba[j]=rgba[j+1]=rgba[j+2]=frame[i];
|
|
23
|
+
return rgba;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"width": 320,
|
|
4
|
+
"height": 320,
|
|
5
|
+
"frames": 9,
|
|
6
|
+
"clips": [
|
|
7
|
+
"tug"
|
|
8
|
+
],
|
|
9
|
+
"gzipBytes": 103549,
|
|
10
|
+
"residentPixelBytes": 291337,
|
|
11
|
+
"sourceSheetSha256": "10fde9dacca5cb45acd68f387e7ed3bcf88cd72cf0f9c4220d31d819db91731e",
|
|
12
|
+
"neutralSha256": "19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a",
|
|
13
|
+
"encodedSha256": "e046e214da585ffda65e8d2c1a86c589af589325b792634f128ab8b71f6dbf3a",
|
|
14
|
+
"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.",
|
|
15
|
+
"registration": {
|
|
16
|
+
"sheetSize": [
|
|
17
|
+
1774,
|
|
18
|
+
887
|
|
19
|
+
],
|
|
20
|
+
"targetBounds": [
|
|
21
|
+
41,
|
|
22
|
+
92,
|
|
23
|
+
231,
|
|
24
|
+
301
|
|
25
|
+
],
|
|
26
|
+
"registration": [
|
|
27
|
+
{
|
|
28
|
+
"cell": 0,
|
|
29
|
+
"scale": 0.5663956639566395,
|
|
30
|
+
"dx": 32,
|
|
31
|
+
"dy": 69
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"cell": 1,
|
|
35
|
+
"scale": 0.5663956639566395,
|
|
36
|
+
"dx": 39,
|
|
37
|
+
"dy": 69
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"cell": 2,
|
|
41
|
+
"scale": 0.5663956639566395,
|
|
42
|
+
"dx": 39,
|
|
43
|
+
"dy": 69
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"cell": 3,
|
|
47
|
+
"scale": 0.5663956639566395,
|
|
48
|
+
"dx": 35,
|
|
49
|
+
"dy": 68
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"cell": 4,
|
|
53
|
+
"scale": 0.5663956639566395,
|
|
54
|
+
"dx": 33,
|
|
55
|
+
"dy": 74
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"cell": 5,
|
|
59
|
+
"scale": 0.5663956639566395,
|
|
60
|
+
"dx": 39,
|
|
61
|
+
"dy": 74
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"cell": 6,
|
|
65
|
+
"scale": 0.5663956639566395,
|
|
66
|
+
"dx": 39,
|
|
67
|
+
"dy": 74
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"cell": 7,
|
|
71
|
+
"scale": 0.5663956639566395,
|
|
72
|
+
"dx": 36,
|
|
73
|
+
"dy": 74
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
package/puppy-companion-life.mjs
CHANGED
|
@@ -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));
|
|
@@ -38,7 +40,8 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
|
|
|
38
40
|
|
|
39
41
|
const rand=mulberry32(seed),range=(a,b)=>a+(b-a)*rand(),side=()=>rand()<.5?'left':'right';
|
|
40
42
|
const needs={energy,social:.5,curiosity:.5};
|
|
41
|
-
const cooldowns={ball:0,treat:0};
|
|
43
|
+
const cooldowns={ball:0,treat:0,tug:0};
|
|
44
|
+
const episodeCooldowns={roll:0,play:0,belly:0,sleep:0};
|
|
42
45
|
let clock=0,decisions=0,lastEpisode=null;
|
|
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;
|
|
@@ -51,25 +54,44 @@ 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:()=>[
|
|
59
|
-
watch:()=>[
|
|
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(22,38)],'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';
|
|
72
90
|
lastEpisode=kind;decisions++;
|
|
91
|
+
if(kind==='roll')episodeCooldowns.roll=90;
|
|
92
|
+
if(kind==='play')episodeCooldowns.play=120;
|
|
93
|
+
if(kind==='belly')episodeCooldowns.belly=80;
|
|
94
|
+
if(kind==='sleep')episodeCooldowns.sleep=100;
|
|
73
95
|
queue=[wait(range(3,9),'pause'),...EPISODES[kind]()];
|
|
74
96
|
}
|
|
75
97
|
|
|
@@ -88,15 +110,18 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
|
|
|
88
110
|
fx(()=>{treat=true;}),cmd('sit',[],'treatSit',()=>!seatedAnyVariant()),
|
|
89
111
|
play('treat-eat','chew'),fx(()=>{treat=null;needs.energy+=.25;needs.social-=.3;})
|
|
90
112
|
];
|
|
113
|
+
const TUG_STEPS=()=>[
|
|
114
|
+
cmd('sit',[],'tug',unseated),cmd('tug',[6],'tug'),wait(.5,'watch'),fx(()=>{needs.energy-=.12;needs.social-=.4;})
|
|
115
|
+
];
|
|
91
116
|
|
|
92
117
|
function startInteraction(){
|
|
93
118
|
active=pending;pending=null;decisions++;hold=0;
|
|
94
|
-
queue=active==='ball'?BALL_STEPS():TREAT_STEPS();
|
|
119
|
+
queue=active==='ball'?BALL_STEPS():active==='tug'?TUG_STEPS():TREAT_STEPS();
|
|
95
120
|
}
|
|
96
121
|
function finishInteraction(){cooldowns[active]=COOLDOWN[active];active=null;}
|
|
97
122
|
|
|
98
123
|
function drift(dt){
|
|
99
|
-
const resting=holdIntent==='rest'||holdIntent==='recover';
|
|
124
|
+
const resting=holdIntent==='rest'||holdIntent==='recover'||actor.state==='sleep'||intent==='belly';
|
|
100
125
|
needs.energy+=resting?.04*dt:-.004*dt;
|
|
101
126
|
if(!active){needs.curiosity+=.01*dt;needs.social+=.008*dt;}
|
|
102
127
|
for(const k in needs)needs[k]=clamp(needs[k],0,1);
|
|
@@ -106,6 +131,7 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
|
|
|
106
131
|
if(typeof dt!=='number'||!Number.isFinite(dt)||dt<0)throw RangeError('dt must be a finite non-negative number');
|
|
107
132
|
dt=Math.min(dt,MAX_DT);clock+=dt;
|
|
108
133
|
for(const k in cooldowns)cooldowns[k]=Math.max(0,cooldowns[k]-dt);
|
|
134
|
+
for(const k in episodeCooldowns)episodeCooldowns[k]=Math.max(0,episodeCooldowns[k]-dt);
|
|
109
135
|
drift(dt);
|
|
110
136
|
const safe=SAFE.has(actor.state);
|
|
111
137
|
if(waiting){
|
|
@@ -133,15 +159,21 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
|
|
|
133
159
|
if(step.when&&!step.when())continue;
|
|
134
160
|
intent=step.intent;label=step.label;
|
|
135
161
|
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};
|
|
162
|
+
if(actor[step.cmd](...(typeof step.args==='function'?step.args():step.args))!==false)waiting={age:0,left:false};
|
|
137
163
|
return;
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
|
|
141
167
|
function interact(kind){
|
|
142
|
-
if(
|
|
168
|
+
if(!['ball','treat','tug'].includes(kind))return false;
|
|
169
|
+
if(kind==='tug'&&typeof actor.tug!=='function')return false;
|
|
143
170
|
if(pending||active||cooldowns[kind]>0)return false;
|
|
144
|
-
if(kind==='treat'){
|
|
171
|
+
if(kind==='treat'||kind==='tug'){
|
|
172
|
+
pending=kind;
|
|
173
|
+
// The pet wakes through its authored recovery, not after a long sleep timer.
|
|
174
|
+
if(['sleep','belly'].includes(actor.sample(clock).state))actor.sit();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
145
177
|
// Ball: launch now and drop obsolete controller plans; the actor's own state is left to chase.
|
|
146
178
|
active='ball';decisions++;ball={mode:'flight',start:clock};
|
|
147
179
|
queue=BALL_STEPS();hold=0;holdIntent=null;gesture=null;waiting=null;
|
|
@@ -172,8 +204,9 @@ export function createCompanionLife(actor,{seed=731,energy=.8}={}){
|
|
|
172
204
|
get report(){
|
|
173
205
|
const interaction=active??pending;
|
|
174
206
|
return {
|
|
175
|
-
intent,label:pending?`${label} · ${PENDING_LABEL[pending]}`:label,interaction,busy:interaction!==null,
|
|
176
|
-
ball:ballReport(),treat:treatReport(),
|
|
207
|
+
intent,label:pending?`${label} · ${PENDING_LABEL[pending]}`:label,episode:lastEpisode,interaction,busy:interaction!==null,
|
|
208
|
+
ball:ballReport(),treat:treatReport(),tug:actor.state==='tug'?{progress:actor.sample(clock).comfortPose?.progress??0,grip:1}:null,
|
|
209
|
+
snore:actor.state==='sleep'?((clock%3.6)/3.6):null,energy:needs.energy,decisions,cooldowns:{...cooldowns},
|
|
177
210
|
// Local hold clock only: progress freezes between updates; null outside the stationary hold.
|
|
178
211
|
gesture:gesture&&SAFE.has(actor.state)?{clip:gesture.clip,progress:clamp(gesture.elapsed/gesture.duration,0,1)}:null
|
|
179
212
|
};
|
|
@@ -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'});
|
|
@@ -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
|
|
93
|
-
|
|
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));}
|
package/puppy-frame-store.mjs
CHANGED
|
@@ -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);
|
package/puppy-paper-world.mjs
CHANGED
|
@@ -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=
|
|
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
|
-
|
|
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
|
|
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-
|
|
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
|
|
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;
|
|
@@ -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;
|
package/retrieviewer-plugin.mjs
CHANGED
|
@@ -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;},
|