@bitefoundry/retrieviewer 0.0.17 → 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.
Files changed (62) hide show
  1. package/ASSET-PROVENANCE.md +11 -0
  2. package/ASSETS-LICENSE.md +6 -0
  3. package/ATTRIBUTION.md +8 -0
  4. package/DESKTOP-PLATFORMS.md +1 -1
  5. package/INSTALLING.md +2 -1
  6. package/LICENSE +16 -16
  7. package/NATIVE-RUNTIME.md +95 -0
  8. package/PREREQUISITES.md +7 -0
  9. package/PUPPY-MEMORY.md +22 -0
  10. package/README.ko.md +17 -1
  11. package/README.md +19 -1
  12. package/RELEASING.md +35 -0
  13. package/SPARSE-PAINT.md +66 -0
  14. package/THIRD-PARTY-NOTICES.md +8 -0
  15. package/WINDOWS-COMPANION.md +15 -6
  16. package/braille.mjs +2 -2
  17. package/integrations/native-windows/CMakeLists.txt +15 -0
  18. package/integrations/native-windows/THIRD-PARTY-LICENSES.txt +77 -0
  19. package/integrations/native-windows/application.manifest +8 -0
  20. package/integrations/native-windows/bin/win32-x64/retrieviewer-native.exe +0 -0
  21. package/integrations/native-windows/desktop.mjs +48 -0
  22. package/integrations/native-windows/host.cpp +85 -0
  23. package/integrations/native-windows/manifest.json +14 -0
  24. package/integrations/native-windows/native-control.hpp +41 -0
  25. package/integrations/native-windows/native-paint.hpp +86 -0
  26. package/integrations/native-windows/native-window.hpp +107 -0
  27. package/integrations/native-windows/paint-regions.hpp +55 -0
  28. package/integrations/native-windows/runtime.bundle.mjs +4694 -0
  29. package/integrations/native-windows/runtime.mjs +55 -0
  30. package/integrations/native-windows/sources.json +23 -0
  31. package/integrations/purrviewer/ART.md +289 -0
  32. package/integrations/purrviewer/ASSETS-LICENSE.md +21 -0
  33. package/integrations/purrviewer/LICENSE +24 -0
  34. package/integrations/purrviewer/art/ginger.bin.gz +0 -0
  35. package/integrations/purrviewer/art/ginger.json +1707 -0
  36. package/integrations/purrviewer/art/ginger.rvz +0 -0
  37. package/integrations/purrviewer/src/contact.mjs +63 -0
  38. package/integrations/purrviewer/src/habitat.mjs +41 -0
  39. package/integrations/purrviewer/src/life.mjs +280 -0
  40. package/integrations/purrviewer/src/purrviewer-core.mjs +130 -0
  41. package/integrations/purrviewer/src/purrviewer.mjs +37 -0
  42. package/integrations/purrviewer/src/walk-cycle.mjs +3 -0
  43. package/integrations/purrviewer/src/world.mjs +216 -0
  44. package/integrations/purrviewer/vendor/retrieviewer/LICENSE-CODE +21 -0
  45. package/integrations/purrviewer/vendor/retrieviewer/braille.mjs +73 -0
  46. package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-file.mjs +15 -0
  47. package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-indexed.mjs +78 -0
  48. package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-predictive.mjs +170 -0
  49. package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-store.mjs +114 -0
  50. package/integrations/windows-overlay/desktop.mjs +11 -3
  51. package/integrations/windows-overlay/worker.mjs +9 -2
  52. package/package.json +42 -4
  53. package/puppy-clean.mjs +2 -2
  54. package/puppy-companion-sprites.mjs +2 -2
  55. package/puppy-frame-indexed.mjs +2 -2
  56. package/puppy-paper-actions.mjs +2 -2
  57. package/puppy-paper-behaviors.mjs +1 -1
  58. package/puppy-paper-contact.mjs +2 -2
  59. package/puppy-paper-seat.mjs +2 -2
  60. package/puppy-paper.mjs +12 -12
  61. package/retrieviewer-cli.mjs +10 -6
  62. package/retrieviewer.mjs +13 -4
@@ -0,0 +1,85 @@
1
+ #include <quickjs.h>
2
+ #include <quickjs-libc.h>
3
+ #include <algorithm>
4
+ #include <cmath>
5
+ #include <cstdint>
6
+ #include <cstdio>
7
+ #include <fstream>
8
+ #include <string>
9
+ #include <vector>
10
+ #include <stdexcept>
11
+ #include <iostream>
12
+ #include <zlib.h>
13
+ #include "native-window.hpp"
14
+
15
+ struct Bytes { JSContext* ctx; JSValue owner; uint8_t* p; size_t n;
16
+ Bytes(JSContext* c,JSValueConst arg):ctx(c){size_t offset,bpe;owner=JS_GetTypedArrayBuffer(c,arg,&offset,&n,&bpe);if(JS_IsException(owner))throw std::runtime_error("Expected pixels");size_t all;p=JS_GetArrayBuffer(c,&all,owner)+offset;}
17
+ ~Bytes(){JS_FreeValue(ctx,owner);}
18
+ };
19
+ static double number(JSContext* c,JSValueConst obj,const char* key){JSValue v=JS_GetPropertyStr(c,obj,key);double n;int result=JS_ToFloat64(c,&n,v);JS_FreeValue(c,v);if(result<0)throw std::runtime_error(key);return n;}
20
+ static double clamp(double n,double low,double high){return std::max(low,std::min(high,n));}
21
+ static uint8_t byteClamp(double v){if(v<=0)return 0;if(v>=255)return 255;double f=floor(v),d=v-f;return (uint8_t)(f+(d>.5||(d==.5&&((int)f&1))?1:0));}
22
+ static JSValue failure(JSContext* c,const std::exception& e){return JS_ThrowTypeError(c,"%s",e.what());}
23
+ static JSValue inflateGzip(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{Bytes compressed(c,a[0]);int32_t size;JS_ToInt32(c,&size,a[1]);if(size<0||size>640*641)throw std::runtime_error("Inflate size");std::vector<uint8_t> out(std::max(1,size));z_stream z{};z.next_in=compressed.p;z.avail_in=(uInt)compressed.n;z.next_out=out.data();z.avail_out=(uInt)out.size();if(inflateInit2(&z,31)!=Z_OK)throw std::runtime_error("Inflate init");int result=inflate(&z,Z_FINISH);auto total=z.total_out;inflateEnd(&z);if(result!=Z_STREAM_END||total!=(uLong)size)throw std::runtime_error("Invalid crop gzip");JSValue buffer=JS_NewArrayBufferCopy(c,out.data(),size);JSValue typed=JS_NewTypedArray(c,1,&buffer,JS_TYPED_ARRAY_UINT8);JS_FreeValue(c,buffer);return typed;}catch(const std::exception& e){return failure(c,e);}}
24
+ static int paeth(int a,int b,int d){int p=a+b-d,aa=abs(p-a),bb=abs(p-b),dd=abs(p-d);return aa<=bb&&aa<=dd?a:bb<=dd?b:d;}
25
+ static JSValue decodeRows(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{Bytes out(c,a[0]),rows(c,a[1]);int32_t x,y,w,h,width;JS_ToInt32(c,&x,a[2]);JS_ToInt32(c,&y,a[3]);JS_ToInt32(c,&w,a[4]);JS_ToInt32(c,&h,a[5]);JS_ToInt32(c,&width,a[6]);if(x<0||y<0||w<0||h<0||width<1||x+w>width||rows.n!=(size_t)h*(w+1)||((size_t)y+h)*width>out.n)throw std::runtime_error("Decode rows bounds");std::fill(out.p,out.p+out.n,0);std::vector<uint8_t> prev(w,0),cur(w,0);size_t at=0;for(int r=0;r<h;r++){int type=rows.p[at++];if(type>4)throw std::runtime_error("Filter");for(int col=0;col<w;col++){int left=col?cur[col-1]:0,up=prev[col],diagonal=col?prev[col-1]:0,p=type==0?0:type==1?left:type==2?up:type==3?(left+up)>>1:paeth(left,up,diagonal),q=(rows.p[at++]+p)&255;if(q>31)throw std::runtime_error("Q5 pixel");cur[col]=(uint8_t)q;out.p[((size_t)y+r)*width+x+col]=(uint8_t)floor(q*255/31.0+.5);}std::swap(cur,prev);}return JS_UNDEFINED;}catch(const std::exception& e){return failure(c,e);}}
26
+ static JSValue clearPixels(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{Bytes out(c,a[0]);std::fill(out.p,out.p+out.n,0);for(size_t i=3;i<out.n;i+=4)out.p[i]=255;return JS_UNDEFINED;}catch(const std::exception& e){return failure(c,e);}}
27
+ static JSValue copyGray(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{Bytes out(c,a[0]),gray(c,a[1]);if(out.n<gray.n*4)throw std::runtime_error("Gray size");for(size_t i=0,j=0;i<gray.n;i++,j+=4)out.p[j]=out.p[j+1]=out.p[j+2]=gray.p[i];return JS_UNDEFINED;}catch(const std::exception& e){return failure(c,e);}}
28
+ static JSValue blendGray(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{Bytes out(c,a[0]),x(c,a[1]),y(c,a[2]);double t;JS_ToFloat64(c,&t,a[3]);if(x.n!=y.n||out.n<x.n*4)throw std::runtime_error("Blend size");for(size_t i=0,j=0;i<x.n;i++,j+=4){const auto v=byteClamp(x.p[i]*(1-t)+y.p[i]*t);out.p[j]=out.p[j+1]=out.p[j+2]=v;}return JS_UNDEFINED;}catch(const std::exception& e){return failure(c,e);}}
29
+ static JSValue catBlit(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{
30
+ Bytes out(c,a[0]),gray(c,a[1]);const auto opts=a[2];int width=(int)number(c,opts,"width"),height=(int)number(c,opts,"height");double px=number(c,opts,"x"),py=number(c,opts,"y"),size=number(c,opts,"size");JSValue mirrored=JS_GetPropertyStr(c,opts,"mirror");bool mirror=JS_ToBool(c,mirrored);JS_FreeValue(c,mirrored);
31
+ if(width<1||height<1||(int64_t)width*height>1000000||out.n!=(size_t)width*height*4||gray.n!=320*320||!std::isfinite(size)||size<=0)throw std::runtime_error("Cat raster bounds");
32
+ struct Pin{double sx,sy,dx,dy,radius;};std::vector<Pin> pins;JSValue list=JS_GetPropertyStr(c,opts,"pins"),len=JS_GetPropertyStr(c,list,"length");uint32_t count;JS_ToUint32(c,&count,len);JS_FreeValue(c,len);if(count>16)throw std::runtime_error("Cat pin limit");for(uint32_t i=0;i<count;i++){JSValue p=JS_GetPropertyUint32(c,list,i),s=JS_GetPropertyStr(c,p,"source"),t=JS_GetPropertyStr(c,p,"target");double radius=number(c,p,"radius");if(!std::isfinite(radius))radius=28;Pin pin{number(c,s,"x"),number(c,s,"y"),number(c,t,"x")-number(c,s,"x"),number(c,t,"y")-number(c,s,"y"),radius};JS_FreeValue(c,s);JS_FreeValue(c,t);JS_FreeValue(c,p);pins.push_back(pin);}JS_FreeValue(c,list);
33
+ double left=px-size*.5,top=py-size*(290.0/320);int x0=std::max(0,(int)floor(left)),y0=std::max(0,(int)floor(top)),x1=std::min(width,(int)ceil(left+size)),y1=std::min(height,(int)ceil(top+size));
34
+ for(int y=y0;y<y1;y++)for(int x=x0;x<x1;x++){double u=(x+.5-left)*320/size-.5,v=(y+.5-top)*320/size-.5;if(mirror)u=319-u;if(v>225)for(const auto& p:pins){if(p.radius<=0||hypot(p.dx,p.dy)>std::min(6.0,.22*p.radius)||fabs(p.dy)>3||hypot(u-p.sx,v-p.sy)>=p.radius)continue;double q=u-p.dx,r=v-p.dy;for(int i=0;i<12;i++){double distance=hypot(q-p.sx,r-p.sy),t=clamp((distance-p.radius*.3)/(p.radius*.7),0,1),weight=1-t*t*(3-2*t);q=u-p.dx*weight;r=v-p.dy*weight;}u=q;v=r;break;}if(u<0||v<0||u>319||v>319)continue;int ax=(int)floor(u),by=(int)floor(v),aa=std::min(319,ax+1),bb=std::min(319,by+1);double fu=u-ax,fv=v-by,value=(gray.p[by*320+ax]*(1-fu)+gray.p[by*320+aa]*fu)*(1-fv)+(gray.p[bb*320+ax]*(1-fu)+gray.p[bb*320+aa]*fu)*fv;auto b=(uint8_t)floor(value+.5);size_t at=((size_t)y*width+x)*4;out.p[at]=out.p[at+1]=out.p[at+2]=b;}
35
+ return JS_UNDEFINED;
36
+ }catch(const std::exception& e){return failure(c,e);}}
37
+ static JSValue braille(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{
38
+ Bytes pixels(c,a[0]);int32_t width,height;double brightness,contrast;JS_ToInt32(c,&width,a[1]);JS_ToInt32(c,&height,a[2]);JS_ToFloat64(c,&brightness,a[3]);JS_ToFloat64(c,&contrast,a[4]);
39
+ if(width<1||height<1||(int64_t)width*height>1000000||pixels.n!=(size_t)width*height*4)throw std::runtime_error("Braille dimensions");
40
+ static float table[256];static double lastB=0,lastC=0;if(lastB!=brightness||lastC!=contrast){for(int i=0;i<256;i++){double l=(.2126*i+.7152*i+.0722*i)/255;table[i]=(float)(l==0?0:clamp((pow(l,.9)*brightness-.5)*contrast+.5,0,1));}lastB=brightness;lastC=contrast;}
41
+ const int cols=(width+1)/2,rows=(height+3)/4;static const int bits[4][2]={{1,8},{2,16},{4,32},{64,128}};std::vector<uint8_t> masks(cols*rows,0);int dots=0;
42
+ for(int y=0;y<height;y++)for(int x=0;x<width;x++){auto* p=pixels.p+((size_t)y*width+x)*4;int r=p[0],g=p[1],b=p[2],alpha=p[3];if(alpha==0||!(r|g|b))continue;float value;
43
+ if(alpha==255&&r==g&&g==b)value=table[r];else{double l=(.2126*r+.7152*g+.0722*b)/255*(alpha/255.0);value=(float)(l==0?0:clamp((pow(l,.9)*brightness-.5)*contrast+.5,0,1));}if(value<=0)continue;
44
+ uint32_t hash=(uint32_t)(x+1)*0x1f123bb5u^(uint32_t)(y+1)*0x5f356495u;hash=(hash^(hash>>16))*0x45d9f3bu;hash=(hash^(hash>>16))*0x45d9f3bu;hash^=hash>>16;
45
+ if(value>(hash+.5)/4294967296.0){masks[(y>>2)*cols+(x>>1)]|=bits[y&3][x&1];dots++;}
46
+ }
47
+ std::string text;text.reserve((size_t)cols*rows*3+rows);for(int y=0;y<rows;y++){if(y)text+='\n';for(int x=0;x<cols;x++){uint32_t ch=0x2800+masks[y*cols+x];text+=(char)(0xe0|(ch>>12));text+=(char)(0x80|((ch>>6)&63));text+=(char)(0x80|(ch&63));}}
48
+ JSValue out=JS_NewObject(c);JS_SetPropertyStr(c,out,"text",JS_NewStringLen(c,text.data(),text.size()));JS_SetPropertyStr(c,out,"columns",JS_NewInt32(c,cols));JS_SetPropertyStr(c,out,"rows",JS_NewInt32(c,rows));JS_SetPropertyStr(c,out,"dots",JS_NewInt32(c,dots));return out;
49
+ }catch(const std::exception& e){return failure(c,e);}}
50
+ struct Layer {double lo,hi,dx,dy,toe,span,a,lastY=NAN,lastSource=NAN;};
51
+ static double smooth(double t){return t<=0?0:t>=1?1:t*t*(3-2*t);}
52
+ static double gy(const Layer& l,double s){double t=(s-226)/l.span;return t<=0?0:t>=1?1:(1-l.a)*smooth(t)+l.a*t;}
53
+ static double inverseY(Layer& l,double y){if(l.dy==0)return y;if(y>=l.toe+l.dy)return y-l.dy;if(y==l.lastY)return l.lastSource;double lo=226,hi=l.toe;for(int i=0;i<48&&hi-lo>1e-9;i++){double m=(lo+hi)/2;if(m+l.dy*gy(l,m)<y)lo=m;else hi=m;}l.lastY=y;l.lastSource=(lo+hi)/2;return l.lastSource;}
54
+ static JSValue blit(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{
55
+ Bytes output(c,a[0]),image(c,a[1]);JSValueConst opts=a[2];const int width=(int)number(c,opts,"width"),height=(int)number(c,opts,"height"),sourceN=(int)number(c,opts,"sourceN");
56
+ const double left=number(c,opts,"left"),top=number(c,opts,"top"),side=number(c,opts,"side"),sideX=number(c,opts,"sideX"),originX=number(c,opts,"originX"),originY=number(c,opts,"originY"),drawTop=number(c,opts,"drawTop"),bottom=number(c,opts,"bottom"),drawLeft=number(c,opts,"drawLeft"),drawRight=number(c,opts,"drawRight");
57
+ JSValue flipValue=JS_GetPropertyStr(c,opts,"flip");bool flip=JS_ToBool(c,flipValue);JS_FreeValue(c,flipValue);
58
+ if(width<1||height<1||(int64_t)width*height>1000000||sourceN<1||sourceN>640||output.n!=(size_t)width*height*4||image.n<(size_t)sourceN*sourceN*4||side<=0||sideX<=0)throw std::runtime_error("Blit dimensions");
59
+ JSValue layersValue=JS_GetPropertyStr(c,opts,"layers"),length=JS_GetPropertyStr(c,layersValue,"length");uint32_t count;JS_ToUint32(c,&count,length);JS_FreeValue(c,length);if(count>16)throw std::runtime_error("Layer limit");std::vector<Layer> layers;
60
+ for(uint32_t i=0;i<count;i++){JSValue v=JS_GetPropertyUint32(c,layersValue,i);Layer l;l.lo=number(c,v,"lo");l.hi=number(c,v,"hi");l.dx=number(c,v,"dx");l.dy=number(c,v,"dy");l.toe=number(c,v,"toeTop");l.span=l.toe-226;l.a=0;if(l.dy<0){double k=.9*l.span/-l.dy;if(k<1)l.a=1;else if(k<1.5)l.a=std::min(1.0,3-2*k);}JS_FreeValue(c,v);layers.push_back(l);}JS_FreeValue(c,layersValue);
61
+ const bool minify=side<320||sideX<320;int pitch=sourceN+1;std::vector<uint32_t> sums;
62
+ if(minify){sums.assign(pitch*pitch,0);for(int y=0;y<sourceN;y++){uint32_t row=0;for(int x=0;x<sourceN;x++){row+=image.p[((size_t)y*sourceN+x)*4];sums[(y+1)*pitch+x+1]=sums[y*pitch+x+1]+row;}}}
63
+ auto integral=[&](double x,double y){x=clamp(x,0,sourceN);y=clamp(y,0,sourceN);int ix=std::min(sourceN-1,(int)floor(x)),iy=std::min(sourceN-1,(int)floor(y));double fx=x-ix,fy=y-iy;size_t k=iy*pitch+ix;return (sums[k]*(1-fx)+sums[k+1]*fx)*(1-fy)+(sums[k+pitch]*(1-fx)+sums[k+pitch+1]*fx)*fy;};
64
+ auto area=[&](double x,double y){double dx=320/sideX,dy=320/side,l=x-dx/2,r=x+dx/2,t=y-dy/2,b=y+dy/2;return std::max(0.0,(integral(r,b)-integral(l,b)-integral(r,t)+integral(l,t))/(dx*dy));};
65
+ auto opaque=[&](double x,double y){double xx=x+originX,yy=y+originY;if(xx<0||xx>=sourceN||yy<0||yy>=sourceN)return false;int sx=(int)floor(xx);if(flip)sx=sourceN-1-sx;return image.p[((size_t)floor(yy)*sourceN+sx)*4]>16;};
66
+ const int y0=std::max(0,(int)(minify?floor(drawTop):ceil(drawTop-.5))),y1=std::min(height,(int)(minify?ceil(bottom):ceil(bottom-.5))),x0=std::max(0,(int)(minify?floor(drawLeft):ceil(drawLeft-.5))),x1=std::min(width,(int)(minify?ceil(drawRight):ceil(drawRight-.5)));
67
+ for(int y=y0;y<y1;y++)for(int x=x0;x<x1;x++){
68
+ double px=(x+.5-left)/sideX*320,py=(y+.5-top)/side*320,wx=px,wy=py;bool claimed=true;
69
+ if(py>=226&&!layers.empty()){claimed=false;bool fallback=false;double fallbackX=0,fallbackY=0;for(auto& l:layers){double sx,sy;if(l.dx==0&&l.dy==0){sx=px;sy=py;}else{sy=inverseY(l,py);sx=sy>=l.toe?px-l.dx:px-l.dx*smooth((sy-226)/l.span);}if(!(sx>=l.lo&&sx<l.hi))continue;if(opaque(sx,sy)){wx=sx;wy=sy;claimed=true;break;}if(!fallback){fallback=true;fallbackX=sx;fallbackY=sy;}}if(!claimed&&fallback){wx=fallbackX;wy=fallbackY;claimed=true;}}
70
+ if(!claimed)continue;double rawX=wx+originX,rawY=wy+originY;int sx=(int)clamp(floor(rawX),0,sourceN-1),sy=(int)clamp(floor(rawY),0,sourceN-1);if(flip)sx=sourceN-1-sx;size_t j=((size_t)y*width+x)*4,k=((size_t)sy*sourceN+sx)*4;
71
+ uint8_t value=byteClamp(minify?area(flip?sourceN-rawX:rawX,rawY):(rawY<0||rawY>=sourceN||rawX<0||rawX>=sourceN?0:image.p[k]));output.p[j]=output.p[j+1]=output.p[j+2]=value;
72
+ }
73
+ return JS_UNDEFINED;
74
+ }catch(const std::exception& e){return failure(c,e);}}
75
+ int wmain(int argc,wchar_t** wideArgs){
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;}
78
+ bool production=false;for(int i=1;i<argc;i++)if(std::string(argv[i])=="--production")production=true;std::string configuration;
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;}
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);
81
+ JSValue global=JS_GetGlobalObject(ctx),fast=JS_NewObject(ctx);if(production){JSValue config=JS_ParseJSON(ctx,configuration.data(),configuration.size(),"native-config");if(JS_IsException(config)){js_std_dump_error(ctx);return 2;}JS_SetPropertyStr(ctx,global,"__nativeConfig",config);}JS_SetPropertyStr(ctx,fast,"catBlit",JS_NewCFunction(ctx,catBlit,"catBlit",3));JS_SetPropertyStr(ctx,fast,"inflate",JS_NewCFunction(ctx,inflateGzip,"inflate",2));JS_SetPropertyStr(ctx,fast,"decodeRows",JS_NewCFunction(ctx,decodeRows,"decodeRows",7));JS_SetPropertyStr(ctx,fast,"clear",JS_NewCFunction(ctx,clearPixels,"clear",1));JS_SetPropertyStr(ctx,fast,"copyGray",JS_NewCFunction(ctx,copyGray,"copyGray",2));JS_SetPropertyStr(ctx,fast,"blendGray",JS_NewCFunction(ctx,blendGray,"blendGray",4));JS_SetPropertyStr(ctx,fast,"braille",JS_NewCFunction(ctx,braille,"braille",5));JS_SetPropertyStr(ctx,fast,"blit",JS_NewCFunction(ctx,blit,"blit",3));JS_SetPropertyStr(ctx,global,"__fast",fast);JS_FreeValue(ctx,global);
82
+ std::ifstream input(std::filesystem::u8path(argv[1]),std::ios::binary);if(!input){fprintf(stderr,"Native runtime bundle is missing\n");return 2;}std::string source((std::istreambuf_iterator<char>(input)),std::istreambuf_iterator<char>());JSValue value=JS_Eval(ctx,source.data(),source.size(),argv[1],JS_EVAL_TYPE_MODULE);value=js_std_await(ctx,value);int code=0;if(JS_IsException(value)){js_std_dump_error(ctx);code=1;}JS_FreeValue(ctx,value);if(!code)js_std_loop(ctx);
83
+ global=JS_GetGlobalObject(ctx);JSValue desktop=JS_GetPropertyStr(ctx,global,"__desktopReady");bool runWindow=JS_ToBool(ctx,desktop);JS_FreeValue(ctx,desktop);JS_FreeValue(ctx,global);if(!code&&runWindow)try{code=NativePet::run(ctx,argc,argv);}catch(const std::exception& e){fprintf(stderr,"%s\n",e.what());code=1;}
84
+ js_std_free_handlers(rt);JS_FreeContext(ctx);JS_FreeRuntime(rt);return code;
85
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 1,
3
+ "platform": "win32-x64",
4
+ "files": {
5
+ "runtime.bundle.mjs": {
6
+ "bytes": 235608,
7
+ "sha256": "e9da942dbe57ac778c44ea8f300e2e868face5e73b00ffcd4399aeb7e94ee09c"
8
+ },
9
+ "bin/win32-x64/retrieviewer-native.exe": {
10
+ "bytes": 1564672,
11
+ "sha256": "288150c7112c50214996174250ce71bfa4ffc91779c8ea899a47d85f4fef71dd"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,41 @@
1
+ #include <atomic>
2
+ #include <mutex>
3
+ #include <thread>
4
+ #include <sstream>
5
+ #include <fstream>
6
+ #include <filesystem>
7
+ #include <sddl.h>
8
+ #include <psapi.h>
9
+ #include <functional>
10
+
11
+ static std::wstring wide(const std::string& text){if(text.empty())return L"";int n=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,text.data(),(int)text.size(),nullptr,0);if(!n)throw std::runtime_error("Invalid UTF-8");std::wstring result(n,0);MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,text.data(),(int)text.size(),result.data(),n);return result;}
12
+ static std::string userSid(){HANDLE token;if(!OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&token))throw std::runtime_error("Cannot read process owner");DWORD n=0;GetTokenInformation(token,TokenUser,nullptr,0,&n);std::vector<uint8_t> data(n);BOOL ok=GetTokenInformation(token,TokenUser,data.data(),n,&n);CloseHandle(token);if(!ok)throw std::runtime_error("Cannot read process owner");LPWSTR sid;if(!ConvertSidToStringSidW(((TOKEN_USER*)data.data())->User.Sid,&sid))throw std::runtime_error("Cannot read process owner");std::wstring s(sid);LocalFree(sid);return std::string(s.begin(),s.end());}
13
+ static std::string propertyString(JSContext* ctx,JSValueConst object,const char* name,const std::string& fallback=""){JSValue value=JS_GetPropertyStr(ctx,object,name);if(JS_IsUndefined(value)||JS_IsNull(value)){JS_FreeValue(ctx,value);return fallback;}const char* text=JS_ToCString(ctx,value);if(!text){JS_FreeValue(ctx,value);throw std::runtime_error(name);}std::string out(text);JS_FreeCString(ctx,text);JS_FreeValue(ctx,value);return out;}
14
+ static bool propertyBool(JSContext* ctx,JSValueConst object,const char* name){JSValue value=JS_GetPropertyStr(ctx,object,name);bool out=JS_ToBool(ctx,value);JS_FreeValue(ctx,value);return out;}
15
+ class NativeControl {
16
+ std::thread worker;std::atomic<bool> alive{false};std::mutex mutex;std::string status,token,session;std::wstring name;HANDLE pipe=INVALID_HANDLE_VALUE;PSECURITY_DESCRIPTOR security=nullptr;std::function<void(const std::string&)> action;
17
+ void serve(){
18
+ while(alive){BOOL connected=ConnectNamedPipe(pipe,nullptr);if(!connected&&GetLastError()!=ERROR_PIPE_CONNECTED){if(!alive)break;continue;}
19
+ std::string body;const ULONGLONG deadline=GetTickCount64()+1800;
20
+ while(alive&&body.size()<=2048&&GetTickCount64()<deadline){DWORD available=0;if(!PeekNamedPipe(pipe,nullptr,0,nullptr,&available,nullptr))break;if(!available){Sleep(10);continue;}char bytes[2048];DWORD got=0;if(!ReadFile(pipe,bytes,std::min<DWORD>(available,sizeof(bytes)),&got,nullptr)||!got)break;body.append(bytes,got);if(body.find('\n')!=std::string::npos)break;}
21
+ const std::string prefix="{\"token\":\""+token+"\",\"command\":\"";std::string response;
22
+ std::string command;for(const auto* choice:{"stop","pet-dog","pet-cat"})if(body==prefix+choice+"\"}\n")command=choice;
23
+ if(body==prefix+"status\"}\n"){std::lock_guard<std::mutex> lock(mutex);response=status+"\n";}
24
+ else if(!command.empty())response=command=="stop"?"{\"stopped\":true}\n":"{\"accepted\":true}\n";
25
+ if(!response.empty()){DWORD wrote=0;WriteFile(pipe,response.data(),(DWORD)response.size(),&wrote,nullptr);if(!command.empty())action(command);FlushFileBuffers(pipe);}
26
+ DisconnectNamedPipe(pipe);
27
+ }
28
+ }
29
+ public:
30
+ void start(const std::string& address,const std::string& secret,const std::string& file,std::function<void(const std::string&)> callback){
31
+ if(secret.size()!=64||secret.find_first_not_of("0123456789abcdef")!=std::string::npos||address.rfind("\\\\.\\pipe\\retrieviewer-overlay-",0)!=0)throw std::runtime_error("Invalid native control configuration");
32
+ token=secret;name=wide(address);session=file;action=callback;std::wstring sddl=wide("D:P(A;;GA;;;"+userSid()+")");if(!ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),SDDL_REVISION_1,&security,nullptr))throw std::runtime_error("Cannot protect native control pipe");SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES),security,FALSE};
33
+ pipe=CreateNamedPipeW(name.c_str(),PIPE_ACCESS_DUPLEX|FILE_FLAG_FIRST_PIPE_INSTANCE,PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT|PIPE_REJECT_REMOTE_CLIENTS,1,8192,4096,0,&attributes);if(pipe==INVALID_HANDLE_VALUE)throw std::runtime_error("Cannot open native control pipe");alive=true;worker=std::thread([this]{serve();});
34
+ }
35
+ void update(const std::string& value){std::lock_guard<std::mutex> lock(mutex);status=value;}
36
+ void close(){if(alive.exchange(false)){if(worker.joinable()){CancelSynchronousIo(worker.native_handle());HANDLE wake=CreateFileW(name.c_str(),GENERIC_READ|GENERIC_WRITE,0,nullptr,OPEN_EXISTING,0,nullptr);if(wake!=INVALID_HANDLE_VALUE)CloseHandle(wake);worker.join();}if(pipe!=INVALID_HANDLE_VALUE){CloseHandle(pipe);pipe=INVALID_HANDLE_VALUE;}}
37
+ if(security){LocalFree(security);security=nullptr;}
38
+ if(!session.empty()){auto path=std::filesystem::u8path(session);DWORD attr=GetFileAttributesW(path.c_str()),parent=GetFileAttributesW(path.parent_path().c_str());if(attr!=INVALID_FILE_ATTRIBUTES&&parent!=INVALID_FILE_ATTRIBUTES&&!(attr&FILE_ATTRIBUTE_REPARSE_POINT)&&!(parent&FILE_ATTRIBUTE_REPARSE_POINT)){std::ifstream file(path,std::ios::binary);std::string bytes((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());file.close();if(bytes.size()<8192&&bytes.find("\"token\":\""+token+"\"")!=std::string::npos)DeleteFileW(path.c_str());}session.clear();}
39
+ }
40
+ ~NativeControl(){close();}
41
+ };
@@ -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
+ }
@@ -0,0 +1,107 @@
1
+ #include <windows.h>
2
+ #include <gdiplus.h>
3
+ #include <memory>
4
+ #include <array>
5
+ #include "paint-regions.hpp"
6
+ #include "native-control.hpp"
7
+ namespace NativePet {
8
+ static JSContext* context;
9
+ static HWND target=nullptr;
10
+ static bool showing=false,paused=false,failed=false;
11
+ static ULONGLONG startTime=0,lastTick=0,durationMs=0;
12
+ static int stage=0,painted=0;
13
+ static std::string previous;
14
+ static std::vector<double> timings;
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";
17
+ static bool production=false,diagnostic=false,testPicker=false,visible=false,inkExplicit=false;
18
+ static HWND followed=nullptr,picker=nullptr,windowList=nullptr;
19
+ static DWORD followedPid=0;
20
+ static HANDLE instanceMutex=nullptr;
21
+ static NativeControl control;
22
+ static std::thread startupGuard;
23
+ static std::atomic<bool> detached{false};
24
+ static std::string petKind="dog",inkName="cream",returnStyle="auto",lastKind;
25
+ static bool lastAccepted=false;
26
+ static uint64_t frames=0;static size_t timingCursor=0;
27
+ static std::vector<std::pair<HWND,DWORD>> choices;
28
+ static POINT lastCanvas{INT_MIN,INT_MIN};static int lastWidth=0,lastHeight=0;
29
+ static Gdiplus::Color ink(){return inkName=="dark"?Gdiplus::Color(245,67,48,24):inkName=="gold"?(petKind=="cat"?Gdiplus::Color(245,241,182,107):Gdiplus::Color(245,231,184,103)):Gdiplus::Color(245,248,242,232);}
30
+ static void updateStatus();static void chooseWindow();static void layoutControls();
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;}
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();}
33
+ #include "native-paint.hpp"
34
+ static void updateStatus(){
35
+ if(!production)return;
36
+ PROCESS_MEMORY_COUNTERS_EX memory{};GetProcessMemoryInfo(GetCurrentProcess(),(PROCESS_MEMORY_COUNTERS*)&memory,sizeof(memory));
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\":";
38
+ if(lastKind.empty())out<<"null";else out<<"{\"kind\":\""<<lastKind<<"\",\"accepted\":"<<(lastAccepted?"true":"false")<<"}";out<<"}";control.update(out.str());
39
+ }
40
+ static void switchPet(const std::string& next){
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();
42
+ }
43
+ static BOOL CALLBACK enumerate(HWND window,LPARAM){
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;
45
+ }
46
+ static void attachSelection(){
47
+ LRESULT selected=SendMessage(windowList,LB_GETCURSEL,0,0);if(selected==LB_ERR)return;LRESULT item=SendMessage(windowList,LB_GETITEMDATA,selected,0);if(item<0||(size_t)item>=choices.size())return;const auto candidate=choices[(size_t)item];DWORD pid=0;GetWindowThreadProcessId(candidate.first,&pid);if(!IsWindow(candidate.first)||pid!=candidate.second)return;followed=candidate.first;followedPid=candidate.second;HWND old=picker;picker=nullptr;DestroyWindow(old);previous.clear();SetForegroundWindow(followed);updateStatus();
48
+ }
49
+ static void addPetCombo(HWND parent){HWND combo=CreateWindow(L"COMBOBOX",L"",WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST,8,8,114,180,parent,(HMENU)9,GetModuleHandle(nullptr),nullptr);SendMessage(combo,CB_ADDSTRING,0,(LPARAM)L"Retriever");SendMessage(combo,CB_ADDSTRING,0,(LPARAM)L"Ginger cat");SendMessage(combo,CB_SETCURSEL,petKind=="cat"?1:0,0);}
50
+ static LRESULT CALLBACK pickerProcedure(HWND window,UINT message,WPARAM w,LPARAM l){
51
+ if(message==WM_COMMAND){int id=LOWORD(w);if(id==101||(id==100&&HIWORD(w)==LBN_DBLCLK)){attachSelection();return 0;}if(id==102){SendMessage(windowList,LB_RESETCONTENT,0,0);choices.clear();EnumWindows(enumerate,0);return 0;}if(id==9&&HIWORD(w)==CBN_SELCHANGE){switchPet(SendMessage((HWND)l,CB_GETCURSEL,0,0)==1?"cat":"dog");return 0;}}
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);
53
+ }
54
+ static void chooseWindow(){
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);
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");
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();
58
+ }
59
+ static void layoutControls(){
60
+ if(!production||diagnostic||!target)return;const std::vector<int> ids=petKind=="cat"?std::vector<int>{1,2,3,6,8,4,5,7}:std::vector<int>{1,2,3,4,5,7};
61
+ for(int id:{1,2,3,4,5,6,7,8})ShowWindow(GetDlgItem(target,id),SW_HIDE);int x=128;
62
+ for(int id:ids){const wchar_t* label=id==1?(petKind=="cat"?L"Greet":L"Ball"):id==2?L"Treat":id==3?(petKind=="cat"?L"Company":L"Tug"):id==4?(paused?L"Resume":L"Pause"):id==5?L"Window":id==6?L"Tower":id==8?L"Wheel":L"Quit";HWND button=GetDlgItem(target,id);SetWindowText(button,label);SetWindowPos(button,nullptr,x,6,64,28,SWP_NOACTIVATE|SWP_NOZORDER|SWP_SHOWWINDOW);x+=68;}
63
+ SendMessage(GetDlgItem(target,9),CB_SETCURSEL,petKind=="cat"?1:0,0);SetWindowPos(target,nullptr,0,0,x+6,40,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);previous.clear();
64
+ }
65
+ static void tick(){
66
+ const ULONGLONG now=GetTickCount64(),elapsed=now-startTime;if((durationMs&&elapsed>=durationMs)||(production&&!detached&&elapsed>12000)){DestroyWindow(target);return;}
67
+ if(!showing&&!testPicker&&(!production||diagnostic)){const int next=elapsed>=120000?5:elapsed>=95000?4:elapsed>=70000?3:elapsed>=45000?2:elapsed>=1000?1:0;if(next>stage){stage=next;if(petKind=="cat")interact(stage==1?"toy":stage==2?"treat":stage==3?"company":stage==4?"tower":"wheel");else if(stage==1||stage==2)interact("ball");else if(stage==3)interact("treat");else if(stage==4)interact("tug");else if(stage==5)interact("wall");}}
68
+ double dt=lastTick?std::min(.1,(now-lastTick)/1000.0):0;lastTick=now;
69
+ HWND scene=production&&(!diagnostic||testPicker)?followed:target;
70
+ DWORD scenePid=0;if(scene)GetWindowThreadProcessId(scene,&scenePid);
71
+ if(production&&(!diagnostic||testPicker)&&scene&&(!IsWindow(scene)||scenePid!=followedPid)){followed=nullptr;chooseWindow();scene=nullptr;}
72
+ if(picker||!scene||IsIconic(scene)||(production&&(!diagnostic||testPicker)&&GetForegroundWindow()!=scene&&GetForegroundWindow()!=target)||(showing&&GetForegroundWindow()!=target)){
73
+ visible=false;hidePanels();if(production&&(!diagnostic||testPicker))ShowWindow(target,SW_HIDE);previous.clear();updateStatus();return;
74
+ }
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;}
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;}
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);}
78
+ if(paused){updateStatus();return;}
79
+ LARGE_INTEGER freq,t0,t1;QueryPerformanceFrequency(&freq);QueryPerformanceCounter(&t0);JSValue args[]={JS_NewFloat64(context,dt),JS_NewInt32(context,columns),JS_NewInt32(context,rows),JS_NewBool(context,previous.empty())},result=call("__desktopFrame",4,args);for(auto value:args)JS_FreeValue(context,value);if(JS_IsException(result)||JS_IsNull(result)||JS_IsUndefined(result)){JS_FreeValue(context,result);updateStatus();return;}
80
+ frames++;size_t length;const char* value=JS_ToCStringLen(context,&length,result);if(!value){JS_FreeValue(context,result);throw std::runtime_error("Native frame string");}std::string text(value,length);JS_FreeCString(context,value);JS_FreeValue(context,result);if(text!=previous){paint(text,columns,rows,cell,canvas);previous=std::move(text);}QueryPerformanceCounter(&t1);double cost=(t1.QuadPart-t0.QuadPart)*1000.0/freq.QuadPart;if(production&&timings.size()>=1024)timings[timingCursor++%1024]=cost;else timings.push_back(cost);updateStatus();
81
+ }
82
+ static LRESULT CALLBACK procedure(HWND window,UINT message,WPARAM w,LPARAM l){
83
+ if(message==WM_TIMER){try{tick();}catch(const std::exception& e){fprintf(stderr,"Native renderer: %s\n",e.what());failed=true;DestroyWindow(window);}return 0;}
84
+ if(message==WM_APP+1){if(w==1||w==2)switchPet(w==2?"cat":"dog");return 0;}
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;}
86
+ if(message==WM_SIZE||message==WM_MOVE){previous.clear();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;}
88
+ return DefWindowProc(window,message,w,l);
89
+ }
90
+ static int run(JSContext* ctx,int argc,char** argv){
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);}
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");
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");
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");}
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);
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;
97
+ WNDCLASS pet{};pet.lpfnWndProc=DefWindowProc;pet.hInstance=instance;pet.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&pet);
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();}
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);}
100
+ if(!attach)ShowWindow(target,showing?SW_SHOW:SW_SHOWNOACTIVATE);startTime=GetTickCount64();lastTick=0;timings.reserve(1024);SetTimer(target,1,33,nullptr);
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);}});}
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);}
103
+ JS_FreeValue(ctx,config);JS_FreeValue(ctx,global);MSG msg;while(GetMessage(&msg,nullptr,0,0)>0){TranslateMessage(&msg);DispatchMessage(&msg);}
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);
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;
106
+ }
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
+ }