- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
13 KiB
13 KiB
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对象高度,取对象/地形最大值。
- 以 200cm
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 上限探测地形,返回离射线起点更近的对象或地形命中。
- 先在 culling 对象上做
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对已加载区块插值。
- 已按 200cm 格、TL-BR 三角形和
extension/src/metin2_world.cpp::build_chunk- 为每区块建立地形 Mesh/HeightMapShape3D、属性对象
.mdatr碰撞体、对象高度三角形和水面 Mesh;区块根节点统一卸载。
- 为每区块建立地形 Mesh/HeightMapShape3D、属性对象
extension/src/metin2_world.cpp::sample_height- 合并裸地形和已加载对象
.mdatr高度,桥梁/平台命中时取更高表面;边界支持邻接区块的已加载对象查询。
- 合并裸地形和已加载对象
extension/src/metin2_world.cpp::sample_attribute/is_blocked- 读取 100cm 属性格;当前调用方主要使用
ATTRIBUTE_BLOCK和ATTRIBUTE_WATER的 bit0/bit1。
- 读取 100cm 属性格;当前调用方主要使用
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/胶囊近似。
- 通过每 1m 采样加二分寻找
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是两套并行碰撞来源;当前角色移动主要走属性逻辑,静态对象物理体主要服务射线/弹道,尚没有证明两者在斜坡、对象边缘、跨区块和卸载帧的结果与 40250isPhysicalCollision/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:真实 A1ATTRIBUTE_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、GodotHeightMapShape3D、逻辑is_blocked和对象 AABB/胶囊是并行近似,未统一成同一 culling/attribute query 链。 - 因此本轮完成
AttributeData、CollisionData、CullingManager、Decal的活跃源码静态核对,但没有把静态符号存在误记为 parity 通过;静态对象优先级、跨区块卸载和碰撞/贴花清理仍是PARTIAL。