技能管理器执行、克隆复用与私有随机数(v40)

2026-09-20。本批接入管理器的执行实例选择与活动顺序,拆开施法状态/冷却状态,并保存技能私有种子的来源。已有击杀回血与受击回血使用新路径。完整攻击型 proc 事件消费者仍待接入,TYPE201/204 没有提前开放。

构造审计保持:1419 个定义,1099 可构造、273 明确阻塞、47 缺基础定义/创建等级、0 构造异常。全武器目标未完成。

原版调用链与状态

来源:原 Torchlight2.exe,SHA256 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e。只读 IDA 捕获和独立机器码参考,不 attach 原游戏、不修改 EXE/PAK。

入口本批使用的事实
6D6FA0按指针执行;空指针/全局禁止/CANPROC 门;exclusive abort;活动指针转名字查找;reseed 后 start;成功才追加活动项和消耗 charges
6D6CD0名字表里选第一个不在活动表的实例;全忙则克隆最后一个,再尝试启动
6D69D0克隆共享原定义,拷贝源 charges 等字段;临时克隆不进入基础列表,但进入名字索引
6D61F0退休后只有名字已不在索引才排入删除;不能因不在基础列表就删除克隆
6BE7A0保存请求种子;请求为零才用 GetTickCount 初始化实际 RNG
6C3A30–6C3A60 / 676D90启动 CHANCE 的无符号检查与技能自身 MWC 流
6C1C30 / 6D7370每次活动项 tick 扣 float 冷却;仍在施法或冷却为正都可留在活动表;移除采用末项补位

活动表 +52/count+56 与 CSkill+64 的施法标记不是一个状态。冷却可以已负但布局仍执行;这时重入会找另一个空闲实例或克隆。临时克隆结束后仍留在名字索引,下次可复用,而不是无限创建又立刻销毁。

EXCLUSIVE 比较技能定义的 UNIQUE_GUID。abortHard 清除施法状态/事件,不立即等同于从管理器活动表移除;指针入口和名字入口各自的 exclusive 扫描顺序保留。外部事件中止副作用要求消费者暂存处理,不能只删一条计时器。

charges 保留原 6D3B80 规则:成功后读取同名第一个实例的 charges;仅当它为正时,给所有同名实例写 first-1。零 charges 不在这个执行入口阻止 proc;手动技能使用门是另一层。

私有种子公式

6D6FA0/6D6CD0 在启动前计算:

requested = (manager_seed64 + (has_target ? target_runtime_guid64 : 0)
             + sign_extend_i32(salt)) mod 2^64
effective = requested != 0 ? requested : zero_extend_u32(captured_GetTickCount)
  • requested 写 CSkill+472/+476;effective 写 +456/+460,必须分开保存。
  • 没有目标指针时忽略 target GUID;有目标但 GUID 未知时不能擅自填零。
  • salt 按有符号 32 位扩展,包含负数、跨高低字进位和 64 位回绕。
  • 零种子的外部 TickCount 由宿主捕获后注入,纯核心不读墙钟。
  • target_runtime_guid 不是 DAT 的定义 GUID,也不是 CoreSim 的整数单位 ID。

实际运行对象的 GUID 来自 CEditorBaseObject/EditorMakeGuid 路径;已有 NativeEditorGuid 内核以原 UUID 字节和 synced 流计算,但当前战斗对象生产方尚未完整接入该身份。因此不能据此宣称所有攻击型 proc 的技能私有随机数已经自动可用。

已有固定回血程序没有私有游戏随机数消费者,未捕获的两个种子字段继续保存为 null;没有伪造值。非 effect-only 程序必须提供种子和事件运行消费者,否则明确拒绝。

两层概率不混用

外层武器 proc 的概率(582380 等)即使 100% 也要掷骰,沿用已有 ProcEffects 规则。

技能内部启动 CHANCE(6C3A40)是另一层:

if uint32(CHANCE) >= 100: pass without a draw
else: roll skill_MWC[0, 100]; pass iff roll <= CHANCE

所以 CHANCE 负整数在这里转无符号后走无抽样成功分支;0 仍会抽一次且可能 roll0 成功。不能用统一的 RollPercent 快捷逻辑覆盖两层。

启动失败也必须返回变更后的技能状态:CHANCE 已消耗的私有随机数不回退,但不加入活动表、不扣 charges。NativeProcStartAttempt 用独立 Started 标志表达这一点;不再以 null 丢弃失败前状态。

实现与存档

  • NativeProcExecution:实例选择、克隆/复用、exclusive 的暂存回调、私有 reseed、启动结果与活动顺序维护。
  • NativeProcSkills:已有 effect-only 开始/推进改用新路径;BeginEffectOnly 返回真正启动的实例 ID,效果绑定不再默认指向最初请求的那个实例。
  • F2WeaponProcs.ExecutionDefinition:读取原层级上的 CANPROC、EXCLUSIVE、CHANCE、冷却和 UNIQUE_GUID;CANPROC 不等于 CAN_PROC_FROM_SKILL。
  • schema 40:ActiveOrder、Executing、InBaseList、InitialSeed、EffectOnly、Available 与已有 EffectRng 一起保存;数组在复制/恢复时脱离原存储。
  • v39 以前正式实现只会在每个回血技能管理器里保留最多一个活动实例,可无歧义恢复该顺序。任意旧多活动实例缺顺序时不按注册顺序猜测,显式报告 legacy_proc_active_order_unknown。
  • 冷却为 float 并保留负数过冲;当前 effect-only start 事件结束与计时结束分开,临时克隆仍在名字集合中。

验证范围

  • 341 组原版管理器参考:真实执行 6D6FA0、6D6CD0、6BE180、6BE1A0、6BE7A0。覆盖不同活动顺序、空闲/全忙、克隆、模式、概率前门、exclusive、成功/失败、charges 与种子边界。
  • 名字树、内存分配/克隆构造、level setter、abort 的外部事件、完整 CSkill start 和 charges 更新是明确 stub 边界;并未把它们伪称为机器码全链路实测。
  • 另 54 组执行原 6C3A30 概率片段及原 676D90 MWC,比较成功标志和私有流状态。片段前后的完整事件启动流程不在这个参考内。
  • 1698 条核心断言通过;包含克隆退休后复用、施法跨过冷却、末项补位、概率失败保留 RNG、数组隔离和 v40 字节稳定重放。
  • 349 项相关 Python 回归通过;C# 构建 0 warning / 0 error,coresimlint 通过。
  • 实际 ECHO PASS headless 主场景:GREATHAMMER_U03 原击杀回血,seed43;0.1 秒后 HP 100→137.760009765625,逐步恢复、到期和重放通过。
  • 同期新跑 SWORD_U03 原受击回血,seed25;原怪物攻击先使 HP 100→94,0.1 秒后到 131.760009765625,回归通过。

两条场景都使用既有隔离 HP/等级/种子夹具。没有新增视觉资源;headless 结果不当作视觉一致性证据。

产物:build/native_proc_execution_oracle/results.json;build/proc_execution_core.log、proc_execution_regression.log、proc_execution_world.log、proc_execution_struck_world.log、proc_execution_audit.log。

剩余工作与安全

这里完成的是管理器执行/活动状态和私有 RNG 边界,不是完整 CSkill 生命周期。仍需:真实运行 GUID 的产生/恢复接线、CSkill start 的有效等级/施法上下文、事件实例创建与 tick/end/abort、施法者/目标死亡与所有者变更策略、实际世界查询,以及攻击/召唤型事件消费者。

下一批继续上述运行输入和事件消费者,优先让 Stormclaw 真正从原武器命中触发;不能以这批管理器参考代替它的完整世界验收。

按 godot-master 使用定义/状态/显示分层;没有套通用技能示例的墙钟冷却或额外全局冷却。

在 main 工作,未提交。默认 warp_autosave.json SHA256 保持 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f;测试只写隔离槽。全武器目标仍在继续。