iter54-卡3:精英专属池批(elite 行类与字段互锁/dangling_base/effective_pool 池继承合并/ENCOUNTER_ROW_KINDS 寻址门暂不接线/goblin_elite 落表+存量冒烟同步+图鉴导出认得精英行)——对照表 R1-R7 全绿(elite_row_test 六组/五件存量套件直跑绿/生产表 error 诊断 0),T3.8 里程碑出口 --code 六闸全绿【验收通过】

This commit is contained in:
DKChan committed 2026-11-07 03:05:12 +08:00
1 parent 4c424af358
commit f9c9caf56b
14 files changed
+540 -40

No files matched your search

@@ -256,16 +256,16 @@ 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 封闭枚举(非法值拒——键面换代后新增判据)
# 负向:row_kind 封闭枚举(非法值拒——键面换代后新增判据;elite 随精英专属池批转合法,非法样本换 miniboss)
var t_kind = EnemyTableScript.new()
t_kind.rows.assign([mk_row.call(1, "elite", 5, "goblin"), mk_row.call(1, "battle", 0, "goblin"),
t_kind.rows.assign([mk_row.call(1, "miniboss", 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)
Kit.check("非法 row_kind 被拒(封闭枚举 {battle, boss, elite})〔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")])
@@ -0,0 +1,305 @@
# tests/unit/data_config/elite_row_test.gd — 精英行单元套(精英专属池批)
# 断言面 = 精英行加载期校验(字段互锁/引用校验/错误类别)·有效池合并(基础池取深度带最小行
# +附加技)·继承不复制(改基础池传到精英、改写返回不回灌)·合并池过池构成纪律(跨表件)·
# 暂不接线(寻址行类门只放 battle/boss,elite 行静置合法)·生产表 goblin_elite 行(随 T3 落表追加)。
# 断言从公开入口进出:EnemyTable.validate()/effective_pool()/validate_skill_pool_links()/
# resolve_enemy_rowset()/ConfigDB.load_tables()。夹具一律 rows.assign([...])(定型数组直赋会触发
# 脚本错误静默中断——config_db_test 同款坑注)。
# 运行:godot --headless --path game --script res://tests/unit/data_config/elite_row_test.gd(expect 0)
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
const EnemyTableScript := preload("res://data/schemas/enemy_table.gd")
const ValidatorScript := preload("res://core/config_db_cross_validator.gd")
const SkillTableScript := preload("res://data/schemas/skill_table.gd")
const StatusTableScript := preload("res://data/schemas/status_effect_table.gd")
const ResolverScript := preload("res://core/config_db_resolver.gd")
var _diags: Array[String] = [] # T2.4 夹具 resolver 诊断捕获
func _init() -> void:
_test_elite_validation_rejects()
_test_effective_pool_merge()
_test_base_pool_change_propagates()
_test_merged_pool_composition()
_test_elite_unreachable_by_encounter()
_test_production_goblin_elite()
quit(Kit.finish("elite_row_test", "PASS — 精英行校验/有效池/继承不复制/合并池纪律/暂不接线/生产表落行 全过"))
# 行构造助手(照 config_db_test mk_row 写全字段;elite 行形态=去掉 pool、加 base_ref/pool_extra)
func _mk_row(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}]}
func _mk_elite_row(l: int, band: int, eid: String, base_ref: String, extra: Array) -> Dictionary:
return {"level_index": l, "row_kind": "elite", "depth_band": band, "enemy_id": eid, "display_name": eid + "名",
"unit_count": 1, "hp": 20, "atk": 2, "base_ref": base_ref, "pool_extra": extra}
# 合法夹具(三行——battle 锚/boss/elite 引用同关 battle 敌种)
func _legal_rows() -> Array:
var goblin := _mk_row(1, "battle", 0, "goblin")
goblin["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
return [goblin, _mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}])]
# category 计数(错误列表按类别筛)
func _count_cat(errs: Array, cat: String) -> int:
var n := 0
for e in errs:
if String((e as Dictionary).get("category", "")) == cat:
n += 1
return n
# ---- R1:elite 行类合法与字段互锁、引用校验(合法零错+十一违例各自「含」预期类别)----
func _test_elite_validation_rejects() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:325 elite 行字段互锁与基础引用违例加载期返回错误
var t_ok = EnemyTableScript.new()
t_ok.rows.assign(_legal_rows())
var errs: Array = t_ok.validate()
Kit.check("合法夹具 validate 零错(battle 锚+boss+elite 引用在位——实得 %s)" % str(errs), errs.is_empty())
# ① elite 删 base_ref → cross_field
var rows1 := _legal_rows()
rows1[2].erase("base_ref")
Kit.check("① elite 删 base_ref 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows1), "cross_field") >= 1)
# ② base_ref 与自身同名 → cross_field
var rows2 := _legal_rows()
rows2[2]["base_ref"] = "goblin_elite"
Kit.check("② base_ref 与 enemy_id 同名报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows2), "cross_field") >= 1)
# ③ pool_extra 为空数组 → cross_field
var rows3 := _legal_rows()
rows3[2]["pool_extra"] = []
Kit.check("③ pool_extra 空数组报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows3), "cross_field") >= 1)
# ④ elite 删 pool_extra 键 → cross_field
var rows4 := _legal_rows()
rows4[2].erase("pool_extra")
Kit.check("④ elite 删 pool_extra 键报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows4), "cross_field") >= 1)
# ⑤ elite 加 pool → cross_field
var rows5 := _legal_rows()
rows5[2]["pool"] = [{"skill_id": "attack", "weight": 9}]
Kit.check("⑤ elite 带 pool 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows5), "cross_field") >= 1)
# ⑥ battle 行加 base_ref → cross_field
var rows6 := _legal_rows()
rows6[0]["base_ref"] = "ogre_chief"
Kit.check("⑥ battle 行带 base_ref 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows6), "cross_field") >= 1)
# ⑦ battle 行加 pool_extra → cross_field
var rows7 := _legal_rows()
rows7[0]["pool_extra"] = [{"skill_id": "heavy", "weight": 2}]
Kit.check("⑦ battle 行带 pool_extra 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows7), "cross_field") >= 1)
# ⑧ base_ref="hobgoblin"(同关无此 battle 敌种)→ dangling_base
var rows8 := _legal_rows()
rows8[2]["base_ref"] = "hobgoblin"
Kit.check("⑧ base_ref 悬空报 dangling_base〔精英批 V1〕", _count_cat(_validate_rows(rows8), "dangling_base") >= 1)
# ⑨ 再加 elite2 行、base_ref 改指它 → dangling_base 且 message 含「elite」
var rows9 := _legal_rows()
rows9.append(_mk_elite_row(1, 0, "goblin_elite2", "goblin", [{"skill_id": "heavy", "weight": 2}]))
rows9[2]["base_ref"] = "goblin_elite2"
var errs9: Array = _validate_rows(rows9)
var has_elite_base := false
for e in errs9:
if String((e as Dictionary).get("category", "")) == "dangling_base" \
and String((e as Dictionary).get("message", "")).contains("elite"):
has_elite_base = true
Kit.check("⑨ base_ref 指向 elite 行报 dangling_base 且 message 含 elite〔精英批 V1〕", has_elite_base)
# ⑩ pool_extra 含基础池已有 lunge → dup_key
var rows10 := _legal_rows()
rows10[2]["pool_extra"] = [{"skill_id": "heavy", "weight": 2}, {"skill_id": "lunge", "weight": 1}]
Kit.check("⑩ 附加技与基础池重复报 dup_key〔精英批 V1〕", _count_cat(_validate_rows(rows10), "dup_key") >= 1)
# ⑪ pool_extra 条目权重 0 → domain 且 message 含 pool_extra
var rows11 := _legal_rows()
rows11[2]["pool_extra"] = [{"skill_id": "heavy", "weight": 0}]
var errs11: Array = _validate_rows(rows11)
var has_extra_domain := false
for e in errs11:
if String((e as Dictionary).get("category", "")) == "domain" \
and String((e as Dictionary).get("message", "")).contains("pool_extra"):
has_extra_domain = true
Kit.check("⑪ pool_extra 权重越域报 domain 且定位含 pool_extra〔精英批 V1〕", has_extra_domain)
func _validate_rows(rows: Array) -> Array:
var t = EnemyTableScript.new()
t.rows.assign(rows)
return t.validate()
# ---- R2:有效池=基础池+附加技,基础池取深度带最小行(深带行在前防取首行假绿)----
func _test_effective_pool_merge() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:149 精英有效池=同关战斗行类基础敌种深度带最小行的池+附加技
var rows := [
_mk_row(1, "battle", 3, "goblin"), # 深带行在前(取首行的错误实现必红)
_mk_row(1, "battle", 0, "goblin"),
_mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
]
rows[0]["pool"] = [{"skill_id": "attack", "weight": 5}, {"skill_id": "charge", "weight": 2}]
rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
var t = EnemyTableScript.new()
t.rows.assign(rows)
var got := {}
for e in t.effective_pool(rows[3]):
got[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
Kit.check("精英有效池=浅带基础池+附加技({attack9,lunge1,heavy2}——非深带行池)〔精英批 V2〕",
got.get("attack", 0) == 9 and got.get("lunge", 0) == 1 and got.get("heavy", 0) == 2 and got.size() == 3)
var got_b0 := {}
for e in t.effective_pool(rows[1]):
got_b0[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
Kit.check("battle 行有效池=自身 pool(读法等价面——{attack9,lunge1})〔精英批 V2〕",
got_b0.get("attack", 0) == 9 and got_b0.get("lunge", 0) == 1 and got_b0.size() == 2)
# ---- R3:继承不复制——基础池改表传到精英、改写返回数组不回灌 ----
func _test_base_pool_change_propagates() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:149 继承不复制——基础池调参自动传到精英
var rows := [
_mk_row(1, "battle", 3, "goblin"),
_mk_row(1, "battle", 0, "goblin"),
_mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
]
rows[0]["pool"] = [{"skill_id": "attack", "weight": 5}, {"skill_id": "charge", "weight": 2}]
rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
var t = EnemyTableScript.new()
t.rows.assign(rows)
var first: Array = t.effective_pool(rows[3])
for e in first:
if String((e as Dictionary).get("skill_id", "")) == "attack":
e["weight"] = 99 # 改写返回数组条目——表内行不得被回灌
rows[1]["pool"][0]["weight"] = 7 # 基础行(depth_band==0)attack 权重改 7
var got := {}
for e in t.effective_pool(rows[3]):
got[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
Kit.check("基础池改权重 7 传到精英有效池(按需现算零缓存)〔精英批 V3〕", got.get("attack", 0) == 7)
Kit.check("改写返回数组不回灌表内行(基础行 attack 仍=7)〔精英批 V3〕",
int(rows[1]["pool"][0]["weight"]) == 7)
# ---- R4:合并池过池构成纪律(跨表件校验对象=合并后的池)----
func _test_merged_pool_composition() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:150 池构成纪律校验对象是合并后的池
var skills := SkillTableScript.new()
skills.rows.assign([
{"skill_id": "attack", "type": "basic"},
{"skill_id": "lunge", "type": "damage"},
{"skill_id": "heavy", "type": "damage"},
{"skill_id": "harden", "type": "buff"},
{"skill_id": "charge", "type": "buff"},
])
var status := StatusTableScript.new()
status.rows.assign([]) # 空状态表——buff 无 buff_ref 可解析时 cast_side 不报(cross_validator :179-180)
# 合规精英:基础 attack/lunge+附加 heavy → 恰一普攻/增益≤1/无条件特殊≥1 全合规
var enemy_ok = EnemyTableScript.new()
enemy_ok.rows.assign([
_mk_row(1, "battle", 0, "goblin"),
_mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
])
enemy_ok.rows[0]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
enemy_ok.rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}] # boss 行自带合规池(纪律对全行生效——本组只数精英违规)
var errs_ok: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemy_ok, status)
Kit.check("合规精英合并池 pool_composition=0(实得 %s)" % str(errs_ok), _count_cat(errs_ok, "pool_composition") == 0)
# 附加第二普攻:基础 attack+附加 attack → basic=2 → pool_composition=1 且 message 含 goblin_elite
var enemy_dup_basic = EnemyTableScript.new()
enemy_dup_basic.rows.assign([
_mk_row(1, "battle", 0, "goblin"),
_mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "attack", "weight": 2}]),
])
enemy_dup_basic.rows[0]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
enemy_dup_basic.rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}] # boss 行自带合规池
var errs_dup: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemy_dup_basic, status)
Kit.check("附加第二普攻报 pool_composition=1 且 message 含 goblin_elite〔精英批 V4〕",
_count_cat(errs_dup, "pool_composition") == 1 and _msgs(errs_dup).any(func(m: String) -> bool: return m.contains("goblin_elite")))
# 附加第二增益:基础 attack/lunge/harden+附加 charge → buff=2 → pool_composition=1
var enemy_dup_buff = EnemyTableScript.new()
enemy_dup_buff.rows.assign([
_mk_row(1, "battle", 0, "goblin"),
_mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "charge", "weight": 2}]),
])
enemy_dup_buff.rows[0]["pool"] = [
{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}, {"skill_id": "harden", "weight": 2}]
enemy_dup_buff.rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}] # boss 行自带合规池
var errs_buff: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemy_dup_buff, status)
Kit.check("合并后增益超 1 报 pool_composition=1 且 message 含 goblin_elite〔精英批 V4〕",
_count_cat(errs_buff, "pool_composition") == 1 and _msgs(errs_buff).any(func(m: String) -> bool: return m.contains("goblin_elite")))
func _msgs(errs: Array) -> Array[String]:
var out: Array[String] = []
for e in errs:
out.append(String((e as Dictionary).get("message", "")))
return out
# ---- R5:暂不接线——表里有 elite 行时寻址拿不到它(行类门改动特有判据)----
func _test_elite_unreachable_by_encounter() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:151 精英行接线前不会被任何节点读到
_diags.clear()
var resolver = ResolverScript.new(
func(level, category, message) -> void:
_diags.append("%s/%s/%s" % [str(level), str(category), str(message)]),
func() -> Array: return [],
func() -> Array: return [
_mk_row(1, "battle", 0, "goblin"),
_mk_row(1, "boss", 9, "ogre_chief"),
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
],
func(): return null,
)
# ① 请求 elite → 空数组且诊断含 fatal 与 request_oob(只改枚举不改行类门时此请求会返回 goblin_elite——判据绑行类门改动)
var hit: Array = resolver.resolve_enemy_rowset(1, 0, "elite")
var has_fatal_oob := false
for d in _diags:
if d.contains("fatal") and d.contains("request_oob"):
has_fatal_oob = true
Kit.check("请求 elite 返回空且诊断含 fatal/request_oob〔精英批 V5〕", hit.is_empty() and has_fatal_oob)
# ② 同夹具 battle/boss 查询返回行中 elite 计数=0(非空前置:合计行数>0)
var battle_rows: Array = resolver.resolve_enemy_rowset(1, 0, "battle")
var boss_rows: Array = resolver.resolve_enemy_rowset(1, 9, "boss")
var elite_n := 0
for r in battle_rows + boss_rows:
if String((r as Dictionary).get("row_kind", "")) == "elite":
elite_n += 1
Kit.check("battle/boss 查询合计>0 且零 elite 行(非空前置在判据内)〔精英批 V5〕",
battle_rows.size() + boss_rows.size() > 0 and elite_n == 0)
# ---- R6:生产表地精精英落表(真实加载零错+行值逐位+有效池字典)----
func _test_production_goblin_elite() -> void:
# oracle: design/gdd/systems/战斗循环/敌人系统.md:147 精英=敌人表行类为 elite 的独立一行
var db = load("res://core/config_db.gd").new()
var ok: bool = db.load_tables("res://data/tables", true)
Kit.check("生产表 ConfigDB.load_tables 装载(is_ready=%s)〔精英批 V6〕" % str(db.is_ready), ok and db.is_ready)
Kit.check("生产表加载 error 级诊断计数=0(实得 %d)〔精英批 V6〕" % db.diagnostics_count("error"),
db.diagnostics_count("error") == 0)
var elite_rows: Array = []
for row in db.combat.enemy.rows:
if String(row.get("enemy_id", "")) == "goblin_elite":
elite_rows.append(row)
Kit.check("goblin_elite 行恰 1 条(实得 %d)〔精英批 V6〕" % elite_rows.size(), elite_rows.size() == 1)
if elite_rows.is_empty():
return
var r: Dictionary = elite_rows[0]
var checks := {
"level_index": 1, "row_kind": "elite", "depth_band": 0,
"display_name": "精英·地精", "hp": 20, "atk": 2, "atk_min": 1, "atk_max": 3,
"armor_class": 2, "armor_type": "light", "unit_count": 1, "base_ref": "goblin",
}
var bad: Array[String] = []
for k in checks:
if r.get(k) != checks[k]:
bad.append("%s=%s≠%s" % [k, str(r.get(k)), str(checks[k])])
Kit.check("行值 12 字段逐位相等(失配项 %s)〔精英批 V6〕" % [bad if not bad.is_empty() else "无"], bad.is_empty())
Kit.check("无 unit_count_max 键、无 pool 键(精英行形态面)〔精英批 V6〕",
not r.has("unit_count_max") and not r.has("pool"))
var got := {}
for e in db.combat.enemy.effective_pool(r):
got[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
Kit.check("有效池字典={attack9,lunge1,desperate_guard1,heavy2}(实得 %s)〔精英批 V6〕" % str(got),
got.get("attack", 0) == 9 and got.get("lunge", 0) == 1 and got.get("desperate_guard", 0) == 1 and got.get("heavy", 0) == 2 and got.size() == 4)
@@ -1,7 +1,7 @@
# tests/unit/data_config/table_structure_guard_test.gd — 表结构守卫单元测试宿主
# 断言面 = data/tables 各表的结构锚与守卫,七套:
# adventure 冒险表(档位配额/件值对/同调目录 12 件/装配→战斗贯通)
# enemy 敌人表(29 行带键行序/混编行集/三 Boss 行)
# enemy 敌人表(30 行带键行序/混编行集/三 Boss 行/精英行)
# AC 单轨与掷点参数(status_effect/reward/settlement 三处无双轨/免伤墙护栏/敌 AC 带宽)
# variation 地形变异(行数/候选池门控/跨字段互锁负样本)
# difficulty 难度表(空表零行为/修饰套乘/越域 clamp)
@@ -208,18 +208,18 @@ func _smoke_adventure_table() -> void:
v18_ok and locked_n == 12)
# ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:29 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单)----
# ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:30 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单·精英行)----
func _smoke_enemy_bands() -> void:
# oracle: design/gdd/systems/冒险循环/难度曲线与Boss.md:316 难度与敌人带校验(敌人带组)
print("--- 敌人表深度带键结构(29 行键序 · (1,3) 混编 · Boss 带键覆盖表)---")
# ① 29 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前)
print("--- 敌人表深度带键结构(30 行键序 · (1,3) 混编 · Boss 带键覆盖表 · 精英行)---")
# ① 30 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前;精英行随精英专属池批)
# 迭代 35 生成图深度扩面覆盖批随动:战斗带顶扩到各关最大生成深度(L1→6/L2→9/L3→13)、
# Boss 行按终点深度分带(L1 6/L2 8·9/L3 11·12·13),模板时代带键 4/9/9 全部在册不动
var expect_keys: Array[String] = [
"1|battle|0|goblin", "1|battle|1|goblin", "1|battle|2|bugbear", "1|battle|3|bugbear", "1|battle|3|goblin",
"1|battle|6|bugbear",
"1|boss|4|ogre_chief", "1|boss|6|ogre_chief",
"1|boss|4|ogre_chief", "1|boss|6|ogre_chief", "1|elite|0|goblin_elite",
"2|battle|0|bugbear", "2|battle|1|bugbear", "2|battle|4|giant_spider",
"2|battle|5|bugbear", "2|battle|5|giant_spider", "2|battle|6|giant_spider", "2|battle|7|giant_spider",
"2|battle|9|giant_spider",
@@ -232,8 +232,8 @@ func _smoke_enemy_bands() -> void:
for row in _db.combat.enemy.rows:
got_keys.append("%d|%s|%d|%s" % [int(row.get("level_index", -1)), String(row.get("row_kind", "")),
int(row.get("depth_band", -1)), String(row.get("enemy_id", ""))])
Kit.check("敌人表 29 行且键序 = 覆盖表序((1,3) 两行相邻、熊地精在前)〔L1混编 v1〕",
_db.combat.enemy.rows.size() == 29 and got_keys == expect_keys)
Kit.check("敌人表 30 行且键序 = 覆盖表序((1,3) 两行相邻、熊地精在前;精英行接 boss 行后)〔L1混编 v1〕",
_db.combat.enemy.rows.size() == 30 and got_keys == expect_keys)
# ② (1,3) 混编构成与行序+静态最坏骰和+行值整行复用(R1-R4)
var rowset13: Array[Dictionary] = _db.combat.enemy.resolve_rowset(1, 3, "battle")
var ids13: Array[String] = []
@@ -386,6 +386,8 @@ func _smoke_ac_swap_guard() -> void:
and l12_pool == {"attack": 6, "charge": 2, "heavy": 2})
var l1_totals := {}
for row in _db.combat.enemy.rows:
if String(row.get("row_kind", "")) == "elite":
continue # 曲线护栏管遭遇曲线——elite 不进遭遇(敌人系统.md:151),不跳过则 band0 失真
if int(row.get("level_index", -1)) != 1:
continue
var cnt_lo: int = int(row.get("unit_count", 1))