Files
mtgodot-poc/audit/contracts/combat.affect-status.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

96 lines
8.6 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.
# combat.affect_status —— 受击、眩晕、击倒、状态旗标和状态图标
状态:`PARTIAL`
本合同对照 40250 Windows 客户端的两条相关但不能合并的链路:
1. Actor 的 `AffectFlagContainer` 变化如何驱动附着效果、眩晕/睡眠显示和石头烟雾。
2. 服务端 `GC_DAMAGE_INFO``GC_STUN``GC_AFFECT_ADD/REMOVE` 和角色更新如何进入实体、动作、状态图标与清理流程。
注意:40250 Windows 客户端不在本地决定普攻中毒率、眩晕率、减速率或持续伤害;这些结果由服务端计算,客户端消费状态包和伤害包。当前端的 `combat_status_effect_system.gd` 是本地/server-like 模拟器,因此即使其单元测试通过,也不能作为客户端等价实现的证据。
## 40250 参考调用链
### Actor affect flags
- `UserInterface/AffectFlagContainer.cpp`
- `Clear``CopyInstance``CopyData` 按位复制固定字节数据。
- `Set`/`IsSet` 对每个 affect bit 做边界检查。
- `UserInterface/InstanceBaseEffect.cpp`
- `SetAffectFlagContainer`:建筑直接忽略;石头走 `__SetStoneSmokeFlagContainer`;普通 Actor 走 `__SetNormalAffectFlagContainer`
- 普通 Actor 逐 bit 比较旧/新值,只对变化位调用 `__SetAffect``GraphicThingInstance.__OnSetAffect``__OnResetAffect`,最后复制新容器。
- `__ClearAffects` 在删除、重建和重置时销毁附着效果并清空容器;石头按烟雾等级重建烟雾。
- `AFFECT_INVISIBILITY` 会清附着特效和文字尾;`AFFECT_STUN` 调用 `SetSleep`
- `UserInterface/NetworkActorManager.cpp` / `InstanceBase.cpp`
- 角色创建和更新顺序包含 `SetAffectFlagContainer`,随后才刷新速度、攻击速度、阵营、PK 和 state flags。
- `SetStateFlags` 仅负责 killer/party 等展示与目标相关状态,不等同于 affect bitset。
### 受击、眩晕和伤害
- `UserInterface/PythonNetworkStreamPhaseGame.cpp`
- `RecvDamageInfoPacket` 找到 VID 后调用 `AddDamageEffect(damage, flag, bSelf, bTarget)`;负伤害只记录错误,不由客户端扣血。
- `RecvAffectAddPacket` 处理能量状态时间,并调用 `BINARY_NEW_AddAffect(type, point, value, duration)`
- `RecvAffectRemovePacket` 调用 `BINARY_NEW_RemoveAffect(type, point)`
- `InstanceBase`/`ActorInstanceBattle`
- `__HitGood``__HitGreate``__HitStone` 只负责表现层受击动作、震屏、击倒/起身队列;服务器伤害和状态仍是权威来源。
- `__CanProcessNetworkStatePacket` 在死亡、击倒或不能取消的技能动作中拒绝网络状态输入。
- `PythonNetworkStreamPhaseGameActor.cpp`
- `CharacterAdd2`/`CharacterUpdate` 搬运两个 32-bit affect words 和 state flag;更新进入 Actor manager 后再刷新 Actor 表现。
## 当前端实现映射
- `extension/src/net/classic/classic_parser.cpp``extension/src/net/entity_store.cpp`
- 已解析角色创建/更新的两个 affect words、`GC_STUN``GC_AFFECT_ADD/REMOVE``GC_DAMAGE_INFO`
- `Entity.affect_flags` 使用 64-bit 保存 bitset`Affect`/`AffectChange` 保存本地玩家的状态图标数据。
- `m2_client.cpp` 已把 affect change 转为 `affect_added`/`affect_removed` 信号。
- `project/net_world.gd``project/ui/player_view.gd``project/ui/mob_view.gd`
- 远端节点会收到 `set_affect_flags`,并有附着特效映射;`hit_reaction.gd` 实现 damage/knockdown/standup 队列和 shake/InsertDelay 近似。
- `game_scene.gd`/`hud.gd`/`quickbar.gd` 已消费一般 affect 数据,但图标目前是通用色块,未逐 affect 对照 40250 的 icon/附着资源。
- `project/net_play.gd`
- 本地主角消费 damage、stunned、knock_down 门控,并在攻击成功时驱动目标受击表现。
- `project/combat_status_effect_system.gd`
- 另有客户端本地中毒、眩晕、减速、灼烧/流血施加、计时和直接改 HP 的模拟链;它不是 40250 Windows 客户端的协议消费链。
## 分支等价矩阵
| 参考判据/副作用 | 当前实现 | 结论 |
|---|---|---|
| AffectFlagContainer 按位复制、边界检查和旧/新差分 | native 64-bit 字段 + `mut_affect_flags`;视图用 `AFFECT_EFFECT_MAP` 遍历 | 基本等价;未覆盖完整 bit 宽度和所有 effect resource |
| 普通 Actor affect 变化触发设置/重置附着效果 | `NetWorld._apply_field_updates``set_affect_flags` | 部分等价;当前按映射表重扫,未证明逐 bit 的旧/新回调和失败清理顺序 |
| 石头烟雾按 0/1/2/3 等级销毁并重建 | 当前通用 affect 映射,没有对应 stone smoke 分支证据 | 缺失/不等价 |
| 建筑忽略 affect flag | 当前 `_apply_field_updates` 依赖节点是否有 setter,没有显式建筑 no-op 规则 | 未证明等价 |
| AFFECT_INVISIBILITY 清附着效果、文字尾和可见性 | 当前有 affect effect 映射,但目标选择/文字尾的隐身门控分散在其它脚本 | 不完整 |
| AFFECT_STUN 驱动睡眠/眩晕表现 | 当前 `mut_stun` 仅置 `Entity.stunned`;本地门控可阻止行动,远端主要靠命中回调 | 不完整;没有完整远端 `SetSleep`/清除链 |
| GC_DAMAGE_INFO 只产生 DamageEffect/飘字,不在客户端扣血 | `net_world._on_damage` 产生飘字;基础 `damage_effect_test` 通过 | 基本等价;本地主角 `_on_damage` 额外使用通用 damage/击退锁,flag 细分未完整 |
| GC_AFFECT_ADD/REMOVE → UI 状态图标和能量时间 | native signal → `game_scene`/HUD/quickbar,基础协议测试通过 | 基本等价;图标资源、重复/乱序/过期 duration 未证明 |
| 客户端不本地结算 poison/stun/slow/fire 结果 | `combat_status_effect_system.gd` 本地 roll、tick 并直接改 HP | 明确不等价;数据权威错误 |
| 死亡、击倒、技能不可取消时拒收网络状态 | `EntityStore::can_process_network_state``NetPlay._can_process_network_state` 有 dead/knockdown/stun 门 | 部分等价;native 门不含 stunned,远端动作消费也未统一 |
| 受击动作 `HitGood/Great/Stone` 的资源选择和队列 | `hit_reaction.gd`/PlayerView/MobView 有近似队列 | 部分等价;`hit_view_test.gd` 当前有 1 项失败,前向 damage 仍停留在 combo 资源 |
## 已执行测试
- `project/test_combat_status_effect_parity.gd`:24/24 PASS;只能证明当前本地模拟器自身行为,不证明客户端权威模型正确。
- `project/damage_effect_test.gd`:PASS;覆盖伤害飘字分类和显示门。
- `project/combat_fx_test.gd`:PASS;覆盖命中、连击、shake、affect 和死亡主链。
- `project/test_mob_combat_reaction_parity.gd`:17/17 PASS;覆盖怪物受击和击倒 helper。
- `project/test_combat_death_pickup_entry_parity.gd`:PASS;覆盖死亡/动作锁相关入口。
- `build/extension/net_entity_test`PASS;覆盖 native stun/affect add/remove 等状态存储。
- `build/extension/net_classic_session_test`PASS;覆盖 classic session 状态包入口。
- `project/hit_view_test.gd`FAIL1 项:`fScalar < 0` 的前向受击期望 `damage/damage_1.msa`,实际仍为 `onehand_sword/combo_01.msa`
## 已确认差异和未验证项
1. `combat_status_effect_system.gd` 把服务端战斗状态结算复制到客户端,包含随机 roll、持续时间、毒/火跳伤害和直接改 HP;必须与协议消费链分离,否则会造成双重结算或离线假状态。
2. native `GC_STUN` 目前只置 `stunned=true`,没有专门的解除包/清除路径;当前依赖后续实体更新/重建恢复,未证明与 40250 Actor affect 清除顺序一致。
3. `NetWorld._apply_field_updates` 没有 40250 `__SetNormalAffectFlagContainer` 的逐 bit 差分回调,也没有 stone smoke/building/no-op 的显式分支。
4. 远端 `stunned`/`knock_down` 没有统一进入 `SetSleep`、击倒动作和网络状态拒收链;本地 `NetPlay` 与远端 `NetWorld` 的门控职责不一致。
5. `hit_view_test.gd` 的失败说明受击动作资源/动作锁时序仍有真实差异,当前不能将受击合同标记为通过。
6. 需要补齐 affect bit→effect/icon 资源索引、重复/乱序 add/remove、duration 过期、死亡/切图/重连清理、隐身文字尾、stone smoke 和建筑 no-op 测试。
## 下一轮验证入口
- 先移除或隔离本地状态结算器的权威写入,只保留服务端 affect/damage 包驱动的视图与 UI。
- 为每个 affect bit 建立 40250 资源、附着骨骼、开关、清理和石头烟雾矩阵。
- 增加 `GC_AFFECT_ADD → duration → GC_AFFECT_REMOVE`、重复/乱序/重连/换图的 native→HUD 端到端测试。
- 增加远端 Actor 的 stunned/knockdown/skill-cancel 状态机测试,并修复 `hit_view_test.gd` 的前向 damage 资源选择后再提升合同状态。