第1話. TANREN — Atari Pong 21–0 と、進化のループが自力で見つけたキルショット

本記事は TANREN 連載の第1話です。プロジェクト全体像(装置とは何か、戦果の一覧)は こちら。この記事では最初の戦役 Atari Pong を深掘りします。

TANREN を 30 秒で

TANREN(鍛錬) は、安価で凍結された LLM に解き方のコードを数千本書かせ、決定論の検証器で一つずつ採点し、勝った一本を次の親にする——これを繰り返して、その現場専用のアルゴリズムを鍛造する装置です。モデルの重みは訓練しません。賢くなるのは読める Python 関数の方です。適用できるのは採点基準を決定論のプログラムとして書ける課題に限られます。Pong はまさにその一例で、スコアは点差であり、曖昧さがありません。

設定: 128 バイト、ラベルなし

的は Arcade Learning Environment(ALE)を通した Atari Pong です。方策はピクセルを見ません。入力はゲーム機の生 RAM 128 バイトだけ——しかも、どのバイトがボールで、どれがパドルで、各値が何を意味するかは一切教えられません。

つまり鍛造装置は、採点信号だけを頼りに、2 つの問題を同時に解く必要があります。

  1. ゲームをリバースエンジニアリングする — バイトの意味を突き止める。
  2. 完璧にプレイする — その理解を勝てる方策に変える。

遺伝子は 1 つの Python 関数 act(ram, prev, t, mem) -> action です。検証器はゲームをシード単位で決定論的に実行し、点差をシード平均で採点します。train シードと holdout シードは分離しているので、1 つの軌道を丸暗記しただけの方策は通過できません。

進化が自力で発見したこと

最終的な王者を解析すると、進化したコードは——教師なしで——4 つの RAM バイトの意味を特定していました。

RAM バイト意味(教師なしで特定)
ram[49]ボール x
ram[50]ボール y
ram[53]前フレームのボール x
ram[51]自パドル y

ram[49]ram[53](現在と 1 フレーム前のボール x)から、方策は移動平均でボール速度を推定し、ボールが来る場所を予測してそこで待ち構えます——追いかけるのではなく。さらに、自分のサーブのタイミングまで操作することを覚えていました。

flowchart TB
    A["128 バイトの RAM<br/>(ラベルなし)"] --> B["ボール x/y と自パドルを特定<br/>ram49 ram50 ram51 ram53"]
    B --> C["ボール速度を推定<br/>(x の移動平均)"]
    C --> D["到達点を予測して<br/>そこで待つ"]
    D --> E["サーブのタイミングを操作"]
    E --> F["キルショット:<br/>全ポイントを一撃で終える"]

これらの段階は一つも指定していません。それぞれが「前より高く採点された」から現れたものです。

キルショット

ここがスコアよりも興味深い部分です。完全体を分解すると、際立った規則性が見えます。全ポイントが同じ 94 ステップのリズムで進み、方策は 1 ポイントあたりちょうど 1.0 回だけ自分の返球をしているのです。

つまり、進化したコードは「上手にラリーして」勝っているのではありません。内蔵の相手 AI が物理的に追えないサーブ角度を見つけ、全ポイントを一撃で終わらせています。これは DeepMind の Breakout「トンネル掘り」と同種の結果です。検証器だけを頼りに、誰も説明していない環境の穴を掘り当てました。

結果を正直に

Atari Pong — 21 点満点中のスコア(高いほど良い)
人間テスター
9.3
DQN(2015)
18.9
MuZero(2020)
21.0
TANREN(97行 Python)
21.0
ランダムプレイは −20.7。スコアは 40 試合すべてで 21–0 でした。train 開始 10 局面と、一度も見ていない holdout 開始 30 局面です。

21 は飽和した物差し——完全試合です。だから誠実な主張は「TANREN が MuZero を上回った」ではありません。TANREN は上限に同点で並び、この課題は、それに先に届いた物量を必要としなかった、ということです。同じ内蔵の相手に人間テスターは 9.3、2015 年の DQN は 2 億フレームの学習を経て 18.9、MuZero は大規模計算で上限に到達しました。進化した方策は、読めるコードと数ドルの API 代でそこに届いています。

40 試合の holdout が効いています。そのうち 30 試合は進化中に一度も見ていない開始局面ですが、それでも 21–0 でした。これが「軌道を丸暗記したのではなく戦略を見つけた」証拠です。

なぜ数ドルで足りたのか — 診断ループ

素朴に回すと、スコアは 16 あたりで止まります。そこを抜けたのは、より多く費やしたからではなく、診断ループによってでした。

flowchart LR
    M["計測<br/>失点した瞬間の<br/>フレームを採取"] --> I["解釈は一度だけ<br/>(賢いモデルが<br/>計測事実のみ言語化)"]
    I --> J["事実を注入<br/>変異プロンプトへ<br/>(処方も推測も書かない)"]
    J --> P["その事実を使う<br/>変奏を量産"]
    P --> S["検証器が<br/>噛み合う一本を選抜"]

ここで重要な規律は、賢いモデルには計測された事実を述べることだけを許す点です——「自陣は高 x 側にある」等——処方(直し方)は決して書かせません。あとは安価な LLM がその事実を使った変奏を大量に書き、検証器が勝者を拾います。

この分業の価値を具体的に示す結果がありました。同じ計測事実から、人間(私)が手で書いた「原理的に正しい」修正は −15 に崩壊し、一方で進化は 21.0 に到達したのです。知識が探索の円錐を絞り、物量が円錐の中を埋め、検証器が正解を選ぶ。どれか 1 つ欠けても崩れます。

正直な限界

結果を過大に読まれないよう、明記します。

  • 入力は RAM でありピクセルではありません。そしてゲームごとに専用の方策を鍛えます。
  • 相手は標準の内蔵 AI です(人間プロが 9.3 の相手)。ラリーが短いのは全返球がウィナーだからで、その計測値(1 ポイントあたり 1.0 返球)は隠さず出しています。
  • 21 は飽和した天井です。「同点」であって「上回った」ではありません。
  • モデルの重みは不変です——品質リスクはなく、方策は 1 行で元に戻せます。

コードの公開

この戦役のチャンピオンコード——RAM 128 バイトから 21–0 を出す、進化した act() 関数(空行を除いて 97 行)——と ALE 検証器・実行手順は、GitHub で公開しています: matu79go/tanren-championspip install ale-py だけで、train 10 局面と未見の holdout 30 局面すべての 21–0 を run_pong.py で手元から再現できます。

コスト

Pong の全戦役で API 代はおよそ $5——1 run あたり $0.5〜0.8、変異器は gemini-2.5-flash-lite、採点は純 Python の CPU 処理で GPU 学習はありません。同点に届いた相手は 2 億フレーム(DQN’15)や大規模分散計算(MuZero)で学習されたものです。価値が無数の反射ではなく構造の発見に宿る課題では、このコスト差はまるごと不要でした。


連載の次回: 実トレースで 20 年もの古典を上回ったキャッシュ追い出し方策。プロジェクト全体像に戻る。

この記事をシェア

関連記事