Mistboardの闘獣棋エンジンには巣レースという概念がない。局面の評価は駒の価値に、相手の巣へ前進した駒ごとのボーナスを加えたもので、自分の巣へ向かって走る駒が先に着くかどうかを問うことはない。それが分かるのは探索が巣に到達したとき、およそ12手先である。

これは明らかな欠落に見え、ボットが鼠を行き来させている間に相手の鼠が入ってきた対局のあと、課題として登録した。1日かけてこれを埋めようとした。3つの経路、いずれも効果なしか悪化。以下は計測が示したことと、そこから残すべきものである。

エンジンがすでにできていること

切り分けておく価値がある。これがその後のすべての枠組みを決めたからだ。エンジンは勝利条件を完全に理解している。巣への到達は詰みとして評価され、候補手の先頭に並べられ、静止探索でも探索される。欠けているのは先読みであって、理解ではない。

それでも勝てるのは、闘獣棋が駒の損得に支配されるゲームで、評価が駒の損得を正確に読むからだ。巣に入る瞬間、勝者は中央値で20点リードしており、猫1匹ぶんほどである。劣勢だった側が勝つ対局は4.7%しかない。

この概念なしで指す手

この調査の第10局は4手しかなく、以下のどの表よりもうまく問題を片づけている。

闘獣棋の中央は3段ぶんの水域だ。それを渡れる陸の橋はdファイルだけである。虎は水を縦には飛び越えるが横には飛べないので、この通路に立った虎はほとんど行き場がない。

闘獣棋の局面:dファイルの陸橋に赤の虎が単独、両側は水域、その真上に青の象 20手目、青の手番。赤いマスは直前の手:赤の虎が d6の狼を取り、いま通路に立っている。青の象はその1マス上に あり、格上である。20手目、赤のd5の虎には 合法手が2つあった。前か後ろ、どちらも通路の中だ。虎は前に出て狼を 取り、青に40点の損を与え、虎を青の象の隣に置いた。

青の象は虎より格上なので、これは贈り物に見える。青はすぐには取らなかった。これが説明を求められた手である。エンジンの理由は、虎が逃げられなかったことだ。象がd7にいると虎の唯一の手はd5、d5からの唯一の手はd4で、自分を破れる駒に一車線の通路を追い下される。取りに行くのは、すでに手に入れたものを回収するために一手損することになる。青は無料で局面を良くする手を指し、2手後にd6ではなくd5で虎を取った。どちらの手順も同じ駒の損得に行き着く。

対局を決めた4手:

41  Blue  elephant d7 -> d6    steps into the corridor after the tiger
42  Red   lion     c3 -> c7    jumps into the square the elephant just left
43  Blue  elephant d6 -> d5    takes the tiger, up a piece
    Red's lion is now three moves from a den Blue cannot defend

虎は餌だった。犠牲ではない。どうせほぼ捕らえられていたし、狼を取る手は+10、ほぼすべての代替手は+6で、その差は4点にすぎず作戦とは言えない。ただ盤から消える途中で、獅子より格上の唯一の駒をdファイルへ引き下ろす。20手目では獅子の飛び越えは-195、c7がd7の青の象の隣だからだ。2手後には同じ飛び越えが勝ちになる。象がいなくなったからである。

青はこの手順の終わりに13点リードし、そして負ける。

作戦を読み返すと、エンジンが巣レースを理解していたように聞こえる。だが理解していない。この評価のどこにも巣レースの項はない。このプロジェクトの前も後もだ。500万ノードを探索し、手順が出てきた。これが結論のすべてである。課題が入れたかった振る舞いはすでにそこにあり、知識ではなく深さが生んだものだった。

基礎出現率:全局の4.7%、しかも実運用の強さで測ること

1手500万ノードという本番設定での自己対局172局。

終局形 割合
千日手による引き分け 52.9%
巣への進入 26.2%
進展なしによる引き分け 20.9%
真のレース(10手前の時点で勝者が互角または劣勢) 4.7%

同じ計測環境を20万ノードで動かすとレースは22%と出る。弱い指し手が数字をおよそ5倍に膨らませる。安い計測は使えたのだが、それは作業を増やす方向に5倍ずれていただろう。

伸びしろ:12倍深い探索で得られるのは2手

各レースについて、敗者側の対局を二分探索して、それぞれの探索予算が強制負けを認識する最も早い手を求め、その差を取った。

レース数
0手 4
2手 3
4手 1

6000万ノード対500万ノードで、深い探索が負けを見抜くのは中央値で2手早いだけだ。その地平より前では両者はほぼ一致し、最後の1、2手でようやく分かれる。

これがプロジェクトを終わらせるべき数字だった。探索を12倍にして2手しか回収できないなら、賢い評価が掘り出すのを待っている情報などそこにはない。

経路A:探索延長、無料版と有料版

実装せずに探った。計測環境の方針として、いずれかの駒が相手の巣からR マス以内にいるとき片側に大きなノード予算を与える。これは現実のどんな延長に対しても厳格な上限になる。

Elo以前の最初の発見:この発動条件は疎ではない。

半径 発動する手の割合
2 23%
3 43%
4 72%

闘獣棋の盤は小さく、巣は最奥段にある。レースを早く見るために必要な半径 R=4 では、延長は単に「どこでも12倍探索する」ことになり、外科的だという枠組みは最初から間違っていた。

R=2、各条件ごとに先後入れ替え300ペア:

条件 Elo 対照区比のノード数
巣付近で12倍の深さ、無料で付与 +45 3.3倍
予算中立、基本3.7M+巣付近10M -15 1.04倍
予算中立、基本1.1M+巣付近20M -12 0.98倍

同じ発想、同じコード、符号は逆。エンジンの500万ノードはプレイヤーが待てる時間で固定されているので、現実の延長は深さを生み出せない。探索の他の部分から奪うのだ。無料の条件が測っていたのは追加の計算資源である。巣とは無関係だ。

予算中立の条件を走らせたのは、+45が良すぎたからだ。そこで止めていたら、わずかにゼロ未満の価値しかない機能に1週間を費やしていた。

どちらの条件も半日で済んだ。エンジンに手を入れる必要がなかったからだ。延長は盤面条件でノード予算を変える計測環境の方針にすぎない。「Xについてもっと賢くする」という形の提案には、まずXを無料で完璧に与えてみるのが、Xがそもそも重要かを知る最も安い方法である。

経路B:評価項そのもの

それでも作った。延長が失敗した理由は評価項には当てはまらないからだ。延長はノードを食うが、評価項は葉1つあたりほとんど無料である。

当時の評価はこうだった。盤を1回ループし、各駒が独立に寄与する。

for idx in 0..N {
    let v = VAL[role_of(code) as usize];
    let friendly = color_of(code) == me;
    s += if friendly { 2 * v } else { -2 * v };

    // Advancement toward the relevant den: the enemy's for our pieces, ours for theirs.
    let target = if friendly { enemy_den } else { own_den };
    let dist = manhattan(idx as u8, target);
    let advance = if dist <= 1 { 400 } else { (16 - dist) * 3 };
    s += if friendly { advance } else { -advance };
}

この前進項こそ、評価がレースを見られない理由であり、その理由は対称性にある。自分の走者が相手の巣から3マスなら+39。相手の走者が自分の巣から3マスなら-39。両者が走っていれば数字はゼロ近くになり、最も決着に近い瞬間にちょうど局面は静かだと読まれる。

だからこの項はもう一つの駒別ボーナスにはできない。片側につき1つの判定として、一度だけ計算し、2つの走者を互いに比較しなければならない。

for side in [me, 1 - me] {
    // Nearest enemy runner to this side's den, and this side's nearest defender
    // to its own trap ring.
    let (mut att, mut def) = (99, 99);
    for idx in 0..N { /* ...scan once... */ }

    if att > RACE_RADIUS { continue; }   // no race here: contribute nothing

    // The side to move gets one free step in the race for its own den.
    let margin = att - def + i32::from(side == me);
    let pen = if margin <= 0 { RACE_LOST }
              else if margin == 1 { RACE_TIGHT }
              else { 0 };
    score += if side == me { -pen } else { pen };
}

守る側は巣までではなく罠の輪までで測る。巣の3つの隣接マスがそのまま3つの罠であり、罠に立った駒は格に関係なくどの守り駒にも取られるため、それが正しい迎撃区域になる。自分の巣に踏み込む象は、自分の鼠が取れるマスを越えなければならない。

これは設計対象の局面の17%で選択手を変え、同一ノード数で実時間を約17%余計に使う。重みごとに先後入れ替え300ペア、そのコストを課す前の結果:

重み Elo
10 -3
25 +9

効果なし、しかも甘く見た方向で。

残すべきこと

予算は一定に保つこと。そうしないと測っているのは予算だ。実験の無料版は作るのが楽で、自信のある正の数字を渡してくる。その変更が何かから奪わなければならないなら、実際に奪って、結果が生き残るかを見ること。

この罠には同じ日に2回引っかかった。同じ午後に走らせた引き分け回避値のスイープでは決着局が32%から50%へ上がったが、これは自己対局のスイープでは両者に同じ設定が入るので、引き分け回避値を上げれば構造上そうなる。代償があったかを言えるのは、2つの値を互いに対戦させた場合だけだ。代償はなかったので、こちらは採用した。

評価の欠落は実在し、診断も正しかった。ただ、その裏には何もなかった。同じ棋譜群で見つかった2つの定数、対局を早く終わらせていた無取り引き分けカウンタと、ゼロ近くだった引き分け回避値は、闘獣棋の決着率を4分の1から約半分へ引き上げたが、どちらもエンジンに何かを教えるものではない。課題が求めたのは理解だった。理解はもともと制約ではなかった。

棋譜は公開しており、上の局面も公開している。解析盤は同じエンジンで動くので、青が虎を取るべきだったと思うなら、その手順を実際に指させることができる。