大富豪AI開発の道

大富豪ラウンジにおける公式レーティング戦の試験運用を開始。本格ローンチに備えてAIを強化中。ヒューリスティックとMC(モンテカルロ)では早くも限界に達し、本格的な機械学習に向けての備え。備忘録として。

Feature Specification: 特徴量方策 storm(feature-vector learned policy)

Feature Branch: 002-feature-policy-fp-v1

Created: 2026-06-03

Status: Draft

Input: User description: “特徴量方策 storm。大富豪CPUの次世代エンジン。優先50特徴ベクトルを抽出し score=dot(weights,features) の決定論的argmaxで手を選ぶ。重みは人が決めず学習。最重要成果物は過去の評価失敗を構造的に封じる事前登録・多段の評価プロトコル。TypeScript完結、PyTorch+PPOは将来契約。”

Overview

大富豪CPUの強さは、これまで人が重みを決めるヒューリスティック改良で伸ばしてきたが、ほぼ頭打ちになった(shape/danger/surplus は反証され、per-seat dominance と MC-override だけが僅差で効いた)。次の伸びしろは 「人が重みを決めない」特徴量方策 — 局面と候補手から多数の特徴量を抽出し、学習で得た重みでスコアして手を選ぶ新エンジン storm

ただし、このプロジェクトで繰り返された失敗は「特徴の良し悪し」ではなく 「対局=評価の設定そのものが効果的でなかった」 ことにある。データを見てから合格ラインを決める後付け(p-hack)、自己相互作用で約3割水増しする対戦形式、少数試合での見かけの優位(2400局 +0.96 → 6000局 −0.18 の反転)、自己対戦の中だけで最適化して偽のシグナルを拾う過学習、相手手札を完全に覗いても +2pt で頭打ちというカウント精度の限界、そして自己報告の数値捏造。

したがって本フィーチャーの中心成果物は「学習エンジン」ではなく「過去の評価失敗を構造的に封じ込める、事前登録された多段の評価プロトコル」である。 強いエンジンが作れるかどうかは結果でしかなく、その結果を 正直に・再現可能に・誇張なく 判定できる土台こそが価値。

本フィーチャーは TypeScript で完結する(特徴抽出・重み学習・評価ファネル)。Python(PyTorch + PPO)への拡張は、TS 段で具体的なシグナルが確認できた場合に着手する将来フェーズとし、本フィーチャーでは契約(着手条件)の明文化のみを行う。

Clarifications

Session 2026-06-03

  • 重み学習の方式: ハイブリッド(MC教師から線形回帰で初期重みを蒸留 → frozen-v2 を相手にした direct A/B の成績を直接最適化する探索で精錆)。判定指標は常に direct A/B であり、教師との手一致率は判定に使わない。
  • スコープ: TypeScript 完結。Python+PPO は本フィーチャーのタスク対象外(将来契約のみ)。
  • 命名: エンジン識別はトークン storm/世代識別子 storm@<train-id>。表示名は (既存の単漢字名 紺/翠/藍/鶯 と揃え、学習族を別 namespace 化)。表示名は PROMOTE 時に cpu_profiles へ反映する(それまでコード未配線)。
  • 強さ判定の対戦形式: 1体の候補を強基準3体の場に入れる hero-vs-field 形式のみを使用。同種2体ずつの対戦形式は自己相互作用で結果を水増しするため使用しない(補助診断としてのみ残す)。

User Scenarios & Testing (mandatory)

User Story 1 – 特徴抽出器と決定論的スコアラ(Priority: P1)

CPU 開発者として、任意の局面と合法手に対し固定長の特徴ベクトルを抽出し、重みベクトルとの内積でスコアして最良手を選べるようにしたい。これにより「人が重みを決めない」方策の土台ができる。

Why this priority: 特徴抽出器がなければ何も学習・評価できない。本フィーチャーの全ての基盤であり、かつ重みの良し悪しと独立に「正しく決定論的に動くか」を検証できる。

Independent Test: 候補手ごとに固定長ベクトルが返ること、および 同一固定配牌バンクを用い、同一エンジンを hero として4席すべてに巡回させた hero-vs-field sanity(hero=field)で hero 平均着順が厳密に 2.500000 になることを確認する(決定論の証明)。重みが強いかどうかとは無関係に成立する不変条件。注: この厳密 2.5 は「固定配牌バンク+4席巡回+決定論エンジン」の3条件が揃って初めて成立し、ランダム自己対戦や席巡回なしでは成立しない。

Acceptance Scenarios:

  1. Given 任意の局面と合法手集合、When 特徴抽出を実行、Then 全候補に対し同じ長さの数値ベクトルが決定論的に返る(同入力→同出力)。
  2. Given 同一重みを hero と field(基準席)に与え、固定配牌バンクで hero を4席巡回させる hero=field sanity、When 対戦、Then hero 平均着順が 厳密に 2.500000(同点手の決着順が決定論的=事前登録した決着規則 actionKey 昇順に従う)。
  3. Given 重みファイルが読めない/スキーマ不一致の状況、When 推論を要求、Then 既存の最強ヒューリスティック(基準エンジン frozen-v2)に安全に縮退し、反則上がりを誘発しない。
  4. Given 重みが極端な値で score が反則上がりや見逃しmateを示す手を高評価しても、When 手を選択、Then hard guard(即時合法上がり/mate/反則上がり回避)が score より上位に効き、ルール上不利な手を選ばない。

User Story 2 – 重み学習と事前登録された評価ファネル(Priority: P1)

CPU 開発者として、学習用データだけで重みを探索し、互いに素なデータ群を通る多段ファネルで候補を絞り込み、最終的に PROMOTE / TIE / REJECT のいずれかを逐語の数値とともに判定したい。後付けの合格ライン変更ができない仕組みであること。

Why this priority: これが本フィーチャーの心臓。過去の失敗は全てこの段で起きた。事前登録・データ分離・正直な失敗パスを構造として持たせることが最大の目的。

Independent Test: 4つの互いに素なデータバンク(学習/検証/確認/最終ホールドアウト)を通して end-to-end でファネルを回し、各段の試合数(N)と合否・判定を出力できる。自己対戦の不変条件(平均着順2.5)を通過し、特徴の寄与分析がデータバンク間での符号の一貫性を報告する。

Acceptance Scenarios:

  1. Given 互いに素な4バンク、When 学習バンクのみで重みを探索し検証バンクで候補を選別、Then 確認バンクは1回だけ参照され、合格ラインは探索開始前に確定済みである。
  2. Given 確認バンクで合格した1つの重み、When 最終ホールドアウト(凍結後に新規生成)で評価、Then 確認時からの成績低下が事前登録した許容幅以内であること(超えたら過学習として却下)。
  3. Given ある段で合格ラインに届かない候補、When 判定、Then 「実質互角=強さ目的でデプロイしない」と正直に記録され、合格ラインを後から緩めない。
  4. Given 全段通過の候補、When 最終判定、Then 固定配牌では hero scoreDelta/top2Delta/rank4Delta、実環境では H2H(hero が各 baseline 席より上位だった比率)を、いずれも95%信頼区間(deal-block bootstrap)下限を添えて PROMOTE/TIE/REJECT として文書化する。

User Story 3 – 実環境アリーナ候補枠と Python 昇格ゲート(Priority: P2)

CPU 運用管理者として、固定配牌で有望と出た候補を実環境のセルフプレイ・アリーナの候補枠に置き、現行の運用4体と直接対戦させて実環境での強さを確認したい。あわせて、Python(PyTorch+PPO)フェーズへ進む価値があるかの判断材料を記録したい。

Why this priority: 固定配牌の検出力と実環境の代表性は用途が違う(直近セッションで「アリーナ=総合力モニタ/固定配牌=強さ判定」の二本立てが確立済み)。実環境確認は最終ゲートだが、P1 の土台がなければ意味をなさないため P2。

Independent Test: 候補がデプロイ済みラダーの順位に合算されず暫定計測されること、σ収束後の head-to-head 判定が管理画面で読めること、そして Python 着手条件(事前登録)の充足可否が記録されること。

Acceptance Scenarios:

  1. Given 固定配牌で有望と出た候補、When アリーナ候補枠に投入、Then 既存4体のレーティングに影響を与えず暫定的に対戦・計測される。
  2. Given σ収束した実環境の対戦結果、When 最終ゲートを適用、Then PROMOTE/TIE/REJECT が確定し、結果が永続記録される。
  3. Given 評価結果と特徴寄与分析、When Python 着手判断、Then 事前登録した昇格条件(近PROMOTE もしくはバンク間一貫の特徴シグナル)の充足可否が明記される。

Edge Cases

  • 候補手のスコアが同点のとき、決着が局面オブジェクトの並び順などの非決定要素に依存すると自己対戦の不変条件(2.5)が崩れる → 決着規則を事前登録し決定論化する。
  • 重みファイルが本番で読めない(配信失敗)とき → 基準エンジンに縮退し、反則上がりを増やさない。
  • 確認バンクを一度参照した後に重みを再調整したくなったとき → 確認バンクは「汚染」したものとして再生成しない限り再利用禁止(後付け最適化の防止)。
  • 学習バンクで好成績だが検証/確認/ホールドアウトで低下するとき → 過学習として却下(見かけの優位を採用しない)。
  • 真の差が実質ゼロのとき → どれだけ試合を重ねても有意にならない。「互角」を正しい結論とし、無限に回し続けない。
  • 自己相互作用で水増しする対戦形式(同種2体ずつ)の数字を強さの根拠にしてしまう → 強さ判定は hero-vs-field のみ。水増し形式は補助診断に限定。

Requirements (mandatory)

Functional Requirements

  • FR-001: システムは任意の局面と各合法手に対し、決定論的で固定長の数値特徴ベクトルを生成しなければならない(同入力→同出力)。
  • FR-002: 手の選択は特徴ベクトルと重みの内積スコアに基づく決定論的な最良選択でなければならず、同点時の決着規則は探索開始前に事前登録すること。
  • FR-003: 重みはコードに手書きせず、名前で差し替え可能な外部データとして読み込まなければならない(学習・探索で決定する)。
  • FR-004: 重みの探索は学習バンクのみで最適化し、候補選択は検証バンク、最終確認は確認バンク(1回のみ)最終ホールドアウト(凍結後に新規生成・1回のみ)で行わなければならない。これら4バンクは互いに素であること。
  • FR-005: すべての合格ライン(しきい値)と各段の必要試合数は、初回の探索を実行する前に仕様として事前登録しなければならない。
  • FR-006: すべての固定配牌評価は試合数(N)を報告し、自己対戦の不変条件(平均着順がちょうど2.5)を通過しなければならない。通過しない結果は無効として破棄し、根拠に使ってはならない。
  • FR-007: 強さ判定は hero-vs-field 形式(候補1体 vs 基準エンジン3体)のみを用いなければならない(自己相互作用で水増しする同種2体ずつの形式は使わない)。最終判定は固定配牌の hero scoreDelta/top2Delta/rank4Delta と実環境の H2H(hero が各 baseline 席より上位だった比率)に基づく PROMOTE/TIE/REJECT の3区分で行う。
  • FR-008: 重みの読み込みに失敗した場合、システムは既存の最強ヒューリスティック(基準エンジン)へ安全に縮退しなければならない(反則上がりを増やさない)。
  • FR-009: 新エンジンの導入は、稼働中の個体が0体である限り既存CPUの挙動を一切変えてはならない(デプロイ中立)。
  • FR-010: REJECT または TIE の判定は「検出可能な差なし=実質互角」として正直に記録し、強さを理由とするデプロイをしてはならない。合格ラインを後から緩めてはならない。
  • FR-011: 特徴ごとの寄与分析(除去時の成績変化)を検証バンク上で行い、学習バンクと検証バンクで符号が一貫する特徴を識別できなければならない(Python 昇格判断の材料)。
  • FR-012: Python(PyTorch+PPO)フェーズへ進む条件を事前登録し、本フィーチャーでは契約として記載するのみとする(実装は対象外)。
  • FR-013: すべての特徴量は事前定義された範囲に正規化されなければならない。原則 [0,1] または [-1,1] に収め、範囲外値はクリップして診断ログに記録する。
  • FR-014: 重みセットは feature_schema_versionfeature_schema_hash を持たなければならない。実行時の特徴スキーマと一致しない重みは読み込まず、基準エンジンへ縮退する。
  • FR-015: 信頼区間は deal-level block bootstrap で計算しなければならない。同一 deal の4席巡回結果を独立試合として扱ってはならない(1 deal の4ゲームを1ブロックとして deal 単位でリサンプリング)。
  • FR-016: Type-B scenario rollout(正解不明局面の単一 rollout)の結果を、単一の正解ラベルまたは重みの最終採用判定に使用してはならない。Type-B は診断・回帰・特徴分析のみに使用する。
  • FR-017: 診断シナリオは人工局面より実敗北局面を優先する。初期シナリオセットは現行強基準(frozen-v2 等)が4位になった対局から hand<=7 の局面を自動抽出して作る。人工局面は不足カテゴリの補完にのみ使う。
  • FR-018: feature policy は本番ルール判定を再実装してはならない。合法手生成・反則上がり・8切り・革命・縛り・mate/dominance は既存本番関数に委譲する(Constitution II 単一真実源)。
  • FR-019: storm の採用判定は同一 deal bank 上の hero-vs-field 4席巡回に限定する。AA/BB 形式は自己相互作用検出の補助診断としてのみ使用できる。

Pre-Registered Evaluation Thresholds(v1初期値・探索前に確定。走らせる前に書く)

数字は仮値でも構わないが「書いてから走らせる」ことが本質(後付けの p-hack 防止)。CI はすべて deal-block bootstrap(FR-015)。

バンクと N(固定配牌×4席巡回):

  • train: 500 deals(低コスト探索の目的関数。探索ループのみ)
  • validation: 200 deals × 4 = 800 games(候補選別・ablation)
  • confirmation(test): 1000 deals × 4 = 4000 games(採用1vector・1回のみ)
  • final holdout(FRESH): 1500 deals × 4 = 6000 games(vector 凍結後に新規生成・1回のみ)

Validation pass(候補保持): scoreDelta > +0.03top2Delta > +1.0ptrank4Delta < −1.0ptillegalFinishRateDelta <= +0.2pt

Confirmation pass: scoreDelta > +0.04top2Delta > +1.0ptrank4Delta < −1.0ptscoreDelta の95%CI下限 > 0

Final holdout PROMOTE候補: scoreDelta > +0.0495%CI下限 > +0.01rank4Delta < 0illegalFinishRateDelta <= 0confirmation比 scoreDelta 低下 <= 0.03

Arena PROMOTE(最終・実環境): 上記固定配牌 PROMOTE候補に加え、001 アリーナで σ収束後 H2H ≥ 55% vs 紺(frozen-v2)・鶯(frozen-v3) 両方(95%CI下限 > 50%)かつ ≥ 藍(selectheur-v2)。

TIE: scoreDelta の95%CI が0を含む または |scoreDelta| <= 0.02(実質互角)。アリーナ H2H が ≥50% だが <55% も TIE。

REJECT: scoreDelta < −0.02 または rank4/illegal が悪化 または holdout 低下が許容幅(0.03)を超過 または アリーナ H2H < 50% vs 藍。

Key Entities (include if feature involves data)

  • 特徴ベクトル: ある局面と合法手から抽出される固定長の数値列。v1 = base 50特徴 + 交互作用6項 = 56次元固定(正規化済 [0,1]/[-1,1])。各特徴は feature_name / description / range / normalization / dependency / is_interaction を持つスキーマ(features.json)で定義され、feature_schema_hash で版固定する。「出した後の状態が勝ち筋/負け筋/テンポ/相手の上がり阻止にどう効くか」を表現する。
  • 重みセット: 学習で得た係数列。世代識別子 storm@<train-id> で版管理され、名前で差し替え可能。feature_schema_version/feature_schema_hash を保持し、実行時スキーマと不一致なら読込拒否→基準エンジン縮退。
  • 配牌バンク: 学習/検証/確認/最終ホールドアウトの4種。互いに素な固定配牌の集合で、再現性を担保する。
  • 評価ラン: ある段・あるバンク・試合数N・成績差・判定(PROMOTE/TIE/REJECT)を逐語で保持する記録。
  • アリーナ候補: 実環境セルフプレイの暫定計測枠(既存のセルフプレイ・アリーナ機能を再利用)。デプロイ済みラダーには合算しない。

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: hero=field sanity(同一重みを hero と field に与え固定配牌で4席巡回)で hero 平均着順が厳密に 2.500000(決定論の不変条件が成立する)。
  • SC-002: 採用する重みについて、確認バンクでの成績差が事前登録した最小改善量を満たし(符号安定・反則悪化なし)、その判定が逐語の数値で文書化される。
  • SC-003: 確認から最終ホールドアウトへの成績低下が事前登録した許容幅以内(バンク特異の過学習がない)。
  • SC-004: 実環境アリーナにおける強基準との H2H(hero が各 baseline 席より上位だった比率)が、deal-block bootstrap の95%信頼区間下限を添えて規定の必要試合数で PROMOTE/TIE/REJECT として確定する。
  • SC-005: 特徴寄与分析が、学習・検証バンク間で符号一貫の特徴サブセットを識別する(Python 昇格の客観的根拠になる)。
  • SC-006: 明示的な昇格決定が下されるまで、デプロイ済みCPUの挙動が一切変化しない(稼働個体0体)。
  • SC-007: すべての評価判定が、後付けのしきい値変更なしに事前登録ルールへ追跡可能である(p-hack 不能性が監査できる)。

Assumptions

  • 評価基盤(固定配牌生成・hero-vs-field・共通スケール)は既存資産を再利用でき、新規の対戦基盤は不要。
  • 依存リスク: US3/Stage5 の実環境アリーナ候補枠は feature 001(specs/001-cpu-selfplay-arena/)に依存するが、001 は現状 draft・DB実機未検証・未マージ。候補枠が稼働済であることが US3 の前提。未稼働の場合は Stage5 を保留し、固定配牌(Stage0-4)+共通スケール(rate-cpu-ladder)のみで暫定判定する(PROMOTE は 001 候補枠の稼働を待つ)。
  • 配牌の再現性は乱数シードではなく配牌そのものの永続化で担保する(生成経路にシード注入口がないため、生成済みバンクJSONを版として固定する)。
  • 候補エンジンは完全決定論(モンテカルロ等の乱数を手選択に用いない)であり、これにより自己対戦の厳密2.5不変条件が成立する。
  • 優先50特徴の約7割は既存ヘルパから構築でき、完全新規は約5個に留まる(特徴抽出の実装コストは小さく、主たる難所は評価設計)。
  • v1 の特徴次元は 56固定(base 50+交互作用6)。交互作用を増やす場合はスキーマ変更=feature_schema_hash 変更で旧重みを拒否し、別版(storm schema v1.x/次世代 storm)として扱う。
  • 手選択順は hard guard(即時合法上がり/mate/反則回避)→ 合法手集合 → feature score → 決定論 tie-break(actionKey 昇順)。ルール判定は本番関数に委譲(再実装しない)。
  • 信頼区間は deal-block bootstrap(同一 deal の4席巡回を1ブロック)。ゲーム単位 CI は過信になるため使わない。
  • 重み探索の初期化は MC教師からの線形回帰蒸留、精錆は frozen-v2 相手 scoreDelta を直接最適化するブラックボックス探索(CMA-ES 等。random/Optuna も可)。判定指標は常に direct A/B。
  • 強さの最終的な合格基準は既存運用の最上位ティアを基準とする(場の平均ではなく現行最強に対して測る)。
  • Python フェーズに必要な外部参照実装やRL基盤は、本フィーチャーの完了要件ではない。