iter41-卡1:核心数据面结构收敛批:85键全清——三族拆域+config_db归位容器+存档拆件+meta瘦身+adventure拆段+spawn束化;行为零变化,8新件在阈
This commit is contained in:
1 parent
bda377eb16
commit
a613a3cb90
157 files changed
+7453
-4288
No files matched your search
@@ -24,7 +24,7 @@ func _init() -> void:
|
||||
|
||||
func _trigger_oob() -> void:
|
||||
# 越域请求(关卡序号 0 < 合法域 ≥1)——_fatal 内部 push fatal 诊断并 quit(1)(帧末退出)
|
||||
var got: Dictionary = _db.resolve_board_gen(0, 0)
|
||||
var got: Dictionary = _db.combat.board_gen.resolve_row(0, 0)
|
||||
if _db.diagnostics_count("fatal") >= 1:
|
||||
print("[fatal_oob_test] fatal 通道已触发(诊断在案)")
|
||||
quit(1) # 与 _fatal 的 quit(1) 同码双保险
|
||||
|
||||
@@ -30,7 +30,7 @@ func _init() -> void:
|
||||
# ---- 进度坐标 ceil 解析(R1:向上下取整保证偏难;越最大行 clamp 取最大值 + 警告去重)----
|
||||
|
||||
func _test_progress_resolution(db) -> void:
|
||||
var bg_rows: Array = db.board_gen.rows
|
||||
var bg_rows: Array = db.combat.board_gen.rows
|
||||
# 行键全集自表推导(禁写死关卡/深度数值)
|
||||
var level_keys: Array = []
|
||||
var depth_keys_by_level: Dictionary = {}
|
||||
@@ -51,7 +51,7 @@ func _test_progress_resolution(db) -> void:
|
||||
|
||||
# 精确命中:请求 = 表内现存 (L,D) → 原行返回(字段逐位相等)
|
||||
var probe_row: Dictionary = bg_rows[0]
|
||||
var got: Dictionary = db.resolve_board_gen(int(probe_row.level_index), int(probe_row.node_depth))
|
||||
var got: Dictionary = db.combat.board_gen.resolve_row(int(probe_row.level_index), int(probe_row.node_depth))
|
||||
Kit.check("进度解析精确命中返回原行(board_gen 首行)",
|
||||
int(got.get("level_index", -99)) == int(probe_row.level_index)
|
||||
and int(got.get("node_depth", -99)) == int(probe_row.node_depth)
|
||||
@@ -59,12 +59,12 @@ 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), "battle")
|
||||
var en_got: Array = db.combat.enemy.resolve_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()
|
||||
var deep: Dictionary = db.resolve_board_gen(min_l, int(min_depths[-1]) + 1)
|
||||
var deep: Dictionary = db.combat.board_gen.resolve_row(min_l, int(min_depths[-1]) + 1)
|
||||
Kit.check("深度越最大行 clamp 取该关最大深度行",
|
||||
int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]))
|
||||
Kit.check("clamp 解析发警告诊断(resolve_depth_clamp)", db.diagnostics_count("warning") >= 1)
|
||||
@@ -74,19 +74,19 @@ func _test_progress_resolution(db) -> void:
|
||||
found_clamp = true
|
||||
Kit.check("警告类别 = resolve_depth_clamp", found_clamp)
|
||||
var warn_before: int = db.diagnostics_count("warning")
|
||||
db.resolve_board_gen(min_l, int(min_depths[-1]) + 1)
|
||||
db.combat.board_gen.resolve_row(min_l, int(min_depths[-1]) + 1)
|
||||
Kit.check("同键重复请求警告去重(四元组注册表)", db.diagnostics_count("warning") == warn_before)
|
||||
|
||||
# 关卡 clamp:请求关卡超最大行 → 取最大关卡行
|
||||
db.reset_diagnostics()
|
||||
var high: Dictionary = db.resolve_board_gen(max_l + 1, 0)
|
||||
var high: Dictionary = db.combat.board_gen.resolve_row(max_l + 1, 0)
|
||||
Kit.check("关卡越最大行 clamp 取最大关卡行", int(high.get("level_index", -99)) == max_l)
|
||||
|
||||
|
||||
# ---- 变异池门控(unlock_level 闸 + ∅ board_types 通配 + 节点身份第三维)----
|
||||
|
||||
func _test_variant_pool_gate(db) -> void:
|
||||
var rows: Array = db.variation.rows
|
||||
var rows: Array = db.combat.variation.rows
|
||||
var gated_row := {}
|
||||
var wildcard_row := {}
|
||||
var global_row := {}
|
||||
@@ -103,19 +103,19 @@ func _test_variant_pool_gate(db) -> void:
|
||||
if not gated_row.is_empty():
|
||||
var u: int = int(gated_row.unlock_level)
|
||||
var tag: String = String(gated_row.board_types[0])
|
||||
var before: Array = db.variant_pool(u - 1, tag)
|
||||
var after: Array = db.variant_pool(u, tag)
|
||||
var before: Array = db.combat.variation.variant_pool(u - 1, tag)
|
||||
var after: Array = db.combat.variation.variant_pool(u, tag)
|
||||
Kit.check("变异池 unlock 前不含未解锁行(level=unlock−1)", not _rows_contain(before, gated_row))
|
||||
Kit.check("变异池 unlock 当级含该行", _rows_contain(after, gated_row))
|
||||
if not wildcard_row.is_empty():
|
||||
var u2: int = int(wildcard_row.unlock_level)
|
||||
var any_pool: Array = db.variant_pool(u2, "nonexistent_board_type_tag")
|
||||
var any_pool: Array = db.combat.variation.variant_pool(u2, "nonexistent_board_type_tag")
|
||||
Kit.check("∅ board_types 行通配(任一 board_type 命中)", _rows_contain(any_pool, wildcard_row))
|
||||
if not global_row.is_empty():
|
||||
var u3: int = int(global_row.unlock_level)
|
||||
Kit.check("global 行 boss 节点入池(board_types ∅ 通配)", _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row))
|
||||
Kit.check("global 行缺省双参调用跳过(既有调用口径零扰动)", not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row))
|
||||
Kit.check("global 行 battle 节点静默跳过(门控兜底零报错)", not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row))
|
||||
Kit.check("global 行 boss 节点入池(board_types ∅ 通配)", _rows_contain(db.combat.variation.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row))
|
||||
Kit.check("global 行缺省双参调用跳过(既有调用口径零扰动)", not _rows_contain(db.combat.variation.variant_pool(u3, "nonexistent_board_type_tag"), global_row))
|
||||
Kit.check("global 行 battle 节点静默跳过(门控兜底零报错)", not _rows_contain(db.combat.variation.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row))
|
||||
|
||||
|
||||
func _rows_contain(pool: Array, row: Dictionary) -> bool:
|
||||
@@ -128,28 +128,28 @@ func _rows_contain(pool: Array, row: Dictionary) -> bool:
|
||||
# ---- ShopTable 消费面(行集解析单点——runner/编排禁自扫表的契约消费侧验证)----
|
||||
|
||||
func _test_shop_accessors(db) -> void:
|
||||
var products: Array = db.shop_products()
|
||||
var products: Array = db.run.shop.product_rows()
|
||||
if products.is_empty():
|
||||
Kit.check("ShopTable products 行族非空(表已已入库前提)", false)
|
||||
return
|
||||
Kit.check("ShopTable products 行族非空", true)
|
||||
var first: Dictionary = products[0]
|
||||
var got: Dictionary = db.shop_product_row(String(first.product_id))
|
||||
var got: Dictionary = db.run.shop.product_row(String(first.product_id))
|
||||
Kit.check("shop_product_row 按 product_id 精确命中首行", got == first)
|
||||
Kit.check("shop_product_row 未知 id 返回空字典(容错通道)", db.shop_product_row("no_such_product_id__").is_empty())
|
||||
Kit.check("shop_product_row 未知 id 返回空字典(容错通道)", db.run.shop.product_row("no_such_product_id__").is_empty())
|
||||
var tier_rows: Array = db._shop_rows("gp_drops")
|
||||
if not tier_rows.is_empty():
|
||||
var tier0: String = String(tier_rows[0].tier)
|
||||
Kit.check("shop_gp_drop_row 按 tier 命中", not db.shop_gp_drop_row(tier0).is_empty())
|
||||
Kit.check("shop_gp_drop_row 按 tier 命中", not db.run.shop.gp_drop_row(tier0).is_empty())
|
||||
var bonus_rows: Array = db._shop_rows("level_gp_bonus")
|
||||
if not bonus_rows.is_empty():
|
||||
var blv: int = int(bonus_rows[0].level_index)
|
||||
Kit.check("shop_level_gp_bonus 按关卡序号命中行值", db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus))
|
||||
Kit.check("shop_level_gp_bonus 未登记关卡返回 0(兜底通道)", db.shop_level_gp_bonus(-1) == 0)
|
||||
Kit.check("shop_level_gp_bonus 按关卡序号命中行值", db.run.shop.level_gp_bonus_at(blv) == int(bonus_rows[0].bonus))
|
||||
Kit.check("shop_level_gp_bonus 未登记关卡返回 0(兜底通道)", db.run.shop.level_gp_bonus_at(-1) == 0)
|
||||
var price_rows: Array = db._shop_rows("price_level_bonus")
|
||||
if not price_rows.is_empty():
|
||||
var plv: int = int(price_rows[0].level_index)
|
||||
Kit.check("shop_price_level_bonus 按关卡序号命中行值", db.shop_price_level_bonus(plv) == int(price_rows[0].bonus))
|
||||
Kit.check("shop_price_level_bonus 按关卡序号命中行值", db.run.shop.price_level_bonus_at(plv) == int(price_rows[0].bonus))
|
||||
|
||||
|
||||
# ---- MetaTable 消费面 ----
|
||||
@@ -159,11 +159,16 @@ func _test_meta_accessors(db) -> void:
|
||||
var key_rows: Array = db._meta_rows("boss_keystone")
|
||||
if not key_rows.is_empty():
|
||||
var klv: int = int(key_rows[0].level_index)
|
||||
Kit.check("meta_boss_keystone_row 按关卡序号命中", db.meta_boss_keystone_row(klv) == key_rows[0])
|
||||
Kit.check("meta_boss_keystone_row 按关卡序号命中", db.crossrun.meta.boss_keystone_row(klv) == key_rows[0])
|
||||
var conv_rows: Array = db._meta_rows("conversion")
|
||||
if not conv_rows.is_empty():
|
||||
var cattr: String = String(conv_rows[0].attr)
|
||||
Kit.check("meta_conversion_row 按 attr 命中", db.meta_conversion_row(cattr) == conv_rows[0])
|
||||
var conv_hit := {}
|
||||
for r in db.crossrun.meta.conversion:
|
||||
if String(r.get("attr", "")) == cattr:
|
||||
conv_hit = r
|
||||
break
|
||||
Kit.check("meta.conversion 行集按 attr 命中(访问器退役改直扫同判据)", conv_hit == conv_rows[0])
|
||||
|
||||
|
||||
# ---- TABLE_MANIFEST ↔ data/tables 目录同步(各系统→D2 单点契约的登记面)----
|
||||
|
||||
@@ -26,7 +26,7 @@ func _init() -> void:
|
||||
# ---- 生产表六行正向(V1 生产面 + V11 行字段闭集)----
|
||||
|
||||
func _test_production_rows(db) -> void:
|
||||
var rows: Array = db.forge_routes()
|
||||
var rows: Array = db.crossrun.forge.route_rows()
|
||||
Kit.check("六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕", rows.size() == 6)
|
||||
var ids := []
|
||||
for row in rows:
|
||||
@@ -52,39 +52,44 @@ func _test_production_rows(db) -> void:
|
||||
initial.sort()
|
||||
Kit.check("初始持有恰 sword+light〔锻造工坊批 V1〕", initial == ["light", "sword"])
|
||||
# 默认两轴(契约 2 所有权移交——裸体态 normal/light)
|
||||
Kit.check("默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕", db.forge_default_attack_type() == "normal")
|
||||
Kit.check("默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕", db.forge_default_armor_type() == "light")
|
||||
Kit.check("默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕", db.crossrun.forge.default_attack_type == "normal")
|
||||
Kit.check("默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕", db.crossrun.forge.default_armor_type == "light")
|
||||
# 类型轴分族与并联双通道前置(4b-1 裁)
|
||||
Kit.check("长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕",
|
||||
String(db.forge_route("spear").get("prereq_route", "")) == "sword"
|
||||
and String(db.forge_route("staff").get("prereq_route", "")) == "sword")
|
||||
String(db.crossrun.forge.route("spear").get("prereq_route", "")) == "sword"
|
||||
and String(db.crossrun.forge.route("staff").get("prereq_route", "")) == "sword")
|
||||
Kit.check("重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕",
|
||||
String(db.forge_route("heavy").get("prereq_route", "")) == "light"
|
||||
and String(db.forge_route("ward").get("prereq_route", "")) == "light")
|
||||
String(db.crossrun.forge.route("heavy").get("prereq_route", "")) == "light"
|
||||
and String(db.crossrun.forge.route("ward").get("prereq_route", "")) == "light")
|
||||
Kit.check("四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕",
|
||||
int(db.forge_route("spear").get("unlock_price", 0)) == 200
|
||||
and int(db.forge_route("staff").get("unlock_price", 0)) == 200
|
||||
and int(db.forge_route("heavy").get("unlock_price", 0)) == 200
|
||||
and int(db.forge_route("ward").get("unlock_price", 0)) == 200)
|
||||
int(db.crossrun.forge.route("spear").get("unlock_price", 0)) == 200
|
||||
and int(db.crossrun.forge.route("staff").get("unlock_price", 0)) == 200
|
||||
and int(db.crossrun.forge.route("heavy").get("unlock_price", 0)) == 200
|
||||
and int(db.crossrun.forge.route("ward").get("unlock_price", 0)) == 200)
|
||||
# 甲型基础 AC(provisional 现役结构值——settlement.tres 矩阵同行)
|
||||
Kit.check("甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕",
|
||||
int(db.forge_route("light").get("base_ac", 0)) == 10
|
||||
and int(db.forge_route("heavy").get("base_ac", 0)) == 12
|
||||
and int(db.forge_route("ward").get("base_ac", 0)) == 9)
|
||||
int(db.crossrun.forge.route("light").get("base_ac", 0)) == 10
|
||||
and int(db.crossrun.forge.route("heavy").get("base_ac", 0)) == 12
|
||||
and int(db.crossrun.forge.route("ward").get("base_ac", 0)) == 9)
|
||||
# 按类型轴反查(甲路线 id=甲型同值的显式化入口)
|
||||
Kit.check("forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕",
|
||||
String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward")
|
||||
Kit.check("forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕",
|
||||
String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear")
|
||||
String(db.crossrun.forge.armor_route_by_type("ward").get("route_id", "")) == "ward")
|
||||
var wpn_hit := {}
|
||||
for r in db.crossrun.forge.route_rows():
|
||||
if String(r.get("kind", "")) == "weapon" and String(r.get("type_axis", "")) == "pierce":
|
||||
wpn_hit = r
|
||||
break
|
||||
Kit.check("forge route_rows 按轴反查武器路线(pierce)=长枪锻造〔锻造工坊批 V1——访问器退役改直扫同判据〕",
|
||||
String(wpn_hit.get("route_id", "")) == "spear")
|
||||
# 缺行/未知轴 = 空字典合法态
|
||||
Kit.check("未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕",
|
||||
db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty())
|
||||
db.crossrun.forge.route("blade").is_empty() and db.crossrun.forge.armor_route_by_type("plate").is_empty())
|
||||
|
||||
|
||||
# ---- 几何价格梯(V2 价格面单测半边——购买写在 T3 冒烟)----
|
||||
|
||||
func _test_price_ladder(db) -> void:
|
||||
var row: Dictionary = db.forge_route("sword")
|
||||
var row: Dictionary = db.crossrun.forge.route("sword")
|
||||
var prices := []
|
||||
for owned in range(5):
|
||||
prices.append(ForgeTableScript.level_price(row, owned))
|
||||
|
||||
@@ -41,9 +41,9 @@ func _table(rows: Array) -> Variant:
|
||||
# ---- 生产三行正向(V2)----
|
||||
|
||||
func _test_production(db) -> void:
|
||||
var r1: Dictionary = db.level_gen_params_row(1)
|
||||
var r2: Dictionary = db.level_gen_params_row(2)
|
||||
var r3: Dictionary = db.level_gen_params_row(3)
|
||||
var r1: Dictionary = db.run.level_gen_params.row_by_level_index(1)
|
||||
var r2: Dictionary = db.run.level_gen_params.row_by_level_index(2)
|
||||
var r3: Dictionary = db.run.level_gen_params.row_by_level_index(3)
|
||||
Kit.check("访问器读回 L1 行五旋钮齐备〔fv2〕",
|
||||
int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3)
|
||||
and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1)
|
||||
@@ -52,7 +52,7 @@ func _test_production(db) -> void:
|
||||
Kit.check("三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕",
|
||||
int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2
|
||||
and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8)
|
||||
Kit.check("生产表 validate 零错误〔fv2〕", db.level_gen_params.validate().is_empty())
|
||||
Kit.check("生产表 validate 零错误〔fv2〕", db.run.level_gen_params.validate().is_empty())
|
||||
|
||||
|
||||
# ---- 访问器缺行防御 ----
|
||||
@@ -69,40 +69,37 @@ func _test_negative() -> void:
|
||||
var base := [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4),
|
||||
_row(2, 9, 0.5, [0.5, 0.2, 0.15, 0.15], 2, 2, 6),
|
||||
_row(3, 13, 0.7, [0.55, 0.15, 0.15, 0.15], 2, 3, 8)]
|
||||
Kit.check("合法三行域校验零错误(夹具自证)〔fv2〕", _table(base).validate().is_empty())
|
||||
# ① L1 出口改 2 → 出口恒 1 断言红(负样本夹具)
|
||||
var bad_l1_exit := [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 2, 4), base[1], base[2]]
|
||||
Kit.check("L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕", not _table(bad_l1_exit).validate().is_empty())
|
||||
# ② 配比合计 0.5 → 合计 1 断言红(负样本夹具)
|
||||
var bad_ratio := [_row(1, 6, 0.3, [0.2, 0.1, 0.1, 0.1], 1, 1, 4), base[1], base[2]]
|
||||
Kit.check("配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕", not _table(bad_ratio).validate().is_empty())
|
||||
# ③ 层深低于下限
|
||||
var bad_layers := [_row(1, 5, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4), base[1], base[2]]
|
||||
Kit.check("层深 5<6 被拒(层深下限已锁)〔fv2〕", not _table(bad_layers).validate().is_empty())
|
||||
# ④ 增幅未变大(6/9/11:Δ12=3、Δ23=2)
|
||||
var bad_growth := [base[0], _row(2, 9, 0.5, [0.5, 0.2, 0.15, 0.15], 2, 2, 6),
|
||||
_row(3, 11, 0.7, [0.55, 0.15, 0.15, 0.15], 2, 3, 8)]
|
||||
Kit.check("层深增幅倒挂被拒(递增且增幅变大)〔fv2〕", not _table(bad_growth).validate().is_empty())
|
||||
# ⑤ 出口越上界(L3 出口 4)
|
||||
var bad_exit := [base[0], base[1], _row(3, 13, 0.7, [0.55, 0.15, 0.15, 0.15], 2, 4, 8)]
|
||||
Kit.check("出口 4 越域 [1,3] 被拒〔fv2〕", not _table(bad_exit).validate().is_empty())
|
||||
# ⑥ 岔口去路数改 (2,4)
|
||||
var bad_fork_row := _row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4)
|
||||
bad_fork_row["fork_ways_max"] = 4
|
||||
Kit.check("岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕",
|
||||
not _table([bad_fork_row, base[1], base[2]]).validate().is_empty())
|
||||
# ⑦ 键集非闭集(多塞未知键)
|
||||
var bad_keys_row: Dictionary = _row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4)
|
||||
bad_keys_row["mystery"] = 1
|
||||
# 逐类负向表:[个案标签, 行集, 期望零错误]——每行仍逐案断言,消息带期望/实际与违例明细
|
||||
var cases: Array = [
|
||||
["合法三行域校验零错误(夹具自证)", base, true],
|
||||
["L1 出口=2 被拒(出口 L1 恒 1 已锁)", bad_l1_exit, false],
|
||||
["配比合计 0.5 被拒(四类合计 1 已锁)", bad_ratio, false],
|
||||
["层深 5<6 被拒(层深下限已锁)", bad_layers, false],
|
||||
["层深增幅倒挂被拒(递增且增幅变大)", bad_growth, false],
|
||||
["出口 4 越域 [1,3] 被拒", bad_exit, false],
|
||||
["岔口去路数 (2,4) 被拒(恒 2/3 已锁)", [bad_fork_row, base[1], base[2]], false],
|
||||
["字段键集非 ROW_KEYS 闭集被拒", [bad_keys_row, base[1], base[2]], false],
|
||||
["键重复行被拒", [base[0], base[0]], false],
|
||||
["战斗保底 -1 被拒(≥0)", [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]], false],
|
||||
["空表域校验零错误(合法态)", [], true],
|
||||
]
|
||||
for c in cases:
|
||||
var errs: Array = _table(c[1]).validate()
|
||||
var expect_label := "零错误" if c[2] else "有违例"
|
||||
var actual_label := "零错误" if errs.is_empty() else "违例 %d 条 %s" % [errs.size(), str(errs)]
|
||||
Kit.check("域校验:%s(期望%s,实际%s)〔fv2〕" % [c[0], expect_label, actual_label],
|
||||
errs.is_empty() == c[2])
|
||||
Kit.check("字段键集非 ROW_KEYS 闭集被拒〔fv2〕",
|
||||
not _table([bad_keys_row, base[1], base[2]]).validate().is_empty())
|
||||
# ⑧ level_index 重复
|
||||
Kit.check("键重复行被拒〔fv2〕", not _table([base[0], base[0]]).validate().is_empty())
|
||||
# ⑨ 战斗保底负值
|
||||
Kit.check("战斗保底 -1 被拒(≥0)〔fv2〕",
|
||||
not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty())
|
||||
# ⑩ 空表=合法态(沿 difficulty_table 先例)
|
||||
Kit.check("空表域校验零错误(合法态)〔fv2〕", _table([]).validate().is_empty())
|
||||
@@ -34,11 +34,14 @@ func _smoke_initialize() -> void:
|
||||
rng.set_master_seed(42)
|
||||
# 统一流黄金向量(数据配置 AC-3:单盐沿用 board_gen 盐,
|
||||
# scope 0-3 与 board_gen 向量逐位连续;Python 参考实现逐位交叉验证一致)
|
||||
var golden := [8672514112661456564, 8834291164406037215, 7714373262647006282, 1280102845971471347,
|
||||
82585501446482399, 8078924421474470922, 6638052740430622424, 8264196488417721294]
|
||||
for i in golden.size():
|
||||
Kit.check("统一流向量 m=42 scope=%d(黄金向量;scope0=旧 board_gen 连续性锚)" % i,
|
||||
rng.derive_at(i) == golden[i])
|
||||
Kit.check("统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)", rng.derive_at(0) == 8672514112661456564)
|
||||
Kit.check("统一流向量 m=42 scope=1", rng.derive_at(1) == 8834291164406037215)
|
||||
Kit.check("统一流向量 m=42 scope=2", rng.derive_at(2) == 7714373262647006282)
|
||||
Kit.check("统一流向量 m=42 scope=3", rng.derive_at(3) == 1280102845971471347)
|
||||
Kit.check("统一流向量 m=42 scope=4", rng.derive_at(4) == 82585501446482399)
|
||||
Kit.check("统一流向量 m=42 scope=5", rng.derive_at(5) == 8078924421474470922)
|
||||
Kit.check("统一流向量 m=42 scope=6", rng.derive_at(6) == 6638052740430622424)
|
||||
Kit.check("统一流向量 m=42 scope=7", rng.derive_at(7) == 8264196488417721294)
|
||||
Kit.check("derive_at 纯函数恒等", rng.derive_at(0) == rng.derive_at(0))
|
||||
# scope 取号语义:首个 open_stream = 全局 scope 0;计数器单调;用途标签不参与派生(只进事件日志)
|
||||
var s0 = rng.open_stream("board_gen")
|
||||
|
||||
@@ -75,7 +75,7 @@ static func _errs_has_cross_field(errs: Array) -> bool:
|
||||
# 首遇带数据驱动定位(契约 8):该 (L, battle, enemy_id) 的最低 depth_band,禁写死坐标数字
|
||||
func _first_encounter_band(level_index: int, enemy_id: String) -> int:
|
||||
var best := -1
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
if int(row.get("level_index", -1)) != level_index:
|
||||
continue
|
||||
if String(row.get("row_kind", "")) != "battle" or String(row.get("enemy_id", "")) != enemy_id:
|
||||
@@ -92,28 +92,28 @@ func _smoke_adventure_table() -> void:
|
||||
Kit.check("db.adventure 已加载(adventure.tres 入 manifest)", _db.adventure != null)
|
||||
if _db.adventure == null:
|
||||
return
|
||||
Kit.check("AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)", _db.adventure.validate().is_empty())
|
||||
Kit.check("clear_heal_pct ∈ [0,100](%d——关卡完成部分回血比例)" % int(_db.adventure.clear_heal_pct),
|
||||
int(_db.adventure.clear_heal_pct) >= 0 and int(_db.adventure.clear_heal_pct) <= 100)
|
||||
for tier in _db.adventure.TIERS:
|
||||
Kit.check("档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])],
|
||||
int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]))
|
||||
Kit.check("令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕", int(_db.adventure.deck_total()) == 102)
|
||||
Kit.check("AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)", _db.run.adventure.validate().is_empty())
|
||||
Kit.check("clear_heal_pct ∈ [0,100](%d——关卡完成部分回血比例)" % int(_db.run.adventure.clear_heal_pct),
|
||||
int(_db.run.adventure.clear_heal_pct) >= 0 and int(_db.run.adventure.clear_heal_pct) <= 100)
|
||||
for tier in _db.run.adventure.TIERS:
|
||||
Kit.check("档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" % [tier, int(_db.run.adventure.TIER_QUOTAS[tier])],
|
||||
int(_db.run.adventure.tier_total(tier)) == int(_db.run.adventure.TIER_QUOTAS[tier]))
|
||||
Kit.check("令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕", int(_db.run.adventure.deck_total()) == 102)
|
||||
Kit.check("数值对(四级换代承袭):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2",
|
||||
int(_db.adventure.curio_value("gp", "common")) == 20 and int(_db.adventure.curio_value("gp", "excellent")) == 50
|
||||
and int(_db.adventure.curio_value("attack", "common")) == 1 and int(_db.adventure.curio_value("attack", "excellent")) == 2
|
||||
and int(_db.adventure.curio_value("max_hp", "common")) == 5 and int(_db.adventure.curio_value("max_hp", "excellent")) == 10
|
||||
and int(_db.adventure.curio_value("armor", "common")) == 1 and int(_db.adventure.curio_value("armor", "excellent")) == 2)
|
||||
int(_db.run.adventure.curio_value("gp", "common")) == 20 and int(_db.run.adventure.curio_value("gp", "excellent")) == 50
|
||||
and int(_db.run.adventure.curio_value("attack", "common")) == 1 and int(_db.run.adventure.curio_value("attack", "excellent")) == 2
|
||||
and int(_db.run.adventure.curio_value("max_hp", "common")) == 5 and int(_db.run.adventure.curio_value("max_hp", "excellent")) == 10
|
||||
and int(_db.run.adventure.curio_value("armor", "common")) == 1 and int(_db.run.adventure.curio_value("armor", "excellent")) == 2)
|
||||
Kit.check("恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕",
|
||||
int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1)
|
||||
int(_db.run.adventure.curio_value("heal", "excellent")) == 6 and int(_db.run.adventure.curio_value("ap", "rare")) == 1)
|
||||
Kit.check("cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕",
|
||||
int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99)
|
||||
int(_db.run.adventure.curio_cap("gp")) == 99 and int(_db.run.adventure.curio_cap("attack")) == 99)
|
||||
Kit.check("同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕",
|
||||
Attunement.slots(_db, null) == 0)
|
||||
Kit.check("同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕",
|
||||
Attunement.slots(_db, _TreeSaveMock.new()) == 1)
|
||||
# 同调目录 12 件(V1/V2 断言落点;装配→战斗贯通与空装配基线的现行主语 = morning_order)
|
||||
var trows: Array = _db.treasure_attunement_rows()
|
||||
var trows: Array = _db.crossrun.treasure_items.attunement_rows()
|
||||
var tid_set := {}
|
||||
for r in trows:
|
||||
tid_set[String(r.get("item_id", ""))] = true
|
||||
@@ -160,11 +160,11 @@ func _smoke_adventure_table() -> void:
|
||||
alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2)
|
||||
var orch_l = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(191))
|
||||
Kit.check("装配→战斗贯通:enc.attunement=[morning_order] 起战 attune_has=true(注入面+只读出口)〔秘宝批换主语〕",
|
||||
orch_l.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "attunement": ["morning_order"]})
|
||||
orch_l.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp), "attunement": ["morning_order"]})
|
||||
and orch_l.attune_has("morning_order"))
|
||||
var orch_e = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(192))
|
||||
Kit.check("空装配基线:不带 attunement 起战 attune_has=false(off 态零扰动)〔秘宝批换主语〕",
|
||||
orch_e.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)})
|
||||
orch_e.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)})
|
||||
and not orch_e.attune_has("morning_order"))
|
||||
# V9 静态词表断言(棋盘事实不可变契约 3:12 行 triggers do.kind 与 effect_kinds
|
||||
# 全集零棋盘改写类词〔rewrite_board/remove_mines/cell_removal=改写族——现表零引用〕;
|
||||
@@ -223,14 +223,14 @@ func _smoke_enemy_bands() -> void:
|
||||
"3|boss|9|duergar_king", "3|boss|11|duergar_king", "3|boss|12|duergar_king", "3|boss|13|duergar_king",
|
||||
]
|
||||
var got_keys: Array[String] = []
|
||||
for row in _db.enemy.rows:
|
||||
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("敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕",
|
||||
_db.enemy.rows.size() == 28 and got_keys == expect_keys)
|
||||
_db.combat.enemy.rows.size() == 28 and got_keys == expect_keys)
|
||||
# ② hobgoblin 缺口行留任随迁(契约 9:不删行不补立绘——缺口登记随行)
|
||||
var hob := {}
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
if String(row.get("enemy_id", "")) == "hobgoblin":
|
||||
hob = row
|
||||
Kit.check("hobgoblin 行在 (1,battle,3) 留任随迁(缺口行不删不补——补齐归敌种内容批)〔fv12〕〔fv25〕",
|
||||
@@ -242,7 +242,7 @@ func _smoke_enemy_bands() -> void:
|
||||
# 生成图多终点要求每终点深度解析到互异带键 ⇒ 每关 Boss 行必然多于一行——
|
||||
# 迭代 35 覆盖批随动改写为带键清单,「带键互异」的运行时把关在 level_shop_economy_test〔v11〕)
|
||||
var boss_bands := {}
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
if String(row.get("row_kind", "")) == "boss":
|
||||
var lk: int = int(row.get("level_index", -1))
|
||||
if not boss_bands.has(lk):
|
||||
@@ -269,47 +269,47 @@ func _smoke_enemy_bands() -> void:
|
||||
func _smoke_ac_swap_guard() -> void:
|
||||
print("--- 护甲等级掷点批·换装护栏 ---")
|
||||
# ① 三处无双轨
|
||||
Kit.check("状态表 defense_up 行已退役(无双轨)", _db.status_effect_row("defense_up").is_empty())
|
||||
var ac_row: Dictionary = _db.status_effect_row("ac_up")
|
||||
Kit.check("状态表 defense_up 行已退役(无双轨)", _db.combat.status_effects.row_by_id("defense_up").is_empty())
|
||||
var ac_row: Dictionary = _db.combat.status_effects.row_by_id("ac_up")
|
||||
Kit.check("ac_up 行在位(hero · +1 ac · 只续时不叠层——2026-09-20 主角增益续时批)",
|
||||
not ac_row.is_empty() and String(ac_row.get("target_side", "")) == "hero" \
|
||||
and String(ac_row.get("unit", "")) == "ac" and int(ac_row.get("magnitude", 0)) == 1 \
|
||||
and String(ac_row.get("stack_policy", "")) == "refresh")
|
||||
var ac_reward := {}
|
||||
for row in _db.reward.rows:
|
||||
for row in _db.run.reward.rows:
|
||||
if String(row.get("cell_type", "")) == "defense" and String(row.get("polarity", "")) == "positive":
|
||||
ac_reward = row
|
||||
Kit.check("奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)",
|
||||
String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护")
|
||||
Kit.check("effect_pool_map 无 defense_up/ac_up 键(减伤不入池——AC 不经乘区)",
|
||||
not _db.settlement.core.effect_pool_map.has("defense_up") and not _db.settlement.core.effect_pool_map.has("ac_up"))
|
||||
not _db.combat.settlement.core.effect_pool_map.has("defense_up") and not _db.combat.settlement.core.effect_pool_map.has("ac_up"))
|
||||
# ② 免伤墙归零护栏(泛化——BUG-0003 免疫)
|
||||
var wall_ok := true
|
||||
var wall_detail := ""
|
||||
for eff_id in _db.settlement.core.effect_pool_map.keys():
|
||||
if String(_db.settlement.core.effect_pool_map[eff_id]) != "hero_defense":
|
||||
for eff_id in _db.combat.settlement.core.effect_pool_map.keys():
|
||||
if String(_db.combat.settlement.core.effect_pool_map[eff_id]) != "hero_defense":
|
||||
continue
|
||||
var row: Dictionary = _db.status_effect_row(eff_id)
|
||||
var row: Dictionary = _db.combat.status_effects.row_by_id(eff_id)
|
||||
if row.is_empty() or String(row.get("unit", "")) != "percent":
|
||||
continue
|
||||
var floor_v: int = int(row.get("magnitude", 0)) * int(row.get("max_stack", 1))
|
||||
if floor_v <= int(_db.settlement.core.pool_clamp_lo):
|
||||
if floor_v <= int(_db.combat.settlement.core.pool_clamp_lo):
|
||||
wall_ok = false
|
||||
wall_detail += " %s(%d)" % [eff_id, floor_v]
|
||||
Kit.check("hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" % [
|
||||
wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.core.pool_clamp_lo)], wall_ok)
|
||||
wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.combat.settlement.core.pool_clamp_lo)], wall_ok)
|
||||
var hd_maps := 0
|
||||
for eff_id in _db.settlement.core.effect_pool_map.keys():
|
||||
if String(_db.settlement.core.effect_pool_map[eff_id]) == "hero_defense":
|
||||
for eff_id in _db.combat.settlement.core.effect_pool_map.keys():
|
||||
if String(_db.combat.settlement.core.effect_pool_map[eff_id]) == "hero_defense":
|
||||
hd_maps += 1
|
||||
Kit.check("hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)", hd_maps == 0)
|
||||
# ③ 单位表 armor_class:域内校验(配置推导——正式投值归怪物/难度批,改表不断锚)
|
||||
Kit.check("主角 armor_class 在域内(%d——暂定基准随表走)" % int(_db.hero.armor_class),
|
||||
int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30)
|
||||
Kit.check("主角 armor_class 在域内(%d——暂定基准随表走)" % int(_db.combat.hero.armor_class),
|
||||
int(_db.combat.hero.armor_class) >= 0 and int(_db.combat.hero.armor_class) <= 30)
|
||||
var ac_band := true
|
||||
var ac_detail := ""
|
||||
var heavy_names := ["熊地精重击手", "灰矮人重装手"] # 逐行手设值 8/9,非精英编码位
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
var ac: int = int(row.get("armor_class", -1))
|
||||
var dname := String(row.get("display_name", "?"))
|
||||
var boss_row: bool = String(row.get("enemy_id", "")).ends_with("_chief") or \
|
||||
@@ -325,7 +325,7 @@ func _smoke_ac_swap_guard() -> void:
|
||||
ac_detail if ac_detail != "" else "无"], ac_band)
|
||||
# ③b Boss 台阶断言(关卡即台阶——L1 8 < L2 10 < L3 12)+ 首遇位教学位低 AC(≤3)
|
||||
var boss_ac := {}
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
var eid := String(row.get("enemy_id", ""))
|
||||
if eid == "ogre_chief" or eid == "drider" or eid == "duergar_king":
|
||||
boss_ac[int(row.get("level_index", 0))] = int(row.get("armor_class", 0))
|
||||
@@ -337,7 +337,7 @@ func _smoke_ac_swap_guard() -> void:
|
||||
# 曲线护栏 = 深度带升序相邻带「hp × 中位数量」总量比 ≤ 2.5(12/10/22/35/72 现行全绿;不用单调不回撤——会红在首带→次带)
|
||||
var l12_band := _first_encounter_band(1, "bugbear")
|
||||
var l12_row: Dictionary = {}
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
if int(row.get("level_index", -1)) == 1 and String(row.get("row_kind", "")) == "battle" \
|
||||
and int(row.get("depth_band", -1)) == l12_band and String(row.get("enemy_id", "")) == "bugbear":
|
||||
l12_row = row
|
||||
@@ -350,7 +350,7 @@ func _smoke_ac_swap_guard() -> void:
|
||||
and int(l12_row.get("armor_class", -1)) == 2 \
|
||||
and l12_pool == {"attack": 6, "charge": 2, "heavy": 2})
|
||||
var l1_totals := {}
|
||||
for row in _db.enemy.rows:
|
||||
for row in _db.combat.enemy.rows:
|
||||
if int(row.get("level_index", -1)) != 1:
|
||||
continue
|
||||
var cnt_lo: int = int(row.get("unit_count", 1))
|
||||
@@ -370,12 +370,12 @@ func _smoke_ac_swap_guard() -> void:
|
||||
Kit.check("第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" % [
|
||||
curve_detail if curve_detail != "" else "无"], curve_ok)
|
||||
# ④ 掷点参数域(加载校验已过——此处钉形状语义)
|
||||
var bounds: Array = _db.settlement.damage_roll.roll_tier_bounds
|
||||
var discounts: Array = _db.settlement.damage_roll.roll_tier_discounts
|
||||
var bounds: Array = _db.combat.settlement.damage_roll.roll_tier_bounds
|
||||
var discounts: Array = _db.combat.settlement.damage_roll.roll_tier_discounts
|
||||
Kit.check("掷点参数拍板值在位(d20 / nat20 暴击 ×200%% / 下限 1 / attack_bonus 0 / armor_roll 流)",
|
||||
int(_db.settlement.damage_roll.roll_faces) == 20 and int(_db.settlement.damage_roll.crit_roll) == 20 \
|
||||
and int(_db.settlement.damage_roll.crit_multiplier) == 200 and int(_db.settlement.damage_roll.damage_floor) == 1 \
|
||||
and int(_db.settlement.damage_roll.attack_bonus) == 0 and String(_db.settlement.damage_roll.armor_roll_stream) == "armor_roll")
|
||||
int(_db.combat.settlement.damage_roll.roll_faces) == 20 and int(_db.combat.settlement.damage_roll.crit_roll) == 20 \
|
||||
and int(_db.combat.settlement.damage_roll.crit_multiplier) == 200 and int(_db.combat.settlement.damage_roll.damage_floor) == 1 \
|
||||
and int(_db.combat.settlement.damage_roll.attack_bonus) == 0 and String(_db.combat.settlement.damage_roll.armor_roll_stream) == "armor_roll")
|
||||
Kit.check("档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)",
|
||||
bounds == [3, 7] and discounts == [75, 50, 25])
|
||||
|
||||
@@ -385,24 +385,24 @@ func _smoke_ac_swap_guard() -> void:
|
||||
func _smoke_variation_framework() -> void:
|
||||
print("--- 地形变异 框架面(首批四类型 + 候选池门控 + 跨字段互锁)---")
|
||||
Kit.check("变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)",
|
||||
_db.variation.rows.size() == 6)
|
||||
_db.combat.variation.rows.size() == 6)
|
||||
Kit.check("变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)",
|
||||
_db.variation.validate().is_empty())
|
||||
_db.combat.variation.validate().is_empty())
|
||||
var pool1_ids: Array = []
|
||||
for r in _db.variant_pool(1, "dungeon"):
|
||||
for r in _db.combat.variation.variant_pool(1, "dungeon"):
|
||||
pool1_ids.append(String(r.variant_id))
|
||||
Kit.check("L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)", pool1_ids == ["thorn", "vine"])
|
||||
var pool2_ids: Array = []
|
||||
for r in _db.variant_pool(2, "underdark"):
|
||||
for r in _db.combat.variation.variant_pool(2, "underdark"):
|
||||
pool2_ids.append(String(r.variant_id))
|
||||
Kit.check("L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)", pool2_ids == ["magma", "ash"])
|
||||
Kit.check("unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)", _db.variant_pool(1, "underdark").is_empty())
|
||||
Kit.check("unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)", _db.combat.variation.variant_pool(1, "underdark").is_empty())
|
||||
var pool2d_ids: Array = []
|
||||
for r in _db.variant_pool(2, "dungeon"):
|
||||
for r in _db.combat.variation.variant_pool(2, "dungeon"):
|
||||
pool2d_ids.append(String(r.variant_id))
|
||||
Kit.check("board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)",
|
||||
pool2d_ids == ["thorn", "vine"])
|
||||
Kit.check("清单外 variant_id 返回空(读取通道在位)", _db.variation.row_by_id("nonexistent").is_empty())
|
||||
Kit.check("清单外 variant_id 返回空(读取通道在位)", _db.combat.variation.row_by_id("nonexistent").is_empty())
|
||||
# 跨字段互锁负样本(加载期致命——枚举语义一致性闸)
|
||||
var vt = load("res://data/schemas/variation_table.gd").new()
|
||||
vt.rows.assign([{"variant_id": "bad1", "display_name": "坏行甲", "unlock_level": 1, "scope": "region",
|
||||
@@ -419,30 +419,30 @@ func _smoke_variation_framework() -> void:
|
||||
|
||||
func _smoke_difficulty_table() -> void:
|
||||
print("--- 难度曲线与Boss·DifficultyTable 结构位 ---")
|
||||
Kit.check("DifficultyTable MVP 空表加载(结构位)", _db.difficulty != null and _db.difficulty.rows.is_empty())
|
||||
Kit.check("DifficultyTable MVP 空表加载(结构位)", _db.difficulty != null and _db.run.difficulty.rows.is_empty())
|
||||
# ① 空表零行为:敌人系统.spawn 敌值 = EnemyTable 行值原样
|
||||
var enemy_system = load("res://core/unit_container.gd").new(_db)
|
||||
Kit.check("spawn 受理", enemy_system.spawn(1, 0, int(_db.hero.max_hp)))
|
||||
Kit.check("spawn 受理", enemy_system.spawn({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}))
|
||||
var unit = enemy_system.all_enemies()[0]
|
||||
var d0: Dictionary = _db.resolve_enemy_rowset(1, 0, "battle")[0] # 行集首行(带键寻址:战斗节点查 battle 键域)
|
||||
var d0: Dictionary = _db.combat.enemy.resolve_rowset(1, 0, "battle")[0] # 行集首行(带键寻址:战斗节点查 battle 键域)
|
||||
Kit.check("空表零修饰(hp=%d atk=%d = 行值 %d/%d)" % [int(unit.hp), int(unit.atk), int(d0.hp), int(d0.atk)],
|
||||
int(unit.hp) == int(d0.hp) and int(unit.atk) == int(d0.atk))
|
||||
# ② 修饰行运行时注入(debug 通道——R3 override 同族):套乘生效于 hp/atk
|
||||
_db.difficulty.rows.append({"level_index": 1, "node_depth": 0, "hp_mult": 1.5, "atk_mult": 2.0})
|
||||
_db.run.difficulty.rows.append({"level_index": 1, "node_depth": 0, "hp_mult": 1.5, "atk_mult": 2.0})
|
||||
var enemy_system_b = load("res://core/unit_container.gd").new(_db)
|
||||
Kit.check("修饰后 spawn 受理", enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)))
|
||||
Kit.check("修饰后 spawn 受理", enemy_system_b.spawn({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}))
|
||||
var g2 = enemy_system_b.all_enemies()[0]
|
||||
Kit.check("hp 修饰 ×1.5(%d→%d,max_hp 同步)" % [int(d0.hp), int(g2.hp)],
|
||||
int(g2.hp) == int(round(float(d0.hp) * 1.5)) and int(g2.max_hp) == int(g2.hp))
|
||||
Kit.check("atk 修饰 ×2.0(%d→%d)" % [int(d0.atk), int(g2.atk)], int(g2.atk) == int(d0.atk) * 2)
|
||||
# ③ 越域 clamp:乘数 50 → clamp 10
|
||||
_db.difficulty.rows.clear()
|
||||
_db.difficulty.rows.append({"level_index": 1, "node_depth": 0, "hp_mult": 50.0, "atk_mult": 1.0})
|
||||
_db.run.difficulty.rows.clear()
|
||||
_db.run.difficulty.rows.append({"level_index": 1, "node_depth": 0, "hp_mult": 50.0, "atk_mult": 1.0})
|
||||
var enemy_system_c = load("res://core/unit_container.gd").new(_db)
|
||||
Kit.check("越域修饰 spawn 受理", enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)))
|
||||
Kit.check("越域修饰 spawn 受理", enemy_system_c.spawn({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}))
|
||||
Kit.check("越域乘数 clamp 至 10(%d→%d)" % [int(d0.hp), int(enemy_system_c.all_enemies()[0].hp)],
|
||||
int(enemy_system_c.all_enemies()[0].hp) == int(d0.hp) * 10)
|
||||
_db.difficulty.rows.clear() # 还原空表(后续冒烟/关卡断言基线)
|
||||
_db.run.difficulty.rows.clear() # 还原空表(后续冒烟/关卡断言基线)
|
||||
|
||||
|
||||
# ---- ⑤ 格面资源九型 + gp_drop 流(黄金向量)----
|
||||
@@ -455,7 +455,7 @@ func _smoke_nine_types_gp_stream() -> void:
|
||||
var pos_weight := 0
|
||||
var gp_pos := -1
|
||||
var gp_neg := -1
|
||||
for row in _db.reward.rows:
|
||||
for row in _db.run.reward.rows:
|
||||
if String(row.get("pool_tag", "")) == "cell":
|
||||
cell_rows += 1
|
||||
if String(row.get("polarity", "")) == "positive":
|
||||
@@ -468,7 +468,7 @@ func _smoke_nine_types_gp_stream() -> void:
|
||||
cell_rows == 18)
|
||||
# 危险格负向直伤当量(负向≈正向 2 倍定价——真人反馈「三雷 3/4 血无后果感」的投值口径)
|
||||
var mine_power := {"heal_backlash": 6, "pierce_snare": 8, "magic_recoil": 8} # 危险格负向直伤当量(抬档值;依据见 numfix 报告)
|
||||
for row in _db.reward.rows:
|
||||
for row in _db.run.reward.rows:
|
||||
var rid := String(row.get("reward_id", ""))
|
||||
if mine_power.has(rid):
|
||||
Kit.check("危险格负向直伤 %s power=%d(卡⑧投值)" % [rid, int(mine_power[rid])],
|
||||
@@ -477,7 +477,7 @@ func _smoke_nine_types_gp_stream() -> void:
|
||||
# 合计即配置自身的和(改表自动跟随,不再硬编码合计值)。
|
||||
var pos_all_in_pool := true
|
||||
var neg_all_zero := true
|
||||
for row in _db.reward.rows:
|
||||
for row in _db.run.reward.rows:
|
||||
if String(row.get("pool_tag", "")) != "cell":
|
||||
continue
|
||||
if String(row.get("polarity", "")) == "positive" and int(row.get("weight", 0)) < 1:
|
||||
@@ -506,11 +506,11 @@ func _smoke_meta() -> void:
|
||||
and meta_tbl.conversion.size() == 3 and meta_tbl.attune_refresh_upgrade.size() == 2)
|
||||
var attrs_ok := true
|
||||
for a in ["max_hp", "atk", "armor_class"]:
|
||||
attrs_ok = attrs_ok and not _db.meta_conversion_row(a).is_empty()
|
||||
attrs_ok = attrs_ok and not _conversion_row_of(_db.crossrun.meta.conversion, a).is_empty()
|
||||
Kit.check("conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)", attrs_ok)
|
||||
var boss_ok := true
|
||||
for li in [1, 2, 3]:
|
||||
var brow: Dictionary = _db.meta_boss_keystone_row(li)
|
||||
var brow: Dictionary = _db.crossrun.meta.boss_keystone_row(li)
|
||||
boss_ok = boss_ok and not brow.is_empty() and int(brow.get("first_kill", 0)) >= int(brow.get("repeat_kill", -1))
|
||||
Kit.check("boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)", boss_ok)
|
||||
|
||||
@@ -519,3 +519,10 @@ class _TreeSaveMock:
|
||||
# 核代码增补:最小 save 形态——attunement.slots 只读 tree_level("att_socketN")
|
||||
func tree_level(n: String) -> int:
|
||||
return 1 if n == "att_socket1" else 0
|
||||
|
||||
|
||||
func _conversion_row_of(rows: Array, attr: String) -> Dictionary:
|
||||
for r in rows:
|
||||
if String(r.get("attr", "")) == attr:
|
||||
return r
|
||||
return {}
|
||||
Reference in new issue
Block a user