解除固定图片产物并支持自定义图集切片
移除视觉 Agent 固定图片路径与完成门禁 新增 sliceCount 请求参数并贯通客户端与服务端 允许按项目需求生成单图、多图和自定义图集 更新 OpenAPI、提示词和运行时合同
This commit is contained in:
@@ -2114,7 +2114,7 @@ fn direct_registered_taonier_slice_paths(root: &Path) -> Vec<String> {
|
||||
let Ok(validated_slices) = validated_art_slices(root) else {
|
||||
return Vec::new();
|
||||
};
|
||||
if validated_slices.len() != 4 {
|
||||
if validated_slices.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut resource_ids = std::collections::HashSet::with_capacity(validated_slices.len());
|
||||
@@ -2680,6 +2680,7 @@ async fn generate_direct_taonier_art_asset_at(
|
||||
asset_kind: asset_kind.to_string(),
|
||||
asset_label: asset_label.to_string(),
|
||||
replace_existing: root.join(output_path).is_file(),
|
||||
slice_count: None,
|
||||
};
|
||||
let runtime_context =
|
||||
direct_taonier_art_generation_runtime_context(root, output_path, asset_kind)?;
|
||||
@@ -2783,9 +2784,9 @@ fn direct_taonier_art_package_result(
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
if includes_spritesheet && slice_paths.len() != 4 {
|
||||
if includes_spritesheet && slice_paths.is_empty() {
|
||||
slice_warnings.push(
|
||||
"当前核心图集没有可验证的独立切片;只能使用完整图集,不得猜测切片或伪造衍生素材"
|
||||
"当前图集没有可验证的独立切片;只能使用完整图集,不得猜测切片或伪造衍生素材"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2072,6 +2072,7 @@ async fn bridge_generate_image(state: &DirectToolBridgeState, arguments: &Value)
|
||||
asset_kind: kind.clone(),
|
||||
asset_label: asset_name.clone(),
|
||||
replace_existing: false,
|
||||
slice_count: None,
|
||||
};
|
||||
let _generation_guard = state.image_generation_gate.lock().await;
|
||||
let generated = with_direct_editor_api_credentials(
|
||||
|
||||
@@ -412,6 +412,7 @@ pub(crate) struct PlatformArtAssetGenerationOptions {
|
||||
pub(crate) asset_kind: String,
|
||||
pub(crate) asset_label: String,
|
||||
pub(crate) replace_existing: bool,
|
||||
pub(crate) slice_count: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for PlatformArtAssetGenerationOptions {
|
||||
@@ -423,6 +424,7 @@ impl Default for PlatformArtAssetGenerationOptions {
|
||||
asset_kind: "game-art".to_string(),
|
||||
asset_label: "AI 游戏首版美术素材".to_string(),
|
||||
replace_existing: false,
|
||||
slice_count: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1755,24 +1757,16 @@ fn canonical_art_spritesheet_icon_descriptions(prompt: &str) -> Vec<String> {
|
||||
// long creation request cannot reject the atlas before it is queued.
|
||||
const MAX_DESCRIPTION_CHARS: usize = 200;
|
||||
const CONTEXT_PREFIX: &str = ";遵循同一项目视觉规范:";
|
||||
[
|
||||
"第 1 类(左上):当前玩法的玩家主体或主要操作对象;只生成一个轮廓连贯、可独立使用的完整素材",
|
||||
"第 2 类(右上):当前玩法的方块、目标物、收集物、敌对实体或危险物;只生成一个完整素材",
|
||||
"第 3 类(左下):当前玩法需要的地块、障碍、资源物件或场景装饰;只生成一个完整素材",
|
||||
"第 4 类(右下):得分、受击、成长、失败、胜利或操作反馈特效;只生成一个完整素材",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|category| {
|
||||
let context_budget = MAX_DESCRIPTION_CHARS.saturating_sub(
|
||||
category
|
||||
.chars()
|
||||
.count()
|
||||
.saturating_add(CONTEXT_PREFIX.chars().count()),
|
||||
);
|
||||
let project_context = truncate_inline_bounded(prompt.trim(), context_budget);
|
||||
format!("{category}{CONTEXT_PREFIX}{project_context}")
|
||||
})
|
||||
.collect()
|
||||
let category =
|
||||
"按当前项目需求生成一组可独立使用的透明素材;数量、类别、排列和切片方式由本次需求决定";
|
||||
let context_budget = MAX_DESCRIPTION_CHARS.saturating_sub(
|
||||
category
|
||||
.chars()
|
||||
.count()
|
||||
.saturating_add(CONTEXT_PREFIX.chars().count()),
|
||||
);
|
||||
let project_context = truncate_inline_bounded(prompt.trim(), context_budget);
|
||||
vec![format!("{category}{CONTEXT_PREFIX}{project_context}")]
|
||||
}
|
||||
|
||||
fn truncate_inline_bounded(value: &str, max_chars: usize) -> String {
|
||||
@@ -2084,13 +2078,13 @@ pub(crate) async fn generate_platform_art_asset_with_required_slices_at(
|
||||
}
|
||||
let generation_prompt = build_platform_art_asset_prompt(prompt, briefs, options);
|
||||
let runtime_context =
|
||||
standalone_platform_art_generation_runtime_context(&generation_prompt, options, true)?;
|
||||
standalone_platform_art_generation_runtime_context(&generation_prompt, options, false)?;
|
||||
generate_platform_art_asset_with_runtime_options_at(
|
||||
root,
|
||||
prompt,
|
||||
briefs,
|
||||
options,
|
||||
true,
|
||||
false,
|
||||
&runtime_context,
|
||||
)
|
||||
.await
|
||||
@@ -2655,7 +2649,7 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at
|
||||
serde_json::json!({
|
||||
"referenceId": reference_id,
|
||||
"iconDescriptions": canonical_art_spritesheet_icon_descriptions(&generation_prompt),
|
||||
"sliceLayout": "grid-2x2",
|
||||
"sliceCount": options.slice_count,
|
||||
"screenColor": "auto",
|
||||
"aspectRatio": options.aspect_ratio,
|
||||
"imageSize": options.image_size,
|
||||
@@ -6358,11 +6352,8 @@ fn validate_strict_platform_art_spritesheet_contract(
|
||||
has_transparent_pixels: bool,
|
||||
has_visible_pixels: bool,
|
||||
) -> Result<(), String> {
|
||||
if slices.len() != 4 {
|
||||
return Err(format!(
|
||||
"strict spritesheet 图集必须恰好包含 4 个独立切片,实际为 {} 个",
|
||||
slices.len()
|
||||
));
|
||||
if slices.is_empty() {
|
||||
return Err("spritesheet 图集至少需要一个独立切片".to_string());
|
||||
}
|
||||
let resource_id = resource_id
|
||||
.map(str::trim)
|
||||
@@ -6393,12 +6384,7 @@ fn validate_strict_platform_art_spritesheet_contract(
|
||||
{
|
||||
return Err("strict spritesheet 图集生成 route/kind 与严格图集合同不一致".to_string());
|
||||
}
|
||||
if spritesheet_slice_layout.map(str::trim) != Some("grid-2x2") {
|
||||
return Err(
|
||||
"strict spritesheet 图集必须由 External Editor 以 grid-2x2 固定切片合同生成"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
let _requested_slice_layout = spritesheet_slice_layout;
|
||||
if reference_resource_ids.len() != 1
|
||||
|| reference_resource_ids[0].trim().is_empty()
|
||||
|| reference_resource_ids[0].trim() == resource_id
|
||||
@@ -6733,7 +6719,7 @@ fn existing_platform_art_slice_registrations_are_complete(
|
||||
manifest: &GameCreationAppManifest,
|
||||
registrations: &[PlatformArtSliceManifestRegistration],
|
||||
) -> Result<bool, String> {
|
||||
if registrations.len() != 4 {
|
||||
if registrations.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut resource_ids = std::collections::HashSet::with_capacity(registrations.len());
|
||||
|
||||
File diff suppressed because one or more lines are too long
+3
-10
@@ -453,16 +453,9 @@ pub(in crate::agent) fn validate_agent_runtime_autonomous_initial_collaboration_
|
||||
"首批 art-director 必须是非只读规范图生成任务",
|
||||
));
|
||||
}
|
||||
let art_artifacts =
|
||||
// 图片产物由 Codex 按项目需求决定;不再要求固定 art-spec.png。
|
||||
let _art_artifacts =
|
||||
autonomous_initial_delegate_expected_artifacts(art_director, "art-director")?;
|
||||
if !art_artifacts
|
||||
.iter()
|
||||
.any(|path| path == "assets/art-spec.png")
|
||||
{
|
||||
return Err(autonomous_initial_collaboration_contract_error(
|
||||
"首批 art-director 的 expectedArtifacts 必须包含 assets/art-spec.png",
|
||||
));
|
||||
}
|
||||
|
||||
let code_director = code_director.ok_or_else(|| {
|
||||
autonomous_initial_collaboration_contract_error("首批缺少 code-director 委派")
|
||||
@@ -1976,7 +1969,7 @@ mod tests {
|
||||
plan: Vec::new(),
|
||||
actions: vec![
|
||||
autonomous_initial_delegate("design-director", &[]),
|
||||
autonomous_initial_delegate("art-director", &["assets/art-spec.png"]),
|
||||
autonomous_initial_delegate("art-director", &[]),
|
||||
autonomous_initial_delegate("code-director", &[]),
|
||||
],
|
||||
response: String::new(),
|
||||
|
||||
@@ -1199,32 +1199,36 @@ pub(in crate::agent) fn visual_asset_completion_blocker_at_locked(
|
||||
agent_id: &str,
|
||||
required_run_id: Option<&str>,
|
||||
) -> Option<AgentRuntimeToolObservation> {
|
||||
if !editor_api_key_is_configured() {
|
||||
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-asset-plan" => (
|
||||
"assets/art-spritesheet.png",
|
||||
"art-spritesheet",
|
||||
"首版美术素材图",
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
let manifest = match read_manifest_for_project(root) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(error) => {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: format!("无法核对{label},不能完成任务"),
|
||||
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
|
||||
});
|
||||
// 图片产物由 Codex 按项目需求选择,不再存在固定视觉资产完成门禁。
|
||||
return None;
|
||||
#[allow(unreachable_code)]
|
||||
{
|
||||
if !editor_api_key_is_configured() {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if let Err(error) = validate_manifest_required_visual_asset(root, &manifest, agent_id) {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
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-asset-plan" => (
|
||||
"assets/art-spritesheet.png",
|
||||
"art-spritesheet",
|
||||
"首版美术素材图",
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
let manifest = match read_manifest_for_project(root) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(error) => {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: format!("无法核对{label},不能完成任务"),
|
||||
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
|
||||
});
|
||||
}
|
||||
};
|
||||
if let Err(error) = validate_manifest_required_visual_asset(root, &manifest, agent_id) {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: format!("{label}尚未按正式视觉流程生成并登记,不能完成任务"),
|
||||
@@ -1234,29 +1238,30 @@ pub(in crate::agent) fn visual_asset_completion_blocker_at_locked(
|
||||
redact_agent_runtime_project_paths(root, &error, 300),
|
||||
)),
|
||||
});
|
||||
}
|
||||
if agent_id != "design-foundation" {
|
||||
return None;
|
||||
}
|
||||
match ui_prototype_visual_inspection_blocker_detail_at_locked(
|
||||
root,
|
||||
agent_id,
|
||||
required_run_id,
|
||||
expected_path,
|
||||
) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(detail)) => Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "策划界面原型图尚未通过结构化 UI 视觉检查,不能完成任务".to_string(),
|
||||
detail: Some(detail),
|
||||
}),
|
||||
Err(error) => Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "无法核对策划界面原型图的结构化 UI 视觉证据,不能完成任务".to_string(),
|
||||
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
|
||||
}),
|
||||
}
|
||||
if agent_id != "design-foundation" {
|
||||
return None;
|
||||
}
|
||||
match ui_prototype_visual_inspection_blocker_detail_at_locked(
|
||||
root,
|
||||
agent_id,
|
||||
required_run_id,
|
||||
expected_path,
|
||||
) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(detail)) => Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "策划界面原型图尚未通过结构化 UI 视觉检查,不能完成任务".to_string(),
|
||||
detail: Some(detail),
|
||||
}),
|
||||
Err(error) => Some(AgentRuntimeToolObservation {
|
||||
tool: "runtime.visual_asset".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "无法核对策划界面原型图的结构化 UI 视觉证据,不能完成任务".to_string(),
|
||||
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1756,11 +1756,7 @@ pub(in crate::agent) fn project_autonomous_manifest_ready_task_terminal_at_locke
|
||||
}
|
||||
|
||||
pub(super) fn autonomous_manifest_ready_task_requires_visual_asset(task_id: &str) -> bool {
|
||||
editor_api_key_is_configured()
|
||||
&& matches!(
|
||||
task_id,
|
||||
"art-director" | "design-foundation" | "art-asset-plan"
|
||||
)
|
||||
false
|
||||
}
|
||||
|
||||
fn render_autonomous_manifest_ready_task_owner_prompt(task: &GameCreationAppTaskState) -> String {
|
||||
@@ -1777,11 +1773,7 @@ fn render_autonomous_manifest_ready_task_owner_prompt(task: &GameCreationAppTask
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let visual_requirement = if task.id == "art-asset-plan" && editor_api_key_is_configured() {
|
||||
"art-asset-plan 的固定成功路径是:调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png(assetKind=art-spritesheet),然后调用 asset.list 核对图集及四个 canonical 切片已经登记,再调用 file.write 写入 assets/manifest.art.json;完成这组动作后把结构化计划最后一步标记 completed 并立即交付。不要调用 image.inspect,不要根据图片主观观感发起返工或 agent.message;图集视觉质量由后续质量任务处理,Runtime 会在收束门内验证文件和资产登记状态。"
|
||||
} else {
|
||||
"任务声明中的视觉图片继续按现有 visual gate 生成、登记并验收。"
|
||||
};
|
||||
let visual_requirement = "任务声明中的视觉图片按项目需求选择工具、数量、输出路径、尺寸和布局;需要图集时用 sliceCount 指定切片数量。Runtime 只核对实际声明的资源登记,不要求固定图片合同。";
|
||||
let verification_requirement = match task.id.as_str() {
|
||||
"code-prototype" => "code-prototype 必须对可玩入口执行 game.static_smoke;完整 DAG 的最终静态与浏览器验收继续由后续质量任务承担。",
|
||||
task_id if agent_runtime_autonomous_uses_owner_artifact_validation(task_id) => "完成固定正式产物后直接交付,由 Runtime 在收束门内验证本人固定 owner 产物;禁止调用 game.static_smoke、project.verify、command.run_limited 或 preview 工具冒充 owner 产物验证。",
|
||||
@@ -1810,7 +1802,7 @@ pub(in crate::agent) fn render_autonomous_manifest_ready_task_background_prompt(
|
||||
if task.id == "art-director" {
|
||||
if autonomous_manifest_ready_task_requires_visual_asset(&task.id) {
|
||||
return format!(
|
||||
"{base}\n\n这是 autonomous-game-build 的非只读视觉规范生成任务。{AGENT_RUNTIME_AUTONOMOUS_ART_DIRECTOR_CANVAS_ONLY_TASK_MARKER};必须用固定合同生成并登记 assets/art-spec.png(assetKind=icon-spec、aspectRatio=1:1),该受控素材事务会同时提交当前 run 的 mutation 与验证凭证。禁止调用 file.write、file.patch、file.delete、project.patchset、project.restore 或写入其它路径。生成成功后直接交付视觉规范结论;不要调用 task.update,Runtime 会在子 Run 终态后幂等投影 manifest。"
|
||||
"{base}\n\n这是 autonomous-game-build 的视觉方向任务。根据项目需求决定是否调用 canvas.asset_generate,不规定固定图片名称、数量、素材类别或布局;生成成功后直接交付结论。"
|
||||
);
|
||||
}
|
||||
return format!(
|
||||
|
||||
@@ -534,15 +534,7 @@ pub(crate) fn observe_agent_runtime_agent_delegate_at_locked(
|
||||
agent_runtime_tool_input_text(input, &["repairOfDelegationId", "repair_of_delegation_id"]);
|
||||
let repair_of_delegation_id =
|
||||
(!repair_of_delegation_id.is_empty()).then_some(repair_of_delegation_id);
|
||||
let required_visual_artifact = if editor_api_key_is_configured() {
|
||||
match target_agent_id.as_str() {
|
||||
"design-foundation" => Some("assets/ui-prototype.png"),
|
||||
"art-asset-plan" => Some("assets/art-spritesheet.png"),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let required_visual_artifact: Option<&str> = None;
|
||||
if repair_of_delegation_id.is_none()
|
||||
&& required_visual_artifact.is_some_and(|required| {
|
||||
!expected_artifacts
|
||||
|
||||
@@ -416,39 +416,6 @@ pub(in crate::agent) fn observe_agent_runtime_file_delete(
|
||||
return agent_runtime_mutation_gate_failure_observation(root, "file.delete", &error);
|
||||
}
|
||||
}
|
||||
if agent_id == "art-asset-plan" && path == "assets/art-spritesheet.png" {
|
||||
let manifest = match read_existing_manifest_for_project(root) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(error) => {
|
||||
return AgentRuntimeToolObservation {
|
||||
tool: "file.delete".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "无法确认首版美术素材登记状态,未执行删除".to_string(),
|
||||
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
|
||||
};
|
||||
}
|
||||
};
|
||||
let registered_fixed_asset_exists = manifest.assets.iter().any(|asset| {
|
||||
asset.local_path == "assets/art-spritesheet.png"
|
||||
&& asset.kind == "art-spritesheet"
|
||||
&& asset.media_type.starts_with("image/")
|
||||
&& asset.source.kind == GameCreationAppAssetSourceKind::Canvas
|
||||
&& resolve_local_project_path(root, &asset.local_path)
|
||||
.ok()
|
||||
.is_some_and(|path| path.is_file())
|
||||
});
|
||||
if registered_fixed_asset_exists {
|
||||
return AgentRuntimeToolObservation {
|
||||
tool: "file.delete".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "首版美术素材已生成并登记,禁止删除固定正式产物".to_string(),
|
||||
detail: Some(
|
||||
"path=assets/art-spritesheet.png · 请复用现有画布资产并核对 assets/manifest.art.json,不得重复生成或扣费"
|
||||
.to_string(),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
if let Err(error) =
|
||||
prepare_agent_runtime_project_mutation_locked(root, agent_id, run_id, "file.delete")
|
||||
{
|
||||
|
||||
@@ -540,6 +540,11 @@ pub(in crate::agent) async fn observe_agent_runtime_platform_art_asset_generatio
|
||||
.or_else(|| input.get("replace_existing"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let slice_count = input
|
||||
.get("sliceCount")
|
||||
.or_else(|| input.get("slice_count"))
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|value| value as usize);
|
||||
let requested_options = PlatformArtAssetGenerationOptions {
|
||||
output_path: (!output_path.trim().is_empty()).then_some(output_path),
|
||||
aspect_ratio,
|
||||
@@ -547,83 +552,9 @@ pub(in crate::agent) async fn observe_agent_runtime_platform_art_asset_generatio
|
||||
asset_kind,
|
||||
asset_label,
|
||||
replace_existing,
|
||||
slice_count,
|
||||
};
|
||||
let canonical_options = match agent_id {
|
||||
"art-director" => Some(PlatformArtAssetGenerationOptions {
|
||||
output_path: Some(AGENT_RUNTIME_ART_SPEC_PATH.to_string()),
|
||||
aspect_ratio: "1:1".to_string(),
|
||||
image_size: "1K".to_string(),
|
||||
asset_kind: "icon-spec".to_string(),
|
||||
asset_label: "游戏统一视觉规范图".to_string(),
|
||||
replace_existing: false,
|
||||
}),
|
||||
"design-foundation"
|
||||
if requested_options
|
||||
.output_path
|
||||
.as_deref()
|
||||
.is_some_and(design_foundation_ui_page_output_path_is_valid) =>
|
||||
{
|
||||
Some(PlatformArtAssetGenerationOptions {
|
||||
output_path: requested_options.output_path.clone(),
|
||||
aspect_ratio: "16:9".to_string(),
|
||||
image_size: "2K".to_string(),
|
||||
asset_kind: "ui-prototype".to_string(),
|
||||
asset_label: if requested_options.asset_label.trim().is_empty() {
|
||||
"游戏功能页面设计图".to_string()
|
||||
} else {
|
||||
requested_options.asset_label.clone()
|
||||
},
|
||||
replace_existing: false,
|
||||
})
|
||||
}
|
||||
"design-foundation" => Some(PlatformArtAssetGenerationOptions {
|
||||
output_path: Some("assets/ui-prototype.png".to_string()),
|
||||
aspect_ratio: "16:9".to_string(),
|
||||
image_size: "2K".to_string(),
|
||||
asset_kind: "ui-prototype".to_string(),
|
||||
asset_label: "游戏横屏界面原型图".to_string(),
|
||||
replace_existing: false,
|
||||
}),
|
||||
"art-asset-plan" => Some(PlatformArtAssetGenerationOptions {
|
||||
output_path: Some("assets/art-spritesheet.png".to_string()),
|
||||
aspect_ratio: "1:1".to_string(),
|
||||
image_size: "1K".to_string(),
|
||||
asset_kind: "art-spritesheet".to_string(),
|
||||
asset_label: "游戏首版核心美术素材".to_string(),
|
||||
replace_existing: false,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
let mut options = if let Some(canonical) = canonical_options {
|
||||
let mismatch = requested_options
|
||||
.output_path
|
||||
.as_deref()
|
||||
.is_some_and(|value| Some(value) != canonical.output_path.as_deref())
|
||||
|| (!requested_options.aspect_ratio.is_empty()
|
||||
&& requested_options.aspect_ratio != canonical.aspect_ratio)
|
||||
|| (!requested_options.image_size.is_empty()
|
||||
&& requested_options.image_size != canonical.image_size)
|
||||
|| (!requested_options.asset_kind.is_empty()
|
||||
&& requested_options.asset_kind != canonical.asset_kind)
|
||||
|| (!requested_options.asset_label.is_empty()
|
||||
&& requested_options.asset_label != canonical.asset_label);
|
||||
if mismatch {
|
||||
return AgentRuntimeToolObservation {
|
||||
tool: "canvas.asset_generate".to_string(),
|
||||
status: "failed".to_string(),
|
||||
summary: format!(
|
||||
"图片产物型专业任务不能覆盖固定输出合同:outputPath={} · aspectRatio={} · imageSize={} · assetKind={} · assetLabel={}",
|
||||
canonical.output_path.as_deref().unwrap_or("null"),
|
||||
canonical.aspect_ratio,
|
||||
canonical.image_size,
|
||||
canonical.asset_kind,
|
||||
canonical.asset_label,
|
||||
),
|
||||
detail: None,
|
||||
};
|
||||
}
|
||||
canonical
|
||||
} else {
|
||||
let mut options = {
|
||||
let defaults = PlatformArtAssetGenerationOptions::default();
|
||||
PlatformArtAssetGenerationOptions {
|
||||
output_path: requested_options.output_path,
|
||||
@@ -648,6 +579,7 @@ pub(in crate::agent) async fn observe_agent_runtime_platform_art_asset_generatio
|
||||
requested_options.asset_label
|
||||
},
|
||||
replace_existing,
|
||||
slice_count,
|
||||
}
|
||||
};
|
||||
options.replace_existing = replace_existing;
|
||||
|
||||
@@ -1386,7 +1386,7 @@ fn runtime_tool_description(tool: &str) -> &'static str {
|
||||
"preview.validate" => "用真实浏览器验证桌面和移动预览并保存证据。",
|
||||
"image.inspect" => "让视觉模型检查一至两张项目内图片。",
|
||||
"canvas.asset_generate" => {
|
||||
"通过已配置的 External Editor API 生成图片并登记到画布、素材库和项目 assets;art-director 先生成 icon-spec 规范图,ui-prototype 与透明 art-spritesheet 都固定复用该规范图;只有唯一返工委派可显式替换已登记正式图片。"
|
||||
"通过已配置的 External Editor API 按项目需求生成图片或图集并登记到画布、素材库和项目 assets;可使用已登记资源作为参考,也可通过 sliceCount 指定图集切片数量。"
|
||||
}
|
||||
"ui.workflow.run" => {
|
||||
"先用 discover 从受控 game/ui-pages.json 或页面声明标记自动发现全部功能页面,再把已登记 ui-prototype 与每个页面的设计图桥接成独立 UI JSON State;可同时载入已登记图片、图标和项目字体,执行 Provider 结构识别、多树合并与分批组件绑定、回读阶段,并且只有所有页面已绑定且已应用到 game/ 后才允许 finalize。项目根目录由 Runtime 注入,模型不得传入宿主路径。"
|
||||
|
||||
@@ -3372,8 +3372,14 @@
|
||||
},
|
||||
"sliceLayout": {
|
||||
"type": "string",
|
||||
"enum": ["grid-2x2"],
|
||||
"description": "可选固定图集切片合同。省略时沿用全图 alpha 连通域自动拆分;传 grid-2x2 时服务端要求生成四个固定象限,并按左上、右上、左下、右下各持久化一个独立切片。该模式适用于需要恰好四类核心运行时素材的游戏,不会猜测等分裁切。"
|
||||
"deprecated": true,
|
||||
"description": "历史兼容字段,新的调用请使用 sliceCount。"
|
||||
},
|
||||
"sliceCount": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
"description": "可选的目标切片数量;省略时按图像内容自动识别。"
|
||||
},
|
||||
"screenColor": {
|
||||
"type": ["string", "null"],
|
||||
@@ -3597,15 +3603,21 @@
|
||||
},
|
||||
"iconImageSrcs": {
|
||||
"type": "array",
|
||||
"description": "默认模式识别图集中全部有效 alpha 连通域并持久化的独立素材,按视觉阅读顺序命名为“素材 N”;数量由图集内容决定,不由 iconDescriptions 数量决定。sliceLayout=grid-2x2 时固定返回左上、右上、左下、右下四个格子的切片,各格内的零散视觉细节不会被拆成额外素材。",
|
||||
"description": "识别图集中有效 alpha 连通域并持久化的独立素材,按视觉阅读顺序命名为“素材 N”;可通过 sliceCount 指定目标数量。",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EditorIconSpritesheetIconResult"
|
||||
}
|
||||
},
|
||||
"sliceLayout": {
|
||||
"type": "string",
|
||||
"enum": ["grid-2x2"],
|
||||
"description": "仅当请求使用固定切片合同且主图完成透明化、切片持久化后返回。调用方可将该字段与 iconImageSrcs=4 共同作为固定四类素材的来源证明。"
|
||||
"deprecated": true,
|
||||
"description": "历史兼容字段。"
|
||||
},
|
||||
"sliceCount": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"description": "实际生成的切片数量。"
|
||||
},
|
||||
"sliceWarning": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -1070,7 +1070,7 @@ game-project/
|
||||
- 内部 owner 验证只接受 GUI / CLI 完整 16 任务 DAG 中 `agent-ready-task-scheduler` 启动的确定性直接 child、当前活跃根和完整 project/source/profile/Agent/run/parent/root/binding 身份。错误 source、delegated run、历史或终态根、非当前活跃根、跨 Agent/run 凭证均失败关闭;再次 mutation 使旧凭证失效,相同身份恢复可按当前事实确定性重验。本阶段不扩到后置 `publish-package`。`code-prototype` 与 `preview-readiness` 继续执行真实 `game.static_smoke`,`preview-playtest` 继续独立执行浏览器验收;任何 owner 文件凭证都不能替代可玩证据。
|
||||
- `design-foundation` 的 2026-07-26 职责隔离继续有效:项目文件仍只允许 `memory/project.md`、`game/game_design.md` 和配置 Key 时的固定 `assets/ui-prototype.png`,禁止修改 `game/index.html`、调用 smoke / preview / process 或恢复整项目。未配置 External Editor API Key 时 `art-director` 保持只读协调;配置 Key 时它是条件 Canvas owner,必须生成并登记 `assets/art-spec.png`,成功 `canvas.asset_generate` 为本人当前 revision 形成普通验证凭证,不能被只读分类吞掉。配置 Key 时 UI 原型、透明图集、Canvas 登记和视觉门仍按既有合同执行,内部 owner 文件验证不替代图片证据。
|
||||
- `canvas.asset_generate.replaceExisting` 默认并必须保持 `false`;只有静态专业 Agent 的 `delegated-*` 唯一 repair run 才能申请 `true`。Runtime 要求当前 delivery 带 `repairOfDelegationId`,原 delivery 已被同一父 Agent / 父 run 认领,原始与返工合同的目标 Agent 和精确 `expectedArtifacts` 路径一致;普通 run、未声明路径、错误 Agent、未认领原交付或缺失原图都失败关闭。图片生成仍服从 `art-director` / `design-foundation` / `art-asset-plan` 的固定输出路径、比例、尺寸、kind 和 label,禁止先删除正式图片;请求前记录旧文件 SHA-256,外部生成返回后在项目写锁内复核,旧图在网络请求期间变化即拒绝覆盖。授权替换先写私有临时文件,再以备份 / rename 切换;落盘或 manifest 登记失败时恢复旧图,不把新旧文件并存状态当作成功。
|
||||
- 在既有 16-task manifest 内固定正式视觉 DAG,不新增平行任务系统:`art-director` 用当前调用模式的图片生成 `kind=spec` 生成 `assets/art-spec.png` 并登记为 `assetKind=icon-spec`;`design-foundation` 使用该规范图的稳定资源 ID 作为视觉规范参考,用同模式图片生成 `kind=ui-design` 生成 `assets/ui-prototype.png`;`art-asset-plan` 以同一 resource ID 调用同模式图标 spritesheet 生成,产出透明 `assets/art-spritesheet.png`。普通模式使用内部 `/api/editor/*`,standalone/高级模式使用对应 `/api/external/v1/*`;业务请求、依赖和验收完全一致。规范图缺失、未登记或缺少稳定资源 ID 时,下游任务不得退回普通生图。图集 warning、透明像素与切片门禁保持不变。
|
||||
- 视觉 Agent 只负责指导 Codex 选择合适的图片/编辑/图集工具并提供项目上下文,不再固定图片数量、文件槽位、素材类别或 spritesheet 布局;请求可按玩法需要生成单图、多图或任意切片布局。普通模式使用内部 `/api/editor/*`,standalone/高级模式使用对应 `/api/external/v1/*`;权限、计费、幂等、资源登记和安全校验保持不变。
|
||||
- 旧项目已有同路径派生图但缺少上述 provenance 时,一律标记为 legacy,不得只因文件、kind 或通用视觉检查存在就完成。原位替换仍走显式 repair:`design-foundation` 与 `art-asset-plan` 先在同一 Supervisor 批次分别建立 owner 精确原合同并交付 `needs-repair`,父 run 认领后再在同一批次分别发起各自唯一 repair;两个 repair 合称一个显式视觉返工阶段。`art-director` 不得跨 owner 声明或替换 UI / spritesheet,Runtime 在委派落盘前就拒绝这类合同,不再等到生图阶段才失败。
|
||||
- 2026-07-27 新起的“16 任务正式产物 + 两张真实画布图片 + current revision 静态 / 双视口浏览器 / PNG 证据 + 受限 repair 替换”独立外部 Provider 验收,使用 `npm run agc:test:chat -- --timeout-minutes 75`,约 `59m50s` 后以退出码 `0` 完整 **PASS**。同一轮真实生成并登记 `assets/ui-prototype.png`(`2829418` bytes)与 `assets/art-spritesheet.png`(`1361906` bytes),固定 `16` 个 manifest task 均为当前父 Run 下唯一 logical run、一次 started、一次 completed、零 failed / cancelled 和一次 manifest projection;七份基础正式产物、两张 PNG、当前 revision 的 `game.static_smoke`、desktop / mobile `lane-defense-v1` playtest、浏览器报告与截图全部通过。`turn.report=settled` 且唯一 assistant,busy / pending / running / confirmation / user-input / reconciliation 均为 `0`;隔离 Runner、一次性项目和隔离 AppData 已自动清理。此前失败轮继续独立保留,不与本轮拼接;未来合同变化仍须新起完整轮次复验。
|
||||
- 2026-07-27 补充 tool-plan 成功响应交接的内容边界:Provider 的自然语言计划叙述,以及结构化 arguments 中 `body / code / content / css / html / newText / oldText / patch / script / text` 等源码内容字段,只检查真实密钥 token 形状、凭据头标记和不安全控制字符;仅仅提及 `.env` 或 `game-creator.config` 不能阻断已经计费的安全响应。结构化输入中的敏感 JSON key、非内容字段中的配置痕迹或绝对路径、真实 token、容量、thinking、身份、顺序和账本完整性门禁仍失败关闭。成功 handoff 失败进入 reconciliation 时,Runtime 额外只持久化受控 `failureKind`、脱敏错误 SHA-256 和字符数,不保存 Provider 正文、function arguments、密钥或绝对路径。定向回归覆盖叙述/源码字段放行、`.env.local` 路径和真实 token 拒绝、全部 tool-plan handoff 回归及诊断零正文。
|
||||
|
||||
@@ -863,6 +863,7 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
reference_id,
|
||||
reference_image_srcs: Some(reference_image_srcs),
|
||||
icon_descriptions: args.icon_descriptions,
|
||||
slice_count: None,
|
||||
slice_layout: None,
|
||||
style: None,
|
||||
model: Some(args.model),
|
||||
|
||||
@@ -8579,6 +8579,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
spritesheet_height: source_height,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_layout: None,
|
||||
slice_count: None,
|
||||
slice_warning: None,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
@@ -8645,6 +8646,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
spritesheet_height: source_height,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_layout: None,
|
||||
slice_count: None,
|
||||
slice_warning: None,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
@@ -8729,6 +8731,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
slice_source,
|
||||
request_context.external_call_deadline(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -8891,6 +8894,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
spritesheet_height,
|
||||
icon_image_srcs,
|
||||
slice_layout: None,
|
||||
slice_count: None,
|
||||
slice_warning,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
@@ -19059,7 +19063,7 @@ mod tests {
|
||||
.checked_sub(Duration::from_millis(1))
|
||||
.expect("expired deadline should be representable");
|
||||
|
||||
let error = slice_editor_icon_spritesheet_all(source, Some(expired), None)
|
||||
let error = slice_editor_icon_spritesheet_all(source, Some(expired), None, None)
|
||||
.await
|
||||
.err()
|
||||
.expect("expired CPU budget must fail before decoding");
|
||||
@@ -19748,6 +19752,7 @@ mod tests {
|
||||
spritesheet_height: 512,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_layout: None,
|
||||
slice_count: None,
|
||||
slice_warning: Some(EditorIconSpritesheetSliceWarningResponse {
|
||||
code: EDITOR_ICON_SPRITESHEET_SLICE_WARNING_COMPONENTS,
|
||||
reason: "图集中未识别到可拆分的独立素材。".to_string(),
|
||||
|
||||
@@ -251,6 +251,9 @@ pub(crate) struct EditorIconSpritesheetGenerationRequest {
|
||||
pub(crate) reference_id: String,
|
||||
pub(crate) reference_image_srcs: Option<Vec<String>>,
|
||||
pub(crate) icon_descriptions: Vec<String>,
|
||||
/// 用户要求的切片数量;未提供时按图像中的连通素材自动识别。
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) slice_count: Option<usize>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) slice_layout: Option<EditorIconSpritesheetSliceLayout>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -267,8 +270,7 @@ pub(crate) struct EditorIconSpritesheetGenerationRequest {
|
||||
pub(crate) canvas_completion: Option<EditorCanvasGenerationCompletionPayload>,
|
||||
}
|
||||
|
||||
/// Opt-in fixed atlas slicing. Existing callers remain on the default
|
||||
/// connected-component path unless they explicitly request this layout.
|
||||
/// Deprecated compatibility layout. New callers should use `sliceCount`。
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub(crate) enum EditorIconSpritesheetSliceLayout {
|
||||
#[serde(rename = "grid-2x2")]
|
||||
@@ -313,6 +315,8 @@ pub(crate) struct EditorIconSpritesheetGenerationResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) slice_layout: Option<EditorIconSpritesheetSliceLayout>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) slice_count: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) slice_warning: Option<EditorIconSpritesheetSliceWarningResponse>,
|
||||
pub(crate) prompt: String,
|
||||
pub(crate) actual_prompt: Option<String>,
|
||||
@@ -1783,6 +1787,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
spritesheet_height: source_height,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_layout: payload.slice_layout,
|
||||
slice_count: Some(0),
|
||||
slice_warning: None,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
@@ -1864,6 +1869,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
spritesheet_height: source_height,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_layout: payload.slice_layout,
|
||||
slice_count: Some(0),
|
||||
slice_warning: None,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
@@ -1961,6 +1967,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
slice_source,
|
||||
request_context.external_call_deadline(),
|
||||
payload.slice_layout,
|
||||
payload.slice_count,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -2053,6 +2060,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
"spritesheetHeight": spritesheet_height,
|
||||
"iconImageSrcs": &icon_image_srcs,
|
||||
"sliceLayout": payload.slice_layout,
|
||||
"sliceCount": payload.slice_count,
|
||||
"sliceWarning": &slice_warning,
|
||||
"warning": &generation_warning,
|
||||
"prompt": user_prompt.clone(),
|
||||
@@ -2121,6 +2129,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
icon.asset = item.asset.map(editor_asset_payload_from_record);
|
||||
}
|
||||
|
||||
let slice_count = icon_image_srcs.len();
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
EditorIconSpritesheetGenerationResponse {
|
||||
@@ -2129,6 +2138,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
spritesheet_height,
|
||||
icon_image_srcs,
|
||||
slice_layout: payload.slice_layout,
|
||||
slice_count: Some(slice_count),
|
||||
slice_warning,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
@@ -2323,6 +2333,7 @@ pub async fn split_editor_icon_spritesheet(
|
||||
processing_deadline,
|
||||
memory_admission,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let prompt = source_resource
|
||||
@@ -2398,6 +2409,7 @@ pub(crate) async fn slice_editor_icon_spritesheet_all(
|
||||
source: DownloadedImage,
|
||||
request_deadline: Option<Instant>,
|
||||
slice_layout: Option<EditorIconSpritesheetSliceLayout>,
|
||||
slice_count: Option<usize>,
|
||||
) -> Result<PreparedEditorIconSpritesheetPlan, AppError> {
|
||||
let processing_deadline =
|
||||
resolve_editor_icon_spritesheet_processing_deadline(Instant::now(), request_deadline);
|
||||
@@ -2408,6 +2420,7 @@ pub(crate) async fn slice_editor_icon_spritesheet_all(
|
||||
processing_deadline,
|
||||
memory_admission,
|
||||
slice_layout,
|
||||
slice_count,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -2446,6 +2459,7 @@ async fn slice_editor_icon_spritesheet_all_with_memory_admission(
|
||||
processing_deadline: Instant,
|
||||
memory_admission: Arc<EditorIconSpritesheetMemoryAdmission>,
|
||||
slice_layout: Option<EditorIconSpritesheetSliceLayout>,
|
||||
slice_count: Option<usize>,
|
||||
) -> Result<PreparedEditorIconSpritesheetPlan, AppError> {
|
||||
if Instant::now() >= processing_deadline {
|
||||
return Err(editor_icon_spritesheet_processing_timeout_error());
|
||||
@@ -2484,7 +2498,9 @@ async fn slice_editor_icon_spritesheet_all_with_memory_admission(
|
||||
}
|
||||
None => prepare_generated_icon_spritesheet_all_by_connected_components(
|
||||
&source,
|
||||
EDITOR_ICON_SPRITESHEET_MAX_SLICES,
|
||||
slice_count
|
||||
.unwrap_or(EDITOR_ICON_SPRITESHEET_MAX_SLICES)
|
||||
.min(EDITOR_ICON_SPRITESHEET_MAX_SLICES),
|
||||
EDITOR_ICON_SPRITESHEET_MAX_TOTAL_CROP_PIXELS,
|
||||
),
|
||||
}
|
||||
@@ -2505,6 +2521,14 @@ async fn slice_editor_icon_spritesheet_all_with_memory_admission(
|
||||
}
|
||||
Err(_) => return Err(editor_icon_spritesheet_processing_timeout_error()),
|
||||
};
|
||||
if let Some(expected) = slice_count {
|
||||
if expected == 0 || expected > EDITOR_ICON_SPRITESHEET_MAX_SLICES || plan.len() != expected {
|
||||
return Err(AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
"message": format!("请求切片数量为 {expected},实际识别到 {} 个。请调整 sliceCount 或素材排布。", plan.len()),
|
||||
})));
|
||||
}
|
||||
}
|
||||
if plan.is_empty() {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
@@ -2915,6 +2939,7 @@ mod tests {
|
||||
source,
|
||||
None,
|
||||
Some(EditorIconSpritesheetSliceLayout::Grid2x2),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("declared 2x2 sheet should slice");
|
||||
|
||||
@@ -2617,13 +2617,8 @@ mod tests {
|
||||
.is_some_and(|description| description.contains("同步返回 400"))
|
||||
);
|
||||
assert_eq!(
|
||||
icon_spritesheet_request["properties"]["sliceLayout"]["enum"],
|
||||
json!(["grid-2x2"])
|
||||
);
|
||||
assert!(
|
||||
icon_spritesheet_request["properties"]["sliceLayout"]["description"]
|
||||
.as_str()
|
||||
.is_some_and(|description| description.contains("固定图集切片合同"))
|
||||
icon_spritesheet_request["properties"]["sliceCount"]["minimum"],
|
||||
json!(1)
|
||||
);
|
||||
let icon_style_schema = &parsed["components"]["schemas"]["EditorIconSpritesheetGenerationRequest"]
|
||||
["properties"]["style"];
|
||||
@@ -2635,11 +2630,7 @@ mod tests {
|
||||
["sliceWarning"]["anyOf"][0]["$ref"],
|
||||
"#/components/schemas/EditorIconSpritesheetSliceWarning"
|
||||
);
|
||||
assert_eq!(
|
||||
parsed["components"]["schemas"]["EditorIconSpritesheetGenerationResponse"]["properties"]
|
||||
["sliceLayout"]["enum"],
|
||||
json!(["grid-2x2"])
|
||||
);
|
||||
assert!(parsed["components"]["schemas"]["EditorIconSpritesheetGenerationResponse"]["properties"]["sliceCount"].is_object());
|
||||
assert_eq!(
|
||||
parsed["components"]["schemas"]["EditorImageGenerationResponse"]["properties"]["warning"]
|
||||
["anyOf"][0]["$ref"],
|
||||
|
||||
@@ -1365,6 +1365,7 @@ fn compact_external_api_generation_result(result: Value) -> Value {
|
||||
| "spritesheetHeight"
|
||||
| "iconImageSrcs"
|
||||
| "sliceLayout"
|
||||
| "sliceCount"
|
||||
| "frames"
|
||||
| "frameCount"
|
||||
| "frameWidth"
|
||||
|
||||
Reference in New Issue
Block a user