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

@@ -24,9 +24,9 @@ func _init() -> void:
# 门谓词:卓尔 (2,9) hp 130(×mult 1.0)→ venom_curb unlock 50 → 阈值 round(130×0.5)=65
func _boss(db, L: int, D: int):
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))
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
@@ -82,7 +82,7 @@ func _test_latch_irreversible(db) -> void:
# 普通行无字段逐位零扰动:地精 (1,0) 池快照零 unlock 键、过滤恒空转、可摇=全池
func _test_plain_rows_zero_drift(db) -> void:
var ec = _boss(db, 1, 0)
var ec = _boss(db, 1, 0, "battle")
var u: Dictionary = _boss_u(ec)
var any_unlock_key := false
for e in u.pool:
@@ -94,7 +94,7 @@ func _test_plain_rows_zero_drift(db) -> void:
var pool_count: int = (u.pool as Array).size()
Kit.check("零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)", rollable.size() == pool_count)
# 同种子确定性:容器零随机——两实例同输入查询逐位同
var ec2 = _boss(db, 1, 0)
var ec2 = _boss(db, 1, 0, "battle")
var u2: Dictionary = _boss_u(ec2)
ec.apply_damage(u, 3)
ec2.apply_damage(u2, 3)
@@ -16,6 +16,7 @@ func _init() -> void:
_test_all_links_valid()
_test_dangling_skill_id()
_test_cast_side_mismatch()
_test_level_node_band_rules()
quit(Kit.finish("config_db_cross_validator_test"))
@@ -51,7 +52,7 @@ func _test_all_links_valid() -> void:
{"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_strong"},
])
var enemies := _enemy([
{"level_index": 1, "node_depth": 0, "enemy_id": "goblin", "pool": [
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [
{"skill_id": "attack", "weight": 9}, {"skill_id": "raise_morale", "weight": 3}]},
])
var status := _status([{"effect_id": "st_strong", "target_side": "enemy"}])
@@ -63,7 +64,7 @@ func _test_all_links_valid() -> void:
func _test_dangling_skill_id() -> void:
var skills := _skill([{"skill_id": "attack", "type": "basic"}])
var enemies := _enemy([
{"level_index": 1, "node_depth": 0, "enemy_id": "goblin", "pool": [
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [
{"skill_id": "attack", "weight": 9}, {"skill_id": "not_in_table", "weight": 3}]},
])
var errs: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemies, null)
@@ -76,9 +77,67 @@ func _test_dangling_skill_id() -> void:
func _test_cast_side_mismatch() -> void:
var skills := _skill([{"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_hero_side"}])
var enemies := _enemy([
{"level_index": 1, "node_depth": 0, "enemy_id": "goblin", "pool": [
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [
{"skill_id": "raise_morale", "weight": 3}]},
])
var status := _status([{"effect_id": "st_hero_side", "target_side": "hero"}])
var errs: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemies, status)
Kit.check("buff 挂主角侧状态被报(施放类 side 守卫)", _cats(errs).has("cast_side"))
# ---- ④ 节点 × 敌人表带寻址(单终点零误报/同带双 Boss 报错/顶 clamp 消解报错)----
const LevelTableScript := preload("res://data/schemas/level_table.gd")
const BoardGenTableScript := preload("res://data/schemas/board_gen_table.gd")
const RewardTableScript := preload("res://data/schemas/reward_table.gd")
const SettlementTableScript := preload("res://data/schemas/settlement_table.gd")
func _loaded(level_nodes: Array, bg_depths: Array, boss_bands: Array, battle_bands: Array) -> Dictionary:
var lv := LevelTableScript.new()
lv.rows.assign([{"level_index": 1, "display_name": "夹具关", "templates": [{"nodes": level_nodes}]}])
var bg := BoardGenTableScript.new()
var bg_rows: Array = []
for d in bg_depths:
bg_rows.append({"level_index": 1, "node_depth": int(d), "board_width": 10})
bg.rows.assign(bg_rows)
var en := EnemyTableScript.new()
var en_rows: Array = []
for b in battle_bands:
en_rows.append({"level_index": 1, "row_kind": "battle", "depth_band": int(b), "enemy_id": "goblin",
"unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]})
for b in boss_bands:
en_rows.append({"level_index": 1, "row_kind": "boss", "depth_band": int(b), "enemy_id": "ogre_chief",
"unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]})
en.rows.assign(en_rows)
var st := SettlementTableScript.new()
st.source_label_map.assign({"safe_cell": "a", "seal_hit": "b", "mine_hit": "c", "clear_reward": "d",
"hero_attack": "e", "enemy_action": "f", "seal_miss": "g"})
return {"status_effects": StatusTableScript.new(), "reward": RewardTableScript.new(),
"settlement": st, "level": lv, "board_gen": bg, "enemy": en}
func _cross_cats(loaded: Dictionary, needle: String) -> Array[String]:
var out: Array[String] = []
ValidatorScript.new().cross_validate(loaded,
func(_sev, cat, msg) -> void:
if String(msg).contains(needle):
out.append(String(cat)))
return out
func _test_level_node_band_rules() -> void:
# 正向:单终点三关同构(battle 节点落 band 0、boss 节点精确落顶带)→ ④ 零误报
var ok_nodes := [{"type": "battle", "node_depth": 0}, {"type": "boss", "node_depth": 9}]
var ok_cats := _cross_cats(_loaded(ok_nodes, [0, 9], [9], [0]), "level 1")
Kit.check("单终点图 ④ 敌侧带寻址零误报〔fv17〕", ok_cats.is_empty())
# 负向:一路两终点落同一 Boss 带(深度 3/4 都落带 4)→ rowset_rule
var same_band_nodes := [{"type": "battle", "node_depth": 0},
{"type": "boss", "node_depth": 3}, {"type": "boss", "node_depth": 4}]
var same_band_cats := _cross_cats(_loaded(same_band_nodes, [0, 3, 4], [4, 9], [0]), "同带")
Kit.check("同关双终点共带报 rowset_rule〔fv15〕",
same_band_cats.has("rowset_rule"))
# 负向:Boss 节点深度高于顶带键(顶 clamp 消解)→ level_node_dangling
var over_nodes := [{"type": "battle", "node_depth": 0}, {"type": "boss", "node_depth": 10}]
var over_cats := _cross_cats(_loaded(over_nodes, [0, 10], [9], [0]), "顶 clamp 消解")
Kit.check("Boss 节点越顶带报 level_node_dangling〔fv16〕",
over_cats.has("level_node_dangling"))
+63 -19
View File
@@ -1,6 +1,6 @@
# tests/unit/core/config_db_resolver_test.gd — 配置解析直接单测
# 断言面 = ceil 进度键解析语义(向上取整保证偏难)、行集多行的行序保序、
# 难度修饰乘数的 clamp 域。行集由夹具注入,不碰生产表——期望值由注入行数出来。
# 断言面 = 深度带寻址语义(请求落带取偏难一侧、行集多行行序保序、越顶带 clamp+告警去重、
# 行类路由、越域 fatal)、难度修饰乘数的 clamp 域。行集由夹具注入,不碰生产表——期望值由注入行数出来。
# 运行:godot --headless --path game --script res://tests/unit/core/config_db_resolver_test.gd
extends SceneTree
@@ -19,8 +19,11 @@ func _init() -> void:
func() -> Array: return _enemy_rows(),
func(): return _difficulty_table(),
)
_test_ceil_progress_key()
_test_band_hit()
_test_rowset_order_preserved()
_test_top_band_clamp()
_test_row_kind_routing()
_test_request_oob()
_test_difficulty_clamp()
quit(Kit.finish("config_db_resolver_test"))
@@ -33,11 +36,13 @@ func _board_gen_rows() -> Array:
]
# 带键 [0,5]:请求 4 未精确落键,取首个 ≥4 的带 5(偏难一侧),不回带 0
func _enemy_rows() -> Array:
return [
{"level_index": 1, "node_depth": 0, "enemy_id": "goblin"},
{"level_index": 1, "node_depth": 0, "enemy_id": "bugbear"},
{"level_index": 1, "node_depth": 5, "enemy_id": "hobgoblin"},
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin"},
{"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "bugbear"},
{"level_index": 1, "row_kind": "battle", "depth_band": 5, "enemy_id": "hobgoblin"},
{"level_index": 1, "row_kind": "boss", "depth_band": 9, "enemy_id": "ogre_chief"},
]
@@ -48,26 +53,65 @@ func _difficulty_table():
return _FakeDifficulty.new(rows)
# ceil 语义:请求 (1,1) 落在 d=0 与 d=3 之间,向上取到 d=3(偏难),不是向下取 d=0
func _test_ceil_progress_key() -> void:
var hit: Dictionary = _resolver.resolve_board_gen(1, 1)
Kit.check("ceil 进度键:请求 (1,1) 命中 (1,3) 而非 (1,0)——偏难语义",
int(hit.get("node_depth", -1)) == 3 and int(hit.get("board_width", 0)) == 12)
var exact: Dictionary = _resolver.resolve_board_gen(2, 0)
Kit.check("精确命中:(2,0) 取本行", int(exact.get("board_width", 0)) == 14)
func _test_band_hit() -> void:
var hit: Array = _resolver.resolve_enemy_rowset(1, 4, "battle")
Kit.check("带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕",
hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin")
var exact: Array = _resolver.resolve_enemy_rowset(1, 5, "battle")
Kit.check("精确落带:请求深度 5 命中带 5〔fv1〕",
exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin")
func _test_rowset_order_preserved() -> void:
var rows: Array = _resolver.resolve_enemy_rowset(1, 0)
Kit.check("行集返回同坐标全部行(2 行)", rows.size() == 2)
var rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle")
Kit.check("行集返回同带全部行(2 行)〔fv2〕", rows.size() == 2)
var ids: Array[String] = []
for r in rows:
ids.append(String((r as Dictionary).get("enemy_id", "")))
Kit.check("行集按表文件行序返回(goblin 先于 bugbear——行序即契约)",
Kit.check("行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕",
ids == ["goblin", "bugbear"])
var far: Array = _resolver.resolve_enemy_rowset(1, 4)
Kit.check("ceil 行集:请求 (1,4) 命中 (1,5)", far.size() == 1
and String((far[0] as Dictionary).get("enemy_id", "")) == "hobgoblin")
func _test_top_band_clamp() -> void:
_diags.clear()
var rows: Array = _resolver.resolve_enemy_rowset(1, 99, "battle")
Kit.check("越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕",
rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin")
var clamp_hit := false
for d in _diags:
if d.contains("warning/resolve_depth_clamp"):
clamp_hit = true
Kit.check("越顶带发 warning resolve_depth_clamp〔fv3〕", clamp_hit)
var warn_before: int = _diags.size()
_resolver.resolve_enemy_rowset(1, 99, "battle")
Kit.check("同键重复请求警告去重(四元组注册表)〔fv3〕", _diags.size() == warn_before)
func _test_row_kind_routing() -> void:
var battle_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle")
var boss_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "boss")
Kit.check("行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕",
battle_rows.size() == 2 and boss_rows.size() == 1
and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief")
func _test_request_oob() -> void:
_diags.clear()
_resolver.resolve_enemy_rowset(0, 0, "battle")
Kit.check("关卡序号 <1 → fatal request_oob〔fv5〕", _has_fatal_oob())
_diags.clear()
_resolver.resolve_enemy_rowset(1, -1, "battle")
Kit.check("节点深度 <0 → fatal request_oob〔fv5〕", _has_fatal_oob())
_diags.clear()
_resolver.resolve_enemy_rowset(1, 0, "elite")
Kit.check("row_kind 非法 → fatal request_oob〔fv5〕", _has_fatal_oob())
func _has_fatal_oob() -> bool:
for d in _diags:
if d.contains("fatal/request_oob"):
return true
return false
func _test_difficulty_clamp() -> void:
+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)