Files
mtgodot-poc/audit/contracts/world.terrain-water-collision.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

118 lines
13 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.
# world.terrain-water-collision
## Scope
比较 40250 的户外地图高度、对象高度、属性阻挡、水面层、地形/对象拾取射线、桥梁和地面贴合调用链;同时核对加载失败、区块边界、地图切换、资源清理以及水面动画时序。
## Reference call chain
- `GameLib/MapOutdoor.cpp::GetTerrainHeight` / `GetHeight`
- 以 200cm `CELLSCALE` 在 `height.raw` 的 131×131 样本上按 TL-BR 对角线做三角插值。
- `GetHeight` 在非 terrain-only 模式下通过 culling 的 `CAttributeInstance::GetHeight` 合并桥梁、楼梯和平台等 `.mdatr` 对象高度,取对象/地形最大值。
- `GameLib/AreaTerrain.cpp::CTerrain::GetHeight`
- 使用 `HeightScale`、跨区块边界高度样本和固定的左/右三角分支;地图外返回 0。
- `GameLib/MapOutdoor.cpp::isAttrOn` / `GetAttr` / `CMapManager::isPhysicalCollision`
- 世界厘米坐标转换为 100cm 属性格;bit flag 使用按位判断,16 以上的属性值使用高半字节相等规则。
- `isPhysicalCollision` 只判断 `ATTRIBUTE_BLOCK`,并把 Metin2 的 Y 取反后交给地图属性查询。
- `PRTerrainLib/Terrain.cpp::LoadWaterMapFile` / `GameLib/AreaTerrain.cpp::GetWaterHeight`
- 读取 magic=5426、128×128 水区块、layer count 和 u16/u32 水高;水 id 映射到具体层高,错误加载会清空水图并返回失败。
- `MapOutdoorWater.cpp` 按水深计算顶点 alpha,并以 70ms/帧循环 30 张水纹理;水面共享 0 到 -15cm 的 1000~3000ms 高度扰动。
- `GameLib/MapOutdoor.cpp::GetPickingPointWithRay`
- 先在 culling 对象上做 `CAttributeInstance::Picking`,再以 5/10/100cm 自适应步长和 5000/10000/100000cm 上限探测地形,返回离射线起点更近的对象或地形命中。
- `EterLib/AttributeInstance.cpp::GetHeight` / `Picking`
- 对 `.mdatr` 高度三角形做 2D 包围盒、三角形和法向求交;对象命中包含可拾取的静态桥梁/平台,而不是只有网络角色。
## Current call chain
- `formats/terrain_files.cpp::load_height_map` / `load_attr_map` / `load_water_map`
- 读取 `height.raw`、`attr.atr` 和 `water.wtr`,验证尺寸、magic、数据长度并支持 u16/u32 水高。
- `formats/terrain_mesh.cpp::terrain_height_at` / `build_terrain_mesh`
- 已按 200cm 格、TL-BR 三角形和 `HeightScale` 生成 Godot 高度网格;`Metin2World::sample_height` 对已加载区块插值。
- `extension/src/metin2_world.cpp::build_chunk`
- 为每区块建立地形 Mesh/HeightMapShape3D、属性对象 `.mdatr` 碰撞体、对象高度三角形和水面 Mesh;区块根节点统一卸载。
- `extension/src/metin2_world.cpp::sample_height`
- 合并裸地形和已加载对象 `.mdatr` 高度,桥梁/平台命中时取更高表面;边界支持邻接区块的已加载对象查询。
- `extension/src/metin2_world.cpp::sample_attribute` / `is_blocked`
- 读取 100cm 属性格;当前调用方主要使用 `ATTRIBUTE_BLOCK` 和 `ATTRIBUTE_WATER` 的 bit0/bit1。
- `extension/src/water_builder.cpp::build_chunk_water` / `water_depth.h` / `water_motion.h`
- 每个水格保留四角深度 alpha,使用 400×HeightScale 的透明度公式、30 帧 shader 和共享高度扰动。
- `project/player_controller.gd::_ray_ground`
- 通过每 1m 采样加二分寻找 `sample_height` 地表;实体点击单独使用网络实体 AABB/胶囊近似。
- `project/net_world.gd::_ground_world` / `_blocked` / `_pushed_position`
- 网络实体和主角贴 `sample_height`;移动/击退通过 `is_blocked` 截断。
## Audit findings (2026-09-20)
### 已对齐的部分
- `terrain_height_at` 的厘米单位、200cm 格、TL-BR 对角线、`xdist <= ydist` 三角分支和 `HeightScale` 已与 `CTerrain::GetHeight` 对齐;真实 A1/C1 桥面测试证明 `.mdatr` 高度会优先于河床。
- `attr.atr` 的 2634 magic、256×256 数据、100cm 属性格和 bit0 阻挡/bit1 水面查询已有实现;移动、击退和钓鱼入口都使用同一属性读取路径。
- `water.wtr` 的 5426 magic、128×128 id、layer count、u16/u32 高度兼容和失败尺寸检查已有实现;水面四角深度、浅滩透明度、动画纹理与共享高度扰动已有回归。
- 桥梁、平台等 `.mdatr` 高度三角形已进入 `sample_height`,区块卸载由根节点统一回收;A1/C1 全地图高度探针未发现桥面掉到河床的回归。
### 材料差异
- 40250 的 `GetPickingPointWithRay` 同时比较静态对象 `CAttributeInstance::Picking` 和地形,并采用 5/10/100cm 自适应步长;当前 `PlayerController::_ray_ground` 只比较 `sample_height`,静态建筑/桥梁的射线命中没有进入点击结果。网络实体点击也只是节点 AABB/胶囊近似,尚未复用统一的世界拾取合同。
- 40250 暴露 `GetWaterHeight(int,int)`,钓鱼抛竿会把 600cm 目标点的 Z 设置为具体水层高;当前端 `get_fishing_rotation` 只验证 `ATTRIBUTE_WATER`,`_new_fishing` 没有读取水层高来生成等价的鱼钩目标位置。水面可见且钓鱼方向可判定,不代表抛竿位置已等价。
- `CTerrain::isAttrOn` 对 `byAttr >= 16` 使用高半字节相等规则;当前 `sample_attribute` 只返回原始字节、`is_blocked` 只支持 bit0,尚没有通用的 `is_attr_on` 兼容接口,因此 `BANPK` 以外的高半字节调用方缺少等价证据。
- 40250 的 `GetHeight` 会根据 `m_bEnableTerrainOnlyForHeight` 选择是否合并 culling 对象,并且对象高度查询、地形加载状态、地图坐标边界有明确返回分支;当前 `sample_height` 依赖区块已被实际加载以及 `objects_enabled/registry_ok`,资源未加载或 streaming 队列中时会暂时回退为裸地形,缺少“查询触发加载/延迟重试”的同等状态契约。
- Godot `HeightMapShape3D`/`.mdatr` 碰撞体和逻辑 `is_blocked` 是两套并行碰撞来源;当前角色移动主要走属性逻辑,静态对象物理体主要服务射线/弹道,尚没有证明两者在斜坡、对象边缘、跨区块和卸载帧的结果与 40250 `isPhysicalCollision`/`CAttributeInstance` 顺序一致。
- 当前水面视觉测试覆盖 geometry/material 和真实 A1 水深,但没有验证 `water id -> layer height -> fishing target Z` 的运行时链,也没有覆盖缺失水图后水状态清零、边界水格和地图切换期间 shader/motion 状态的完整清理矩阵。
- `TerrainDecal` 的高度贴花三角选择、`MapOutdoor` 的 normal/shadow/visibility 依赖和当前地形 patch 视锥剔除尚未形成可执行的功能合同;它们不阻塞当前移动主链,但不能因地形网格测试通过而宣称渲染/拾取完全一致。
## Equivalence matrix
| 项目 | 状态 | 备注 |
|---|---|---|
| Preconditions | PARTIAL | 资源尺寸/magic/区块加载已有检查;对象拾取、水层目标、高半字节属性和 streaming 未闭合 |
| Branch structure | PARTIAL | 高度、属性、水面主分支已映射;对象/地形射线选择、地图外、延迟加载和水失败分支未全等价 |
| Algorithms/formulas | PARTIAL | 高度插值、桥面取高、水深 alpha 已对齐;拾取步长、对象 picking 和水高接口不同 |
| State transition order | PARTIAL | 区块构建→地形/碰撞/水面→对象高度基本一致;加载队列、点击命中优先级、钓鱼目标 Z 顺序未闭合 |
| Constants/units | PARTIAL | 200cm terrain cell、100cm attr cell、600cm fishing probe、水深/动画常量已记录;射线 5/10/100cm 与当前 1m 采样不同 |
| Timing/event sources | PARTIAL | 水纹 70ms 和扰动时序已有实现;streaming、地图卸载、拾取/钓鱼事件的端到端时序证据不足 |
| Resource/data sources | PARTIAL | height/attr/water/mdatr 可读取;对象 culling、water layer 查询、terrain normal/decal/shadow 统一资源入口未完成 |
| Protocol side effects | PARTIAL | 属性阻挡/钓鱼方向会影响移动和发包;水层高度未进入抛竿副作用,静态对象点击也未统一 |
| Interruption/failure/cleanup | PARTIAL | 区块根节点卸载和基本加载失败已有路径;水状态、待处理拾取、streaming 中断和地图切换边界仍缺完整矩阵 |
## Regression evidence
- `build/formats/formats_map_test`:高度插值、地形网格、地图格式和资源边界;当前无 `M2_ASSETS` 时跳过 live A1 断言。
- `project/test_bridge_height_parity.gd`:真实 A1 两座桥的 `.mdatr` 桥面高度、河床回退和跨度采样。
- `project/test_all_map_height_probe.gd`:A1/C1 真实地图桥面高度、地形回退、属性阻挡和跨跨度采样。
- `project/water_reference_test.gd`:真实 A1 水格、四角深度 alpha、浅滩和水材质/动画结构。
- `project/fishing_water_test.gd`:真实 A1 `ATTRIBUTE_WATER`、600cm 方向扫描和地图外拒绝。
- `project/test_click_target_effect_parity.gd`:地面命中信号、网络实体点选和静态弹道障碍;该测试的地面段使用 mock world,不能替代 40250 静态对象 ray picking。
- `project/player_move_test.gd`、`project/test_skill_ground_fix.gd`:移动阻挡和角色/技能贴地回归。
## Deep audit round 2026-09-21
### Fresh regression results
- `test_bridge_height_parity.gd`、`test_all_map_height_probe.gd`、`water_reference_test.gd`、`fishing_water_test.gd`、`player_move_test.gd`、`test_skill_ground_fix.gd` 的断言均通过;A1 水面为 `failures=0`,15 pieces、212587 vertices。
- `test_click_target_effect_parity.gd` 断言通过,但进程报告 3 个 ObjectDB 和 1 个资源未释放;`fly_target_bounds_test.gd` 断言通过但 exit 139,并泄漏 48 个 ObjectDB。
- `formats_map_test`、`net_bounds_test`、`net_entity_test`、`net_classic_session_test`、`net_classic_wire_test` 均退出码 0;formats live A1 断言因未设置 `M2_ASSETS` 跳过。
### Static parity conclusion
- 高度插值、桥面取高、属性 bit0/bit1、水图读取和局部水面渲染结构继续与参考接近;这部分测试不能证明完整拾取/碰撞等价。
- 当前 `_ray_ground` 主要走 1m 采样的 `sample_height`,没有实现 40250 对静态 `.mdatr` 对象先做 `Picking`、再以 5/10/100cm 自适应步长比较对象与地形命中的统一射线链。
- 水面方向检测尚未把 `water id -> layer height -> fishing target Z` 接入抛竿副作用;高半字节属性规则、streaming 中延迟重试和对象/地形双碰撞源也未闭合。
- 点击/飞行和真实地图测试的泄漏/exit 139 表明跨图、区块卸载和特效/目标清理仍不能视为稳定等价。
### Active GameLib/PRTerrainLib terrain-core review
- `GameLib/MapOutdoor.cpp` 除高度/水面外还负责 terrain/area 初始化、environment 绑定、视锥、对象和地形 ray picking;`MapOutdoorQuadtree.cpp` 建立/销毁四叉树,`MapOutdoorIndexBuffer.cpp` 为不同 patch/warp 方向生成索引,`TerrainPatch.cpp` 同时维护硬件/软件地形顶点、水顶点、光照和 patch proxy。
- `MapOutdoorCharacterShadow.cpp` 创建角色 shadow render target/depth surface、切换 viewport/render target 后恢复旧状态;当前 Godot terrain/splat/WorldEnvironment 有视觉替代,但没有同一设备资源、patch LOD、四叉树和 shadow pass 生命周期。
- `PRTerrainLib/Terrain.h`/`TerrainType.h`/`TextureSet.cpp`/`TextureSet.h` 补充 height/normal/water/texture-set 的底层数据和纹理层状态。当前格式测试与 A1 水面测试覆盖读取结果,但未证明 TextureSet、patch、quadtree、shadow 与 streaming 卸载保持同一资源边界。
- 本轮完成上述 GameLib/PRTerrainLib 活跃地图源码静态核对;静态网格与水面测试不能替代对象 picking、patch/culling、shadow 和资源清理等价性。
## Status
`PARTIAL`。地形插值、桥面高度、属性阻挡、水面渲染和钓鱼方向已经有较强实现与真实地图证据;静态对象射线优先级、具体水层高度、高半字节属性、streaming 边界和统一碰撞/清理时序仍未达到 40250 的一致实现。
### Active EterLib collision-core review
- `EterLib/AttributeData.cpp`/`AttributeInstance.h`/`CollisionData.cpp`/`CollisionData.h` 提供静态属性数据、碰撞形状、法线/命中信息和实例级查询;`CullingManager.cpp` 负责范围、射线、可见性注册和对象集合维护;`Decal.cpp` 还参与地表贴花的对象/高度依赖。
- 这些源码确认参考端的碰撞/拾取不是单纯高度采样:对象注册、射线、属性实例和清理顺序共同决定桥梁/平台/遮挡结果。当前端的 `sample_height`、Godot `HeightMapShape3D`、逻辑 `is_blocked` 和对象 AABB/胶囊是并行近似,未统一成同一 culling/attribute query 链。
- 因此本轮完成 `AttributeData`、`CollisionData`、`CullingManager`、`Decal` 的活跃源码静态核对,但没有把静态符号存在误记为 parity 通过;静态对象优先级、跨区块卸载和碰撞/贴花清理仍是 `PARTIAL`。