Misty は、観測したすべてと矛盾しない盤面の集合 P を保持することで霧のチェスを指す。P は FEN 文字列として保存されるが、FEN のフィールドのうち一つは局面ではなくルールによって書かれる。

   
症状 P が1手で625盤面から41盤面に減り、真の盤面はその41の中になかった
影響 Misty はその対局の残りを、どれも実在しない41の世界から探索のルートを引いて指した
原因 キングの安全に関するルールがない変則ルールで、アンパッサンのマスを標準チェスの合法性に従って記録していた
修正 疑似合法のルール(EnPassantMode.XFEN)で記録し、必ず一つの共有関数を通す
頻度 完了した273対局、12,866手のうち1手
検出 なし。4か月後、別の目的で信念集合の大きさを測っているときに見つかった

バグ

対局 e751c6c4、Misty が黒番、29手目。白が g ポーンを2マス進め、黒の f4 ポーンの隣に置く。d6 の白クイーンが黒キングを攻撃している。

. . . . . k . .
. . Q . . . . p
P . . Q p . . .
. . . . N . . .
. . . . . p P .
B . . . . . . .
. . . . P P . P
R N . . K B . R
After 29.g4, from Black's side. Tinted: White's pawn on g4, Black's pawn on f4, and the empty square g3 between them. The arrow is White's queen attacking the black king.

黒の f4 ポーンは g3 でアンパッサンできる。標準チェスではこれは禁止だ。黒はチェックされており、この捕獲はチェックを解消しないからである。霧のチェスにそのルールはない。自分のキングへの攻撃は見えないので、そのまま放置してよく、対局はメイトではなくキングが取られて終わる。ここでは f4xg3 はごく普通の手だ。

霧の中での視界とは、自分の駒が動けるマスの集合である。アンパッサンできるポーンは着地するマスが見え、取る相手のポーンも見える。したがってアンパッサンのマスは、合法性だけでなく知覚に関する事実でもある。

. . . . . k . .
. . . . . . . p
. . . . p . . .
. . . . . . . .
. . . . . p P .
. . . . . . . .
. . . . . . . .
. . . . . . . .
With the en passant square. 14 squares. Black sees g3, and sees the pawn on g4.
. . . . . k . .
. . . . . . . p
. . . . p . . .
. . . . . . . .
. . . . . p . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
Without it. 12 squares. g3 and g4 go dark. Same pieces, same board.

python-chess は既定で EnPassantMode.LEGAL を使ってこのフィールドを書き、標準ルールで捕獲が合法な場合にのみマスを記録する。そのため - と書いた。

stored  5k2/2Q4p/P2Qp3/4N3/5pP1/B7/4PP1P/RN2KB1R b KQ -  0 29
needed  5k2/2Q4p/P2Qp3/4N3/5pP1/B7/4PP1P/RN2KB1R b KQ g3 0 29

信念の更新は、これらの文字列から局面を再構築する。保存された文字列から再構築すると、真の局面は黒に14マスではなく12マスしか見せず、自分自身が生み出した観測と一致しなくなり、P から脱落した。

修正

ポーンが取れるなら、取るのが賢明かどうかにかかわらずマスを記録する。それが EnPassantMode.XFEN であり、霧のルールそのものである。隣に敵ポーンがいなければ引き続きマスを省くので、誰も使えないマスをめぐって信念が分裂することはない。

二つの書き手はいま、それぞれが独自の既定値を持つシリアライザを呼ぶのではなく、一つの名前付き関数を通る。メンバーシップ判定は盤面を受け取って正規化する。生の board.fen() を渡すことが、元の誤りの形そのものだったからだ。

問題の手で P はいま625から617になり、真の局面は保たれる。271対局すべてを再生しても、サンプリングされた対局の外で真の局面を失った判断は報告されない。以前は4件あった。

なぜ何も捕まえなかったのか

トリップワイヤーが逆の極端に仕掛けられていた。 列挙器は P が空になったときに例外を出すが、それには候補が一つも残らないほど小さな信念が必要だ。41の中から真の局面を失うのは無音であり、無音でしかありえない。対局中、エンジンは真の局面を決して知らないので、照合する相手がない。

差分テストは、両側が同じように間違っていたから通った。 Misty のホットパスは Rust 拡張で、python-chess をバイト単位で再現するよう書かれた関数を持っている。それは成功した。178,145回の着手適用にわたって両者を比較するテストは緑のままで、同じ誤りを犯した二つのエンコーダを比べていた。

再生なら見えたはずだが、見ていなかった。 分析パスは真の局面が P にあるかどうかを記録するが、その列はサンプリング上限を超えた対局でしか読まれておらず、そこでは真の局面を失うのは想定内だった。上限内の対局では、それに無害な説明はない。誰もそのクエリを実行していなかった。

一般化できること

標準ルールのライブラリのシリアライザは中立なエンコーダではない。 それはライブラリの判断をバイト列に書き込み、その判断とはルールである。その出力をキーとして使えば、読んだことのないルールブックを取り込んだことになる。

二つの実装が一致しても、わかるのは一致していることだけだ。 私の実装は何か月も一致していて、その一致がバグを隠した。Rust 移植がずれていないかと思うたびに、緑のテストが「ずれていない」と答えた。正しいが、問いはそれではなかった。

真の局面を失った信念は、曖昧になるのではなく鋭くなる。 41の世界は、マスを正しく記録したときに残る617の世界よりも自信のある信念だ。この失敗は、内側からは不確実さには見えない。

エンジンでは 2026-09-10 に修正済みで、次の Misty リリースに含まれる。上のすべての盤面はエンジン自身の視界コードがその局面から描いたもので、描画スクリプトは二つのビューの差がちょうど g3 と g4 でなくなると実行を拒否する。