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

243 lines
18 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.
# 40250 客户端差异修复路线
本文件记录当前修复顺序和调度规则。它是给人和其他 AI 工具读取的当前路线,不替代机器可读的 `audit/manifest.json`;合同状态、剩余分支和证据以账本为准。
## 当前基线
统计快照:2026-09-21。
| 项目 | 数量 |
| --- | ---: |
| 总合同 | 45 |
| 已排除 | 1 |
| 未完全闭合合同 | 44 |
| 未闭合分支条目 | 224 |
| P0 分支条目 | 93 |
| P1 分支条目 | 118 |
| P2 分支条目 | 13 |
| P3 分支条目 | 0 |
这些数量是快照,不是手工修改的真相。每次修复前后都要运行账本刷新和报告脚本,以 `audit/manifest.json` 的当前内容为准。
## 修复阶段
### 阶段 0:账本和调度卫生
- 读取本文件、`audit/manifest.json`、相关合同和最近的 `audit/history.jsonl`。
- 先运行 `audit_ledger.py refresh --write`、`report --write` 和 `validate`。
- 将一个合同中的每个剩余分支作为独立的修复目标;修复后必须同步合同、账本和历史。
- 问题/分支 ID 使用稳定格式 `<contract-id>/<branch-slug>`;历史中只有合同 ID 的旧记录不能证明该合同的所有分支都已完成,首次处理某个分支时要补建具体 ID。
- 如果只是缺少真实服务端、Windows 客户端或视觉运行证据,不要伪装成代码已完成,记录为验证阻塞。
### 阶段 1:P0 在线核心链路
按完整的用户可玩链路推进:
1. 登录、认证、选人、Loading、进入地图和失败重试:`network.login.phase_flow`、`network.character_select.create_delete`。
2. WASD、停止、转向、远程同步、传送和地图切换:`movement.keyboard.motion`、`movement.remote_sync_state`、`world.map_load_transition`、`world.reset_cleanup`。
3. 攻击、受击、死亡、经验、掉落和拾取:`combat.attack_combo_damage`、`combat.death.exp_drop`、`item.drop_pickup`。
4. 技能释放、动作事件、特效和服务端结果:`skill.cast.effect_timing`、`skill.player_ui_cooldown`。
5. 协议分派、重连、安全分支和资源/协议加载:`network.packet_dispatch_protocol`、`network.reconnect_warp`、`network.security_encoding`、`resource.pack_locale_proto`。
阶段 1 未完成前,不主动扩大 P1 的视觉或外围功能修复范围。
### 阶段 2:P1 游戏功能
依次处理背包/装备、NPC/任务、社交、目标选择、状态、模型和效果资源。`combat.flying_projectile` 在这一阶段处理;除非它直接阻塞 P0 或引入回归,否则不提前插队。
### 阶段 3:P2/P3 平台和表现
处理音频、天气、平台发布、移动端细节和其他不阻塞核心游戏流程的差异。
## 防止重复修复规则
- 每一轮只设置一个主合同,并为本轮选定一个明确的问题/分支 ID;可以同时修复被它直接调用的跨合同根因,但必须在历史中列出关联 ID。
- 同一个问题/分支 ID 不得重复修复或重复审计,除非出现新的回归、实现文件发生实质变化,或新增证据证明原结论不成立。
- 同一合同可以在后续轮次继续处理,但必须选择不同的 `remaining` 分支;合同是功能分组,不是问题去重单位。
- 一个合同仍为 `PARTIAL` 不代表此前修改无效;只处理其 `remaining` 中尚未闭合的分支,不能重新审计已通过的测试路径。
- 每轮开始前必须检查 `audit/history.jsonl`,确认本轮问题/分支 ID 没有已完成记录;如果历史记录不够细,先补充问题 ID,再开始修改。
- 每轮结束必须记录:参考调用链、当前调用链、修改的分支、修复前失败证据、修复后测试、剩余阻塞和下一轮候选。
- 下一轮候选按 `P0 → P1 → P2 → P3` 排序;同优先级按用户可玩链路和阻塞关系排序,不按“最容易修改”排序。
## 完成门槛
单个分支只有同时满足以下条件才可关闭:
1. 40250 完整调用链和当前实现调用链已对齐。
2. 前置条件、分支、算法、状态顺序、单位、时序、资源、协议副作用和清理行为均有等价证据。
3. 有修复前失败、修复后通过的自动测试;必要时覆盖拒绝、打断、重复包、死亡、重连和地图切换。
4. 需要真实服务端、Windows 客户端或视觉证据时,已完成对应验证;否则保持 `PARTIAL` 并记录阻塞原因。
合同只有在全部剩余分支关闭后,才能从 `PARTIAL` 提升为 `STATIC_VERIFIED` 或 `TEST_VERIFIED`。
## 下一轮
下一轮主合同固定为 `network.login.phase_flow`,具体处理其尚未闭合的问题/分支。除非发现新的 P0 回归或登录链路被其他合同阻塞,否则不切回已处理过的问题。
上一轮已闭合分支:`network.login.phase_flow/auth-game-phase-matrix/peer-close-login-owner`。
真实 `/logout` 已证明建立连接后的 peer close,AppFlow 现在按 40250 回到唯一 LoginPhase owner;
不得重复修改“保留 GameScene/自动重连遮罩”这一已闭合分支。
上一轮已闭合分支:`network.login.phase_flow/auth-game-phase-matrix/phase-close-server-fixture`。
本地等价 fixture 已证明显式 `PHASE_CLOSE` 不再伪造 `disconnected`;真实服务端主动
`PHASE_CLOSE` 包序仍是证据阻塞,后续只在获得新 live fixture 时验证,不重复修改 native owner。
上一轮已处理分支:`network.login.phase_flow/auth-game-phase-matrix/empty-role-live-fixture`。
已补充 `net_classic_e2e` 的 `MT_CLASSIC_EXPECT_EMPTY=1` 只读探针;当前账号仅有部分空槽,
不能将其当作全空角色证据,也不要通过删号制造 fixture。全空角色的正向 live 证据仍保持在
`remaining`,后续只在获得安全 fixture 时验证,不重复修改本地 SELECT owner。
上一轮已处理分支:`network.login.phase_flow/login-failure-retry-cleanup/login-form-retry-owner`。
已按 40250 `LoginWindow.Connect -> AccountConnector.Connect` 增加 LoginPhase retry owner;登录失败后
只替换认证 transport,不再由 AppFlow 重试触发全局 `disconnect_from_server/world_reset`。本地 UI/source
断言和 native loopback 包序已通过;真实 Windows 可视化重试和服务器“失败->重新输入->成功”仍是证据阻塞,
不得重复修改该 owner。
前一阶段首选分支:`network.login.phase_flow/auth-game-phase-matrix/cross-phase-retry-order`。
下一步只处理跨阶段失败/重复进入的真实或等价包序:失败后旧 auth/game stream 的迟到包、重复
`PHASE_LOGIN/PHASE_AUTH` 的替换顺序、以及完整 UI retry 的真实服务端证据;优先补测试和 fixture,
不要重新审计已经由
`direct-enter-loading-select-packet`、`gc-warp-live-package-order`、`peer-close-login-owner` 或
`phase-close-server-fixture` 闭合的成功进入、peer-close owner 和显式 PHASE_CLOSE transport-owner
分离链路。
本轮已处理该分支中的认证成功尾包子路径:`GC_AUTH_SUCCESS` 后的旧 `PHASE_AUTH` 会随旧
AccountConnector 接收缓冲一起丢弃,不重复发送 `CG_LOGIN3`。后续必须使用新的稳定子分支
`network.login.phase_flow/auth-game-phase-matrix/cross-phase-retry-order-game-login-stale-tail`
处理游戏登录失败后的迟到 phase;不得重复修改认证成功的清理边界。
本轮已处理 `cross-phase-retry-order-game-login-stale-tail`:游戏 LoginPhase/SelectPhase/
Loading 自有包在当前应用帧完成后按 40250 phase-local return 停止,迟到 receive tail 延后到
下一次处理周期;Loading 转交 GamePhase 的包仍保留游戏批处理语义。后续应选择新的稳定子分支
处理真实失败后重新输入成功、重复进入包序或服务器主动 PHASE_CLOSE live 证据,不得重复修改
本轮 `return_after_current_packet` 机制。
本轮已处理 `network.login.phase_flow/auth-game-phase-matrix/auth-connect-immediate-failure-owner`:
认证地址解析/socket 同步失败与异步 `OnConnectFailure` 统一回到 40250 的 Offline owner,并保留
登录重试资格。不得重复修改本轮 `handle_auth_connect_failure()` 的认证清理边界。
本轮已处理 `network.login.phase_flow/auth-game-phase-matrix/game-connect-immediate-failure-owner`:
普通认证成功后的游戏连接失败现在走独立 `OnConnectFailure -> LoginWindow` owner,DirectEnter
和 GC_WARP 失败走 `ClosePhase -> SetLoginPhase`,不再触发凭据 `OnLoginFailure` 或伪造
`disconnected`。修复前 DirectEnter/普通 game-connect fixture 均有失败断言,修复后 native、
Godot bridge、CTest、ASAN 和阶段/包结构审计通过;不得重复修改本轮
`handle_game_connect_failure()`、`connect_failed` signal 或 GC_WARP owner 分离。
本轮已处理 `network.login.phase_flow/auth-game-phase-matrix/auth-connect-bridge-owner`:按
40250 `AccountConnector::OnConnectFailure -> Offline -> LoginWindow` 修复初始认证同步/异步
连接失败的 M2Client bridge,新增独立 `connect_failed` 回调、保留认证 session 和
`retry_login` owner;修复前专项桥接测试失败,修复后 native/CTest/ASAN/Godot 与阶段/包结构
审计通过。不得重复修改本轮 `on_auth_connect_failure`、`classic_auth_connect_failure_pending`
或认证 session 保留逻辑。
本轮已处理 `network.login.phase_flow/auth-game-phase-matrix/auth-peer-close-bridge-owner`:按
40250 `CAccountConnector::OnRemoteDisconnect -> __OfflineState_Set` 增加认证 peer-close
独立 callback;M2Client 不再把认证 socket 被动关闭伪造成 `login_failed` 或 `connect_failed`,
并保留非终态 LoginPhase owner。修复前后均由真实本地 TCP 接受/关闭/再次替换 transport 的
Godot 回归固定;不得重复修改本轮 `on_auth_remote_disconnect` 或
`classic_auth_remote_disconnect_pending`。
本轮已处理 `network.login.phase_flow/auth-game-phase-matrix/phase-close-disconnect-login-owner`:
重新拆分 40250 的 `PHASE_LOGIN -> CPythonNetworkStream::SetLoginPhase` 与
`PHASE_CLOSE/OnRemoteDisconnect -> Python networkmodule.SetLoginPhase -> net.Disconnect` 两种
语义;只有真正的 `PHASE_LOGIN` 会在当前 transport 上发送 `CG_LOGIN/CG_LOGIN2`,显式关闭、peer close
和 DirectEnter 失败会清理旧 transport 并回到 LoginWindow owner。`net_classic_session_test` 的修复前
失败、修复后无旧连接登录包/旧 transport Offline、CTest/ASAN/Godot 回归均已固定;不得重复修改
`enter_login_phase` 的发送分支或本地 PHASE_CLOSE owner。真实服务器主动 PHASE_CLOSE 包序仍需 live 证据。
本轮已处理 `network.login.phase_flow/login-failure-retry-cleanup/game-peer-close-retry-owner`:
按 40250 `OnRemoteDisconnect -> SetLoginPhase -> LoginWindow` 与后续
`LoginWindow.Connect -> ConnectToAccountServer` 的 owner 边界,游戏态 peer close 现在保留同一
`ClassicSession` 的显式重试资格;重试只替换认证 transport,新的 `PHASE_AUTH` 只发送一份使用新密码的
`CG_LOGIN3`,不再回退到全局 session/world teardown。修复前失败和 loopback 替换 transport 回归已记录;
不得重复修改 `m_login_retry_pending` 的游戏 peer-close owner。真实 Windows LoginWindow 时序、真实服务端
失败后重试成功包序和完整跨阶段 retry 仍需 live 证据。
下一主候选:`network.login.phase_flow/auth-game-phase-matrix/live-package-order/server-initiated-phase-close`。
本地结构和 owner 分支已覆盖,下一轮只取真实 Windows/40250 服务端证据:失败后重新输入成功、
服务器主动 `PHASE_CLOSE`、完整跨阶段 retry 和 DirectEnter/GC_WARP 失败回退包序;不要回到已关闭
的认证或游戏连接失败 owner。
本轮已执行该候选的真实 `/logout` 探针并登记为
`network.login.phase_flow/auth-game-phase-matrix/live-package-order/server-initiated-phase-close`:
登录到 PHASE_GAME、执行命令、peer close、world reset 和 LoginPhase owner 均通过,但
`MT_NET_TRACE=1` 没有出现 `phase -> 0`。服务端 `DESC::SetPhase(PHASE_CLOSE)` 的源码状态链已
确认,当前 build 因 close 后的 `DESC::Packet` guard 没有提供客户端可观察的 PHASE_CLOSE 包,
因此不能用客户端改动伪造这个证据。外层 wrapper 的失败仅来自既有 4 个 ParticlesShaderRD/RID
泄漏门禁。除非更换服务端 build/fixture,否则不要重复 `/logout` 探针;下一步改为获取真实
PHASE_CLOSE fixture,并在同一 fixture 上完成失败重试和跨阶段包序审计。
本轮新增并闭合本地分支:
`network.login.phase_flow/auth-game-phase-matrix/auth-success-game-before-auth-clear`。
对照 40250 `__AuthState_RecvAuthSuccess -> SetLoginKey -> MainStream.Connect -> AccountConnector.Disconnect`
发现当前 `ClassicSession::pump()` 曾反向执行 auth disconnect 再 game connect;修复后使用 loopback
双 transport 状态回调固定为 `game:connecting -> auth:offline`。该分支不得重复修改。下一主候选仍是
新的真实失败后重试/跨阶段包序 fixture;如服务端 fixture 没有变化,不重复 `/logout`。
本轮新增并闭合本地分支:
`network.login.phase_flow/auth-game-phase-matrix/game-login-retry-process-boundary`。
对照 40250 `CPythonApplication::Process -> CPythonNetworkStream::Process -> CAccountConnector::Process`
发现认证成功后当前端在同一 pump 再次处理新游戏流;修复后重试期间保留旧主流的处理机会,
认证成功建立替换流后延迟到下一 pump 才处理新流。该分支不得重复修改;真实服务端失败后重试
成功包序和 Windows LoginWindow 仍是未验证证据,下一候选继续选择新的 live/跨阶段分支。
本轮新增并闭合本地分支:
`network.login.phase_flow/legacy-text-wire-encoding/ui-gb2312-input-capacity`。
发现 native 已按 UTF-8 -> GB2312 转换后的 wire 字节校验,但 `CharSelectScreen` 仍按 UTF-8
字节限制角色名输入,导致“龙驹简体中文免费版”在 UI 层被提前拒绝。现改为 UI 只做 Unicode
字符/控制字符校验,实际 24-byte GB2312 容量由 native 发包边界统一判断,并加入 UI/native
回归;不得重复把角色名 UI 改回 UTF-8 字节限制,也不得把地图名、任务名、系统提示套用该
24-byte 字段限制。
本轮新增并处理分支:
`network.login.phase_flow/ticket-slot-warp-order/direct-enter-failure-login-owner-reset`。
对照 40250 `OnConnectFailure -> ClosePhase -> net.Disconnect -> SetOffLinePhase`,修复
DirectEnter/GC_WARP 失败回到 LoginPhase 时残留 `m_direct_enter`、导致替换 `PHASE_LOGIN`
跳过普通角色列表清理的问题;修复前失败测试和修复后通过证据已登记。真实 Windows/服务端
失败回退包序仍是 `PARTIAL`,下一轮继续获取新的 live fixture,不重复修改本地 ClosePhase owner。
本轮新增并处理分支:
`network.login.phase_flow/auth-game-phase-matrix/phase-close-login-retry-owner`。
对照 40250 `PHASE_CLOSE -> networkmodule.SetLoginPhase -> net.Disconnect -> LoginWindow.Connect`
补齐显式关闭后的 retry owner;同一 `ClassicSession` 现在可替换认证 transport,收到新的
`PHASE_AUTH` 时只发送一份 `CG_LOGIN3`。真实服务端 `PHASE_CLOSE` 包序和完整跨阶段 retry
仍是 live 证据阻塞,不能据此关闭整个登录合同。
本轮新增并处理分支:
`network.login.phase_flow/auth-game-phase-matrix/phase-handshake-reentry-owner`。
对照 40250 `RecvPhasePacket(PHASE_HANDSHAKE) -> SetHandShakePhase` 和
`CAccountConnector::__AuthState_RecvPhase -> __HandshakeState_Set`,补齐认证/游戏
HandShake owner 重入;从 Loading/Game 重入时先执行旧 phase-leave world 清理,后续只有新的
`PHASE_LOGIN/PHASE_AUTH` 才能继续登录。真实 Windows/服务端重试包序仍未闭合;不得重复修改
本轮的 `PHASE_HANDSHAKE` owner 和 world-leave 边界。
本轮新增并处理分支:
`network.login.phase_flow/auth-game-phase-matrix/auth-success-result-authority`。
对照 40250 `__AuthState_RecvAuthSuccess` 的 `bResult` 分支,修复当前端把 `login_key == 0`
额外解释为凭据失败的问题;成功/失败现在只由 `result` 决定,票据值仍交给后续游戏连接。
真实服务端不会主动构造成功零票据包,不能用该本地边界测试替代完整 live retry 或安全模块证据;
不得重复修改本轮的认证结果判定条件。
本轮新增并处理分支:
`network.login.phase_flow/auth-game-phase-matrix/auth-phase-close-silent-owner`。
对照 40250 `CAccountConnector::__AuthState_RecvPhase` 只处理 `PHASE_HANDSHAKE/PHASE_AUTH`
的分支表,修复当前端把认证 `PHASE_CLOSE` 错误转成游戏侧 `phase_closed` 和
`auth server closed phase`。认证 close 现在保持 AUTH owner,直到真实认证 socket
disconnect/failure;不得重复修改本轮的认证 close 静默语义。服务器主动 `PHASE_CLOSE`
的 live 包序仍需外部 fixture,不能用本地 phase 注入替代。
本轮新增并处理分支:
`network.login.phase_flow/auth-game-phase-matrix/phase-select-reentry-owner`。
对照 40250 `RecvPhasePacket(PHASE_SELECT) -> SetSelectPhase -> m_phaseLeaveFunc.Run()`,
修复当前端从 Game 返回 Select 时未清理旧地图 world、未通知宿主 phase-leave 的差异;现在先执行
Game owner 的 `reset_for_map_change()` 和 `on_phase_leave`,再安装 Select/DirectEnter owner。
该分支不得重复修改;真实 Windows/服务端跨阶段 retry、服务器主动 PHASE_CLOSE 和 GC_WARP
失败回退包序仍需 live fixture。
本轮新增并处理分支:
`network.login.phase_flow/auth-game-phase-matrix/phase-dead-noop-time-sync`。
对照 40250 `RecvPhasePacket(PHASE_DEAD)` 的显式空分支,修复当前端先打开 time-sync mode、
导致后续重复握手错误发出 `CG_TIME_SYNC`,以及清除显式 PHASE_CLOSE owner 标记的差异;现在
`PHASE_DEAD` 不改变阶段 owner 或 time-sync 状态。该分支不得重复修改;完整跨阶段 retry、
服务器主动 PHASE_CLOSE、空角色 live 和 GC_WARP 失败回退包序仍需独立证据。