## 同调配装件:装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期——冒险域拆分姊妹批自共享充能账字典轨迁移)。 ## 同调充能单源化:物化行(treasure_inventory 内 source_class="attunement" 行)为使用面唯一事实源—— ## 剩余次数由行的未用数派生(唯一读法,无影子账);字典轨与三处事后差额对账退役,改「写时种行/补行」。 ## 种行/补行写时同步:冒险开始全量种行(不筛周期——battle/level 周期件开局即有行,C 区投影行数不变); ## battle/level 周期回满走 reset_cycle(已耗 handle 不回收=作废、差额补新——语义逐字承原编排器重置); ## 同调未用行跨关存活(关卡域库存提为冒险级共享、关末清空改选择性保留——单源化的行承载前提)。 ## 旧档读侧以行集为源恢复使用面,attunement_charges 键读值仅作一致性校验读数(不符记日志不 fatal——键值本就派生自行)。 ## Core 层零 UI;零随机(充能域无 RNG 消费);随机无关、数值只读 db。 class_name AttuneLoadout extends RefCounted var db: ConfigDb var _ids: Array[Variant] = [] # 装配集(活引用注入——宿主 start/resume 时点下传) func _init(db_ref: ConfigDb, attunement_ids: Array) -> void: db = db_ref set_attunement(attunement_ids) # 装配集注入(活引用——宿主 start_adventure/resume 参数面锁定后下传) func set_attunement(attunement_ids: Array) -> void: _ids = attunement_ids func item_ids() -> Array: return _ids.duplicate() # ---- 写时种行/补行(替代三处事后差额对账)---- # 冒险开始:全部 active_charged 件种行(**不筛周期**——承现 _seed_attune_charges 全量种账+sync 全量投行 # 语义;battle/level 周期件开局即有行,C 区投影行数不变) func seed_for_adventure(inventory: TreasureItems) -> void: for iid_v in _ids: var iid := String(iid_v) var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(iid) if String(arow.get("form", "")) != "active_charged": continue _ensure_rows(iid, maxi(0, int(arow.get("charges_per_adventure", 0))), inventory) # battle/level 周期回满(**按周期筛**;已耗行作废、差额补新——语义逐字承原编排器 _reseed_attune_charges; # charges_per_adventure=0=自动兑付件不入使用面〔试炼镐——不走骑库存面〕) func reset_cycle(cycle: String, inventory: TreasureItems) -> void: for iid_v in _ids: var iid := String(iid_v) var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(iid) if String(arow.get("form", "")) != "active_charged": continue if String(arow.get("charge_reset", "adventure")) != cycle: continue var want := int(arow.get("charges_per_adventure", 0)) if want < 1: continue # charges=0=自动兑付件(不走骑库存面,兑付=积点段自动 grant_ap) _ensure_rows(iid, want, inventory) # 差额补行单点(未用行数不足 want 即补——「差额注入防续玩双份」现役语义逐字) func _ensure_rows(iid: String, want: int, inventory: TreasureItems) -> void: var existing := 0 for r in inventory.snapshot_inventory(): if String(r.item_id) == iid and String(r.source_class) == "attunement" and not bool(r.used): existing += 1 for i in range(maxi(0, want - existing)): inventory.add_item(iid, "attunement", Vector2i(-1, -1), 0) # ---- 使用面(战斗路径改指)---- # 充能消耗(承原 attune_charge_consume「扣账 -1」语义——单源化后=行标 used 幂等承载: # 使用管线 consume 句柄已标行 used,本口兜底标未用行〔双入口同点防御——行 used 不可逆幂等〕) func consume_charge(item_id: String, inventory: TreasureItems) -> void: for r in inventory.snapshot_inventory(): if String(r.item_id) == item_id and String(r.source_class) == "attunement" and not bool(r.used): inventory.consume(int(r.handle)) # 行标 used(与使用管线 consume 同口——幂等兜底) return # ---- 读面(行派生——唯一读法)---- # 剩余次数(未用行数派生——原字典轨读数逐位等价) func charges_left(item_id: String, inventory: TreasureItems) -> int: var n := 0 for r in inventory.snapshot_inventory(): if String(r.item_id) == item_id and String(r.source_class) == "attunement" and not bool(r.used): n += 1 return n # ---- 断点自交片段(应用层拼盘口收件——键名与策略不变)---- # attunement_charges 键(值形状=原字典轨 {item_id:剩余}——由行派生,键名/策略不变) func breakpoint_fragment(inventory: TreasureItems) -> Dictionary[Variant, Variant]: var charges: Dictionary[Variant, Variant] = {} for iid_v in _ids: var iid := String(iid_v) var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(iid) if String(arow.get("form", "")) == "active_charged": charges[iid] = charges_left(iid, inventory) return {"attunement_charges": charges} # 旧档读侧(以行集为源恢复使用面——键值仅校验读数:不符记日志不 fatal;缺键保持种子态=原语义)。 # 调用时点=行集已随 aux_items 回填(恢复链序:库存回填先于本口)。 func restore_fragment(frag: Dictionary, inventory: TreasureItems) -> void: var stored: Variant = frag.get("attunement_charges", null) if not (stored is Dictionary) or (stored as Dictionary).is_empty(): return # 缺键 = 旧档/无断点,保持种子态(原语义) for iid in (stored as Dictionary).keys(): var derived := charges_left(String(iid), inventory) var recorded := int((stored as Dictionary)[iid]) if derived != recorded: # 跨版续玩兜底:改前档关卡完成停点的同调行已被关末清空(键在行缺)——按键值差额补行, # 使用面与改前字典轨续玩逐位等价;新档行集为源时差额=0 零操作(行集仍为读数唯一来源) push_warning("[同调配装] 充能校验读数与行派生不一致(%s 档=%d 行派生=%d——按键值差额补行)" % [String(iid), recorded, derived]) _ensure_rows(String(iid), recorded, inventory)