diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs index b3d60bc64..86416a42d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs @@ -550,6 +550,7 @@ fn extract_mcp_arguments(root: &Path, tool: &str, arguments: &Value) -> Value { "agc_generate_image" => { copy_string(object, "kind", &mut out); copy_string(object, "sliceMode", &mut out); + copy_number(object, "sliceCount", &mut out); copy_string(object, "screenColor", &mut out); copy_string(object, "aspectRatio", &mut out); copy_string(object, "imageSize", &mut out); @@ -1195,7 +1196,13 @@ mod tests { "item": { "type": "mcpToolCall", "tool": "agc_generate_image", - "arguments": { "prompt": prompt, "kind": "icon-spec" } + "arguments": { + "prompt": prompt, + "kind": "art-spritesheet", + "sliceMode": "connected-components", + "sliceCount": 8, + "screenColor": "#CFEFFF" + } } })); audit.finish(true); @@ -1206,6 +1213,8 @@ mod tests { let stored = item["arguments"]["prompt"].as_str().expect("prompt"); assert_eq!(stored.chars().count(), DIRECT_CODEX_AUDIT_BRIEF_CHARS); assert_eq!(item["arguments"]["promptChars"], json!(5000)); + assert_eq!(item["arguments"]["sliceCount"], json!(8)); + assert_eq!(item["arguments"]["screenColor"], json!("#CFEFFF")); assert_eq!( item["arguments"]["promptSha256"], json!(sha256_hex("收".repeat(5000).as_bytes())) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs index 164bd1543..7eb8011df 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs @@ -2224,7 +2224,7 @@ fn validate_generate_image_slice_declaration( } if slice_mode.is_some() || grid_x.is_some() || grid_y.is_some() || slice_count.is_some() { return Err(format!( - "工具参数 sliceMode/gridX/gridY 仅对 kind=art-spritesheet 生效,当前 kind={kind}" + "工具参数 sliceMode/gridX/gridY/sliceCount 仅对 kind=art-spritesheet 生效,当前 kind={kind}" )); } Ok(()) @@ -2915,6 +2915,13 @@ mod tests { wrong_kind.contains("仅对 kind=art-spritesheet 生效"), "{wrong_kind}" ); + let wrong_kind_count = + validate_generate_image_slice_declaration("image", None, None, None, Some(8)) + .expect_err("sliceCount-only violation must be rejected"); + assert!( + wrong_kind_count.contains("sliceCount"), + "sliceCount-only violation must name sliceCount: {wrong_kind_count}" + ); assert!(validate_generate_image_slice_declaration("image", None, None, None, None).is_ok()); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs index 39857aff7..80e0dbc2a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs @@ -2209,9 +2209,8 @@ pub(crate) async fn generate_platform_art_asset_with_required_slices_at( /// 动作,必须各自独立成槽,才能在同一项目里同时在途。 /// /// 升级前遗留账本仍由旧材料函数定位;新请求把显式切分模式纳入身份,避免同一图集 -/// 请求在网格与连通域之间误复用。`slice_count` 继续保持历史兼容语义,不进身份。 -/// `screen_color` 是付费语义不同的输入,进入身份;None 时跳过序列化,未使用该字段的 -/// 请求身份与升级前逐字节一致。 +/// 请求在网格与连通域之间误复用。`slice_count` 与 `screen_color` 同样改变付费产出, +/// 一并进入身份;None 时跳过序列化,未使用这些字段的请求身份与旧版逐字节一致。 #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct StandalonePlatformArtGenerationFingerprintMaterial<'a> { @@ -2227,6 +2226,8 @@ struct StandalonePlatformArtGenerationFingerprintMaterial<'a> { grid_x: Option, grid_y: Option, #[serde(skip_serializing_if = "Option::is_none")] + slice_count: Option, + #[serde(skip_serializing_if = "Option::is_none")] screen_color: Option<&'a str>, } @@ -2267,6 +2268,7 @@ fn standalone_platform_art_generation_runtime_context( slice_mode: options.slice_mode.as_deref(), grid_x: options.grid_x, grid_y: options.grid_y, + slice_count: options.slice_count, screen_color: options.screen_color.as_deref(), }) .map_err(|error| format!("序列化 standalone 图片生成动作身份失败:{error}"))?; @@ -8516,6 +8518,9 @@ mod canvas_generation_tests { let mut changed = options.clone(); changed.screen_color = Some("#CFEFFF".to_string()); changed_options.push(changed); + let mut changed = options.clone(); + changed.slice_count = Some(8); + changed_options.push(changed); for changed in changed_options { let context = standalone_platform_art_generation_runtime_context( "完整生成提示词", diff --git a/docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md b/docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md index e368e3611..66a3adbda 100644 --- a/docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md +++ b/docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md @@ -185,7 +185,7 @@ camelCase JSON。禁止出现附件正文、命令 stdout、patch diff、宿主 | `agc_list_project_files` | `path`、`query`(120)、`kind`、`offset`、`limit` | 无 | | `agc_write_file` | `path`、`contentChars`(`content` 的字符数,不是正文) | 不落 `content` | | `taonier_prepare_game_art` | `mode`、`brief`(截断 4000)、`briefChars`、`briefSha256` | **要 brief 原文**(分析定玩法的吸烟枪;上限已是 MCP 合同) | -| `agc_generate_image` | `kind`、`sliceMode`、`screenColor`、`aspectRatio`、`imageSize`、`assetName`、`outputPath`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 不落 32k 全文 | +| `agc_generate_image` | `kind`、`sliceMode`、`sliceCount`、`screenColor`、`aspectRatio`、`imageSize`、`assetName`、`outputPath`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 不落 32k 全文 | | `agc_edit_image` | `sourceLocalAssetId`、`assetName`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 同上 | | `agc_create_or_derive_resource` | `kind`、`mode`、`sourceLocalAssetId`、`assetName`、`prompt` 截断 4000、`promptChars`、`promptSha256` | MCP 上限已是 4000 | | `agc_list_registered_assets` | `kind`、`assetId`、`includeSequenceFrames`、`offset`、`limit` | 无 |