Skip to content

ADR-110: IMmdModel 接口类型补全 — 上游 PR 计划

状态: 草案 · 待立项(作为「babylon-mmd 上游贡献登记册」总入口;条目 1 = IMmdModel 接口补全已立项,条目 2–11 = 跨 ADR / 研究候选 / 已否决 / 已延期,统一归集避免散落) 日期: 2026-07-14

决策者: Riku(联邦首席架构师 AI)、Jieling(人类侧首席架构师)

创建日期: 2026-07-14

来源: docs/research/babylon-mmd-api-analysis.md §3.1 接口缺口 / §五 P0

关联: ADR-064(IMmdModel 类型缺口即时止血,本地 module augmentation)、ADR-098(vmd-layers cast 消解,批次一)、ADR-187(babylon-mmd 剩余 API 调研)、ADR-188(PBR 材质构建器,草案)、ADR-056(WASM 运动层合成)、ADR-058(Shift-JIS 纹理解码)、ADR-029(WASM 物理调参)、ADR-083(碰撞摩擦/弹性)、ADR-024(SSS/ PBR 阻塞)、ADR-054(上游阻塞路线图)、ADR-016(gaze 手动计时)、ADR-085(脚部 IK 求解器暴露)

影响面: frontend/src/core/types.ts(本地 augmentation 待移除)、babylon-mmd 上游仓库(noname0310/babylon-mmd


上游贡献候选登记册(跨 ADR 汇总)

本 ADR 同时作为「向 noname0310/babylon-mmd 提 PR 的单一权威登记册」。各源 ADR 正文中的「提上游 PR / 推动上游 / fork」表述均归集于此,避免散落、口径不一。 导航入口见 docs/upstream/README.md(上游贡献文档区统一安放点)。 决策语义:✅ 已立项 = 采用提 PR;⏸ 延期/评估 = 本地已替代,远期可重议;❌ 已否决 = 本地方案更优或维护成本不可控;⛔ 上游阻塞 = 依赖上游自身路线图,非本项目范畴。

#来源 ADR建议的上游 PR / 贡献内容决策上游价值落地难度
1ADR-110IMmdModel 接口补全 setRuntimeAnimation / createRuntimeAnimation / currentAnimation已立项(本文详述)高(消 3 处 cast,社区通用)
2ADR-056MmdWasmRuntime 原生支持 MmdCompositeAnimation(合成下沉 WASM)⏸ 留作远期(Option A 否决,本地 JS 帧流合成)中(去运行时开销)
3ADR-058外层 zip/磁盘文件名 Shift-JIS/GBK 兜底(corruptIndex 修正 U+FFFD 乱码)❌ 已否决(本地「损坏映射」兜底,免维护 fork)中(根源修)中(需 fork)
4ADR-029暴露 WASM 物理 stiffness / damping / friction 运行时 API❌ 不追(本地仅用 0/1 刚体开关,已覆盖 100% 可用 API)高(WASM 内存 hack)
5ADR-083暴露碰撞体 friction / restitution 运行时 API❌ 封存(等上游或自行 hack WASM 内存)
6ADR-024PBR 材质 + morph 目标支持(SSS 次表面散射前置)⛔ 上游阻塞(非本项目范畴,待上游突破)高(解锁 SSS/PBR)极高
7ADR-054推动上游贡献(SSS / PBR 整体路线图)⏸ 评估中(只能等 / 推动上游)极高
8ADR-016暴露 beforePhysics / afterPhysics 钩子供手动计时(gaze 优化项)⏸ 优化项(当前 WASM frontBuffer 直写 + JS linkedBone 双路径已落地)
9ADR-085fork 暴露 ikSolver 字段及 solve()(脚部 IK 统一 JS/WASM 路径)⏸ 长期(方案 C WASM 手动 IK 已于 2026-07-26 落地,方案 A 降级)高(fork)
10ADR-188 / ADR-187PBRMaterialBuilder 落地(morph 兼容 PBR,根治 ADR-024 阻塞)📋 草案(P1 长期规划,独立专项 ADR-188)高(材质升级)
11研究 wind-affect-wasm-physics.mdfork babylon-mmd 在 MmdWasmPhysicssetWind(windForce: Vector3)(WASM Bullet 刚体受风)❌ 不追(本地方案 A+C:runtime 反射 + onBeforeRenderObservable 已覆盖;fork/PR 仅作兜底,与条目 4/5 同桶)低(边际收益,仅 MMD 骨髁物理受风)高(fork + WASM 内存)

登记册治理规则

  • 唯一入口:任何 ADR 出现「向 babylon-mmd 提 PR」类建议,必须在此登记册追加一行并回链源 ADR,不得在源 ADR 内自成体系。
  • 决策冻结点:条目 1 已采纳;条目 3/4/5/11 已否决/封存(本地替代方案已闭环,不再追溯);条目 2/8/9 为远期可重议;条目 6/7/10 受上游路线图或专项 ADR 约束。
  • 验收后清理:条目 1 上游合并后,按本文「步骤四」移除 core/types.ts augmentation;其余条目若未来启动 PR,从本登记册升级为独立实现计划。

问题

IMmdModel 接口(babylon-mmd/esm/Runtime/IMmdModel)缺少运行时方法/属性,导致项目在 3 个位置使用 as any / as unknown as 绕过类型检查(第 2 处已由 ADR-098 消解)。

类型缺口清单

#位置问题代码根因
1core/types.ts:70-73RuntimeModel = IMmdModel & { setRuntimeAnimation(...); createRuntimeAnimation(...); }IMmdModel 接口缺少 setRuntimeAnimationcreateRuntimeAnimation 方法
2vmd-layers.ts:577composite as unknown as IMmdBindableModelAnimation✅ ADR-098 已消解(激活 MmdCompositeRuntimeModelAnimation module augmentation 后直接传入)
3vmd-loader.ts:313(vmdLoader as unknown as { dispose?: () => void }).dispose?.()VmdLoader 无实例状态需释放,该 cast 是空调用;已同步删除
4frontend/src/scene/motion/vmd-loader.ts:148-149(inst.mmdModel as { currentAnimation?: ... }).currentAnimationIMmdModel 不含 currentAnimation 属性

当前止血方案

core/types.ts 通过 TypeScript module augmentation 本地声明合并:

typescript
// core/types.ts:106-110
export type RuntimeModel = IMmdModel & {
    setRuntimeAnimation(handle: Nullable<MmdRuntimeAnimationHandle>, updateMorphTarget?: boolean): void;
    createRuntimeAnimation(animation: IMmdBindableModelAnimation, retargetingMap?: { [key: string]: string }): MmdRuntimeAnimationHandle;
    currentAnimation?: Nullable<IMmdRuntimeModelAnimation>;
};

vmd-loader.ts 中一处 cast 仍存在(#4 currentAnimation),未纳入 augmentation。#3 经核实为无操作空调用,已在 vmd-loader.ts 中直接删除,不依赖上游 PR。


决策

noname0310/babylon-mmd 提交 PR,补全 IMmdModel 接口声明,验收合并后移除本地 augmentation。

选项

选项结论理由
A. 提上游 PR✅ 采用根治问题,消除 3 处 cast / augmentation,项目不再依赖本地 augmentation
B. 仅本地 augmentation 全覆盖❌ 否决临时止血已完成,长期应推动上游修复
C. 不动(维持现状)❌ 否决3 处 cast 在 babylon-mmd 升级时是静默断裂风险点

约束

PR 内容范围

修改目标需要添加的声明当前状态
IMmdModel 接口setRuntimeAnimation(handle: Nullable<MmdRuntimeAnimationHandle>, updateMorphTarget?: boolean): void缺失
IMmdModel 接口createRuntimeAnimation(animation: IMmdBindableModelAnimation, retargetingMap?: { [key: string]: string }): MmdRuntimeAnimationHandle缺失
IMmdModel 接口currentAnimation?: Nullable<RuntimeModelAnimation>缺失

不包含的范围

  • VmdLoader.dispose()——经核实 babylon-mmd VmdLoader 的 JS 实现中不存在 dispose() 方法,vmd-loader.ts 中对应的 cast 是空调用(调用了不存在的方法,静默忽略),已在项目内直接删除该 cast,不依赖上游 PR。
  • IMmdRuntimeBone.worldMatrix 类型——已在 IMmdRuntimeBone 中正确定义为 Float32Arrayproc-motion-bridge.ts 中的 as any 是冗余 cast,属项目内部清理,不涉及上游。
  • MmdCompositeAnimationIMmdBindableModelAnimation 兼容——已在 mmdCompositeRuntimeModelAnimation.d.ts 中通过 module augmentation 声明,无需重复提交。

⚠️ 架构注意事项

setRuntimeAnimation / createRuntimeAnimation / currentAnimation 目前仅存在于具体类 MmdModelMmdWasmModelIMmdModel 接口上未定义。PR 需与上游作者确认是否愿意将这些方法提升到 IMmdModel 通用接口中。

若上游认为这些是内部实现细节不愿暴露,则回退到本地 augmentation 全覆盖方案(见「回退方案」)。

验证方法

  1. 提交 PR 后,在项目 package.json 中临时指向 PR 分支("babylon-mmd": "noname0310/babylon-mmd#pr-xxx"
  2. 删除 core/types.tsRuntimeModel 的 intersection type,改为 type RuntimeModel = IMmdModel
  3. 删除 frontend/src/scene/motion/vmd-loader.tscurrentAnimation 的 cast
  4. 运行 npm run checktsc --noEmit)确认零类型错误
  5. 运行 npm run build 确认构建通过
  6. 运行 npm run test 确认单元测试无回归

实现计划

步骤一:fork 并本地验证(预估 1 天)

bash
# fork noname0310/babylon-mmd 到个人仓库
# clone 到本地
git clone https://github.com/<user>/babylon-mmd.git
cd babylon-mmd

# 在 src/Runtime/IMmdModel.ts 中添加:
#   setRuntimeAnimation(handle: Nullable<MmdRuntimeAnimationHandle>, updateMorphTarget?: boolean): void;
#   createRuntimeAnimation(animation: IMmdBindableModelAnimation, retargetingMap?: { [key: string]: string }): MmdRuntimeAnimationHandle;
#   currentAnimation?: Nullable<RuntimeModelAnimation>;

# 本地构建
npm run build

步骤二:项目内临时指向验证(预估 0.5 天)

json
// package.json 临时指向
"babylon-mmd": "file:../babylon-mmd/packages/core"

步骤三:提交 PR(预估 0.5 天)

  • 提交到 noname0310/babylon-mmd 主仓库
  • PR 描述中说明:
    • 缺失的 3 个声明项
    • 项目侧因此产生的 3 处 cast / augmentation(附代码位置)
    • 项目侧 module augmentation 的代码(证明最小修复集)
    • 类型兼容性验证结果(tsc --noEmit 通过)

步骤四:上游合并后清理(预估 0.5 天)

  • 更新 package.json 版本号(或指向合并后的 commit)
  • 删除 core/types.tsRuntimeModel 的 intersection type 定义
  • 清理 frontend/src/scene/motion/vmd-loader.tscurrentAnimation 的 cast
  • 删除 (inst.mmdModel as { currentAnimation?: ... }).currentAnimation → 改为 inst.mmdModel.currentAnimation

后果

正面

  • ✅ 消除 3 处 as any / as unknown as / augmentation,类型安全与可维护性提升
  • ✅ 移除本地 module augmentation,减少 core/types.ts 样板代码
  • ✅ 上游社区受益,其他 babylon-mmd 使用者不再遇到相同问题
  • ✅ babylon-mmd 升级时不再需要核查 augmentation 是否冲突

负面

  • ⚠️ 上游 PR 的合并时间不可控,可能需等待数个版本周期
  • ⚠️ 若上游拒绝(如作者认为 setRuntimeAnimation 是内部方法),需回退到本地 augmentation 全覆盖方案
  • ⚠️ PR 提交后 core/types.ts 的 augmentation 需保留直到上游版本发布,期间升级 babylon-mmd 需检查 augmentation 是否与新版冲突

回退方案

若上游 PR 长时间未合并(> 2 个版本周期),回退到本地 augmentation 全覆盖:

  • core/types.ts 中补全 currentAnimation 声明
  • 不在 vmd-loader.ts 中保留 as unknown as cast
  • 添加注释说明上游未合并,待日后清理

与 ADR-064 / ADR-098 的关系

ADR处理的内容与本 ADR 的关系
ADR-064本地 module augmentation 止血 RuntimeModel本 ADR 的上游 PR 合并后可移除 augmentation
ADR-098消解 vmd-layers.ts 的 composite cast已消解 1 处,剩余 3 处由本 ADR 处理
本 ADR上游 PR 根治剩余 3 处 + 移除本地 augmentation是 ADR-064 的长期方案 + ADR-098 的延续