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:
1 parent
5ebd05d4b4
commit
3333187351
9 files changed
+111
-63
No files matched your search
@@ -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]
|
||||
|
||||
@@ -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=首开资源类型〔地质学家〕;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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"))
|
||||
@@ -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 前置:冒险启动")
|
||||
|
||||
Reference in new issue
Block a user