探索部不具合の処理台帳
更新日: 2026-10-29

最新の全280件の移植可否レビュー: tools/memo/stockfish-port-review-20261029.txt
一覧: tools/memo/search-audit-20261029-stockfish.tsv（一次分類を全件個別判定へ更新）。
SA-17/18を追加。Linux共有メモリとspeedtestの前回記述を第3巡で訂正した。

この文書は調査結果の記録。探索部・その他の本体実装は修正していない。
文書のみをcommitした。初回調査の記録は当時の状態を記し、追加調査は末尾に追記する。
チェック欄は「対策の実装と検証が完了したとき」にだけ埋める。
再現確認済み・静的確認・未再現候補・テスト側の問題を区別して扱う。

推奨する処理順 / 固定ID
[ ] SA-01 1手詰めのTTスコア正規化                  再現済み / 本体 / 優先
[ ] SA-02 不成を指定したsearchmovesの制限違反        通常USIで再現済み / 本体 / 優先
[ ] SA-03 ponder抽出のMove::none処理                 assert有効の通常USIで再現済み / 本体
[ ] SA-04 MultiPVのbound表示                         関数単位で再現済み / 出力
[ ] SA-05 root moveのeffortの1ノード不足              静的確認 / 時間配分
[ ] SA-06 null moveのcapturedPiece初期化              状態不整合を再現済み / 探索実害未確認
[ ] SA-07 pthread_create失敗処理                     失敗注入で確認 / pthread版のみ
[ ] SA-08 深い階層のLMR延長の連鎖                    Stockfish修正未反映 / 将棋では未再現
[ ] SA-09 Position::UnitTestのスタック使用量          再現済み / テスト側
[ ] SA-10 部分キーUnitTestのStateInfo添字超過         ASanで再現済み / テスト側
[ ] SA-11 共有TTEntryの非atomicアクセス              静的確認 / C++データ競合 / 本体
[ ] SA-12 CPU番号欠番時のL3/NUMA構成欠落             模擬sysfsで再現済み / Linux
[ ] SA-13 大容量メモリ確保失敗時のNULL書込み          失敗注入+UBSanで再現済み / 基盤
[ ] SA-14 hash_bytes末尾の符号拡張                   実関数で衝突再現済み / TTキーとは別
[ ] SA-15 LEB128の左シフトUB・assert依存の入力検査    関数単位で再現 / 評価ファイル読込
[ ] SA-16 cctype関数への負のchar引数                 静的確認 / 高bit文字列の入力条件
[ ] SA-17 spinの不正・過大入力で未処理例外終了       通常releaseで再現済み / 入力処理
[ ] SA-18 speedtestの秒→ms変換でint overflow         実関数+UBSanで再現済み / 計測基盤
[ ] PC-01 malloc arenaのNUMA間再利用                 性能候補 / 複数物理NUMAで未計測

SA-01〜SA-05を先に処理し、その後SA-11〜SA-13を並行せず順番に検討する。
SA-06〜SA-10と周辺処理SA-14〜SA-16は対象環境・運用に応じて順番を決める。
SA-08は移植前に将棋での再現・棋力検証を行う。PC-01は不具合確定項目に数えない。
この順序は作業提案であり、自動的に修正へ着手する許可を意味しない。
Stockfishの修正は、同じ原因・同じ実行経路があることを確認してから適用する。

台帳IDと下の詳細項目の対応:
SA-01=1、SA-02=2、SA-03=3、SA-04=4、SA-05=5、SA-08=6、
SA-06=7、SA-07=8、SA-09=9a、SA-10=9b。

再現条件に関する注意
・SA-01は保存/再取得する実関数のテストで再現したもの。通常USIの最終mate表示が
  必ず誤ると確認したわけではない。
・SA-03のクラッシュはASSERT_LV=3のビルド。assert無効の通常ビルドでも
  同じ入力で必ずクラッシュすると確認したわけではない。
・標準unittestはSA-09/SA-10で止まるため、全テスト合格とはしていない。
・証拠ログはbuild/search-audit-20261029/に保存している。このディレクトリは
  Git管理対象外であり、別のcheckoutには自動では引き継がれない。

以下は最初の調査の詳細記録。

やねうら王 探索部・Stockfish直近半年修正の監査
調査日: 2026-10-29

対象と制約
・YaneuraOu HEAD: 0f421391。元の追跡ファイルは一切変更せず、commitも行っていない。
・Stockfish HEAD/FETCH_HEAD: ff7cee4244ec96ea488c402669e3674c0574dce6。
  origin/masterをfetchし、ローカルHEADと一致することを確認。
・比較期間: 2026-04-29〜2026-10-29。全280コミットから探索・TT・時間管理・
  Position・スレッド/NUMAに関する127コミットを抽出。不具合修正を中心に22件の
  変更内容を保存して照合した。全コミットの全変更を形式検証したものではない。
・評価関数: /workspace/YaneuraOuWork/inbox/evalTuyo12-20261025
  SFNN_halfka2_2304_15_64_progress8 / FV_SCALE=24。
・git archiveによる隔離コピーをcheckout/に作成してビルド。
  AVX512VNNI / clang++ / ASan + UBSan / ASSERT_LV=3 / DEBUG=ON。
  USE_LAZY_EVALUATE・USE_FINNY_TABLES・USE_FINNY_RESETを有効化。
  元ソースとの一致・モデルSHA256・ビルドコマンドはmetadata.jsonに記録。
・外部C++プローブは隔離コピーから作ったオブジェクトをリンクしている。
  private関数・状態を調べるためプローブ側に-fno-access-controlを用いたが、
  探索・Position・TT等の実装は変更していない。直接関数テストと通常USI再現を区別する。

1. 優先度高: 1手詰めのTT保存時に詰み距離を正規化していない【再現済み】
場所:
  source/engine/yaneuraou-engine/yaneuraou-search.cpp:2918-2924 (通常探索)
  source/engine/yaneuraou-engine/yaneuraou-search.cpp:4769-4778 (静止探索)
  同:5217 value_to_tt、同:5233以降 value_from_tt

両経路で bestValue=mate_in(ss->ply+1) をそのままTTへ保存する。
一方、TTから読む際はvalue_from_ttで現在のplyを引くため、同じ局面・同じplyへの
再訪問でも詰みまでの距離が変わる。通常探索内の「変換してはならない」コメントも
TTの実際の保存規約と一致しない。

実際の関数呼び出しで確認した値:
  局面: 7nk/8l/7R1/9/9/9/9/9/K8 b R2B4G4S3N3L18P 1
  mate_1plyの返す手: B*2b
  ss->ply=4、1手詰めなので返値はmate_in(5)=31995。
  保存されたTT値=31995、正しくは局面からの距離でmate_in(1)=31999。
  2回目の呼び出しの返値=31991、正しくは1回目と同じ31995。
  通常探索・静止探索の両方で同じ誤差を再現した。

影響: 詰み距離、詰みスコアの大小比較、詰み距離枝刈りで誤った情報を使いうる。
今回、特定の実戦局面での最善手変更や棋力差までは検証していない。
修正方向: 保存値をvalue_to_tt(bestValue,ss->ply)で正規化する等。
証拠: mate_tt_probe.cpp / mate-tt-probe.log。通常USIだけの再現ではなく関数単位の再現。
Stockfish半年の同名修正ではなく、将棋独自の1手詰め経路の不具合。
補足: 全ての手が次の1手詰めを許す局面を通常USIで4回繰り返したテストでは、
最終表示は毎回score mate -2で正しかった（mate-usi-probe.json）。
したがって「通常USIの最終表示が必ず誤る」ではなく、保存値と再取得値の不整合を
関数単位で再現した指摘である。

2. searchmovesで指定した合法な不成が無視される【通常ビルドで再現済み】
場所: source/thread.cpp:439-466

GenerateAllLegalMoves=falseでは、指定手を選択的なMoveList<LEGAL>と照合する。
合法でも通常の生成で省略される不成が全て除外されると、rootMoves.empty()を
「制限なし」とみなすフォールバックで全候補手が再登録される。

再現（指定モデル・FV_SCALE=24・BookFile=no_book）:
  setoption name GenerateAllLegalMoves value false
  position startpos moves 7g7f 3c3d
  go depth 3 searchmoves 8h2b
  → bestmove 2g2f （指定集合外）
同じ入力でGenerateAllLegalMoves=true:
  → bestmove 8h2b （指定どおり）

cshogiでも8h2bが合法であることを確認した。
修正方向: 明示的なsearchmovesは全合法手として検証し、rootのMovePicker側にも
その候補が生成されるよう反映する。setup_rootMovesのフィルタだけ変えても十分とは限らない。
当面の回避策はGenerateAllLegalMoves=true。
証拠: searchmoves-probe.json / searchmoves-release-probe.log / searchmoves_probe.py。
将棋の選択的な不成省略に起因する独自問題。

3. 浅い探索終了時にponder抽出がMove::noneを擬似合法性判定へ渡す【通常USIで再現済み】
場所:
  source/engine/yaneuraou-engine/yaneuraou-search.cpp:5894-5901
  source/position.cpp:1386以降、特に1442
  source/types.h:765

置換表にhitしても、手がMove::noneの静的評価・静止探索エントリーがありうる。
extract_ponder_from_ttはm.is_ok()を確認せずpseudo_legal_s<true>(m)を呼ぶ。
pseudo_legal_s側も入口で特殊手を弾かず、from_sq()のASSERT_LV3で停止する。
pseudo_legalのコメントにある「特殊手ならfalseを返す」契約も満たしていない。

再現:
  新規起動、指定モデル・Hash64・定跡無効、isready後に
  position startpos
  go depth 1
  → ASSERT(is_ok()) / Move::from_sq → pseudo_legal_s → extract_ponder_from_tt。
  bench 64 1 1 default depth でも同じ停止を再現した。

これはASSERT_LV=3ビルドでの停止を確認したもので、既定のassert無効ビルドが
この入力でクラッシュするとは主張しない。
修正方向: ponder抽出で特殊手を弾く。pseudo_legal自体の特殊手に対する契約も確認する。
証拠: fresh-startpos-depth1.log / fresh-depth1.log / fresh_depth1_probe.py。

4. MultiPVの境界付きスコアが確定値として表示される【関数プローブで再現済み】
場所:
  source/engine/yaneuraou-engine/yaneuraou-search.cpp:5747-5751、5850
原因: isExact = i != pvIdx || !updated として、探索対象のPV以外のboundフラグを落とす。

状態を構成して実際のSearchManager::pvを呼んだ結果:
  MultiPV=2、pvIdx=1、rootMoves[0].scoreLowerbound=true
  → multipv=1のInfoFull.boundが空文字列。
  rootMoves[1].scoreUpperbound=true
  → multipv=2のInfoFull.boundはupperbound。
したがって前者だけが不正に確定値扱いになる。

影響: 解析・GUI・教師データ収集が、上界/下界を確定値と誤認しうる。
修正方向: 「更新済みか」と実際のboundフラグで表示を決める。
関連する中断時の詰みスコア/PVロールバックもまとめて確認する必要がある。
証拠: pv_boundary_probe.cpp / pv-boundary-probe.logのPROBE行。
同プローブ末尾には通常のdepth1探索もあり、項目3のassertも起きる。
対応するStockfish修正:
  3e3e976fdf54adf6fce636ff21f7c0f4ce66a70a (2026-05-04)
  https://github.com/official-stockfish/Stockfish/commit/3e3e976fdf54adf6fce636ff21f7c0f4ce66a70a
  [bugfix] Respect bound flags for searched moves in multiPV analysis

5. root moveのeffortで最初の1ノードを数え落とす【静的確認】
場所: source/engine/yaneuraou-engine/yaneuraou-search.cpp:3869-3874、4103、1970
Worker::do_moveはnodesを1増やすが、その後にnodeCountを保存している。
rm.effort += nodes-nodeCount はrootのdo_moveを1回分ずつ除外する。
通常のnodes統計自体は正しい。誤るのは各root moveのeffortと、それを使う時間配分。
修正方向: nodeCountの採取をdo_moveの前へ移す。
対応するStockfish修正:
  944bee71177601649150f44f4d2eee91418594ff (2026-05-10)
  https://github.com/official-stockfish/Stockfish/commit/944bee71177601649150f44f4d2eee91418594ff
  Fix calculation of searched nodes (effort)

6. LMRの負の削減が深い階層まで延長として連鎖する対策が未反映【要追加再現】
場所: source/engine/yaneuraou-engine/yaneuraou-search.cpp:3978
現状:
  max(1, min(newDepth-r/1024,newDepth+2)) + PvNode
StockfishはrootDepthの2倍以上深い階層では追加延長を抑制する修正を入れている。
やねうら王にはその条件がない。MAX_PLYの停止はあるため、無限再帰だとは主張しない。
探索量の膨張・応答遅延の候補だが、今回の将棋テストで同じ探索爆発は再現できていない。
将棋固有の延長調整もあるため、修正の移植前には局面・探索量・棋力の検証が必要。
対応するStockfish修正:
  1548538edecc4bd8aae119050c5f853935d25173 (2026-09-18)
  https://github.com/official-stockfish/Stockfish/commit/1548538edecc4bd8aae119050c5f853935d25173
  Fix deep recursion.

7. null moveでcapturedPieceが残る【状態の不整合を再現、現行探索への実害未確認】
場所: source/position.cpp:2426以降
StateInfo全体をコピーした後、capturedPieceをNO_PIECEへ戻していない。
平手から7g7f 3c3d 8h2b+と進めて、王手されていないことを確認してnull moveすると、
captured_piece()は21（直前に取った後手の角）のまま。期待は0（NO_PIECE）。
ただし現行のpriorCapture利用箇所はprevSq/currentMoveでguardされ、Stockfishで
実害が出たstalemate判定は将棋側では無効。これによる棋力低下や誤った手は未確認。
証拠: pv-boundary-probe.logのNULL_PROBE行。
対応するStockfish修正:
  782852b26a5bd22eb0ea6a834cbeb48d4356a07b (2026-06-25)
  https://github.com/official-stockfish/Stockfish/commit/782852b26a5bd22eb0ea6a834cbeb48d4356a07b
  Clear capturedPiece in do_null_move

8. pthread版NativeThreadが生成失敗を無視する【失敗注入で確認、プラットフォーム限定】
場所: source/thread_win32_osx.h:49、source/thread.cpp:31-66
対象: __APPLE__ / __MINGW32__ / __MINGW64__ / USE_PTHREADS。
pthread_createの戻り値を確認しない。ヘッダーをそのまま使う小さなテストでEAGAINを
返すようにすると、workerを作れないままコンストラクターが正常終了することを確認した。
Thread側はworkerの起動完了を待つため、この失敗は待機から復帰できない原因になる。
今回のLinux既定ビルドはstd::threadを使うので、同じ経路は通らない。
Mac/MinGW実機でのエンジン全体のハング再現は未実施。
証拠: native_thread_failure_probe.cpp / native-thread-failure-probe.log。
対応するStockfish修正:
  3512dea68483f9daf3b014361c7ea36a44adb338 (2026-08-24)
  https://github.com/official-stockfish/Stockfish/commit/3512dea68483f9daf3b014361c7ea36a44adb338
  Native thread fixes

9. 標準unittestに別途2つの問題【再現済み、探索本体とは区別】
(9a) FT幅2304のビルドでPosition::UnitTestのスタックが大きすぎる。
  場所: source/position.cpp:3342、3468、3690、3740、3809、3960
  StateInfo s[512]等が複数存在する。StateInfoはこの構成で9856 byte。
  ASan版の関数入口はrspを17,638,528 byte（約16.82 MiB）減らしている。
  通常スタックではASan版・通常版ともPositionテスト開始前にSIGSEGV。
  子プロセスだけスタックを64 MiBにすると先へ進めた。
(9b) 部分キーのテストがStateInfo配列の末尾を1要素超える。
  場所: source/position.cpp:3809-3820
  StateInfo si[MAX_PLY]に対し、j=1; j<MAX_PLY; ++j のループでsi[j+1]へ書く。
  MAX_PLY=246では最後にsi[246]を渡す。ASanがWRITE of size 80の
  stack-buffer-overflowを報告し、変数si1847の終端と完全に一致した。
標準unittest全体が合格したとは言えない。2件とも元ソースを直さず記録している。
証拠: unit-tests.log / release-unit-normal-stack.log / asan-unit-64mb-stack.log。

未反映に見えても、そのまま移植すべきでないStockfish修正
・47be34c5の継続履歴オーバーフロー: 原因となるbonus*weight*multiplierの3因子乗算を
  やねうら王は使っていない。同じ修正は不要。
・db11ca42 / c67c1ec6のTT深さ減算・競合対策: 問題のpenalize/aging処理自体が
  やねうら王のTTにない。同じunderflowを未反映バグと数えていない。
・92c90f41 / ceb059eb / 6e4c77f5 / 9287f165 / f5940894のnodestime関連:
  やねうら王では該当オプションと時間管理経路がSTOCKFISHブロックで無効。
  有効な将棋用TimeManagementは別実装なので同一原因の不具合とは判断しない。
・ee515ad9等のSyzygyによるMAX_PLY超過PV、9700f6f2のLimitStrength+Syzygy:
  将棋にSyzygy/Skill機能がないので同じ再現条件はない。
・seekMateスコア範囲の修正: 元になるseekMate機能がやねうら王にない。
・1eff8b03 / 74a0a737等のMultiPV詰みPV完全性・中断回復は関連するが、
  将棋独自の1手詰め/宣言勝ち/千日手/PV補完と併せた検証が必要。
  項目4以外の全corner caseを今回実戦局面で再現したとは主張しない。
・cctype/破損LEB128の防御等は探索外の周辺処理として参照したが、
  今回の探索不具合の件数には含めない。

通った検証
・ASan/UBSan + ASSERT_LV=3、指定モデルでbench標準4局面・各100,000ノード。
  合計400,240ノード、異常なし。baseline-sanitizer-bench.log。
・39局面×4構成=156検索。
  (Threads,MultiPV,Hash MiB,全合法手,検討モード):
  (1,1,64,false,false)、(1,8,1,true,false)、(8,4,1,false,false)、(32,4,64,true,true)。
  各20,000ノード。cshogiでPV・bestmove・ponderの合法性を検証。
・stop/ponderhitを混ぜた24検索。上と合計180ケース、異常なし。
・どの合法手を指しても次に詰まされる局面で、1/8/32スレッド、MultiPV1/8、
  nodes=1〜4096を組み合わせた48検索。
・時計指定・movetime・searchmoves・実際の手順を持つ千日手の8検索。
  上と合計56ケース、異常なし。通常ケース全体では236ケース。
・上記とは別に、強制詰み局面を同じTTのまま4回再探索し、最終mate表示が正しいことを確認。
・独立実装cshogiとのperft比較6局面。開始局面depth3=25470で一致。
  中盤・終盤5局面のdepth2も全て一致。perft-compare.json。
・test nn test_accumulator 100: 23,442手、checksum 7678100571246805065で合格。
  accumulator-100.log。

限界
・236ケースに通ったことは、全局面や全タイミングでの正しさの証明ではない。
・ThreadSanitizerによる全共有データの検証、Mac/Windows実機、長時間の棋力対戦は未実施。
・深い再帰の候補とMultiPV中断時の全corner caseは、再現確認済みの問題と区別した。
・原因の特定と再現まで。元のソース修正、commit、pushは行っていない。


追加調査（第2巡、2026-10-29）

調査範囲と分類
・前回の127件の抽出から範囲を広げ、期間内全280コミットの題名と変更パスを
  一覧化した。全パッチはround2/all-commits.patchに保存（約2.3MB）。
・不具合修正・その導入元・関連する整理等57コミットは個別に理由を記して照合した。
  全280件の一覧はtools/memo/search-audit-20261029-stockfish.tsv。
・一覧の「一次分類」は対象範囲の分類であり、そのコミットの全変更行を検証して
  不具合がないと証明したという意味ではない。棋力調整やNNUE構成差を、単に
  未移植だからという理由で「未反映のバグ」とは扱わない。
・探索本体に加えて、メモリ/NUMA/ハッシュ/評価ファイル読込/文字列処理まで調査を
  拡張した。このためSA-14〜SA-16は初回の探索不具合リストとは分けて読むこと。
・以下の行番号は本体ソース0f421391のもの。docsの追加以外、本体に変更なし。

SA-11: 共有TTEntryの各フィールドが非atomicで読み書きされる【静的確認】
場所: source/tt.cpp:100-136、同:149-167、同:399-440。
Stockfish: 0111d11e2356147bad21c1f890b271a30d10a580 (2026-06-09)
https://github.com/official-stockfish/Stockfish/commit/0111d11e2356147bad21c1f890b271a30d10a580

key / depth8 / genBound8 / move16 / value16 / eval16 が通常の整数/Move16のまま。
同じTTを複数Workerが共有し、probe/readとsaveを同期せず実行するため、C++の
メモリモデル上はデータ競合となる。Stockfishは該当フィールドをrelaxed atomicに
しており、その修正が未反映。TT全体のエントリーを一括atomicにする話ではない。

注意: 複数フィールドを異なる世代から読んでしまうTTの仕様上の不整合と、
非atomicアクセス自体のC++未定義動作は別問題。ハードウェアで個々の値が
分断されずに読めても、C++の競合が解消されるわけではない。
現行historyの共有要素には既にstd::atomicを使う仕組みがあるため、Stockfishの
ラッパー全体を無条件に移す必要があるとは結論していない。

検証の限界: 最小の2スレッドプログラムでもThreadSanitizerが
「FATAL: ThreadSanitizer: unexpected memory mapping」で起動できなかった。
non-PIEでの再試行も失敗。エンジンをTSanで検証済みとはしていない。
特定の誤bestmoveやクラッシュの再現結果ではなく、共有アクセスの静的な指摘。
証拠: round2/tsan-probe.log、round2/tsan-probe-nopie.log。
修正時はTTE_KEY_TYPEの64bit拡張、Move16の格納形式、TTEntry/Clusterのサイズと
アラインメントを保ち、性能を再計測する必要がある。

SA-12: CPU番号の欠番で後続CPUをL3/NUMA構成から落とす【模擬入力で再現】
場所: source/numa.h:1429-1467。
Stockfish: 564f9f9c858ce8bc7af47a7b7763c11b1b0a467c (2026-05-27)
https://github.com/official-stockfish/Stockfish/commit/564f9f9c858ce8bc7af47a7b7763c11b1b0a467c

nextUnseenCpuがCPU 0から連続した番号を探し、最初のcacheファイル欠落でbreakする。
その時点でL3ドメインが1件でもあれば、後ろのCPUを含まない構成を採用できてしまう。
Stockfishは既知のsystemConfig.nodeByCpuのCPU番号を列挙するよう修正している。

再現: 実numa.hを使用し、read_file_to_stringだけをリンク時の--wrapで差し替えた。
実際のsysfs、CPU online状態、アフィニティ設定は変更していない。
  node/online = 0
  node0/cpulist = 0-1,4-5
  cpu0,1のL3 shared_cpu_list = 0-1
  cpu4,5のL3 shared_cpu_list = 4-5
  cpu2のcacheファイル = 存在しない
結果:
  SYSTEM cpus=4 map=0-1,4-5
  L3 cpus=2 map=0-1 expected_cpus=4
影響: CPU番号が疎な構成で利用CPUの欠落、偏ったWorker binding/性能低下。
現在のPCでCPUが実際に欠落しているという報告ではない。
証拠: round2/numa_gap_probe.cpp、round2/numa-gap.log。

SA-13: 大容量メモリ確保失敗時にNULLへplacement newする【失敗注入で再現】
場所: source/memory.h:120-145、source/memory.cpp:137-151。
使用先例: source/history.h:156、source/thread.cpp:51、
source/engine/yaneuraou-engine/yaneuraou-search.cpp:322。
Stockfish: 7c37212ec7eba49be3c89887640f646215269526 (2026-08-19)
https://github.com/official-stockfish/Stockfish/commit/7c37212ec7eba49be3c89887640f646215269526

memory_allocatorはalloc_funcの返値を検査せず、そのアドレスへ構築する。
配列版では要素数をNULLの先頭へ書き込む。Linuxの大容量アロケータも、
std_aligned_alloc失敗時のnullptrをそのまま返す。

再現コード（実ヘッダーのテンプレートに失敗を注入）:
  memory_allocator<int[]>([](size_t)->void* { return nullptr; }, size_t(4));
UBSan: memory.h:142:22「constructor call on null pointer of type 'size_t'」。
通常のメモリ不足時に、理由を表示して終了せず未定義動作/クラッシュへ進みうる。
メモリを大量に消費させてOS全体をOOMにするテストは行っていない。
証拠: round2/utility_probe.cpp、round2/utility-allocation.log。

SA-14: hash_bytes末尾のsigned charが上位バイトを消す【実関数で再現】
場所: source/misc.cpp:557-594、特に583行。
Stockfish: d68041e3201e6c5fc37d1219168f568647f0f93c (2026-08-19)
https://github.com/official-stockfish/Stockfish/commit/d68041e3201e6c5fc37d1219168f568647f0f93c

末尾の処理が k=(k<<8)|(uint64_t)end[i] となっている。
charがsignedの環境で0x80以上の値をuint64_tへ直接変換すると符号拡張され、
既に組み立てた上位バイトがORで消える。
実際のhash_bytesに2バイトの配列を渡した結果:
  入力A = 80 01
  入力B = 80 02
  hash(A) = hash(B) = 817358306566259125
  std::numeric_limits<char>::is_signed = true
ハッシュなので一般に衝突はありうるが、これは符号拡張で末尾の情報が失われる
系統的な衝突。Stockfishはunsigned byteとして読み取るよう修正済み。

影響範囲: misc.hのhash_string/hash/combine等、およびNNUE等の構造体ハッシュ。
探索のTT/Position用Zobristキーとは別なので、これをSA-01やTT誤ヒットの
直接の原因とはしていない。具体的な探索結果変更・棋力差は未検証。
証拠: round2/utility-hash.log。

SA-15: LEB128デコードの左シフトUBとassertだけの形式検査【関数単位で再現】
場所: source/eval/nnue/nnue_common.h:166-214。
Stockfish: 49f8e667c0ebf146f3ce5a1ea2d49ac78b9f777e (2026-08-19)
https://github.com/official-stockfish/Stockfish/commit/49f8e667c0ebf146f3ce5a1ea2d49ac78b9f777e

a) IntType=int32_tの実関数へ、INT32_MINの正当なsigned LEB128である
   80 80 80 80 78を渡すと、195行の(byte & 0x7f)<<shiftで
   「left shift of 120 by 28 places cannot be represented in type 'int'」。
   UBSanが検出して終了した。
   指定SFNNモデルのFTのBiasType/WeightTypeはint16_tであるため、このint32の
   プローブを根拠に「指定モデルの通常ロードでUBが発生する」とはしていない。
b) magic文字列とbytes_leftの検査がassertだけ。外部プローブのみNDEBUGで
   実テンプレートをコンパイルし、magic先頭をCからXへ変えても
   int16_tの0を読み出し、stream.fail()==falseのまま返った。
   出力: LEB_BAD_MAGIC result=0 fail=0 expected_fail=1。
c) バッファ読み込みの実際のgcountを追跡していない。短い/壊れたファイルに
   対する防御もStockfishと差がある。短いファイルが全て受理されると主張する
   ものではない（stream.read自体がfailbitを設定する場合もある）。

通常探索時のFT演算そのものの問題とは分けること。指定モデルは変更していない。
証拠: round2/utility-leb-0.log、round2/utility-lebmagic-0.log。
初回の空ログSIGSEGVだけでは原因を特定できず、証拠に数えていない。
修正方向: unsigned中間値、明示的なstreamエラー、読み込めたバイト数の検査。

SA-16: cctypeへの負のchar引数【静的確認・条件付き】
場所: source/misc.cpp:1949,1954、source/usioption.cpp:19。
Stockfish: 01d71fc9a735a13d84402ac9bee8d54b6b5669cd (2026-09-03)
https://github.com/official-stockfish/Stockfish/commit/01d71fc9a735a13d84402ac9bee8d54b6b5669cd

isspace/tolowerへplain charをそのまま渡している。charがsignedで、引数がEOFでも
unsigned charで表せる値でもない場合はC/C++の要求する引数範囲を外れる。
UTF8等の高bitのある文字列を扱う条件で問題になる。
通常のASCIIだけのUSIオプション名・sysfs入力ではこの条件にならない。
この環境のlibcでクラッシュすることや、通常探索が誤ることは確認していない。
unsigned charに変換してから渡す修正の候補として記録する。

PC-01: 大容量malloc arenaのNUMA間再利用【性能候補・未計測】
場所: source/memory.cpp:137-151。
Stockfish: 7ab49b9bda97a608e500762c1fd1d1c5857936f0 (2026-08-25)
https://github.com/official-stockfish/Stockfish/commit/7ab49b9bda97a608e500762c1fd1d1c5857936f0

Linuxの大きいaligned allocationをmallocに任せると、Threads変更後に別NUMAの
arena/メモリを再利用し、remote memory accessを増やす可能性がある。
将棋側はmalloc系のまま。現在のPCは複数物理NUMAを再現できる検証環境ではなく、
NPS改善を計測したわけではない。採用を勧める確定バグには数えない。
移植する場合は後続の5cae0fab（MAP_FAILEDの検査）も併せて扱う必要がある。

追加検証で否定できた候補・適用条件が異なるもの
・86f1df71のmaterial key修正:
  将棋はチェスのpieceCountを使った同じ式ではない。実Position::do_move/undo_moveを
  200局、計48,322手実行し、各手後のSFENから再構築した状態と照合。
  board_key/hand_key/pawnKey/nonPawnKey[両色]/materialKey/minorPieceKeyが全て一致。
  捕獲8,090手、成り2,267手、駒打ち7,369手を含む。undo後も親SFEN/キーが一致。
  指定モデルをロードしたASan/UBSanビルド。StateInfoはプローブ側でheapに置き、
  標準UnitTestのSA-09/SA-10を回避した。本体テストの修正ではない。
  証拠: round2/partial_keys_probe.cpp、round2/partial-keys.log。
・50221673の共有continuationHistory初期化:
  将棋のcontinuationHistoryはWorkerごとの配列（search.h:456）。全Workerで初期化する
  現行処理が必要であり、SFのNUMA先頭Workerだけの初期化を移すと逆に壊れる。
・b4ea9205のsysfs末尾改行:
  SFはstrtoull末尾検査の厳格化で問題を導入した。将棋のstoullは末尾改行を許容する。
・23df8290の負の時間入力:
  将棋のparse_limitsも負数を受理する。ただしSFの新しい時間優位計算は存在せず、
  将棋側はremain_timeの下限を設けている。movetimeは別経路なので堅牢化余地はある。
  同じタイムアウト不具合を確認したという判定にはしない。
・4d65257bのcyclic time control、各nodestime修正:
  該当するmovestogo/Stockfish時間管理経路が将棋側では無効。
・5cae0fab/MADV_POPULATE_WRITE/shm_unix関連（第3巡で理由を訂正）:
  現行にも旧shm_linux.hはありLinuxで有効。MADV_POPULATE_WRITEと新shm_unixは未導入。
  5cae0fabの対象となる大容量malloc代替mmap経路も未導入。
  旧backendが存在しないという理由で一括して非該当とした前回の説明は誤り。
  6a50807eのTHP化には適用余地があるため、全件レビューで実験候補へ分類した。
・WindowsのANSIパス、旧macOS、各ISAのコンパイル/配布問題:
  実行経路の違いと実機不足を一覧に明示した。未検証を「問題なし」に置き換えない。

第2巡の結論と未完了範囲
・新しいSA-11〜SA-16を追記し、候補PC-01を分離した。実装は全て未修正。
・SA-12/SA-13/SA-14/SA-15は模擬入力または関数単位の再現。
  SA-11/SA-16は静的確認であり、動的再現済みの項目と混同しない。
・初回の236通常探索ケース・bench・perft・accumulatorに加え、48,322手の部分キー検証。
  標準unittestがSA-09/SA-10で失敗する状態は変わっていない。
・SA-08、MultiPV中断時の残りのmate PV corner case、Windows/Mac実機、TSan、
  複数物理NUMA、長時間の棋力対戦は未完了。全不具合を洗い出した保証ではない。
・保存対象はdocsの台帳と280件一覧。再現プローブとログは引き続きGit管理外の
  build/search-audit-20261029/にあるため、必要ならそのまま別途保存すること。


全280件の個別レビュー（第3巡、2026-10-29）

成果物
・tools/memo/stockfish-port-review-20261029.txt: 全280件の移植可否、対応実装、理由、条件。
・tools/memo/search-audit-20261029-stockfish.tsv: 同じ280件の検索/比較用一覧。
・前回の「一次分類」から、全件を個別の判定へ更新した。
・修正推奨14件、条件付き推奨43件、実験候補66件、見送り34件、適用不要122件、
  同種機構の対応済み1件。これはcommit数で、独立した不具合数ではない。
・同じ系列の中間版やrevertを順番に全部適用する提案ではない。特にMultiPV、
  TT aging、Linux共有メモリ、NMP/seekMate、NNUE architectureの依存を明記した。
・本体のコードは引き続き未修正。今回の実行はSA-17/18の追加再現であり、
  280通りを移植して棋力/NPSを測定したものではない。

訂正・重要な区別
1) Linux共有メモリ:
   旧shm_linux.hはsource/shm.hからincludeされ、LinuxのSharedMemoryBackendで有効。
   未導入なのは新しいmemfd/socket型shm_unix等の経路。旧backendのTHP化は候補になる。
2) speedtest:
   現行にもUSIEngine::benchmark → Benchmark::setup_benchmarkの経路がある。
   「将棋benchは別なので30290aa0は対象外」とした前回判定を訂正。SA-18を再現した。
3) TT照合key幅:
   TTE_KEY_TYPEを決める条件はTT_CLUSTER_SIZE。3ならuint16_t、その他の対応構成はuint64_t。
   HASH_KEY_BITSだけで21bit SWAR化の適用可否を決めない。
4) No functional change表記:
   73826352は説明がbehaviour-identicalでも実際の数式は同値でない。
   depth=1で旧bonus=41、新bonus=114。単純整理として無検証採用しない。
5) 既試行のFT/NNZ融合、specialized等は以前の比較と重複する。
   上流との差を具体化しない再実験は優先しない。TT/historyのprefetchは別の案。

SA-17: spin optionの不正値・過大値で未処理例外終了【通常releaseで再現】
場所: source/usioption.cpp:215-220付近、Option::operator=(const std::string&)。
Stockfish: cd75d31127b2d351edde4a1eac33bb2ccbac0db2 (2026-07-10)、全件一覧SF-109。
https://github.com/official-stockfish/Stockfish/commit/cd75d31127b2d351edde4a1eac33bb2ccbac0db2

範囲検査でstd::stollを直接呼び、invalid_argument/out_of_rangeを処理しない。
通常releaseの実行ファイルbuild/finny-gates/avx512-onで確認:
  setoption name Threads value abc
    → std::invalid_argument / what(): stoll / SIGABRT (returncode=-6)
  setoption name Threads value 999999999999999999999999999999
    → std::out_of_range / what(): stoll / SIGABRT (returncode=-6)
評価関数ロード前の入力処理で失敗するため、この再現にはモデルを必要としない。
通常の合法な数値入力の探索結果を誤るという指摘ではなく、入力の堅牢化。

修正方向: 非例外の数値変換と全桁/範囲検査。不正値は従来設定を維持して拒否する。
将棋のspinはint64_tなので、Stockfishのint min/maxをそのまま移植しない。
同commitのNUMA文字列/範囲の入力検査も対象となるが、実メモリを枯渇させる試験は未実施。
後続b4ea9205の末尾空白許容、01d71fc9のunsigned charを合わせて検討。
証拠: round3/invalid-spin-0.log、round3/invalid-spin-1.log。

SA-18: speedtestの時間引数でsigned int overflow【実関数+UBSanで再現】
場所: source/benchmark.cpp:142-211、特に198行のdesiredTimeS * 1000。
呼出し: source/usi.cpp:342のspeedtest → 同:802のsetup_benchmark。
Stockfish: 30290aa0fffa491046ca95a285ce2a505d9c34cc (2026-08-19)、全件一覧SF-179。
https://github.com/official-stockfish/Stockfish/commit/30290aa0fffa491046ca95a285ce2a505d9c34cc

desiredTimeSはintで、floatへ変換する前にintの掛け算が起きる。
外部プローブから元の実オブジェクトの関数を呼んだ:
  std::istringstream args("1 1 2147484");
  auto setup = Benchmark::setup_benchmark(args);
UBSan: benchmark.cpp:198:58
  signed integer overflow: 2147484 * 1000 cannot be represented in type 'int'
正常対照の"1 1 1"はThreads=1/Hash=1と4局面の正のgo movetime列を生成し成功。
モデルや巨大なメモリを確保せず、引数解析/コマンド生成関数だけで確認した。

注意: threads/ttSizeは将棋ではsize_tへ拡張済み。threads=20000000を関数だけへ渡す
検査ではHash=2560000000を生成し、int overflowは起きていない。
上流で説明される全てのoverflow箇所がそのまま同じではない。
空ログの初回SIGSEGVは原因未特定で、この件の再現証拠に数えない。

修正方向: 時間引数を広い型で読み、ms変換可能範囲へclamp/拒否し、threads/hashも
実際のUSI option上限と整合させる。秒→msの乗算は広い型で行う。
これはユーザーが従来NPS測定に使った標準benchの通常引数で起きたという報告ではない。
証拠: round3/benchmark_args_probe.cpp、round3/benchmark-args-seconds-0.log、
round3/benchmark-args-normal-0.log、round3/benchmark-args-threads-1.log。

第3巡でも未実施のもの
・本体修正、280案の対局/NPS比較、全ISA/全OSのビルド、Windows/Mac/BSD実機、
  複数物理NUMA、TSan、長時間の棋力試験は行っていない。
・標準unittestのSA-09/10は未修正であり、全テスト合格とはしていない。
・モデルを使う探索検証は前回記録の指定SFNNモデルの結果を引き継ぐ。
