From 7df97e01f9ac514e4e777a1af7d3f7d4a20d84c6 Mon Sep 17 00:00:00 2001 From: shenlei Date: Tue, 22 Sep 2026 16:17:00 +0900 Subject: [PATCH] fix(movement): camera auto-rotates to face WASD input direction 40250 parity: CPythonPlayer::NEW_MoveToDirection's m_isCmrRot branch continuously rolls the camera toward the held movement direction every frame while a direction key is down, independent of whether a new translation target was just set. Add _camera_auto_rotate() to replicate this (forward/back = no rotation, strafe = max rate, diagonal = half rate), called unconditionally after the frozen/locked/shop/emotion gate in _process(). Godot's yaw-increase handedness is opposite the reference's D3D Roll(), so the sign is flipped as a platform adaptation; verified by convergence invariants in keyboard_motion_timeline_test.gd rather than bit-for-bit angle matching. Closes movement.keyboard.motion/camera-auto-follow-rotation. Co-Authored-By: Claude Sonnet 5 --- audit/contracts/movement.keyboard-motion.md | 75 +++++- audit/history.jsonl | 1 + audit/manifest.json | 280 ++++++++++---------- audit/remediation-roadmap.md | 37 +++ audit/reports/coverage.md | 4 +- project/keyboard_motion_timeline_test.gd | 67 +++++ project/player_controller.gd | 29 ++ 7 files changed, 354 insertions(+), 139 deletions(-) diff --git a/audit/contracts/movement.keyboard-motion.md b/audit/contracts/movement.keyboard-motion.md index 15217302..dd5fac71 100644 --- a/audit/contracts/movement.keyboard-motion.md +++ b/audit/contracts/movement.keyboard-motion.md @@ -12,10 +12,15 @@ - `UserInterface/InstanceBaseMotion.cpp` - `GameLib/ActorInstanceMotion.cpp` - `GameLib/ActorInstanceMotionEvent.cpp` +- `GameLib/GameUtil.cpp`(`CameraRotationToCharacterRotation`/`CharacterRotationToCameraRotation`) +- `EterLib/Camera.cpp`(`CCamera::Roll`/`CalculateRoll`,增量式) +- `GameLib/ActorInstanceBattle.cpp`(`CanAct`/`CanMove`) +- `GameLib/ActorInstance.cpp`(`IsParalysis`/`IsFaint`/`IsSleep`) ## Current call chain - `project/player_controller.gd` +- `project/game_camera.gd` - `project/net_play.gd` - `project/net_world.gd` - `project/test_wasd_steering_parity.gd` @@ -81,8 +86,8 @@ | 项目 | 状态 | 备注 | |---|---|---| | Preconditions | PARTIAL | active/失焦/按键残留、锁门、私店、情绪和锁门 held WASD 已覆盖;同步、受击、死亡和传送门未全覆盖 | -| Branch structure | PARTIAL | W/S、A/D、八方向和释放顺序已覆盖;相机旋转与所有禁止移动分支未闭合 | -| Algorithms/formulas | PARTIAL | 方向优先级和归一化一致;固定 300 像素 Dst 与根运动被连续速度替代 | +| Branch structure | PARTIAL | W/S、A/D、八方向、释放顺序和 `m_isCmrRot` 相机跟随已覆盖;Paralysis/Faint/Sleep 未接入移动门 | +| Algorithms/formulas | PARTIAL | 方向优先级、归一化和相机跟随的 fold-to-±90/rate 公式一致(`movement.keyboard.motion/camera-auto-follow-rotation`);固定 300 像素 Dst 与根运动仍被连续速度替代 | | State transition order | PARTIAL | 输入状态、锁门/移动技能恢复、PlayerController、NetPlay 入口已定位;资源事件与网络事件的完整顺序未证实 | | Constants/units | PARTIAL | 角度/旋转阈值和 0/300/100ms 节流已记录;米/厘米、移动速度和目标距离不同 | | Timing/event sources | PARTIAL | 当前是 `_process` 预测 + signal;参考是 Actor motion event 驱动 | @@ -95,7 +100,16 @@ - 用真实 `camera.heading()`、模型动作速度和网络 fake 记录同一方向序列的输入事件、移动目标、停止请求、动画状态和包时间线。 - 补充死亡/传送/受击以及私店、情绪、移动技能和普通锁定的多门叠加顺序;各单门 held WASD 恢复已覆盖基础路径。 - 继续对照 `.msa` 的 motion event、`AccumulationMovement` 根运动和当前 `get_move_motion_speeds` fallback,决定是否需要根运动适配层。 -- 核对相机旋转副作用与角色方向叠加,以及真实角色模型在 W/S 快切时根节点/骨骼姿态不翻转。 +- 真实角色模型在 W/S 快切时根节点/骨骼姿态不翻转的模型级测试仍缺。 +- **新增(2026-09-22 发现,未修复)**:`CPythonPlayer::__CanMove()` 还委托 + `CActorInstance::CanAct()`,后者在 `IsParalysis()/IsFaint()/IsSleep()` 时也拒绝移动 + (`ActorInstanceBattle.cpp` `CanAct`/`CanMove`)。当前端 `net_play.gd::_can_process_network_state()` + 只挡 `dead`/`stunned`/`knock_down`,没有 Paralysis/Faint/Sleep 三个状态的移动门。 + 三者的触发源分别是萨满技能 `__Shaman_SetParalysis`(`InstanceBaseEffect.cpp:821`)、 + Python 侧 `SetFaint`(`PythonCharacterModule.cpp:1094/1101`)和效果可见性绑定的 + `SetSleep`(`InstanceBaseEffect.cpp:933`),当前端尚未定位这些触发源对应的服务端 + affect/状态位,需要先把三者映射到现有 affect 管线再决定移动门怎么接,属于比本轮 + camera 分支更大的独立子项,留给后续轮次。 ### Audit round 2026-09-21 @@ -153,3 +167,58 @@ - 继续对照 `NEW_SetMultiDirKeyState`、`NEW_MoveToDirection`、`AccumulationMovement` 和 `OnMove/OnMoving/OnWaiting/OnStop` 后,确认当前端仍是 Godot `_process` 连续速度预测,不能用输入测试替代 40250 `.msa` 根运动/动作事件源证据。 结论:本轮未修改实现;输入回归保持稳定,合同继续 `PARTIAL`。下一轮需要用真实动作资源和 fake network timeline 对拍固定 300cm 目标、根运动、停止事件和 MOVE/WAIT 包边沿。 + +### Implementation fix round 2026-09-22 — `movement.keyboard.motion/camera-auto-follow-rotation` + +本轮处理的具体分支:40250 相机旋转副作用(此前 Remaining 与 Branch structure 行标注 +的 `没有实现同一套相机旋转副作用`)。 + +**参考实现**(`UserInterface/PythonPlayerInput.cpp:445-491` `CPythonPlayer::NEW_MoveToDirection`): +`IsOpenPrivateShop()`/`isLock() && !IsUsingMovingSkill()` 提前返回(不跑相机分支)之后, +若 `m_isCmrRot`(硬编码为 true,从未被关闭): +把方向角 `fDirRot`(0=前 90=左 180=后 270=右,来自 `NEW_GetMultiKeyDirRotation` → +`NEW_GetMouseDirRotation` → `GetDegreeFromPosition` 的屏幕空间角)折成有符号 +`fSigDirRot ∈ (-180,180]`,再折叠到 `fRotRat ∈ [-90,90]`(正前/正后为 0,正左/右为 +±90 最大速率,对角线线性减半),套公式 +`fRotDeg = -m_fCmrRotSpd(20.0) * fElapsedTime * fRotRat / 90`,通过 `CCamera::Roll(fRotDeg)` +(D3D 增量式 roll,非绝对值)应用;这一步与是否已建立平移目标无关,且 `CPythonPlayer::Update` +每帧对仍按住的方向键重试整条链路,所以只要方向键按住、且通过门检查,相机就持续转动。 + +**当前实现(修复前)**:`player_controller.gd::_keyboard_wish()` 只读 `camera.heading()` +构造世界方向向量,从不写回 `camera.yaw`——完全没有这个副作用。 + +**修复**:新增 `PlayerController._camera_auto_rotate(dir, dt)`(`player_controller.gd`), +在 `_process()` 的 `frozen/locked/private_shop_open/processing_emotion` 门之后、其余 +移动逻辑之前,用当前帧 `_wasd()` 的方向向量无条件调用(门未通过时函数整体在早退分支 +里不会被调用,等价于参考实现的 `IsOpenPrivateShop`/`isLock` 早退)。公式与参考实现的 +折算/折叠逻辑逐项相同(`fSigDirRot`/`fRotRat` 折叠、`CAMERA_AUTO_ROTATE_SPEED_DEG=20.0` +对应 `m_fCmrRotSpd` 默认值、按 `dt` 与 `fRotRat/90` 线性缩放)。**平台适配**:D3D +`CCamera::Roll` 的正方向与 Godot `yaw` 增大(视线转向左)手性相反,因此增量对 `fRotRat` +取正号而非参考实现的负号;这一符号翻转用不变式核验,而不是逐位对照 D3D 数值—— +"持续按住某一方向时,相机朝该方向收敛(正前/正后不转,正左右转速最快,对角线减半), +从不反向或越界",与参考实现的稳态行为一致。移动技能中的转向、以及触屏/摇杆 +`mobile_axis` 路径本轮未改动(后者是纯前端手势适配,参考实现没有对应的按键状态机, +留作独立分支,不在本次改动范围内)。 + +**测试**:`keyboard_motion_timeline_test.gd` 新增六项断言(`GameCamera` 实例挂到 +`pc.camera`,不加入场景树,只读写 `yaw`):纯前/后不转、纯左/右等速反向、多帧速率恒定、 +对角线为纯左的一半速率、`locked` 门下按住方向键相机不转。修复前四项方向性断言必然 +失败(`cam.yaw` 恒为初始值 0),修复后全部通过。 + +**测试结果**:`keyboard_motion_timeline_test.gd`、`test_wasd_steering_parity.gd`、 +`test_no_auto_move_regression.gd`、`game_camera_test.gd`、`movement_parity_test.gd`、 +`player_move_test.gd`、`mouse_controller_test.gd`、`test_alignment_parity.gd` 修复后全部 +退出码 0(Godot 4.7.1 headless)。`player_motion_test.gd`(旧文件名,非本轮新增)在本环境 +用 `godot --headless --path project --script player_motion_test.gd` 单独运行时因脚本自身的 +`GDScriptNativeClass.clear_texture_cache()` 静态方法找不到而解析失败——本轮未改动此文件, +`git log` 确认最后一次改动早于本会话;这是与本分支无关的预置环境问题,未修复,仅记录。 +`build-debug/` 的 `CMakeCache.txt` 记录的是另一个已废弃的检出路径 +(`/Users/shen/Work/Code/Metin2/...`),导致 `cmake --build build-debug` 在本环境直接报错, +无法据此重跑 ctest/ASAN 套件;本轮改动只涉及 GDScript,未触及 `extension/` 下任何 C++, +判断为与本分支无关的预置构建环境问题,同样未修复,仅记录供后续轮次或维护者决定是否 +重新配置构建目录。 + +**equivalence 更新**:Branch structure、Algorithms/formulas 两行的相机旋转子项由未闭合 +改为已闭合(见上表),`Remaining` 移除相机副作用项、新增 Paralysis/Faint/Sleep 移动门 +子项(见上)。合同整体仍为 `PARTIAL`——根运动/资源事件/同步受击门/Paralysis-Faint-Sleep +仍未闭合。 diff --git a/audit/history.jsonl b/audit/history.jsonl index 8784feac..4c66ae83 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -480,3 +480,4 @@ {"time":"2026-09-21T16:56:06Z","event":"implementation_fix_round","result":"PARTIAL","ids":["network.login.phase_flow/auth-game-phase-matrix/phase-select-reentry-owner"],"files":["extension/src/net/classic/classic_session.cpp","extension/tests/net_classic_session_test.cpp","audit/contracts/network.login.phase-flow.md","audit/remediation-roadmap.md","audit/manifest.json","audit/history.jsonl","audit/reports/coverage.md"],"tests":["40250 reference source inspection (PASS: PythonNetworkStreamPhaseSelect.cpp SetSelectPhase runs m_phaseLeaveFunc before installing SelectPhase; Game owner maps to __LeaveGamePhase)","pre-fix build-debug/extension/net_classic_session_test (FAIL: Game -> Select retained the old world and skipped phase-leave notification; 2 checks)","post-fix build-debug/extension/net_classic_session_test (PASS)","cmake --build build-debug -j4 (PASS)","ctest --test-dir build-debug --output-on-failure -j2 (PASS; 23/23)","ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 build-asan/extension/net_classic_session_test (PASS)","MT_PROTOCOL=classic godot --headless --path project --script app_flow_lifecycle_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script p10_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script netplay_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script disconnect_world_reset_test.gd (PASS)","audit_phase_dispatch.py (PASS; no payload differences)","audit_packet_registry.py (PARTIAL; only approved HDR_GC_SYMBOL_DATA adaptation)","audit_sequence_table.py (PASS; 32768 entries identical)","audit_source_coverage.py (PASS; 0 unowned behavior candidates)","audit_ledger.py refresh --write/report --write/validate (PASS; 45 contracts)","manifest/history JSON validation (PASS)","git diff --check (PASS)"],"reason":"按 40250 SetSelectPhase 的旧 phase-leave 语义,修复 Game -> Select 时旧 world 未清理、宿主未收到 phase-leave 的结构差异;只在 InGame owner 分支执行 reset_for_map_change/on_phase_leave,随后进入 Select/DirectEnter owner。未修改飞行物、技能、战斗、角色名编码或 UI;真实 Windows/服务器主动 PHASE_CLOSE、空角色 live、完整跨阶段 retry、GC_WARP 失败回退和可选安全 runtime仍未闭合,合同保持 PARTIAL。"} {"time":"2026-09-21T17:01:24Z","event":"implementation_fix_round","result":"PARTIAL","ids":["network.login.phase_flow/auth-game-phase-matrix/phase-dead-noop-time-sync"],"files":["extension/src/net/classic/classic_session.cpp","extension/tests/net_classic_session_test.cpp","audit/contracts/network.login.phase-flow.md","audit/remediation-roadmap.md","audit/manifest.json","audit/history.jsonl","audit/reports/coverage.md"],"tests":["40250 reference source inspection (PASS: PythonNetworkStream.cpp RecvPhasePacket(PHASE_DEAD) has an explicit empty branch and returns true without a phase setter)","pre-fix build-debug/extension/net_classic_session_test (FAIL: PHASE_DEAD enabled time-sync and changed the next handshake response; 1 check)","post-fix build-debug/extension/net_classic_session_test (PASS)","cmake --build build-debug -j4 (PASS)","ctest --test-dir build-debug --output-on-failure -j2 (PASS; 23/23)","ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 build-asan/extension/net_classic_session_test (PASS)","MT_PROTOCOL=classic godot --headless --path project --script app_flow_lifecycle_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script p10_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script netplay_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script disconnect_world_reset_test.gd (PASS)","audit_phase_dispatch.py (PASS; no payload differences)","audit_packet_registry.py (PARTIAL; only approved HDR_GC_SYMBOL_DATA adaptation)","audit_sequence_table.py (PASS; 32768 entries identical)","audit_source_coverage.py (PASS; 0 unowned behavior candidates)","audit_ledger.py refresh --write/report --write/validate (PASS; 45 contracts)","manifest/history JSON validation (PASS)","git diff --check (PASS)"],"reason":"按 40250 RecvPhasePacket(PHASE_DEAD) 的空操作语义,移除当前端错误打开 time-sync mode 的副作用,保持重复握手使用普通 HANDSHAKE 回包。未修改飞行物、技能、战斗、角色名编码或 UI;真实 Windows/服务器主动 PHASE_CLOSE、空角色 live、完整跨阶段 retry、GC_WARP 失败回退和可选安全 runtime仍未闭合,合同保持 PARTIAL。"} {"time":"2026-09-21T17:08:09Z","event":"implementation_fix_round","result":"PARTIAL","ids":["network.login.phase_flow/auth-game-phase-matrix/phase-dead-noop-time-sync"],"files":["extension/src/net/classic/classic_session.cpp","extension/tests/net_classic_session_test.cpp","audit/contracts/network.login.phase-flow.md","audit/remediation-roadmap.md","audit/manifest.json","audit/history.jsonl","audit/reports/coverage.md"],"tests":["40250 reference source inspection (PASS: PythonNetworkStream.cpp RecvPhasePacket(PHASE_DEAD) is an explicit empty branch; only real phase setters clear the close owner)","pre-fix build-debug/extension/net_classic_session_test (FAIL: PHASE_DEAD cleared the explicit PHASE_CLOSE owner marker; 1 check)","post-fix build-debug/extension/net_classic_session_test (PASS: plain HANDSHAKE response and PHASE_CLOSE marker are both preserved)","cmake --build build-debug -j4 (PASS)","ctest --test-dir build-debug --output-on-failure -j2 (PASS; 23/23)","ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 build-asan/extension/net_classic_session_test (PASS)","MT_PROTOCOL=classic godot --headless --path project --script app_flow_lifecycle_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script p10_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script netplay_test.gd (PASS)","MT_PROTOCOL=classic godot --headless --path project --script disconnect_world_reset_test.gd (PASS)","audit_phase_dispatch.py (PASS; no payload differences)","audit_packet_registry.py (PARTIAL; only approved HDR_GC_SYMBOL_DATA adaptation)","audit_sequence_table.py (PASS; 32768 entries identical)","audit_source_coverage.py (PASS; 0 unowned behavior candidates)","audit_ledger.py refresh --write/report --write/validate (PASS; 45 contracts)","manifest/history JSON validation (PASS)","git diff --check (PASS)"],"reason":"在同一分支的进一步等价审计中补齐 PHASE_DEAD 的第二个无副作用约束:通用 phase != PHASE_CLOSE 条件会清除显式关闭 owner,修复为仅真实 phase setter 清除 m_phase_closed。此为同一稳定分支的实质性实现补充,不是重复修复。未修改飞行物、技能、战斗、角色名编码或 UI;真实 Windows/服务器主动 PHASE_CLOSE、空角色 live、完整跨阶段 retry、GC_WARP 失败回退和可选安全 runtime仍未闭合,合同保持 PARTIAL。"} +{"time": "2026-09-22T00:00:00Z", "event": "implementation_fix_round", "result": "PARTIAL", "ids": ["movement.keyboard.motion/camera-auto-follow-rotation"], "files": ["project/player_controller.gd", "project/keyboard_motion_timeline_test.gd", "audit/contracts/movement.keyboard-motion.md", "audit/remediation-roadmap.md", "audit/manifest.json", "audit/history.jsonl"], "tests": ["40250 reference source inspection (PASS: PythonPlayerInput.cpp:445-491 CPythonPlayer::NEW_MoveToDirection m_isCmrRot branch folds fDirRot to fSigDirRot/fRotRat and applies CCamera::Roll(-m_fCmrRotSpd*fElapsedTime*fRotRat/90) every frame a direction key is held and the movement gates pass)", "pre-fix keyboard_motion_timeline_test.gd (FAIL by construction: camera.yaw never written by _keyboard_wish/_process, new directional assertions would fail)", "post-fix godot --headless --path project --script keyboard_motion_timeline_test.gd (PASS)", "godot --headless --path project --script test_wasd_steering_parity.gd (PASS)", "godot --headless --path project --script test_no_auto_move_regression.gd (PASS)", "godot --headless --path project --script game_camera_test.gd (PASS)", "godot --headless --path project --script movement_parity_test.gd (PASS)", "godot --headless --path project --script player_move_test.gd (PASS)", "godot --headless --path project --script mouse_controller_test.gd (PASS)", "godot --headless --path project --script test_alignment_parity.gd (PASS)", "git diff --check (PASS)", "audit_ledger.py refresh --write/report --write/validate (PASS)", "cmake --build build-debug -j4 / ctest (BLOCKED: pre-existing stale CMakeCache.txt from a different checkout path, unrelated to this GDScript-only change; not run)", "godot --headless --path project --script player_motion_test.gd (BLOCKED: pre-existing parse error, GDScriptNativeClass.clear_texture_cache() not found in this Godot 4.7.1 build; file untouched this round, last changed in a prior commit)"], "reason": "按 40250 CPythonPlayer::NEW_MoveToDirection 的 m_isCmrRot 分支,在 player_controller.gd 新增 _camera_auto_rotate():按住方向键且通过 frozen/locked/private_shop_open/processing_emotion 门后,每帧按方向相对相机的方位角折算 fRotRat(正前/正后 0,正左右 ±90 最大速率,对角线减半),以 m_fCmrRotSpd=20.0 的速率持续把 camera.yaw 转向新朝向。D3D CCamera::Roll 与 Godot yaw 的转向手性相反,增量对 fRotRat 取正号(参考实现取负号),作为显式记录的平台适配,用『按住方向时相机朝该方向收敛、正前后不转、从不越界』的不变式核验,而非逐位对照 D3D 数值。未修改飞行物、技能、战斗、UI 或根运动/资源事件路径;Paralysis/Faint/Sleep 未接入移动门(新发现,记录于 Remaining,未修复)、根运动/AccumulationMovement、同步/受击门恢复顺序、真实模型姿态测试仍未闭合,合同保持 PARTIAL。"} diff --git a/audit/manifest.json b/audit/manifest.json index 5e416d87..ee45b897 100644 --- a/audit/manifest.json +++ b/audit/manifest.json @@ -198,9 +198,9 @@ ], "notes": "按 40250 SetLoadingPhase/SetGamePhase 修复 Loading 遮罩阶段归属和无 SceneTree 选人确认吞包;按 ClearLoginInfo 修复 CG_LOGIN/CG_LOGIN3 成功发送后的密码生命周期;auth-game-phase-matrix 修复 PHASE_CLOSE 回 LoginPhase、Loading/Game phase-leave world 清理、宿主 world_reset 通知、重复 phase handler、auth/game 登录拒绝保留 phase 和登录失败回调;本轮 auth-success handoff 按 CAccountConnector::__AuthState_RecvAuthSuccess -> SetLoginKey -> MainStream.Connect -> AccountConnector.Disconnect 固定为 game transport 先连接、auth transport 后清理,并以 loopback 失败前/后顺序回归;login-failure-retry-cleanup 现已补齐认证端 peer close -> Offline owner、认证 peer-close bridge 保留非终态 LoginPhase owner、认证 PHASE_CLOSE 静默保留 AUTH owner、游戏 peer close 前置通知、旧 world 清理、DirectEnter/普通 LoginPhase game connect failure 的独立 owner、认证 connect_failed bridge、显式重试请求保护、普通 Select/Game -> Login 的旧角色/world 清理、SELECT/Loading 的 phase-only LoginPhase owner、LOGIN 阶段失败保留原 LoginPhase UI、LoginPhase retry 只替换认证 transport 且不触发重复全局 world_reset、PHASE_AUTH 前登录包的阶段隔离、active Loading/Game 未知头按 RecvErrorPacket 清理缓冲并保留 phase owner、游戏态 PHASE_CLOSE/peer-close 后 AppFlow 释放 GameScene 并切换为唯一 LoginPhase owner、显式 PHASE_CLOSE 不再伪造 transport disconnected、Loading PHASE_CLOSE leave owner/world 清理、GC_WARP/快速重连同步连接失败后的 retry owner,以及空重连角色列表回到 SELECT 且不发送过期槽位;同步增加 ClassicSession 析构前 stream 回调解绑,避免最终 Offline 通知写入已析构字段;本轮 legacy-text-wire-encoding 固定 UTF-8 内部与 GB2312/兼容 wire 边界,修正 24-byte 角色名容量和 GCLoginSuccess4 329-byte 布局;本轮 live-package-order 让实时探针观察应用层登录失败和 game remote-disconnect 回调,不把 40250 的失败回调误报为 timeout;本轮 ticket-slot-warp-order 修复 GC_WARP 复用 session 时继承旧 PHASE_GAME 在线标记,并用回归测试核对 DirectEnter/GC_WARP 的 CG_LOGIN2 包序和 login key;本轮 direct-enter-loading-select-packet 按 40250 introloading.LoadingWindow.Open 的实际调用链补发 DirectEnter 的 CG_PLAYER_SELECT,实时探针已到达 PHASE_GAME;本轮 gc-warp-live-package-order 新增 e2e/Godot live 验证入口,确认真实 GC_WARP 的 world_reset、replacement CG_LOGIN2/CG_PLAYER_SELECT、Loading 和第二次 PHASE_GAME 成功,未发现生产连接逻辑差异;本轮 optional-security-branches 为静态可选包和 HybridCrypt 动态包增加显式 deployment guard/optional_security_required,保持 phase owner 但不伪装成已实现 vendor runtime;本轮 peer-close-login-owner 依据真实 /logout peer-close 证据,移除 AppFlow 的自动重连遮罩/旧 GameScene 保留,回归 LoginPhase owner;本轮 phase-close-server-fixture 分离显式 PHASE_CLOSE 与 OnRemoteDisconnect,不再由 native bridge 发出伪造的 disconnected 信号;本轮 empty-role-live-fixture 为真实空角色包序增加 MT_CLASSIC_EXPECT_EMPTY=1 探针模式,当前仍缺全空账号正向 live 证据;本轮 login-form-retry-owner 按 40250 LoginWindow.Connect/AccountConnector.Connect 增加 LoginPhase retry owner 和只替换认证 transport 的 native/UI 回归;本轮 game-connect-immediate-failure-owner 按 CPythonNetworkStream::OnConnectFailure 分离普通 LoginWindow.OnConnectFailure 与 DirectEnter ClosePhase,并新增 M2Client connect_failed signal,GC_WARP 失败不再伪造 disconnected;本轮 auth-connect-bridge-owner 按 CAccountConnector::OnConnectFailure -> Offline -> LoginWindow 分离认证连接失败和凭据失败,保留 ClassicSession 供 retry_login 复用,并以 auth_connect_bridge_test.gd 固定修复前失败/修复后通过证据;本轮 auth-peer-close-bridge-owner 按 CAccountConnector::OnRemoteDisconnect -> Offline 让认证 peer close 不再伪造 login_failed/connect_failed,并以 auth_peer_close_bridge_test.gd 固定旧/新认证 transport 的回归证据。ClassicStream 的 TCP connect deadline、半包、本地 loopback、文本编码回归、真实成功包序、真实失败回调、DirectEnter CG_PLAYER_SELECT 到 PHASE_GAME、真实 GC_WARP 成功 round trip、auth-peer-close owner、auth-peer-close bridge、登录 UI owner、PHASE_CLOSE 旧 world owner、跨阶段 LoginPhase owner、显式 PHASE_CLOSE transport-owner 分离、空角色本地 SELECT owner、auth handshake owner 隔离、认证 PHASE_CLOSE 静默 owner、Loading unknown-header owner、HybridCrypt guard、Loading PHASE_CLOSE leave owner、warp connect failure retry owner、空重连 SELECT owner、pre-game LoginPhase owner、本轮 peer-close LoginPhase owner、login-form-retry-owner、game-connect-immediate-failure-owner 和 auth-connect-bridge-owner 已有本地证据;空角色全空 live fixture、服务器主动 PHASE_CLOSE live 包序、认证其它完整副作用、跨阶段完整 UI retry/真实重复进入包序、GC_WARP 失败回退包序、真实 Windows LoginWindow.OnConnectFailure 和可选安全 runtime 仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "f5437f05ed4f07b67fe1650626e3402c15b40d35556be89b4b894756d6867036", - "implementation": "4ccd216584c72f075a6bf6b437e6fcecb3868378fb42caff2c70cbe0e263bec8", - "tests": "4676ffdf7c24266f79b2f5dad54ed12727c26a144f7be28e5bf2c68f37acfc09" + "reference": "79759dc3fcc9e1a4d9b779e53484b1d3abfb2b822e26d1c45d7364e9dba5f7a3", + "implementation": "be1421c9c311c980829da8db8edf39ca930661fca905813b29549598bd457c17", + "tests": "d6d994c08fabd5100f5d9f84e32948ff16aaea4ec2f7b4b8407ff7f2136c9b17" } }, { @@ -216,7 +216,11 @@ "UserInterface/InstanceBaseMovement.cpp", "UserInterface/InstanceBaseMotion.cpp", "GameLib/ActorInstanceMotion.cpp", - "GameLib/ActorInstanceMotionEvent.cpp" + "GameLib/ActorInstanceMotionEvent.cpp", + "GameLib/GameUtil.cpp", + "EterLib/Camera.cpp", + "GameLib/ActorInstanceBattle.cpp", + "GameLib/ActorInstance.cpp" ], "symbols": [ "CPythonPlayer", @@ -228,6 +232,7 @@ "implementation": { "files": [ "project/player_controller.gd", + "project/game_camera.gd", "project/net_play.gd", "project/net_world.gd", "project/test_wasd_steering_parity.gd", @@ -252,7 +257,13 @@ "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, - "platform_adaptations": [], + "platform_adaptations": [ + { + "description": "40250 CPythonPlayer::NEW_MoveToDirection m_isCmrRot 相机跟随:D3D CCamera::Roll 的正方向与 Godot yaw 增大(视线转向左)手性相反,_camera_auto_rotate() 对折叠后的 fRotRat 取正号而非参考实现的负号。", + "invariant": "按住某一方向键且通过移动门时,camera.yaw 持续朝该方向收敛:正前/正后 fRotRat=0(不转),正左/右 fRotRat=±90(最大速率),对角线线性减半;速率恒为 CAMERA_AUTO_ROTATE_SPEED_DEG(20.0)*dt*fRotRat/90,从不反向或越界。", + "test": "project/keyboard_motion_timeline_test.gd" + } + ], "evidence": { "contract": "audit/contracts/movement.keyboard-motion.md", "tests": [ @@ -266,14 +277,15 @@ "remaining": [ "固定 300 像素 Dst 已按事件创建并在目标到达时消费;锁定/移动技能期间 held WASD 会在解锁后重试;.msa 根运动和当前连续速度/米制推进的时序差异仍未闭合", "OnMove/OnMoving/OnWaiting/OnStop 与当前每帧 signal、NetPlay 节流的事件源逐项证明", - "同步、私店、受击、死亡、传送、情绪以及多种锁门叠加顺序;移动技能/普通锁定的 held WASD 恢复已覆盖基础路径", - "相机旋转副作用和真实角色模型/骨骼在快速反向输入时不翻转", - "输入事件到移动请求、停止请求和网络包的完整时间线" + "同步、私店、受击、死亡、传送、情绪以及多种锁门叠加顺序;移动技能/普通锁定/私店/情绪的 held WASD 恢复已覆盖基础路径", + "真实角色模型/骨骼在快速反向输入时不翻转的模型级测试", + "输入事件到移动请求、停止请求和网络包的完整时间线", + "CActorInstance::CanAct() 的 Paralysis/Faint/Sleep 三个移动门未接入当前端 net_play.gd 的移动状态判定(新发现,2026-09-22,未修复,见合同 Remaining)" ], "fingerprints": { - "reference": "299f39fd0c470bfe84c8520b30f23d5f25ffc563cfd277c1f30e5b622627d958", - "implementation": "751a83ed849b318ee47472ccb3998dbe54742b76d39169abbfa932b737f5bfbf", - "tests": "fa6838d2ca54407b8e8257eba0ed006e5edb2ee78f62cae3a40cb4a0db9fafb6" + "reference": "20c74407914aba18dde8f76f7b86917372a56cc7333b2ca8bfeee76c39e357e4", + "implementation": "0ad8d1ee88bce90cd1ccf5b8dc26f8e37541e91056a2c32eb3202b07b1d762d5", + "tests": "57893b1268088b1b393dac6a94142e792ab697e55cb29a22234df4bb93cac034" } }, { @@ -380,9 +392,9 @@ ], "notes": "本轮已按 40250 SendClickItemPacket/GetCloseItem/__OnPressItem 修复 party/anti-flag 所有权、DISTANCE_APPROX/300cm 选择、最高 VID tie-break、骑马固定边界和 150cm 点击入口距离门;并修复 RecvCreateFlyPacket 在本地主角 EntityStore 行暂缺时通过 GetMainInstancePtr 语义回退 _local_node,同时要求远端起点/终点实例存在后才创建 indexed fly。相关回归通过;真实服务端死亡包序、重复/乱序/重连/拾取竞争、死亡资源生命周期和跨图清理仍保持 PARTIAL。", "fingerprints": { - "reference": "2fd8011f984a11ce6ae2aa357a7a8be5ba838a4cc65885ac5af7b02c8a7ef73a", - "implementation": "37890f7f10720c9d567bcf4d22ce699145b0838e0ffd1ab0d89a5e0a8a34426c", - "tests": "2ea78de25b5e51361dd6b6256c29254bb29ffc9f2a2a6ba35c8fb03affd73cb3" + "reference": "6a3545f5d1f424e1404c3dd6a66f49dfa28a105b639fce5f8009860fa50928b6", + "implementation": "12eea2672396df3fb36c8ac2b4ab10f6ec1ec9824b6e33e095a7dfb4cc3251c7", + "tests": "f066c45415ecd31e61da172c633c1fd7d29f89a8d6a8c5a7a13ca2f47b946af8" } }, { @@ -480,9 +492,9 @@ "逐技能 MSA 事件到 MSE 的挂点、颜色、混合、飞行/特殊攻击和服务端结果语义" ], "fingerprints": { - "reference": "ec50966d172f454a71ce8368aa4f5c088a4b7613f508070397db14e405c07fae", - "implementation": "0b5ce6ab0b751378fdad8ae8140471df51f2ab0572d7bd5d209c5b2663975abf", - "tests": "cd487545321a14364ab51e2fd39cc232944f68fdeaa4489e1633fd8348036a84" + "reference": "e390db94ec7e4c5d0d6518beff0319fe512af840ebe31d07d6136d48dd21687e", + "implementation": "b46796eab17505be66bb99b605f082d781fbda00b06bde98297dd9cd6faea565", + "tests": "ec3c2c7f212a4d0af4da04590dd61ebefc8e200258877c9cdf45195f8913b107" } }, { @@ -564,9 +576,9 @@ "重复打开关闭、切换目标、空装备和远程更新测试" ], "fingerprints": { - "reference": "510b9218d7276cdc30688f45a1a8c113264cc40a429f0d9e82768b946bcac938", - "implementation": "2380ee1375b882e42e374ed25a59ca346cb46ec8f6932922683f193e460ef67d", - "tests": "59f2c3750d6845db69795e01ceb51a664e384d8f8a79ef9d80691b8453bd78f6" + "reference": "7dd24059f8bdd75e76798702c0b2e951452232ab65ce20eb91ad155312087026", + "implementation": "db411e1ce47a22608c1d3dbab7ee77105ec68298f9a159611ec66c3bc6ce7fc2", + "tests": "5de299be840bc0d46f5eea3acd55af332dd3e1c71472e9ae428e7762bc7f72b8" } }, { @@ -687,9 +699,9 @@ ], "notes": "本轮完成启动、阶段、Loading、DEAD/CLOSE、焦点/后台、旧 login.gd 入口和销毁调用链审计;按 40250 修正 DeathUI 的 PHASE_DEAD 错误依赖、classic Loading 旧 world 清理、两条入口的 close_requested 消费和幂等退出,以及 _goto_game 重复夺取网络 pump。完整清理 owner、CLOSE 状态语义、跨节点阶段时序、旧入口和销毁顺序仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "de448fadf6154e7d7e8375358fc41253fdbf123d141734b366eae631bad04acb", - "implementation": "b4525f86e212e902df4899dc11591d4fc2516ce39138227008652ab973465795", - "tests": "311276b58ab3a25978e7d0659b0376ced27fccd075f6f70b0e98046e158b166f" + "reference": "7538cd86aa2937e6545ddf2d16bd75eba40cefcd4c9a2aa87b8e367906bfb3a5", + "implementation": "0845a372bd9f71cf3b2537dd537b85ff7c18c6af6a0efe81c67c0c646e1b15ca", + "tests": "e865f9782600d3d2027db84beb10913a22a22c751938b707768e7c838d894a1a" } }, { @@ -798,9 +810,9 @@ ], "notes": "按 40250 固定槽位、帝国写入时点、未知改名 PID、DirectEnter SelectPhase 分支、角色 CRUD 阶段门禁和中文 GB2312 fixed-name bytes 修复 classic 实现;native 角色/网络回归、角色 UI、选人和生命周期回归通过,非中文 locale、异常双窗口、重复进入、角色实例管理器和真实包序仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "a77f684dbef9f1c7cb4ef195ee699df504137062ff1a0e19f0a6e13c9a7c8f3b", - "implementation": "51bc523ef7997cca1b8355ea25f0127c4f9b1381ef15a8e5f06a53d7b236a4e2", - "tests": "d85f1b2f17296f6c2308730d34a326c3f07f8520a7513a9ccd7bd08ad3b07790" + "reference": "19428229f676c622ba55bf9b59d788730fc0751b9cf10ecf3006fe5b88df66e4", + "implementation": "9b8136a0062b3268f0bda3d9f13e52e3a6a5aa2d105c22fb16cd38afb56e5c18", + "tests": "f56863d12c0cb2b8dcf535c2db8cab9b432d2dadd22f2bf133f0ec0630d7dafd" } }, { @@ -886,9 +898,9 @@ ], "notes": "已按 40250 固定头+文本尾语义修复 GC_WHISPER;ClassicSession 已收口 Login/Select/DirectEnter/Loading/Game 的阶段门禁,且由 audit_phase_dispatch.py 证明四阶段 payload case 按数值一致;Loading 的 ITEM_SET2 通过 GamePhase fallback,HybridCrypt 由 ClassicStream 消费后返回,phase/握手/初始密钥协商后 return,KEY_AGREEMENT_COMPLETED 会解密并继续派发同一缓冲区的密文,错误阶段包在当前帧消费后清空剩余 buffer;ClassicStream 已加入 Game 阶段小 burst 的 MAX_RECV_COUNT 边界、已知 handler 失败后的 RecvErrorPacket 清缓冲/owner 保留、CheckPacket 前导零语义,并补齐旧版 85 字节主角色包。按实际非 GAIDEN 条件,主游戏注册表数值/类别已覆盖 117/117,移除 6 个未注册普通流兼容包头,guild-mark 的 SYMBOL_DATA 保留为独立适配;动态 parser 对 SCRIPT、QUEST_INFO、MESSENGER、SHOP、GUILD、DUNGEON、NPC_POSITION、LAND_LIST、CHAT 的截断字段改为失败并复用该清理路径。wire/stream/session/encstream/phase-matrix 回归通过;逐项 sizeof/byte layout、8192 字节阈值、大量 burst 和 QUEST_INFO/GUILD 特殊边界仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "4ac673e74894dc9530fa534bc88907d4d79d1066647ed2885167b237df324c51", - "implementation": "90ae03258aea10f7f2e452d9209c82c42877eb49199c73b1d13d1de2d74d6e1e", - "tests": "a64d32c7e6d21377c0abe5f98f1810387ede81799a2172fcdbbd19c329d1cb54" + "reference": "70235b4c609e89c03f033249fcd53dd4e775f579f4fae8c9cc86cc1dad3a77d3", + "implementation": "0be8e78eb0a0f6da7e1af10a3aa77fa43e93915fefc1bd7fc60c117174a57404", + "tests": "b45313ce3f4ab491face17e762d6715ca8d2d802175eb28c37ab138be844ed46" } }, { @@ -996,9 +1008,9 @@ ], "notes": "已按 40250 handshake/handshake_ok 修复后续 server clock 二次校准,并通过 sequence table 32768 项逐字节核对;DH2/CTR fixture、全局 clock owner、legacy/optional security 分支、Windows codepage 和完整 bSeq 矩阵仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "24cfabc7a6c89246ffeac9aa9e8c94a9e2717910c41273d87a7d17586b95fca0", - "implementation": "03324b9b59bb41f2e94ac6b0dc406fcfe01b2a7c3b38c986a49aa12a5b30ec58", - "tests": "34b87887c7c0bdb66d00aafe532b07c2ac8d0e967dcee0085f0e980f2a38e4d5" + "reference": "40701de0c80d0226ca751b34ef81a6f96f2cb005f8012a5c1f7ed5148ec5e4d6", + "implementation": "b1a5d95818f5d0e4e21da62f9262cbd560f452ff42a9b0a2db5ac0e4120d5f18", + "tests": "f6a16e75e4a98ee53f2757dd3ecc05b715cc353ee98c40365e624f7e9386039d" } }, { @@ -1081,9 +1093,9 @@ ], "notes": "已按 40250 同一 CNetworkStream Clear->Connect 语义修复 classic generic reconnect 的 session/selected slot/login key 保留,加入 3000ms 连接 deadline、连接中幂等 guard,并修复多 warp cue 在第一个重连 return 后被整批清空的问题;显式 disconnect 现在先 reset 活动 EntityStore、发 world_reset,再释放 transport,并有独立回归。真实阻塞连接、旧新 socket 竞态、包序和清理 owner 时序仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "ca61e74a7ce0c2779443be398bd2ad02aa3a74609abd068ce955377b793706ba", - "implementation": "0ff81cfa25396fa829d8e38658fe307ec7b3ec9260cba4b63f598247077eac6c", - "tests": "16d39cce884e002d68ef6e4f19c8471d7f96bf468f7842122577c01113820a32" + "reference": "3797fce63b45cc47556f83c588699d6bf04000ff1bf821fa3b568b20006fac6c", + "implementation": "de467a517c36ed61f0fd0bb94a6f2df85644d57690c50e198ec6c9e5c4e8ddd0", + "tests": "9dfa17b7705966830888646381d7abe70653a2b381383144489c2f0d456899e5" } }, { @@ -1128,9 +1140,9 @@ ], "notes": "本轮按 40250 修复认证密码生命周期、两套 transport 的连接 deadline/可写完成判定,以及 ChannelStatus 旧 probe 与重入 callback 清理;频道检测仍保留当前端逐频道 TCP fallback,认证失败/远端关闭/完整恢复与边界矩阵仍未闭合,合同保持 PARTIAL。", "fingerprints": { - "reference": "56e0c6a176da6228610bc6b5ce7ad6ca3cca202df2d3eaa50a57593c4c58f2c5", - "implementation": "f3c70a4d708138acba6cb7c0893cc0ab6da69d8f878487e23d99c020ac0e3017", - "tests": "c329ba998983d0697f38b332c5c65253603912a590f45870990db2e8f33b3e00" + "reference": "cbfb26c28228d50ea6af675e69f7766d06a6292574bd913e5db5d56979bedd4e", + "implementation": "c203e25d94a95cf8c342692525a4f8a2bf8d5a1885f209b52e4e884be291bf4d", + "tests": "0977a59547cbe394ab546237bd1ababe4e3069d946487b91ede25ea3e4dc5778" } }, { @@ -1236,9 +1248,9 @@ ], "notes": "本轮补充了同 VID 普通重建/上下马位置保留、淡出期间同 VID 立即重建、单槽 pending、逐帧可见性、主角 MainSet 通知次数,以及 classic/generic Add/Add2/AdditionalInfo/Update 的完整字段、隐身过滤、unknown VID 和事件粒度回归;net_entity_test、net_classic_session_test、net_world_respawn_test 与 net_world_vis_test 通过,Delete/map reset 后迟到包、旧 dead-list/tween 跨帧并存、主 VID 清理和 Godot 渲染副作用顺序仍未闭合,当前不能视为 40250 等价。", "fingerprints": { - "reference": "aee5f08fcc01b8e184946ffc9df293f3e8e333be020eed5b8deefc3dd429d62f", - "implementation": "d40006e8de66b5361dc0f24f9d9d3280c39014a4e3aae21d410513652b0a13d3", - "tests": "71b61849d68202d3fa637f8023b4f47ca2320d333796a036fde7ea36e3ec3138" + "reference": "e59399f499670d966befae34478d84bb3ae4699da942c4707c1bc059c84c4788", + "implementation": "80a2daf3ea649b43f2e882068103c04774fdd4bec021fca67f6a1b0d15f85b93", + "tests": "720b1c6a90f62614e9867f51a9dd039b695c4141d2f8d568e9298637620a1779" } }, { @@ -1339,9 +1351,9 @@ ], "notes": "本轮补充了 LoadMap/UnloadMap/UpdateMap 与当前 Metin2World 的 Leave/Enter、初始同步建队列、延迟回收、map-name 校验、双 world 和旧请求 generation 差异;gamescene/p9/environment 测试通过但不覆盖这些边界,合同保持 PARTIAL。", "fingerprints": { - "reference": "ec4e3738fddb4c5c4cb57ea40acd3c52726866be1b2446f99435bf2eb6a1e0bf", - "implementation": "4366e30239d6ce01bb832e572345d6f2f6c234a6f0c981e0fa71f1d15214dd69", - "tests": "ad9e63a216905f67290bfa73137325d354616b6961e9b97f6c9be0b56bf54ebd" + "reference": "e985648a82b4de1f2d28872b21ffc2b885622ee94f9eb0dc03aa1d6dfc4ff3b8", + "implementation": "7c308d9ca6c888fa85f23d8d3465a6f41c6d8caae1092c0fac72271a7b9a64d7", + "tests": "67a02ec9abd6ee1ead261c0e1353115e69b34934975dbb071b2faab05c436e57" } }, { @@ -1462,9 +1474,9 @@ ], "notes": "已完成 40250 actor/ground/shop-sign 调用链和当前端 parser/store/view 对照;合同保持 PARTIAL。net_world_vis_test 复现 4 项可见性生命周期失败,本轮仅登记审计结果,未修改实现。", "fingerprints": { - "reference": "a5dad293d8fadb4a794de113f80df9ca52703259aa6341e781490836e121bed3", - "implementation": "717ef4134897b3c87fdeb5132085db5bc72394d7faee763b3c393c3147d4f133", - "tests": "ff01079b1b3eaebc9d9adac57c57ba18acc7adca12a8aaf9e99417237901a470" + "reference": "3bf9532d6c9f258fbbc3dba6864171160594f94c7cd708e056c250f88be126ff", + "implementation": "3a7d87cad0ba405495ba51e13feab294af9ebddbde2fb18b5df9c606bbe3af08", + "tests": "9783566cc5ee2f17b89509082c9fd8d9334154f9b03fb499c1616613bba3a2ab" } }, { @@ -1569,9 +1581,9 @@ ], "notes": "本轮按 SetLoadingPhase、CMapManager::UnloadMap、CSoundManager::Destroy 和 CPythonApplication::Destroy 的清理边界,新增 net_world_reset_test,修复 _upgrade_queue、_chat_tails、target/hover VID、target effect 残留,并将 GameScene 换图改为旧 world 先 unload 再创建新 world;AppFlow、GameScene、Audio 与 GDExtension 退出路径清空模型/动作/NPC/UI/音频缓存,rendering_scenario 的 139/54 泄漏和 Debug 下 2 个 AudioStream 泄漏已修复。FlyInstance handler 自捕获环也已解除,fly_reset_test 的 5 个 ObjectDB 泄漏已清零;显式 disconnect 现在先 reset 活动 EntityStore、发 world_reset 再释放 transport,test_exp_fly_parity 仍有 3 个旧 fixture 警告。真实重连包序、统一 generation 和通用 EffectManager 仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "52c08c1d2ec0401a4243d173eb8eb62cf4bef2c5f0d3c83690ec8927c31e06da", - "implementation": "a974581290c07c275aa969985266d96862e538943d508da75f4fb86a45f0749a", - "tests": "84e92fe6b6cd4859cf6a215294b858f15b3dd1259dfb62b99dfdde2b39ec8955" + "reference": "30cb04eed7ba1b82165b30583723a2aaf89f98d1a7a2e7e17b327bb16be10605", + "implementation": "260e6182daa7f44706bebb081eea849b0d4d58c57e5cea129c2920d715c47743", + "tests": "d3cc153e1d36bb4f390b88efc623e3cd56d1703626d7b9f325f9ca3c457adf1d" } }, { @@ -1671,9 +1683,9 @@ ], "notes": "本轮补充核对了 Smart press/hold/release、click miss 的停止语义、运行时距离门、真实 MSM 碰撞球、第二碰撞 BlockMovement 和对象 picking 的差异;四组移动/点选回归均通过,但 click/target/fly 测试有 3 个 ObjectDB 泄漏和 1 个资源退出警告。当前仍不能用固定半径替代真实 MSM sphere,合同保持 PARTIAL。", "fingerprints": { - "reference": "ea99a64535a55097bf4a6d53a1ec7cf5ca027646044211d470814b9a4c9b6942", - "implementation": "2e0a5e8f37317910e00a8a92fcaa575dbe1300e3fe89859ab07e4bc23d404edf", - "tests": "211cd06c8af452f3c4057ced8676787753556a215af01d953d28e635bd290c63" + "reference": "15850029efadc3a50e52511fb45f51da5ad77e6ce0841a41a53d66fd6306d03d", + "implementation": "7996dad026befa17ce75b4146ddf06774dabb899382f5a55810cba532feb6ad3", + "tests": "cdee05a904f96b260c201553915af522bd3623eed78ffb5b23d16ae8e0b2a88a" } }, { @@ -1759,9 +1771,9 @@ ], "notes": "主队列和时钟链已逐分支审计;远端 FUNC_MOVE 已按 40250 统一 PC/NPC/monster 的穿越 Dst 分支,TEMP_Push 已补 correction serial、100 点碰撞探测、1 秒表现缓动和大位移击倒门。因技能数据权威来源、双人情绪目标、真实资源/地图碰撞和服务器时序差异保持 PARTIAL。", "fingerprints": { - "reference": "b2bbc9ce74027fcfb6af30c0d6437b34ef7261cb841e13cef6d883a9275d0453", - "implementation": "e57ada209ed9eca1cf991501a69f696eebd154bf1d2b87a99db55c6a42f4683e", - "tests": "cf93c0617087617b1d5c506112e5497369ec9ee5f9be3bd14625084b543797d1" + "reference": "2dc90fd41d26f4a61085fdd57d961ffd8c83f5b1ef4d359559c3104dcbabe801", + "implementation": "99d49c732d9d141818db12ef3b2f98f081980fac372d34692fdb50ff775d9dca", + "tests": "f3fdfa1a6d0ebed1c8fb78588e44d92fb79ff6b1059d77453d9e7cd5f962e15c" } }, { @@ -1889,9 +1901,9 @@ ], "notes": "已完成 40250 RaceData/RaceMotionData/ActorInstanceMotion/ActorInstanceMotionEvent 与当前 MSA/MSM/AnimPlayer/视图/NetPlay 对照;本轮接入 playersettingmodule 驱动的 PC motion registry、复刻注册权重和 HORSE 父模式查找,并修正击倒/起身受击早退;MSA MotionDuration、LoopData 与热切换缓存回归仍通过,合同保持 PARTIAL。", "fingerprints": { - "reference": "8ca2f11b20ce32df4fbecae8bed930d97dc14a31194542d07f47de302bfa6139", - "implementation": "c3d953416803727f31c26edee73bc8d3c0bc6783daa9942180977373320809c8", - "tests": "418a7fc6496f570cd97183fafb4a1964f90a704f55410df9c6bd42a35f6a517f" + "reference": "f59276d75e5cdf0fc54c95fdce5ca85c794080b94ba7fdee540870273004a471", + "implementation": "01ca0b13b2db5489c1fa37dc48d555d68da096dc2d2370d9d64b2cf78c8f9fc7", + "tests": "7126549b43695f0569d9ed8aaff9080404cf4cd3cbc9aecb8aa0ea7a825ae2af" } }, { @@ -1949,9 +1961,9 @@ ], "notes": "已按 40250 isValidAttacking/HitDataContainer 删除缺失动作攻击数据、空命中窗和缺少 .msm 防御球时的本地合成命中;状态包、命中窗、去重、受击、弓箭 FLY、本地主角伤害飘字和 WeaponTrace 多部件生命周期有回归证据。WeaponTrace 目前通过 weapon_trace.gd 适配层接入,包含武器类型过滤、每个 Weapon/Shield 部件独立 trace、双轨迹时间队列、cubic spline triangle strip、alpha/texture 材质、攻击动作开关、纹理控制入口、bound-box 最大长度/局部 Z/reach scale 和换装清理;AFFECT_GEOMGYEONG 的 1.5/1.0 reach scale 已同时接入刀光与普通攻击扫掠。native 骨骼矩阵坐标语义、远端/怪物覆盖、真实视觉资源及服务端伤害闭环仍不等价。强化配置中的 has_trail 仍是独立未消费字段。", "fingerprints": { - "reference": "6b08585e316d89de1e827937e6bf7f4c8b3dc5f6c982f076573ca68c5798a740", - "implementation": "0e2acfc1b3813d5e7f04d7e87678637b0f5d3238fdefe5acd459a7946ed43090", - "tests": "f76a2ba96b83375afd9c355fd944dca182b3e969367e448f25da08ad0522df1e" + "reference": "3b79deef2e50cb16c6b7226e8900e4d96c82f5dbe4e409f40609b6a656586608", + "implementation": "c2226db3856b7499f505d6e26b09f4402c36bd82848f6893ce8426a283ea2a66", + "tests": "228e1bb5ebb0ef52822956e3d33d9c51d55cc2090c323300b466f937249e5d3c" } }, { @@ -2051,9 +2063,9 @@ ], "notes": "已完成目标攻击资格、目标可选中、目标框和安全区的逐分支静态审计;目标入口已统一接入 CanChangeTarget/CanPickInstance、重复目标窗口和 CG_TARGET(0) 清理,合同保持 PARTIAL。", "fingerprints": { - "reference": "79cc9ece9c1b7fc69d44e304e1e1c49ef242f727fa027058e1798b52918d4521", - "implementation": "0cd8742f2171a9dc9f4b05add49297fe9574142b5382af983d6d502a0ca3abc2", - "tests": "07dfa7500946c1dd57346ff4a0d7489b4dd514582d304df4a9af571309dc5953" + "reference": "818e201fffe6335de4b2c79b505d8749b7ef00d61267865861a8fefc21c5bdf5", + "implementation": "073975693379576d0b2f3e2418a1b942c1af4796484b0380a680b39b0a65245e", + "tests": "9ad10ac749fbc12f4537fe489f592ac8e2159446c6bb1ab01855076c3b6c39ec" } }, { @@ -2156,9 +2168,9 @@ ], "notes": "已完成飞行物数据、目标、弹道、背景碰撞、命中/穿透、网络目标追加和地图清理的逐分支静态审计;本轮按 PythonEffectModule/playersettingmodule 接入 .msf 解析、索引资源映射并修正 FLY 枚举,同时解除 FlyInstance handler 自捕获环,fly_reset_test 退出已无 ObjectDB 泄漏。动态 Actor 碰撞、动作事件 FLY 起点/owner/skill、test_exp_fly_parity 旧 fixture 清理和完整特效生命周期仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "4c3f953999e53f7c8c8454d8aaaab2630c19438b2236e3053c0cf5f9c8aa3c55", - "implementation": "6f961e0a3e2b4d9f41925e299a000a809ad3d62823b373c2984e9e53aa8000d2", - "tests": "504420e0e84251b95f9676e72c07ea5cc875fb6c4ad139f79c0bebabf1189f1b" + "reference": "78aa9c45552beea0f552c072bdf663049fb14d1e7ca8bd7c916bdfda7f6d8339", + "implementation": "c9ef1ee8219d239c2b1abe821e8fac5a4d562ba407cabfb4054135a8c768ab7c", + "tests": "b6106f8af47ea86f4c289e56455da78642f2ee35e074dbd073a640171e55aec8" } }, { @@ -2263,9 +2275,9 @@ ], "notes": "已完成 40250 AffectFlagContainer、Actor affect/受击动作、damage/affect 协议和当前 native/GDScript 链的逐分支审计;确认本地状态结算器的数据权威错误,且 hit_view_test 有 1 项失败。合同保持 PARTIAL,本轮仅登记审计结果,未修改实现。", "fingerprints": { - "reference": "2ad471af8361cb0a640a4fc702c6223734e68948c905eef4f8841c8113e79c7b", - "implementation": "26dcade5219459cf5386691f1706c8db04e15d6f03e48a30d869bd223cefb239", - "tests": "aed0edc923b5a81de18a2d4545dadccdfd1463e5dd785e80f945d56e0a34b749" + "reference": "a17de5fbf004c1c6b799300ee026c44250c7be01839e79f4e2df927c65748301", + "implementation": "c0d2d09d88eddafb7e3586b6a5894e95d3d143182f486f7572c790b9c031bd4f", + "tests": "c8c6a05be637f15e1ca812a4a59cd5f3be900104ce325e729de3690beb594d3b" } }, { @@ -2374,9 +2386,9 @@ ], "notes": "已完成 40250 技能数据、三层施放校验、CanUseSkill、casting-speed 冷却公式、快捷栏、普通/公会技能发送入口、skill/group/cooldown 协议和 FUNC_SKILL→CG_USE_SKILL 动作/意图包序的逐分支审计;CanUseSkill 五类动作门、POINT_CASTING_SPEED 桥接和公会技独立路径已实现,但真实拒绝/乱序/跨生命周期 fixture、完整动作资源包序和服务端 damage 顺序仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "3803e0c7a0b8492f95084c9fe2b4828c04e831b29f0ca7b538891e3beb6f03fd", - "implementation": "bc0d501069c7be30f144a8a0be8fa01570cfc4f7c40bc032b5056f13f5f64bd6", - "tests": "dd17c0582f03373c66903290123f327f61662311c0dd44e912b53a59e10bb0ab" + "reference": "41123a16a7c43e570f57854c4165bc04bb2dfb875efb0bf34d9f5e6afd08e383", + "implementation": "bad2f21e5c0ea4de37508d94ed1724931696d9409db5f487a708da0bdfbd3cc7", + "tests": "0746ef57d85bea05245ba44d178e24790d28623c40559520508db7acbd7970f0" } }, { @@ -2504,9 +2516,9 @@ ], "notes": "本轮回归确认非法 cell/空槽 Update/native 行动门/装备分类/Set2 highlight/未知 vnum 校验和 item_proto 音效分类未回归;完整 sequence、旧监听者中间快照、空槽字段 API、扩展槽位乱序、服务器拒绝回滚和断线回滚仍保持 PARTIAL。view_equipment_ui_test 与 netbridge_test 通过但各有 5 个 ObjectDB 泄漏。", "fingerprints": { - "reference": "e68fce79b4828584f177c198f692d4a644a59d7ec5624012cd1c9963a294385c", - "implementation": "e6ce2b71c4c49f24330955166450f8f895b09a208a67b1ad42f6f932c7558b82", - "tests": "d2cef96677ae633de20ac87e91e8edb7a8e039b3abe49279363c7272fe64d7ce" + "reference": "1278f3e04f31a69efd71c1d5542e3d697e279575675031bdf96d30aaa0d1ff75", + "implementation": "b8bc1a86bd1781601e4faa66724fe3674bb5b6658d82f6e8c8b57bbae83b261e", + "tests": "b486458e209c415e1373ae96e83a6be7c558aca8afb99822baba350c89fc24be" } }, { @@ -2630,9 +2642,9 @@ ], "notes": "已将拆分、快捷出售和快捷栏增删交换统一到 request→server/GC 确认路径,并修复购买模式误发出售请求;快捷栏使用、消耗品真实反馈包序、音效/sequence 和断线清理仍使合同保持 PARTIAL。", "fingerprints": { - "reference": "1eec38f9a52f1dfc25aae2e709e2bfb202231216366bb14c2be0ec38907fc9f4", - "implementation": "7e30ec050cb2f0829696a7a1702b18386ead8cea6866d2c09e51f232f67b4f61", - "tests": "d77435893b0cb2bf1b62a39bcbaff81495bdb8df0f55d5685b33d1da7412d93a" + "reference": "46e51b7a21012ae531c21f8a752fde5592659081feec6d67edfa86c19f7c2082", + "implementation": "0139c7d3d92f8d812eaef51d3d8dfb70f0143f0b4660fa21cbca4c2f16284bfd", + "tests": "bc8250918b7945d30b8174a2206ad3f95f9f9fe6523217e312e9090317a03f0c" } }, { @@ -2742,9 +2754,9 @@ ], "notes": "本轮完成掉落/拾取/所有权/名条/协议逐分支审计;基础回归通过但资源、权限和反馈链仍不等价,合同保持 PARTIAL。", "fingerprints": { - "reference": "26895989dc143ba7f263a5e49bb66659c0b2d55c76cbe32e50bf49adc76c25e1", - "implementation": "2c235060d70c557cb9d5d38b60988c4cb851ac63d72d652faa6d24289b06d476", - "tests": "2db9bff916c9ea0f835855a3bb02ee51656231a0452060186baa9a5c5e4957e6" + "reference": "18e94abecd2cbd63407ec94fe4f2b692505b0ea156e3149fcfa426920a10856b", + "implementation": "d67db273d2baea25c2b7e21ec6f930d08aae35821370b13b98dd849b9b7541f4", + "tests": "0713f0aeef0e53fdf447537c2a8e785559269b45ca63825c4b507382fbc95019" } }, { @@ -2872,9 +2884,9 @@ ], "notes": "本轮已修复 M2Client::refine 对 40250 取消 sentinel(255,255) 的错误拦截,并补充边界/EntityStore 回归;属性、socket、时装和幻化仍绕过服务器权威回包,合同保持 PARTIAL。", "fingerprints": { - "reference": "a384bc07cc6d859077733ccff5d9168941e9ca4d27b5f9fd94b1dc01dc41930c", - "implementation": "c41748c572b6a1c8ffb7c5ddf01cb47c0a8ee86419fce805270edb4ed512a121", - "tests": "79edadc2084f36c3126cbe6586f356f043320fd860cf814c328c3d7c667a7939" + "reference": "e76ca9bb01f7b7838943820f3fe5633f68a32364c3f3ece48cbc66bf74a6a247", + "implementation": "176ad923087d84b8f11dee97d2bd198c495d91014d8b720d3bf58acd0bd26fb8", + "tests": "6bb13ce9e81271dfa30eaaab03c5cf5e801ef22965c32d4b4d38af95f6874bfb" } }, { @@ -2989,9 +3001,9 @@ ], "notes": "已完成 40250 与当前端的 NPC 点击、脚本对话、普通/多货架商店、买卖、个人商店招牌和摆摊提交逐分支/协议/状态审计。协议骨架和本地 UI 测试存在,但多货架更新、个人商店点击/状态、动作门控及本地直改旁路使合同保持 PARTIAL。", "fingerprints": { - "reference": "f9d6038483ca4867d34f526301765a0abe8282cf177ec5e7c7e829b8bc7c5f65", - "implementation": "23c313ef8d6c47891c58a5f60bda19ab6e1155ab2af2dda0d759dbcbb90f2dd6", - "tests": "ea2f077e6a4bfe0d2a4fda60d1477f55b8a5a5530d67cdb7df8b74e1ce3119c9" + "reference": "75f3901cc3c0f8cdb60bb5d5c5c04e695776ce365d9988ce83e4ea64f24e4dee", + "implementation": "3396b617791fddb530e44180457248d2222fd3e971a1bd2b08503486de73213d", + "tests": "3829ff07408c019b3a4a65fa8d4f283fe723342bcc2d1ed51ec4993d7f9c1874" } }, { @@ -3108,9 +3120,9 @@ ], "notes": "已完成 40250 与当前端的公共聊天、私聊、忽略、聊天气泡、名字/称号/等级/公会名尾标及文本布局逐分支/协议/状态审计。已修正 whisper mode 映射、截断 GC_WHISPER 拒绝逻辑,并将 live ChatUI 的普通/GM whisper 接入独立会话和 ignore 判断;基础网络与静态尾标测试存在,但窗口通知、公共提示/过滤/表情/颜色和 Godot 字体布局差异使合同保持 PARTIAL。", "fingerprints": { - "reference": "6ce929fb1b892d5297a2a65bb2200248bdd26022fecdace8f0d258f76c50643a", - "implementation": "c3a926547c75038a57c01cb9fb3b2340f78e506744344ed26119a3b48c31bd1b", - "tests": "828b09c0d2b7758092e53f4aea7c3abbe42c203a53e2f0f934f2ae7fc9609e36" + "reference": "82802154cb44736cfa53a3dc2de5879c1fb9ccb4f47037b70827a2ba8262c9f8", + "implementation": "cd02d44596b377534a4200e0ef7002b3c6de8e7c328fbdd96afc7e9ed8a6cb8f", + "tests": "c86122ea9b1448a74f4af2a90bc25382ae7b477d6a2cc96b27c5ac3b48d97e39" } }, { @@ -3251,9 +3263,9 @@ ], "notes": "已完成 40250 与当前端的组队、公会、好友/Messenger、会徽/战旗及相关 UI/native 回包链审计。live native 主路径较完整,但本地队伍/技能/公会战/Messenger/会徽 helper 仍存在直接状态旁路,真实权限拒绝、mark 独立连接和重连清理证据不足,合同保持 PARTIAL。", "fingerprints": { - "reference": "5deb754b5059a0d687d0f091f8227ca04c29001764bcc8a341d83e349ad140e1", - "implementation": "98b1c1116db4f0dbdbb0e41eedd5aabb2ffcbf72ae1b4d4d0d5449189e776209", - "tests": "73b284f6af5967c17fd27e89235a6273b4f5cf76950d7dd215c37be380ee5dea" + "reference": "6223e48f6be2d1471133c76658c264f2eaf5fcef7e2870ee21f538ff81e0d0ec", + "implementation": "df7b3599aa8255d8134668158fda22e2914854e30b763adc1deceb9c345a6873", + "tests": "89f4d155ee7d3b4f3457ef2114aed642ef6030a7a7c226d5731fcc20cd2ba2dd" } }, { @@ -3349,8 +3361,8 @@ ], "notes": "已完成交易、仓库、安全箱、商城和金币确认的逐分支/协议/状态审计;native 主链存在但与 40250 仍是 PARTIAL,本轮未修改实现。", "fingerprints": { - "reference": "927a9d6b8d15700537be48563b13161b23267db74e51b65a81ef7d5dea0181fd", - "implementation": "2025bae6f6d7b66a301e02c8db364ac9eb75b507218b7a046961888c21bcc516", + "reference": "32d03a88d33723e087656bdfcbe806507b2e88e5ef20e36211d9f2cbab26a745", + "implementation": "d2cad2b8bdf7fc421478157ea0085bef07a4979d3d3799ff81823e1dd16216ab", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, @@ -3464,9 +3476,9 @@ ], "notes": "已完成任务状态、事件脚本、对话、HUD、地图导航和过场链的逐分支/协议/时序审计;GC_QUEST_INFO END、script-button 任务索引 sentinel、DONE close-only/DoneEvent、ESC/CANCEL answer 分支和 RUN_CINEMA 缺失资源分支已按 40250 修复,其他明确差异继续保持 PARTIAL。", "fingerprints": { - "reference": "2a149f122184733ce357001e14a2da22fb3f4aa2aa37fb886924d567be8fb916", - "implementation": "b1551f6d0a249dd70e825184d6833dcff23225ae8d0f805561e7b78663915984", - "tests": "7f3267d7a9c959763bc6739ef2cd307046323cf4b4e7387bd99a04e74cfc9898" + "reference": "ba46c9195d60a257062b207f81d4fbc730b71d8098d6b2a2a531640f05debe38", + "implementation": "5aa1707764f5a74db510210bfd8803a9e90e0a4d989202795fe506d1f29a8395", + "tests": "a65c31ca4ade9fe4359f1238ed7080a5a50c3a336b75a9d9ee2a1e2f7faeb02a" } }, { @@ -3549,8 +3561,8 @@ ], "notes": "已完成 GC_DUNGEON 目标点、结果窗口、地图事件及本地 Boss/排行/副本 helper 的逐分支、协议、资源和时序审计;目标状态算法接近参考,但 compass 副作用未接通,本地 authority 不能视为 40250 客户端实现。", "fingerprints": { - "reference": "4915243dd9acc3a4c1eeb63971e71c5e8454d6bbd57fea4d27c939801a751946", - "implementation": "b2ad0a9d4c42a74e886fa73846dbdd9d255c55fdee40618494a4265743f65f70", + "reference": "45e83143617b16eda5b4ad7cc38e701fa9590b02a4c36d5adb2dd8a3bae77b7f", + "implementation": "c4885c44a33a107d3095a20b988f26a5a62d50814514e34ffb03c86bfa7a745d", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, @@ -3643,8 +3655,8 @@ ], "notes": "已完成钓鱼输入/水面扫描、GC_FISHING、GC_DIG_MOTION、地图水面和当前端生活技能 helper 的逐分支、协议、状态与资源审计;协议骨架和部分算法接近参考,但 live 动作/门控/清理证据不足,本地收益脚本不是 40250 客户端 authority。", "fingerprints": { - "reference": "1bdeb0e741ba1796fb80ff5a8aeaa2743dca3236182ce5a46cb6b0c8f8a8f7e2", - "implementation": "305d81086fd8f9b4e29ce4eae5437be8ccd22e252290239b2186b3f1d5d448f8", + "reference": "bbc26abb3d3a2cdca5abae515be1dbd0a83188cdf44c5e55738e2a3b664a0ee4", + "implementation": "c72913ee472ed61dfa156b67ba7468f8098512972e49c4a1e307aa8740152fca", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, @@ -3734,8 +3746,8 @@ ], "notes": "已完成 mount_vnum/GC_MOUNT、骑乘动作/攻击门、变身和当前端宠物/马匹 helper 的逐分支、协议、资源和时序审计;网络字段与部分门控存在,但实际坐骑模型挂接和变身 native 闭环缺失,本地宠物/成长/技能规则不是 40250 客户端 authority。", "fingerprints": { - "reference": "7340380e8dd27e2dd1c2d16c85b7b99e9f67353b021c3429c9788a91d27ffe04", - "implementation": "a76f72fcf3ff39788e76d776687b21531d5e82479e863b526c75c2e95acb6926", + "reference": "deefe3ad38347b0ab207653391cd54b5f06811f6892ee15a8650cc42e8637e98", + "implementation": "333ea5a5c89e7628044ea4b548354e73d27e602b6348eb46cedd24f8a85d7568", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, @@ -3818,9 +3830,9 @@ ], "notes": "已完成 40250 ThingInstance/LODController/ModelInstance/ActorInstanceAttach/RaceData/ItemData 与当前 GR2/Godot 模型链的首轮逐分支审计;确认默认 LOD 常量、LOD 子模型生命周期和 03150 双手武器姿态存在真实差异,合同保持 PARTIAL,未修改实现。", "fingerprints": { - "reference": "dc4d5eb93d161677fdb136680fa2d78c0c0b46713c86a85601684e6b0f524718", - "implementation": "55dcbfef8cfc0d33df167126a63592c12c7e3f229d451991b7392c4eb9848add", - "tests": "53cb669198620dc27a371857d5d0be616f98952bef1446a1e75ca89d04c9cd10" + "reference": "553e2ce8e6b51e4acf8192cc1f7983865f52a2d68ae2d7c2f1d8aee9fa63f349", + "implementation": "2fedab89ead277ee74f94bda5ee2e5bdc4810fe3a2897f250e5911c0be81df04", + "tests": "74ebcaa45ad2a714c654a1e76045df3594d858b3c83f265789e39cf6d8bba1e1" } }, { @@ -3902,9 +3914,9 @@ ], "notes": "基础 MDE/MSE/粒子/挂点已覆盖,但 40250 ActorInstanceMotionEvent 的完整分支和所有 Actor 消费链仍不等价;保持 PARTIAL。", "fingerprints": { - "reference": "7058842aee771df163233b1b3df496c0769d23675eb18e4c9547b243d3220285", - "implementation": "7943c156a15667eb5601e05bdb70d75493bd5a3a5b8b395f20fa370eaa3186cc", - "tests": "308c2684c98ef86f21e6b91525095b44a28593b0b1d18fa1916c65bc57759a3f" + "reference": "0f027842dc3a7991870c155a9a9453babb4c0f8fc55ac2c4c7f0bb75aaa445c8", + "implementation": "1e7106b1936bf6ac79849544cc8dd473bb0a421b3666d98e99721f0f84fc4825", + "tests": "cb6e83d8a0c3e1696d6638eb582a6c95f48fc3c19e3fbfb9c285ba5906f3aa52" } }, { @@ -4003,9 +4015,9 @@ ], "notes": "已完成地形/水面/碰撞/拾取首轮逐分支审计;高度、桥面和水面 happy path 已验证,但对象 ray picking、水层高度、高半字节属性和 streaming/清理边界保持 PARTIAL。", "fingerprints": { - "reference": "23634378b4f11a57b203309ffbab1589bdb59240379d74bd8e8a3496829d0589", - "implementation": "1ca29934bef9d573f5e877d054f59d87515644f4cc954beb2335b9c17330fed9", - "tests": "d8d295894a46aa70ba1366a03847682ec2e54f6de09005d4444513a66dfef62f" + "reference": "46da799d71b3ad4ec459ebaa5364dac82a1cd503a30c55692ccadaf183ade1cc", + "implementation": "70a4c4f810b3fd2e3f01b42ceeb73036a0f17f31e9ba3b210e4f84d12d15c7f1", + "tests": "a2a38be2cfb31518ed7f30aab7a2aeb324b71dce353271fa4ea1f330d21195f3" } }, { @@ -4100,9 +4112,9 @@ ], "notes": "已完成 .msenv、天空/云、双方向光、fog、天气粒子、服务器 xmas_snow 命令和正式场景过滤的逐分支审计;命令链与四张排除地图已有回归,但 SnowEnvironment 池、DayMode、filter/lens flare/wind、线性 fog 仍与 40250 不一致或缺失,故保持 PARTIAL。", "fingerprints": { - "reference": "21e01b810af1c9128223aeb2bf953dddac6b63f35d96b758fa2c98754b0424e4", - "implementation": "d8fdef03f7b298d2163115d1bd7244becabde51d1df58047595d4462abc94801", - "tests": "2271c3ff7a417e846a4896dbbf47cd8e646091f20ca7008589d42640f9ecd13f" + "reference": "0a076b8a9779c989184c20ef617cddf5538720e8ac7e2e0773d63af3d72e6eb0", + "implementation": "dfc0fe4b13645b06daeea26f517d813a8c8ebb4cfcb2db19ebe2ee7e8f573c7c", + "tests": "05fd8d3202d9774f7043311e8615e1210a09cc579f1346eb2fb9ebbfec339b5b" } }, { @@ -4183,9 +4195,9 @@ ], "notes": "已完成首轮逐分支审计;移动端基础适配测试通过,但相机默认/恢复、鼠标统一分派、光标状态和 IME 仍未达到 40250 等价。", "fingerprints": { - "reference": "9fa59e1a4ab5fda8a93338bbea6fa20aa7f776ddde26ebbb732645883033ec73", - "implementation": "e5051d7363cb8ffae29a590a388e28a2632b2d831e86250f17bfdb74da5c1b7e", - "tests": "efea4d26a5a0f3b1978b4c9fc6f9672ecb84223b1cd76b20b492f0528a780320" + "reference": "11181965574e3b0c3e235b1403e17494f8fc1b8777a2b18b955f53a84e0bae54", + "implementation": "086901e59cc5768fd74062b39ad4de534827cd0195f6c92b3d8a0885219e203d", + "tests": "eb158440ac1fcfce57259aad60a090493d2cebaeab7e380c3513cb34563a90fd" } }, { @@ -4288,9 +4300,9 @@ ], "notes": "已完成 PythonTextTail/PythonMiniMap/PythonPlayer/TargetBoard/TaskBar 与当前 GameScene/UI 链的首轮逐分支审计;确认 TargetUI 未在正式 GameScene 注入、HUD 与新 Minimap 重复创建、Minimap scale 常量与 40250 不同,合同保持 PARTIAL,未修改实现。", "fingerprints": { - "reference": "b12d02d7f7b9cf93e50239d3685ff5ab4c1220227d94005afe55aa5a1999b700", - "implementation": "393de2d6c1d13ab16fb72bef3a8abcda503ee4eb16a4ab40f64f674436304a25", - "tests": "1068c2809dff1a923da5f78f132c8802374ec42f7fad72730a15eb0fed84f9a4" + "reference": "a948059544c578dfb6f67d4e64ed41322095fb5734835aaa60aa4dd53567d152", + "implementation": "6b31f5832b14c75d5210ed1bf312c88f550c8d03eadbcf5b9f57b1983c4ec2c4", + "tests": "fd53e0750b11c9d06bfc1613b53ed864f712732719c2c495a19b30fed6663c9c" } }, { @@ -4408,9 +4420,9 @@ ], "notes": "proto 和格式解析已核对并有测试;主资源入口、EterPack 原格式、locale 编码和失败回退存在材料差异,保持 PARTIAL。", "fingerprints": { - "reference": "ecc3507b4bdd39d894826c16226c618d3f1ac82688589e16f3694f35cec22897", - "implementation": "31c9548b53a2154f452ff50f47127536ca74bfb7eb7f450c16b3101dc9c53ec5", - "tests": "f156b2178727e31985a7aa11bbd50b1c911c5c47c1cbb560246cb92b18d6b912" + "reference": "772c0563544d7e07bc38ae2c2a3684b5f1a248d22200dfaa108b2967e26e3ca8", + "implementation": "313cb99fea6b2d3c7cdec918aad2c328d81b42fd75ee582bf301241b6507621d", + "tests": "40416fb46b72679bfba5de491b7945b0cf95d08f524c9786589625f3641e6da3" } }, { @@ -4502,9 +4514,9 @@ ], "notes": "已完成首轮逐分支审计;基础音量/监听器/环境声/动作音效路径存在,后台 BGM/SFX 同时静音恢复与退出 Audio 缓存清理已有回归,但 Miles 实例池和 BGM 逐帧状态机仍不等价。", "fingerprints": { - "reference": "b76a647a0c58e49b1b3bd47c1eb89c1d6508d8abed6eddb8092ab09e4c5ddbae", - "implementation": "e2cb3ea4a6dec8d7e2113f15e8419f3dae0f9fef4a78267ae42bd24ef1ed88cf", - "tests": "03cb7dda64dc2cf49320e2715614290790c4086e7bec1349d65c1937a69ce024" + "reference": "2d3182776cf583a5e474701489a417e87ff2f8d7952a4ce03a4523dd570528cd", + "implementation": "f4c3fed1f7cbf08c1032c536927a689a9a1b71b1c431de2afb3c0041ee577315", + "tests": "a7ca9e4555b71f92776b56669a0b74e443dc2aaab78a2f208c86f9fc4bc75bfc" } }, { @@ -4585,9 +4597,9 @@ ], "notes": "已完成首轮逐分支审计;AppFlow、AssetPack、焦点激活音量保存恢复和 macOS/Android 适配存在,但 Windows 原生窗口/导出、全屏与 resize、CRC 更新检查、Eter 异常处理和完整发布门禁仍缺失。", "fingerprints": { - "reference": "aad501a1d500710629402dd2be24703a6b61f87b17323624d32c5057efe2100f", - "implementation": "addf830b10a8d794a8a26308f07014f347e510eb97982390df7f8b74c931d348", - "tests": "e1b36cb9802208f74c3387529e9684ae65a415b8a57cea0af313025b3c1453fa" + "reference": "ddccccedfb929112d89c6c14b3746569a64493245a0e8b70145df3979dfd611d", + "implementation": "84417dac55504e5f626731fd2d3c78decec8808187fa70b427fdf83f66adf040", + "tests": "10987358f838e043daaed30ccb4639f738a6bbd19522783091a9916fbb5e2bc1" } }, { @@ -4622,7 +4634,7 @@ "exclusion_reason": "在指定 40250 ClientVS22 source 中没有可达对应行为;记录该当前端扩展以防止漏审,但不计入 40250 等价率。", "fingerprints": { "reference": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "implementation": "8184e1ac57157516cbdb98a191e9318ccdfe55b5c6cea888c377d5d18910432b", + "implementation": "fdaf946a6bffa5e914dfe7f40c2bbd42dc00e64d43e60231e9290021f14cbba2", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } } diff --git a/audit/remediation-roadmap.md b/audit/remediation-roadmap.md index c3e70360..e2a79492 100644 --- a/audit/remediation-roadmap.md +++ b/audit/remediation-roadmap.md @@ -240,3 +240,40 @@ Game owner 的 `reset_for_map_change()` 和 `on_phase_leave`,再安装 Select/ 导致后续重复握手错误发出 `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` 包序证据或第三方闭源安全 SDK(Matrix/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 移动门是其中两个具体、 +无需真实服务端/客户端证据即可推进的候选)。 + +`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 分支。 diff --git a/audit/reports/coverage.md b/audit/reports/coverage.md index 077fa321..2b7d1ef6 100644 --- a/audit/reports/coverage.md +++ b/audit/reports/coverage.md @@ -24,8 +24,8 @@ Total contracts: 45 ## Pending branch items -- Total: 224 -- P0: 93 +- Total: 225 +- P0: 94 - P1: 118 - P2: 13 - P3: 0 diff --git a/project/keyboard_motion_timeline_test.gd b/project/keyboard_motion_timeline_test.gd index 43631a99..4cbd47a2 100644 --- a/project/keyboard_motion_timeline_test.gd +++ b/project/keyboard_motion_timeline_test.gd @@ -3,6 +3,7 @@ extends SceneTree const PlayerController = preload("res://player_controller.gd") +const GameCamera = preload("res://game_camera.gd") var _fail := 0 @@ -129,9 +130,75 @@ func _init() -> void: "held direction resumes after emotion ends") pc._input(_key(KEY_W, false)) + pc._input(_key(KEY_W, false)) + + # 40250 CPythonPlayer::NEW_MoveToDirection m_isCmrRot: holding a pure + # forward/backward direction must not roll the camera (fRotRat == 0 at the + # axis), while pure left/right rolls it fastest, and diagonals roll at + # half rate. See player_controller.gd::_camera_auto_rotate. pc.stop() + var cam := GameCamera.new() + pc.camera = cam + cam.yaw = 0.0 + pc._unhandled_input(_key(KEY_W, true)) + pc._process(0.1) + _ck(is_zero_approx(cam.yaw), "holding pure forward does not roll the camera") + pc._input(_key(KEY_W, false)) + + cam.yaw = 0.0 + pc._unhandled_input(_key(KEY_S, true)) + pc._process(0.1) + _ck(is_zero_approx(cam.yaw), "holding pure backward does not roll the camera") + pc._input(_key(KEY_S, false)) + + cam.yaw = 0.0 + pc._unhandled_input(_key(KEY_A, true)) + var yaw_after_one := 0.0 + for _i in range(5): + pc._process(0.1) + if _i == 0: + yaw_after_one = cam.yaw + _ck(yaw_after_one > 0.001, "holding left starts rolling the camera immediately") + _ck(is_equal_approx(yaw_after_one * 5.0, cam.yaw), "camera roll rate is constant while held") + var left_yaw := cam.yaw + pc._input(_key(KEY_A, false)) + + cam.yaw = 0.0 + pc._unhandled_input(_key(KEY_D, true)) + pc._process(0.1) + pc._process(0.1) + pc._process(0.1) + pc._process(0.1) + pc._process(0.1) + _ck(is_equal_approx(cam.yaw, -left_yaw), + "holding right rolls the camera the same rate opposite left") + pc._input(_key(KEY_D, false)) + + cam.yaw = 0.0 + pc._unhandled_input(_key(KEY_W, true)) + pc._unhandled_input(_key(KEY_A, true)) + for _i in range(5): + pc._process(0.1) + _ck(is_equal_approx(cam.yaw, left_yaw * 0.5), + "a forward-left diagonal rolls the camera at half the pure-left rate") + pc._input(_key(KEY_A, false)) + pc._input(_key(KEY_W, false)) + + # The camera-roll side effect is gated exactly like translation: no roll + # while the actor cannot move. + cam.yaw = 0.0 + pc.stop() + pc.locked = true + pc._unhandled_input(_key(KEY_A, true)) + pc._process(0.2) + _ck(is_zero_approx(cam.yaw), "locked movement gate also suppresses the camera roll") + pc.locked = false + pc._input(_key(KEY_A, false)) + pc.stop() + player.free() pc.free() + cam.free() if _fail == 0: print("PASS: keyboard_motion_timeline_test") quit(0) diff --git a/project/player_controller.gd b/project/player_controller.gd index c79c5a65..fd266c17 100644 --- a/project/player_controller.gd +++ b/project/player_controller.gd @@ -38,6 +38,7 @@ const SPEED_RUN := 4.8 const KEYBOARD_MOVE_TARGET_M := 3.0 # 40250 NEW_MoveToDirection: Dst = Cur + 300 px const RUN_HOLD_KEY := KEY_SHIFT const ARRIVE_EPS := 0.05 +const CAMERA_AUTO_ROTATE_SPEED_DEG := 20.0 # 40250 CPythonPlayer::m_fCmrRotSpd 默认值 const PICK_RADIUS := 1.4 # 点选命中半径(米) # __IsMovableGroundDistance:点地目标离脚下太近就不动(避免原地抖)。参考默认由 # player.SetMovableGroundDistance 从 game.py 设置;此处用参考缺省值(~1 m)。§3.1 @@ -503,6 +504,29 @@ func _wasd() -> Vector2: d.x = 1.0 return d.normalized() +# 对齐 40250 CPythonPlayer::NEW_MoveToDirection 的 m_isCmrRot 分支:dir 是 _wasd() 的相机相对 +# 输入向量(0,-1=正前)。折算成角色相对相机的方位角 fDirRot(0=前 90=左 180=后 270=右, +# 与 GetDegreeFromPosition 的取值一致),再按参考实现同样的公式折叠到 [-90,90] 的 +# fRotRat:正前/正后为 0(不转),正左/右为 ±90(转速最快),对角线为 ±45(减半)。 +# 参考实现用 CCamera::Roll(fRotDeg)(D3D 增量式 roll,fRotDeg=-speed*dt*fRotRat/90); +# Godot 的 yaw 增大对应视线转向左,和 D3D 的 Roll 正方向手性相反,因此这里的增量对 +# fRotRat 取正号而不是参考的负号 —— 这是platform_adaptation,用“相机持续转向新朝向、 +# 从不越过目标”这一收敛不变式核验(keyboard_motion_timeline_test.gd),而不是逐位对照 +# D3D 数值。 +func _camera_auto_rotate(dir: Vector2, dt: float) -> void: + if camera == null or not camera.has_method("heading") or dir == Vector2.ZERO: + return + var dir_rot_deg := fposmod(rad_to_deg(atan2(-dir.x, -dir.y)), 360.0) + var sig_dir_rot := dir_rot_deg + if sig_dir_rot > 180.0: + sig_dir_rot -= 360.0 + var rot_rat := sig_dir_rot + if rot_rat > 90.0: + rot_rat = 180.0 - rot_rat + elif rot_rat < -90.0: + rot_rat = -180.0 - rot_rat + camera.yaw += deg_to_rad(CAMERA_AUTO_ROTATE_SPEED_DEG * dt * rot_rat / 90.0) + func _blocked(x: float, z: float) -> bool: return world != null and world.has_method("is_blocked") and bool(world.call("is_blocked", x, z)) @@ -595,6 +619,11 @@ func _process(dt: float) -> void: if world and world.has_method("sample_height") and is_instance_valid(player): player.position.y = float(world.call("sample_height", player.position.x, player.position.z)) return + # 40250 CPythonPlayer::Update 每帧对仍按住的方向键重试 NEW_SetMultiDirKeyState -> + # NEW_MoveToDirection,其 m_isCmrRot 分支与是否已建立平移目标无关,门检查(上面 + # 的 frozen/locked/shop/emotion)通过后即执行,因此放在移动逻辑之前、无条件按 + # 当前方向键调用。 + _camera_auto_rotate(_wasd(), dt) if force_walk: _run = false elif force_run: