@bitefoundry/retrieviewer 0.0.10 → 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.
- package/ASSET-PROVENANCE.md +7 -0
- package/EMBEDDING.md +6 -0
- package/MOTION-TABLES.md +61 -0
- package/PUPPY-MEMORY.md +32 -0
- package/RELEASING.md +36 -1
- package/WATCH-REDRAW.md +11 -0
- package/integrations/windows-overlay/protocol.mjs +4 -1
- package/package.json +7 -2
- package/pet-q5-manifest.json +45 -6
- package/puppy-companion-life.mjs +8 -5
- package/puppy-motion-lookup.mjs +20 -0
- package/puppy-motion-tables.generated.mjs +4 -0
- package/puppy-paper-actions.mjs +5 -4
- package/puppy-paper-behaviors-q5.bin.gz +0 -0
- package/puppy-paper-contact.mjs +10 -0
- package/puppy-paper.mjs +53 -20
- package/retrieviewer.d.mts +2 -0
- package/retrieviewer.mjs +26 -4
package/ASSET-PROVENANCE.md
CHANGED
|
@@ -95,6 +95,13 @@ puppy sheet, not new photographs. COMFORT-ART.md records the prompt, source hash
|
|
|
95
95
|
and fixed-scale preparation. The ninth archive is covered explicitly by
|
|
96
96
|
ASSETS-LICENSE.md and ATTRIBUTION.md. Original sheet PNGs remain outside npm.
|
|
97
97
|
|
|
98
|
+
Local candidate2026-10-24 changes only offline watch-frame selection: the same
|
|
99
|
+
15 approved whole-body drawings are held, not crossfaded into double contours.
|
|
100
|
+
Source sheet SHA256 and registration stay unchanged; `watchRegistration` now
|
|
101
|
+
records the exact36 source-key indices and `pixelBlending:false`. No new generated
|
|
102
|
+
or photographic input, asset family or attribution is added. Existing CC BY-SA4.0
|
|
103
|
+
terms and all non-watch pixels remain unchanged. See WATCH-REDRAW.md for the A/B.
|
|
104
|
+
|
|
98
105
|
Copyrightability of AI-generated expression, the precise derivative-work analysis,
|
|
99
106
|
and worldwide name clearance are not conclusively determined here. Existing
|
|
100
107
|
domestic trademark research records possible similar marks; owning an npm scope
|
package/EMBEDDING.md
CHANGED
|
@@ -30,6 +30,12 @@ pet.dispose();
|
|
|
30
30
|
- Frame contains text,columns,rows,artProfile,pixels,state,report,placement,
|
|
31
31
|
projection,mouth,contacts. pixels is a **borrowed** RGBA buffer: valid only until
|
|
32
32
|
next frame/dispose; copy if retaining. The wrapper also retains no frame.
|
|
33
|
+
- needsFrame(frameOptions): optional pre-render check for a host that only needs
|
|
34
|
+
changed pictures. False means a known held pose and all visible objects match
|
|
35
|
+
the last frame. Always keep calling step(dt); needs/cooldowns/autonomy continue.
|
|
36
|
+
Moving poses, gestures, visitors and sleep cues conservatively return true.
|
|
37
|
+
frame() itself never skips or returns stale metadata. Force a frame when the
|
|
38
|
+
host loses its display contents (for example after hiding or reconnecting).
|
|
33
39
|
- camera:{viewportWidth,viewportHeight,rect:{left,top,width,height}} places a pane
|
|
34
40
|
within a larger viewport. Keep it consistent across time for metric perspective.
|
|
35
41
|
- interact('ball'|'treat'): false while busy/cooling down; unknown kind throws.
|
package/MOTION-TABLES.md
ADDED
|
@@ -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/PUPPY-MEMORY.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Runtime weight and performance
|
|
2
2
|
|
|
3
|
+
## Held-pose invalidation (local candidate, 2026-10-24)
|
|
4
|
+
|
|
5
|
+
The desktop pump now asks `pet.needsFrame(viewport)` before rasterization. Only
|
|
6
|
+
known held seated/down poses qualify; the key includes their source selection,
|
|
7
|
+
world position, camera/grid and visible objects. Moving/turning poses, gestures,
|
|
8
|
+
butterflies and sleep cues take the normal rendering path. Simulation, needs,
|
|
9
|
+
cooldowns and autonomous decisions still advance. Accepted input, viewport
|
|
10
|
+
revision and pause/resume invalidate immediately. `frame()` itself still paints
|
|
11
|
+
and returns current metadata and the same borrowed-buffer contract. One small
|
|
12
|
+
key is retained, not another raster; no dependency, timer or process is added.
|
|
13
|
+
|
|
14
|
+
Same seed731, 480x135 cells, 450 ticks at30Hz, first throw at tick24, brake return:
|
|
15
|
+
without the hint450 renderer calls, with it300; both publish259 identical native
|
|
16
|
+
frames (transport SHA256 `4069444df1bc5c1a43375ff8f3f9a4cee9bc9965c047ae5f6bfecdcc01721985`).
|
|
17
|
+
During one settled1s offer, full renderer calls fall from30 to0. One local pair
|
|
18
|
+
measured CPU2469ms ->1499ms; timing is a sample, not a universal CPU saving.
|
|
19
|
+
Reproduce with `node tools/benchmark-held-render.mjs`.
|
|
20
|
+
|
|
21
|
+
Offscreen fresh-process diagnostic-GC RSS is effectively unchanged: loaded
|
|
22
|
+
102,940,672 ->102,952,960 bytes; rendered179,982,336 ->180,019,200 bytes.
|
|
23
|
+
The retained ArrayBuffer difference is -19,124 bytes, mainly fewer unique watch
|
|
24
|
+
samples. This is a CPU/allocation-work improvement, NOT a large desktop RAM cut.
|
|
25
|
+
No live native-helper RSS or Linux/macOS performance was measured in this pass.
|
|
26
|
+
|
|
27
|
+
Long-run treat/tug attention now replans a local brake from the current speed:
|
|
28
|
+
`d = v*v/(2*a)`, retaining heading, distance-driven gait phase and existing
|
|
29
|
+
run/walk deceleration. It cancels obsolete waypoints rather than waiting for a
|
|
30
|
+
15m trip to finish. A hand-fed treat appears immediately; recovery at the tested
|
|
31
|
+
rear-running speeds finishes in1.08-1.94s, including turning and sitting.
|
|
32
|
+
Existing roll/tug/walk-entry recovery still reaches connected source endpoints.
|
|
33
|
+
This does not change ball-return variants or multiply physical simulation time.
|
|
34
|
+
|
|
3
35
|
## Spatial autonomy and allocation pass (0.0.10)
|
|
4
36
|
|
|
5
37
|
Autonomous strolls, dashes and butterfly visits no longer end with an unconditional
|
package/RELEASING.md
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
|
-
# Retrieviewer 0.0.
|
|
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)
|
|
20
|
+
|
|
21
|
+
Current target: `@bitefoundry/retrieviewer@0.0.11`, public `latest`.
|
|
22
|
+
On2026-10-24 the owner accepted the reviewed candidate and authorized immediate
|
|
23
|
+
release. This scope removes blended watch contours while retaining all15 approved
|
|
24
|
+
whole-body keys, interrupts treat/tug journeys with velocity-continuous braking,
|
|
25
|
+
and checks held-pose dirtiness before rasterization. Existing fetch patterns and
|
|
26
|
+
all three return choices remain. No new runtime dependency, timer or process.
|
|
27
|
+
RAM is effectively unchanged; this pass reduces repeated CPU/allocation work.
|
|
28
|
+
|
|
29
|
+
Build a fresh exact tarball, install outside the source tree, compare every
|
|
30
|
+
installed byte, check its owned Windows native lifecycle and bilingual browser
|
|
31
|
+
runtime, then publish that exact tarball. Verify registry integrity and GitHub
|
|
32
|
+
Pages runtime bytes afterwards. Do not restart the user's existing companion.
|
|
33
|
+
Earlier npm artifacts remain immutable. MIT code / CC BY-SA4.0 artwork and
|
|
34
|
+
experimental macOS / X11-only Linux boundaries remain unchanged.
|
|
35
|
+
|
|
36
|
+
## Previous 0.0.10 scope (historical)
|
|
2
37
|
|
|
3
38
|
The owner explicitly requested publication and website deployment after reviewing
|
|
4
39
|
the repaired watch/fetch motion and restoring the original braking return as an
|
package/WATCH-REDRAW.md
CHANGED
|
@@ -159,6 +159,17 @@ after accepting candidates; the pending package tarball is obsolete.
|
|
|
159
159
|
|
|
160
160
|
## Local review
|
|
161
161
|
|
|
162
|
+
Local follow-up (2026-10-24): source-key selection replaces the pixel crossfade
|
|
163
|
+
in `prepare-watch-redraw.py`. All15 registered whole-body keys, common scale,
|
|
164
|
+
ground registration,36 logical samples and canonical seat endpoints remain.
|
|
165
|
+
Each sample is now one exact authored key; outward/return paths reuse the same
|
|
166
|
+
bytes instead of drawing two semi-transparent muzzles/shoulders. No new image,
|
|
167
|
+
geometry warp or runtime blend is introduced. Non-watch decoded pixels remain
|
|
168
|
+
byte-exact. The behavior archive drops from1,286,465 to1,270,759 gzip bytes.
|
|
169
|
+
`dist/watch-no-ghost-v1/` preserves the candidate and source-key receipt;
|
|
170
|
+
`git show HEAD:puppy-paper-behaviors-q5.bin.gz` remains the immutable release A/B.
|
|
171
|
+
Automated checks do not replace final owner approval of the new held-key cadence.
|
|
172
|
+
|
|
162
173
|
- `node tools/prepare-watch-redraw.mjs` writes a candidate, not shipped assets.
|
|
163
174
|
- `node tools/check-watch-redraw.mjs` makes a same-state old/new text replay.
|
|
164
175
|
- `node tools/prepare-scramble-art.mjs` writes a candidate, not shipped assets.
|
|
@@ -30,13 +30,16 @@ export function createFramePump({pet,send,now=()=>performance.now()}){
|
|
|
30
30
|
alive();if(inFlight||!viewport||paused)return false;
|
|
31
31
|
const t=now(),dt=last===null?0:Math.max(0,(t-last)/1000);last=t;
|
|
32
32
|
pet.step(dt);if(t<nextPaint)return false;
|
|
33
|
+
// Do this BEFORE rasterization/Braille conversion. Older embedders without
|
|
34
|
+
// the optional hint retain the original byte-comparison fallback below.
|
|
35
|
+
if(lastFrame&&pet.needsFrame?.(viewport)===false)return false;
|
|
33
36
|
const source=pet.frame(viewport),frame=cropFrame(source,revision);
|
|
34
37
|
nextPaint=source.state?.state==='sleep'?t+200:0;
|
|
35
38
|
// A held seat/down pose can be byte-identical for seconds. Advance the life
|
|
36
39
|
// clock normally but avoid JSON, IPC, bitmap allocation and native repaint.
|
|
37
40
|
if(lastFrame&&frame.x===lastFrame.x&&frame.y===lastFrame.y&&frame.columns===lastFrame.columns&&frame.rows===lastFrame.rows&&frame.text===lastFrame.text)return false;
|
|
38
41
|
inFlight=true;
|
|
39
|
-
try{send(frame);lastFrame=frame;}catch(error){inFlight=false;throw error;}return true;
|
|
42
|
+
try{send(frame);lastFrame=frame;}catch(error){inFlight=false;lastFrame=null;nextPaint=0;throw error;}return true;
|
|
40
43
|
},
|
|
41
44
|
interact(kind){alive();if(!['ball','treat','tug'].includes(kind))throw Error('Invalid overlay interaction');const accepted=pet.interact(kind);if(accepted){lastFrame=null;nextPaint=0;}return accepted;},
|
|
42
45
|
get hidden(){return !viewport||paused;},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitefoundry/retrieviewer",
|
|
3
|
-
"version": "0.0.
|
|
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": {
|
package/pet-q5-manifest.json
CHANGED
|
@@ -77,14 +77,14 @@
|
|
|
77
77
|
"width": 320,
|
|
78
78
|
"height": 320,
|
|
79
79
|
"frames": 162,
|
|
80
|
-
"uniqueFrames":
|
|
80
|
+
"uniqueFrames": 126,
|
|
81
81
|
"sourceSha256": "7918261fb64c57a91739729841fd84c6dd53891196c7fb76ee9c27712ab6ef5d",
|
|
82
82
|
"sourcePackedSha256": "7785ef65b8a2a5bf6f7ce600fe9957a60d9af82066ff49275bba061eac5392d1",
|
|
83
|
-
"encodedBytes":
|
|
84
|
-
"gzipBytes":
|
|
85
|
-
"residentPixelBytes":
|
|
86
|
-
"encodedSha256": "
|
|
87
|
-
"quantizedSha256": "
|
|
83
|
+
"encodedBytes": 5797825,
|
|
84
|
+
"gzipBytes": 1270759,
|
|
85
|
+
"residentPixelBytes": 3605128,
|
|
86
|
+
"encodedSha256": "cb045558ca490436c5d6703f6468daba26f20ed76bc886e829787889253ccbb9",
|
|
87
|
+
"quantizedSha256": "eb5007588c9f1843a9279fa21ee701e0f1ac3238d2dda70cc8e8c0c900e3c34b",
|
|
88
88
|
"watchRegistration": {
|
|
89
89
|
"sourceSize": [
|
|
90
90
|
1619,
|
|
@@ -99,6 +99,45 @@
|
|
|
99
99
|
"nonlinearWarp": false,
|
|
100
100
|
"perKeyScale": false,
|
|
101
101
|
"runtimeCompositing": false,
|
|
102
|
+
"pixelBlending": false,
|
|
103
|
+
"sourceKeys": [
|
|
104
|
+
7,
|
|
105
|
+
8,
|
|
106
|
+
9,
|
|
107
|
+
10,
|
|
108
|
+
11,
|
|
109
|
+
11,
|
|
110
|
+
12,
|
|
111
|
+
13,
|
|
112
|
+
14,
|
|
113
|
+
14,
|
|
114
|
+
13,
|
|
115
|
+
12,
|
|
116
|
+
11,
|
|
117
|
+
11,
|
|
118
|
+
10,
|
|
119
|
+
9,
|
|
120
|
+
8,
|
|
121
|
+
7,
|
|
122
|
+
7,
|
|
123
|
+
6,
|
|
124
|
+
5,
|
|
125
|
+
4,
|
|
126
|
+
3,
|
|
127
|
+
3,
|
|
128
|
+
2,
|
|
129
|
+
1,
|
|
130
|
+
0,
|
|
131
|
+
0,
|
|
132
|
+
1,
|
|
133
|
+
2,
|
|
134
|
+
3,
|
|
135
|
+
3,
|
|
136
|
+
4,
|
|
137
|
+
5,
|
|
138
|
+
6,
|
|
139
|
+
7
|
|
140
|
+
],
|
|
102
141
|
"keys": 15,
|
|
103
142
|
"frames": 36,
|
|
104
143
|
"sourceSheetSha256": "34b9bbca507f82c739c39e4b42e2390202863189d6d1b5e6e8ca7293555e19d3",
|
package/puppy-companion-life.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Autonomous companion "life" controller layered over a paper puppy actor.
|
|
2
2
|
// The parent calls actor.update(dt) THEN life.update(dt). Commands are only issued
|
|
3
3
|
// while the actor rests at a shared safe endpoint (idle/down); everything else waits.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Accepted interactions ask the actor for safe interruption: chase for a ball,
|
|
5
|
+
// attend for a hand-fed treat/tug. Substitute actors can use safe-endpoint fallback.
|
|
6
6
|
// Parameters are authored for feel, not canine science.
|
|
7
7
|
import {paperCameraPoint,paperGroundTarget,PAPER_SCREEN,PAPER_CAMERA} from './puppy-paper-world.mjs';
|
|
8
8
|
import {createBallPath,createFetchPatterns} from './puppy-companion-ball.mjs';
|
|
@@ -260,12 +260,15 @@ export function createCompanionLife(actor,{seed=731,energy=.8,returnStyle='auto'
|
|
|
260
260
|
quiet=0;
|
|
261
261
|
if(butterfly||lastEpisode==='butterfly'){
|
|
262
262
|
butterfly=null;queue=[];hold=0;holdIntent=null;gesture=null;waiting=null;
|
|
263
|
-
if(kind!=='ball')actor.sit();
|
|
264
263
|
}
|
|
265
264
|
if(kind==='treat'||kind==='tug'){
|
|
266
265
|
pending=kind;
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
queue=[];hold=0;holdIntent=null;gesture=null;waiting=null;
|
|
267
|
+
// Show the hand now, not several seconds later after the old route ends.
|
|
268
|
+
if(kind==='treat')treat=true;
|
|
269
|
+
if(typeof actor.attend==='function')actor.attend();
|
|
270
|
+
// Backward-compatible substitute actors may only support sleep recovery.
|
|
271
|
+
else if(['sleep','belly'].includes(actor.sample(clock).state))actor.sit();
|
|
269
272
|
return true;
|
|
270
273
|
}
|
|
271
274
|
// Ball: launch now and drop obsolete controller plans; the actor's own state is left to chase.
|
|
@@ -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"}}});
|
package/puppy-paper-actions.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
|
Binary file
|
package/puppy-paper-contact.mjs
CHANGED
|
@@ -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,7 +1,8 @@
|
|
|
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
|
-
import {planPaperRun,planPaperWalk,planPaperCarry,planPaperStumble,paperWalkTarget,planPaperApproach,planPaperApproachTravel,PAPER_GAITS,PAPER_GROUND,paperGroundTarget} from './puppy-paper-world.mjs';
|
|
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';
|
|
6
7
|
import {createPaperBehaviorSampler,paperBehaviorPose} from './puppy-paper-behaviors.mjs';
|
|
7
8
|
import {createPaperAreaSampler} from './puppy-paper-area.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'){
|
|
@@ -97,6 +98,9 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
97
98
|
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');};
|
|
98
99
|
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();};
|
|
99
100
|
const issue=(skipEntry=false)=>{
|
|
101
|
+
// The walk-enter drawing finishes at a shared standing endpoint. A direct
|
|
102
|
+
// interaction queued during it must not start the obsolete walk afterwards.
|
|
103
|
+
if(skipEntry&&deferred){drain();return;}
|
|
100
104
|
if(movementMode==='walk'&&!skipEntry){playAction('walk-enter',()=>issue(true));return;}
|
|
101
105
|
arrived=false;completed=false;travelAge=0;travelDistance=0;const x=animal.position.x,z=animal.position.z;if(straight&&view==='approach')target[0]=x;
|
|
102
106
|
const dx=target[0]-x,dz=target[1]-z,distance=Math.hypot(dx,dz),run=movementMode==='walk'?planPaperWalk(distance):planPaperRun(distance);phase=Math.ceil(phase);
|
|
@@ -107,6 +111,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
107
111
|
}
|
|
108
112
|
if(scrambleArmed&&fetchMotions?.scramble.trajectory==='single-pass-target'&&view===fetchMotions.scramble.entryView&&!waypoints.length){
|
|
109
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');
|
|
110
115
|
Object.assign(travel,{plan:stumble.plan,stride:stumble.stride,stumble,scrambleAt:stumble.entryTime});
|
|
111
116
|
}else if(scrambleArmed&&fetchMotions&&view==='front'&&!waypoints.length&&fetchMotions.scramble.trajectory!=='single-pass-target'){
|
|
112
117
|
// Last matching forepaw contact before the endpoint. Invert the existing
|
|
@@ -120,11 +125,16 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
120
125
|
const prepareAction=()=>{
|
|
121
126
|
const name=pendingAction,desired=name==='spin'?'front':'approach';
|
|
122
127
|
if(restProgress>0){enter('wake');return;}
|
|
123
|
-
if(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
138
|
// Spin's own cache starts at key24; prepare it once, only AFTER the standing
|
|
129
139
|
// facing change. Do not crouch before a standing turn and then crouch again.
|
|
130
140
|
if(walkTransitions&&name==='spin'&&!spinReady){spinReady=true;enter('launch');return;}
|
|
@@ -140,7 +150,13 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
140
150
|
}
|
|
141
151
|
playAction(name,()=>finishTrick(name));
|
|
142
152
|
};
|
|
143
|
-
const prepareDirection=()=>{
|
|
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
|
+
};
|
|
144
160
|
const requestAction=(name,{urgent=false}={})=>{
|
|
145
161
|
preparationRate=urgent?3:1;
|
|
146
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;}
|
|
@@ -182,6 +198,22 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
182
198
|
}
|
|
183
199
|
}
|
|
184
200
|
return{
|
|
201
|
+
// Attention interrupts a journey, not its momentum. Continue the current
|
|
202
|
+
// heading/gait while braking from the actual velocity, then recover to sit.
|
|
203
|
+
// Rolls/tugs still finish their connected authored recovery via dispatch.
|
|
204
|
+
attend(){
|
|
205
|
+
if(state!=='run'||!travel||!target||motionSpeed<1e-8)return dispatch(()=>requestAction('sit',{urgent:true}),{urgent:true});
|
|
206
|
+
const n=Math.hypot(travel.dx,travel.dz),remaining=Math.max(0,travel.plan.distance-(travelDistance-(travel.distanceBase??0)));
|
|
207
|
+
const a=movementMode==='walk'?PAPER_DYNAMICS.walkDeceleration:PAPER_DYNAMICS.deceleration;
|
|
208
|
+
const distance=Math.min(remaining,motionSpeed**2/(2*a));
|
|
209
|
+
if(!n||distance<1e-10)return dispatch(()=>requestAction('sit',{urgent:true}),{urgent:true});
|
|
210
|
+
const x=animal.position.x,z=animal.position.z,dx=travel.dx/n*distance,dz=travel.dz/n*distance;
|
|
211
|
+
const plan=planPaperTravel(distance,{speed:motionSpeed,startSpeed:motionSpeed,deceleration:motionSpeed**2/(2*distance)});
|
|
212
|
+
travel={...travel,x,z,dx,dz,plan,distanceBase:travelDistance,attending:true};
|
|
213
|
+
travelAge=0;target=[x+dx,z+dz];waypoints=[];route=false;deferred=null;
|
|
214
|
+
pendingAction='sit';preparationRate=3;scrambleArmed=false;completed=false;
|
|
215
|
+
return true;
|
|
216
|
+
},
|
|
185
217
|
go(x,z){if(![x,z].every(Number.isFinite))throw Error('Invalid paper target');return dispatch(()=>{route=false;go(x,z);});},
|
|
186
218
|
// Eager attention changes preparation/recovery cadence only. Existing dispatch
|
|
187
219
|
// keeps actions/walks at safe endpoints; root physics and gait are unchanged.
|
|
@@ -210,7 +242,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
210
242
|
let enteredScramble=false;
|
|
211
243
|
if(state==='run'&&travel&&target){
|
|
212
244
|
travelAge+=dt;const p=travel.plan.at(travelAge),ratio=travel.plan.distance?p.distance/travel.plan.distance:1;
|
|
213
|
-
travelDistance=p.distance;motionSpeed=p.speed;motionStage=p.stage;
|
|
245
|
+
travelDistance=(travel.distanceBase??0)+p.distance;motionSpeed=p.speed;motionStage=p.stage;
|
|
214
246
|
const gain=travel.plan.distance?p.speed/travel.plan.distance:0;
|
|
215
247
|
animal.follow({x:travel.x+travel.dx*ratio,y:0,z:travel.z+travel.dz*ratio},{x:travel.dx*gain,y:0,z:travel.dz*gain},{heading:travel.heading});
|
|
216
248
|
arrived=p.done;
|
|
@@ -240,7 +272,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
240
272
|
director.update(dt,state==='run',distance);
|
|
241
273
|
if(state==='run'){
|
|
242
274
|
phase+=distance/(travel.carry&&travelDistance<travel.carry.runDistance?PAPER_GAITS.approach.stride:travel.stride);
|
|
243
|
-
if(target&&arrived){completed=true;stop();target=null;if(movementMode==='walk')playAction('walk-exit',()=>{movementMode='run';enter(walkTransitions?'idle':'settle');drain();});else enter('finish');}
|
|
275
|
+
if(target&&arrived){completed=true;stop();target=null;if(movementMode==='walk')playAction('walk-exit',()=>{movementMode='run';enter(walkTransitions?'idle':'settle');if(pendingAction)prepareDirection();else drain();});else enter('finish');}
|
|
244
276
|
}else if(state==='action'&&age>=activeAction.duration){
|
|
245
277
|
const done=actionDone;activeAction=null;actionDone=null;done();
|
|
246
278
|
}else if(state==='launch'&&age>=launchSeconds()){
|
|
@@ -281,7 +313,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
281
313
|
// This clip is a calibrated standing half-turn, not walk-specific motion.
|
|
282
314
|
// The old turn's intro/outro added an unrelated squat to every reversal.
|
|
283
315
|
if(walkTransitions&&state==='turn'){actionName='walk-turn';actionProgress=Math.min(1,age/turnSeconds());}
|
|
284
|
-
if(view==='approach'&&approachPlan&&(state==='run'||(state==='finish'&&completed))){const p=approachPlan.at(travelDistance);if(state==='finish'){behavior='brake';behaviorProgress=1;}else if(p.clip!=='run'){behavior=p.clip;behaviorProgress=p.progress;}}
|
|
316
|
+
if(view==='approach'&&approachPlan&&(state==='run'||(state==='finish'&&completed))){const p=approachPlan.at(travelDistance);if(state==='finish'&&!travel?.attending){behavior='brake';behaviorProgress=1;}else if(p.clip!=='run'){behavior=p.clip;behaviorProgress=p.progress;}}
|
|
285
317
|
if(view==='approach'&&travel?.carry&&(state==='run'||state==='finish')){
|
|
286
318
|
const p=state==='finish'?{clip:'carry-settle',progress:1}:travel.carry.poseAt(travelDistance);
|
|
287
319
|
if(p.clip!=='run'){comfortPose=p;behavior=null;actionName=null;}
|
|
@@ -314,7 +346,7 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
314
346
|
};
|
|
315
347
|
}
|
|
316
348
|
|
|
317
|
-
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}={}){
|
|
318
350
|
if(![width,height].every(Number.isInteger)||width<2||height<2||width*height>1_000_000)throw Error('Invalid paper grid');
|
|
319
351
|
const tween=createCachedTween(cache,info),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
|
|
320
352
|
const out=new Uint8ClampedArray(width*height*4),area=createPaperAreaSampler();
|
|
@@ -331,14 +363,15 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
|
|
|
331
363
|
const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info,spriteScratch):null;
|
|
332
364
|
const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info,spriteScratch):null;
|
|
333
365
|
const comfortSample=comfort?createComfortSpriteSampler(comfort.bytes,comfort.info,spriteScratch):null;
|
|
334
|
-
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;
|
|
335
|
-
const
|
|
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;
|
|
336
369
|
const sourceEpisodes=rear?{
|
|
337
|
-
front:
|
|
338
|
-
rear:
|
|
339
|
-
approach:approach?
|
|
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,
|
|
340
373
|
}:null;
|
|
341
|
-
const behaviorEpisodes=behaviors?Object.fromEntries(Object.entries(behaviors.info.clips).map(([name,indices])=>[name,
|
|
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'})])):{};
|
|
342
375
|
const render=(state,{x=.5,y=.85,size=.85,pixelAspect=1,contactEnabled=contacts,groundFromPixel,pixelFromGround}={})=>{
|
|
343
376
|
if(![x,y,size].every(Number.isFinite)||size<=0||size>3)throw Error('Invalid paper placement');
|
|
344
377
|
const isRear=state.rearPair!==null&&state.rearPair!==undefined&&rearSample;
|
|
@@ -360,7 +393,7 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
|
|
|
360
393
|
feet=motion.feet[k].map(f=>({...f,episode:`carry/${state.comfortPose.cycle}/${f.id}`}));
|
|
361
394
|
if(state.flip)feet=feet.map(p=>({...p,x:319-p.x}));
|
|
362
395
|
}else if(contactEnabled&&isAction&&state.actionName==='walk'){
|
|
363
|
-
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);
|
|
364
397
|
if(state.flip)feet=feet.map(p=>({...p,x:319-p.x}));
|
|
365
398
|
}else if(contactEnabled&&rear&&state.state==='run'&&!isAction&&!isComfort){
|
|
366
399
|
const b=isBehavior?paperBehaviorPose(state,behaviors.info):null;
|
package/retrieviewer.d.mts
CHANGED
|
@@ -89,6 +89,8 @@ export interface Pet {
|
|
|
89
89
|
/** Seconds. Fixed 60 Hz simulation, at most .1 s catch-up per call. */
|
|
90
90
|
step(dt: number): number;
|
|
91
91
|
frame(options: FrameOptions): PetFrame;
|
|
92
|
+
/** Conservative pre-render check for held poses. Simulation still needs step(). */
|
|
93
|
+
needsFrame(options: FrameOptions): boolean;
|
|
92
94
|
/** False when busy or cooling down. */
|
|
93
95
|
interact(kind: 'ball' | 'treat' | 'tug'): boolean;
|
|
94
96
|
/** Sets the next throw's return style. Does not interrupt a fetch in progress. */
|
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().
|
|
@@ -59,6 +60,20 @@ async function gunzip(bytes){
|
|
|
59
60
|
// Only immutable art/metadata is shared. Each pet still owns its animal, actor,
|
|
60
61
|
// life, renderer and output. Last dispose drops the shared lease, not other pets.
|
|
61
62
|
const sharedAssets=new Map();
|
|
63
|
+
// Conservative pre-render invalidation: only known held source poses qualify.
|
|
64
|
+
// Moving poses, gestures, sleep cues and visitors always take the normal path.
|
|
65
|
+
// One small key, no second pixel buffer or animation cache. Needs/cooldowns are
|
|
66
|
+
// intentionally absent: they still advance, but do not draw anything themselves.
|
|
67
|
+
function heldFrameKey(state,report,{columns,rows,cellAspect=.5,camera,poseFps=24,contactEnabled=true}={}){
|
|
68
|
+
const seated=state.state==='idle'&&state.seatProgress===1&&!state.behavior&&!state.actionName&&!state.comfortPose;
|
|
69
|
+
const down=state.state==='down'&&(state.actionName==='lie-down'&&state.actionProgress===1||state.behavior==='rest');
|
|
70
|
+
if((!seated&&!down)||report.gesture||report.butterfly||report.snore!==null&&report.snore!==undefined)return null;
|
|
71
|
+
return JSON.stringify([columns,rows,cellAspect,poseFps,!!contactEnabled,camera?.viewportWidth,camera?.viewportHeight,
|
|
72
|
+
camera?.rect?.left,camera?.rect?.top,camera?.rect?.width,camera?.rect?.height,
|
|
73
|
+
state.state,state.x,state.z,state.bodyLift??0,state.view,state.flip,state.seatProgress,state.seatVariant,
|
|
74
|
+
state.actionName,down?state.actionProgress:0,state.behavior,state.behaviorProgress,
|
|
75
|
+
report.ball,report.treat]);
|
|
76
|
+
}
|
|
62
77
|
async function loadPetAssets(loadAsset,artProfile){
|
|
63
78
|
// Node keeps filtered crops compressed; only the existing two-frame LRU is
|
|
64
79
|
// expanded. Browser bundles retain the portable bit-packed implementation.
|
|
@@ -107,14 +122,17 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
107
122
|
let lease=await acquireAssets(loadAsset,artProfile),data=lease.data;
|
|
108
123
|
const release=lease.release;lease=null;
|
|
109
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;
|
|
110
128
|
let animal,actor,life;
|
|
111
129
|
try{
|
|
112
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');
|
|
113
131
|
animal=createPaperBody();
|
|
114
|
-
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});
|
|
115
133
|
life=createCompanionLife(actor,{seed,returnStyle,...energy===undefined?{}:{energy}});
|
|
116
134
|
}catch(e){animal?.dispose?.();release();throw e;}
|
|
117
|
-
let paint=null,paintKey='',time=0,acc=0,disposed=false;
|
|
135
|
+
let paint=null,paintKey='',time=0,acc=0,disposed=false,lastHeldFrameKey=null;
|
|
118
136
|
const alive=()=>{if(disposed)throw Error('Pet is disposed');};
|
|
119
137
|
return {
|
|
120
138
|
// Fixed 1/60 s simulation; host deltas are clamped to 0.1 s so a stalled host
|
|
@@ -125,6 +143,9 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
125
143
|
while(acc>=STEP_SECONDS-1e-9){actor.update(STEP_SECONDS);life.update(STEP_SECONDS);time+=STEP_SECONDS;acc=Math.max(0,acc-STEP_SECONDS);n++;}
|
|
126
144
|
return n;
|
|
127
145
|
},
|
|
146
|
+
// Optional host optimization. False only when a known held pose and all
|
|
147
|
+
// visible objects/settings match the last frame; frame() itself never skips.
|
|
148
|
+
needsFrame(options){alive();const key=heldFrameKey(actor.sample(time,{poseFps:options?.poseFps??24}),life.report,options);return key===null||key!==lastHeldFrameKey;},
|
|
128
149
|
// Plain Braille text sized exactly columns x rows (2x4 dots per cell). `pixels`
|
|
129
150
|
// is the renderer's buffer, valid only until the next frame()/dispose().
|
|
130
151
|
frame({columns,rows,cellAspect=.5,camera,poseFps=24,contactEnabled=true}={}){
|
|
@@ -133,7 +154,7 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
133
154
|
if(typeof cellAspect!=='number'||!Number.isFinite(cellAspect)||cellAspect<=0)throw RangeError('cellAspect must be a positive number');
|
|
134
155
|
if(![12,24,30].includes(poseFps))throw RangeError('poseFps must be 12, 24 or 30');
|
|
135
156
|
const width=columns*2,height=rows*4,key=`${width}x${height}`;
|
|
136
|
-
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;}
|
|
137
158
|
const viewportWidth=camera?.viewportWidth??columns*cellAspect,viewportHeight=camera?.viewportHeight??rows;
|
|
138
159
|
const projection=createPaperProjection({viewportWidth,viewportHeight,rect:camera?.rect??{left:0,top:0,width:viewportWidth,height:viewportHeight},width,height});
|
|
139
160
|
const state=actor.sample(time,{poseFps}),report=life.report;
|
|
@@ -141,6 +162,7 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
141
162
|
const placement=projection.placement(state.x,state.z,state.bodyLift??0),pixels=paint(state,{...placement,contactEnabled:!!contactEnabled});
|
|
142
163
|
paintCompanionObjects(pixels,width,height,projection,report,state,paint.lastMouth);
|
|
143
164
|
const {text}=rgbaToBraille(pixels,width,height,{method:'stable',brightness:1.18,contrast:1.1});
|
|
165
|
+
lastHeldFrameKey=heldFrameKey(state,report,{columns,rows,cellAspect,camera,poseFps,contactEnabled});
|
|
144
166
|
return {text,columns,rows,pixels,state,report,placement,projection,artProfile,mouth:paint.lastMouth,contacts:paint.report()};
|
|
145
167
|
},
|
|
146
168
|
// 'ball' | 'treat' | 'tug'. False when busy or cooling down; unknown kinds throw.
|
|
@@ -151,7 +173,7 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
151
173
|
dispose(){
|
|
152
174
|
if(disposed)return;disposed=true;
|
|
153
175
|
try{animal.dispose?.();}finally{
|
|
154
|
-
animal=actor=life=paint=cache=info=meta=null;paintKey='';
|
|
176
|
+
animal=actor=life=paint=cache=info=meta=null;paintKey='';lastHeldFrameKey=null;
|
|
155
177
|
for(const k of BUNDLES)assets[k]=null;
|
|
156
178
|
release();
|
|
157
179
|
}
|