From 6fd7d782cc6da4c0f6d88d4bc98e1a97f90983e0 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 14 Oct 2026 23:25:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E8=BF=AD=E4=BB=A327=20T3):=20?= =?UTF-8?q?=E6=B3=A2=E4=B8=89=20=E8=B4=A6=E6=9C=AC=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=E2=80=94=E2=80=94RunLedger=20=E5=8D=95=E7=B1=BB=E6=8C=81?= =?UTF-8?q?=E5=9B=9B=E8=B4=A6+=E5=88=B7=E6=96=B0=E5=8F=8C=E8=B4=A6?= =?UTF-8?q?=E5=88=A0=E5=89=AF=E6=9C=AC+gp=5Ffinal=20=E9=94=AE=E5=88=A0?= =?UTF-8?q?=E5=BD=B1=E5=AD=90=E8=B4=A6=E2=80=94=E2=80=94=E9=80=90=E4=BD=8D?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E5=AF=B9=E6=8B=8D=E5=85=A8=E5=90=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P1-4 共享账本 Ledger 类化(裁点②甲 单类持四账): - game/core/run_ledger.gd: attune_charges/vision_counter/adventure_counters/bonus 四账 单类承载;战斗侧只拿 read/append 原语(attune_charge_has/set/consume、vision_count_add、 counters_seed_snapshot/merge_export、bonus_add_axis)——战末 export/开局 seed 时点语义不变 - AdventureRunner 持账(run_ledger)→ LevelRunner set_run_ledger+sync_attune_inventory (原四散 setter 收口)→ enc 单键 ledger 下传;编排器三裸引用成员退役为 _ledger 原语调用 - 调试/单场缺省新账=零扰动面(vision_counter 缺省空=不计,原 enc 缺键守卫语义保留) P1-2 刷新双账收口: - main 刷新计数副本(run_scope.refresh_used_round)删除;单一事实源=编排器受理计数 - presentation_snapshot 成文面升键 refresh_used(can_refresh 邻位)——HUD 余量/遥测 round_refresh_index 单源读快照;V5 快照一致性断言×3(smoke+ui_click HUD 文本比对) P1-3 GP 影子账收口: - adventure_runner summary 增 gp_final 键(close_wallet 清账前快照) - gp_shadow_ledger.gd 全件退役删除;main 委托缝/会话账本成员删除;终局面板 GP 行 取数=summary 键;ui_probe 影子账断言换代为 summary 键断言 验收: 逐位一致对拍三变体全同;冒烟 1079/0+run_all 26 套+probe click+render golden 0.00% --- game/core/adventure_runner.gd | 55 +++++++++---------- game/core/battle_orchestrator.gd | 39 +++++-------- game/core/level_runner.gd | 52 +++++++----------- game/core/run_ledger.gd | 51 +++++++++++++++++ game/core/run_ledger.gd.uid | 1 + game/presentation/gp_shadow_ledger.gd | 31 ----------- game/presentation/gp_shadow_ledger.gd.uid | 1 - game/presentation/hud_presenter.gd | 2 +- game/presentation/main.gd | 35 +++++------- game/presentation/run_scope.gd | 2 - game/presentation/views/level_map_screen.gd | 6 +- game/smoke/smoke_all.gd | 41 +++++++------- game/smoke/ui_click_probe.gd | 9 ++- game/smoke/ui_probe.gd | 9 +-- game/tests/unit/core/feat_vouchers_test.gd | 12 ++-- game/tests/unit/core/treasure_effects_test.gd | 11 ++-- 16 files changed, 174 insertions(+), 183 deletions(-) create mode 100644 game/core/run_ledger.gd create mode 100644 game/core/run_ledger.gd.uid delete mode 100644 game/presentation/gp_shadow_ledger.gd delete mode 100644 game/presentation/gp_shadow_ledger.gd.uid diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 083de223..1d084e7b 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -56,7 +56,7 @@ var db: ConfigDb var rng: RngServiceCore var state := "idle" # idle / running / settling(冒险结构.md 三态) var ledger := {"hero_hp": 0, "gp": 0} # 冒险账本(生命 + GP 钱包——经 start_level_external 注入 runner 共享) -var bonus := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 统一冒险加算区(商店购买与三选一属性件同一本账;四轴 +ap——迭代 10 奖励重构) +var run_ledger := RunLedger.new() # 共享四本账(迭代 27 T3——P1-4 收口:attune_charges/vision_counter/adventure_counters/bonus 单类持账;裁点②甲) var purchased := {} # product_id → 已购档数(跨关延续、冒险收束清零——递增定价输入) var level_index := 0 # 当前关卡序号(1 起) var levels_total := 0 # 序列总关数(读 level 表行数) @@ -79,9 +79,6 @@ var _attunement: Array = [] # 同调装配集(迭代 11 第① var _attune_refresh_x := 0 # 刷新件余量(迭代 11 第①批 T5b P1 甲——每碰三选一重置=X 表值;优先消耗、不结转) var _attune_refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值) var _curio_paid_refreshes := 0 # 本次停点已付费刷新次数(2026-10-06 用户令对齐商店 MC-1——GP 刷新价曲线输入,随停点创建清零=与商店进店清零同构) -var attune_charges := {} # 同调主动件冒险充能账本(迭代 11 第①批 T6——跨关卡共享 dict;settlement use_treasure 扣减直改同引用;断点随 attunement_charges 键入档) -var vision_counter := {"used": 0} # 真视类工具使用计数(迭代 22 秘宝批 T3——跨关卡共享 dict;编排器受理成功 +1;断点随 vision_counter_used 键入档,收束聚合入档 save.add_vision_used) -var adventure_counters := {} # adventure 域计数器账本(迭代 20 T6——攒层/付费计数等跨战持久件账;效果引擎 rebuild 回灌/战末导出回写共享引用;断点随 adventure_counters 键入档) # ---- 凭证件三本新账(迭代 20 T4——奇物.md 凭证件表新 5 件;F-1 刷新写错账修复承载面)---- var _refresh_stock := 0 # 刷新存量账(冒险级——刷新券加仓;**不经 _attune_refresh_x**:X 每停点重置会吞存量=旧写错账缺陷 F-1) var _pending_rap := 0 # 储备金袋待注入(节点级——下一战 RAP +N,战首注入后清零) @@ -123,7 +120,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: master_seed = seed_val rng.new_adventure(seed_val) ledger = {"hero_hp": 0, "gp": 0} - bonus = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} + run_ledger.bonus = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} purchased = {} keystone = 0 summary = {} @@ -140,7 +137,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: _attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5) _attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池) _seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义) - vision_counter = {"used": 0} # 迭代 22 秘宝批 T3——真视使用计数冒险开始归零(跨局累计归生涯档) + run_ledger.vision_counter = {"used": 0} # 迭代 22 秘宝批 T3——真视使用计数冒险开始归零(跨局累计归生涯档) # 刷新练习开局注入(迭代 22 秘宝批 T3——refresh_stock_grant 装配即得一次性加仓: # 装配集 kind 分发扫描;只入存量账 _refresh_stock、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕) for iid_v2 in _attunement: @@ -183,7 +180,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0))) ledger = {"hero_hp": int(bp.get("hero_hp", 0)), "gp": int(bp.get("gp", 0))} var bp_bonus: Dictionary = bp.get("bonus", {}) as Dictionary - bonus = {"atk": maxi(0, int(bp_bonus.get("atk", 0))), + run_ledger.bonus = {"atk": maxi(0, int(bp_bonus.get("atk", 0))), "max_hp": maxi(0, int(bp_bonus.get("max_hp", 0))), "armor_class": maxi(0, int(bp_bonus.get("armor_class", 0))), "ap": maxi(0, int(bp_bonus.get("ap", 0)))} @@ -240,9 +237,9 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: # 迭代 20 T6/T7——adventure 域计数器回填(缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先) var bp_counters = bp.get("adventure_counters", null) if bp_counters is Dictionary: - adventure_counters = (bp_counters as Dictionary).duplicate(true) + run_ledger.adventure_counters = (bp_counters as Dictionary).duplicate(true) _refresh_stock = maxi(0, int(bp.get("refresh_stock", 0))) # 迭代 20 T4/T7——旧档缺键缺省 0 - vision_counter = {"used": maxi(0, int(bp.get("vision_counter_used", 0)))} # 迭代 22 秘宝批 T3——真视计数随断点回填(缺键=旧断点缺省 0) + run_ledger.vision_counter = {"used": maxi(0, int(bp.get("vision_counter_used", 0)))} # 迭代 22 秘宝批 T3——真视计数随断点回填(缺键=旧断点缺省 0) _battery_pending = maxi(0, int(bp.get("battery_pending", 0))) var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary if not bp_pending.is_empty(): @@ -273,7 +270,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: # 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态) var bp_charges = bp.get("attunement_charges", null) if bp_charges is Dictionary: - attune_charges = (bp_charges as Dictionary).duplicate(true) + run_ledger.attune_charges = (bp_charges as Dictionary).duplicate(true) state = "running" return _start_current_level() @@ -288,7 +285,7 @@ func _reset_curio_state() -> void: _curio_result = {} _keystone_drops = [] _node_curio_pending = {} - adventure_counters = {} + run_ledger.adventure_counters = {} _refresh_stock = 0 _pending_rap = 0 _battery_pending = 0 @@ -333,7 +330,7 @@ func set_hero_override(atk_pct: int, hp_pct: int) -> void: # 逐字一致。旧口径两处只算 flat 不乘 pct:调生命上限 26→104 时开局血仍 26(账本 26 / 战斗内 104)、 # 通关 20% 回血封顶按 26 算。pct=0(模拟/冒烟默认)时乘法恒等——MC 锚零漂移。 func effective_max_hp() -> int: - var base: int = int(db.hero.max_hp) + int(_base_override.get("max_hp", 0)) + int(bonus.get("max_hp", 0)) + var base: int = int(db.hero.max_hp) + int(_base_override.get("max_hp", 0)) + int(run_ledger.bonus.get("max_hp", 0)) return maxi(1, int(round(float(base) * (100.0 + float(_override[1])) / 100.0))) @@ -467,7 +464,7 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = # adventure_table.CURIO_APPLY_ROUTES 声明路由(bonus 落点键),加新加算类令牌零代码) var sink := db.adventure.curio_apply_route(kind) if sink != "": - bonus[sink] = int(bonus.get(sink, 0)) + value + run_ledger.bonus[sink] = int(run_ledger.bonus.get(sink, 0)) + value _curio_taken[kind] = int(_curio_taken.get(kind, 0)) + 1 return true # 通道面(副作用形态——显式分支) @@ -590,11 +587,11 @@ func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> # 冒险开始充能账本重置(T6——装备 active_charged 件各回 charges_per_adventure;被动件不入账) func _seed_attune_charges() -> void: - attune_charges = {} + run_ledger.attune_charges = {} for iid in _attunement: var arow: Dictionary = db.treasure_item_row(String(iid)) if String(arow.get("form", "")) == "active_charged": - attune_charges[String(iid)] = maxi(0, int(arow.get("charges_per_adventure", 0))) + run_ledger.attune_charges[String(iid)] = maxi(0, int(arow.get("charges_per_adventure", 0))) # ---- 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源:X=刷新件每碰三选一重给〔优先消耗、不结转〕、 @@ -640,7 +637,7 @@ func choose_battery_target(item_id: String) -> bool: push_error("[冒险结构] 电池分配拒绝——非主动充能件: %s" % item_id) return false _battery_pending -= 1 - attune_charges[item_id] = int(attune_charges.get(item_id, 0)) + 1 + run_ledger.attune_charges[item_id] = int(run_ledger.attune_charges.get(item_id, 0)) + 1 return true @@ -908,7 +905,7 @@ func abandon() -> bool: # 独立成口=V10 断言面直呼) func _reset_tree_limited_counters() -> void: for cid in ["tree_sense_sharp_used", "tree_sense_guard_used"]: - adventure_counters.erase(cid) + run_ledger.adventure_counters.erase(cid) func _start_current_level() -> bool: @@ -936,12 +933,12 @@ func _start_current_level() -> bool: # 停点前移批 2026-09-23:defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在 # 该回调重取 runner 实例),后入口候选呈现;顺序反了选路口会被呈现层的 runner 空防御静默丢弃。 # 呈现零 RNG 消耗、pick 走同一 _enter_node——测量面逐位零漂移。 - var ok: bool = _runner.start_level_external(level_index, ledger, bonus, purchased, + _runner.set_run_ledger(run_ledger) # 迭代 27 T3——共享四账单类下传(原四 setter/参数散传收口;run_ledger=四账类,ledger=关卡账沿用旧名) + _runner.sync_attune_inventory() # 迭代 11 第①批 T6 原语义——能力件骑库存面注入(随账下传同拍) + var ok: bool = _runner.start_level_external(level_index, ledger, purchased, Callable(self, "apply_rest"), _base_override, true, template_pick, resume_state) if ok: _runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算) - _runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入) - _runner.set_vision_counter(vision_counter) # 迭代 22 秘宝批 T3——真视使用计数共享 dict 下传(编排器受理成功 +1) _runner.set_feats(_feats) # 迭代 12 T1——特性持有集下传(begin_battle enc.feats 求值面) _runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费) _runner.set_hero_axes(hero_attack_type, hero_armor_type) # 迭代 23 T4——两根类型轴快照下传(effective_ac 甲型基础 AC 层+enc 携带进 spawn) @@ -949,7 +946,6 @@ func _start_current_level() -> bool: # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动。 # 迭代 20 T2——cap 检查回调四参注入) _runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"), Callable(self, "_feat_ticket_consider"), Callable(self, "_curio_cap_ok")) - _runner.set_adventure_counters(adventure_counters) # 迭代 20 T6——adventure 域计数器共享引用下传 # 迭代 20 T4——凭证件三账注入(节点级/回合级一次性:注入即清零,未用即过期=寿命档语义) _runner.set_voucher_grants(_pending_rap, _time_sand_queue, _pending_insurance) _pending_rap = 0 @@ -1028,7 +1024,7 @@ func _settle(result: String) -> void: "master_seed": master_seed, "aux_cleared_last": _last_treasure_cleared, # additive 扩签(迭代 7 T4——末关清空反馈;非末关走 bonus 载荷) "keystone_drops": _keystone_drops.duplicate(true), # additive 扩签(迭代 10——逐杀掉落明细;settle 入账明细/复盘归因数据源) - "vision_used_delta": int(vision_counter.get("used", 0)), # additive 扩签(迭代 22 秘宝批 T3——真视使用本局计数;settle 聚合入档) + "vision_used_delta": int(run_ledger.vision_counter.get("used", 0)), # additive 扩签(迭代 22 秘宝批 T3——真视使用本局计数;settle 聚合入档) "pool_stock": _pool_stock_snapshot(), # additive 扩签(迭代 11 第②批 T5——终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面) "relics": _feats.duplicate(), # additive 扩签(迭代 12 T1——特性终局持有集:清账前快照,成型判读取数面) } @@ -1041,6 +1037,7 @@ func _settle(result: String) -> void: "node_type": String(_last_level_payload.get("node_type", "")), } if _runner != null: + summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(迭代 27 T3——P1-3 GP 影子账收口:终局面板取数源,影子账随批退役) _runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现) _reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化) if _save_manager != null: @@ -1080,16 +1077,16 @@ func _snapshot_breakpoint() -> void: "node_id": _runner.current_node_id() if _runner != null else -1, "hero_hp": int(ledger.get("hero_hp", 0)), "bonus": { - "atk": int(bonus.get("atk", 0)), - "max_hp": int(bonus.get("max_hp", 0)), - "armor_class": int(bonus.get("armor_class", 0)), - "ap": int(bonus.get("ap", 0)), + "atk": int(run_ledger.bonus.get("atk", 0)), + "max_hp": int(run_ledger.bonus.get("max_hp", 0)), + "armor_class": int(run_ledger.bonus.get("armor_class", 0)), + "ap": int(run_ledger.bonus.get("ap", 0)), }, "gp": int(ledger.get("gp", 0)), "shop_purchased": purchased.duplicate(true), "shop_shelf": _runner.shop_bp_shelf() if _runner != null else [], # 迭代 11 第②批 T2——P2 甲货架入断点(离开店/非店停点 = 空架合法态) "shop_visit_buys": _runner.shop_bp_visit_buys() if _runner != null else 0, # 迭代 11 第②批 T2——本次进店已购次数(A 链定价输入,P2 甲扩列) - "attunement_charges": attune_charges.duplicate(true), # 迭代 11 第①批 T6——同调主动件充能余量随断点(save v4 域) + "attunement_charges": run_ledger.attune_charges.duplicate(true), # 迭代 11 第①批 T6——同调主动件充能余量随断点(save v4 域) "template_pick": _runner.active_template_index if _runner != null else -1, "master_seed": master_seed, "rng_counter": rng.counter(), @@ -1102,8 +1099,8 @@ func _snapshot_breakpoint() -> void: "bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约) "feat_ticket_pity": _feat_ticket_pity, # T5 门票保底计数(扩列向后兼容——旧档缺键缺省 0) "feat_ticket_sold": _feat_ticket_sold, # T5 门票限购计数(同上) - "adventure_counters": adventure_counters.duplicate(true), # 迭代 20 T6/T7——adventure 域计数器随断点(续玩跨战账不丢) - "vision_counter_used": int(vision_counter.get("used", 0)), # 迭代 22 秘宝批 T3——真视使用计数随断点(续玩跨战账不丢) + "adventure_counters": run_ledger.adventure_counters.duplicate(true), # 迭代 20 T6/T7——adventure 域计数器随断点(续玩跨战账不丢) + "vision_counter_used": int(run_ledger.vision_counter.get("used", 0)), # 迭代 22 秘宝批 T3——真视使用计数随断点(续玩跨战账不丢) "refresh_stock": _refresh_stock, # 迭代 20 T4/T7——刷新存量账随断点(F-1 新账) "attune_refresh_y": _attune_refresh_y, # 2026-10-13 缺陷修批——Y 冒险周期池余量随断点(续玩不再自参数面整额重注入=堵「用完退菜单再续玩满血复活」可利用漏洞;扩列向后兼容——旧档缺键回退参数面) "battery_pending": _battery_pending, # 迭代 20 T4/T7——电池待分配充能随断点 diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index feb73f5a..27e76139 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -84,16 +84,14 @@ var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换 var _flag_consumed := {} # 兑付资格以雷格实例为键(板级;换图重置) # 同调装配运行态(迭代 11 第①批 T5——begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动) var attunement: Array = [] # 装备中同调件 item_id 集 -var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_treasure 消费直改) +var _ledger: RunLedger = null # 共享四本账(迭代 27 T3——begin_battle 经 enc["ledger"] 持引用;缺省新账=调试面) var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(迭代 22 T3 通用化——装配集 ap_charge_safe 行扫描;反哺批⑤ 2026-10-12 用户裁:满 charge_n 自动兑付当轮 AP+1,无玩家发起入口) # ---- 效果引擎扩签运行态(迭代 20 专长实装批 T1——新效果类型施加面;空订阅零扰动)---- var _fx_hero_ac_base := -1 # 主角 AC 基线快照(enc.hero_armor_class;-1=表值兜底未解析) var _fx_ac_round := 0 # ac_bonus 本轮累积器(封印桩形态——round_start 清零) var nest_preview_count := -1 # 巢穴预告读数口(迭代 22 秘宝批 T3——board_installed 命中落值;-1=未装配/未发射;呈现桥消费) var _fx_ac_battle := 0 # ac_bonus 本战累积器(石肤形态——cap 封顶,begin_battle 清零) -var _fx_adventure_counters_ref: Dictionary = {} # adventure 域计数器共享引用(T6——attune_charges 先例:begin_battle 回灌 rebuild、battle 末 counters_export 回写) -var _fx_bonus_ref: Dictionary = {} # 冒险加算区共享引用(bonus_delta 战末汇入——血税双向账) -var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末并入 _fx_bonus_ref 后清零) +var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末经 _ledger.bonus_add_axis 汇入后清零) var _fx_new_resource_types := {} # 本战已见资源类型集(地质学家 fact new_resource_type 判定——battle 域) var _fx_pending_reveals: Array[Dictionary] = [] # 揭示效果待兑队列(见血开窍/斩首学者/地质学家/跳格采掘——授予段末统一兑付,防轮 facts 迭代中追加重入) var _fx_revealing := false # fx 揭示兑付中标记(_on_cell_revealed 缓冲旁路——fx 揭示的奖励由兑付点直接入账,防二次缓冲二次兑付) @@ -260,8 +258,7 @@ func _reseed_attune_charges(reset: String) -> void: var want := int(arow.get("charges_per_adventure", 0)) if want < 1: continue # charges=0=自动兑付件(试炼镐——不走骑库存面,兑付=_on_cell_revealed 积点段自动 grant_ap) - if _attune_charges_ref != null: - _attune_charges_ref[iid] = want + _ledger.attune_charge_set(iid, want) if treasure_inventory != null: var existing := 0 for r in treasure_inventory.snapshot_inventory(): @@ -384,7 +381,6 @@ func begin_battle(enc: Dictionary) -> bool: # 同调装配运行态注入(迭代 11 第①批 T5——enc.attunement 携带;off=空集零扰动。淬毒 N 注入 # 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install) attunement = (enc.get("attunement", []) as Array).duplicate() - _attune_charges_ref = enc.get("attune_charges", {}) # 共享 dict 引用(level/adventure 同账本——use_treasure 扣减直改) _attune_safe_seen = 0 # 淬毒/偏运注入拆除(内容数据驱动扩展批 T4——两件被动改 triggers 订阅行:淬毒=clamp_check # modify 窗口、偏运=reward_roll bias_rewards;value 行字段 value_from 定影,零 begin_battle 注入) @@ -397,10 +393,8 @@ 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 bonus_ref = enc.get("bonus_ref", null) - _fx_bonus_ref = bonus_ref if bonus_ref is Dictionary else {} - var counters_ref = enc.get("adventure_counters", null) - _fx_adventure_counters_ref = counters_ref if counters_ref is Dictionary else {} + var ledger_ref = enc.get("ledger", null) + _ledger = ledger_ref if ledger_ref is RunLedger else RunLedger.new() # 缺省新账=调试/单场零扰动面 _damage.fx_reveal_request = Callable(self, "_fx_enqueue_reveal") _damage.fx_ac_request = Callable(self, "_apply_ac_bonus") var feat_ids: Array = enc.get("feats", []) as Array @@ -417,7 +411,7 @@ func begin_battle(enc: Dictionary) -> bool: # 树规则合成订阅同面装载。装载序=feats 表序→treasure 表序→树规则,写死保种子复放。 # 迭代 20 T6——adventure 域计数器从共享引用回灌〔跨战持久〕) _fx.rebuild(feat_ids, attunement, tree_rules) - _fx.counters_seed(_fx_adventure_counters_ref) + _fx.counters_seed(_ledger.counters_seed_snapshot()) # 秘宝常时参数注入(迭代 22 秘宝批 T3——装配集 kind 分发:远程镐挖距放宽/信标网络旗可达豁免; # 缺省 0/false=零装配零扰动。注入先于首板 install 已不成立〔板在早段〕——本批板生成不读两参数, # 参数只影响挖掘/插棋动作判据,时序安全) @@ -777,15 +771,12 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary: var rec: Dictionary = treasure_inventory.consume(handle) # 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用) if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement": - var used_iid: String = String(rec.get("item_id", "")) - if _attune_charges_ref.has(used_iid): - _attune_charges_ref[used_iid] = maxi(0, int(_attune_charges_ref[used_iid]) - 1) + _ledger.attune_charge_consume(String(rec.get("item_id", ""))) # 真视类工具使用计数(迭代 22 秘宝批 T3——信标网络解锁行为条件:受理成功恰计一次; # 共享 dict 引用下传〔attune_charges 同款〕,冒险收束聚合入档 save.add_vision_used) if kinds.has("reveal_tiles") or kinds.has("safe_dig") or kinds.has("mine_reveal"): - var vc: Dictionary = encounter.get("vision_counter", {}) if encounter is Dictionary else {} - if vc is Dictionary and not vc.is_empty(): - vc["used"] = int(vc.get("used", 0)) + 1 + if not _ledger.vision_counter.is_empty(): + _ledger.vision_count_add(1) # 迭代 22 秘宝批 T3——受理成功恰计一次(缺省空账=调试不计) return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec} @@ -1149,15 +1140,12 @@ func _battle_over(result: String) -> void: hu_w["hp"] = mini(int(hu_w.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_w.get("max_hp", 1)))) # bonus_delta 战末汇入(迭代 20 T1——血税双向账:本战累积并入冒险加算区共享引用后清零)+ # adventure 域计数器导出回写(T6——共享引用覆写,attune_charges 先例) - if _fx_bonus_ref != null and not _fx_bonus_ref.is_empty(): + if not _ledger.bonus.is_empty(): for axis in _fx_bonus_delta.keys(): - if _fx_bonus_ref.has(axis): - _fx_bonus_ref[axis] = int(_fx_bonus_ref.get(axis, 0)) + int(_fx_bonus_delta[axis]) + _ledger.bonus_add_axis(axis, int(_fx_bonus_delta[axis])) _fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} - if _fx != null and not _fx_adventure_counters_ref.is_empty(): - var exported: Dictionary = _fx.counters_export() - for cid in exported.keys(): - _fx_adventure_counters_ref[cid] = int(exported[cid]) + if _fx != null: + _ledger.counters_merge_export(_fx.counters_export()) # 映射随战斗结束退役(地图战斗资源 义务④——下一场战斗 = 新板新 scope 批,roll_board 相位守卫的前提) var hero_hp_left := int(_enemy_system.hero_unit().get("hp", 0)) # despawn 前捕获(信号载荷供呈现层"HP 带入下一层") var gp_dropped := _roll_gp_drop() # 战斗掉落(GP+商店房实施批——escrow 随此值上抛即弃) @@ -1239,6 +1227,7 @@ func presentation_snapshot() -> Dictionary: "phase": String(action_economy.phase), "ap": int(action_economy.ap), "rap": int(action_economy.rap), "can_refresh": can_refresh(), + "refresh_used": _refresh_used_this_round, # 迭代 27 T3——P1-2 刷新双账收口:受理计数入呈现成文面(HUD 余量单源读此处) "hero": hero, "enemies": enemies, "hero_effects": _status_effects.query_active("hero").duplicate(true), # 值拷贝出户(code-review 2026-09-29 P2-3——快照消费方直改不渗入状态容器) diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 264868ac..54541dc6 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -57,7 +57,6 @@ var hero_hp: int: var gp: int: # GP 冒险钱包(局内货币——冒险开始 = 0、收束清零;不入存档不入摘要) get: return int(_ledger["gp"]) set(v): _ledger["gp"] = v -var bonus := {"atk": 0, "max_hp": 0, "armor_class": 0} # 统一冒险加算区(冒险开始全 0)——商店购买与关卡大奖励写同一本账(冒险模式 = AdventureRunner 共享字典;大奖励接线归 AdventureRunner._on_level_finished) var base_override := {"atk": 0, "max_hp": 0, "armor_class": 0} # 永久强化 flat 注入(元进度实现批——冒险模式 = AdventureRunner 共享字典经 start_level_external 注入;单关测试模式 = 私有全零) var _shop_purchased := {} # product_id → 已购档数(现价重算输入;冒险开始清零) # 协作对象私有(E1/E3 封装收口 2026-09-13):settlement 读取经 current_battle() 成文出口 @@ -65,9 +64,7 @@ var _settlement: BattleOrchestrator = null # 当前战斗编排器(关 var treasure_inventory: TreasureItems = null # 秘宝 关卡域库存(迭代 7 T3——TreasureItems;跨战斗延续, var _treasure_unlocked := {} # aux 解锁门控集(迭代 7 T5——拍板③甲;空集=零投放) var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——adventure params.attunement 下传,begin_battle enc 携带进战斗结算;空集=off 零扰动) -var _attune_charges: Dictionary = {} # 同调主动件充能账本(迭代 11 第①批 T6——adventure 共享 dict 引用;settlement use_treasure 扣减直改同引用) # 关卡末清空+快照计数入 level_finished 载荷) -var _vision_counter: Dictionary = {"used": 0} # 真视类工具使用计数(迭代 22 秘宝批 T3——adventure 共享 dict 引用;编排器受理成功 +1,收束聚合入档) # 两根类型轴(迭代 23 T4——adventure 参数面快照经 set_hero_axes 下传;空串=未注入→锻造配置默认值回退。 # 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照) var _hero_attack_type := "" @@ -115,12 +112,12 @@ func set_hero_override(atk_pct: int, hp_pct: int) -> void: # 叠加次序(元进度实现批钉死):表值 → 永久强化 flat(base_override)→ 加算区 flat(bonus)先加 # → 调试浮窗百分比后乘(调试数值工具恒乘最后——代拍板 6,与 GP 批既有次序兼容) func effective_atk() -> int: - var base: int = int(db.hero.atk) + int(base_override.get("atk", 0)) + int(bonus.get("atk", 0)) + var base: int = int(db.hero.atk) + int(base_override.get("atk", 0)) + int(run_ledger.bonus.get("atk", 0)) return maxi(1, int(round(float(base) * (100.0 + float(hero_atk_pct)) / 100.0))) func effective_max_hp() -> int: - var base: int = int(db.hero.max_hp) + int(base_override.get("max_hp", 0)) + int(bonus.get("max_hp", 0)) + var base: int = int(db.hero.max_hp) + int(base_override.get("max_hp", 0)) + int(run_ledger.bonus.get("max_hp", 0)) return maxi(1, int(round(float(base) * (100.0 + float(hero_hp_pct)) / 100.0))) @@ -128,7 +125,7 @@ func effective_max_hp() -> int: # 甲路线等级加值〔base_override.armor_class——hero_override 锻造分量〕+ 加算区 armor_class 轴; # 无调试浮窗百分比通道,纯 flat) func effective_ac() -> int: - return maxi(0, _armor_base_ac() + int(base_override.get("armor_class", 0)) + int(bonus.get("armor_class", 0))) + return maxi(0, _armor_base_ac() + int(base_override.get("armor_class", 0)) + int(run_ledger.bonus.get("armor_class", 0))) # 甲型基础 AC(三层之第一层——选中甲路线行 base_ac;未注入/缺行=锻造配置默认值路线=轻甲 10, @@ -144,7 +141,7 @@ func _armor_base_ac() -> int: # 行动点加成生效值(迭代 10 奖励重构批——统一冒险加算区第四轴 ap;经战斗开始入参 hero_ap_bonus # 传入战斗域,兑现 = 每场战斗开始首轮行动点基线 +加成值〔行动经济 GDD 跨冒险行动点加成轴节〕) func effective_ap_bonus() -> int: - return maxi(0, int(bonus.get("ap", 0))) + return maxi(0, int(run_ledger.bonus.get("ap", 0))) # 覆盖复位(关卡结束归零——表值不可变,仅清百分比;调用方兼容保留) @@ -176,7 +173,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary = visited = [] _graph_mode = false gp = 0 # 冒险钱包清零开局(GP+商店房实施批——局内货币生命周期起点) - bonus = {"atk": 0, "max_hp": 0, "armor_class": 0} # 加算区冒险开始全 0 + run_ledger.bonus = {"atk": 0, "max_hp": 0, "armor_class": 0} # 加算区冒险开始全 0(单关入口零元进度——注入面见上) base_override = {"atk": 0, "max_hp": 0, "armor_class": 0} # 永久强化 flat:缺省全零白板(单关入口零元进度——注入面见上) _shop_purchased = {} # 已购档数清零(跨冒险不延续——递增定价按冒险内累计) for k in base_override.keys(): @@ -209,7 +206,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary = # 信号顺序契约:呈现层须先经 level_started 重取 runner 实例,choices_presented 才不会被 # _on_choices_presented 的 runner 空防御静默丢弃。入口呈现零 RNG 消耗、pick 走同一个 # _enter_node(entry)——与旧自动进入逐位等价(测量面零漂移红线)。 -func start_level_external(level_index: int, ledger: Dictionary, bonus_ref: Dictionary, +func start_level_external(level_index: int, ledger: Dictionary, purchased_ref: Dictionary, rest_handler: Callable, base_override_ref: Dictionary = {}, defer_entry: bool = false, template_pick: int = -1, resume_state: Dictionary = {}) -> bool: var row := _find_level_row(level_index) @@ -218,7 +215,6 @@ func start_level_external(level_index: int, ledger: Dictionary, bonus_ref: Dicti return false _external = true _ledger = ledger - bonus = bonus_ref base_override = base_override_ref _shop_purchased = purchased_ref _rest_handler = rest_handler @@ -498,13 +494,10 @@ 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, # 迭代 23 T4——两根类型轴(spawn 英雄快照;空串=锻造配置默认值回退) "attunement": _attunement, - "attune_charges": _attune_charges, - "vision_counter": _vision_counter, # 迭代 22 秘宝批 T3——真视使用计数共享 dict(编排器受理成功 +1,收束入档) + "ledger": run_ledger, # 迭代 27 T3——共享四账单类下传(原四散 dict 引用键收口;裁点②甲) "feats": _feats, "tree_rules": _tree_rules, "battle_index": _battle_index(), - "bonus_ref": bonus, - "adventure_counters": _adventure_counters_ref, "voucher_rap": _voucher_rap, "voucher_time_sand": _voucher_time_sand, "voucher_insurance": _voucher_insurance, @@ -661,14 +654,14 @@ func set_hero_axes(attack_type: String, armor_type: String) -> void: _hero_armor_type = armor_type -# adventure 域计数器共享引用注入(迭代 20 T6——AdventureRunner 冒险级账本引用下传; -# begin_battle enc.adventure_counters 携带进效果引擎 rebuild 回灌与战末 counters_export 回写—— -# **共享引用不复制**,attune_charges 同款;缺省空 dict=零扰动) -var _adventure_counters_ref: Dictionary = {} +# 共享四本账注入(迭代 27 T3——P1-4 收口:RunLedger 单类下传,替代原 bonus/attune_charges/ +# vision_counter/adventure_counters 四散引用;战斗侧只走账类原语,战末 export/开局 seed +# 时点语义不变〔裁点②甲〕) +var run_ledger: RunLedger = RunLedger.new() -func set_adventure_counters(ref: Dictionary) -> void: - _adventure_counters_ref = ref +func set_run_ledger(l: RunLedger) -> void: + run_ledger = l # 凭证件三账注入(迭代 20 T4——储备金袋 RAP/时沙 首轮 AP/完美保险 标错豁免;节点级一次性, @@ -684,10 +677,12 @@ func set_voucher_grants(rap: int, time_sand: int, insurance: int) -> void: _voucher_insurance = maxi(0, insurance) -# 同调主动件充能账本注入(迭代 11 第①批 T6——共享 dict 引用直改;能力件骑库存面注入: -# 差额注入防续玩双份——resume 路径 restore_inventory 已含断点内 attune 记录) -func set_attune_charges(charges: Dictionary) -> void: - _attune_charges = charges +# 同调主动件充能账骑库存面注入(迭代 11 第①批 T6 原语义 + 迭代 27 T3 换代——账本住 +# RunLedger;能力件骑库存面注入保留:差额注入防续玩双份——resume 路径 restore_inventory +# 已含断点内 attune 记录。原 set_attune_charges/set_vision_counter/set_adventure_counters +# 三散 setter 随单类下传承接) +func sync_attune_inventory() -> void: + var charges: Dictionary = run_ledger.attune_charges if treasure_inventory == null: treasure_inventory = TreasureItems.new(db) for iid in charges: @@ -698,11 +693,6 @@ func set_attune_charges(charges: Dictionary) -> void: for i in range(maxi(0, int(charges[iid]) - existing)): treasure_inventory.add_item(String(iid), "attunement", Vector2i(-1, -1), 0) - -# 真视使用计数共享引用注入(迭代 22 秘宝批 T3——adventure 持有、编排器受理成功 +1) -func set_vision_counter(counter: Dictionary) -> void: - _vision_counter = counter - # 三选一 GP 袋件入账(迭代 10——钱包收口扩「奖励入账」入口;复用战斗完成同一入账原语, # wallet_changed 载荷带 source 供呈现归因) func grant_curio_gp(amount: int) -> void: @@ -786,7 +776,7 @@ func buy_product(product_id: String) -> bool: push_error("[关卡节点地图] 商店房未知商品: %s" % product_id) return false var attr := String(prow.get("attr", "")) - if not bonus.has(attr): + if not run_ledger.bonus.has(attr): push_error("[关卡节点地图] 商品属性键出域: %s(attr=%s)" % [product_id, attr]) return false var price := current_price(prow) @@ -795,7 +785,7 @@ func buy_product(product_id: String) -> bool: return false # 余额不足(覆盖层按钮禁用为常态防线——此处兜底拒绝;2026-10-02 补静默留痕) gp -= price var flat := int(prow.get("flat_value", 0)) - bonus[attr] = int(bonus[attr]) + flat + run_ledger.bonus[attr] = int(run_ledger.bonus[attr]) + flat if attr == "max_hp": hero_hp = mini(hero_hp + flat, effective_max_hp()) # 购血:上限与当前生命同步 +Δ(封顶新上限) _shop_purchased[product_id] = purchased_tier(product_id) + 1 diff --git a/game/core/run_ledger.gd b/game/core/run_ledger.gd new file mode 100644 index 00000000..12480a22 --- /dev/null +++ b/game/core/run_ledger.gd @@ -0,0 +1,51 @@ +class_name RunLedger +extends RefCounted +# 共享四本账显式承载(迭代 27 T3——审计 P1-4「共享可变字典跨三层:所有权名义单点、实际三处 +# 可写」收口;裁点②甲 = 单类持四账)。 +# 所有权:AdventureRunner 持有(开局构造、冒险全程唯一宿主);LevelRunner 经 set_run_ledger +# 下传并装配 encounter;战斗侧(BattleOrchestrator)经 encounter["ledger"] 拿本类引用, +# 只走下方 read/append 原语——战末 export/开局 seed 时点语义不变(确定性设计原样保留, +# 只把「函数签名上不可见的共享可变引用」换成签名可见的显式类)。 +var attune_charges: Dictionary = {} # 同调主动件充能账(迭代 11 第①批 T6——use_treasure 扣减;断点随局回档) +var vision_counter: Dictionary = {} # 真视类使用计数(迭代 22 秘宝批 T3——冒险开始置 {"used":0}; + # 缺省空 = 调试/单场战斗不计——原 enc 缺键守卫语义) +var adventure_counters: Dictionary = {} # adventure 域效果计数器(迭代 20 T6——rebuild 回灌/战末导出回写目标) +var bonus: Dictionary = {} # 统一冒险加算区(冒险开始置四轴零账——商店购买/三选一属性件/战末 bonus_delta 汇入) + + +# ---- 战斗侧原语(裁点②甲:战斗侧只拿 read/append 两族;宿主侧冒险/关卡域直读字段域) ---- + +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) + + +func vision_count_add(n: int) -> void: + vision_counter["used"] = int(vision_counter.get("used", 0)) + n + + +func counters_seed_snapshot() -> Dictionary: + return adventure_counters # 回灌读面(时点 = begin_battle rebuild 后——语义不变) + + +func counters_merge_export(exported: Dictionary) -> void: + # 战末回写(原编排器逐字语义:仅非空账参与、逐键覆写) + if adventure_counters.is_empty(): + return + for cid in exported.keys(): + adventure_counters[cid] = int(exported[cid]) + + +func bonus_add_axis(axis: String, delta: int) -> void: + # 原编排器战末汇入逐字语义(has 闸——账外轴丢弃) + if bonus.has(axis): + bonus[axis] = int(bonus.get(axis, 0)) + delta diff --git a/game/core/run_ledger.gd.uid b/game/core/run_ledger.gd.uid new file mode 100644 index 00000000..e1e24a62 --- /dev/null +++ b/game/core/run_ledger.gd.uid @@ -0,0 +1 @@ +uid://7afr7e1gtrj5 diff --git a/game/presentation/gp_shadow_ledger.gd b/game/presentation/gp_shadow_ledger.gd deleted file mode 100644 index d075d9bf..00000000 --- a/game/presentation/gp_shadow_ledger.gd +++ /dev/null @@ -1,31 +0,0 @@ -# VERTICAL SLICE - NOT FOR PRODUCTION -# GP 冒险钱包呈现侧影子账本(迭代 6 T7 卡⑩;T3 拆件批 2026-10-02 自 main.gd 迁出——规范 1.1): -# 冒险进行中余额影子 + 收束清零拍转存的最终合计,供冒险终局面板 GP 行取数。 -# 手工镜像边界:wallet_changed gain/buy 载荷 = 入账后总额(level_runner.gd:482/539),"clear" 到达即 -# 影子转存 final 并归零——纯呈现侧账本,零核心触碰(C1 呈现层方案);写入口唯一 = apply_event -#(组合根 _on_wallet_changed 投喂;探针合成注入经 set_view_total——组合根委托面转发)。 -extends RefCounted - -var _view := 0 # 冒险进行中余额影子(gain/buy 载荷直录) -var _final := 0 # 收束清零拍转存的最终合计("clear" 到达时自影子转存) - - -func apply_event(event: String, gp: int) -> void: - match event: - "gain", "buy": - _view = gp - "clear": - _final = _view - _view = 0 - - -func set_view_total(v: int) -> void: - _view = v # 探针合成注入面(ui_probe 影子账清零转存断言) - - -func view_total() -> int: - return _view - - -func final_total() -> int: - return _final diff --git a/game/presentation/gp_shadow_ledger.gd.uid b/game/presentation/gp_shadow_ledger.gd.uid deleted file mode 100644 index 496868fd..00000000 --- a/game/presentation/gp_shadow_ledger.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://uk77ei6hjhca diff --git a/game/presentation/hud_presenter.gd b/game/presentation/hud_presenter.gd index fbd0793a..103bc550 100644 --- a/game/presentation/hud_presenter.gd +++ b/game/presentation/hud_presenter.gd @@ -23,7 +23,7 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB # 共账本〔level_runner bonus{atk,max_hp,armor_class}〕;空加算=整节隐藏〔视图内空态纪律〕。 # 呈现只读投影零核心触碰;行文格式=代拍板白名单项) if runner != null and level_active: - var b: Dictionary = runner.bonus + var b: Dictionary = runner.run_ledger.bonus # 迭代 27 T3——加算区住 RunLedger(P1-4 收口) var lines: Array[String] = [] if int(b.get("atk", 0)) != 0: lines.append("攻击 %+d" % int(b.get("atk", 0))) diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 00f45356..3d60f8ba 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -49,7 +49,6 @@ const TuneSimController := preload("res://presentation/tune_sim_controller.gd") const DebugSessionScript := preload("res://presentation/debug_session.gd") const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批) const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批) -const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批;T3 收口波将退役) const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批) const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批) const MenuScreenScene := preload("res://presentation/scenes/menu_screen.tscn") # 三屏壳场景(迭代 27 T1) @@ -104,18 +103,6 @@ var _treasure_ui = null # 秘宝两步交互(treasur var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入) var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发) -# GP 影子账委托面(探针/终局面板取数保留——ui_probe 合成注入经 set 转发账本,读面即账本实况; -# 账本实例随会话住 run_scope.gp_ledger——迭代 27 T1) -var _adventure_gp_view: int: - get: - return run_scope.gp_ledger.view_total() if run_scope != null and run_scope.gp_ledger != null else 0 - set(value): - if run_scope != null and run_scope.gp_ledger != null: - run_scope.gp_ledger.set_view_total(value) -var _adventure_gp_final: int: - get: - return run_scope.gp_ledger.final_total() if run_scope != null and run_scope.gp_ledger != null else 0 - # 排播队列只读委托面(探针/测试读 main._row_queue——RowPacer 迁移后保留的同名可读面,共享同一 Array 引用) var _row_queue: Array: get: @@ -192,7 +179,6 @@ func _begin_session(adventure_session: bool) -> void: run_scope.queue_free() run_scope = RunScope.new() run_scope.is_adventure = adventure_session - run_scope.gp_ledger = GpShadowLedgerScript.new() add_child(run_scope) @@ -637,7 +623,7 @@ func _on_adventure_finished(summary: Dictionary) -> void: text += "\n" + Narrator.keystone_settle_text(detail, _bonus.item_name_lookup()) if result == "completed": _end_overlay.open_victory(text, "返回冒险", board_view.get_global_rect(), - "本局 GP 入账 · 合计 %d" % _adventure_gp_final) # 卡⑩ GP 行;钥石明细已在 text(keystone_settle_text)——M9 三行账 + "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))) # 卡⑩ GP 行(迭代 27 T3——取数=summary gp_final 清账前快照,影子账已退役);钥石明细已在 text(keystone_settle_text)——M9 三行账 else: _end_overlay.open_defeat(text, "返回", board_view.get_global_rect()) logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷) @@ -817,7 +803,7 @@ func _gp_summary_text(payload: Dictionary) -> String: func _bonus_summary_text() -> String: if run_scope.runner == null: return "" - var b: Dictionary = run_scope.runner.bonus + var b: Dictionary = run_scope.runner.run_ledger.bonus # 迭代 27 T3——加算区住 RunLedger var parts: Array[String] = [] if int(b.get("atk", 0)) != 0: parts.append("攻 %+d" % int(b.get("atk", 0))) @@ -887,7 +873,6 @@ func _on_round_started(ri: int) -> void: var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs) if not vlines.is_empty(): settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines)) - run_scope.refresh_used_round = 0 _refresh_presentations() @@ -1140,7 +1125,6 @@ func _camp_data() -> Dictionary: func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void: # wallet 行族转发(到账/购买/清零——试玩日志验收取数面)+ 常驻余额与在开商店覆盖层重刷 logger.log_wallet(event, gp, extra) - run_scope.gp_ledger.apply_event(event, gp) # GP 影子账(迭代 6 T7——收束清零前取值,纯呈现零核心触碰=C1;账本住 run_scope——迭代 27 T1) if String(event) == "gain": run_scope.last_gain_amount = int(extra.get("amount", 0)) # 卡⑥ GP 增量取数(每战恰一笔 gain) if shop_overlay.visible and run_scope.runner != null: @@ -1220,15 +1204,14 @@ func _on_refresh_confirmed() -> void: ok = run_scope.settlement.player_request_refresh() logger.log_action("refresh", Vector2i(-1, -1), ok) if ok: - run_scope.refresh_used_round += 1 - logger.log_ux("ux_refresh_attempt", {"result": "accepted", "round_refresh_index": run_scope.refresh_used_round}) + logger.log_ux("ux_refresh_attempt", {"result": "accepted", "round_refresh_index": _refresh_used_snapshot()}) board_zone.toast.show_toast("RAP−%d · 旧板旗作废——新图已就位,AP 保留继续挖" % int(db.action_economy.rap_refresh_cost), "gold") else: # 资源面/上限拒绝(EC13 合法拒绝可见;行动经济 无载荷——客户端按上下文推断) var rap: int = int(run_scope.settlement.presentation_snapshot().get("rap", 0)) if run_scope.settlement != null else 0 var cost: int = int(db.action_economy.rap_refresh_cost) var result := "rejected_rap" if rap < cost else "rejected_cap" - logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": run_scope.refresh_used_round + 1}) + logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_snapshot() + 1}) if result == "rejected_rap": board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn") ap_bar.pulse_rap() @@ -1251,9 +1234,17 @@ func _handle_outcome_feedback(outcome: Dictionary) -> void: # ==================== 全屏 States 状态机(挖掘/结算/窗口/终局)==================== +# 本轮已受理刷新次数只读面(迭代 27 T3——P1-2 刷新双账收口:main 副本已删, +# 单一事实源=战斗编排器 _refresh_used_this_round,经 presentation_snapshot 成文面读取) +func _refresh_used_snapshot() -> int: + if run_scope == null or run_scope.settlement == null: + return 0 + return int(run_scope.settlement.presentation_snapshot().get("refresh_used", 0)) + + func _refresh_presentations() -> void: # 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查) - var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_cap_per_round) - run_scope.refresh_used_round) + var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_cap_per_round) - _refresh_used_snapshot()) HudPresenter.refresh(stage, info_panel, ap_bar, run_scope.settlement, run_scope.runner, run_scope.level_active, db, refresh_remaining) _treasure_ui.refresh_slots() # C 区秘宝槽重查(反哺批⑤——试炼镐积点进度随挖格刷新悬停浮窗) _sync_settlement_visuals() diff --git a/game/presentation/run_scope.gd b/game/presentation/run_scope.gd index 6b3e82d7..d2a122dd 100644 --- a/game/presentation/run_scope.gd +++ b/game/presentation/run_scope.gd @@ -13,7 +13,6 @@ var settlement = null # 当前战斗编排器(冒险与调试两 var is_adventure := false # 冒险会话标记(inter 层确认分派与终局分派) var level_active := false # 对局激活(状态面板入口点亮/刷新受理分派) var level_screen_active := false # 关卡独立屏激活状态(屏级互斥——形态升格批语义) -var refresh_used_round := 0 # 本轮已受理刷新次数(遥测 round_refresh_index——每轮 round_started 归零) var boss_active := false # Boss 遭遇变体(阶段指示附 Boss 名) var was_settling := false # 结算相位上升沿检测(结算轮光带触发——演出链批) var hints_shown := {} # 一次性非模态提示去重(每 run 一次:chord/no_adjacent) @@ -21,4 +20,3 @@ var settlement_stop_timer := ArmedTimer.new() # 卡⑥ 结算框挂起计时 var pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷) var locked_click := {} # 锁期点击缓存(T17 v3.1——最后一次;演播完边沿重放) var last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷) -var gp_ledger = null # GP 冒险钱包影子账(gp_shadow_ledger.gd 实例——wallet 事件投喂;T3 账本收口波将退役) diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 9cbcb146..b987404f 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -357,8 +357,8 @@ func _refresh_status() -> void: var max_hp: int = _runner.effective_max_hp() _hp_value.text = "HP %d/%d" % [int(_runner.hero_hp), int(max_hp)] _hp_bar.queue_redraw() - var atk_bonus := int(_runner.bonus.get("atk", 0)) - var ac_bonus := int(_runner.bonus.get("armor_class", 0)) + var atk_bonus := int(_runner.run_ledger.bonus.get("atk", 0)) # 迭代 27 T3——加算区住 RunLedger + var ac_bonus := int(_runner.run_ledger.bonus.get("armor_class", 0)) var atk_txt := "%d" % _runner.effective_atk() if atk_bonus > 0: atk_txt += " +%d" % atk_bonus # 加成角标(ASCII 加号——防像素字体缺 ⁺ 字形出豆腐块) @@ -366,7 +366,7 @@ func _refresh_status() -> void: if ac_bonus > 0: ac_txt += " +%d" % ac_bonus _attr_value.text = "攻击 %s · 护甲 AC %s" % [atk_txt, ac_txt] - var hp_bonus := int(_runner.bonus.get("max_hp", 0)) + var hp_bonus := int(_runner.run_ledger.bonus.get("max_hp", 0)) var parts: Array[String] = [] if atk_bonus > 0: parts.append("攻 +%d" % atk_bonus) diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index d68b8282..6339a089 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -131,8 +131,8 @@ func _smoke_scene_split() -> void: # ③ 探针挂载假设句柄在座(main.X 一级路径——探针迁移面收口证明) check(main._start_overlay == main._menu_screen.start_overlay and main.camp_overlay == main._camp_screen.camp_overlay and main.level_screen == main._adventure_screen.level_screen and main._battle_root == main._adventure_screen.battle_root and main.board_view == main._adventure_screen.board_view, "V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)") - check(main.run_scope != null and main.run_scope.gp_ledger != null and not main.run_scope.is_adventure, - "V2 闲时会话自 _ready 即在(永非 null——探针注入面时序)") + check(main.run_scope != null and not main.run_scope.is_adventure, + "V2 闲时会话自 _ready 即在(永非 null;迭代 27 T3——gp 影子账退役无账本成员)") main.queue_free() await process_frame @@ -156,11 +156,10 @@ func _smoke_run_lifecycle() -> void: await process_frame check(main.run_scope != null and main.run_scope.is_adventure and main.run_scope.adventure != null, "V3 开局建会话(is_adventure+adventure 在座)") - check(main.run_scope.hints_shown.is_empty() and main.run_scope.refresh_used_round == 0, + check(main.run_scope.hints_shown.is_empty(), "V3 新会话局态零值") - # 注入脏状态(旧手工清单时代的典型泄漏源——新会话必须整体吞掉) + # 注入脏状态(旧手工清单时代的典型泄漏源——新会话必须整体吞掉;迭代 27 T3——刷新计数已单源化入编排器,不再属会话脏面) main.run_scope.hints_shown["chord"] = true - main.run_scope.refresh_used_round = 2 main.run_scope.boss_active = true main.run_scope.last_gain_amount = 77 var finished := {"done": false} @@ -177,7 +176,7 @@ func _smoke_run_lifecycle() -> void: await process_frame check(not main.run_scope.is_adventure and main.run_scope.adventure == null and main.run_scope.runner == null, "V3 会话收束(新闲时会话零冒险/关卡对象)") - check(main.run_scope.hints_shown.is_empty() and main.run_scope.refresh_used_round == 0 and not main.run_scope.boss_active and main.run_scope.last_gain_amount == 0, + check(main.run_scope.hints_shown.is_empty() and not main.run_scope.boss_active and main.run_scope.last_gain_amount == 0, "V3 局态旗/账本全复位(queue_free 物化——注入脏状态零残留)") check(main._start_overlay.visible, "V3 回开始菜单(终局出口路径)") # ---- 第二局:放弃终局无断点残留→直进开局→全新会话 ---- @@ -185,7 +184,7 @@ func _smoke_run_lifecycle() -> void: main.debug_overlay.seed_edit.text = "43" main._start_adventure_from_menu() await process_frame - check(main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty() and main.run_scope.refresh_used_round == 0, + check(main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty(), "V3 第二连局全新会话(两连局零泄漏)") check(not main._start_overlay.visible, "V3 直进入局(菜单隐)") main.queue_free() @@ -278,7 +277,7 @@ func _smoke_node_map_templates() -> void: rng0.new_adventure(910001) var runner0 = load("res://core/level_runner.gd").new(_db, rng0) var c_before: int = rng0.counter() - check(runner0.start_level_external(2, {"hero_hp": 20, "gp": 0}, {}, {}, + check(runner0.start_level_external(2, {"hero_hp": 20, "gp": 0}, {}, # 迭代 27 T3——start_level_external 签名收口(bonus 参数随 RunLedger 单类下传退役) Callable(self, "_smoke_rest_stub"), {}, true, 0), "强制 template_pick=0 起关受理") check(rng0.counter() == c_before, "强制取模板零 RNG 消耗(零抽签——首次脚本语义)") var tpl0: Dictionary = {} @@ -293,7 +292,7 @@ func _smoke_node_map_templates() -> void: var rg = load("res://core/rng_service.gd").new() rg.new_adventure(910002) var rn = load("res://core/level_runner.gd").new(_db, rg) - rn.start_level_external(2, {"hero_hp": 20, "gp": 0}, {}, {}, + rn.start_level_external(2, {"hero_hp": 20, "gp": 0}, {}, Callable(self, "_smoke_rest_stub"), {}, true, -1) names.append(rn.active_template_name) check(names[0] == names[1] and String(names[0]) != "", @@ -732,7 +731,7 @@ func _smoke_adventure() -> void: check(gp0 >= hp_price + l2_price_bonus, "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" % [hp_price + l2_price_bonus, hp_price, l2_price_bonus]) check(r8.buy_product("hp_up"), "购买 hp_up 受理(出账 + 加算区)") - var acc_shop := int(a8.bonus.get("max_hp", -1)) # L1 三选一后 + 商店 3(首件随种子) + var acc_shop := int(a8.run_ledger.bonus.get("max_hp", -1)) # L1 三选一后 + 商店 3(首件随种子) check(acc_shop >= 3, "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop) a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index, "node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关) @@ -743,8 +742,8 @@ func _smoke_adventure() -> void: check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))), "三选一点选兑付受理(候选取首件)") var picked2: String = String(a8._curio_result.get("picked", "")) var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0) - check(int(a8.bonus.get("max_hp", -1)) == expect_acc, - "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.bonus.get("max_hp", -1)), acc_shop, picked2]) + check(int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc, + "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2]) var max_eff8: int = a8.effective_max_hp() # T17 口径修复随批:账本生效上限含调试浮窗 pct(a8 已 set 400) check(int(a8.ledger["hero_hp"]) <= max_eff8, "通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [int(a8.ledger["hero_hp"]), max_eff8]) @@ -2335,6 +2334,10 @@ func _smoke_active_refresh() -> void: minesweeper.settle_batch([{"action": "dig", "cell": plan3.dig_cell}]) var ok3: bool = settlement.player_request_refresh() check(not ok3, "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" % settlement.action_economy.rap) + # V5(迭代 27 T3——刷新双账收口):受理计数单源=结算快照 economy.refresh_used,与 can_refresh 门一致 + check(int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2, + "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕") + check(not settlement.can_refresh(), "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕") # 跨图 facts 完整兑现:拒绝后挖到 AP 耗尽自动结算(扩大跨图样本——三段挖掘分属三张图) var dig_guard := 20 while settlement.action_economy.phase == "Active" and settlement.round_index == 4 and dig_guard > 0: @@ -4322,7 +4325,7 @@ func _smoke_shop_room() -> void: var gp0: int = runner.gp check(runner.buy_product("atk_up"), "购买 atk_up 受理(现价 6)") check(runner.gp == gp0 - 6, "钱包扣现价 6(%d → %d)" % [gp0, runner.gp]) - check(int(runner.bonus.get("atk", -1)) == 1 and int(runner.bonus.get("max_hp", -1)) == 0 and int(runner.bonus.get("armor_class", -1)) == 0, + check(int(runner.run_ledger.bonus.get("atk", -1)) == 1 and int(runner.run_ledger.bonus.get("max_hp", -1)) == 0 and int(runner.run_ledger.bonus.get("armor_class", -1)) == 0, # 迭代 27 T3——加算区住 RunLedger "统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)") by_id = _shop_products_by_id(runner.shop_products()) check(int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1, @@ -4513,10 +4516,10 @@ func _smoke_shop_pool() -> void: var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"} var gp0: int = int(r8.gp) var taken0: int = int(a8._curio_taken.get(kind, 0)) - var val0: int = int(a8.bonus.get(String(val_map[kind]), -999)) + var val0: int = int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) check(r8.buy_pool_item(kind), "购买受理(pool 口——%s|%s)" % [kind, tier]) check(int(r8.gp) == gp0 - int(buy_c.get("price", 0)), "钱包扣现价(%d → %d)" % [gp0, int(r8.gp)]) - check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)), + check(int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)), "加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))]) check(int(a8._curio_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))]) # A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式) @@ -5194,7 +5197,7 @@ func _meta_settle_and_workshop() -> void: var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12)) check(a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}), "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕") - check(int(a12.attune_charges.get("incense_stick", -1)) == 1 and int(a12.attune_charges.get("sky_eye", -1)) == 1, + check(int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1, # 迭代 27 T3——充能账住 RunLedger "充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)") a12._attunement = [] a12._attune_refresh_x = 1 @@ -5402,10 +5405,10 @@ func _smoke_tree_rules() -> void: "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)") # 按关重置清零点(adventure_runner._start_current_level) var ar = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(777003)) - ar.adventure_counters = {"tree_sense_sharp_used": 2, "tree_sense_guard_used": 1, "unrelated_key": 9} + ar.run_ledger.adventure_counters = {"tree_sense_sharp_used": 2, "tree_sense_guard_used": 1, "unrelated_key": 9} # 迭代 27 T3——计数账住 RunLedger ar._reset_tree_limited_counters() - check(not ar.adventure_counters.has("tree_sense_sharp_used") and not ar.adventure_counters.has("tree_sense_guard_used") - and int(ar.adventure_counters.get("unrelated_key", 0)) == 9, + check(not ar.run_ledger.adventure_counters.has("tree_sense_sharp_used") and not ar.run_ledger.adventure_counters.has("tree_sense_guard_used") + and int(ar.run_ledger.adventure_counters.get("unrelated_key", 0)) == 9, "限次按关重置:_start_current_level 清零点恰两键(他键零扰动——生命周期四态闭合)") # 财富之道·散:商品标价折价(level_runner 计价单点) var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004)) diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index a0294613..1415c6d0 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -622,8 +622,13 @@ func _run_modal_probes_once() -> void: _problems.append("轮内刷新后相位丢失(%s)" % main.run_scope.settlement.action_economy.phase) if main.run_scope.settlement.minesweeper.current_board() == board_before: _problems.append("轮内刷新未当场换图") - if main.run_scope.refresh_used_round != 1: - _problems.append("刷新轮内计数错误(%d)" % main.run_scope.refresh_used_round) + # V5(迭代 27 T3——刷新双账收口):轮内计数单源=结算快照(main 副本已删)+ HUD 余量一致 + var used_now: int = int(main.run_scope.settlement.presentation_snapshot().get("refresh_used", 0)) + if used_now != 1: + _problems.append("刷新轮内计数错误(%d)" % used_now) + var remain_expect: int = maxi(0, int(main.db.action_economy.refresh_cap_per_round) - used_now) + if not main.ap_bar.refresh_remaining_text().contains(str(remain_expect)): + _problems.append("HUD 刷新余量与快照推导失符(HUD='%s' 应含 %d)" % [main.ap_bar.refresh_remaining_text(), remain_expect]) func _play_one_action() -> void: diff --git a/game/smoke/ui_probe.gd b/game/smoke/ui_probe.gd index 22e05fcf..095c957e 100644 --- a/game/smoke/ui_probe.gd +++ b/game/smoke/ui_probe.gd @@ -212,12 +212,9 @@ func _ready() -> void: break main.board_view.set_desaturated(true) main.board_view.set_desaturated(false) - # ---- GP 终局行(迭代 6 T7 卡⑩):影子账清零转存+胜利面板 GP 行呈现(C5:GP 行只上冒险终局面板)---- + # ---- GP 终局行(迭代 6 T7 卡⑩):summary gp_final 键取数+胜利面板 GP 行呈现(C5:GP 行只上冒险终局面板; + # 迭代 27 T3——影子账随批退役,原清零转存断言换代为 summary 键断言)---- # (置于战斗态断言之后——合成收束会灰化站桩并清 settlement 引用,具污染性) - main._adventure_gp_view = 57 - main._on_wallet_changed("clear", 0, {}) - if int(main._adventure_gp_final) != 57 or int(main._adventure_gp_view) != 0: - problems.append("GP 影子账清零转存失败(final=%s view=%s)" % [str(main._adventure_gp_final), str(main._adventure_gp_view)]) var meta_saved = main.meta.save for p2 in ["user://smoke_ui_probe_settle_scratch.json", "user://smoke_ui_probe_settle_scratch.json.tmp"]: if FileAccess.file_exists(p2): @@ -225,7 +222,7 @@ func _ready() -> void: main.meta.save = load("res://core/save_manager.gd").new("user://smoke_ui_probe_settle_scratch.json") # 合成摘要不落隔离档(settle_adventure 写盘改道 scratch——跨跑自污染断根),GP 行只验呈现 main._on_adventure_finished({"result": "completed", "cleared": true, "levels_cleared": 3, - "stopped_at": 0, "keystone_total": 30, "master_seed": 42}) + "stopped_at": 0, "keystone_total": 30, "master_seed": 42, "gp_final": 57}) main.meta.save = meta_saved if not main._end_overlay.is_open(): problems.append("冒险终局面板未开(合成摘要路径)") diff --git a/game/tests/unit/core/feat_vouchers_test.gd b/game/tests/unit/core/feat_vouchers_test.gd index 05f59316..9c3bf2f8 100644 --- a/game/tests/unit/core/feat_vouchers_test.gd +++ b/game/tests/unit/core/feat_vouchers_test.gd @@ -58,8 +58,8 @@ func _init() -> void: Kit.check_quiet(not a.choose_battery_target("incense_stick"), "电池反例:件不在装配集拒绝(禁自动替选——零副作用)〔专长批 V9·秘宝批换主语〕") a._attunement = ["incense_stick"] # 注入装配集(incense_stick=新目录 active_charged) - a.attune_charges = {"incense_stick": 1} - Kit.check_quiet(a.choose_battery_target("incense_stick") and int(a.attune_charges["incense_stick"]) == 2, + a.run_ledger.attune_charges = {"incense_stick": 1} # 迭代 27 T3——四账住 RunLedger + Kit.check_quiet(a.choose_battery_target("incense_stick") and int(a.run_ledger.attune_charges["incense_stick"]) == 2, "电池正例:玩家点选 incense_stick→充能账 +1(跨层写 attune_charges)〔专长批 V9·秘宝批换主语〕") Kit.check_quiet(a.battery_pending() == 0, "电池:分配后待账清零〔专长批 V9〕") @@ -82,14 +82,14 @@ func _init() -> void: # V10-2 行动点上限归属性轴:ap kind 走加算区第四轴 a._curio_deck = [{"kind": "ap", "tier": "rare"}] a._curio_pending = {"candidates": a._curio_deck.duplicate()} - var ap_bonus_before := int(a.bonus.get("ap", 0)) - Kit.check_quiet(a.pick_curio("ap", "rare") and int(a.bonus.get("ap", 0)) == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")), + var ap_bonus_before := int(a.run_ledger.bonus.get("ap", 0)) + Kit.check_quiet(a.pick_curio("ap", "rare") and int(a.run_ledger.bonus.get("ap", 0)) == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")), "行动点上限归属性轴:ap 兑付写加算区第四轴(bonus.ap 承载)〔专长批 V10〕") # V16——断点扩容往返:新账本随断点快照/续玩回填(breakpoint_resume_test P5 同构) var sm = load("res://core/save_manager.gd").new("user://_feat_vouchers_bp.json") sm.load_from_disk() - a.adventure_counters = {"pain_layers": 3, "twin_digs": 5} + a.run_ledger.adventure_counters = {"pain_layers": 3, "twin_digs": 5} # 迭代 27 T3——四账住 RunLedger a._refresh_stock = 2 a._battery_pending = 1 a.set_save_manager(sm) @@ -100,7 +100,7 @@ func _init() -> void: "V16:新账本三件入断点快照(adventure_counters/refresh_stock/battery_pending)〔专长批 V16〕") var b2 = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new()) Kit.check_quiet(b2.resume_adventure(bp16.duplicate(), {}), "V16 前置:续玩受理") - Kit.check_quiet(int(b2.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2 and b2.battery_pending() == 1, + Kit.check_quiet(int(b2.run_ledger.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2 and b2.battery_pending() == 1, "V16:续玩回填逐项一致(计数恢复——跨战账不丢)〔专长批 V16〕") sm.clear_breakpoint() diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 4bf8880b..627e1a07 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -269,12 +269,13 @@ func _init() -> void: Kit.check_quiet(ob12b.minesweeper.flag_range_free == false, "信标网络反例:未装配旗可达豁免=缺省 false(零装配零扰动)") - # ⑬ 真视使用计数链(受理成功恰计一次——vision_counter 共享 dict) + # ⑬ 真视使用计数链(受理成功恰计一次——RunLedger 单类持账;迭代 27 T3 换代) var rng13 = load("res://core/rng_service.gd").new(); rng13.set_master_seed(321) var ob13 = orch_s.new(db, rng13) - var vc13 := {"used": 0} - Kit.check_quiet(ob13.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["incense_stick"], "vision_counter": vc13}), - "计数链:装配起战(vision_counter 共享 dict 携带)") + var lg13 = load("res://core/run_ledger.gd").new() + lg13.vision_counter = {"used": 0} + Kit.check_quiet(ob13.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["incense_stick"], "ledger": lg13}), + "计数链:装配起战(RunLedger 携带)") var inv13 = load("res://core/treasure_items.gd").new(db) var rec13: Dictionary = inv13.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0) ob13.treasure_inventory = inv13 @@ -282,7 +283,7 @@ func _init() -> void: var tgt13: Vector2i = _first_closed_safe(ob13) if tgt13 != Vector2i(-9, -9): ob13.use_treasure(h13, tgt13) - Kit.check_quiet(int(vc13.get("used", 0)) == 1, + Kit.check_quiet(int(lg13.vision_counter.get("used", 0)) == 1, "计数链正常:受理成功恰 +1(信标网络解锁行为条件数据源)") # ⑭ 装配不可变(迭代 22 秘宝批 T6——V6:冒险全程装配快照三面同源且零写口——