建立AGC资源语义类型契约
共享契约新增 canonical 资源类型目录和历史类型读时映射 角色动画派生结果写入 character-animation 主类型 补充角色动画落盘与 legacy 类型映射回归测试 更新资源自由画板技术方案的类型边界说明
This commit is contained in:
@@ -3950,9 +3950,14 @@ fn commit_resource_edit_asset_internal(
|
|||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let image_sequence_duration_ms = ledger.remote_sequence_duration_ms;
|
let image_sequence_duration_ms = ledger.remote_sequence_duration_ms;
|
||||||
|
let asset_kind = if input.edit_kind == LocalProjectResourceEditKind::CharacterAnimation {
|
||||||
|
"character-animation".to_string()
|
||||||
|
} else {
|
||||||
|
source.asset_kind.clone()
|
||||||
|
};
|
||||||
let asset = GameCreationAppAssetManifestEntry {
|
let asset = GameCreationAppAssetManifestEntry {
|
||||||
id: asset_id.clone(),
|
id: asset_id.clone(),
|
||||||
kind: source.asset_kind.clone(),
|
kind: asset_kind,
|
||||||
media_type: staged_media_type.to_string(),
|
media_type: staged_media_type.to_string(),
|
||||||
local_path: relative_path.clone(),
|
local_path: relative_path.clone(),
|
||||||
image_sequence_frames,
|
image_sequence_frames,
|
||||||
@@ -8967,6 +8972,72 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn character_animation_commit_uses_animation_asset_kind() {
|
||||||
|
let directory = tempfile::tempdir().expect("create animation commit fixture");
|
||||||
|
let root = directory.path();
|
||||||
|
init_local_game_project_at(root, PROJECT_ID, "角色动画提交类型测试")
|
||||||
|
.expect("initialize project");
|
||||||
|
let uploaded = upload_local_asset_at(
|
||||||
|
root,
|
||||||
|
"character-source.png",
|
||||||
|
"image/png",
|
||||||
|
&resource_editor_test_png(),
|
||||||
|
)
|
||||||
|
.expect("upload source image");
|
||||||
|
let manifest = read_existing_manifest_for_project(root).expect("read source manifest");
|
||||||
|
let source_asset = manifest
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.id == uploaded.id)
|
||||||
|
.cloned()
|
||||||
|
.expect("find source image");
|
||||||
|
|
||||||
|
let mut request = input(
|
||||||
|
root,
|
||||||
|
Uuid::new_v4().to_string(),
|
||||||
|
LocalProjectResourceEditKind::CharacterAnimation,
|
||||||
|
format!("asset:{}", source_asset.id),
|
||||||
|
);
|
||||||
|
request.source_asset_id = Some(source_asset.id.clone());
|
||||||
|
request.source_path = Some(source_asset.local_path.clone());
|
||||||
|
request.source_media_type = Some(source_asset.media_type.clone());
|
||||||
|
request.source_subtype = Some(source_asset.kind.clone());
|
||||||
|
let source = resolve_resource_edit_source(
|
||||||
|
root,
|
||||||
|
&read_existing_manifest_for_project(root).expect("reread manifest"),
|
||||||
|
&request,
|
||||||
|
)
|
||||||
|
.expect("resolve animation source");
|
||||||
|
let mut ledger = ledger_for(&request, &source, ResourceEditLedgerPhase::MediaDownloaded);
|
||||||
|
ledger.staged_media_type = Some("video/mp4".to_string());
|
||||||
|
ledger.staged_extension = Some("mp4".to_string());
|
||||||
|
ledger.remote_sequence_duration_ms = Some(4_000);
|
||||||
|
write_resource_edit_staging(
|
||||||
|
root,
|
||||||
|
&request.operation_id,
|
||||||
|
b"\0\0\0\x18ftypisom\0\0\0\0isomiso2",
|
||||||
|
)
|
||||||
|
.expect("stage animation preview");
|
||||||
|
|
||||||
|
let result = commit_resource_edit_asset(
|
||||||
|
root,
|
||||||
|
&request,
|
||||||
|
&source,
|
||||||
|
&request.prompt,
|
||||||
|
&request.asset_name,
|
||||||
|
&mut ledger,
|
||||||
|
)
|
||||||
|
.expect("commit animation derivative");
|
||||||
|
let derivative = result.asset.expect("animation derivative");
|
||||||
|
assert_eq!(derivative.kind, "character-animation");
|
||||||
|
assert_eq!(
|
||||||
|
derivative.source.generation_kind.as_deref(),
|
||||||
|
Some("character-animation")
|
||||||
|
);
|
||||||
|
assert_eq!(derivative.image_sequence_duration_ms, Some(4_000));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn platform_session_switch_waits_until_local_asset_commit_finishes() {
|
fn platform_session_switch_waits_until_local_asset_commit_finishes() {
|
||||||
let directory = tempfile::tempdir().expect("create commit account switch fixture");
|
let directory = tempfile::tempdir().expect("create commit account switch fixture");
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
### 栏目分页投影与设计文档缺失边界
|
### 栏目分页投影与设计文档缺失边界
|
||||||
|
|
||||||
- 栏目内容只由当前权威资源投影构造:manifest assets、已完成任务的 artifacts、用户已导入附件和持久化 Agent 文本回执。前端不得扫描项目目录自行发明资源。
|
- 栏目内容只由当前权威资源投影构造:manifest assets、已完成任务的 artifacts、用户已导入附件和持久化 Agent 文本回执。前端不得扫描项目目录自行发明资源。
|
||||||
|
- 资源依赖图的精确引用身份同时支持两种稳定索引:远端 `source.resourceId` 与本地 `local-asset:<manifest assetId>`。派生资源把 `referenceResourceIds` 写成本地 canonical 身份时,必须能解析到同一项目的唯一资源;候选去重后才允许建立一条 `asset-reference`,无法唯一解析的引用继续进入 `unresolvedReferenceResourceIds`,不得在前端猜测连线。
|
||||||
|
- AGC 侧共享契约维护 canonical 资产类型目录和历史读时映射:`game-background -> scene`、`character-art -> character`、`ui-prototype -> ui-design`、`art-spritesheet -> icon-spritesheet`、`art-spritesheet-slice -> icon`、`illustration/game-art -> image`。该目录只约束 AGC 新写入与投影,不要求现役 Web 美术画布同步改造;历史 manifest 保持原值,读取时可归一到 canonical 展示。
|
||||||
|
- 角色动画派生结果的 manifest `kind` 固定写 `character-animation`,`source.generationKind` 同步保留 `character-animation`,媒体仍以预览视频加正式序列帧登记。图片编辑等普通派生继续继承源资源语义 kind;后续如需改写为 canonical kind,只能在读取投影或显式迁移中完成,不重写历史 manifest。
|
||||||
- 固定栏目页始终为 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本`。若某栏目没有对应投影,Dock 仍显示该栏目并允许打开空画布;空画布只表达“当前没有已登记资源”,不得由 UI 补假数据。
|
- 固定栏目页始终为 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本`。若某栏目没有对应投影,Dock 仍显示该栏目并允许打开空画布;空画布只表达“当前没有已登记资源”,不得由 UI 补假数据。
|
||||||
- Direct Codex 的游戏生成链路以 `game/index.html`、`game/style.css`、`game/game.js` 作为代码资产登记;是否额外产出设计文档由当前任务和 Agent 决定,资源页只消费已登记结果。
|
- Direct Codex 的游戏生成链路以 `game/index.html`、`game/style.css`、`game/game.js` 作为代码资产登记;是否额外产出设计文档由当前任务和 Agent 决定,资源页只消费已登记结果。
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
canonicalGameCreationAppAssetKind,
|
||||||
createGameCreationAppManifest,
|
createGameCreationAppManifest,
|
||||||
createGameCreationAppSeedTasks,
|
createGameCreationAppSeedTasks,
|
||||||
GAME_CREATION_AGENT_CAPABILITIES,
|
GAME_CREATION_AGENT_CAPABILITIES,
|
||||||
GAME_CREATION_AGENT_RUN_MAX_PASSES,
|
GAME_CREATION_AGENT_RUN_MAX_PASSES,
|
||||||
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||||||
GAME_CREATION_AGENT_TOOL_CALL_MAX,
|
GAME_CREATION_AGENT_TOOL_CALL_MAX,
|
||||||
|
GAME_CREATION_APP_CANONICAL_ASSET_KINDS,
|
||||||
GAME_CREATION_APP_COMMANDS,
|
GAME_CREATION_APP_COMMANDS,
|
||||||
GAME_CREATION_APP_LIMITED_RUN_COMMANDS,
|
GAME_CREATION_APP_LIMITED_RUN_COMMANDS,
|
||||||
GAME_CREATION_APP_MANIFEST_SCHEMA_VERSION,
|
GAME_CREATION_APP_MANIFEST_SCHEMA_VERSION,
|
||||||
@@ -718,4 +720,24 @@ describe('AI 游戏创作 App 共享契约', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('maps legacy canvas asset kinds to canonical kinds', () => {
|
||||||
|
expect(GAME_CREATION_APP_CANONICAL_ASSET_KINDS).toContain(
|
||||||
|
'character-animation',
|
||||||
|
);
|
||||||
|
expect(canonicalGameCreationAppAssetKind('game-background')).toBe('scene');
|
||||||
|
expect(canonicalGameCreationAppAssetKind('character-art')).toBe(
|
||||||
|
'character',
|
||||||
|
);
|
||||||
|
expect(canonicalGameCreationAppAssetKind('ui-prototype')).toBe('ui-design');
|
||||||
|
expect(canonicalGameCreationAppAssetKind('art-spritesheet')).toBe(
|
||||||
|
'icon-spritesheet',
|
||||||
|
);
|
||||||
|
expect(canonicalGameCreationAppAssetKind('art-spritesheet-slice')).toBe(
|
||||||
|
'icon',
|
||||||
|
);
|
||||||
|
expect(canonicalGameCreationAppAssetKind('illustration')).toBe('image');
|
||||||
|
expect(canonicalGameCreationAppAssetKind('character')).toBe('character');
|
||||||
|
expect(canonicalGameCreationAppAssetKind('unknown-kind')).toBe('image');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -476,6 +476,57 @@ export interface GameCreationAppAssetManifestEntry {
|
|||||||
imageSequenceDurationMs?: number | null;
|
imageSequenceDurationMs?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GAME_CREATION_APP_CANONICAL_ASSET_KINDS = [
|
||||||
|
'image',
|
||||||
|
'scene',
|
||||||
|
'character',
|
||||||
|
'character-animation',
|
||||||
|
'icon',
|
||||||
|
'icon-spritesheet',
|
||||||
|
'icon-spec',
|
||||||
|
'ui-design',
|
||||||
|
'publication-material',
|
||||||
|
'spec',
|
||||||
|
'video',
|
||||||
|
'sound-effect',
|
||||||
|
'background-music',
|
||||||
|
'audio',
|
||||||
|
'document',
|
||||||
|
'code',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type GameCreationAppCanonicalAssetKind =
|
||||||
|
(typeof GAME_CREATION_APP_CANONICAL_ASSET_KINDS)[number];
|
||||||
|
|
||||||
|
const GAME_CREATION_APP_LEGACY_ASSET_KINDS: Record<
|
||||||
|
string,
|
||||||
|
GameCreationAppCanonicalAssetKind
|
||||||
|
> = {
|
||||||
|
'game-background': 'scene',
|
||||||
|
'character-art': 'character',
|
||||||
|
'ui-prototype': 'ui-design',
|
||||||
|
'art-spritesheet': 'icon-spritesheet',
|
||||||
|
'art-spritesheet-slice': 'icon',
|
||||||
|
illustration: 'image',
|
||||||
|
'game-art': 'image',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function canonicalGameCreationAppAssetKind(
|
||||||
|
value: string,
|
||||||
|
): GameCreationAppCanonicalAssetKind {
|
||||||
|
const normalized = value.trim();
|
||||||
|
const legacyKind = GAME_CREATION_APP_LEGACY_ASSET_KINDS[normalized];
|
||||||
|
if (legacyKind) return legacyKind;
|
||||||
|
if (
|
||||||
|
(GAME_CREATION_APP_CANONICAL_ASSET_KINDS as readonly string[]).includes(
|
||||||
|
normalized,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return normalized as GameCreationAppCanonicalAssetKind;
|
||||||
|
}
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
|
||||||
export const GAME_CREATION_RESOURCE_LAYOUT_SCHEMA_VERSION =
|
export const GAME_CREATION_RESOURCE_LAYOUT_SCHEMA_VERSION =
|
||||||
'game-creator-resource-layout.v1' as const;
|
'game-creator-resource-layout.v1' as const;
|
||||||
export const GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION = 9_007_199_254_740_991;
|
export const GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION = 9_007_199_254_740_991;
|
||||||
|
|||||||
@@ -499,6 +499,43 @@ pub struct GameCreationAppAssetManifestEntry {
|
|||||||
pub image_sequence_duration_ms: Option<u64>,
|
pub image_sequence_duration_ms: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 16] = [
|
||||||
|
"image",
|
||||||
|
"scene",
|
||||||
|
"character",
|
||||||
|
"character-animation",
|
||||||
|
"icon",
|
||||||
|
"icon-spritesheet",
|
||||||
|
"icon-spec",
|
||||||
|
"ui-design",
|
||||||
|
"publication-material",
|
||||||
|
"spec",
|
||||||
|
"video",
|
||||||
|
"sound-effect",
|
||||||
|
"background-music",
|
||||||
|
"audio",
|
||||||
|
"document",
|
||||||
|
"code",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn canonical_game_creation_app_asset_kind(value: &str) -> &'static str {
|
||||||
|
match value.trim() {
|
||||||
|
"game-background" => "scene",
|
||||||
|
"character-art" => "character",
|
||||||
|
"ui-prototype" => "ui-design",
|
||||||
|
"art-spritesheet" => "icon-spritesheet",
|
||||||
|
"art-spritesheet-slice" => "icon",
|
||||||
|
"illustration" | "game-art" => "image",
|
||||||
|
canonical => match GAME_CREATION_APP_CANONICAL_ASSET_KINDS
|
||||||
|
.iter()
|
||||||
|
.find(|candidate| **candidate == canonical)
|
||||||
|
{
|
||||||
|
Some(value) => value,
|
||||||
|
None => "image",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const GAME_CREATION_RESOURCE_LAYOUT_SCHEMA_VERSION: &str = "game-creator-resource-layout.v1";
|
pub const GAME_CREATION_RESOURCE_LAYOUT_SCHEMA_VERSION: &str = "game-creator-resource-layout.v1";
|
||||||
pub const GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION: u64 = 9_007_199_254_740_991;
|
pub const GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION: u64 = 9_007_199_254_740_991;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -125,7 +125,7 @@ export default defineConfig({
|
|||||||
'apps/ai-game-creator-shell/tests/**/*.test.tsx',
|
'apps/ai-game-creator-shell/tests/**/*.test.tsx',
|
||||||
'miniprogram/**/*.test.js',
|
'miniprogram/**/*.test.js',
|
||||||
'scripts/**/*.test.ts',
|
'scripts/**/*.test.ts',
|
||||||
'packages/shared/src/contracts/hostBridge.test.ts',
|
'packages/shared/src/contracts/*.test.ts',
|
||||||
'packages/shared/src/components/**/*.test.ts',
|
'packages/shared/src/components/**/*.test.ts',
|
||||||
'packages/shared/src/components/**/*.test.tsx',
|
'packages/shared/src/components/**/*.test.tsx',
|
||||||
'packages/shared/src/stores/**/*.test.ts',
|
'packages/shared/src/stores/**/*.test.ts',
|
||||||
|
|||||||
Reference in New Issue
Block a user