拆件批v1.3-B1(主角账领域拆分): hero_ledger.gd 整件退役→三新件按领域各归各账——hero_stats.gd(生命/特性/类型轴/生效公式与回血封顶单点/续玩钳制收口 clamp_hp_to_resume_cap/断点片段 hero_hp+relics 两键,构造注入 GainJournal 只读引用消费加算汇总)、gp_wallet.gd(gp 单写原语+片段 gp 键)、gain_journal.gd(获得记录三层+三通道 gain_bonus 单写原语+购买档数/钥石/真视+reset_adventure_accounts+片段六键,record_gain 转私有,_record_gain 词表不变);断点拼盘改三片段并集(BREAKPOINT_FIELDS 28 键序不变,_resume_ledger 三还原各取各键,日记缺键汇总直载/在档推导比对拒载逐字承现);消费方改址——adventure_runner(建件三引用+件出口 hero()→stats()/wallet()/journal()/deck(),start/restart 三件先日记后钱包再置满同序,_resume_params 钳制改 clamp_hp_to_resume_cap,载荷_base 随附钱包读数——停点件零钱包依赖)、level_runner(ctx hero→stats/wallet/journal 三键,私有三件同源成对自建,gp/hero_hp 属性改指三件,effective_ap_bonus→journal,hero()→stats()+journal() 出口,_bind_shop refs 换代)、shop_session(refs wallet+journal,BONUS_AXES 归 GainJournal)、battle_orchestrator(encounter "hero"→"journal" 键+_hero 类型 GainJournal)、curio_stop(构造参 stats+journal,兑付落账 split 指针,头注换代清 HeroLedger 表述)、呈现/探针/模拟六文件 12 处读面改址、测试六文件改址(hero_ledger_test 重写为三件测试——文件名与 manifest 行不变,新增 hs1 零引用源码断言)。验收:全量套件除 round_flow EV 锚 goblin 已知预期红外全绿;bp 对拍 292dumps×28 键 mismatch=0;冒烟 17 项 0 失败〔领域拆分批 V1〕

This commit is contained in:
DKChan committed 2026-10-27 08:10:08 +08:00
1 parent e40aa6f8c1
commit fde14f1c1c
28 files changed
+679 -532

No files matched your search

@@ -86,7 +86,7 @@ func _feats_breakpoint_roundtrip() -> void:
var sm_r = load("res://core/save_manager.gd").new(bp_path)
sm_r.load_from_disk()
a_r.set_save_manager(sm_r)
a_r._on_level_finished({"result": "victory", "hp": int(a_r.hero().hero_hp), "node_index": 1, "node_id": 1, "node_type": "shop"})
a_r._on_level_finished({"result": "victory", "hp": int(a_r.stats().hero_hp), "node_index": 1, "node_id": 1, "node_type": "shop"})
var bp_r: Dictionary = sm_r.breakpoint_snapshot()
Kit.check("断点附随件:relics 持有集入快照(迭代 12 T1 扩列)", (bp_r.get("relics", []) as Array).has("x3"))
Kit.check("断点附随件:adventure 计数器账本键在案(T6/T7 扩列)", bp_r.has("adventure_counters"))
+7 -7
View File
@@ -353,7 +353,7 @@ func _smoke_adventure() -> void:
l2_price_bonus = int(pb.get("bonus", 0))
Kit.check("进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" % [hp_price + l2_price_bonus, hp_price, l2_price_bonus], gp0 >= hp_price + l2_price_bonus)
Kit.check("购买 hp_up 受理(出账 + 加算区)", r8.buy_product("hp_up"))
var acc_shop: int = a8.hero().bonus_total("max_hp") # L1 三选一后 + 商店 3(首件随种子)
var acc_shop: int = a8.journal().bonus_total("max_hp") # L1 三选一后 + 商店 3(首件随种子)
Kit.check("统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop, acc_shop >= 3)
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 关卡完成(victory 载荷直调——非末关)
@@ -364,9 +364,9 @@ func _smoke_adventure() -> void:
Kit.check("三选一点选兑付受理(候选取首件)", a8.stop().pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))))
var picked2: String = String(a8.stop()._resolved.get("picked", ""))
var expect_acc: int = acc_shop + (int(a8.stop()._resolved.get("value", 0)) if picked2 == "max_hp" else 0)
Kit.check("三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [a8.hero().bonus_total("max_hp"), acc_shop, picked2], a8.hero().bonus_total("max_hp") == expect_acc)
Kit.check("三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [a8.journal().bonus_total("max_hp"), acc_shop, picked2], a8.journal().bonus_total("max_hp") == expect_acc)
var max_eff8: int = a8.effective_max_hp() # 账本生效上限含调试浮窗 pct(a8 已 set 400)
Kit.check("关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [a8.hero().hero_hp, max_eff8], a8.hero().hero_hp <= max_eff8)
Kit.check("关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [a8.stats().hero_hp, max_eff8], a8.stats().hero_hp <= max_eff8)
Kit.check("兑付后推进受理(下一关开始)", a8.continue_to_next_level())
# ⑨ 恢复点数学(接口③封顶公式三读数 + 同式两落点一致)
var a9 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(99))
@@ -416,8 +416,8 @@ func _smoke_adventure() -> void:
a12.start_adventure(12)
var cap := {"payload": {}}
a12.stop().curio_choice_presented.connect(func(p): cap["payload"] = p) # 停点件信号面(拆件批 T3 直连)
var max_full: int = int(_db.hero.max_hp) + int(a12.hero().base_override_flat().get("max_hp", 0))
a12.hero().hero_hp = max_full
var max_full: int = a12.stats().effective_max_hp() # 白板局无 override 无加算——置满口径=生效上限
a12.stats().hero_hp = max_full
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
Kit.check("满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)", int(cap["payload"].get("heal_gained", -1)) == 0)
# ⑬ 奖励节点共享池(同库同池同摘牌;TP0:attack 令牌耗尽后节点域同样零出现)
@@ -436,8 +436,8 @@ func _smoke_adventure() -> void:
Kit.check("奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13), not has_attack13)
a13.stop().present_node_curio(ncands13)
a13.stop().present_node_curio([{"kind": "gp", "tier": "common"}])
var gp13: int = a13.hero().gp
Kit.check("奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")), a13.stop().apply_node_curio("gp", "common") and a13.hero().gp == gp13 + int(_db.adventure.curio_value("gp", "common")))
var gp13: int = a13.wallet().gp
Kit.check("奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")), a13.stop().apply_node_curio("gp", "common") and a13.wallet().gp == gp13 + int(_db.adventure.curio_value("gp", "common")))
Kit.check("节点停点态兑付后清零(恰一次语义同构)", a13.stop()._node_pending.is_empty())
var s10: Dictionary = a10.summary
Kit.check("放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)", String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)))
+4 -4
View File
@@ -101,15 +101,15 @@ func _init() -> void:
var gp_taken_before := int(a._curio.taken.get("gp", 0))
Kit.check_quiet(a.stop().pick_curio("gp", "common") and int(a._curio.taken.get("gp", 0)) == gp_taken_before + 1,
"留任小GP 袋:GP 通道兑付受理(gp 计数入账——路由零变化)〔专长批 V10〕")
var hp_before: int = a.hero().hero_hp
var hp_before: int = a.stats().hero_hp
a.stop()._pending = {"candidates": [{"kind": "heal", "tier": "excellent"}]}
Kit.check_quiet(a.stop().pick_curio("heal", "excellent") and a.hero().hero_hp == mini(hp_before + int(db.adventure.curio_value("heal", "excellent")), a.effective_max_hp()),
Kit.check_quiet(a.stop().pick_curio("heal", "excellent") and a.stats().hero_hp == mini(hp_before + int(db.adventure.curio_value("heal", "excellent")), a.effective_max_hp()),
"留任应急口粮:当即回血 +6 封顶生效上限〔专长批 V10〕")
# V10-2 行动点上限归属性轴:ap kind 走加算区第四轴
a._curio.deck = [{"kind": "ap", "tier": "rare"}]
a.stop()._pending = {"candidates": a._curio.deck.duplicate()}
var ap_bonus_before: int = a.hero().bonus_total("ap")
Kit.check_quiet(a.stop().pick_curio("ap", "rare") and a.hero().bonus_total("ap") == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
var ap_bonus_before: int = a.journal().bonus_total("ap")
Kit.check_quiet(a.stop().pick_curio("ap", "rare") and a.journal().bonus_total("ap") == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
"行动点上限归属性轴:ap 兑付写加算区第四轴(bonus.ap 承载)〔专长批 V10〕")
# V16——断点扩容往返:新账本随断点快照/续玩回填(breakpoint_resume_test P5 同构)
+60 -37
View File
@@ -1,11 +1,14 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 主角实体账单测(拆件批 T1——hr1 公式逐值/hr1-s 商店加算同值/hr2 三通道日记与断点片段)。
# 主角账三件单测(领域拆分批——hr1 公式逐值/hr1-s 商店加算同值/hr2 三通道日记与断点片段;
# HeroStats 数值域+GpWallet 钱包域+GainJournal 加算日记域逐件直构,另附源码断言面)。
# 公式判据值全部由表读值推导(禁数值字面量断言);真链段走 start_adventure 与 runner 接口③。
# 运行:godot --headless --path game --script res://tests/unit/core/hero_ledger_test.gd(expect 0)
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
const HeroLedgerScript := preload("res://core/hero_ledger.gd")
const HeroStatsScript := preload("res://core/hero_stats.gd")
const GpWalletScript := preload("res://core/gp_wallet.gd")
const GainJournalScript := preload("res://core/gain_journal.gd")
const AdventureRunnerScript := preload("res://core/adventure_runner.gd")
const LevelRunnerScript := preload("res://core/level_runner.gd")
@@ -17,8 +20,9 @@ func _init() -> void:
quit(1)
return
# ---- hr1:生效公式逐值(表值+永久强化 flat 后乘调试 pct——与改前同式)----
var hero = HeroLedgerScript.new(db, {"atk": 3, "max_hp": 5, "armor_class": 2})
# ---- hr1:生效公式逐值(表值+永久强化 flat+加算区后乘调试 pct——与改前同式)----
var journal_a = GainJournalScript.new()
var hero = HeroStatsScript.new(db, {"atk": 3, "max_hp": 5, "armor_class": 2}, journal_a)
Kit.check_quiet(hero.effective_max_hp() == int(db.hero.max_hp) + 5,
"hr1 生效上限 = 表值+永久强化 flat(pct=0 乘法恒等)")
Kit.check_quiet(hero.effective_atk() == int(db.hero.atk) + 3,
@@ -29,47 +33,47 @@ func _init() -> void:
Kit.check_quiet(hero.effective_max_hp() == int(round(float(int(db.hero.max_hp) + 5) * 1.5)),
"hr1 调试 pct=50 后乘——上限 ×1.5(叠加次序:flat 先加 pct 后乘)")
hero.set_debug_override(0, 0)
hero.gain_bonus("max_hp", 4, "battle_export", "max_hp")
journal_a.gain_bonus("max_hp", 4, "battle_export", "max_hp")
Kit.check_quiet(hero.effective_max_hp() == int(db.hero.max_hp) + 5 + 4,
"hr1 加算区 flat 与永久强化 flat 同列基数(三段叠加)")
"hr1 加算区 flat 与永久强化 flat 同列基数(三段叠加——公式消费日记汇总)")
# hr1 回血封顶公式逐值(算术宿主=HeroLedger.rest_heal 单点——改前 LevelRunner.rest_heal 同式)
Kit.check_quiet(HeroLedgerScript.rest_heal(30, 25, 26) == mini(int(floor(26.0 * 30.0 / 100.0)), 26 - 25),
# hr1 回血封顶公式逐值(算术宿主=HeroStats.rest_heal 单点——改前 LevelRunner.rest_heal 同式)
Kit.check_quiet(HeroStatsScript.rest_heal(30, 25, 26) == mini(int(floor(26.0 * 30.0 / 100.0)), 26 - 25),
"hr1 回血 = floor(上限×比例÷100) 后封顶缺口(比例部分 7 > 缺口 1 → 1)")
Kit.check_quiet(HeroLedgerScript.rest_heal(30, 10, 26) == int(floor(26.0 * 30.0 / 100.0)),
Kit.check_quiet(HeroStatsScript.rest_heal(30, 10, 26) == int(floor(26.0 * 30.0 / 100.0)),
"hr1 回血比例部分未触封顶时按比例整值")
# ---- hr1 真链段:start_adventure 置满+接口③治疗封顶回报(runner._rest_handler → 主角账公式)----
# ---- hr1 真链段:start_adventure 置满+接口③治疗封顶回报(runner._rest_handler → 数值账公式)----
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(1234)
var adr = AdventureRunnerScript.new(db, rng)
Kit.check_quiet(adr.start_adventure(1234, {}), "hr1 真链冒险启动(seed 1234)")
var runner = adr.runner()
Kit.check_quiet(runner.hero_hp == adr.hero().effective_max_hp(),
Kit.check_quiet(runner.hero_hp == adr.stats().effective_max_hp(),
"hr1 冒险开始生命账本置满生效上限(含 pct 口径——与战斗内同源)")
var healed: int = adr.apply_rest(30, runner.hero_hp, runner.effective_max_hp()) # 接口③真链(关卡恢复点兑付的同一 handler)
Kit.check_quiet(healed == HeroLedgerScript.rest_heal(30, runner.hero_hp - healed, runner.effective_max_hp()),
"hr1 接口③真链回报 = 主角账回血公式(恢复点兑付同链)")
Kit.check_quiet(healed == HeroStatsScript.rest_heal(30, runner.hero_hp - healed, runner.effective_max_hp()),
"hr1 接口③真链回报 = 数值账回血公式(恢复点兑付同链)")
# ---- hr1-s:商店属性商品加算走主角账单写原语(购买后 bonus_total 与旧径同值)----
# ---- hr1-s:商店属性商品加算走日记单写原语(购买后 bonus_total 与旧径同值)----
runner._gain_wallet(1000) # 钱包注入(测试直调写口——测试构造惯例)
var products: Array = runner.shop_products()
var bought := false
for p in products:
if int(p.get("price", 1 << 30)) <= 1000 and String(p.get("attr", "")) != "":
var attr := String(p.get("attr", ""))
var before: int = runner.hero().bonus_total(attr)
var before: int = runner.journal().bonus_total(attr)
bought = runner.buy_product(String(p.get("product_id", "")))
if bought:
Kit.check_quiet(runner.hero().bonus_total(attr) == before + int(p.get("flat_value", 0)),
Kit.check_quiet(runner.journal().bonus_total(attr) == before + int(p.get("flat_value", 0)),
"hr1-s 商店购买后 bonus_total 增量 = 商品 flat(加算走 gain_bonus 单写原语——同值承载)")
Kit.check_quiet(runner.hero().purchase_tier(String(p.get("product_id", ""))) == 1,
"hr1-s 购买档数记主角账(递增定价输入单点)")
Kit.check_quiet(runner.journal().purchase_tier(String(p.get("product_id", ""))) == 1,
"hr1-s 购买档数记日记(递增定价输入单点)")
break
Kit.check_quiet(bought, "hr1-s 真链购买受理(GP 充足路径)")
# ---- hr2:三通道日记(明细来源指回+汇总同步维护)----
var h2 = HeroLedgerScript.new(db, {})
var h2 = GainJournalScript.new()
h2.gain_bonus("atk", 5, "curio_pick", "attack")
h2.gain_bonus("atk", 2, "shop_product", "p_atk_1")
h2.gain_bonus("armor_class", 1, "battle_export", "armor_class")
@@ -94,32 +98,45 @@ func _init() -> void:
h2.record_vision_use()
Kit.check_quiet(h2.vision_used() == 1, "hr2 真视使用恰计一次")
# hr2 断点片段往返(日记在档=正主)
var frag: Dictionary = h2.breakpoint_fragment()
var h3 = HeroLedgerScript.new(db, {})
h3.restore_fragment(frag, {})
Kit.check_quiet(h3.bonus_total("atk") == 7 and h3.bonus_total("armor_class") == 1,
# hr2 断点片段往返(日记在档=现行事实源)——三件各交各键、并集拼装
var frag: Dictionary = {}
frag.merge(h2.breakpoint_fragment())
var w2 = GpWalletScript.new()
w2.gain_gp(37)
frag.merge(w2.breakpoint_fragment())
var s2 = HeroStatsScript.new(db, {}, GainJournalScript.new())
s2.hero_hp = 21
s2.feats.append("twin_pick")
frag.merge(s2.breakpoint_fragment())
var j3 = GainJournalScript.new()
var w3 = GpWalletScript.new()
var s3 = HeroStatsScript.new(db, {}, j3)
j3.restore_fragment(frag, {})
w3.restore_fragment(frag, {})
s3.restore_fragment(frag, {})
Kit.check_quiet(j3.bonus_total("atk") == 7 and j3.bonus_total("armor_class") == 1,
"hr2 片段往返:日记在档由明细推导汇总(逐轴相等)")
Kit.check_quiet(h3.hero_hp == h2.hero_hp and h3.gp == h2.gp and h3.keystone_gained() == 3 and h3.vision_used() == 1,
"hr2 片段往返:账面键逐值(生命/钱包/钥石/真视)")
Kit.check_quiet((h3._gain_log as Array).size() == 3 and (h3._bonus_detail as Array).size() == 3,
Kit.check_quiet(w3.gp == 37 and j3.keystone_gained() == 3 and j3.vision_used() == 1
and s3.hero_hp == 21 and (s3.feats_held() as Array).has("twin_pick"),
"hr2 片段往返:三件账面键逐值(生命/特性/钱包/钥石/真视)")
Kit.check_quiet((j3._gain_log as Array).size() == 3 and (j3._bonus_detail as Array).size() == 3,
"hr2 片段往返:日记全量还原")
# hr2 旧档回退(无日记键→汇总直载跳过比对)
var legacy: Dictionary = frag.duplicate(true)
legacy.erase("gain_journal")
var h4 = HeroLedgerScript.new(db, {})
h4.restore_fragment(legacy, {})
Kit.check_quiet(h4.bonus_total("atk") == 7 and h4.bonus_total("armor_class") == 1 and (h4._gain_log as Array).is_empty(),
var j4 = GainJournalScript.new()
j4.restore_fragment(legacy, {})
Kit.check_quiet(j4.bonus_total("atk") == 7 and j4.bonus_total("armor_class") == 1 and (j4._gain_log as Array).is_empty(),
"hr2 旧档缺日记→汇总键直载(跳过比对——旧档行为零变化)")
# hr2 日记在档推导比对不符→拒绝载入(fail-fast——汇总不回填)
var tampered: Dictionary = frag.duplicate(true)
(tampered["bonus"] as Dictionary)["atk"] = 99
var h5 = HeroLedgerScript.new(db, {})
h5.restore_fragment(tampered, {})
Kit.check_quiet(h5.bonus_total("atk") == 0,
"hr2 汇总与日记推导不符→拒载(汇总不回填——日记为正主)")
var j5 = GainJournalScript.new()
j5.restore_fragment(tampered, {})
Kit.check_quiet(j5.bonus_total("atk") == 0,
"hr2 汇总与日记推导不符→拒载(汇总不回填——日记为现行事实源)")
# ---- sc3 源码断言(拆件批 T3——构造注入收口与转发族退役的静态面)----
var adv_src := FileAccess.get_file_as_string("res://core/adventure_runner.gd")
@@ -127,8 +144,7 @@ func _init() -> void:
var bind_shop_adv: int = adv_src.count("func _bind_shop")
var bind_shop_lvl: int = lvl_src.count("func _bind_shop")
var bind_calls := 0 # 调用行计数(排除 func 定义行——行首剥缩进后以 _bind_shop() 起始)
for line in (adv_src + lvl_src).split("
"):
for line in (adv_src + lvl_src).split("\n"):
var t := line.strip_edges()
if t.begins_with("_bind_shop()"):
bind_calls += 1
@@ -145,5 +161,12 @@ func _init() -> void:
runledger_hits += FileAccess.get_file_as_string(f).count("RunLedger") + FileAccess.get_file_as_string(f).count("run_ledger")
Kit.check_quiet(runledger_hits == 0 and not FileAccess.file_exists("res://core/run_ledger.gd"),
"al4 RunLedger 全仓零引用+类文件已删(充能单源化退役收口)〔配装批 V4〕")
# hs1 源码断言(领域拆分批——hero_ledger 整类退役全仓零引用)
var heroledger_hits := 0
for f in ["res://core/adventure_runner.gd", "res://core/level_runner.gd", "res://core/battle_orchestrator.gd",
"res://core/shop_session.gd", "res://core/curio_stop.gd"]:
heroledger_hits += FileAccess.get_file_as_string(f).count("HeroLedger") + FileAccess.get_file_as_string(f).count("hero_ledger")
Kit.check_quiet(heroledger_hits == 0 and not FileAccess.file_exists("res://core/hero_ledger.gd"),
"hs1 hero_ledger 全仓零引用+类文件已删(主角账三件拆分退役收口)〔领域拆分批 V1〕")
quit(Kit.finish("hero_ledger_test", "PASS — 主角账公式/三通道日记/断点片段往返与旧档回退/源码断言 全绿"))
quit(Kit.finish("hero_ledger_test", "PASS — 主角账三件公式/三通道日记/断点片段往返与旧档回退/源码断言 全绿"))
@@ -1032,8 +1032,8 @@ func _smoke_shop_room() -> void:
var gp0: int = runner.gp
Kit.check("购买 atk_up 受理(现价 6)", runner.buy_product("atk_up"))
Kit.check("钱包扣现价 6(%d → %d)" % [gp0, runner.gp], runner.gp == gp0 - 6)
# 加算区住主角账
Kit.check("统一冒险加算区 atk +1(加算区住主角账)", runner.hero().bonus_total("atk") == 1 and runner.hero().bonus_total("max_hp") == 0 and runner.hero().bonus_total("armor_class") == 0)
# 加算区住加算日记
Kit.check("统一冒险加算区 atk +1(加算区住加算日记)", runner.journal().bonus_total("atk") == 1 and runner.journal().bonus_total("max_hp") == 0 and runner.journal().bonus_total("armor_class") == 0)
by_id = _shop_products_by_id(runner.shop_products())
Kit.check("atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)", int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1)
# 行进段百分比归零(购买/生效断言按加算区裸值口径——调试浮窗百分比后乘;hp 快照钳到新口径上限防 spawn 越界,测试通道)
@@ -1318,10 +1318,10 @@ func _smoke_shop_pool() -> void:
var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"}
var gp0: int = int(r8.gp)
var taken0: int = int(a8._curio.taken.get(kind, 0))
var val0: int = a8.hero().bonus_total(String(val_map[kind]))
var val0: int = a8.journal().bonus_total(String(val_map[kind]))
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.buy_pool_item(kind))
Kit.check("钱包扣现价(%d → %d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - int(buy_c.get("price", 0)))
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))], a8.hero().bonus_total(String(val_map[kind])) == val0 + int(_db.adventure.curio_value(kind, tier)))
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))], a8.journal().bonus_total(String(val_map[kind])) == val0 + int(_db.adventure.curio_value(kind, tier)))
Kit.check("共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio.taken.get(kind, 0))], int(a8._curio.taken.get(kind, 0)) == taken0 + 1)
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
# A 链:白盒抬 GP 保证第二购场景确定构造(直改测试基建——非生产行为)
@@ -365,7 +365,7 @@ func _meta_injection() -> void:
int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)))
Kit.check("首战 hero_max_hp 与开局生命账本 = 表值 + 注入档",
int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0))
and int(adr.hero().hero_hp) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)))
and int(adr.stats().hero_hp) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)))
# 白板:同种子下 空参数 = 零强化参数 = 纯表值(逐位一致——注入通道默认值漏改在此转红)
var adr_w = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(920251))
Kit.check("白板冒险启动(无 params 实参——B1 空参数路径)", adr_w.start_adventure(920251))
@@ -511,10 +511,10 @@ func _smoke_forge_axes() -> void:
Kit.check("入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕",
int(r5.effective_ac()) == 12 + 1)
Kit.check("参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕",
adr5.hero().attack_type == "normal" and adr5.hero().armor_type == "heavy")
adr5.stats().attack_type == "normal" and adr5.stats().armor_type == "heavy")
# V6 锁死:开冒险后改存档选中→参数面/注入值不变(快照冻结;light=初始持有已解锁可切)
Kit.check("冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕",
mp5.forge_select_armor("light") and adr5.hero().armor_type == "heavy"
mp5.forge_select_armor("light") and adr5.stats().armor_type == "heavy"
and int(r5.effective_ac()) == 12 + 1)
# V5/V7 末端:enc 两轴直达 spawn 英雄快照(hero_attack 行携带选中类型键)
var run_ax = _run_battle(1, 0, {"attack_type": "pierce", "armor_type": "ward"})
@@ -273,9 +273,9 @@ func _init() -> void:
# ⑬ 真视使用计数链(受理成功恰计一次——主角账单点记录)
var rng13 = load("res://core/rng_service.gd").new(); rng13.set_master_seed(321)
var ob13 = orch_s.new(db, rng13)
var lg13 = load("res://core/hero_ledger.gd").new(db, {})
Kit.check_quiet(ob13.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["incense_stick"], "hero": lg13}),
"计数链:装配起战(HeroLedger 携带)")
var lg13 = load("res://core/gain_journal.gd").new()
Kit.check_quiet(ob13.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["incense_stick"], "journal": lg13}),
"计数链:装配起战(GainJournal 携带)")
var inv13 = load("res://core/treasure_items.gd").new(db)
var rec13: Dictionary = inv13.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0)
ob13.treasure_inventory = inv13