- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
14 KiB
14 KiB
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.cppLoadMap先调用当前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.cppCMapOutdoor::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 有独立生命周期。
- terrain/area 请求进入线程安全队列,由 loader thread 读取资源,主线程
Current implementation and evidence
extension/src/metin2_world.cppload_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_freeroot;没有 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,之后才对旧 worldqueue_free;旧 world 在新 world 失败时也可能仍作为有效场景引用存在。- 同图 warp 仅放置本地主角;跨图 warp 才重建 world。地图切换后重新绑定 camera/player/net_world/HUD/atlas,但网络实体刷新与 world 加载之间没有原版
Enter阶段契约。
- 初次 setup 先解析 map path、创建 fallback lighting,再显式
project/net_play.gd/project/net_world.gd- world reset 清除当前 Actor、移动/战斗/投射物/临时 UI 生命周期;场景层通过
queue_free延迟删除节点。 - reset 前后没有与
Metin2World共用的 generation;旧网络包或旧模型回调如果晚于新 world,依赖各模块自己的 guard。
- world reset 清除当前 Actor、移动/战斗/投射物/临时 UI 生命周期;场景层通过
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 做了实现级复核:
- 40250 的
LoadMap顺序是当前 mapLeave→ 设置 map name/加载 Property →CMapOutdoor::Load(x,y,z)→ 注册并设置环境 →Enter;当前load_map()先无条件unload_map(),然后同步解析 setting/AssetResolver/Property,再按半径把整个stream_queue建完,最后才解析.msenv。因此当前没有 reference 的 Leave/Enter/ready 边界,且 streaming 初始加载并非真正逐帧。 - 当前
stream_update()立即queue_free半径外 chunk,并清空再重建待加载队列;40250Update通过 3×3 terrain/area 指针窗口和 delete vector 延迟回收。当前在 focus tile 边界快速往返时,旧 chunk 的节点仍可能处于 Godot queue_free 期,同时新 chunk 已入队,资源/碰撞峰值和事件时序不同。 - 40250
UnloadMap(name)会校验传入 map name,不匹配时拒绝清理;当前Metin2World::unload_map()无 map-name 参数,任何调用都直接清空当前 world。GameScene 的_reload_map_for_warp()还会先创建新 world,成功后才对 old worldqueue_free,形成短暂双 world/双碰撞/双环境窗口。 - setting 缺失时当前
load_map()返回 false,但GameScene::_build_world()将其降级为 warning 并继续平地运行;这与 40250LoadMap失败后由 phase/调用方决定是否继续不同。.msenv解析失败也使用 fallback lighting,旧_sun/_env的释放与新地图失败回滚没有统一 generation/owner。 - 当前
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。