收敛 AGC 资源 kind 写入与读取到正式枚举成员

- assets.rs:`register_local_asset_at` / `register_local_asset_entry` 改为接收 `GameCreationAppAssetKind`,画板导入与画板同步在边界解析 kind
- assets.rs:`infer_canvas_export_asset_kind` 直接返回枚举成员,去掉仅供兜底的 canonical 归一
- commands.rs:资源登记按 `asset.register` 上下文解析 kind,字体导入写 `Font`,本地文件类型表与平台导入归一改用枚举
- direct_runtime.rs:直连游戏工程产物统一写 `Code`,图集切片读取按 `Icon` 叠加切片路径判定,陶泥儿美术身份改收枚举
- canvas_generation.rs:新增 `platform_art_asset_manifest_kind` 收口平台美术请求词汇,切片登记 category 改用 `Icon.as_str()`
- manifest.rs:必填视觉资产校验改用 `IconSpec` / `UiDesign` / `IconSpritesheet`,修正此前与写入侧对不上的比对
- project_gates.rs、autonomous_policy.rs、recovery_scan.rs、direct_tool_bridge.rs、ui_editor workflow/persistence/resource_bridge:读取侧与 fixture 改用正式枚举成员
- tests:资源相关 fixture 与断言改用正式枚举成员
This commit is contained in:
2026-09-16 19:07:12 +08:00
parent 2a75a2d2c8
commit a61b0cce81
24 changed files with 454 additions and 267 deletions
@@ -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<String> {
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<String> {
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(
&registered_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<String>,
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,
@@ -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()
@@ -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")
@@ -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)
@@ -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,
@@ -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(),
),
]
@@ -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<usize, String>
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<String, String> {
/// 画板导出图层推断出的资源 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"] {
@@ -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 {
@@ -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
@@ -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 {
@@ -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 {
@@ -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),
@@ -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(),
@@ -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",
@@ -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()],
@@ -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"
@@ -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(),
@@ -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"] {
@@ -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",
@@ -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,
@@ -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",
);
@@ -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 {
@@ -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 {
@@ -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 {