触发器卡(触发器批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:
1 parent
4889fd257c
commit
4868e11b9d
14 files changed
+1756
-1413
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user