コンピュータ将棋の開発で出会ったバグや失敗

4年前の
USI対応の思考エンジンを作ったときに遭遇したバグ - merom686's blog
4ヶ月前の
コンピュータ将棋の開発中に遭遇したバグ - merom686's blog
Killerは王手がかかっていないときだけ使っていたので、重複を避けるためCounter手からKiller手を除いていた。しかし、王手がかかっているときも除いていたため、合法なKiller手があるとその手を読まなくなっていた(致命的なバグ)。
NullMove探索で得た評価値をPVの探索に使ってはいけなかった。値は全く信頼できないので、ガンガンfail-lowとかする。
βカットした手以外をHistoryで減点するとき、手生成してないケースがあることを忘れていたため、未初期化のポインタでアクセスして落ちた。
実戦例が多いはずの局面で定跡が切れた。定跡ファイルの仕様が古いからだった。
速く読めるようになったと思ったら、Counter手を読まないケースがあるせいだった。オーダリングでCounter手に負のスコアが付く可能性を見落とした。
特定条件下のRootで王手をかけないようにしたつもりが、そうなっていなかった。よく考えたら、手を進めるときに王手かをチェックしていたので、判定に使う物理深さが既に変化していた。
SEEの計算で、玉の交換値が0であることを仮定していたが、いつの間にか0じゃなくなっていた。
連続してNullMoveしないためのフラグを持っていたが、直前の指し手で判定する方法と混在していたため、フラグをいじっても動作が変わらずはまった。
自前のassertと局面のvalidatorは簡単なものでいいから早めに書いたほうがいい。
多重反復深化には苦手意識があり、なかなか有効に使えなかった。せっかくのハッシュ手を使ってなかったり、ハッシュ手を得ずにreturnすることがあったり、テストした局面で多重反復深化が有効じゃなかったり。呪いがかかっていると、単純な理由に全く気づかなくなる。
excludedMoveが無く、指し手がNullMoveのとき、move == excludedMoveが成立してしまった(どちらも0)。
指し手の配列の近くでスタック破壊。挿入ソートの番兵に対応していないところがあった(仕様変更時の対応漏れ)。
エラーも吐かずに終了してしまい、デバッガにもかからず困っていた。continueと書くべきところをreturnと書いていた(酷いオチ)。
if (!checkCheck<TN>(move)) continue; と書いてあって疑問に思わなかったけど、moveではなくmlist[i].moveと書くべきだった(違う指し手を渡していた)(王手チェックのcheckCheckという関数名は悪ふざけが過ぎた)。
学習。2駒関係の要素に対称性で同値関係を入れて同値類をいっぺんに処理している。最終的に値を書き込むとき、先後逆の要素で符号反転させるのを忘れた。忘れたというか何も考えてなかった。このせいで変な棋風になっていた。
玉を動かす手でSEEが負になることはない(玉が取り返されたら違法手)ことに気づいてなかった。
スレッド毎に持つデータは何かというのを開発初期段階から意識しておくべきだった。
高速1手詰めが、詰まないのに詰むと返すことがある。利きを玉の周囲9升しか正しく取得していないので、王手した桂馬を取られる手を読み抜けする場合があった。桂馬はイレギュラーな駒なのでうっかりしやすい。
シグモイド関数を使い始めたときは、シグモイド関数の値と評価ベクトルの勾配と実際の変動幅をよく混同して時間を消費した。
▲88金と△22金の関係に値が付いていた。手番を使っていないなら、これはゼロにきまっている。こんなことにも気づかない程度の思考で2駒相対をやっていた。
自作のsign関数が、0のとき1を返す仕様だったのを忘れていた(せめて0を検出できるassertを書こう)。
出現しなかった特徴にペナルティをかけてなかった(以前は別の方法できつく減らしていたので)。
search関数を同じ物理深さで呼び出すことがけっこうあって、そのときに祖先ノードの印を消すのを忘れていた。これで置換表絡みっぽかった弱さはかなりマシになった。
unsigned longな変数に対して0以上かを確認していた(符号無しなので常に0以上)。
指し手を進める前に枝刈りした手をカウントしておらず、one replyと誤認することがあった。これはでかいバグ。
info stringを忘れて「表示されない」と悩んでいた(疲れているとこういうところで時間を遣う)。
0よりはマシだろうと安易に考え、手番の価値を1とした。枝刈りの挙動を考えていなかったので弱くなっていた。
現在の物理深さが手を戻したときに戻ってない。元の物理深さを記録しておくとき、自分自身の参照へコピー(意味のない行為)していた。データ構造を一括置換などで変えていたので、こういうことが起こる。
深く読みすぎて落ちるようになった。静止探索で置換表を使うようになって、残り深さに関わらずハッシュ手のあるかぎりハッシュ手だけを読み進めていた(千日手にはまったら大変)。
captureOrPromotionを逆の意味で使ってる箇所があった。以前は確かis_quietという変数を使っていて、手動で置換したのだと思う。
follpwup moveが全く働かないと思ったら、既存のローカル変数と同じ名前でifスコープ内で宣言していた。
探索の並列化を始めて、開発中は当然落ちまくるんだけど、デバッグモードで実行するという発想がしばらく出なかった。思い詰めていると、当たり前のことができない。
スレッドを停止するフラグを立てても終了しないスレッドがある。と思ったら単にPonderを始めているだけだった。終了はしてたけど、別のスレッドが開始されていたのだ。並列化という難しい問題を相手にしていても、簡単な問題は普段と同じように襲ってくる。
デバッグ中、ある形勢に差が付いた局面で返ってくる評価値がαで謎だった(負け確の評価値なはずだと思った)。NullMoveで、「パスしてさえ勝ち確」の局面でβを返すからだった(将棋だと大抵は大丈夫そうに思うけど一応βを返してる)。