コンテンツにスキップ

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) が commit 707e1c5 (rule agent の 整形・挙動調整、18 agent) と不整合になっていた → main.py 側の変更が 意図的なコミットのため、tools/regen_frozen_hashes.py で lock を追従
  • 探索空間の choice values に現行 params.yaml の値が入っていない次元が 2 つあり warm-start が partial になっていた → values に現行値を追加 (「choice には現行値を必ず含める」を SKILL とドキュメントに明文化)

次のアクション

  1. raging_bolt_ogerpon の実 tuning ラウンド: --stage optuna --n-trials 80 --games-per-config 200 --evaluator selfplay --confirm-top-k 3 --selfplay-workers <固定値>
  2. confirmed.csv レビュー → winner_source=search なら apply → 500 games ホールドアウト
  3. future work: games 数 multi-fidelity 化 / 終盤の浅い全探索 / turn planner (prize_race が材料)