From 5f82cc8b7288ee8ece6badfb1ff55dcc1d47407d Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 14 Aug 2026 12:01:59 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=8C=E7=BE=8E?= =?UTF-8?q?=E5=83=8F=E7=B4=A0=E6=97=A0=E6=B3=95=E8=AF=86=E5=88=AB=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E4=BA=A7=E7=89=A9=E7=9A=84=E7=BD=91=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 边缘 profile 用的是跨度 2 的中心差分,硬块边界落在列 b-1|b 之间时 profile[b-1] 与 profile[b] 逐位相等:两者跨的是同一条边,逐行被加数 相同、求和顺序相同。峰值判定两侧都用严格大于,会把这个 2 宽平台的 两端一起丢掉,于是最干净的输入反而 0 个峰、被判为未识别到网格—— 完美像素处理不了自己的输出。 estimate_step_size 左邻比较放宽为 >=,右邻保持严格 >:平台只保留 一个索引,且是右端,正好是左闭右开语义下的真实块边。两侧都放宽则 会把长度 K 的常数段整段判为峰。 walk 与 snap_uniform_cuts 的 argmax 同样改为 >=,取等值段的最后一 个索引。取第一个会让每条切线落在真实边界左边 1px,每个 cell 因此 混进 1/4 的邻块。实测在 266x253 的最近邻放大像素画上,切线命中真实 块边从 2.6% 升到 98.5%,左右镜像、上下翻转、180° 旋转下一致;龙图 内容区内 134/134 全中。 Co-Authored-By: Claude Opus 5 --- .../platform-image/src/pixel_art_snapper.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/server-rs/crates/platform-image/src/pixel_art_snapper.rs b/server-rs/crates/platform-image/src/pixel_art_snapper.rs index e22f63c22..cca5e58a2 100644 --- a/server-rs/crates/platform-image/src/pixel_art_snapper.rs +++ b/server-rs/crates/platform-image/src/pixel_art_snapper.rs @@ -588,10 +588,18 @@ fn estimate_step_size(profile: &[f64], config: SnapConfig) -> Option { return None; } let threshold = maximum * config.peak_threshold_multiplier; + // The profile is a two-span central difference, so a hard block edge between + // columns `b - 1` and `b` makes `profile[b - 1]` and `profile[b]` bitwise equal: + // both straddle the same edge and sum the same per-row terms in the same order. + // A strict comparison on both sides rejects both ends of that plateau, which is + // why perfectly snapped pixel art used to yield zero peaks. Accepting equality on + // the left keeps exactly one index per plateau — the right end, which is the real + // boundary — while a strict comparison on the right still rejects the interior of + // wider constant runs. let peaks = (1..profile.len().saturating_sub(1)) .filter(|&index| { profile[index] > threshold - && profile[index] > profile[index - 1] + && profile[index] >= profile[index - 1] && profile[index] > profile[index + 1] }) .collect::>(); @@ -694,7 +702,10 @@ fn walk( let mut best_index = start; let mut best_value = -1.0f64; for (index, value) in profile.iter().enumerate().take(end).skip(start) { - if *value > best_value { + // `>=` keeps the last index of an equal run, matching the plateau end that + // `estimate_step_size` selects. Picking the first index instead would place + // every cut one pixel left of the real block boundary. + if *value >= best_value { best_value = *value; best_index = index; } @@ -878,7 +889,8 @@ fn snap_uniform_cuts( .take(end.min(profile.len().saturating_sub(1)) + 1) .skip(start) { - if *value > best_value { + // Same plateau-end convention as `walk`. + if *value >= best_value { best_value = *value; best_index = index; } -- 2.52.0 From f5dc6050c4a9993a299e558d8e80accdb1cd918c Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 14 Aug 2026 12:19:30 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=80=E8=BF=91?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=89=93=E5=BC=80=E7=9B=AE=E5=BD=95=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=AB=9E=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 状态文案是在 await invoke 之后才 setStatus 的,而测试的 waitFor 只等到 invoke 被调用——invoke 在点击处理器里是同步发出的,第一次检查就通过并 立即返回,此时状态写入和随后的渲染都还没发生。断言能否看到文案取决于 RTL 内部恰好冲刷了几个微任务,CI 负载高时就翻车。 改用 findByText 显式等待。1543 那条是同一形态的隐患,一并改掉。 Co-Authored-By: Claude Opus 5 --- apps/ai-game-creator-shell/tests/appSurface/home.suite.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts index f4046af9b..ace0c0a08 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts @@ -1515,7 +1515,7 @@ export function registerRecentProjectsTests() { 'open_game_creator_workspace_window', expect.anything(), ); - expect(screen.getByText('已打开项目目录')).not.toBeNull(); + expect(await screen.findByText('已打开项目目录')).not.toBeNull(); expect(window.localStorage.length).toBe(1); }); @@ -1540,7 +1540,7 @@ export function registerRecentProjectsTests() { projectPath: '/tmp/typed-game', }); }); - expect(screen.getByText('已打开项目目录')).not.toBeNull(); + expect(await screen.findByText('已打开项目目录')).not.toBeNull(); }); it('rejects invalid typed launcher project directories before opening them', () => { -- 2.52.0