- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
119 lines
14 KiB
Markdown
119 lines
14 KiB
Markdown
# 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/unload;unload 立即 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` 返回 false,GameScene 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。
|