主命中与后续 strike proc 的统一事务

本批将 CombatState.Attack 的主命中与后续 strike 消费者接入同一工作副本。解决的是“后续技能失败时主伤害/护盾/随机数已提交”的接线风险。正式 Stormclaw 的原选靶、事件体与表现生产者仍未绑定,不开放 TYPE201 武器准入。 后续核实:这里曾称作“HIT 统计”的 16/17 实际是 CHARACTER/WEAPONHIT、GOTWEAPONHIT 消息,不是 CStat 自增;见后续事件取证文档。

原版顺序证据

原 EXE SHA256:186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

直接读取原机器码确认:

  • 0x54E091 → 0x53D5A0:先调用 applyDamage。
  • 0x54E21D → 0x53D3C0:随后分派 152;前面的 0x54E216 压入 152。
  • 0x54E237 → 0x53D3C0:再分派 201;0x54E230 压入 201。
  • 0x54E1F5(格挡结果)与 0x54E202(NOSTRIKEEFFECTS)均跳往 0x54E376,越过 strike proc 段。

两段之间还存在原 CHARACTER 消息/观察者与反馈调用,不能声称“扣血后立刻随机选个敌人”就是完整原版。后续已确认这对消息不是数值统计操作,并独立绑定当前完整语料中的空观察者桶;INativeStrikeProcRuntime 继续负责真实 proc,测试消费者不代替正式实现。

同时删掉了 AttackResolver 中旧的无条件 fireProcEffects 诊断调用。它们既处于真正扣血之前,也没有遵守格挡/0x2000/武器分类门,不能拿来接真实 proc。伤害数值解算未改。

当前实现

  • 检测已表示的活动 strike 类型;装备遍历不因 HIDDEN 或 StatsEnabled=false 而跳过装备槽。
  • 没有相关 proc 或有 NOSTRIKEEFFECTS 时保留现有直达路径,不为普通无 proc 攻击创建整个工作副本。
  • 有相关 proc 时,在工作副本中执行一次完整主命中,包括现有控制、护盾、资源变化和已建模死亡工作。
  • 只有主命中成功且原 strike 门允许时调用后续消费者。格挡/未命中的不可达消费者不会被要求绑定。
  • 后续攻击仍走正常 CombatState.Attack,原 Stormclaw strike 的 NOSTRIKEEFFECTS 可阻止再次进入 strike 链。
  • Volatile 与 Synced 两条流共同提交;发生缺失输入或后续失败时,两者和主命中都不发布。
  • 提交保留现有 CombatUnit/Item 对象引用,更新其可变状态,避免前端或攻击控制器仍握着旧对象。
  • 先向独立验证副本发布,再对全部快照内容做结构比较:数组顺序、float/double 位模式、身份与新增字段都参与比较。尚不能发布的状态变化显式拒绝,不静默丢失。
  • 结构比较使用纯内存数据,不把 JSON/文件 I/O 放进 CoreSim。成员访问缓存仅属于当次比较,没有静态可变状态。
  • 框架服务在工作副本与读档时重绑;本批无新增持久字段,仍为 v48。

主要文件:core_sim/SimStrikeTransaction.csCombat.csAttackResolve.csSimState.csSimPeriodicDeath.cs

按 godot-master 保持模拟权威与表现分离;这一层不创建 Godot 节点、不读取系统时间、不代替原目标/视线查询。

具体测试

核心测试使用真实原 Stormclaw 的 40% 电伤 strike 定义,但后续消费者/选靶是明确夹具,不声称完成 proc 全程序。

  • 主命中后,后续回调确实观察到目标已扣血/已死亡。
  • 通过普通攻击 API 对第三个单位执行二次武器伤害。
  • 故意使后续失败:主 HP、二次 HP、护盾消耗、金币、两条 RNG 和存档字节全部回滚。
  • 成功时二次伤害和护盾移除一并发布,角色与物品引用保持原对象。
  • 存档恢复后整次攻击逐字节重放。
  • 格挡/NOSTRIKEEFFECTS、stats-disabled 装备槽、不支持的额外状态写入均有回归。
  • Godot 隔离测试通过实际 CombatState.Attack 验证顺序、回滚和重放,不读写默认玩家存档。

最终验证:2104 条核心断言、453 项相关 Python 回归、Godot 47 项检查通过;C# 构建零警告/零错误,195 个核心文件静态检查无硬错。旧元素正式数值场景与四元素 GPU 探针重新运行通过。结果记录在 build/strike_transaction_*.logbuild/native_named_stat_engine/result.json

明确边界与后续工作

  1. INativeStrikeProcRuntime 目前没有在正式加载器绑定完整原版消费者;达到需处理的 strike 分支会明确返回 native_strike_proc_runtime_unbound
  2. 仍需正式概率/管理器执行、目标查询与 Stormclaw 事件体,以及光束端点输出。后续核实:曾称为 source-skill 的 a7 实为当前攻击武器;原版 16 个可达 proc 的左右手要求均无约束。16/17 消息的后续确认不代表更早的 CHARACTER/HIT、GOTHIT 等观察者已经实现。
  3. 当前发布器不支持新增/移除单位、移动物品拓扑、未登记来源物品、无法重建的旧引用别名,或任意任务/布局状态改变。这些操作会拒绝而非只提交其中一部分;召唤/变形类 proc 继续需要扩展。
  4. 绑定实时对象的旧反应回调不能直接复用到工作副本,需要 work-bound factory;因此显式拒绝相应未知情况。
  5. 为先保证状态完整性,本路径使用两次恢复与结构验证。高频攻击型 proc 的性能预算尚待实际生产消费者接通后测量,不宣称生产性能已验收。

构造覆盖仍为 1099 / 1419,273 阻塞、47 数据/等级缺口、0 构造异常;全部武器目标未完成。

main 原地,未提交。默认存档 SHA256 保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f