Files
mtgodot-poc/audit/remediation-roadmap.md
T
shenleiandClaude Sonnet 5 c5c183da37 fix(movement): remove unreferenced [0.25,3.0] clamp on local player's movSpd scale
CInstanceBase::SetMoveSpeed only guards moving_speed > 1100 -> 0; the local
player's set_server_speed() was additionally floor/ceil-clamping the scale to
an arbitrary [0.25, 3.0] band with no basis in the reference or in this
project's already-fixed remote-entity equivalent (EntityStore::motion_move_speed).
A heavy haste stack or slow debuff on the local player would silently diverge
from what everyone else sees. Removed the extra clamp; kept the moving_speed<=0
early-return guard against pre-spawn/malformed values.

audit: movement.keyboard.motion/server-speed-scale-clamp closed with reference
citation, regression test, and full related-suite pass; contract stays PARTIAL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:31:17 +09:00

336 lines
25 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 客户端或视觉运行证据,不要伪装成代码已完成,记录为验证阻塞。
### 阶段 1P0 在线核心链路
按完整的用户可玩链路推进:
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 的视觉或外围功能修复范围。
### 阶段 2P1 游戏功能
依次处理背包/装备、NPC/任务、社交、目标选择、状态、模型和效果资源。`combat.flying_projectile` 在这一阶段处理;除非它直接阻塞 P0 或引入回归,否则不提前插队。
### 阶段 3P2/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 closeAppFlow 现在按 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` ownerDirectEnter
和 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
独立 callbackM2Client 不再把认证 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 失败回退包序仍需独立证据。
### 2026-09-22:本轮临时切换主合同到 `movement.keyboard.motion`
`network.login.phase_flow` 的剩余分支目前全部卡在真实 Windows 客户端/服务端主动
`PHASE_CLOSE` 包序证据或第三方闭源安全 SDKMatrix/Passpod/Panama/HybridCrypt/HS/XTrap),
本地代码层面已无可继续推进的动作。按阶段 1 的可玩链路顺序(登录 → 移动 → 战斗 → 技能 →
网络),本轮改为处理 `movement.keyboard.motion`
本轮新增并闭合分支:`movement.keyboard.motion/camera-auto-follow-rotation`
对照 40250 `CPythonPlayer::NEW_MoveToDirection``m_isCmrRot` 相机跟随分支(每帧对按住
的方向键,把相对相机的方位角折算到 `fRotRat∈[-90,90]` 并按 `m_fCmrRotSpd=20.0` 的速率用
`CCamera::Roll` 持续转动相机),当前端此前完全没有实现这个副作用。已在
`player_controller.gd` 新增 `_camera_auto_rotate()`,用同样的折叠/缩放公式驱动
`GameCamera.yaw`D3D `Roll` 与 Godot `yaw` 转向手性相反,增量符号作为显式记录的平台适配
取反,用『按住方向时相机朝该方向收敛、正前后不转、从不越界』的不变式在
`keyboard_motion_timeline_test.gd` 中核验(因为没有真实 Windows 客户端可供逐位对照)。
详见 `audit/contracts/movement.keyboard-motion.md``Implementation fix round
2026-09-22` 小节。不得重复修改本轮的 `_camera_auto_rotate()` 折叠公式或符号约定,除非
出现新的 live 证据推翻当前的收敛不变式假设。
本轮过程中额外发现但未修复(已记录进合同 `Remaining` 与本文件供下一轮参考):
`CActorInstance::CanAct()``Paralysis/Faint/Sleep` 三个移动门当前端完全没有接入
`net_play.gd::_can_process_network_state()` 只挡 `dead/stunned/knock_down`)。这三个
状态的触发源分别在萨满技能、Python 侧角色模块和效果可见性绑定,尚未定位到当前端对应的
服务端 affect 管线,属于比相机分支更大的独立子项,不要在没有先做好触发源映射的情况下
仓促接线。
`movement.keyboard.motion` 合同仍为 `PARTIAL`:根运动/`AccumulationMovement`
`OnMove/OnMoving/OnWaiting/OnStop` 资源事件源、同步/受击/死亡/传送门叠加顺序、上述
Paralysis/Faint/Sleep 移动门和真实模型姿态测试仍未闭合,下一轮可以在这些分支中任选一个
继续(`AccumulationMovement` 根运动差异或 Paralysis/Faint/Sleep 移动门是其中两个具体、
无需真实服务端/客户端证据即可推进的候选)。
### 2026-09-22T01:30Z`movement.keyboard.motion/canact-paralysis-faint-sleep-gate` 调查关闭(无需修复)
对上一轮标记的两个候选之一(`Paralysis/Faint/Sleep` 移动门)做完整可达性追踪后确认它是
**假阳性**,不是代码可执行的差异,本轮未改动 GDScript/C++ 实现:
- Paralysis:唯一 setter `__Shaman_SetParalysis``InstanceBaseEffect.cpp:821`)在整个可达
40250 源码树中零调用点,是死代码,40250 客户端实际运行时 `IsParalysis()` 恒为 false。
- Sleep`SetAffect()``AFFECT_SLEEP` 分支已被注释掉,唯一存活路径是
`AFFECT_STUN → SetSleep()``InstanceBaseEffect.cpp:932`),即 `IsSleep()` 实际上是
AFFECT_STUN 的别名,已被当前端 `entity_store.h:119``stunned` 字段和
`net_play.gd::_can_process_network_state()``dead/stunned/knock_down` 门覆盖。
- Faint:唯一 setter `chrFaintTest()``PythonCharacterModule.cpp:1087`)是与
`chrtestRestoreRenderMode` 并列的调试用 Python 绑定,作用于 `GetSelectedInstancePtr()`
现有 40250 检出没有 root/ Python 脚本可证明/证伪在线可达性,evidence-blocked,未修复。
调查过程中发现的衍生项(`IsStun()``RecvStunPacket()` 对主角 `GC_STUN``Die()`
而非 `Stun()`)核实后确认**不是** `movement.keyboard.motion` 差异——`CanAct()`
`IsDead()`/`IsStun()` 的移动阻断效果相同,当前端 `_can_process_network_state()` 也已对
`dead`/`stunned` 同等阻断,移动结果已正确等价;`Die()`/`Stun()` 分流只影响死亡系统本身
EXP 掉落/尸体/复活),已在 `combat.affect-status.md` Remaining #2、分支矩阵第
63/67 行登记,不在本合同重复记录。详见
`audit/contracts/movement.keyboard-motion.md` 的「调查轮次 2026-09-22T」小节。
**该分支已关闭,不要在没有新 live 证据的情况下重新调查 Paralysis/Faint/Sleep 或
GC_STUN 主角分流。**
`movement.keyboard.motion` 下一轮候选收窄为:`AccumulationMovement` 固定 300cm
根运动/连续速度替代差异、`OnMove/OnMoving/OnWaiting/OnStop` 资源事件源、同步/受击/
死亡/传送门叠加顺序、真实模型骨骼快切不翻转的模型级测试——四者均仍未闭合。
`network.login.phase_flow` 的下一主候选仍保持文件前面记录的
`network.login.phase_flow/auth-game-phase-matrix/live-package-order/server-initiated-phase-close`
本地结构和 owner 分支均已覆盖,只等新的真实服务端/Windows fixture;在没有新 fixture 前不要
重复 `/logout` 探针或重复修改已闭合的 owner 分支。
### 2026-09-22T03:15Z`movement.keyboard.motion/server-speed-scale-clamp` 修复
追查上一轮列出的四个候选之一(`AccumulationMovement` 根运动/连续速度替代差异)时,先确认
远端 `entity_store.cpp::advance_walk_by_motion` 和本地 `_process()` 的位移积分在种类上同构
(都是恒速 `speed*dt` 直线插值),排除了「需要整体迁移到逐帧根运动采样」这个更大方向;转而
逐项核对两条路径各自的速度缩放公式,发现真正的差异:本地 `set_server_speed()`
`moving_speed/100.0` 塞进 `clampf(..., 0.25, 3.0)`,而参考 `CInstanceBase::SetMoveSpeed`
`InstanceBaseMovement.cpp`)和本项目已修复的远端 `EntityStore::motion_move_speed` 都只有
`moving_speed>1100 → 0` 一个门,没有下限/上限夹钳。已修复为
`0.0 if moving_speed > 1100 else moving_speed/100.0`,新增 `keyboard_motion_timeline_test.gd`
断言覆盖旧边界内外和 1100 冻结门,随同全部相关回归套件(`test_wasd_steering_parity`
`test_no_auto_move_regression``game_camera_test``movement_parity_test``player_move_test`
`mouse_controller_test``test_alignment_parity``netplay_test`)通过。详见
`audit/contracts/movement.keyboard-motion.md``Implementation fix round 2026-09-22 —
server-speed-scale-clamp` 小节。**该分支已关闭,不要在没有新证据的情况下重新调整
`set_server_speed` 的缩放边界。**
副产品发现(未下定论,供下一轮参考):`CInstanceBase::SHORSE::SetMoveSpeed`
`InstanceBase.cpp:40-135`)是坐骑上玩家的独立速度设定,只在 `IsMounting()` 时生效;
远端实体对 `mount_vnum!=0` 直接归零,而本地玩家改走 `MOTION_MODE_HORSE` 动作资源——
两者是否构成差异本轮未核实,不要不经调查直接假设一致或假设有 bug。
`movement.keyboard.motion` 下一轮候选收窄为:`AccumulationMovement` 固定 300cm
根运动/连续速度替代差异(积分种类已确认同构,但逐帧时序/`.msa` motion event 源仍未对拍)、
`OnMove/OnMoving/OnWaiting/OnStop` 资源事件源、同步/受击/死亡/传送门叠加顺序、真实模型骨骼
快切不翻转的模型级测试、坐骑速度设定(`SHORSE::SetMoveSpeed`)——五者均仍未闭合。