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

14 KiB
Raw Blame History

world.map_load_transition

Scope

审计 40250 地图 Leave/Clear/Load/Enter、玩家坐标驱动的区域加载、terrain/property/object/environment 初始化、区域卸载,以及当前 Metin2WorldGameScene 的换图、失败和恢复行为。目标是比较可观察的生命周期与资源/碰撞边界,不要求 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::LoadDestroy,读取 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/areaAssignTerrainPtr 绑定 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_CMCM_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/UpdateMapMetin2World::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 对应的请求 tokenstream_queue 是主线程 vector。换图或快速 reset 后没有 generation 防止旧队列/旧模型回调回灌新 world,网络 world_reset 的 generation 与 Metin2World 也未共享。

本轮测试结果:gamescene_test.gdp9_test.gdenvironment_test.gd 通过;测试只证明当前场景装配、warp signal 和 .msenv parser 可以运行,不能证明 Leave/Enter、map-name 校验、延迟回收、双 world 窗口或中断加载与 40250 一致。

Active GameLib map-support review

  • GameLib/MapBase.cppClear/Enter/Leave/SetEnvironmentDataPtr/ResetEnvironmentDataPtr 是地图基类边界;MapType.cpp/MapUtil.cpp 负责 MapProperty、.msenv 的 fog/filter/skybox/lensflare/ambience/dungeon block 解析和插值 helperAreaLoaderThread.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.hGameType/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。