# VERTICAL SLICE - NOT FOR PRODUCTION # 换图系双基座七件专项宿主(迭代 46 卡A——对照表 V1–V12 按轮落地:V5=T2 全局受伤链路、 # V2/V3=T3 两基座、V4/V10=T4 成功事实与 GP、V6–V9=T5 五件后续、V11=T6 数据与池; # V12 无专长基线回归归 round_flow/seal_polarity 既有断言承载)。 # 临时承伤计数专长经现行 ConfigDB 注入(表行追加+持有集传 id——正常数据配置路径,卡片 §12); # 真实入口驱动:AutoPlayer 盲挖循环+停手/封印动作;判据绑可观测提交面(damage_taken 命中计数器 # 与 target=hero 明细行)。 # 运行:godot --headless --path game --script res://tests/unit/battle_settlement/refresh_feats_test.gd(expect 0) extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const ConfigDBScript = preload("res://core/config_db.gd") const EffectEngineScript = preload("res://core/effect_engine.gd") const AutoPlayer := preload("res://sim/auto_player.gd") # 直接伤型资源类型(负向极性兑付=主角直伤单据的三型——reward 表 pierce/magic/heal 负向行) const DIRECT_TYPES := ["pierce", "magic", "heal"] var _db func _init() -> void: _db = ConfigDBScript.new() if not _db.load_tables("res://data/tables", true): print("[refresh_feats_test] FAIL — 数值表加载失败") quit(1) return _install_dt_probe() test_all_hero_damage_paths_emit_once() test_no_duplicate_damage_taken_event() test_reserve_on_accepted_dig() test_refresh_discount_gate_and_charge() test_double_base_stack() test_no_feats_baseline() test_board_refresh_fact_only_on_success() test_mine_profit_gp() quit(Kit.finish("refresh_feats_test")) # ---- 夹具族(真实入口驱动——settle_batch/stop/封印动作;直构只用于编排器受理)---- # 临时承伤计数专长(damage_taken → counter_add battle 桶——每张主角承伤单据恰 +1) func _install_dt_probe() -> void: if _db.crossrun.feats.row_by_id("probe_dt_counter").is_empty(): (_db.crossrun.feats.rows as Array).append({ "feat_id": "probe_dt_counter", "display_name": "承伤计数探针", "rarity": "common", "desc": "测试夹具:每次主角承伤计数 +1", "triggers": [{"on": "damage_taken", "do": {"kind": "counter_add", "counter_id": "dt_probe_hits", "value": 1}}], }) func _orch(seed_v: int): var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(seed_v) return load("res://core/battle_orchestrator.gd").new(_db, rng) func _enc(feats: Array) -> Dictionary: return {"level_index": 1, "node_depth": 4, "hero_hp": 200, "hero_atk": 8, "hero_max_hp": 200, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": [], "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}} func _probe_count(orch) -> int: return EffectEngineScript.counter(orch.fx_host().counters_ctx, "dt_probe_hits") # 主角承伤明细行收集(type=damage 且 target=hero——敌方技与棋盘主角直伤两类单据的行面) func _collect_hero_rows(orch) -> Array: var rows: Array = [] orch.step_row.connect(func(row): if String(row.get("type", "")) == "damage" and String(row.get("target", "")) == "hero": rows.append(row)) return rows # 按来源类分桶计数(rows 收集器的只读后处理) func _count_by_source(rows: Array, source: String) -> int: var n := 0 for r_v in rows: if String((r_v as Dictionary).get("source_class", "")) == source: n += 1 return n # AutoPlayer 盲挖循环直到 cell_revealed 谓词命中(真实挖掘入口;返回 {"cell","evt"} 或空) func _dig_until(orch, pred: Callable) -> Dictionary: var ms = orch.minesweeper var hit := {} ms.cell_revealed.connect(func(evt): if hit.is_empty() and bool(pred.call(evt)): hit["cell"] = evt.cell hit["evt"] = evt) var guard := 40 while hit.is_empty() and guard > 0: guard -= 1 var plan = AutoPlayer.choose_dig(ms) if plan.is_empty(): break if plan.hero_cell != ms.hero_pos(): ms.settle_batch([{"action": "move", "cell": plan.hero_cell}]) ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) return hit # 直接伤型快照判定(未揭示格可查——reward_economy 装板期摇定身份,示警烟火同源读面) func _is_direct_type(orch, c: Vector2i) -> bool: var snap_v = orch.reward_grant().reward_economy.query_full_snapshot(c) return snap_v is Dictionary and DIRECT_TYPES.has(String((snap_v as Dictionary).get("cell_type", ""))) # 可封印的直接伤型安全格扫描(未揭示+非雷+直接伤型+邻接可达 revealed 格——封印动作真实落点位) func _find_sealable_direct(orch) -> Vector2i: var ms = orch.minesweeper var b = ms.current_board() var revealed := {} for c_v in ms.revealed_cells(): revealed[c_v] = true for y in b.height: for x in b.width: var c := Vector2i(x, y) if ms.is_revealed(c) or b.is_mine(c) or ms.has_seal(c) or not _is_direct_type(orch, c): continue for n_v in b.neighbors8(c): var n := Vector2i(n_v) if revealed.has(n) and (ms.compute_move_path(n).size() > 0 or n == ms.hero_pos()): return c return Vector2i(-1, -1) # 盲挖扩板直至出现可封印直接伤型安全格(有界;返回格位或 (-1,-1)) func _seal_direct_with_expansion(orch) -> Vector2i: var c := _find_sealable_direct(orch) var guard := 12 while c.x < 0 and guard > 0: guard -= 1 var plan = AutoPlayer.choose_dig(orch.minesweeper) if plan.is_empty(): break if plan.hero_cell != orch.minesweeper.hero_pos(): orch.minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}]) orch.minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) c = _find_sealable_direct(orch) return c # ---- V5 全局受伤链路(迭代 46 卡A T2)---- # 四条真实路径(敌方攻击/危险格/错误封印/地形)逐路驱动:每条至少一张主角承伤单据在案, # 且承伤计数器 == 主角承伤单据行数(每单据恰发射一次的全量判据——重复发射即计数器>行数)。 func test_all_hero_damage_paths_emit_once() -> void: print("--- V5 全局 damage_taken:四路径逐路恰一次 ---") # ① 敌方攻击路径(Boss 战零挖掘连停三轮——干净样本:零地形零资源单据;多轮保证伤害技在案) var orch1 = _orch(46001) var rows1 := _collect_hero_rows(orch1) Kit.check("V5① 前置:战斗受理", orch1.begin_battle(_enc(["probe_dt_counter"]))) for _i in 3: orch1.minesweeper.settle_batch([{"action": "stop"}]) var enemy_rows := _count_by_source(rows1, "enemy_action") Kit.check("V5① 敌方攻击路径:承伤单据在案且逐单恰一次(行 %d=计数 %d)〔换图系批 V5〕" % [enemy_rows, _probe_count(orch1)], enemy_rows >= 1 and _probe_count(orch1) == enemy_rows) # ② 地形路径(AutoPlayer 真挖至伤害型变异格开出——发射在授予段开单点、单据标 hero_damage_consumed) var orch2 = _orch(46002) var rows2 := _collect_hero_rows(orch2) Kit.check("V5② 前置:战斗受理", orch2.begin_battle(_enc(["probe_dt_counter"]))) var terr := _dig_until(orch2, func(evt): for vid_v in (evt.get("variant_ids", []) as Array): if String(_db.combat.variation.row_by_id(String(vid_v)).get("effect", "")) == "flat_damage": return true return false) Kit.check("V5② 前置:伤害型变异格已挖开(%s)" % str(terr.get("cell", Vector2i(-1, -1))), not terr.is_empty()) orch2.minesweeper.settle_batch([{"action": "stop"}]) var terrain_rows := _count_by_source(rows2, "地形变异") Kit.check("V5② 地形路径:地形单据在案且全单据逐张恰一次(行 %d=计数 %d——盲挖途经单据一并计入)〔换图系批 V5〕" % [rows2.size(), _probe_count(orch2)], terrain_rows >= 1 and _probe_count(orch2) == rows2.size()) # ③ 危险格路径(AutoPlayer 真挖直接伤型危险格——负向直伤单据) var orch3 = _orch(46003) var rows3 := _collect_hero_rows(orch3) Kit.check("V5③ 前置:战斗受理", orch3.begin_battle(_enc(["probe_dt_counter"]))) var mine_hit := _dig_until(orch3, func(evt): return String(evt.get("kind", "")) == "mine" and _is_direct_type(orch3, evt.cell)) Kit.check("V5③ 前置:直接伤型危险格已挖开(%s)" % str(mine_hit.get("cell", Vector2i(-1, -1))), not mine_hit.is_empty()) orch3.minesweeper.settle_batch([{"action": "stop"}]) var danger_rows := _count_by_source(rows3, "mine_hit") Kit.check("V5③ 危险格路径:直伤单据在案且全单据逐张恰一次(行 %d=计数 %d)〔换图系批 V5〕" % [rows3.size(), _probe_count(orch3)], danger_rows >= 1 and _probe_count(orch3) == rows3.size()) # ④ 错误封印路径(扩板→封直接伤型安全格不挖→停手轮末判标错——负向直伤单据) var orch4 = _orch(46004) var rows4 := _collect_hero_rows(orch4) Kit.check("V5④ 前置:战斗受理", orch4.begin_battle(_enc(["probe_dt_counter"]))) var scell := _seal_direct_with_expansion(orch4) Kit.check("V5④ 前置:可封直接伤型安全格在位(%s)" % str(scell), scell.x >= 0) if scell.x >= 0: var plan4 = AutoPlayer.choose_dig(orch4.minesweeper) # 封印动作真实落点:先就位到目标邻接可达格(封印距离同挖掘距离) var seal_plan := _plan_seal(orch4, scell) if not seal_plan.is_empty(): if seal_plan["hero_cell"] != orch4.minesweeper.hero_pos(): orch4.minesweeper.settle_batch([{"action": "move", "cell": seal_plan["hero_cell"]}]) var seal_out: Array = orch4.minesweeper.settle_batch([{"action": "seal_place", "cell": scell}]) Kit.check("V5④ 前置:封印受理", not seal_out.is_empty() and bool((seal_out[0] as Dictionary).get("accepted", false))) orch4.minesweeper.settle_batch([{"action": "stop"}]) var sealwrong_rows := _count_by_source(rows4, "seal_miss") Kit.check("V5④ 错误封印路径:直伤单据在案且全单据逐张恰一次(行 %d=计数 %d)〔换图系批 V5〕" % [rows4.size(), _probe_count(orch4)], sealwrong_rows >= 1 and _probe_count(orch4) == rows4.size()) # 封印落点计划(目标格邻接可达 revealed 位——AutoPlayer._plan_on 同构最小私有形) func _plan_seal(orch, target: Vector2i) -> Dictionary: var ms = orch.minesweeper for n_v in ms.current_board().neighbors8(target): var n := Vector2i(n_v) if ms.is_revealed(n) and (ms.compute_move_path(n).size() > 0 or n == ms.hero_pos()): return {"hero_cell": n} return {} # 同一伤害不得出现两次消费(重复发射=计数器>行数):敌方路径与地形路径两样本逐位核对—— # 地形单据带 hero_damage_consumed 标记,战斗段末 _resolve_board_flat_hero 跳过二次发射由此钉住。 func test_no_duplicate_damage_taken_event() -> void: print("--- V5 全局 damage_taken:无重复消费 ---") var orch1 = _orch(46011) var rows1 := _collect_hero_rows(orch1) Kit.check("ND 前置:战斗受理", orch1.begin_battle(_enc(["probe_dt_counter"]))) for _i in 2: orch1.minesweeper.settle_batch([{"action": "stop"}]) Kit.check("ND 敌方路径零重复(行 %d=计数 %d——重复消费即计数>行)〔换图系批 V5〕" % [rows1.size(), _probe_count(orch1)], rows1.size() >= 1 and _probe_count(orch1) == rows1.size()) var orch2 = _orch(46012) var rows2 := _collect_hero_rows(orch2) Kit.check("ND 前置:战斗受理", orch2.begin_battle(_enc(["probe_dt_counter"]))) var terr2 := _dig_until(orch2, func(evt): for vid_v in (evt.get("variant_ids", []) as Array): if String(_db.combat.variation.row_by_id(String(vid_v)).get("effect", "")) == "flat_damage": return true return false) Kit.check("ND 前置:伤害型变异格已挖开(%s)" % str(terr2.get("cell", Vector2i(-1, -1))), not terr2.is_empty()) orch2.minesweeper.settle_batch([{"action": "stop"}]) Kit.check("ND 地形路径零重复(行 %d=计数 %d——context 标记闸失效即计数翻倍)〔换图系批 V5〕" % [rows2.size(), _probe_count(orch2)], rows2.size() >= 1 and _probe_count(orch2) == rows2.size()) # ---- V2/V3 两件基座(迭代 46 卡A T3)---- # 表驱动停手攒 RAP(每轮 1:1 转存——无转存率订阅缺省) func _bank_rap(orch, rounds_n: int) -> void: for _i in rounds_n: orch.minesweeper.settle_batch([{"action": "stop"}]) # AutoPlayer 单次付费挖格(真实挖掘入口——移位+dig 一动作) func _auto_dig(orch) -> void: var plan = AutoPlayer.choose_dig(orch.minesweeper) if not plan.is_empty(): if plan.hero_cell != orch.minesweeper.hero_pos(): orch.minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}]) orch.minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) # V2 矿脉储能:受理的付费挖格 RAP 恰 +1、AP 按表扣、板资源不变 func test_reserve_on_accepted_dig() -> void: print("--- V2 矿脉储能:付费挖格产 RAP ---") var orch = _orch(46021) Kit.check("V2 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve"]))) _bank_rap(orch, 1) var rap_before: int = orch.action_economy.rap var ap_before: int = orch.action_economy.ap _auto_dig(orch) var dig_cost: int = int(_db.run.action_economy.dig_ap_cost) Kit.check("V2 受理付费挖格 RAP 恰 +1(%d→%d)〔换图系批 V2〕" % [rap_before, orch.action_economy.rap], orch.action_economy.rap == rap_before + 1) Kit.check("V2 AP 按表扣(%d→%d,dig 价 %d)〔换图系批 V2〕" % [ap_before, orch.action_economy.ap, dig_cost], orch.action_economy.ap == ap_before - dig_cost) Kit.check("V2 受理记账闭合:AP 耗尽轮 RAP 增量恰等于受理挖数(AP 消耗数——每受理恰 +1,未受理零入池)〔换图系批 V2〕", _drain_round_rap_accounting(orch)) # V2 辅证:挖到 AP 耗尽——RAP 增量 == 受理挖数(每受理恰 +1;移位耗 AP 不入账故按挖计数) func _drain_round_rap_accounting(orch) -> bool: var rap0: int = orch.action_economy.rap var digs := 0 var guard := 24 while orch.action_economy.phase == "Active" and guard > 0: guard -= 1 var plan = AutoPlayer.choose_dig(orch.minesweeper) if plan.is_empty(): break if plan.hero_cell != orch.minesweeper.hero_pos(): orch.minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}]) if orch.action_economy.phase != "Active": break # 移位耗尽 AP 收轮——本轮不再有受理挖 orch.minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) digs += 1 return digs > 0 and orch.action_economy.rap - rap0 == digs # V3 省力周转:折价门槛与扣费(can 与执行同源;base 8→扣 4) func test_refresh_discount_gate_and_charge() -> void: print("--- V3 省力周转:折价门槛与扣费 ---") var orch = _orch(46022) Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["easy_swap"]))) _bank_rap(orch, 1) var base_cost: int = int(_db.run.action_economy.rap_refresh_cost) var discounted: int = int(floor(float(base_cost) * 50.0 / 100.0)) var rap0: int = orch.action_economy.rap Kit.check("V3 门同源:can_refresh 与折后价同源(RAP %d ≥ 折后 %d)〔换图系批 V3〕" % [rap0, discounted], orch.can_refresh()) Kit.check("V3 第 1 次刷新受理", orch.player_request_refresh()) Kit.check("V3 折后单次扣费(%d→%d,−%d)〔换图系批 V3〕" % [rap0, orch.action_economy.rap, discounted], orch.action_economy.rap == rap0 - discounted) Kit.check("V3 第 2 次刷新受理(RAP=%d)" % orch.action_economy.rap, orch.player_request_refresh()) Kit.check("V3 两次折后扣费累计(RAP=%d=%d−2×%d)〔换图系批 V3〕" % [orch.action_economy.rap, rap0, discounted], orch.action_economy.rap == rap0 - 2 * discounted) Kit.check("V3 门槛拒:RAP 不足折后价零受理(can=false 且执行拒)〔换图系批 V3〕", orch.action_economy.rap < discounted and not orch.can_refresh() and not orch.player_request_refresh()) # V3 双基座叠加:挖格产 RAP 与刷新折价同场生效 func test_double_base_stack() -> void: print("--- V3 双基座叠加 ---") var orch = _orch(46023) Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve", "easy_swap"]))) _bank_rap(orch, 1) var rap_before: int = orch.action_economy.rap var base_cost: int = int(_db.run.action_economy.rap_refresh_cost) _auto_dig(orch) Kit.check("V3 双基座:挖格产 RAP +1(%d→%d)〔换图系批 V3〕" % [rap_before, orch.action_economy.rap], orch.action_economy.rap == rap_before + 1) var rap_after_dig: int = orch.action_economy.rap Kit.check("V3 双基座:刷新按折后价扣(−%d)〔换图系批 V3〕" % int(base_cost / 2), orch.player_request_refresh() and orch.action_economy.rap == rap_after_dig - int(base_cost / 2)) # V12(refresh_feats 侧):无专长基线——挖格零产 RAP、刷新按表基线扣、AP 保留、上限照旧 func test_no_feats_baseline() -> void: print("--- V12 无专长基线(refresh_feats 侧) ---") var orch = _orch(46024) Kit.check("V12 前置:战斗受理", orch.begin_battle(_enc([]))) _bank_rap(orch, 3) var rap_before: int = orch.action_economy.rap var ap_before: int = orch.action_economy.ap var base_cost: int = int(_db.run.action_economy.rap_refresh_cost) _auto_dig(orch) Kit.check("V12 无基座:挖格零产 RAP(%d 不变)〔换图系批 V12〕" % rap_before, orch.action_economy.rap == rap_before) Kit.check("V12 刷新受理并按表基线扣(−%d)〔换图系批 V12〕" % base_cost, orch.player_request_refresh() and orch.action_economy.rap == rap_before - base_cost) Kit.check("V12 AP 保留、相位 Active(基线刷新不重置)〔换图系批 V12〕", orch.action_economy.ap == ap_before - int(_db.run.action_economy.dig_ap_cost) and orch.action_economy.phase == "Active") Kit.check("V12 每轮上限照旧:第 3 次拒(RAP 足额仍拒)〔换图系批 V12〕", orch.player_request_refresh() and not orch.player_request_refresh()) # ---- V4/V10 成功刷新事实与命中施加(迭代 46 卡A T4)---- # 成功刷新事实探针专长安装(board_refreshed → counter_add——成功事实的可观测计数面) func _install_br_probe() -> void: if _db.crossrun.feats.row_by_id("probe_br_counter").is_empty(): (_db.crossrun.feats.rows as Array).append({ "feat_id": "probe_br_counter", "display_name": "刷新事实探针", "rarity": "common", "desc": "测试夹具:每次成功刷新计数 +1", "triggers": [{"on": "board_refreshed", "do": {"kind": "counter_add", "counter_id": "br_probe_hits", "value": 1}}], }) func _br_count(orch) -> int: return EffectEngineScript.counter(orch.fx_host().counters_ctx, "br_probe_hits") # V4 成功事实只在新板就位后发射:成功恰一次;非 Active/每轮上限/RAP 不足三拒面全零事实零换图 func test_board_refresh_fact_only_on_success() -> void: print("--- V4 成功刷新事实:拒绝零事实 ---") _install_br_probe() var orch = _orch(46031) var swaps := {"n": 0} var waiting_rejected := {"v": false} orch.board_changed.connect(func(): swaps["n"] += 1) # 非 Active 探针(round_flow 同款:借衰减行在结算段内同步探测一次——Waiting 相位刷新必拒) orch.step_row.connect(func(row): if String(row.get("type", "")) == "decay" and not bool(waiting_rejected["v"]): waiting_rejected["v"] = not orch.player_request_refresh()) Kit.check("V4 前置:战斗受理", orch.begin_battle(_enc(["probe_br_counter"]))) _bank_rap(orch, 3) # RAP=3×AP;首轮结算衰减行触发 Waiting 探针 var swaps0: int = swaps["n"] Kit.check("V4 非 Active 拒零事实(结算段内探测拒绝)〔换图系批 V4〕", bool(waiting_rejected["v"]) and _br_count(orch) == 0 and int(swaps["n"]) == swaps0) # 成功 ×2:事实计数与换图信号逐次 +1 Kit.check("V4 第 1 次成功(事实 +1 换图 +1)〔换图系批 V4〕", orch.player_request_refresh() and _br_count(orch) == 1 and int(swaps["n"]) == swaps0 + 1) Kit.check("V4 第 2 次成功(事实 +1 换图 +1)〔换图系批 V4〕", orch.player_request_refresh() and _br_count(orch) == 2 and int(swaps["n"]) == swaps0 + 2) # 每轮上限拒:used=2 满门、RAP 足额——零事实零换图 Kit.check("V4 上限拒零事实(RAP %d 足额仍拒)〔换图系批 V4〕" % orch.action_economy.rap, not orch.can_refresh() and not orch.player_request_refresh() and _br_count(orch) == 2 and int(swaps["n"]) == swaps0 + 2) # RAP 不足拒:停手进新轮(used 重置、停手再入 RAP)→ 消费至 RAP 0 → 拒(零事实零换图; # 纯 RAP 不足拒绝面已由 V3 门槛拒单测钉住,此处并拦形态) orch.minesweeper.settle_batch([{"action": "stop"}]) Kit.check("V4 新轮受理两次(used 重置、RAP 复入)〔换图系批 V4〕", orch.action_economy.phase == "Active" and orch.player_request_refresh() and orch.player_request_refresh() and _br_count(orch) == 4) Kit.check("V4 RAP 不足拒零事实(RAP %d < 价 %d)〔换图系批 V4〕" % [orch.action_economy.rap, int(_db.run.action_economy.rap_refresh_cost)], orch.action_economy.rap < int(_db.run.action_economy.rap_refresh_cost) and not orch.can_refresh() and not orch.player_request_refresh() and _br_count(orch) == 4 and int(swaps["n"]) == swaps0 + 4) # V10 矿道红利:每次成功刷新 escrow 恰 +1 且 fx_grant_gp 行在案;拒绝零变化 func test_mine_profit_gp() -> void: print("--- V10 矿道红利:刷新固定 GP ---") var orch = _orch(46032) var gp_rows := {"n": 0} orch.step_row.connect(func(row): if String(row.get("type", "")) == "effect" and String(row.get("effect_id", "")) == "fx_grant_gp": gp_rows["n"] += 1) Kit.check("V10 前置:战斗受理", orch.begin_battle(_enc(["mine_profit"]))) _bank_rap(orch, 2) var escrow0: int = orch.reward_grant().escrow Kit.check("V10 第 1 次成功 escrow +1(%d→%d)〔换图系批 V10〕" % [escrow0, orch.reward_grant().escrow], orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 1) Kit.check("V10 fx_grant_gp 行在案(%d 行)〔换图系批 V10〕" % int(gp_rows["n"]), int(gp_rows["n"]) == 1) Kit.check("V10 第 2 次成功 escrow +1(%d→%d)〔换图系批 V10〕" % [orch.reward_grant().escrow, orch.reward_grant().escrow + 1], orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 2) # 拒绝零变化:RAP 不足 Kit.check("V10 拒绝零变化(escrow=%d)〔换图系批 V10〕" % orch.reward_grant().escrow, not orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 2 and int(gp_rows["n"]) == 2)