fix(models): 默认模型改用「压缩包 sha256」匹配(转换产物非字节级确定)
实测发现:同一份 zip 连续转换三次得到三个不同的产物 sha256(大小都是 1,152,464 字节,差异集中在 BIN chunk 的十几字节),说明 Assimp 的 GLB 导出不是字节级确定的。 因此原先把「转换产物 sha256」当作默认模型匹配键的设计是错的——换一次转换就失效。 - default-model.ts / default-model-source.ts:种子拆成 sourceSha256(上传压缩包的 sha256,稳定,主匹配键)与 fileSha256(产物 sha256,仅作记录) - models.service.ts:resolveDefaultModelId 依次按 压缩包 sha256 → 产物 sha256 → 名称 匹配,都落空才回落到 VRM 优先规则并告警 - ModelDto 增加 sourceSha256(非压缩包上传为 null) - scripts/import-default-model.mjs:写种子时用 sourceSha256;顺带修掉位置参数解析 (文件当第一个参数时识别不出,会打印用法) - 契约与文档同步:data-models §4.1、model-formats §1.1 记录该确定性结论; README / DSH / tasks/005 的「按产物 sha256 匹配」表述已更正 另附:把模型格式能力的实测矩阵写进 Release 说明(原生只支持 glTF 系; PMX→FBX 失败;VRM→GLB 丢 VRM 扩展;humanoid 骨骼命名覆盖率)。 验证:125 项单测通过;清理掉两个"文件只在本机临时目录"的模型(避免 sha256 去重 导致后续上传不落盘);种子按压缩包 sha256 命中后 defaultModelId 正确解析。
This commit is contained in:
@@ -363,14 +363,16 @@ describe('默认模型写入源码', () => {
|
||||
id: 16,
|
||||
name: '初音未来 Hatsune Miku',
|
||||
format: 'glb',
|
||||
sha256: 'a'.repeat(64),
|
||||
sourceSha256: 'a'.repeat(64),
|
||||
fileSha256: 'b'.repeat(64),
|
||||
convertedFrom: 'Hatsune Miku.pmx → GLB',
|
||||
updatedAt: '2026-09-15T00:00:00.000Z',
|
||||
});
|
||||
expect(content).toContain('export interface DefaultModelSeed');
|
||||
expect(content).toContain("name: \"初音未来 Hatsune Miku\"");
|
||||
expect(content).toContain('id: 16');
|
||||
expect(content).toContain('sha256: "' + 'a'.repeat(64) + '"');
|
||||
expect(content).toContain('sourceSha256: "' + 'a'.repeat(64) + '"');
|
||||
expect(content).toContain('fileSha256: "' + 'b'.repeat(64) + '"');
|
||||
expect(content).toContain('convertedFrom: "Hatsune Miku.pmx → GLB"');
|
||||
expect(content).toContain('export function hasDefaultModelSeed');
|
||||
});
|
||||
@@ -380,7 +382,8 @@ describe('默认模型写入源码', () => {
|
||||
id: null,
|
||||
name: '',
|
||||
format: '',
|
||||
sha256: '',
|
||||
sourceSha256: '',
|
||||
fileSha256: '',
|
||||
convertedFrom: null,
|
||||
updatedAt: null,
|
||||
});
|
||||
@@ -397,20 +400,23 @@ describe('默认模型写入源码', () => {
|
||||
id: null,
|
||||
name: '',
|
||||
format: '',
|
||||
sha256: ' ',
|
||||
sourceSha256: ' ',
|
||||
fileSha256: '',
|
||||
convertedFrom: null,
|
||||
updatedAt: null,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(hasDefaultModelSeed(DEFAULT_MODEL_SEED)).toBe(
|
||||
DEFAULT_MODEL_SEED.sha256.trim().length > 0,
|
||||
DEFAULT_MODEL_SEED.sourceSha256.trim().length > 0 ||
|
||||
DEFAULT_MODEL_SEED.fileSha256.trim().length > 0,
|
||||
);
|
||||
expect(
|
||||
hasDefaultModelSeed({
|
||||
id: 1,
|
||||
name: 'x',
|
||||
format: 'glb',
|
||||
sha256: 'b'.repeat(64),
|
||||
sourceSha256: 'b'.repeat(64),
|
||||
fileSha256: '',
|
||||
convertedFrom: null,
|
||||
updatedAt: null,
|
||||
}),
|
||||
@@ -422,7 +428,8 @@ describe('默认模型写入源码', () => {
|
||||
id: 3,
|
||||
name: '带"引号"与\\反斜杠的模型',
|
||||
format: 'glb',
|
||||
sha256: 'c'.repeat(64),
|
||||
sourceSha256: 'c'.repeat(64),
|
||||
fileSha256: 'd'.repeat(64),
|
||||
convertedFrom: 'a.pmx → GLB',
|
||||
updatedAt: '2026-09-15T00:00:00.000Z',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user