422 lines
22 KiB
GDScript
422 lines
22 KiB
GDScript
# 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)
|