@bitefoundry/retrieviewer 0.0.18 → 0.0.19

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/NATIVE-RUNTIME.md CHANGED
@@ -1,4 +1,4 @@
1
- # Lightweight desktop runtime · 0.0.18
1
+ # Lightweight desktop runtime · 0.0.19
2
2
 
3
3
  Windows x64 basic desktop mode runs the existing JavaScript pet behavior in
4
4
  QuickJS-NG, inside the same native process that paints the transparent window.
@@ -41,15 +41,15 @@ play/treat/attention or tower/wheel actions. No forced GC or working-set trimmin
41
41
 
42
42
  | Pet | Median RSS | Sampled maximum | OS lifetime peak |
43
43
  |---|---:|---:|---:|
44
- | Retriever |29.4MiB|39.3MiB|39.9MiB|
45
- | Ginger cat |44.2MiB|61.8MiB|65.3MiB|
44
+ | Retriever |22.6MiB|35.3MiB|36.5MiB|
45
+ | Ginger cat with tower/wheel |29.5MiB|34.6MiB|39.1MiB|
46
46
 
47
- The cat's scene also includes the tower and wheel. It is typically below50MiB
48
- in this measurement, but its peaks exceed50MiB; this is not a strict50MiB cap.
49
- The preceding Node/CLR dog had a sampled maximum170.7MiB on the same host.
50
- Raw evidence: dist/native-product-memory/{dog,cat,report}.json (local QA output).
51
- Reproduce with the measurement command below. Package/website figures are
52
- rounded; memory remains dependent on scene, window dimensions and OS behavior.
47
+ Version0.0.19 uses up to8 disjoint cell-aligned paint regions inside the same
48
+ process, reclaiming oversized/hidden surfaces. The preceding0.0.18 final run
49
+ had medians29.4/44.2MiB and OS peaks40.3/65.4MiB for dog/cat respectively.
50
+ See [sparse surface measurements](SPARSE-PAINT.md) for the matching workloads,
51
+ pixel comparisons and CPU tradeoff. This is not a universal50MiB memory cap.
52
+ Raw local evidence: dist/sparse-paint-memory/{dog,cat,report}.json.
53
53
 
54
54
  ## Scope
55
55
 
@@ -87,3 +87,9 @@ off-screen diagnostic windows, preserving an already-running user companion.
87
87
  The executable embeds the Windows UTF-8 process code-page manifest (Windows10
88
88
  1903+ /11) so QuickJS stdio also reads Korean/non-ASCII installation paths.
89
89
  A regression test launches both species from Korean bundle, art and state paths.
90
+
91
+ ## Sparse paint surfaces
92
+
93
+ [Sparse paint regions](SPARSE-PAINT.md) preserve final masks while reducing
94
+ empty bitmap space. Their HWNDs remain click-through and non-activating; no
95
+ extra pet process, runtime dependency or animation asset is introduced.
package/PUPPY-MEMORY.md CHANGED
@@ -1,3 +1,14 @@
1
+ # 0.0.19 sparse painter result
2
+
3
+ Windows x64 basic mode retains one native process and splits sparse content
4
+ into bounded cropped surfaces. In the same real4K150s schedule, dog median RSS
5
+ was22.6MiB (OS peak36.5MiB); cat with tower/wheel29.5MiB (peak39.1MiB).
6
+ Final masks and behavior are unchanged. No forced GC/trim; optional watching,
7
+ other OS hosts and browser tabs are outside this memory claim. See
8
+ [sparse paint evidence](SPARSE-PAINT.md) and [runtime scope](NATIVE-RUNTIME.md).
9
+
10
+ ## Previous0.0.18 native result (historical)
11
+
1
12
  # 0.0.18 native desktop result
2
13
 
3
14
  Windows x64 basic mode now uses a single QuickJS/Win32 process. In real4K
package/README.ko.md CHANGED
@@ -148,6 +148,13 @@ JS 플러그인과 JSONL 브리지를 제공해요. 화면·입력·시간 제
148
148
  AI로 생성한 그림에 자세 정렬·접지·프레임 처리를 적용한 애니메이션입니다.
149
149
  실제 모션 캡처나 자의식이 있는 강아지라고 주장하지 않아요.
150
150
 
151
+ ## 0.0.19
152
+
153
+ Windows 기본 실행에서 표시 영역을 나누고 불필요하게 커진 버퍼를 회수해요.
154
+ 그림과 동작은 그대로예요. 실제4K150초 측정에서 강아지는 중앙값22.6MiB·최고36.5MiB,
155
+ 가구가 있는 고양이는29.5MiB·39.1MiB였어요. 선택형 감시·다른OS·브라우저는
156
+ 별도 범위예요. [측정과 검증 기록](SPARSE-PAINT.md)을 참고하세요.
157
+
151
158
  ## 0.0.18
152
159
 
153
160
  Windows x64 기본 실행은 네이티브 프로세스 하나만 남고 Node는 종료돼요.
package/README.md CHANGED
@@ -189,6 +189,14 @@ The puppy artwork was developed with AI-generated imagery and programmatic pose,
189
189
  grounding and frame processing. It is authored animation, not motion capture or
190
190
  a claim of sentience.
191
191
 
192
+ ## 0.0.19
193
+
194
+ Windows basic mode now splits distant content into small paint regions and
195
+ reclaims oversized/hidden surfaces. Same pets, artwork and behavior. In real4K
196
+ 150s tests: dog median22.6MiB / OS peak36.5MiB; cat with tower/wheel29.5 /39.1MiB.
197
+ Optional watching and other OS/browser hosts keep their separate memory scope.
198
+ See [measurements and verification](SPARSE-PAINT.md).
199
+
192
200
  ## 0.0.18
193
201
 
194
202
  Windows x64 basic mode now runs one native process; Node exits after launch.
package/RELEASING.md CHANGED
@@ -1,4 +1,20 @@
1
- # Retrieviewer 0.0.18 release process
1
+ # Retrieviewer 0.0.19 release process
2
+
3
+ Current target: `@bitefoundry/retrieviewer@0.0.19`, public `latest`.
4
+ The owner explicitly authorized npm and website deployment on2026-10-25.
5
+ Ship the verified sparse Windows painter: bounded disjoint final-mask regions,
6
+ reclaimable buffers, preserved artwork/behavior and existing compatibility modes.
7
+ Keep code MIT and both artworks CC BY-SA4.0; no new generation inputs or assets.
8
+
9
+ Existing evidence covers300 exact native paint frames,56 partition cases,
10
+ real4K150s operating memory and owned-window shrink/hide/restore/switch/teardown.
11
+ Run the suite/release gate, build and install a fresh exact tarball, exercise its
12
+ actual native resources/picker, then publish that same artifact after CI.
13
+ Verify registry integrity, fresh registry installation and production website.
14
+ Keep website figures scoped to Windows basic mode; update EN/KO copy and cache
15
+ keys together. Preserve old releases, the user's running pet and privacy setup.
16
+
17
+ ## Previous0.0.18 scope (historical)
2
18
 
3
19
  Current target: `@bitefoundry/retrieviewer@0.0.18`, public `latest`.
4
20
  The owner authorized immediate npm and website deployment of the lightweight
@@ -0,0 +1,66 @@
1
+ # Sparse Windows paint surfaces · 0.0.19
2
+
3
+ Version0.0.19 builds on0.0.18. JavaScript behavior, Q5 art, world/contact math, colors and
4
+ Braille masks are unchanged; the native painter allocates less empty space.
5
+
6
+ ## Change
7
+
8
+ The old painter kept one bitmap spanning every nonempty cell. The cat, tower
9
+ and wheel therefore retained a large transparent rectangle between objects.
10
+ Its width and height only grew until species switch/exit. A held-pose ablation
11
+ at3840×2160 measured cat+furniture38.6MiB versus cat alone17.6MiB; the equivalent
12
+ single DIB capacities were12.22MiB and1.63MiB respectively.
13
+
14
+ The new painter partitions the FINAL cell mask into up to8 disjoint cropped
15
+ regions. Greedy splits account for64px allocation rounding and must save at
16
+ least256KiB of bitmap space. No glyph is resampled, dropped, drawn twice or
17
+ recolored; geometry and palette still share the original stamp atlas. A dense
18
+ scene can remain a single region. This is a bounded native composition change,
19
+ not extra animal simulations or one process per region.
20
+
21
+ Each surface reuses capacity while it is within2× its current rounded target
22
+ area. Oversized capacity is reclaimed on repaint; unused regions are destroyed.
23
+ Hidden/minimized/retargeted scenes release their surfaces and overlay windows.
24
+ Resume repaints from the current simulation state. Windows system/compositor
25
+ allocations remain separate from the reported `paintSurfaceBytes` DIB counter.
26
+
27
+ ## Measurement
28
+
29
+ Same Windows11 x64 host, real native painter and an owned off-screen physical
30
+ 3840×2160 scene. Seed731; the existing150s diagnostic action schedule and300
31
+ roughly0.5s samples per pet. No forced GC/working-set trimming. Watch/brain off;
32
+ harness and external DWM/GPU memory excluded. Baseline is the tested0.0.18
33
+ installed-artifact run. These finite runs do not establish a universal RAM cap.
34
+
35
+ | Scene |0.0.18 median RSS|0.0.19 median RSS|0.0.18 OS peak|0.0.19 OS peak|
36
+ |---|---:|---:|---:|---:|
37
+ |Dog|29.35MiB|22.63MiB|40.34MiB|36.45MiB|
38
+ |Cat with tower/wheel|44.22MiB|29.46MiB|65.45MiB|39.13MiB|
39
+
40
+ The short fixed-pose cat+furniture comparison fell from38.6 to27.3MiB. This is
41
+ a different workload from the moving150s runs above, not a replacement for them.
42
+ Actual resize testing reduced allocated DIBs from6.52 to1.66MiB. Minimizing
43
+ reported0 DIB bytes and0 overlay HWNDs; restore and dog/cat switching recovered.
44
+
45
+ A paired16s cat timing check had92 paints/340 rendered calls in both variants.
46
+ Mean render time changed3.06→3.94ms, P95 12.42→16.68ms. Partitioning trades some
47
+ CPU for memory; these timings are not a constant-FPS guarantee.
48
+
49
+ ## Verification
50
+
51
+ - Native self-test:56 grids across7 cell sizes. Occupied masks covered once,
52
+ all regions disjoint/in bounds, partition plans no larger than one crop,
53
+ at most8 regions, and real DIB shrink/release verified.
54
+ - Real painter:300 moving frames across dog/cream and cat/gold/dark compared
55
+ against the old monolithic GDI+ ellipse output, byte-for-byte including alpha.
56
+ This tests per-frame paint pixels, not atomicity of compositor presentation.
57
+ - Real owned windows: pick/reselect,4K→small→4K, minimize/restore, species
58
+ switching, bootstrap detach, startup EOF, stop and Korean paths passed.
59
+ - Local full suite:243 passed,2 macOS-only tests skipped on this Windows host.
60
+
61
+ Reproduce with `node --test native-desktop.test.mjs`,
62
+ `node tools/check-native-paint.mjs`, `node tools/check-native-resources.mjs`,
63
+ and `node tools/measure-native-pet.mjs 150` after `npm run build:native`.
64
+ Local evidence is under dist/sparse-paint-memory, dist/paint-parity-HlzSTl,
65
+ dist/paint-resource-m3dMB1 and dist/sparse-timing-REFNLe. QA output is not
66
+ packaged. Linux/macOS/browser and optional Node/CLR watching paths are unchanged.
@@ -25,7 +25,10 @@ export async function startNativeDesktop(options={}){
25
25
  if(process.platform!=='win32'||process.arch!=='x64')throw Error('Native desktop currently requires Windows x64');
26
26
  await verifyNativeArtifact();const paths=await prepareRoot(options);await noLink(paths.session);
27
27
  const pipe=controlAddress({uuid:randomUUID()}),token=randomBytes(32).toString('hex');
28
- const child=spawn(fileURLToPath(binary),[fileURLToPath(bundle),fileURLToPath(new URL('../../',root)),'--production'],{detached:true,windowsHide:true,stdio:['pipe','pipe','pipe'],env:{...process.env,OPENROUTER_API_KEY:undefined}});
28
+ const args=[fileURLToPath(bundle),fileURLToPath(new URL('../../',root)),'--production'];
29
+ if(options.nativeDiagnostic&&options.nativeVerifyPaint)args.push('--verify-regions');
30
+ if(options.nativeDiagnostic&&options.nativeProofPath)args.push('--proof='+options.nativeProofPath);
31
+ const child=spawn(fileURLToPath(binary),args,{detached:true,windowsHide:true,stdio:['pipe','pipe','pipe'],env:{...process.env,OPENROUTER_API_KEY:undefined}});
29
32
  let owns=false,complete=false,stderr='';const session={owner:'bitefoundry-retrieviewer-overlay-v1',pid:child.pid,pipe,token};
30
33
  child.stderr.on('data',b=>stderr=(stderr+b.toString('utf8')).slice(-2048));child.stdin.on('error',()=>{});
31
34
  try{
@@ -74,6 +74,7 @@ static JSValue blit(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{
74
74
  }catch(const std::exception& e){return failure(c,e);}}
75
75
  int wmain(int argc,wchar_t** wideArgs){
76
76
  std::vector<std::string> argText;for(int i=0;i<argc;i++){int n=WideCharToMultiByte(CP_UTF8,0,wideArgs[i],-1,nullptr,0,nullptr,nullptr);std::string value(n,0);WideCharToMultiByte(CP_UTF8,0,wideArgs[i],-1,value.data(),n,nullptr,nullptr);value.pop_back();argText.push_back(value);}std::vector<char*> args;for(auto& text:argText)args.push_back(text.data());char** argv=args.data();
77
+ if(argc==2&&std::string(argv[1])=="--self-test-regions")try{return NativePet::selfTestPaintRegions();}catch(const std::exception& e){fprintf(stderr,"%s\n",e.what());return 1;}
77
78
  bool production=false;for(int i=1;i<argc;i++)if(std::string(argv[i])=="--production")production=true;std::string configuration;
78
79
  if(production){char ch;while(std::cin.get(ch)&&ch!='\n'){if(configuration.size()>=8192)return 2;configuration+=ch;}if(configuration.empty())return 2;}
79
80
  if(argc<2)return 2;JSRuntime* rt=JS_NewRuntime();js_std_init_handlers(rt);JSContext* ctx=JS_NewContext(rt);JS_SetModuleLoaderFunc2(rt,nullptr,js_module_loader,js_module_check_attributes,nullptr);js_init_module_std(ctx,"qjs:std");js_init_module_os(ctx,"qjs:os");js_std_add_helpers(ctx,argc-1,argv+1);
@@ -7,8 +7,8 @@
7
7
  "sha256": "e9da942dbe57ac778c44ea8f300e2e868face5e73b00ffcd4399aeb7e94ee09c"
8
8
  },
9
9
  "bin/win32-x64/retrieviewer-native.exe": {
10
- "bytes": 1551872,
11
- "sha256": "317841c3a61f78ee57e56b9eb7f778d59d17ee6a3ab49e04eab37a40509799c1"
10
+ "bytes": 1564672,
11
+ "sha256": "288150c7112c50214996174250ce71bfa4ffc91779c8ea899a47d85f4fef71dd"
12
12
  }
13
13
  }
14
14
  }
@@ -0,0 +1,86 @@
1
+ // Included inside NativePet. Shared final-mask stamps preserve original pixels.
2
+ struct Surface {
3
+ HDC dc=nullptr;HBITMAP bitmap=nullptr;HGDIOBJ old=nullptr;uint8_t* bits=nullptr;int width=0,height=0;
4
+ ~Surface(){release();}
5
+ uint64_t bytes() const {return (uint64_t)width*height*4;}
6
+ void release(){if(dc&&old)SelectObject(dc,old);if(bitmap)DeleteObject(bitmap);if(dc)DeleteDC(dc);dc=nullptr;bitmap=nullptr;old=nullptr;bits=nullptr;width=height=0;}
7
+ void ensure(int w,int h){
8
+ const int rw=PetPaint::rounded(w),rh=PetPaint::rounded(h);
9
+ if(w<=width&&h<=height&&(uint64_t)width*height<=(uint64_t)rw*rh*2)return;
10
+ int nw=std::max(width,rw),nh=std::max(height,rh);
11
+ if((uint64_t)nw*nh>(uint64_t)rw*rh*2){nw=rw;nh=rh;}
12
+ release();width=nw;height=nh;BITMAPINFO info{};info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);info.bmiHeader.biWidth=nw;info.bmiHeader.biHeight=-nh;info.bmiHeader.biPlanes=1;info.bmiHeader.biBitCount=32;info.bmiHeader.biCompression=BI_RGB;
13
+ HDC screen=GetDC(nullptr);dc=CreateCompatibleDC(screen);bitmap=CreateDIBSection(screen,&info,DIB_RGB_COLORS,(void**)&bits,nullptr,0);ReleaseDC(nullptr,screen);
14
+ if(!bitmap||!dc)throw std::runtime_error("Native surface allocation");old=SelectObject(dc,bitmap);
15
+ }
16
+ };
17
+ struct Panel {
18
+ HWND window=nullptr;Surface surface;PetPaint::Rect crop;
19
+ void release(){if(window)DestroyWindow(window);window=nullptr;surface.release();crop={};}
20
+ };
21
+ static std::array<Panel,PetPaint::MaxRegions> panels;
22
+ static void releasePanels(){for(auto& p:panels)p.release();}
23
+ static void hidePanels(){releasePanels();}
24
+ static uint64_t paintBytes(){uint64_t total=0;for(const auto& p:panels)total+=p.surface.bytes();return total;}
25
+ static size_t paintCount(){size_t n=0;for(const auto& p:panels)if(p.window)n++;return n;}
26
+ struct DotAtlas {
27
+ int cell=0;uint32_t color=0;std::vector<uint8_t> pixels;
28
+ void ensure(int value){
29
+ if(cell==value&&color==ink().GetValue())return;cell=value;color=ink().GetValue();size_t length=(size_t)cell*cell*2*4;pixels.assign(length*256,0);
30
+ static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
31
+ for(int mask=0;mask<256;mask++){Gdiplus::Bitmap bitmap(cell,cell*2,cell*4,PixelFormat32bppPARGB,pixels.data()+mask*length);Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float x=(dx[bit]+.5f)*step,y=(dy[bit]+.5f)*step;g.FillEllipse(&brush,x-diameter/2,y-diameter/2,diameter,diameter);}}
32
+ }
33
+ };
34
+ static DotAtlas atlas;
35
+ static uint64_t verifiedPaints=0;
36
+ static std::vector<uint8_t> assemble(const PetPaint::Rect& all,int cell){
37
+ const int width=all.width()*cell,height=all.height()*cell*2;std::vector<uint8_t> image((size_t)width*height*4,0);
38
+ for(const auto& p:panels)if(p.window){const auto& r=p.crop;const int w=r.width()*cell,h=r.height()*cell*2;
39
+ for(int y=0;y<h;y++)memcpy(image.data()+((size_t)((r.top-all.top)*cell*2+y)*width+(r.left-all.left)*cell)*4,p.surface.bits+(size_t)y*p.surface.width*4,(size_t)w*4);
40
+ }
41
+ return image;
42
+ }
43
+ static void verifyPaint(const std::vector<uint8_t>& masks,int columns,const PetPaint::Rect& all,int cell){
44
+ const int w=all.width()*cell,h=all.height()*cell*2;std::vector<uint8_t> reference((size_t)w*h*4,0);
45
+ {Gdiplus::Bitmap bitmap(w,h,w*4,PixelFormat32bppPARGB,reference.data());Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());
46
+ static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
47
+ for(int y=all.top;y<all.bottom;y++)for(int x=all.left;x<all.right;x++){int mask=masks[(size_t)y*columns+x];for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float px=(x-all.left)*cell+(dx[bit]+.5f)*step,py=(y-all.top)*cell*2+(dy[bit]+.5f)*step;g.FillEllipse(&brush,px-diameter/2,py-diameter/2,diameter,diameter);}}g.Flush(Gdiplus::FlushIntentionSync);
48
+ }
49
+ if(reference!=assemble(all,cell))throw std::runtime_error("Partitioned dots changed pixels");verifiedPaints++;
50
+ if(!atlasVerified){printf("{\"stage\":\"dot-atlas-verified\",\"pixelExact\":true,\"regions\":%zu}\n",paintCount());fflush(stdout);}atlasVerified=true;
51
+ }
52
+ static void paint(const std::string& text,int columns,int rows,int cell,POINT canvas){
53
+ std::vector<uint8_t> masks((size_t)columns*rows);size_t at=0;
54
+ for(int y=0;y<rows;y++){if(y){if(at>=text.size()||text[at++]!='\n')throw std::runtime_error("Native row separator");}for(int x=0;x<columns;x++){if(at+3>text.size())throw std::runtime_error("Native text length");const auto* p=(const unsigned char*)text.data()+at;uint32_t ch=((p[0]&15)<<12)|((p[1]&63)<<6)|(p[2]&63);at+=3;if(ch<0x2800||ch>0x28ff)throw std::runtime_error("Native glyph");masks[(size_t)y*columns+x]=(uint8_t)(ch-0x2800);}}
55
+ if(at!=text.size())throw std::runtime_error("Native trailing text");
56
+ const auto regions=PetPaint::partition(masks,columns,rows,cell);if(regions.empty()){releasePanels();return;}
57
+ PetPaint::Rect all;for(const auto& r:regions)all.include(r);
58
+ if((int64_t)all.width()*cell*all.height()*cell*2>8000000){hidePanels();visible=false;return;}
59
+ atlas.ensure(cell);const size_t stampSize=(size_t)cell*cell*2*4;
60
+ for(size_t i=0;i<regions.size();i++){
61
+ auto& panel=panels[i];panel.crop=regions[i];const auto& r=panel.crop;auto& surface=panel.surface;
62
+ if(!panel.window){panel.window=CreateWindowEx(WS_EX_LAYERED|WS_EX_TRANSPARENT|WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE|WS_EX_TOPMOST,L"RetrieviewerNativeDots",L"",WS_POPUP,0,0,1,1,nullptr,nullptr,GetModuleHandle(nullptr),nullptr);if(!panel.window)throw std::runtime_error("Native overlay region");}
63
+ surface.ensure(r.width()*cell,r.height()*cell*2);std::fill(surface.bits,surface.bits+(size_t)surface.width*surface.height*4,0);
64
+ for(int y=r.top;y<r.bottom;y++)for(int x=r.left;x<r.right;x++){int mask=masks[(size_t)y*columns+x];if(!mask)continue;const uint8_t* stamp=atlas.pixels.data()+mask*stampSize;for(int sy=0;sy<cell*2;sy++)memcpy(surface.bits+((size_t)((y-r.top)*cell*2+sy)*surface.width+(x-r.left)*cell)*4,stamp+(size_t)sy*cell*4,cell*4);}
65
+ }
66
+ for(size_t i=regions.size();i<panels.size();i++)panels[i].release();
67
+ if(verifyAtlas&&(!atlasVerified||verifyEveryPaint))verifyPaint(masks,columns,all,cell);
68
+ if(saveProof&&!proofSaved){const int w=all.width()*cell,h=all.height()*cell*2;auto image=assemble(all,cell);Gdiplus::Bitmap source(w,h,w*4,PixelFormat32bppPARGB,image.data()),proof(w,h,PixelFormat32bppARGB);Gdiplus::Graphics pg(&proof);pg.Clear(Gdiplus::Color(255,0,0,0));pg.DrawImage(&source,Gdiplus::Rect(0,0,w,h),0,0,w,h,Gdiplus::UnitPixel);pg.Flush(Gdiplus::FlushIntentionSync);CLSID png={0x557cf406,0x1a04,0x11d3,{0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e}};if(proof.Save(wide(proofPath).c_str(),&png,nullptr)!=Gdiplus::Ok)throw std::runtime_error("PNG proof failed");proofSaved=true;}
69
+ // Prepare every region before uploading any. The disjoint cell boundaries
70
+ // neither duplicate alpha nor clip a glyph; all share the same source frame.
71
+ HDC screen=GetDC(nullptr);BLENDFUNCTION blend={AC_SRC_OVER,0,255,AC_SRC_ALPHA};POINT origin={0,0};
72
+ for(size_t i=0;i<regions.size();i++){auto& p=panels[i];POINT destination={canvas.x+p.crop.left*cell,canvas.y+p.crop.top*cell*2};SIZE size={p.crop.width()*cell,p.crop.height()*cell*2};BOOL ok=UpdateLayeredWindow(p.window,screen,&destination,&size,p.surface.dc,&origin,0,&blend,ULW_ALPHA);if(!ok){ReleaseDC(nullptr,screen);throw std::runtime_error("Native layered update");}SetWindowPos(p.window,HWND_TOPMOST,destination.x,destination.y,size.cx,size.cy,SWP_NOACTIVATE|SWP_SHOWWINDOW);}
73
+ ReleaseDC(nullptr,screen);painted++;
74
+ }
75
+ static int selfTestPaintRegions(){
76
+ uint32_t random=731;auto next=[&](){random^=random<<13;random^=random>>17;random^=random<<5;return random;};size_t checks=0;
77
+ for(int cell:{6,8,10,12,16,24,32})for(int type=0;type<8;type++){
78
+ const int cols=type==7?1000:240,rows=type==7?125:90;std::vector<uint8_t> masks((size_t)cols*rows);
79
+ for(int y=0;y<rows;y++)for(int x=0;x<cols;x++){bool on=type==1||type==2&&x%61==0||type==3&&y%31==0||type==4&&(x<20||x>cols-30)&&y>10||type==5&&(next()%100<4)||type==6&&((x-cols/2)*(x-cols/2)+(y-rows/2)*(y-rows/2)<900)||type==7&&x%97<3&&y%17<2;if(on)masks[(size_t)y*cols+x]=(uint8_t)(1+next()%255);}
80
+ const auto regions=PetPaint::partition(masks,cols,rows,cell);if(regions.size()>PetPaint::MaxRegions)throw std::runtime_error("Region count overflow");std::vector<uint8_t> seen(masks.size(),0);PetPaint::Rect all;uint64_t bytes=0;
81
+ for(const auto& r:regions){if(r.empty()||r.left<0||r.top<0||r.right>cols||r.bottom>rows)throw std::runtime_error("Region outside grid");all.include(r);bytes+=PetPaint::bytes(r,cell);for(int y=r.top;y<r.bottom;y++)for(int x=r.left;x<r.right;x++)if(++seen[(size_t)y*cols+x]!=1)throw std::runtime_error("Overlapping paint regions");}
82
+ for(size_t i=0;i<masks.size();i++)if(masks[i]&&seen[i]!=1)throw std::runtime_error("Missing mask cell");if(bytes>PetPaint::bytes(all,cell))throw std::runtime_error("Partition grew allocation");checks++;
83
+ }
84
+ Surface s;s.ensure(2048,1024);const auto big=s.bytes();s.ensure(1800,900);if(s.bytes()!=big)throw std::runtime_error("Surface churn on small size change");s.ensure(96,64);if(s.bytes()>(uint64_t)128*64*4*2)throw std::runtime_error("Surface did not shrink");s.release();if(s.bytes()!=0)throw std::runtime_error("Surface did not release");
85
+ printf("{\"passed\":true,\"partitionCases\":%zu,\"coverageAndDisjointness\":true,\"surfaceShrink\":true}\n",checks);return 0;
86
+ }
@@ -1,16 +1,19 @@
1
1
  #include <windows.h>
2
2
  #include <gdiplus.h>
3
3
  #include <memory>
4
+ #include <array>
5
+ #include "paint-regions.hpp"
4
6
  #include "native-control.hpp"
5
7
  namespace NativePet {
6
8
  static JSContext* context;
7
- static HWND target=nullptr,overlay=nullptr;
9
+ static HWND target=nullptr;
8
10
  static bool showing=false,paused=false,failed=false;
9
11
  static ULONGLONG startTime=0,lastTick=0,durationMs=0;
10
12
  static int stage=0,painted=0;
11
13
  static std::string previous;
12
14
  static std::vector<double> timings;
13
- static bool saveProof=false,verifyAtlas=false,proofSaved=false,atlasVerified=false;
15
+ static bool saveProof=false,verifyAtlas=false,verifyEveryPaint=false,proofSaved=false,atlasVerified=false;
16
+ static std::string proofPath="dist/native-memory/native-window-proof.png";
14
17
  static bool production=false,diagnostic=false,testPicker=false,visible=false,inkExplicit=false;
15
18
  static HWND followed=nullptr,picker=nullptr,windowList=nullptr;
16
19
  static DWORD followedPid=0;
@@ -27,39 +30,15 @@ static Gdiplus::Color ink(){return inkName=="dark"?Gdiplus::Color(245,67,48,24):
27
30
  static void updateStatus();static void chooseWindow();static void layoutControls();
28
31
  static JSValue call(const char* name,int argc=0,JSValue* args=nullptr){JSValue global=JS_GetGlobalObject(context),fn=JS_GetPropertyStr(context,global,name),out=JS_Call(context,fn,global,argc,args);JS_FreeValue(context,fn);JS_FreeValue(context,global);if(JS_IsException(out)){js_std_dump_error(context);failed=true;if(target)PostMessage(target,WM_CLOSE,0,0);}return out;}
29
32
  static void interact(const char* kind){JSValue arg=JS_NewString(context,kind),result=call("__desktopInteract",1,&arg);bool accepted=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,result);JS_FreeValue(context,arg);lastKind=kind;lastAccepted=accepted;if(!production||diagnostic){printf("{\"interaction\":\"%s\",\"accepted\":%s}\n",kind,accepted?"true":"false");fflush(stdout);}previous.clear();updateStatus();}
30
- struct Surface {
31
- HDC dc=nullptr;HBITMAP bitmap=nullptr;HGDIOBJ old=nullptr;uint8_t* bits=nullptr;int width=0,height=0;
32
- ~Surface(){release();}
33
- void release(){if(dc){if(old)SelectObject(dc,old);if(bitmap)DeleteObject(bitmap);DeleteDC(dc);}dc=nullptr;bitmap=nullptr;old=nullptr;bits=nullptr;width=height=0;}
34
- void ensure(int w,int h){if(w<=width&&h<=height)return;w=std::max(width,(w+63)/64*64);h=std::max(height,(h+63)/64*64);release();width=w;height=h;BITMAPINFO info{};info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);info.bmiHeader.biWidth=w;info.bmiHeader.biHeight=-h;info.bmiHeader.biPlanes=1;info.bmiHeader.biBitCount=32;info.bmiHeader.biCompression=BI_RGB;HDC screen=GetDC(nullptr);dc=CreateCompatibleDC(screen);bitmap=CreateDIBSection(screen,&info,DIB_RGB_COLORS,(void**)&bits,nullptr,0);ReleaseDC(nullptr,screen);if(!bitmap||!dc)throw std::runtime_error("Native surface allocation");old=SelectObject(dc,bitmap);}
35
- };
36
- static Surface surface;
37
- struct DotAtlas {
38
- int cell=0;uint32_t color=0;std::vector<uint8_t> pixels;
39
- void ensure(int value){if(cell==value&&color==ink().GetValue())return;cell=value;color=ink().GetValue();size_t length=(size_t)cell*cell*2*4;pixels.assign(length*256,0);static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
40
- for(int mask=0;mask<256;mask++){Gdiplus::Bitmap bitmap(cell,cell*2,cell*4,PixelFormat32bppPARGB,pixels.data()+mask*length);Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float x=(dx[bit]+.5f)*step,y=(dy[bit]+.5f)*step;g.FillEllipse(&brush,x-diameter/2,y-diameter/2,diameter,diameter);}}
41
- }
42
- };
43
- static DotAtlas atlas;
44
- static void paint(const std::string& text,int columns,int rows,int cell,POINT canvas){
45
- std::vector<uint8_t> masks((size_t)columns*rows);size_t at=0;int left=columns,top=rows,right=-1,bottom=-1;
46
- for(int y=0;y<rows;y++){if(y){if(at>=text.size()||text[at++]!='\n')throw std::runtime_error("Native row separator");}for(int x=0;x<columns;x++){if(at+3>text.size())throw std::runtime_error("Native text length");const auto* p=(const unsigned char*)text.data()+at;uint32_t ch=((p[0]&15)<<12)|((p[1]&63)<<6)|(p[2]&63);at+=3;if(ch<0x2800||ch>0x28ff)throw std::runtime_error("Native glyph");uint8_t mask=(uint8_t)(ch-0x2800);masks[y*columns+x]=mask;if(mask){left=std::min(left,x);right=std::max(right,x);top=std::min(top,y);bottom=std::max(bottom,y);}}}
47
- if(at!=text.size())throw std::runtime_error("Native trailing text");if(right<left){ShowWindow(overlay,SW_HIDE);return;}
48
- int w=(right-left+1)*cell,h=(bottom-top+1)*cell*2;if((int64_t)w*h>8000000){ShowWindow(overlay,SW_HIDE);visible=false;return;}surface.ensure(w,h);std::fill(surface.bits,surface.bits+(size_t)surface.width*surface.height*4,0);
49
- atlas.ensure(cell);size_t stampSize=(size_t)cell*cell*2*4;
50
- for(int y=top;y<=bottom;y++)for(int x=left;x<=right;x++){int mask=masks[y*columns+x];if(!mask)continue;const uint8_t* stamp=atlas.pixels.data()+mask*stampSize;for(int sy=0;sy<cell*2;sy++)memcpy(surface.bits+((size_t)((y-top)*cell*2+sy)*surface.width+(x-left)*cell)*4,stamp+(size_t)sy*cell*4,cell*4);}
51
- if(verifyAtlas&&!atlasVerified){std::vector<uint8_t> reference((size_t)w*h*4,0);Gdiplus::Bitmap bitmap(w,h,w*4,PixelFormat32bppPARGB,reference.data());Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush ink(NativePet::ink());static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);for(int y=top;y<=bottom;y++)for(int x=left;x<=right;x++){int mask=masks[y*columns+x];for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float px=(x-left)*cell+(dx[bit]+.5f)*step,py=(y-top)*cell*2+(dy[bit]+.5f)*step;g.FillEllipse(&ink,px-diameter/2,py-diameter/2,diameter,diameter);}}g.Flush(Gdiplus::FlushIntentionSync);for(int y=0;y<h;y++)if(memcmp(reference.data()+(size_t)y*w*4,surface.bits+(size_t)y*surface.width*4,w*4)!=0)throw std::runtime_error("Dot atlas changed pixels");atlasVerified=true;printf("{\"stage\":\"dot-atlas-verified\",\"pixelExact\":true}\n");fflush(stdout);}
52
- if(saveProof&&!proofSaved){Gdiplus::Bitmap source(surface.width,surface.height,surface.width*4,PixelFormat32bppPARGB,surface.bits),proof(w,h,PixelFormat32bppARGB);Gdiplus::Graphics pg(&proof);pg.Clear(Gdiplus::Color(255,0,0,0));pg.DrawImage(&source,Gdiplus::Rect(0,0,w,h),0,0,w,h,Gdiplus::UnitPixel);pg.Flush(Gdiplus::FlushIntentionSync);CLSID png={0x557cf406,0x1a04,0x11d3,{0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e}};if(proof.Save(L"dist/native-memory/native-window-proof.png",&png,nullptr)!=Gdiplus::Ok)throw std::runtime_error("PNG proof failed");proofSaved=true;}
53
- POINT destination={canvas.x+left*cell,canvas.y+top*cell*2},origin={0,0};SIZE size={w,h};BLENDFUNCTION blend={AC_SRC_OVER,0,255,AC_SRC_ALPHA};HDC screen=GetDC(nullptr);BOOL result=UpdateLayeredWindow(overlay,screen,&destination,&size,surface.dc,&origin,0,&blend,ULW_ALPHA);ReleaseDC(nullptr,screen);if(!result)throw std::runtime_error("Native layered update");SetWindowPos(overlay,HWND_TOPMOST,destination.x,destination.y,w,h,SWP_NOACTIVATE|SWP_SHOWWINDOW);painted++;
54
- }
33
+ #include "native-paint.hpp"
55
34
  static void updateStatus(){
56
35
  if(!production)return;
57
36
  PROCESS_MEMORY_COUNTERS_EX memory{};GetProcessMemoryInfo(GetCurrentProcess(),(PROCESS_MEMORY_COUNTERS*)&memory,sizeof(memory));
58
- std::ostringstream out;out<<"{\"running\":true,\"reachable\":true,\"engine\":\"quickjs-native\",\"pet\":\""<<petKind<<"\",\"ink\":\""<<inkName<<"\",\"returnStyle\":\""<<returnStyle<<"\",\"selected\":"<<(followed?"true":"false")<<",\"visible\":"<<(visible?"true":"false")<<",\"pickerVisible\":"<<(picker?"true":"false")<<",\"paused\":"<<(paused?"true":"false")<<",\"frames\":"<<frames<<",\"paintedFrames\":"<<painted<<",\"pid\":"<<GetCurrentProcessId()<<",\"painterPid\":"<<GetCurrentProcessId()<<",\"rssBytes\":"<<memory.WorkingSetSize<<",\"peakWorkingSetBytes\":"<<memory.PeakWorkingSetSize<<",\"privateBytes\":"<<memory.PrivateUsage<<",\"watch\":{\"source\":\"off\",\"state\":\"off\",\"pendingCount\":0},\"brain\":{\"provider\":\"off\",\"state\":\"off\"},\"lastInteraction\":";
37
+ std::ostringstream out;out<<"{\"running\":true,\"reachable\":true,\"engine\":\"quickjs-native\",\"pet\":\""<<petKind<<"\",\"ink\":\""<<inkName<<"\",\"returnStyle\":\""<<returnStyle<<"\",\"selected\":"<<(followed?"true":"false")<<",\"visible\":"<<(visible?"true":"false")<<",\"pickerVisible\":"<<(picker?"true":"false")<<",\"paused\":"<<(paused?"true":"false")<<",\"frames\":"<<frames<<",\"paintedFrames\":"<<painted<<",\"pid\":"<<GetCurrentProcessId()<<",\"painterPid\":"<<GetCurrentProcessId()<<",\"rssBytes\":"<<memory.WorkingSetSize<<",\"peakWorkingSetBytes\":"<<memory.PeakWorkingSetSize<<",\"privateBytes\":"<<memory.PrivateUsage<<",\"paintSurfaceBytes\":"<<paintBytes()<<",\"paintRegionCount\":"<<paintCount()<<",\"paintVerificationCount\":"<<verifiedPaints<<",\"watch\":{\"source\":\"off\",\"state\":\"off\",\"pendingCount\":0},\"brain\":{\"provider\":\"off\",\"state\":\"off\"},\"lastInteraction\":";
59
38
  if(lastKind.empty())out<<"null";else out<<"{\"kind\":\""<<lastKind<<"\",\"accepted\":"<<(lastAccepted?"true":"false")<<"}";out<<"}";control.update(out.str());
60
39
  }
61
40
  static void switchPet(const std::string& next){
62
- if(next==petKind)return;JSValue arg=JS_NewString(context,next.c_str()),result=call("__desktopSwitchPet",1,&arg);bool ok=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,arg);JS_FreeValue(context,result);if(!ok)return;petKind=next;if(!inkExplicit)inkName=next=="cat"?"gold":"cream";previous.clear();surface.release();lastCanvas={INT_MIN,INT_MIN};layoutControls();updateStatus();
41
+ if(next==petKind)return;JSValue arg=JS_NewString(context,next.c_str()),result=call("__desktopSwitchPet",1,&arg);bool ok=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,arg);JS_FreeValue(context,result);if(!ok)return;petKind=next;if(!inkExplicit)inkName=next=="cat"?"gold":"cream";previous.clear();releasePanels();lastCanvas={INT_MIN,INT_MIN};layoutControls();updateStatus();
63
42
  }
64
43
  static BOOL CALLBACK enumerate(HWND window,LPARAM){
65
44
  if(!IsWindowVisible(window)||GetWindow(window,GW_OWNER))return TRUE;DWORD pid=0;GetWindowThreadProcessId(window,&pid);if(!pid||pid==GetCurrentProcessId())return TRUE;wchar_t title[512];if(!GetWindowText(window,title,512))return TRUE;RECT rect;if(!GetClientRect(window,&rect)||rect.right<160||rect.bottom<120)return TRUE;LRESULT index=SendMessage(windowList,LB_ADDSTRING,0,(LPARAM)title);if(index!=LB_ERR&&index!=LB_ERRSPACE){SendMessage(windowList,LB_SETITEMDATA,index,(LPARAM)choices.size());choices.emplace_back(window,pid);}return TRUE;
@@ -73,7 +52,7 @@ static LRESULT CALLBACK pickerProcedure(HWND window,UINT message,WPARAM w,LPARAM
73
52
  if(message==WM_DESTROY){if(picker==window)picker=nullptr;if(!followed&&target)PostMessage(target,WM_CLOSE,0,0);updateStatus();return 0;}return DefWindowProc(window,message,w,l);
74
53
  }
75
54
  static void chooseWindow(){
76
- if(!production||(diagnostic&&!testPicker))return;if(picker){SetForegroundWindow(picker);return;}visible=false;ShowWindow(overlay,SW_HIDE);ShowWindow(target,SW_HIDE);previous.clear();WNDCLASS wc{};wc.lpfnWndProc=pickerProcedure;wc.hInstance=GetModuleHandle(nullptr);wc.lpszClassName=L"RetrieviewerNativePicker";wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);RegisterClass(&wc);
55
+ if(!production||(diagnostic&&!testPicker))return;if(picker){SetForegroundWindow(picker);return;}visible=false;hidePanels();ShowWindow(target,SW_HIDE);previous.clear();WNDCLASS wc{};wc.lpfnWndProc=pickerProcedure;wc.hInstance=GetModuleHandle(nullptr);wc.lpszClassName=L"RetrieviewerNativePicker";wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);RegisterClass(&wc);
77
56
  picker=CreateWindowEx(WS_EX_TOOLWINDOW,wc.lpszClassName,L"Retrieviewer - choose an app window",WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU, std::max(0,(GetSystemMetrics(SM_CXSCREEN)-650)/2),std::max(0,(GetSystemMetrics(SM_CYSCREEN)-450)/2),650,450,nullptr,nullptr,wc.hInstance,nullptr);if(!picker)throw std::runtime_error("Cannot open window picker");
78
57
  addPetCombo(picker);CreateWindow(L"STATIC",L"Choose the app window your companion should follow.",WS_CHILD|WS_VISIBLE,136,12,490,24,picker,nullptr,wc.hInstance,nullptr);windowList=CreateWindowEx(WS_EX_CLIENTEDGE,L"LISTBOX",L"",WS_CHILD|WS_VISIBLE|WS_VSCROLL|LBS_NOTIFY,12,44,610,310,picker,(HMENU)100,wc.hInstance,nullptr);CreateWindow(L"BUTTON",L"Attach",WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON,434,367,90,30,picker,(HMENU)101,wc.hInstance,nullptr);CreateWindow(L"BUTTON",L"Refresh",WS_CHILD|WS_VISIBLE,532,367,90,30,picker,(HMENU)102,wc.hInstance,nullptr);choices.clear();EnumWindows(enumerate,0);ShowWindow(picker,SW_SHOW);SetWindowPos(picker,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);SetForegroundWindow(picker);updateStatus();
79
58
  }
@@ -91,9 +70,9 @@ static void tick(){
91
70
  DWORD scenePid=0;if(scene)GetWindowThreadProcessId(scene,&scenePid);
92
71
  if(production&&(!diagnostic||testPicker)&&scene&&(!IsWindow(scene)||scenePid!=followedPid)){followed=nullptr;chooseWindow();scene=nullptr;}
93
72
  if(picker||!scene||IsIconic(scene)||(production&&(!diagnostic||testPicker)&&GetForegroundWindow()!=scene&&GetForegroundWindow()!=target)||(showing&&GetForegroundWindow()!=target)){
94
- visible=false;ShowWindow(overlay,SW_HIDE);if(production&&(!diagnostic||testPicker))ShowWindow(target,SW_HIDE);previous.clear();updateStatus();return;
73
+ visible=false;hidePanels();if(production&&(!diagnostic||testPicker))ShowWindow(target,SW_HIDE);previous.clear();updateStatus();return;
95
74
  }
96
- RECT rect;GetClientRect(scene,&rect);POINT canvas{0,0};ClientToScreen(scene,&canvas);int cell=8,columns=rect.right/cell,rows=rect.bottom/(cell*2);while(columns*rows>125000&&cell<32){cell+=2;columns=rect.right/cell;rows=rect.bottom/(cell*2);}if(columns<8||rows<4||columns*rows>125000){visible=false;ShowWindow(overlay,SW_HIDE);previous.clear();updateStatus();return;}
75
+ RECT rect;GetClientRect(scene,&rect);POINT canvas{0,0};ClientToScreen(scene,&canvas);int cell=8,columns=rect.right/cell,rows=rect.bottom/(cell*2);while(columns*rows>125000&&cell<32){cell+=2;columns=rect.right/cell;rows=rect.bottom/(cell*2);}if(columns<8||rows<4||columns*rows>125000){visible=false;hidePanels();previous.clear();updateStatus();return;}
97
76
  visible=true;if(canvas.x!=lastCanvas.x||canvas.y!=lastCanvas.y||rect.right!=lastWidth||rect.bottom!=lastHeight){previous.clear();lastCanvas=canvas;lastWidth=rect.right;lastHeight=rect.bottom;}
98
77
  if(production&&(!diagnostic||testPicker)){RECT bar;GetWindowRect(target,&bar);int x=canvas.x+std::max<LONG>(0,rect.right-(bar.right-bar.left)),y=canvas.y+8;if(bar.left!=x||bar.top!=y||!IsWindowVisible(target))SetWindowPos(target,HWND_TOPMOST,x,y,0,0,SWP_NOSIZE|SWP_NOACTIVATE|SWP_SHOWWINDOW);}
99
78
  if(paused){updateStatus();return;}
@@ -105,24 +84,24 @@ static LRESULT CALLBACK procedure(HWND window,UINT message,WPARAM w,LPARAM l){
105
84
  if(message==WM_APP+1){if(w==1||w==2)switchPet(w==2?"cat":"dog");return 0;}
106
85
  if(message==WM_COMMAND){int id=LOWORD(w);if(id==9&&HIWORD(w)==CBN_SELCHANGE)switchPet(SendMessage((HWND)l,CB_GETCURSEL,0,0)==1?"cat":"dog");else if(id==1)interact(petKind=="cat"?"greet":"ball");else if(id==2)interact("treat");else if(id==3)interact(petKind=="cat"?"company":"tug");else if(id==4){paused=!paused;previous.clear();layoutControls();updateStatus();}else if(id==5){if(production)chooseWindow();else interact("paw");}else if(id==6)interact(production?"tower":"wall");else if(id==8)interact("wheel");else if(id==7)DestroyWindow(window);return 0;}
107
86
  if(message==WM_SIZE||message==WM_MOVE){previous.clear();return 0;}
108
- if(message==WM_DESTROY){KillTimer(window,1);followed=nullptr;if(picker){HWND old=picker;picker=nullptr;SetWindowLongPtr(old,GWLP_WNDPROC,(LONG_PTR)DefWindowProc);DestroyWindow(old);}if(overlay)DestroyWindow(overlay);overlay=nullptr;PostQuitMessage(0);return 0;}
87
+ if(message==WM_DESTROY){KillTimer(window,1);followed=nullptr;if(picker){HWND old=picker;picker=nullptr;SetWindowLongPtr(old,GWLP_WNDPROC,(LONG_PTR)DefWindowProc);DestroyWindow(old);}releasePanels();PostQuitMessage(0);return 0;}
109
88
  return DefWindowProc(window,message,w,l);
110
89
  }
111
90
  static int run(JSContext* ctx,int argc,char** argv){
112
- context=ctx;showing=false;durationMs=150000;for(int i=1;i<argc;i++){std::string arg=argv[i];if(arg=="--production"){production=true;durationMs=0;}if(arg=="--show"){showing=true;durationMs=0;}if(arg=="--proof")saveProof=true;if(arg=="--verify-atlas")verifyAtlas=true;if(arg.rfind("--duration=",0)==0)durationMs=(ULONGLONG)(std::stod(arg.substr(11))*1000);}
91
+ context=ctx;showing=false;durationMs=150000;for(int i=1;i<argc;i++){std::string arg=argv[i];if(arg=="--production"){production=true;durationMs=0;}if(arg=="--show"){showing=true;durationMs=0;}if(arg=="--proof")saveProof=true;if(arg=="--verify-atlas")verifyAtlas=true;if(arg=="--verify-regions"){verifyAtlas=true;verifyEveryPaint=true;}if(arg.rfind("--proof=",0)==0){saveProof=true;proofPath=arg.substr(8);}if(arg.rfind("--duration=",0)==0)durationMs=(ULONGLONG)(std::stod(arg.substr(11))*1000);}
113
92
  JSValue global=JS_GetGlobalObject(ctx),config=JS_GetPropertyStr(ctx,global,"__nativeConfig");if(JS_IsObject(config)){petKind=propertyString(ctx,config,"pet","dog");inkName=propertyString(ctx,config,"ink",petKind=="cat"?"gold":"cream");returnStyle=propertyString(ctx,config,"returnStyle","auto");inkExplicit=propertyBool(ctx,config,"inkExplicit");diagnostic=propertyBool(ctx,config,"diagnostic");testPicker=diagnostic&&propertyBool(ctx,config,"testPicker");}if(petKind!="dog"&&petKind!="cat")throw std::runtime_error("Invalid native pet");if(inkName!="cream"&&inkName!="gold"&&inkName!="dark")throw std::runtime_error("Invalid native ink");
114
93
  SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);Gdiplus::GdiplusStartupInput gdip;ULONG_PTR token;if(Gdiplus::GdiplusStartup(&token,&gdip,nullptr)!=Gdiplus::Ok)throw std::runtime_error("GDI+ startup");
115
94
  if(production){std::wstring name=wide("Local\\BiteFoundry.Retrieviewer.Overlay."+userSid()+(diagnostic?".Diagnostics."+std::to_string(GetCurrentProcessId()):""));instanceMutex=CreateMutexW(nullptr,TRUE,name.c_str());if(!instanceMutex||GetLastError()==ERROR_ALREADY_EXISTS)throw std::runtime_error("A Retrieviewer companion is already running");}
116
95
  HINSTANCE instance=GetModuleHandle(nullptr);WNDCLASS wc{};wc.lpfnWndProc=procedure;wc.hInstance=instance;wc.lpszClassName=L"RetrieviewerNativeHost";wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);RegisterClass(&wc);
117
96
  const bool attach=production&&(!diagnostic||testPicker);target=CreateWindowEx(WS_EX_TOOLWINDOW|(attach?WS_EX_NOACTIVATE:0),wc.lpszClassName,attach?L"Retrieviewer controls":L"Retrieviewer diagnostic scene",showing?WS_OVERLAPPEDWINDOW:WS_POPUP,showing?80:-12000,showing?80:-12000,attach?552:showing?1280:3840,attach?40:showing?800:2160,nullptr,nullptr,instance,nullptr);if(!target)throw std::runtime_error("Native host window");if(!attach)followed=target;
118
- WNDCLASS pet{};pet.lpfnWndProc=DefWindowProc;pet.hInstance=instance;pet.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&pet);overlay=CreateWindowEx(WS_EX_LAYERED|WS_EX_TRANSPARENT|WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE|WS_EX_TOPMOST,pet.lpszClassName,L"",WS_POPUP,0,0,1,1,nullptr,nullptr,instance,nullptr);if(!overlay)throw std::runtime_error("Native overlay window");
97
+ WNDCLASS pet{};pet.lpfnWndProc=DefWindowProc;pet.hInstance=instance;pet.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&pet);
119
98
  if(attach){addPetCombo(target);for(int id=1;id<=8;id++)CreateWindow(L"BUTTON",L"",WS_CHILD|BS_PUSHBUTTON,0,0,1,1,target,(HMENU)(INT_PTR)id,instance,nullptr);layoutControls();chooseWindow();}
120
99
  else if(showing){const wchar_t* names[]={L"Ball",L"Treat",L"Tug",L"Pause",L"Paw",L"Wall",L"Quit"};for(int i=0;i<7;i++)CreateWindow(L"BUTTON",names[i],WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,8+i*70,8,66,28,target,(HMENU)(INT_PTR)(i+1),instance,nullptr);}
121
100
  if(!attach)ShowWindow(target,showing?SW_SHOW:SW_SHOWNOACTIVATE);startTime=GetTickCount64();lastTick=0;timings.reserve(1024);SetTimer(target,1,33,nullptr);
122
101
  if(production){updateStatus();control.start(propertyString(ctx,config,"pipe"),propertyString(ctx,config,"token"),propertyString(ctx,config,"sessionFile"),[](const std::string& command){PostMessage(target,command=="stop"?WM_CLOSE:WM_APP+1,command=="pet-cat"?2:command=="pet-dog"?1:0,0);});printf("{\"ready\":true,\"pid\":%lu}\n",GetCurrentProcessId());fflush(stdout);startupGuard=std::thread([]{std::string ack;bool ok=std::getline(std::cin,ack)&&ack=="DETACH";if(ok){detached=true;printf("{\"detached\":true}\n");fflush(stdout);}else{DWORD pid=0;GetWindowThreadProcessId(target,&pid);if(pid==GetCurrentProcessId())PostMessage(target,WM_CLOSE,0,0);}});}
123
102
  else{detached=true;printf("{\"stage\":\"native-window\",\"width\":%d,\"height\":%d,\"durationMs\":%llu,\"pid\":%lu}\n",showing?1280:3840,showing?800:2160,durationMs,GetCurrentProcessId());fflush(stdout);}
124
103
  JS_FreeValue(ctx,config);JS_FreeValue(ctx,global);MSG msg;while(GetMessage(&msg,nullptr,0,0)>0){TranslateMessage(&msg);DispatchMessage(&msg);}
125
- if(startupGuard.joinable()){CancelSynchronousIo(startupGuard.native_handle());startupGuard.join();}control.close();JSValue result=call("__desktopDispose");JS_FreeValue(ctx,result);surface.release();if(instanceMutex){ReleaseMutex(instanceMutex);CloseHandle(instanceMutex);instanceMutex=nullptr;}Gdiplus::GdiplusShutdown(token);
104
+ if(startupGuard.joinable()){CancelSynchronousIo(startupGuard.native_handle());startupGuard.join();}control.close();JSValue result=call("__desktopDispose");JS_FreeValue(ctx,result);releasePanels();if(instanceMutex){ReleaseMutex(instanceMutex);CloseHandle(instanceMutex);instanceMutex=nullptr;}Gdiplus::GdiplusShutdown(token);
126
105
  std::sort(timings.begin(),timings.end());double sum=0;for(auto t:timings)sum+=t;if(!production||diagnostic){printf("{\"stage\":\"native-exit\",\"painted\":%d,\"renders\":%llu,\"meanMs\":%.3f,\"p95Ms\":%.3f,\"maxMs\":%.3f,\"failed\":%s}\n",painted,frames,timings.empty()?0:sum/timings.size(),timings.empty()?0:timings[(size_t)(timings.size()*.95)],timings.empty()?0:timings.back(),failed?"true":"false");fflush(stdout);}return failed?1:0;
127
106
  }
128
107
  }
@@ -0,0 +1,55 @@
1
+ #pragma once
2
+ #include <algorithm>
3
+ #include <cstdint>
4
+ #include <limits>
5
+ #include <stdexcept>
6
+ #include <vector>
7
+
8
+ // Cell-aligned, non-overlapping crops of the FINAL mask. No animal/layer redraw.
9
+ namespace PetPaint {
10
+ constexpr size_t MaxRegions=8;
11
+ struct Rect {
12
+ int left=std::numeric_limits<int>::max(),top=std::numeric_limits<int>::max();
13
+ int right=0,bottom=0;
14
+ bool empty() const {return left>=right||top>=bottom;}
15
+ int width() const {return empty()?0:right-left;}
16
+ int height() const {return empty()?0:bottom-top;}
17
+ void include(int x,int y){left=std::min(left,x);top=std::min(top,y);right=std::max(right,x+1);bottom=std::max(bottom,y+1);}
18
+ void include(const Rect& r){if(!r.empty()){left=std::min(left,r.left);top=std::min(top,r.top);right=std::max(right,r.right);bottom=std::max(bottom,r.bottom);}}
19
+ };
20
+ inline int rounded(int n){return (n+63)/64*64;}
21
+ inline uint64_t bytes(const Rect& r,int cell){return r.empty()?0:(uint64_t)rounded(r.width()*cell)*rounded(r.height()*cell*2)*4;}
22
+ struct Split {Rect box,a,b;uint64_t saved=0;};
23
+ inline Split bestSplit(const std::vector<uint8_t>& masks,int columns,const Rect& box,int cell){
24
+ Split result;result.box=box;const auto parent=bytes(box,cell);
25
+ for(bool horizontal:{false,true}){
26
+ const int n=horizontal?box.height():box.width();std::vector<Rect> bins(n),suffix(n+1);
27
+ for(int y=box.top;y<box.bottom;y++)for(int x=box.left;x<box.right;x++)
28
+ if(masks[(size_t)y*columns+x])bins[horizontal?y-box.top:x-box.left].include(x,y);
29
+ for(int i=n-1;i>=0;i--){suffix[i]=suffix[i+1];suffix[i].include(bins[i]);}
30
+ Rect prefix;
31
+ for(int i=0;i+1<n;i++){
32
+ prefix.include(bins[i]);const Rect& other=suffix[i+1];if(prefix.empty()||other.empty())continue;
33
+ const auto cost=bytes(prefix,cell)+bytes(other,cell);
34
+ if(cost<parent&&parent-cost>result.saved){result.saved=parent-cost;result.a=prefix;result.b=other;}
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ inline std::vector<Rect> partition(const std::vector<uint8_t>& masks,int columns,int rows,int cell){
40
+ if(columns<1||rows<1||(uint64_t)columns*rows>125000||masks.size()!=(size_t)columns*rows||cell<6||cell>32)throw std::runtime_error("Invalid paint mask grid");
41
+ Rect all;for(int y=0;y<rows;y++)for(int x=0;x<columns;x++)if(masks[(size_t)y*columns+x])all.include(x,y);
42
+ if(all.empty())return {};
43
+ std::vector<Split> nodes{bestSplit(masks,columns,all,cell)};
44
+ // A new HWND must save meaningful surface space, not split every fur fleck.
45
+ constexpr uint64_t MinimumSaving=256*1024;
46
+ while(nodes.size()<MaxRegions){
47
+ size_t best=0;for(size_t i=1;i<nodes.size();i++)if(nodes[i].saved>nodes[best].saved)best=i;
48
+ if(nodes[best].saved<MinimumSaving)break;
49
+ const auto selected=nodes[best];nodes[best]=bestSplit(masks,columns,selected.a,cell);
50
+ nodes.push_back(bestSplit(masks,columns,selected.b,cell));
51
+ }
52
+ std::vector<Rect> result;for(const auto& node:nodes)result.push_back(node.box);
53
+ return result;
54
+ }
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitefoundry/retrieviewer",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
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/",
@@ -197,7 +197,10 @@
197
197
  "integrations/purrviewer/vendor/retrieviewer/puppy-frame-store.mjs",
198
198
  "integrations/purrviewer/vendor/retrieviewer/puppy-frame-file.mjs",
199
199
  "integrations/purrviewer/vendor/retrieviewer/puppy-frame-indexed.mjs",
200
- "integrations/native-windows/application.manifest"
200
+ "integrations/native-windows/application.manifest",
201
+ "integrations/native-windows/paint-regions.hpp",
202
+ "integrations/native-windows/native-paint.hpp",
203
+ "SPARSE-PAINT.md"
201
204
  ],
202
205
  "scripts": {
203
206
  "bake:motion": "node tools/bake-pet-motion.mjs",