- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
15 KiB
lifecycle.bootstrap_phase_host
Scope
对比 40250 的应用启动、单帧处理顺序、网络阶段属主、Loading 入口清理、DEAD/CLOSE、窗口焦点/后台和最终销毁,与当前 Godot client_main、AppFlow、AppLifecycle、M2Client 和场景树行为。
Reference call chain
启动与每帧
UserInterface/PythonApplication.cpp::RunUserInterface/PythonApplication.cpp::ProcessELTimer_SetFrameMSec/CTimer::AdvanceCPythonNetworkStream::Process- guild mark uploader/downloader、account connector
- keyboard/mouse input
- camera、resource manager、camera update、mouse update、UI update
UserInterface/PythonApplicationEvent.cppUserInterface/PythonApplicationProcedure.cpp
阶段属主与清理
UserInterface/PythonNetworkStream.cpp::RecvPhasePacketPHASE_CLOSE -> ClosePhase -> SetLoginPhasePHASE_HANDSHAKE -> SetHandShakePhasePHASE_LOGIN -> SetLoginPhasePHASE_SELECT -> SetSelectPhasePHASE_LOADING -> SetLoadingPhasePHASE_GAME -> SetGamePhasePHASE_DEAD只消费阶段包,不改变网络阶段
UserInterface/PythonNetworkStreamPhaseLogin.cpp::SetLoginPhaseUserInterface/PythonNetworkStreamPhaseSelect.cpp::SetSelectPhaseUserInterface/PythonNetworkStreamPhaseLoading.cpp::SetLoadingPhaseCPythonPlayer::ClearCFlyingManager::DeleteAllInstancesCEffectManager::DeleteAllInstances- direct-enter 状态初始化
UserInterface/PythonNetworkStreamPhaseGame.cpp::SetGamePhaseUserInterface/PythonNetworkStreamPhaseGame.cpp::__LeaveGamePhase- PVP key、network actor、combo flag、character manager、item manager
UserInterface/PythonNetworkStreamPhaseGameActor.cpp::__ClearNetworkActorManager
关闭与销毁
EterLib/MSApplication.cpp::MessageProcessEterLib/MSWindow.cpp::WindowProcedure/DestroyUserInterface/PythonApplicationProcedure.cpp::WindowProcedureWM_ACTIVATEAPP: 保存/恢复音量;全屏窗口最小化/恢复WM_CLOSE: debug 退出,release 走RunPressExitKey
UserInterface/PythonApplication.cpp::Destroy- UI、事件、飞行物、角色、物品、背景、特效、网络、资源、声音、设备按显式顺序销毁
Current call chain
启动与每帧
project/client_main.gd::_readyAssetPack.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
- 以协程构造 GameScene;等待模型时只让出帧,不夺取
project/login.gd- 保留一条独立的 SceneTree 入口:自行创建
AppLifecycle、M2Client、登录层和GameScene - 不经过
client_main.gd -> AppFlow,因此与主入口不是同一套阶段、关闭和销毁属主
- 保留一条独立的 SceneTree 入口:自行创建
阶段属主与清理
extension/src/net/classic/classic_session.cpp::on_phase- LOGIN/SELECT/LOADING/GAME 映射到
INetSession::Stage PHASE_CLOSE直接映射为Stage::FailedPHASE_DEAD没有 case,仅被前置的 time-sync 判断覆盖,未改变 stage
- LOGIN/SELECT/LOADING/GAME 映射到
extension/src/net/m2_client.cpp::pump_classic- Stage 变化才发
phase_changed - stage 表只有 close/handshake/login/select/loading/in_game/failed,没有 dead
EntityStore的实体、死亡、掉落、地图 warp 变化在同一 pump 中派发
- Stage 变化才发
project/ui/loading_screen.gd- 只按
phase_changed显示/隐藏 Loading 遮罩
- 只按
project/game_scene.gd::_on_world_reset- 清 NetPlay、NetWorld、掉落、弹道、临时 UI 和 BGM
- 由
M2Client的 classicon_loading_phase在保留旧 world 时触发;空 world 或已由 GC_WARP 预清理的连接不重复触发
project/net_play.gd::clear_for_map_changeproject/net_world.gd::clear_for_map_changeextension/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
- application paused/resumed 时保存/静音/恢复音频、暂停/恢复
AppFlow与旧login.gd分别消费各自AppLifecycle.close_requested,关闭时按网络→音频解绑→场景释放→进程退出顺序协调;两条入口仍不是同一个启动拓扑。
销毁
AppFlow::_goto_login对 GameScene/UI 使用queue_freeM2Client::~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 直接跳过 |
PARTIAL:NetPlay/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 |
PARTIAL:NetPlay/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。