iter45-卡②T2: 断点域拆件 breakpoint_store(R2整族归件v1.1)——六成员(save_manager/resume_bp/pending_next_level/last_level_payload/last_treasure_cleared/loadout_seeded)+BREAKPOINT_FIELDS 27键冻结+函数族九个归件:纯读侧三函数与_bp_parts/snapshot body逐字(宿主输入state/level_index/master_seed/runner/counters逐调传参——件禁反向持宿主),四_resume_*按所有权缝语句分家:件侧(主角账三件restore_fragment/curio池与特性还原/stop与bank片段回填/数值账规范化钳制)迁store五个回填函数,宿主侧(八项重绑定与_reset_curio_state/_attune_refresh_x_total调用)按原语句序吸收进resume_adventure壳,引用束九引用解包为原名成员保body逐字(6+9=15贴顶);宿主留_snapshot_breakpoint一行委托壳(CurioStop的Callable目标零改)与set_save_manager转发件,断点键面零变化,语句序逐步保形;测试:185改址BreakpointStoreScript.BREAKPOINT_FIELDS(判据零变化——第14节申报)〔补件 V2〕;验证:run_all三套件全绿+run_smoke 15项0失败
This commit is contained in:
1 parent
96ec80aa97
commit
6b5a96b6c7
5 files changed
+277
-232
No files matched your search
+55
-231
@@ -27,19 +27,14 @@ var levels_total := 0 # 序列总关数(读 level 表行
|
||||
var master_seed := 0 # 本局主种子(摘要第五件事)
|
||||
var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成)
|
||||
var _runner: LevelRunner # 当前关卡行进器(每关新建——关卡域状态天然隔离)
|
||||
var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认
|
||||
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
|
||||
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
|
||||
var _treasure_unlocked := {} # aux 解锁门控集(params.treasure_unlocked 接收面)
|
||||
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 _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源账与付费报价;宿主预算注入与断点片段直收)
|
||||
var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源)
|
||||
var _bp_store: BreakpointStore # 断点组装与回放件(_init 建;六成员+27 键登记单源+读写侧机组随件——宿主读写经件)
|
||||
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)
|
||||
@@ -57,6 +52,8 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
_snapshot_breakpoint)
|
||||
_loadout = AttuneLoadout.new(db, _attunement)
|
||||
adventure_inventory = TreasureItems.new(db)
|
||||
_bp_store = BreakpointStore.new({"db": db, "rng": rng, "stats": _stats, "wallet": _wallet,
|
||||
"journal": _journal, "curio": _curio, "stop": _stop, "bank": _refresh_bank, "loadout": _loadout})
|
||||
|
||||
|
||||
# 件出口(组件交出非委托壳——呈现/测量面取主角账三件与令牌池单点;领域拆分批接口块)
|
||||
@@ -107,7 +104,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_reset_curio_state()
|
||||
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
||||
level_index = 1
|
||||
_pending_next_level = false
|
||||
_bp_store._pending_next_level = false
|
||||
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动)
|
||||
_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
|
||||
@@ -115,7 +112,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
|
||||
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
|
||||
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
|
||||
_loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
|
||||
_bp_store._loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
|
||||
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
|
||||
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
|
||||
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
|
||||
@@ -152,99 +149,50 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
return false
|
||||
master_seed = int(bp.get("master_seed", 0))
|
||||
rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0)))
|
||||
_resume_ledger(bp, params)
|
||||
_resume_curio_state(bp)
|
||||
_resume_params(bp, params)
|
||||
_resume_counters(bp)
|
||||
# ---- 断点回放(R2 整族归件——件侧语句入 _bp_store、宿主侧语句按原语句序留本壳,序逐步保形)----
|
||||
_bp_store.restore_ledger(bp, params) # 续玩账面回填——主角账三件片段各还原各键(件侧;clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
|
||||
summary = {}
|
||||
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
||||
level_index = int(bp.get("level_index", 1))
|
||||
_bp_store._pending_next_level = false
|
||||
_reset_curio_state() # 续玩三选一态回填前置清理(池与件计数归件、停点态归本类;桶归宿主字典)
|
||||
_bp_store.restore_curio_state(bp) # 续玩三选一态回填(池余库/获取计数/特性持有集——件侧;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
|
||||
_bp_store.apply_resume_params(params) # 续玩参数面——数值账规范化与越界钳制(件侧)
|
||||
_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
|
||||
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
|
||||
_bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面)
|
||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
|
||||
# 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
|
||||
var bp_counters = _bp_store._bp_read("adventure_counters", bp)
|
||||
if bp_counters is Dictionary:
|
||||
adventure_counters = (bp_counters as Dictionary).duplicate(true)
|
||||
var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary
|
||||
var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
|
||||
if not bp_pending.is_empty():
|
||||
_pending_next_level = true
|
||||
_bp_store._pending_next_level = true
|
||||
_state = State.RUNNING
|
||||
_stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继)
|
||||
return true
|
||||
if not bp_result.is_empty():
|
||||
_pending_next_level = true
|
||||
_bp_store._pending_next_level = true
|
||||
_stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件)
|
||||
_state = State.RUNNING
|
||||
return true
|
||||
_resume_bp = bp.duplicate(true)
|
||||
_bp_store._resume_bp = bp.duplicate(true)
|
||||
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
|
||||
_loadout_seeded = false
|
||||
_bp_store._loadout_seeded = false
|
||||
_loadout.set_attunement(_attunement)
|
||||
adventure_inventory.reset()
|
||||
_state = State.RUNNING
|
||||
return _start_current_level()
|
||||
|
||||
|
||||
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
|
||||
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
|
||||
func _resume_ledger(bp: Dictionary, params: Dictionary) -> void:
|
||||
_stats.restore_fragment(bp, params)
|
||||
_wallet.restore_fragment(bp, params)
|
||||
_journal.restore_fragment(bp, params)
|
||||
summary = {}
|
||||
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
||||
level_index = int(bp.get("level_index", 1))
|
||||
_pending_next_level = false
|
||||
|
||||
|
||||
# 续玩三选一态回填(池与件计数归件、停点态归本类;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
|
||||
func _resume_curio_state(bp: Dictionary) -> void:
|
||||
_reset_curio_state()
|
||||
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
|
||||
for id in _curio.taken.keys():
|
||||
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
||||
for id in bp_taken.keys():
|
||||
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
|
||||
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
||||
# relic 令牌 feat_id 随还原)
|
||||
var bp_deck = _bp_read("bonus_deck", bp)
|
||||
if bp_deck is Array:
|
||||
var restored: Array[Variant] = []
|
||||
for t in (bp_deck as Array):
|
||||
var tok: Dictionary[Variant, Variant] = t as Dictionary
|
||||
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
|
||||
if String(rtok["kind"]) == "relic":
|
||||
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
|
||||
restored.append(rtok)
|
||||
_curio.deck = restored
|
||||
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
|
||||
_stats.feats = []
|
||||
for r in (bp.get("relics", []) as Array):
|
||||
_stats.feats.append(String(r))
|
||||
|
||||
# 续玩参数面回填(同 start_adventure 参数面;生命账本越界钳制在此落——clamp 类)
|
||||
func _resume_params(bp: Dictionary, params: Dictionary) -> void:
|
||||
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
|
||||
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
|
||||
_stats.armor_type = String(params.get("armor_type", ""))
|
||||
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
|
||||
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
|
||||
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
|
||||
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
|
||||
_stats.clamp_hp_to_resume_cap(int(db.combat.hero.max_hp))
|
||||
_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
|
||||
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
|
||||
_stop.restore_fragment(bp, params) # 停点件片段回填(凭证计数)
|
||||
_refresh_bank.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 下传)
|
||||
|
||||
|
||||
# 续玩计数器回填(冒险域计数——凭证计数/刷新存量/Y 池随停点件片段回填)
|
||||
func _resume_counters(bp: Dictionary) -> void:
|
||||
# adventure 域计数器回填(缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
|
||||
var bp_counters = _bp_read("adventure_counters", bp)
|
||||
if bp_counters is Dictionary:
|
||||
adventure_counters = (bp_counters as Dictionary).duplicate(true)
|
||||
|
||||
|
||||
func _reset_curio_state() -> void:
|
||||
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
|
||||
_stop.reset_adventure_state()
|
||||
_loadout_seeded = false
|
||||
_bp_store._loadout_seeded = false
|
||||
_stats.feats = []
|
||||
adventure_counters = {}
|
||||
|
||||
@@ -263,7 +211,7 @@ func effective_max_hp() -> int:
|
||||
|
||||
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
|
||||
func set_save_manager(sm: SaveManagerCore) -> void:
|
||||
_save_manager = sm
|
||||
_bp_store._save_manager = sm
|
||||
|
||||
|
||||
# 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取)
|
||||
@@ -278,13 +226,13 @@ func apply_rest(pct: int, hp: int, max_hp: int) -> int:
|
||||
|
||||
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
|
||||
func continue_to_next_level() -> bool:
|
||||
if not _pending_next_level:
|
||||
if not _bp_store._pending_next_level:
|
||||
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
|
||||
return false
|
||||
if _stop.curio_stop_pending():
|
||||
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
|
||||
return false
|
||||
_pending_next_level = false
|
||||
_bp_store._pending_next_level = false
|
||||
_stop.clear_resolved()
|
||||
level_index += 1
|
||||
return _start_current_level()
|
||||
@@ -343,9 +291,9 @@ func _loadout_on_level_inventory() -> void:
|
||||
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 not _bp_store._loadout_seeded:
|
||||
_bp_store._loadout_seeded = true
|
||||
var bp_charges: Variant = _bp_store._bp_read("attunement_charges", _bp_store._resume_bp, null) if not _bp_store._resume_bp.is_empty() else null
|
||||
if bp_charges is Dictionary:
|
||||
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
|
||||
else:
|
||||
@@ -368,8 +316,8 @@ func _loadout_on_level_inventory() -> void:
|
||||
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
|
||||
func _boss_keystone_drop(li: int) -> void:
|
||||
var first := true
|
||||
if _save_manager != null:
|
||||
first = not _save_manager.meta_state.career_state().boss_first_kill.has(str(li))
|
||||
if _bp_store._save_manager != null:
|
||||
first = not _bp_store._save_manager.meta_state.career_state().boss_first_kill.has(str(li))
|
||||
var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li)
|
||||
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
|
||||
_journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
|
||||
@@ -419,9 +367,9 @@ func _start_current_level() -> bool:
|
||||
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
|
||||
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
|
||||
var resume_state: Dictionary[Variant, Variant] = {}
|
||||
if not _resume_bp.is_empty():
|
||||
resume_state = {"node_id": int(_bp_read("node_id", _resume_bp, -1)),
|
||||
"visited_ids": _bp_read("visited_ids", _resume_bp, []) as Array}
|
||||
if not _bp_store._resume_bp.is_empty():
|
||||
resume_state = {"node_id": int(_bp_store._bp_read("node_id", _bp_store._resume_bp, -1)),
|
||||
"visited_ids": _bp_store._bp_read("visited_ids", _bp_store._resume_bp, []) as Array}
|
||||
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
|
||||
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
|
||||
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
|
||||
@@ -431,7 +379,7 @@ func _start_current_level() -> bool:
|
||||
if ok:
|
||||
_emit_level_started()
|
||||
_present_level_entry(resume_state)
|
||||
_resume_bp = {}
|
||||
_bp_store._resume_bp = {}
|
||||
return ok
|
||||
|
||||
|
||||
@@ -455,7 +403,7 @@ func _present_level_entry(resume_state: Dictionary) -> void:
|
||||
if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0:
|
||||
_runner.present_entry_choice()
|
||||
else:
|
||||
_runner.restore_treasure_items(_bp_read("aux_items", _resume_bp, []) as Array)
|
||||
_runner.restore_treasure_items(_bp_store._bp_read("aux_items", _bp_store._resume_bp, []) as Array)
|
||||
_runner.present_resumed_choice()
|
||||
|
||||
|
||||
@@ -464,7 +412,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
if state != "running":
|
||||
push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state)
|
||||
return
|
||||
_last_level_payload = payload.duplicate(true)
|
||||
_bp_store._last_level_payload = payload.duplicate(true)
|
||||
var result := String(payload.get("result", ""))
|
||||
if result == "defeat":
|
||||
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
|
||||
@@ -473,7 +421,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
if result != "victory":
|
||||
push_error("[冒险结构] 关卡结束结果出域: %s" % result)
|
||||
return
|
||||
_last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面)
|
||||
_bp_store._last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面)
|
||||
# Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone)
|
||||
_boss_keystone_drop(level_index)
|
||||
if level_index < levels_total:
|
||||
@@ -481,7 +429,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
|
||||
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
|
||||
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
|
||||
_pending_next_level = true
|
||||
_bp_store._pending_next_level = true
|
||||
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
|
||||
stop_base["gp"] = _wallet.gp
|
||||
_stop.open_level_clear(stop_base)
|
||||
@@ -499,7 +447,7 @@ func _settle(result: String) -> void:
|
||||
"stopped_at": 0 if result == "completed" else level_index,
|
||||
"keystone_total": _journal.keystone_gained(),
|
||||
"master_seed": master_seed,
|
||||
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
|
||||
"aux_cleared_last": _bp_store._last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
|
||||
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
||||
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
|
||||
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
|
||||
@@ -507,19 +455,19 @@ func _settle(result: String) -> void:
|
||||
}
|
||||
# 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源;
|
||||
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
|
||||
if result == "failed" and not _last_level_payload.is_empty():
|
||||
if result == "failed" and not _bp_store._last_level_payload.is_empty():
|
||||
summary["defeat"] = {
|
||||
"level_index": level_index,
|
||||
"node_id": int(_last_level_payload.get("node_id", -1)),
|
||||
"node_type": String(_last_level_payload.get("node_type", "")),
|
||||
"node_id": int(_bp_store._last_level_payload.get("node_id", -1)),
|
||||
"node_type": String(_bp_store._last_level_payload.get("node_type", "")),
|
||||
}
|
||||
if _runner != null:
|
||||
summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点)
|
||||
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
|
||||
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
||||
if _save_manager != null:
|
||||
_save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
|
||||
_save_manager.write_to_disk()
|
||||
if _bp_store._save_manager != null:
|
||||
_bp_store._save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
|
||||
_bp_store._save_manager.write_to_disk()
|
||||
_state = State.SETTLING
|
||||
adventure_finished.emit(summary)
|
||||
_state = State.IDLE
|
||||
@@ -530,131 +478,7 @@ func _on_level_choices_presented(_candidates: Array) -> void:
|
||||
_snapshot_breakpoint()
|
||||
|
||||
|
||||
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
|
||||
# 键序即快照键序(27 顶层键——写侧/读侧/键集断言三面同源)。读侧回退策略词表:
|
||||
# plain 无特殊回退面(缺键取代码内缺省)/clamp 数值夹 ≥0/keep_on_null 缺键或类型不符保持既有态
|
||||
# dual_merge 双向合并(bonus_taken)/params_fallback 缺键回退参数面(attune_refresh_y)
|
||||
# token_migration 旧版候选形态迁移(bonus_pending.candidates)
|
||||
# 注入缺省 null = 整面静默(模拟/冒烟零扰动)。
|
||||
const BREAKPOINT_FIELDS := {
|
||||
"level_index": {"strategy": "plain"},
|
||||
"node_id": {"strategy": "plain"},
|
||||
"hero_hp": {"strategy": "clamp"},
|
||||
"bonus": {"strategy": "clamp"},
|
||||
"gp": {"strategy": "plain"},
|
||||
"shop_purchased": {"strategy": "plain"},
|
||||
"shop_shelf": {"strategy": "plain"},
|
||||
"shop_visit_buys": {"strategy": "plain"},
|
||||
"attunement_charges": {"strategy": "keep_on_null"},
|
||||
"graph_gen_v2": {"strategy": "plain"},
|
||||
"master_seed": {"strategy": "plain"},
|
||||
"rng_counter": {"strategy": "plain"},
|
||||
"keystone": {"strategy": "plain"},
|
||||
"visited_ids": {"strategy": "plain"},
|
||||
"aux_items": {"strategy": "plain"},
|
||||
"bonus_taken": {"strategy": "dual_merge"},
|
||||
"bonus_deck": {"strategy": "keep_on_null"},
|
||||
"relics": {"strategy": "plain"},
|
||||
"bonus_pending": {"strategy": "token_migration"},
|
||||
"feat_ticket_pity": {"strategy": "plain"},
|
||||
"feat_ticket_sold": {"strategy": "plain"},
|
||||
"adventure_counters": {"strategy": "keep_on_null"},
|
||||
"vision_counter_used": {"strategy": "clamp"},
|
||||
"refresh_stock": {"strategy": "clamp"},
|
||||
"attune_refresh_y": {"strategy": "params_fallback"},
|
||||
"bonus_result": {"strategy": "plain"},
|
||||
"keystone_drops": {"strategy": "plain"},
|
||||
"gain_journal": {"strategy": "plain"}, # 获得日记(拆件批 T4 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
|
||||
}
|
||||
|
||||
|
||||
# ---- 断点快照委托(组装与读侧策略机组归 breakpoint_store 件——六成员+27 键登记单源随件;
|
||||
# 本壳一行转发:CurioStop 构造注入的 Callable 目标与本类 _on_level_choices_presented 调用点零改)----
|
||||
func _snapshot_breakpoint() -> void:
|
||||
if _save_manager == null or state != "running":
|
||||
return
|
||||
var parts: Dictionary[Variant, Variant] = _bp_parts()
|
||||
var bp: Dictionary[Variant, Variant] = {}
|
||||
for key in BREAKPOINT_FIELDS.keys():
|
||||
bp[key] = parts[key]
|
||||
_save_manager.set_breakpoint(bp)
|
||||
_save_manager.write_to_disk()
|
||||
|
||||
|
||||
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
|
||||
func _bp_parts() -> Dictionary[Variant, Variant]:
|
||||
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
|
||||
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
|
||||
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
|
||||
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
|
||||
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
|
||||
var level_frag: Dictionary[Variant, Variant] = _runner.breakpoint_fragment() if _runner != null else {
|
||||
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
|
||||
"shop_shelf": [] as Array, "shop_visit_buys": 0} # 三选一停点续玩分支 _runner 为 null——节点域按缺省落档
|
||||
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照)
|
||||
var parts: Dictionary[Variant, Variant] = {
|
||||
"level_index": level_index,
|
||||
"node_id": int(level_frag.get("node_id", -1)),
|
||||
"hero_hp": int(stats_frag.get("hero_hp", 0)),
|
||||
"bonus": journal_frag.get("bonus", {}),
|
||||
"gp": int(wallet_frag.get("gp", 0)),
|
||||
"shop_purchased": journal_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": (_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(),
|
||||
"keystone": int(journal_frag.get("keystone", 0)),
|
||||
"visited_ids": level_frag.get("visited_ids", []),
|
||||
"aux_items": level_frag.get("aux_items", []),
|
||||
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
||||
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
||||
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
|
||||
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
||||
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
|
||||
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
|
||||
"adventure_counters": adventure_counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
|
||||
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
|
||||
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
|
||||
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
|
||||
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
|
||||
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
|
||||
}
|
||||
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
|
||||
var stray: Array[Variant] = []
|
||||
for key in parts.keys():
|
||||
if not BREAKPOINT_FIELDS.has(key):
|
||||
stray.append(key)
|
||||
if not stray.is_empty():
|
||||
push_error("[冒险结构] 断点拼盘含未登记键: %s(fail-fast——片段与登记单源失同步)" % str(stray))
|
||||
return parts
|
||||
|
||||
|
||||
|
||||
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
|
||||
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
|
||||
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
|
||||
if not BREAKPOINT_FIELDS.has(key):
|
||||
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
|
||||
var v: Variant = bp.get(key, fallback)
|
||||
if _bp_strategy(key) == "keep_on_null" and v != null and not (v is Dictionary or v is Array):
|
||||
return null # 类型不符——保持既有态(调用方跳过覆盖)
|
||||
return v
|
||||
|
||||
|
||||
# 读侧策略标签(BREAKPOINT_FIELDS 读侧消费面单点)
|
||||
func _bp_strategy(key: String) -> String:
|
||||
return String((BREAKPOINT_FIELDS.get(key, {}) as Dictionary).get("strategy", "plain"))
|
||||
|
||||
|
||||
# 数值类读侧(clamp 夹 ≥0 / params_fallback 断点缺键回退参数面整额注入 / plain 直取)
|
||||
func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary = {}) -> int:
|
||||
match _bp_strategy(key):
|
||||
"clamp":
|
||||
return maxi(0, int(bp.get(key, fallback)))
|
||||
"params_fallback":
|
||||
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
|
||||
_:
|
||||
return int(bp.get(key, fallback))
|
||||
|
||||
|
||||
_bp_store.snapshot(state, level_index, master_seed, _runner, adventure_counters)
|
||||
@@ -0,0 +1,218 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 存档断点组装与回放唯一承载(AdventureRunner 拆件——单一变化原因=断点快照键面与回放策略):
|
||||
## 断点快照拼盘、回放回填、读侧策略分发;BREAKPOINT_FIELDS 27 键登记单源随件(键名/策略标签/键序冻结)。
|
||||
## 构造=引用束字典注入(db/rng/主角账三件/curio/stop/bank/loadout——件禁反向持宿主);
|
||||
## 快照宿主输入(state/level_index/master_seed/runner/adventure_counters)逐调传参;
|
||||
## resume_adventure 留宿主(壳+宿主侧回填语句——所有权缝分家,语句序逐步保形)。
|
||||
class_name BreakpointStore
|
||||
extends RefCounted
|
||||
|
||||
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
|
||||
# 键序即快照键序(27 顶层键——写侧/读侧/键集断言三面同源)。读侧回退策略词表:
|
||||
# plain 无特殊回退面(缺键取代码内缺省)/clamp 数值夹 ≥0/keep_on_null 缺键或类型不符保持既有态
|
||||
# dual_merge 双向合并(bonus_taken)/params_fallback 缺键回退参数面(attune_refresh_y)
|
||||
# token_migration 旧版候选形态迁移(bonus_pending.candidates)
|
||||
# 注入缺省 null = 整面静默(模拟/冒烟零扰动)。
|
||||
const BREAKPOINT_FIELDS := {
|
||||
"level_index": {"strategy": "plain"},
|
||||
"node_id": {"strategy": "plain"},
|
||||
"hero_hp": {"strategy": "clamp"},
|
||||
"bonus": {"strategy": "clamp"},
|
||||
"gp": {"strategy": "plain"},
|
||||
"shop_purchased": {"strategy": "plain"},
|
||||
"shop_shelf": {"strategy": "plain"},
|
||||
"shop_visit_buys": {"strategy": "plain"},
|
||||
"attunement_charges": {"strategy": "keep_on_null"},
|
||||
"graph_gen_v2": {"strategy": "plain"},
|
||||
"master_seed": {"strategy": "plain"},
|
||||
"rng_counter": {"strategy": "plain"},
|
||||
"keystone": {"strategy": "plain"},
|
||||
"visited_ids": {"strategy": "plain"},
|
||||
"aux_items": {"strategy": "plain"},
|
||||
"bonus_taken": {"strategy": "dual_merge"},
|
||||
"bonus_deck": {"strategy": "keep_on_null"},
|
||||
"relics": {"strategy": "plain"},
|
||||
"bonus_pending": {"strategy": "token_migration"},
|
||||
"feat_ticket_pity": {"strategy": "plain"},
|
||||
"feat_ticket_sold": {"strategy": "plain"},
|
||||
"adventure_counters": {"strategy": "keep_on_null"},
|
||||
"vision_counter_used": {"strategy": "clamp"},
|
||||
"refresh_stock": {"strategy": "clamp"},
|
||||
"attune_refresh_y": {"strategy": "params_fallback"},
|
||||
"bonus_result": {"strategy": "plain"},
|
||||
"keystone_drops": {"strategy": "plain"},
|
||||
"gain_journal": {"strategy": "plain"}, # 获得日记(拆件批 T4 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
|
||||
}
|
||||
|
||||
# 引用束(宿主构造注入——成员名与宿主侧原名一致,迁移函数体逐字)
|
||||
var db: ConfigDb
|
||||
var rng: RngServiceCore
|
||||
var _stats: HeroStats
|
||||
var _wallet: GpWallet
|
||||
var _journal: GainJournal
|
||||
var _curio: CurioDeck = null # 令牌池件(池余库/获取计数随断点)
|
||||
var _stop: CurioStop = null # 停点件(停点态/凭证计数片段)
|
||||
var _refresh_bank: RefreshBank = null # 刷新账件(刷新存量/Y 池片段)
|
||||
var _loadout: AttuneLoadout = null # 同调配装件(充能行集读侧)
|
||||
# 断点域六成员(自宿主收编——宿主其余读写点经本件)
|
||||
var _save_manager = null # 断点承载注入(null = 不写断点;模拟/冒烟缺省)
|
||||
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
||||
var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认
|
||||
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
|
||||
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
|
||||
var _loadout_seeded := false # 同调行已种标记(每冒险一次——首关 bind 时点投行;行跨关存活后续关零动作)
|
||||
|
||||
|
||||
func _init(refs: Dictionary) -> void:
|
||||
db = refs["db"]
|
||||
rng = refs["rng"]
|
||||
_stats = refs["stats"]
|
||||
_wallet = refs["wallet"]
|
||||
_journal = refs["journal"]
|
||||
_curio = refs["curio"]
|
||||
_stop = refs["stop"]
|
||||
_refresh_bank = refs["bank"]
|
||||
_loadout = refs["loadout"]
|
||||
|
||||
|
||||
# 断点快照拼盘落盘(宿主 _snapshot_breakpoint 委托目标——宿主输入逐调传参)
|
||||
func snapshot(state: String, level_index: int, master_seed: int, runner: LevelRunner, counters: Dictionary[Variant, Variant]) -> void:
|
||||
if _save_manager == null or state != "running":
|
||||
return
|
||||
var parts: Dictionary[Variant, Variant] = _bp_parts(level_index, master_seed, runner, counters)
|
||||
var bp: Dictionary[Variant, Variant] = {}
|
||||
for key in BREAKPOINT_FIELDS.keys():
|
||||
bp[key] = parts[key]
|
||||
_save_manager.set_breakpoint(bp)
|
||||
_save_manager.write_to_disk()
|
||||
|
||||
|
||||
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
|
||||
func _bp_parts(level_index: int, master_seed: int, runner: LevelRunner, counters: Dictionary[Variant, Variant]) -> Dictionary[Variant, Variant]:
|
||||
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
|
||||
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
|
||||
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
|
||||
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
|
||||
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
|
||||
var level_frag: Dictionary[Variant, Variant] = runner.breakpoint_fragment() if runner != null else {
|
||||
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
|
||||
"shop_shelf": [] as Array, "shop_visit_buys": 0} # 三选一停点续玩分支 runner 为 null——节点域按缺省落档
|
||||
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照)
|
||||
var parts: Dictionary[Variant, Variant] = {
|
||||
"level_index": level_index,
|
||||
"node_id": int(level_frag.get("node_id", -1)),
|
||||
"hero_hp": int(stats_frag.get("hero_hp", 0)),
|
||||
"bonus": journal_frag.get("bonus", {}),
|
||||
"gp": int(wallet_frag.get("gp", 0)),
|
||||
"shop_purchased": journal_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": (_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(),
|
||||
"keystone": int(journal_frag.get("keystone", 0)),
|
||||
"visited_ids": level_frag.get("visited_ids", []),
|
||||
"aux_items": level_frag.get("aux_items", []),
|
||||
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
||||
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
||||
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
|
||||
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
||||
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
|
||||
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
|
||||
"adventure_counters": counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
|
||||
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
|
||||
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
|
||||
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
|
||||
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
|
||||
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
|
||||
}
|
||||
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
|
||||
var stray: Array[Variant] = []
|
||||
for key in parts.keys():
|
||||
if not BREAKPOINT_FIELDS.has(key):
|
||||
stray.append(key)
|
||||
if not stray.is_empty():
|
||||
push_error("[冒险结构] 断点拼盘含未登记键: %s(fail-fast——片段与登记单源失同步)" % str(stray))
|
||||
return parts
|
||||
|
||||
|
||||
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
|
||||
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
|
||||
#(宿主侧账面语句——summary/levels_total/level_index/_pending_next_level——按语句序留宿主壳)
|
||||
func restore_ledger(bp: Dictionary, params: Dictionary) -> void:
|
||||
_stats.restore_fragment(bp, params)
|
||||
_wallet.restore_fragment(bp, params)
|
||||
_journal.restore_fragment(bp, params)
|
||||
|
||||
|
||||
# 续玩三选一态回填(池与件计数归件、停点态归宿主;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
|
||||
#(前置清理 _reset_curio_state 留宿主——桶归宿主字典;本函数承接其后的件侧还原语句,序逐步保形)
|
||||
func restore_curio_state(bp: Dictionary) -> void:
|
||||
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
|
||||
for id in _curio.taken.keys():
|
||||
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
||||
for id in bp_taken.keys():
|
||||
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
|
||||
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
||||
# relic 令牌 feat_id 随还原)
|
||||
var bp_deck = _bp_read("bonus_deck", bp)
|
||||
if bp_deck is Array:
|
||||
var restored: Array[Variant] = []
|
||||
for t in (bp_deck as Array):
|
||||
var tok: Dictionary[Variant, Variant] = t as Dictionary
|
||||
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
|
||||
if String(rtok["kind"]) == "relic":
|
||||
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
|
||||
restored.append(rtok)
|
||||
_curio.deck = restored
|
||||
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
|
||||
_stats.feats = []
|
||||
for r in (bp.get("relics", []) as Array):
|
||||
_stats.feats.append(String(r))
|
||||
|
||||
|
||||
# 续玩参数面——数值账规范化与越界钳制(件侧;宿主侧四重绑定与预算注入按语句序夹在两件侧调用之间)
|
||||
func apply_resume_params(params: Dictionary) -> void:
|
||||
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
|
||||
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
|
||||
_stats.armor_type = String(params.get("armor_type", ""))
|
||||
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
|
||||
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
|
||||
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
|
||||
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
|
||||
_stats.clamp_hp_to_resume_cap(int(db.combat.hero.max_hp))
|
||||
|
||||
|
||||
# 停点件/刷新账片段回填(凭证计数;刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
|
||||
func restore_stop_bank_fragments(bp: Dictionary, params: Dictionary) -> void:
|
||||
_stop.restore_fragment(bp, params)
|
||||
_refresh_bank.restore_fragment(bp, params)
|
||||
|
||||
|
||||
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
|
||||
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
|
||||
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
|
||||
if not BREAKPOINT_FIELDS.has(key):
|
||||
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
|
||||
var v: Variant = bp.get(key, fallback)
|
||||
if _bp_strategy(key) == "keep_on_null" and v != null and not (v is Dictionary or v is Array):
|
||||
return null # 类型不符——保持既有态(调用方跳过覆盖)
|
||||
return v
|
||||
|
||||
|
||||
# 读侧策略标签(BREAKPOINT_FIELDS 读侧消费面单点)
|
||||
func _bp_strategy(key: String) -> String:
|
||||
return String((BREAKPOINT_FIELDS.get(key, {}) as Dictionary).get("strategy", "plain"))
|
||||
|
||||
|
||||
# 数值类读侧(clamp 夹 ≥0 / params_fallback 断点缺键回退参数面整额注入 / plain 直取)
|
||||
func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary = {}) -> int:
|
||||
match _bp_strategy(key):
|
||||
"clamp":
|
||||
return maxi(0, int(bp.get(key, fallback)))
|
||||
"params_fallback":
|
||||
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
|
||||
_:
|
||||
return int(bp.get(key, fallback))
|
||||
@@ -0,0 +1 @@
|
||||
uid://cw5qdd012h728
|
||||
@@ -0,0 +1 @@
|
||||
uid://dojreaughx7tf
|
||||
@@ -8,6 +8,7 @@ extends SceneTree
|
||||
|
||||
const SaveManagerScript := preload("res://core/save_manager.gd")
|
||||
const AdventureRunnerScript := preload("res://core/adventure_runner.gd")
|
||||
const BreakpointStoreScript := preload("res://core/breakpoint_store.gd")
|
||||
const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:check/汇总退出样板单点(静默收集通道)
|
||||
|
||||
var _bp_path := "user://test_breakpoint_resume.json"
|
||||
@@ -182,7 +183,7 @@ func _init() -> void:
|
||||
"P7 同种子重开局同图(开局入口复现承载)〔v7〕")
|
||||
|
||||
# ---- Part 8:断点字段登记单源(登记键集 == 写侧实拼字典键集;键名增删即红)----
|
||||
var fields: Dictionary = AdventureRunnerScript.BREAKPOINT_FIELDS
|
||||
var fields: Dictionary = BreakpointStoreScript.BREAKPOINT_FIELDS # 断点域拆件改址——登记单源随件(判据零变化)
|
||||
var reg_keys: Array = fields.keys()
|
||||
reg_keys.sort()
|
||||
var bp_keys: Array = bp_j.keys()
|
||||
|
||||
Reference in new issue
Block a user