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

182 lines
15 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.
# lifecycle.bootstrap_phase_host
## Scope
对比 40250 的应用启动、单帧处理顺序、网络阶段属主、Loading 入口清理、DEAD/CLOSE、窗口焦点/后台和最终销毁,与当前 Godot `client_main``AppFlow``AppLifecycle``M2Client` 和场景树行为。
## 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`
- 创建 `LoadingScreen``AppLifecycle``ReconnectUI`
- 进入 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 入口:自行创建 `AppLifecycle``M2Client`、登录层和 `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::Stage``M2Client` 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 phase`DeathUI` 现在只从 `GC_DEAD -> entity_dead` 进入,和 40250 的死亡包入口一致 | PARTIAL:死亡入口已闭环,GameOver/决斗例外、死亡动作和真实包序仍待补齐 |
| PHASE_CLOSE | 回调 `SetLoginPhase`,由登录阶段继续处理 | `PHASE_CLOSE -> Stage::Failed`,再发 `login_failed``disconnected` | 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.gd``project/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.cpp``PythonApplicationWebPage.cpp``MovieMan.cpp` 管理视频/网页/媒体 surface 的创建、更新、跳过、淡出和释放。
- `PythonSystem.cpp`/`PythonSystemModule.cpp` 以 D3D adapter/resolution/frequency、窗口状态、音量、阴影级别、保存 ID、配置文件和 interface status 为权威;当前 `AppLifecycle`/系统设置只覆盖 Godot 的一部分状态,Windows display mode、旧配置格式和媒体资源清理没有等价证据。
- `ProcessCRC.cpp`/`CheckLatestFiles.h``ProcessScanner.cpp``PythonExceptionSender.cpp``HackShield`/`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。