@bitefoundry/retrieviewer 0.0.19 → 0.0.21

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.19
1
+ # Lightweight desktop runtime · 0.0.20
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,14 @@ 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 |22.6MiB|35.3MiB|36.5MiB|
45
- | Ginger cat with tower/wheel |29.5MiB|34.6MiB|39.1MiB|
44
+ | Retriever |22.9MiB|37.9MiB|38.7MiB|
45
+ | Ginger cat with tower/wheel |44.4MiB|64.6MiB|65.5MiB|
46
46
 
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.
47
+ Version 0.0.20 uses one cropped layered window for the complete scene. This
48
+ fixes flicker from the 0.0.19 multi-window painter; its lower memory figures are
49
+ superseded. Buffer shrinking and hidden-state cleanup remain. See the
50
+ [presentation fix](SPARSE-PAINT.md). These finite measurements do not establish
51
+ a universal 50 MiB memory cap. Raw evidence: dist/coherent-paint-memory/.
53
52
 
54
53
  ## Scope
55
54
 
@@ -88,8 +87,9 @@ The executable embeds the Windows UTF-8 process code-page manifest (Windows10
88
87
  1903+ /11) so QuickJS stdio also reads Korean/non-ASCII installation paths.
89
88
  A regression test launches both species from Korean bundle, art and state paths.
90
89
 
91
- ## Sparse paint surfaces
90
+ ## Coherent scene presentation
92
91
 
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.
92
+ [One complete-scene upload](SPARSE-PAINT.md) keeps the final pet, props and
93
+ furniture in the same HWND. That window stays click-through and non-activating.
94
+ The 128-frame presentation regression checks window identity, full-frame pixel
95
+ parity and hidden-resource release separately from pet-core pixel parity.
package/PUPPY-MEMORY.md CHANGED
@@ -1,3 +1,17 @@
1
+ # 0.0.20 coherent scene painter
2
+
3
+ The complete scene now updates through one native layered window to fix the
4
+ 0.0.19 flicker regression. Buffer shrinking, hidden-state release, stamp atlas
5
+ and indexed art loading remain; no art or pet behavior changed. Distant cat
6
+ furniture can enlarge the one bounding surface. The 0.0.19 figures below are
7
+ historical and must not describe the current version. See
8
+ [presentation evidence](SPARSE-PAINT.md) and [current measurements](NATIVE-RUNTIME.md).
9
+
10
+ Real 4K 150s measurements: dog median22.9MiB / OS peak38.7MiB; cat with
11
+ tower/wheel44.4 /65.5MiB. Watch/brain off, no forced GC/trim; not a hard cap.
12
+
13
+ ## Superseded 0.0.19 result (presentation regression)
14
+
1
15
  # 0.0.19 sparse painter result
2
16
 
3
17
  Windows x64 basic mode retains one native process and splits sparse content
package/README.ko.md CHANGED
@@ -148,7 +148,20 @@ JS 플러그인과 JSONL 브리지를 제공해요. 화면·입력·시간 제
148
148
  AI로 생성한 그림에 자세 정렬·접지·프레임 처리를 적용한 애니메이션입니다.
149
149
  실제 모션 캡처나 자의식이 있는 강아지라고 주장하지 않아요.
150
150
 
151
- ## 0.0.19
151
+ ## 0.0.21
152
+
153
+ 고양이가 앞뒤로 돌아서 걷는 자세를 연결하고, 점프 중간 그림과 작은 수면
154
+ 움직임을 추가했어요. 한 원화에 겹쳐 있던 고양이도 지웠어요. 강아지는
155
+ 공놀이 출발·복귀 자세와 입에 문 공의 연결을 다듬었습니다. 기존 두 프레임
156
+ 캐시는 유지해요. 코드는 MIT, 두 펫의 그림은 CC BY-SA4.0입니다.
157
+
158
+ ## 0.0.20
159
+
160
+ Windows에서 화면 일부가 깜빡이던 문제를 고쳤어요. 장면 전체를 창 하나로
161
+ 갱신하고, 버퍼 축소와 숨김 상태 정리는 유지합니다. 그림과 동작은 그대로예요.
162
+ [수정과 측정 기록](SPARSE-PAINT.md)을 참고하세요.
163
+
164
+ ## 0.0.19 (이전 버전 · 깜빡임 수정으로 대체)
152
165
 
153
166
  Windows 기본 실행에서 표시 영역을 나누고 불필요하게 커진 버퍼를 회수해요.
154
167
  그림과 동작은 그대로예요. 실제4K150초 측정에서 강아지는 중앙값22.6MiB·최고36.5MiB,
package/README.md CHANGED
@@ -189,7 +189,22 @@ 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
192
+ ## 0.0.21
193
+
194
+ Cat turns now connect through standing poses before walking or pouncing. Jump
195
+ breakdowns and small sleep-fur/ear changes add motion without enlarging the
196
+ two-frame cache. A stray duplicate cat in one drawing is removed. Dog departure
197
+ and return turns share standing endpoints, with the carried ball attached to
198
+ the visible muzzle. Code remains MIT; both pets’ artwork is CC BY-SA4.0.
199
+
200
+ ## 0.0.20
201
+
202
+ Fixes Windows flicker introduced by the split-window painter. The whole scene
203
+ updates through one stable transparent window again; buffer shrinking and
204
+ hidden-state cleanup remain. Artwork and behavior are unchanged.
205
+ See [presentation fix and measurements](SPARSE-PAINT.md).
206
+
207
+ ## 0.0.19 (historical; superseded by flicker fix)
193
208
 
194
209
  Windows basic mode now splits distant content into small paint regions and
195
210
  reclaims oversized/hidden surfaces. Same pets, artwork and behavior. In real4K
package/RELEASING.md CHANGED
@@ -1,4 +1,38 @@
1
- # Retrieviewer 0.0.19 release process
1
+ # Retrieviewer 0.0.21 release process
2
+
3
+ Current target: `@bitefoundry/retrieviewer@0.0.21`, public `latest`.
4
+ The owner accepted the local six-finding repair candidate and asked to proceed.
5
+ Ship the reviewed dog standing-turn/mouth repairs and77-key cat atlas, preserving
6
+ 64 original cat drawings exactly. Cat source pin: `79a47c2d618b91945997035e6c7502c43f471f62`.
7
+ Keep physics, bounded caches, the coherent native painter and existing licenses.
8
+
9
+ Run full tests and release/package checks, install and compare the exact archive,
10
+ exercise its Windows adapter, then push and wait for Windows/Linux CI. Publish
11
+ that same archive, verify registry integrity and a fresh consumer, and verify
12
+ the matching bilingual Pages deployment and pinned cat hashes. Current memory
13
+ evidence is4K35s basic-mode sampling, not a long-run ceiling; see
14
+ [repair evidence](research/pet-motion-repair-20261025.md). Raw sheets stay private.
15
+
16
+ ## Previous0.0.20 scope (historical)
17
+
18
+ # Retrieviewer 0.0.20 release process
19
+
20
+ Current target: `@bitefoundry/retrieviewer@0.0.20`, public `latest`.
21
+ Hotfix for the reported flicker in the previously authorized npm/site release.
22
+ Replace the dynamically repartitioned HWNDs with one complete-scene layered
23
+ window and one pixel/position/size upload. Keep stamp caching, indexed art,
24
+ buffer shrinking and hidden-state release. Artwork and pet behavior are unchanged.
25
+
26
+ Verify moving/disjoint masks keep one stable window, exact native paint pixels,
27
+ resize/hide/restore/switch/teardown, full suite and fresh installed artifact.
28
+ Publish that same tested tarball after CI, verify registry integrity and a fresh
29
+ consumer, then confirm the matching bilingual website. Memory figures must
30
+ come from this version, not the regressed sparse painter. Existing code/art
31
+ licenses, optional integrations, user pet and privacy setup are preserved.
32
+
33
+ ## Previous 0.0.19 scope (historical; presentation regression)
34
+
35
+ ### Retrieviewer 0.0.19 release process
2
36
 
3
37
  Current target: `@bitefoundry/retrieviewer@0.0.19`, public `latest`.
4
38
  The owner explicitly authorized npm and website deployment on2026-10-25.
package/SPARSE-PAINT.md CHANGED
@@ -1,3 +1,56 @@
1
+ # Coherent native painting · 0.0.20
2
+
3
+ The 0.0.19 sparse-window optimization is superseded. Per-frame pixel comparison
4
+ passed, but a moving scene could change its partition and destroy/reposition
5
+ multiple HWNDs at different compositor moments. The owner reported visible
6
+ flicker. Preparing all DIBs first did not make those window updates atomic.
7
+
8
+ Version 0.0.20 paints the complete final mask into one cropped layered window.
9
+ `UpdateLayeredWindow` uploads all pixels, position and size together. The same
10
+ HWND survives movement and crop changes. Visibility is changed only when first
11
+ shown; there is no extra per-frame `SetWindowPos` after upload. No animal assets,
12
+ anatomy, motion, Braille masks or optional integrations changed.
13
+
14
+ The 256-mask stamp atlas and one-process QuickJS host remain. Capacity still
15
+ shrinks when it exceeds twice the rounded target area; hidden/minimized scenes
16
+ release their surface and window. The tradeoff is one bounding bitmap spanning
17
+ distant furniture: memory can exceed the regressed split-painter measurements.
18
+ Those old numbers below must not be presented as current measurements.
19
+
20
+ ## Verification
21
+
22
+ - Real Win32 presentation regression: 128 changing/disjoint scenes retain one
23
+ visible HWND, with exact full-scene BGRA pixels; hidden resources release.
24
+ - Native mask/capacity tests: 56 grids across seven cell sizes.
25
+ - Real moving dog/cream, cat/gold and cat/dark: 301 exact paint comparisons.
26
+ - Owned 4K -> small -> 4K, minimize/restore and same-process species switching.
27
+ DIB capacity shrank from 12.22 to 1.50 MiB and reached zero when minimized.
28
+
29
+ Reproduce with `node --test native-desktop.test.mjs`,
30
+ `node tools/check-native-paint.mjs` and `node tools/check-native-resources.mjs`.
31
+ This checks actual window lifetime and upload coherence as well as image bytes;
32
+ finite pixel/lifetime checks are not a claim that every display was observed.
33
+
34
+ ## Current memory measurement
35
+
36
+ Same Windows x64 host, owned off-screen 3840x2160 target, real native painter,
37
+ seed731 and 150s action schedule, 300 roughly half-second samples per pet
38
+ (157.5/156.9s wall time). Watch/brain off; no forced GC or working-set trim.
39
+ Harness and external DWM/GPU allocations are excluded.
40
+
41
+ | Pet | Median RSS | Sampled maximum | OS lifetime peak |
42
+ |---|---:|---:|---:|
43
+ | Retriever |22.9MiB|37.9MiB|38.7MiB|
44
+ | Cat with tower/wheel |44.4MiB|64.6MiB|65.5MiB|
45
+
46
+ Cat furniture spans a large bounding bitmap again. This restores coherent
47
+ presentation at a higher RAM cost than 0.0.19; it is not a universal 50MiB cap.
48
+ Both owned processes stopped and removed their sessions. Raw local evidence:
49
+ `dist/coherent-paint-memory/{dog,cat,report}.json`. Full suite:244 passed,
50
+ two macOS-only tests skipped on Windows. No artwork or motion change.
51
+
52
+ ## Superseded 0.0.19 experiment (historical)
53
+
1
54
  # Sparse Windows paint surfaces · 0.0.19
2
55
 
3
56
  Version0.0.19 builds on0.0.18. JavaScript behavior, Q5 art, world/contact math, colors and
@@ -75,6 +75,7 @@ static JSValue blit(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{
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
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
+ if(argc==2&&std::string(argv[1])=="--self-test-presentation")try{return NativePet::selfTestPaintPresentation();}catch(const std::exception& e){fprintf(stderr,"%s\n",e.what());return 1;}
78
79
  bool production=false;for(int i=1;i<argc;i++)if(std::string(argv[i])=="--production")production=true;std::string configuration;
79
80
  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
81
  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);
@@ -3,12 +3,12 @@
3
3
  "platform": "win32-x64",
4
4
  "files": {
5
5
  "runtime.bundle.mjs": {
6
- "bytes": 235608,
7
- "sha256": "e9da942dbe57ac778c44ea8f300e2e868face5e73b00ffcd4399aeb7e94ee09c"
6
+ "bytes": 242314,
7
+ "sha256": "0ce3c30a1b23901510a6a794373b20dd0f2b69093382e9ecd29d9103bc8cba41"
8
8
  },
9
9
  "bin/win32-x64/retrieviewer-native.exe": {
10
- "bytes": 1564672,
11
- "sha256": "288150c7112c50214996174250ce71bfa4ffc91779c8ea899a47d85f4fef71dd"
10
+ "bytes": 1562624,
11
+ "sha256": "0844d0e5303938e11d3e57acb457d7f6df2f138ee84246ecdf006395055f0fba"
12
12
  }
13
13
  }
14
14
  }
@@ -46,7 +46,7 @@ static void verifyPaint(const std::vector<uint8_t>& masks,int columns,const PetP
46
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
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
48
  }
49
- if(reference!=assemble(all,cell))throw std::runtime_error("Partitioned dots changed pixels");verifiedPaints++;
49
+ if(reference!=assemble(all,cell))throw std::runtime_error("Native dots changed pixels");verifiedPaints++;
50
50
  if(!atlasVerified){printf("{\"stage\":\"dot-atlas-verified\",\"pixelExact\":true,\"regions\":%zu}\n",paintCount());fflush(stdout);}atlasVerified=true;
51
51
  }
52
52
  static void paint(const std::string& text,int columns,int rows,int cell,POINT canvas){
@@ -66,10 +66,11 @@ static void paint(const std::string& text,int columns,int rows,int cell,POINT ca
66
66
  for(size_t i=regions.size();i<panels.size();i++)panels[i].release();
67
67
  if(verifyAtlas&&(!atlasVerified||verifyEveryPaint))verifyPaint(masks,columns,all,cell);
68
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.
69
+ // The complete scene (pet, ball and furniture) has exactly one HWND.
70
+ // UpdateLayeredWindow changes its pixels, size and position in one call.
71
+ // Do not add another per-frame SetWindowPos/show: it exposes separate state.
71
72
  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
+ 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");}if(!IsWindowVisible(p.window))ShowWindow(p.window,SW_SHOWNOACTIVATE);}
73
74
  ReleaseDC(nullptr,screen);painted++;
74
75
  }
75
76
  static int selfTestPaintRegions(){
@@ -77,10 +78,39 @@ static int selfTestPaintRegions(){
77
78
  for(int cell:{6,8,10,12,16,24,32})for(int type=0;type<8;type++){
78
79
  const int cols=type==7?1000:240,rows=type==7?125:90;std::vector<uint8_t> masks((size_t)cols*rows);
79
80
  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
+ const auto regions=PetPaint::partition(masks,cols,rows,cell);if(regions.size()>PetPaint::MaxRegions)throw std::runtime_error("Non-atomic scene partition");std::vector<uint8_t> seen(masks.size(),0);PetPaint::Rect all;uint64_t bytes=0;
81
82
  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
83
  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
  }
84
85
  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
86
  printf("{\"passed\":true,\"partitionCases\":%zu,\"coverageAndDisjointness\":true,\"surfaceShrink\":true}\n",checks);return 0;
86
87
  }
88
+
89
+ // Real Win32 regression: moving/disjoint masks must retain one display window.
90
+ // Byte equality alone did not catch the multi-HWND presentation regression.
91
+ static int selfTestPaintPresentation(){
92
+ Gdiplus::GdiplusStartupInput input;ULONG_PTR token=0;
93
+ if(Gdiplus::GdiplusStartup(&token,&input,nullptr)!=Gdiplus::Ok)throw std::runtime_error("Presentation GDI+ startup");
94
+ WNDCLASS wc{};wc.lpfnWndProc=DefWindowProc;wc.hInstance=GetModuleHandle(nullptr);wc.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&wc);
95
+ const int columns=240,rows=90,cell=8;HWND first=nullptr;size_t checked=0;
96
+ verifyAtlas=verifyEveryPaint=atlasVerified=true;
97
+ try{
98
+ for(int frame=0;frame<128;frame++){
99
+ std::string text;
100
+ for(int y=0;y<rows;y++){if(y)text+='\n';for(int x=0;x<columns;x++){
101
+ bool pet=x>=20+frame%81&&x<55+frame%81&&y>=20&&y<65;
102
+ bool object=frame%3!=0&&x>=200-frame%13&&x<210-frame%13&&y>=5&&y<30;
103
+ unsigned ch=0x2800+(pet||object?1+(x+y+frame)%255:0);
104
+ text+=(char)(0xe0|(ch>>12));text+=(char)(0x80|((ch>>6)&63));text+=(char)(0x80|(ch&63));
105
+ }}
106
+ paint(text,columns,rows,cell,POINT{-12000,-12000});
107
+ if(paintCount()!=1||!IsWindowVisible(panels[0].window))throw std::runtime_error("Scene lost coherent visible window");
108
+ if(!first)first=panels[0].window;
109
+ if(panels[0].window!=first)throw std::runtime_error("Moving scene recreated its display window");
110
+ checked++;
111
+ }
112
+ hidePanels();if(paintCount()!=0||paintBytes()!=0)throw std::runtime_error("Hidden scene retained paint resources");
113
+ }catch(...){releasePanels();atlas.pixels.clear();Gdiplus::GdiplusShutdown(token);throw;}
114
+ atlas.pixels.clear();Gdiplus::GdiplusShutdown(token);
115
+ printf("{\"passed\":true,\"frames\":%zu,\"singleWindow\":true,\"stableWindow\":true,\"pixelExact\":true,\"hiddenResourcesReleased\":true}\n",checked);return 0;
116
+ }
@@ -5,9 +5,10 @@
5
5
  #include <stdexcept>
6
6
  #include <vector>
7
7
 
8
- // Cell-aligned, non-overlapping crops of the FINAL mask. No animal/layer redraw.
8
+ // One final-mask crop and one layered-window upload per scene. Separate HWNDs
9
+ // cannot present a repartitioned frame atomically, even with identical pixels.
9
10
  namespace PetPaint {
10
- constexpr size_t MaxRegions=8;
11
+ constexpr size_t MaxRegions=1;
11
12
  struct Rect {
12
13
  int left=std::numeric_limits<int>::max(),top=std::numeric_limits<int>::max();
13
14
  int right=0,bottom=0;
@@ -19,37 +20,9 @@ struct Rect {
19
20
  };
20
21
  inline int rounded(int n){return (n+63)/64*64;}
21
22
  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
23
  inline std::vector<Rect> partition(const std::vector<uint8_t>& masks,int columns,int rows,int cell){
40
24
  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
25
  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;
26
+ return all.empty()?std::vector<Rect>{}:std::vector<Rect>{all};
54
27
  }
55
28
  }