# 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`:FAIL,1 项:`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 资源选择后再提升合同状态。 ### 2026-09-22 死代码删除 以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:`combat_status_effect_system.gd`, `test_combat_status_effect_parity.gd`, `test_mob_combat_reaction_parity.gd`。