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

90 lines
7.9 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.
# 40250 客户端对拍缺陷发现与修复工程指南 (CLIENT-PARITY-AUDIT-AND-FIX-GUIDE)
> **目标**:建立系统化的排查、探针探测与修复流程,解决 Godot 客户端从 40250 移植过程中由于“管线实现不完整”、“只实现裸数据漏掉复合层”导致的隐性细节缺陷(典型案例如:桥梁可行走但高度采样遗漏 `.mdatr` 导致掉落穿透)。
---
## 一、问题本质剖析:为何会出现此类缺陷?
在 Metin2 40250 原版中,几乎所有物理、移动、战斗与视觉表现都是**“多层复合叠加”**的。在向 Godot 移植的早期原型阶段,很多系统往往只实现了最底层的第 1 层,缺少了第 2 层和第 3 层的复合逻辑:
```mermaid
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)** | • 桥梁、楼梯、门廊台阶悬空或掉落穿透<br>• 水陆交界处判断错误(干地游泳或踩深水不减速/不可钓鱼)<br>• 地下城/山洞进出门廊穿透掉落 | • `CMapOutdoor::GetHeight` 查询 `.mdatr` 中的 `heights` 网格多边形,与裸高度图取 `fMAX`<br>• `attr.atr` 中的 `ATTRIBUTE_WATER` 与水面多边形绝对高度差<br>• `CDungeonBlock::GetObjectHeight` | `GameLib/MapOutdoor.cpp`<br>`EterLib/AttributeInstance.cpp`<br>`GameLib/AreaTerrain.cpp` |
| **2. 物理与移动 (Physics & Locomotion)** | • 遇到陡坡时直接穿山或以相同速度瞬移上山<br>• 被怪物或技能击退(Knockback)时穿透墙体飞出地图<br>• 自动寻路走直线卡死在栅栏/岩石缝隙 | • `ActorInstanceSync.cpp` 坡度倾角与重力滑落向量计算<br>• `ActorInstanceCollisionDetection.cpp` 击退时沿障碍物法线切向滑动<br>• `CMapOutdoor::is_blocked` 的栅格阻挡光栅化与逐轴滑动降级 | `GameLib/ActorInstanceSync.cpp`<br>`GameLib/ActorInstanceCollisionDetection.cpp` |
| **3. 动作与挂点 (Animation & Attachment)** | • 武器/盾牌朝向偏转或拿反、脱离手部<br>• 骑马时双手未握缰绳或双腿穿透马鞍<br>• 更换头盔/发型时头部穿模或发色异常 | • `CItemData::GetItemScale` 与骨骼挂点矩阵 `m_matBone` 变换<br>• `InstanceBaseMotion.cpp` 的骑乘姿势 override (`COMBO_TYPE_HORSE`)<br>• `CRaceData::SetHair` 的 MSM 顶点隐藏机制与 Alpha Blending 管道 | `GameLib/ActorInstanceAttach.cpp`<br>`UserInterface/InstanceBaseMotion.cpp`<br>`GameLib/RaceData.cpp` |
| **4. 战斗与射弹 (Combat & Flying)** | • 弓箭/法球穿透墙体或直接打入地底<br>• 普攻挥刀动作还没劈下来伤害数值已提前跳出<br>• 怪物死后不立刻倒地或死亡姿势嵌入地面 | • `CFlyingInstance::Update` 中的 `BackgroundHit` 碰撞(与地面/建筑交点)<br>• `.msa` 中的 `MotionEvent`(命中帧 `HIT` 严格帧事件驱动)<br>• `ActorInstanceBlend.cpp` 死亡动作的地面投影与骨骼终帧冻结 | `GameLib/FlyingInstance.cpp`<br>`GameLib/ActorInstanceBattle.cpp`<br>`GameLib/ActorInstanceBlend.cpp` |
| **5. 交互与拾取 (Picking & Decal)** | • 鼠标点击桥面、屋顶或 NPC 拾取点偏移到桥下<br>• 地面范围技能光圈(Decal)漏到桥底或悬空 | • `CMapOutdoor::GetPickingPointWithRay` 射线先求交物体再求交地形<br>• `CTerrainDecal` 的多边形表面投影裁切 | `GameLib/MapOutdoor.cpp`<br>`GameLib/TerrainDecal.cpp` |
---
## 三、四大系统化排查手段(主动发现)
### 手段 1:全图几何与阻挡坐标探针(Headless Coordinate Probes
由于地图静态放置数据由 `areadata.txt``attr.atr` 完全定义,无需人工跑图,可由自动化脚本扫盘检测:
1. **特殊静态模型巡检**
- 提取地图上全部引用了含有 `.mdatr`(带有 `heights` 网格)的物体(桥梁、台阶、悬崖栈道等)。
- 在该物体几何中心上方向下打垂直射线或采样 `sample_height(x, z)`
- **断言**:采样高度必须落在物体表面标高区间内,与裸地形高度应有明显的阶跃高差;若 `sample_height == terrain_height`,直接报警并定位对应模型路径与坐标。
2. **地貌高度一阶导数(梯度突变)扫描**
- 按 0.5m 步长对可通行路网进行网格采样。
- 检测相邻点高度差:若连续两点高度差 $> 2.5\text{m}$ 且未标记 `ATTRIBUTE_BLOCK`,判定为地貌穿透或缝隙掉落点。
### 手段 240250 核心链路白盒对拍(Static Call-Graph Audit
以 40250 源码的关键函数为索引,走查当前客户端对应的实现:
- **查阅 40250 源码**`40250/Server Client TMP4/ClientVS22/source/`
- **重点审查模式**
- 40250 中包含 `fMAX(...)``min(...)`、多层分支判断的函数,当前客户端是否只实现了其中单条简单路径?
- 40250 中从资源读取参数的逻辑(如 `.msa` 帧事件、`.msm` 挂点、`.mdatr` 碰撞体),当前客户端是否被硬编码(hardcoded)常量替代?
### 手段 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 测试套件),任何一次打包或构建前运行,确保旧缺陷绝不回退。