@@ -0,0 +1,650 @@
# 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 ( )
test_stone_mitigation_all_damage ( )
test_rest_rotation_cap_and_clear ( )
test_charge_layers_consumed_same_round ( )
test_blast_defers_to_combat_segment ( )
test_seven_feats_validate_and_pool ( )
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 , voucher_rap : int = 0 , armor : int = 10 , hero_hp : int = 200 ) - > Dictionary :
return { " level_index " : 1 , " node_depth " : 4 , " hero_hp " : hero_hp , " hero_atk " : 8 , " hero_max_hp " : 200 ,
" hero_armor_class " : armor , " hero_ap_bonus " : 0 , " attunement " : [ ] , " feats " : feats , " tree_rules " : [ ] ,
" battle_index " : 1 , " voucher_rap " : voucher_rap , " 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 )
# ---- V6/V8/V9/V7 五件后续效果(迭代 46 卡A T5)----
func _counter ( orch , counter_id : String ) - > int :
return EffectEngineScript . counter ( orch . fx_host ( ) . counters_ctx , counter_id )
# 伤害明细行泛收集(type=damage 全量——按来源类与值断言)
func _collect_damage_rows ( orch ) - > Array :
var rows : Array = [ ]
orch . step_row . connect ( func ( row ) :
if String ( row . get ( " type " , " " ) ) == " damage " :
rows . append ( row ) )
return rows
func _damage_rows_by_source ( rows : Array , source : String ) - > Array :
var out : Array = [ ]
for r_v in rows :
var r := r_v as Dictionary
if String ( r . get ( " source_class " , " " ) ) == source :
out . append ( r )
return out
# V6 石层缓冲:全局固定减伤(危险格 pierce 8→6 两档钉死:单刷减 2·同轮多刷不叠加·未刷新轮不减;
# 地形路径减后值=max(下限, effect_value−2)——含下限保持;敌方路径照常单次发射)
func test_stone_mitigation_all_damage ( ) - > void :
print ( " --- V6 石层缓冲:全局固定减伤 --- " )
# S1 同轮双刷 → 危险格 pierce 8 减 2=6(叠加即 4——多刷不增钉死)
var orch1 = _orch ( 46041 )
var rows1 := _collect_damage_rows ( orch1 )
Kit . check ( " V6 S1 前置:战斗受理 " , orch1 . begin_battle ( _enc ( [ " stone_mitigation " ] ) ) )
_bank_rap ( orch1 , 3 )
Kit . check ( " V6 S1 同轮双刷受理 " , orch1 . player_request_refresh ( ) and orch1 . player_request_refresh ( ) )
Kit . check ( " V6 S1 guard 累计 2(同轮双刷) " , _counter ( orch1 , " round_swap_guard " ) == 2 )
var mine1 := _dig_until ( orch1 , func ( evt ) :
return String ( evt . get ( " kind " , " " ) ) == " mine " and _is_direct_type ( orch1 , evt . cell ) )
Kit . check ( " V6 S1 前置:直接伤型危险格已挖开( %s ) " % str ( mine1 . get ( " cell " , Vector2i ( - 1 , - 1 ) ) ) , not mine1 . is_empty ( ) )
orch1 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var pierce_rows1 := _damage_rows_by_source ( rows1 , " mine_hit " )
Kit . check ( " V6 S1 危险格减伤恰 2 且多刷不叠加(行值 %s ∈[6]——叠加态应为 4)〔换图系批 V6〕 " % str ( pierce_rows1 . map ( func ( r ) : return int ( r . value ) ) ) ,
pierce_rows1 . size ( ) > = 1 and int ( ( pierce_rows1 [ 0 ] as Dictionary ) . get ( " value " , - 1 ) ) == 6 )
# S2 未刷新轮:guard 随 round_start 清空 → 同型危险格不减(pierce/magic 表幂 8——heal 型幂 6 不同锚勿混)
var mine2 := _dig_until ( orch1 , func ( evt ) :
var snap_v2 = orch1 . reward_grant ( ) . reward_economy . query_full_snapshot ( evt . cell )
return String ( evt . get ( " kind " , " " ) ) == " mine " and snap_v2 is Dictionary \
and [ " pierce " , " magic " ] . has ( String ( ( snap_v2 as Dictionary ) . get ( " cell_type " , " " ) ) ) )
Kit . check ( " V6 S2 前置:第二直接伤型危险格已挖开( %s ) " % str ( mine2 . get ( " cell " , Vector2i ( - 1 , - 1 ) ) ) , not mine2 . is_empty ( ) )
orch1 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var pierce_rows2 := _damage_rows_by_source ( rows1 , " mine_hit " )
Kit . check ( " V6 S2 未刷新轮不减伤(新轮行值 %s 含 8——round 桶轮始清空)〔换图系批 V6〕 " % str ( pierce_rows2 . map ( func ( r ) : return int ( r . value ) ) ) ,
pierce_rows2 . size ( ) > = 2 and int ( ( pierce_rows2 [ pierce_rows2 . size ( ) - 1 ] as Dictionary ) . get ( " value " , - 1 ) ) == 8 )
# S3 地形路径:减后值 = max(下限, effect_value− 2)——下限保持与定值减一并钉死
# (种子 46002=V5② 已证含 flat_damage 变异板;刷新换板后变异重布—— digging guard 内可达)
var orch3 = _orch ( 46002 )
var rows3 := _collect_damage_rows ( orch3 )
Kit . check ( " V6 S3 前置:战斗受理 " , orch3 . begin_battle ( _enc ( [ " stone_mitigation " ] ) ) )
_bank_rap ( orch3 , 2 )
Kit . check ( " V6 S3 单刷受理 " , orch3 . player_request_refresh ( ) )
var terr3 := _dig_until ( orch3 , 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 ( " V6 S3 前置:伤害型变异格已挖开( %s ) " % str ( terr3 . get ( " cell " , Vector2i ( - 1 , - 1 ) ) ) , not terr3 . is_empty ( ) )
orch3 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var terrain_rows := _damage_rows_by_source ( rows3 , " 地形变异 " )
var floor_v : int = int ( _db . combat . settlement . damage_roll . damage_floor )
var ev_ok := true
for tr_v in terrain_rows :
var tr := tr_v as Dictionary
var vid := String ( tr . get ( " variant_id " , " " ) )
var ev : int = int ( _db . combat . variation . row_by_id ( vid ) . get ( " effect_value " , 0 ) )
if int ( tr . get ( " value " , - 1 ) ) != maxi ( floor_v , ev - 2 ) :
ev_ok = false
Kit . check ( " V6 S3 地形减伤=max(下限,effect_value− 2) 逐单闭合( %d 单)〔换图系批 V6〕 " % terrain_rows . size ( ) ,
terrain_rows . size ( ) > = 1 and ev_ok )
# V9 坑道小憩:轮末合计回复(白盒预置承载上限 3——计数器直改属数据面注入;
# 自然累计 2 与满血未生效两形态分战场钉死)
func test_rest_rotation_cap_and_clear ( ) - > void :
print ( " --- V9 坑道小憩:轮末恢复 --- " )
# R1 白盒预置 3 层 + 刷新闸(when<3 拒第 4 层)→ 停手回复 min(3, 缺血)——护甲降档保缺血 ≥3
var orch1 = _orch ( 46051 )
var rows1 : Array = [ ]
orch1 . step_row . connect ( func ( row ) : rows1 . append ( row ) )
Kit . check ( " V9 R1 前置:战斗受理 " , orch1 . begin_battle ( _enc ( [ " tunnel_rest " ] , 0 , 10 , 100 ) ) )
_bank_rap ( orch1 , 1 ) # 开局血差 100( hero_hp 100/max 200)——缺血 ≥3 恒成立,回复面确定
var missing : int = 200 - int ( orch1 . _enemy_system . hero_unit ( ) . get ( " hp " , 0 ) )
EffectEngineScript . counter_add ( orch1 . fx_host ( ) . counters_ctx , " round_swap_rest " , 3 )
Kit . check ( " V9 R1 预置 3 层后刷新被闸(when<3——上限 3) " , orch1 . player_request_refresh ( ) and _counter ( orch1 , " round_swap_rest " ) == 3 )
orch1 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var heal_rows1 : Array = [ ]
var idle_rows1 := 0
for r_v in rows1 :
var r := r_v as Dictionary
if String ( r . get ( " type " , " " ) ) == " heal " and String ( r . get ( " source_class " , " " ) ) == " fx_board_refresh " :
heal_rows1 . append ( r )
if String ( r . get ( " type " , " " ) ) == " reward_ineffective " and String ( r . get ( " batch_key " , " " ) ) == " round_swap_rest " :
idle_rows1 + = 1
Kit . check ( " V9 R1 轮末回复=min(3,缺血 %d ) 且层清 0( heal 行值 %s )〔换图系批 V9〕 " % [ missing , str ( heal_rows1 . map ( func ( r ) : return int ( r . value ) ) ) ] ,
missing > = 3 and heal_rows1 . size ( ) == 1 and int ( ( heal_rows1 [ 0 ] as Dictionary ) . get ( " value " , - 1 ) ) == 3 and _counter ( orch1 , " round_swap_rest " ) == 0 )
# R2 满血未生效:首轮刷新后停手——授予段 heal 时点主角仍满血(combat 段还手在其后)
var orch2 = _orch ( 46052 )
var rows2 : Array = [ ]
orch2 . step_row . connect ( func ( row ) : rows2 . append ( row ) )
Kit . check ( " V9 R2 前置:战斗受理 " , orch2 . begin_battle ( _enc ( [ " tunnel_rest " ] , 8 ) ) )
Kit . check ( " V9 R2 首轮刷新受理(voucher_rap 入池) " , orch2 . player_request_refresh ( ) )
orch2 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var heal_rows2 := 0
var idle2 := 0
for r2_v in rows2 :
var r2 := r2_v as Dictionary
if String ( r2 . get ( " type " , " " ) ) == " heal " and String ( r2 . get ( " source_class " , " " ) ) == " fx_board_refresh " :
heal_rows2 + = 1
if String ( r2 . get ( " type " , " " ) ) == " reward_ineffective " and String ( r2 . get ( " batch_key " , " " ) ) == " round_swap_rest " :
idle2 + = 1
Kit . check ( " V9 R2 满血未生效行在案且零 heal 行(未生效 %d ·heal %d )〔换图系批 V9〕 " % [ idle2 , heal_rows2 ] ,
idle2 == 1 and heal_rows2 == 0 and _counter ( orch2 , " round_swap_rest " ) == 0 )
# R3 自然累计:同轮双刷 → 回复 2(上限 3 的闸面由 R1 白盒钉住,此处证自然路径)
var orch3 = _orch ( 46053 )
var rows3 : Array = [ ]
orch3 . step_row . connect ( func ( row ) : rows3 . append ( row ) )
Kit . check ( " V9 R3 前置:战斗受理 " , orch3 . begin_battle ( _enc ( [ " tunnel_rest " ] ) ) )
_bank_rap ( orch3 , 2 )
Kit . check ( " V9 R3 同轮双刷受理 " , orch3 . player_request_refresh ( ) and orch3 . player_request_refresh ( ) )
Kit . check ( " V9 R3 自然累计 2 层 " , _counter ( orch3 , " round_swap_rest " ) == 2 )
orch3 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var heal3 := 0
for r3_v in rows3 :
if String ( ( r3_v as Dictionary ) . get ( " type " , " " ) ) == " heal " and String ( ( r3_v as Dictionary ) . get ( " source_class " , " " ) ) == " fx_board_refresh " :
heal3 + = 1
Kit . check ( " V9 R3 轮末 heal 行值 2(自然累计路径)〔换图系批 V9〕 " ,
heal3 == 1 and _counter ( orch3 , " round_swap_rest " ) == 0 )
# V8 蓄力周转:当轮攻击消费全部层数(上限 3 闸=白盒预置;战斗新建自然清空)
func test_charge_layers_consumed_same_round ( ) - > void :
print ( " --- V8 蓄力周转:攻击消费层 --- " )
var orch1 = _orch ( 46061 )
var rows1 := _collect_damage_rows ( orch1 )
Kit . check ( " V8 前置:战斗受理 " , orch1 . begin_battle ( _enc ( [ " charge_rotation " ] , 16 ) ) )
# 上限 3 闸:白盒预置 3 层后刷新被拒层(when<3)
EffectEngineScript . counter_add ( orch1 . fx_host ( ) . counters_ctx , " swap_charge_layers " , 3 )
Kit . check ( " V8 上限 3 闸(预置 3 刷新不加层) " , orch1 . player_request_refresh ( ) and _counter ( orch1 , " swap_charge_layers " ) == 3 )
# 当轮攻击消费:停手 → 主角先手攻击 → 层数全消费清零
orch1 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var attack_rows := 0
for r_v in rows1 :
if String ( ( r_v as Dictionary ) . get ( " source_class " , " " ) ) == " hero_attack " :
attack_rows + = 1
Kit . check ( " V8 当轮攻击消费全部层(攻击行在案 %d ·层清 0)〔换图系批 V8〕 " % attack_rows ,
attack_rows > = 1 and _counter ( orch1 , " swap_charge_layers " ) == 0 )
# 自然累计:双刷 2 层(voucher_rap 16 入池)→ 停手消费归零
var orch2 = _orch ( 46062 )
Kit . check ( " V8 前置:战斗受理 " , orch2 . begin_battle ( _enc ( [ " charge_rotation " ] , 16 ) ) )
Kit . check ( " V8 战斗新建层 0(引擎重建自然清空) " , _counter ( orch2 , " swap_charge_layers " ) == 0 )
Kit . check ( " V8 双刷累计 2 层 " , orch2 . player_request_refresh ( ) and orch2 . player_request_refresh ( ) and _counter ( orch2 , " swap_charge_layers " ) == 2 )
orch2 . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
Kit . check ( " V8 攻击后层清 0(消费面闭合)〔换图系批 V8〕 " , _counter ( orch2 , " swap_charge_layers " ) == 0 )
# V7 崩壁冲击:延迟群伤单(刷新时点零伤害行——战斗段末按存活目标结账)
func test_blast_defers_to_combat_segment ( ) - > void :
print ( " --- V7 崩壁冲击:延迟群伤 --- " )
var orch = _orch ( 46071 )
var rows := _collect_damage_rows ( orch )
Kit . check ( " V7 前置:战斗受理 " , orch . begin_battle ( _enc ( [ " wall_blast " ] , 8 ) ) )
Kit . check ( " V7 刷新受理(voucher_rap 入池) " , orch . player_request_refresh ( ) )
Kit . check ( " V7 刷新时点零伤害行(授予段不结账——案C 延迟)〔换图系批 V7〕 " ,
_damage_rows_by_source ( rows , " fx_board_refresh " ) . is_empty ( ) )
orch . minesweeper . settle_batch ( [ { " action " : " stop " } ] )
var blast_rows := _damage_rows_by_source ( rows , " fx_board_refresh " )
Kit . check ( " V7 战斗段末群伤单恰一单(全体存活敌 ×2——行值 2 count≥1)〔换图系批 V7〕 " ,
blast_rows . size ( ) == 1 and int ( ( blast_rows [ 0 ] as Dictionary ) . get ( " value " , - 1 ) ) == 2
and int ( ( blast_rows [ 0 ] as Dictionary ) . get ( " count " , 0 ) ) > = 1 )
# ---- V11 数据与池(迭代 46 卡A T6) ----
# 七件 validate/在册/稀有度 cap 对表/跨表计数器引用闭合/desc 禁词
func test_seven_feats_validate_and_pool ( ) - > void :
print ( " --- V11 七件数据与池 --- " )
var seven := { " vein_reserve " : " excellent " , " easy_swap " : " excellent " , " wall_blast " : " excellent " ,
" charge_rotation " : " excellent " , " stone_mitigation " : " common " , " tunnel_rest " : " common " , " mine_profit " : " rare " }
Kit . check ( " V11 feats.validate 零违例(七行全过词表/行键白名单/前缀校验)〔换图系批 V11〕 " ,
_db . crossrun . feats . validate ( ) . is_empty ( ) )
var all_in := true
var desc_bad := 0
for fid in seven . keys ( ) :
var row : Dictionary = _db . crossrun . feats . row_by_id ( String ( fid ) )
if row . is_empty ( ) or String ( row . get ( " rarity " , " " ) ) != String ( seven [ fid ] ) or int ( row . get ( " cap " , 0 ) ) != 1 :
all_in = false
if String ( row . get ( " desc " , " " ) ) . contains ( " 雷 " ) or String ( row . get ( " desc " , " " ) ) . contains ( " 旗 " ) :
desc_bad + = 1
Kit . check ( " V11 七行在册且稀有度/cap 逐件对表〔换图系批 V11〕 " , all_in )
Kit . check ( " V11 七件 desc 禁词零命中(雷/旗字面)〔换图系批 V11〕 " , desc_bad == 0 )
# 跨表计数器引用闭合(判据域=换图系七件自身:value_counter/when-id 读取全部有 counter_add 写入方;
# 预存行的消费点记账形态〔如完美保险 insurance_used 由授予段落账〕不在此判据域)
var written := { }
for row_v in ( _db . crossrun . feats . rows as Array ) :
for tv in ( ( row_v as Dictionary ) . get ( " triggers " , [ ] ) as Array ) :
var do_d : Dictionary = ( tv as Dictionary ) . get ( " do " , { } )
if String ( do_d . get ( " kind " , " " ) ) == " counter_add " :
written [ String ( do_d . get ( " counter_id " , " " ) ) ] = true
var refs_ok := true
for fid2 in seven . keys ( ) :
var row2 : Dictionary = _db . crossrun . feats . row_by_id ( String ( fid2 ) )
for tv2 in ( ( row2 . get ( " triggers " , [ ] ) as Array ) ) :
var tr2 := tv2 as Dictionary
var do2 : Dictionary = tr2 . get ( " do " , { } )
for reader in [ String ( do2 . get ( " value_counter " , " " ) ) , String ( do2 . get ( " value_from " , " " ) ) ] :
if reader != " " and not written . has ( reader ) :
refs_ok = false
for c_v in ( tr2 . get ( " when " , [ ] ) as Array ) :
var cid := String ( ( c_v as Dictionary ) . get ( " id " , " " ) )
if ( c_v as Dictionary ) . get ( " fact " , " " ) == " counter " and cid != " " and not written . has ( cid ) :
refs_ok = false
Kit . check ( " V11 七件跨表计数器引用闭合(value_counter/when-id 全部有 counter_add 写入方)〔换图系批 V11〕 " , refs_ok )
Kit . check ( " V11 换图系三计数器在写入集(swap_charge_layers/round_swap_guard/round_swap_rest)〔换图系批 V11〕 " ,
written . has ( " swap_charge_layers " ) and written . has ( " round_swap_guard " ) and written . has ( " round_swap_rest " ) )