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 973c39caf..ce9467e77 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 @@ -63,32 +63,31 @@ const DIRECT_CODEX_ART_ASSET_PATHS: [&str; 3] = [ const DIRECT_CODEX_ART_AGENT_ID: &str = "direct-codex-art"; const DIRECT_CODEX_HOME_CREATE_PROJECT_MARKER: &str = "[[AGC_CREATE_PROJECT]]"; -fn direct_codex_game_outputs(root: &Path) -> Vec<(String, &'static str, &'static str)> { +/// 直连 Codex 生成的游戏工程文件 → manifest 登记项。 +/// +/// 这些不是用户素材,而是游戏工程自身的源码/配置(`index.html`、`style.css`、`game.js`、 +/// `package.json`、`package-lock.json`、`vite.config.js`)。它们的 manifest kind 统一是正式成员 +/// `code`:`code` 与旧的 `game-*` / `image` 兜底一样派生 `unclassified`(「待归类」), +/// 不会把工程文件塞进内容栏目,同时写入侧不再产出非 canonical 的 kind。 +fn direct_codex_game_outputs(root: &Path) -> Vec<(String, GameCreationAppAssetKind, &'static str)> { let entry = agent_runtime_game_entry_relative_path(root); let prefix = if entry == AGENT_RUNTIME_GAME_ENTRY_ROOT_PATH { "" } else { "game/" }; + let code = GameCreationAppAssetKind::Code; vec![ - (entry.to_string(), "game-entry", "text/html"), - (format!("{prefix}style.css"), "game-style", "text/css"), - (format!("{prefix}game.js"), "game-script", "text/javascript"), - ( - format!("{prefix}package.json"), - "game-package", - "application/json", - ), + (entry.to_string(), code, "text/html"), + (format!("{prefix}style.css"), code, "text/css"), + (format!("{prefix}game.js"), code, "text/javascript"), + (format!("{prefix}package.json"), code, "application/json"), ( format!("{prefix}package-lock.json"), - "game-lockfile", + code, "application/json", ), - ( - format!("{prefix}vite.config.js"), - "game-build-config", - "text/javascript", - ), + (format!("{prefix}vite.config.js"), code, "text/javascript"), ] } @@ -540,7 +539,7 @@ fn direct_taonier_spritesheet_matches_retained_stage_result_at( let Some(art_spec) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -550,7 +549,7 @@ fn direct_taonier_spritesheet_matches_retained_stage_result_at( let Some(spritesheet) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_SPRITESHEET_ASSET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "/api/external/v1/editor/icon-spritesheets/generations", "icon-spritesheet", Some(&art_spec), @@ -1986,7 +1985,7 @@ fn direct_taonier_art_generation_runtime_context( fn direct_taonier_art_asset_identity( root: &Path, expected_path: &str, - expected_kind: &str, + expected_kind: GameCreationAppAssetKind, expected_generation_route: &str, expected_generation_kind: &str, expected_reference_source: Option<&DirectTaonierArtAssetIdentity>, @@ -2094,7 +2093,7 @@ fn direct_taonier_art_base_is_valid(root: &Path) -> bool { let Some(art_spec) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -2104,7 +2103,7 @@ fn direct_taonier_art_base_is_valid(root: &Path) -> bool { let Some(background) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "/api/external/v1/editor/images/generations", "spec", Some(&art_spec), @@ -2119,7 +2118,7 @@ fn direct_taonier_art_package_is_valid(root: &Path) -> bool { let Some(art_spec) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -2132,7 +2131,7 @@ fn direct_taonier_art_package_is_valid(root: &Path) -> bool { let Some(spritesheet) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_SPRITESHEET_ASSET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "/api/external/v1/editor/icon-spritesheets/generations", "icon-spritesheet", Some(&art_spec), @@ -2183,7 +2182,7 @@ fn direct_registered_taonier_slice_paths(root: &Path) -> Vec { let Some(art_spec) = direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -2225,7 +2224,10 @@ fn direct_registered_taonier_slice_paths(root: &Path) -> Vec { return Vec::new(); }; if assets.next().is_some() - || asset.kind != "art-spritesheet-slice" + || asset.kind != GameCreationAppAssetKind::Icon + || !asset + .local_path + .starts_with("assets/art-spritesheet-slices/") || asset.media_type != "image/png" || asset.source.kind != GameCreationAppAssetSourceKind::Canvas || asset.source.generation_route.as_deref() @@ -2443,7 +2445,7 @@ fn snapshot_direct_spritesheet_recovery_manifest_for_install( let current_art_spec = direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -2462,7 +2464,7 @@ fn register_direct_recovered_spritesheet_at( register_local_asset_at( root, DIRECT_CODEX_SPRITESHEET_ASSET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "image/png", "platform-art-recovery", source, @@ -2499,7 +2501,7 @@ fn install_direct_recovered_spritesheet_binding_at( ®istered_asset.id, content_sha256, "image/png", - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet.as_str(), )?; let binding = new_external_editor_resource_binding( project_id, @@ -2816,7 +2818,7 @@ async fn generate_direct_taonier_art_asset_at( let art_spec = direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -2976,7 +2978,7 @@ pub(crate) async fn ensure_direct_taonier_art_package_at( direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -2987,7 +2989,7 @@ pub(crate) async fn ensure_direct_taonier_art_package_at( direct_taonier_art_asset_identity( root, DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "/api/external/v1/editor/images/generations", "spec", Some(art_spec), @@ -3053,7 +3055,7 @@ pub(crate) async fn ensure_direct_taonier_art_package_at( direct_taonier_art_asset_identity( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -3071,7 +3073,7 @@ pub(crate) async fn ensure_direct_taonier_art_package_at( || direct_taonier_art_asset_identity( root, DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "/api/external/v1/editor/images/generations", "spec", Some(&art_spec), @@ -3802,7 +3804,7 @@ fn sync_direct_codex_project_file_projection_at( register_local_asset_at( root, &local_path, - "game-source", + GameCreationAppAssetKind::Code, media_type, "direct-codex", direct_codex_generated_source(), @@ -5878,7 +5880,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "replacement-before-crash", Vec::new(), &replacement_spec, @@ -5976,7 +5978,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "strict-crash-spec", Vec::new(), &new_spec, @@ -5990,7 +5992,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "strict-crash-background", vec!["strict-crash-spec".to_string()], &new_background, @@ -6206,7 +6208,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "replacement-before-crash", Vec::new(), &tiny_replacement_png(64, 96, 224), @@ -6289,7 +6291,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "unknown-stage-replacement", Vec::new(), &tiny_replacement_png(224, 64, 32), @@ -6885,7 +6887,7 @@ mod tests { let art_spec = direct_taonier_art_asset_identity( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "/api/external/v1/editor/images/generations", "spec", None, @@ -6900,7 +6902,7 @@ mod tests { let concurrent = register_local_asset_at( root.path(), concurrent_path, - "illustration", + GameCreationAppAssetKind::Image, "image/png", "uploaded", GameCreationAppAssetSource { @@ -6933,7 +6935,7 @@ mod tests { let recovery = register_local_asset_at( root.path(), recovery_path, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "image/png", "platform-art-recovery", GameCreationAppAssetSource { @@ -6981,7 +6983,7 @@ mod tests { let registered = register_local_asset_at( root.path(), local_path, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "image/png", "platform-art-recovery", GameCreationAppAssetSource { @@ -7098,14 +7100,18 @@ mod tests { })); } - fn register_direct_taonier_art_asset_fixture(root: &Path, local_path: &str, kind: &str) { + fn register_direct_taonier_art_asset_fixture( + root: &Path, + local_path: &str, + kind: GameCreationAppAssetKind, + ) { let (generation_route, generation_kind, reference_resource_ids) = match kind { - "art-spritesheet" => ( + GameCreationAppAssetKind::IconSpritesheet => ( "/api/external/v1/editor/icon-spritesheets/generations", "icon-spritesheet", vec!["taonier-resource-icon-spec".to_string()], ), - "game-background" => ( + GameCreationAppAssetKind::Scene => ( "/api/external/v1/editor/images/generations", "spec", vec!["taonier-resource-icon-spec".to_string()], @@ -7116,6 +7122,14 @@ mod tests { Vec::new(), ), }; + // 平台侧资源/对象/任务 id 按生成阶段命名(art-spec / game-background / + // art-spritesheet),与 manifest kind 是两套词汇;kind 收紧成枚举后不能让这些 + // fixture 标识跟着改名。 + let identity_slug = match kind { + GameCreationAppAssetKind::IconSpritesheet => "art-spritesheet", + GameCreationAppAssetKind::Scene => "game-background", + _ => kind.as_str(), + }; let mut png_bytes = Vec::new(); let image = image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel( 1, @@ -7138,9 +7152,9 @@ mod tests { GameCreationAppAssetSource { kind: GameCreationAppAssetSourceKind::Canvas, canvas_project_id: Some("taonier-project".to_string()), - resource_id: Some(format!("taonier-resource-{kind}")), - asset_object_id: Some(format!("taonier-object-{kind}")), - task_id: Some(format!("taonier-task-{kind}")), + resource_id: Some(format!("taonier-resource-{identity_slug}")), + asset_object_id: Some(format!("taonier-object-{identity_slug}")), + task_id: Some(format!("taonier-task-{identity_slug}")), prompt: None, model: Some("gpt-image-2".to_string()), generation_route: Some(generation_route.to_string()), @@ -7156,7 +7170,7 @@ mod tests { register_direct_taonier_art_asset_fixture( root, DIRECT_CODEX_SPRITESHEET_ASSET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, ); let main_bytes = std::fs::read(root.join(DIRECT_CODEX_SPRITESHEET_ASSET_PATH)) .expect("spritesheet bytes"); @@ -7233,7 +7247,7 @@ mod tests { register_local_asset_at( root, path, - "art-spritesheet-slice", + GameCreationAppAssetKind::Icon, "image/png", "platform-art", GameCreationAppAssetSource { @@ -7345,7 +7359,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root, DIRECT_CODEX_SPRITESHEET_ASSET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, &spritesheet_resource_id, vec![art_spec_resource_id.to_string()], &spritesheet_bytes, @@ -7432,19 +7446,20 @@ mod tests { fn replace_direct_taonier_art_asset_fixture( root: &Path, local_path: &str, - kind: &str, + kind: GameCreationAppAssetKind, resource_id: &str, reference_resource_ids: Vec, bytes: &[u8], ) { - let (generation_route, generation_kind) = if kind == "art-spritesheet" { - ( - "/api/external/v1/editor/icon-spritesheets/generations", - "icon-spritesheet", - ) - } else { - ("/api/external/v1/editor/images/generations", "spec") - }; + let (generation_route, generation_kind) = + if kind == GameCreationAppAssetKind::IconSpritesheet { + ( + "/api/external/v1/editor/icon-spritesheets/generations", + "icon-spritesheet", + ) + } else { + ("/api/external/v1/editor/images/generations", "spec") + }; std::fs::write(root.join(local_path), bytes).expect("replace fixture bytes"); register_local_asset_at( root, @@ -7473,12 +7488,12 @@ mod tests { register_direct_taonier_art_asset_fixture( root, DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, ); register_direct_taonier_art_asset_fixture( root, DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, ); } @@ -7930,7 +7945,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "replacement-spec", Vec::new(), &tiny_replacement_png(32, 64, 192), @@ -7941,7 +7956,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "replacement-background", vec!["replacement-spec".to_string()], &tiny_replacement_png(16, 128, 224), @@ -8029,7 +8044,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "durable-replacement-spec", Vec::new(), &tiny_replacement_png(32, 64, 192), @@ -8042,7 +8057,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "durable-replacement-background", vec!["durable-replacement-spec".to_string()], &tiny_replacement_png(16, 128, 224), @@ -8127,7 +8142,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "uncertain-committed-spec", Vec::new(), &replacement, @@ -8187,7 +8202,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_SPRITESHEET_ASSET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "uncertain-committed-spritesheet", vec!["taonier-resource-icon-spec".to_string()], &replacement, @@ -8243,7 +8258,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "late-committed-spec", Vec::new(), &tiny_replacement_png(32, 64, 192), @@ -8297,7 +8312,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "replacement-spec-before-background", Vec::new(), &tiny_replacement_png(32, 64, 192), @@ -8316,7 +8331,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "late-committed-background", vec!["replacement-spec-before-background".to_string()], &tiny_replacement_png(16, 128, 224), @@ -8361,7 +8376,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_ART_SPEC_ASSET_PATH, - "icon-spec", + GameCreationAppAssetKind::IconSpec, "replacement-spec", Vec::new(), &replacement_spec, @@ -8372,7 +8387,7 @@ mod tests { replace_direct_taonier_art_asset_fixture( root.path(), DIRECT_CODEX_BACKGROUND_ASSET_PATH, - "game-background", + GameCreationAppAssetKind::Scene, "replacement-background", vec!["replacement-spec".to_string()], &replacement_background, 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 e05fe4e28..8a3ded3c6 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 @@ -1210,7 +1210,8 @@ fn bridge_list_registered_assets(root: &Path, arguments: &Value) -> Value { arguments, "kind", DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS, - )?; + )? + .map(|kind| GameCreationAppAssetKind::parse_with_context(&kind, "asset.list.kind")); let asset_id = bridge_optional_bounded_string( arguments, "assetId", @@ -1230,7 +1231,7 @@ fn bridge_list_registered_assets(root: &Path, arguments: &Value) -> Value { let mut assets = manifest .assets .iter() - .filter(|asset| kind.as_ref().is_none_or(|kind| asset.kind == *kind)) + .filter(|asset| kind.is_none_or(|kind| asset.kind == kind)) .filter(|asset| { asset_id .as_ref() 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 256076602..d13898551 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 @@ -463,6 +463,27 @@ pub(crate) fn normalize_platform_art_asset_generation_kind(kind: &str) -> Option }) } +/// 平台美术生成请求的 `assetKind` → manifest 资源 kind。 +/// +/// `PlatformArtAssetGenerationOptions::asset_kind` 是**平台请求词汇**(`ui-prototype` / +/// `art-spritesheet` / `game-background` / `game-art` …),跟 manifest 的正式 +/// `GameCreationAppAssetKind` 不是同一套词表。生成结果登记进 manifest 之前必须在这里显式 +/// 转换,否则 manifest 会落进非 canonical 值,读侧只能靠 shared-contracts 的别名表兜底。 +/// +/// 词表外的值不猜:返回 `Unknown`,由调用方按既有未知输入口径处理。 +pub(crate) fn platform_art_asset_manifest_kind(asset_kind: &str) -> GameCreationAppAssetKind { + match asset_kind.trim() { + "image" | "game-art" => GameCreationAppAssetKind::Image, + "character" | "character-art" => GameCreationAppAssetKind::Character, + "spec" | "icon-spec" => GameCreationAppAssetKind::IconSpec, + "ui-prototype" | "ui-design" => GameCreationAppAssetKind::UiDesign, + "art-spritesheet" | "icon-spritesheet" => GameCreationAppAssetKind::IconSpritesheet, + "publication-material" => GameCreationAppAssetKind::PublicationMaterial, + "game-background" | "scene" => GameCreationAppAssetKind::Scene, + _ => GameCreationAppAssetKind::Unknown, + } +} + pub(in crate::agent) fn recover_persisted_visual_generation_options( root: &Path, pending: &AgentRuntimePendingToolAction, @@ -1625,7 +1646,7 @@ async fn canonical_art_spec_reference_at( .iter() .find(|asset| { asset.local_path == AGENT_RUNTIME_ART_SPEC_PATH - && asset.kind == "icon-spec" + && asset.kind == GameCreationAppAssetKind::IconSpec && asset.media_type.starts_with("image/") && asset.source.kind == GameCreationAppAssetSourceKind::Canvas }) @@ -6820,7 +6841,9 @@ fn register_platform_art_slice_manifest_entries_at( local_path: registration.local_path.clone(), image_sequence_frames: None, image_sequence_duration_ms: None, - category: game_creation_app_asset_category_for_kind("art-spritesheet-slice"), + category: game_creation_app_asset_category_for_kind( + GameCreationAppAssetKind::Icon.as_str(), + ), tags: Vec::new(), source, }); @@ -7555,7 +7578,7 @@ fn commit_prepared_platform_art_asset_with_before_replace_hook( let registered = match register_local_asset_entry( root, &local_path, - &options.asset_kind, + platform_art_asset_manifest_kind(&options.asset_kind), &download.media_type, "platform-art", GameCreationAppAssetSource { @@ -10499,7 +10522,7 @@ mod canvas_generation_tests { register_local_asset_at( root, "assets/art-spec.png", - "icon-spec", + GameCreationAppAssetKind::IconSpec, "image/png", "changed-reference-test", GameCreationAppAssetSource { @@ -12185,7 +12208,7 @@ mod canvas_generation_tests { register_local_asset_entry( root, AGENT_RUNTIME_ART_SPRITESHEET_PATH, - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "image/png", "platform-art", GameCreationAppAssetSource { @@ -12251,7 +12274,7 @@ mod canvas_generation_tests { register_local_asset_entry( root, "assets/art-spritesheet-slices/player.png", - "art-spritesheet-slice", + GameCreationAppAssetKind::Icon, "image/png", "platform-art", GameCreationAppAssetSource { @@ -13807,7 +13830,10 @@ mod canvas_generation_tests { let manifest = read_manifest_for_project(root).expect("read replacement manifest"); assert_eq!(manifest.assets.len(), 1); assert_eq!(manifest.assets[0].local_path, "assets/art-spritesheet.png"); - assert_eq!(manifest.assets[0].kind, "art-spritesheet"); + assert_eq!( + manifest.assets[0].kind, + GameCreationAppAssetKind::IconSpritesheet + ); assert_eq!( fs::read_dir(root.join("assets")) .expect("read assets directory") diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs index 071298d77..a2f24c3d4 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs @@ -2061,17 +2061,23 @@ mod tests { .expect("resume first manifest task fixture"); for (local_path, kind) in [ - ("assets/art-spec.png", "icon-spec"), - ("assets/ui-prototype.png", "ui-prototype"), - ("assets/art-spritesheet.png", "art-spritesheet"), + ("assets/art-spec.png", GameCreationAppAssetKind::IconSpec), + ( + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + ), + ( + "assets/art-spritesheet.png", + GameCreationAppAssetKind::IconSpritesheet, + ), ] { let (generation_route, generation_kind, reference_resource_ids) = match kind { - "icon-spec" => ( + GameCreationAppAssetKind::IconSpec => ( "/api/external/v1/editor/images/generations", "spec", Vec::new(), ), - "ui-prototype" => ( + GameCreationAppAssetKind::UiDesign => ( "/api/external/v1/editor/images/generations", "ui-design", vec!["resource-icon-spec".to_string()], @@ -2082,7 +2088,7 @@ mod tests { vec!["resource-icon-spec".to_string()], ), }; - let bytes = if kind == "art-spritesheet" { + let bytes = if kind == GameCreationAppAssetKind::IconSpritesheet { use image::ImageEncoder; let mut bytes = Vec::new(); image::codecs::png::PngEncoder::new(&mut bytes) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs index fa5bbdc7f..72cdcb5b6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs @@ -1216,11 +1216,19 @@ pub(in crate::agent) fn visual_asset_completion_blocker_at_locked( return None; } let (expected_path, expected_kind, label) = match agent_id { - "art-director" => (AGENT_RUNTIME_ART_SPEC_PATH, "icon-spec", "统一视觉规范图"), - "design-foundation" => ("assets/ui-prototype.png", "ui-prototype", "策划界面原型图"), + "art-director" => ( + AGENT_RUNTIME_ART_SPEC_PATH, + GameCreationAppAssetKind::IconSpec, + "统一视觉规范图", + ), + "design-foundation" => ( + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + "策划界面原型图", + ), "art-asset-plan" => ( "assets/art-spritesheet.png", - "art-spritesheet", + GameCreationAppAssetKind::IconSpritesheet, "首版美术素材图", ), _ => return None, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/recovery_scan.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/recovery_scan.rs index ed93b5560..d1c01b738 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/recovery_scan.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/recovery_scan.rs @@ -1561,10 +1561,14 @@ mod orphaned_external_generation_recovery_tests { fs::create_dir_all(workflow_path.parent().expect("workflow parent")) .expect("workflow directory"); let rollback_assets = [ - ("assets/art-spec.png", "icon-spec", b"old-spec".as_slice()), + ( + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + b"old-spec".as_slice(), + ), ( "assets/direct-game-background.png", - "game-background", + GameCreationAppAssetKind::Scene, b"old-background".as_slice(), ), ] diff --git a/apps/ai-game-creator-shell/src-tauri/src/assets.rs b/apps/ai-game-creator-shell/src-tauri/src/assets.rs index 38e10f511..cde968976 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/assets.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/assets.rs @@ -563,7 +563,7 @@ pub(crate) fn upload_local_asset_at( register_local_asset_entry( root, &relative_path, - uploaded_asset_kind(file_name, media_type).as_str(), + uploaded_asset_kind(file_name, media_type), media_type, "upload", GameCreationAppAssetSource { @@ -584,7 +584,7 @@ pub(crate) fn upload_local_asset_at( pub(crate) fn register_local_asset_at( root: &Path, local_path: &str, - kind: &str, + kind: GameCreationAppAssetKind, media_type: &str, source_kind: &str, source: GameCreationAppAssetSource, @@ -647,7 +647,7 @@ pub(crate) fn register_design_artifacts_at(root: &Path) -> Result register_local_asset_at( root, &relative, - "document", + GameCreationAppAssetKind::Document, media_type, "document", GameCreationAppAssetSource { @@ -686,6 +686,7 @@ pub(crate) fn import_canvas_asset_at( if resource_id.is_none() && asset_object_id.is_none() { return Err("画板资源 ID 和资产对象 ID 至少需要一个".to_string()); } + let kind = GameCreationAppAssetKind::parse_with_context(kind.trim(), "canvas.asset_import"); register_local_asset_at( root, @@ -918,7 +919,12 @@ pub(crate) async fn sync_canvas_project_assets_at( &local_path, json_string_field(resource, "assetKind") .as_deref() - .unwrap_or("canvas-resource"), + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(|value| { + GameCreationAppAssetKind::parse_with_context(value, "canvas.asset_sync") + }) + .unwrap_or(GameCreationAppAssetKind::Image), &download.media_type, "canvas", GameCreationAppAssetSource { @@ -1772,33 +1778,28 @@ pub(crate) fn normalized_zip_entry_name(name: &str) -> Result { /// 画板导出图层推断出的资源 kind。 /// -/// 返回值必须是 **canonical kind**:这个值会被 `register_local_asset_entry` 原样写入 -/// manifest,并据以派生落盘 `category`。历史上这里写过非 canonical 的 -/// `ui` / `animation` / `asset`,只能靠别名表兜底,等于同时维护两套词汇;别名表只用于 -/// 兼容存量数据,不作为新写入值的来源。 +/// 直接返回正式枚举成员:这个值会被 `register_local_asset_entry` 原样写入 manifest, +/// 并据以派生落盘 `category`。分支字面量写成非 canonical 值在这里已经无法表达, +/// 不需要别名表兜底。 pub(crate) fn infer_canvas_export_asset_kind( layer: &CanvasExportLayerMetadata, file: &str, -) -> &'static str { +) -> GameCreationAppAssetKind { let layer_type = layer.visible.layer_type.as_str(); - let inferred = if file.starts_with("sequences/") - || contains_any(layer_type, &["序列", "动画", "动作"]) - { - "character-animation" + if file.starts_with("sequences/") || contains_any(layer_type, &["序列", "动画", "动作"]) { + GameCreationAppAssetKind::CharacterAnimation } else if file.starts_with("media/") || contains_any(layer_type, &["音频", "音乐", "音效"]) { - "audio" + GameCreationAppAssetKind::Audio } else if contains_any(layer_type, &["角色"]) { - "character" + GameCreationAppAssetKind::Character } else if contains_any(layer_type, &["场景", "背景"]) { - "scene" + GameCreationAppAssetKind::Scene } else if contains_any(layer_type, &["UI", "界面", "图标"]) { - "ui-design" + GameCreationAppAssetKind::UiDesign } else { - "image" - }; - // 防御性归一:分支字面量写错时由覆盖测试暴露,这里再兜一层。 - shared_contracts::game_creation_app::canonical_game_creation_app_asset_kind(inferred) + GameCreationAppAssetKind::Image + } } pub(crate) fn infer_canvas_export_media_type(file: &str) -> &'static str { @@ -1871,7 +1872,7 @@ pub(crate) fn build_canvas_project_url( pub(crate) fn register_local_asset_entry( root: &Path, local_path: &str, - kind: &str, + kind: GameCreationAppAssetKind, media_type: &str, id_prefix: &str, source: GameCreationAppAssetSource, @@ -1879,10 +1880,6 @@ pub(crate) fn register_local_asset_entry( let normalized_path = normalize_relative_path(local_path)?; let absolute_path = resolve_local_project_path(root, &normalized_path)?; let manifest_path = root.join(".agent/manifest.json"); - let kind = GameCreationAppAssetKind::parse_with_context( - if kind.is_empty() { "unknown" } else { kind }, - "register_local_asset_entry", - ); let media_type = if media_type.is_empty() { "application/octet-stream" } else { @@ -2163,13 +2160,21 @@ mod tests { } // 每个分支的代表输入 → 期望的 canonical kind。 - let cases: [(&str, &str, &str); 6] = [ - ("序列", "sequences/01.png", "character-animation"), - ("动画", "layer.png", "character-animation"), - ("音频", "layer.png", "audio"), - ("角色", "layer.png", "character"), - ("场景", "layer.png", "scene"), - ("UI", "layer.png", "ui-design"), + let cases: [(&str, &str, GameCreationAppAssetKind); 6] = [ + ( + "序列", + "sequences/01.png", + GameCreationAppAssetKind::CharacterAnimation, + ), + ( + "动画", + "layer.png", + GameCreationAppAssetKind::CharacterAnimation, + ), + ("音频", "layer.png", GameCreationAppAssetKind::Audio), + ("角色", "layer.png", GameCreationAppAssetKind::Character), + ("场景", "layer.png", GameCreationAppAssetKind::Scene), + ("UI", "layer.png", GameCreationAppAssetKind::UiDesign), // 无匹配时落到 image。 ]; for (layer_type, file, expected) in cases { @@ -2179,20 +2184,20 @@ mod tests { // media/ 前缀同样走音频分支。 assert_eq!( infer_canvas_export_asset_kind(&layer("图层"), "media/bgm.mp3"), - "audio" + GameCreationAppAssetKind::Audio ); // 兜底分支是 image 而不是旧的 "asset"。 assert_eq!( infer_canvas_export_asset_kind(&layer("图层"), "layer.png"), - "image" + GameCreationAppAssetKind::Image ); - // 所有分支的返回值都必须在 canonical 目录里,且不能是旧的非 canonical 写法。 + // 所有分支的返回值都必须是正式枚举成员,且不能是旧的非 canonical 写法。 for layer_type in ["序列", "音频", "角色", "场景", "UI", "图层", "其他"] { let kind = infer_canvas_export_asset_kind(&layer(layer_type), "layer.png"); assert!( shared_contracts::game_creation_app::GAME_CREATION_APP_CANONICAL_ASSET_KINDS - .contains(&kind), + .contains(&kind.as_str()), "非 canonical kind: {kind}(layer_type={layer_type})" ); for legacy in ["ui", "animation", "asset"] { diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index fb2e64306..b200bc574 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -2111,7 +2111,7 @@ pub(crate) fn register_local_asset( register_local_asset_at( root, local_path.trim(), - kind.trim(), + GameCreationAppAssetKind::parse_with_context(kind.trim(), "asset.register"), media_type.trim(), source_kind.trim(), GameCreationAppAssetSource { @@ -2181,7 +2181,7 @@ pub(crate) fn create_ui_design_resource( let asset = match register_local_asset_at( root, &relative_path, - crate::ui_editor::persistence::UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, crate::ui_editor::persistence::UI_DESIGN_DOC_MEDIA_TYPE, "generated", GameCreationAppAssetSource { @@ -2672,7 +2672,7 @@ pub(crate) fn import_ui_editor_local_fonts( let registered = register_local_asset_entry( root, &relative_path, - "font", + GameCreationAppAssetKind::Font, validated.metadata.format.media_type(), "font", GameCreationAppAssetSource { @@ -2784,7 +2784,7 @@ mod ui_editor_font_tests { let registered = register_local_asset_entry( root, relative_path, - "font", + GameCreationAppAssetKind::Font, "font/ttf", "font", GameCreationAppAssetSource { @@ -3202,24 +3202,34 @@ mod agent_asset_import_tests { /// 本用例必须变红(`character` / `scene` / 空值 / 未知值四组断言都会失败)。 #[test] fn imported_platform_asset_kind_prefers_payload_type_over_ui_default() { - assert_eq!(imported_platform_asset_kind(Some("character")), "character"); - assert_eq!(imported_platform_asset_kind(Some("scene")), "scene"); + use GameCreationAppAssetKind as Kind; + assert_eq!( + imported_platform_asset_kind(Some("character")), + Kind::Character + ); + assert_eq!(imported_platform_asset_kind(Some("scene")), Kind::Scene); assert_eq!( imported_platform_asset_kind(Some("character-animation")), - "character-animation" + Kind::CharacterAnimation + ); + assert_eq!( + imported_platform_asset_kind(Some("icon-spec")), + Kind::IconSpec ); - assert_eq!(imported_platform_asset_kind(Some("icon-spec")), "icon-spec"); // 平台确实说它是 UI 设计稿时,才允许落 `ui-design`(→ 「UI 交互」); // 大写 `UI` 与旧值 `ui` 都要归一到同一口径,但**不允许由我们替它默认**。 - assert_eq!(imported_platform_asset_kind(Some("UI")), "ui-design"); - assert_eq!(imported_platform_asset_kind(Some("ui")), "ui-design"); + assert_eq!(imported_platform_asset_kind(Some("UI")), Kind::UiDesign); + assert_eq!(imported_platform_asset_kind(Some("ui")), Kind::UiDesign); // 缺失 / 空白 / 未知值一律落 canonical `image` 兜底(→ 「待归类」)。 - assert_eq!(imported_platform_asset_kind(None), "image"); - assert_eq!(imported_platform_asset_kind(Some(" ")), "image"); - assert_eq!(imported_platform_asset_kind(Some("mystery")), "image"); + assert_eq!(imported_platform_asset_kind(None), Kind::Image); + assert_eq!(imported_platform_asset_kind(Some(" ")), Kind::Image); + assert_eq!(imported_platform_asset_kind(Some("mystery")), Kind::Image); // `kind` 是外部输入,原型链键必须走 `image` 兜底,不能取到原型上的值。 - assert_eq!(imported_platform_asset_kind(Some("__proto__")), "image"); - assert_eq!(imported_platform_asset_kind(Some("constructor")), "image"); + assert_eq!(imported_platform_asset_kind(Some("__proto__")), Kind::Image); + assert_eq!( + imported_platform_asset_kind(Some("constructor")), + Kind::Image + ); } /// 反查门禁:两处平台素材导入必须继续用 `imported_platform_asset_kind` 解析 `kind`, @@ -3748,7 +3758,7 @@ pub(crate) async fn list_editor_assets_for_agent_at( #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct AgentLocalProjectFileType { category: &'static str, - asset_kind: &'static str, + asset_kind: GameCreationAppAssetKind, media_type: &'static str, max_file_size: u64, } @@ -3803,14 +3813,14 @@ fn agent_local_project_file_type( // 错值放大成显式的 `ui-interaction`。 media_type.map(|media_type| AgentLocalProjectFileType { category: "image", - asset_kind: "image", + asset_kind: GameCreationAppAssetKind::Image, media_type, max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }) } "ttf" | "otf" | "woff" | "woff2" => Some(AgentLocalProjectFileType { category: "font", - asset_kind: "font", + asset_kind: GameCreationAppAssetKind::Font, media_type: match extension.as_str() { "ttf" => "font/ttf", "otf" => "font/otf", @@ -3821,68 +3831,68 @@ fn agent_local_project_file_type( }), "mp3" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/mpeg", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "wav" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/wav", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "ogg" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/ogg", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "flac" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/flac", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "m4a" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/mp4", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "aac" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/aac", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "opus" => Some(AgentLocalProjectFileType { category: "audio", - asset_kind: "audio", + asset_kind: GameCreationAppAssetKind::Audio, media_type: "audio/opus", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "mp4" => Some(AgentLocalProjectFileType { category: "video", - asset_kind: "video", + asset_kind: GameCreationAppAssetKind::Video, media_type: "video/mp4", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "webm" => Some(AgentLocalProjectFileType { category: "video", - asset_kind: "video", + asset_kind: GameCreationAppAssetKind::Video, media_type: "video/webm", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "mov" => Some(AgentLocalProjectFileType { category: "video", - asset_kind: "video", + asset_kind: GameCreationAppAssetKind::Video, media_type: "video/quicktime", max_file_size: UI_EDITOR_IMAGE_MAX_FILE_SIZE, }), "md" | "markdown" | "mdx" | "txt" | "json" | "yaml" | "yml" | "toml" | "csv" | "ini" | "conf" | "xml" => Some(AgentLocalProjectFileType { category: "document", - asset_kind: "document", + asset_kind: GameCreationAppAssetKind::Document, media_type: if extension == "json" { "application/json" } else if matches!(extension.as_str(), "yaml" | "yml") { @@ -3899,7 +3909,7 @@ fn agent_local_project_file_type( | "swift" | "php" | "rb" | "lua" | "sh" | "bash" | "zsh" | "sql" | "graphql" | "gql" | "vue" | "svelte" => Some(AgentLocalProjectFileType { category: "code", - asset_kind: "code", + asset_kind: GameCreationAppAssetKind::Code, media_type: if matches!(extension.as_str(), "html" | "htm") { "text/html" } else if matches!(extension.as_str(), "css" | "scss" | "less") { @@ -4017,7 +4027,7 @@ pub(crate) fn import_local_project_assets_for_agent( } inputs.push(( local_path, - file_type.asset_kind.to_string(), + file_type.asset_kind, file_type.media_type.to_string(), bytes, )); @@ -4075,7 +4085,7 @@ pub(crate) fn import_local_project_assets_for_agent( let registered = register_local_asset_entry( root, &local_path, - &asset_kind, + asset_kind, &media_type, "local", GameCreationAppAssetSource { @@ -4094,7 +4104,7 @@ pub(crate) fn import_local_project_assets_for_agent( imported.push(ImportedAsset { id: registered.id, local_path: registered.local_path, - asset_kind: Some(asset_kind), + asset_kind: Some(asset_kind.to_string()), }); advance_agent_runtime_project_revision_locked(root).map_err(|error| { format!("reconciliation-required: 本地资源已登记,但项目 revision 未能推进:{error}") @@ -4111,15 +4121,14 @@ pub(crate) fn import_local_project_assets_for_agent( /// 唯一一条"这东西是什么"的事实,并把角色立绘、怪物、道具、场景图一并钉进「UI 交互」栏 /// (`ui → ui-design → ui-interaction`)。落盘 `category` 一旦是非 `unclassified` 值, /// 读时自愈永远救不回来,所以宁可写 `image`(待归类)也不能写假 `ui`。 -fn imported_platform_asset_kind(platform_kind: Option<&str>) -> String { +fn imported_platform_asset_kind(platform_kind: Option<&str>) -> GameCreationAppAssetKind { match platform_kind.map(str::trim).filter(|kind| !kind.is_empty()) { - // 平台侧词汇可能与 canonical 目录不完全一致(例如大写 `UI`),统一过 canonical 归一, - // 保证写入侧只产出 canonical `kind`。 - Some(kind) => { - shared_contracts::game_creation_app::canonical_game_creation_app_asset_kind(kind) - .to_string() - } - None => "image".to_string(), + // 平台是外部系统,它的 `assetKind` 词汇(含大写 `UI` 这类变体)由共享契约统一收口到 + // canonical 值;这里只把收口结果转成正式枚举,写入侧不再产出裸字符串。 + Some(kind) => GameCreationAppAssetKind::from_str_lossy( + shared_contracts::game_creation_app::canonical_game_creation_app_asset_kind(kind), + ), + None => GameCreationAppAssetKind::Image, } } @@ -4284,7 +4293,7 @@ pub(crate) async fn import_account_editor_assets_for_agent( let registered = register_local_asset_entry( root, &local_path, - &asset_kind, + asset_kind, &media_type, "canvas", GameCreationAppAssetSource { @@ -4402,7 +4411,7 @@ pub(crate) async fn import_ui_editor_remote_assets( let registered = register_local_asset_entry( root, &local_path, - &asset_kind, + asset_kind, &media_type, "canvas", GameCreationAppAssetSource { diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs b/apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs index a3fe7968f..7407563d5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs @@ -1069,9 +1069,15 @@ pub(crate) fn validate_manifest_required_visual_asset( task_id: &str, ) -> Result<(), String> { let (expected_path, expected_kind) = match task_id { - "art-director" => ("assets/art-spec.png", "icon-spec"), - "design-foundation" => ("assets/ui-prototype.png", "ui-prototype"), - "art-asset-plan" => ("assets/art-spritesheet.png", "art-spritesheet"), + "art-director" => ("assets/art-spec.png", GameCreationAppAssetKind::IconSpec), + "design-foundation" => ( + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + ), + "art-asset-plan" => ( + "assets/art-spritesheet.png", + GameCreationAppAssetKind::IconSpritesheet, + ), _ => return Ok(()), }; let asset = manifest @@ -1141,7 +1147,10 @@ pub(crate) fn validate_manifest_required_visual_asset( let art_spec = manifest .assets .iter() - .find(|asset| asset.local_path == "assets/art-spec.png" && asset.kind == "icon-spec") + .find(|asset| { + asset.local_path == "assets/art-spec.png" + && asset.kind == GameCreationAppAssetKind::IconSpec + }) .ok_or_else(|| "缺少当前统一视觉规范图".to_string())?; let art_spec_project_id = art_spec .source diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/manifest/classification_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/project/manifest/classification_tests.rs index d66a0d736..b65f7b86a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/manifest/classification_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/manifest/classification_tests.rs @@ -19,7 +19,7 @@ fn project_with_two_assets(test_name: &str) -> (PathBuf, String) { let first = register_local_asset_entry( &root, "assets/hero.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "asset", GameCreationAppAssetSource { @@ -39,7 +39,7 @@ fn project_with_two_assets(test_name: &str) -> (PathBuf, String) { register_local_asset_entry( &root, "assets/theme.mp3", - "background-music", + GameCreationAppAssetKind::BackgroundMusic, "audio/mpeg", "asset", GameCreationAppAssetSource { diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/manifest/recovery_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/project/manifest/recovery_tests.rs index 6b1c7f12e..20fa2f825 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/manifest/recovery_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/manifest/recovery_tests.rs @@ -96,7 +96,7 @@ fn manifest_task_and_asset_mutations_are_serialized_across_the_full_transaction( crate::assets::register_local_asset_entry( &second_root, "assets/concurrent.png", - "ui-prototype", + GameCreationAppAssetKind::UiDesign, "image/png", "test", GameCreationAppAssetSource { diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/asset_delete.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/asset_delete.rs index 90c1367c8..225bdc6a5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/asset_delete.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/asset_delete.rs @@ -35,7 +35,7 @@ fn register_asset_delete_fixture_asset(root: &Path, relative_path: &str, task_id register_local_asset_at( root, relative_path, - "character", + GameCreationAppAssetKind::Character, "image/png", "generated", generated_asset_source(task_id), diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/asset_rename.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/asset_rename.rs index 21764f49f..83068345d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/asset_rename.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/asset_rename.rs @@ -9,7 +9,7 @@ fn asset_rename_project_fixture() -> (PathBuf, String) { let asset = register_local_asset_at( &root, "assets/hero.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "canvas", asset_rename_source(), @@ -192,7 +192,7 @@ fn rename_local_project_asset_rejects_extension_changes() { let extensionless = register_local_asset_at( &root, "assets/notes", - "asset", + GameCreationAppAssetKind::Document, "text/plain", "canvas", asset_rename_source(), diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/delegation.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/delegation.rs index 7dd5c64a7..3c46205f0 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/delegation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/delegation.rs @@ -1401,7 +1401,11 @@ async fn canvas_replacement_rejects_parent_run_that_terminates_during_external_r ); init_local_game_project_at(&root, "project-canvas-repair-atomic", "月光厨房") .expect("project init"); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); bind_canvas_visual_asset_fixture_to_current_editor( &root, "assets/art-spec.png", diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs index f66a45540..d47697cb1 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs @@ -3596,28 +3596,32 @@ fn spawn_mock_external_canvas_generation_failure_server() -> String { base_url } -fn register_canvas_visual_asset_fixture(root: &Path, local_path: &str, kind: &str) { +fn register_canvas_visual_asset_fixture( + root: &Path, + local_path: &str, + kind: GameCreationAppAssetKind, +) { let absolute_path = root.join(local_path); fs::create_dir_all(absolute_path.parent().expect("visual asset parent")) .expect("create visual asset fixture directory"); - let bytes = if kind == "art-spritesheet" { + let bytes = if kind == GameCreationAppAssetKind::IconSpritesheet { transparent_test_png_bytes() } else { valid_test_png_bytes() }; fs::write(&absolute_path, bytes).expect("write visual asset fixture"); let (generation_route, generation_kind, reference_resource_ids) = match kind { - "icon-spec" => ( + GameCreationAppAssetKind::IconSpec => ( "/api/external/v1/editor/images/generations", "spec", Vec::new(), ), - "ui-prototype" => ( + GameCreationAppAssetKind::UiDesign => ( "/api/external/v1/editor/images/generations", "ui-design", vec!["resource-icon-spec".to_string()], ), - "art-spritesheet" => ( + GameCreationAppAssetKind::IconSpritesheet => ( "/api/external/v1/editor/icon-spritesheets/generations", "icon-spritesheet", vec!["resource-icon-spec".to_string()], diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs index 839132da7..1d181d438 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs @@ -369,9 +369,21 @@ fn canonical_visual_completion_requires_persisted_route_kind_and_current_spec_re let root = unique_project_path(); init_local_game_project_at(&root, "visual-provenance", "视觉来源门禁") .expect("init visual provenance project"); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); - register_canvas_visual_asset_fixture(&root, "assets/ui-prototype.png", "ui-prototype"); - register_canvas_visual_asset_fixture(&root, "assets/art-spritesheet.png", "art-spritesheet"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spritesheet.png", + GameCreationAppAssetKind::IconSpritesheet, + ); let mut manifest = read_manifest_for_project(&root).expect("read visual manifest"); for task_id in ["art-director", "design-foundation", "art-asset-plan"] { validate_manifest_required_visual_asset(&root, &manifest, task_id) @@ -704,7 +716,11 @@ async fn background_agent_runtime_can_generate_platform_art_asset() { &canvas_base_url, ); init_local_game_project_at(&root, "project-1", "月光厨房").expect("project init"); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); bind_canvas_visual_asset_fixture_to_current_editor( &root, "assets/art-spec.png", @@ -854,7 +870,7 @@ async fn background_agent_runtime_can_generate_platform_art_asset() { asset["source"]["referenceResourceIds"], serde_json::json!(["resource-icon-spec"]) ); - assert_eq!(asset["kind"], "art-spritesheet"); + assert_eq!(asset["kind"], "icon-spritesheet"); assert_eq!(asset["localPath"], "assets/art-spritesheet.png"); assert_eq!( fs::read(root.join(asset["localPath"].as_str().unwrap())).unwrap(), @@ -1063,7 +1079,11 @@ async fn canonical_art_spec_and_ui_requests_use_the_shared_reference_chain() { assert!(spec_request.contains(r#""assetKind":"icon-spec""#)); assert!(spec_request.contains(r#""referenceImageSrcs":[]"#)); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); let ui_config_dir = unique_project_path(); let ui_request = capture_generation_request( &root, @@ -1105,7 +1125,11 @@ async fn platform_art_external_request_does_not_hold_project_lock_or_overwrite_m ); init_local_game_project_at(&root, "project-canvas-request-lock", "月光厨房") .expect("project init"); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); bind_canvas_visual_asset_fixture_to_current_editor( &root, "assets/art-spec.png", @@ -1932,7 +1956,7 @@ fn register_local_asset_records_existing_asset_with_canvas_source() { let result = register_local_asset_at( &root, "assets/hero.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "canvas", GameCreationAppAssetSource { @@ -1975,7 +1999,7 @@ fn register_local_asset_records_existing_asset_with_canvas_source() { let updated = register_local_asset_at( &root, "assets/hero.png", - "ui", + GameCreationAppAssetKind::UiDesign, "image/png", "generated", GameCreationAppAssetSource { @@ -1997,7 +2021,7 @@ fn register_local_asset_records_existing_asset_with_canvas_source() { serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap()) .expect("manifest json"); assert_eq!(manifest["assets"].as_array().unwrap().len(), 1); - assert_eq!(manifest["assets"][0]["kind"], "ui"); + assert_eq!(manifest["assets"][0]["kind"], "ui-design"); // kind 变了必须重派生 category:旧分类 `character` 是非 unclassified 值,会被读侧 // 无条件信任、自愈也不会触发,资产就永远停在「角色与对象」。 assert_eq!(manifest["assets"][0]["category"], "ui-interaction"); @@ -2031,7 +2055,7 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() { let registered = register_local_asset_at( &root, "ui/UI 设计 1.json", - shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", source(), @@ -2053,7 +2077,7 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() { register_local_asset_at( &root, "ui/UI 设计 1.json", - shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", source(), @@ -2102,7 +2126,7 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() { register_local_asset_at( &root, "ui/UI 设计 1.json", - shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", source(), @@ -2111,7 +2135,7 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() { register_local_asset_at( &root, "assets/ui-font.ttf", - "font", + GameCreationAppAssetKind::Font, "font/ttf", "font", source(), @@ -2136,12 +2160,12 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() { assert_eq!( categories, vec![ + ("font".to_string(), "document".to_string()), ( shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND .to_string(), "ui-interaction".to_string(), ), - ("font".to_string(), "document".to_string()), ] ); @@ -2167,7 +2191,7 @@ fn register_local_asset_rejects_missing_or_unsafe_path() { assert!(register_local_asset_at( &root, "../outside.png", - "asset", + GameCreationAppAssetKind::Image, "image/png", "generated", source.clone() @@ -2176,7 +2200,7 @@ fn register_local_asset_rejects_missing_or_unsafe_path() { assert!(register_local_asset_at( &root, "assets/missing.png", - "asset", + GameCreationAppAssetKind::Image, "image/png", "generated", source @@ -2525,7 +2549,8 @@ async fn generate_platform_art_asset_downloads_and_registers_external_image() { serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap()) .expect("manifest json"); let asset = &manifest["assets"][0]; - assert_eq!(asset["kind"], "game-art"); + // 平台请求词汇 `game-art` 在写入边界收口成正式成员 `image`。 + assert_eq!(asset["kind"], "image"); assert_eq!(asset["mediaType"], "image/png"); assert_eq!(asset["source"]["kind"], "canvas"); assert_eq!(asset["source"]["canvasProjectId"], "canvas-project-1"); @@ -2716,7 +2741,11 @@ async fn generate_local_project_asset_command_generates_art_spritesheet_from_the "{missing_reference}" ); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); bind_canvas_visual_asset_fixture_to_current_editor( &root, "assets/art-spec.png", @@ -2745,7 +2774,8 @@ async fn generate_local_project_asset_command_generates_art_spritesheet_from_the .iter() .find(|entry| entry["id"].as_str() == Some(asset.id.as_str())) .expect("registered toolbar spritesheet asset"); - assert_eq!(entry["kind"], "art-spritesheet"); + // 平台请求词汇 `art-spritesheet` 登记时收口成正式成员 `icon-spritesheet`。 + assert_eq!(entry["kind"], "icon-spritesheet"); assert_eq!( entry["source"]["generationRoute"], "/api/external/v1/editor/icon-spritesheets/generations" diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs index 8709bc5b0..7636655ef 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project_tools.rs @@ -6105,7 +6105,7 @@ fn local_project_image_preview_obeys_auto_file_read_policy() { register_local_asset_at( &root, "assets/preview.png", - "ui-prototype", + GameCreationAppAssetKind::UiDesign, "image/png", "canvas", GameCreationAppAssetSource { @@ -6219,7 +6219,7 @@ fn local_project_resource_previews_require_registered_safe_resources() { register_local_asset_at( &root, "game/design.md", - "document", + GameCreationAppAssetKind::Document, "text/markdown", "generated", source(), @@ -6228,7 +6228,7 @@ fn local_project_resource_previews_require_registered_safe_resources() { register_local_asset_at( &root, "assets/icon.svg", - "icon", + GameCreationAppAssetKind::Icon, "image/svg+xml", "generated", source(), @@ -6237,7 +6237,7 @@ fn local_project_resource_previews_require_registered_safe_resources() { register_local_asset_at( &root, "assets/bgm.mp3", - "bgm", + GameCreationAppAssetKind::BackgroundMusic, "audio/mpeg", "generated", source(), diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs index 762b67983..7d66da553 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/action_execution.rs @@ -752,8 +752,16 @@ async fn background_agent_runtime_can_schedule_ready_manifest_tasks() { async fn background_agent_runtime_can_schedule_ready_tasks_from_tool() { let root = unique_project_path(); init_local_game_project_at(&root, "project-1", "月光厨房").expect("project init"); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); - register_canvas_visual_asset_fixture(&root, "assets/ui-prototype.png", "ui-prototype"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + ); update_manifest_task_status_at(&root, "art-director", GameCreationAppTaskStatus::Completed) .expect("complete art spec dependency fixture"); write_project_permission_policy_at( @@ -1805,7 +1813,11 @@ async fn background_agent_runtime_image_inspect_sends_two_images_without_persist async fn design_ui_image_inspect_fails_scene_and_persists_canonical_checks() { let root = unique_project_path(); init_local_game_project_at(&root, "project-1", "场景图拒绝测试").expect("project init"); - register_canvas_visual_asset_fixture(&root, AGENT_RUNTIME_UI_PROTOTYPE_PATH, "ui-prototype"); + register_canvas_visual_asset_fixture( + &root, + AGENT_RUNTIME_UI_PROTOTYPE_PATH, + GameCreationAppAssetKind::UiDesign, + ); let (sender, receiver) = mpsc::channel(); let base_url = spawn_mock_llm_server_responses_with_capture( vec![ui_prototype_assessment_fixture(false)], @@ -2157,9 +2169,21 @@ fn seed_refresh_preserves_completed_visual_tasks_when_registered_file_is_missing ); let root = unique_project_path(); init_local_game_project_at(&root, "project-1", "旧视觉任务升级测试").expect("project init"); - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); - register_canvas_visual_asset_fixture(&root, "assets/ui-prototype.png", "ui-prototype"); - register_canvas_visual_asset_fixture(&root, "assets/art-spritesheet.png", "art-spritesheet"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spritesheet.png", + GameCreationAppAssetKind::IconSpritesheet, + ); let mut manifest = read_manifest_for_project(&root).expect("manifest with visual assets"); for task_id in ["art-director", "design-foundation", "art-asset-plan"] { diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs index 3aa224d0d..f68cc1abc 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_game_build.rs @@ -468,9 +468,21 @@ fn autonomous_visual_gate_degrades_to_text_without_key_and_requires_images_with_ ); } - register_canvas_visual_asset_fixture(&root, "assets/art-spec.png", "icon-spec"); - register_canvas_visual_asset_fixture(&root, "assets/ui-prototype.png", "ui-prototype"); - register_canvas_visual_asset_fixture(&root, "assets/art-spritesheet.png", "art-spritesheet"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spec.png", + GameCreationAppAssetKind::IconSpec, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + ); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spritesheet.png", + GameCreationAppAssetKind::IconSpritesheet, + ); for task_id in ["art-director", "design-foundation", "art-asset-plan"] { update_manifest_task_status_at(&root, task_id, GameCreationAppTaskStatus::Completed) .expect("complete visual owner task with registered image"); @@ -3654,7 +3666,11 @@ async fn autonomous_static_art_asset_delivery_uses_runtime_owner_validation_with br#"{"assets":[{"path":"assets/art-spritesheet.png","kind":"art-spritesheet"}]}"#, ) .expect("write static art manifest after the parent completion baseline"); - register_canvas_visual_asset_fixture(&root, "assets/art-spritesheet.png", "art-spritesheet"); + register_canvas_visual_asset_fixture( + &root, + "assets/art-spritesheet.png", + GameCreationAppAssetKind::IconSpritesheet, + ); prepare_agent_runtime_project_mutation_locked( &root, "art-asset-plan", diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs index aa366c760..326eb1b5e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/support.rs @@ -98,11 +98,12 @@ pub(super) use crate::{ AgentRuntimePendingToolAction, AgentRuntimePlanUpdate, AgentRuntimePlanUpdateStep, AgentRuntimeProviderActionBatchPreparation, AgentRuntimeState, AgentRuntimeTaskLink, AgentRuntimeTaskRecord, AgentRuntimeToolAction, AgentRuntimeToolObservation, - AgentRuntimeToolPlan, AgentRuntimeToolPolicyBlock, GameCreationAppTaskStatus, - LocalConversationMessage, ProjectAgentPermissionPolicy, ProjectPermissionPolicy, - SupervisorCollaborationPolicy, AGENT_RUNTIME_ACTION_EXECUTION_MODE_AUTO, - AGENT_RUNTIME_ACTION_EXECUTION_MODE_CONFIRMATION, AGENT_RUNTIME_ACTION_FINGERPRINT_VERSION, - AGENT_RUNTIME_ACTION_RECEIPT_RECORD_TYPE, AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT, + AgentRuntimeToolPlan, AgentRuntimeToolPolicyBlock, GameCreationAppAssetKind, + GameCreationAppTaskStatus, LocalConversationMessage, ProjectAgentPermissionPolicy, + ProjectPermissionPolicy, SupervisorCollaborationPolicy, + AGENT_RUNTIME_ACTION_EXECUTION_MODE_AUTO, AGENT_RUNTIME_ACTION_EXECUTION_MODE_CONFIRMATION, + AGENT_RUNTIME_ACTION_FINGERPRINT_VERSION, AGENT_RUNTIME_ACTION_RECEIPT_RECORD_TYPE, + AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT, AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT, AGENT_RUNTIME_AUTONOMOUS_SCAFFOLD_MAX_OUTPUT_TOKENS, AGENT_RUNTIME_AUTONOMOUS_TRUNCATED_SCAFFOLD_MAX_OUTPUT_TOKENS, diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/version_resource_replacement.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/version_resource_replacement.rs index b3c0221e1..2a8d3e389 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/version_resource_replacement.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/version_resource_replacement.rs @@ -42,7 +42,7 @@ fn replacement_generated_asset_source(task_id: &str) -> GameCreationAppAssetSour fn register_replacement_fixture_asset( root: &Path, relative_path: &str, - kind: &str, + kind: GameCreationAppAssetKind, media_type: &str, task_id: &str, ) -> String { @@ -146,14 +146,14 @@ fn replacement_rewrites_the_version_binding_without_adding_a_version() { let source_asset = register_replacement_fixture_asset( &root, "assets/hero-a.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-a", ); let untouched_asset = register_replacement_fixture_asset( &root, "assets/scene.png", - "scene", + GameCreationAppAssetKind::Scene, "image/png", "art-scene", ); @@ -162,7 +162,7 @@ fn replacement_rewrites_the_version_binding_without_adding_a_version() { let replacement_asset = register_replacement_fixture_asset( &root, "assets/hero-b.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-b", ); @@ -280,14 +280,14 @@ fn replacement_drops_source_binding_when_replacement_is_already_bound() { let source_asset = register_replacement_fixture_asset( &root, "assets/legacy.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-legacy", ); let already_bound_replacement = register_replacement_fixture_asset( &root, "assets/final.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-final", ); @@ -352,21 +352,21 @@ fn replacement_drops_source_binding_when_replacement_is_already_bound() { fn replacement_blocks_category_and_subtype_mismatch_but_only_hints_size_spec() { struct BlockingCase { name: &'static str, - source_kind: &'static str, - target_kind: &'static str, + source_kind: GameCreationAppAssetKind, + target_kind: GameCreationAppAssetKind, expected_reason: &'static str, } let blocking_cases = [ BlockingCase { name: "分类不同", - source_kind: "character", - target_kind: "scene", + source_kind: GameCreationAppAssetKind::Character, + target_kind: GameCreationAppAssetKind::Scene, expected_reason: "分类不同", }, BlockingCase { name: "类型不同", - source_kind: "character", - target_kind: "character-animation", + source_kind: GameCreationAppAssetKind::Character, + target_kind: GameCreationAppAssetKind::CharacterAnimation, expected_reason: "类型不同", }, ]; @@ -420,7 +420,7 @@ fn replacement_blocks_category_and_subtype_mismatch_but_only_hints_size_spec() { let source_asset = register_replacement_fixture_asset( &root, "assets/hero.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-hero", ); @@ -428,7 +428,7 @@ fn replacement_blocks_category_and_subtype_mismatch_but_only_hints_size_spec() { let webp_asset = register_replacement_fixture_asset( &root, "assets/hero.webp", - "character", + GameCreationAppAssetKind::Character, "image/webp", "art-hero-webp", ); @@ -486,14 +486,14 @@ fn replacement_hints_known_frame_size_and_duration_differences() { let source_asset = register_replacement_fixture_asset( &root, "assets/seq-source.png", - "character-animation", + GameCreationAppAssetKind::CharacterAnimation, "image/png", "anim-source", ); let target_asset = register_replacement_fixture_asset( &root, "assets/seq-target.png", - "character-animation", + GameCreationAppAssetKind::CharacterAnimation, "image/png", "anim-target", ); @@ -551,14 +551,14 @@ fn replacement_heals_persisted_unclassified_category_like_the_shared_contract() let source_asset = register_replacement_fixture_asset( &root, "assets/ui-a.png", - "ui-design", + GameCreationAppAssetKind::UiDesign, "image/png", "ui-a", ); let healed_asset = register_replacement_fixture_asset( &root, "assets/ui-b.png", - "ui-design", + GameCreationAppAssetKind::UiDesign, "image/png", "ui-b", ); @@ -611,7 +611,7 @@ fn replacement_rejects_unresolvable_source_and_target() { let bound_asset = register_replacement_fixture_asset( &root, "assets/bound.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-bound", ); @@ -620,7 +620,7 @@ fn replacement_rejects_unresolvable_source_and_target() { let unbound_asset = register_replacement_fixture_asset( &root, "assets/late.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-late", ); @@ -672,14 +672,14 @@ fn replacement_enforces_revision_and_identity_cas() { let source_asset = register_replacement_fixture_asset( &root, "assets/cas-source.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-cas-source", ); let target_asset = register_replacement_fixture_asset( &root, "assets/cas-target.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-cas-target", ); @@ -722,35 +722,35 @@ fn replacement_candidates_report_authoritative_compatibility() { let source_asset = register_replacement_fixture_asset( &root, "assets/pick-source.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-pick-source", ); let compatible_asset = register_replacement_fixture_asset( &root, "assets/pick-compatible.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-pick-compatible", ); let category_mismatch = register_replacement_fixture_asset( &root, "assets/pick-scene.png", - "scene", + GameCreationAppAssetKind::Scene, "image/png", "art-pick-scene", ); let subtype_mismatch = register_replacement_fixture_asset( &root, "assets/pick-animation.png", - "character-animation", + GameCreationAppAssetKind::CharacterAnimation, "image/png", "art-pick-animation", ); let format_mismatch = register_replacement_fixture_asset( &root, "assets/pick-webp.webp", - "character", + GameCreationAppAssetKind::Character, "image/webp", "art-pick-webp", ); @@ -814,7 +814,7 @@ fn replacement_candidates_report_authoritative_compatibility() { let late_asset = register_replacement_fixture_asset( &root, "assets/pick-late.png", - "character", + GameCreationAppAssetKind::Character, "image/png", "art-pick-late", ); diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs index f9f1a018a..15276bb22 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs @@ -819,7 +819,7 @@ mod tests { let asset = register_local_asset_at( directory.path(), relative_path, - UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, UI_DESIGN_DOC_MEDIA_TYPE, "test", GameCreationAppAssetSource { diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs index 658ce3ddf..bde9b6b34 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs @@ -122,7 +122,7 @@ pub(crate) fn ensure_ui_design_resource_for_prototype( let asset = match register_local_asset_at( root, &relative_path, - UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, UI_DESIGN_DOC_MEDIA_TYPE, "generated", GameCreationAppAssetSource { @@ -248,7 +248,7 @@ mod tests { register_local_asset_entry( directory.path(), "assets/ui-prototype.png", - "ui-design", + GameCreationAppAssetKind::UiDesign, "image/png", "canvas", GameCreationAppAssetSource { diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs index 05064ad65..076b2f5ed 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs @@ -750,7 +750,7 @@ fn ensure_page_ui_resource( let registered = register_local_asset_at( root, &relative_path, - UI_DESIGN_DOC_ASSET_KIND, + GameCreationAppAssetKind::UiDesignDoc, UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", GameCreationAppAssetSource { @@ -1464,7 +1464,7 @@ mod tests { fn fixture_asset( root: &Path, relative_path: &str, - kind: &str, + kind: GameCreationAppAssetKind, resource_id: &str, ) -> GameCreationAppAssetManifestEntry { register_local_asset_at( @@ -1607,8 +1607,18 @@ mod tests { let design_path = root.join("assets/home.png"); fixture_png(&source_path); fixture_png(&design_path); - let source = fixture_asset(root, "assets/ui-prototype.png", "ui-design", "source-ui"); - let design = fixture_asset(root, "assets/home.png", "ui-design", "design-home"); + let source = fixture_asset( + root, + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + "source-ui", + ); + let design = fixture_asset( + root, + "assets/home.png", + GameCreationAppAssetKind::UiDesign, + "design-home", + ); let page = |operation| UiWorkflowRunInput { operation, source_asset_id: source.id.clone(), @@ -1658,9 +1668,24 @@ mod tests { fixture_png(&root.join("assets/ui-prototype.png")); fixture_png(&root.join("assets/home.png")); fixture_png(&root.join("assets/start-button.png")); - let source = fixture_asset(root, "assets/ui-prototype.png", "ui-design", "source-ui"); - let design = fixture_asset(root, "assets/home.png", "ui-design", "design-home"); - let sprite = fixture_asset(root, "assets/start-button.png", "ui-icon", "start-button"); + let source = fixture_asset( + root, + "assets/ui-prototype.png", + GameCreationAppAssetKind::UiDesign, + "source-ui", + ); + let design = fixture_asset( + root, + "assets/home.png", + GameCreationAppAssetKind::UiDesign, + "design-home", + ); + let sprite = fixture_asset( + root, + "assets/start-button.png", + GameCreationAppAssetKind::Icon, + "start-button", + ); let font_path = root.join("assets/ui-font.ttf"); fs::copy( Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../public/fusion-pixel.ttf"), @@ -1670,7 +1695,7 @@ mod tests { let font = register_local_asset_at( root, "assets/ui-font.ttf", - "font", + GameCreationAppAssetKind::Font, "font/ttf", "workflow-test", GameCreationAppAssetSource {