test(敌人表深度带): 冒烟与单测断言按 S 清单改写

冒烟全部寻址点改为三参 resolve_enemy_rowset(关卡, 深度, 行类),结算/单元/探针直呼的
spawn 与 begin_battle 显式带行类(battle/boss)——终点战仍按 Boss 行集取数,行为不变。
首遇位断言改为扫表取该 (关卡, battle, 敌种) 的最低深度带,不再写死坐标〔fv21〕;新增
_enemy_bands 结构段验 20 行键序、hobgoblin 留任 (1,battle,3)、三 Boss 行类与带键〔fv11–fv13〕;
L2 混编 (2,battle,5) 行序与骰和、巨蜘蛛/bugbear/duergar 首遇带固定数、Boss 行类显式传入
等断言逐条加〔fv18/fv19/fv22–fv24〕尾缀。职业节奏探针与伤害探针同步三参寻址。

变异验证七项全部命中(记录表进实现报告):band_of 反向 floor → fv1/fv3 红;row_kind 枚举
判据注释 → fv7 红;Boss 同带判据删除 → fv15 红;顶带消解判据短路 → fv16 红;巨蜘蛛首遇行
加 unit_count_max → fv22 红;混编两行文件内对调 → fv18/fv11 红;hobgoblin 迁错带键 →
fv11/fv12 红(直接迁到带 2 先被加载期同带规则拦下,改用与带 2 行互换带键,表可加载后键位断言红)。
This commit is contained in:
DKChan committed 2026-10-22 04:17:40 +08:00
1 parent 07f3eb55e3
commit 61537da889
7 files changed
+410 -165

No files matched your search

+51 -22
View File
@@ -11,7 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇
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)
Kit.check("全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕", load_ok and db.diagnostics_count("error") == 0)
Kit.check("is_ready 通道随加载置位", db.is_ready)
if not load_ok:
for d in db.diagnostics():
@@ -59,8 +59,8 @@ func _test_progress_resolution(db) -> void:
# enemy 行集同通道:同 (L,D) 行集数组非空(关卡节点地图 前置——battle 节点双表精确行集契约;
# 行集面契约:返回形态是行集数组,精确性断言随行集面)
var en_got: Array = db.resolve_enemy_rowset(int(probe_row.level_index), int(probe_row.node_depth))
Kit.check("enemy 行集同坐标解析非空(双表精确行契约)", not en_got.is_empty())
var en_got: Array = db.resolve_enemy_rowset(int(probe_row.level_index), int(probe_row.node_depth), "battle")
Kit.check("enemy 行集同坐标解析非空(双表精确行契约——battle 行类)", not en_got.is_empty())
# 深度 clamp:请求深度超最大行 → 取该关最大深度行 + 警告诊断一次(去重)
db.reset_diagnostics()
@@ -197,44 +197,46 @@ func _test_rowset_discipline() -> void:
# 数组字面量会触发「Invalid assignment」脚本错误——该错误不改退出码,却会**中断本函数剩余断言**
#(本函数五条纪律断言曾因此全部静默不跑,套件仍报 all green,是零覆盖假象)。
const EnemyTableScript := preload("res://data/schemas/enemy_table.gd")
var mk_row := func(l: int, d: int, eid: String) -> Dictionary:
return {"level_index": l, "node_depth": d, "enemy_id": eid, "display_name": eid + "名",
var mk_row := func(l: int, kind: String, band: int, eid: String) -> Dictionary:
return {"level_index": l, "row_kind": kind, "depth_band": band, "enemy_id": eid, "display_name": eid + "名",
"unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]}
# 正向:同坐标两行不同敌种(相邻)= 混编合法,validate 零错
# 正向:同带键两行不同敌种(相邻)= 混编合法,validate 零错
var t_mix = EnemyTableScript.new()
t_mix.rows.assign([mk_row.call(1, 5, "goblin"), mk_row.call(1, 5, "bugbear"),
mk_row.call(1, 0, "goblin")]) # 末行=depth-0 锚行(validate 的 anchor 规则:每 level 必须有)
t_mix.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "battle", 5, "bugbear"),
mk_row.call(1, "battle", 0, "goblin"), mk_row.call(1, "boss", 9, "ogre_chief")])
var errs: Array = t_mix.validate()
Kit.check("混编行集加载通过(同坐标两行不同敌种相邻——行集恰一敌种校验退役的正向面)", errs.is_empty())
# 正向:Boss 坐标两行同构合法(P4 乙——Boss 行集校验同样放开,加载层无 Boss 单行硬闸)
Kit.check("混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)", errs.is_empty())
# 正向:Boss 行集两行同构合法(P4 乙——Boss 行集校验同样放开,加载层无 Boss 单行硬闸)
var t_boss = EnemyTableScript.new()
t_boss.rows.assign([mk_row.call(2, 9, "drider"), mk_row.call(2, 9, "bugbear"),
mk_row.call(2, 0, "drider")]) # 末行=depth-0 锚行
t_boss.rows.assign([mk_row.call(2, "battle", 0, "drider"), mk_row.call(2, "boss", 9, "drider"),
mk_row.call(2, "boss", 9, "bugbear")])
errs = t_boss.validate()
Kit.check("Boss 坐标混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)", errs.is_empty())
# 负向:同坐标行不相邻 = rowset_rule 且报错定位到坐标(R6 行序纪律 + R3 错误类别可定位)
Kit.check("Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)", errs.is_empty())
# 负向:同行集行不相邻 = rowset_rule 且报错定位到带键(R6 行序纪律 + R3 错误类别可定位)
var t_gap = EnemyTableScript.new()
t_gap.rows.assign([mk_row.call(1, 5, "goblin"), mk_row.call(1, 9, "hobgoblin"), mk_row.call(1, 5, "bugbear"),
mk_row.call(1, 0, "goblin")]) # 末行=depth-0 锚行(隔断关系不受影响)
t_gap.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "battle", 9, "hobgoblin"),
mk_row.call(1, "battle", 5, "bugbear"), mk_row.call(1, "battle", 0, "goblin"),
mk_row.call(1, "boss", 9, "ogre_chief")])
errs = t_gap.validate()
var gap_hit := false
for e in errs:
if String(e.get("category", "")) == "rowset_rule" and String(e.get("message", "")).contains("(1,5)"):
if String(e.get("category", "")) == "rowset_rule" and String(e.get("message", "")).contains("(1,battle,5)"):
gap_hit = true
Kit.check("行序相邻校验:同坐标行被隔断报 rowset_rule 且定位坐标(R6——行序即契约加载期兜底)", gap_hit)
Kit.check("行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕", gap_hit)
# 负向:同敌种第二行仍被 dup_key 拦(键含 enemy_id——纪律①既有通道不回归)
var t_dup = EnemyTableScript.new()
t_dup.rows.assign([mk_row.call(1, 5, "goblin"), mk_row.call(1, 5, "goblin"),
mk_row.call(1, 0, "goblin")]) # 末行=depth-0 锚行
t_dup.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "battle", 5, "goblin"),
mk_row.call(1, "battle", 0, "goblin"), mk_row.call(1, "boss", 9, "ogre_chief")])
errs = t_dup.validate()
var dup_hit := false
for e in errs:
if String(e.get("category", "")) == "dup_key":
dup_hit = true
Kit.check("行集内同敌种第二行仍被 dup_key 拦(纪律①——enemy_id 唯一)", dup_hit)
Kit.check("同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕", dup_hit)
# 负向:域校验不回归(单行 unit_count 越域仍拦——UNIT_COUNT_MAX 单种数语义下的同款域闸)
var t_dom = EnemyTableScript.new()
t_dom.rows.assign([mk_row.call(1, 5, "goblin"), mk_row.call(1, 0, "goblin")]) # 末行=depth-0 锚行
t_dom.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "battle", 0, "goblin"),
mk_row.call(1, "boss", 9, "ogre_chief")])
t_dom.rows[0]["unit_count"] = 4
errs = t_dom.validate()
var dom_hit := false
@@ -242,3 +244,30 @@ func _test_rowset_discipline() -> void:
if String(e.get("category", "")) == "domain" and String(e.get("message", "")).contains("unit_count"):
dom_hit = true
Kit.check("单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)", dom_hit)
# 负向:row_kind 封闭枚举(非法值拒——键面换代后新增判据)
var t_kind = EnemyTableScript.new()
t_kind.rows.assign([mk_row.call(1, "elite", 5, "goblin"), mk_row.call(1, "battle", 0, "goblin"),
mk_row.call(1, "boss", 9, "ogre_chief")])
errs = t_kind.validate()
var kind_hit := false
for e in errs:
if String(e.get("category", "")) == "domain" and String(e.get("message", "")).contains("row_kind"):
kind_hit = true
Kit.check("非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕", kind_hit)
# 负向:anchor 两半——缺 battle band 0 锚行、缺 Boss 行集,各自报 anchor
var t_no_anchor = EnemyTableScript.new()
t_no_anchor.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "boss", 9, "ogre_chief")])
errs = t_no_anchor.validate()
var no_anchor_hit := false
for e in errs:
if String(e.get("category", "")) == "anchor" and String(e.get("message", "")).contains("band 0"):
no_anchor_hit = true
Kit.check("缺 (battle, band 0) 锚行报 anchor〔fv10〕", no_anchor_hit)
var t_no_boss = EnemyTableScript.new()
t_no_boss.rows.assign([mk_row.call(1, "battle", 0, "goblin"), mk_row.call(1, "battle", 5, "bugbear")])
errs = t_no_boss.validate()
var no_boss_hit := false
for e in errs:
if String(e.get("category", "")) == "anchor" and String(e.get("message", "")).contains("Boss"):
no_boss_hit = true
Kit.check("缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕", no_boss_hit)