原版技能更新调度与软结束消费(v48)
本批完成 CSkill::tick(0x6C1C30)的时间与阶段调度,接入现有 proc 管理器、统一战斗帧和保存/重放。没有把它当成完整的技能效果执行器,也没有开放尚未完成的元素武器。
原版证据
原文件:E:/Torchlight 2/Torchlight2.exe,SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e。
只读核对原游戏 IDA 库,未 attach 正在运行的游戏。导出位于项目的 build/research_weapon_families/:
- 006c1c30.c / .asm:技能更新和结束阶段。
- 006c14a0.c / .asm:hard abort,具有自己的事件与清理,不能等同于清空数组。
- 006caf80.c / .asm:活动事件层停止入口。
- 006dbdb0.c / .asm:结束事件需要克隆时的构造入口。
- 006dc320.c / .asm:属性字段与 DAT 键的对应。
- 006be830.c / .asm:上一批软结束请求写入 +F8 = 0.001。
时间计算和严格顺序
- owner 缺失时先调用 hard abort;之后才扣冷却。
cooldown(+F0) = float(cooldown - delta),不受技能倍率影响;未执行技能也扣减。- 仅在执行位 +40 为真时,令
q = double(speed(+EC)) * delta。 elapsed(+FC) = float(elapsed + q)。- 非 CONTINUOUSLOOPING 时,
endTimer(+F8) = float(endTimer - q);循环技能保留该倒计时,但 elapsed 和 cooldown 仍推进。 - +56 为真时向活动层传原 delta,否则传
float(q)。该标志在代码中称 UnscaledLayers,不冒认一个未核实的 DAT 键。 - 按 +9C 活动向量实时顺序更新层。层返回 false:先停止,再用最后一项补位,在同帧更新补位项。回调若已清空整个向量则立即返回。
- 更新完层之后才判断结束。
保留原 x87 的中间乘积到目标 float 写回,不先把 q 舍入成 float 再加减。当前持久状态要求有限数值;不声称支持被破坏的 NaN 状态。
普通结束门
全部满足才进入结束阶段:
- 不是“仍有层、DURATIONOVERRIDEMS <= 0、requested animation == -1”的组合;
- endTimer <= 0;
- ACTIVATION_TYPE != 4;
- elapsed >= DURATIONOVERRIDEMS 换算后的秒值。
因此请求 +F8=0.001 并不保证下一帧必定结束。零倍率、循环技能、时长覆盖和层状态均可能改变结果。这里比较的 +BC 是 DURATIONOVERRIDEMS,不是 +B8 的 MINIMUMTIMEMS。
最大时长
MAXDURATIONALLOWED > 0 && elapsed > MAXDURATIONALLOWED 强制进入结束阶段,即使普通结束门未通过、或 ACTIVATION_TYPE 为 4。
比较是严格大于;恰好相等不触发。先按 index 0/末项补位顺序停止所有层,再处理结束事件。
结束锁存与活动表
存在 owner 且 +41 未锁存时尝试 EVENT_END。尝试之后写 +41=true;若启动了至少一个结束层,本帧不 hard abort,新层留到下一帧更新。否则进入 hard abort。
后续 tick 不重复触发 EVENT_END。技能已经停止执行但冷却仍为正时,管理器仍保留活动成员;两者均结束才移出活动表,名字登记仍保留供复用。
实现与接线
core_sim/NativeSkillTick.cs:上述调度、快照时钟、工作副本回调接口。NativeProcExecution.Tick:带 Clock 的技能走原更新调度;冷却只扣一次。旧 effect-only 恢复技能保留旧路径。RequestSoftStop/SimState.RequestItemSkillSoftStop:将真实层观测对应到指定物品管理器的实例,按原门写结束计时。不把角色管理器 ID 与装备管理器 ID 混用。SimState.ConfigureProcSkillRuntime:消费者绑定到工作副本;AdvanceCombatEffects经过整帧事务后再提交,失败回滚 HP、RNG、计时与层状态。- 没有稳定 item identity 的运行时钟显式拒绝,避免事务只提交角色变化却漏掉物品技能状态。
- v48 保存 Clock、结束锁存和活动层顺序;复制与恢复不共享数组。旧版本没有 Clock 时保持未知,不补造。
- 完整生命周期实例的克隆构造尚未接通时显式拒绝,不把正在运行的层数组复制给新实例。
遵循 godot-master 的模拟/显示分离;没有让 Godot 动画或 UI 决定技能结束,也未添加墙钟驱动或通用冷却规则替代原行为。
验证范围
tools/native_skill_tick_oracle.c 执行原 0x6C1C30 机器码,1024 个用例全部匹配计时 bit pattern、层顺序、回调顺序和结束锁存。实际执行原空/单个未激活 EVENT_END 的选择流程。
明确的输入边界:layer tick/stop/start、owner pose、hard abort 是测试提供的回调。此对照不是完整事件体、世界特效、原版 NPC 实战或所有 END 克隆分支的验证。
另外覆盖:软结束到 END 层、次帧清理、冷却保留、最大时长严格边界、无动画层等待、循环计时、深复制、真实 item 快照、失败整帧回滚及恢复后的重放。Godot 隔离探针同样走统一战斗帧;不读写默认玩家存档。
最终验证:2077 条核心断言、445 项相关 Python 回归、Godot 44 项检查通过;C# 构建零警告/零错误,193 个核心文件静态检查无硬错。旧元素正式数值场景与四元素 GPU 探针重新运行通过。
产物:build/skill_tick_*.log、build/native_skill_tick_oracle/results.json、build/native_named_stat_engine/result.json。
尚未完成
- 正常 NPC 的真实技能和层活动生产者,以及受击反应宿主绑定。
INativeSkillTickEvents的完整原版层更新、EVENT_END 复用/克隆/启动与 hard-abort 世界消费者。当前隔离探针提供明确的测试消费者;正式未知路径仍拒绝。- 完整生命周期的实例克隆构造、跨管理器重入及效果产生新单位等事务扩展。
- 仇恨、Charged Bolt 和逐武器实战/表现验收。
构造覆盖保持 1099 / 1419,273 阻塞、47 数据/等级缺口、0 构造异常。这不是全武器完成率。
main 原地工作,未提交。默认存档 SHA256 保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f。