feat(close): 迭代20 T5+T8+T9+T10 C段收尾(专长实装批)

T5 Boss 兑付面换代:通关渠道三选一改按档配比加权抽取(无放回加权抽 boss_feat_weight
100/45/15/5 provisional 归调参批——奇物.md v3.1 rarity 语义约束兑付面);信号注释换代
「提升三选一」→「专长三选一」(V11 正名);V12 频带断言(300 停点档位频率出带±15pp)。

T8 MC 对账适配:formed 画像 14 旧件→五事件族 14 新件(开格 4/插旗 3/踩雷 4/节奏 2/战斗 1);
新基线取证:normal/off 100 局 71000 档 通关 0%(master 对照同为 0%=零配基线本相),
止步分布 90/10→93/7 可归因曝光槽候选构成;同种子重放 30 局逐位复现 ✓(V15)。

T9 迁出收尾:treasure_items 删 venom_edge/flag_burst 两行(同调目录收窄);schema 词面
venom_rider/flag_burst 摘除(EFFECT_KINDS/MAGNITUDE_KINDS);rewrite_board 效果类型除名
(净化工兵退役——V14 连带清理闭环);V13 断言入 smoke。

T10 空配置验收闸:三选一池空 skip_empty_curio 消化+非空拒绝反例+商店空货架合法态断言。

测试:pool_case_d 17 checks(+V12/T10)/feat_vouchers 23 checks(+V16 断点往返);
冒烟 864/0+run_all 21 套全绿。
This commit is contained in:
DKChan committed 2026-10-12 00:28:22 +08:00
1 parent 5ebd05d4b4
commit 3333187351
9 files changed
+111 -63

No files matched your search

+30 -1
View File
@@ -41,7 +41,7 @@ class_name AdventureRunner
extends RefCounted
signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行
signal curio_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
signal curio_choice_presented(payload: Dictionary) # 专长三选一停点(非末关通关恰一次;迭代 20 T5 换代正名——旧「提升三选一」退役,通道白名单后候选恒专长+回血在载荷)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
signal curio_resolved(result: Dictionary) # 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_curio 道具件满位时)——items=现有持有件记录、incoming=新件记录
signal adventure_finished(summary: Dictionary) # 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops 迭代 10)
@@ -872,6 +872,35 @@ func _draw_curio_candidates(channel: String = "reward_node") -> Array:
var t: Dictionary = tok as Dictionary
return _curio_cap_ok(String(t.get("kind", "")), String(t.get("feat_id", ""))))
var rg: RandomNumberGenerator = rng.open_stream("reward_pick")
if channel == "level_clear":
# 迭代 20 T5——Boss 渠道按档配比加权抽取(无放回加权抽:逐席按 boss_feat_weight 掷点,
# 身份去重照旧;rarity 语义约束兑付面——奇物.md v3.1,配比数值归调参批)
var out_w: Array = []
var seen_w: Array = []
var want_w := int(db.adventure.curio_candidate_count)
while out_w.size() < want_w and not pool.is_empty():
var total_w := 0
for tok_w in pool:
total_w += db.adventure.boss_feat_weight(String((tok_w as Dictionary).get("tier", "")))
if total_w <= 0:
break
var roll_w := rg.randi_range(1, total_w)
var pick_i := 0
for i_w in pool.size():
roll_w -= db.adventure.boss_feat_weight(String((pool[i_w] as Dictionary).get("tier", "")))
if roll_w <= 0:
pick_i = i_w
break
var tok_p: Dictionary = pool[pick_i]
var ident_p := _token_identity(tok_p)
if seen_w.has(ident_p):
pool.remove_at(pick_i) # 同身份已出——剔牌重掷(无放回)
continue
seen_w.append(ident_p)
var cand_p := {"kind": String(tok_p.get("kind", "")), "tier": String(tok_p.get("tier", "")), "feat_id": String(tok_p.get("feat_id", ""))}
out_w.append(cand_p)
pool.remove_at(pick_i)
return out_w
for i in pool.size():
var j := rg.randi_range(i, pool.size() - 1)
var tmp = pool[i]
+3 -3
View File
@@ -32,7 +32,7 @@ const MINE_EVENTS := ["dig_reveal", "dig_paid", "flag_hit", "mine_hit", "flag_se
const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket",
"attack_bonus", "attack_segments", "yield_mult", "spawn_spell", "overflow", "grant_material",
"grant_ap", "grant_gp", "bias_wallet", "shield", "spike", "apply_status", "counter_add",
"counter_spend", "rewrite_board", "reveal_tiles", "remove_mines", "refresh_pick",
"counter_spend", "reveal_tiles", "remove_mines", "refresh_pick",
"bias_rewards", "preview",
# 迭代 20 专长实装批 T1 扩签六型(奇物.md 基础件表 27 件声明清单——落法映射 commit 留痕):
# heal=回复生命〔血蜜/战地包扎〕;ac_bonus=护甲加值累积器〔封印桩本轮/石肤本场 cap〕;
@@ -42,8 +42,8 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open
# target_attack_bonus=单位标记加伤〔处刑台——敌血量阈值挂单位字典,主角攻击面通用消费〕
"heal", "ac_bonus", "damage_reduce", "bonus_delta", "blast_scaled", "preview_area",
"target_attack_bonus"]
# 注:rewrite_board 保留至本批 T3——老 27 行 purge_sapper(净化工兵)仍在表,删行同批除名
# (迭代 20 卡 V14 连带清理;编排器消费分支已先行摘除=命中零消费 no-op 过渡)
# 注:rewrite_board 已除名(迭代 20 T9——净化工兵随老 27 件退役,V14 连带清理;
# 编排器消费分支 T1 先行摘除)
## fact 条件词汇(封闭枚举——迭代 20 T1 扩签 9→17:payload 同名键直读面〔fact 取值序 payload
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
+11
View File
@@ -187,6 +187,17 @@ func curio_cap(kind: String) -> int:
return 99
## Boss 专长三选一按档配比加权(迭代 20 T5——奇物.md v3.1 rarity 语义约束「候选按档配比或
## 加权抽取」兑付面;权重 provisional 归调参批——普通打底/优秀期待/稀有稀缺/传说头奖形态)
func boss_feat_weight(tier: String) -> int:
match tier:
TIER_COMMON: return 100
TIER_EXCELLENT: return 45
TIER_RARE: return 15
TIER_LEGENDARY: return 5
return 1
## 档位判读序(MC 策略 316-f——1 级最稀有:legendary > rare > excellent > common;并列同档取最左由调用方稳定序保证)
func tier_rank(tier: String) -> int:
match tier:
+2 -2
View File
@@ -41,7 +41,7 @@ const DIMENSIONS := ["info", "risk", "action", "plan", "counter"]
# 内容数据驱动扩展批 T2 挂账:主动件 kind 词族换代(reveal_tiles/preview/grant_ap/remove_mines/
# refresh_pick——效果引擎 ACTIVE_KINDS 同名闭集),被动件 effect_kinds 词族随 T4 秘宝迁移退役
# (passive 行改 triggers 事件订阅形态);旧词保留共存至迁移完成。
const EFFECT_KINDS := ["mine_reveal", "reward_preview", "ap_grant", "venom_rider", "flag_burst",
const EFFECT_KINDS := ["mine_reveal", "reward_preview", "ap_grant",
"mine_hit_halve", "reflect_damage", "ap_rounds_front", "ap_charge_safe", "cell_removal",
"reward_weight_bias", "bonus_refresh",
"reveal_tiles", "preview", "grant_ap", "remove_mines", "refresh_pick"]
@@ -49,7 +49,7 @@ const TARGETS := ["none", "area_3x3", "board_wide"]
const USE_PHASES := ["active_dig"]
const FORMS := ["", "passive", "active_charged"]
# magnitude 消费面(≥1 校验):grant_ap 与同调 N 值 kind;其余 kind 活行 magnitude 恒 0
const MAGNITUDE_KINDS := ["ap_grant", "venom_rider", "flag_burst", "reflect_damage", "ap_rounds_front",
const MAGNITUDE_KINDS := ["ap_grant", "reflect_damage", "ap_rounds_front",
"ap_charge_safe", "reward_weight_bias", "bonus_refresh", "grant_ap"]
# 🔴 零裸 flat 结构护栏:行键白名单封闭(键出域=加载致命)——机制轴禁无条件属性加项的表结构承载
const KEY_WHITELIST := ["item_id", "display_name", "dimension_tags", "effect_kinds", "target",
-53
View File
@@ -73,59 +73,6 @@ rows = [{
"slot_cost": 1,
"charges_per_adventure": 2
}, {
"item_id": "venom_edge",
"display_name": "淬毒刃纹",
"dimension_tags": ["counter"],
"desc": "克制正面直伤+2",
"target": "none",
"magnitude": 2,
"use_phase": "active_dig",
"form": "passive",
"slot_cost": 1,
"triggers": [{
"on": "clamp_check",
"when": [{
"fact": "matchup",
"op": "eq",
"value": "pierce_vs_ward"
}],
"do": {
"kind": "modify_ticket",
"delta_from": "magnitude",
"mode": "clamp_bonus"
}
}, {
"on": "clamp_check",
"when": [{
"fact": "matchup",
"op": "eq",
"value": "magic_vs_heavy"
}],
"do": {
"kind": "modify_ticket",
"delta_from": "magnitude",
"mode": "clamp_bonus"
}
}]
}, {
"item_id": "flag_burst",
"display_name": "标中殉爆",
"dimension_tags": ["action"],
"desc": "每标中一旗全体敌受2",
"target": "none",
"magnitude": 2,
"use_phase": "active_dig",
"form": "passive",
"slot_cost": 1,
"triggers": [{
"on": "flag_hit",
"do": {
"kind": "open_ticket",
"value_from": "magnitude",
"channel": "blast"
}
}]
}, {
"item_id": "nest_counter",
"display_name": "惊巢反制",
"dimension_tags": ["risk"],
+7 -4
View File
@@ -50,11 +50,14 @@ func _initialize() -> void:
feat_mode = args[6]
var feat_profile := {}
if feat_mode == "formed":
# 迭代 20 T8 画像换代——旧 14 件随老 27 件退役;新画像=五事件族覆盖 14 件
# (开格 4/插旗 3/踩雷 4/节奏 2/战斗 1——每族含跨事件账本件,账本件=攒层/计数面 MC 读数承载)
feat_profile = {"mode": "on", "seed_ids": [
"arcane_ember", "mana_weaver", "pierce_shot", "chain_arrows",
"miner_stubborn", "miner_hammer", "greedy_lantern", "vein_intuition",
"mine_atlas", "nest_hunter", "armory_prep", "iron_wall",
"ley_harvest", "thorn_weaver"]}
"twin_pick", "geologist", "hard_hat", "frontier_miner",
"seal_echo", "seal_smith", "seal_stake",
"pain_memory", "trap_instinct", "chain_traps", "blood_honey",
"last_drop", "market_close",
"execution_scholar"]}
elif feat_mode == "ab_a":
feat_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]}
elif feat_mode == "ab_b":
+2
View File
@@ -654,6 +654,8 @@ func _smoke_feats() -> void:
# ② 旧 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〕")
check(_db.treasure_items.row_by_id("venom_edge").is_empty() and _db.treasure_items.row_by_id("flag_burst").is_empty(),
"迁出收尾:treasure_items 无 venom_edge/flag_burst 行(同调目录自然收窄)〔专长批 V13〕")
# ③ 契约面静态钉扎(V18/V19/V21/V22——人工核对 V20/V21 裁决面以外的可机检半)
var kinds_ok := true
for t in a_r0._curio_deck:
@@ -86,4 +86,22 @@ func _init() -> void:
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〕")
# V16——断点扩容往返:新账本随断点快照/续玩回填(breakpoint_resume_test P5 同构)
var sm = load("res://core/save_manager.gd").new("user://_feat_vouchers_bp.json")
sm.load_from_disk()
a.adventure_counters = {"pain_layers": 3, "twin_digs": 5}
a._refresh_stock = 2
a._battery_pending = 1
a.set_save_manager(sm)
a._on_level_finished({"result": "victory", "hp": 10, "node_index": 1, "node_id": 1, "node_type": "shop"})
var bp16: Dictionary = sm.breakpoint_snapshot()
Kit.check_quiet(int((bp16.get("adventure_counters", {}) as Dictionary).get("pain_layers", -1)) == 3
and int(bp16.get("refresh_stock", -1)) == 2 and int(bp16.get("battery_pending", -1)) == 1,
"V16:新账本三件入断点快照(adventure_counters/refresh_stock/battery_pending)〔专长批 V16〕")
var b2 = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(b2.resume_adventure(bp16.duplicate(), {}), "V16 前置:续玩受理")
Kit.check_quiet(int(b2.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2 and b2.battery_pending() == 1,
"V16:续玩回填逐项一致(计数恢复——跨战账不丢)〔专长批 V16〕")
sm.clear_breakpoint()
quit(Kit.finish("feat_vouchers"))
+38
View File
@@ -27,6 +27,26 @@ func _init() -> void:
var c = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
check_v8(c, db)
# V12——Boss 三选一按档配比加权抽取(MC 同种子批量抽样档位频率出带;权重 provisional
# 100/45/15/5 ×表内件数 12/9/8/0 → 期望占比 ≈ 69/26/4/0,带 ±15pp 容差)
var m = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
check_v12(m, db)
# T10——空配置验收闸(三选一池空兜底/商店空货架)
var e = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
e.start_adventure(501, {"first_script": true})
e._curio_pending = {"candidates": []}
Kit.check_quiet(e.skip_empty_curio() and e._curio_pending.is_empty(),
"空配置闸:三选一池空=停点整体跳过(skip_empty_curio 消化)〔专长批 T10〕")
e._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
Kit.check_quiet(not e.skip_empty_curio(),
"空配置闸反例:非空停点拒绝跳过(零自动推进契约不破)〔专长批 T10〕")
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
lr.set_shop_pool([], Callable(), Callable(), Callable())
lr._shop_draw_shelf()
Kit.check_quiet((lr.shop_bp_shelf() as Array).is_empty(),
"空配置闸:商店空货架=合法态(牌库空=零扰动不崩)〔专长批 T10〕")
quit(Kit.finish("pool_case_d"))
@@ -118,6 +138,24 @@ func check_v6(b, db) -> void:
Kit.check_quiet(unchanged, "只刷出不计:抽签不改已拿账〔专长批 V6〕")
func check_v12(m, db) -> void:
if not m.start_adventure(401, {"first_script": true}):
Kit.check_quiet(false, "V12 前置:冒险启动")
return
var tier_n := {"common": 0, "excellent": 0, "rare": 0, "legendary": 0}
var total := 0
for i in 300:
for t in m._draw_curio_candidates("level_clear"):
tier_n[String(t.get("tier", ""))] = int(tier_n.get(String(t.get("tier", "")), 0)) + 1
total += 1
Kit.check_quiet(total == 900, "V12 前置:300 停点 ×3 候选=900 席(实 %d)" % total)
var p_common := float(tier_n["common"]) / float(total)
var p_excellent := float(tier_n["excellent"]) / float(total)
var p_rare := float(tier_n["rare"]) / float(total)
Kit.check_quiet(p_common > 0.54 and p_common < 0.84 and p_excellent > 0.11 and p_excellent < 0.41 and p_rare >= 0.0 and p_rare < 0.19 and tier_n["legendary"] == 0,
"Boss 档配比频率出带(common %.2f/excellent %.2f/rare %.2f/legendary 0——权重 100/45/15/5×件数 12/9/8/0)〔专长批 V12〕" % [p_common, p_excellent, p_rare])
func check_v8(c, db) -> void:
if not c.start_adventure(203, {"first_script": true}):
Kit.check_quiet(false, "V8 前置:冒险启动")