- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
7.9 KiB
7.9 KiB
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.cppEterLib/AttributeInstance.cppGameLib/AreaTerrain.cpp |
| 2. 物理与移动 (Physics & Locomotion) | • 遇到陡坡时直接穿山或以相同速度瞬移上山 • 被怪物或技能击退(Knockback)时穿透墙体飞出地图 • 自动寻路走直线卡死在栅栏/岩石缝隙 |
• ActorInstanceSync.cpp 坡度倾角与重力滑落向量计算• ActorInstanceCollisionDetection.cpp 击退时沿障碍物法线切向滑动• CMapOutdoor::is_blocked 的栅格阻挡光栅化与逐轴滑动降级 |
GameLib/ActorInstanceSync.cppGameLib/ActorInstanceCollisionDetection.cpp |
| 3. 动作与挂点 (Animation & Attachment) | • 武器/盾牌朝向偏转或拿反、脱离手部 • 骑马时双手未握缰绳或双腿穿透马鞍 • 更换头盔/发型时头部穿模或发色异常 |
• CItemData::GetItemScale 与骨骼挂点矩阵 m_matBone 变换• InstanceBaseMotion.cpp 的骑乘姿势 override (COMBO_TYPE_HORSE)• CRaceData::SetHair 的 MSM 顶点隐藏机制与 Alpha Blending 管道 |
GameLib/ActorInstanceAttach.cppUserInterface/InstanceBaseMotion.cppGameLib/RaceData.cpp |
| 4. 战斗与射弹 (Combat & Flying) | • 弓箭/法球穿透墙体或直接打入地底 • 普攻挥刀动作还没劈下来伤害数值已提前跳出 • 怪物死后不立刻倒地或死亡姿势嵌入地面 |
• CFlyingInstance::Update 中的 BackgroundHit 碰撞(与地面/建筑交点)• .msa 中的 MotionEvent(命中帧 HIT 严格帧事件驱动)• ActorInstanceBlend.cpp 死亡动作的地面投影与骨骼终帧冻结 |
GameLib/FlyingInstance.cppGameLib/ActorInstanceBattle.cppGameLib/ActorInstanceBlend.cpp |
| 5. 交互与拾取 (Picking & Decal) | • 鼠标点击桥面、屋顶或 NPC 拾取点偏移到桥下 • 地面范围技能光圈(Decal)漏到桥底或悬空 |
• CMapOutdoor::GetPickingPointWithRay 射线先求交物体再求交地形• CTerrainDecal 的多边形表面投影裁切 |
GameLib/MapOutdoor.cppGameLib/TerrainDecal.cpp |
三、四大系统化排查手段(主动发现)
手段 1:全图几何与阻挡坐标探针(Headless Coordinate Probes)
由于地图静态放置数据由 areadata.txt 和 attr.atr 完全定义,无需人工跑图,可由自动化脚本扫盘检测:
- 特殊静态模型巡检:
- 提取地图上全部引用了含有
.mdatr(带有heights网格)的物体(桥梁、台阶、悬崖栈道等)。 - 在该物体几何中心上方向下打垂直射线或采样
sample_height(x, z)。 - 断言:采样高度必须落在物体表面标高区间内,与裸地形高度应有明显的阶跃高差;若
sample_height == terrain_height,直接报警并定位对应模型路径与坐标。
- 提取地图上全部引用了含有
- 地貌高度一阶导数(梯度突变)扫描:
- 按 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)常量替代?
- 40250 中包含
手段 3:未决协议与资源缺失日志审计(Log & Protocol Auditing)
在测试与实机运行期间,关注并收集控制台日志中的警告:
- 过滤关键 Warning:
unhandled packet、missing model、cannot find mdatr、mesh 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++ 引擎层解析
.mdatr的heights并求交平面),确保全服数十张地图、数百座桥梁与所有建筑台阶自动获得修复。
原则 2:路线 A(严格 40250 原版实现)
- 不凭空发明“更好”的经验算法,遇到模糊细节直接翻阅 40250 对应 C++ 模块源码,将原版的数学公式、坐标系旋转(Z-up 转 Y-up、米制转换)与浮点容差 1:1 映射。
原则 3:修一处问题,必补一个 Parity 自动化回归测试
- 修复缺陷后,必须在
project/下建立对应的test_*_parity.gd单元/集成测试(例如针对桥梁建立的test_bridge_height_parity.gd)。 - 将测试纳入持续集成验证集(134+ Parity 测试套件),任何一次打包或构建前运行,确保旧缺陷绝不回退。