角色词缀实例与唯一子效果所有权:v43

2026-09-20。本批把词缀 / 子效果实例放进角色权威状态,建立唯一 ID 引用、续期视图、解绑和父清理事务,并接入完整模拟快照。正常武器 HIT 尚未创建这些实例,实际 canApply / apply / register 与两阶段列表仍待绑定,未放宽武器准入。

数据与所有权

CombatUnit.AffixBook 持有 NativeAffixBookSnapshot:

  • Affixes 保留父实例顺序、有效等级、来源身份、剩余时间及子效果 ID 顺序。
  • Effects 是唯一的子效果登记表。父 children 和四张列表只保存 ID,不各自复制一份时钟。
  • Lists[0..2] 分别对应 PASSIVE / DYNAMIC / TRANSFER;Lists[3] 对应惰性旁路表。
  • AllocatedByAffix 表示内存 / 实例归属;OwnerAffix 表示原 +0xD0 的可清除回指,二者不能混为一谈。
  • DefinitionOrdinal 与 CatalogId、父 NAME 一起关联原始冻结 DAT。快照不重复嵌入完整资源定义。
  • GUID2 保存为十进制字符串,保持 64 位精度;图标 intern ID 单独保存。
  • 稳定 ID 是迁移实现中的引用,不冒充原版内存地址或 UNIQUE_GUID。

NativeAffixInstances.Run 在工作副本和暂存 RNG 上修改,成功 Capture / Validate 后才发布。输入、返回状态、五槽 / 曲线数组和外部读取副本均脱离;事务结束后不可继续通过保留的工作引用修改。

SimState.EditAffixBook 提供角色级发布入口,检查已有目录身份。复制 / 保存 / 恢复与致死工作副本都保留该字段。v42 及更早没有实例数据时保持 null,不构造假实例。

真实 DAT 构造接入

PreparedElementalAffix.OwnershipSeed 输出独立的所有权种子。保存父级的有效等级,不是未夹紧的请求等级;例如请求 2000,对这批原词缀实际为 999。

先分配整个子效果向量,再进行逐项操作。不能只创建当前正分派的子项,否则重入的续期会漏掉后面的子效果。

原 7B21E0 明确区分:

  • 若 effect 已属于当前 manager 的 affix,直接链接原对象;
  • 否则才 CopyCtor 创建副本。

因此这里不能把每个已由 affix 持有的子效果再次复制到活动池。测试中的 FROZEN 拥有五个 child:三个被动数值、一个主题、一个 ADD STAT;活动列表引用前四个,ADD STAT 不被错误地当作每帧执行行。

种子导入仅建立所有权,不等于重放了之前的派发回调。 正式创建还必须在原始 setOwnerAffix / canApply / apply / register 的位置接入这些对象。Link 当前是列表成员操作,不冒充完整 EXCLUSIVE / onLinked / UI 分组处理器。

续期与清理

续期通过 NativeAffixLifetime 的视图读写同一个登记表:

  • 写父剩余时间;
  • 更新未显式指定时长的子效果 duration;
  • 全部 child elapsed 清零;
  • 保留过期位,不自动重新链接已摘除的子效果。

父到期沿上一批 NativeAffixCleanup 的顺序清理。本批依据原 7A48C0、7A8D90、7AEBC0、7A5070 核对并实现:

  1. 若子效果来源非空,先通知来源单位移除;不是把接收者当作来源。
  2. 清除子效果 OwnerAffix 回指。
  3. setOwnerAffix(null) 会重新求值,即使效果早已不在活动列表里;保留这一步 RNG 消耗。
  4. 从列表摘除首个对应引用,采用末项补位。旁路表的路径不调用普通 onRemoved / 描述失效回调。
  5. 全部 child 先完成 detach,再逐个调用销毁宿主并删除登记。
  6. 移除父实例,数值重算和描述缓存处理仍沿原父清理内核。

带来源技能的私有 RNG 清理仍显式拒绝;没有擅自改用全局 RNG。与外部系统有关的来源通知、onRemoved、粒子 / 技能销毁和重算由必需回调提供;测试回调只观察这些边界,不宣称真实粒子或 proc 已完成解绑

失败测试在已经重新掷值后故意让销毁回调抛错,确认调用者的实例簿和 RNG 都不变。回调自己的外部副作用仍要求调用者放进工作事务,不能通过此接口自动撤销任意外部写入。

四元素显示身份的正确来源

566DD0 查询的是 SKILL 注册表,并从相应数据组读取 UNIQUE_GUID,不是 Combo STAT 或 UNITTHEME。

标记原版 SKILL GUIDSKILL_ICON
EFFECT_FREEZE8410566014256113120effect_freeze
EFFECT_BURN-600680373144864542effect_burn
EFFECT_POISON-3837733024961805086effect_poison
EFFECT_SHOCK-76290341261347009effect_shock

新增 tools/build_elemental_effect_markers.py 与 F2ElementalEffectMarkers,输出 build/f2/elemental_effect_markers.json,包含四份 MEDIA/SKILLS/EFFECT_*.DAT 的来源和 SHA256。

GUID2、图标标志与 NAME 分开:设置显示标记不会把 COMBOICE / PROC CHARGEDBOLT 名称覆盖掉。Godot 检查使用原版 FREEZE GUID;图标 intern ID=12 是明确的测试输入,实际全局字符串登记和图标显示尚未绑定

保护边界

非空 AffixBook 的正式更新 / 攻击暂时分别报:

  • native_affix_frame_binding_pending
  • native_affix_combat_binding_pending

这样尚未接入消费者的实例不会被忽略后仍返回“成功伤害”。现有已就绪武器没有此类未绑定实例,不受此保护影响。后续接入真正列表 / 处理器后才能移除此门,不能只凭保存成功开放武器。

本批快照 v43 仍是完整模拟状态 / 重放格式,不宣称复制原版按 SAVES 字段选择 .SVS 内容的策略。

验证

  • 1892 条核心断言通过:唯一引用、源数组隔离、真实 DAT 续期、有效等级、标记身份、解绑顺序、RNG 回滚、孤立 / 悬空引用拒绝及 v43 恢复。
  • 420 项相关 Python 回归通过。
  • Godot C# 构建零警告 / 零错误;coresimlint 无硬错。
  • 独立 headless Godot 检查扩展至 16 项:角色统计与 affix 图经过实际 Godot JSON 往返;父清理与恢复重放一致。
  • ECHO PASS 混合回复 / DoT / 控制回归通过,死亡通知一次,XP / 击杀统计及列表补位正确,日志无 ERROR / SCRIPT ERROR。该测试仍是原有已表示通路,不是元素 HIT 验收。
  • 本批没有新增“完整析构机器码对照”声明;父清理的 384 组原对照属于上一批,当前新增的是所有权 / 事务接线测试和原始指令顺序核对。

主要产物:

  • core_sim/NativeAffixInstances.cs
  • core_sim.io/F2ElementalEffectMarkers.cs
  • build/f2/elemental_effect_markers.json
  • build/native_affix_instances_core.log
  • build/native_affix_instances_regression.log
  • build/native_affix_instances_world.log
  • build/native_named_stat_engine/result.json

下一段是把实例登记接进正常命中后的词缀创建、原派发顺序及两阶段更新;随后完成真实主题 / proc / 粒子回收与元素武器场景验证。

当前武器审计仍为 1099 / 1419 可构造,273 阻塞、47 基础缺口、0 构造异常。遵循 godot-master 的数据 / 状态 / 表现分离,未把保存层完成当作战斗复原完成。

main 原地工作,未提交,未 attach 原游戏。默认存档 SHA256 未变: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f