refactor(迭代27 T3): 波三 账本收口——RunLedger 单类持四账+刷新双账删副本+gp_final 键删影子账——逐位一致对拍全同

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%
This commit is contained in:
DKChan committed 2026-10-14 23:25:45 +08:00
1 parent 26301baa9c
commit 6fd7d782cc
16 files changed
+174 -183

No files matched your search

+26 -29
View File
@@ -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——电池待分配充能随断点
+14 -25
View File
@@ -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——快照消费方直改不渗入状态容器)
+21 -31
View File
@@ -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
+51
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
uid://7afr7e1gtrj5