139 lines
7.2 KiB
GDScript
139 lines
7.2 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 奖励未生效行 reason 结构化断言(结算期文字列表退役批 T2——日志零文案后未生效原因的分析判读面):
|
||
# 四类合法例外(满层拒收 / 满血治疗 / 无存活敌人 / 主角已倒下)的真实结算行各带结构化 reason 键,
|
||
# 取值逐一命中 full_layers / full_hp / no_enemy_alive / hero_down(分析工具按此四值分类——
|
||
# 旧 text 子串判读退役后的替代判据面)。
|
||
# 真实入口:真实板格身份快照(query_full_snapshot)→ grant_routed 正向路由 → 轮末结账;
|
||
# 两处受控例外直改(对照表申报:无公开 setter 可钉)——hero hp=0(倒下因)与敌 hp=1(先手清场因)。
|
||
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/reward_ineffective_reason_test.gd(expect 0)
|
||
extends SceneTree
|
||
|
||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||
const ConfigDBScript := preload("res://core/config_db.gd")
|
||
const RewardTable := preload("res://data/schemas/reward_table.gd")
|
||
|
||
var _db
|
||
|
||
|
||
func _init() -> void:
|
||
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:80 奖励未生效行=满层拒收、满血治疗等合法例外一行
|
||
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:87 显示名等玩家可见文案是数据面字段(日志消费端零文案——reason 结构化替代)
|
||
_db = ConfigDBScript.new()
|
||
if not _db.load_tables("res://data/tables", true):
|
||
print("[reward_ineffective_reason_test] FAIL — 数值表加载失败")
|
||
quit(1)
|
||
return
|
||
_assert_four_reasons()
|
||
quit(Kit.finish("reward_ineffective_reason_test"))
|
||
|
||
|
||
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)
|
||
|
||
|
||
# 真实板扫描:取某 cell_type 的首个非封锁格身份快照(seal_polarity 负向直验同款形态)
|
||
func _snap_of_type(settlement, ctype: String):
|
||
var board = settlement.minesweeper.current_board()
|
||
var r0 := {}
|
||
for c in board.compute_r0():
|
||
r0[c] = true
|
||
for y in board.height:
|
||
for x in board.width:
|
||
var c := Vector2i(x, y)
|
||
if r0.has(c):
|
||
continue
|
||
var ident = settlement.query_reward_identity(c)
|
||
if ident != null and String(ident.cell_type) == ctype \
|
||
and String(ident.cell_type) != RewardTable.TREASURE_ITEM_TYPE:
|
||
return settlement._reward_economy.query_full_snapshot(c)
|
||
return null
|
||
|
||
|
||
func _ineffective_rows(rows: Array) -> Array:
|
||
var out: Array = []
|
||
for r in rows:
|
||
if String(r.get("type", "")) == "reward_ineffective":
|
||
out.append(r)
|
||
return out
|
||
|
||
|
||
# 四类 reason 逐一命中(断言前提=各类未生效行在案;reason 键取值=分析判读契约四值)
|
||
func _assert_four_reasons() -> void:
|
||
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:80 奖励未生效行四类合法例外
|
||
print("--- 奖励未生效 reason 四类逐一命中 ---")
|
||
|
||
# ---- ① full_hp:满血主角 + heal 型格正向(_flat_heal 满血分支即时兑付)----
|
||
var s1 = _orch(20261108)
|
||
var rows1: Array = []
|
||
s1.step_row.connect(func(row): rows1.append(row.duplicate(true)))
|
||
Kit.check("① 前提:战斗受理", s1.begin_battle({"level_index": 1, "node_depth": 0,
|
||
"hero_hp": int(_db.combat.hero.max_hp), "hero_max_hp": int(_db.combat.hero.max_hp)}))
|
||
var heal_snap = _snap_of_type(s1, "heal")
|
||
Kit.check("① 前提:heal 型格在板", heal_snap != null)
|
||
if heal_snap != null:
|
||
s1.reward_grant().grant_routed(heal_snap, "positive", {"source_class": "safe_cell", "cell": Vector2i(0, 0), "ri": 1})
|
||
var hits := 0
|
||
for r in _ineffective_rows(rows1):
|
||
if String(r.get("reason", "")) == "full_hp":
|
||
hits += 1
|
||
Kit.check("① 满血治疗行带 reason=full_hp(命中 %d 行)" % hits, hits >= 1)
|
||
|
||
# ---- ② hero_down:主角 hp=0(受控例外直改——无公开 setter)+ heal 正向 → 倒下分支 ----
|
||
var s2 = _orch(20261109)
|
||
var rows2: Array = []
|
||
s2.step_row.connect(func(row): rows2.append(row.duplicate(true)))
|
||
Kit.check("② 前提:战斗受理", s2.begin_battle({"level_index": 1, "node_depth": 0,
|
||
"hero_hp": int(_db.combat.hero.max_hp), "hero_max_hp": int(_db.combat.hero.max_hp)}))
|
||
var heal_snap2 = _snap_of_type(s2, "heal")
|
||
Kit.check("② 前提:heal 型格在板", heal_snap2 != null)
|
||
if heal_snap2 != null:
|
||
var hu = s2._enemy_system.hero_unit() # 受控例外:hero hp 直改(倒下因无公开写口)
|
||
hu["hp"] = 0
|
||
s2.reward_grant().grant_routed(heal_snap2, "positive", {"source_class": "safe_cell", "cell": Vector2i(0, 0), "ri": 1})
|
||
var hits2 := 0
|
||
for r in _ineffective_rows(rows2):
|
||
if String(r.get("reason", "")) == "hero_down":
|
||
hits2 += 1
|
||
Kit.check("② 主角已倒下行带 reason=hero_down(命中 %d 行)" % hits2, hits2 >= 1)
|
||
|
||
# ---- ③ full_layers:defense 型格负向 → off_guard(stack 策略 max 16)反复授予至满层 → 全体拒收行
|
||
# (满层拒收只发生在 stack 策略——accumulate 恒加层无拒收面;负向 mine_hit 路由=真实入口)----
|
||
var s3 = _orch(20261110)
|
||
var rows3: Array = []
|
||
s3.step_row.connect(func(row): rows3.append(row.duplicate(true)))
|
||
Kit.check("③ 前提:战斗受理", s3.begin_battle({"level_index": 1, "node_depth": 0,
|
||
"hero_hp": int(_db.combat.hero.max_hp), "hero_max_hp": int(_db.combat.hero.max_hp)}))
|
||
var def_snap = _snap_of_type(s3, "defense")
|
||
Kit.check("③ 前提:defense 型格在板", def_snap != null)
|
||
if def_snap != null:
|
||
var max_stack: int = int(_db.combat.status_effects.row_by_id("off_guard").get("max_stack", 16))
|
||
for i in range(max_stack + 3): # 堆满后再给 3 份——拒收段必然到达
|
||
s3.reward_grant().grant_routed(def_snap, "negative", {"source_class": "mine_hit", "cell": Vector2i(0, 0), "ri": 1})
|
||
var hits3 := 0
|
||
for r in _ineffective_rows(rows3):
|
||
if String(r.get("reason", "")) == "full_layers":
|
||
hits3 += 1
|
||
Kit.check("③ 满层拒收行带 reason=full_layers(off_guard max_stack=%d,命中 %d 行)" % [max_stack, hits3], hits3 >= 1)
|
||
|
||
# ---- ④ no_enemy_alive:敌 hp=1(受控例外直改)+ blast 正向单两张 defer → 轮末结账首单清场、次单无敌 ----
|
||
var s4 = _orch(20261111)
|
||
var rows4: Array = []
|
||
s4.step_row.connect(func(row): rows4.append(row.duplicate(true)))
|
||
Kit.check("④ 前提:战斗受理", s4.begin_battle({"level_index": 1, "node_depth": 0,
|
||
"hero_hp": int(_db.combat.hero.max_hp), "hero_max_hp": int(_db.combat.hero.max_hp)}))
|
||
var blast_snap = _snap_of_type(s4, "blast")
|
||
Kit.check("④ 前提:blast 型格在板", blast_snap != null)
|
||
if blast_snap != null:
|
||
for u in s4._enemy_system.query_alive("enemy"): # 受控例外:敌 hp 直改(先手清场因)
|
||
u["hp"] = 1
|
||
s4.reward_grant().grant_routed(blast_snap, "positive", {"source_class": "safe_cell", "cell": Vector2i(0, 0), "ri": 1})
|
||
s4.reward_grant().grant_routed(blast_snap, "positive", {"source_class": "safe_cell", "cell": Vector2i(0, 0), "ri": 1}) # 次单=结账时已无敌
|
||
s4.minesweeper.settle_batch([{"action": "stop"}]) # 轮末五段——战斗段末 defer 统一结账
|
||
var hits4 := 0
|
||
for r in _ineffective_rows(rows4):
|
||
if String(r.get("reason", "")) == "no_enemy_alive":
|
||
hits4 += 1
|
||
Kit.check("④ 无存活敌人行带 reason=no_enemy_alive(命中 %d 行)" % hits4, hits4 >= 1)
|