fix(movement): drop idle FUNC_WAIT resend with no 40250 counterpart

40250 only calls OnWaiting inside CInstanceBase::Transform() while
IsWalking() (InstanceBase.cpp:1922-1930); it never resends position
while standing. Remove _tick_on_waiting and its throttle state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-22 17:12:16 +09:00
co-authored by Claude Opus 5
parent ad6ea9ad7e
commit 6a175ee293
5 changed files with 17 additions and 26 deletions
@@ -331,3 +331,10 @@ server_speed_scale = 0.0 if moving_speed > 1100 else float(moving_speed) / 100.0
**Equivalence**`Constants/units` 行的 `movSpd` 缩放边界差异关闭;合同状态维持
`PARTIAL`(固定 300 像素 Dst/根运动适配层、资源与网络事件完整顺序、坐骑速度细节等
仍未闭合)。
### Port round 2026-09-22 — `PythonPlayerEventHandler.cpp`
- `OnMove`/`OnMoving`/`OnStop``net_play.gd::_on_local_moved`/`_on_anim_state``_was_moving` 边沿与参考一致(PORTED)。
- `OnWaiting`:参考只在 `CInstanceBase::Transform()``IsWalking() || IsUsingMovingSkill()` 分支内、本帧位移 ≤1.0 时调用(`InstanceBase.cpp:1922-1930`),100ms 节流,位移基准为 `m_kPPosPrevWaiting`(由 `OnSyncing`/`OnWaiting` 更新)。当前端原有的 `_tick_on_waiting()` 在站立期间周期补发 `FUNC_WAIT`,参考没有该机制,已删除(含 `SEND_WAITING_*``_last_waiting_sent_t`)。走/跑中本帧无位移的 OnWaiting 路径尚未移植(DIVERGENT)。
- `CInstanceBase::SHORSE::SetMoveSpeed` 只同步坐骑模型动画速率,不是另一套移动速度公式,归 `world.mount_pet_polymorph`
- 测试:`netplay_test.gd` 新增“站立被推开不发包”断言;netplay/keyboard_motion_timeline/movement_parity/player_move/test_wasd_steering_parity/test_no_auto_move_regression/motion_event_warp/playable_combat 均 PASS。
+1
View File
@@ -483,3 +483,4 @@
{"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。"}
{"time": "2026-09-22T01:30:00Z", "event": "audit_round", "result": "PARTIAL", "ids": ["movement.keyboard.motion/canact-paralysis-faint-sleep-gate"], "files": ["audit/contracts/movement.keyboard-motion.md", "audit/manifest.json", "audit/history.jsonl"], "tests": ["40250 reference source reachability audit (exhaustive grep -arn across UserInterface/ + GameLib/ for SetParalysis/Paralysis, AFFECT_SLEEP/SetSleep, SetFaint/FaintTest, Stun/RecvStunPacket)"], "reason": "Investigated the movement.keyboard-motion.md Remaining item \"CActorInstance::CanAct() Paralysis/Faint/Sleep movement gates not wired into net_play.gd\" (flagged unfixed by the prior round). Findings: (1) IsParalysis()/m_isParalysis has exactly one setter in the whole reachable 40250 source, CInstanceBase::__Shaman_SetParalysis (InstanceBaseEffect.cpp:821-823), which itself has zero callers anywhere in the tree -- Paralysis is dead code, never true in live gameplay, so not wiring it is correct parity, not a gap. (2) IsSleep()/m_isSleep: the AFFECT_SLEEP case in SetAffect()s switch is commented out (dead); the only live setter is the AFFECT_STUN case (m_GraphicThingInstance.SetSleep(isVisible), ~InstanceBaseEffect.cpp:932-933) -- IsSleep() is functionally just an alias for AFFECT_STUN in practice, which the current clients Entity.stunned field already represents (entity_store.h:119) and net_play.gd::_can_process_network_state() already gates movement on (dead/stunned/knock_down) -- already covered, not a gap. (3) IsFaint()/m_isFaint: only setter is chrFaintTest() (PythonCharacterModule.cpp:1087-1105), a Python-bound debug helper (\"FaintTest\", registered alongside chrtestRestoreRenderMode) operating on GetSelectedInstancePtr(); the available 40250 checkout has no root/ Python UI/quest scripts to prove or disprove live-gameplay reachability -- evidence-blocked, left unresolved per SKILL.md do-not-audit-unreachable-code-as-product-behavior rule, no fix attempted. Byproduct finding while tracing IsStun(): CActorInstance::Stun() is only reachable via RecvStunPacket() (PythonNetworkStreamPhaseGame.cpp:1556-1578), which explicitly routes GC_STUN to Die() for the main character and to Stun() only for remote actors; verified this does NOT create a movement.keyboard.motion discrepancy because CanAct() blocks movement identically on IsDead() and IsStun(), and net_play.gd::_can_process_network_state() already blocks movement identically on dead and stunned -- the movement-blocking outcome is already correct parity regardless of which reference path fires. The Die()-vs-Stun() distinction only affects death-system semantics (m_isRealDead, EXP drop, corpse/respawn flow), which is combat.death.exp_drop/combat.affect_status territory and is already tracked there (combat.affect-status.md Remaining #2, equivalence rows 63/67: native GC_STUN only sets stunned=true with no dedicated clear path) -- not re-logged as a new finding to avoid duplicate tracking. No code changed this round; removed the resolved Remaining bullet from manifest.json and movement.keyboard-motion.md, replaced with investigation evidence and citations. Contract stays PARTIAL (root-motion/resource-event-source/sync-hit-death-gate-stacking/real-model-bone-flip items remain open)."}
{"time": "2026-09-22T03:15:00Z", "event": "implementation_fix_round", "result": "PARTIAL", "ids": ["movement.keyboard.motion/server-speed-scale-clamp"], "files": ["project/player_controller.gd", "project/keyboard_motion_timeline_test.gd", "audit/contracts/movement.keyboard-motion.md", "audit/manifest.json", "audit/history.jsonl", "audit/remediation-roadmap.md", "audit/reports/coverage.md"], "tests": ["40250 reference source inspection (PASS: InstanceBaseMovement.cpp CInstanceBase::SetMoveSpeed(UINT uMovSpd) only guards uMovSpd>1100 -> 0; otherwise applies uMovSpd/100.0f unclamped)", "extension/src/net/entity_store.cpp EntityStore::motion_move_speed re-read (PASS: already-fixed remote-entity path mirrors the same >1100 -> 0 / mount_vnum!=0 -> 0 guards, no [0.25,3.0]-style band)", "pre-fix player_controller.gd::set_server_speed (FAIL by construction: clampf(moving_speed/100.0, 0.25, 3.0) silently floors/ceils haste or slow stacks outside the reference's 25..300 movSpd band)", "post-fix godot --headless --path project --script keyboard_motion_timeline_test.gd (PASS, incl. new set_server_speed(10/500/1200/100) assertions)", "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)", "godot --headless --path project --script netplay_test.gd (PASS; unrelated mock set_server_speed in netplay_test.gd has no clamp, unaffected)", "git diff --check (PASS)", "audit_ledger.py refresh --write/report --write/validate (PASS)"], "reason": "在同一合同下追查『根运动 vs 连续速度』假设时发现这是假线索——entity_store.cpp::advance_walk_by_motion 与本地 _process() 的位移积分在种类上同构(都是恒速直线插值);真正的差异是 set_server_speed() 把 moving_speed/100.0 塞进无参考依据的 clampf(0.25, 3.0),而参考实现 CInstanceBase::SetMoveSpeed 和本项目已修复的 EntityStore::motion_move_speed 都只有 moving_speed>1100 -> 0 这一个门。移除该私有夹钳,改为 0.0 if moving_speed > 1100 else moving_speed/100.0,两处调用方(net_world.gd、net_play.gd)均不额外夹钳,改动范围限于 player_controller.gd。新增回归断言覆盖旧边界内外和 1100 冻结门。副产品发现:CInstanceBase::SHORSE::SetMoveSpeed 是坐骑上玩家的独立速度设定,只在 IsMounting() 时生效,与远端 mount_vnum!=0 -> 0 的处理方式不同——本轮未下定论,留作独立候选。未修改根运动/AccumulationMovement 适配层、资源与网络事件完整顺序、同步/受击门叠加或真实模型测试,合同保持 PARTIAL。"}
{"time": "2026-09-22T06:00:00Z", "event": "port_round", "unit": "UserInterface/PythonPlayerEventHandler.cpp", "ported": ["CPythonPlayerEventHandler::OnMove", "CPythonPlayerEventHandler::OnMoving", "CPythonPlayerEventHandler::OnStop"], "deleted": ["net_play.gd _tick_on_waiting idle FUNC_WAIT resend"], "divergent": ["CPythonPlayerEventHandler::OnWaiting (walking-but-stalled path not ported)"], "needs_live": [], "tests": ["project/netplay_test.gd PASS", "project/keyboard_motion_timeline_test.gd PASS", "project/movement_parity_test.gd PASS", "project/player_move_test.gd PASS"]}
+1 -1
View File
@@ -276,7 +276,7 @@
},
"remaining": [
"固定 300 像素 Dst 已按事件创建并在目标到达时消费;锁定/移动技能期间 held WASD 会在解锁后重试;.msa 根运动和当前连续速度/米制推进的时序差异仍未闭合",
"OnMove/OnMoving/OnWaiting/OnStop 与当前每帧 signal、NetPlay 节流的事件源逐项证明",
"OnWaiting(走/跑动作中本帧无位移时 100ms+1.0 相对 m_kPPosPrevWaiting 发 FUNC_WAITInstanceBase.cpp:1930)未移植;站立期补发已删除",
"同步、私店、受击、死亡、传送、情绪以及多种锁门叠加顺序;移动技能/普通锁定/私店/情绪的 held WASD 恢复已覆盖基础路径",
"真实角色模型/骨骼在快速反向输入时不翻转的模型级测试",
"输入事件到移动请求、停止请求和网络包的完整时间线"