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