Files
mtgodot-poc/docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight
  - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程
  - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题
  - 新增 test_bridge_height_parity.gd 自动化对拍测试
- 40250 怪物击杀经验动效:
  - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附
- 40250 客户端全系统功能对齐(Batches 1-31):
  - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试
- 文档沉淀:
  - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

7.9 KiB
Raw Blame History

40250 客户端对拍缺陷发现与修复工程指南 (CLIENT-PARITY-AUDIT-AND-FIX-GUIDE)

目标:建立系统化的排查、探针探测与修复流程,解决 Godot 客户端从 40250 移植过程中由于“管线实现不完整”、“只实现裸数据漏掉复合层”导致的隐性细节缺陷(典型案例如:桥梁可行走但高度采样遗漏 .mdatr 导致掉落穿透)。


一、问题本质剖析:为何会出现此类缺陷?

在 Metin2 40250 原版中,几乎所有物理、移动、战斗与视觉表现都是**“多层复合叠加”**的。在向 Godot 移植的早期原型阶段,很多系统往往只实现了最底层的第 1 层,缺少了第 2 层和第 3 层的复合逻辑:

flowchart TD
    subgraph 原版 40250 表现
        L1["基础数据层 (如: PRTerrain height.raw 裸高度图)"]
        L2["静态实体层 (如: CGraphicThingInstance / .mdatr 高度多边形)"]
        L3["动态状态层 (如: 骨骼动作高度、骑乘偏置、水体/浮力判定)"]
        L1 --> L2 --> L3 --> R1["最终真实表现 (如: 角色稳稳立于桥面)"]
    end

    subgraph 缺陷实现模式
        D1["基础数据层 (fmt::terrain_height_at)"]
        D1 --> R2["直接作为最终结果 (角色跌落到桥底裸地形)"]
    end

如果仅依赖玩家跑图偶然触发,发现成本极高且容易重复反弹。必须转为白盒走查、探针自动化与协议日志审计


二、高危缺陷领域清单与 40250 真实机制对照

领域 常见隐性缺陷表现 40250 原版真实机制(唯一真相源) 关键代码位置
1. 空间与地貌 (Terrain & Objects) • 桥梁、楼梯、门廊台阶悬空或掉落穿透
• 水陆交界处判断错误(干地游泳或踩深水不减速/不可钓鱼)
• 地下城/山洞进出门廊穿透掉落
CMapOutdoor::GetHeight 查询 .mdatr 中的 heights 网格多边形,与裸高度图取 fMAX
attr.atr 中的 ATTRIBUTE_WATER 与水面多边形绝对高度差
CDungeonBlock::GetObjectHeight
GameLib/MapOutdoor.cpp
EterLib/AttributeInstance.cpp
GameLib/AreaTerrain.cpp
2. 物理与移动 (Physics & Locomotion) • 遇到陡坡时直接穿山或以相同速度瞬移上山
• 被怪物或技能击退(Knockback)时穿透墙体飞出地图
• 自动寻路走直线卡死在栅栏/岩石缝隙
ActorInstanceSync.cpp 坡度倾角与重力滑落向量计算
ActorInstanceCollisionDetection.cpp 击退时沿障碍物法线切向滑动
CMapOutdoor::is_blocked 的栅格阻挡光栅化与逐轴滑动降级
GameLib/ActorInstanceSync.cpp
GameLib/ActorInstanceCollisionDetection.cpp
3. 动作与挂点 (Animation & Attachment) • 武器/盾牌朝向偏转或拿反、脱离手部
• 骑马时双手未握缰绳或双腿穿透马鞍
• 更换头盔/发型时头部穿模或发色异常
CItemData::GetItemScale 与骨骼挂点矩阵 m_matBone 变换
InstanceBaseMotion.cpp 的骑乘姿势 override (COMBO_TYPE_HORSE)
CRaceData::SetHair 的 MSM 顶点隐藏机制与 Alpha Blending 管道
GameLib/ActorInstanceAttach.cpp
UserInterface/InstanceBaseMotion.cpp
GameLib/RaceData.cpp
4. 战斗与射弹 (Combat & Flying) • 弓箭/法球穿透墙体或直接打入地底
• 普攻挥刀动作还没劈下来伤害数值已提前跳出
• 怪物死后不立刻倒地或死亡姿势嵌入地面
CFlyingInstance::Update 中的 BackgroundHit 碰撞(与地面/建筑交点)
.msa 中的 MotionEvent(命中帧 HIT 严格帧事件驱动)
ActorInstanceBlend.cpp 死亡动作的地面投影与骨骼终帧冻结
GameLib/FlyingInstance.cpp
GameLib/ActorInstanceBattle.cpp
GameLib/ActorInstanceBlend.cpp
5. 交互与拾取 (Picking & Decal) • 鼠标点击桥面、屋顶或 NPC 拾取点偏移到桥下
• 地面范围技能光圈(Decal)漏到桥底或悬空
CMapOutdoor::GetPickingPointWithRay 射线先求交物体再求交地形
CTerrainDecal 的多边形表面投影裁切
GameLib/MapOutdoor.cpp
GameLib/TerrainDecal.cpp

三、四大系统化排查手段(主动发现)

手段 1:全图几何与阻挡坐标探针(Headless Coordinate Probes

由于地图静态放置数据由 areadata.txtattr.atr 完全定义,无需人工跑图,可由自动化脚本扫盘检测:

  1. 特殊静态模型巡检
    • 提取地图上全部引用了含有 .mdatr(带有 heights 网格)的物体(桥梁、台阶、悬崖栈道等)。
    • 在该物体几何中心上方向下打垂直射线或采样 sample_height(x, z)
    • 断言:采样高度必须落在物体表面标高区间内,与裸地形高度应有明显的阶跃高差;若 sample_height == terrain_height,直接报警并定位对应模型路径与坐标。
  2. 地貌高度一阶导数(梯度突变)扫描
    • 按 0.5m 步长对可通行路网进行网格采样。
    • 检测相邻点高度差:若连续两点高度差 > 2.5\text{m} 且未标记 ATTRIBUTE_BLOCK,判定为地貌穿透或缝隙掉落点。

手段 2:40250 核心链路白盒对拍(Static Call-Graph Audit

以 40250 源码的关键函数为索引,走查当前客户端对应的实现:

  • 查阅 40250 源码40250/Server Client TMP4/ClientVS22/source/
  • 重点审查模式
    • 40250 中包含 fMAX(...)min(...)、多层分支判断的函数,当前客户端是否只实现了其中单条简单路径?
    • 40250 中从资源读取参数的逻辑(如 .msa 帧事件、.msm 挂点、.mdatr 碰撞体),当前客户端是否被硬编码(hardcoded)常量替代?

手段 3:未决协议与资源缺失日志审计(Log & Protocol Auditing

在测试与实机运行期间,关注并收集控制台日志中的警告:

  • 过滤关键 Warningunhandled packetmissing modelcannot find mdatrmesh has no height
  • 检查服务端发送的所有 GC 封包(如 GC_MOVE, GC_SYNC_POSITION, GC_ATTACK, GC_FLY_TARGETING),确认不仅有网络层的解析反序列化,更在表现层驱动了正确的物理与动画状态。

手段 4:自动化无头路网巡逻测试(Headless Patrol Suite

利用 Godot --headless 启动无头客户端:

  • 让角色沿关键地貌路网(广场 \to 跨河桥梁 \to 仓库台阶 \to 城门通道 \to 郊外野怪区)自动移动。
  • 监控指标:
    • 角色 Y 坐标是否发生非预期的下坠(掉水、穿地);
    • _blocked 是否产生虚假阻挡导致角色提前停步卡死;
    • 状态机是否在 walk / run / wait 之间异常震荡。

四、工程化修改与防回退治理原则

每次发现并修复类似细节问题时,必须严格执行以下三条工程原则:

原则 1:拒绝打补丁式硬编码(Treat the Root, Never Patch Coordinates

  • 禁止针对个别坐标做特殊条件判断(如 if x > 750 and x < 770: pos.y = 206.0)。
  • 必须在底层管线实现通用机制(如在 C++ 引擎层解析 .mdatrheights 并求交平面),确保全服数十张地图、数百座桥梁与所有建筑台阶自动获得修复。

原则 2:路线 A(严格 40250 原版实现)

  • 不凭空发明“更好”的经验算法,遇到模糊细节直接翻阅 40250 对应 C++ 模块源码,将原版的数学公式、坐标系旋转(Z-up 转 Y-up、米制转换)与浮点容差 1:1 映射。

原则 3:修一处问题,必补一个 Parity 自动化回归测试

  • 修复缺陷后,必须在 project/ 下建立对应的 test_*_parity.gd 单元/集成测试(例如针对桥梁建立的 test_bridge_height_parity.gd)。
  • 将测试纳入持续集成验证集(134+ Parity 测试套件),任何一次打包或构建前运行,确保旧缺陷绝不回退。