Tuning 信頼性改善 + 共有 combat 層 — 判断ログ (2026-07-09)¶
2026-07-05 の再設計 の続き。tuning 基盤 (Optuna + SelfplayEvaluator) を実運用に耐える信頼性にする Track T と、 対戦アルゴリズムの費用対効果最大の一手 (リーサル計算の共有化) を入れる Track A の判断記録。仕組みの正典は architecture/ 側: tuning / tuning-optuna / combat / ADR 0004。
実装したもの¶
| 項目 | 要点 |
|---|---|
| T1 warm-start | trial 0 = 現行 params.yaml の射影 (enqueue_trial + skip/clamp/snap サニタイズ) |
| T2 seed 固定 | --seed-mode fixed 既定化。部分 CRN (下記の発見) |
| T3 確認ラン | --confirm-top-k: top-K + baseline を 3 倍 games・別 seed で再評価し best.yaml を確定 |
| T4 加重 objective | configs/rule_agents.yaml の match_weight を per-opponent 勝率の重みに。--worst-opponent-weight も追加 |
| A1 combat.py | best_ko_attack / ko_plan / opponent_can_ko_me。raging_bolt View は委譲化 (パリティテストで挙動不変を証明) |
| A2 tier 化 | score_attack のベタ書きを attack.tier.* に。tuning 空間 +7 次元 (~19 次元) |
| A3 prize race + 同ターン文脈 | estimate_prize_race (params default 0 で無効) / parse_turn_context (Glass Trumpet 連打抑制) |
主要な判断と理由¶
完全な共通乱数 (CRN) は不可能 — 確認ランを本命に¶
調査の結果、ゲーム内シャッフルは cg.game.battle_start(deck0, deck1) →
lib.BattleStart に seed 引数がなく、libcg.so にも seed 系エクスポートが
無い。seed 固定で消せるのはデッキマッチアップ系列の分散のみ。
このため計画当初「CRN で実質 2〜4 倍の解像度」と見込んでいた効果を下方修正し、
分散対策の本命を確認ラン (T3) に置いた。seed 固定はコストゼロなので
既定 ON のまま残す。
baseline は「確認ランの候補」としても常に入れる¶
warm-start (探索の基準点) と champion/challenger (確認ランで現行値と直接対決)
は別の役割。--no-warm-start でも確認ランには baseline が入る。
winner_source=baseline は「探索空振り」の明示シグナルで、apply 中止の
判断材料になる。
tier の逆転防止は「互いに素な range」方式¶
attack.tier.ko < attack.tier.big のような逆転を TPE が提案できないように
する方法として、(a) delta パラメタ化 (big = ko - delta)、(b) 探索 range を
互いに素に設計、を比較。(b) を採用: params.yaml が実スコアのまま読める
可読性と、space yaml だけで制約が完結する単純さを優先。winning_ko は
40000 固定とし、supporter_tier.lethal の下限を 42000 に上げて
「ボス KO 確定 > 素の勝利攻撃」の相対順序を space 側で保証した。
挙動不変の証明手順 (View 委譲)¶
ゲーム内シャッフルが seed 不可 = 決定的 A/B diff が不可能なので、
委譲前の View 実装に対して combat.best_ko_attack の一致を合成盤面
128 ケースで先に green にしてから 委譲した
(tests/rule_agents/test_raging_bolt_scoring.py の tier 境界パリティも同様)。
統計 A/B は selfplay smoke で補完。
prize_race / TurnContext は default 無効で導入¶
prize_race.behind_aggression_bonus / ahead_conserve_penalty は default 0
(= race 計算自体スキップ) で入れ、効くかどうかの判断は tuning に委ねる。
人手で「劣勢時 +2000」等を決め打ちしない。
途中で直したもの¶
- 凍結 hash lock (
frozen_hashes.json) が commit707e1c5(rule agent の 整形・挙動調整、18 agent) と不整合になっていた → main.py 側の変更が 意図的なコミットのため、tools/regen_frozen_hashes.pyで lock を追従 - 探索空間の choice values に現行 params.yaml の値が入っていない次元が 2 つあり warm-start が partial になっていた → values に現行値を追加 (「choice には現行値を必ず含める」を SKILL とドキュメントに明文化)
次のアクション¶
- raging_bolt_ogerpon の実 tuning ラウンド:
--stage optuna --n-trials 80 --games-per-config 200 --evaluator selfplay --confirm-top-k 3 --selfplay-workers <固定値> - confirmed.csv レビュー → winner_source=search なら apply → 500 games ホールドアウト
- future work: games 数 multi-fidelity 化 / 終盤の浅い全探索 / turn planner (prize_race が材料)