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

119 lines
14 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.map_load_transition
## Scope
审计 40250 地图 `Leave/Clear/Load/Enter`、玩家坐标驱动的区域加载、terrain/property/object/environment 初始化、区域卸载,以及当前 `Metin2World``GameScene` 的换图、失败和恢复行为。目标是比较可观察的生命周期与资源/碰撞边界,不要求 Godot 复制 DirectX 线程实现。
## Reference implementation and evidence
- `../40250/Server Client TMP4/ClientVS22/source/GameLib/MapManager.cpp`
- `LoadMap` 先调用当前 `CMapOutdoor::Leave()`,设置 map name、`LoadProperty()`,按地图类型加载;户外地图 `Load(x,y,z)` 成功后注册/设置 environment,最后 `Enter()`
- `UnloadMap` 校验当前 map name 后 `Clear()`;不匹配时失败,不清理另一张地图。
- `UpdateMap` 把玩家位置交给 `CMapOutdoor::Update`,触发区域/terrain 窗口更新。
- `../40250/Server Client TMP4/ClientVS22/source/GameLib/MapOutdoorLoad.cpp`
- `CMapOutdoor::Load``Destroy`,读取 `Setting.txt`,建立 terrain patch/quadtree/water/shadow,调用 `Update(x,y,z)`,再确定本地或 `d:/ymir work/environment/``.msenv` 路径。
- `LoadTerrain` 要求 `AreaProperty.txt` 的脚本类型和字段,按区域加载 water/height/attr/tile/shadow/minimap,并生成 ready terrain。
- `LoadArea` 按区域目录加载 object/area 数据;请求重复区域时不重复创建。
- `../40250/Server Client TMP4/ClientVS22/source/GameLib/MapOutdoorUpdate.cpp`
- 首次或跨 `LOAD_SIZE_WIDTH` 窗口时加载周边 terrain/area`AssignTerrainPtr` 绑定 3×3 指针窗口;移动时更新 terrain patch、area 和延迟垃圾回收。
- 区域离开窗口后先放入 delete vector,按回收间隔销毁,不是瞬间删除所有资源。
- `../40250/Server Client TMP4/ClientVS22/source/GameLib/AreaLoaderThread.cpp`
- terrain/area 请求进入线程安全队列,由 loader thread 读取资源,主线程 `Fetch` 后再接入地图;请求队列和 shutdown 有独立生命周期。
## Current implementation and evidence
- `extension/src/metin2_world.cpp`
- `load_map` 一开始 `unload_map()`,解析 `setting.txt`、建立 AssetResolver/Property registry、按 `load_radius` 构建 chunk、解析 `.msenv`;失败时保留 `setting_ok=false` 并返回 false。
- `load_radius < 0` 时同步构建整图;否则先 `stream_update()`,再在 `load_map` 内把整个队列同步建完,之后 `_process` 才按 `stream_budget` 增量构建新 focus 范围。
- `stream_update` 以方形 tile 半径加载/卸载 chunk,卸载立即从 vector 移除并 `queue_free` root;没有 40250 的 terrain/area delete vector 与延迟垃圾回收语义。
- `unload_map` 清空 chunk/队列/ambience、queue_free objects/environment,重置统计和错误状态;没有 load generation/token 防止旧异步结果回灌。
- `project/game_scene.gd`
- 初次 setup 先解析 map path、创建 fallback lighting,再显式 `_build_world`,地图失败只 warning 并继续平地运行。
- `_on_world_reset` 清理 NetPlay、NetWorld、掉落物、临时 UI 和 BGM;之后等待/接收 warp,再由 `_reload_map_for_warp` 创建新 world。
- `_reload_map_for_warp` 先保存 `old_world`,调用 `_build_world` 完整加载新 world,之后才对旧 world `queue_free`;旧 world 在新 world 失败时也可能仍作为有效场景引用存在。
- 同图 warp 仅放置本地主角;跨图 warp 才重建 world。地图切换后重新绑定 camera/player/net_world/HUD/atlas,但网络实体刷新与 world 加载之间没有原版 `Enter` 阶段契约。
- `project/net_play.gd` / `project/net_world.gd`
- world reset 清除当前 Actor、移动/战斗/投射物/临时 UI 生命周期;场景层通过 `queue_free` 延迟删除节点。
- reset 前后没有与 `Metin2World` 共用的 generation;旧网络包或旧模型回调如果晚于新 world,依赖各模块自己的 guard。
## Reference-to-current call chain
| 40250 | 当前端 |
|---|---|
| `CMapManager::LoadMap` -> `Leave` -> `LoadProperty` -> `CMapOutdoor::Load` -> `Register/SetEnvironment` -> `Enter` | `GameScene::_reload_map_for_warp` -> `_build_world` -> `Metin2World::load_map` -> `_place_player_at_net_pos` |
| `CMapOutdoor::Load(x,y,z)` -> `Update` -> terrain/area 3×3 window | `load_map` -> `load_radius` 方形 chunk queue;首轮队列在 load_map 内同步建完 |
| `Update` -> `AssignTerrainPtr` -> `UpdateTerrain/UpdateAreaList` -> 延迟垃圾回收 | `set_focus_position` -> `stream_update` -> `_process` build/unloadunload 立即 queue_free |
| `UnloadMap(name)` 校验 name -> `Clear` -> map resources reset | `unload_map` 无 map-name 校验,直接清当前 world |
| loader thread 请求/Fetch/Shutdown | `stream_queue` 在主线程逐帧处理,没有独立异步加载线程或旧请求取消 token |
## Branch matrix
| 场景 | 40250 行为 | 当前端行为 | 结论 |
|---|---|---|---|
| 首次加载 | 读取 Setting/Property,按玩家坐标加载周边 terrain/area,设置 environment 后 Enter | 读取 setting/registry,按 load radius 建 chunk,再建 `.msenv`/fallback | PARTIAL:资源阶段接近,入口/ready 语义不同 |
| 同图 warp | 保持当前 map,更新背景位置并更新区域窗口;Loading 阶段同步 Actor/临时对象清理 | 只放置主角;网络 reset 由前置 world_reset 处理 | PARTIAL:地图与网络阶段未形成同一状态机 |
| 跨图 warp | 旧 map Leave/Clear 后再 Load 新 map,旧资源不与新 map 长时间共存 | 新 world Load 成功后才 queue_free 旧 world | GAP:存在双 world/双环境/资源峰值和旧引用窗口 |
| Setting 缺失/格式错误 | `LoadSetting`/`Load` 失败;MapManager 返回 false,调用方决定是否进入下一阶段 | `load_map` 返回 falseGameScene warning 后继续平地 | PARTIAL:失败可见性和后续网络行为不等价 |
| TextureSet/区域文件缺失 | 记录失败并影响 terrain/area ready;周边窗口仍有明确失败状态 | chunk 可单独失败并继续;缺失对象以统计项/空节点容忍 | ADAPTATION/PARTIAL:需要证明角色、碰撞和可见性边界一致 |
| environment 缺失 | 仍通过 environment data name/注册流程;设置失败由原版环境 API报告 | `.msenv` 失败后 fallback lighting;跨图可能保留旧 `_sun/_env` | PARTIAL:旧环境释放和失败回滚未闭合 |
| streaming 进入新 tile | 3×3 指针窗口、请求新 terrain/area、旧区域进 delete vector | 以方形 radius 立即卸载并重新入队,`stream_budget` 逐帧构建 | PARTIAL:窗口公式相近,时序/回收不同 |
| 重复 warp / 中断加载 | map manager 当前状态和 loader queue 有明确销毁/重建边界 | 无显式 warp generation;新旧 `_build_world` 和 delayed queue_free 可交错 | PARTIAL |
| map name 不匹配卸载 | 拒绝卸载并保留当前 map | world 实例无 map-name 校验,直接清理 | GAP |
## Equivalence matrix
| 项目 | 状态 | 证据/缺口 |
|---|---|---|
| Preconditions | PARTIAL | path/assets/setting 检查已存在;Unload 的 map-name 校验和 ready gate 不同 |
| Branch structure | PARTIAL | 同图/跨图、失败、重复 warp、缺 environment 和中断加载尚未用同一状态 fixture 覆盖 |
| Algorithms/formulas | PARTIAL | cm/m 坐标、base position、chunk tile 和 height/attr 查询已有转换;区域窗口与 `LOAD_SIZE_WIDTH` 的精确边界尚未逐格证明 |
| State transition order | GAP/PARTIAL | 40250 `Leave -> Load -> Environment -> Enter`;当前新旧 world 共存且旧 world 后释放 |
| Constants/units | PARTIAL | `CHUNK_CM``CM_TO_M`、map base、focus tile、load radius 与原版 terrain/cell/patch 常量需要 fixture |
| Timing/event sources | PARTIAL | 参考端 loader thread + manager update;当前 Godot `_process` budget,异步回调/旧请求取消未等价 |
| Resource/data sources | MAPPED | setting、property、textureset、height/attr/water/tile/shadow、object、environment 的源文件均已定位 |
| Protocol side effects | PARTIAL | GC_WARP/world_reset/主角放置已接线,但 map ready、Actor burst 和 Enter 的相对时序未闭环 |
| Interruption/failure/cleanup | PARTIAL | `queue_free`、fallback、重复 warp、旧 world 引用、stream queue 和失败重试缺少完整证据 |
## Required tests
- 同图 warp:不创建第二个 World,清理 Actor/掉落物/飞行物/临时 UI 后主角和 focus tile 位置正确。
- 跨图 warp:记录 Leave/reset/build/Enter 的事件顺序,断言旧 world 不与新 world 长时间共存,且失败时旧 world 保留策略明确。
- 缺失/格式错误的 setting、textureset、terrain/area、object、`.msenv`;区分 hard failure、fallback 和可继续运行的缺失资源。
- 重复 warp、快速连续 warp、加载中再次 reset、旧 world `queue_free` 尚未执行时收到旧实体/模型回调。
- focus tile 边界逐格验证:load/unload 集合、height/attr/water/collision、对象/树/ambience 和实体可见性保持一致。
- 加载成功后的 map-ready/主角放置/GC_MAIN_CHARACTER 顺序,以及失败后重试同图和跨图的状态复位。
## Test evidence
- `project/gamescene_test.gd`:通过,验证缺图 fallback、GameScene 装配、主角定位、world reset 和实体镜像;它不证明 40250 的 Leave/Enter 顺序。
- `project/p9_test.gd`:通过,验证小地图、昼夜、天气和 warp signal 接收;它不创建真实 world transition。
- `project/forest_map_render_test.gd`:这是由 `script/forest_map_render_test.sh`/客户端主循环启动的 Node 脚本,不能用 `godot --headless --script` 直接当 SceneTree 测试;本轮直接启动会被 Godot 拒绝,故不得计为通过。
## Deep audit round 2026-09-20
本轮对 `MapManager::LoadMap/UnloadMap/UpdateMap``Metin2World::load_map/stream_update/_process/unload_map` 做了实现级复核:
1. 40250 的 `LoadMap` 顺序是当前 map `Leave` → 设置 map name/加载 Property → `CMapOutdoor::Load(x,y,z)` → 注册并设置环境 → `Enter`;当前 `load_map()` 先无条件 `unload_map()`,然后同步解析 setting/AssetResolver/Property,再按半径把整个 `stream_queue` 建完,最后才解析 `.msenv`。因此当前没有 reference 的 Leave/Enter/ready 边界,且 streaming 初始加载并非真正逐帧。
2. 当前 `stream_update()` 立即 `queue_free` 半径外 chunk,并清空再重建待加载队列;40250 `Update` 通过 3×3 terrain/area 指针窗口和 delete vector 延迟回收。当前在 focus tile 边界快速往返时,旧 chunk 的节点仍可能处于 Godot queue_free 期,同时新 chunk 已入队,资源/碰撞峰值和事件时序不同。
3. 40250 `UnloadMap(name)` 会校验传入 map name,不匹配时拒绝清理;当前 `Metin2World::unload_map()` 无 map-name 参数,任何调用都直接清空当前 world。GameScene 的 `_reload_map_for_warp()` 还会先创建新 world,成功后才对 old world `queue_free`,形成短暂双 world/双碰撞/双环境窗口。
4. setting 缺失时当前 `load_map()` 返回 false,但 `GameScene::_build_world()` 将其降级为 warning 并继续平地运行;这与 40250 `LoadMap` 失败后由 phase/调用方决定是否继续不同。`.msenv` 解析失败也使用 fallback lighting,旧 `_sun/_env` 的释放与新地图失败回滚没有统一 generation/owner。
5. 当前 `Metin2World` 没有 loader thread/Fetch/Shutdown 对应的请求 token`stream_queue` 是主线程 vector。换图或快速 reset 后没有 generation 防止旧队列/旧模型回调回灌新 world,网络 `world_reset` 的 generation 与 Metin2World 也未共享。
本轮测试结果:`gamescene_test.gd``p9_test.gd``environment_test.gd` 通过;测试只证明当前场景装配、warp signal 和 `.msenv` parser 可以运行,不能证明 Leave/Enter、map-name 校验、延迟回收、双 world 窗口或中断加载与 40250 一致。
### Active GameLib map-support review
- `GameLib/MapBase.cpp``Clear/Enter/Leave/SetEnvironmentDataPtr/ResetEnvironmentDataPtr` 是地图基类边界;`MapType.cpp`/`MapUtil.cpp` 负责 MapProperty、`.msenv` 的 fog/filter/skybox/lensflare/ambience/dungeon block 解析和插值 helper`AreaLoaderThread.h` 描述区域加载线程的 Create/Setup/Destroy 生命周期。
- `GameLib/Property.cpp`/`PropertyLoader.cpp`/`PropertyManager.cpp` 以 pack-backed property、CRC、reserve、注册/替换/删除/清理为地图对象、树、建筑、效果、环境和 dungeon block 提供权威配置;当前 `PropertyRegistry`/AssetResolver 只做部分文本/路径适配。
- `GameLib/MapOutdoor.h`/`MapManager.h`/`MapBase.h``GameType`/`Interface` 和 terrain patch/quad 文件的静态核对确认当前 `Metin2World` 的方形 chunk 队列不是完整 `CMapOutdoor::Update`/loader-thread/延迟回收链;地图合同继续为 `PARTIAL`
### Active UserInterface background bridge review
- `PythonBackground.h`/`PythonBackgroundModule.cpp` 暴露地图 Load/Destroy/Warp、environment register/get、terrain/object picking、height、shadow level/texture、visible part、splat/view distance、guild area、target/special effect 和 dungeon map name。
- 当前 `Metin2World`/`GameScene` 已有 map load、height/attr/water、environment 和目标效果入口,但没有同一套 background bridge 的 environment index、shadow/splat/view-distance/guild-area/特殊效果清理契约;`CameraProcedure.cpp` 还会通过 terrain-only ray 做多方向相机碰撞调整。
- 本轮完成 Background/CameraProcedure 静态核对,地图合同继续为 `PARTIAL`,相机碰撞差异同时保留在点击/输入合同中。
## Status
`PARTIAL`。本轮完成了 40250 与当前端地图生命周期、区域窗口、资源释放和失败分支的逐文件审计;发现跨图双 World、失败 fallback、Unload 校验、区域回收和异步中断边界尚未等价。现有回归通过只说明当前路径可运行,不能标记为已修复或 EQUIVALENT。