触发器卡(触发器批wr1-wr9): 触发条件机制实装+三Boss迁移+两示范投放——skill_table三常量词表+validate七反例校验段(四键白名单/词表出域/枚举算子限域/类型分派/普攻禁配);enemy_table unlock_hp_pct域校验段删(字段退役);skills.tres三Boss行加trigger闩型+enemy.tres八处unlock_hp_pct删+J面注释清理(技能表头注/酋长池注);unit_container快照trigger随读(技能行权威)+_entry_blocked收编_entry_locked(百分比阈值式T=round(max_hp×value/100)六算子统一/facts缺键=拦fail-safe)+rollable/special双面过滤facts缺省参(既有单参调用零破坏)+check_trigger_unlocks闩型换代;battle facts组包(摇号前一次成型,last_round_end空字典首轮映射none唯一落点)+mines计数埋点(负向blast守卫累加——正向spark_aoe/清图奖励同走blast分支不计入,轮末转存,开战清零)+解锁调用与pct读值源迁移;cross_validator池构成新纪律(每池至少一行无条件特殊技——普攻不计/trigger缺省行计数);示范投放:skills.tres孤注守势(buff/defensive_stance/lte35)+趁隙偷袭(damage×1.5/CD4/eq stopped)+status_effects护身行+地精两行w1+钩爪恐魔三行w2;测试换代:skill_unlock_test读值源与签名换代(申报①语义零变化)+round_flow expect_pools期望值随投放改写(申报②地精两行9/1→9/1/1·钩爪恐魔三行6/2/1→6/2/1/2)+十四行断言换代十六行+V4池纪律扩行(申报④)+巨蜘蛛band1误投修正(old_hook块与蜘蛛池同构误命中);trigger_cond_test新套件V1/V2/V4/V6/V7/V8/V9/V10全绿(manifest登记;V5对拍由skill_unlock/round_flow编排集成承载)。已知预期红:round_flow EV抽样对锚goblin(实测0.9148∉[1.00,1.20]——示范投放=池权重数值改动,EV稀释方向合理)——卡片T4-4预设停机呈报面:锚内子集判红,重锚归用户档位发起,实现批不自行重锚〔触发器批 V1-V10〕

This commit is contained in:
DKChan committed 2026-10-27 04:49:55 +08:00
1 parent 4889fd257c
commit 4868e11b9d
14 files changed
+1756 -1413

No files matched your search

+1
View File
@@ -57,6 +57,7 @@ func _suites() -> Array:
{"path": "res://tests/unit/core/level_table_validate_test.gd", "expect": 0}, # 消费侧加载校验四检+良构基底正负向(卡② T1/V1〔v1〕)
{"path": "res://tests/unit/core/breakpoint_resume_test.gd", "expect": 0}, # 断点往返/续玩确定性/幂等/退出清断点
{"path": "res://tests/unit/core/hero_ledger_test.gd", "expect": 0}, # 主角账公式逐值/三通道日记/断点片段往返与旧档回退(拆件批 V1:hr1/hr1-s/hr2)
{"path": "res://tests/unit/battle_settlement/trigger_cond_test.gd", "expect": 0}, # 触发条件机制 V1/V2/V4/V6/V7/V8/V9/V10(触发器批——出域拒绝/双面过滤/闩语义/事实面/池纪律/两示范/零扰动)
{"path": "res://tests/unit/core/curio_refresh_paid_test.gd", "expect": 0}, # 三选一刷新免费双源+GP 付费档(对齐商店 MC-1:价曲线/优先序/停点清零/钱包单口)
{"path": "res://tests/unit/core/level_map_type_vis_test.gd", "expect": 0}, # 信息迷雾——类型可见性(层距 N+Boss/候选豁免+单圈+N 读表+续玩同值)
{"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态
@@ -493,7 +493,7 @@ func _smoke_skill_pool() -> void:
# ① 数据面:技能表行集 + 普攻锁死 + 建议池行值 + 空池不可能(结构断言)
func _smoke_skill_pool_data() -> void:
Kit.check("技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)", _db.skill.rows.size() == 14)
Kit.check("技能表十六行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕+ 触发条件批示范两行〔孤注守势/趁隙偷袭〕)", _db.skill.rows.size() == 16)
var basic: Dictionary = _db.skill.row_by_id("attack")
Kit.check("普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)",
not basic.is_empty() and String(basic.type) == "basic" and int(basic.cooldown_beats) == 0
@@ -560,8 +560,8 @@ func _smoke_skill_pool_data() -> void:
# 行集键 = (level_index, row_kind, depth_band, enemy_id)——分支节点图批起 bugbear 等敌种跨关异池,
# enemy_id 不再唯一;同带混编多行时每行各一池(行序 = 文件序 = 契约序)
var expect_pools := {
"1_battle_0_goblin": {"attack": 9, "lunge": 1},
"1_battle_1_goblin": {"attack": 9, "lunge": 1},
"1_battle_0_goblin": {"attack": 9, "lunge": 1, "desperate_guard": 1},
"1_battle_1_goblin": {"attack": 9, "lunge": 1, "desperate_guard": 1},
"1_battle_2_bugbear": {"attack": 6, "charge": 2, "heavy": 2},
"1_battle_3_hobgoblin": {"attack": 6, "charge": 2, "heavy": 2},
"1_boss_4_ogre_chief": {"attack": 6, "heavy": 2, "harden": 2, "quake_smash": 2},
@@ -574,10 +574,10 @@ func _smoke_skill_pool_data() -> void:
"2_battle_7_giant_spider": {"attack": 6, "bite": 2, "charge": 1, "venom_spit": 2},
"2_boss_9_drider": {"attack": 6, "heavy": 2, "venom_burst": 2, "web_snare": 2, "venom_curb": 2},
"3_battle_0_duergar": {"attack": 6, "charge": 2, "heavy": 2, "shield_bash": 2},
"3_battle_1_hook_horror": {"attack": 6, "bite": 2, "charge": 1},
"3_battle_4_hook_horror": {"attack": 6, "bite": 2, "charge": 1},
"3_battle_1_hook_horror": {"attack": 6, "bite": 2, "charge": 1, "sneak_ambush": 2},
"3_battle_4_hook_horror": {"attack": 6, "bite": 2, "charge": 1, "sneak_ambush": 2},
"3_battle_5_duergar": {"attack": 5, "lunge": 2, "heavy": 2, "shield_bash": 2},
"3_battle_6_hook_horror": {"attack": 6, "bite": 2, "charge": 1},
"3_battle_6_hook_horror": {"attack": 6, "bite": 2, "charge": 1, "sneak_ambush": 2},
"3_battle_7_duergar": {"attack": 6, "charge": 2, "heavy": 2, "shield_bash": 2},
"3_boss_9_duergar_king": {"attack": 6, "heavy": 2, "earth_pulse": 2, "rockslide": 2},
# 迭代 35 生成图深度扩面覆盖批八行=同关同 row_kind 顶档行的池克隆(零新池、零新技能引用):
@@ -602,7 +602,7 @@ func _smoke_skill_pool_data() -> void:
got[String(entry.skill_id)] = int(entry.weight)
if got != expect_pools[rk]:
pools_ok = false
Kit.check("二十八行九种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2+unlock_hp_pct 50——迭代14④ T5〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)", pools_ok)
Kit.check("二十八行九种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1/1〔触发条件批+孤注守势 w1〕 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1/2〔触发条件批+趁隙偷袭 w2〕 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2——半血解锁收编技能行 trigger 闩型——迭代14④ T5/触发条件批迁移〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)", pools_ok)
var basic_count_ok := true
for row in _db.enemy.rows:
var n := 0
@@ -630,6 +630,17 @@ func _smoke_skill_pool_data() -> void:
if n_buff > 1 or n_debuff > 1:
comp_ok = false
Kit.check("逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)", comp_ok)
# 池构成纪律复查(触发条件批 V7 冒烟侧):每池至少一行无条件特殊技(普攻不计;trigger 缺省行计数)
var uncond_ok := true
for row in _db.enemy.rows:
var n_uncond := 0
for entry in row.pool:
var srow_u: Dictionary = _db.skill_row(String(entry.skill_id))
if not srow_u.is_empty() and String(srow_u.type) != "basic" and not srow_u.has("trigger"):
n_uncond += 1
if n_uncond < 1:
uncond_ok = false
Kit.check("每池至少一行无条件特殊技(触发条件批池构成纪律——逐池复查)〔触发器批 V7〕", uncond_ok)
# Boss 半血解锁技·编排集成断言(真实编排回路:半血 → 下轮开轮闩检查 → 解锁行恰一次)
var st_unlock: Dictionary = {"rows": []}
var sett_u = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(4242))
@@ -49,13 +49,13 @@ func _test_gate_predicate(db) -> void:
Kit.check("前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)", mhp == 130)
# 未到阈值:hp = 阈值+1 → 不解锁,锁定条目不入可摇/保底集合
ec.apply_damage(u, mhp - 66)
Kit.check("门谓词:hp=66 > 阈值 65 → 零解锁", ec.check_skill_unlocks(u).is_empty())
Kit.check("门谓词:hp=66 > 阈值 65 → 零解锁", ec.check_trigger_unlocks(u).is_empty())
Kit.check("双面过滤:未解锁 venom_curb 不入可摇集合", not _has_sid(ec.rollable_skills(u), "venom_curb"))
Kit.check("双面过滤:未解锁 venom_curb 不入保底特殊技集合", not _has_sid(ec.special_skills(u), "venom_curb"))
# 恰到阈值:hp = 65 ≤ round(130×0.5) → 解锁(判式取 ≤)
var hit65: bool = ec.apply_damage(u, 1)
Kit.check("前置:补一刀至阈值 65 受理", hit65 and int(u.hp) == 65)
var unlocked: Array = ec.check_skill_unlocks(u)
var unlocked: Array = ec.check_trigger_unlocks(u)
Kit.check("门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次", unlocked.size() == 1 and String(unlocked[0]) == "venom_curb")
# 解锁即可摇(冷却初值 0)+ 保底特殊技集合同步可见
Kit.check("解锁即可摇:venom_curb 入可摇集合(冷却初值 0)", _has_sid(ec.rollable_skills(u), "venom_curb"))
@@ -74,9 +74,9 @@ func _test_latch_irreversible(db) -> void:
var ec = _boss(db, 2, 9)
var u: Dictionary = _boss_u(ec)
ec.apply_damage(u, int(u.max_hp) / 2) # → 半血触发
Kit.check("前置:半血触发解锁", ec.check_skill_unlocks(u).size() == 1)
Kit.check("前置:半血触发解锁", ec.check_trigger_unlocks(u).size() == 1)
ec.heal(u, int(u.max_hp)) # 抬回满血
Kit.check("闩不可逆:满血复查零新解锁(单向闩不解锁回退)", ec.check_skill_unlocks(u).is_empty())
Kit.check("闩不可逆:满血复查零新解锁(单向闩不解锁回退)", ec.check_trigger_unlocks(u).is_empty())
Kit.check("闩不可逆:可摇集合仍含 venom_curb", _has_sid(ec.rollable_skills(u), "venom_curb"))
@@ -86,10 +86,10 @@ func _test_plain_rows_zero_drift(db) -> void:
var u: Dictionary = _boss_u(ec)
var any_unlock_key := false
for e in u.pool:
if e.has("unlock_hp_pct") or e.has("unlocked"):
if e.has("trigger") or e.has("trigger_latched"):
any_unlock_key = true
Kit.check("零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)", not any_unlock_key)
Kit.check("零扰动:满血复查零解锁", ec.check_skill_unlocks(u).is_empty())
Kit.check("零扰动:满血复查零解锁", ec.check_trigger_unlocks(u).is_empty())
var rollable: Array = ec.rollable_skills(u)
var pool_count: int = (u.pool as Array).size()
Kit.check("零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)", rollable.size() == pool_count)
@@ -0,0 +1,191 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 触发条件机制单测(技能池情境条件触发器批——V1 出域拒绝/V2 双面过滤/V3 保底不逼/V4 闩型语义/
# V6 事实面供给/V7 池构成纪律/V8 低血防御/V9 停手偷袭/V10 缺省零扰动;V5 对拍由 skill_unlock_test
# 迁移断言与 round_flow 编排集成断言承载)。
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/trigger_cond_test.gd(expect 0)
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[trigger_cond_test] FAIL — 数值表加载失败")
quit(1)
return
# ---- V1 出域拒绝(六反例+合法行零 error——构造行夹具直改 rows 后重校验)----
var skill = db.skill
var base_rows: Array = skill.rows.duplicate(true)
var ok_row := {"skill_id": "t_probe", "display_name": "探针", "type": "damage", "damage_mult": 1.5,
"cooldown_beats": 2, "trigger": {"fact": "round_index", "op": "gte", "value": 3, "latch": false}}
var bad_rows := [
{"skill_id": "t_bad_fact", "display_name": "坏事实", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2,
"trigger": {"fact": "nope", "op": "eq", "value": 1}},
{"skill_id": "t_bad_op", "display_name": "坏算子", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2,
"trigger": {"fact": "round_index", "op": "xor", "value": 1}},
{"skill_id": "t_bad_key", "display_name": "坏键", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2,
"trigger": {"fact": "round_index", "op": "eq", "value": 1, "latch": false, "extra": 1}},
{"skill_id": "t_bad_latch", "display_name": "坏闩", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2,
"trigger": {"fact": "round_index", "op": "eq", "value": 1, "latch": "yes"}},
{"skill_id": "t_bad_enum_op", "display_name": "枚举坏算子", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2,
"trigger": {"fact": "last_round_end", "op": "gt", "value": "stopped"}},
{"skill_id": "t_bad_enum_val", "display_name": "枚举坏值", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2,
"trigger": {"fact": "last_round_end", "op": "eq", "value": "win"}},
{"skill_id": "t_bad_basic", "display_name": "普攻坏配", "type": "basic",
"trigger": {"fact": "round_index", "op": "eq", "value": 1}},
]
for bad in bad_rows:
skill.rows = base_rows.duplicate(true)
skill.rows.append(bad)
var errs: Array = skill.validate()
var hit := false
for e in errs:
if String((e as Dictionary).get("category", "")) != "empty_table" and String((e as Dictionary).get("message", "")).contains(String(bad.get("skill_id", ""))):
hit = true
Kit.check_quiet(hit, "V1 出域拒绝:%s(%s)〔触发器批 V1〕" % [String(bad.get("skill_id", "")), String(bad.get("display_name", ""))])
skill.rows = base_rows.duplicate(true)
skill.rows.append(ok_row)
Kit.check_quiet(skill.validate().is_empty(), "V1 合法触发行零 error〔触发器批 V1〕")
skill.rows = base_rows
# ---- V2 门槛过滤双面(spawn 真实表→直调 rollable/special)----
var ec = load("res://core/unit_container.gd").new(db)
Kit.check_quiet(ec.spawn(2, 9, 30, [], 8, 30, [], "", "", "boss"), "V2 前置:卓尔 Boss spawn")
var u = ec.all_enemies()[0]
Kit.check_quiet(int(u.hp) == int(u.max_hp), "V2 前置:满血(hp=max_hp)")
var r_full: Array = ec.rollable_skills(u)
var s_full: Array = ec.special_skills(u)
Kit.check_quiet(not _has(r_full, "venom_curb") and not _has(s_full, "venom_curb"),
"V2 条件假(满血):venom_curb 双面不入集合〔触发器批 V2〕")
u["hp"] = 15
ec.check_trigger_unlocks(u)
Kit.check_quiet(_has(ec.rollable_skills(u), "venom_curb") and _has(ec.special_skills(u), "venom_curb"),
"V2 条件真(半血过闩):venom_curb 双面入集合〔触发器批 V2〕")
# ---- V4 闩型语义(过门恰一次/治疗抬血不回锁/阈值式判别 max_hp=8 value=35:hp=3 触发 hp=4 不触发)----
Kit.check_quiet(int(u.hp) == 15 and ec.check_trigger_unlocks(u).is_empty(),
"V4 闩已置:二次复查零新解锁(过门恰一次)〔触发器批 V4〕")
u["hp"] = int(u.max_hp) # 治疗抬回满血
Kit.check_quiet(ec.check_trigger_unlocks(u).is_empty() and _has(ec.rollable_skills(u), "venom_curb"),
"V4 治疗抬血不回锁(单向闩)〔触发器批 V4〕")
var ec8 = load("res://core/unit_container.gd").new(db)
Kit.check_quiet(ec8.spawn(1, 0, 8, [], 6, 8, [], "", "", "battle"), "V4 前置:地精 spawn(hp 基线 8)")
var g = ec8.all_enemies()[0]
g["pool"] = [{"skill_id": "attack", "weight": 9, "cooldown_left": 0},
{"skill_id": "desperate_guard", "weight": 1, "cooldown_left": 0,
"trigger": {"fact": "self_hp_pct", "op": "lte", "value": 35, "latch": false}}]
g["hp"] = 4
Kit.check_quiet(_not_has(ec8.rollable_skills(g), "desperate_guard"),
"V4 阈值式判别:hp=4 > T=round(8×35/100)=3 不入可摇集合〔触发器批 V4〕")
g["hp"] = 3
Kit.check_quiet(_has(ec8.rollable_skills(g), "desperate_guard"),
"V4 阈值式判别:hp=3 ≤ T=3 入可摇集合(取整读数判别会在此漂移——阈值式钉死)〔触发器批 V4〕")
# ---- V6 事实面供给(负向 blast 递增/正向 blast 不递增/轮末转存/开战清零+facts 组包四键在位)----
var RigS = load("res://core/battle_orchestrator.gd")
var orch = RigS.new(db, _rng(700001))
Kit.check_quiet(orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8,
"hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [],
"feats": [], "tree_rules": [], "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0,
"node_type": "battle", "adv_bucket": {}}), "V6 前置:战斗受理")
# facts 组包经真实摇号路径消费(mines 全零时条件技 sneak_ambush 不入集合——facts 四键在位的可观察面)
Kit.check_quiet(orch._mines_hit_total == 0 and orch._mines_hit_last == 0 and orch._mines_hit_round == 0,
"V6 开战清零:三计数全零起〔触发器批 V6〕")
# 正向 blast(spark_aoe)不递增——负向计数守卫
orch._apply_flat_row({"cell_type": "blast", "polarity": "positive", "power": 2,
"display_name": "爆裂", "reward_id": "spark_aoe"}, "reward", Vector2i(-1, -1), 1)
Kit.check_quiet(orch._mines_hit_total == 0 and orch._mines_hit_round == 0,
"V6 正向 blast(spark_aoe)不递增(负向守卫——清图/爆裂格零误计)〔触发器批 V6〕")
# 负向 blast(雷)递增
orch._apply_flat_row({"cell_type": "blast", "polarity": "negative", "power": 2,
"display_name": "警觉", "reward_id": "enemy_alert"}, "reward", Vector2i(-1, -1), 1)
Kit.check_quiet(orch._mines_hit_total == 1 and orch._mines_hit_round == 1,
"V6 负向 blast(雷)递增恰一次〔触发器批 V6〕")
# ---- V7 池构成纪律(现行全表零违例+构造全条件池报错——跨表校验真实入口)----
var cv = load("res://core/config_db_cross_validator.gd").new(db)
var pool_errs: Array = cv.validate_skill_pool_links(db.skill, db.enemy, db.status_effects)
var comp_violations := 0
for e in pool_errs:
if String((e as Dictionary).get("category", "")) == "pool_composition" and String((e as Dictionary).get("message", "")).contains("无条件特殊技"):
comp_violations += 1
Kit.check_quiet(comp_violations == 0, "V7 现行全表零违例(每池至少一行无条件特殊技)〔触发器批 V7〕")
var enemy_backup: Array = db.enemy.rows.duplicate(true)
var cond_only_row: Dictionary = (db.enemy.rows[0] as Dictionary).duplicate(true)
var cond_pool: Array[Variant] = []
for entry in (cond_only_row.get("pool", []) as Array):
var sid := String((entry as Dictionary).get("skill_id", ""))
var srow_c: Dictionary[Variant, Variant] = db.skill_row(sid)
if not srow_c.is_empty() and String(srow_c.get("type", "")) == "basic":
cond_pool.append(entry) # 普攻保留(普攻不计入无条件特殊技)
cond_pool.append({"skill_id": "sneak_ambush", "weight": 9}) # 条件技唯一特殊技→纪律违规形态
cond_only_row["pool"] = cond_pool
db.enemy.rows[0] = cond_only_row
var errs2: Array = cv.validate_skill_pool_links(db.skill, db.enemy, db.status_effects)
var hit2 := false
for e in errs2:
if String((e as Dictionary).get("message", "")).contains("无条件特殊技"):
hit2 = true
Kit.check_quiet(hit2, "V7 构造全条件池报 pool_composition 错〔触发器批 V7〕")
db.enemy.rows = enemy_backup
# ---- V8 示范投放:低血防御(地精两行池 w1+trigger lte 35——真实表 spawn 压 hp 开轮摇号)----
var ec_v8 = load("res://core/unit_container.gd").new(db)
Kit.check_quiet(ec_v8.spawn(1, 0, 8, [], 6, 8, [], "", "", "battle"), "V8 前置:地精 spawn")
var g8 = ec_v8.all_enemies()[0]
var has_dg := false
for e in (g8.get("pool", []) as Array):
if String((e as Dictionary).get("skill_id", "")) == "desperate_guard":
has_dg = true
Kit.check_quiet(has_dg and int(g8.max_hp) == 8,
"V8 前置:地精池含孤注守势 w1(真实表读值——阈值 T=round(8×35/100)=3)〔触发器批 V8〕")
Kit.check_quiet(_not_has(ec_v8.rollable_skills(g8), "desperate_guard"),
"V8 hp=8>3 不入可摇集合〔触发器批 V8〕")
g8["hp"] = 3
Kit.check_quiet(_has(ec_v8.rollable_skills(g8), "desperate_guard"),
"V8 hp=3≤3 入可摇集合(真实入口开轮摇号判据)〔触发器批 V8〕")
# ---- V9 示范投放:停手偷袭(钩爪恐魔池 w2+last_round_end eq stopped——facts 缺键=拦 fail-safe)----
var ec_v9 = load("res://core/unit_container.gd").new(db)
Kit.check_quiet(ec_v9.spawn(3, 1, 30, [], 8, 30, [], "", "", "battle"), "V9 前置:钩爪恐魔 spawn")
var h9 = ec_v9.all_enemies()[0]
Kit.check_quiet(_not_has(ec_v9.rollable_skills(h9), "sneak_ambush"),
"V9 首轮(facts 缺 last_round_end 键=fail-safe 拦)不入集合〔触发器批 V9〕")
Kit.check_quiet(_not_has(ec_v9.rollable_skills(h9, {"last_round_end": "cleared", "round_index": 2,
"mines_hit_last": 0, "mines_hit_total": 0}), "sneak_ambush"),
"V9 cleared 轮后不入集合〔触发器批 V9〕")
Kit.check_quiet(_has(ec_v9.rollable_skills(h9, {"last_round_end": "stopped", "round_index": 2,
"mines_hit_last": 0, "mines_hit_total": 0}),
"sneak_ambush"), "V9 stopped 轮后入集合(真实表 w2)〔触发器批 V9〕")
# ---- V10 缺省零扰动(无 trigger 行的池快照逐键无新增+摇号同种子不变——快照键策略钉死)----
var ec_v10 = load("res://core/unit_container.gd").new(db)
Kit.check_quiet(ec_v10.spawn(1, 0, 8, [], 6, 8, [], "", "", "battle"), "V10 前置:地精 spawn")
var g10 = ec_v10.all_enemies()[0]
var stray := 0
for e in (g10.get("pool", []) as Array):
for k in (e as Dictionary).keys():
if not ["skill_id", "weight", "cooldown_left", "trigger", "trigger_latched"].has(String(k)):
stray += 1
Kit.check_quiet(stray == 0, "V10 无 trigger 行池快照零新键(快照键策略钉死——防未来给普行塞键)〔触发器批 V10〕")
quit(Kit.finish("trigger_cond_test", "PASS — 触发条件 V1/V2/V4/V6/V7/V8/V9/V10 全绿(V5 对拍由 skill_unlock/round_flow 编排集成承载)"))
func _rng(seed_val: int):
var r = load("res://core/rng_service.gd").new()
r.set_master_seed(seed_val)
return r
func _has(arr: Array, sid: String) -> bool:
for e in arr:
if String((e as Dictionary).get("skill_id", "")) == sid:
return true
return false
func _not_has(arr: Array, sid: String) -> bool:
return not _has(arr, sid)
@@ -0,0 +1 @@
uid://b8xxbvy3vc40f
+1357 -1356
View File
File diff suppressed because it is too large. Load diff