@bitefoundry/retrieviewer 0.0.17 → 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/INSTALLING.md +2 -1
- package/LICENSE +16 -16
- package/NATIVE-RUNTIME.md +89 -0
- package/PREREQUISITES.md +7 -0
- package/PUPPY-MEMORY.md +11 -0
- package/README.ko.md +10 -1
- package/README.md +11 -1
- package/RELEASING.md +19 -0
- 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 +39 -4
- package/puppy-clean.mjs +2 -2
- package/puppy-companion-sprites.mjs +2 -2
- package/puppy-frame-indexed.mjs +2 -2
- package/puppy-paper-actions.mjs +2 -2
- package/puppy-paper-behaviors.mjs +1 -1
- package/puppy-paper-contact.mjs +2 -2
- package/puppy-paper-seat.mjs +2 -2
- package/puppy-paper.mjs +12 -12
- package/retrieviewer-cli.mjs +10 -6
- package/retrieviewer.mjs +13 -4
|
@@ -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",
|
|
@@ -164,7 +165,39 @@
|
|
|
164
165
|
"vendor/procedural-animals/LICENSE",
|
|
165
166
|
"puppy-frame-indexed.mjs",
|
|
166
167
|
"puppy-frame-file.mjs",
|
|
167
|
-
"pet-indexed-manifest.json"
|
|
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"
|
|
168
201
|
],
|
|
169
202
|
"scripts": {
|
|
170
203
|
"bake:motion": "node tools/bake-pet-motion.mjs",
|
|
@@ -178,7 +211,9 @@
|
|
|
178
211
|
"release:check": "npm run check:motion && node tools/check-release.mjs",
|
|
179
212
|
"prepublishOnly": "npm run release:check",
|
|
180
213
|
"bake:art-index": "node tools/index-pet-art.mjs",
|
|
181
|
-
"check:art-index": "node tools/index-pet-art.mjs --check"
|
|
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"
|
|
182
217
|
},
|
|
183
218
|
"dependencies": {
|
|
184
219
|
"jsonc-parser": "3.3.1"
|
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
|
}
|
|
@@ -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
|
}
|
package/puppy-frame-indexed.mjs
CHANGED
|
@@ -31,7 +31,7 @@ const display=Uint8Array.from({length:32},(_,q)=>Math.round(q*255/31));
|
|
|
31
31
|
|
|
32
32
|
// The host provides bounded positional reads and a synchronous gzip decoder.
|
|
33
33
|
// Only two reconstructed frames survive; compressed art stays on disk.
|
|
34
|
-
export function createIndexedFrameStore(index,{read,expand,close=()=>{},frameIndices}={}){
|
|
34
|
+
export function createIndexedFrameStore(index,{read,expand,close=()=>{},frameIndices,kernel=null}={}){
|
|
35
35
|
if(typeof read!=='function'||typeof expand!=='function')fail('reader');
|
|
36
36
|
const {width,height,frameCount,uniqueCount,map,rects,indexBytes}=index,n=width*height;
|
|
37
37
|
const retained=frameIndices===undefined?null:new Uint8Array(frameCount);
|
|
@@ -50,7 +50,7 @@ export function createIndexedFrameStore(index,{read,expand,close=()=>{},frameInd
|
|
|
50
50
|
const rows=expand(compressed,expected);if(!(rows instanceof Uint8Array)||rows.length!==expected)fail('expanded length');
|
|
51
51
|
// Mark the evicted slot invalid before parsing: a failed decode is never a hit.
|
|
52
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++){
|
|
53
|
+
if(kernel?.decodeRows)kernel.decodeRows(frame,rows,x,y,w,h,width);else for(let r=0,at=0;r<h;r++){
|
|
54
54
|
const type=rows[at++];if(type>4)fail('filter');
|
|
55
55
|
unfilterRow(type,rows,at,cur,prev,w);at+=w;
|
|
56
56
|
for(let c=0;c<w;c++){
|
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
|
}
|
|
@@ -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
|
}
|
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
|
}
|
package/puppy-paper.mjs
CHANGED
|
@@ -354,9 +354,9 @@ export function createPaperActor(animal,info,{manualTricks=false,trickTimings=nu
|
|
|
354
354
|
};
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,comfort,contacts=true,pawTransport=false,motionTables=null}={}){
|
|
357
|
+
export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,seat,arrivalSeat,behaviors,actions,interactions,comfort,contacts=true,pawTransport=false,motionTables=null,kernel=null}={}){
|
|
358
358
|
if(![width,height].every(Number.isInteger)||width<2||height<2||width*height>1_000_000)throw Error('Invalid paper grid');
|
|
359
|
-
const tween=createCachedTween(cache,info),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
|
|
359
|
+
const tween=createCachedTween(cache,info,kernel),renderMeta={...meta,pairs:info.pair_map,run_sequence:info.run_sequence};
|
|
360
360
|
const out=new Uint8ClampedArray(width*height*4),area=createPaperAreaSampler();
|
|
361
361
|
// Only one authored source is drawn at a time. Small samplers borrow a view of
|
|
362
362
|
// the action-sized scratch. A sleep blend reads each base pixel before writing
|
|
@@ -364,14 +364,14 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
|
|
|
364
364
|
const actionScratch=new Uint8ClampedArray(Math.max(320*320,(actions?.info.width??320)*(actions?.info.height??320))*4);
|
|
365
365
|
const spriteScratch=actionScratch.subarray(0,320*320*4);
|
|
366
366
|
for(let i=3;i<actionScratch.length;i+=4)actionScratch[i]=255;
|
|
367
|
-
const rearSample=rear?createRearSampler(rear.bytes,rear.info,spriteScratch):null,lock=createPaperFootLock({maxImageStretch:pawTransport?1.45:Infinity});
|
|
368
|
-
const approachSample=approach?createRearSampler(approach.bytes,approach.info,spriteScratch):null;
|
|
369
|
-
const seatSample=seat?createPaperSeatSampler(seat.bytes,seat.info,spriteScratch):null;
|
|
370
|
-
const arrivalSeatSample=arrivalSeat?createPaperSeatSampler(arrivalSeat.bytes,arrivalSeat.info,spriteScratch):null;
|
|
371
|
-
const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info,spriteScratch):null;
|
|
372
|
-
const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info,spriteScratch):null;
|
|
367
|
+
const rearSample=rear?createRearSampler(rear.bytes,rear.info,spriteScratch,kernel):null,lock=createPaperFootLock({maxImageStretch:pawTransport?1.45:Infinity});
|
|
368
|
+
const approachSample=approach?createRearSampler(approach.bytes,approach.info,spriteScratch,kernel):null;
|
|
369
|
+
const seatSample=seat?createPaperSeatSampler(seat.bytes,seat.info,spriteScratch,kernel):null;
|
|
370
|
+
const arrivalSeatSample=arrivalSeat?createPaperSeatSampler(arrivalSeat.bytes,arrivalSeat.info,spriteScratch,kernel):null;
|
|
371
|
+
const behaviorSample=behaviors?createPaperBehaviorSampler(behaviors.bytes,behaviors.info,spriteScratch,kernel):null;
|
|
372
|
+
const interactionSample=interactions?createCompanionSpriteSampler(interactions.bytes,interactions.info,spriteScratch,kernel):null;
|
|
373
373
|
const comfortSample=comfort?createComfortSpriteSampler(comfort.bytes,comfort.info,spriteScratch):null;
|
|
374
|
-
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;
|
|
374
|
+
const actionSample=actions?createPaperActionSampler(actions.bytes,actions.info,actionScratch.subarray(0,actions.info.width*actions.info.height*4),motionTables?.actionClips,kernel):null,actionArea=actions?createPaperAreaSampler(actions.info.width):null;
|
|
375
375
|
const episodes=(key,labels,indices,samples,options)=>motionTables?.support[key]?createBakedSupportEpisodes(motionTables.support[key]):createPaperSupportEpisodes(labels,indices,samples,options);
|
|
376
376
|
const walkEpisodes=actions?episodes('walk',actions.info.feet,actions.info.clips.walk,9):null;
|
|
377
377
|
const sourceEpisodes=rear?{
|
|
@@ -415,8 +415,8 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
|
|
|
415
415
|
let sx=Math.floor(rawX);if(state.flip)sx=sourceN-1-sx;
|
|
416
416
|
return image[(Math.floor(rawY)*sourceN+sx)*4]>16;
|
|
417
417
|
}}):null;render.lastTransport=transport;
|
|
418
|
-
const minify=side<320||sideX<320,filter=isAction?actionArea:area;if(minify)filter.prepare(image);
|
|
419
|
-
out.fill(0);for(let i=3;i<out.length;i+=4)out[i]=255;
|
|
418
|
+
const minify=side<320||sideX<320,filter=isAction?actionArea:area;if(minify&&!(kernel?.blit&&transport))filter.prepare(image);
|
|
419
|
+
if(kernel?.clear)kernel.clear(out);else{out.fill(0);for(let i=3;i<out.length;i+=4)out[i]=255;}
|
|
420
420
|
render.lastPlacement={left,top,side,sideX};
|
|
421
421
|
// Measured drawn mouth of the EXACT sample above (same priority, quantization,
|
|
422
422
|
// placement and mirror). Side/rear/action/interaction stay null: unsupported.
|
|
@@ -430,7 +430,7 @@ export function createPaperRenderer(cache,info,meta,width,height,{rear,approach,
|
|
|
430
430
|
}
|
|
431
431
|
const drawTop=top-originY/320*side,bottom=top+Math.max(sourceN-originY,...pins.map(p=>p.target.y+20))/320*side;
|
|
432
432
|
const drawLeft=left+Math.min(-originX,...pins.map(p=>p.target.x-20))/320*sideX,drawRight=left+Math.max(sourceN-originX,...pins.map(p=>p.target.x+20))/320*sideX;
|
|
433
|
-
for(let dy=Math.max(0,minify?Math.floor(drawTop):Math.ceil(drawTop-.5));dy<Math.min(height,minify?Math.ceil(bottom):Math.ceil(bottom-.5));dy++)for(let dx=Math.max(0,minify?Math.floor(drawLeft):Math.ceil(drawLeft-.5));dx<Math.min(width,minify?Math.ceil(drawRight):Math.ceil(drawRight-.5));dx++){
|
|
433
|
+
if(kernel?.blit&&transport)kernel.blit(out,image,{width,height,sourceN,left,top,side,sideX,originX,originY,drawTop,bottom,drawLeft,drawRight,flip:state.flip,layers:transport.layers});else for(let dy=Math.max(0,minify?Math.floor(drawTop):Math.ceil(drawTop-.5));dy<Math.min(height,minify?Math.ceil(bottom):Math.ceil(bottom-.5));dy++)for(let dx=Math.max(0,minify?Math.floor(drawLeft):Math.ceil(drawLeft-.5));dx<Math.min(width,minify?Math.ceil(drawRight):Math.ceil(drawRight-.5));dx++){
|
|
434
434
|
const px=(dx+.5-left)/sideX*320,py=(dy+.5-top)/side*320;
|
|
435
435
|
const warped=transport?transport(px,py):footSourcePoint(px,py,pins);if(!warped)continue;
|
|
436
436
|
const rawX=warped.x+originX,rawY=warped.y+originY;
|
package/retrieviewer-cli.mjs
CHANGED
|
@@ -13,7 +13,7 @@ Usage:
|
|
|
13
13
|
retrieviewer doctor [--agent ID] [--json]
|
|
14
14
|
retrieviewer agents [--json]
|
|
15
15
|
retrieviewer preview [--original]
|
|
16
|
-
retrieviewer desktop [--ink cream|gold|dark] [--return-style auto|brake|trot|run-trot] [--watch stackbite|off] [--brain openrouter|off]
|
|
16
|
+
retrieviewer desktop [--pet dog|cat] [--engine auto|native|node] [--ink cream|gold|dark] [--return-style auto|brake|trot|run-trot] [--watch stackbite|off] [--brain openrouter|off]
|
|
17
17
|
retrieviewer brain-check [--json]
|
|
18
18
|
retrieviewer attention [--json]
|
|
19
19
|
retrieviewer desktop-status [--json]
|
|
@@ -26,8 +26,10 @@ Non-interactive changes require --agent and --yes. Unsupported native renderers
|
|
|
26
26
|
are refused: MCP connectivity alone is never treated as in-screen rendering.
|
|
27
27
|
No command changes credentials, shell profiles or agent executables.
|
|
28
28
|
Desktop is a separate native overlay, NOT a Codex plugin. Choose a window once;
|
|
29
|
-
use Codex normally. Exit from its
|
|
30
|
-
Windows
|
|
29
|
+
use Codex normally. Exit from its controls or desktop-stop. No auto-start is added.
|
|
30
|
+
Windows x64 basic mode uses one native process; Node exits after launch.
|
|
31
|
+
--pet cat selects the ginger cat. Watch/brain use the Node compatibility engine.
|
|
32
|
+
Windows and AlmaLinux8 X11 are verified; macOS is experimental. Native Wayland
|
|
31
33
|
app tracking is not supported. Linux targets AlmaLinux8/GLIBC2.28, system Python3.6+.
|
|
32
34
|
Return style defaults to auto (mix all three). Restart the companion to change it.
|
|
33
35
|
Watch is OFF by default. Inside a live StackBite Seat, --watch stackbite reads only
|
|
@@ -42,7 +44,7 @@ export function parseArguments(args){
|
|
|
42
44
|
const [command='help',...rest]=args,options={};
|
|
43
45
|
for(let i=0;i<rest.length;i++){
|
|
44
46
|
const token=rest[i];
|
|
45
|
-
if(token==='--agent'||token==='--ink'||token==='--return-style'||token==='--watch'||token==='--brain'){
|
|
47
|
+
if(token==='--agent'||token==='--ink'||token==='--return-style'||token==='--watch'||token==='--brain'||token==='--pet'||token==='--engine'){
|
|
46
48
|
const key=token.slice(2);
|
|
47
49
|
if(options[key]||!rest[i+1]||rest[i+1].startsWith('-'))throw Error(token+' requires one value');
|
|
48
50
|
options[key]=rest[++i];
|
|
@@ -50,13 +52,15 @@ export function parseArguments(args){
|
|
|
50
52
|
else throw Error('Unknown argument: '+token);
|
|
51
53
|
}
|
|
52
54
|
if(options.agent&&!AGENTS.some(a=>a.id===options.agent))throw Error('Unknown agent: '+options.agent);
|
|
55
|
+
if(options.pet&&!['dog','cat'].includes(options.pet))throw Error('--pet must be dog or cat');
|
|
56
|
+
if(options.engine&&!['auto','native','node'].includes(options.engine))throw Error('--engine must be auto, native or node');
|
|
53
57
|
if(options.ink&&!['cream','gold','dark'].includes(options.ink))throw Error('--ink must be cream, gold or dark');
|
|
54
58
|
if(options['return-style']&&!['auto','brake','trot','run-trot'].includes(options['return-style']))throw Error('--return-style must be auto, brake, trot or run-trot');
|
|
55
59
|
if(options.watch&&!['off','stackbite'].includes(options.watch))throw Error('--watch must be stackbite or off');
|
|
56
60
|
if(options.brain&&!['off','openrouter'].includes(options.brain))throw Error('--brain must be openrouter or off');
|
|
57
61
|
if(options.brain==='openrouter'&&options.watch!=='stackbite')throw Error('--brain openrouter requires --watch stackbite');
|
|
58
62
|
if(!['help','--help','-h','enable','disable','disconnect','status','doctor','agents','preview','attention','brain-check','desktop','desktop-status','desktop-stop','desktop-clean','desktop-remove'].includes(command))throw Error('Unknown command: '+command);
|
|
59
|
-
const allowed={desktop:['ink','return-style','watch','brain'],attention:['json'],'brain-check':['json'],enable:['agent','yes','json','dry-run'],disconnect:['agent','yes','json','dry-run'],disable:['agent','json'],status:['agent','json'],doctor:['agent','json'],agents:['json'],preview:['original'],'desktop-status':['json'],'desktop-clean':['yes'],'desktop-remove':['yes']};
|
|
63
|
+
const allowed={desktop:['ink','return-style','watch','brain','pet','engine'],attention:['json'],'brain-check':['json'],enable:['agent','yes','json','dry-run'],disconnect:['agent','yes','json','dry-run'],disable:['agent','json'],status:['agent','json'],doctor:['agent','json'],agents:['json'],preview:['original'],'desktop-status':['json'],'desktop-clean':['yes'],'desktop-remove':['yes']};
|
|
60
64
|
for(const key of Object.keys(options))if(!(allowed[command]??[]).includes(key))throw Error(`--${key} is not supported by ${command}`);
|
|
61
65
|
return {command,options};
|
|
62
66
|
}
|
|
@@ -82,7 +86,7 @@ export async function runCli(args,{input=process.stdin,output=process.stdout,err
|
|
|
82
86
|
if(['desktop-clean','desktop-remove'].includes(command)&&!options.yes)throw Error('Use '+command+' --yes to remove only the companion cache.');
|
|
83
87
|
const api=desktop??await import('./integrations/windows-overlay/desktop.mjs');
|
|
84
88
|
const action={'desktop':'startDesktop','desktop-status':'desktopStatus','desktop-stop':'stopDesktop','desktop-clean':'cleanDesktop','desktop-remove':'removeDesktop'}[command];
|
|
85
|
-
emit(await api[action]({...options.ink?{ink:options.ink}:{},...options['return-style']?{returnStyle:options['return-style']}:{},...options.watch?{watch:options.watch}:{},...options.brain?{brain:options.brain}:{} }));return 0;
|
|
89
|
+
emit(await api[action]({...options.pet?{pet:options.pet}:{},...options.engine?{engine:options.engine}:{},...options.ink?{ink:options.ink}:{},...options['return-style']?{returnStyle:options['return-style']}:{},...options.watch?{watch:options.watch}:{},...options.brain?{brain:options.brain}:{} }));return 0;
|
|
86
90
|
}
|
|
87
91
|
if(command==='preview'){await import('./examples/live.mjs');return 0;}
|
|
88
92
|
if(['status','doctor','agents'].includes(command)){
|
package/retrieviewer.mjs
CHANGED
|
@@ -123,7 +123,7 @@ async function acquireAssets(loadAsset,artProfile){
|
|
|
123
123
|
try{const data=await entry.promise;entry.data=data;return{data,release};}catch(error){release();throw error;}
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
function petOptions({loadAsset=defaultLoadAsset,seed,energy,pawTransport=true,artProfile=DEFAULT_ART_PROFILE,returnStyle='auto'}={}){
|
|
127
127
|
if(typeof loadAsset!=='function')throw TypeError('loadAsset must be a function');
|
|
128
128
|
if(seed===undefined)seed=globalThis.crypto?.getRandomValues?globalThis.crypto.getRandomValues(new Uint32Array(1))[0]:731;
|
|
129
129
|
if(!Number.isInteger(seed)||seed<0||seed>0xffffffff)throw RangeError('seed must be a uint32');
|
|
@@ -131,7 +131,16 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
131
131
|
if(typeof pawTransport!=='boolean')throw TypeError('pawTransport must be boolean');
|
|
132
132
|
if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
|
|
133
133
|
if(!FETCH_RETURN_STYLES.includes(returnStyle))throw RangeError('returnStyle must be auto, brake, trot or run-trot');
|
|
134
|
-
|
|
134
|
+
return {loadAsset,seed,energy,pawTransport,artProfile,returnStyle};
|
|
135
|
+
}
|
|
136
|
+
export async function createPet(options={}){
|
|
137
|
+
const normalized=petOptions(options),lease=await acquireAssets(normalized.loadAsset,normalized.artProfile);
|
|
138
|
+
return createPetFromAssets(normalized,lease);
|
|
139
|
+
}
|
|
140
|
+
// Shared simulation/render core for local native hosts. The provider owns I/O.
|
|
141
|
+
export function createPetFromAssets(options,lease,kernel=null){
|
|
142
|
+
const {loadAsset,seed,energy,pawTransport,artProfile,returnStyle}=petOptions(options);
|
|
143
|
+
let data=lease.data;
|
|
135
144
|
const release=lease.release;lease=null;
|
|
136
145
|
let {cache,info,meta}=data;const assets={...data.assets};data=null;
|
|
137
146
|
// Baked tables describe the bundled Q5 source only. External/optional artwork
|
|
@@ -166,14 +175,14 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
|
|
|
166
175
|
if(typeof cellAspect!=='number'||!Number.isFinite(cellAspect)||cellAspect<=0)throw RangeError('cellAspect must be a positive number');
|
|
167
176
|
if(![12,24,30].includes(poseFps))throw RangeError('poseFps must be 12, 24 or 30');
|
|
168
177
|
const width=columns*2,height=rows*4,key=`${width}x${height}`;
|
|
169
|
-
if(paintKey!==key){paint=createPaperRenderer(cache,info,meta,width,height,{...assets,pawTransport,motionTables});paintKey=key;}
|
|
178
|
+
if(paintKey!==key){paint=createPaperRenderer(cache,info,meta,width,height,{...assets,pawTransport,motionTables,kernel});paintKey=key;}
|
|
170
179
|
const viewportWidth=camera?.viewportWidth??columns*cellAspect,viewportHeight=camera?.viewportHeight??rows;
|
|
171
180
|
const projection=createPaperProjection({viewportWidth,viewportHeight,rect:camera?.rect??{left:0,top:0,width:viewportWidth,height:viewportHeight},width,height});
|
|
172
181
|
const state=actor.sample(time,{poseFps}),report=life.report;
|
|
173
182
|
if(report.gesture)state.interactionPose=report.gesture;
|
|
174
183
|
const placement=projection.placement(state.x,state.z,state.bodyLift??0),pixels=paint(state,{...placement,contactEnabled:!!contactEnabled});
|
|
175
184
|
paintCompanionObjects(pixels,width,height,projection,report,state,paint.lastMouth);
|
|
176
|
-
const {text}=rgbaToBraille(pixels,width,height,{method:'stable',brightness:1.18,contrast:1.1});
|
|
185
|
+
const {text}=rgbaToBraille(pixels,width,height,{method:'stable',brightness:1.18,contrast:1.1,kernel});
|
|
177
186
|
lastHeldFrameKey=heldFrameKey(state,report,{columns,rows,cellAspect,camera,poseFps,contactEnabled});
|
|
178
187
|
return {text,columns,rows,pixels,state,report,placement,projection,artProfile,mouth:paint.lastMouth,contacts:paint.report()};
|
|
179
188
|
},
|