WEAPONHIT/GOTWEAPONHIT:角色消息,不是统计自增

本批纠正前一批的错误称呼,并把这对消息按已验证的原版空观察者桶绑定到正常命中和读档路径。没有为不存在的“命中计数”编造公式或加一。

原函数与消息内容

原 Torchlight2.exe SHA256:186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

  • 7BF790 构造的是 CCharacterEventMsg。写入 kind(+8)、subtype(+0C)、creator(+10)、other(+14),并置 float payload(+18)为 0。偏移均为十六进制。
  • 7BFA90 取得/建立事件管理器单例,不是执行数值加一。
  • 7BF8D022 * kind + subtype 取观察者桶并按顺序回调。
  • kind=0 为 CHARACTER。静态字符串构造 1B912F31B91308 证明 subtype 16、17 分别为 WEAPONHITGOTWEAPONHIT
  • 主命中的顺序是:applyDamage/已有死亡处理 → attacker 创建 WEAPONHIT(other=target)→ target 创建 GOTWEAPONHIT(other=attacker)→ 后续反馈和 strike proc。
  • 格挡和 NOSTRIKEEFFECTS 的 late gate 位于这对消息之后,不能因此跳过消息。未进入 applyDamage 尾部的失败攻击则不发布。

405910 只初始化 Runic 对象基础字段并递增其对象计数;不能将这个内存对象计数错认成 CStat。没有在上述消息构造/分派里找到所谓命中整数统计的更新。

为什么原版这两桶可为空分派

全量冻结目录包含 233 份 TRIGGERABLEACTION DAT,不是只检查 54 个武器引用条目:

  • WEAPONHIT:0 定义。
  • GOTWEAPONHIT:0 定义。
  • 更早的 HIT:15 定义;GOTHIT:7 定义。它们是不同的 subtype,不能据此推断也为空或已经完成
  • 当前 233 份均无 BASEFILE,事件路由明确。
  • IDA 对原注册函数 7BFB30 的交叉引用指向 triggerable enable 路径 6DEBC5;原 6DFEB0 从 GAMEEVENTTYPE/SUBTYPE 解析其桶。

tools/build_weapon_hit_observer_proof.py 同时核对目录完整性、所有源哈希、冻结 raw 属性、枚举名和完整触发器目录哈希,输出 build/f2/weapon_hit_observers.json。不覆盖外部/mod 观察者;发现继承、未知路由或新观察者时,不能沿用空桶结论。

实现

  • NativeWeaponHitEvents.Pair 保留两个 creator/other 的反向对应,StatOwner/StatValue 不承载伪造的计数。
  • F2WeaponHitObservers 校验完整 frozen catalog 与 proof,拒绝非空桶、目录变化或未知继承/路由。
  • 正常 F2LootData 加载与 CoreSimBridge 的重新绑定使用此 proof;没有改变原 weapon_triggerables.json,也不无故改变现有成长/统计 catalog ID。
  • CombatState.AttackCore 在已建模伤害/死亡之后、后续 strike proc 之前发布消息。空桶只留下诊断记录,不修改 RNG、CStat 或 HP。
  • 主命中/后续技能事务的工作副本和周期效果工作副本都会继承冻结 proof;失败的后续 proc 不向实时对象发布工作副本消息。
  • 未绑定 proof 的旧兼容路径仍明确处于未验证状态,不能把“没有 modeled listener”自动解释成原版空桶。
  • proof 已接入 build_player_attack_events.py 重建流程。新增的是冻结数据服务与诊断消息,没有新增持久字段,仍为 v48。

遵循 godot-master 的模拟/显示分离;不让 Godot UI、动画回调或可视特效决定消息次数。

验证范围

独立执行原 4059107BF7907BF8D0,16 组用例检查消息字段、零浮点载荷、空桶和合成监听器的原顺序。监听器体仅记录投递,不声称实现原触发动作。

还验证了:完整 233 文件集合、所有哈希、两个空桶与 HIT/GOTHIT 的区别;NOSTRIKEEFFECTS 不抑制该消息对;没有虚构 CStat;空桶投递与旧路径的存档/RNG 一致;消息先于后续 proc;失败事务不泄漏消息;非空 proof 不能配置为已支持。

Godot 独立检查包含真实命中、消息顺序和 CStat 不变;实际 ECHO PASS 探针检查正常加载及快照恢复均自动绑定 proof。最终验证:2112 条核心断言、455 项相关 Python 回归、Godot 50 项检查通过;C# 构建零警告/零错误,197 个核心/IO 文件静态检查无硬错。旧元素正式场景和四元素 GPU 回归重新运行通过。记录在 build/weapon_hit_events_*.log

仍需完成

  • 正式攻击型 proc 的目标/射线查询、实例启动和事件体,以及光束端点生产者。后续已纠正 a7 的误读:它是当前武器,不是来源技能;当前 16 个原 proc 的左右手要求均无约束。
  • 更早的 HIT/GOTHIT、CRIT 等其他事件不能用本 proof 代替;扩展相应语义时需检查各自实际定义与过滤/观察者状态。
  • 不开放尚未完成的武器构造分支。当前仍为 1099 / 1419,273 阻塞、47 数据/等级缺口、0 构造异常。

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