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

15 KiB
Raw Blame History

lifecycle.bootstrap_phase_host

Scope

对比 40250 的应用启动、单帧处理顺序、网络阶段属主、Loading 入口清理、DEAD/CLOSE、窗口焦点/后台和最终销毁,与当前 Godot client_mainAppFlowAppLifecycleM2Client 和场景树行为。

Reference call chain

启动与每帧

  • UserInterface/PythonApplication.cpp::Run
  • UserInterface/PythonApplication.cpp::Process
    1. ELTimer_SetFrameMSec / CTimer::Advance
    2. CPythonNetworkStream::Process
    3. guild mark uploader/downloader、account connector
    4. keyboard/mouse input
    5. camera、resource manager、camera update、mouse update、UI update
  • UserInterface/PythonApplicationEvent.cpp
  • UserInterface/PythonApplicationProcedure.cpp

阶段属主与清理

  • UserInterface/PythonNetworkStream.cpp::RecvPhasePacket
    • PHASE_CLOSE -> ClosePhase -> SetLoginPhase
    • PHASE_HANDSHAKE -> SetHandShakePhase
    • PHASE_LOGIN -> SetLoginPhase
    • PHASE_SELECT -> SetSelectPhase
    • PHASE_LOADING -> SetLoadingPhase
    • PHASE_GAME -> SetGamePhase
    • PHASE_DEAD 只消费阶段包,不改变网络阶段
  • UserInterface/PythonNetworkStreamPhaseLogin.cpp::SetLoginPhase
  • UserInterface/PythonNetworkStreamPhaseSelect.cpp::SetSelectPhase
  • UserInterface/PythonNetworkStreamPhaseLoading.cpp::SetLoadingPhase
    • CPythonPlayer::Clear
    • CFlyingManager::DeleteAllInstances
    • CEffectManager::DeleteAllInstances
    • direct-enter 状态初始化
  • UserInterface/PythonNetworkStreamPhaseGame.cpp::SetGamePhase
  • UserInterface/PythonNetworkStreamPhaseGame.cpp::__LeaveGamePhase
    • PVP key、network actor、combo flag、character manager、item manager
  • UserInterface/PythonNetworkStreamPhaseGameActor.cpp::__ClearNetworkActorManager

关闭与销毁

  • EterLib/MSApplication.cpp::MessageProcess
  • EterLib/MSWindow.cpp::WindowProcedure/Destroy
  • UserInterface/PythonApplicationProcedure.cpp::WindowProcedure
    • WM_ACTIVATEAPP: 保存/恢复音量;全屏窗口最小化/恢复
    • WM_CLOSE: debug 退出,release 走 RunPressExitKey
  • UserInterface/PythonApplication.cpp::Destroy
    • UI、事件、飞行物、角色、物品、背景、特效、网络、资源、声音、设备按显式顺序销毁

Current call chain

启动与每帧

  • project/client_main.gd::_ready
    • AssetPack.ensure
    • 创建 AppFlow
  • project/app_flow.gd::start
    • 创建并挂载一个 M2Client
    • 创建 LoadingScreenAppLifecycleReconnectUI
    • 进入 Login
  • extension/src/net/m2_client.cpp::_process/net_poll
    • 依次 pump classic/auth/game/mark 连接
    • classic 侧再转发 EntityStore 变化和 Godot signals
  • project/game_scene.gd::_process
    • 主角同步、诊断、音频 listener/环境音
  • project/app_flow.gd::_goto_game
    • 以协程构造 GameScene;等待模型时只让出帧,不夺取 M2Client._process 的网络 pump owner
  • project/login.gd
    • 保留一条独立的 SceneTree 入口:自行创建 AppLifecycleM2Client、登录层和 GameScene
    • 不经过 client_main.gd -> AppFlow,因此与主入口不是同一套阶段、关闭和销毁属主

阶段属主与清理

  • extension/src/net/classic/classic_session.cpp::on_phase
    • LOGIN/SELECT/LOADING/GAME 映射到 INetSession::Stage
    • PHASE_CLOSE 直接映射为 Stage::Failed
    • PHASE_DEAD 没有 case,仅被前置的 time-sync 判断覆盖,未改变 stage
  • extension/src/net/m2_client.cpp::pump_classic
    • Stage 变化才发 phase_changed
    • stage 表只有 close/handshake/login/select/loading/in_game/failed,没有 dead
    • EntityStore 的实体、死亡、掉落、地图 warp 变化在同一 pump 中派发
  • project/ui/loading_screen.gd
    • 只按 phase_changed 显示/隐藏 Loading 遮罩
  • project/game_scene.gd::_on_world_reset
    • 清 NetPlay、NetWorld、掉落、弹道、临时 UI 和 BGM
    • M2Client 的 classic on_loading_phase 在保留旧 world 时触发;空 world 或已由 GC_WARP 预清理的连接不重复触发
  • project/net_play.gd::clear_for_map_change
  • project/net_world.gd::clear_for_map_change
  • extension/src/net/entity_store.cpp::reset_for_map_change

窗口与后台

  • project/app_lifecycle.gd::_notification
    • application paused/resumed 时保存/静音/恢复音频、暂停/恢复 M2Client 和 SceneTree
    • focus 只发 focus_changed,没有 40250 的音量/全屏处理
    • NOTIFICATION_WM_CLOSE_REQUEST 只发 close_requested
  • AppFlow 与旧 login.gd 分别消费各自 AppLifecycle.close_requested,关闭时按网络→音频解绑→场景释放→进程退出顺序协调;两条入口仍不是同一个启动拓扑。

销毁

  • AppFlow::_goto_login 对 GameScene/UI 使用 queue_free
  • M2Client::~M2Client 为 default;连接由成员 RAII 析构关闭
  • 未发现与 40250 CPythonApplication::Destroy 对应的、可观测且有顺序保证的网络/实体/特效/UI/音频总清理函数

Branch matrix

场景 40250 当前端 结论
启动/正常帧 固定在 Process 中先计时、网络,再输入、资源、相机和 UI Godot 由 M2Client._process 持有网络 pump_goto_game 等待模型只 yield 并同步视图 PARTIAL:跨节点顺序和模型构建期间的完整事件时序仍未证明一致
主入口/旧入口 CPythonApplication 只有一个启动和销毁拓扑 client_main.gd -> AppFlow 与旧的 login.gd 各自创建生命周期、客户端和游戏场景 GAP:主入口测试不能证明旧入口也遵守单一生命周期属主
LOGIN/SELECT RecvPhasePacket 作为唯一阶段 owner,阶段切换先执行旧 phase leave,再安装新 process/leave 回调 ClassicSession::StageM2Client signals、AppFlow 三层分担 PARTIAL:状态 owner 不唯一,乱序和重复 phase 规则未统一
SELECT -> LOADING SetLoadingPhase 先切 phase,再清 player、飞行物、特效和 direct-enter 状态 classic on_loading_phase 在阶段回调内先清旧 EntityStore 并发 world_reset,再继续 Loading;空 world 直接跳过 PARTIALNetPlay/NetWorld/特效的完整 owner 顺序和真实 server 包序仍待补齐
GAME -> DEAD 40250 的 PHASE_DEAD 本身不改 phase,死亡包在 Game phase 内走 GameOver/角色死亡链 ClassicSession 不发 dead phaseDeathUI 现在只从 GC_DEAD -> entity_dead 进入,和 40250 的死亡包入口一致 PARTIAL:死亡入口已闭环,GameOver/决斗例外、死亡动作和真实包序仍待补齐
PHASE_CLOSE 回调 SetLoginPhase,由登录阶段继续处理 PHASE_CLOSE -> Stage::Failed,再发 login_faileddisconnected GAP:状态和 UI 入口不同
GC_WARP 进入 Loading,显式清旧 actor/物品/特效,再连接/加载新地图 EntityStore::reset_for_map_change -> world_reset -> connect_warp,并由 Loading 回调的空 world guard 避免重复 reset PARTIALNetPlay/NetWorld/特效的完整 owner 顺序和真实 server 包序仍待补齐
断线重连 old phase leave、连接状态和角色数据按网络 owner 协调 ReconnectUI 负责重试,M2Client 重新建/切 session;场景可能保留,world reset 不是统一前置 PARTIAL;与 network.reconnect_warp 交叉记录
窗口失焦/后台 Windows WM_ACTIVATEAPP 保存/恢复音量并处理全屏,主循环仍由应用控制 focus 仅广播;application pause 会暂停 socket/SceneTree 并静音 PARTIAL / platform adaptation:行为不是 1:1
窗口关闭 WM_CLOSE 经退出键/退出流程,最终 Destroy AppFlow/旧 login.gd 唯一消费 close_requested,先断开并解绑资源后退出;Windows 退出确认/保存细节仍未证明 PARTIAL
最终销毁 明确 manager 顺序、网络/声音/设备显式 Destroy Godot queue_free + C++ default destructor/RAII PARTIAL:资源最终可能释放,但副作用顺序与可观测状态未等价

Equivalence matrix

项目 状态 证据
Preconditions PARTIAL 当前入口有资源、扩展和 serverlist 检查,但没有统一阶段/关闭前置条件
Branch structure PARTIAL LOGIN/SELECT/LOADING/GAME 已映射;DEAD、CLOSE、窗口关闭和重复启动/关闭缺统一分支
Algorithms/formulas MAPPED 主循环没有业务公式;计时和网络 pump owner 不同
State transition order PARTIAL 参考由 RecvPhasePacket + phaseLeaveFunc 单 owner 串行;当前由 session/stage/signal/AppFlow 分散推进
Constants/units PARTIAL 1500ms enter-game 等已有实现,但 frame/timer、Loading 清理和重连窗口未证明相同
Timing/event sources PARTIAL 当前有 Godot _process、signal、协程和模型同步;网络 pump 已收敛到 M2Client owner,参考是 Windows message + phase callback
Resource/data sources PARTIAL 当前资源由 scene/RAII 管理;参考是各 manager 显式拥有,且正常 Loading 与 close 清理源不同;client_main 与旧 login.gd 还存在两套资源/生命周期装配源
Protocol side effects PARTIAL LOGIN/SELECT/ENTERGAME/warp 已有路径;CLOSE、DEAD 和 loading reset 的副作用不等价
Interruption/failure/cleanup PARTIAL 断线/warp/关闭消费者已有部分回归;普通重连 reset、重复关闭、socket 失败和 final destroy 的完整顺序仍缺证据

Evidence and regression boundary

project/app_flow_lifecycle_test.gd 已验证:AppFlow 只创建一个 AppLifecycle、登录/游戏切换不重复创建、游戏断线保留场景并按原槽位重进、显式槽位不存在时不错误自动选人,并且桌面关闭信号有唯一消费者。project/test_death_restart_parity.gd 另外验证 PHASE_DEAD 不作为死亡面板入口,GC_DEAD -> entity_dead 才打开 RestartDialog。project/gamescene_test.gdproject/channel_status_test.gd 只提供场景装配/频道状态旁证。它们没有证明 40250 的固定每帧顺序、Loading 清理、DEAD/CLOSE 的完整副作用、旧 login.gd 入口或最终销毁等价,因此本合同不能升级为 MAPPED。

Required tests

  • 记录并断言 M2Client._process、GameScene/UI _process 和 loading 协程的同帧顺序,禁止等待模型时重入 pump 造成重复 phase/entered_game。
  • LOGIN/SELECT/LOADING/GAME/DEAD/CLOSE 的正常、乱序、重复和断线状态矩阵;特别断言 PHASE_DEAD 的消费和 PHASE_CLOSE 的目标状态。
  • SELECT -> LOADING、GC_WARP、普通重连三条路径分别验证 actor、飞行物、特效、NetPlay 动作、掉落、临时 UI、BGM 和 EntityStore 的清理顺序。
  • focus out/in、application pause/resume、重复 pause/resume、窗口 close、网络失败和重复 close 的音频、socket、SceneTree、退出状态测试。
  • GameScene queue_free、M2Client 析构、旧 session 关闭和再次启动的资源/信号泄漏测试。
  • 分别从 client_main.tscn 和旧 login.gd 启动,验证 AppLifecycle、M2Client、GameScene、音频和 close 信号没有重复属主或悬空连接。

Active UserInterface application/security review

  • UserInterface/AbstractApplication.h 把鼠标、全局时间、服务器时间、中心位置、事件/默认相机和 IME update/tab/return/code-page/candidate/reading callbacks 作为应用宿主接口;这与当前仅由 Godot notification/signal 分散处理不是同一 host contract。
  • UserInterface/PythonApplicationModule.cpp 暴露应用 process/update/render FPS、相机参数、硬件/软件光标、连接数据、文本 loader 和 pack 读取等 Python 边界;PythonApplicationLogo.cppPythonApplicationWebPage.cppMovieMan.cpp 管理视频/网页/媒体 surface 的创建、更新、跳过、淡出和释放。
  • PythonSystem.cpp/PythonSystemModule.cpp 以 D3D adapter/resolution/frequency、窗口状态、音量、阴影级别、保存 ID、配置文件和 interface status 为权威;当前 AppLifecycle/系统设置只覆盖 Godot 的一部分状态,Windows display mode、旧配置格式和媒体资源清理没有等价证据。
  • ProcessCRC.cpp/CheckLatestFiles.hProcessScanner.cppPythonExceptionSender.cppHackShield/NProtectGameGuard/WiseLogicXTrap 构成参考端完整性、进程扫描、安全组件和异常上报链。当前客户端没有对应 Windows 安全/CRC/异常服务链;这些是明确的部署能力差异,不应被普通启动测试掩盖。
  • 本轮完成上述 UserInterface 应用/安全活跃文件静态核对,并修正 DeathUI 对不可达 phase_changed("dead") 的错误依赖;当前端基础 AppFlow 可运行,但 bootstrap/关闭/安全/发布合同继续为 PARTIAL

Status

PARTIAL。本轮完成了 40250 参考调用链与当前实现调用链的逐分支审计,并修正了 DeathUI 对不可达 phase_changed("dead") 的错误依赖、classic Loading 阶段缺少旧 world 清理入口、主入口/旧入口遗漏关闭消费者,以及 _goto_game 等待模型时重复夺取网络 pump 的问题;DEAD/CLOSE 其余状态语义、focus/后台、旧入口分叉和显式销毁链仍存在未闭合差异,因此不能宣称已全部按 40250 修复。

Implementation round 2026-09-21T04:20Z — close request ownership

本轮针对 40250 PythonApplicationProcedure::WindowProcedure(WM_CLOSE)CPythonApplication::Destroy 的关闭入口补齐 Godot 两条启动拓扑:

  • AppFlow.start 将唯一的 AppLifecycle.close_requested 连接到 _on_close_requested;处理器具备幂等锁,先调用 M2Client.disconnect_from_server(),再解绑生命周期持有的音频、释放 GameScene,最后退出 SceneTree。
  • login.gd 自己创建 AppLifecycle,因此也接入同样的断开/解绑/场景释放/退出顺序,避免只修 client_main 主入口。
  • app_flow_lifecycle_test.gd 新增失败回归:没有消费者时失败,修复后验证 AppFlow 连接了唯一关闭处理器;Godot editor/headless 脚本加载检查通过。

仍保持 PARTIAL:Windows 退出确认与保存细节、真实 socket close 回调顺序、重复 close 与最终 native manager 销毁尚未由运行时 fixture 完整证明。

Implementation round 2026-09-21T04:25Z — frame pump ownership

本轮按 40250 CPythonApplication::Process 的单一网络处理 owner 修复进入游戏等待段:

  • 删除 AppFlow::_goto_game 等待 _model_built 时的直接 client.net_poll();每次循环只同步模型并让出一帧,由 M2Client._process 继续负责唯一网络 pump。
  • 失败回归在 app_flow_lifecycle_test.gd 中检查 _goto_game 源码段不得重新调用 client.net_poll();修复后生命周期、GameScene 和 world reset 回归均通过。

仍保持 PARTIAL:GameScene 内部模型构建仍有传入 pump callable 的资源加载适配,完整跨节点同帧顺序、阶段重复/乱序包和 entered_game 重入矩阵仍需运行时 fixture。