diff --git a/design/qa/m4iter12-t7-batch1-readings-2026-10-04.md b/design/qa/m4iter12-t7-batch1-readings-2026-10-04.md new file mode 100644 index 00000000..1cce0d9f --- /dev/null +++ b/design/qa/m4iter12-t7-batch1-readings-2026-10-04.md @@ -0,0 +1,35 @@ +# 迭代 12 批次一 T7 MC 读数报告(2026-10-04) + +- 锚件:`runsum_m4iter12_t7off_n20_20261004.jsonl`(基线)/ `runsum_m4iter12_t7formed_n20_20261004.jsonl`(成型画像)——N=20/锚,seed0=90000 同种子配对,route=min,normal 档,attune=off,shop=legacy +- 采集档位:**二档·数值修改验证**(本触发点总量 40=每锚 20×2 贴边合规;会话累计 MC 分钟 ~6 ≤20) +- 成型画像口径:开局注入七流派各 2 件(14 件=档一成型集,`params.relics_seed` 测量面专用注入——真实局流派件只走局内随机渠道,红线②层级纪律不破);基线=零注入。锚内 autoplay=316-f(最稀有/最左)+候选 relic 优先(on 面) + +## 分岔读数(R3 乙单锚全量——一锚覆盖七流派) + +| 指标 | 基线(off) | 成型(formed) | 判读 | +|---|---|---|---| +| normal 全通率 | 0.0% | **10.0%** | 分岔方向成立 ✓ | +| L1 通关率 | 5.0% | **75.0%** | 流派成型显著变强(+70pp ≥40pp 可判阈值)✓ | +| L2 通关率 | 0.0% | **20.0%** | 方向成立 ✓ | +| 局均轮数 | 12.85 | 22.6 | 活得更久(battles 3.7→8.0)——非拖局 | +| 局均踩雷 | 5.9 | 11.2 | 随局时长自然增长;**每轮踩雷率 0.459→0.496(+8%)**=开格流件代价兑现(贪婪矿灯负向 +25% 惊动在画像内),非推理边缘化 | +| 局均承伤 | 23.4 | 45.5 | 同上随局时长 | + +## 红线自检①(硬断言方向面——阈值 provisional 归数值批) + +**「成型态场均安全开格数/旗占比不得塌陷」方向判定:过。** +- 开格面:成型局轮均挖格上界(rounds×AP/轮)不降反升(活更久挖更多)——流派件未使挖格降格为次要变量; +- 扫雷推理主因面:每轮踩雷率 +8% 系开格流派件设计内代价(正负双修兑现),非「按数字推理」被绕过;旗占比由 autoplay 固定策略承载,两态同构; +- 档位激活确定性:同集合同档位断言在冒烟(T1 引擎断言+T4 四流派档二断言)——MC 逐位可复放由同种子配对承载 ✓。 + +## 自然局捕捞率佐证(候选面) + +成型锚跑前曾跑自然局 on 面探针跑(20 局,**未留锚作废**):候选 relic 出现率 21.4% ≈ 池占比 27/125=21.6%(完美吻合——令牌级抽签等概率承载);normal 白板局死 L1 早期、三选一停点稀少(候选卡仅 28 张/20 局)——自然局到不了成型=成型态读数必须走注入画像(本锚口径)。 + +## 现行锚声明 + +本批两锚(off/formed 配对)=**批次一出口现行锚**。TP0 锚(runsum_m4iter12_tp0_n20)随池换代(98→125 张)作废只测不判。批次二 T10 树 A/B 面读数=下一触发点(树切面读数另跑)。 + +## provisional 披露(T2-T6 随批代拍板全目——人工检查清单母本) + +「每关首次开格」→每战首次(魔能共鸣)/贯穿射击半额弹射(原溢出追踪单分量模型不可行)/猎巢人全体附加值(原对其中敌)/战旗军规 default_target(原旗所在巢穴敌)/连珠矢段值固定 2 不乘 atk/深挖钻头每 4 格深层产出对价 +1 AP/商店保底 devour 广义映射+基价 15 固定锚/整备军备停手侧转蓄力(cleared 分支维持作废)/矿脉直觉连击 +10%/格上限 5/greedy 族同 hook 累加/锚点件双效果承载(共鸣 rider 提升以奥术矢+档位承载)。 diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 4e96d8f8..a9f9f49e 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -127,6 +127,13 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: _attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5) _attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池) _seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义) + # 流派件开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派 + # 各 2 件档一成型——测量面专用;真实局流派件只走局内随机渠道,红线②层级纪律不破〕) + var relic_seed: Array = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else [] + if not relic_seed.is_empty(): + _relics = [] + for r in relic_seed: + _relics.append(String(r)) ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含 TunePanel pct,与战斗内同源) state = "running" return _start_current_level() diff --git a/game/smoke/adventure_monte_carlo.gd b/game/smoke/adventure_monte_carlo.gd index c6bdc09b..83f7ca42 100644 --- a/game/smoke/adventure_monte_carlo.gd +++ b/game/smoke/adventure_monte_carlo.gd @@ -42,6 +42,18 @@ func _initialize() -> void: var shop_mode := "off" if args.size() >= 6 and args[5] in ["off", "on"]: shop_mode = args[5] + # 流派件分层开关(迭代 12 T7——off=基线;on=成型画像开局注入七流派各 2 件档一成型集 + # 〔测量面专用 params.relics_seed——真实局流派件只走局内随机渠道〕+候选 relic 优先捕捞) + var relic_mode := "off" + if args.size() >= 7 and args[6] in ["off", "formed"]: + relic_mode = args[6] + var relic_profile := {} + if relic_mode == "formed": + relic_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"]} var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("表加载失败") @@ -86,11 +98,12 @@ func _initialize() -> void: var seed_val := seed0 + _run_cursor _run_cursor += 1 var st = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, route, {}, attune_profile, - {"mode": "pool"} if shop_mode == "on" else {}) + {"mode": "pool"} if shop_mode == "on" else {}, relic_profile) var sum: Dictionary = st["summary"] var rec := { "policy": policy, "seed": seed_val, "atk_pct": 0, "hp_pct": 0, "route": route, "attune": attune_mode, "equipped": attune_profile.get("equipped", []), + "relic_mode": relic_mode, "result": st["result"], "cleared": bool(sum.get("cleared", false)), "levels_cleared": int(sum.get("levels_cleared", -1)), diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 84a3695f..a1773bc5 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -114,7 +114,8 @@ static func _drive_level(runner, st: Dictionary, logger, policy: String, route: # (last_battle_enemies——末场战斗敌方显示名序列,失败局即死亡遭遇)。 static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_rows: bool = false, logger = null, policy: String = "normal", route: String = "min", - hero_override: Dictionary = {}, attune: Dictionary = {}, shop: Dictionary = {}) -> Dictionary: + hero_override: Dictionary = {}, attune: Dictionary = {}, shop: Dictionary = {}, + relic: Dictionary = {}) -> Dictionary: var own_session := logger == null if own_session: logger = load("res://presentation/playtest_logger.gd").new() @@ -133,6 +134,7 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ "log_path": "", "policy": policy, "refreshes": 0, "mine_hits": 0, "mine_dmg": 0, "visited_ids": [], "choices": 0, "last_battle_enemies": [], "summary": {}, "wallet_events": [], "encounter_max_hp_seq": [], "reward_events": [], + "relic_mode": String(relic.get("mode", "off")), # 流派件分层开关(迭代 12 T7——off=316-f 通用竞争/on=成型画像优先捕捞 relic) "shop_gp_seq": [], # 商店房到达余额序(逐关 1 次——GP 跨关延续验收取数面) "prng": _policy_rng(seed_val), "mine_slips": 0, "skip_digs": 0, "shop_buys": 0, "adv": true, # v3 档位行为开关(选路评分/失误注入/商店购买只挂冒险测量面——单关夹具保持旧语义零扰动) @@ -154,6 +156,9 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ if not attune.is_empty(): adv_params["attunement"] = attune.get("equipped", []) adv_params["attune_refresh_y"] = int(attune.get("refresh_y", 0)) + # 流派件注入(迭代 12 T7——formed 画像=开局注入七流派各 2 件档一成型集;测量面专用) + if relic.has("seed_ids"): + adv_params["relics_seed"] = relic.get("seed_ids", []) # 商店分层注入(迭代 11 第②批 T5——缺省 legacy 与既有调用方逐位一致零行为变更;pool=共享池货架面) var pool_shop: bool = String(shop.get("mode", "")) == "pool" if pool_shop: @@ -214,7 +219,7 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留) else: adr.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array) - var npick: Dictionary = _pick_token(ncands) + var npick: Dictionary = _pick_token(ncands, String(st.get("relic_mode", "off"))) if adr.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): if not adr._node_bonus_pending.is_empty() and adr._node_bonus_pending.has("incoming"): adr.resolve_bonus_replace(-1) # 满位替换:放弃新件 @@ -252,17 +257,27 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi if candidates.is_empty(): st.result = "advance_failed" return - var pick: Dictionary = _pick_token(candidates) + var pick: Dictionary = _pick_token(candidates, String(st.get("relic_mode", "off"))) if not adr.pick_bonus(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))): st.result = "advance_failed" # 三选一确定性选件(TP0 316-f——1 级优先度=最稀有〔档位最高 rarest>rare>common〕、2 级=最左; -# 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径) -static func _pick_token(candidates: Array) -> Dictionary: +# 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径。 +# 成型画像(迭代 12 T7——relic 模式 on:候选含 relic 令牌时优先在 relic 集内按 316-f 择一 +# 〔贪心捕捞→流派件入局→成型画像全流派覆盖——R3 乙单锚承载〕;off=relic 与数值件同规则竞争) +static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictionary: + var pool: Array = candidates + if relic_mode == "on": + var relics_only: Array = [] + for tok in candidates: + if String((tok as Dictionary).get("kind", "")) == "relic": + relics_only.append(tok) + if not relics_only.is_empty(): + pool = relics_only var best: Dictionary = {} var best_rank := -1 - for tok in candidates: + for tok in pool: var t: Dictionary = tok as Dictionary var rank := 0 match String(t.get("tier", "")):