feat(凭证件裁削): 卡② 同调电池删除+完美保险迁专长+配额净删落地(迭代 31 卡②)

用户 2026-10-18 逐裁四项落码:
- T1/T2 同调电池整件删除:CURIO_KINDS 摘 kind、三字段与三处分发表分支删净、
  runner 侧 _battery_pending/断点键/getter/choose_battery_target 全清(呈现层零接线=有账本无入口)。
- T3 完美保险出池:kind/字段/分发摘除,_pending_insurance 与 level_runner 注入通道
  (voucher_insurance 键、set_voucher_grants 第三参)整链拆除,battle_orchestrator._fx_insurance 删。
- T4 引擎扩签 forgive_seal_miss(EFFECT_KINDS 31→32)+两账相减:新增 _perfect_forgiven 单向累加、
  谓词抽成 perfect_sweep_earned()(有效误标=误标总数−豁免数),_perfect_hit_false 保持纯净=契约 2;
  seal_missed 发射时机与消费点零改动=契约 3(危险直觉·守同事件订阅面在验收组里对拍)。
- T5 feats.tres 第 30 行完美保险专长:rarity rare/cap 省略/domain=battle 显式写出(战斗域首例)/
  desc「本场首次封印落空,不计入完美封印判定」;限次由行 when counter(insurance_used) lt 1 承载,
  do 自带 counter_id 记账由授予段消费点落账(代拍板 3 选型:单行落表,理由见实现报告)。
- T6 配额净删:TIER_QUOTAS rare 9→7、令牌池总量 104→102,validate() 硬断言过;smoke 配额锚/牌库
  构成/专长行数随批换代。
- T7 验收:feat_vouchers_test 39 项全绿(出池反例/断点缺键兼容/账本保留/配额锚/装载/限次一次/
  计数纯净/两账相减真值表/跨战清零/事件不吞)+forge_table 词表闭集断言 31→32。
  变异验证 6 条:5 条证红;「domain battle→adventure」在本 rig 不可观测(未红),已如实登记。
- 存量缺陷顺带修:旧 _emit_row("insurance_used","",…) 空来源会触发行来源断言(旧路径无消费点故从未走红),
  新订阅面给出 source_class=seal_miss。

基线(卡① 合回后 master 4f953ce8):冒烟 1151/0、单元 34 套全绿、探针 --click 全绿(user:// 存档移开态)。
This commit is contained in:
DKChan committed 2026-10-19 04:45:49 +08:00
1 parent 4f953ce829
commit 072c19b4a5
10 files changed
+183 -110

No files matched your search

+14 -14
View File
@@ -800,8 +800,8 @@ func _smoke_adventure_table() -> void:
"clear_heal_pct ∈ [0,100](%d——通关部分回血比例)" % int(_db.adventure.clear_heal_pct))
for tier in _db.adventure.TIERS:
check(int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]),
"档位配额结构锚(四级 70/25/3/0 直映射承袭——迭代 20 T2):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])])
check(int(_db.adventure.deck_total()) == 104, "令牌池总量 = 104 张(四级 普通 70/优秀 25/稀有 9/传说 0——T2 直映射 98+T4 凭证投放 6)〔专长批 V5〕")
"档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])])
check(int(_db.adventure.deck_total()) == 102, "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕")
check(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
@@ -1280,8 +1280,8 @@ func _smoke_adventure() -> void:
func _smoke_feats() -> void:
print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---")
# ① 现行表牌库构成:重设计 29 件全量入池——98 数值令牌+29 relic 令牌(四级 rarity 直映射)
check(_db.feats != null and (_db.feats.rows as Array).size() == 29, "feat_table 已加载(重设计 29 件=基础 27+迁入 2——卡文 §10.4 推荐案口径)")
# ① 现行表牌库构成:重设计 30 件全量入池——102 数值令牌+30 relic 令牌(四级 rarity 直映射;迭代 31 卡② 完美保险迁入)
check(_db.feats != null and (_db.feats.rows as Array).size() == 30, "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)")
check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)")
var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71))
check(a_r0.start_adventure(71, {"first_script": true}), "专长带场景:冒险启动(seed 71)")
@@ -1292,8 +1292,8 @@ func _smoke_feats() -> void:
feat_tokens += 1
var tk := String((t as Dictionary).get("tier", ""))
tier_counts[tk] = int(tier_counts.get(tk, 0)) + 1
check(feat_tokens == 29 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 8 and a_r0._curio_deck.size() == 133,
"专长带入池:29 件四级直映射(普通 12/优秀 9/稀有 8/传说 0)——牌库 104+29=133〔专长批 V2〕")
check(feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0._curio_deck.size() == 132,
"专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕")
# ② 旧 27 件退役钉面(V2——arcane_ember 系旧件表首行;全仓 grep 零残留归批末静态核查)
check(_db.feats.row_by_id("arcane_ember").is_empty() and _db.feats.row_by_id("magma_body").is_empty(),
"旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕")
@@ -1311,14 +1311,14 @@ func _smoke_feats() -> void:
for tv in ((row_v as Dictionary).get("triggers", []) as Array):
if String(((tv as Dictionary).get("do", {}) as Dictionary).get("kind", "")) == "rewrite_board":
rewrite_rows += 1
check(rewrite_rows == 0, "棋盘事实不可变:新 29 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕")
check(rewrite_rows == 0, "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕")
var bad_words := 0
for row_v in (_db.feats.rows as Array):
var r_d: Dictionary = row_v as Dictionary
for txt_v in [String(r_d.get("display_name", "")), String(r_d.get("desc", ""))]:
if String(txt_v).contains("雷") or String(txt_v).contains("旗"):
bad_words += 1
check(bad_words == 0, "对外命名纪律:29 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕")
check(bad_words == 0, "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕")
check(float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0
and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5
and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3,
@@ -1346,8 +1346,8 @@ func _smoke_feats() -> void:
for t in a_r._curio_deck:
if String((t as Dictionary).get("kind", "")) == "relic":
deck_relic += 1
check(deck_relic == 5 and a_r._curio_deck.size() == 109,
"专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 104+5=109")
check(deck_relic == 5 and a_r._curio_deck.size() == 107,
"专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107")
# T4 渠道白名单断言(迭代 15 卡②——渠道轴定位:通关渠道只出专长/节点渠道零专长)
var feat_seen: Dictionary = {}
var node_relic_seen := false
@@ -6094,7 +6094,7 @@ func _smoke_tree_rules() -> void:
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
"feats": [], "tree_rules": ["sense_guard"], "battle_index": 1,
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
"voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}),
"守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)")
var board = orch.minesweeper.current_board()
var safe := Vector2i(-1, -1)
@@ -6122,7 +6122,7 @@ func _smoke_tree_rules() -> void:
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
"feats": [], "tree_rules": ["sense_guard"], "battle_index": 1,
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
"voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}),
"守⑤ rig:次战编排启动(新战新队列实例)")
check(int(orch2.action_economy.pending_ap_delta()) == 0,
"守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)")
@@ -6146,7 +6146,7 @@ func _smoke_tree_rules() -> void:
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
"feats": [], "tree_rules": [], "battle_index": 1,
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
"voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}),
"聚 rig:基线编排启动(无增益侧)")
orch3.encounter["tree_rules"] = []
var gp_base: int = orch3._roll_gp_drop()
@@ -6155,7 +6155,7 @@ func _smoke_tree_rules() -> void:
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
"feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1,
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
"voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}),
"聚 rig:增益编排启动(tree_rules=economy_hoard)")
orch4.encounter["tree_rules"] = ["economy_hoard"]
var gp_hoard: int = orch4._roll_gp_drop()