From f9ee0d5ac7d7bfad5834e563acd0d815310bdfc1 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:53:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E7=9B=B4=E8=BF=9E=E5=B7=B2?= =?UTF-8?q?=E6=9C=89=E6=B8=B8=E6=88=8F=E6=84=8F=E5=9B=BE=E5=88=86=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 已有游戏默认进入同一 Codex thread 编辑与试玩链路 - 仅明确新建或重做美术请求触发平台素材准备 - 补充已有游戏编辑与外部美术生成意图分流测试 --- .../src-tauri/src/agent/direct_runtime.rs | 103 ++++++++++++++++-- 1 file changed, 94 insertions(+), 9 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs index 606f25217..053c13cfb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs @@ -41,6 +41,32 @@ const DIRECT_CODEX_GAME_OUTPUTS: [(&str, &str, &str); 3] = [ ("game/style.css", "game-style", "text/css"), ("game/game.js", "game-script", "text/javascript"), ]; + +fn direct_existing_game_sources_exist(root: &Path) -> bool { + DIRECT_CODEX_GAME_OUTPUTS + .iter() + .all(|(path, _, _)| root.join(path).is_file()) +} + +/// Art generation is an external, billable side effect. Existing direct +/// projects therefore stay in their same-thread code-edit/preview loop unless +/// the user explicitly asks for a new game or a visual regeneration. +fn direct_prompt_requests_fresh_art_generation(prompt: &str) -> bool { + let prompt = prompt.trim().to_lowercase(); + [ + "新游戏", + "新建", + "从头", + "重新生成", + "重做美术", + "换一套美术", + "重新生图", + "生成美术", + "做素材", + ] + .iter() + .any(|marker| prompt.contains(marker)) +} const DIRECT_CODEX_BUILTIN_SKILL: &str = "技能 direct-game-creation:先检查当前 game/ 入口和已有玩法,优先最小修改;需要生成新游戏时直接创建可运行的 game/index.html、style.css 和 game.js,保证 Canvas、输入、主要玩法、重开和移动端布局。游戏必须适配 16:9 横屏逻辑视口,页面本身不得出现横向或纵向滚动条。客户端会在本回合开始前通过陶泥儿平台准备已登记的真实美术资源。优先使用规范图、场景背景和可用图集/切片;如果平台只返回完整透明图集或某些切片缺失,不要伪造切片,也不要因此停止生成。至少让一个真实平台素材进入实际渲染路径,并通过真实浏览器试玩检查画面和交互。不要用 emoji、纯色块或 CSS 图形冒充平台核心美术,也不要只在回复中提及图片路径。不要自行调用生图 API、不要伪造图片或修改 .agent/manifest.json;客户端会负责平台调用与资源登记。完成文件修改后运行可用的静态检查并通过 AGC 本地预览确认真实页面;执行环境没有命令或浏览器工具时必须如实说明。收到客户端试玩证据后,必须读取实际文件并按证据继续修复;最终回复结构化列出检查文件、真实启动或试玩动作、desktop/mobile 观察、平台素材实际使用、已修复问题和剩余风险。只使用当前工作区文件变更,不启动任何 Supervisor、child、harness 或隐藏返工。"; const DIRECT_TAONIER_ART_WORKFLOW: &str = "陶泥儿游戏创作工作流:客户端先准备并登记平台美术资源。项目结构固定为 `assets/` 存放已登记的真实媒体、`game/index.html` 为入口、`game/style.css` 与 `game/game.js` 为游戏代码、`memory/` 存放玩法记忆、`.agent/` 为客户端维护的项目状态。规范图用于统一视觉风格;背景、完整图集和平台返回的切片可按实际可用性接入。不得手写或修改 `.agent/manifest.json`,不得创建伪造 PNG、data URL、emoji 或纯 CSS 图形来代替真实平台素材。至少一个已登记平台图片必须被游戏源码引用并在页面中实际加载。"; @@ -1590,8 +1616,20 @@ async fn run_direct_game_creator_turn_inner( root: &Path, prompt: &str, ) -> Result { + if direct_existing_game_sources_exist(root) + && !direct_prompt_requests_fresh_art_generation(prompt) + { + emit_direct_game_creator_progress( + root, + "request.edit-existing-game", + "已识别为已有游戏的继续编辑,正在直接修改并试玩", + ); + return run_direct_game_creator_turn_with_private_editor_credentials(root, prompt, false) + .await; + } if direct_taonier_art_base_is_valid(root) { - return run_direct_game_creator_turn_with_private_editor_credentials(root, prompt).await; + return run_direct_game_creator_turn_with_private_editor_credentials(root, prompt, true) + .await; } let external_editor_credentials = ensure_private_external_editor_api_credentials() .await @@ -1599,7 +1637,7 @@ async fn run_direct_game_creator_turn_inner( DirectCodexTurnFailure::new(DirectCodexFailureStage::ArtPreparation, error) })?; with_external_editor_api_credentials(external_editor_credentials, async { - run_direct_game_creator_turn_with_private_editor_credentials(root, prompt).await + run_direct_game_creator_turn_with_private_editor_credentials(root, prompt, true).await }) .await } @@ -1607,16 +1645,24 @@ async fn run_direct_game_creator_turn_inner( async fn run_direct_game_creator_turn_with_private_editor_credentials( root: &Path, prompt: &str, + prepare_art: bool, ) -> Result { - ensure_direct_taonier_art_package_at(root, prompt) - .await - .map_err(|error| { - DirectCodexTurnFailure::new(DirectCodexFailureStage::ArtPreparation, error) - })?; + if prepare_art { + ensure_direct_taonier_art_package_at(root, prompt) + .await + .map_err(|error| { + DirectCodexTurnFailure::new(DirectCodexFailureStage::ArtPreparation, error) + })?; + } let previous_output_fingerprint = direct_codex_output_fingerprint(root); let mut system_prompt = build_direct_codex_system_prompt(root); - system_prompt.push_str("\n本回合已由陶泥儿平台准备并登记真实美术资源。请读取当前 `assets/` 与 `game/` 文件,选择实际存在且适合玩法的素材接入;不要假设四切片一定存在,也不要伪造缺失衍生物。客户端会在回合后启动真实 desktop/mobile 浏览器试玩,把结构化截图、Canvas、控制台、网络和交互证据发回同一会话;你必须依据证据继续修复,并在最终回复中给出检查文件、试玩动作、双视口观察、平台素材实际使用、已修复问题和剩余风险。"); - emit_direct_game_creator_progress(root, "codex.start", "美术素材已准备,正在生成游戏代码"); + if prepare_art { + system_prompt.push_str("\n本回合已由陶泥儿平台准备并登记真实美术资源。请读取当前 `assets/` 与 `game/` 文件,选择实际存在且适合玩法的素材接入;不要假设四切片一定存在,也不要伪造缺失衍生物。客户端会在回合后启动真实 desktop/mobile 浏览器试玩,把结构化截图、Canvas、控制台、网络和交互证据发回同一会话;你必须依据证据继续修复,并在最终回复中给出检查文件、试玩动作、双视口观察、平台素材实际使用、已修复问题和剩余风险。"); + emit_direct_game_creator_progress(root, "codex.start", "美术素材已准备,正在生成游戏代码"); + } else { + system_prompt.push_str("\n这是已有游戏的继续编辑回合:不要生成、下载或请求任何新美术,也不要创建新项目。直接读取当前 `game/` 和已登记 `assets/`,按用户需求最小修改;随后通过真实 desktop/mobile 浏览器试玩验证修改。客户端会在回合后启动真实浏览器并把结构化证据回灌同一会话。"); + emit_direct_game_creator_progress(root, "codex.start", "正在修改已有游戏并进行试玩"); + } let _initial_reply = direct_game_creator_codex_chat_at(root, system_prompt.clone(), prompt.to_string()) .await @@ -1872,6 +1918,45 @@ mod tests { assert!(prompt.contains("desktop/mobile 观察")); } + #[test] + fn existing_game_edits_do_not_request_a_fresh_art_generation_by_default() { + let root = tempfile::tempdir().expect("temp dir"); + init_local_game_project_at(root.path(), "direct-edit-intent", "继续编辑") + .expect("init project"); + std::fs::write(root.path().join("game/index.html"), "").expect("index"); + std::fs::write(root.path().join("game/style.css"), "body {};").expect("style"); + std::fs::write(root.path().join("game/game.js"), "console.log('edit');").expect("script"); + + assert!(direct_existing_game_sources_exist(root.path())); + for prompt in [ + "把棋盘上移一点", + "修复闪烁", + "继续优化交互", + "增加一个重开按钮", + ] { + assert!( + !direct_prompt_requests_fresh_art_generation(prompt), + "{prompt} must stay in the existing-game edit loop" + ); + } + } + + #[test] + fn explicit_new_game_or_art_request_enters_fresh_art_generation_path() { + for prompt in [ + "新建一个游戏", + "从头做一个三消", + "重做美术", + "重新生图", + "做素材", + ] { + assert!( + direct_prompt_requests_fresh_art_generation(prompt), + "{prompt} must explicitly opt into art generation" + ); + } + } + #[test] fn missing_platform_art_reference_becomes_a_same_thread_repair_prompt() { let root = tempfile::tempdir().expect("temp dir");