iter41-卡1:核心数据面结构收敛批:85键全清——三族拆域+config_db归位容器+存档拆件+meta瘦身+adventure拆段+spawn束化;行为零变化,8新件在阈

This commit is contained in:
DKChan committed 2026-10-29 01:33:50 +08:00
1 parent bda377eb16
commit a613a3cb90
157 files changed
+7453 -4288

No files matched your search

@@ -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 {}