@bitefoundry/retrieviewer 0.0.16 → 0.0.18
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 +11 -0
- package/ASSETS-LICENSE.md +6 -0
- package/ATTRIBUTION.md +8 -0
- package/DESKTOP-PLATFORMS.md +1 -1
- package/EMBEDDING.md +15 -9
- package/INSTALLING.md +2 -1
- package/LICENSE +16 -16
- package/NATIVE-RUNTIME.md +89 -0
- package/PREREQUISITES.md +7 -0
- package/PUPPY-MEMORY.md +70 -0
- package/README.ko.md +13 -1
- package/README.md +14 -1
- package/RELEASING.md +36 -1
- package/THIRD-PARTY-NOTICES.md +8 -0
- package/WINDOWS-COMPANION.md +15 -6
- package/braille.mjs +2 -2
- package/integrations/native-windows/CMakeLists.txt +15 -0
- package/integrations/native-windows/THIRD-PARTY-LICENSES.txt +77 -0
- package/integrations/native-windows/application.manifest +8 -0
- package/integrations/native-windows/bin/win32-x64/retrieviewer-native.exe +0 -0
- package/integrations/native-windows/desktop.mjs +45 -0
- package/integrations/native-windows/host.cpp +84 -0
- package/integrations/native-windows/manifest.json +14 -0
- package/integrations/native-windows/native-control.hpp +41 -0
- package/integrations/native-windows/native-window.hpp +128 -0
- package/integrations/native-windows/runtime.bundle.mjs +4694 -0
- package/integrations/native-windows/runtime.mjs +55 -0
- package/integrations/native-windows/sources.json +23 -0
- package/integrations/purrviewer/ART.md +289 -0
- package/integrations/purrviewer/ASSETS-LICENSE.md +21 -0
- package/integrations/purrviewer/LICENSE +24 -0
- package/integrations/purrviewer/art/ginger.bin.gz +0 -0
- package/integrations/purrviewer/art/ginger.json +1707 -0
- package/integrations/purrviewer/art/ginger.rvz +0 -0
- package/integrations/purrviewer/src/contact.mjs +63 -0
- package/integrations/purrviewer/src/habitat.mjs +41 -0
- package/integrations/purrviewer/src/life.mjs +280 -0
- package/integrations/purrviewer/src/purrviewer-core.mjs +130 -0
- package/integrations/purrviewer/src/purrviewer.mjs +37 -0
- package/integrations/purrviewer/src/walk-cycle.mjs +3 -0
- package/integrations/purrviewer/src/world.mjs +216 -0
- package/integrations/purrviewer/vendor/retrieviewer/LICENSE-CODE +21 -0
- package/integrations/purrviewer/vendor/retrieviewer/braille.mjs +73 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-file.mjs +15 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-indexed.mjs +78 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-predictive.mjs +170 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-store.mjs +114 -0
- package/integrations/windows-overlay/desktop.mjs +11 -3
- package/integrations/windows-overlay/worker.mjs +9 -2
- package/package.json +53 -13
- package/pet-indexed-manifest.json +106 -0
- package/puppy-clean.mjs +2 -2
- package/puppy-companion-comfort-q5.rvz +0 -0
- package/puppy-companion-interactions-v3-q5.rvz +0 -0
- package/puppy-companion-sprites.mjs +2 -2
- 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-actions.mjs +2 -2
- 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-behaviors.mjs +1 -1
- package/puppy-paper-contact.mjs +2 -2
- package/puppy-paper-rear-gallop-q5.rvz +0 -0
- package/puppy-paper-seat-q5.rvz +0 -0
- package/puppy-paper-seat.mjs +2 -2
- package/puppy-paper.mjs +12 -12
- package/puppy-stride-tweens-q5.rvz +0 -0
- package/retrieviewer-cli.mjs +10 -6
- package/retrieviewer.d.mts +1 -1
- package/retrieviewer.mjs +35 -14
- 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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Lossless packed grayscale frames (RVF1). Exact duplicate frames are stored
|
|
2
|
+
// once and each unique frame keeps only the bounding rectangle of its nonzero
|
|
3
|
+
// pixels; every other pixel is exactly zero. No quantization or resampling.
|
|
4
|
+
//
|
|
5
|
+
// Little-endian layout:
|
|
6
|
+
// 0 'RVF1' 4 u32 width 8 u32 height 12 u32 logicalFrameCount 16 u32 uniqueCount
|
|
7
|
+
// 20 u32[logicalFrameCount] logical -> unique index
|
|
8
|
+
// then uniqueCount records of u32 x,y,w,h,offset,length (offset into pixel data)
|
|
9
|
+
// then the cropped row-major pixels of every unique frame, contiguous in order.
|
|
10
|
+
import {isPredictiveFrames,readPredictiveFrames} from './puppy-frame-predictive.mjs';
|
|
11
|
+
const MAGIC=[0x52,0x56,0x46,0x31],HEADER=20,RECORD=24,MAX_SIDE=16384;
|
|
12
|
+
const stores=new WeakSet();
|
|
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
|
+
|
|
20
|
+
export function isPackedFrames(value){return stores.has(value);}
|
|
21
|
+
export function isGrayFrameSource(value){return value instanceof Uint8Array||stores.has(value);}
|
|
22
|
+
|
|
23
|
+
const fnv=(bytes,start,end)=>{let h=0x811c9dc5;for(let i=start;i<end;i++)h=Math.imul(h^bytes[i],0x01000193);return h>>>0;};
|
|
24
|
+
const sameBytes=(bytes,a,b,n)=>{for(let i=0;i<n;i++)if(bytes[a+i]!==bytes[b+i])return false;return true;};
|
|
25
|
+
|
|
26
|
+
// Pack a legacy concatenated raster (frameCount full width*height frames).
|
|
27
|
+
export function packFrames(raw,width,height,frameCount){
|
|
28
|
+
checkShape(width,height,frameCount);
|
|
29
|
+
const n=width*height;
|
|
30
|
+
if(!(raw instanceof Uint8Array)||raw.length!==n*frameCount)throw Error('Invalid gray frames: byte length');
|
|
31
|
+
const map=new Uint32Array(frameCount),firsts=[],rects=[],buckets=new Map();let total=0;
|
|
32
|
+
for(let f=0;f<frameCount;f++){
|
|
33
|
+
const start=f*n,h=fnv(raw,start,start+n),bucket=buckets.get(h);
|
|
34
|
+
const hit=bucket?.find(u=>sameBytes(raw,firsts[u]*n,start,n));
|
|
35
|
+
if(hit!==undefined){map[f]=hit;continue;}
|
|
36
|
+
let x0=width,y0=height,x1=-1,y1=-1;
|
|
37
|
+
for(let y=0;y<height;y++){const row=start+y*width;for(let x=0;x<width;x++)if(raw[row+x]){if(x<x0)x0=x;if(x>x1)x1=x;if(y<y0)y0=y;y1=y;}}
|
|
38
|
+
const rect=x1<0?{x:0,y:0,w:0,h:0}:{x:x0,y:y0,w:x1-x0+1,h:y1-y0+1};
|
|
39
|
+
rect.offset=total;rect.length=rect.w*rect.h;total+=rect.length;
|
|
40
|
+
map[f]=firsts.length;if(bucket)bucket.push(firsts.length);else buckets.set(h,[firsts.length]);firsts.push(f);rects.push(rect);
|
|
41
|
+
}
|
|
42
|
+
const unique=firsts.length,data=HEADER+4*frameCount+RECORD*unique,out=new Uint8Array(data+total),view=new DataView(out.buffer);
|
|
43
|
+
out.set(MAGIC,0);[width,height,frameCount,unique].forEach((v,i)=>view.setUint32(4+4*i,v,true));
|
|
44
|
+
for(let f=0;f<frameCount;f++)view.setUint32(HEADER+4*f,map[f],true);
|
|
45
|
+
rects.forEach((r,u)=>{
|
|
46
|
+
const at=HEADER+4*frameCount+RECORD*u;[r.x,r.y,r.w,r.h,r.offset,r.length].forEach((v,i)=>view.setUint32(at+4*i,v,true));
|
|
47
|
+
const start=firsts[u]*n;for(let y=0;y<r.h;y++){const src=start+(r.y+y)*width+r.x;out.set(raw.subarray(src,src+r.w),data+r.offset+y*r.w);}
|
|
48
|
+
});
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkShape(width,height,frameCount){
|
|
53
|
+
if(!Number.isInteger(width)||!Number.isInteger(height)||width<1||height<1||width>MAX_SIDE||height>MAX_SIDE)throw Error('Invalid gray frames: dimensions');
|
|
54
|
+
if(!Number.isInteger(frameCount)||frameCount<1||frameCount>0xffffffff||!Number.isSafeInteger(width*height*frameCount))throw Error('Invalid gray frames: frame count');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Parse and fully validate an RVF1 store. The returned store keeps `bytes`
|
|
58
|
+
// resident (crops only) and reconstructs at most two full frames at a time.
|
|
59
|
+
// A returned frame is valid until two other unique frames have been requested.
|
|
60
|
+
// An 'RVQ5' store (lossy 32-level comparison codec) is delegated and branded.
|
|
61
|
+
export function readPackedFrames(bytes,options){
|
|
62
|
+
if(!(bytes instanceof Uint8Array))throw Error('Invalid packed frames: bytes');
|
|
63
|
+
if(isPredictiveFrames(bytes)){const store=readPredictiveFrames(bytes,options);stores.add(store);return store;}
|
|
64
|
+
if(bytes.length<HEADER||MAGIC.some((v,i)=>bytes[i]!==v))throw Error('Invalid packed frames: magic');
|
|
65
|
+
const view=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength),u32=at=>view.getUint32(at,true);
|
|
66
|
+
const width=u32(4),height=u32(8),frameCount=u32(12),unique=u32(16);
|
|
67
|
+
try{checkShape(width,height,frameCount);}catch{throw Error('Invalid packed frames: header');}
|
|
68
|
+
if(unique<1||unique>frameCount)throw Error('Invalid packed frames: unique count');
|
|
69
|
+
const data=HEADER+4*frameCount+RECORD*unique,n=width*height;
|
|
70
|
+
if(bytes.length<data)throw Error('Invalid packed frames: truncated');
|
|
71
|
+
const map=new Uint32Array(frameCount),used=new Uint8Array(unique);
|
|
72
|
+
for(let f=0;f<frameCount;f++){const u=u32(HEADER+4*f);if(u>=unique)throw Error('Invalid packed frames: map');map[f]=u;used[u]=1;}
|
|
73
|
+
if(used.includes(0))throw Error('Invalid packed frames: unreferenced frame');
|
|
74
|
+
const rects=new Uint32Array(unique*6);let total=0;
|
|
75
|
+
for(let u=0;u<unique;u++){
|
|
76
|
+
const at=HEADER+4*frameCount+RECORD*u,x=u32(at),y=u32(at+4),w=u32(at+8),h=u32(at+12),offset=u32(at+16),length=u32(at+20);
|
|
77
|
+
if((w===0)!==(h===0)||(w===0&&(x||y))||x+w>width||y+h>height)throw Error('Invalid packed frames: rectangle');
|
|
78
|
+
if(length!==w*h||offset!==total)throw Error('Invalid packed frames: offset');
|
|
79
|
+
rects.set([x,y,w,h,offset,length],u*6);total+=length;
|
|
80
|
+
}
|
|
81
|
+
if(bytes.length!==data+total)throw Error('Invalid packed frames: pixel length');
|
|
82
|
+
const pixels=bytes.subarray(data),slots=[{unique:-1,frame:null},{unique:-1,frame:null}];let decodes=0,hits=0;
|
|
83
|
+
const store=Object.freeze({
|
|
84
|
+
width,height,frameCount,uniqueCount:unique,byteLength:n*frameCount,
|
|
85
|
+
get residentBytes(){return bytes.byteLength+map.byteLength+rects.byteLength+slots.reduce((s,e)=>s+(e.frame?.byteLength??0),0);},
|
|
86
|
+
stats(){return{decodes,hits,cachedFrames:slots.filter(e=>e.frame).length};},
|
|
87
|
+
getFrame(index){
|
|
88
|
+
if(!Number.isInteger(index)||index<0||index>=frameCount)throw Error('Invalid packed frames: frame index');
|
|
89
|
+
const u=map[index];
|
|
90
|
+
if(slots[0].unique===u){hits++;return slots[0].frame;}
|
|
91
|
+
if(slots[1].unique===u){hits++;slots.reverse();return slots[0].frame;}
|
|
92
|
+
const slot=slots.pop();slots.unshift(slot);decodes++;
|
|
93
|
+
const frame=slot.frame??=new Uint8Array(n),k=u*6,x=rects[k],y=rects[k+1],w=rects[k+2],h=rects[k+3],offset=rects[k+4];
|
|
94
|
+
frame.fill(0);slot.unique=u;
|
|
95
|
+
for(let r=0;r<h;r++)frame.set(pixels.subarray(offset+r*w,offset+(r+1)*w),(y+r)*width+x);
|
|
96
|
+
return frame;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
stores.add(store);return store;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Uniform frame access over a legacy concatenated Uint8Array or a packed store.
|
|
103
|
+
export function grayFrameReader(source,width,height,frameCount){
|
|
104
|
+
checkShape(width,height,frameCount);
|
|
105
|
+
const n=width*height,byteLength=n*frameCount;
|
|
106
|
+
const check=index=>{if(!Number.isInteger(index)||index<0||index>=frameCount)throw Error('Invalid gray frames: frame index');};
|
|
107
|
+
if(stores.has(source)){
|
|
108
|
+
if(source.width!==width||source.height!==height||source.frameCount!==frameCount)throw Error('Invalid gray frames: store shape');
|
|
109
|
+
return{width,height,frameCount,byteLength,getFrame(index){check(index);return source.getFrame(index);}};
|
|
110
|
+
}
|
|
111
|
+
if(!(source instanceof Uint8Array))throw Error('Invalid gray frames: source');
|
|
112
|
+
if(source.length!==byteLength)throw Error('Invalid gray frames: byte length');
|
|
113
|
+
return{width,height,frameCount,byteLength,getFrame(index){check(index);return source.subarray(index*n,(index+1)*n);}};
|
|
114
|
+
}
|
|
@@ -93,6 +93,8 @@ export async function desktopHelper(options={}){
|
|
|
93
93
|
throw Error('Linux desktop needs system Python3.6+ (AlmaLinux8 platform-python is supported). No packages or system settings were installed.');
|
|
94
94
|
}
|
|
95
95
|
export async function startDesktop(options={}){
|
|
96
|
+
const pet=options.pet??'dog';if(!['dog','cat'].includes(pet))throw Error('Invalid desktop pet');
|
|
97
|
+
const engine=options.engine??'auto';if(!['auto','native','node'].includes(engine))throw Error('Invalid desktop engine');
|
|
96
98
|
const watch=options.watch??'off';if(!['off','stackbite'].includes(watch))throw Error('Invalid desktop watch source');
|
|
97
99
|
const brain=options.brain??'off';if(!['off','openrouter'].includes(brain))throw Error('Invalid desktop brain');
|
|
98
100
|
if(brain==='openrouter'){
|
|
@@ -100,14 +102,20 @@ export async function startDesktop(options={}){
|
|
|
100
102
|
const {createOpenRouterBrain}=await import('../openrouter/brain.mjs');createOpenRouterBrain().dispose();
|
|
101
103
|
}
|
|
102
104
|
if(watch==='stackbite'){const {stackBiteBinding}=await import('../stackbite/attention.mjs');stackBiteBinding();}
|
|
103
|
-
const ink=options.ink??'cream';if(!['cream','gold','dark'].includes(ink))throw Error('Invalid desktop ink');
|
|
105
|
+
const ink=options.ink??(pet==='cat'?'gold':'cream');if(!['cream','gold','dark'].includes(ink))throw Error('Invalid desktop ink');
|
|
104
106
|
const returnStyle=options.returnStyle??'auto';if(!['auto','brake','trot','run-trot'].includes(returnStyle))throw Error('Invalid return style');
|
|
105
107
|
// Fail before creating any cache/session on unsupported desktops (e.g. Wayland).
|
|
106
108
|
desktopPlatform(options);
|
|
107
|
-
const
|
|
109
|
+
const native=process.platform==='win32'&&process.arch==='x64'&&engine!=='node'&&watch==='off'&&brain==='off';
|
|
110
|
+
if(engine==='native'&&!native)throw Error('Native mode requires Windows x64 with watch/brain off; use --engine node for compatibility features');
|
|
111
|
+
const status=await desktopStatus(options);if(status.running){
|
|
112
|
+
if(native&&status.engine==='quickjs-native'&&status.pet!==pet){const {switchNativePet}=await import('../native-windows/desktop.mjs');return switchNativePet(options,await readSession(options),pet);}
|
|
113
|
+
return {...status,alreadyRunning:true,...(status.pet??'dog')!==pet?{requestedPet:pet,restartRequired:true}:{},...status.returnStyle!==returnStyle?{requestedReturnStyle:returnStyle,restartRequired:true}:{},...(status.watch?.source??'off')!==watch?{requestedWatch:watch,restartRequired:true}:{},...(status.brain?.provider??'off')!==brain?{requestedBrain:brain,restartRequired:true}:{}};
|
|
114
|
+
}
|
|
108
115
|
if(status.stale)await stopDesktop(options);
|
|
116
|
+
if(native){const {startNativeDesktop}=await import('../native-windows/desktop.mjs');return startNativeDesktop(options);}
|
|
109
117
|
const helper=await desktopHelper(options);
|
|
110
|
-
const workerEnv={...process.env,RETRIEVIEWER_HOME:getConfigPaths(options).root,RETRIEVIEWER_INK:ink,RETRIEVIEWER_RETURN_STYLE:returnStyle,RETRIEVIEWER_WATCH:watch,RETRIEVIEWER_BRAIN:brain};
|
|
118
|
+
const workerEnv={...process.env,RETRIEVIEWER_HOME:getConfigPaths(options).root,RETRIEVIEWER_PET:pet,RETRIEVIEWER_INK:ink,RETRIEVIEWER_RETURN_STYLE:returnStyle,RETRIEVIEWER_WATCH:watch,RETRIEVIEWER_BRAIN:brain};
|
|
111
119
|
if(brain==='off')delete workerEnv.OPENROUTER_API_KEY;
|
|
112
120
|
return new Promise((resolve,reject)=>{
|
|
113
121
|
const child=spawn(process.execPath,[fileURLToPath(new URL('./worker.mjs',import.meta.url)),'--helper-spec',JSON.stringify(helper)],{detached:true,windowsHide:true,stdio:['ignore','ignore','ignore','ipc'],env:workerEnv});
|
|
@@ -14,6 +14,7 @@ let attention=null,brain=null,attentionLabel=null;
|
|
|
14
14
|
const returnStyle=process.env.RETRIEVIEWER_RETURN_STYLE??'auto';
|
|
15
15
|
const watchSource=process.env.RETRIEVIEWER_WATCH??'off';
|
|
16
16
|
const brainSource=process.env.RETRIEVIEWER_BRAIN??'off';
|
|
17
|
+
const petKind=process.env.RETRIEVIEWER_PET??'dog';
|
|
17
18
|
let nativeStderr='';
|
|
18
19
|
const sendParent=message=>{if(process.connected)try{process.send(message,error=>{if(error&&!detached)stop();});}catch{if(!detached)stop();}};
|
|
19
20
|
const starting=()=>{if(stopping||(!process.connected&&!detached))throw Error('Desktop launcher closed during startup');};
|
|
@@ -44,7 +45,7 @@ try{
|
|
|
44
45
|
body+=bytes.toString('utf8');if(body.length>2048)return socket.destroy();if(!body.includes('\n'))return;
|
|
45
46
|
let command;try{command=JSON.parse(body.split('\n')[0]);}catch{return socket.destroy();}
|
|
46
47
|
if(command.token!==session.token||!['status','stop'].includes(command.command))return socket.destroy();
|
|
47
|
-
socket.end(JSON.stringify(command.command==='stop'?{stopped:true}:{running:true,reachable:true,selected,visible,pickerVisible,frames,paintedFrames,lastInteraction,returnStyle,attentionLabel,watch:{source:watchSource,...attention?.status??{state:'off',pendingCount:0}},brain:brain?.status??{provider:'off',state:'off'},pid:process.pid,painterPid:helper?.pid,rssBytes:process.memoryUsage().rss})+'\n');
|
|
48
|
+
socket.end(JSON.stringify(command.command==='stop'?{stopped:true}:{running:true,reachable:true,pet:petKind,engine:"node-compat",selected,visible,pickerVisible,frames,paintedFrames,lastInteraction,returnStyle,attentionLabel,watch:{source:watchSource,...attention?.status??{state:'off',pendingCount:0}},brain:brain?.status??{provider:'off',state:'off'},pid:process.pid,painterPid:helper?.pid,rssBytes:process.memoryUsage().rss})+'\n');
|
|
48
49
|
if(command.command==='stop')stop();
|
|
49
50
|
});
|
|
50
51
|
});
|
|
@@ -72,7 +73,13 @@ try{
|
|
|
72
73
|
else if(m.type==='quit')stop();
|
|
73
74
|
}catch(error){write({type:'error',message:error.message});stop();}
|
|
74
75
|
});
|
|
75
|
-
starting();
|
|
76
|
+
starting();let pet;
|
|
77
|
+
if(petKind==='cat'){
|
|
78
|
+
const {createCat}=await import('../purrviewer/src/purrviewer.mjs');starting();const cat=await createCat();
|
|
79
|
+
pet={step:dt=>cat.step(dt),frame:o=>cat.frame(o),dispose:()=>cat.dispose(),
|
|
80
|
+
interact:kind=>cat.interact(({ball:'toy',tug:'company'})[kind]??kind),
|
|
81
|
+
notify:()=>cat.interact('greet'),clearNotice:()=>{}};
|
|
82
|
+
}else {const {createPet}=await import('../../retrieviewer.mjs');starting();pet=await createPet({returnStyle});}
|
|
76
83
|
if(stopping)pet.dispose();else{
|
|
77
84
|
pump=createFramePump({pet,send:write});if(pendingViewport)pump.viewport(pendingViewport);
|
|
78
85
|
if(!['off','stackbite'].includes(watchSource))throw Error('Invalid desktop watch source');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitefoundry/retrieviewer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
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/",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
},
|
|
58
58
|
"./tui": "./integrations/opencode/tui.tsx",
|
|
59
59
|
"./opencode/tui": "./integrations/opencode/tui.tsx",
|
|
60
|
-
"./package.json": "./package.json"
|
|
60
|
+
"./package.json": "./package.json",
|
|
61
|
+
"./cat": "./integrations/purrviewer/src/purrviewer.mjs"
|
|
61
62
|
},
|
|
62
63
|
"files": [
|
|
63
64
|
"LICENSE",
|
|
@@ -142,26 +143,61 @@
|
|
|
142
143
|
"puppy-continuous.mjs",
|
|
143
144
|
"puppy-zoomies.mjs",
|
|
144
145
|
"puppy-zoomies30.mjs",
|
|
145
|
-
"puppy-stride-tweens-q5.
|
|
146
|
+
"puppy-stride-tweens-q5.rvz",
|
|
146
147
|
"puppy-stride-meta.json",
|
|
147
148
|
"puppy-stable-meta.json",
|
|
148
|
-
"puppy-paper-rear-gallop-q5.
|
|
149
|
+
"puppy-paper-rear-gallop-q5.rvz",
|
|
149
150
|
"puppy-paper-rear-gallop.json",
|
|
150
|
-
"puppy-paper-approach-q5.
|
|
151
|
+
"puppy-paper-approach-q5.rvz",
|
|
151
152
|
"puppy-paper-approach.json",
|
|
152
|
-
"puppy-paper-seat-q5.
|
|
153
|
+
"puppy-paper-seat-q5.rvz",
|
|
153
154
|
"puppy-paper-seat.json",
|
|
154
|
-
"puppy-paper-arrival-seat-q5.
|
|
155
|
+
"puppy-paper-arrival-seat-q5.rvz",
|
|
155
156
|
"puppy-paper-arrival-seat.json",
|
|
156
|
-
"puppy-paper-behaviors-q5.
|
|
157
|
+
"puppy-paper-behaviors-q5.rvz",
|
|
157
158
|
"puppy-paper-behaviors.json",
|
|
158
|
-
"puppy-paper-actions-v9-q5.
|
|
159
|
+
"puppy-paper-actions-v9-q5.rvz",
|
|
159
160
|
"puppy-paper-actions-v9.json",
|
|
160
|
-
"puppy-companion-interactions-v3-q5.
|
|
161
|
+
"puppy-companion-interactions-v3-q5.rvz",
|
|
161
162
|
"puppy-companion-interactions-v3.json",
|
|
162
|
-
"puppy-companion-comfort-q5.
|
|
163
|
+
"puppy-companion-comfort-q5.rvz",
|
|
163
164
|
"puppy-companion-comfort.json",
|
|
164
|
-
"vendor/procedural-animals/LICENSE"
|
|
165
|
+
"vendor/procedural-animals/LICENSE",
|
|
166
|
+
"puppy-frame-indexed.mjs",
|
|
167
|
+
"puppy-frame-file.mjs",
|
|
168
|
+
"pet-indexed-manifest.json",
|
|
169
|
+
"integrations/native-windows/desktop.mjs",
|
|
170
|
+
"integrations/native-windows/runtime.mjs",
|
|
171
|
+
"integrations/native-windows/runtime.bundle.mjs",
|
|
172
|
+
"integrations/native-windows/manifest.json",
|
|
173
|
+
"integrations/native-windows/bin/win32-x64/retrieviewer-native.exe",
|
|
174
|
+
"integrations/native-windows/host.cpp",
|
|
175
|
+
"integrations/native-windows/native-window.hpp",
|
|
176
|
+
"integrations/native-windows/native-control.hpp",
|
|
177
|
+
"integrations/native-windows/CMakeLists.txt",
|
|
178
|
+
"integrations/native-windows/sources.json",
|
|
179
|
+
"integrations/native-windows/THIRD-PARTY-LICENSES.txt",
|
|
180
|
+
"NATIVE-RUNTIME.md",
|
|
181
|
+
"integrations/purrviewer/ART.md",
|
|
182
|
+
"integrations/purrviewer/ASSETS-LICENSE.md",
|
|
183
|
+
"integrations/purrviewer/LICENSE",
|
|
184
|
+
"integrations/purrviewer/art/ginger.bin.gz",
|
|
185
|
+
"integrations/purrviewer/art/ginger.json",
|
|
186
|
+
"integrations/purrviewer/art/ginger.rvz",
|
|
187
|
+
"integrations/purrviewer/src/contact.mjs",
|
|
188
|
+
"integrations/purrviewer/src/habitat.mjs",
|
|
189
|
+
"integrations/purrviewer/src/life.mjs",
|
|
190
|
+
"integrations/purrviewer/src/purrviewer-core.mjs",
|
|
191
|
+
"integrations/purrviewer/src/purrviewer.mjs",
|
|
192
|
+
"integrations/purrviewer/src/walk-cycle.mjs",
|
|
193
|
+
"integrations/purrviewer/src/world.mjs",
|
|
194
|
+
"integrations/purrviewer/vendor/retrieviewer/braille.mjs",
|
|
195
|
+
"integrations/purrviewer/vendor/retrieviewer/LICENSE-CODE",
|
|
196
|
+
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-predictive.mjs",
|
|
197
|
+
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-store.mjs",
|
|
198
|
+
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-file.mjs",
|
|
199
|
+
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-indexed.mjs",
|
|
200
|
+
"integrations/native-windows/application.manifest"
|
|
165
201
|
],
|
|
166
202
|
"scripts": {
|
|
167
203
|
"bake:motion": "node tools/bake-pet-motion.mjs",
|
|
@@ -173,7 +209,11 @@
|
|
|
173
209
|
"test": "node --test",
|
|
174
210
|
"test:package": "node check-pet-package.mjs",
|
|
175
211
|
"release:check": "npm run check:motion && node tools/check-release.mjs",
|
|
176
|
-
"prepublishOnly": "npm run release:check"
|
|
212
|
+
"prepublishOnly": "npm run release:check",
|
|
213
|
+
"bake:art-index": "node tools/index-pet-art.mjs",
|
|
214
|
+
"check:art-index": "node tools/index-pet-art.mjs --check",
|
|
215
|
+
"build:native": "node tools/build-native-windows.mjs",
|
|
216
|
+
"check:native-pixels": "node tools/check-native-pixels.mjs"
|
|
177
217
|
},
|
|
178
218
|
"dependencies": {
|
|
179
219
|
"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
|
+
}
|
package/puppy-clean.mjs
CHANGED
|
@@ -31,7 +31,7 @@ export function cleanGroundOffset(state,meta){
|
|
|
31
31
|
const a=v[Math.max(30,i-1)],b=v[i],c=v[i+1],d=v[Math.min(41,i+2)];
|
|
32
32
|
return .5*(2*b+(-a+c)*p+(2*a-5*b+4*c-d)*p*p+(-a+3*b-3*c+d)*p*p*p);
|
|
33
33
|
}
|
|
34
|
-
export function createCachedTween(cache,info){
|
|
34
|
+
export function createCachedTween(cache,info,kernel=null){
|
|
35
35
|
if(!isGrayFrameSource(cache)||cache.byteLength!==CLEAN_BYTES||info.pairs!==47||info.samples!==17||info.width!==320||info.height!==320||info.channels!==1||info.bytes!==CLEAN_BYTES)throw Error('Invalid baked tween cache');
|
|
36
36
|
const frames=grayFrameReader(cache,320,320,47*17),output=new Uint8ClampedArray(320*320*4),stride=320*320;
|
|
37
37
|
for(let i=3;i<output.length;i+=4)output[i]=255;
|
|
@@ -39,7 +39,7 @@ export function createCachedTween(cache,info){
|
|
|
39
39
|
if(!Number.isInteger(state.pair)||state.pair<0||state.pair>=47||!Number.isFinite(state.blend))throw Error('Invalid baked tween state');
|
|
40
40
|
const phase=Math.max(0,Math.min(1,state.blend))*16,index=Math.min(15,Math.floor(phase)),alpha=phase-index;
|
|
41
41
|
const a=frames.getFrame(state.pair*17+index),b=frames.getFrame(state.pair*17+index+1);
|
|
42
|
-
for(let i=0,j=0;i<stride;i++,j+=4){const value=a[i]*(1-alpha)+b[i]*alpha;output[j]=output[j+1]=output[j+2]=value;}
|
|
42
|
+
if(kernel?.blendGray)kernel.blendGray(output,a,b,alpha);else for(let i=0,j=0;i<stride;i++,j+=4){const value=a[i]*(1-alpha)+b[i]*alpha;output[j]=output[j+1]=output[j+2]=value;}
|
|
43
43
|
return output;
|
|
44
44
|
};
|
|
45
45
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {grayFrameReader,isGrayFrameSource} from './puppy-frame-store.mjs';
|
|
2
2
|
// Prebaked interaction samples; the original seat is the exact shared endpoint.
|
|
3
3
|
// Pure sampling, no live image morph, root motion, camera or pose-size fitting.
|
|
4
|
-
export function createCompanionSpriteSampler(bytes,info,output){
|
|
4
|
+
export function createCompanionSpriteSampler(bytes,info,output,kernel=null){
|
|
5
5
|
const n=320*320;
|
|
6
6
|
if(!isGrayFrameSource(bytes)||info?.width!==320||info.height!==320||info.samples!==9||!Number.isInteger(info.pairs)||info.pairs<1||info.pairs>128||bytes.byteLength!==info.pairs*9*n)throw Error('Invalid companion sprite cache');
|
|
7
7
|
for(const key of ['treat-eat','ball-pickup','ball-drop'])if(!Array.isArray(info.clips?.[key])||!info.clips[key].length||!info.clips[key].every(p=>Number.isInteger(p)&&p>=0&&p<info.pairs))throw Error('Invalid companion sprite clip');
|
|
@@ -11,6 +11,6 @@ export function createCompanionSpriteSampler(bytes,info,output){
|
|
|
11
11
|
return({clip,progress})=>{
|
|
12
12
|
const list=info.clips[clip];if(!list||!Number.isFinite(progress)||progress<0||progress>1)throw Error('Invalid companion sprite pose');
|
|
13
13
|
const q=progress*list.length,index=Math.min(list.length-1,Math.floor(q)),s=Math.round((q-index)*8),frame=frames.getFrame(list[index]*9+s);
|
|
14
|
-
for(let i=0,j=0;i<n;i++,j+=4)rgba[j]=rgba[j+1]=rgba[j+2]=frame[i];return rgba;
|
|
14
|
+
if(kernel?.copyGray)kernel.copyGray(rgba,frame);else for(let i=0,j=0;i<n;i++,j+=4)rgba[j]=rgba[j+1]=rgba[j+2]=frame[i];return rgba;
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -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,kernel=null}={}){
|
|
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
|
+
if(kernel?.decodeRows)kernel.decodeRows(frame,rows,x,y,w,h,width);else 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
|
package/puppy-paper-actions.mjs
CHANGED
|
@@ -38,7 +38,7 @@ export function paperActionPose(name,progress,info,compiledClips){
|
|
|
38
38
|
if(!Number.isFinite(blend))throw Error('Invalid paper action: blend');
|
|
39
39
|
return{pair:list[index],blend};
|
|
40
40
|
}
|
|
41
|
-
export function createPaperActionSampler(bytes,info,output,compiledClips){
|
|
41
|
+
export function createPaperActionSampler(bytes,info,output,compiledClips,kernel=null){
|
|
42
42
|
if(!isGrayFrameSource(bytes))throw Error('Invalid action cache: bytes');
|
|
43
43
|
validatePaperActionInfo(info,bytes.byteLength);
|
|
44
44
|
const stride=info.width*info.height,pairs=info.pairs,frames=grayFrameReader(bytes,info.width,info.height,pairs*9);
|
|
@@ -47,5 +47,5 @@ export function createPaperActionSampler(bytes,info,output,compiledClips){
|
|
|
47
47
|
for(let i=3;i<out.length;i+=4)out[i]=255;
|
|
48
48
|
return state=>{const p=paperActionPose(state.actionName,state.actionProgress,info,compiledClips),sample=Math.round(p.blend*8);
|
|
49
49
|
if(p.pair>=pairs||sample<0||sample>8)throw Error('Invalid paper action: sample out of range');
|
|
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
|
+
const frame=frames.getFrame(p.pair*9+sample);if(kernel?.copyGray)kernel.copyGray(out,frame);else for(let i=0,j=0;i<stride;i++,j+=4)out[j]=out[j+1]=out[j+2]=frame[i];return out;};
|
|
51
51
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -5,4 +5,4 @@ export function paperBehaviorPose(state,info){
|
|
|
5
5
|
const q=Math.min(list.length-1e-9,progress*list.length),i=Math.floor(q),blend=Math.round((q-i)*8)/8;
|
|
6
6
|
return{pair:list[i],blend};
|
|
7
7
|
}
|
|
8
|
-
export function createPaperBehaviorSampler(bytes,info,output){const sample=createRearSampler(bytes,info,output);return state=>{const p=paperBehaviorPose(state,info);return sample(p.pair,p.blend);};}
|
|
8
|
+
export function createPaperBehaviorSampler(bytes,info,output,kernel=null){const sample=createRearSampler(bytes,info,output,kernel);return state=>{const p=paperBehaviorPose(state,info);return sample(p.pair,p.blend);};}
|
package/puppy-paper-contact.mjs
CHANGED
|
@@ -101,7 +101,7 @@ export function footSourcePoint(x,y,pins){
|
|
|
101
101
|
return chosen?{x:x-chosen.dx*best,y:y-chosen.dy*best}:{x,y};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export function createRearSampler(bytes,info,output){
|
|
104
|
+
export function createRearSampler(bytes,info,output,kernel=null){
|
|
105
105
|
if(!isGrayFrameSource(bytes)||info.pairs!==18||info.samples!==9||bytes.byteLength!==18*9*320*320)throw Error('Invalid rear sprite cache');
|
|
106
106
|
const frames=grayFrameReader(bytes,320,320,18*9),out=output??new Uint8ClampedArray(320*320*4);
|
|
107
107
|
if(!(out instanceof Uint8ClampedArray)||out.length!==320*320*4)throw Error('Invalid rear output');
|
|
@@ -109,7 +109,7 @@ export function createRearSampler(bytes,info,output){
|
|
|
109
109
|
return(pair,blend)=>{
|
|
110
110
|
if(!Number.isInteger(pair)||pair<0||pair>=18||!Number.isFinite(blend)||blend<0||blend>1)throw Error('Invalid rear pose');
|
|
111
111
|
const frame=frames.getFrame(pair*9+Math.round(blend*8));
|
|
112
|
-
for(let j=0,i=0;i<320*320;i++,j+=4)out[j]=out[j+1]=out[j+2]=frame[i];
|
|
112
|
+
if(kernel?.copyGray)kernel.copyGray(out,frame);else for(let j=0,i=0;i<320*320;i++,j+=4)out[j]=out[j+1]=out[j+2]=frame[i];
|
|
113
113
|
return out;
|
|
114
114
|
};
|
|
115
115
|
}
|
|
Binary file
|
|
Binary file
|
package/puppy-paper-seat.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {grayFrameReader,isGrayFrameSource} from './puppy-frame-store.mjs';
|
|
2
2
|
// Exact cached samples; no live crossfade. Reverse the same path to stand up.
|
|
3
|
-
export function createPaperSeatSampler(bytes,info,output){
|
|
3
|
+
export function createPaperSeatSampler(bytes,info,output,kernel=null){
|
|
4
4
|
const stride=320*320;
|
|
5
5
|
if(!isGrayFrameSource(bytes)||info.width!==320||info.height!==320||info.pairs!==3||info.samples!==17||bytes.byteLength!==3*17*stride)throw Error('Invalid paper sitting cache');
|
|
6
6
|
const frames=grayFrameReader(bytes,320,320,3*17),out=output??new Uint8ClampedArray(stride*4);
|
|
@@ -9,7 +9,7 @@ export function createPaperSeatSampler(bytes,info,output){
|
|
|
9
9
|
return progress=>{
|
|
10
10
|
if(!Number.isFinite(progress)||progress<0||progress>1)throw Error('Invalid paper sitting progress');
|
|
11
11
|
const q=progress*3,pair=Math.min(2,Math.floor(q)),sample=Math.round((q-pair)*16),frame=frames.getFrame(pair*17+sample);
|
|
12
|
-
for(let i=0,j=0;i<stride;i++,j+=4)out[j]=out[j+1]=out[j+2]=frame[i];
|
|
12
|
+
if(kernel?.copyGray)kernel.copyGray(out,frame);else for(let i=0,j=0;i<stride;i++,j+=4)out[j]=out[j+1]=out[j+2]=frame[i];
|
|
13
13
|
return out;
|
|
14
14
|
};
|
|
15
15
|
}
|