@bitefoundry/retrieviewer 0.0.16 → 0.0.17
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/EMBEDDING.md +15 -9
- package/PUPPY-MEMORY.md +59 -0
- package/README.ko.md +3 -0
- package/README.md +3 -0
- package/RELEASING.md +17 -1
- package/package.json +17 -12
- package/pet-indexed-manifest.json +106 -0
- package/puppy-companion-comfort-q5.rvz +0 -0
- package/puppy-companion-interactions-v3-q5.rvz +0 -0
- package/puppy-frame-file.mjs +15 -0
- package/puppy-frame-indexed.mjs +78 -0
- package/puppy-frame-predictive.mjs +1 -1
- package/puppy-frame-store.mjs +6 -0
- package/puppy-paper-actions-v9-q5.rvz +0 -0
- package/puppy-paper-approach-q5.rvz +0 -0
- package/puppy-paper-arrival-seat-q5.rvz +0 -0
- package/puppy-paper-behaviors-q5.rvz +0 -0
- package/puppy-paper-rear-gallop-q5.rvz +0 -0
- package/puppy-paper-seat-q5.rvz +0 -0
- package/puppy-stride-tweens-q5.rvz +0 -0
- package/retrieviewer.d.mts +1 -1
- package/retrieviewer.mjs +22 -10
- package/puppy-companion-comfort-q5.bin.gz +0 -0
- package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
- package/puppy-paper-actions-v9-q5.bin.gz +0 -0
- package/puppy-paper-approach-q5.bin.gz +0 -0
- package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
- package/puppy-paper-behaviors-q5.bin.gz +0 -0
- package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
- package/puppy-paper-seat-q5.bin.gz +0 -0
- package/puppy-stride-tweens-q5.bin.gz +0 -0
package/EMBEDDING.md
CHANGED
|
@@ -52,10 +52,16 @@ Redirected stdout prints one plain frame and exits.
|
|
|
52
52
|
## Assets / original option
|
|
53
53
|
|
|
54
54
|
Default files resolve against the module URL, never cwd (lazy Node fs or browser
|
|
55
|
-
HTTP fetch). getPetAssetFiles(profile?) returns
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
HTTP fetch). getPetAssetFiles(profile?) returns 19 filenames: nine art archives
|
|
56
|
+
plus ten metadata files. Copy these unchanged for bundlers. The default Q5 files
|
|
57
|
+
use `.rvz`, an index followed by individually gzip-compressed crops. A custom
|
|
58
|
+
loader returns the original ArrayBuffer/typed bytes, without unpacking the archive.
|
|
59
|
+
|
|
60
|
+
Local Node hosts using the default loader read only the indices at creation and
|
|
61
|
+
read/decode requested crops synchronously into a two-frame cache per family.
|
|
62
|
+
There is no background loader or extra process. The final pet's `dispose()` closes
|
|
63
|
+
the shared files. Custom loaders and browsers use the portable memory-backed
|
|
64
|
+
store; they do not receive the local file-backed memory reduction.
|
|
59
65
|
|
|
60
66
|
```js
|
|
61
67
|
import {createPet,getPetAssetFiles} from '@bitefoundry/retrieviewer';
|
|
@@ -74,11 +80,11 @@ They are **not bundled** in the small distribution. For external original art,
|
|
|
74
80
|
copy getPetAssetFiles('original') from the workspace and supply loadAsset. Missing
|
|
75
81
|
originals fail clearly, never silently fall back.
|
|
76
82
|
|
|
77
|
-
Q5 is lossy quantization (max source
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
See [PUPPY-
|
|
83
|
+
Q5 is lossy quantization (max original-source pixel error 4/255). The indexed
|
|
84
|
+
repack is lossless relative to the approved Q5 art: all 2,401 logical frames and
|
|
85
|
+
their timing remain unchanged. The nine indexed archives total 19,326,068 bytes;
|
|
86
|
+
their indices total 54,040 bytes. No Three.js dependency runs in the pet core.
|
|
87
|
+
See [PUPPY-MEMORY.md](./PUPPY-MEMORY.md) for measured costs and host limitations.
|
|
82
88
|
|
|
83
89
|
## JSONL bridge (Python/Rust/other process hosts)
|
|
84
90
|
|
package/PUPPY-MEMORY.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Runtime weight and performance
|
|
2
2
|
|
|
3
|
+
## Indexed local art (local candidate, 2026-10-25)
|
|
4
|
+
|
|
5
|
+
The default local Node loader opens nine RVZ5 files and reads only 54,040 bytes
|
|
6
|
+
of indices (plus 20-byte header probes). Each file contains the exact approved
|
|
7
|
+
RVQ5 filtered rows, individually gzip-compressed offline at level 9. Frame access
|
|
8
|
+
reads and inflates one requested crop into the existing two-frame-per-family
|
|
9
|
+
cache. No artwork family is decompressed/recompressed during startup; no loader
|
|
10
|
+
thread, process, dependency, polling timer or persistent user cache is added.
|
|
11
|
+
Shared pets hold one set of file descriptors. Last disposal closes all nine;
|
|
12
|
+
partial startup failure closes previously opened files. Repeated disposal is safe.
|
|
13
|
+
|
|
14
|
+
All 2,401 logical source frames are pixel-identical, including duplicates and
|
|
15
|
+
random seeks. The indexed repack round-trips to byte-identical RVQ5 archives.
|
|
16
|
+
The 450-frame fetch replay retains the same trajectory and visible SHA256
|
|
17
|
+
`76d5ec6c58622f4330a277852e0d6cb16f4bf2f9fd118688d9ec97b35f80f6a8`.
|
|
18
|
+
Timing, contacts, physics, palettes, art resolution and behavior are unchanged.
|
|
19
|
+
|
|
20
|
+
Fresh-process Windows Node 24 samples, seed 731, 480x135 cells, the existing
|
|
21
|
+
`tools/benchmark-pet.mjs` workload. ArrayBuffers/RSS samples below use diagnostic
|
|
22
|
+
GC after native zlib-close callbacks; these are not desktop memory guarantees.
|
|
23
|
+
|
|
24
|
+
| Metric | Previous | Indexed |
|
|
25
|
+
|---|---:|---:|
|
|
26
|
+
| Initial load | 1,481ms | 17ms |
|
|
27
|
+
| Loaded ArrayBuffers | 21,122,810B | 206,439B |
|
|
28
|
+
| Rendered ArrayBuffers | 27,658,258B | 6,741,887B |
|
|
29
|
+
| Loaded process RSS | 92,504,064B | 66,420,736B |
|
|
30
|
+
| Rendered process RSS | 166,457,344B | 148,267,008B |
|
|
31
|
+
| Peak benchmark process RSS | 207,421,440B | 158,302,208B |
|
|
32
|
+
| Mean frame time | 4.04ms | 4.09ms |
|
|
33
|
+
| P95 frame time | 6.44ms | 6.80ms |
|
|
34
|
+
|
|
35
|
+
This is a RAM/startup tradeoff, not a smaller download: art bytes increase from
|
|
36
|
+
19,095,012 to 19,326,068 (about 1.2%) to allow independent crop reads. The npm
|
|
37
|
+
candidate is about 19.5MB versus 19.3MB; it contains only indexed Q5 art, not
|
|
38
|
+
duplicate legacy archives. Legacy source archives remain for authoring and exact
|
|
39
|
+
regression comparisons. `pet-indexed-manifest.json` describes the shipped format;
|
|
40
|
+
the older `pet-q5-manifest.json` describes its legacy source inputs.
|
|
41
|
+
|
|
42
|
+
Random-access storage adds synchronous small reads during rendering. The local
|
|
43
|
+
sample used OS-cached files; cold disks, network filesystems, antivirus and other
|
|
44
|
+
platforms can have different latency. Frame access validates gzip length/CRC and
|
|
45
|
+
filtered pixels on demand; headers, maps, crop bounds and offsets are validated
|
|
46
|
+
at opening. This differs from the old eager whole-archive pixel validation.
|
|
47
|
+
|
|
48
|
+
Browser/custom-loader compatibility is retained by restoring each indexed family
|
|
49
|
+
into the existing portable store during async creation. Those hosts do not get
|
|
50
|
+
the local file-backed RAM reduction. Actual Aside browser loading, Braille output
|
|
51
|
+
and a ball interaction were checked; no Node file module was requested there.
|
|
52
|
+
An isolated npm-installed Windows native check painted real frames, accepted the
|
|
53
|
+
native ball button and exited worker/painter cleanly. Its short Node sample was
|
|
54
|
+
73MiB RSS, with native-helper memory separate; it is not a peak or long-run budget.
|
|
55
|
+
No live Linux/macOS check was performed for this storage change.
|
|
56
|
+
|
|
57
|
+
Evidence: local `dist/indexed-art-{before,after}.json`,
|
|
58
|
+
`dist/indexed-art-tests.txt`, `dist/package-check-*/package-check.json` and
|
|
59
|
+
`dist/windows-overlay-qa/`. Rebuild/check deterministic archives with
|
|
60
|
+
`npm run bake:art-index` / `npm run check:art-index` in the source checkout.
|
|
61
|
+
|
|
3
62
|
## Held-pose invalidation (local candidate, 2026-10-24)
|
|
4
63
|
|
|
5
64
|
The desktop pump now asks `pet.needsFrame(viewport)` before rasterization. Only
|
package/README.ko.md
CHANGED
|
@@ -108,6 +108,9 @@ npm uninstall -g @bitefoundry/retrieviewer
|
|
|
108
108
|
|
|
109
109
|
## StackBite 알림
|
|
110
110
|
|
|
111
|
+
0.0.17은 기존 그림과 움직임을 유지하고, 기본 로컬 Node 실행에서 필요한 그림만
|
|
112
|
+
파일에서 읽어요. [메모리 측정 기록](PUPPY-MEMORY.md)을 참고하세요.
|
|
113
|
+
|
|
111
114
|
0.0.16은 앞발로 살살 부르기와 두 발로 서서 벽 박박 긁기를 번갈아 보여줘요.
|
|
112
115
|
홈페이지에서도 두 동작을 눌러볼 수 있어요. Windows의 npm 설치형 StackBite도
|
|
113
116
|
감시할 수 있게 했어요. 작업 완료 체크표시는 아직 알림 대상이 아니에요.
|
package/README.md
CHANGED
|
@@ -141,6 +141,9 @@ Version 0.0.15 alerts without waiting for the model, shows the Seat name and
|
|
|
141
141
|
pending kind locally on Windows, and lets the puppy approach before looking
|
|
142
142
|
toward the sidebar. It reuses existing artwork; names never enter model input.
|
|
143
143
|
|
|
144
|
+
Version 0.0.17 keeps the same artwork and motion while loading only requested
|
|
145
|
+
frames from disk in the default local Node host. See [memory measurements](PUPPY-MEMORY.md).
|
|
146
|
+
|
|
144
147
|
Version 0.0.16 adds two approved attention gestures: a gentle seated paw and a
|
|
145
148
|
standing two-paw glass scrabble. They alternate for new alerts; both are playable
|
|
146
149
|
in the website demo. Windows watching also supports npm's PowerShell launcher.
|
package/RELEASING.md
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
# Retrieviewer 0.0.
|
|
1
|
+
# Retrieviewer 0.0.17 release process
|
|
2
|
+
|
|
3
|
+
Current target: `@bitefoundry/retrieviewer@0.0.17`, public `latest`.
|
|
4
|
+
On 2026-10-25 the owner authorized publication of the indexed local-art update
|
|
5
|
+
and measurement of actual operating memory. Local Node hosts read only the art
|
|
6
|
+
indices at startup, then reconstruct requested crops from file-backed archives.
|
|
7
|
+
All 2,401 approved Q5 frames, timing and behavior remain unchanged. The browser
|
|
8
|
+
and custom-loader paths retain the compatible memory-backed representation.
|
|
9
|
+
No new dependency, background loader, process or persistent user cache is added.
|
|
10
|
+
|
|
11
|
+
Publish one fresh installed/native-tested tarball after Windows/Linux CI and
|
|
12
|
+
verify its registry integrity and a fresh registry consumer. Keep existing pets
|
|
13
|
+
running; measure only owned diagnostic windows and report Node and native-helper
|
|
14
|
+
working sets separately. No real approval, paid inference or user-window change
|
|
15
|
+
is needed for the measurement. Preserve all existing registry releases.
|
|
16
|
+
|
|
17
|
+
## Previous 0.0.16 scope (historical)
|
|
2
18
|
|
|
3
19
|
Current target: `@bitefoundry/retrieviewer@0.0.16`, public `latest`.
|
|
4
20
|
On2026-10-24 the owner accepted the gentle-paw and standing-wall gestures and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitefoundry/retrieviewer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
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/",
|
|
@@ -142,26 +142,29 @@
|
|
|
142
142
|
"puppy-continuous.mjs",
|
|
143
143
|
"puppy-zoomies.mjs",
|
|
144
144
|
"puppy-zoomies30.mjs",
|
|
145
|
-
"puppy-stride-tweens-q5.
|
|
145
|
+
"puppy-stride-tweens-q5.rvz",
|
|
146
146
|
"puppy-stride-meta.json",
|
|
147
147
|
"puppy-stable-meta.json",
|
|
148
|
-
"puppy-paper-rear-gallop-q5.
|
|
148
|
+
"puppy-paper-rear-gallop-q5.rvz",
|
|
149
149
|
"puppy-paper-rear-gallop.json",
|
|
150
|
-
"puppy-paper-approach-q5.
|
|
150
|
+
"puppy-paper-approach-q5.rvz",
|
|
151
151
|
"puppy-paper-approach.json",
|
|
152
|
-
"puppy-paper-seat-q5.
|
|
152
|
+
"puppy-paper-seat-q5.rvz",
|
|
153
153
|
"puppy-paper-seat.json",
|
|
154
|
-
"puppy-paper-arrival-seat-q5.
|
|
154
|
+
"puppy-paper-arrival-seat-q5.rvz",
|
|
155
155
|
"puppy-paper-arrival-seat.json",
|
|
156
|
-
"puppy-paper-behaviors-q5.
|
|
156
|
+
"puppy-paper-behaviors-q5.rvz",
|
|
157
157
|
"puppy-paper-behaviors.json",
|
|
158
|
-
"puppy-paper-actions-v9-q5.
|
|
158
|
+
"puppy-paper-actions-v9-q5.rvz",
|
|
159
159
|
"puppy-paper-actions-v9.json",
|
|
160
|
-
"puppy-companion-interactions-v3-q5.
|
|
160
|
+
"puppy-companion-interactions-v3-q5.rvz",
|
|
161
161
|
"puppy-companion-interactions-v3.json",
|
|
162
|
-
"puppy-companion-comfort-q5.
|
|
162
|
+
"puppy-companion-comfort-q5.rvz",
|
|
163
163
|
"puppy-companion-comfort.json",
|
|
164
|
-
"vendor/procedural-animals/LICENSE"
|
|
164
|
+
"vendor/procedural-animals/LICENSE",
|
|
165
|
+
"puppy-frame-indexed.mjs",
|
|
166
|
+
"puppy-frame-file.mjs",
|
|
167
|
+
"pet-indexed-manifest.json"
|
|
165
168
|
],
|
|
166
169
|
"scripts": {
|
|
167
170
|
"bake:motion": "node tools/bake-pet-motion.mjs",
|
|
@@ -173,7 +176,9 @@
|
|
|
173
176
|
"test": "node --test",
|
|
174
177
|
"test:package": "node check-pet-package.mjs",
|
|
175
178
|
"release:check": "npm run check:motion && node tools/check-release.mjs",
|
|
176
|
-
"prepublishOnly": "npm run release:check"
|
|
179
|
+
"prepublishOnly": "npm run release:check",
|
|
180
|
+
"bake:art-index": "node tools/index-pet-art.mjs",
|
|
181
|
+
"check:art-index": "node tools/index-pet-art.mjs --check"
|
|
177
182
|
},
|
|
178
183
|
"dependencies": {
|
|
179
184
|
"jsonc-parser": "3.3.1"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"format": "RVZ5",
|
|
4
|
+
"pixels": "Exact approved RVQ5 rows; no quantization, frame removal or resampling",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"file": "puppy-stride-tweens-q5.rvz",
|
|
8
|
+
"bytes": 7643027,
|
|
9
|
+
"indexBytes": 19608,
|
|
10
|
+
"frames": 799,
|
|
11
|
+
"uniqueFrames": 683,
|
|
12
|
+
"sha256": "e049a3e5dbf2332620d881b8b484b1828dcc7aa3c9e636bf14358b3146458fd9",
|
|
13
|
+
"source": "puppy-stride-tweens-q5.bin.gz",
|
|
14
|
+
"sourceSha256": "abcc6f1825f09655f23e977c56e6790d27edd054c4c24be852c097fb575d6a4f",
|
|
15
|
+
"restoredRvq5Sha256": "eecf92fd402843bef43aa4b154db6eba999959dd16cd8e34c54e42c88eaade10"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"file": "puppy-paper-rear-gallop-q5.rvz",
|
|
19
|
+
"bytes": 1481295,
|
|
20
|
+
"indexBytes": 4100,
|
|
21
|
+
"frames": 162,
|
|
22
|
+
"uniqueFrames": 143,
|
|
23
|
+
"sha256": "fefa5949317e5afde1f5872f8ac43da95206a844cec1d90b27b2f31f32655ddd",
|
|
24
|
+
"source": "puppy-paper-rear-gallop-q5.bin.gz",
|
|
25
|
+
"sourceSha256": "ca731e1979a37ec2e005a80365e3b58358f640b8b26e7036b0e931940a7b1264",
|
|
26
|
+
"restoredRvq5Sha256": "fe96e31d30348cc7d1fc674f40778d2312c9d1b40c57e634dcc397a35cc5ccf4"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"file": "puppy-paper-approach-q5.rvz",
|
|
30
|
+
"bytes": 1462216,
|
|
31
|
+
"indexBytes": 4004,
|
|
32
|
+
"frames": 162,
|
|
33
|
+
"uniqueFrames": 139,
|
|
34
|
+
"sha256": "5f14d0c8f4b2a5a6f59be653cd6a9f3c0450260e0ae4e79074a0da5dc9f66821",
|
|
35
|
+
"source": "puppy-paper-approach-q5.bin.gz",
|
|
36
|
+
"sourceSha256": "17179d3ace7748f66ed3c5a61b5246d24ecd3570b350535bd2edc8cbf58304e6",
|
|
37
|
+
"restoredRvq5Sha256": "6f57a5f506eabea41c00f02738c8124a4ae8e05471bdcd76e0a6e9ed739ca1a3"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"file": "puppy-paper-seat-q5.rvz",
|
|
41
|
+
"bytes": 481353,
|
|
42
|
+
"indexBytes": 1400,
|
|
43
|
+
"frames": 51,
|
|
44
|
+
"uniqueFrames": 49,
|
|
45
|
+
"sha256": "569e943321eaad920f6915c4bd7c60f70571737c57a9407f3268ae7283565c4f",
|
|
46
|
+
"source": "puppy-paper-seat-q5.bin.gz",
|
|
47
|
+
"sourceSha256": "5480c994de0d034d70eff6ea86095f3722cf87bde72f364dd369b94b1ad66f84",
|
|
48
|
+
"restoredRvq5Sha256": "b21aa11039b314ed3dec96818e571d54b6102bd0bc1f76e0a99ceb6d2b5ae097"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"file": "puppy-paper-arrival-seat-q5.rvz",
|
|
52
|
+
"bytes": 492675,
|
|
53
|
+
"indexBytes": 1400,
|
|
54
|
+
"frames": 51,
|
|
55
|
+
"uniqueFrames": 49,
|
|
56
|
+
"sha256": "d69c7d59c99b6f12cebab4e92ec2aa137ba68efb56bb5d37cb52ea1b91d217a2",
|
|
57
|
+
"source": "puppy-paper-arrival-seat-q5.bin.gz",
|
|
58
|
+
"sourceSha256": "7effa0a2150e8ef9ed37d5b6cb10089672e0ce32599c06db21ad4769144bf246",
|
|
59
|
+
"restoredRvq5Sha256": "3b2ef9e322e625340c42a549f17165492cc14f724f4593bf298f67195677bb6e"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"file": "puppy-paper-behaviors-q5.rvz",
|
|
63
|
+
"bytes": 1288500,
|
|
64
|
+
"indexBytes": 3692,
|
|
65
|
+
"frames": 162,
|
|
66
|
+
"uniqueFrames": 126,
|
|
67
|
+
"sha256": "fac739a813a00bfd63294b3926b2463b60a2ab1a1b063b4ae02a053d2f925e8d",
|
|
68
|
+
"source": "puppy-paper-behaviors-q5.bin.gz",
|
|
69
|
+
"sourceSha256": "e4b79387aca974ceec74a02dc3f02780eb53d38af6ec9ebd69994de744b39716",
|
|
70
|
+
"restoredRvq5Sha256": "cb045558ca490436c5d6703f6468daba26f20ed76bc886e829787889253ccbb9"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"file": "puppy-paper-actions-v9-q5.rvz",
|
|
74
|
+
"bytes": 4691230,
|
|
75
|
+
"indexBytes": 13772,
|
|
76
|
+
"frames": 594,
|
|
77
|
+
"uniqueFrames": 474,
|
|
78
|
+
"sha256": "80621dbbdbd5272e9f4a40ac237f1d1f784a01456cdef48f2c10fdd45095f600",
|
|
79
|
+
"source": "puppy-paper-actions-v9-q5.bin.gz",
|
|
80
|
+
"sourceSha256": "abb4677468206c62937c0247dc4c5823e0a7c4a3a197da40194a08dd594aeef9",
|
|
81
|
+
"restoredRvq5Sha256": "615fb04961a38e592a661a20ad50fad45e1e4205e34198fa87a155ace966ea77"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"file": "puppy-companion-interactions-v3-q5.rvz",
|
|
85
|
+
"bytes": 1160887,
|
|
86
|
+
"indexBytes": 4436,
|
|
87
|
+
"frames": 360,
|
|
88
|
+
"uniqueFrames": 124,
|
|
89
|
+
"sha256": "237089d60d0350f08c4570c3379d44a6947d610b59c50856ed5f050d1debf420",
|
|
90
|
+
"source": "puppy-companion-interactions-v3-q5.bin.gz",
|
|
91
|
+
"sourceSha256": "8c7d24254f3f1e9fb85bff5ed1c7904fb4f111b0e47b57eee22dc094c18f3bf3",
|
|
92
|
+
"restoredRvq5Sha256": "5ebfc58c9cfeaf349d30bc65be2e4663f7a3b8be9f35ca5d6da323172624de7a"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"file": "puppy-companion-comfort-q5.rvz",
|
|
96
|
+
"bytes": 624885,
|
|
97
|
+
"indexBytes": 1628,
|
|
98
|
+
"frames": 60,
|
|
99
|
+
"uniqueFrames": 57,
|
|
100
|
+
"sha256": "9df13e0e4c5cd854e42aee595ee675eea716293e392d6e587145ffbaa74c5870",
|
|
101
|
+
"source": "puppy-companion-comfort-q5.bin.gz",
|
|
102
|
+
"sourceSha256": "57942c55e9c467d973a08f1e23804b0f756eaed46dddfe3aba7e7c3621996236",
|
|
103
|
+
"restoredRvq5Sha256": "5f598c2ba15212a73e88c02f0d189b20a74e46f2346f7dfbc3bca8c2d26fc66b"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Loaded only by the default local Node host; browsers never import node:fs.
|
|
2
|
+
import {openSync,readSync,fstatSync,closeSync} from 'node:fs';
|
|
3
|
+
import {gunzipSync} from 'node:zlib';
|
|
4
|
+
import {indexedIndexLength,readIndexedIndex,createIndexedFrameStore} from './puppy-frame-indexed.mjs';
|
|
5
|
+
import {registerFrameStore} from './puppy-frame-store.mjs';
|
|
6
|
+
|
|
7
|
+
export function openFrameFile(url,options){
|
|
8
|
+
const fd=openSync(url,'r');let owned=true;
|
|
9
|
+
try{
|
|
10
|
+
const stat=fstatSync(fd);if(!stat.isFile())throw Error('Invalid frame file');
|
|
11
|
+
const read=(offset,size)=>{const out=new Uint8Array(size);let at=0;while(at<size){const n=readSync(fd,out,at,size-at,offset+at);if(!n)throw Error('Truncated frame file');at+=n;}return out;};
|
|
12
|
+
const header=read(0,20),length=indexedIndexLength(header),index=readIndexedIndex(read(0,length),stat.size);
|
|
13
|
+
const store=registerFrameStore(createIndexedFrameStore(index,{...options,read,expand:(b,n)=>gunzipSync(b,{maxOutputLength:Math.max(1,n)}),close:()=>closeSync(fd)}));owned=false;return store;
|
|
14
|
+
}finally{if(owned)closeSync(fd);}
|
|
15
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// RVZ5: the approved RVQ5 rows, independently gzip-compressed for random access.
|
|
2
|
+
// Header/map match RVQ5; each 24-byte crop record stores x,y,w,h,offset,gzipLength.
|
|
3
|
+
// Offsets are relative to the end of the index. No new quantization or resampling.
|
|
4
|
+
import {unfilterRow} from './puppy-frame-predictive.mjs';
|
|
5
|
+
const HEADER=20,RECORD=24,MAX_PIXELS=16_777_216,MAX_INDEX=64*1024*1024,MAX_EXPANDED=512*1024*1024;
|
|
6
|
+
const fail=why=>{throw Error('Invalid indexed frames: '+why);};
|
|
7
|
+
const viewOf=b=>new DataView(b.buffer,b.byteOffset,b.byteLength);
|
|
8
|
+
export function indexedIndexLength(header){
|
|
9
|
+
if(!(header instanceof Uint8Array)||header.length<HEADER||String.fromCharCode(...header.subarray(0,4))!=='RVZ5')fail('header');
|
|
10
|
+
const v=viewOf(header),width=v.getUint32(4,true),height=v.getUint32(8,true),frames=v.getUint32(12,true),unique=v.getUint32(16,true);
|
|
11
|
+
if(!width||!height||width>16384||height>16384||width*height>MAX_PIXELS||!frames||!unique||unique>frames)fail('shape');
|
|
12
|
+
const length=HEADER+4*frames+RECORD*unique;
|
|
13
|
+
if(length>MAX_INDEX)fail('index size');return length;
|
|
14
|
+
}
|
|
15
|
+
export function readIndexedIndex(bytes,fileSize=bytes.length){
|
|
16
|
+
const length=indexedIndexLength(bytes);if(bytes.length<length||!Number.isSafeInteger(fileSize)||fileSize<length)fail('truncated index');
|
|
17
|
+
const v=viewOf(bytes),width=v.getUint32(4,true),height=v.getUint32(8,true),frameCount=v.getUint32(12,true),uniqueCount=v.getUint32(16,true);
|
|
18
|
+
const map=new Uint32Array(frameCount),used=new Uint8Array(uniqueCount),rects=new Uint32Array(uniqueCount*6);let payload=0,expanded=0;
|
|
19
|
+
for(let i=0;i<frameCount;i++){const u=v.getUint32(HEADER+i*4,true);if(u>=uniqueCount)fail('map');map[i]=u;used[u]=1;}
|
|
20
|
+
if(used.includes(0))fail('unreferenced frame');
|
|
21
|
+
for(let u=0;u<uniqueCount;u++){
|
|
22
|
+
const at=HEADER+frameCount*4+u*RECORD,r=Array.from({length:6},(_,i)=>v.getUint32(at+i*4,true)),[x,y,w,h,offset,size]=r;
|
|
23
|
+
if((w===0)!==(h===0)||(!w&&(x||y))||x+w>width||y+h>height)fail('rectangle');
|
|
24
|
+
if(offset!==payload||size<20)fail('offset');payload+=size;expanded+=h*(w+1);
|
|
25
|
+
if(expanded>MAX_EXPANDED)fail('expanded size');rects.set(r,u*6);
|
|
26
|
+
}
|
|
27
|
+
if(length+payload!==fileSize)fail('file size');
|
|
28
|
+
return {width,height,frameCount,uniqueCount,map,rects,indexBytes:length,expandedBytes:expanded,fileSize};
|
|
29
|
+
}
|
|
30
|
+
const display=Uint8Array.from({length:32},(_,q)=>Math.round(q*255/31));
|
|
31
|
+
|
|
32
|
+
// The host provides bounded positional reads and a synchronous gzip decoder.
|
|
33
|
+
// Only two reconstructed frames survive; compressed art stays on disk.
|
|
34
|
+
export function createIndexedFrameStore(index,{read,expand,close=()=>{},frameIndices}={}){
|
|
35
|
+
if(typeof read!=='function'||typeof expand!=='function')fail('reader');
|
|
36
|
+
const {width,height,frameCount,uniqueCount,map,rects,indexBytes}=index,n=width*height;
|
|
37
|
+
const retained=frameIndices===undefined?null:new Uint8Array(frameCount);
|
|
38
|
+
if(retained){if(!Array.isArray(frameIndices)||!frameIndices.length)fail('retained frames');for(const i of frameIndices){if(!Number.isInteger(i)||i<0||i>=frameCount)fail('retained frame index');retained[i]=1;}}
|
|
39
|
+
const slots=[{unique:-1,frame:null},{unique:-1,frame:null}],cur=new Uint8Array(width),prev=new Uint8Array(width);
|
|
40
|
+
let closed=false,decodes=0,hits=0,diskBytes=0;
|
|
41
|
+
return Object.freeze({width,height,frameCount,uniqueCount,byteLength:n*frameCount,residentPixelBytes:0,
|
|
42
|
+
get residentBytes(){return closed?0:map.byteLength+rects.byteLength+cur.byteLength+prev.byteLength+(retained?.byteLength??0)+slots.reduce((s,e)=>s+(e.frame?.byteLength??0),0);},
|
|
43
|
+
stats(){return {decodes,hits,cachedFrames:slots.filter(s=>s.frame).length,diskReads:decodes,diskBytes,closed};},
|
|
44
|
+
getFrame(i){
|
|
45
|
+
if(closed)throw Error('Frame store is disposed');
|
|
46
|
+
if(!Number.isInteger(i)||i<0||i>=frameCount)fail('frame index');if(retained&&!retained[i])fail('frame was not retained');
|
|
47
|
+
const u=map[i];if(slots[0].unique===u){hits++;return slots[0].frame;}if(slots[1].unique===u){hits++;slots.reverse();return slots[0].frame;}
|
|
48
|
+
const k=u*6,x=rects[k],y=rects[k+1],w=rects[k+2],h=rects[k+3],offset=rects[k+4],size=rects[k+5],expected=h*(w+1);
|
|
49
|
+
const compressed=read(indexBytes+offset,size);if(!(compressed instanceof Uint8Array)||compressed.length!==size)fail('short read');
|
|
50
|
+
const rows=expand(compressed,expected);if(!(rows instanceof Uint8Array)||rows.length!==expected)fail('expanded length');
|
|
51
|
+
// Mark the evicted slot invalid before parsing: a failed decode is never a hit.
|
|
52
|
+
const slot=slots.pop();slots.unshift(slot);slot.unique=-1;const frame=slot.frame??=new Uint8Array(n);frame.fill(0);prev.fill(0);
|
|
53
|
+
for(let r=0,at=0;r<h;r++){
|
|
54
|
+
const type=rows[at++];if(type>4)fail('filter');
|
|
55
|
+
unfilterRow(type,rows,at,cur,prev,w);at+=w;
|
|
56
|
+
for(let c=0;c<w;c++){
|
|
57
|
+
const q=cur[c];if(q>31)fail('quantized value');frame[(y+r)*width+x+c]=display[q];
|
|
58
|
+
}
|
|
59
|
+
prev.set(cur.subarray(0,w));
|
|
60
|
+
}
|
|
61
|
+
slot.unique=u;decodes++;diskBytes+=size;return frame;
|
|
62
|
+
},
|
|
63
|
+
dispose(){if(closed)return;closed=true;for(const slot of slots){slot.frame=null;slot.unique=-1;}close();},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Portable/custom loaders retain the existing in-memory Q5 representation.
|
|
68
|
+
// The default local Node loader instead reads only the index and requested crops.
|
|
69
|
+
export async function expandIndexedArchive(bytes,expand){
|
|
70
|
+
const index=readIndexedIndex(bytes),{frameCount,uniqueCount,rects,indexBytes,expandedBytes}=index;
|
|
71
|
+
const out=new Uint8Array(indexBytes+expandedBytes);out.set(bytes.subarray(0,indexBytes));out.set([82,86,81,53]);const v=viewOf(out);let offset=0;
|
|
72
|
+
for(let u=0;u<uniqueCount;u++){
|
|
73
|
+
const k=u*6,size=rects[k+3]*(rects[k+2]+1),start=indexBytes+rects[k+4],rows=await expand(bytes.subarray(start,start+rects[k+5]),size);
|
|
74
|
+
if(!(rows instanceof Uint8Array)||rows.length!==size)fail('expanded length');
|
|
75
|
+
const at=HEADER+frameCount*4+u*RECORD;v.setUint32(at+16,offset,true);v.setUint32(at+20,size,true);out.set(rows,indexBytes+offset);offset+=size;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
@@ -68,7 +68,7 @@ export function encodePredictiveFrames(rvf1,{filter}={}){
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Exact PNG inverse filter of one row into `cur` (bpp 1, integer arithmetic).
|
|
71
|
-
function unfilterRow(type,src,at,cur,prev,w){
|
|
71
|
+
export function unfilterRow(type,src,at,cur,prev,w){
|
|
72
72
|
switch(type){
|
|
73
73
|
case 0:for(let x=0;x<w;x++)cur[x]=src[at+x];return;
|
|
74
74
|
case 1:for(let x=0;x<w;x++)cur[x]=(src[at+x]+(x?cur[x-1]:0))&255;return;
|
package/puppy-frame-store.mjs
CHANGED
|
@@ -11,6 +11,12 @@ import {isPredictiveFrames,readPredictiveFrames} from './puppy-frame-predictive.
|
|
|
11
11
|
const MAGIC=[0x52,0x56,0x46,0x31],HEADER=20,RECORD=24,MAX_SIDE=16384;
|
|
12
12
|
const stores=new WeakSet();
|
|
13
13
|
|
|
14
|
+
// Indexed local files obey the same borrowed two-frame contract as memory stores.
|
|
15
|
+
export function registerFrameStore(store){
|
|
16
|
+
if(!store||typeof store.getFrame!=='function'||typeof store.stats!=='function'||!Number.isInteger(store.width)||!Number.isInteger(store.height)||!Number.isInteger(store.frameCount))throw Error('Invalid frame store');
|
|
17
|
+
stores.add(store);return store;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
export function isPackedFrames(value){return stores.has(value);}
|
|
15
21
|
export function isGrayFrameSource(value){return value instanceof Uint8Array||stores.has(value);}
|
|
16
22
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/retrieviewer.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** A headless pet: the host owns input, time and terminal rendering. */
|
|
2
2
|
export type FetchReturnStyle = 'auto' | 'brake' | 'trot' | 'run-trot';
|
|
3
3
|
export interface PetOptions {
|
|
4
|
-
/**
|
|
4
|
+
/** Relative names; return unchanged .rvz/.gz file bytes. Custom loaders use memory-backed art. */
|
|
5
5
|
loadAsset?: (name: string) => Promise<ArrayBuffer | ArrayBufferView>;
|
|
6
6
|
seed?: number;
|
|
7
7
|
energy?: number;
|
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 {expandIndexedArchive} from './puppy-frame-indexed.mjs';
|
|
8
9
|
import {BAKED_MOTION} from './puppy-motion-tables.generated.mjs';
|
|
9
10
|
|
|
10
11
|
// Embeddable autonomous puppy. Importing or creating has no DOM, timer, stdin/stdout
|
|
@@ -19,12 +20,12 @@ export const ART_PROFILES=Object.freeze(['original','q5']);
|
|
|
19
20
|
export const PET_ASSETS=Object.freeze({
|
|
20
21
|
// Kept as a legacy identifier only; createPet/getPetAssetFiles no longer load it.
|
|
21
22
|
animal:'locomotion-puppy.animal',
|
|
22
|
-
stride:'puppy-stride-tweens-q5.
|
|
23
|
+
stride:'puppy-stride-tweens-q5.rvz',strideInfo:'puppy-stride-meta.json',stableInfo:'puppy-stable-meta.json',
|
|
23
24
|
rear:'puppy-paper-rear-gallop',approach:'puppy-paper-approach',seat:'puppy-paper-seat',arrivalSeat:'puppy-paper-arrival-seat',
|
|
24
25
|
behaviors:'puppy-paper-behaviors',actions:'puppy-paper-actions-v9',interactions:'puppy-companion-interactions-v3',comfort:'puppy-companion-comfort',
|
|
25
26
|
});
|
|
26
27
|
const BUNDLES=['rear','approach','seat','arrivalSeat','behaviors','actions','interactions','comfort'];
|
|
27
|
-
const frameFile=(base,profile)=>base+(profile==='q5'?'-q5.
|
|
28
|
+
const frameFile=(base,profile)=>base+(profile==='q5'?'-q5.rvz':'-compact.bin.gz');
|
|
28
29
|
/** Exact files a host/bundler must copy, including metadata. Never loads assets. */
|
|
29
30
|
export function getPetAssetFiles(artProfile=DEFAULT_ART_PROFILE){
|
|
30
31
|
if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
|
|
@@ -50,9 +51,9 @@ const bytesOf=(name,value)=>{
|
|
|
50
51
|
if(ArrayBuffer.isView(value))return new Uint8Array(value.buffer,value.byteOffset,value.byteLength);
|
|
51
52
|
throw TypeError(`loadAsset(${name}) must return an ArrayBuffer or typed bytes`);
|
|
52
53
|
};
|
|
53
|
-
async function gunzip(bytes){
|
|
54
|
+
async function gunzip(bytes,size){
|
|
54
55
|
// Native Node gzip avoids Blob/Response/stream copies of every large cache.
|
|
55
|
-
if(typeof process!=='undefined'&&process.versions?.node){const {gunzipSync}=await import('node:zlib');return gunzipSync(bytes);}
|
|
56
|
+
if(typeof process!=='undefined'&&process.versions?.node){const {gunzipSync}=await import('node:zlib');return gunzipSync(bytes,size===undefined?undefined:{maxOutputLength:Math.max(1,size)});}
|
|
56
57
|
if(typeof DecompressionStream!=='undefined')return new Uint8Array(await new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'))).arrayBuffer());
|
|
57
58
|
throw Error('This browser needs DecompressionStream for pet assets');
|
|
58
59
|
}
|
|
@@ -75,8 +76,8 @@ function heldFrameKey(state,report,{columns,rows,cellAspect=.5,camera,poseFps=24
|
|
|
75
76
|
report.ball,report.treat]);
|
|
76
77
|
}
|
|
77
78
|
async function loadPetAssets(loadAsset,artProfile){
|
|
78
|
-
// Node
|
|
79
|
-
//
|
|
79
|
+
// Default local Node hosts keep art on disk and reconstruct two frames per
|
|
80
|
+
// family. Custom/browser loaders retain the compatible memory-backed store.
|
|
80
81
|
let codec;
|
|
81
82
|
if(typeof process!=='undefined'&&process.versions?.node){
|
|
82
83
|
const {deflateRawSync,inflateRawSync}=await import('node:zlib');
|
|
@@ -86,9 +87,18 @@ async function loadPetAssets(loadAsset,artProfile){
|
|
|
86
87
|
}
|
|
87
88
|
const load=async name=>bytesOf(name,await loadAsset(name));
|
|
88
89
|
const json=async name=>JSON.parse(new TextDecoder().decode(await load(name)));
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
let openFrameFile;
|
|
91
|
+
if(artProfile==='q5'&&loadAsset===defaultLoadAsset&&new URL('.',import.meta.url).protocol==='file:'&&typeof process!=='undefined'&&process.versions?.node)({openFrameFile}=await import('./puppy-frame-file.mjs'));
|
|
92
|
+
const frames=async (name,options)=>{
|
|
93
|
+
if(name.endsWith('.rvz')){
|
|
94
|
+
if(openFrameFile)return openFrameFile(new URL(name,import.meta.url),options);
|
|
95
|
+
return readPackedFrames(await expandIndexedArchive(await load(name),gunzip),{...options,codec});
|
|
96
|
+
}
|
|
97
|
+
return readPackedFrames(await gunzip(await load(name)),{...options,codec});
|
|
98
|
+
};
|
|
99
|
+
// Read small indices locally; expand portable archives one family at a time.
|
|
91
100
|
const data={assets:{}};
|
|
101
|
+
try{
|
|
92
102
|
data.info=await json(PET_ASSETS.strideInfo);data.meta=await json(PET_ASSETS.stableInfo);
|
|
93
103
|
data.cache=await frames(frameFile('puppy-stride-tweens',artProfile));
|
|
94
104
|
for(const key of BUNDLES){
|
|
@@ -100,15 +110,17 @@ async function loadPetAssets(loadAsset,artProfile){
|
|
|
100
110
|
data.assets[key]={info,bytes:await frames(frameFile(base,artProfile),options)};
|
|
101
111
|
}
|
|
102
112
|
return data;
|
|
113
|
+
}catch(error){disposeAssets(data);throw error;}
|
|
103
114
|
}
|
|
115
|
+
function disposeAssets(data){data?.cache?.dispose?.();for(const family of Object.values(data?.assets??{}))family.bytes?.dispose?.();}
|
|
104
116
|
async function acquireAssets(loadAsset,artProfile){
|
|
105
117
|
let entry;
|
|
106
118
|
if(loadAsset===defaultLoadAsset){entry=sharedAssets.get(artProfile);if(!entry){entry={users:0,promise:loadPetAssets(loadAsset,artProfile)};sharedAssets.set(artProfile,entry);}}
|
|
107
119
|
else entry={users:0,promise:loadPetAssets(loadAsset,artProfile)};
|
|
108
120
|
entry.users++;
|
|
109
121
|
let released=false;
|
|
110
|
-
const release=()=>{if(released)return;released=true;const previous=entry;entry=null;if(--previous.users===0
|
|
111
|
-
try{
|
|
122
|
+
const release=()=>{if(released)return;released=true;const previous=entry;entry=null;if(--previous.users===0){if(sharedAssets.get(artProfile)===previous)sharedAssets.delete(artProfile);disposeAssets(previous.data);}};
|
|
123
|
+
try{const data=await entry.promise;entry.data=data;return{data,release};}catch(error){release();throw error;}
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTransport=true,artProfile=DEFAULT_ART_PROFILE,returnStyle='auto'}={}){
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|