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>
This commit is contained in:
shenlei
2026-09-22 16:31:17 +09:00
co-authored by Claude Sonnet 5
parent a4ac48db24
commit c5c183da37
6 changed files with 120 additions and 12 deletions
+58 -1
View File
@@ -89,7 +89,7 @@
| Branch structure | PARTIAL | W/S、A/D、八方向、释放顺序和 `m_isCmrRot` 相机跟随已覆盖;Paralysis/Faint/Sleep 已调查——Paralysis 死代码、Sleep 已被 `stunned` 门覆盖、Faint 证据不足,均非差异 |
| 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 节流已记录;米/厘米、移动速度和目标距离不同 |
| Constants/units | PARTIAL | 角度/旋转阈值和 0/300/100ms 节流已记录;`movSpd` 缩放边界已与 `CInstanceBase::SetMoveSpeed` 对齐(`movement.keyboard.motion/server-speed-scale-clamp`);米/厘米和目标距离仍不同 |
| Timing/event sources | PARTIAL | 当前是 `_process` 预测 + signal;参考是 Actor motion event 驱动 |
| Resource/data sources | PARTIAL | 当前模型速度可从动作资源读取但有 fallback;`.msa` event/AccumulationMovement 尚未一一对照 |
| Protocol side effects | PARTIAL | `FUNC_MOVE/FUNC_WAIT` 发送和节流有测试;阻止/重复/相机副作用仍未闭合 |
@@ -106,6 +106,11 @@
Paralysis 是可达代码中的死代码(唯一 setter 零调用点);Sleep 在实际运行中是
AFFECT_STUN 的别名,已被当前端 `stunned` 移动门覆盖;Faint 的可达性在现有 40250
检出材料下无法证明或证伪,evidence-blocked。三者均非需要修复的移动等价差异。
- ~~本地玩家 `set_server_speed()` 对 `moving_speed/100.0` 施加了参考代码没有的
`clampf(..., 0.25, 3.0)` 边界~~ **已修复(2026-09-22,
`movement.keyboard.motion/server-speed-scale-clamp`,见下方实现轮次)**:现在
与 `CInstanceBase::SetMoveSpeed` 和已修复的 `EntityStore::motion_move_speed`
一样,只在 `moving_speed > 1100` 时归零,其余按 `moving_speed/100.0` 直接换算。
### Audit round 2026-09-21
@@ -274,3 +279,55 @@
移动阻断结果无影响,已移交 `combat.affect_status` 既有条目,不重复登记。`Remaining`
中对应条目标记为已调查关闭。合同状态维持 `PARTIAL`(根运动/资源事件/同步受击门等
仍未闭合),Branch structure 行的 Paralysis/Faint/Sleep 备注移除。
### Implementation fix round 2026-09-22 — `movement.keyboard.motion/server-speed-scale-clamp`
**参考链**:`InstanceBaseMovement.cpp:CInstanceBase::SetMoveSpeed(UINT uMovSpd)`——
`if (uMovSpd > 1100) uMovSpd = 0; m_GraphicThingInstance.SetMoveSpeed(uMovSpd/100.0f);`。
除 1100 冻结门外没有其他上下限。
**当前链(修复前)**:`player_controller.gd::set_server_speed(moving_speed)` 把
`moving_speed/100.0` 塞进 `clampf(..., 0.25, 3.0)`,即 `moving_speed<25` 会被强行拉高到
0.25 倍速、`moving_speed>300` 会被强行压低到 3.0 倍速——这个区间在参考代码、
`InstanceBase.cpp` 或本项目已修复的远端实现 `EntityStore::motion_move_speed`
(`extension/src/net/entity_store.cpp`,只有 `moving_speed>1100→0` 和
`mount_vnum!=0→0` 两个门)中都找不到依据,是无参考支持的本地私有夹钳:叠了减速/
加速状态的本地玩家会和其他人看到的自己不一致。
调查过程中还确认 `advance_walk_by_motion`(远端实体)和本地 `_process()` 的位移积分
在**种类**上是同构的(都是恒速 `speed*dt` 直线插值,都从 `get_move_motion_speeds()`
取 `.msa` 基准速度),排除了「根运动 vs 连续速度」这个更大的假设——真正的差异只在
速度缩放公式的边界,不涉及积分模型本身。另确认 `CInstanceBase::SHORSE::SetMoveSpeed`
是坐骑上玩家的另一套速度设定(`InstanceBase.cpp:40-135`),只在 `IsMounting()` 时生效;
本地玩家的动作模式已经在 `net_play.gd::motion_mode_for()` 里按骑乘切到
`MOTION_MODE_HORSE`,而远端实体对 `mount_vnum!=0` 直接归零处理——这是坐骑速度的独立
细节,本轮不下定论,留给未来一轮单独核实是否构成差异。
**修复**:`project/player_controller.gd::set_server_speed()` 把
```gdscript
server_speed_scale = clampf(float(moving_speed) / 100.0, 0.25, 3.0)
```
改为
```gdscript
server_speed_scale = 0.0 if moving_speed > 1100 else float(moving_speed) / 100.0
```
`moving_speed <= 0` 提前 return 的既有保护(防止出生前/畸形值冻结本地预测)保留不变。
两处调用方(`net_world.gd:795-796`、`net_play.gd:921-924`)均不额外夹钳,改动影响面
只限 `player_controller.gd` 本体。
**测试**:新增 `keyboard_motion_timeline_test.gd` 末尾的 `set_server_speed` 断言
(`moving_speed=10→0.1`、`500→5.0`、`1200→0.0`、`100→1.0`,覆盖旧 [0.25,3.0] 边界内外和
1100 冻结门),随同下列既有回归一并执行,全部 PASS:
`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`、
`netplay_test.gd`(`netplay_test.gd` 里的同名 mock `set_server_speed` 是独立测试替身,
不含旧夹钳逻辑,未受影响)。`git diff --check` 无空白错误。
**Equivalence**:`Constants/units` 行的 `movSpd` 缩放边界差异关闭;合同状态维持
`PARTIAL`(固定 300 像素 Dst/根运动适配层、资源与网络事件完整顺序、坐骑速度细节等
仍未闭合)。