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

12 KiB
Raw Blame History

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_WEAPONPART_WEAPON_LEFTPART_HAIRPART_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.gdfailures=0ghosts=48;但有 48 个 ObjectDB 泄漏,且测试证明的是当前自定义网格/ghost 路径,不证明 40250 LOD 控制器等价。
  • project/gpu_pose_bounds_test.gd8 个 race × 6 个 motion × 5 个 samplefailures=0;有 48 个 ObjectDB 泄漏。
  • project/race_motion_assembly_test.gd8 个 race × 6 个 motion × 3 个 samplefailures=0;有 82 个 ObjectDB 泄漏。
  • project/equip_model_test.gdproject/remote_player_test.gdproject/mob_view_test.gd:基础装备/远端角色/怪物模型断言通过;分别观察到 0、8、8 个 ObjectDB 泄漏,其中远端样本的 03150 武器和 00010 发型出现骨骼匹配告警。
  • project/rendering_scenario_test.gdGPU 0/1 两种场景 failures=0;有 54 个 ObjectDB 泄漏。
  • project/fly_target_bounds_test.gdproject/mob_winding_test.gd:断言通过,但仍存在模型资源/RID 清理告警。
  • project/animation_cache_test.gdhot motion switches reuse decoded clipshot 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.gdgpu_pose_bounds_test.gdrace_motion_assembly_test.gd 的断言分别为 0 failures,但均 exit 139,分别泄漏 48、48、82 个 ObjectDB。
  • equip_model_test.gd 断言通过;remote_player_test.gdmob_view_test.gd 断言通过但各有 8 个 ObjectDB 泄漏并 exit 139animation_cache_test.gd 仍有 decoded clip/MSA metadata 热切换各 1 项失败,exit 139。
  • formats_map_testformats_msa_testformats_msm_testnet_bounds_testnet_entity_testnet_classic_session_testnet_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/boundsCGrannyModelInstance::UpdateWorldPose/UpdateWorldMatrices 生成骨骼世界姿态;CGrannyMaterial/Palette 管理材质与 sphere map。
  • 当前 Metin2Model 的单骨骼/网格替换和 slot_pre 附件路径没有覆盖上述每个控制器的状态传播;这组核心源码已静态核对,但 03150 挂点、LOD、动作缓存和资源清理的已知差异仍保持 PARTIAL

Active GameLib attachment review

  • GameLib/ActorInstanceData.cppSetRace/SetShape/SetPartAttachWeaponUpdateAttachingInstances 将 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.gr2run 姿态的根/局部变换差异,建立右手、左手、双手武器、盾牌、弓和远端角色的逐动作挂点矩阵测试;当前不能以固定 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 切换、跨图删除和资源复用纳入稳定回归门禁。