audit(movement.keyboard.motion): close Paralysis/Faint/Sleep gate as false positive
Investigated the prior round's flagged gap ("CanAct()'s Paralysis/Faint/Sleep
movement doors not wired into net_play.gd") via exhaustive reachability
tracing of the 40250 reference source. All three resolve to non-gaps:
- Paralysis: its only setter (__Shaman_SetParalysis, InstanceBaseEffect.cpp:821)
has zero callers anywhere in the reachable source -- dead code, never true
in live gameplay.
- Sleep: the AFFECT_SLEEP case in SetAffect() is commented out; the only live
setter is the AFFECT_STUN case (SetSleep(), InstanceBaseEffect.cpp:932-933),
i.e. IsSleep() is just an alias for AFFECT_STUN -- already covered by the
existing `stunned` field and _can_process_network_state()'s dead/stunned/
knock_down gate.
- Faint: only setter is chrFaintTest(), a debug-only Python binding operating
on GetSelectedInstancePtr(); unverifiable from the available 40250 checkout
(no root/ UI scripts) -- evidence-blocked, left unresolved per the skill's
reachability rule.
Byproduct finding while tracing IsStun(): main-character GC_STUN routes to
Die() in the reference (RecvStunPacket), not Stun(); verified this causes no
movement.keyboard.motion discrepancy since CanAct() blocks movement equally
on IsDead()/IsStun() and the current client already blocks movement equally
on dead/stunned. The Die()-vs-Stun() distinction only affects death-system
semantics, already tracked under combat.affect-status.md (Remaining #2, rows
63/67) -- not duplicated here.
No implementation code changed. Removed the resolved item from manifest.json
and movement.keyboard-motion.md's Remaining list; documented full evidence
in the contract doc and remediation roadmap. Contract stays PARTIAL
(AccumulationMovement root-motion, OnMove/OnMoving event-source parity,
sync/hit/death/warp gate stacking, and real-model bone-flip test remain open
for future rounds).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7df97e01f9
commit
a4ac48db24
@@ -86,7 +86,7 @@
|
||||
| 项目 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| Preconditions | PARTIAL | active/失焦/按键残留、锁门、私店、情绪和锁门 held WASD 已覆盖;同步、受击、死亡和传送门未全覆盖 |
|
||||
| Branch structure | PARTIAL | W/S、A/D、八方向、释放顺序和 `m_isCmrRot` 相机跟随已覆盖;Paralysis/Faint/Sleep 未接入移动门 |
|
||||
| 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 节流已记录;米/厘米、移动速度和目标距离不同 |
|
||||
@@ -101,15 +101,11 @@
|
||||
- 补充死亡/传送/受击以及私店、情绪、移动技能和普通锁定的多门叠加顺序;各单门 held WASD 恢复已覆盖基础路径。
|
||||
- 继续对照 `.msa` 的 motion event、`AccumulationMovement` 根运动和当前 `get_move_motion_speeds` fallback,决定是否需要根运动适配层。
|
||||
- 真实角色模型在 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 分支更大的独立子项,留给后续轮次。
|
||||
- ~~`CanAct()` 的 Paralysis/Faint/Sleep 移动门~~ **已调查关闭(2026-09-22,
|
||||
`movement.keyboard.motion/canact-paralysis-faint-sleep-gate`,见下方调查轮次)**:
|
||||
Paralysis 是可达代码中的死代码(唯一 setter 零调用点);Sleep 在实际运行中是
|
||||
AFFECT_STUN 的别名,已被当前端 `stunned` 移动门覆盖;Faint 的可达性在现有 40250
|
||||
检出材料下无法证明或证伪,evidence-blocked。三者均非需要修复的移动等价差异。
|
||||
|
||||
### Audit round 2026-09-21
|
||||
|
||||
@@ -222,3 +218,59 @@
|
||||
改为已闭合(见上表),`Remaining` 移除相机副作用项、新增 Paralysis/Faint/Sleep 移动门
|
||||
子项(见上)。合同整体仍为 `PARTIAL`——根运动/资源事件/同步受击门/Paralysis-Faint-Sleep
|
||||
仍未闭合。
|
||||
|
||||
### 调查轮次 2026-09-22T — `movement.keyboard.motion/canact-paralysis-faint-sleep-gate`
|
||||
|
||||
本轮对上条 Remaining 中「`CanAct()` 的 Paralysis/Faint/Sleep 三个移动门未接入」逐一做
|
||||
可达性追踪(`grep -arn`,因大量 `UserInterface/*.cpp` 注释含 CP949 字节,需要 `-a` 才能
|
||||
正确匹配),结论:三者均不构成需要修复的移动等价差异,逐项证据如下。
|
||||
|
||||
- **`IsParalysis()`/`m_isParalysis`**:全部可达源码中唯一的写入点是
|
||||
`CInstanceBase::__Shaman_SetParalysis`(`InstanceBaseEffect.cpp:821-823`,直接调用
|
||||
`m_GraphicThingInstance.SetParalysis(...)`,`m_GraphicThingInstance` 声明为
|
||||
`CActorInstance`,`InstanceBase.h:999`,确认改的是 `CanAct()` 读的同一个标志位)。
|
||||
对 `__Shaman_SetParalysis`/`SetParalysis`/`Paralysis` 在整个 `40250/ClientVS22/source`
|
||||
树(`UserInterface`+`GameLib`+其余全部模块)做穷举 grep,**零调用点**——没有任何脚本、
|
||||
网络包处理器或技能逻辑调用它。结论:Paralysis 分支在可达代码中是死代码,40250
|
||||
Windows 客户端实际运行中 `IsParalysis()` 恒为 false。当前端不接入此门是正确等价
|
||||
(无对应行为可对照),不是差异。
|
||||
- **`IsSleep()`/`m_isSleep`**:`InstanceBaseEffect.cpp` 的 `SetAffect()` 分支表中
|
||||
`AFFECT_SLEEP` 分支已被注释掉(死代码);唯一存活的写入路径是
|
||||
`AFFECT_STUN: m_GraphicThingInstance.SetSleep(isVisible);`(约行 932-933)。即
|
||||
`IsSleep()` 在实际运行中就是 AFFECT_STUN 的别名,不是独立信号。当前端
|
||||
`entity_store.h:119` 的 `stunned` 字段本就注释为对应 `AFFECT_STUN`
|
||||
(`InstanceBaseEffect.cpp:932`),`net_play.gd::_can_process_network_state()` 已经在
|
||||
`dead`/`stunned`/`knock_down` 上阻止移动——即 Sleep 对应的移动门已经被现有 `stunned`
|
||||
门覆盖,不需要新增单独的 Sleep 门。
|
||||
- **`IsFaint()`/`m_isFaint`**:唯一写入点是 `chrFaintTest()`
|
||||
(`PythonCharacterModule.cpp:1087-1105`),Python 绑定名 `"FaintTest"`
|
||||
(同文件 1305 行),与另一个调试方法 `chrtestRestoreRenderMode`/`"RestoreRenderMode"`
|
||||
(1072/1319 行)并列在同一张绑定表里,且操作对象是 `GetSelectedInstancePtr()`
|
||||
(更像选人/预览界面而非在线主控角色)。当前可用的 40250 检出只有 8 个 `.py`
|
||||
文件、没有 root/ 下的任何 UI/任务脚本,因此无法从现有材料证明或证伪是否有在线
|
||||
脚本会调用 `char.FaintTest()`。结论:证据不足,按 SKILL.md 的「不要在没有可达性证据
|
||||
时把未验证代码当作产品行为来追」原则,本项目前不作为移动门差异处理,标记为
|
||||
evidence-blocked。
|
||||
|
||||
**过程中新发现(已移交,非本合同范围)**:追踪 `IsStun()` 时发现
|
||||
`CActorInstance::Stun()` 唯一调用者 `CInstanceBase::Stun()`
|
||||
(`InstanceBaseBattle.cpp:683-688`)本身只被 `RecvStunPacket()`
|
||||
(`PythonNetworkStreamPhaseGame.cpp:1556-1578`)调用,且该函数显式区分:
|
||||
`if (GetMainInstancePtr()==pkInstSel) pkInstSel->Die(); else pkInstSel->Stun();`——
|
||||
主角收到 `GC_STUN` 走的是 `Die()`(`InstanceBaseBattle.cpp:691` → `ActorInstance.cpp:365`
|
||||
`m_isRealDead=TRUE`+播放 NAME_DEAD 动作),只有非主角远端 actor 才真正调用 `Stun()`/
|
||||
置位 `IsStun()`。当前端 `entity_store.cpp` 的 `GC_STUN` 处理器对任意 vid(含主角)统一
|
||||
置 `stunned=true`,没有这个主角分流。**但这不是 movement.keyboard.motion 的差异**:
|
||||
`CanAct()` 对 `IsDead()`/`IsStun()` 的移动阻断效果相同,而 `net_play.gd`
|
||||
`_can_process_network_state()` 已经对 `dead`/`stunned` 同等阻止移动——即无论主角
|
||||
`GC_STUN` 走死亡路径还是眩晕路径,当前端的**移动阻断结果**已经正确等价。
|
||||
`Die()`/`Stun()` 分流只影响死亡系统本身(`m_isRealDead`、EXP 掉落、尸体/复活流程等),
|
||||
这是 `combat.death.exp_drop`(或 `combat.affect_status`,见其合同 Remaining #2、
|
||||
分支矩阵第 63/67 行已经记录 `native GC_STUN` 目前只置 `stunned=true` 缺解除路径)的
|
||||
范围,不在此重复登记。
|
||||
|
||||
**结论**:本轮不改动实现代码——三个候选门中两个(Paralysis/Sleep)是死代码/已被现有
|
||||
`stunned` 门覆盖,第三个(Faint)证据不足;衍生发现的 GC_STUN 主角分流差异经核实对
|
||||
移动阻断结果无影响,已移交 `combat.affect_status` 既有条目,不重复登记。`Remaining`
|
||||
中对应条目标记为已调查关闭。合同状态维持 `PARTIAL`(根运动/资源事件/同步受击门等
|
||||
仍未闭合),Branch structure 行的 Paralysis/Faint/Sleep 备注移除。
|
||||
|
||||
Reference in New Issue
Block a user