@bitefoundry/retrieviewer 0.0.11 → 0.0.12

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.
@@ -0,0 +1,61 @@
1
+ # Precomputed motion tables (0.0.12)
2
+
3
+ `npm run bake:motion` compiles the current approved sources into
4
+ `puppy-motion-tables.generated.mjs`. It does not change a picture, camera,
5
+ trajectory, animation duration or source metadata. `npm run check:motion` checks
6
+ the exact deterministic output without writing. Tests and the publication guard
7
+ reject stale output after source metadata or witnessed art changes.
8
+
9
+ ## What is precomputed
10
+
11
+ - Source support episodes, including continuous stance across a cycle seam and
12
+ a complete swing skipped between rendered frames. No stance-history discovery
13
+ occurs while building a default renderer, including after a resize.
14
+ - Clip lists, weights and totals used for action-pose addressing. The runtime no
15
+ longer validates/rebuilds these same lists and totals for each default pose.
16
+ - A dense table of72 facing/seat preparation cases, sharing8 step descriptors.
17
+ Lookup requires a stationary root; it does not insert a turn at running speed.
18
+ - Ten exact-pixel connection witnesses with view, speed and (where applicable)
19
+ gait-phase/velocity-match requirements. These cover walk entry/exit, rear run
20
+ into the authored impact, impact into rear pickup, carry-gather into arrival
21
+ seat, tug into the canonical seat, and four watch endpoints into that seat.
22
+ - SHA256 of every input metadata/art file used by the bake. No source images,
23
+ source hashes or graph search are computed by the running pet.
24
+
25
+ The generated routes preserve the existing approved preparation order. This is
26
+ a bounded compilation of existing paths, not unrestricted motion matching or a
27
+ new animation blend engine. Other actor-specific recovery rules stay in place.
28
+ Unregistered arbitrary-phase joins are not certified: in particular an urgently
29
+ interrupted walk may use its existing recovery rather than a proven phase0 join.
30
+
31
+ ## What remains dynamic
32
+
33
+ World displacement, actual velocity, unwrapped gait phase, perspective and the
34
+ small paw-to-world anchor correction still depend on live state. Those cannot
35
+ be baked as fixed screen coordinates while allowing free travel and resizing.
36
+ Support tables address the original source coordinates; they do not stretch the
37
+ head/torso, replace drawings or cause a camera orbit.
38
+
39
+ Paw labels are visible image-space proxies, not anatomical motion capture. Some
40
+ resting endpoints have exact-pose evidence but no individually labeled paws.
41
+ The authored run-to-impact entry is **airborne**, not a planted-paw handoff:
42
+ its gate requires the correct phase, positive speed and matched velocity.
43
+ Neither exact pixels nor a green numerical test proves general animal realism.
44
+
45
+ ## Compatibility and resource scope
46
+
47
+ The bundled Q5/default-loader path uses the compiled tables. Optional original
48
+ art and custom loaders retain the general metadata-driven implementation instead
49
+ of accidentally applying the bundled pet's bake to unrelated art. The general
50
+ helpers remain available for authoring and equivalence tests.
51
+
52
+ The generated module is about11KB source, shared between instances; there is no
53
+ extra image cache, dependency, timer, worker or asset-fetch round trip. It removes
54
+ repeat discovery/allocation work, not the roughly20MB retained image payload or
55
+ Node/native process baseline. It is not a large-RAM-reduction claim.
56
+
57
+ Verification compares every baked support sample and wrap, all action-addressing
58
+ boundaries, both actor paths through seeded fetches and rare impacts, and the
59
+ public renderer's text, contacts and mouth anchors. Use
60
+ `node --test puppy-motion-tables.test.mjs`. The owner authorized publication of
61
+ this work as0.0.12 on2026-10-24. Earlier published artifacts remain unchanged.
package/RELEASING.md CHANGED
@@ -1,4 +1,22 @@
1
- # Retrieviewer 0.0.11 release process
1
+ # Retrieviewer 0.0.12 release process
2
+
3
+ Current target: `@bitefoundry/retrieviewer@0.0.12`, public `latest`.
4
+ On2026-10-24 the owner authorized npm publication and website deployment of the
5
+ reviewed precomputed motion tables. Support episodes, action addressing and
6
+ bounded preparation routes are compiled offline; live travel, perspective and
7
+ paw correction remain dynamic. Default Q5 uses the tables; custom loaders and
8
+ optional original art retain their metadata-driven path. See MOTION-TABLES.md.
9
+ No new artwork, runtime dependency, process or timer. This removes repeated
10
+ CPU/allocation work, not the retained image payload or Node/native baseline.
11
+ Do not claim a large RAM reduction or new Linux/macOS live GUI certification.
12
+
13
+ Build and verify a fresh exact0.0.12 tarball outside the source tree, including
14
+ its owned Windows native lifecycle and bilingual browser runtime. Publish that
15
+ same tarball, verify registry integrity and matching GitHub Pages runtime bytes.
16
+ Do not restart the user's live pet or change host/agent configuration. Earlier
17
+ npm artifacts remain immutable; MIT code / CC BY-SA4.0 artwork stay unchanged.
18
+
19
+ ## Previous 0.0.11 scope (historical)
2
20
 
3
21
  Current target: `@bitefoundry/retrieviewer@0.0.11`, public `latest`.
4
22
  On2026-10-24 the owner accepted the reviewed candidate and authorized immediate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitefoundry/retrieviewer",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
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
6
  "homepage": "https://bitefoundry.github.io/retrieviewer/",
@@ -90,6 +90,7 @@
90
90
  "PUPPY-Q5-COMPARISON.md",
91
91
  "COMFORT-ART.md",
92
92
  "WATCH-REDRAW.md",
93
+ "MOTION-TABLES.md",
93
94
  "BUTTERFLY.md",
94
95
  "examples/embed.mjs",
95
96
  "examples/live.mjs",
@@ -102,6 +103,8 @@
102
103
  "puppy-paper.mjs",
103
104
  "puppy-paper-world.mjs",
104
105
  "puppy-paper-contact.mjs",
106
+ "puppy-motion-lookup.mjs",
107
+ "puppy-motion-tables.generated.mjs",
105
108
  "puppy-paper-seat.mjs",
106
109
  "puppy-paper-director.mjs",
107
110
  "puppy-paper-behaviors.mjs",
@@ -144,13 +147,15 @@
144
147
  "vendor/procedural-animals/LICENSE"
145
148
  ],
146
149
  "scripts": {
150
+ "bake:motion": "node tools/bake-pet-motion.mjs",
151
+ "check:motion": "node tools/bake-pet-motion.mjs --check",
147
152
  "start": "node tools/preview-site.mjs",
148
153
  "build:site": "node tools/build-site.mjs",
149
154
  "pet:companion": "node examples/live.mjs",
150
155
  "pet:desktop": "node retrieviewer-cli.mjs desktop",
151
156
  "test": "node --test \"*.test.mjs\"",
152
157
  "test:package": "node check-pet-package.mjs",
153
- "release:check": "node tools/check-release.mjs",
158
+ "release:check": "npm run check:motion && node tools/check-release.mjs",
154
159
  "prepublishOnly": "npm run release:check"
155
160
  },
156
161
  "dependencies": {
@@ -0,0 +1,20 @@
1
+ // Pure lookups over offline data. No source analysis, graph search or I/O here.
2
+ const views=Object.freeze({front:0,rear:1,approach:2});
3
+ export function preparationRoute(tables,kind,{view,flip,seatProgress=0,desired,nextFlip=false,speed=0}){
4
+ if(!Number.isFinite(speed)||Math.abs(speed)>tables.routes.maxPreparationSpeed)throw Error('Motion preparation requires a stopped root');
5
+ const viewIndex=views[view],seat=seatProgress===0?0:seatProgress===1?2:1;
6
+ const target=kind==='action'?(desired==='stand'?0:desired==='seat'?1:undefined):views[desired];
7
+ if(viewIndex===undefined||target===undefined||typeof flip!=='boolean'||typeof nextFlip!=='boolean')throw Error('Unknown baked motion route');
8
+ const index=kind==='action'?((viewIndex*2+ +flip)*3+seat)*2+target:((viewIndex*2+ +flip)*3+target)*2+ +nextFlip;
9
+ const code=tables.routes[kind]?.[index],route=tables.routes.steps[code];if(!route)throw Error('Unknown baked motion route');
10
+ return route;
11
+ }
12
+
13
+ export function canJoinMotion(tables,name,{view,speed,phase=0,nextSpeed=speed}){
14
+ const join=tables.joins[name],epsilon=1e-7;
15
+ if(!join||join.view!==view||![speed,phase,nextSpeed].every(Number.isFinite))return false;
16
+ if(speed<join.speed[0]-epsilon||speed>join.speed[1]+epsilon||nextSpeed<join.speed[0]-epsilon||nextSpeed>join.speed[1]+epsilon||join.positiveSpeed&&(speed<=0||nextSpeed<=0))return false;
17
+ if(join.phase!==undefined){const d=phase-join.phase;if(Math.abs(d-Math.round(d))>epsilon)return false;}
18
+ if(join.matchVelocity&&Math.abs(speed-nextSpeed)>epsilon)return false;
19
+ return true;
20
+ }
@@ -0,0 +1,4 @@
1
+ // Generated by tools/bake-pet-motion.mjs. Do not edit by hand.
2
+ // Source paw labels are image-space proxies, not anatomical motion capture.
3
+ const freeze=v=>{if(v&&typeof v==='object'){for(const x of Object.values(v))freeze(x);Object.freeze(v);}return v;};
4
+ export const BAKED_MOTION=freeze({"version":1,"sources":{"puppy-stride-meta.json":"21725d593d9a38ea59f68e359ef85c1d1569990155bf74a0ee7c1c111fb4f9c8","puppy-paper-rear-gallop.json":"d31c7487dcf144ea14323e13ad2e5009ab577431629f983dae1d17f268a4bb8c","puppy-paper-approach.json":"15b1e829e4d2cf76cfdb1a6a0ad27c7e916ccba46ca2879a96acf98d23c90855","puppy-paper-actions-v9.json":"010b3f4eb8e29b36f13cea460e5de424a1a0d25863633df40418d1d820a19a01","puppy-paper-behaviors.json":"c1bd1a01b2c652d0cad6c994be2b92b2c0927a1f808e66f5e1efa8890f61da7a","puppy-companion-comfort.json":"5234cf9566957c91ad0aa76c5dae4c9449f6c34171dcb581bf6d114b607c4a18","puppy-paper-actions-v9-q5.bin.gz":"abb4677468206c62937c0247dc4c5823e0a7c4a3a197da40194a08dd594aeef9","puppy-paper-rear-gallop-q5.bin.gz":"ca731e1979a37ec2e005a80365e3b58358f640b8b26e7036b0e931940a7b1264","puppy-companion-comfort-q5.bin.gz":"9f8c89bf4764eb2f1f17131e2413d941f1eae33ae9a15ffbe76cc33f2d18103a","puppy-paper-arrival-seat-q5.bin.gz":"7effa0a2150e8ef9ed37d5b6cb10089672e0ce32599c06db21ad4769144bf246","puppy-paper-seat-q5.bin.gz":"5480c994de0d034d70eff6ea86095f3722cf87bde72f364dd369b94b1ad66f84","puppy-paper-behaviors-q5.bin.gz":"e4b79387aca974ceec74a02dc3f02780eb53d38af6ec9ebd69994de744b39716"},"support":{"front":{"step":16,"period":128,"cyclic":true,"offsets":{"0":0,"1":16,"2":32,"3":48,"4":64,"5":80,"6":96,"7":112},"starts":{"hind-near":[123,123,123,123,123,123,123,123,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,123,123,123,123,123],"fore-far":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"fore-near":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"hind-far":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]}},"rear":{"step":8,"period":64,"cyclic":true,"offsets":{"5":0,"6":8,"7":16,"8":24,"9":32,"10":40,"11":48,"12":56},"starts":{"hind-far":[60,60,60,60,60,60,60,60,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,60,60,60,60],"hind-near":[62,62,62,62,62,62,62,62,62,62,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,62]}},"approach":{"step":8,"period":64,"cyclic":true,"offsets":{"5":0,"6":8,"7":16,"8":24,"9":32,"10":40,"11":48,"12":56},"starts":{"fore-left":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,39,39,39,39,39,39,-1,-1,-1,-1,-1,-1,-1,-1,-1,54,54,54,54,54,-1,-1,-1,-1,-1],"fore-right":[61,61,61,61,61,61,61,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,-1,-1,-1,-1,55,55,55,55,55,-1,61,61,61]}},"walk":{"step":8,"period":64,"cyclic":true,"offsets":{"42":0,"43":8,"44":16,"45":24,"46":32,"47":40,"48":48,"49":56},"starts":{"hind-far":[52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,52,52,52,52,52,52,52,52,52,52,52],"hind-near":[58,58,58,58,58,58,58,58,58,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,24,24,24,24,24,24,24,24,24,24,24,24,24,24,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,58,58,58,58,58,58],"fore-far":[-1,-1,-1,3,3,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,21,21,21,21,21,-1,-1,-1,-1,-1,31,31,31,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,47,47,47,47,47,-1,-1,-1,-1,-1,-1,-1,59,59,59,-1,-1],"fore-near":[-1,-1,-1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,-1,-1,-1,-1,-1,-1,-1,33,-1,-1,-1,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,-1,-1]}},"behavior/walk":{"step":8,"period":32,"cyclic":true,"offsets":{"0":0,"1":8,"2":16,"3":24},"starts":{"fore-left":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,24,24,-1,-1,-1,-1,-1,-1],"fore-right":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]}},"behavior/walk-enter":{"step":8,"period":8,"cyclic":false,"offsets":{"4":0},"starts":{"fore-left":[-1,-1,-1,-1,-1,-1,-1,-1,-1],"fore-right":[-1,-1,-1,-1,4,4,4,4,4]}},"behavior/brake":{"step":8,"period":32,"cyclic":false,"offsets":{"5":0,"6":8,"7":16,"8":24},"starts":{"fore-left":[-1,-1,-1,-1,-1,-1,-1,-1,-1,9,9,9,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,23,23,23,23,23,23,23,23,23,23],"fore-right":[0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,15,15,15,15,15,15,15,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]}},"behavior/finish":{"step":8,"period":8,"cyclic":false,"offsets":{"9":0},"starts":{}},"behavior/watch-left":{"step":8,"period":16,"cyclic":false,"offsets":{"10":0,"11":8},"starts":{}},"behavior/watch-right":{"step":8,"period":16,"cyclic":false,"offsets":{"12":0,"13":8},"starts":{}},"behavior/rest":{"step":8,"period":32,"cyclic":false,"offsets":{"14":0,"15":8,"16":16,"17":24},"starts":{}}},"actionClips":{"spin":{"list":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"weights":[1.1168,0.8052,0.827,0.7936,1.3421,1.4204,0.9015,0.5275,1.1081,0.7701,0.723,1.0494,1.4422,1.3325,1.0787,0.8964,0.8655],"total":17},"roll":{"list":[17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40],"weights":[1.122,1.4089,1.6479,0.6752,0.753,0.8756,0.9957,0.9586,0.7325,0.8218,1.0968,0.9962,0.8959,0.6589,1.0191,0.8352,0.4557,1.1818,1.1101,0.905,0.6752,1.6479,1.4089,1.122],"total":23.9999},"roll-down":{"list":[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40],"weights":[1.6886,0.6919,0.7717,0.8973,1.0203,0.9823,0.7506,0.8422,1.124,1.0208,0.918,0.6752,1.0443,0.8559,0.467,1.211,1.1376,0.9274,0.6919,1.6886,1.4438,1.1498],"total":22.0002},"lie-down":{"list":[17,18],"weights":[0.8866,1.1134],"total":2},"lie-up":{"list":[39,40],"weights":[1.1134,0.8866],"total":2},"walk-enter":{"list":[41],"weights":[1],"total":1},"walk":{"list":[42,43,44,45,46,47,48,49],"weights":[1,1,1,1,1,1,1,1],"total":8},"walk-exit":{"list":[50],"weights":[1],"total":1},"walk-face-front":{"list":[51],"weights":[1],"total":1},"walk-face-rear":{"list":[52],"weights":[1],"total":1},"walk-turn":{"list":[53,54,55,56,57,58,59,60,61,62,63,64,65],"weights":[1,1,1,1,1,1,1,1,1,1,1,1,1],"total":13}},"routes":{"maxPreparationSpeed":1e-8,"steps":[["ready"],["turn",true],["view-turn","rear"],["view-turn","approach"],["turn",false],["view-turn","front"],["rise"],["sit-down"]],"direction":[0,1,2,2,3,3,4,0,2,2,3,3,5,5,0,0,5,5,5,5,0,0,5,5,5,5,5,5,0,0,5,5,5,5,0,0],"action":[0,3,6,3,6,3,4,4,6,4,6,4,5,5,6,5,6,5,5,5,6,5,6,5,5,7,6,7,6,0,5,5,6,5,6,5]},"joins":{"walk-enter/walk":{"from":["puppy-paper-actions-v9",377],"to":["puppy-paper-actions-v9",378],"poseSha256":"81361614df56226b64be437f189c2e135c13498324286880db0fc06c5df3e86a","view":"front","speed":[0,0],"supportEvidence":"shared-pixels-with-labeled-walk-pose","supports":["hind-far","hind-near"],"phase":0},"walk/walk-exit":{"from":["puppy-paper-actions-v9",449],"to":["puppy-paper-actions-v9",450],"poseSha256":"81361614df56226b64be437f189c2e135c13498324286880db0fc06c5df3e86a","view":"front","speed":[0,0],"supportEvidence":"shared-pixels-with-labeled-walk-pose","supports":["hind-far","hind-near"],"phase":0},"rear-run/scramble":{"from":["puppy-paper-rear-gallop",63],"to":["puppy-companion-comfort",25],"poseSha256":"fa79a8762b57c0d5b70586a687f10e4a313e227e11e2435f6d7af0cd0866cc83","view":"rear","phase":0.25,"speed":[0,4.556913271064087],"positiveSpeed":true,"matchVelocity":true,"supports":[],"supportEvidence":"source-paw-proxies","airborne":true},"scramble/pickup-rear":{"from":["puppy-companion-comfort",36],"to":["puppy-companion-comfort",13],"poseSha256":"8192c504552a3984c80e1c3216e134605d4a930ec14cd7f778036e629e334601","view":"rear","speed":[0,0],"supports":["hind-far","hind-near"],"supportEvidence":"shared-pixels-with-labeled-rear-pose"},"carry-settle/arrival-seat":{"from":["puppy-companion-comfort",44],"to":["puppy-paper-arrival-seat",0],"poseSha256":"5ccf5acd9ca9ea81a0c9fc59d1ab2a2ddab148c942e31009f1a4c18de347fae8","view":"approach","speed":[0,0],"supportEvidence":"shared-pose-only"},"tug/seat":{"from":["puppy-companion-comfort",0],"to":["puppy-paper-seat",50],"poseSha256":"19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a","view":"approach","speed":[0,0],"supportEvidence":"shared-pose-only"},"watch-90/seat":{"from":["puppy-paper-behaviors",90],"to":["puppy-paper-seat",50],"poseSha256":"19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a","view":"approach","speed":[0,0],"supportEvidence":"shared-pose-only"},"watch-107/seat":{"from":["puppy-paper-behaviors",107],"to":["puppy-paper-seat",50],"poseSha256":"19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a","view":"approach","speed":[0,0],"supportEvidence":"shared-pose-only"},"watch-108/seat":{"from":["puppy-paper-behaviors",108],"to":["puppy-paper-seat",50],"poseSha256":"19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a","view":"approach","speed":[0,0],"supportEvidence":"shared-pose-only"},"watch-125/seat":{"from":["puppy-paper-behaviors",125],"to":["puppy-paper-seat",50],"poseSha256":"19f89a8bcb5a937e528593a8f36db846a0d45a5ee398f1b0873c056e5be2fa0a","view":"approach","speed":[0,0],"supportEvidence":"shared-pose-only"}}});
@@ -28,23 +28,24 @@ export function validatePaperActionInfo(info,byteLength){
28
28
  if(byteLength!==undefined&&byteLength!==f.width*f.height*f.samples*info.pairs)throw Error('Invalid action cache: byte length');
29
29
  return info;
30
30
  }
31
- export function paperActionPose(name,progress,info){
31
+ export function paperActionPose(name,progress,info,compiledClips){
32
32
  if(typeof progress!=='number'||!Number.isFinite(progress))throw Error('Invalid paper action: progress');
33
- const{list,weights}=actionClip(name,info),total=weights.reduce((a,b)=>a+b,0);
33
+ if(compiledClips&&!own(compiledClips,name))throw Error(`Invalid paper action: unknown compiled clip ${JSON.stringify(name)}`);
34
+ const{list,weights,total=weights.reduce((a,b)=>a+b,0)}=compiledClips?.[name]??actionClip(name,info);
34
35
  let t=(name==='walk'?((progress%1)+1)%1:Math.max(0,Math.min(1,progress)))*total,index=0;
35
36
  while(index<list.length-1&&t>weights[index]){t-=weights[index++];}
36
37
  const blend=Math.round(Math.max(0,Math.min(1,t/weights[index]))*8)/8;
37
38
  if(!Number.isFinite(blend))throw Error('Invalid paper action: blend');
38
39
  return{pair:list[index],blend};
39
40
  }
40
- export function createPaperActionSampler(bytes,info,output){
41
+ export function createPaperActionSampler(bytes,info,output,compiledClips){
41
42
  if(!isGrayFrameSource(bytes))throw Error('Invalid action cache: bytes');
42
43
  validatePaperActionInfo(info,bytes.byteLength);
43
44
  const stride=info.width*info.height,pairs=info.pairs,frames=grayFrameReader(bytes,info.width,info.height,pairs*9);
44
45
  const out=output??new Uint8ClampedArray(stride*4);
45
46
  if(!(out instanceof Uint8ClampedArray)||out.length!==stride*4)throw Error('Invalid action output');
46
47
  for(let i=3;i<out.length;i+=4)out[i]=255;
47
- return state=>{const p=paperActionPose(state.actionName,state.actionProgress,info),sample=Math.round(p.blend*8);
48
+ return state=>{const p=paperActionPose(state.actionName,state.actionProgress,info,compiledClips),sample=Math.round(p.blend*8);
48
49
  if(p.pair>=pairs||sample<0||sample>8)throw Error('Invalid paper action: sample out of range');
49
50
  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;};
50
51
  }
@@ -77,6 +77,16 @@ export function createPaperSupportEpisodes(labels,indices,samples,{cyclic=true}=
77
77
  };
78
78
  }
79
79
 
80
+ // Compiled once from the full source schedule, shared by all default renderers.
81
+ // Only the current index/cycle is evaluated here; no stance scan or indexOf.
82
+ export function createBakedSupportEpisodes(track){
83
+ return(feet,pair,blend,phase)=>{
84
+ const offset=track.offsets[pair];if(offset===undefined)return feet;
85
+ const q=offset+Math.round(blend*track.step),index=track.cyclic?q%track.period:Math.min(track.period,q),cycle=track.cyclic?Math.floor(phase)+Math.floor(q/track.period):0;
86
+ return feet.map(foot=>{const start=track.starts[foot.id]?.[index]??-1;return{...foot,episode:`${start}/${cycle-(track.cyclic&&start>index?1:0)}`};});
87
+ };
88
+ }
89
+
80
90
  // Inverse, nearest-pixel leg warp; the entire head/torso above y=225 is identity.
81
91
  // A compact support covers both the old and new toe positions to avoid a second toe.
82
92
  export function footSourcePoint(x,y,pins){
package/puppy-paper.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import {createCachedTween,retimeRun,cleanGroundOffset} from './puppy-clean.mjs';
2
- import {createPaperFootLock,footSourcePoint,createRearSampler,createPaperSupportEpisodes} from './puppy-paper-contact.mjs';
2
+ import {createPaperFootLock,footSourcePoint,createRearSampler,createPaperSupportEpisodes,createBakedSupportEpisodes} from './puppy-paper-contact.mjs';
3
+ import {preparationRoute,canJoinMotion} from './puppy-motion-lookup.mjs';
3
4
  import {createPaperSeatSampler} from './puppy-paper-seat.mjs';
4
5
  import {planPaperRun,planPaperWalk,planPaperCarry,planPaperStumble,planPaperTravel,paperWalkTarget,planPaperApproach,planPaperApproachTravel,PAPER_DYNAMICS,PAPER_GAITS,PAPER_GROUND,paperGroundTarget} from './puppy-paper-world.mjs';
5
6
  import {createPaperDirector} from './puppy-paper-director.mjs';
@@ -37,7 +38,7 @@ export function paperWatchProgress(age){
37
38
  }
38
39
 
39
40
  // Original artwork only. Physics supplies ground travel; no procedural dog is drawn.
40
- export function createPaperActor(animal,info,{manualTricks=false,trickTimings=null,walkTransitions=false,comfortTimings=PAPER_COMFORT_TIMINGS,scrambleSeconds=0,fetchMotions=null}={}){
41
+ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=null,walkTransitions=false,comfortTimings=PAPER_COMFORT_TIMINGS,scrambleSeconds=0,fetchMotions=null,motionTables=null}={}){
41
42
  if(!Number.isFinite(scrambleSeconds)||scrambleSeconds<0||scrambleSeconds>3)throw Error('Invalid scramble duration');
42
43
  if(fetchMotions&&(!Number.isFinite(fetchMotions.scramble?.entryPhase)||fetchMotions.scramble.entryPhase<=0||fetchMotions.scramble.entryPhase>=1||!Number.isFinite(fetchMotions.scramble.brakeSeconds)||fetchMotions.scramble.brakeSeconds<=0||fetchMotions.scramble.brakeSeconds>scrambleSeconds||!['front','rear'].every(v=>Number.isFinite(fetchMotions.pickups?.[v]?.duration)&&fetchMotions.pickups[v].duration>0&&fetchMotions.pickups[v].duration<=3)))throw Error('Invalid fetch actor metadata');
43
44
  if(fetchMotions?.scramble.trajectory==='single-pass-target'){
@@ -110,6 +111,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
110
111
  }
111
112
  if(scrambleArmed&&fetchMotions?.scramble.trajectory==='single-pass-target'&&view===fetchMotions.scramble.entryView&&!waypoints.length){
112
113
  const stumble=planPaperStumble(distance,fetchMotions.scramble);
114
+ if(motionTables&&!canJoinMotion(motionTables,'rear-run/scramble',{view,phase:stumble.entryPhase,speed:stumble.entrySpeed,nextSpeed:stumble.impactAt(0).speed}))throw Error('Unsafe baked run/impact connection');
113
115
  Object.assign(travel,{plan:stumble.plan,stride:stumble.stride,stumble,scrambleAt:stumble.entryTime});
114
116
  }else if(scrambleArmed&&fetchMotions&&view==='front'&&!waypoints.length&&fetchMotions.scramble.trajectory!=='single-pass-target'){
115
117
  // Last matching forepaw contact before the endpoint. Invert the existing
@@ -123,11 +125,16 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
123
125
  const prepareAction=()=>{
124
126
  const name=pendingAction,desired=name==='spin'?'front':'approach';
125
127
  if(restProgress>0){enter('wake');return;}
126
- if(name==='spin'&&seatProgress>0){enter('rise');return;}
127
- if(flip&&view!=='front'){turnView='front';enter('view-turn');return;}
128
- if(flip){turnFlip=false;enter('turn');return;}
129
- if(view!==desired){turnView=view==='front'?desired:'front';enter('view-turn');return;}
130
- if(name!=='spin'&&seatProgress<1){enter('sit-down');return;}
128
+ if(motionTables){
129
+ const [step,target]=preparationRoute(motionTables,'action',{view,flip,seatProgress,desired:name==='spin'?'stand':'seat',speed:motionSpeed});
130
+ if(step!=='ready'){if(step==='view-turn')turnView=target;else if(step==='turn')turnFlip=target;enter(step);return;}
131
+ }else{
132
+ if(name==='spin'&&seatProgress>0){enter('rise');return;}
133
+ if(flip&&view!=='front'){turnView='front';enter('view-turn');return;}
134
+ if(flip){turnFlip=false;enter('turn');return;}
135
+ if(view!==desired){turnView=view==='front'?desired:'front';enter('view-turn');return;}
136
+ if(name!=='spin'&&seatProgress<1){enter('sit-down');return;}
137
+ }
131
138
  // Spin's own cache starts at key24; prepare it once, only AFTER the standing
132
139
  // facing change. Do not crouch before a standing turn and then crouch again.
133
140
  if(walkTransitions&&name==='spin'&&!spinReady){spinReady=true;enter('launch');return;}
@@ -143,7 +150,13 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
143
150
  }
144
151
  playAction(name,()=>finishTrick(name));
145
152
  };
146
- const prepareDirection=()=>{if(pendingAction){prepareAction();return;}if(!target){settle();return;}if(nextView!==view){turnView=view==='front'?nextView:'front';enter('view-turn');}else if(view==='front'&&nextFlip!==flip){turnFlip=nextFlip;enter('turn');}else issue();};
153
+ const prepareDirection=()=>{
154
+ if(pendingAction){prepareAction();return;}if(!target){settle();return;}
155
+ if(motionTables){
156
+ const [step,targetView]=preparationRoute(motionTables,'direction',{view,flip,desired:nextView,nextFlip,speed:motionSpeed});
157
+ if(step==='view-turn'){turnView=targetView;enter(step);}else if(step==='turn'){turnFlip=targetView;enter(step);}else issue();
158
+ }else if(nextView!==view){turnView=view==='front'?nextView:'front';enter('view-turn');}else if(view==='front'&&nextFlip!==flip){turnFlip=nextFlip;enter('turn');}else issue();
159
+ };
147
160
  const requestAction=(name,{urgent=false}={})=>{
148
161
  preparationRate=urgent?3:1;
149
162
  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;}
@@ -333,7 +346,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
333
346
  };
334
347
  }
335
348
 
336
- export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,comfort,contacts=true,pawTransport=false}={}){
349
+ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,comfort,contacts=true,pawTransport=false,motionTables=null}={}){
337
350
  if(![width,height].every(Number.isInteger)||width<2||height<2||width*height>1_000_000)throw Error('Invalid paper grid');
338
351
  const tween=createCachedTween(cache,info),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
339
352
  const out=new Uint8ClampedArray(width*height*4),area=createPaperAreaSampler();
@@ -350,14 +363,15 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
350
363
  const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info,spriteScratch):null;
351
364
  const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info,spriteScratch):null;
352
365
  const comfortSample=comfort?createComfortSpriteSampler(comfort.bytes,comfort.info,spriteScratch):null;
353
- const actionSample=actions?createPaperActionSampler(actions.bytes,actions.info,actionScratch.subarray(0,actions.info.width*actions.info.height*4)):null,actionArea=actions?createPaperAreaSampler(actions.info.width):null;
354
- const walkEpisodes=actions?createPaperSupportEpisodes(actions.info.feet,actions.info.clips.walk,9):null;
366
+ const actionSample=actions?createPaperActionSampler(actions.bytes,actions.info,actionScratch.subarray(0,actions.info.width*actions.info.height*4),motionTables?.actionClips):null,actionArea=actions?createPaperAreaSampler(actions.info.width):null;
367
+ const episodes=(key,labels,indices,samples,options)=>motionTables?.support[key]?createBakedSupportEpisodes(motionTables.support[key]):createPaperSupportEpisodes(labels,indices,samples,options);
368
+ const walkEpisodes=actions?episodes('walk',actions.info.feet,actions.info.clips.walk,9):null;
355
369
  const sourceEpisodes=rear?{
356
- front:createPaperSupportEpisodes(rear.info.frontFeet,[0,1,2,3,4,5,6,7],17),
357
- rear:createPaperSupportEpisodes(rear.info.rearFeet,[5,6,7,8,9,10,11,12],9),
358
- approach:approach?createPaperSupportEpisodes(approach.info.feet,[5,6,7,8,9,10,11,12],9):null,
370
+ front:episodes('front',rear.info.frontFeet,[0,1,2,3,4,5,6,7],17),
371
+ rear:episodes('rear',rear.info.rearFeet,[5,6,7,8,9,10,11,12],9),
372
+ approach:approach?episodes('approach',approach.info.feet,[5,6,7,8,9,10,11,12],9):null,
359
373
  }:null;
360
- const behaviorEpisodes=behaviors?Object.fromEntries(Object.entries(behaviors.info.clips).map(([name,indices])=>[name,createPaperSupportEpisodes(behaviors.info.feet,indices,9,{cyclic:name==='walk'})])):{};
374
+ const behaviorEpisodes=behaviors?Object.fromEntries(Object.entries(behaviors.info.clips).map(([name,indices])=>[name,episodes('behavior/'+name,behaviors.info.feet,indices,9,{cyclic:name==='walk'})])):{};
361
375
  const render=(state,{x=.5,y=.85,size=.85,pixelAspect=1,contactEnabled=contacts,groundFromPixel,pixelFromGround}={})=>{
362
376
  if(![x,y,size].every(Number.isFinite)||size<=0||size>3)throw Error('Invalid paper placement');
363
377
  const isRear=state.rearPair!==null&&state.rearPair!==undefined&&rearSample;
@@ -379,7 +393,7 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
379
393
  feet=motion.feet[k].map(f=>({...f,episode:`carry/${state.comfortPose.cycle}/${f.id}`}));
380
394
  if(state.flip)feet=feet.map(p=>({...p,x:319-p.x}));
381
395
  }else if(contactEnabled&&isAction&&state.actionName==='walk'){
382
- const p=paperActionPose('walk',state.actionProgress,actions.info);feet=walkEpisodes(actions.info.feet[p.pair][Math.round(p.blend*8)],p.pair,p.blend,state.actionProgress);
396
+ const p=paperActionPose('walk',state.actionProgress,actions.info,motionTables?.actionClips);feet=walkEpisodes(actions.info.feet[p.pair][Math.round(p.blend*8)],p.pair,p.blend,state.actionProgress);
383
397
  if(state.flip)feet=feet.map(p=>({...p,x:319-p.x}));
384
398
  }else if(contactEnabled&&rear&&state.state==='run'&&!isAction&&!isComfort){
385
399
  const b=isBehavior?paperBehaviorPose(state,behaviors.info):null;
package/retrieviewer.mjs CHANGED
@@ -5,6 +5,7 @@ import {createSnappyTrickTimings} from './puppy-paper-trick-timing.mjs';
5
5
  import {createCompanionLife,FETCH_RETURN_STYLES} from './puppy-companion-life.mjs';
6
6
  import {paintCompanionObjects} from './puppy-companion-objects.mjs';
7
7
  import {readPackedFrames} from './puppy-frame-store.mjs';
8
+ import {BAKED_MOTION} from './puppy-motion-tables.generated.mjs';
8
9
 
9
10
  // Embeddable autonomous puppy. Importing or creating has no DOM, timer, stdin/stdout
10
11
  // or listener side effects: the host owns the loop and calls step() then frame().
@@ -121,11 +122,14 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
121
122
  let lease=await acquireAssets(loadAsset,artProfile),data=lease.data;
122
123
  const release=lease.release;lease=null;
123
124
  let {cache,info,meta}=data;const assets={...data.assets};data=null;
125
+ // Baked tables describe the bundled Q5 source only. External/optional artwork
126
+ // keeps the general metadata-driven path instead of using an unrelated bake.
127
+ const motionTables=loadAsset===defaultLoadAsset&&artProfile==='q5'?BAKED_MOTION:null;
124
128
  let animal,actor,life;
125
129
  try{
126
130
  if(!assets.actions.info.clips?.['lie-down']||!assets.actions.info.clips?.['lie-up'])throw Error('v9 action cache lacks lie-down/lie-up clips');
127
131
  animal=createPaperBody();
128
- actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),comfortTimings:createPaperComfortTimings(assets.actions.info),walkTransitions:true,scrambleSeconds:assets.comfort.info.scramble?.duration??0,fetchMotions:assets.comfort.info.version>=4?assets.comfort.info:null});
132
+ actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),comfortTimings:createPaperComfortTimings(assets.actions.info),walkTransitions:true,scrambleSeconds:assets.comfort.info.scramble?.duration??0,fetchMotions:assets.comfort.info.version>=4?assets.comfort.info:null,motionTables});
129
133
  life=createCompanionLife(actor,{seed,returnStyle,...energy===undefined?{}:{energy}});
130
134
  }catch(e){animal?.dispose?.();release();throw e;}
131
135
  let paint=null,paintKey='',time=0,acc=0,disposed=false,lastHeldFrameKey=null;
@@ -150,7 +154,7 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
150
154
  if(typeof cellAspect!=='number'||!Number.isFinite(cellAspect)||cellAspect<=0)throw RangeError('cellAspect must be a positive number');
151
155
  if(![12,24,30].includes(poseFps))throw RangeError('poseFps must be 12, 24 or 30');
152
156
  const width=columns*2,height=rows*4,key=`${width}x${height}`;
153
- if(paintKey!==key){paint=createPaperRenderer(cache,info,meta,width,height,{...assets,pawTransport});paintKey=key;}
157
+ if(paintKey!==key){paint=createPaperRenderer(cache,info,meta,width,height,{...assets,pawTransport,motionTables});paintKey=key;}
154
158
  const viewportWidth=camera?.viewportWidth??columns*cellAspect,viewportHeight=camera?.viewportHeight??rows;
155
159
  const projection=createPaperProjection({viewportWidth,viewportHeight,rect:camera?.rect??{left:0,top:0,width:viewportWidth,height:viewportHeight},width,height});
156
160
  const state=actor.sample(time,{poseFps}),report=life.report;