# T-WEB-013 动作内部标准与模型骨骼重绑定 - **状态**: done - **依赖**: T-WEB-006 / T-WEB-012 - **文档**: v1.6 §4.10 - **契约**: `skeleton-topology 0.2.0`、`data-models 0.7.0` ## 目标 1. **动作内部标准**:所有动作(BVH / VMD / FBX / glTF)统一预处理成 MediaPipe 33 点关键点序列(`mediapipe33@0.1.0`),客户端只认这一种; 2. **模型骨骼重绑定**:任意命名风格的模型都能被关键点驱动 —— 按**名字**映射到 canonical humanoid 骨骼,而不是要求模型符合某一种命名规范。 ## 方案 ### 动作内部标准 `preprocess.service.ts` 按扩展名分派解析器,统一产出 NDJSON 关键点序列 + 9 个关节角特征(108 维),与 Android 端推流结构完全同构 —— 因此客户端 DTW 的两侧输入是同一套特征。 ### 骨骼重绑定 | 项 | 做法 | |--|--| | 归一化 | `normalizeBoneName()`:保留 CJK 与假名(**不能再只留 `a-z0-9`**),全角 ASCII 折叠(`足IK` → `足ik`) | | 候选表 | `HUMANOID_CANDIDATES` 覆盖 VRM/VRoid、Mixamo、MMD 日文名、中文常见名 | | 生成而非手写 | Godot 端 `src/model/humanoid_map.gd` 的 CANDIDATES **由服务端表生成**,并有跨端一致性测试 | | 覆盖度 | `coverage()` / `missing_bones()` 暴露给审核界面与客户端 | > **踩坑记录**:第一版 `normalizeBoneName()` 只保留 `[a-z0-9]`,把 CJK 名字 > 整个抹成空串,导致 MMD 模型 humanoid 覆盖 **0/22**。修复后实测 > 奥黛塔 **21/22**、初音未来 **19/22**,两者 `drivable = true` 且 VRM 正常产出。 ## 验收 | 验收项 | 结果 | |--|--| | MMD(日文骨骼名)模型可驱动 | ✅ 实测 21/22、19/22 覆盖;客户端可正常驱动 | | Godot 端候选表与服务端一致 | ✅ 一致性测试(跨仓库生成 + 校验) | | 动作统一为 33 点 | ✅ `keypoint_format = mediapipe33@0.1.0`;Android 端推流同构 | ## 真实样例回归(T-WEB-006) `test/real-samples.spec.ts` 用仓库根 `示例文件/【动作】霜雪千年.vmd`(0.8 MB,真实舞蹈, 96.7 s)跑完整 `parseVmd → vmdToWorldFrames → worldFramesToKeypointFrames` 管线。 VMD 通路不依赖 Assimp,因此只要样例在就会真跑;样例缺失时整个 describe 跳过。 实测:`maxFrame = 2900` → **2901 帧**(30 fps / 96.7 s),轨道 31 条, 标准骨架 `MMD_SKELETON` 的 **31 根全部命中**(缺失 0),双侧腿部 IK 链 (`左/右足IK` + `ひざ` + `足首` + `つま先`)齐备,因此 IK 解算路径生效; 重定向 135 ms(0.05 ms/帧),产出 33 点 + 2 虚拟点 + 9 关节角,NDJSON 6.51 MB。 用例锁定的重绑定不变量: | 断言 | 实测 | |--|--| | 世界帧键名恰为 joint-map 候选名(MMD 名 → 英文规范名的重绑定结果) | 15 个键逐一比对 | | 直接绑定的 15 个索引置信度**只能**是 `1.0`(不许退化成推算值) | 0/11/12/13/14/15/16/23/24/25/26/27/28/31/32 → 全部 `[1]` | | 其余 18 个点由层级推算 | 全部 `[0.5]`,缺失(`0`)为 0 | | 骨盆归零 + 图像空间投影 | `max|xy| = 0.85 ≤ 1`,越界 0 | | 关节角(cos 值)有限且 ∈ [-1,1] | 范围 `[-1.000, 0.995]` | | NDJSON 往返一致 / 缩略图有效 | 2901 行往返一致;PNG magic `89504e470d0a1a0a` | | 动作非退化 | 左右腕 x 方向摆幅 > 0.1,鼻尖摆幅显著小于手腕 | > 性能注意:2901 帧 × 33 点逐元素 `expect` 会拖满 vitest 默认 5 s 超时。 > 用例改成先聚合违规项、最后只断言一次(保留具体帧号与下标),并显式给 > 大用例 120–300 s 超时。