写入侧产出 canonical kind,读取侧读时重派生自愈存量分类

- 写入侧:infer_canvas_export_asset_kind 直接返回 canonical kind(animation → character-animation、ui → ui-design、asset → image),不再让别名表替写入侧兜底
- 写入侧补防御性归一,并新增 canvas_export_asset_kind_is_always_canonical 逐分支钉死返回值为 canonical、且不再写回 ui / animation / asset
- 读取侧:gameCreationAppAssetCategory 加收窄覆盖规则——落盘 category 为 unclassified 且 kind 能派生出明确非 unclassified 分类时采用派生值
- 该规则不写迁移脚本、永久自愈:真机 57 条 ui 资产待归类从 58 降到 1,UI 交互从 2 升到 59,仅剩 code 类 game-entry 在待归类(分类表设计口径)
- 收窄条件把覆盖窗口压到最小:落盘值本身是明确分类时仍信任落盘值(保留用户在分类与标签面板手动设置的权威性)
- kind 派生结果本身即 unclassified 的 image / video / code / publication-material 不受影响,补断言钉住
- resourceCardPreviewRealManifest 由「记录缺陷」改为「守住修复」:断言归位后的 58 → 1 分布与 57 条 ui 全部落 UI 交互
- PRD 与 pitfalls 补记分类取值优先级、读时自愈规则、唯一盲区(手动把可明确分类的资产设为待归类会被覆盖)及写入侧 canonical 化
This commit is contained in:
2026-09-10 23:15:04 +08:00
parent b87120445c
commit 30acd9552c
5 changed files with 175 additions and 69 deletions
@@ -357,6 +357,8 @@ type UpdateProjectResourceCanvasLayoutResult =
实现状态(2026-09-10):资源画布分区口径是 manifest 资产的功能分类 `category``ui-interaction / character / scene / audio / document / unclassified`)加末尾独立的「项目版本」栏目,不再按扩展名或 mediaType 派生分区。`icon / icon-spritesheet / icon-spec / ui-design` 进入 UI 交互,`character / character-animation` 进入角色与对象,`scene` 进入场景与环境,`sound-effect / background-music / audio` 进入音频,`spec` 与合法 Agent 文本回执进入文档;`image / video / code / publication-material` 以及任务产物、导入附件进入待归类,只登记游戏代码的项目因此有可见栏目与卡片,不再出现四栏全空;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本。扩展名分类器只保留准入与卡片显示类型职责:无法识别的二进制任务产物和附件不进入资源画布。受控读取、中央聚焦、失败空态与媒体播放不改变 manifest 真相;编辑成功后只追加新的 asset 或版本子记录。既有布局 sidecar 的旧栏目坐标按读时归并继续生效,`x / y / manuallyPlaced` 原样保留。
分类取值优先级(2026-09-10 收口):落盘 `category` 是权威值(用户可在「分类与标签」面板手动设置),缺失或非法时按 `assets[].kind` 派生。唯一例外是读时自愈——落盘值为 `unclassified` 而该资产 `kind` 能派生出明确的非 `unclassified` 分类时采用派生值,用于修复历史上被系统误写成 `unclassified` 的存量数据(无需迁移脚本、永久自愈);`kind` 派生结果本身就是 `unclassified` 的(`image / video / code / publication-material`)仍信任落盘值。该例外的已知盲区是「用户手动把 kind 已能明确分类的资产设为待归类」会被覆盖,属有意接受的最小覆盖窗口。写入侧必须只产出 canonical kind(画板导出推断同样如此),别名表仅用于兼容存量数据。
资源身份固定使用 manifest asset ID、正式 version ID、Agent ID + run ID 或已导入资源稳定路径;显示标题、来源文案变化不得改变 `resourceId`,从而避免布局、依赖边、选择和聚焦状态因改名失效。
```ts
@@ -5163,3 +5163,13 @@
- 另一处必须成对维护:别名表有**两份实现**——TS 侧 `packages/shared/src/contracts/gameCreationApp.ts``GAME_CREATION_APP_LEGACY_ASSET_KINDS`(读投影用)与 Rust 侧 `server-rs/crates/shared-contracts/src/game_creation_app.rs``canonical_game_creation_app_asset_kind`(写入侧按 kind 派生 category 用)。只改一边就会让落盘 category 与读侧栏目互相矛盾。交叉守卫见 `apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts`(直接解析 Rust 源码比对)。
- 真机计数守卫见 `apps/ai-game-creator-shell/tests/resourceCardPreviewRealManifest.test.ts`
- 关联:`apps/ai-game-creator-shell/src-tauri/src/assets.rs``apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs``packages/shared/src/contracts/gameCreationApp.ts``server-rs/crates/shared-contracts/src/game_creation_app.rs`
## 读时重派生:为什么可以覆盖落盘 category,以及它的唯一盲区(2026-09-10)
- 规则(已拍板落地):`gameCreationAppAssetCategory` 在「落盘 `category === 'unclassified'` **且** 该资产 `kind` 能派生出明确的非 `unclassified` 分类」时采用派生值,其余情况信任落盘值。
- 为什么需要这条:`register_local_asset_entry``assets.rs`)命中同 `localPath` 的既有资产时只覆盖 `kind` / `media_type` / `source`**不重算 `category`**;于是历史上被写成 `unclassified` 的资产(典型是 `kind:"ui"` 因不在 canonical 目录而落到 `image → unclassified`)在补齐别名后**不会自愈**。选读时重派生而不是写迁移脚本:不需要迁移、且永久自愈(任何历史上被系统错判成 unclassified 的都会自动归位)。真机验证:`What do u wanna do kitten` 的待归类从 58 降到 1(只剩 code 类 `game-entry`),UI 交互从 2 升到 59。
- 为什么可以覆盖落盘值:落盘 `category` 的权威性来自「用户可在分类与标签面板手动设置」(`update_manifest_asset_classification_at``project/manifest.rs`)。收窄条件把覆盖窗口压到最小——只有当落盘值是 `unclassified`(即"没有明确分类")时才覆盖。
- **唯一盲区**:用户**手动**把一个 kind 已能明确分类的资产设成「待归类」时,该手动值会被覆盖。这是有意接受的取舍:「手动设为待归类」意图边缘,且 kind 已经表达了分类;而漏掉这条规则,所有历史误判都无法自愈。若将来产品需要"显式待归类",应改成在 manifest 里区分"未设置"与"显式 unclassified"(例如 `category` 缺省 vs 显式写入),而不是取消本条规则。
- 不受影响:`image` / `video` / `code` / `publication-material` 的 canonical 分类本身就是 `unclassified`,派生结果等于落盘值,规则不触发(已用断言钉住)。
- 写入侧已同步 canonical 化:`infer_canvas_export_asset_kind``assets.rs`)现在直接产出 canonical kind`ui → ui-design``animation → character-animation``asset → image`),不再依赖别名表兜底;`canvas_export_asset_kind_is_always_canonical` 用例逐分支钉死,别名表从此只承担存量兼容。
- 关联:`packages/shared/src/contracts/gameCreationApp.ts``gameCreationAppAssetCategory``apps/ai-game-creator-shell/src-tauri/src/assets.rs``apps/ai-game-creator-shell/tests/resourceCardPreviewRealManifest.test.ts`