Files
gplan-2/game/tests/unit/battle_settlement/skill_unlock_test.gd
T
DKChan 4868e11b9d 触发器卡(触发器批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〕
2026-10-27 04:49:55 +08:00

104 lines
5.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tests/unit/battle_settlement/skill_unlock_test.gd — Boss 半血解锁技·单位容器机制单元测试
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/skill_unlock_test.gd
# 覆盖:门谓词(hp ≤ round(max_hp×pct/100))/闩不可逆(治疗抬血不解锁)/
# 双面过滤(rollable+special 不入锁定)/保底不逼锁定(保底集合排除锁定)/解锁即可摇(冷却初值 0)/
# 普通行无字段逐位零扰动(goblin 池快照零 unlock 键、过滤恒空转)/同种子确定性(容器零随机)。
# 红线:期望值由表读值推导(零玩法数值字面量);零自建 RNG。
extends SceneTree
const ConfigDBScript := preload("res://core/config_db.gd")
const UnitContainerScript := preload("res://core/unit_container.gd")
const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
var db = ConfigDBScript.new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0)
if load_ok:
_test_gate_predicate(db)
_test_latch_irreversible(db)
_test_plain_rows_zero_drift(db)
quit(Kit.finish("skill_unlock_test"))
# 门谓词:卓尔 (2,9) hp 130(×mult 1.0)→ venom_curb unlock 50 → 阈值 round(130×0.5)=65
func _boss(db, L: int, D: int, kind := "boss"):
var ec = UnitContainerScript.new(db)
var ok: bool = ec.spawn(L, D, int(db.hero.max_hp), [], -1, -1, [], "", "", kind)
Kit.check("前置:spawn (%d,%d) 受理" % [L, D], ok)
return ec
func _boss_u(ec) -> Dictionary:
return ec.all_enemies()[0]
func _has_sid(arr: Array, sid: String) -> bool:
for e in arr:
if String(e.skill_id) == sid:
return true
return false
func _test_gate_predicate(db) -> void:
var ec = _boss(db, 2, 9)
var u: Dictionary = _boss_u(ec)
var mhp: int = int(u.max_hp)
Kit.check("前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)", mhp == 130)
# 未到阈值:hp = 阈值+1 → 不解锁,锁定条目不入可摇/保底集合
ec.apply_damage(u, mhp - 66)
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_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"))
Kit.check("解锁即可摇:venom_curb 入保底特殊技集合", _has_sid(ec.special_skills(u), "venom_curb"))
# 保底不逼锁定:锁定期保底集合 = web_snare 唯一(venom_burst 被过滤——保底不得逼出未解锁技)
var ec2 = _boss(db, 2, 9)
var u2: Dictionary = _boss_u(ec2)
ec2.apply_damage(u2, mhp - 66)
var specials: Array = ec2.special_skills(u2)
# 特殊技集合语义 = 全部非普攻池条目(含伤害技 heavy——保底「必出特殊技」口径;proposal §2.1)
Kit.check("保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)", specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb"))
# 闩不可逆:解锁后治疗抬回满血 → 复查零新解锁、可摇集合不回退
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_trigger_unlocks(u).size() == 1)
ec.heal(u, int(u.max_hp)) # 抬回满血
Kit.check("闩不可逆:满血复查零新解锁(单向闩不解锁回退)", ec.check_trigger_unlocks(u).is_empty())
Kit.check("闩不可逆:可摇集合仍含 venom_curb", _has_sid(ec.rollable_skills(u), "venom_curb"))
# 普通行无字段逐位零扰动:地精 (1,0) 池快照零 unlock 键、过滤恒空转、可摇=全池
func _test_plain_rows_zero_drift(db) -> void:
var ec = _boss(db, 1, 0, "battle")
var u: Dictionary = _boss_u(ec)
var any_unlock_key := false
for e in u.pool:
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_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)
# 同种子确定性:容器零随机——两实例同输入查询逐位同
var ec2 = _boss(db, 1, 0, "battle")
var u2: Dictionary = _boss_u(ec2)
ec.apply_damage(u, 3)
ec2.apply_damage(u2, 3)
var a := JSON.stringify(ec.rollable_skills(u))
var b := JSON.stringify(ec2.rollable_skills(u2))
Kit.check("确定性:两实例同输入可摇集合逐位同(容器零随机纪律)", a == b)