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

@@ -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: