Files
mtgodot-poc/audit/contracts/render.model-attachment.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

101 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# render.model_attachment —— Granny 模型、LOD、骨骼、武器、盾牌和发型挂点
状态:`PARTIAL`
本合同检查 40250 的模型实例生命周期、Granny 骨骼姿态、LOD 控制器、装备/发型挂点、资源换装、材质替换、碰撞/包围盒和清理语义。判断标准是模型数据来源、LOD 分档、姿态传播、子模型重新挂点、装备更新顺序、缺失资源回退和销毁顺序一致;“能加载模型”或“测试没有断言失败”不能单独视为与 40250 等价。
## 40250 参考调用链
- `EterGrnLib/ThingInstance.cpp` / `ThingInstance.h`
- `CGraphicThingInstance` 为每个 Granny LOD 持有 `CGrannyLODController`,模型实例和附着子模型在每个 LOD 上保持关联。
- `AttachModelInstance` 将子模型挂到父模型骨骼;切换活动 LOD 时要同步活动模型、动作和附着模型,而不是只替换一张渲染网格。
- `EterGrnLib/LODController.cpp` / `LODController.h`
- Actor LOD 使用 500cm 高度/5000cm 距离范围,距离分档的关键阈值为 500cm、1500cm、2500cm;选中模型后复制动作、刷新附着模型,并按距离调整动作 FPS。
- 控制器同时参与更新、变形、渲染、碰撞和包围盒计算;LOD 资源缺失时按 40250 的模型队列回退。
- `EterGrnLib/ModelInstance*.cpp`
- `CGrannyModelInstance::UpdateWorldPose` 采样骨骼姿态;`GetBoneMatrixPointer` / `GetCompositeBoneMatrixPointer` 为挂点提供骨骼矩阵;`SetParentModelInstance` 维护子模型父骨骼关系。
- `GameLib/ActorInstanceAttach.cpp`
- `PART_WEAPON``PART_WEAPON_LEFT``PART_HAIR``PART_ARMOR` 通过 RaceData 注册的 attaching bone 连接;缺少左手挂点时不伪造挂接。
- `GameLib/RaceData.cpp` / `RaceData.h` / `GameLib/ItemData.cpp`
- RaceData 解析基础模型、LOD 模型、挂点骨骼和 HairSkinItemData 提供装备模型、LOD 模型列表、材质与皮肤数据,换装时对所有 LOD 控制器生效。
## 当前端实现映射
- `extension/src/metin2_model.cpp` / `.h`
- 已加载 GR2/MSM、构造 Skeleton3D 和 CPU/GPU skin mesh,解析兄弟文件 `_lod_01/_02/_03.gr2`,提供发型、武器、盾牌和材质替换入口。
- 当前默认 LOD 距离为 `[18, 42, 90]` 米;`_set_lod` 只在一个基础骨骼上替换网格,并增加 0.18 秒 `LodGhost` 淡入淡出。它不是 40250 的多 `CGrannyModelInstance` 控制器切换,且未证明切换时动作、子模型、碰撞和所有挂点同步。
- 刚性武器/盾牌通过 `slot_pre * base_world_pose[bone]` 更新;该路径已有实际 03150 双手武器姿态失败,说明矩阵语义尚未达到参考实现。
- `extension/src/metin2_anim.cpp`
- 每个采样动作姿态后调用 `Metin2Model::update_weapon_pose`,当前能驱动本模型的武器/盾牌,但尚无完整 LOD 控制器级动作复制和附着子模型矩阵证据。
- `extension/src/gr2_bridge.cpp`
- 提供 GR2 文件、骨骼、材质、网格和姿态桥接;资源解析及单位/坐标转换是 Godot 适配层,不等同于 40250 的 Granny Thing/LOD 生命周期。
- `project/ui/player_view.gd` / `remote_player_view.gd` / `mob_view.gd`
- 已按角色、远端角色和怪物创建 `Metin2Model`,设置动作、武器/左手骨骼、发型和基础模型;远端与怪物共享部分 PlayerView 逻辑,但不同 Actor 的完整装备/LOD/事件消费矩阵还没有 40250 级别的逐分支证据。
- `project/ui/equip_model.gd`
- 已实现身体、发型/头部、武器的刷新顺序、武器双手判定、MSM HairData 与皮肤选择以及部分精炼效果;当前装备模型主要解析单个 GR2,`_is_poly()` 仍为固定 `false`,变身/遮挡/资源列表与 40250 的 ItemData/RaceData 语义不完全一致。
## 分支等价矩阵
| 40250 判据/副作用 | 当前实现 | 结论 |
|---|---|---|
| GR2/MSM、骨骼绑定姿态、CPU/GPU 变形 | `gr2_bridge` + `Metin2Model` + `Metin2AnimPlayer` 已能加载并采样真实 GR2 | 基础路径存在;GPU/CPU、材质边界和骨骼矩阵仍缺逐资产等价证明 |
| Actor LOD 阈值 500/1500/2500cm、模型队列和距离范围 | 默认 `[18,42,90]m`,即约 1800/4200/9000cm,并带自定义 15% hysteresis | 不等价;默认常量已与 40250 Actor 分档不同 |
| LOD 切换时复制动作、刷新装备/发型/盾牌挂点、碰撞和包围盒 | 只替换当前 `MeshInstance3D` 网格,保留一个骨骼并创建 `LodGhost` 淡入淡出 | 不等价;没有控制器级子模型和完整副作用同步 |
| 右手/左手骨骼解析及武器/盾牌挂接 | `player_view`/`equip_model` 注册骨骼,`update_weapon_pose` 更新刚性附件 | 部分等价;`weapon_attach_test.gd` 的 warrior/run/03150 左手握持距离为 0.75m,超过 0.12m 阈值 |
| 发型骨骼按名称重映射、HairSkin/SourceSkin→TargetSkin | `_load_hair` 重映射基座骨骼并合并 CPU/GPU mesh,支持 skin override | 部分等价;缺少对所有 LOD、材质数组和缺失骨骼回退的参考级证据 |
| Armor/weapon/hair 资源更新及多 LOD 生命周期 | `EquipModel` 按身体→头发→武器重建 Godot 子树 | 部分等价;重建顺序存在,但不是 RaceData/ItemData 驱动的全 LOD `Change*`/`SetMaterialImagePointer` 链 |
| 缺模型、缺骨骼、无左手挂点、缺 LOD 的回退 | 有 GR2/MSM 加载失败日志、骨骼索引 `-1` 和连续 `_lod_0N` 查找 | 部分等价;资源列表、非连续 LOD、子模型清理和缺失挂点的所有边界尚未对齐 |
| visual/collision/fly target bounds 与地面贴合 | 提供 visual AABB、骨骼 OBB、ground offset 和飞行目标 bounds | 部分等价;当前测试覆盖基础包围盒,但未证明 40250 LOD/碰撞控制器语义和动作切换后的 bounds 一致 |
| 删除、换图、死亡和重复换装时释放模型/材质/纹理 | `reload` 清理子节点、GR2、材质和缓存引用 | 部分等价;真实模型测试普遍出现 ObjectDB 泄漏,进程有 exit 139,清理不能作为已等价 |
## 已执行测试
- `project/weapon_attach_test.gd`:失败 1 项;warrior race 0 的 `run` 动作中 `03150.gr2` 左手到武器可见 AABB 距离为 0.750m,期望小于 0.12m;进程另有 exit 139 和 52 个 ObjectDB 泄漏。
- `project/gpu_lod_attachment_test.gd``failures=0``ghosts=48`;但有 48 个 ObjectDB 泄漏,且测试证明的是当前自定义网格/ghost 路径,不证明 40250 LOD 控制器等价。
- `project/gpu_pose_bounds_test.gd`8 个 race × 6 个 motion × 5 个 sample`failures=0`;有 48 个 ObjectDB 泄漏。
- `project/race_motion_assembly_test.gd`8 个 race × 6 个 motion × 3 个 sample`failures=0`;有 82 个 ObjectDB 泄漏。
- `project/equip_model_test.gd``project/remote_player_test.gd``project/mob_view_test.gd`:基础装备/远端角色/怪物模型断言通过;分别观察到 0、8、8 个 ObjectDB 泄漏,其中远端样本的 03150 武器和 00010 发型出现骨骼匹配告警。
- `project/rendering_scenario_test.gd`GPU 0/1 两种场景 `failures=0`;有 54 个 ObjectDB 泄漏。
- `project/fly_target_bounds_test.gd``project/mob_winding_test.gd`:断言通过,但仍存在模型资源/RID 清理告警。
- `project/animation_cache_test.gd``hot motion switches reuse decoded clips``hot switches reuse parsed MSA metadata` 失败各 1 项;统计为 hits=3/misses=3、msa_hits=3/msa_misses=3,进程 exit 139。该结果是模型动作资源生命周期的相关回归证据,不能视为挂接已通过。
## Deep audit round 2026-09-21
### Fresh regression results
- `weapon_attach_test.gd` 仍失败 1 项:warrior/run/03150 双手姿态左手挂点距离 0.750m,超过 0.12m 阈值;进程 exit 139,并泄漏 52 个 ObjectDB。
- `gpu_lod_attachment_test.gd``gpu_pose_bounds_test.gd``race_motion_assembly_test.gd` 的断言分别为 0 failures,但均 exit 139,分别泄漏 48、48、82 个 ObjectDB。
- `equip_model_test.gd` 断言通过;`remote_player_test.gd``mob_view_test.gd` 断言通过但各有 8 个 ObjectDB 泄漏并 exit 139`animation_cache_test.gd` 仍有 decoded clip/MSA metadata 热切换各 1 项失败,exit 139。
- `formats_map_test``formats_msa_test``formats_msm_test``net_bounds_test``net_entity_test``net_classic_session_test``net_classic_wire_test` 均退出码 0formats live A1 断言因未设置 `M2_ASSETS` 跳过。
### Static parity conclusion
- 40250 的 500/1500/2500cm Actor LOD 控制器、动作复制和每个 LOD 的附着模型生命周期仍未被当前端的 `[18,42,90]m` 单骨骼网格替换等价实现。
- 03150 左手姿态失败和 animation cache 失败是可重复的实现差异;不能用 GPU/CPU bounds 或基础装备测试通过掩盖矩阵和资源缓存问题。
- 当前端真实模型路径仍有大量 exit 139/ObjectDB 泄漏,换图、死亡、重复换装、LOD 切换和远端模型删除的清理语义尚不能视为等价。
### Active source group review
- 对 Visual Studio `EterGrnLib` 活跃编译单元逐组核对后,参考链补充确认:`CGraphicThing` 维护模型/动作资源,`CGraphicThingInstance` 管理多模型实例、LOD 注册、Attach/Detach、材质/高光重绑、motion、bounds 和清理;`CGrannyLODController` 在每个 LOD 上同步模型、动作、材质、骨骼、shadow/bounds`CGrannyModelInstance::UpdateWorldPose/UpdateWorldMatrices` 生成骨骼世界姿态;`CGrannyMaterial/Palette` 管理材质与 sphere map。
- 当前 `Metin2Model` 的单骨骼/网格替换和 `slot_pre` 附件路径没有覆盖上述每个控制器的状态传播;这组核心源码已静态核对,但 03150 挂点、LOD、动作缓存和资源清理的已知差异仍保持 `PARTIAL`
### Active GameLib attachment review
- `GameLib/ActorInstanceData.cpp``SetRace`/`SetShape`/`SetPart``AttachWeapon``UpdateAttachingInstances` 将 RaceData/ItemData 的 base、LOD、发型、武器/盾牌和挂接数据注册到多模型实例;`ActorInstance.h` 还暴露按 part 的 bone name、collision piece 和 effect attach 入口。
- 当前 `EquipModel`/`PlayerView` 能重建身体、头发、武器和 `slot_pre` 附件,但没有同等的多 `CGraphicThingInstance` 控制器传播;03150 左手姿态失败、LOD ghost 泄漏和动作缓存失败仍是可重复差异。
- 本轮完成活跃 `GameLib` actor 数据/挂接文件的静态核对;该证据进一步确认“单骨骼网格替换”不是 40250 的 1:1 实现,合同保持 `PARTIAL`
## 已确认差异和未验证项
1. 必须先统一 Actor LOD 常量、距离单位、分档边界和缺失 LOD 回退,再决定 Godot 网格替换是否能保留 40250 的可观察副作用。
2. 必须把 LOD 切换时的动作复制、武器/盾牌/发型/盔甲挂点、材质和 bounds 更新做成可观测状态,并用同一组真实 race/item/motion 资产与 40250 快照对照。
3. 必须定位 `03150.gr2``run` 姿态的根/局部变换差异,建立右手、左手、双手武器、盾牌、弓和远端角色的逐动作挂点矩阵测试;当前不能以固定 `slot_pre` 通过一组静态样本代替。
4. 必须覆盖非连续/缺失 LOD、缺骨骼、重复换装、死亡、换图和节点释放,并清除真实模型测试的 ObjectDB 泄漏与 exit 139。
5. 必须补齐 `_is_poly`、变身装备遮挡和 ItemData/RaceData 多模型资源列表的等价实现或明确其平台适配边界。
## 下一轮验证入口
- 先用参考常量和真实 `_lod_0N.gr2` 资产建立 LOD 分档快照,分别验证 0/500/1500/2500/5000cm 边界。
- 再对 03150、盾牌、弓、发型和远端 Actor 采集每个动作采样点的父骨骼/子模型矩阵与可见 bounds,定位坐标系或 authored root offset 差异。
- 最后修复模型测试清理/崩溃,再把装备换装、LOD 切换、跨图删除和资源复用纳入稳定回归门禁。