From 8a1917b7e148f44904ea0d2e0c42292ff8cdbc75 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Fri, 11 Sep 2026 12:16:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=88=86=E7=B1=BB=E8=87=AA?= =?UTF-8?q?=E6=84=88=E8=A7=84=E5=88=99=E7=9A=84=E5=A5=91=E7=BA=A6=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=EF=BC=9A=E6=94=B9=E6=96=AD=E8=A8=80=E5=B9=B6=E6=8A=8A?= =?UTF-8?q?=E7=9B=B2=E5=8C=BA=E8=A1=A5=E6=88=90=E6=98=BE=E5=BC=8F=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 resolves asset category and tags with legacy and forward compatibility 中 { kind: 'character', category: 'unclassified' } 的期望:由 'unclassified' 改为 'character' - 该断言是 09-09 引入的旧口径,与 2026-09-10 落地的分类自愈规则冲突;按拍板保留自愈规则(它修复真机 57 条 kind:ui 资产落待归类),因此精确更新断言而非放宽为 not.toBe - 在断言处补注释说明这是本批有意行为,并写明已知盲区:用户手动把可明确分类的资产设为待归类时会被覆盖 - 新增用例「自愈规则覆盖落盘 unclassified:这是显式接受的盲区」,覆盖 character / ui / art-spritesheet 三条具体映射 - 新增用例「派生结果本身就是 unclassified 的 kind 不受自愈规则影响,保持落盘值」,覆盖 image / video / code / publication-material 与未知 kind - 新增用例「落盘为明确分类时信任落盘值,不被 kind 派生结果覆盖」,覆盖 image+scene / character+audio / ui+document - 变异验证:临时移除自愈规则后上述 2 条断言失败(expected 'unclassified' to be 'character'),恢复后全部通过 --- .../src/contracts/gameCreationApp.test.ts | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/contracts/gameCreationApp.test.ts b/packages/shared/src/contracts/gameCreationApp.test.ts index 799e18eab..b69a12497 100644 --- a/packages/shared/src/contracts/gameCreationApp.test.ts +++ b/packages/shared/src/contracts/gameCreationApp.test.ts @@ -793,9 +793,13 @@ describe('AI 游戏创作 App 共享契约', () => { expect(gameCreationAppAssetCategory(legacy)).toBe('character'); expect(gameCreationAppAssetTags(legacy)).toEqual([]); + // 有意行为(2026-09-10 分类自愈批次):落盘 `unclassified` 而 kind 能派生出明确的 + // 非 `unclassified` 分类时,读取侧采用派生值。这条规则用于自愈历史上被系统误写成 + // `unclassified` 的存量(真机 57 条 `kind:"ui"` 资产),无需迁移脚本且永久生效。 + // 已知盲区:用户手动把可明确分类的资产设为「待归类」时该手动值会被覆盖,属有意接受。 expect( gameCreationAppAssetCategory({ ...legacy, category: 'unclassified' }), - ).toBe('unclassified'); + ).toBe('character'); expect(gameCreationAppAssetCategory({ ...legacy, category: 'audio' })).toBe( 'audio', ); @@ -836,4 +840,63 @@ describe('AI 游戏创作 App 共享契约', () => { ).toEqual(['主角', '战斗']); expect(normalizeGameCreationAppAssetTags([])).toEqual([]); }); + + /** + * 分类自愈规则的三条边界(2026-09-10 批次)。 + * + * 规则:落盘 `category === 'unclassified'` 且该资产 `kind` 能派生出明确的非 + * `unclassified` 分类时采用派生值;其余情况信任落盘值。目的是自愈历史上被系统误写成 + * `unclassified` 的存量(真机 57 条 `kind:"ui"` 资产因此从「待归类」归位「UI 交互」), + * 不写迁移脚本且永久生效。 + */ + it('自愈规则覆盖落盘 unclassified:这是显式接受的盲区', () => { + // 落盘 unclassified + kind 可明确分类 → 派生值覆盖落盘值。 + // 盲区:用户手动把可明确分类的资产设为「待归类」时同样会被覆盖,属有意接受的 + // 最小覆盖窗口;若将来产品需要「显式待归类」,应改为在 manifest 区分 + // 「未设置」与「显式 unclassified」,而不是取消这条规则。 + expect( + gameCreationAppAssetCategory({ + kind: 'character', + category: 'unclassified', + }), + ).toBe('character'); + expect( + gameCreationAppAssetCategory({ kind: 'ui', category: 'unclassified' }), + ).toBe('ui-interaction'); + expect( + gameCreationAppAssetCategory({ + kind: 'art-spritesheet', + category: 'unclassified', + }), + ).toBe('ui-interaction'); + }); + + it('派生结果本身就是 unclassified 的 kind 不受自愈规则影响,保持落盘值', () => { + // image / video / code / publication-material 的 canonical 分类就是 unclassified, + // 派生结果等于落盘值,规则不触发,必须继续信任落盘值。 + for (const kind of ['image', 'video', 'code', 'publication-material']) { + expect( + gameCreationAppAssetCategory({ kind, category: 'unclassified' }), + ).toBe('unclassified'); + } + // 无法识别的 kind 落到 image 兜底,派生结果同样是 unclassified。 + expect( + gameCreationAppAssetCategory({ + kind: 'unknown-kind', + category: 'unclassified', + }), + ).toBe('unclassified'); + }); + + it('落盘为明确分类时信任落盘值,不被 kind 派生结果覆盖', () => { + expect( + gameCreationAppAssetCategory({ kind: 'image', category: 'scene' }), + ).toBe('scene'); + expect( + gameCreationAppAssetCategory({ kind: 'character', category: 'audio' }), + ).toBe('audio'); + expect( + gameCreationAppAssetCategory({ kind: 'ui', category: 'document' }), + ).toBe('document'); + }); });