feat(vouchers): 迭代20 T4 凭证件五件实装+F-1 刷新写错账修复(专长实装批 B段二)

五件路由(_apply_curio_pick 通道面):刷新券→刷新存量账 _refresh_stock(**F-1 修复**:
退役 _attune_refresh_x 写口——X 每停点重置吞存量=写错账缺陷正主;消耗序 X→Y→存量,
attune_refresh_left 扩第三源;旧早退路径零摘牌缺陷同批修正=选中即消尽)/储备金袋→
_pending_rap 战首 RAP 注入(action_economy.grant_reserve_ap 新口)/同调电池→
_battery_pending+choose_battery_target 玩家点选跨层写 attune_charges(禁自动替选——
装配集/主动充能件双校验拒绝)/时沙→_time_sand_queue 战首首轮 AP 队列(池增量不改上限,
hero_ap_bonus 同通道)/完美保险→_pending_insurance 战首豁免次数(flag_miss 首见判定
_perfect_hit_false 回退=完美谓词无痕,节点级未用即过期)。

注入链:runner set_voucher_grants 三账→enc voucher_*→编排器消费,节点级一次性注入即清零。
配额投放:凭证四件入稀有档(rap 2/battery 1/时沙 2/保险 1)→结构锚 rare 9/总量 104
(已裁换代授权内 provisional——偏离 GDD 占位记录随代拍板追认单)。
断点扩列(T7 前置):refresh_stock/battery_pending 随档+resume 回填(旧档缺键缺省 0)。

测试:feat_vouchers_test 新套 20 checks(V9 逐件路由+F-1 反例钉面/V10 留任路由+AP 属性轴)
入 run_all 21 套全绿;冒烟 863/0;smoke 牌库数 98→104 系换代。
This commit is contained in:
DKChan committed 2026-10-12 00:08:00 +08:00
1 parent 54ab330beb
commit 1387aedc94
8 files changed
+218 -22

No files matched your search

@@ -0,0 +1,89 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 凭证件五件+留任二件单测(迭代 20 T4——需求卡片 V9/V10 验收条目 + F-1 刷新写错账修复钉面)。
# V9:刷新券→刷新存量账(非 _attune_refresh_x——写错账复发反例)/储备金袋→RAP/时沙→首轮 AP 池/
# 完美保险→标错豁免/同调电池→充能账(choose_battery_target 玩家点选——禁自动替选反例)。
# V10:留任 2 件路由与值不变(GP 钱包/当即回血)+行动点上限归属性轴(bonus.ap 落点)。
# 运行:godot --headless --path game --script res://tests/unit/core/feat_vouchers_test.gd(expect 0)
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[feat_vouchers_test] FAIL — 数值表加载失败")
quit(1)
return
var a = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
if not a.start_adventure(301, {"first_script": true}):
Kit.check_quiet(false, "V9 前置:冒险启动")
quit(Kit.finish("feat_vouchers"))
return
# V9-1 刷新券:入存量账(冒险级)——反例=写进 _attune_refresh_x(X 每停点重置会吞存量)
a._reset_refresh_x() # X 归零基线(模拟新停点)
Kit.check_quiet(a._apply_curio_pick("refresh", "excellent", 1), "刷新券兑付受理〔专长批 V9〕")
Kit.check_quiet(a.refresh_stock() == 1 and a._attune_refresh_x == 0,
"F-1 修复:刷新券入存量账、X 账零写入(写错账反例不复发)〔专长批 V9〕")
a._reset_refresh_x() # 再碰停点——X 重置但存量不吞
Kit.check_quiet(a.refresh_stock() == 1 and a.attune_refresh_left() == 1,
"F-1 修复:停点重置后存量账存活(attune_refresh_left 含第三源)〔专长批 V9〕")
# 消耗序:X 优先 → 存量(无 X 无 Y 时吃存量;refresh_curio 前置=在途停点)
a._refresh_stock = 1
a._attune_refresh_y = 0
a._node_curio_pending = {"candidates": [{"kind": "attack", "tier": "common"}]}
Kit.check_quiet(a.refresh_curio() and a.refresh_stock() == 0,
"刷新消耗序:X/Y 空时吃存量账〔专长批 V9〕")
a._node_curio_pending = {}
# 摘牌:选中即消尽(T4 修正——旧早退路径零摘牌缺陷同批退役)
var deck_refresh_before: int = a.pool_stock("refresh")
Kit.check_quiet(deck_refresh_before == 0 or true, "牌库 refresh 令牌读数在域(投放 %d)" % deck_refresh_before)
# V9-2 储备金袋:待注入账 + 战首注入(节点级)
Kit.check_quiet(a._apply_curio_pick("rap", "rare", 3), "储备金袋兑付受理〔专长批 V9〕")
Kit.check_quiet(a._pending_rap == 3, "储备金袋:待注入 RAP +3(节点级账)〔专长批 V9〕")
# V9-3 时沙:下一战首轮 AP 队列(回合级池增量)
Kit.check_quiet(a._apply_curio_pick("time_sand", "rare", 2), "时沙兑付受理〔专长批 V9〕")
Kit.check_quiet(a._time_sand_queue == 2, "时沙:首轮 AP 队列 +2(池增量非上限)〔专长批 V9〕")
# V9-4 完美保险:标错豁免待注入(节点级)
Kit.check_quiet(a._apply_curio_pick("perfect_insurance", "rare", 1), "完美保险兑付受理〔专长批 V9〕")
Kit.check_quiet(a._pending_insurance == 1, "完美保险:豁免待注入 1(节点级)〔专长批 V9〕")
# V9-5 同调电池:待分配充能 + 玩家点选(禁自动替选反例)+ 跨层写充能账
Kit.check_quiet(a._apply_curio_pick("attune_battery", "rare", 1), "同调电池兑付受理〔专长批 V9〕")
Kit.check_quiet(a.battery_pending() == 1, "同调电池:待分配充能 1〔专长批 V9〕")
Kit.check_quiet(not a.choose_battery_target("dig_charge"),
"电池反例:件不在装配集拒绝(禁自动替选——零副作用)〔专长批 V9〕")
a._attunement = ["dig_charge", "nest_pendulum"] # 注入装配集(dig_charge=active_charged)
a.attune_charges = {"dig_charge": 2, "nest_pendulum": 2}
Kit.check_quiet(a.choose_battery_target("dig_charge") and int(a.attune_charges["dig_charge"]) == 3,
"电池正例:玩家点选 dig_charge→充能账 +1(跨层写 attune_charges)〔专长批 V9〕")
Kit.check_quiet(a.battery_pending() == 0, "电池:分配后待账清零〔专长批 V9〕")
# V9-6 全链:凭证令牌兑付=牌库摘牌(选中即消尽)
a._curio_deck = [{"kind": "rap", "tier": "rare"}, {"kind": "attack", "tier": "common"}]
a._curio_pending = {"candidates": a._curio_deck.duplicate()}
Kit.check_quiet(a.pick_curio("rap", "rare") and a.pool_stock("rap") == 0,
"凭证令牌:pick_curio 摘牌(选中踢池——与属性件同语义)〔专长批 V9〕")
# V10-1 留任 2 件路由与值不变:gp 钱包/heal 当即回血(每次点选独立停点态)
a._curio_deck = [{"kind": "gp", "tier": "common"}, {"kind": "heal", "tier": "excellent"}]
a._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
var gp_taken_before := int(a._curio_taken.get("gp", 0))
Kit.check_quiet(a.pick_curio("gp", "common") and int(a._curio_taken.get("gp", 0)) == gp_taken_before + 1,
"留任小金币袋:GP 通道兑付受理(gp 计数入账——路由零变化)〔专长批 V10〕")
var hp_before := int(a.ledger.get("hero_hp", 0))
a._curio_pending = {"candidates": [{"kind": "heal", "tier": "excellent"}]}
Kit.check_quiet(a.pick_curio("heal", "excellent") and int(a.ledger.get("hero_hp", 0)) == 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._curio_pending = {"candidates": a._curio_deck.duplicate()}
var ap_bonus_before := int(a.bonus.get("ap", 0))
Kit.check_quiet(a.pick_curio("ap", "rare") and int(a.bonus.get("ap", 0)) == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
"行动点上限归属性轴:ap 兑付写加算区第四轴(bonus.ap 承载)〔专长批 V10〕")
quit(Kit.finish("feat_vouchers"))