iter50-卡2:卡B(断言补齐): 四域13新断言+2处既有扩展——①增益结算链V1-V5 ②设置存储V6-V8 ⑤Boss门控V9/V10 ⑥锻造V11-V16; 新件settings_store_test与boss_gate_test; manifest+2; 73套件全绿

This commit is contained in:
DKChan committed 2026-11-03 06:31:05 +08:00
1 parent e658492703
commit cd133932f9
9 files changed
+329 -1

No files matched your search

@@ -37,6 +37,10 @@ func _init() -> void:
_smoke_forge_axes()
_smoke_tree()
_smoke_tree_rules()
_test_forge_buy_reject()
_test_blessing_no_forge_leak()
_test_default_axes_pre_key()
_test_armor_switch_dual_axes()
quit(Kit.finish("meta_progression_forge_test"))
@@ -50,7 +54,8 @@ const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry
"user://smoke_meta_tree.json", "user://smoke_meta_career.json",
"user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json",
"user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json",
"user://smoke_meta_forge4.json", "user://smoke_meta_forge5.json"]
"user://smoke_meta_forge4.json", "user://smoke_meta_forge5.json",
"user://smoke_meta_reject.json", "user://smoke_meta_blessing.json", "user://smoke_meta_axesw.json"]
func _meta_smoke_cleanup() -> void:
@@ -418,6 +423,12 @@ func _smoke_forge_axes() -> void:
Kit.check("长剑二级购:扣 30、等级→2;轻甲独立零扰动(六路线各练各的)〔锻造工坊批 V2〕",
mp2.forge_buy_level("sword") and sv2.meta_state.forge_state().levels.get("sword", 0) == 2
and sv2.meta_state.keystone_balance() == 50 and sv2.meta_state.forge_state().levels.get("light", 0) == 0)
# V12:长剑购买链走至 3 级(45 档扣款——余额 50−45=5,几何价第三档);链后续夹具段余额基准
# 由 add_keystone 补回(注入口径——直写存档域构造状态,下游 V3 段 50−20 语义不变)
Kit.check("长剑三级购:扣 45、等级→3(余额 50−45=5)〔断言补齐批 V12〕",
mp2.forge_buy_level("sword") and sv2.meta_state.forge_state().levels.get("sword", 0) == 3
and sv2.meta_state.keystone_balance() == 5)
sv2.meta_state._add_keystone(45)
# V2③:余额不足拒且零副作用(等级/选中/解锁三面零变化)
var sv2d = _meta_new_save(_META_SMOKE_PATHS[14])
var mp2d = load("res://core/meta_progression.gd").new(_db, sv2d)
@@ -760,3 +771,72 @@ func _smoke_tree_rules() -> void:
Kit.check("聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)",
gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base)
_meta_smoke_cleanup()
# ---- 锻造契约补齐(断言补齐批——⑥锻造工坊):拒购零副作用/祝福件零泄漏/默认值解耦/甲型双轴 ----
func _test_forge_buy_reject() -> void:
var sv = _meta_new_save("user://smoke_meta_reject.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(10) # 低于首购价 20——余额不足面
var fs0: Dictionary = sv.meta_state.forge_state().duplicate(true)
var bal0: int = sv.meta_state.keystone_balance()
Kit.check("余额不足拒购:sword false、等级与余额零变化〔断言补齐批 V13〕",
not mp.forge_buy_level("sword") and sv.meta_state.forge_state() == fs0 and sv.meta_state.keystone_balance() == bal0)
sv.meta_state._add_keystone(1000) # 充值后未解锁面不变(spear 直购未走/练满未达)
Kit.check("未解锁拒购:spear false、等级与余额零变化〔断言补齐批 V13〕",
not mp.forge_buy_level("spear") and sv.meta_state.forge_state() == fs0
and sv.meta_state.keystone_balance() == bal0 + 1000)
func _test_blessing_no_forge_leak() -> void:
var sv = _meta_new_save("user://smoke_meta_blessing.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
var fs0: Dictionary = sv.meta_state.forge_state().duplicate(true)
# 真实入口=商店购买口(LevelRunner 装配的 shop_session.buy_product——祝福件走 GP 钱包+局内加算区)
var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(80))
var lvl_ok: bool = runner.start_level(1, 80)
Kit.check("V14 前置:关卡启动(L1 行集)", lvl_ok)
if not lvl_ok:
return
runner.gp = 100
Kit.check("V14 前置:攻击祝福受理", runner.shop.buy_product("atk_up"))
Kit.check("V14 前置:护甲祝福受理", runner.shop.buy_product("armor_up"))
Kit.check("祝福件零泄漏:购买后 forge_state 投影逐位不变(levels/选中/解锁三面)〔断言补齐批 V14〕",
sv.meta_state.forge_state() == fs0)
Kit.check("祝福件住局内加算区(atk +1 在加算日记——通道分离实证)", runner.journal().bonus_total("atk") == 1)
func _test_default_axes_pre_key() -> void:
var sv = _meta_new_save("user://smoke_meta_axesw.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
var ax: Dictionary = mp.hero_type_axes()
Kit.check("树钥匙未点亮:hero_type_axes 返回锻造配置默认 normal/light(默认值常驻解耦)〔断言补齐批 V15〕",
String(ax["attack"]) == "normal" and String(ax["armor"]) == "light")
func _test_armor_switch_dual_axes() -> void:
var sv = _meta_new_save("user://smoke_meta_axesw.json")
sv.meta_state._add_keystone(1000)
var mp = load("res://core/meta_progression.gd").new(_db, sv)
Kit.check("V16 前置:重甲直购解锁+首购+选中(200+20 钥石)",
mp.forge_buy_unlock("heavy") and mp.forge_buy_level("heavy") and mp.forge_select("armor", "heavy"))
var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(81))
var p: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv, mp)
var adv_ok: bool = adr.start_adventure(81, p)
Kit.check("V16 前置:参数面冒险启动", adv_ok)
if not adv_ok:
return
Kit.check("甲型双轴:heavy 选中 → AC 轴 12 基础+路线 Lv1=13(light 同级 11 的 +2 档差)〔断言补齐批 V16〕",
int(adr.runner().effective_ac()) == 13 and String(mp.hero_type_axes()["armor"]) == "heavy")
Kit.check("甲型双轴:挨普通攻击修正 −1(克制矩阵 normal→heavy)〔断言补齐批 V16〕",
int(_db.combat.settlement.damage_roll.armor_effectiveness["normal"]["heavy"]) == -1)
Kit.check("V16 前置:切回 light(初始持有已解锁可切)", mp.forge_select("armor", "light"))
var adr2 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(82))
var p2b: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv, mp)
var adv2_ok: bool = adr2.start_adventure(82, p2b)
Kit.check("V16 前置:切回后新参数面冒险启动(快照冻结契约——新 runner 读新选中)", adv2_ok)
if not adv2_ok:
return
Kit.check("甲型双轴:切回 light 复原(AC 10+0)且 heavy 等级保留(Lv1 不丢)〔断言补齐批 V16〕",
int(adr2.runner().effective_ac()) == 10 and sv.meta_state.forge_state().levels.get("heavy", 0) == 1)
@@ -29,6 +29,7 @@ func _init() -> void:
_test_shop_and_attribution()
_test_battle_floor()
_test_exit_structure()
_test_boss_exit_per_region()
_test_rng_single_point()
_test_negative_fixtures()
quit(Kit.finish("node_map_generator_test"))
@@ -303,6 +304,41 @@ func _terminals_reachable(g: Dictionary, id: int) -> int:
return c
# ---- V9 Boss 出场门控(难度曲线与Boss——每区域恰一 Boss 终点)----
func _test_boss_exit_per_region() -> void:
# 真实生成多区域图:全部终点 type=="boss"、boss 终点数==exit_count、深度=L−r(r∈[0,exit_count)
# ——每区域恰一 boss、深度互异即分属不同区域)
# 节点 depth 字段=入口起最长路径深(生成器校验轴);区域序号 r 的可观测形态=最深 boss(r=0)
# 与该 boss 的深度差(同层 boss 深度 = d0 − r——区域段 L−r 的 1 步差)
var all_ok := true
for L in [1, 2, 3]:
for s in [42, 43, 44]:
var g := _graph(L, s)
var by_id := {}
for n in g["nodes"]:
by_id[int(n["id"])] = n
var exits := int(_params(L)["exit_count"])
var d0 := 0
for tid in g["terminals"]:
d0 = maxi(d0, int(by_id[int(tid)]["depth"]))
var per_region := {}
for tid in g["terminals"]:
var tn: Dictionary = by_id[int(tid)]
if String(tn["type"]) != "boss":
all_ok = false
var r := d0 - int(tn["depth"])
if r < 0 or r >= exits:
all_ok = false
per_region[r] = int(per_region.get(r, 0)) + 1
if per_region.size() != exits:
all_ok = false
for r in per_region:
if int(per_region[r]) != 1:
all_ok = false
Kit.check("每区域恰一 Boss 终点(type=boss × exit_count 个、深度=d0−r 互异分域——区域段 L−r 的可观测形态)〔断言补齐批 V9〕", all_ok)
# ---- V10 RNG 单点(纯流面 counter 不变)----
func _test_rng_single_point() -> void: