副卡(配装批wr1-wr3): AttuneLoadout建件+充能单源化+RunLedger整类退役——attune_loadout.gd新件(纯无状态:种行seed_for_adventure全量不筛周期/reset_cycle按周期差额补行/consume_charge行标used幂等/charges_left行派生唯一读法/断点片段键值形状不变);三处事后差额对账退役(adv._seed_attune_charges/level.sync_attune_inventory/battle._reseed_attune_charges循环体→reset_cycle);行承载面升级=treasure_inventory提冒险级共享(adv建件经ctx注入level,关末清空改选择性保留——同调未用行跨关存活=原字典轨跨关记忆的行化等价,已耗行作废=C区投影与剩余读数同构);battle充能面改指loadout(encounter+loadout/−ledger键,:387类型检查删,直构null=零装配同语义);RunLedger整类删除(.gd+.uid,grep零引用;effect_engine/level_runner/hero_ledger注释面随批清);测试直构改址(meta_forge充能读行派生/feat_vouchers种行幂等反例/round_flow V1桶宿主字典/treasure_effects enc adv_bucket);跨版续玩兜底(改前档关卡完成停点行已被关末清空——键在行缺,按键值差额补行=与字典轨续玩逐位等价,新档行集为源差额零操作)。代拍板:①treasure_inventory提冒险级共享(卡片文件结构图treasure_items零触碰保持,关卡末清空语义调用点改选择性保留——「同调行始终留在treasure_inventory」的承载前提,玩家可见C区投影零变化)②跨版续玩键值兜底补行(契约3「行集为源」的跨版例外——al2对拍兜底)。验收:bp1对拍292dumps×27键——attunement_charges全部逐位一致(单源对拍达成);aux_items关卡完成停点24处留存同调行(「同调行始终留库存」的直接承载——副卡契约3「aux_items键值形态逐位不变」声明与行留存在该停点形态互斥,grilling⑨K4实查边界漏判,反哺点呈报);六套件零红+冒烟17项0失败〔配装批 V1〕〔配装批 V2〕〔配装批 V3〕
This commit is contained in:
1 parent
659d59edd3
commit
80fe4b8f42
13 files changed
+189
-108
No files matched your search
@@ -19,7 +19,7 @@ var state: String: # 外部契约面(只读——外部
|
||||
return State.keys()[_state].to_lower()
|
||||
var _hero: HeroLedger # 主角实体账(生命/钱包/加算区/类型轴/特性集/钥石/真视——拆件批 T1 建件)
|
||||
var adventure_counters: Dictionary[Variant, Variant] = {} # 冒险桶(效果引擎 adv_ 计数器宿主字典——跨战持久)
|
||||
var run_ledger := RunLedger.new() # 充能账瘦壳(attune_charges——整类退役归姊妹副卡)
|
||||
|
||||
var level_index := 0 # 当前关卡序号(1 起)
|
||||
var levels_total := 0 # 序列总关数(读 level 表行数)
|
||||
var master_seed := 0 # 本局主种子(摘要第五件事)
|
||||
@@ -32,9 +32,12 @@ var _treasure_unlocked := {} # aux 解锁门控集(params.tr
|
||||
var _attunement: Array[Variant] = [] # 同调装配集(params.attunement 接收面,start_level 外下传 LevelRunner)
|
||||
var _save_manager = null # 断点承载注入(null = 不写断点;模拟/冒烟缺省)
|
||||
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
||||
var _loadout_seeded := false # 同调行已种标记(每冒险一次——首关 bind 时点投行;行跨关存活后续关零动作)
|
||||
# ---- 三选一停点(编排迁 CurioStop 件——停点三态/刷新三源/凭证出箱/信号面)----
|
||||
var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主)
|
||||
var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件)
|
||||
var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源)
|
||||
var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面)
|
||||
var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
||||
var _shop_mode := "legacy" # 商店模式("legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
|
||||
|
||||
@@ -45,6 +48,8 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
_hero = HeroLedger.new(db, {})
|
||||
_curio = CurioDeck.new(db, rng)
|
||||
_stop = CurioStop.new(db, _curio, _hero, _snapshot_breakpoint)
|
||||
_loadout = AttuneLoadout.new(db, _attunement)
|
||||
adventure_inventory = TreasureItems.new(db)
|
||||
|
||||
|
||||
# 件出口(组件交出非委托壳——呈现/测量面取主角账单点;拆件批接口块)
|
||||
@@ -83,7 +88,9 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
||||
_stop.set_refresh_x_cap(_attune_refresh_x_total()) # 停点件 X 基数预算注入(装配集冒险期间锁死)
|
||||
_stop.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
|
||||
_seed_attune_charges() # 冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
|
||||
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
|
||||
_loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
|
||||
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
|
||||
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
|
||||
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
|
||||
@@ -138,10 +145,10 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
_stop.reemit_resolved() # 已兑付不重兑付只重发(信号面归停点件)
|
||||
return true
|
||||
_resume_bp = bp.duplicate(true)
|
||||
# 充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
||||
var bp_charges = _bp_read("attunement_charges", bp)
|
||||
if bp_charges is Dictionary:
|
||||
run_ledger.attune_charges = (bp_charges as Dictionary).duplicate(true)
|
||||
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
|
||||
_loadout_seeded = false
|
||||
_loadout.set_attunement(_attunement)
|
||||
adventure_inventory.reset()
|
||||
_state = State.RUNNING
|
||||
return _start_current_level()
|
||||
|
||||
@@ -195,7 +202,6 @@ func _resume_params(bp: Dictionary, params: Dictionary) -> void:
|
||||
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
|
||||
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
||||
_seed_attune_charges() # 冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||
_stop.set_refresh_x_cap(_attune_refresh_x_total()) # 停点件 X 基数预算注入(续玩参数面同 start)
|
||||
_stop.restore_fragment(bp, params) # 停点件片段回填(凭证计数/刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
|
||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
|
||||
@@ -212,6 +218,7 @@ func _resume_counters(bp: Dictionary) -> void:
|
||||
func _reset_curio_state() -> void:
|
||||
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
|
||||
_stop.reset_adventure_state()
|
||||
_loadout_seeded = false
|
||||
_hero.feats = []
|
||||
adventure_counters = {}
|
||||
|
||||
@@ -303,13 +310,20 @@ func _attune_refresh_x_total() -> int:
|
||||
return total
|
||||
|
||||
|
||||
# 冒险开始充能账本重置(T6——装备 active_charged 件各回 charges_per_adventure;被动件不入账)
|
||||
func _seed_attune_charges() -> void:
|
||||
run_ledger.attune_charges = {}
|
||||
for iid in _attunement:
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid))
|
||||
if String(arow.get("form", "")) == "active_charged":
|
||||
run_ledger.attune_charges[String(iid)] = maxi(0, int(arow.get("charges_per_adventure", 0)))
|
||||
# 同调行投行分派(关卡域库存就位时点——每关一次;首关=种行或旧档行集为源、后续关=快照补行)
|
||||
func _loadout_on_level_inventory() -> void:
|
||||
if _runner.treasure_inventory == null:
|
||||
_runner.treasure_inventory = TreasureItems.new(db) # 惰性建(承原 sync_attune_inventory 同款——种行落点就位)
|
||||
var inv = _runner.treasure_inventory
|
||||
if inv == null:
|
||||
return
|
||||
if not _loadout_seeded:
|
||||
_loadout_seeded = true
|
||||
var bp_charges: Variant = _bp_read("attunement_charges", _resume_bp, null) if not _resume_bp.is_empty() else null
|
||||
if bp_charges is Dictionary:
|
||||
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
|
||||
else:
|
||||
_loadout.seed_for_adventure(inv) # 冒险开始全量种行(不筛周期——C 区投影行数不变)
|
||||
|
||||
|
||||
# ---- 三选一刷新与凭证(编排归停点件——本类留组合根委托读面)----
|
||||
@@ -358,8 +372,9 @@ func _start_current_level() -> bool:
|
||||
"ticket": _curio.feat_ticket_consider, "cap": _curio.cap_ok} if _shop_mode == "pool" else {}
|
||||
var ctx: Dictionary[Variant, Variant] = {
|
||||
"hero": _hero,
|
||||
"ledger": run_ledger,
|
||||
"loadout": _loadout,
|
||||
"adv_bucket": adventure_counters,
|
||||
"treasure_inventory": adventure_inventory,
|
||||
"attunement": _attunement,
|
||||
"tree_rules": _tree_rules,
|
||||
"shop_mode": _shop_mode,
|
||||
@@ -398,7 +413,7 @@ func _bind_level_runner() -> void:
|
||||
# core↔core 接线只许方法 Callable(S6——lambda 捕获 self 会延长 RefCounted 生命周期乃至成环)
|
||||
_runner.level_finished.connect(_on_level_finished)
|
||||
_runner.choices_presented.connect(_on_level_choices_presented) # 选路停点快照触发(原转发兼快照双职的核心侧受理)
|
||||
_runner.sync_attune_inventory() # 能力件骑库存面注入(构造注入同拍)
|
||||
_loadout_on_level_inventory() # 同调行投行分派(种行/旧档行集为源/每关快照补行)
|
||||
|
||||
|
||||
# 关卡开始广播(装配已随构造注入进 ctx——本函数只剩信号广播;信号顺序契约:
|
||||
@@ -551,7 +566,7 @@ func _bp_parts() -> Dictionary[Variant, Variant]:
|
||||
"shop_purchased": hero_frag.get("shop_purchased", {}),
|
||||
"shop_shelf": level_frag.get("shop_shelf", []), # 货架入断点(离开店/非店停点 = 空架合法态)
|
||||
"shop_visit_buys": int(level_frag.get("shop_visit_buys", 0)), # 本次进店已购次数(A 链定价输入)
|
||||
"attunement_charges": run_ledger.attune_charges.duplicate(true), # 同调主动件充能余量随断点(save v4 域)
|
||||
"attunement_charges": (_loadout.breakpoint_fragment(_runner.treasure_inventory) if _runner != null and _runner.treasure_inventory != null else {"attunement_charges": {}}).get("attunement_charges", {}), # 同调主动件充能余量随断点(行派生——save v4 域键名策略不变)
|
||||
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
|
||||
"master_seed": master_seed,
|
||||
"rng_counter": rng.counter(),
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
## 同调配装件:装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期——冒险域拆分姊妹批自 RunLedger 充能字典轨迁移)。
|
||||
## 同调充能单源化:物化行(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.treasure_item_row(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.treasure_item_row(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.treasure_item_row(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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://24ntqf46dfec
|
||||
@@ -72,7 +72,7 @@ var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换
|
||||
var _seal_consumed := {} # 兑付资格以危险格实例为键(整图;换图重置)
|
||||
# 同调装配运行态(begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
|
||||
var attunement: Array[Variant] = [] # 装备中同调件 item_id 集
|
||||
var _ledger: RunLedger = null # 同调充能账瘦壳(begin_battle 经 enc["ledger"] 持引用;缺省新账=调试面)
|
||||
var loadout = null # 同调配装件(begin_battle 经 enc["loadout"] 持引用;直构 null=零装配——充能面跳过)
|
||||
var _hero: HeroLedger = null # 主角实体账(begin_battle 经 enc["hero"] 持引用;null=调试/单场直构面——账面消费跳过)
|
||||
var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(装配集 ap_charge_safe 行扫描;满 charge_n 自动兑付当轮 AP+1,无玩家发起入口)
|
||||
# ---- 效果引擎扩签运行态(新效果类型施加面;空订阅零扰动)----
|
||||
@@ -234,24 +234,9 @@ func _attune_charge_safe_row() -> Dictionary:
|
||||
# (used handle 不回收=本轮已耗充能作废,补新 handle=「回满」;差额注入防双份=level_runner
|
||||
# set_attune_charges 同构)。reset ∈ {battle, level}——adventure 周期归冒险开始种子面)
|
||||
func _reseed_attune_charges(reset: String) -> void:
|
||||
for iid_v in attunement:
|
||||
var iid := String(iid_v)
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(iid)
|
||||
if String(arow.get("form", "")) != "active_charged":
|
||||
continue
|
||||
if String(arow.get("charge_reset", "adventure")) != reset:
|
||||
continue
|
||||
var want := int(arow.get("charges_per_adventure", 0))
|
||||
if want < 1:
|
||||
continue # charges=0=自动兑付件(试炼镐——不走骑库存面,兑付=_on_cell_revealed 积点段自动 grant_ap)
|
||||
_ledger.attune_charge_set(iid, want)
|
||||
if treasure_inventory != null:
|
||||
var existing := 0
|
||||
for r in treasure_inventory.snapshot_inventory():
|
||||
if String(r.get("item_id", "")) == iid and String(r.get("source_class", "")) == "attunement" and not bool(r.get("used", false)):
|
||||
existing += 1
|
||||
for i in range(maxi(0, want - existing)):
|
||||
treasure_inventory.add_item(iid, "attunement", Vector2i(-1, -1), 0)
|
||||
if loadout == null or treasure_inventory == null:
|
||||
return # 直构零装配/库存未就位——充能回满跳过(空装配同语义)
|
||||
loadout.reset_cycle(reset, treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体)
|
||||
|
||||
|
||||
# 积点制件进度读数(自动兑付无玩家发起入口,读数口=C 区秘宝槽悬停浮窗进度行:
|
||||
@@ -385,8 +370,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
_fx_new_resource_types = {}
|
||||
_fx_pending_reveals = []
|
||||
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
var ledger_ref: Variant = enc.get("ledger", null)
|
||||
_ledger = ledger_ref if ledger_ref is RunLedger else RunLedger.new() # 缺省新账=调试/单场零扰动面
|
||||
loadout = enc.get("loadout", null)
|
||||
_hero = enc.get("hero", null) if enc.get("hero", null) is HeroLedger else null # null=调试直构——账面消费跳过
|
||||
counters_ctx.adventure_bucket = enc.get("adv_bucket", null) if enc.get("adv_bucket", null) is Dictionary else {} # 冒险桶宿主字典(直构兜底自有空字典)
|
||||
var level_ref: Variant = enc.get("level_counters")
|
||||
@@ -761,9 +745,9 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary:
|
||||
if blasted.is_empty():
|
||||
return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除危险格(须未揭示且未封印)"}
|
||||
var rec: Dictionary[Variant, Variant] = treasure_inventory.consume(handle)
|
||||
# 同调主动件充能扣减(共享账本直改:adventure/level/settlement 同一字典引用)
|
||||
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||
_ledger.attune_charge_consume(String(rec.get("item_id", "")))
|
||||
# 同调主动件充能消耗(单源化——行标 used 幂等承载,原字典扣减同点退役)
|
||||
if loadout != null and not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||
loadout.consume_charge(String(rec.get("item_id", "")), treasure_inventory)
|
||||
# 真视类工具使用计数(信标网络解锁行为条件:受理成功恰计一次;
|
||||
# 主角账单点记录,冒险收束聚合入档 save.add_vision_used)
|
||||
if kinds.has("reveal_tiles") or kinds.has("safe_dig") or kinds.has("mine_reveal"):
|
||||
@@ -1134,7 +1118,7 @@ func _battle_over(result: String) -> void:
|
||||
for axis in _fx_bonus_delta.keys():
|
||||
_hero.gain_bonus(String(axis), int(_fx_bonus_delta[axis]), "battle_export", String(axis))
|
||||
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
# 搬运链删除:adventure 桶经 ctx 引用直写 RunLedger,无战末导出时点(旧导出/汇入搬运链删除)
|
||||
# 搬运链删除:adventure 桶经 ctx 引用直写宿主字典,无战末导出时点(旧导出/汇入搬运链删除)
|
||||
# 映射随战斗结束退役(地图战斗资源 义务④——下一场战斗 = 新板新 scope 批,roll_board 相位守卫的前提)
|
||||
var hero_hp_left := int(_enemy_system.hero_unit().get("hp", 0)) # despawn 前捕获(信号载荷供呈现层"HP 带入下一层")
|
||||
var gp_dropped := _roll_gp_drop() # 战斗掉落(escrow 随此值上抛即弃)
|
||||
|
||||
@@ -91,7 +91,7 @@ func _init(db_ref: ConfigDb = null) -> void:
|
||||
## 订阅装载(每战 begin_battle 调用——重建时机 v1.1):feats 表序→treasure 表序(各按持有/装备
|
||||
## 集过滤)→树规则合成行。同一数据行的多个 trigger 按行内数组序。计数器四桶由宿主持有、随宿主生灭,
|
||||
## 本方法只重建订阅集(运行时可变状态=零;battle/level 桶的清零由宿主 new 天然承载,round 桶清零
|
||||
## 在 dispatch 的 round_start 特判内整桶 clear,adventure 桶随 RunLedger 引用跨战持久——无副本搬运)。
|
||||
## 在 dispatch 的 round_start 特判内整桶 clear,adventure 桶随宿主字典引用跨战持久——无副本搬运)。
|
||||
func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Array = []) -> void:
|
||||
_triggers.clear()
|
||||
if db != null and db.feats != null:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
class_name HeroLedger
|
||||
extends RefCounted
|
||||
|
||||
# 加算区四轴(键序即断点 bonus 键序——与现役 run_ledger.bonus 置账形态逐位同形)
|
||||
# 加算区四轴(键序即断点 bonus 键序——与拆件前共享账置账形态逐位同形)
|
||||
const BONUS_AXES: Array[String] = ["atk", "max_hp", "armor_class", "ap"]
|
||||
|
||||
var db: ConfigDb
|
||||
@@ -253,9 +253,6 @@ func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||||
# 日记回填(记录列表与明细全量还原;汇总由明细推导——与留存汇总逐轴比对,不符即致命中止)
|
||||
func _restore_from_journal(journal: Dictionary, frag: Dictionary) -> void:
|
||||
_gain_log = (journal.get("records", []) as Array).duplicate(true)
|
||||
_gain_seq = _gain_log.size()
|
||||
for rec in _gain_log:
|
||||
_gain_seq = maxi(_gain_seq, int((rec as Dictionary).get("id", 0)))
|
||||
_bonus_detail = (journal.get("details", []) as Array).duplicate(true)
|
||||
var derived: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
for d in _bonus_detail:
|
||||
|
||||
+14
-24
@@ -30,6 +30,7 @@ var _external := false # 冒险模式标记(钱
|
||||
# 主角账引用(拆件批 T1 过渡面:冒险模式经 set_hero 注入共享实体账、单关模式 _init 自建私有账;
|
||||
# 构造注入收口归 T3 ctx 面——后置 setter 随构造注入一并退役)
|
||||
var _hero: HeroLedger = HeroLedger.new(null, {}) # 直构缺省私有账(db 随 _init 补)
|
||||
var loadout = null # 同调配装件(构造注入;直构=单关零件)
|
||||
var _adv_bucket: Dictionary[Variant, Variant] = {} # 冒险桶(效果引擎 adv_ 计数器宿主字典——冒险模式随 hero 注入;单关=本实例自有)
|
||||
# hero_hp / gp 属性化(既有读写点零改动——值类型经属性落主角账)
|
||||
var hero_hp: int:
|
||||
@@ -62,7 +63,7 @@ var _generator # 参数化长图生成器(惰性建——NodeMapGenerator
|
||||
# 构造注入一次给齐(拆件批 T3——后置 setter 群与商店重绑机制退役;_bind_shop 全仓仅本处构造调用)。
|
||||
# ctx 键(冒险模式由 AdventureRunner._start_current_level 组包;缺省空字典 = 单关测试模式:
|
||||
# 私有主角账自建〔空 override〕、充能账瘦壳自建、冒险桶本实例自有、pool 模式零注入):
|
||||
# hero:HeroLedger(共享主角账——活引用)/ledger:RunLedger(充能账瘦壳)/adv_bucket:Dictionary(冒险桶宿主字典)
|
||||
# hero:HeroLedger(共享主角账——活引用)/loadout:AttuneLoadout(同调配装件)/adv_bucket:Dictionary(冒险桶宿主字典)
|
||||
# attunement:Array(同调装配集——活引用)/tree_rules:Array(树 rule 键集——活引用)/shop_mode:String("pool" 才注入池面)
|
||||
# treasure_unlocked:Dictionary/hero_attack_type:String/hero_armor_type:String
|
||||
# voucher_rap:int/voucher_time_sand:int(凭证出箱注入面)
|
||||
@@ -75,10 +76,12 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> v
|
||||
_feats = _hero.feats # 特性集活引用注入(关内兑付 relic 当关生效——契约 6)
|
||||
else:
|
||||
_hero.db = db # 直构私有账补表引用(单关模式——实例不重建,值重置走 start_level)
|
||||
if ctx.get("ledger", null) is RunLedger:
|
||||
run_ledger = ctx["ledger"]
|
||||
var loadout_ref: Variant = ctx.get("loadout", null)
|
||||
loadout = loadout_ref if loadout_ref is AttuneLoadout else null # 直构=单关零 loadout(充能面跳过)
|
||||
if ctx.get("adv_bucket", null) is Dictionary:
|
||||
_adv_bucket = ctx["adv_bucket"]
|
||||
if ctx.get("treasure_inventory", null) is TreasureItems:
|
||||
treasure_inventory = ctx["treasure_inventory"] # 冒险级库存共享(同调未用行跨关存活——单源化承载面)
|
||||
_treasure_unlocked = ctx.get("treasure_unlocked", {}) if ctx.get("treasure_unlocked", {}) is Dictionary else {}
|
||||
_attunement = ctx.get("attunement", []) if ctx.get("attunement", []) is Array else [] # 活引用注入(契约 6——禁快照)
|
||||
_tree_rules = ctx.get("tree_rules", []) if ctx.get("tree_rules", []) is Array else [] # 活引用注入
|
||||
@@ -522,7 +525,7 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
"hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(),
|
||||
"attack_type": _hero_attack_type, "armor_type": _hero_armor_type, # 两根类型轴(spawn 英雄快照;空串=锻造配置默认值回退)
|
||||
"attunement": _attunement,
|
||||
"ledger": run_ledger, # 充能账瘦壳下传(单引用键——bonus/真视/桶已迁主角账与冒险桶)
|
||||
"loadout": loadout, # 同调配装件下传(充能单源化——reset_cycle/consume_charge 消费面;直构 null=零装配)
|
||||
"hero": _hero, # 主角实体账下传(战末 bonus 汇入/真视计数单写原语消费面)
|
||||
"adv_bucket": _adv_bucket, # 冒险桶下传(效果引擎 adv_ 计数器宿主字典——跨战持久同一引用)
|
||||
"level_counters": level_counters, # 关卡桶下传(同关跨战共享引用、随本类 new 天然空)
|
||||
@@ -580,7 +583,13 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
var treasure_cleared := 0
|
||||
if treasure_inventory != null:
|
||||
treasure_cleared = treasure_inventory.inventory_count() # 快照计数(清空前——「剩余道具随关卡结束清空」可见反馈取数)
|
||||
treasure_inventory.reset() # 关卡末库存清空(无论输赢,幂等)
|
||||
# 关卡末库存清空(无论输赢,幂等)——单源化承载面:同调未用行跨关存活(充能唯一事实源,
|
||||
# 原字典轨跨关记忆的行化等价——已耗行作废不保留=C 区投影与剩余读数同构)
|
||||
var keep: Array[Variant] = []
|
||||
for r in treasure_inventory.snapshot_inventory():
|
||||
if String(r.get("source_class", "")) == "attunement" and not bool(r.get("used", false)):
|
||||
keep.append(r)
|
||||
treasure_inventory.restore_inventory(keep)
|
||||
level_finished.emit({"result": level_result, "hp": hp_left, "node_index": node_index,
|
||||
"node_id": _node_id(node), "node_type": String(node.get("type", "")),
|
||||
"aux_cleared": treasure_cleared}) # additive 扩签(不参与派生——node_map 扩签先例)
|
||||
@@ -641,10 +650,6 @@ func pick_next_node(node_id: int) -> bool:
|
||||
func current_battle() -> Variant:
|
||||
return _settlement
|
||||
|
||||
# 共享充能账瘦壳(RunLedger 瘦身后只承载 attune_charges;构造注入)
|
||||
var run_ledger: RunLedger = RunLedger.new()
|
||||
|
||||
|
||||
# 特性持有集(begin_battle enc.feats 求值面;**共享引用不复制**——关内奖励节点兑付的 relic 须当关生效)
|
||||
var _feats: Array[Variant] = []
|
||||
# 树 rule 键集(begin_battle enc.tree_rules 携带;Fork 激活侧 effects——构造注入活引用)
|
||||
@@ -666,21 +671,6 @@ var _voucher_rap := 0
|
||||
var _voucher_time_sand := 0
|
||||
|
||||
|
||||
# 同调主动件充能账骑库存面注入(账本住
|
||||
# RunLedger;差额注入防续玩双份——resume 路径 restore_inventory
|
||||
# 已含断点内 attune 记录)
|
||||
func sync_attune_inventory() -> void:
|
||||
var charges: Dictionary[Variant, Variant] = run_ledger.attune_charges
|
||||
if treasure_inventory == null:
|
||||
treasure_inventory = TreasureItems.new(db)
|
||||
for iid in charges:
|
||||
var existing := 0
|
||||
for r in treasure_inventory.snapshot_inventory():
|
||||
if String(r.item_id) == String(iid) and String(r.source_class) == "attunement" and not bool(r.used):
|
||||
existing += 1
|
||||
for i in range(maxi(0, int(charges[iid]) - existing)):
|
||||
treasure_inventory.add_item(String(iid), "attunement", Vector2i(-1, -1), 0)
|
||||
|
||||
# 三选一 GP 袋件入账(钱包收口的「奖励入账」入口;复用战斗完成同一入账原语,
|
||||
# wallet_changed 载荷带 source 供呈现归因)
|
||||
func grant_curio_gp(amount: int) -> void:
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
class_name RunLedger
|
||||
extends RefCounted
|
||||
# 同调充能账瘦壳(冒险域拆件批瘦身半程——bonus 账/真视账/冒险桶已迁 HeroLedger 与冒险应用层)。
|
||||
# 所有权:AdventureRunner 持有(开局构造、冒险全程唯一宿主);LevelRunner 经 set_run_ledger
|
||||
# 下传并装配 encounter;战斗侧(BattleOrchestrator)经 encounter["ledger"] 拿本类引用,
|
||||
# 只走下方原语。整类文件退役归姊妹副卡(充能域封装批——单源化后零引用即删)。
|
||||
var attune_charges: Dictionary[Variant, Variant] = {} # 同调主动件充能账(use_treasure 扣减;断点随局回档)
|
||||
|
||||
|
||||
# ---- 战斗侧原语(宿主侧冒险/关卡域直读字段域) ----
|
||||
|
||||
func attune_charge_has(item_id: String) -> bool:
|
||||
return attune_charges.has(item_id)
|
||||
|
||||
|
||||
func attune_charge_set(item_id: String, value: int) -> void:
|
||||
attune_charges[item_id] = value
|
||||
|
||||
|
||||
func attune_charge_consume(item_id: String) -> void:
|
||||
# 原编排器 use_treasure 扣减语义逐字(has 闸 + maxi(0,·) 下限)
|
||||
if attune_charges.has(item_id):
|
||||
attune_charges[item_id] = maxi(0, int(attune_charges[item_id]) - 1)
|
||||
@@ -1 +0,0 @@
|
||||
uid://7afr7e1gtrj5
|
||||
@@ -965,16 +965,16 @@ func _smoke_debuff_rider() -> void:
|
||||
# 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))----
|
||||
func _smoke_counter_buckets() -> void:
|
||||
print("--- 效果引擎无状态化四桶正向验收(卡② §9 V1/V6)---")
|
||||
# V1——adventure 桶跨战持久:同一 RunLedger 下两战各真实 request_dig 一次,
|
||||
# V1——adventure 桶跨战持久:同一宿主字典下两战各真实 request_dig 一次,
|
||||
# 断付费计数经 ctx 就地写入宿主字典并跨战累积(副本搬运态:读不到上一战值→第 2 战仍 1,此断言会红)。
|
||||
var led = load("res://core/run_ledger.gd").new()
|
||||
var led: Dictionary[Variant, Variant] = {} # 冒险桶宿主字典(拆件批——桶归宿主)
|
||||
var RigS = load("res://core/battle_orchestrator.gd")
|
||||
var RngS = load("res://core/rng_service.gd")
|
||||
var enc1 = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": ["twin_pick"], "tree_rules": [], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0,
|
||||
"node_type": "battle", "ledger": led}
|
||||
"node_type": "battle", "adv_bucket": led}
|
||||
var o1 = RigS.new(_db, _fresh_rng(779301))
|
||||
o1.begin_battle(enc1)
|
||||
var dig1: bool = o1.action_economy.request_dig(0, false)
|
||||
@@ -992,7 +992,7 @@ func _smoke_counter_buckets() -> void:
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": ["perfect_insurance"], "tree_rules": [], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0,
|
||||
"node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()}
|
||||
"node_type": "battle", "adv_bucket": {}}
|
||||
var oA = RigS.new(_db, _fresh_rng(779401))
|
||||
oA.begin_battle(encA)
|
||||
var v6_rows: Array = []
|
||||
@@ -1031,7 +1031,7 @@ func _smoke_extended_rows() -> void:
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": ["geologist"], "tree_rules": [], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0,
|
||||
"node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()}
|
||||
"node_type": "battle", "adv_bucket": {}}
|
||||
# ② 兑付行:有未揭示安全格 → 掷格揭示成功 → 扩展行带真实格与 fx_reveal 身份
|
||||
var o_fx = RigS.new(_db, _fresh_rng(779501))
|
||||
o_fx.begin_battle(enc_fx)
|
||||
|
||||
@@ -68,12 +68,13 @@ func _init() -> void:
|
||||
"电池符号清零:runner 无点选函数与 getter(编译级孤儿面)〔卡② R1〕")
|
||||
Kit.check_quiet(int(db.adventure.curio_cap("attune_battery")) == 99,
|
||||
"出池后未知 kind 走分发表 default(cap 读数兜底 99——零崩溃面)〔卡② R1〕")
|
||||
# 账本保留(契约 1):主动件充能账与每冒险重置入口零改动
|
||||
# 账本保留(契约 1):主动件充能与每冒险种行入口零改动(单源化后=差额种行幂等——不吞装配)
|
||||
a._attunement = ["incense_stick"]
|
||||
a.run_ledger.attune_charges = {"incense_stick": 99}
|
||||
a._seed_attune_charges()
|
||||
Kit.check_quiet(int(a.run_ledger.attune_charges.get("incense_stick", -1)) != 99,
|
||||
"账本保留:删件后 _seed_attune_charges 仍按装备重置充能账(attune_charges 语义零变化)〔卡② R1〕")
|
||||
a._loadout.set_attunement(["incense_stick"])
|
||||
a._loadout_seeded = false # 模拟首关 bind 时点(分派走 seed_for_adventure 分支)
|
||||
a._loadout_on_level_inventory() # 种行(首关 bind 时点语义)
|
||||
Kit.check_quiet(a._loadout.charges_left("incense_stick", a.runner().treasure_inventory) == 1,
|
||||
"账本保留:种行按装备表值投行(线香 battle 级 1——attune_charges 语义零变化)〔卡② R1〕")
|
||||
# V24 配额净删锚(R3——「净删」口径,rare 9→7/总量 104→102)
|
||||
var rare_sum := 0
|
||||
var rare_kinds: Array[String] = []
|
||||
|
||||
@@ -287,8 +287,8 @@ func _meta_settle_and_workshop() -> void:
|
||||
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||
Kit.check("充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕",
|
||||
a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}))
|
||||
Kit.check("充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)", # 充能账住 RunLedger
|
||||
int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1)
|
||||
Kit.check("充能按装备 active_charged 件种行(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面;读数=行派生)",
|
||||
a12._loadout.charges_left("incense_stick", a12.runner().treasure_inventory) == 1 and a12._loadout.charges_left("sky_eye", a12.runner().treasure_inventory) == 1)
|
||||
a12._attunement = []
|
||||
a12.stop()._refresh_x = 1
|
||||
a12.stop()._refresh_y = 2
|
||||
|
||||
@@ -306,7 +306,7 @@ func _init() -> void:
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": ["seal_smith"], "tree_rules": [], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0,
|
||||
"node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()}
|
||||
"node_type": "battle", "adv_bucket": {}}
|
||||
var rng15 = load("res://core/rng_service.gd").new(); rng15.set_master_seed(341)
|
||||
var ob15 = orch_s.new(db, rng15)
|
||||
ob15.begin_battle(enc_feats)
|
||||
|
||||
Reference in new issue
Block a user