diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/area.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/area.rs index 739492b6c..30dd4e4cf 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/area.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/separation/area.rs @@ -2,11 +2,10 @@ use super::model::BindingArea; use image::RgbaImage; use std::time::Instant; -/// Each edge may move by at most this percentage of the corresponding area -/// dimension returned by the visual model. Keep this policy explicit so -/// changing it is an intentional workflow decision rather than a scattered -/// numeric literal. -pub(crate) const MAX_BINDING_AREA_EDGE_ADJUSTMENT_PERCENT: u32 = 100; +/// Each edge may move by at most this many pixels from the area returned by +/// the visual model. Keep this policy explicit so changing it is an +/// intentional workflow decision rather than a scattered numeric literal. +pub(crate) const MAX_BINDING_AREA_EDGE_ADJUSTMENT_PX: u32 = 32; #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct NormalizedBindingArea { @@ -87,11 +86,6 @@ fn rect_has_visible_pixel(image: &RgbaImage, rect: Rect) -> bool { (rect.top..rect.bottom).any(|y| (rect.left..rect.right).any(|x| image.get_pixel(x, y).0[3] > 0)) } -fn max_edge_adjustment(dimension: u32) -> u32 { - ((u64::from(dimension) * u64::from(MAX_BINDING_AREA_EDGE_ADJUSTMENT_PERCENT)) / 100) - .min(u64::from(u32::MAX)) as u32 -} - fn edge_direction(image: &RgbaImage, rect: Rect, edge: Edge) -> EdgeDirection { if edge_has_visible_pixel(image, rect, edge) { EdgeDirection::Outward @@ -126,15 +120,12 @@ fn edge_displacement(original: Rect, current: Rect, edge: Edge) -> u32 { edge_coordinate(original, edge).abs_diff(edge_coordinate(current, edge)) } -fn edge_adjustment_limit(original: Rect, edge: Edge) -> u32 { - max_edge_adjustment(match edge { - Edge::Left | Edge::Right => original.right - original.left, - Edge::Top | Edge::Bottom => original.bottom - original.top, - }) +fn edge_adjustment_limit() -> u32 { + MAX_BINDING_AREA_EDGE_ADJUSTMENT_PX } fn reached_adjustment_limit(original: Rect, current: Rect, edge: Edge) -> bool { - edge_displacement(original, current, edge) >= edge_adjustment_limit(original, edge) + edge_displacement(original, current, edge) >= edge_adjustment_limit() } fn can_move_geometrically( @@ -393,10 +384,10 @@ mod tests { } #[test] - fn caps_each_edge_at_original_dimension() { - let image = image_with_rect(64, 64, 0, 0, 64, 64); - let result = normalize_binding_area(&image, area(16, 16, 8, 8)).unwrap(); - assert_eq!(result.area, area(8, 8, 24, 24)); + fn caps_each_edge_at_absolute_pixel_limit() { + let image = image_with_rect(128, 128, 0, 0, 128, 128); + let result = normalize_binding_area(&image, area(48, 48, 8, 8)).unwrap(); + assert_eq!(result.area, area(16, 16, 72, 72)); assert!(result.clamped); } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 5e45daf45..2d6cae156 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -8040,6 +8040,10 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - `validater(&T) -> Result<(), String>` 只负责业务校验。网络、模型、tool 缺失、JSON 或反序列化错误只按原 history 重试;只有业务校验失败才把序列化后的响应和校验错误合并为一条 system message 追加到 history。 - history 仅存在本次请求内存中,不重复图片、不截断、不扩展 `platform-llm` 消息协议;重试次数参数统一使用 `max_retries`。 +## 2026-09-11 UI 编辑器素材切分边界使用固定像素上限 + +- UI 编辑器自动切分在 cut 前对视觉模型返回的 `BindingArea` 做像素边界归一化时,每条边相对原始区域最多移动 `32px`,不再按原始区域宽高的百分比计算;Rust 常量为 `MAX_BINDING_AREA_EDGE_ADJUSTMENT_PX`,技术方案同步记录该固定上限。 + ## 2026-08-29 DirectProject 受控联网搜索默认与边界 - 正式产品本次只覆盖 `DirectProject` 单 Codex Agent。`Provider`、`ToolHost`、`DirectHome` 不是 Agent,也不是本次联网主链路;不新增全路由联网或工具桥。唯一受控联网工具为 `agc_tools.agc_web_search`,链路固定为 Codex MCP 工具目录 -> 客户端 loopback `DirectToolBridge` -> 有界 Bing RSS HTTPS -> 过滤 / 脱敏 -> MCP 结果回传。 diff --git a/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md b/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md index 4f19e0e78..7bce4bfc5 100644 --- a/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md +++ b/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md @@ -1,6 +1,6 @@ # UI 编辑器自动切分素材工作流 -更新时间:`2026-09-08` +更新时间:`2026-09-11` ## 目标 @@ -51,7 +51,7 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正 `spawn_blocking` 任务;image-edit 与 visual binding 网络请求仍运行在 async future 中。 - 视觉 binding 输入源图与处理图,必须为当前 batch 每个节点恰好返回一次 `Ok` 或 `NeedRework`。 - `Ok` 返回 `NodeId + BindingArea`;Rust 仅校验 NodeId、区域边界和非零尺寸,不检查与原节点框的偏差,也不要求区域不重叠。 -- cut 前会对视觉模型返回的 `BindingArea` 做本地像素边界归一化。处理图是透明 PNG,有效像素定义为 `alpha > 0`。四条边以模型 area 为起点,每条边根据首次扫描结果固定方向:边上无有效像素则只向内收缩,边上有有效像素则只向外扩展;四边每轮从同一矩形快照同时逐像素推进,直到达到“内侧有像素、外侧无像素”的分界、图像边界或每条边相对原始 area 的位移上限。该上限由模块级常量 `MAX_BINDING_AREA_EDGE_ADJUSTMENT_PERCENT` 定义。方向固定用于避免稀疏像素造成边界来回振荡;没有理想分界时使用受限范围内的最终 area,不重新请求视觉模型,也不转 problematic。全透明处理图不走特殊错误分支,仍沿同一规则得到最终 area 后裁切。归一化只影响本地 cut,不改写原始 `BindingDecision`、sidecar 或 DTO;日志记录原始 area、最终 area、是否变更,以及仍需移动时是否受到该常量上限、图像边界或非零尺寸约束。性能优化列 TODO。 +- cut 前会对视觉模型返回的 `BindingArea` 做本地像素边界归一化。处理图是透明 PNG,有效像素定义为 `alpha > 0`。四条边以模型 area 为起点,每条边根据首次扫描结果固定方向:边上无有效像素则只向内收缩,边上有有效像素则只向外扩展;四边每轮从同一矩形快照同时逐像素推进,直到达到“内侧有像素、外侧无像素”的分界、图像边界或每条边相对原始 area 的位移上限。每条边最多相对原始 area 移动 `32px`,由模块级常量 `MAX_BINDING_AREA_EDGE_ADJUSTMENT_PX` 定义,与原始 area 尺寸无关。方向固定用于避免稀疏像素造成边界来回振荡;没有理想分界时使用受限范围内的最终 area,不重新请求视觉模型,也不转 problematic。全透明处理图不走特殊错误分支,仍沿同一规则得到最终 area 后裁切。归一化只影响本地 cut,不改写原始 `BindingDecision`、sidecar 或 DTO;日志记录原始 area、最终 area、是否变更,以及仍需移动时是否受到该常量上限、图像边界或非零尺寸约束。性能优化列 TODO。 - `NeedRework` 携带短问题描述(最多 512 个 Unicode 字符);通过校验后按产生顺序追加到目标 `SeparationNode.note.rework_notes`,下一次该节点进入 image-edit 时全部意见会注入提取 prompt。结构化工具调用失败时使用可复用 repair harness,把错误反馈给模型并额外请求一次;image-edit 不使用该 harness。 - 达到返工上限时仍先保留最后一条视觉模型意见,再把节点追加到 problematic;网络、IO、裁切等基础设施错误不写入节点意见。 - 达到模块级重做常量后,节点移入 problematic;不中断整条工作流,最终统一通知用户。image-edit、图像写入或裁切失败保留当前 state 并返回错误,不自动把整批标记为 problematic。