## 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:)随快照还原 # 令牌池余库还原(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)同走此口; # 公开化(5.1 封装修复——宿主读侧同口,_bp_read 更名非并存) 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")) # ---- 断点域六成员公开读写面(5.1 封装修复——宿主私取清零:方法体一律一行转发自有成员,零逻辑增量)---- # 断点承载注入(宿主构造后接线——save_manager 后绑) func bind_save_manager(sm: SaveManagerCore) -> void: _save_manager = sm # 断点承载读投影(可 null——模拟/冒烟缺省态) func save_manager() -> Variant: return _save_manager func set_pending_next_level(v: bool) -> void: _pending_next_level = v func pending_next_level() -> bool: return _pending_next_level func set_loadout_seeded(v: bool) -> void: _loadout_seeded = v func loadout_seeded() -> bool: return _loadout_seeded func set_resume_bp(bp: Dictionary) -> void: _resume_bp = bp func clear_resume_bp() -> void: _resume_bp = {} # 续玩快照读(返引用零拷贝——宿主读侧只读) func resume_bp() -> Dictionary: return _resume_bp func set_last_level_payload(payload: Dictionary) -> void: _last_level_payload = payload func last_level_payload() -> Dictionary: return _last_level_payload func set_last_treasure_cleared(v: int) -> void: _last_treasure_cleared = v func last_treasure_cleared() -> int: return _last_treasure_cleared