- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
12 KiB
12 KiB
render.model_attachment —— Granny 模型、LOD、骨骼、武器、盾牌和发型挂点
状态:PARTIAL
本合同检查 40250 的模型实例生命周期、Granny 骨骼姿态、LOD 控制器、装备/发型挂点、资源换装、材质替换、碰撞/包围盒和清理语义。判断标准是模型数据来源、LOD 分档、姿态传播、子模型重新挂点、装备更新顺序、缺失资源回退和销毁顺序一致;“能加载模型”或“测试没有断言失败”不能单独视为与 40250 等价。
40250 参考调用链
EterGrnLib/ThingInstance.cpp/ThingInstance.hCGraphicThingInstance为每个 Granny LOD 持有CGrannyLODController,模型实例和附着子模型在每个 LOD 上保持关联。AttachModelInstance将子模型挂到父模型骨骼;切换活动 LOD 时要同步活动模型、动作和附着模型,而不是只替换一张渲染网格。
EterGrnLib/LODController.cpp/LODController.h- Actor LOD 使用 500cm 高度/5000cm 距离范围,距离分档的关键阈值为 500cm、1500cm、2500cm;选中模型后复制动作、刷新附着模型,并按距离调整动作 FPS。
- 控制器同时参与更新、变形、渲染、碰撞和包围盒计算;LOD 资源缺失时按 40250 的模型队列回退。
EterGrnLib/ModelInstance*.cppCGrannyModelInstance::UpdateWorldPose采样骨骼姿态;GetBoneMatrixPointer/GetCompositeBoneMatrixPointer为挂点提供骨骼矩阵;SetParentModelInstance维护子模型父骨骼关系。
GameLib/ActorInstanceAttach.cppPART_WEAPON、PART_WEAPON_LEFT、PART_HAIR、PART_ARMOR通过 RaceData 注册的 attaching bone 连接;缺少左手挂点时不伪造挂接。
GameLib/RaceData.cpp/RaceData.h/GameLib/ItemData.cpp- RaceData 解析基础模型、LOD 模型、挂点骨骼和 HairSkin;ItemData 提供装备模型、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 双手武器姿态失败,说明矩阵语义尚未达到参考实现。
- 已加载 GR2/MSM、构造 Skeleton3D 和 CPU/GPU skin mesh,解析兄弟文件
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 语义不完全一致。
- 已实现身体、发型/头部、武器的刷新顺序、武器双手判定、MSM HairData 与皮肤选择以及部分精炼效果;当前装备模型主要解析单个 GR2,
分支等价矩阵
| 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均退出码 0;formats 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 泄漏和动作缓存失败仍是可重复差异。 - 本轮完成活跃
GameLibactor 数据/挂接文件的静态核对;该证据进一步确认“单骨骼网格替换”不是 40250 的 1:1 实现,合同保持PARTIAL。
已确认差异和未验证项
- 必须先统一 Actor LOD 常量、距离单位、分档边界和缺失 LOD 回退,再决定 Godot 网格替换是否能保留 40250 的可观察副作用。
- 必须把 LOD 切换时的动作复制、武器/盾牌/发型/盔甲挂点、材质和 bounds 更新做成可观测状态,并用同一组真实 race/item/motion 资产与 40250 快照对照。
- 必须定位
03150.gr2在run姿态的根/局部变换差异,建立右手、左手、双手武器、盾牌、弓和远端角色的逐动作挂点矩阵测试;当前不能以固定slot_pre通过一组静态样本代替。 - 必须覆盖非连续/缺失 LOD、缺骨骼、重复换装、死亡、换图和节点释放,并清除真实模型测试的 ObjectDB 泄漏与 exit 139。
- 必须补齐
_is_poly、变身装备遮挡和 ItemData/RaceData 多模型资源列表的等价实现或明确其平台适配边界。
下一轮验证入口
- 先用参考常量和真实
_lod_0N.gr2资产建立 LOD 分档快照,分别验证 0/500/1500/2500/5000cm 边界。 - 再对 03150、盾牌、弓、发型和远端 Actor 采集每个动作采样点的父骨骼/子模型矩阵与可见 bounds,定位坐标系或 authored root offset 差异。
- 最后修复模型测试清理/崩溃,再把装备换装、LOD 切换、跨图删除和资源复用纳入稳定回归门禁。