技能事件选择/复用与 hard-abort 顺序

延续 v48 的更新调度,本批补齐它调用的 END 事件选择及 hard-abort 控制流程,提供共享 NativeSkillLifecycleEvents 消费者。事件体本身的布局加载、世界伤害、粒子、召唤和资源清理仍需真实实现;本批不把调度通过计作完整武器通过。

原版证据

  • 原 Torchlight2.exe SHA256:186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e
  • 0x6C1C30:正常更新中的 END 选择与启动。
  • 0x6C14A0:hard abort,包括 END 的前/后置、14 组 reset、弱引用释放和 unit event 6。
  • 0x6C39E0:START 使用同样的原事件/空闲克隆/新克隆选择顺序;本批对照执行器直接执行的是前两项,不声称执行了完整 START。
  • 0x6DBDB0:克隆来自原事件,+208 保存原事件指针,克隆登记在属性的事件组中。
  • 0x6CD960:调用 6CAF80 停止事件后清 +126;不是删除事件实例。
  • 0x1B8EB80:14 个事件组名的静态构造。
  • 0x218A070:14 字节 CAN_CLONE 默认表。这里是 BYTE,不是 DWORD。

原文件导出在 build/research_weapon_families/。只读使用原游戏 IDA 库,未 attach 游戏进程。

组内选择顺序

  1. 遍历非克隆事件(+208 = 0)。空闲事件加入待启动列表;活动事件需要克隆时加入另一列表。
  2. 只要任何活动原事件 CAN_CLONE=false,整个组立即退出;此前收集的空闲事件也不启动。
  3. 有克隆需求时,按组内顺序寻找空闲克隆。原版没有核对空闲克隆的 parent 是否等于待克隆原事件
  4. 每复用一个克隆,待克隆列表 index 0 用末项补位。这会影响随后真正克隆的顺序,不能改成 FIFO、排序或按 parent 匹配。
  5. 对剩余项调用克隆构造;全部选择完才开始启动。
  6. 每个非零实例先进入技能 +9C 活动向量,再调用 6CC710。该调用没有供分派器读取的成功返回值:即使事件体内部拒绝,分派器仍将它计作已调用,当前活动项也不在此删除。

例如待克隆原事件 [1,2,3],复用一个指向原事件 2 的空闲克隆后,剩余克隆顺序是 [3,2];不能自行“修正”为 [1,3]

hard-abort 顺序

如果 ACTIVATION_TYPE=4 或 requested animation != -1:先尝试 END,再 reset 所有事件组。

否则:先 reset 所有事件组,再尝试 END。

两条路径都遵守 END 锁存。尝试 END 时先读取 owner 朝向、位置(mode=1);即使没有 property 或没有可启动事件,只要进入这次尝试,之后也会置结束锁存。

reset 遍历的是全部 14 个属性事件组,包含空闲原事件和克隆,不只是当前活动层。之后依次释放 +1C、+24 的非空弱引用,清活动向量,写 elapsed=0、executing=false、speed=1、offset=ZERO。仅当重置前执行位为真且存在 manager owner 时,最后发 unit event 6。

这里有一个容易误实现的结果:无动画普通技能的 END 在全组 reset 之后才启动,随后清的是活动向量;不能再追加一轮全组 reset。反过来,有动画技能在 END 启动之后才进行全组 reset。

数据与接线

  • core_sim/NativeSkillEvents.cs:分组常量、默认 CAN_CLONE、组选择、hard-abort 控制及共享生命周期适配器。
  • WeaponProcCatalog.EventDefinitions:按原组序导出当前 16 个可达原版 proc 技能的 18 个事件,保留完整原始事件体与组内索引。
  • Stormclaw 保留两个独立 EVENT_START(首项 40% 武器伤害、第二项无额外武器伤害),没有补造 EVENT_END。
  • 14 类事件默认均可克隆,唯独 index 11 的 EVENT_CASTERDIE 默认不可克隆;DAT 的 CAN_CLONE 可覆盖。
  • Godot 隔离 SkillTickFixture 改用共享生命周期处理器,不再自行把 hard abort 简化为清数组。测试事件体仍明确为空,不冒充正式世界效果。
  • 本批没有新增持久字段,存档版本仍为 v48;新实例池和完整事件体状态尚待建立,不能先把现有实例当作可随意克隆。

按 godot-master 保持模拟与显示分离,所有回调都要求绑定工作副本;正式缺失消费者仍拒绝,不能让可视节点的消失决定技能执行状态。

验证

tools/native_skill_events_oracle.c 执行原 6C1C30 与 6C14A0,768 例匹配:原事件门条件、空闲克隆复用、克隆顺序、活动列表、14 组 reset、弱引用、锁存和通知顺序。事件克隆/启动/reset 的具体内容及世界变换/通知是明确的回调边界,不是原版世界执行。

14 个名字与 CAN_CLONE 默认值直接从原 EXE 读取对照。另覆盖整组拒绝、跨 parent 复用、内部拒绝仍登记、动画/无动画 hard-abort 差异、结束只触发一次、原 proc 事件数据完整性。

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

下一步与边界

继续实现 6CC710 启动和 6CD980 层更新中的真实布局/效果消费者,先沿原 Stormclaw 两事件路径组合验证,再扩展其他攻击/击杀 proc。还需事件实例与克隆的持久化、正常 NPC 活动生产者、受击反应宿主、仇恨与 Charged Bolt。

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

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