[
  {
    "path": ".clang-format",
    "content": "AccessModifierOffset: -1\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: Consecutive\nAlignConsecutiveDeclarations: Consecutive\nAlignEscapedNewlines: DontAlign\nAlignOperands: AlignAfterOperator\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortEnumsOnASingleLine: false\nAllowShortIfStatementsOnASingleLine: false\nBreakTemplateDeclarations: Yes\nBasedOnStyle: WebKit\nBitFieldColonSpacing: After\nBinPackParameters: false\nBreakBeforeBinaryOperators: NonAssignment\nBreakBeforeBraces: Custom\nBraceWrapping:\n  AfterFunction: false\n  AfterClass: false\n  AfterControlStatement: true\n  BeforeElse: true\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializers: AfterColon\nBreakStringLiterals: false\nColumnLimit: 100\nContinuationIndentWidth: 2\nCpp11BracedListStyle: true\nIndentGotoLabels: false\nIndentPPDirectives: BeforeHash\nIndentWidth: 4\nMaxEmptyLinesToKeep: 2\nNamespaceIndentation: None\nPackConstructorInitializers: Never\nReflowComments: false\nSortIncludes: false\nSortUsingDeclarations: false\nSpaceAfterCStyleCast: true\nSpaceAfterTemplateKeyword: false\nSpaceBeforeCaseColon: true\nSpaceBeforeCpp11BracedList: false\nSpaceBeforeInheritanceColon: false\nSpaceInEmptyBlock: false\nSpacesBeforeTrailingComments: 2\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# .git-blame-ignore-revs\n# Ignore commit which added clang-format\n2d0237db3f0e596fb06e3ffbadba84dcc4e018f6\n\n# Post commit formatting fixes\n0fca5605fa2e5e7240fde5e1aae50952b2612231\n08ed4c90db31959521b7ef3186c026edd1e90307"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG-REPORT.yml",
    "content": "name: Report issue\ndescription: Create a report to help us fix issues with the engine\nbody:\n- type: textarea\n  attributes:\n    label: Describe the issue\n    description: A clear and concise description of what you're experiencing.\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Expected behavior\n    description: A clear and concise description of what you expected to happen.\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Steps to reproduce\n    description: |\n      Steps to reproduce the behavior.\n      You can also use this section to paste the command line output.\n    placeholder: |\n      ```\n      position startpos moves g2g4 e7e5 f2f3\n      go mate 1\n      info string NNUE evaluation using nn-6877cd24400e.nnue enabled\n      info depth 1 seldepth 1 multipv 1 score mate 1 nodes 33 nps 11000 tbhits 0 time 3 pv d8h4\n      bestmove d8h4\n      ```\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Anything else?\n    description: |\n      Anything that will give us more context about the issue you are encountering.\n      You can also use this section to propose ideas on how to solve the issue. \n  validations:\n    required: false\n\n- type: dropdown\n  attributes:\n    label: Operating system\n    options:\n      - All\n      - Windows\n      - Linux\n      - MacOS\n      - Android\n      - Other or N/A\n  validations:\n    required: true\n\n- type: input\n  attributes:\n    label: Stockfish version\n    description: |\n      This can be found by running the engine.\n      You can also use the commit ID.\n    placeholder: Stockfish 15 / e6e324e\n  validations:\n    required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discord server\n    url: https://discord.gg/GWDRS3kU6R\n    about: Feel free to ask for support or have a chat with us on our Discord server!\n  - name: Discussions, Q&A, ideas, show us something...\n    url: https://github.com/official-stockfish/Stockfish/discussions/new\n    about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it!\n"
  },
  {
    "path": ".github/ci/arm_matrix.json",
    "content": "{\n  \"config\": [\n    {\n      \"name\": \"Android NDK aarch64\",\n      \"os\": \"ubuntu-22.04\",\n      \"simple_name\": \"android\",\n      \"compiler\": \"aarch64-linux-android29-clang++\",\n      \"emu\": \"qemu-aarch64\",\n      \"comp\": \"ndk\",\n      \"shell\": \"bash\",\n      \"archive_ext\": \"tar\"\n    },\n    {\n      \"name\": \"Android NDK arm\",\n      \"os\": \"ubuntu-22.04\",\n      \"simple_name\": \"android\",\n      \"compiler\": \"armv7a-linux-androideabi29-clang++\",\n      \"emu\": \"qemu-arm\",\n      \"comp\": \"ndk\",\n      \"shell\": \"bash\",\n      \"archive_ext\": \"tar\"\n    }\n  ],\n  \"binaries\": [\"armv8-dotprod\", \"armv8\", \"armv7\", \"armv7-neon\"],\n  \"exclude\": [\n    {\n      \"binaries\": \"armv8-dotprod\",\n      \"config\": {\n        \"compiler\": \"armv7a-linux-androideabi29-clang++\"\n      }\n    },\n    {\n      \"binaries\": \"armv8\",\n      \"config\": {\n        \"compiler\": \"armv7a-linux-androideabi29-clang++\"\n      }\n    },\n    {\n      \"binaries\": \"armv7\",\n      \"config\": {\n        \"compiler\": \"aarch64-linux-android29-clang++\"\n      }\n    },\n    {\n      \"binaries\": \"armv7-neon\",\n      \"config\": {\n        \"compiler\": \"aarch64-linux-android29-clang++\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/ci/libcxx17.imp",
    "content": "[\n    # Mappings for libcxx's internal headers\n    { include: [ \"<__fwd/fstream.h>\", private, \"<iosfwd>\", public ] },\n    { include: [ \"<__fwd/ios.h>\", private, \"<iosfwd>\", public ] },\n    { include: [ \"<__fwd/istream.h>\", private, \"<iosfwd>\", public ] },\n    { include: [ \"<__fwd/ostream.h>\", private, \"<iosfwd>\", public ] },\n    { include: [ \"<__fwd/sstream.h>\", private, \"<iosfwd>\", public ] },\n    { include: [ \"<__fwd/streambuf.h>\", private, \"<iosfwd>\", public ] },\n    { include: [ \"<__fwd/string_view.h>\", private, \"<string_view>\", public ] },\n    { include: [ \"<__system_error/errc.h>\", private, \"<system_error>\", public ] },\n\n    # Mappings for includes between public headers\n    { include: [ \"<ios>\", public, \"<iostream>\", public ] },\n    { include: [ \"<streambuf>\", public, \"<iostream>\", public ] },\n    { include: [ \"<istream>\", public, \"<iostream>\", public ] },\n    { include: [ \"<ostream>\", public, \"<iostream>\", public ] },\n    { include: [ \"<iosfwd>\", public, \"<iostream>\", public ] },\n\n    # Missing mappings in include-what-you-use's libcxx.imp\n    { include: [\"@<__condition_variable/.*>\", private, \"<condition_variable>\", public ] },\n    { include: [\"@<__mutex/.*>\", private, \"<mutex>\", public ] },\n]\n"
  },
  {
    "path": ".github/ci/matrix.json",
    "content": "{\n  \"config\": [\n    {\n      \"name\": \"Ubuntu 22.04 GCC\",\n      \"os\": \"ubuntu-22.04\",\n      \"simple_name\": \"ubuntu\",\n      \"compiler\": \"g++\",\n      \"comp\": \"gcc\",\n      \"shell\": \"bash\",\n      \"archive_ext\": \"tar\",\n      \"sde\": \"/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --\"\n    },\n    {\n      \"name\": \"macOS 15 Apple Clang\",\n      \"os\": \"macos-15-intel\",\n      \"simple_name\": \"macos\",\n      \"compiler\": \"clang++\",\n      \"comp\": \"clang\",\n      \"shell\": \"bash\",\n      \"archive_ext\": \"tar\"\n    },\n    {\n      \"name\": \"macOS 15 Apple Clang M1\",\n      \"os\": \"macos-15\",\n      \"simple_name\": \"macos-m1\",\n      \"compiler\": \"clang++\",\n      \"comp\": \"clang\",\n      \"shell\": \"bash\",\n      \"archive_ext\": \"tar\"\n    },\n    {\n      \"name\": \"Windows 2022 Mingw-w64 GCC x86_64\",\n      \"os\": \"windows-2022\",\n      \"simple_name\": \"windows\",\n      \"compiler\": \"g++\",\n      \"comp\": \"mingw\",\n      \"msys_sys\": \"mingw64\",\n      \"msys_env\": \"x86_64-gcc\",\n      \"shell\": \"msys2 {0}\",\n      \"ext\": \".exe\",\n      \"sde\": \"/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-win/sde.exe -future --\",\n      \"archive_ext\": \"zip\"\n    },\n    {\n      \"name\": \"Windows 11 Mingw-w64 Clang arm64\",\n      \"os\": \"windows-11-arm\",\n      \"simple_name\": \"windows\",\n      \"compiler\": \"clang++\",\n      \"comp\": \"clang\",\n      \"msys_sys\": \"clangarm64\",\n      \"msys_env\": \"clang-aarch64-clang\",\n      \"shell\": \"msys2 {0}\",\n      \"ext\": \".exe\",\n      \"archive_ext\": \"zip\"\n    }\n  ],\n  \"binaries\": [\n    \"x86-64\",\n    \"x86-64-sse41-popcnt\",\n    \"x86-64-avx2\",\n    \"x86-64-bmi2\",\n    \"x86-64-avxvnni\",\n    \"x86-64-avx512\",\n    \"x86-64-vnni512\",\n    \"x86-64-avx512icl\",\n    \"apple-silicon\",\n    \"armv8\",\n    \"armv8-dotprod\"\n  ],\n  \"exclude\": [\n    {\n      \"binaries\": \"x86-64\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-sse41-popcnt\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx2\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-bmi2\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avxvnni\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx512\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-vnni512\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx512icl\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avxvnni\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx512\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-vnni512\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx512icl\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-sse41-popcnt\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx2\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-bmi2\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avxvnni\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx512\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-vnni512\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"x86-64-avx512icl\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"apple-silicon\",\n      \"config\": {\n        \"os\": \"windows-2022\"\n      }\n    },\n    {\n      \"binaries\": \"apple-silicon\",\n      \"config\": {\n        \"os\": \"windows-11-arm\"\n      }\n    },\n    {\n      \"binaries\": \"apple-silicon\",\n      \"config\": {\n        \"os\": \"ubuntu-20.04\"\n      }\n    },\n    {\n      \"binaries\": \"apple-silicon\",\n      \"config\": {\n        \"os\": \"ubuntu-22.04\"\n      }\n    },\n    {\n      \"binaries\": \"apple-silicon\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"armv8\",\n      \"config\": {\n        \"os\": \"windows-2022\"\n      }\n    },\n    {\n      \"binaries\": \"armv8\",\n      \"config\": {\n        \"os\": \"ubuntu-20.04\"\n      }\n    },\n    {\n      \"binaries\": \"armv8\",\n      \"config\": {\n        \"os\": \"ubuntu-22.04\"\n      }\n    },\n    {\n      \"binaries\": \"armv8\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"armv8\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    },\n    {\n      \"binaries\": \"armv8-dotprod\",\n      \"config\": {\n        \"os\": \"windows-2022\"\n      }\n    },\n    {\n      \"binaries\": \"armv8-dotprod\",\n      \"config\": {\n        \"os\": \"ubuntu-20.04\"\n      }\n    },\n    {\n      \"binaries\": \"armv8-dotprod\",\n      \"config\": {\n        \"os\": \"ubuntu-22.04\"\n      }\n    },\n    {\n      \"binaries\": \"armv8-dotprod\",\n      \"config\": {\n        \"os\": \"macos-15-intel\"\n      }\n    },\n    {\n      \"binaries\": \"armv8-dotprod\",\n      \"config\": {\n        \"os\": \"macos-15\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/arm_compilation.yml",
    "content": "name: Compilation\non:\n  workflow_call:\n    inputs:\n      matrix:\n        type: string\n        required: true\njobs:\n  Compilation:\n    name: ${{ matrix.config.name }} ${{ matrix.binaries }}\n    runs-on: ${{ matrix.config.os }}\n    env:\n      COMPCXX: ${{ matrix.config.compiler }}\n      COMP: ${{ matrix.config.comp }}\n      EMU: ${{ matrix.config.emu }}\n      EXT: ${{ matrix.config.ext }}\n      BINARY: ${{ matrix.binaries }}\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(inputs.matrix) }}\n    defaults:\n      run:\n        working-directory: src\n        shell: ${{ matrix.config.shell }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n\n      - name: Download required linux packages\n        if: runner.os == 'Linux'\n        run: |\n          sudo apt update\n          sudo apt install qemu-user\n\n      - name: Install NDK\n        if: runner.os == 'Linux'\n        run: |\n          if [ $COMP == ndk ]; then\n            NDKV=\"27.2.12479018\"\n            ANDROID_ROOT=/usr/local/lib/android\n            ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk\n            SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager\n            echo \"y\" | $SDKMANAGER \"ndk;$NDKV\"\n            ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV\n            ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin\n            echo \"ANDROID_NDK_BIN=$ANDROID_NDK_BIN\" >> $GITHUB_ENV\n          fi\n\n      - name: Extract the bench number from the commit history\n        run: |\n          for hash in $(git rev-list -100 HEAD); do\n            benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\\b[Bb]ench[ :]\\+[1-9][0-9]\\{5,7\\}\\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true\n          done\n          [[ -n \"$benchref\" ]] && echo \"benchref=$benchref\" >> $GITHUB_ENV && echo \"From commit: $hash\" && echo \"Reference bench: $benchref\" || echo \"No bench found\"\n\n      - name: Download the used network from the fishtest framework\n        run: make net\n\n      - name: Check compiler\n        run: |\n          if [ $COMP == ndk ]; then\n            export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n          fi\n          $COMPCXX -v\n\n      - name: Test help target\n        run: make help\n\n      - name: Check git\n        run: git --version\n\n      # Compile profile guided builds\n\n      - name: Compile ${{ matrix.binaries }} build\n        run: |\n          if [ $COMP == ndk ]; then\n            export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n            export LDFLAGS=\"-static -Wno-unused-command-line-argument\"\n          fi\n          make clean\n          make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX=$EMU\n          make strip ARCH=$BINARY COMP=$COMP\n          RUN_PREFIX=$EMU ../tests/signature.sh $benchref\n          mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT\n\n      - name: Remove non src files\n        run: git clean -fx\n\n      - name: Upload artifact for (pre)-release\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}\n          path: |\n            .\n            !.git\n            !.output\n"
  },
  {
    "path": ".github/workflows/avx2_compilers.yml",
    "content": "name: AVX2 Compiler Matrix\n\non:\n  workflow_call:\n\njobs:\n  avx2-compiler-matrix:\n    name: avx2 (${{ matrix.name }})\n    runs-on: ubuntu-latest\n    container:\n      image: ${{ matrix.image }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - { name: gcc-10, comp: gcc, cxx: g++, image: \"gcc:10\" }\n          - { name: gcc-11, comp: gcc, cxx: g++, image: \"gcc:11\" }\n          - { name: gcc-12, comp: gcc, cxx: g++, image: \"gcc:12\" }\n          - { name: gcc-13, comp: gcc, cxx: g++, image: \"gcc:13\" }\n          - { name: gcc-14, comp: gcc, cxx: g++, image: \"gcc:14\" }\n          - { name: gcc-15, comp: gcc, cxx: g++, image: \"gcc:15\" }\n\n          # Using silkeh/clang for older versions\n          - { name: clang-10, comp: clang, cxx: clang++, image: \"silkeh/clang:10\", is_clang: true, ver: \"10\" }\n          - { name: clang-11, comp: clang, cxx: clang++, image: \"silkeh/clang:11\", is_clang: true, ver: \"11\" }\n          - { name: clang-12, comp: clang, cxx: clang++, image: \"silkeh/clang:12\", is_clang: true, ver: \"12\" }\n          - { name: clang-13, comp: clang, cxx: clang++, image: \"silkeh/clang:13\", is_clang: true, ver: \"13\" }\n          - { name: clang-14, comp: clang, cxx: clang++, image: \"silkeh/clang:14\", is_clang: true, ver: \"14\" }\n          - { name: clang-15, comp: clang, cxx: clang++, image: \"silkeh/clang:15\", is_clang: true, ver: \"15\" }\n          - { name: clang-16, comp: clang, cxx: clang++, image: \"silkeh/clang:16\", is_clang: true, ver: \"16\" }\n          - { name: clang-17, comp: clang, cxx: clang++, image: \"silkeh/clang:17\", is_clang: true, ver: \"17\" }\n\n          - { name: clang-18, comp: clang, cxx: clang++-18, image: \"ubuntu:rolling\", is_clang: true, ver: \"18\" }\n          - { name: clang-19, comp: clang, cxx: clang++-19, image: \"ubuntu:rolling\", is_clang: true, ver: \"19\" }\n          - { name: clang-20, comp: clang, cxx: clang++-20, image: \"ubuntu:rolling\", is_clang: true, ver: \"20\" }\n          - { name: clang-21, comp: clang, cxx: clang++-21, image: \"ubuntu:rolling\", is_clang: true, ver: \"21\" }\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install dependencies\n        run: |\n          if grep -q \"buster\" /etc/os-release; then\n            echo \"Debian Buster detected. Switching to archive repositories...\"\n            echo \"deb http://archive.debian.org/debian buster main contrib non-free\" > /etc/apt/sources.list\n            echo \"deb http://archive.debian.org/debian-security buster/updates main contrib non-free\" >> /etc/apt/sources.list\n            echo 'Acquire::Check-Valid-Until \"false\";' > /etc/apt/apt.conf.d/99-ignore-valid-until\n          fi\n\n          apt-get update\n          apt-get install -y curl git make\n\n      - name: Set up Clang\n        if: ${{ matrix.is_clang && matrix.image == 'ubuntu:rolling' }}\n        run: |\n          if [ \"${{ matrix.ver }}\" -le 20 ]; then\n            apt-get install -y clang-${{ matrix.ver }}\n          else\n            apt-get install -y \\\n              clang-${{ matrix.ver }} \\\n              llvm-${{ matrix.ver }}-dev \\\n              llvm-${{ matrix.ver }}-linker-tools \\\n              lld-${{ matrix.ver }}\n          fi\n\n      - name: Download network\n        working-directory: src\n        run: make net\n\n      - name: Build avx2 binary\n        working-directory: src\n        run: |\n          export CXXFLAGS=\"-Werror\"\n          if [ \"${{ matrix.ver }}\" -ge 20 ]; then\n            apt install -y lld\n          fi\n          make clean\n          make -j build ARCH=x86-64-avx2 COMP=${{ matrix.comp }} COMPCXX=${{ matrix.cxx }}\n\n      - name: Smoke test\n        working-directory: src\n        run: ./stockfish bench 16 1 6"
  },
  {
    "path": ".github/workflows/clang-format.yml",
    "content": "# This workflow will run clang-format and comment on the PR.\n# Because of security reasons, it is crucial that this workflow\n# executes no shell script nor runs make.\n# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/\n\nname: Clang-Format\non:\n  pull_request_target:\n    branches:\n      - \"master\"\n    paths:\n      - \"**.cpp\"\n      - \"**.h\"\n\npermissions:\n  pull-requests: write\n\njobs:\n  Clang-Format:\n    name: Clang-Format\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - name: Run clang-format style check\n        uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # @v4.15.0\n        id: clang-format\n        continue-on-error: true\n        with:\n          clang-format-version: \"20\"\n          exclude-regex: \"incbin\"\n\n      - name: Comment on PR\n        if: steps.clang-format.outcome == 'failure'\n        uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0\n        with:\n          message: |\n            clang-format 20 needs to be run on this PR.\n            If you do not have clang-format installed, the maintainer will run it when merging.\n            For the exact version please see https://packages.ubuntu.com/plucky/clang-format-20.\n\n            _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_\n          comment_tag: execution\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Comment on PR\n        if: steps.clang-format.outcome != 'failure'\n        uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0\n        with:\n          message: |\n            _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_\n          create_if_not_exists: false\n          comment_tag: execution\n          mode: delete\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [\"master\"]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [\"master\"]\n  schedule:\n    - cron: \"17 18 * * 1\"\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [\"cpp\"]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Use only 'java' to analyze code written in Java, Kotlin, or both\n        # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both\n        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n          # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n          # queries: security-extended,security-and-quality\n\n      - name: Build\n        working-directory: src\n        run: make -j build\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3\n        with:\n          category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/compilation.yml",
    "content": "name: Compilation\non:\n  workflow_call:\n    inputs:\n      matrix:\n        type: string\n        required: true\njobs:\n  Compilation:\n    name: ${{ matrix.config.name }} ${{ matrix.binaries }}\n    runs-on: ${{ matrix.config.os }}\n    env:\n      COMPCXX: ${{ matrix.config.compiler }}\n      COMP: ${{ matrix.config.comp }}\n      EXT: ${{ matrix.config.ext }}\n      NAME: ${{ matrix.config.simple_name }}\n      BINARY: ${{ matrix.binaries }}\n      SDE: ${{ matrix.config.sde }}\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(inputs.matrix) }}\n    defaults:\n      run:\n        working-directory: src\n        shell: ${{ matrix.config.shell }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Install fixed GCC on Linux\n        if: runner.os == 'Linux'\n        uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3\n        with:\n          version: 11\n\n      - name: Setup msys and install required packages\n        if: runner.os == 'Windows'\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: ${{ matrix.config.msys_sys }}\n          install: mingw-w64-${{ matrix.config.msys_env }} make git zip\n\n      - name: Download SDE package\n        if: runner.os == 'Linux' || runner.os == 'Windows'\n        uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4\n        with:\n          environmentVariableName: SDE_DIR\n          sdeVersion: 9.33.0\n\n      - name: Download the used network from the fishtest framework\n        run: make net\n\n      - name: Check compiler\n        run: $COMPCXX -v\n\n      - name: Test help target\n        run: make help\n\n      - name: Check git\n        run: git --version\n\n      - name: Check compiler\n        run: $COMPCXX -v\n\n      - name: Show compiler cpu info\n        run: |\n          if [[ \"$COMPCXX\" == clang* ]]; then\n             $COMPCXX -E - -march=native -###\n          else\n            $COMPCXX -Q -march=native --help=target\n          fi\n\n      # x86-64 with newer extensions tests\n\n      - name: Compile ${{ matrix.config.binaries }} build\n        run: |\n          make clean\n          make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX=\"$SDE\"\n          make strip ARCH=$BINARY COMP=$COMP\n          RUN_PREFIX=\"$SDE\" ../tests/signature.sh $benchref\n          mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT\n\n      - name: Remove non src files\n        run: git clean -fx\n\n      - name: Upload artifact for (pre)-release\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}\n          path: |\n             .\n             !.git\n             !.output\n"
  },
  {
    "path": ".github/workflows/games.yml",
    "content": "# This workflow will play games with a debug enabled SF using the PR\n\nname: Games\non:\n  workflow_call:\njobs:\n  Matetrack:\n    name: Games\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout SF repo \n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          path: Stockfish\n          persist-credentials: false\n\n      - name: build debug enabled version of SF\n        working-directory: Stockfish/src\n        run: make -j build debug=yes\n\n      - name: Checkout fastchess repo\n        uses: actions/checkout@v4\n        with:\n          repository: Disservin/fastchess\n          path: fastchess\n          ref: 894616028492ae6114835195f14a899f6fa237d3\n          persist-credentials: false\n\n      - name: fastchess build\n        working-directory: fastchess\n        run: make -j\n\n      - name: Run games\n        working-directory: fastchess\n        run: |\n          ./fastchess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\\\n               -engine name=sf1 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\\\n               -engine name=sf2 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\\\n               -ratinginterval 1 -report penta=true -each proto=uci tc=4+0.04 -log file=fast.log | tee fast.out\n          cat fast.log\n          ! grep \"Assertion\" fast.log > /dev/null\n          ! grep \"disconnect\" fast.out > /dev/null\n"
  },
  {
    "path": ".github/workflows/iwyu.yml",
    "content": "name: IWYU\non:\n  workflow_call:\njobs:\n  Analyzers:\n    name: Check includes\n    runs-on: ubuntu-22.04\n    defaults:\n      run:\n        working-directory: Stockfish/src\n        shell: bash\n    steps:\n      - name: Checkout Stockfish\n        uses: actions/checkout@v4\n        with:\n          path: Stockfish\n          persist-credentials: false\n\n      - name: Checkout include-what-you-use\n        uses: actions/checkout@v4\n        with:\n          repository: include-what-you-use/include-what-you-use\n          ref: f25caa280dc3277c4086ec345ad279a2463fea0f\n          path: include-what-you-use\n          persist-credentials: false\n\n      - name: Download required linux packages\n        run: |\n          sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'\n          wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -\n          sudo apt update\n          sudo apt install -y libclang-17-dev clang-17 libc++-17-dev\n\n      - name: Set up include-what-you-use\n        run: |\n          mkdir build && cd build\n          cmake -G \"Unix Makefiles\" -DCMAKE_PREFIX_PATH=\"/usr/lib/llvm-17\" ..\n          sudo make install\n        working-directory: include-what-you-use\n\n      - name: Check include-what-you-use\n        run: include-what-you-use --version\n\n      - name: Check includes\n        run: >\n          make analyze\n          COMP=clang\n          CXX=include-what-you-use\n          CXXFLAGS=\"-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/ci/libcxx17.imp' -Xiwyu --error\"\n"
  },
  {
    "path": ".github/workflows/matetrack.yml",
    "content": "# This workflow will run matetrack on the PR\n\nname: Matetrack\non:\n  workflow_call:\njobs:\n  Matetrack:\n    name: Matetrack\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout SF repo \n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          path: Stockfish\n          persist-credentials: false\n\n      - name: build SF\n        working-directory: Stockfish/src\n        run: make -j profile-build\n\n      - name: Checkout matetrack repo\n        uses: actions/checkout@v4\n        with:\n          repository: vondele/matetrack\n          path: matetrack\n          ref: 6c8405fac9028ca66a077f5c96c918fec0ef8d1d\n          persist-credentials: false\n\n      - name: matetrack install deps\n        working-directory: matetrack\n        run: pip install -r requirements.txt\n\n      - name: cache syzygy\n        id: cache-syzygy\n        uses: actions/cache@v4\n        with:\n           path: |\n              matetrack/3-4-5-wdl/\n              matetrack/3-4-5-dtz/\n           key: key-syzygy\n\n      - name: download syzygy 3-4-5 if needed\n        working-directory: matetrack\n        if: steps.cache-syzygy.outputs.cache-hit != 'true'\n        run: |\n          wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject=\"index.html*\" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-wdl/\n          wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject=\"index.html*\" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-dtz/\n\n      - name: Run matetrack th1\n        working-directory: matetrack\n        run: |\n          python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheck1.out\n          ! grep \"issues were detected\" matecheck1.out > /dev/null\n\n      - name: Run matetrack th4\n        working-directory: matetrack\n        run: |\n          python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheck4.out\n          ! grep \"issues were detected\" matecheck4.out > /dev/null\n\n      - name: Run matetrack th4 gameplay\n        working-directory: matetrack\n        run: |\n          python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --time 3 --timeinc 0.01 --threads 4 | tee matecheck4g.out\n          ! grep \"issues were detected\" matecheck4g.out > /dev/null\n\n      - name: Run matetrack th4 go-mate\n        working-directory: matetrack\n        run: |\n          head -n 21 matetrack.epd > gomates.epd\n          head -n 44 matedtrack.epd >> gomates.epd\n          head -n 18 mates2000.epd >> gomates.epd\n          python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile gomates.epd --mate 0 --threads 4 | tee matecheck4gm.out\n          ! grep \"issues were detected\" matecheck4gm.out > /dev/null\n          total=$(grep \"Total FENs:\" matecheck4gm.out | awk '{print $3}')\n          bmates=$(grep \"Best mates:\" matecheck4gm.out | awk '{print $3}')\n          if [ $bmates -ne $total ]; then\n            echo \"At least one go-mate search did not yield expected mate, see matecheck4gm.out\" >&2\n            exit 1\n          fi\n\n      - name: Run matetrack th1 with --syzygy50MoveRule false\n        working-directory: matetrack\n        run: |\n          grep 5men cursed.epd > cursed5.epd\n          python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed1.out\n          ! grep \"issues were detected\" matecheckcursed1.out > /dev/null\n\n      - name: Run matetrack th4 with --syzygy50MoveRule false\n        working-directory: matetrack\n        run: |\n          grep 5men cursed.epd > cursed5.epd\n          python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --threads 4 --syzygy50MoveRule false | tee matecheckcursed4.out\n          ! grep \"issues were detected\" matecheckcursed4.out > /dev/null\n\n      - name: Verify mate and TB win count for matecheckcursed[14].out\n        working-directory: matetrack\n        run: |\n          mates=$(grep \"Found mates:\" matecheckcursed1.out | awk '{print $3}')\n          tbwins=$(grep \"Found TB wins:\" matecheckcursed1.out | awk '{print $4}')\n          if [ $(($mates + $tbwins)) -ne 32 ]; then\n            echo \"Sum of mates and TB wins is not 32 in matecheckcursed1.out\" >&2\n            exit 1\n          fi\n          mates=$(grep \"Found mates:\" matecheckcursed4.out | awk '{print $3}')\n          tbwins=$(grep \"Found TB wins:\" matecheckcursed4.out | awk '{print $4}')\n          if [ $(($mates + $tbwins)) -ne 32 ]; then\n            echo \"Sum of mates and TB wins is not 32 in matecheckcursed4.out\" >&2\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/sanitizers.yml",
    "content": "name: Sanitizers\non:\n  workflow_call:\njobs:\n  Test-under-sanitizers:\n    name: ${{ matrix.sanitizers.name }}\n    runs-on: ${{ matrix.config.os }}\n    env:\n      COMPCXX: ${{ matrix.config.compiler }}\n      COMP: ${{ matrix.config.comp }}\n      CXXFLAGS: \"-Werror\"\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n          - name: Ubuntu 22.04 GCC\n            os: ubuntu-22.04\n            compiler: g++\n            comp: gcc\n            shell: bash\n        sanitizers:\n          - name: Run with thread sanitizer\n            make_option: sanitize=thread\n            cxx_extra_flags: \"\"\n            instrumented_option: sanitizer-thread\n          - name: Run with UB sanitizer\n            make_option: sanitize=undefined\n            cxx_extra_flags: \"\"\n            instrumented_option: sanitizer-undefined\n          - name: Run under valgrind\n            make_option: \"\"\n            cxx_extra_flags: \"\"\n            instrumented_option: valgrind\n          - name: Run under valgrind-thread\n            make_option: \"\"\n            cxx_extra_flags: \"\"\n            instrumented_option: valgrind-thread\n          - name: Run non-instrumented\n            make_option: \"\"\n            cxx_extra_flags: \"\"\n            instrumented_option: none\n          - name: Run with glibcxx assertions\n            make_option: \"\"\n            cxx_extra_flags: -D_GLIBCXX_ASSERTIONS\n            instrumented_option: none\n    defaults:\n      run:\n        working-directory: src\n        shell: ${{ matrix.config.shell }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Download required linux packages\n        run: |\n          sudo apt update\n          sudo apt install expect valgrind g++-multilib\n\n      - name: Download the used network from the fishtest framework\n        run: make net\n\n      - name: Check compiler\n        run: $COMPCXX -v\n\n      - name: Test help target\n        run: make help\n\n      - name: Check git\n        run: git --version\n\n      # Since Linux Kernel 6.5 we are getting false positives from the ci,\n      # lower the ALSR entropy to disable ALSR, which works as a temporary workaround.\n      # https://github.com/google/sanitizers/issues/1716\n      # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762\n\n      - name: Lower ALSR entropy\n        run: sudo sysctl -w vm.mmap_rnd_bits=28\n\n      # Sanitizers\n\n      - name: ${{ matrix.sanitizers.name }}\n        run: |\n          export CXXFLAGS=\"-O1 -fno-inline ${{ matrix.sanitizers.cxx_extra_flags }}\"\n          make clean\n          make -j4 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null\n          python3 ../tests/instrumented.py --${{ matrix.sanitizers.instrumented_option }} ./stockfish\n"
  },
  {
    "path": ".github/workflows/stockfish.yml",
    "content": "name: Stockfish\non:\n  push:\n    tags:\n      - \"*\"\n    branches:\n      - master\n      - tools\n      - github_ci\n  pull_request:\n    branches:\n      - master\n      - tools\njobs:\n  Prerelease:\n    if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'))\n    runs-on: ubuntu-latest\n    needs: [Matrix]\n    permissions:\n      contents: write # For deleting/creating a prerelease\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      # returns null if no pre-release exists\n      - name: Get Commit SHA of Latest Pre-release\n        run: |\n          # Install required packages\n          sudo apt-get update\n          sudo apt-get install -y curl jq\n\n          echo \"COMMIT_SHA_TAG=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))\" >> $GITHUB_ENV\n\n      # delete old previous pre-release and tag\n      - run: gh release delete ${{ env.COMMIT_SHA_TAG }} --cleanup-tag\n        if: env.COMMIT_SHA_TAG != 'null'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      # Make sure that an old ci that still runs on master doesn't recreate a prerelease\n      - name: Check Pullable Commits\n        id: check_commits\n        run: |\n          git fetch\n          CHANGES=$(git rev-list HEAD..origin/master --count)\n          echo \"CHANGES=$CHANGES\" >> $GITHUB_ENV\n\n      - name: Get last commit SHA\n        id: last_commit\n        run: echo \"COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)\" >> $GITHUB_ENV\n\n      - name: Get commit date\n        id: commit_date\n        run: echo \"COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)\" >> $GITHUB_ENV\n\n      - name: Official Release?\n        id: official_release\n        # Check for \"Official release version of Stockfish\" in the commit message\n        run: |\n          if git log -1 --pretty=%B | grep -q \"Official release version of Stockfish\"; then\n            echo \"OFFICIAL_RELEASE=true\" >> $GITHUB_ENV\n          else\n            echo \"OFFICIAL_RELEASE=false\" >> $GITHUB_ENV\n          fi\n\n      # Create a new pre-release, the other upload_binaries.yml will upload the binaries\n      # to this pre-release.\n      - name: Create Prerelease\n        if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false'\n        uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981\n        with:\n          name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}\n          tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}\n          prerelease: true\n  Matrix:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n      arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n      - id: set-matrix\n        run: |\n          TASKS=$(echo $(cat .github/ci/matrix.json) )\n          echo \"MATRIX=$TASKS\" >> $GITHUB_OUTPUT\n      - id: set-arm-matrix\n        run: |\n          TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) )\n          echo \"ARM_MATRIX=$TASKS_ARM\" >> $GITHUB_OUTPUT\n  # Testing Jobs\n  IWYU:\n    uses: ./.github/workflows/iwyu.yml\n  Sanitizers:\n    if: ${{ always() }}\n    uses: ./.github/workflows/sanitizers.yml\n  Tests:\n    if: ${{ always() }}\n    uses: ./.github/workflows/tests.yml\n  Matetrack:\n    if: ${{ always() }}\n    uses: ./.github/workflows/matetrack.yml\n  Games:\n    if: ${{ always() }}\n    uses: ./.github/workflows/games.yml\n  CompilerCheck:\n    if: ${{ always() }}\n    uses: ./.github/workflows/avx2_compilers.yml\n  # Release Jobs\n  Compilation:\n    needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck]\n    uses: ./.github/workflows/compilation.yml\n    with:\n      matrix: ${{ needs.Matrix.outputs.matrix }}\n  ARMCompilation:\n    needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck]\n    uses: ./.github/workflows/arm_compilation.yml\n    with:\n      matrix: ${{ needs.Matrix.outputs.arm_matrix }}\n  Binaries:\n    if: github.repository == 'official-stockfish/Stockfish'\n    needs: [Prerelease, Matrix, Compilation]\n    uses: ./.github/workflows/upload_binaries.yml\n    with:\n      matrix: ${{ needs.Matrix.outputs.matrix }}\n    permissions:\n      contents: write # For deleting/creating a (pre)release\n    secrets:\n      token: ${{ secrets.GITHUB_TOKEN }}\n  ARM_Binaries:\n    if: github.repository == 'official-stockfish/Stockfish'\n    needs: [Prerelease, Matrix, ARMCompilation]\n    uses: ./.github/workflows/upload_binaries.yml\n    with:\n      matrix: ${{ needs.Matrix.outputs.arm_matrix }}\n    permissions:\n      contents: write # For deleting/creating a (pre)release\n    secrets:\n      token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\non:\n  workflow_call:\njobs:\n  Test-Targets:\n    name: ${{ matrix.config.name }}\n    runs-on: ${{ matrix.config.os }}\n    env:\n      COMPCXX: ${{ matrix.config.compiler }}\n      COMP: ${{ matrix.config.comp }}\n      CXXFLAGS: \"-Werror\"\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n          - name: Ubuntu 22.04 GCC\n            os: ubuntu-22.04\n            compiler: g++\n            comp: gcc\n            run_32bit_tests: true\n            run_64bit_tests: true\n            shell: bash\n          - name: Ubuntu 22.04 Clang\n            os: ubuntu-22.04\n            compiler: clang++\n            comp: clang\n            run_32bit_tests: true\n            run_64bit_tests: true\n            shell: bash\n          - name: Android NDK aarch64\n            os: ubuntu-22.04\n            compiler: aarch64-linux-android29-clang++\n            comp: ndk\n            run_armv8_tests: true\n            shell: bash\n          - name: Android NDK arm\n            os: ubuntu-22.04\n            compiler: armv7a-linux-androideabi29-clang++\n            comp: ndk\n            run_armv7_tests: true\n            shell: bash\n          # Currently segfaults in the CI unrelated to a Stockfish change.\n          # - name: Linux GCC riscv64\n          #   os: ubuntu-22.04\n          #   compiler: g++\n          #   comp: gcc\n          #   run_riscv64_tests: true\n          #   base_image: \"riscv64/alpine:edge\"\n          #   platform: linux/riscv64\n          #   shell: bash\n          - name: Linux GCC ppc64\n            os: ubuntu-22.04\n            compiler: g++\n            comp: gcc\n            run_ppc64_tests: true\n            base_image: \"ppc64le/alpine:latest\"\n            platform: linux/ppc64le\n            shell: bash\n          - name: macOS 15 Apple Clang\n            os: macos-15-intel\n            compiler: clang++\n            comp: clang\n            run_64bit_tests: true\n            shell: bash\n          - name: macOS 15 Apple Clang M1\n            os: macos-15\n            compiler: clang++\n            comp: clang\n            run_64bit_tests: false\n            run_m1_tests: true\n            shell: bash\n          - name: macOS 15 GCC 11\n            os: macos-15-intel\n            compiler: g++-11\n            comp: gcc\n            run_64bit_tests: true\n            shell: bash\n          - name: Windows 2022 Mingw-w64 GCC x86_64\n            os: windows-2022\n            compiler: g++\n            comp: mingw\n            run_64bit_tests: true\n            msys_sys: mingw64\n            msys_env: x86_64-gcc\n            shell: msys2 {0}\n          - name: Windows 2022 Mingw-w64 GCC i686\n            os: windows-2022\n            compiler: g++\n            comp: mingw\n            run_32bit_tests: true\n            msys_sys: mingw32\n            msys_env: i686-gcc\n            shell: msys2 {0}\n          - name: Windows 2022 Mingw-w64 Clang x86_64\n            os: windows-2022\n            compiler: clang++\n            comp: clang\n            run_64bit_tests: true\n            msys_sys: clang64\n            msys_env: clang-x86_64-clang\n            shell: msys2 {0}\n          - name: Windows 11 Mingw-w64 Clang arm64\n            os: windows-11-arm\n            compiler: clang++\n            comp: clang\n            run_armv8_tests: true\n            msys_sys: clangarm64\n            msys_env: clang-aarch64-clang\n            shell: msys2 {0}\n    defaults:\n      run:\n        working-directory: src\n        shell: ${{ matrix.config.shell }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n\n      - name: Download required linux packages\n        if: runner.os == 'Linux'\n        run: |\n          sudo apt update\n          sudo apt install expect valgrind g++-multilib qemu-user-static\n\n      - name: Install NDK\n        if: runner.os == 'Linux'\n        run: |\n          if [ $COMP == ndk ]; then\n            NDKV=\"27.2.12479018\"\n            ANDROID_ROOT=/usr/local/lib/android\n            ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk\n            SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager\n            echo \"y\" | $SDKMANAGER \"ndk;$NDKV\"\n            ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV\n            ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin\n            echo \"ANDROID_NDK_BIN=$ANDROID_NDK_BIN\" >> $GITHUB_ENV\n          fi\n\n      - name: Set up QEMU\n        if: matrix.config.base_image\n        uses: docker/setup-qemu-action@v3\n\n      - name: Set up Docker Buildx\n        if: matrix.config.base_image\n        uses: docker/setup-buildx-action@v3\n\n      - name: Build Docker container\n        if: matrix.config.base_image\n        run: |\n          docker buildx build --platform ${{ matrix.config.platform }} --load -t sf_builder - << EOF\n          FROM ${{ matrix.config.base_image }}\n          WORKDIR /app\n          RUN apk update && apk add make g++\n          CMD [\"sh\", \"src/script.sh\"]\n          EOF\n\n      - name: Download required macOS packages\n        if: runner.os == 'macOS'\n        run: brew install coreutils gcc@11\n\n      - name: Setup msys and install required packages\n        if: runner.os == 'Windows'\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: ${{ matrix.config.msys_sys }}\n          install: mingw-w64-${{ matrix.config.msys_env }} make git expect\n\n      - name: Download the used network from the fishtest framework\n        run: make net\n\n      - name: Extract the bench number from the commit history\n        run: |\n          for hash in $(git rev-list -100 HEAD); do\n            benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\\b[Bb]ench[ :]\\+[1-9][0-9]\\{5,7\\}\\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true\n          done\n          [[ -n \"$benchref\" ]] && echo \"benchref=$benchref\" >> $GITHUB_ENV && echo \"From commit: $hash\" && echo \"Reference bench: $benchref\" || echo \"No bench found\"\n\n      - name: Check compiler\n        run: |\n          if [ -z \"${{ matrix.config.base_image }}\" ]; then\n            if [ $COMP == ndk ]; then\n              export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n            fi\n            $COMPCXX -v\n          else\n            echo \"$COMPCXX -v\" > script.sh\n            docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder\n          fi\n\n      - name: Test help target\n        run: make help\n\n      - name: Check git\n        run: git --version\n\n      # x86-32 tests\n\n      - name: Test debug x86-32 build\n        if: matrix.config.run_32bit_tests\n        run: |\n          export CXXFLAGS=\"-Werror -D_GLIBCXX_DEBUG\"\n          make clean\n          make -j4 ARCH=x86-32 optimize=no debug=yes build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-32 build\n        if: matrix.config.run_32bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-32 build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-32-sse41-popcnt build\n        if: matrix.config.run_32bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-32-sse41-popcnt build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-32-sse2 build\n        if: matrix.config.run_32bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-32-sse2 build\n          ../tests/signature.sh $benchref\n\n      - name: Test general-32 build\n        if: matrix.config.run_32bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=general-32 build\n          ../tests/signature.sh $benchref\n\n      # x86-64 tests\n\n      - name: Test debug x86-64-avx2 build\n        if: matrix.config.run_64bit_tests\n        run: |\n          export CXXFLAGS=\"-Werror -D_GLIBCXX_DEBUG\"\n          make clean\n          make -j4 ARCH=x86-64-avx2 optimize=no debug=yes build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-64-bmi2 build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-bmi2 build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-64-avx2 build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-avx2 build\n          ../tests/signature.sh $benchref\n\n      # Test a deprecated arch\n      - name: Test x86-64-modern build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-modern build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-64-sse41-popcnt build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-sse41-popcnt build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-64-ssse3 build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-ssse3 build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-64-sse3-popcnt build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-sse3-popcnt build\n          ../tests/signature.sh $benchref\n\n      - name: Test x86-64 build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64 build\n          ../tests/signature.sh $benchref\n\n      - name: Test general-64 build\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=general-64 build\n          ../tests/signature.sh $benchref\n\n      - name: Test apple-silicon build\n        if: matrix.config.run_m1_tests\n        run: |\n          make clean\n          make -j4 ARCH=apple-silicon build\n          ../tests/signature.sh $benchref\n\n      # armv8 tests\n\n      - name: Test armv8 build\n        if: matrix.config.run_armv8_tests\n        run: |\n          if [ $COMP == ndk ]; then\n            export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n            export LDFLAGS=\"-static -Wno-unused-command-line-argument\"\n          fi\n          make clean\n          make -j4 ARCH=armv8 build\n          ../tests/signature.sh $benchref\n\n      - name: Test armv8-dotprod build\n        if: matrix.config.run_armv8_tests\n        run: |\n          if [ $COMP == ndk ]; then\n            export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n            export LDFLAGS=\"-static -Wno-unused-command-line-argument\"\n          fi\n          make clean\n          make -j4 ARCH=armv8-dotprod build\n          ../tests/signature.sh $benchref\n\n      # armv7 tests\n\n      - name: Test armv7 build\n        if: matrix.config.run_armv7_tests\n        run: |\n          export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n          export LDFLAGS=\"-static -Wno-unused-command-line-argument\"\n          make clean\n          make -j4 ARCH=armv7 build\n          ../tests/signature.sh $benchref\n\n      - name: Test armv7-neon build\n        if: matrix.config.run_armv7_tests\n        run: |\n          export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH\n          export LDFLAGS=\"-static -Wno-unused-command-line-argument\"\n          make clean\n          make -j4 ARCH=armv7-neon build\n          ../tests/signature.sh $benchref\n\n      # riscv64 tests\n\n      - name: Test riscv64 build\n        if: matrix.config.run_riscv64_tests\n        run: |\n          echo \"cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build\" > script.sh\n          docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder\n          ../tests/signature.sh $benchref\n\n      # ppc64 tests\n\n      - name: Test ppc64 build\n        if: matrix.config.run_ppc64_tests\n        run: |\n          echo \"cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=ppc-64 build\" > script.sh\n          docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder\n          ../tests/signature.sh $benchref\n\n      # Other tests\n\n      - name: Check perft and search reproducibility\n        if: matrix.config.run_64bit_tests\n        run: |\n          make clean\n          make -j4 ARCH=x86-64-avx2 build\n          ../tests/perft.sh\n          ../tests/reprosearch.sh\n"
  },
  {
    "path": ".github/workflows/upload_binaries.yml",
    "content": "name: Upload Binaries\non:\n  workflow_call:\n    inputs:\n      matrix:\n        type: string\n        required: true\n    secrets:\n      token:\n        required: true\n\njobs:\n  Artifacts:\n    name: ${{ matrix.config.name }} ${{ matrix.binaries }}\n    runs-on: ubuntu-latest\n    env:\n      EXT: ${{ matrix.config.ext }}\n      NAME: ${{ matrix.config.simple_name }}\n      BINARY: ${{ matrix.binaries }}\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(inputs.matrix) }}\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Download artifact from compilation\n        uses: actions/download-artifact@v4\n        with:\n          name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}\n          path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}\n\n      - name: Create Package\n        run: |\n          mkdir stockfish\n\n      - name: Download wiki\n        run: |\n          git clone https://github.com/official-stockfish/Stockfish.wiki.git wiki\n          rm -rf wiki/.git\n          mv wiki stockfish/\n\n      - name: Copy files\n        run: |\n          mv \"${{ matrix.config.simple_name }} ${{ matrix.binaries }}\" stockfish-workflow\n          cd stockfish-workflow\n          cp -r src ../stockfish/\n          cp -r scripts ../stockfish/\n          cp stockfish-$NAME-$BINARY$EXT ../stockfish/\n          cp \"Top CPU Contributors.txt\" ../stockfish/\n          cp Copying.txt ../stockfish/\n          cp AUTHORS ../stockfish/\n          cp CITATION.cff ../stockfish/\n          cp README.md ../stockfish/\n          cp CONTRIBUTING.md ../stockfish/\n\n      - name: Create tar\n        if: ${{ !startsWith(matrix.config.os, 'windows') }}\n        run: |\n          chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT\n          tar -cvf stockfish-$NAME-$BINARY.tar stockfish\n\n      - name: Create zip\n        if: ${{ startsWith(matrix.config.os, 'windows') }}\n        run: |\n          zip -r stockfish-$NAME-$BINARY.zip stockfish\n\n      - name: Release\n        if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'\n        uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981\n        with:\n          files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}\n          token: ${{ secrets.token }}\n\n      - name: Get last commit sha\n        id: last_commit\n        run: echo \"COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)\" >> $GITHUB_ENV\n\n      - name: Get commit date\n        id: commit_date\n        run: echo \"COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)\" >> $GITHUB_ENV\n\n      # Make sure that an old ci that still runs on master doesn't recreate a prerelease\n      - name: Check Pullable Commits\n        id: check_commits\n        run: |\n          git fetch\n          CHANGES=$(git rev-list HEAD..origin/master --count)\n          echo \"CHANGES=$CHANGES\" >> $GITHUB_ENV\n\n      - name: Official Release?\n        id: official_release\n        # Check for \"Official release version of Stockfish\" in the commit message\n        run: |\n          if git log -1 --pretty=%B | grep -q \"Official release version of Stockfish\"; then\n            echo \"OFFICIAL_RELEASE=true\" >> $GITHUB_ENV\n          else\n            echo \"OFFICIAL_RELEASE=false\" >> $GITHUB_ENV\n          fi\n\n      - name: Prerelease\n        if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false'\n        continue-on-error: true\n        uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981\n        with:\n          name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}\n          tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}\n          prerelease: true\n          files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}\n          token: ${{ secrets.token }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Files from build\n**/*.o\n**/*.s\nsrc/.depend\n.build_sha.txt\n.build_date.txt\n\n# Built binary\nsrc/stockfish*\nsrc/-lstdc++.res\n\n# Neural network for the NNUE evaluation\n**/*.nnue\n\n# Files generated by the instrumented tests\ntsan.supp\n__pycache__/\ntests/syzygy\ntests/bench_tmp.epd"
  },
  {
    "path": "AUTHORS",
    "content": "# Founders of the Stockfish project and Fishtest infrastructure\nTord Romstad (romstad)\nMarco Costalba (mcostalba)\nJoona Kiiski (zamar)\nGary Linscott (glinscott)\n\n# Authors and inventors of NNUE, training, and NNUE port\nYu Nasu (ynasu87)\nMotohiro Isozaki (yaneurao)\nHisayori Noda (nodchip)\n\n# All other authors of Stockfish code (in alphabetical order)\n87flowers\nAditya (absimaldata)\nAdrian Petrescu (apetresc)\nAhmed Kerimov (wcdbmv)\nAjith Chandy Jose (ajithcj)\nAlain Savard (Rocky640)\nAlayan Feh (Alayan-stk-2)\nAlexander Kure\nAlexander Pagel (Lolligerhans)\nAlfredo Menezes (lonfom169)\nAli AlZhrani (Cooffe)\nAliceRoselia\nAndreas Jan van der Meulen (Andyson007)\nAndreas Matthies (Matthies)\nAndrei Vetrov (proukornew)\nAndrew Grant (AndyGrant)\nAndrey Neporada (nepal)\nAndy Duplain\nAntoine Champion (antoinechampion)\nAram Tumanian (atumanian)\nArjun Temurnikar\nAron Petkovski (fury)\nArseniy Surkov (codedeliveryservice)\nArtem Solopiy (EntityFX)\nAuguste Pop\nBalazs Szilagyi\nBalint Pfliegel\nBaptiste Rech (breatn)\nBen Chaney (Chaneybenjamini)\nBen Koshy (BKSpurgeon)\nBill Henry (VoyagerOne)\nBojun Guo (noobpwnftw, Nooby)\nborg323\nBoštjan Mejak (PedanticHacker)\nbraich\nBrian Sheppard (SapphireBrand, briansheppard-toast)\nBruno de Melo Costa (BM123499)\nBruno Pellanda (pellanda)\nBryan Cross (crossbr)\ncandirufish\nCarlos Esparza Sánchez (ces42)\nChess13234\nChris Bao (sscg13)\nChris Cain (ceebo)\nCiekce\nclefrks\nClemens L. (rn5f107s2)\nCody Ho (aesrentai)\nCSTENTOR\nDale Weiler (graphitemaster)\nDaniel Axtens (daxtens)\nDaniel Dugovic (ddugovic)\nDaniel Monroe (daniel-monroe)\nDaniel Samek (DanSamek)\nDan Schmidt (dfannius)\nDariusz Orzechowski (dorzechowski)\nDavid (dav1312)\nDavid Zar\nDaylen Yang (daylen)\nDeshawn Mohan-Smith (GoldenRare)\nDieter Dobbelaere (ddobbelaere)\nDiscanX\nDominik Schlösser (domschl)\ndouble-beep\nDouglas Matos Gomes (dsmsgms)\nDubslow\nEduardo Cáceres (eduherminio)\nEelco de Groot (KingDefender)\nEhsan Rashid (erashid)\nElvin Liu (solarlight2)\nerbsenzaehler\nErnesto Gatti\nevqsx\nFabian Beuke (madnight)\nFabian Fichter (ianfab)\nFanael Linithien (Fanael)\nfanon\nFauzi Akram Dabat (fauzi2)\nFelix Wittmann\ngamander\nGabriele Lombardo (gabe)\nGahtan Nahdi\nGary Heckman (gheckman)\nGeorge Sobala (gsobala)\ngguliash\nGiacomo Lorenzetti (G-Lorenz)\nGian-Carlo Pascutto (gcp)\nGoh CJ (cj5716)\nGontran Lemaire (gonlem)\nGoodkov Vasiliy Aleksandrovich (goodkov)\nGregor Cramer\nGuardianRM\nGuy Vreuls (gvreuls)\nGünther Demetz (pb00067, pb00068)\nHenri Wiechers\nHiraoka Takuya (HiraokaTakuya)\nhomoSapiensSapiens\nHongzhi Cheng\nIvan Ivec (IIvec)\nJacques B. (Timshel)\nJake Senne (w1wwwwww)\nJakub Ciolek (jake-ciolek)\nJan Ondruš (hxim)\nJared Kish (Kurtbusch, kurt22i)\nJarrod Torriero (DU-jdto)\nJasper Shovelton (Beanie496)\nJean-Francois Romang (jromang)\nJean Gauthier (OuaisBla)\nJekaa\nJerry Donald Watson (jerrydonaldwatson)\njjoshua2\nJonathan Buladas Dumale (SFisGOD)\nJonathan Calovski (Mysseno)\nJonathan McDermid (jonathanmcdermid)\nJoost VandeVondele (vondele)\nJoseph Ellis (jhellis3)\nJoseph R. Prostko\nJost Triller (tsoj)\nJörg Oster (joergoster)\nJulian Willemer (NightlyKing)\njundery\nJustin Blanchard (UncombedCoconut)\nKazuki Yamashita (KazApps)\nKelly Wilson\nKen Takusagawa\nKenneth Lee (kennethlee33)\nkevlu8\nKian E (KJE-98)\nKieren Pearson (KierenP)\nkinderchocolate\nKiran Panditrao (Krgp)\nKirill Zaripov (kokodio)\nKojirion\nKrisztián Peőcz\nKrystian Kuzniarek (kuzkry)\nLeonardo Ljubičić (ICCF World Champion)\nLeonid Pechenik (lp--)\nLi Ying (yl25946)\nLiam Keegan (lkeegan)\nLinmiao Xu (linrock)\nLinus Arver (listx)\nloco-loco\nLub van den Berg (ElbertoOne)\nLuca Brivio (lucabrivio)\nLucas Braesch (lucasart)\nLyudmil Antonov (lantonov)\nMaciej Żenczykowski (zenczykowski)\nMalcolm Campbell (xoto10)\nMark Marosi (Mapika)\nMark Tenzer (31m059)\nmarotear\nMathias Parnaudeau (mparnaudeau)\nMatt Ginsberg (mattginsberg)\nMatthew Lai (matthewlai)\nMatthew Sullivan (Matt14916)\nMax A. (Disservin)\nMaxim Masiutin (maximmasiutin)\nMaxim Molchanov (Maxim)\nMichael An (man)\nMichael Byrne (MichaelB7)\nMichael Chaly (Vizvezdenec)\nMichael Stembera (mstembera)\nMichael Whiteley (protonspring)\nMichel Van den Bergh (vdbergh)\nMiguel Lahoz (miguel-l)\nMikael Bäckman (mbootsector)\nMike Babigian (Farseer)\nMira\nMiroslav Fontán (Hexik)\nMoez Jellouli (MJZ1977)\nMohammed Li (tthsqe12)\nMuzhen J (XInTheDark)\nNathan Rugg (nmrugg)\nNguyen Pham (nguyenpham)\nNicklas Persson (NicklasPersson)\nNick Pelling (nickpelling)\nNicolas Duhamel (nikloskoda)\nNiklas Fiekas (niklasf)\nNikolay Kostov (NikolayIT)\nNorman Schmidt (FireFather)\nnotruck\nNour Berakdar (Nonlinear)\nOfek Shochat (OfekShochat, ghostway)\nOndrej Mosnáček (WOnder93)\nOndřej Mišina (AndrovT)\nOskar Werkelin Ahlin\nÖmer Faruk Tutkun (OmerFarukTutkun)\nPablo Vazquez\nPanthee\nPascal Romaret\nPasquale Pigazzini (ppigazzini)\nPatrick Jansen (mibere)\nPatrick Leonhardt (Yoshie2000)\nPeter Schneider (pschneider1968)\nPeter Zsifkovits (CoffeeOne)\nPieter te Brake (pieterteb)\nPikaCat\nPraveen Kumar Tummala (praveentml)\nProkop Randáček (ProkopRandacek)\nRahul Dsilva (silversolver1)\nRalph Stößer (Ralph Stoesser)\nRaminder Singh\nrenouve\nReuven Peleg (R-Peleg)\nRichard Lloyd (Richard-Lloyd)\nRobert Nürnberg (robertnurnberg)\nRodrigo Exterckötter Tjäder\nRodrigo Roim (roim)\nRonald de Man (syzygy1, syzygy)\nRon Britvich (Britvich)\nrqs\nRui Coelho (ruicoelhopedro)\nrustam-cpp\nRyan Hirsch\nRyan Schmitt\nRyan Takker\nSami Kiminki (skiminki)\nSebastian Buchwald (UniQP)\nSergei Antonov (saproj)\nSergei Ivanov (svivanov72)\nSergio Vieri (sergiovieri)\nsf-x\nShahin M. Shahin (peregrine)\nShane Booth (shane31)\nShawn Varghese (xXH4CKST3RXx)\nShawn Xu (xu-shawn)\nSiad Daboul (Topologist)\nStefan Geschwentner (locutus2)\nStefano Cardanobile (Stefano80)\nStefano Di Martino (StefanoD)\nSteinar Gunderson (sesse)\nStéphane Nicolet (snicolet)\nStephen Touset (stouset)\nStockfisher69\nStyx (styxdoto)\nSyine Mineta (MinetaS)\nTaras Vuk (TarasVuk)\nThanar2\nthaspel\ntheo77186\nTierynnB\nTimothy Herchen (anematode)\nTing-Hsuan Huang (fffelix-huang)\nTobias Steinmann\nTomasz Sobczyk (Sopel97)\nTom Truscott\nTom Vijlbrief (tomtor)\nTorsten Franz (torfranz, tfranzer)\nTorsten Hellwig (Torom)\nTracey Emery (basepr1me)\ntttak\nUnai Corzo (unaiic)\nUri Blass (uriblass)\nVince Negri (cuddlestmonkey)\nViren\nWencey Wang\nWill Miles (willm)\nwindfishballad\nxefoci7612\nXiang Wang (KatyushaScarlet)\nYen-Chao Shen (lemteay)\nZlomenyMesic\nzz4032\n\n# Additionally, we acknowledge the authors and maintainers of fishtest,\n# an amazing and essential framework for Stockfish development!\n#\n# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS\n"
  },
  {
    "path": "CITATION.cff",
    "content": "# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\ncff-version: 1.2.0\ntitle: Stockfish\nmessage: >-\n  Please cite this software using the metadata from this\n  file.\ntype: software\nauthors:\n  - name: The Stockfish developers (see AUTHORS file)\nrepository-code: 'https://github.com/official-stockfish/Stockfish'\nurl: 'https://stockfishchess.org/'\nrepository-artifact: 'https://stockfishchess.org/download/'\nabstract: Stockfish is a free and strong UCI chess engine.\nkeywords:\n  - chess\n  - artificial intelligence (AI)\n  - tree search\n  - alpha-beta search\n  - neural networks (NN)\n  - efficiently updatable neural networks (NNUE)\nlicense: GPL-3.0\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Stockfish\n\nWelcome to the Stockfish project! We are excited that you are interested in\ncontributing. This document outlines the guidelines and steps to follow when\nmaking contributions to Stockfish.\n\n## Table of Contents\n\n- [Building Stockfish](#building-stockfish)\n- [Making Contributions](#making-contributions)\n  - [Reporting Issues](#reporting-issues)\n  - [Submitting Pull Requests](#submitting-pull-requests)\n- [Code Style](#code-style)\n- [Community and Communication](#community-and-communication)\n- [License](#license)\n\n## Building Stockfish\n\nIn case you do not have a C++ compiler installed, you can follow the\ninstructions from our wiki.\n\n- [Ubuntu][ubuntu-compiling-link]\n- [Windows][windows-compiling-link]\n- [macOS][macos-compiling-link]\n\n## Making Contributions\n\n### Reporting Issues\n\nIf you find a bug, please open an issue on the\n[issue tracker][issue-tracker-link]. Be sure to include relevant information\nlike your operating system, build environment, and a detailed description of the\nproblem.\n\n_Please note that Stockfish's development is not focused on adding new features.\nThus any issue regarding missing features will potentially be closed without\nfurther discussion._\n\n### Submitting Pull Requests\n\n- Functional changes need to be tested on fishtest. See\n  [Creating my First Test][creating-my-first-test] for more details.\n  The accompanying pull request should include a link to the test results and\n  the new bench.\n\n- Non-functional changes (e.g. refactoring, code style, documentation) do not\n  need to be tested on fishtest, unless they might impact performance.\n\n- Provide a clear and concise description of the changes in the pull request\n  description.\n\n_First time contributors should add their name to [AUTHORS](./AUTHORS)._\n\n_Stockfish's development is not focused on adding new features. Thus any pull\nrequest introducing new features will potentially be closed without further\ndiscussion._\n\n## Code Style\n\nChanges to Stockfish C++ code should respect our coding style defined by\n[.clang-format](.clang-format). You can format your changes by running\n`make format`. This requires clang-format version 20 to be installed on your system.\n\n## Navigate\n\nFor experienced Git users who frequently use git blame, it is recommended to\nconfigure the blame.ignoreRevsFile setting.\nThis setting is useful for excluding noisy formatting commits.\n\n```bash\ngit config blame.ignoreRevsFile .git-blame-ignore-revs\n```\n\n## Community and Communication\n\n- Join the [Stockfish discord][discord-link] to discuss ideas, issues, and\n  development.\n- Participate in the [Stockfish GitHub discussions][discussions-link] for\n  broader conversations.\n\n## License\n\nBy contributing to Stockfish, you agree that your contributions will be licensed\nunder the GNU General Public License v3.0. See [Copying.txt][copying-link] for\nmore details.\n\nThank you for contributing to Stockfish and helping us make it even better!\n\n[copying-link]:           https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt\n[discord-link]:           https://discord.gg/GWDRS3kU6R\n[discussions-link]:       https://github.com/official-stockfish/Stockfish/discussions/new\n[creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test\n[issue-tracker-link]:     https://github.com/official-stockfish/Stockfish/issues\n[ubuntu-compiling-link]:  https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-1\n[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler\n[macos-compiling-link]:   https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-2\n"
  },
  {
    "path": "Copying.txt",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n  [![Stockfish][stockfish128-logo]][website-link]\n\n  <h3>Stockfish</h3>\n\n  A free and strong UCI chess engine.\n  <br>\n  <strong>[Explore Stockfish docs »][wiki-link]</strong>\n  <br>\n  <br>\n  [Report bug][issue-link]\n  ·\n  [Open a discussion][discussions-link]\n  ·\n  [Discord][discord-link]\n  ·\n  [Blog][website-blog-link]\n\n  [![Build][build-badge]][build-link]\n  [![License][license-badge]][license-link]\n  <br>\n  [![Release][release-badge]][release-link]\n  [![Commits][commits-badge]][commits-link]\n  <br>\n  [![Website][website-badge]][website-link]\n  [![Fishtest][fishtest-badge]][fishtest-link]\n  [![Discord][discord-badge]][discord-link]\n\n</div>\n\n## Overview\n\n[Stockfish][website-link] is a **free and strong UCI chess engine** derived from\nGlaurung 2.1 that analyzes chess positions and computes the optimal moves.\n\nStockfish **does not include a graphical user interface** (GUI) that is required\nto display a chessboard and to make it easy to input moves. These GUIs are\ndeveloped independently from Stockfish and are available online. **Read the\ndocumentation for your GUI** of choice for information about how to use\nStockfish with it.\n\nSee also the Stockfish [documentation][wiki-usage-link] for further usage help.\n\n## Files\n\nThis distribution of Stockfish consists of the following files:\n\n  * [README.md][readme-link], the file you are currently reading.\n\n  * [Copying.txt][license-link], a text file containing the GNU General Public\n    License version 3.\n\n  * [AUTHORS][authors-link], a text file with the list of authors for the project.\n\n  * [src][src-link], a subdirectory containing the full source code, including a\n    Makefile that can be used to compile Stockfish on Unix-like systems.\n\n  * a file with the .nnue extension, storing the neural network for the NNUE\n    evaluation. Binary distributions will have this file embedded.\n\n## Contributing\n\n__See [Contributing Guide](CONTRIBUTING.md).__\n\n### Donating hardware\n\nImproving Stockfish requires a massive amount of testing. You can donate your\nhardware resources by installing the [Fishtest Worker][worker-link] and viewing\nthe current tests on [Fishtest][fishtest-link].\n\n### Improving the code\n\nIn the [chessprogramming wiki][programming-link], many techniques used in\nStockfish are explained with a lot of background information.\nThe [section on Stockfish][programmingsf-link] describes many features\nand techniques used by Stockfish. However, it is generic rather than\nfocused on Stockfish's precise implementation.\n\nThe engine testing is done on [Fishtest][fishtest-link].\nIf you want to help improve Stockfish, please read this [guideline][guideline-link]\nfirst, where the basics of Stockfish development are explained.\n\nDiscussions about Stockfish take place these days mainly in the Stockfish\n[Discord server][discord-link]. This is also the best place to ask questions\nabout the codebase and how to improve it.\n\n## Compiling Stockfish\n\nStockfish has support for 32 or 64-bit CPUs, certain hardware instructions,\nbig-endian machines such as Power PC, and other platforms.\n\nOn Unix-like systems, it should be easy to compile Stockfish directly from the\nsource code with the included Makefile in the folder `src`. In general, it is\nrecommended to run `make help` to see a list of make targets with corresponding\ndescriptions. An example suitable for most Intel and AMD chips:\n\n```\ncd src\nmake -j profile-build\n```\n\nDetailed compilation instructions for all platforms can be found in our\n[documentation][wiki-compile-link]. Our wiki also has information about\nthe [UCI commands][wiki-uci-link] supported by Stockfish.\n\n## Terms of use\n\nStockfish is free and distributed under the\n[**GNU General Public License version 3**][license-link] (GPL v3). Essentially,\nthis means you are free to do almost exactly what you want with the program,\nincluding distributing it among your friends, making it available for download\nfrom your website, selling it (either by itself or as part of some bigger\nsoftware package), or using it as the starting point for a software project of\nyour own.\n\nThe only real limitation is that whenever you distribute Stockfish in some way,\nyou MUST always include the license and the full source code (or a pointer to\nwhere the source code can be found) to generate the exact binary you are\ndistributing. If you make any changes to the source code, these changes must\nalso be made available under GPL v3.\n\n## Acknowledgements\n\nStockfish uses neural networks trained on [data provided by the Leela Chess Zero\nproject][lc0-data-link], which is made available under the [Open Database License][odbl-link] (ODbL).\n\n\n[authors-link]:       https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS\n[build-link]:         https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml\n[commits-link]:       https://github.com/official-stockfish/Stockfish/commits/master\n[discord-link]:       https://discord.gg/GWDRS3kU6R\n[issue-link]:         https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml\n[discussions-link]:   https://github.com/official-stockfish/Stockfish/discussions/new\n[fishtest-link]:      https://tests.stockfishchess.org/tests\n[guideline-link]:     https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test\n[license-link]:       https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt\n[programming-link]:   https://www.chessprogramming.org/Main_Page\n[programmingsf-link]: https://www.chessprogramming.org/Stockfish\n[readme-link]:        https://github.com/official-stockfish/Stockfish/blob/master/README.md\n[release-link]:       https://github.com/official-stockfish/Stockfish/releases/latest\n[src-link]:           https://github.com/official-stockfish/Stockfish/tree/master/src\n[stockfish128-logo]:  https://stockfishchess.org/images/logo/icon_128x128.png\n[uci-link]:           https://backscattering.de/chess/uci/\n[website-link]:       https://stockfishchess.org\n[website-blog-link]:  https://stockfishchess.org/blog/\n[wiki-link]:          https://github.com/official-stockfish/Stockfish/wiki\n[wiki-compile-link]:  https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source\n[wiki-uci-link]:      https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands\n[wiki-usage-link]:    https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage\n[worker-link]:        https://github.com/official-stockfish/fishtest/wiki/Running-the-worker\n[lc0-data-link]:      https://storage.lczero.org/files/training_data\n[odbl-link]:          https://opendatacommons.org/licenses/odbl/odbl-10.txt\n\n[build-badge]:        https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github\n[commits-badge]:      https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge\n[discord-badge]:      https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord\n[fishtest-badge]:     https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished\n[license-badge]:      https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success\n[release-badge]:      https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release\n[website-badge]:      https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org\n"
  },
  {
    "path": "Top CPU Contributors.txt",
    "content": "Contributors to Fishtest with >10,000 CPU hours, as of 2025-12-24.\nThank you!\n\nUsername                                CPU Hours     Games played\n------------------------------------------------------------------\nnoobpwnftw                               42692720       3385202467\nvdv                                      39922218       1277282126\ntechnologov                              26354561       1163905856\nlinrock                                  12002255        785641643\nolafm                                     3030005        197722318\nmlang                                     3026000        200065824\nokrout                                    3020471        268364402\npemo                                      2009761         66178221\nTueRens                                   1956328         83294326\nsebastronomy                              1806628         73868874\ndew                                       1689162        100033738\ngrandphish2                               1479778         92306101\nJojoM                                     1130646         73666860\nrpngn                                     1081976         65292619\noz                                        1029329         69522328\ngvreuls                                    844572         59249068\ntvijlbrief                                 796125         51897690\nmibere                                     703840         46867607\nleszek                                     609538         45301765\ncw                                         519602         34988289\nfastgm                                     503862         30260818\nrobal                                      503208         32703510\nmaximmasiutin                              500174         30818270\nCSU_Dynasty                                481663         31916842\nctoks                                      435431         28551199\ncrunchy                                    427414         27371625\nbcross                                     415724         29061187\nmgrabiak                                   380202         27586936\ntolkki963                                  358623         26373242\nvelislav                                   342588         22140902\nncfish1                                    329039         20624527\nFisherman                                  327231         21829379\nFifis                                      323909         16200123\nSylvain27                                  320732         11671388\nmarrco                                     310446         19587107\nCalis007                                   310201         18969692\nViren6                                     297938          5847458\nDantist                                    296386         18031762\nnaclosagc                                  296040         13865010\nanematode                                  293146          3918134\nmaposora                                   278093         20454200\njavran                                     271465         20506096\ncody                                       258835         13301710\nnordlandia                                 249322         16420192\nGoatminola                                 218812         21411814\nTorom                                      211061          7238522\nglinscott                                  208125         13277240\ndrabel                                     204167         13930674\nWencey                                     203584          9943614\nmhoram                                     202894         12601997\nsschnee                                    201756         12874780\nbking_US                                   198894         11876016\nMineta                                     195312         10337614\nThanar                                     179852         12365359\narmo9494                                   169747         11254404\namicic                                     161636         11290899\nDesolatedDodo                              160605         10392474\nmarkkulix                                  158320         13538874\nspams                                      157128         10319326\nsqrt2                                      147963          9724586\nvdbergh                                    141201          9308647\njcAEie                                     140086         10603658\nCoffeeOne                                  137100          5024116\nmalala                                     136182          8002293\nxoto                                       133759          9159372\nDubslow                                    130795          8609646\nzeryl                                      129154          7911565\ndavar                                      129023          8376525\nDMBK                                       122960          8980062\ncuistot                                    122470          8393996\nmegaman7de                                 122254          8066174\ndsmith                                     122059          7570238\nWolfgang                                   120919          8619168\nCypressChess                               120902          8683904\nsterni1971                                 113754          6054022\nSpprtr                                     113356          8129809\nData                                       113305          8220352\nBrunoBanani                                112960          7436849\nskiminki                                   107583          7218170\nMediumBerry5575                            103884          7830022\nMaZePallas                                 102823          6633619\nYvesKn                                     102213          5098076\nsunu                                       100167          7040199\nthirdlife                                   99182          2246960\nElbertoOne                                  99028          7023771\nTechiePirate                                98957          1249064\nDeepnessFulled                              97313          5083358\nTataneSan                                   97257          4239502\nromangol                                    95662          7784954\nbigpen0r                                    94825          6529241\njojo2357                                    94358          7635486\nmalfoy                                      92712          3392874\nvoidedstarlight                             92582          2342038\nbrabos                                      92118          6186135\nMaxim                                       90818          3283364\npsk                                         89957          5984901\nszupaw                                      89775          7800606\njromang                                     87260          5988073\nracerschmacer                               85805          6122790\nVizvezdenec                                 83761          5344740\n0x3C33                                      82614          5271253\nMarcusTullius                               82359          5335665\nBRAVONE                                     81239          5054681\nrn                                          78566          6000852\nnssy                                        76497          5259388\nwoutboat                                    76379          6031688\nteddybaer                                   75125          5407666\nPking_cda                                   73776          5293873\nyurikvelo                                   73611          5046822\nZirie                                       71260          4602355\nBobo1239                                    70579          4794999\nsolarlight                                  70517          5028306\ndv8silencer                                 70287          3883992\n0x539                                       67147          2918044\nmanap                                       66273          4121774\ntinker                                      64333          4268790\nCounterFlow                                 63914          3775062\nmecevdimitar                                62493          3508750\nDanielMiao1                                 62188          1335664\nqurashee                                    61208          3429862\nAGI                                         58316          4336328\nrobnjr                                      57262          4053117\nFreja                                       56938          3733019\nMaxKlaxxMiner                               56879          3423958\nttruscott                                   56010          3680085\nrkl                                         55132          4164467\njmdana                                      54988          4041917\nnotchris                                    53936          4184018\nrenouve                                     53811          3501516\njibarbosa                                   53504          5110028\nsomethingintheshadows                       52333          4344808\nfinfish                                     51360          3370515\neva42                                       51272          3599691\neastorwest                                  51117          3454811\nsylvek                                      50391          3765170\nrap                                         49985          3219146\npb00067                                     49733          3298934\nGPUex                                       48686          3684998\nOuaisBla                                    48626          3445134\nlemtea                                      48563          1672454\nronaldjerum                                 47654          3240695\nabdicj                                      46740          2709482\nbiffhero                                    46564          3111352\noryx                                        46422          3607582\nVoyagerOne                                  45476          3452465\nrdp65536                                    43948          2881890\nspeedycpu                                   43842          3003273\njbwiebe                                     43305          2805433\ngopeto                                      43046          2821514\nAntihistamine                               41788          2761312\nmhunt                                       41735          2691355\nWoodMan777                                  40858          3491196\nEpic29                                      40771          4067404\ndrauh                                       40419          1634770\nhomyur                                      39893          2850481\ngri                                         39871          2515779\nvidar808                                    39774          1656372\nGaster319                                   38994          3477702\nGarf                                        37741          2999686\nSC                                          37299          2731694\nZacHFX                                      36533          2553282\ncsnodgrass                                  36207          2688994\nicewulf                                     34935          2421834\nstrelock                                    34716          2074055\nJopo12321                                   33921          2531448\nxuhdev                                      33798          3295210\ncsnodgra                                    33780          1446866\nEthanOConnor                                33370          2090311\nslakovv                                     32915          2021889\nIslandLambda                                32667          1659344\nKataiser                                    32477          2688862\nshawnxu                                     32330          2830036\nsrowen                                      32248          1791136\nqgluca                                      31941          2491622\nGelma                                       31771          1551204\nkdave                                       31157          2198362\nmanapbk                                     30987          1810399\nvotoanthuan                                 30691          2460856\nPrcuvu                                      30377          2170122\nanst                                        30301          2190091\njkiiski                                     30136          1904470\nspcc                                        29925          1901692\nhyperbolic.tom                              29840          2017394\nchuckstablers                               29659          2093438\nPyafue                                      29650          1902349\nFlopzee                                     29388          1899905\nhoching                                     29054          2067144\nbelzedar94                                  28846          1811530\nwizardassassin                              28007          2318204\npurpletree                                  27892          2061966\nKyrega                                      27674           963872\njoendter                                    27193          1781570\nDanielv123                                  27132          1043614\nchriswk                                     26902          1868317\nxwziegtm                                    26897          2124586\nspotscene                                   26877          2139674\nachambord                                   26582          1767323\nshreven                                     26448          1703328\nPatrick_G                                   26276          1801617\nyorkman                                     26193          1992080\nols                                         26173          1443517\nwer                                         26136           793146\nSkiff84                                     26083          1135002\nRudyMars                                    25980          2211364\nUlysses                                     25544          1714542\nSFTUser                                     25182          1675689\nnabildanial                                 25068          1531665\nSharaf_DG                                   24765          1786697\nrodneyc                                     24376          1416402\njsys14                                      24297          1721230\nAndreasKrug                                 24235          1934711\nagg177                                      23890          1395014\nDisservin                                   23768          1934576\nEnte                                        23752          1678188\nJanErik                                     23408          1703875\nIsidor                                      23388          1680691\nNorabor                                     23371          1603244\nNullvalue                                   23155          2022752\nfishtester                                  23115          1581502\ncisco2015                                   22920          1763301\nHjax                                        22561          1566151\ngerbil                                      22435          1679842\nSerpensin                                   22396          1861156\nteam-oh                                     22272          1636708\nmkstockfishtester                           22253          2029566\nRoady                                       22220          1465606\ntsim67                                      22077          1353048\nMazeOfGalious                               21978          1629593\nsg4032                                      21950          1643373\nsev                                         21791          1983016\nianh2105                                    21725          1632562\nxor12                                       21628          1680365\ndex                                         21612          1467203\nnesoneg                                     21494          1463031\nuser213718                                  21454          1404128\nsphinx                                      21211          1384728\nqoo_charly_cai                              21136          1514927\njjoshua2                                    21001          1423089\nZake9298                                    20938          1565848\nhorst.prack                                 20878          1465656\n0xB00B1ES                                   20590          1208666\nt3hf1sht3ster                               20544           673134\nDinde                                       20459          1292774\nj3corre                                     20405           941444\nAdrian.Schmidt123                           20316          1281436\nwei                                         19973          1745989\nteenychess                                  19819          1762006\nRickGroszkiewicz                            19749          1913986\nrstoesser                                   19569          1293588\neudhan                                      19274          1283717\nnalanzeyu                                   19211           396674\nvulcan                                      18871          1729392\nKarpovbot                                   18766          1053178\nFarseer                                     18536          1078326\njundery                                     18445          1115855\nsebv15                                      18267          1262588\nwhelanh                                     17887           347974\nville                                       17883          1384026\nchris                                       17698          1487385\npurplefishies                               17595          1092533\ndju                                         17414           981289\niisiraider                                  17275          1049015\nKarby                                       17177          1030688\nfogleman                                    17134           815562\nzhujianzhao                                 17111          1666972\nDragonLord                                  17014          1162790\npirt                                        16993          1274363\nredstone59                                  16842          1461780\nAlb11747                                    16787          1213990\nNaven94                                     16414           951718\nscuzzi                                      16155           995347\nIgorLeMasson                                16064          1147232\nmicpilar                                    15866          1399266\nako027ako                                   15671          1173203\ninfinigon                                   15285           965966\nfishtrawler                                 15205          1436165\nNikolay.IT                                  15154          1068349\nAndrew Grant                                15114           895539\nOssumOpossum                                14857          1007129\nLunaticBFF57                                14525          1190310\nYELNAMRON                                   14480          1141420\nenedene                                     14476           905279\nMooTheCow                                   14459          1023868\nBestBoyBerlin                               14353          1365584\nbpfliegel                                   14233           882523\nmpx86                                       14019           759568\njpulman                                     13982           870599\ngetraideBFF                                 13871          1172846\ncrocogoat                                   13817          1119086\nNesa92                                      13806          1116101\njoster                                      13717           946960\nmbeier                                      13650          1044928\nPablohn26                                   13552          1088532\nwxt9861                                     13550          1312306\nbiniek                                      13469           930029\nDark_wizzie                                 13422          1007152\nJackfish                                    13422           914984\nHongildong                                  13297           699288\nRudolphous                                  13244           883140\nPhoenix17                                   13032          1124066\nMachariel                                   13010           863104\nmabichito                                   12903           749391\nFormazChar                                  12899           980413\nthijsk                                      12886           722107\nAdrianSA                                    12860           804972\nszczur90                                    12720           979324\nmschmidt                                    12644           863193\nkorposzczur                                 12606           838168\nfatmurphy                                   12547           853210\nOakwen                                      12537           856257\nSapphireBrand                               12416           969604\nSnuuka                                      12392           509082\ndeflectooor                                 12386           579392\nmodolief                                    12386           896470\nckaz                                        12273           754644\npgontarz                                    12151           848794\ndbernier                                    12103           860824\nrensonthemove                               11999           971993\nstocky                                      11954           699440\nali-al-zhrani                               11887           836126\n3cho                                        11842          1036786\nCraftyawesome                               11736           832254\ndragon123118                                11578          1044142\nImperiumAeternum                            11482           979142\nlvdv                                        11475           594400\ninfinity                                    11470           727027\nkusihe                                      11468           468450\nvaskoul                                     11446           976902\naga                                         11412           695127\nDef9Infinity                                11408           700682\ntorbjo                                      11395           729145\nThomas A. Anderson                          11372           732094\nsavage84                                    11358           670860\nd64                                         11263           789184\nPoly                                        11172           455568\nenizor                                      11140           630194\nsnicolet                                    11106           869170\ndapper                                      11032           771402\nEthnikoi                                    10993           945906\nKarmatron                                   10871           678306\nzarthus                                     10773          1034536\nOliverClarke                                10696           942654\nOmed                                        10680           669816\ncyberthink                                  10647           936538\nbasepi                                      10637           744851\nmichaelrpg                                  10624           748179\nCubox                                       10621           826448\nGBx3TV                                      10499           343266\nStyx                                        10450           867836\nOIVAS7572                                   10420           995586\nGarruk                                      10365           706465\ndzjp                                        10343           732529\nLorenz                                      10311           886308\nborinot                                     10026           902130\n"
  },
  {
    "path": "scripts/.gitattributes",
    "content": "*.sh text eol=lf\n"
  },
  {
    "path": "scripts/get_native_properties.sh",
    "content": "#!/bin/sh\n\n#\n# Returns the best architecture supported by the CPU (as expected by src/Makefile ARCH=).\n#\n# Output format:\n#   \"<true_arch>\\n\"\n#\n\n# ---------------------------\n# Helpers (POSIX)\n# ---------------------------\n\n# Test hooks (optional env overrides)\n#   GP_UNAME_S: override `uname -s`\n#   GP_UNAME_M: override `uname -m`\n#   GP_CPUINFO: path to a cpuinfo-like fixture file (defaults to /proc/cpuinfo)\n#   GP_BITS: override getconf LONG_BIT result (32/64)\n#   GP_SYSCTL_FEATURES: override sysctl feature strings on Darwin x86_64\n\ncpuinfo_path=${GP_CPUINFO:-/proc/cpuinfo}\n\n# Normalize to a single-line, space-separated string.\nnormalize_ws() {\n\tprintf '%s\\n' \"$*\" | tr '\\n\\t' '  ' | tr -s ' '\n}\n\ndie() {\n\tprintf '%s\\n' \"$*\" >&2\n\texit 1\n}\n\n# Populate $flags from /proc/cpuinfo when available,\n# removing underscores and dots to reduce naming variations.\nget_flags() {\n\tif [ -r \"$cpuinfo_path\" ]; then\n\t\tflags=$(\n\t\t\tawk '\n\t\t\t\t/^flags[ \\t]*:|^Features[ \\t]*:/ {\n\t\t\t\t\tif (!found) {\n\t\t\t\t\t\tgsub(/^flags[ \\t]*:[ \\t]*|^Features[ \\t]*:[ \\t]*|[_.]/, \"\");\n\t\t\t\t\t\tline=$0\n\t\t\t\t\t\tfound=1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tEND { print line }\n\t\t\t' \"$cpuinfo_path\" 2>/dev/null\n\t\t)\n\telse\n\t\tflags=''\n\tfi\n\tflags=$(printf '%s\\n' \"$flags\" | tr '[:upper:]' '[:lower:]')\n\tflags=$(normalize_ws \"$flags\")\n}\n\n# Populate $flags from sysctl on Darwin x86_64.\nget_sysctl_flags() {\n\tif [ -n \"${GP_SYSCTL_FEATURES:-}\" ]; then\n\t\tflags=$(printf '%s\\n' \"$GP_SYSCTL_FEATURES\")\n\telse\n\t\tflags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features 2>/dev/null)\n\tfi\n\tflags=$(printf '%s\\n' \"$flags\" | tr '\\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '._')\n\tflags=$(normalize_ws \"$flags\")\n}\n\n# Best-effort bitness for fallback arch selection.\nget_bits() {\n\tif [ -n \"${GP_BITS:-}\" ]; then\n\t\tbits=$GP_BITS\n\telse\n\t\tbits=$(getconf LONG_BIT 2>/dev/null)\n\tfi\n\tcase $bits in\n\t\t32|64) : ;;\n\t\t*) bits=64 ;;\n\tesac\n}\n\n# Extract ARM architecture level (5/6/7/8/...) from /proc/cpuinfo when present.\nget_arm_arch_level() {\n\t[ -r \"$cpuinfo_path\" ] || return 1\n\tawk '\n\t\t/^CPU architecture[ \\t]*:/{\n\t\t\ts=$0\n\t\t\tsub(/^[^:]*:[ \\t]*/, \"\", s)\n\t\t\tif (match(s, /[0-9]+/)) { print substr(s, RSTART, RLENGTH); exit }\n\t\t}\n\t\t/^Processor[ \\t]*:/{\n\t\t\ts=$0\n\t\t\tsub(/^[^:]*:[ \\t]*/, \"\", s)\n\t\t\tif (match(s, /ARMv[0-9]+/)) { print substr(s, RSTART+4, RLENGTH-4); exit }\n\t\t}\n\t' \"$cpuinfo_path\" 2>/dev/null\n}\n\n# Best-effort ARM architecture level (5/6/7/8/...) with a minimal fallback.\n# Prefer /proc/cpuinfo when available; fall back to uname -m only when it encodes it.\nget_arm_level() {\n\tarm_level=$(get_arm_arch_level || :)\n\tif [ -n \"$arm_level\" ]; then\n\t\tprintf '%s\\n' \"$arm_level\"\n\t\treturn 0\n\tfi\n\tcase ${1:-} in\n\t\tarmv5*) printf '5\\n' ;;\n\t\tarmv6*) printf '6\\n' ;;\n\t\tarmv7*) printf '7\\n' ;;\n\t\tarmv8l) printf '8\\n' ;;\n\t\t*) return 1 ;;\n\tesac\n}\n\n# Whole-token membership check.\nhas_flag() {\n\tcase \" $flags \" in\n\t\t*\" $1 \"*) return 0 ;;\n\t\t*)        return 1 ;;\n\tesac\n}\n\nmatch_flags() {\n\tfor f; do\n\t\thas_flag \"$f\" || return 1\n\tdone\n\treturn 0\n}\n\nmatch_any_flags() {\n\tfor f; do\n\t\thas_flag \"$f\" && return 0\n\tdone\n\treturn 1\n}\n\n# SSE3 is often exposed as \"pni\" in /proc/cpuinfo.\nmatch_sse3() {\n\tmatch_any_flags sse3 pni\n}\n\n# AMD Zen1/2 exclusion logic (used for bmi2 tier).\n# https://web.archive.org/web/20250821132355/https://en.wikichip.org/wiki/amd/cpuid\nis_znver_1_2() (\n\t[ -r \"$cpuinfo_path\" ] || exit 1\n\tvendor_id=$(awk '/^vendor_id/{print $3; exit}' \"$cpuinfo_path\" 2>/dev/null)\n\tcpu_family=$(awk '/^cpu family/{print $4; exit}' \"$cpuinfo_path\" 2>/dev/null)\n\t[ \"$vendor_id\" = \"AuthenticAMD\" ] && [ \"$cpu_family\" = \"23\" ]\n)\n\nmatch_not_znver12_and_flags() {\n\tis_znver_1_2 && return 1\n\tmatch_flags \"$@\"\n}\n\nmatch_sse3_popcnt() {\n\thas_flag popcnt || return 1\n\tmatch_sse3\n}\n\nmatch_true() { return 0; }\n\n# Generic selector: reads lines like \"arch|predicate|arg1 arg2 ...\"\n# First match wins; blank lines and lines starting with '#' are ignored.\nselect_arch_from_table() {\n\twhile IFS='|' read -r arch pred args; do\n\t\t[ -z \"$arch\" ] && continue\n\t\tcase $arch in \\#*) continue ;; esac\n\n\t\tif [ -n \"$args\" ]; then\n\t\t\t# Intentional splitting of args into words for the predicate.\n\t\t\t# shellcheck disable=SC2086\n\t\t\t$pred $args && { printf '%s\\n' \"$arch\"; return 0; }\n\t\telse\n\t\t\t$pred && { printf '%s\\n' \"$arch\"; return 0; }\n\t\tfi\n\tdone\n\treturn 1\n}\n\n# ---------------------------\n# Arch selection (table-driven)\n# ---------------------------\n\nset_arch_loongarch64() {\n\ttrue_arch=$(\n\t\tselect_arch_from_table <<'EOF'\nloongarch64-lasx|match_flags|lasx\nloongarch64-lsx|match_flags|lsx\nloongarch64|match_true|\nEOF\n\t)\n}\n\nset_arch_x86_64() {\n\ttrue_arch=$(\n\t\tselect_arch_from_table <<'EOF'\n# Strongest -> weakest (first match wins)\nx86-64-avx512icl|match_flags|avx512f avx512cd avx512vl avx512dq avx512bw avx512ifma avx512vbmi avx512vbmi2 avx512vpopcntdq avx512bitalg avx512vnni vpclmulqdq gfni vaes\nx86-64-vnni512|match_flags|avx512vnni avx512dq avx512f avx512bw avx512vl\nx86-64-avx512|match_flags|avx512f avx512bw\nx86-64-avxvnni|match_flags|avxvnni\nx86-64-bmi2|match_not_znver12_and_flags|bmi2\nx86-64-avx2|match_flags|avx2\nx86-64-sse41-popcnt|match_flags|sse41 popcnt\nx86-64-ssse3|match_flags|ssse3\nx86-64-sse3-popcnt|match_sse3_popcnt|\nx86-64|match_true|\nEOF\n\t)\n}\n\nset_arch_x86_32() {\n\ttrue_arch=$(\n\t\tselect_arch_from_table <<'EOF'\nx86-32-sse41-popcnt|match_flags|sse41 popcnt\nx86-32-sse2|match_flags|sse2\nx86-32|match_true|\nEOF\n\t)\n}\n\n# PPC64 needs a little parsing to distinguish vsx vs altivec.\nset_arch_ppc_64() {\n\tif [ -r \"$cpuinfo_path\" ] && grep -q \"altivec\" \"$cpuinfo_path\" 2>/dev/null; then\n\t\t# Typical: \"cpu : POWER8E\" (extract the number after POWER)\n\t\tpower=$(\n\t\t\tawk -F: '/^cpu[ \\t]*:/{print $2; exit}' \"$cpuinfo_path\" 2>/dev/null \\\n\t\t\t\t| sed -n 's/.*[Pp][Oo][Ww][Ee][Rr][^0-9]*\\([0-9][0-9]*\\).*/\\1/p'\n\t\t)\n\t\tif [ -z \"$power\" ]; then\n\t\t\tpower=$(\n\t\t\t\tawk -F: '/^cpu[ \\t]*:/{print $2; exit}' \"$cpuinfo_path\" 2>/dev/null \\\n\t\t\t\t\t| sed -n 's/.*\\([0-9][0-9]*\\).*/\\1/p'\n\t\t\t)\n\t\tfi\n\t\tcase $power in\n\t\t\t''|*[!0-9]*)\n\t\t\t\ttrue_arch='ppc-64-altivec'\n\t\t\t\t;;\n\t\t\t*)\n\t\t\t\tif [ \"$power\" -gt 7 ] 2>/dev/null; then\n\t\t\t\t\ttrue_arch='ppc-64-vsx'\n\t\t\t\telse\n\t\t\t\t\ttrue_arch='ppc-64-altivec'\n\t\t\t\tfi\n\t\t\t\t;;\n\t\tesac\n\telse\n\t\ttrue_arch='ppc-64'\n\tfi\n}\n\n# ---------------------------\n# OS / machine dispatch\n# ---------------------------\n\nuname_s=$(uname -s 2>/dev/null)\nuname_m=$(uname -m 2>/dev/null)\nuname_s=${GP_UNAME_S:-$uname_s}\nuname_m=${GP_UNAME_M:-$uname_m}\n\ncase $uname_s in\n\tDarwin)\n\t\tcase $uname_m in\n\t\t\tarm64)\n\t\t\t\ttrue_arch='apple-silicon'\n\t\t\t\t;;\n\t\t\tx86_64)\n\t\t\t\tget_sysctl_flags\n\t\t\t\tset_arch_x86_64\n\t\t\t\t;;\n\t\t\t*)\n\t\t\t\tget_bits\n\t\t\t\tif [ \"$bits\" = \"32\" ]; then\n\t\t\t\t\ttrue_arch='general-32'\n\t\t\t\telse\n\t\t\t\t\ttrue_arch='general-64'\n\t\t\t\tfi\n\t\t\t\t;;\n\t\tesac\n\t\t;;\n\n\tLinux)\n\t\tget_flags\n\t\tcase $uname_m in\n\t\t\tx86_64)\n\t\t\t\tset_arch_x86_64\n\t\t\t\t;;\n\t\t\ti?86)\n\t\t\t\tset_arch_x86_32\n\t\t\t\t;;\n\t\t\tppc64*)\n\t\t\t\tset_arch_ppc_64\n\t\t\t\t;;\n\t\t\taarch64|arm64)\n\t\t\t\ttrue_arch='armv8'\n\t\t\t\tif match_flags asimddp; then\n\t\t\t\t\ttrue_arch='armv8-dotprod'\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\tarmv5*|armv6*|armv7*|armv8l|arm*)\n\t\t\t\tarm_level=$(get_arm_level \"$uname_m\" || :)\n\t\t\t\tcase $arm_level in\n\t\t\t\t\t5|6)\n\t\t\t\t\t\ttrue_arch='general-32'\n\t\t\t\t\t\t;;\n\t\t\t\t\t7|8)\n\t\t\t\t\t\ttrue_arch='armv7'\n\t\t\t\t\t\tif match_flags neon; then\n\t\t\t\t\t\t\ttrue_arch='armv7-neon'\n\t\t\t\t\t\tfi\n\t\t\t\t\t\t;;\n\t\t\t\t\t*)\n\t\t\t\t\t\ttrue_arch='general-32'\n\t\t\t\t\t\tif match_flags neon; then\n\t\t\t\t\t\t\ttrue_arch='armv7-neon'\n\t\t\t\t\t\tfi\n\t\t\t\t\t\t;;\n\t\t\t\tesac\n\t\t\t\t;;\n\t\t\tloongarch64*)\n\t\t\t\tset_arch_loongarch64\n\t\t\t\t;;\n\t\t\triscv64)\n\t\t\t\ttrue_arch='riscv64'\n\t\t\t\t;;\n\t\t\te2k*)\n\t\t\t\ttrue_arch='e2k'\n\t\t\t\t;;\n\t\t\tppc|ppc32|powerpc)\n\t\t\t\ttrue_arch='ppc-32'\n\t\t\t\t;;\n\t\t\t*)\n\t\t\t\t# Don't hard-fail: fall back to general-* so ARCH=native still builds\n\t\t\t\tget_bits\n\t\t\t\tif [ \"$bits\" = \"32\" ]; then\n\t\t\t\t\ttrue_arch='general-32'\n\t\t\t\telse\n\t\t\t\t\ttrue_arch='general-64'\n\t\t\t\tfi\n\t\t\t\t;;\n\t\tesac\n\t\t;;\n\n\tMINGW*ARM64*)\n\t\t# Windows ARM64 (MSYS2/MinGW)\n\t\t# Can't reliably detect ARM CPU features here\n\t\ttrue_arch='armv8-dotprod'\n\t\t;;\n\n\tCYGWIN*|MINGW*|MSYS*)\n\t\t# Windows x86_64 (MSYS2/Cygwin/MinGW)\n\t\tget_flags\n\t\tset_arch_x86_64\n\t\t;;\n\n\t*)\n\t\tdie \"Unsupported system type: $uname_s\"\n\t\t;;\nesac\n\nprintf '%s\\n' \"$true_arch\"\n"
  },
  {
    "path": "scripts/net.sh",
    "content": "#!/bin/sh\n\n# download commands with a 5min time-out to ensure things fail if the server stalls\nwget_or_curl=$( (command -v wget >/dev/null 2>&1 && echo \"wget -qO- --timeout=300 --tries=1\") ||\n  (command -v curl >/dev/null 2>&1 && echo \"curl -skL --max-time 300\"))\n\nsha256sum=$( (command -v shasum >/dev/null 2>&1 && echo \"shasum -a 256\") ||\n  (command -v sha256sum >/dev/null 2>&1 && echo \"sha256sum\"))\n\nif [ -z \"$sha256sum\" ]; then\n  >&2 echo \"sha256sum not found, NNUE files will be assumed valid.\"\nfi\n\nget_nnue_filename() {\n  grep \"$1\" evaluate.h | grep \"#define\" | sed \"s/.*\\(nn-[a-z0-9]\\{12\\}.nnue\\).*/\\1/\"\n}\n\nvalidate_network() {\n  # If no sha256sum command is available, assume the file is always valid.\n  if [ -n \"$sha256sum\" ] && [ -f \"$1\" ]; then\n    if [ \"$1\" != \"nn-$($sha256sum \"$1\" | cut -c 1-12).nnue\" ]; then\n      rm -f \"$1\"\n      return 1\n    fi\n  fi\n}\n\nfetch_network() {\n  _filename=\"$(get_nnue_filename \"$1\")\"\n\n  if [ -z \"$_filename\" ]; then\n    >&2 echo \"NNUE file name not found for: $1\"\n    return 1\n  fi\n\n  if [ -f \"$_filename\" ]; then\n    if validate_network \"$_filename\"; then\n      echo \"Existing $_filename validated, skipping download\"\n      return\n    else\n      echo \"Removing invalid NNUE file: $_filename\"\n    fi\n  fi\n\n  if [ -z \"$wget_or_curl\" ]; then\n    >&2 printf \"%s\\n\" \"Neither wget or curl is installed.\" \\\n      \"Install one of these tools to download NNUE files automatically.\"\n    exit 1\n  fi\n\n  for url in \\\n    \"https://tests.stockfishchess.org/api/nn/$_filename\" \\\n    \"https://github.com/official-stockfish/networks/raw/master/$_filename\"; do\n    echo \"Downloading from $url ...\"\n    if $wget_or_curl \"$url\" >\"$_filename\"; then\n      if validate_network \"$_filename\"; then\n        echo \"Successfully validated $_filename\"\n      else\n        rm -f $_filename\n        echo \"Downloaded $_filename is invalid, and has been removed.\"\n        continue\n      fi\n    else\n      rm -f $_filename\n      echo \"Failed to download from $url\"\n    fi\n    if [ -f \"$_filename\" ]; then\n      return\n    fi\n  done\n\n  # Download was not successful in the loop, return false.\n  >&2 echo \"Failed to download $_filename\"\n  return 1\n}\n\nfetch_network EvalFileDefaultNameBig &&\n  fetch_network EvalFileDefaultNameSmall\n"
  },
  {
    "path": "src/Makefile",
    "content": "# Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n# Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n#\n# Stockfish is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Stockfish is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n\n### ==========================================================================\n### Section 1. General Configuration\n### ==========================================================================\n\n### Establish the operating system name\nKERNEL := $(shell uname -s)\nifeq ($(KERNEL),Linux)\n\tOS := $(shell uname -o)\nendif\n\n### Command prefix to run the built executable (e.g. wine, sde, qemu)\n### Backward compatible alias: WINE_PATH (deprecated)\nifneq ($(strip $(WINE_PATH)),)\nifeq ($(strip $(RUN_PREFIX)),)\nRUN_PREFIX := $(WINE_PATH)\nendif\nifeq ($(MAKELEVEL),0)\nifneq ($(strip $(RUN_PREFIX)),$(strip $(WINE_PATH)))\n$(warning *** Both RUN_PREFIX and WINE_PATH are set; ignoring WINE_PATH. ***)\nelse\n$(warning *** WINE_PATH is deprecated; use RUN_PREFIX instead. ***)\nendif\nendif\nendif\n\n### Target Windows OS\nifeq ($(OS),Windows_NT)\n\tifneq ($(COMP),ndk)\n\t\ttarget_windows = yes\n\tendif\nelse ifeq ($(COMP),mingw)\n\ttarget_windows = yes\n\tifeq ($(RUN_PREFIX),)\n\t\tRUN_PREFIX := $(shell which wine)\n\tendif\nendif\n\n### Executable name\nifeq ($(target_windows),yes)\n\tEXE = stockfish.exe\nelse\n\tEXE = stockfish\nendif\n\n### Installation dir definitions\nPREFIX = /usr/local\nBINDIR = $(PREFIX)/bin\n\n### Built-in benchmark for pgo-builds\nPGOBENCH = $(RUN_PREFIX) ./$(EXE) bench\n\n### Source and object files\nSRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \\\n\tmisc.cpp movegen.cpp movepick.cpp position.cpp \\\n\tsearch.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \\\n\tnnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \\\n\tnnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \\\n\tengine.cpp score.cpp memory.cpp\n\nHEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \\\n\t\tnnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \\\n\t\tnnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \\\n\t\tnnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \\\n\t\tnnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \\\n\t\tposition.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \\\n\t\ttt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h shm.h shm_linux.h\n\nOBJS = $(notdir $(SRCS:.cpp=.o))\n\nVPATH = syzygy:nnue:nnue/features\n\n### ==========================================================================\n### Section 2. High-level Configuration\n### ==========================================================================\n#\n# flag                --- Comp switch        --- Description\n# ----------------------------------------------------------------------------\n#\n# debug = yes/no      --- -DNDEBUG           --- Enable/Disable debug mode\n# sanitize = none/<sanitizer> ... (-fsanitize )\n#                     --- ( undefined )      --- enable undefined behavior checks\n#                     --- ( thread    )      --- enable threading error checks\n#                     --- ( address   )      --- enable memory access checks\n#                     --- ...etc...          --- see compiler documentation for supported sanitizers\n# optimize = yes/no   --- (-O3/-fast etc.)   --- Enable/Disable optimizations\n# arch = (name)       --- (-arch)            --- Target architecture\n# bits = 64/32        --- -DIS_64BIT         --- 64-/32-bit operating system\n# prefetch = yes/no   --- -DUSE_PREFETCH     --- Use prefetch asm-instruction\n# popcnt = yes/no     --- -DUSE_POPCNT       --- Use popcnt asm-instruction\n# pext = yes/no       --- -DUSE_PEXT         --- Use pext x86_64 asm-instruction\n# sse = yes/no        --- -msse              --- Use Intel Streaming SIMD Extensions\n# mmx = yes/no        --- -mmmx              --- Use Intel MMX instructions\n# sse2 = yes/no       --- -msse2             --- Use Intel Streaming SIMD Extensions 2\n# ssse3 = yes/no      --- -mssse3            --- Use Intel Supplemental Streaming SIMD Extensions 3\n# sse41 = yes/no      --- -msse4.1           --- Use Intel Streaming SIMD Extensions 4.1\n# avx2 = yes/no       --- -mavx2             --- Use Intel Advanced Vector Extensions 2\n# avxvnni = yes/no    --- -mavxvnni          --- Use Intel Vector Neural Network Instructions AVX\n# avx512 = yes/no     --- -mavx512bw         --- Use Intel Advanced Vector Extensions 512\n# vnni512 = yes/no    --- -mavx512vnni       --- Use Intel Vector Neural Network Instructions 512\n# avx512icl = yes/no  --- ... multiple ...   --- Use All AVX-512 features available on both Intel Ice Lake and AMD Zen 4\n# altivec = yes/no    --- -maltivec          --- Use PowerPC Altivec SIMD extension\n# vsx = yes/no        --- -mvsx              --- Use POWER VSX SIMD extension\n# neon = yes/no       --- -DUSE_NEON         --- Use ARM SIMD architecture\n# dotprod = yes/no    --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions\n# lsx = yes/no        --- -mlsx              --- Use Loongson SIMD eXtension\n# lasx = yes/no       --- -mlasx             --- use Loongson Advanced SIMD eXtension\n#\n# Note that Makefile is space sensitive, so when adding new architectures\n# or modifying existing flags, you have to make sure there are no extra spaces\n# at the end of the line for flag values.\n#\n# Example of use for these flags:\n# make build ARCH=x86-64-avx512 debug=yes sanitize=\"address undefined\"\n\n\n### 2.1. General and architecture defaults\n\nifeq ($(ARCH),)\n   ARCH = native\nendif\n\nifeq ($(ARCH), native)\n   override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d \" \" -f 1)\nendif\n\n# explicitly check for the list of supported architectures (as listed with make help),\n# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true`\nifeq ($(ARCH), $(filter $(ARCH), \\\n                 x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \\\n                 x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \\\n                 x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \\\n                 armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \\\n                 loongarch64 loongarch64-lsx loongarch64-lasx))\n   SUPPORTED_ARCH=true\nelse\n   SUPPORTED_ARCH=false\nendif\n\noptimize = yes\ndebug = no\nsanitize = none\nbits = 64\nprefetch = no\npopcnt = no\npext = no\nsse = no\nmmx = no\nsse2 = no\nssse3 = no\nsse41 = no\navx2 = no\navxvnni = no\navx512 = no\nvnni512 = no\navx512icl = no\naltivec = no\nvsx = no\nneon = no\ndotprod = no\narm_version = 0\nlsx = no\nlasx = no\nSTRIP = strip\n\nifneq ($(shell which clang-format-20 2> /dev/null),)\n\tCLANG-FORMAT = clang-format-20\nelse\n\tCLANG-FORMAT = clang-format\nendif\n\n### 2.2 Architecture specific\n\nifeq ($(findstring x86,$(ARCH)),x86)\n\n# x86-32/64\n\nifeq ($(findstring x86-32,$(ARCH)),x86-32)\n\tarch = i386\n\tbits = 32\n\tsse = no\n\tmmx = yes\nelse\n\tarch = x86_64\n\tsse = yes\n\tsse2 = yes\nendif\n\nifeq ($(findstring -sse,$(ARCH)),-sse)\n\tsse = yes\nendif\n\nifeq ($(findstring -popcnt,$(ARCH)),-popcnt)\n\tpopcnt = yes\nendif\n\nifeq ($(findstring -mmx,$(ARCH)),-mmx)\n\tmmx = yes\nendif\n\nifeq ($(findstring -sse2,$(ARCH)),-sse2)\n\tsse = yes\n\tsse2 = yes\nendif\n\nifeq ($(findstring -ssse3,$(ARCH)),-ssse3)\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\nendif\n\nifeq ($(findstring -sse41,$(ARCH)),-sse41)\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\nendif\n\nifeq ($(findstring -modern,$(ARCH)),-modern)\n        $(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***)\n        $(shell sleep 5)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\nendif\n\nifeq ($(findstring -avx2,$(ARCH)),-avx2)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tavx2 = yes\nendif\n\nifeq ($(findstring -avxvnni,$(ARCH)),-avxvnni)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tavx2 = yes\n\tavxvnni = yes\n\tpext = yes\nendif\n\nifeq ($(findstring -bmi2,$(ARCH)),-bmi2)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tavx2 = yes\n\tpext = yes\nendif\n\nifeq ($(findstring -avx512,$(ARCH)),-avx512)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tavx2 = yes\n\tpext = yes\n\tavx512 = yes\nendif\n\nifeq ($(findstring -vnni512,$(ARCH)),-vnni512)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tavx2 = yes\n\tpext = yes\n\tavx512 = yes\n\tvnni512 = yes\nendif\n\nifeq ($(findstring -avx512icl,$(ARCH)),-avx512icl)\n\tpopcnt = yes\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tavx2 = yes\n\tpext = yes\n\tavx512 = yes\n\tvnni512 = yes\n\tavx512icl = yes\nendif\n\nifeq ($(sse),yes)\n\tprefetch = yes\nendif\n\n# 64-bit pext is not available on x86-32\nifeq ($(bits),32)\n\tpext = no\nendif\n\nelse\n\n# all other architectures\n\nifeq ($(ARCH),general-32)\n\tarch = any\n\tbits = 32\nendif\n\nifeq ($(ARCH),general-64)\n\tarch = any\nendif\n\nifeq ($(ARCH),armv7)\n\tarch = armv7\n\tprefetch = yes\n\tbits = 32\n\tarm_version = 7\nendif\n\nifeq ($(ARCH),armv7-neon)\n\tarch = armv7\n\tprefetch = yes\n\tpopcnt = yes\n\tneon = yes\n\tbits = 32\n\tarm_version = 7\nendif\n\nifeq ($(ARCH),armv8)\n\tarch = armv8\n\tprefetch = yes\n\tpopcnt = yes\n\tneon = yes\n\tarm_version = 8\nendif\n\nifeq ($(ARCH),armv8-dotprod)\n\tarch = armv8\n\tprefetch = yes\n\tpopcnt = yes\n\tneon = yes\n\tdotprod = yes\n\tarm_version = 8\nendif\n\nifeq ($(ARCH),apple-silicon)\n\tarch = arm64\n\tprefetch = yes\n\tpopcnt = yes\n\tneon = yes\n\tdotprod = yes\n\tarm_version = 8\nendif\n\nifeq ($(ARCH),ppc-32)\n\tarch = ppc\n\tbits = 32\nendif\n\nifeq ($(ARCH),ppc-64)\n\tarch = ppc64\n\tpopcnt = yes\n\tprefetch = yes\nendif\n\nifeq ($(ARCH),ppc-64-altivec)\n\tarch = ppc64\n\tpopcnt = yes\n\tprefetch = yes\n\taltivec = yes\nendif\n\nifeq ($(ARCH),ppc-64-vsx)\n\tarch = ppc64\n\tpopcnt = yes\n\tprefetch = yes\n\tvsx = yes\nendif\n\nifeq ($(findstring e2k,$(ARCH)),e2k)\n\tarch = e2k\n\tmmx = yes\n\tbits = 64\n\tsse = yes\n\tsse2 = yes\n\tssse3 = yes\n\tsse41 = yes\n\tpopcnt = yes\nendif\n\nifeq ($(ARCH),riscv64)\n\tarch = riscv64\nendif\n\nifeq ($(findstring loongarch64,$(ARCH)),loongarch64)\n\tarch = loongarch64\n\tprefetch = yes\n\nifeq ($(findstring -lasx,$(ARCH)),-lasx)\n\tlsx = yes\n\tlasx = yes\nendif\n\nifeq ($(findstring -lsx,$(ARCH)),-lsx)\n\tlsx = yes\nendif\n\nendif\nendif\n\n\n### ==========================================================================\n### Section 3. Low-level Configuration\n### ==========================================================================\n\n### 3.1 Selecting compiler (default = gcc)\nifeq ($(MAKELEVEL),0)\n       export ENV_CXXFLAGS := $(CXXFLAGS)\n       export ENV_DEPENDFLAGS := $(DEPENDFLAGS)\n       export ENV_LDFLAGS := $(LDFLAGS)\nendif\n\nCXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)\nDEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17\nLDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS)\n\nifeq ($(COMP),)\n\tCOMP=gcc\nendif\n\nifeq ($(COMP),gcc)\n\tcomp=gcc\n\tCXX=g++\n\tCXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations\n\n\tifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))\n\t\tifeq ($(OS),Android)\n\t\t\tCXXFLAGS += -m$(bits)\n\t\t\tLDFLAGS += -m$(bits)\n\t\tendif\n\t\tifeq ($(ARCH),riscv64)\n\t\t\tCXXFLAGS += -latomic\n\t\tendif\n\telse ifeq ($(arch),loongarch64)\n\t\tCXXFLAGS += -latomic\n\telse\n\t\tCXXFLAGS += -m$(bits)\n\t\tLDFLAGS += -m$(bits)\n\tendif\n\n\tifeq ($(arch),$(filter $(arch),armv7))\n\t\tLDFLAGS += -latomic\n\tendif\n\n\tifneq ($(KERNEL),Darwin)\n\t   LDFLAGS += -Wl,--no-as-needed\n\tendif\nendif\n\nifeq ($(target_windows),yes)\n\tLDFLAGS += -static\nendif\n\nifeq ($(COMP),mingw)\n\tcomp=mingw\n\n\tifeq ($(bits),64)\n\t\tifeq ($(shell which x86_64-w64-mingw32-c++-posix 2> /dev/null),)\n\t\t\tCXX=x86_64-w64-mingw32-c++\n\t\telse\n\t\t\tCXX=x86_64-w64-mingw32-c++-posix\n\t\tendif\n\telse\n\t\tifeq ($(shell which i686-w64-mingw32-c++-posix 2> /dev/null),)\n\t\t\tCXX=i686-w64-mingw32-c++\n\t\telse\n\t\t\tCXX=i686-w64-mingw32-c++-posix\n\t\tendif\n\tendif\n\tCXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations\nendif\n\nifeq ($(COMP),icx)\n\tcomp=icx\n\tCXX=icpx\n\tCXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \\\n\t\t-Wconditional-uninitialized -Wabi -Wdeprecated\nendif\n\nifeq ($(COMP),clang)\n\tcomp=clang\n\tCXX=clang++\n\tifeq ($(target_windows),yes)\n\t\tCXX=x86_64-w64-mingw32-clang++\n\tendif\n\n\tCXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \\\n\t            -Wconditional-uninitialized -flax-vector-conversions=none\n\n\tifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)\n\tifeq ($(target_windows),)\n\tifneq ($(RTLIB),compiler-rt)\n\t\tLDFLAGS += -latomic\n\tendif\n\tendif\n\tendif\n\n\tifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))\n\t\tifeq ($(OS),Android)\n\t\t\tCXXFLAGS += -m$(bits)\n\t\t\tLDFLAGS += -m$(bits)\n\t\tendif\n\t\tifeq ($(ARCH),riscv64)\n\t\t\tCXXFLAGS += -latomic\n\t\tendif\n\telse ifeq ($(arch),loongarch64)\n\t\tCXXFLAGS += -latomic\n\telse\n\t\tCXXFLAGS += -m$(bits)\n\t\tLDFLAGS += -m$(bits)\n\tendif\nendif\n\nifeq ($(KERNEL),Darwin)\n\tCXXFLAGS += -mmacosx-version-min=10.15\n\tLDFLAGS += -mmacosx-version-min=10.15\n\tifneq ($(arch),any)\n\t\tCXXFLAGS += -arch $(arch)\n\t\tLDFLAGS += -arch $(arch)\n\tendif\n\tXCRUN = xcrun\nendif\n\n# To cross-compile for Android, use NDK version r27c or later.\nifeq ($(COMP),ndk)\n\tCXXFLAGS += -stdlib=libc++\n\tcomp=clang\n\tifeq ($(arch),armv7)\n\t\tCXX=armv7a-linux-androideabi29-clang++\n\t\tCXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon\n\t\tifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),)\n\t\t\tSTRIP=arm-linux-androideabi-strip\n\t\telse\n\t\t\tSTRIP=llvm-strip\n\t\tendif\n\tendif\n\tifeq ($(arch),armv8)\n\t\tCXX=aarch64-linux-android29-clang++\n\t\tifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),)\n\t\t\tSTRIP=aarch64-linux-android-strip\n\t\telse\n\t\t\tSTRIP=llvm-strip\n\t\tendif\n\tendif\n\tifeq ($(arch),x86_64)\n\t\tCXX=x86_64-linux-android29-clang++\n\t\tifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),)\n\t\t\tSTRIP=x86_64-linux-android-strip\n\t\telse\n\t\t\tSTRIP=llvm-strip\n\t\tendif\n\tendif\n\tLDFLAGS += -static-libstdc++\nendif\n\n### Allow overwriting CXX from command line\nifdef COMPCXX\n\tCXX=$(COMPCXX)\nendif\n\n# llvm-profdata must be version compatible with the specified CXX (be it clang, or the gcc alias)\n# make -j profile-build CXX=clang++-20 COMP=clang\n# Locate the version in the same directory as the compiler used,\n# with fallback to a generic one if it can't be located\n\tLLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))llvm-profdata\n# for icx\nifeq ($(wildcard $(LLVM_PROFDATA)),)\n\tLLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))/compiler/llvm-profdata\nendif\nifeq ($(wildcard $(LLVM_PROFDATA)),)\n\tLLVM_PROFDATA := llvm-profdata\nendif\n\nifeq ($(comp),icx)\n\tprofile_make = icx-profile-make\n\tprofile_use = icx-profile-use\nelse ifeq ($(comp),clang)\n\tprofile_make = clang-profile-make\n\tprofile_use = clang-profile-use\nelse\n\tprofile_make = gcc-profile-make\n\tprofile_use = gcc-profile-use\n\tifeq ($(KERNEL),Darwin)\n\t\tEXTRAPROFILEFLAGS = -fvisibility=hidden\n\tendif\nendif\n\n### Sometimes gcc is really clang\nifeq ($(COMP),gcc)\n\tgccversion := $(shell $(CXX) --version 2>/dev/null)\n\tgccisclang := $(findstring clang,$(gccversion))\n\tifneq ($(gccisclang),)\n\t\tprofile_make = clang-profile-make\n\t\tprofile_use = clang-profile-use\n\telse\n\t\tCXXFLAGS += -Wstack-usage=128000\n\tendif\nendif\n\n### On mingw use Windows threads, otherwise POSIX\nifneq ($(comp),mingw)\n\tCXXFLAGS += -DUSE_PTHREADS\n\t# On Android Bionic's C library comes with its own pthread implementation bundled in\n\tifneq ($(OS),Android)\n\t\t# Haiku has pthreads in its libroot, so only link it in on other platforms\n\t\tifneq ($(KERNEL),Haiku)\n\t\t\tifneq ($(COMP),ndk)\n\t\t\t\tLDFLAGS += -lpthread\n\n\t\t\t\tadd_lrt = yes\n\t\t\t\tifeq ($(target_windows),yes)\n\t\t\t\t\tadd_lrt = no\n\t\t\t\tendif\n\n\t\t\t\tifeq ($(KERNEL),Darwin)\n\t\t\t\t\tadd_lrt = no\n\t\t\t\tendif\n\n\t\t\t\tifeq ($(add_lrt),yes)\n\t\t\t\t\tLDFLAGS += -lrt\n\t\t\t\tendif\n\t\t\tendif\n\t\tendif\n\tendif\nendif\n\n### 3.2.1 Debugging\nifeq ($(debug),no)\n\tCXXFLAGS += -DNDEBUG\nelse\n\tCXXFLAGS += -g\n\tCXXFLAGS += -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG\nendif\n\n### 3.2.2 Debugging with undefined behavior sanitizers\nifneq ($(sanitize),none)\n        CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize))\n        LDFLAGS += $(addprefix -fsanitize=,$(sanitize))\nendif\n\n### 3.3 Optimization\nifeq ($(optimize),yes)\n\n\tCXXFLAGS += -O3 -funroll-loops\n\n\tifeq ($(comp),gcc)\n\t\tifeq ($(OS), Android)\n\t\t\tCXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp\n\t\tendif\n\tendif\n\n\tifeq ($(KERNEL),Darwin)\n\t\tifeq ($(comp),$(filter $(comp),clang icx))\n\t\t\tCXXFLAGS += -mdynamic-no-pic\n\t\tendif\n\n\t\tifeq ($(comp),gcc)\n\t\t\tifneq ($(arch),arm64)\n\t\t\t\tCXXFLAGS += -mdynamic-no-pic\n\t\t\tendif\n\t\tendif\n\tendif\n\n\tifeq ($(comp),clang)\n\t\tclangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.)\n\t\tifeq ($(shell expr $(clangmajorversion) \\< 16),1)\n\t\t\tCXXFLAGS += -fexperimental-new-pass-manager\n\t\tendif\n\tendif\nendif\n\n### 3.4 Bits\nifeq ($(bits),64)\n\tCXXFLAGS += -DIS_64BIT\nendif\n\n### 3.5 prefetch and popcount\nifeq ($(prefetch),yes)\n\tifeq ($(sse),yes)\n\t\tCXXFLAGS += -msse\n\tendif\nelse\n\tCXXFLAGS += -DNO_PREFETCH\nendif\n\nifeq ($(popcnt),yes)\n\tifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64))\n\t\tCXXFLAGS += -DUSE_POPCNT\n\telse\n\t\tCXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT\n\tendif\nendif\n\n### 3.6 SIMD architectures\nifeq ($(avx2),yes)\n\tCXXFLAGS += -DUSE_AVX2\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mavx2 -mbmi\n\tendif\nendif\n\nifeq ($(avxvnni),yes)\n\tCXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mavxvnni\n\tendif\nendif\n\nifeq ($(avx512),yes)\n\tCXXFLAGS += -DUSE_AVX512\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mavx512f -mavx512bw -mavx512dq -mavx512vl\n\tendif\nendif\n\nifeq ($(vnni512),yes)\n\tCXXFLAGS += -DUSE_VNNI\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl\n\tendif\nendif\n\nifeq ($(avx512icl),yes)\n\tCXXFLAGS += -DUSE_AVX512 -DUSE_VNNI -DUSE_AVX512ICL\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mavx512f -mavx512cd -mavx512vl -mavx512dq -mavx512bw -mavx512ifma -mavx512vbmi -mavx512vbmi2 -mavx512vpopcntdq -mavx512bitalg -mavx512vnni -mvpclmulqdq -mgfni -mvaes\n\tendif\nendif\n\nifeq ($(sse41),yes)\n\tCXXFLAGS += -DUSE_SSE41\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -msse4.1\n\tendif\nendif\n\nifeq ($(ssse3),yes)\n\tCXXFLAGS += -DUSE_SSSE3\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mssse3\n\tendif\nendif\n\nifeq ($(sse2),yes)\n\tCXXFLAGS += -DUSE_SSE2\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -msse2\n\tendif\nendif\n\nifeq ($(mmx),yes)\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mmmx\n\tendif\nendif\n\nifeq ($(altivec),yes)\n\tCXXFLAGS += -maltivec\n\tifeq ($(COMP),gcc)\n\t\tCXXFLAGS += -mabi=altivec\n\tendif\nendif\n\nifeq ($(vsx),yes)\n\tCXXFLAGS += -mvsx\n\tifeq ($(COMP),gcc)\n\t\tCXXFLAGS += -DNO_WARN_X86_INTRINSICS -DUSE_SSE2\n\tendif\nendif\n\nifeq ($(neon),yes)\n\tCXXFLAGS += -DUSE_NEON=$(arm_version)\n\tifeq ($(KERNEL),Linux)\n\tifneq ($(COMP),ndk)\n\tifneq ($(arch),armv8)\n\t\tCXXFLAGS += -mfpu=neon\n\tendif\n\tendif\n\tendif\nendif\n\nifeq ($(dotprod),yes)\n\tCXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD\nendif\n\nifeq ($(lasx),yes)\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mlasx\n\tendif\nendif\n\nifeq ($(lsx),yes)\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mlsx\n\tendif\nendif\n\n### 3.7 pext\nifeq ($(pext),yes)\n\tCXXFLAGS += -DUSE_PEXT\n\tifeq ($(comp),$(filter $(comp),gcc clang mingw icx))\n\t\tCXXFLAGS += -mbmi2\n\tendif\nendif\n\n### 3.8.1 Try to include git info for versioning and avoid recompiles if nothing changes\nBUILD_SHA_FILE  := .build_sha.txt\nBUILD_DATE_FILE := .build_date.txt\nGIT_SHA         := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8 || true)\nGIT_DATE        := $(shell git show -s --date=format:%Y%m%d --format=%cd HEAD 2>/dev/null || true)\nCOMPILER_DATE   := $(shell date +%Y%m%d 2>/dev/null)\n\nBUILD_DATE      := $(if $(GIT_DATE),$(GIT_DATE),$(COMPILER_DATE))\n\ndefine cache_file_contents\n$(shell \\\n\tif [ ! -f \"$(1)\" ] || [ \"$$(cat \"$(1)\" 2>/dev/null)\" != \"$(2)\" ]; then \\\n\t\tprintf '%s\\n' \"$(2)\" > \"$(1)\"; \\\n\tfi)\nendef\n\nifneq ($(filter $(MAKECMDGOALS),help strip install clean net objclean profileclean format config-sanity),$(MAKECMDGOALS))\n_ := $(call cache_file_contents,$(BUILD_SHA_FILE),$(GIT_SHA))\n_ := $(call cache_file_contents,$(BUILD_DATE_FILE),$(BUILD_DATE))\nendif\n\n### 3.8.2 Try to include architecture\nifneq ($(ARCH), )\n\tCXXFLAGS += -DARCH=$(ARCH)\nendif\n\n### 3.9 Link Time Optimization\n### This is a mix of compile and link time options because the lto link phase\n### needs access to the optimization flags.\nifeq ($(optimize),yes)\nifeq ($(debug),no)\n\tifneq ($(KERNEL),Darwin)\n\t\tLLD_BIN := $(shell command -v ld.lld 2>/dev/null)\n\t\tifeq ($(LLD_BIN),)\n\t\t\tLLD_BIN := $(shell command -v lld 2>/dev/null)\n\t\tendif\n\t\tifneq ($(LLD_BIN),)\n\t\t\tifeq ($(comp),clang)\n\t\t\t\tLDFLAGS += -fuse-ld=lld\n\t\t\telse ifeq ($(comp),gcc)\n\t\t\t\tifneq ($(gccisclang),)\n\t\t\t\t\tLDFLAGS += -fuse-ld=lld\n\t\t\t\tendif\n\t\t\tendif\n\t\tendif\n\tendif\n\n\tifeq ($(comp),$(filter $(comp),clang icx))\n\t\tCXXFLAGS += -flto=full\n\t\tifeq ($(comp),icx)\n\t\t\tCXXFLAGS += -fwhole-program-vtables\n\t\tendif\n\t\tLDFLAGS += $(CXXFLAGS)\n\n# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be\n# GCC on some systems.\n\telse ifeq ($(comp),gcc)\n\t\tifeq ($(gccisclang),)\n\t\t\tCXXFLAGS += -flto -flto-partition=one\n\t\t\tLDFLAGS += $(CXXFLAGS) -flto=jobserver\n\t\telse\n\t\t\tCXXFLAGS += -flto=full\n\t\t\tLDFLAGS += $(CXXFLAGS)\n\t\tendif\n\n# To use LTO and static linking on Windows,\n# the tool chain requires gcc version 10.1 or later.\n\telse ifeq ($(comp),mingw)\n\t\tCXXFLAGS += -flto -flto-partition=one\n\t\tLDFLAGS += $(CXXFLAGS) -save-temps\n\tendif\nendif\nendif\n\n### 3.10 Android 5 can only run position independent executables. Note that this\n### breaks Android 4.0 and earlier.\nifeq ($(OS), Android)\n\tCXXFLAGS += -fPIE\n\tLDFLAGS += -fPIE -pie\nendif\n\n### 3.11 Inline settings\nifeq ($(optimize), yes)\n\tifeq ($(comp), clang)\n\t\tCXXFLAGS += -Xclang -mllvm -Xclang -inline-threshold=500\n\tendif\nendif\n\n### ==========================================================================\n### Section 4. Public Targets\n### ==========================================================================\n\nhelp:\n\t@echo \"\" && \\\n\techo \"To compile stockfish, type: \" && \\\n\techo \"\" && \\\n\techo \"make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]\" && \\\n\techo \"\" && \\\n\techo \"Supported targets:\" && \\\n\techo \"\" && \\\n\techo \"help                    > Display architecture details\" && \\\n\techo \"profile-build           > standard build with profile-guided optimization\" && \\\n\techo \"build                   > skip profile-guided optimization\" && \\\n\techo \"net                     > Download the default nnue nets\" && \\\n\techo \"strip                   > Strip executable\" && \\\n\techo \"install                 > Install executable\" && \\\n\techo \"clean                   > Clean up\" && \\\n\techo \"\" && \\\n\techo \"Supported archs:\" && \\\n\techo \"\" && \\\n\techo \"native                  > select the best architecture for the host processor (default)\" && \\\n\techo \"x86-64-avx512icl        > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4\" && \\\n\techo \"x86-64-vnni512          > x86 64-bit with vnni 512bit support\" && \\\n\techo \"x86-64-avx512           > x86 64-bit with avx512 support\" && \\\n\techo \"x86-64-avxvnni          > x86 64-bit with vnni 256bit support\" && \\\n\techo \"x86-64-bmi2             > x86 64-bit with bmi2 support\" && \\\n\techo \"x86-64-avx2             > x86 64-bit with avx2 support\" && \\\n\techo \"x86-64-sse41-popcnt     > x86 64-bit with sse41 and popcnt support\" && \\\n\techo \"x86-64-modern           > deprecated, currently x86-64-sse41-popcnt\" && \\\n\techo \"x86-64-ssse3            > x86 64-bit with ssse3 support\" && \\\n\techo \"x86-64-sse3-popcnt      > x86 64-bit with sse3 compile and popcnt support\" && \\\n\techo \"x86-64                  > x86 64-bit generic (with sse2 support)\" && \\\n\techo \"x86-32-sse41-popcnt     > x86 32-bit with sse41 and popcnt support\" && \\\n\techo \"x86-32-sse2             > x86 32-bit with sse2 support\" && \\\n\techo \"x86-32                  > x86 32-bit generic (with mmx compile support)\" && \\\n\techo \"ppc-64                  > PPC 64-bit\" && \\\n\techo \"ppc-64-altivec          > PPC 64-bit with altivec support\" && \\\n\techo \"ppc-64-vsx              > PPC 64-bit with vsx support\" && \\\n\techo \"ppc-32                  > PPC 32-bit\" && \\\n\techo \"armv7                   > ARMv7 32-bit\" && \\\n\techo \"armv7-neon              > ARMv7 32-bit with popcnt and neon\" && \\\n\techo \"armv8                   > ARMv8 64-bit with popcnt and neon\" && \\\n\techo \"armv8-dotprod           > ARMv8 64-bit with popcnt, neon and dot product support\" && \\\n\techo \"e2k                     > Elbrus 2000\" && \\\n\techo \"apple-silicon           > Apple silicon ARM64\" && \\\n\techo \"general-64              > unspecified 64-bit\" && \\\n\techo \"general-32              > unspecified 32-bit\" && \\\n\techo \"riscv64                 > RISC-V 64-bit\" && \\\n\techo \"loongarch64             > LoongArch 64-bit\" && \\\n\techo \"loongarch64-lsx         > LoongArch 64-bit with SIMD eXtension\" && \\\n\techo \"loongarch64-lasx        > LoongArch 64-bit with Advanced SIMD eXtension\" && \\\n\techo \"\" && \\\n\techo \"Supported compilers:\" && \\\n\techo \"\" && \\\n\techo \"gcc                     > GNU compiler (default)\" && \\\n\techo \"mingw                   > GNU compiler with MinGW under Windows\" && \\\n\techo \"clang                   > LLVM Clang compiler\" && \\\n\techo \"icx                     > Intel oneAPI DPC++/C++ Compiler\" && \\\n\techo \"ndk                     > Google NDK to cross-compile for Android\" && \\\n\techo \"\" && \\\n\techo \"Simple examples. If you don't know what to do, you likely want to run one of: \" && \\\n\techo \"\" && \\\n\techo \"make -j profile-build ARCH=x86-64-avx2    # typically a fast compile for common systems \" && \\\n\techo \"make -j profile-build ARCH=x86-64-sse41-popcnt  # A more portable compile for 64-bit systems \" && \\\n\techo \"make -j profile-build ARCH=x86-64         # A portable compile for 64-bit systems \" && \\\n\techo \"\" && \\\n\techo \"Advanced examples, for experienced users: \" && \\\n\techo \"\" && \\\n\techo \"make -j profile-build ARCH=x86-64-avxvnni\" && \\\n\techo \"make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0\" && \\\n\techo \"make -j build ARCH=x86-64-ssse3 COMP=clang\" && \\\n\techo \"\"\nifneq ($(SUPPORTED_ARCH), true)\n\t@echo \"Specify a supported architecture with the ARCH option for more details\"\n\t@echo \"\"\nendif\n\n\n.PHONY: help analyze build profile-build strip install clean net \\\n\tobjclean profileclean config-sanity \\\n\ticx-profile-use icx-profile-make \\\n\tgcc-profile-use gcc-profile-make \\\n\tclang-profile-use clang-profile-make FORCE \\\n\tformat analyze\n\nanalyze: net config-sanity objclean\n\t$(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)\n\nbuild: net config-sanity\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all\n\nprofile-build: net config-sanity objclean profileclean\n\t@echo \"\"\n\t@echo \"Step 1/4. Building instrumented executable ...\"\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)\n\t@echo \"\"\n\t@echo \"Step 2/4. Running benchmark for pgo-build ...\"\n\t$(PGOBENCH) > PGOBENCH.out 2>&1\n\ttail -n 4 PGOBENCH.out\n\t@echo \"\"\n\t@echo \"Step 3/4. Building optimized executable ...\"\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use)\n\t@echo \"\"\n\t@echo \"Step 4/4. Deleting profile data ...\"\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean\n\nstrip:\n\t$(STRIP) $(EXE)\n\ninstall:\n\t-mkdir -p -m 755 $(BINDIR)\n\t-cp $(EXE) $(BINDIR)\n\t$(STRIP) $(BINDIR)/$(EXE)\n\n# clean all\nclean: objclean profileclean\n\t@rm -f .depend *~ core\n\n# clean binaries and objects\nobjclean:\n\t@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o $(BUILD_SHA_FILE) $(BUILD_DATE_FILE)\n\n# clean auxiliary profiling files\nprofileclean:\n\t@rm -rf profdir\n\t@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out\n\t@rm -f stockfish.profdata *.profraw\n\t@rm -f stockfish.*args*\n\t@rm -f stockfish.*lt*\n\t@rm -f stockfish.res\n\t@rm -f ./-lstdc++.res\n\n# evaluation network (nnue)\nnet:\n\t@$(SHELL) ../scripts/net.sh\n\nformat:\n\t$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file\n\n### ==========================================================================\n### Section 5. Private Targets\n### ==========================================================================\n\nall: $(EXE) .depend\n\nconfig-sanity: net\n\t@echo \"\"\n\t@echo \"Config:\" && \\\n\techo \"debug: '$(debug)'\" && \\\n\techo \"sanitize: '$(sanitize)'\" && \\\n\techo \"optimize: '$(optimize)'\" && \\\n\techo \"arch: '$(arch)'\" && \\\n\techo \"bits: '$(bits)'\" && \\\n\techo \"kernel: '$(KERNEL)'\" && \\\n\techo \"os: '$(OS)'\" && \\\n\techo \"prefetch: '$(prefetch)'\" && \\\n\techo \"popcnt: '$(popcnt)'\" && \\\n\techo \"pext: '$(pext)'\" && \\\n\techo \"sse: '$(sse)'\" && \\\n\techo \"mmx: '$(mmx)'\" && \\\n\techo \"sse2: '$(sse2)'\" && \\\n\techo \"ssse3: '$(ssse3)'\" && \\\n\techo \"sse41: '$(sse41)'\" && \\\n\techo \"avx2: '$(avx2)'\" && \\\n\techo \"avxvnni: '$(avxvnni)'\" && \\\n\techo \"avx512: '$(avx512)'\" && \\\n\techo \"vnni512: '$(vnni512)'\" && \\\n\techo \"avx512icl: '$(avx512icl)'\" && \\\n\techo \"altivec: '$(altivec)'\" && \\\n\techo \"vsx: '$(vsx)'\" && \\\n\techo \"neon: '$(neon)'\" && \\\n\techo \"dotprod: '$(dotprod)'\" && \\\n\techo \"arm_version: '$(arm_version)'\" && \\\n\techo \"lsx: '$(lsx)'\" && \\\n\techo \"lasx: '$(lasx)'\" && \\\n\techo \"target_windows: '$(target_windows)'\" && \\\n\techo \"\" && \\\n\techo \"Flags:\" && \\\n\techo \"CXX: $(CXX)\" && \\\n\techo \"CXXFLAGS: $(CXXFLAGS)\" && \\\n\techo \"LDFLAGS: $(LDFLAGS)\" && \\\n\techo \"\" && \\\n\techo \"Testing config sanity. If this fails, try 'make help' ...\" && \\\n\techo \"\" && \\\n\t(test \"$(debug)\" = \"yes\" || test \"$(debug)\" = \"no\") && \\\n\t(test \"$(optimize)\" = \"yes\" || test \"$(optimize)\" = \"no\") && \\\n\t(test \"$(SUPPORTED_ARCH)\" = \"true\") && \\\n\t(test \"$(arch)\" = \"any\" || test \"$(arch)\" = \"x86_64\" || test \"$(arch)\" = \"i386\" || \\\n\t test \"$(arch)\" = \"ppc64\" || test \"$(arch)\" = \"ppc\" || test \"$(arch)\" = \"e2k\" || \\\n\t test \"$(arch)\" = \"armv7\" || test \"$(arch)\" = \"armv8\" || test \"$(arch)\" = \"arm64\" || \\\n\t test \"$(arch)\" = \"riscv64\" || test \"$(arch)\" = \"loongarch64\") && \\\n\t(test \"$(bits)\" = \"32\" || test \"$(bits)\" = \"64\") && \\\n\t(test \"$(prefetch)\" = \"yes\" || test \"$(prefetch)\" = \"no\") && \\\n\t(test \"$(popcnt)\" = \"yes\" || test \"$(popcnt)\" = \"no\") && \\\n\t(test \"$(pext)\" = \"yes\" || test \"$(pext)\" = \"no\") && \\\n\t(test \"$(sse)\" = \"yes\" || test \"$(sse)\" = \"no\") && \\\n\t(test \"$(mmx)\" = \"yes\" || test \"$(mmx)\" = \"no\") && \\\n\t(test \"$(sse2)\" = \"yes\" || test \"$(sse2)\" = \"no\") && \\\n\t(test \"$(ssse3)\" = \"yes\" || test \"$(ssse3)\" = \"no\") && \\\n\t(test \"$(sse41)\" = \"yes\" || test \"$(sse41)\" = \"no\") && \\\n\t(test \"$(avx2)\" = \"yes\" || test \"$(avx2)\" = \"no\") && \\\n\t(test \"$(avx512)\" = \"yes\" || test \"$(avx512)\" = \"no\") && \\\n\t(test \"$(vnni512)\" = \"yes\" || test \"$(vnni512)\" = \"no\") && \\\n\t(test \"$(avx512icl)\" = \"yes\" || test \"$(avx512icl)\" = \"no\") && \\\n\t(test \"$(altivec)\" = \"yes\" || test \"$(altivec)\" = \"no\") && \\\n\t(test \"$(vsx)\" = \"yes\" || test \"$(vsx)\" = \"no\") && \\\n\t(test \"$(neon)\" = \"yes\" || test \"$(neon)\" = \"no\") && \\\n\t(test \"$(lsx)\" = \"yes\" || test \"$(lsx)\" = \"no\") && \\\n\t(test \"$(lasx)\" = \"yes\" || test \"$(lasx)\" = \"no\") && \\\n\t(test \"$(comp)\" = \"gcc\" || test \"$(comp)\" = \"icx\" || test \"$(comp)\" = \"mingw\" || \\\n\t test \"$(comp)\" = \"clang\" || test \"$(comp)\" = \"armv7a-linux-androideabi16-clang\" || \\\n\t test \"$(comp)\" = \"aarch64-linux-android21-clang\")\n\n$(EXE): $(OBJS)\n\t+$(CXX) -o $@ $(OBJS) $(LDFLAGS)\n\n%.o: %.cpp\n\t$(strip $(CXX) $(CPPFLAGS) $(CXXFLAGS)) -c -o $@ $<\n\n# Cache git metadata when available, otherwise cache the compiler date.\nmisc.o: misc.cpp $(BUILD_SHA_FILE) $(BUILD_DATE_FILE)\n\t@sha=\"$$(cat $(BUILD_SHA_FILE))\"; \\\n\tset -- $(CXX) $(CPPFLAGS) $(CXXFLAGS); \\\n\ttest -n \"$$sha\"  && set -- \"$$@\" -DGIT_SHA=$$sha; \\\n\ttest -n \"$(GIT_DATE)\" && set -- \"$$@\" -DGIT_DATE=$(GIT_DATE); \\\n\tset -- \"$$@\" -c $< -o $@; \\\n\tprintf '%s ' \"$$@\"; \\\n\tprintf '\\n'; \\\n\t\"$$@\"\n\nclang-profile-make:\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \\\n\tEXTRACXXFLAGS='-fprofile-generate ' \\\n\tEXTRALDFLAGS=' -fprofile-generate' \\\n\tall\n\nclang-profile-use:\n\t$(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \\\n\tEXTRACXXFLAGS='-fprofile-use=stockfish.profdata' \\\n\tEXTRALDFLAGS='-fprofile-use ' \\\n\tall\n\ngcc-profile-make:\n\t@mkdir -p profdir\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \\\n\tEXTRACXXFLAGS='-fprofile-generate=profdir' \\\n\tEXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \\\n\tEXTRALDFLAGS='-lgcov' \\\n\tall\n\ngcc-profile-use:\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \\\n\tEXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \\\n\tEXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \\\n\tEXTRALDFLAGS='-lgcov' \\\n\tall\n\nicx-profile-make:\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \\\n\tEXTRACXXFLAGS='-fprofile-instr-generate ' \\\n\tEXTRALDFLAGS=' -fprofile-instr-generate' \\\n\tall\n\nicx-profile-use:\n\t$(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw\n\t$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \\\n\tEXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \\\n\tEXTRALDFLAGS='-fprofile-use ' \\\n\tall\n\n.depend: $(SRCS)\n\t-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null\n\nifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean format config-sanity))\n-include .depend\nendif\n"
  },
  {
    "path": "src/benchmark.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"benchmark.h\"\n#include \"numa.h\"\n\n#include <cstdlib>\n#include <fstream>\n#include <iostream>\n#include <vector>\n\nnamespace {\n\n// clang-format off\nconst std::vector<std::string> Defaults = {\n  \"setoption name UCI_Chess960 value false\",\n  \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\",\n  \"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10\",\n  \"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11\",\n  \"4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19\",\n  \"rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6\",\n  \"r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4\",\n  \"r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15\",\n  \"r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13\",\n  \"r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16\",\n  \"4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17\",\n  \"2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11\",\n  \"r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16\",\n  \"3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22\",\n  \"r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18\",\n  \"4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22\",\n  \"3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26\",\n  \"6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1\",\n  \"3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1\",\n  \"2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3\",\n  \"8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1\",\n  \"7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1\",\n  \"8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1\",\n  \"8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1\",\n  \"8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1\",\n  \"8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1\",\n  \"5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1\",\n  \"6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1\",\n  \"1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1\",\n  \"6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1\",\n  \"8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1\",\n  \"5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90\",\n  \"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21\",\n  \"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16\",\n  \"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40\",\n  \"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1\",\n  \"1r6/1P4bk/3qr1p1/N6p/3pp2P/6R1/3Q1PP1/1R4K1 w - - 1 42\",\n\n  // Positions with high numbers of changed threats\n  \"k7/2n1n3/1nbNbn2/2NbRBn1/1nbRQR2/2NBRBN1/3N1N2/7K w - - 0 1\",\n  \"K7/8/8/BNQNQNB1/N5N1/R1Q1q2r/n5n1/bnqnqnbk w - - 0 1\",\n\n  // 5-man positions\n  \"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1\",     // Kc2 - mate\n  \"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1\",      // Na2 - mate\n  \"8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1\",    // draw\n\n  // 6-man positions\n  \"8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1\",   // Re5 - mate\n  \"8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1\",    // Ka2 - mate\n  \"8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1\",  // Nd2 - draw\n\n  // 7-man positions\n  \"8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124\", // Draw\n\n  // Mate and stalemate positions\n  \"6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1\",\n  \"r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1\",\n  \"8/8/8/8/8/6k1/6p1/6K1 w - -\",\n  \"7k/7P/6K1/8/3B4/8/8/8 b - -\",\n\n  // Chess 960\n  \"setoption name UCI_Chess960 value true\",\n  \"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6\",\n  \"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1\",\n  \"setoption name UCI_Chess960 value false\"\n};\n// clang-format on\n\n// clang-format off\n// human-randomly picked 5 games with <60 moves from\n// https://tests.stockfishchess.org/tests/view/665c71f9fd45fb0f907c21e0\n// only moves for one side\nconst std::vector<std::vector<std::string>> BenchmarkPositions = {\n    {\n        \"rnbq1k1r/ppp1bppp/4pn2/8/2B5/2NP1N2/PPP2PPP/R1BQR1K1 b - - 2 8\",\n        \"rnbq1k1r/pp2bppp/4pn2/2p5/2B2B2/2NP1N2/PPP2PPP/R2QR1K1 b - - 1 9\",\n        \"r1bq1k1r/pp2bppp/2n1pn2/2p5/2B1NB2/3P1N2/PPP2PPP/R2QR1K1 b - - 3 10\",\n        \"r1bq1k1r/pp2bppp/2n1p3/2p5/2B1PB2/5N2/PPP2PPP/R2QR1K1 b - - 0 11\",\n        \"r1b2k1r/pp2bppp/2n1p3/2p5/2B1PB2/5N2/PPP2PPP/3RR1K1 b - - 0 12\",\n        \"r1b1k2r/pp2bppp/2n1p3/2p5/2B1PB2/2P2N2/PP3PPP/3RR1K1 b - - 0 13\",\n        \"r1b1k2r/1p2bppp/p1n1p3/2p5/4PB2/2P2N2/PP2BPPP/3RR1K1 b - - 1 14\",\n        \"r1b1k2r/4bppp/p1n1p3/1pp5/P3PB2/2P2N2/1P2BPPP/3RR1K1 b - - 0 15\",\n        \"r1b1k2r/4bppp/p1n1p3/1P6/2p1PB2/2P2N2/1P2BPPP/3RR1K1 b - - 0 16\",\n        \"r1b1k2r/4bppp/2n1p3/1p6/2p1PB2/1PP2N2/4BPPP/3RR1K1 b - - 0 17\",\n        \"r3k2r/3bbppp/2n1p3/1p6/2P1PB2/2P2N2/4BPPP/3RR1K1 b - - 0 18\",\n        \"r3k2r/3bbppp/2n1p3/8/1pP1P3/2P2N2/3BBPPP/3RR1K1 b - - 1 19\",\n        \"1r2k2r/3bbppp/2n1p3/8/1pPNP3/2P5/3BBPPP/3RR1K1 b - - 3 20\",\n        \"1r2k2r/3bbppp/2n1p3/8/2PNP3/2B5/4BPPP/3RR1K1 b - - 0 21\",\n        \"1r2k2r/3bb1pp/2n1pp2/1N6/2P1P3/2B5/4BPPP/3RR1K1 b - - 1 22\",\n        \"1r2k2r/3b2pp/2n1pp2/1N6/1BP1P3/8/4BPPP/3RR1K1 b - - 0 23\",\n        \"1r2k2r/3b2pp/4pp2/1N6/1nP1P3/8/3RBPPP/4R1K1 b - - 1 24\",\n        \"1r5r/3bk1pp/4pp2/1N6/1nP1PP2/8/3RB1PP/4R1K1 b - - 0 25\",\n        \"1r5r/3bk1pp/2n1pp2/1N6/2P1PP2/8/3RBKPP/4R3 b - - 2 26\",\n        \"1r5r/3bk1pp/2n2p2/1N2p3/2P1PP2/6P1/3RBK1P/4R3 b - - 0 27\",\n        \"1r1r4/3bk1pp/2n2p2/1N2p3/2P1PP2/6P1/3RBK1P/R7 b - - 2 28\",\n        \"1r1r4/N3k1pp/2n1bp2/4p3/2P1PP2/6P1/3RBK1P/R7 b - - 4 29\",\n        \"1r1r4/3bk1pp/2N2p2/4p3/2P1PP2/6P1/3RBK1P/R7 b - - 0 30\",\n        \"1r1R4/4k1pp/2b2p2/4p3/2P1PP2/6P1/4BK1P/R7 b - - 0 31\",\n        \"3r4/4k1pp/2b2p2/4P3/2P1P3/6P1/4BK1P/R7 b - - 0 32\",\n        \"3r4/R3k1pp/2b5/4p3/2P1P3/6P1/4BK1P/8 b - - 1 33\",\n        \"8/3rk1pp/2b5/R3p3/2P1P3/6P1/4BK1P/8 b - - 3 34\",\n        \"8/3r2pp/2bk4/R1P1p3/4P3/6P1/4BK1P/8 b - - 0 35\",\n        \"8/2kr2pp/2b5/R1P1p3/4P3/4K1P1/4B2P/8 b - - 2 36\",\n        \"1k6/3r2pp/2b5/RBP1p3/4P3/4K1P1/7P/8 b - - 4 37\",\n        \"8/1k1r2pp/2b5/R1P1p3/4P3/3BK1P1/7P/8 b - - 6 38\",\n        \"1k6/3r2pp/2b5/2P1p3/4P3/3BK1P1/7P/R7 b - - 8 39\",\n        \"1k6/r5pp/2b5/2P1p3/4P3/3BK1P1/7P/5R2 b - - 10 40\",\n        \"1k3R2/6pp/2b5/2P1p3/4P3/r2BK1P1/7P/8 b - - 12 41\",\n        \"5R2/2k3pp/2b5/2P1p3/4P3/r2B2P1/3K3P/8 b - - 14 42\",\n        \"5R2/2k3pp/2b5/2P1p3/4P3/3BK1P1/r6P/8 b - - 16 43\",\n        \"5R2/2k3pp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 18 44\",\n        \"5R2/2k3pp/2b5/2P1p3/4P3/3B1KP1/r6P/8 b - - 20 45\",\n        \"8/2k2Rpp/2b5/2P1p3/4P3/r2B1KP1/7P/8 b - - 22 46\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 24 47\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/3B1KP1/r6P/8 b - - 26 48\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 28 49\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/3BK1P1/r6P/8 b - - 30 50\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/3K3P/8 b - - 32 51\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/2KB2P1/r6P/8 b - - 34 52\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/2K4P/8 b - - 36 53\",\n        \"3k4/5Rpp/2b5/2P1p3/4P3/1K1B2P1/r6P/8 b - - 38 54\",\n        \"3k4/6Rp/2b5/2P1p3/4P3/1K1B2P1/7r/8 b - - 0 55\",\n        \"3k4/8/2b3Rp/2P1p3/4P3/1K1B2P1/7r/8 b - - 1 56\",\n        \"8/2k3R1/2b4p/2P1p3/4P3/1K1B2P1/7r/8 b - - 3 57\",\n        \"3k4/8/2b3Rp/2P1p3/4P3/1K1B2P1/7r/8 b - - 5 58\",\n        \"8/2k5/2b3Rp/2P1p3/1K2P3/3B2P1/7r/8 b - - 7 59\",\n        \"8/2k5/2b3Rp/2P1p3/4P3/2KB2P1/3r4/8 b - - 9 60\",\n        \"8/2k5/2b3Rp/2P1p3/1K2P3/3B2P1/6r1/8 b - - 11 61\",\n        \"8/2k5/2b3Rp/2P1p3/4P3/2KB2P1/3r4/8 b - - 13 62\",\n        \"8/2k5/2b3Rp/2P1p3/2K1P3/3B2P1/6r1/8 b - - 15 63\",\n        \"4b3/2k3R1/7p/2P1p3/2K1P3/3B2P1/6r1/8 b - - 17 64\",\n    },\n    {\n        \"r1bqkbnr/npp1pppp/p7/3P4/4pB2/2N5/PPP2PPP/R2QKBNR w KQkq - 1 6\",\n        \"r1bqkb1r/npp1pppp/p4n2/3P4/4pB2/2N5/PPP1QPPP/R3KBNR w KQkq - 3 7\",\n        \"r2qkb1r/npp1pppp/p4n2/3P1b2/4pB2/2N5/PPP1QPPP/2KR1BNR w kq - 5 8\",\n        \"r2qkb1r/1pp1pppp/p4n2/1n1P1b2/4pB2/2N4P/PPP1QPP1/2KR1BNR w kq - 1 9\",\n        \"r2qkb1r/1pp1pppp/5n2/1p1P1b2/4pB2/7P/PPP1QPP1/2KR1BNR w kq - 0 10\",\n        \"r2qkb1r/1ppbpppp/5n2/1Q1P4/4pB2/7P/PPP2PP1/2KR1BNR w kq - 1 11\",\n        \"3qkb1r/1Qpbpppp/5n2/3P4/4pB2/7P/rPP2PP1/2KR1BNR w k - 0 12\",\n        \"q3kb1r/1Qpbpppp/5n2/3P4/4pB2/7P/rPP2PP1/1K1R1BNR w k - 2 13\",\n        \"r3kb1r/2pbpppp/5n2/3P4/4pB2/7P/1PP2PP1/1K1R1BNR w k - 0 14\",\n        \"r3kb1r/2Bb1ppp/4pn2/3P4/4p3/7P/1PP2PP1/1K1R1BNR w k - 0 15\",\n        \"r3kb1r/2Bb2pp/4pn2/8/4p3/7P/1PP2PP1/1K1R1BNR w k - 0 16\",\n        \"r3k2r/2Bb2pp/4pn2/2b5/4p3/7P/1PP1NPP1/1K1R1B1R w k - 2 17\",\n        \"r6r/2Bbk1pp/4pn2/2b5/3Np3/7P/1PP2PP1/1K1R1B1R w - - 4 18\",\n        \"r6r/b2bk1pp/4pn2/4B3/3Np3/7P/1PP2PP1/1K1R1B1R w - - 6 19\",\n        \"r1r5/b2bk1pp/4pn2/4B3/2BNp3/7P/1PP2PP1/1K1R3R w - - 8 20\",\n        \"r7/b2bk1pp/4pn2/2r1B3/2BNp3/1P5P/2P2PP1/1K1R3R w - - 1 21\",\n        \"rb6/3bk1pp/4pn2/2r1B3/2BNpP2/1P5P/2P3P1/1K1R3R w - - 1 22\",\n        \"1r6/3bk1pp/4pn2/2r5/2BNpP2/1P5P/2P3P1/1K1R3R w - - 0 23\",\n        \"1r6/3bk1p1/4pn1p/2r5/2BNpP2/1P5P/2P3P1/2KR3R w - - 0 24\",\n        \"8/3bk1p1/1r2pn1p/2r5/2BNpP1P/1P6/2P3P1/2KR3R w - - 1 25\",\n        \"8/3bk3/1r2pnpp/2r5/2BNpP1P/1P6/2P3P1/2K1R2R w - - 0 26\",\n        \"2b5/4k3/1r2pnpp/2r5/2BNpP1P/1P4P1/2P5/2K1R2R w - - 1 27\",\n        \"8/1b2k3/1r2pnpp/2r5/2BNpP1P/1P4P1/2P5/2K1R1R1 w - - 3 28\",\n        \"8/1b1nk3/1r2p1pp/2r5/2BNpPPP/1P6/2P5/2K1R1R1 w - - 1 29\",\n        \"8/1b2k3/1r2p1pp/2r1nP2/2BNp1PP/1P6/2P5/2K1R1R1 w - - 1 30\",\n        \"8/1b2k3/1r2p1p1/2r1nPp1/2BNp2P/1P6/2P5/2K1R1R1 w - - 0 31\",\n        \"8/1b2k3/1r2p1n1/2r3p1/2BNp2P/1P6/2P5/2K1R1R1 w - - 0 32\",\n        \"8/1b2k3/1r2p1n1/6r1/2BNp2P/1P6/2P5/2K1R3 w - - 0 33\",\n        \"8/1b2k3/1r2p3/4n1P1/2BNp3/1P6/2P5/2K1R3 w - - 1 34\",\n        \"8/1b2k3/1r2p3/4n1P1/2BN4/1P2p3/2P5/2K4R w - - 0 35\",\n        \"8/1b2k3/1r2p2R/6P1/2nN4/1P2p3/2P5/2K5 w - - 0 36\",\n        \"8/1b2k3/3rp2R/6P1/2PN4/4p3/2P5/2K5 w - - 1 37\",\n        \"8/4k3/3rp2R/6P1/2PN4/2P1p3/6b1/2K5 w - - 1 38\",\n        \"8/4k3/r3p2R/2P3P1/3N4/2P1p3/6b1/2K5 w - - 1 39\",\n        \"8/3k4/r3p2R/2P2NP1/8/2P1p3/6b1/2K5 w - - 3 40\",\n        \"8/3k4/4p2R/2P3P1/8/2P1N3/6b1/r1K5 w - - 1 41\",\n        \"8/3k4/4p2R/2P3P1/8/2P1N3/3K2b1/6r1 w - - 3 42\",\n        \"8/3k4/4p2R/2P3P1/8/2PKNb2/8/6r1 w - - 5 43\",\n        \"8/4k3/4p1R1/2P3P1/8/2PKNb2/8/6r1 w - - 7 44\",\n        \"8/4k3/4p1R1/2P3P1/3K4/2P1N3/8/6rb w - - 9 45\",\n        \"8/3k4/4p1R1/2P1K1P1/8/2P1N3/8/6rb w - - 11 46\",\n        \"8/3k4/4p1R1/2P3P1/5K2/2P1N3/8/4r2b w - - 13 47\",\n        \"8/3k4/2b1p2R/2P3P1/5K2/2P1N3/8/4r3 w - - 15 48\",\n        \"8/3k4/2b1p3/2P3P1/5K2/2P1N2R/8/6r1 w - - 17 49\",\n        \"2k5/7R/2b1p3/2P3P1/5K2/2P1N3/8/6r1 w - - 19 50\",\n        \"2k5/7R/4p3/2P3P1/b1P2K2/4N3/8/6r1 w - - 1 51\",\n        \"2k5/3bR3/4p3/2P3P1/2P2K2/4N3/8/6r1 w - - 3 52\",\n        \"3k4/3b2R1/4p3/2P3P1/2P2K2/4N3/8/6r1 w - - 5 53\",\n        \"3kb3/6R1/4p1P1/2P5/2P2K2/4N3/8/6r1 w - - 1 54\",\n        \"3kb3/6R1/4p1P1/2P5/2P2KN1/8/8/2r5 w - - 3 55\",\n        \"3kb3/6R1/4p1P1/2P1N3/2P2K2/8/8/5r2 w - - 5 56\",\n        \"3kb3/6R1/4p1P1/2P1N3/2P5/4K3/8/4r3 w - - 7 57\",\n    },\n    {\n        \"rnbq1rk1/ppp1npb1/4p1p1/3P3p/3PP3/2N2N2/PP2BPPP/R1BQ1RK1 b - - 0 8\",\n        \"rnbq1rk1/ppp1npb1/6p1/3pP2p/3P4/2N2N2/PP2BPPP/R1BQ1RK1 b - - 0 9\",\n        \"rn1q1rk1/ppp1npb1/6p1/3pP2p/3P2b1/2N2N2/PP2BPPP/R1BQR1K1 b - - 2 10\",\n        \"r2q1rk1/ppp1npb1/2n3p1/3pP2p/3P2bN/2N5/PP2BPPP/R1BQR1K1 b - - 4 11\",\n        \"r4rk1/pppqnpb1/2n3p1/3pP2p/3P2bN/2N4P/PP2BPP1/R1BQR1K1 b - - 0 12\",\n        \"r4rk1/pppqnpb1/2n3p1/3pP2p/3P3N/7P/PP2NPP1/R1BQR1K1 b - - 0 13\",\n        \"r4rk1/pppq1pb1/2n3p1/3pPN1p/3P4/7P/PP2NPP1/R1BQR1K1 b - - 0 14\",\n        \"r4rk1/ppp2pb1/2n3p1/3pPq1p/3P1N2/7P/PP3PP1/R1BQR1K1 b - - 1 15\",\n        \"r4rk1/pppq1pb1/2n3p1/3pP2p/P2P1N2/7P/1P3PP1/R1BQR1K1 b - - 0 16\",\n        \"r2n1rk1/pppq1pb1/6p1/3pP2p/P2P1N2/R6P/1P3PP1/2BQR1K1 b - - 2 17\",\n        \"r4rk1/pppq1pb1/4N1p1/3pP2p/P2P4/R6P/1P3PP1/2BQR1K1 b - - 0 18\",\n        \"r4rk1/ppp2pb1/4q1p1/3pP1Bp/P2P4/R6P/1P3PP1/3QR1K1 b - - 1 19\",\n        \"r3r1k1/ppp2pb1/4q1p1/3pP1Bp/P2P1P2/R6P/1P4P1/3QR1K1 b - - 0 20\",\n        \"r3r1k1/ppp3b1/4qpp1/3pP2p/P2P1P1B/R6P/1P4P1/3QR1K1 b - - 1 21\",\n        \"r3r1k1/ppp3b1/4q1p1/3pP2p/P4P1B/R6P/1P4P1/3QR1K1 b - - 0 22\",\n        \"r4rk1/ppp3b1/4q1p1/3pP1Bp/P4P2/R6P/1P4P1/3QR1K1 b - - 2 23\",\n        \"r4rk1/pp4b1/4q1p1/2ppP1Bp/P4P2/3R3P/1P4P1/3QR1K1 b - - 1 24\",\n        \"r4rk1/pp4b1/4q1p1/2p1P1Bp/P2p1PP1/3R3P/1P6/3QR1K1 b - - 0 25\",\n        \"r4rk1/pp4b1/4q1p1/2p1P1B1/P2p1PP1/3R4/1P6/3QR1K1 b - - 0 26\",\n        \"r5k1/pp3rb1/4q1p1/2p1P1B1/P2p1PP1/6R1/1P6/3QR1K1 b - - 2 27\",\n        \"5rk1/pp3rb1/4q1p1/2p1P1B1/P2pRPP1/6R1/1P6/3Q2K1 b - - 4 28\",\n        \"5rk1/1p3rb1/p3q1p1/P1p1P1B1/3pRPP1/6R1/1P6/3Q2K1 b - - 0 29\",\n        \"4r1k1/1p3rb1/p3q1p1/P1p1P1B1/3pRPP1/1P4R1/8/3Q2K1 b - - 0 30\",\n        \"4r1k1/5rb1/pP2q1p1/2p1P1B1/3pRPP1/1P4R1/8/3Q2K1 b - - 0 31\",\n        \"4r1k1/5rb1/pq4p1/2p1P1B1/3pRPP1/1P4R1/4Q3/6K1 b - - 1 32\",\n        \"4r1k1/1r4b1/pq4p1/2p1P1B1/3pRPP1/1P4R1/2Q5/6K1 b - - 3 33\",\n        \"4r1k1/1r4b1/1q4p1/p1p1P1B1/3p1PP1/1P4R1/2Q5/4R1K1 b - - 1 34\",\n        \"4r1k1/3r2b1/1q4p1/p1p1P1B1/2Qp1PP1/1P4R1/8/4R1K1 b - - 3 35\",\n        \"4r1k1/3r2b1/4q1p1/p1p1P1B1/2Qp1PP1/1P4R1/5K2/4R3 b - - 5 36\",\n        \"4r1k1/3r2b1/6p1/p1p1P1B1/2Pp1PP1/6R1/5K2/4R3 b - - 0 37\",\n        \"4r1k1/3r2b1/6p1/p1p1P1B1/2P2PP1/3p2R1/5K2/3R4 b - - 1 38\",\n        \"5rk1/3r2b1/6p1/p1p1P1B1/2P2PP1/3p2R1/8/3RK3 b - - 3 39\",\n        \"5rk1/6b1/6p1/p1p1P1B1/2Pr1PP1/3R4/8/3RK3 b - - 0 40\",\n        \"5rk1/3R2b1/6p1/p1p1P1B1/2r2PP1/8/8/3RK3 b - - 1 41\",\n        \"5rk1/3R2b1/6p1/p1p1P1B1/4rPP1/8/3K4/3R4 b - - 3 42\",\n        \"1r4k1/3R2b1/6p1/p1p1P1B1/4rPP1/2K5/8/3R4 b - - 5 43\",\n        \"1r4k1/3R2b1/6p1/p1p1P1B1/2K2PP1/4r3/8/3R4 b - - 7 44\",\n        \"1r3bk1/8/3R2p1/p1p1P1B1/2K2PP1/4r3/8/3R4 b - - 9 45\",\n        \"1r3bk1/8/6R1/2p1P1B1/p1K2PP1/4r3/8/3R4 b - - 0 46\",\n        \"1r3b2/5k2/R7/2p1P1B1/p1K2PP1/4r3/8/3R4 b - - 2 47\",\n        \"5b2/1r3k2/R7/2p1P1B1/p1K2PP1/4r3/8/7R b - - 4 48\",\n        \"5b2/5k2/R7/2pKP1B1/pr3PP1/4r3/8/7R b - - 6 49\",\n        \"5b2/5k2/R1K5/2p1P1B1/p2r1PP1/4r3/8/7R b - - 8 50\",\n        \"8/R4kb1/2K5/2p1P1B1/p2r1PP1/4r3/8/7R b - - 10 51\",\n        \"8/R5b1/2K3k1/2p1PPB1/p2r2P1/4r3/8/7R b - - 0 52\",\n        \"8/6R1/2K5/2p1PPk1/p2r2P1/4r3/8/7R b - - 0 53\",\n        \"8/6R1/2K5/2p1PP2/p2r1kP1/4r3/8/5R2 b - - 2 54\",\n        \"8/6R1/2K2P2/2p1P3/p2r2P1/4r1k1/8/5R2 b - - 0 55\",\n        \"8/5PR1/2K5/2p1P3/p2r2P1/4r3/6k1/5R2 b - - 0 56\",\n    },\n    {\n        \"rn1qkb1r/p1pbpppp/5n2/8/2pP4/2N5/1PQ1PPPP/R1B1KBNR w KQkq - 0 7\",\n        \"r2qkb1r/p1pbpppp/2n2n2/8/2pP4/2N2N2/1PQ1PPPP/R1B1KB1R w KQkq - 2 8\",\n        \"r2qkb1r/p1pbpppp/5n2/8/1npPP3/2N2N2/1PQ2PPP/R1B1KB1R w KQkq - 1 9\",\n        \"r2qkb1r/p1pb1ppp/4pn2/8/1npPP3/2N2N2/1P3PPP/R1BQKB1R w KQkq - 0 10\",\n        \"r2qk2r/p1pbbppp/4pn2/8/1nBPP3/2N2N2/1P3PPP/R1BQK2R w KQkq - 1 11\",\n        \"r2q1rk1/p1pbbppp/4pn2/8/1nBPP3/2N2N2/1P3PPP/R1BQ1RK1 w - - 3 12\",\n        \"r2q1rk1/2pbbppp/p3pn2/8/1nBPPB2/2N2N2/1P3PPP/R2Q1RK1 w - - 0 13\",\n        \"r2q1rk1/2p1bppp/p3pn2/1b6/1nBPPB2/2N2N2/1P3PPP/R2QR1K1 w - - 2 14\",\n        \"r2q1rk1/4bppp/p1p1pn2/1b6/1nBPPB2/1PN2N2/5PPP/R2QR1K1 w - - 0 15\",\n        \"r4rk1/3qbppp/p1p1pn2/1b6/1nBPPB2/1PN2N2/3Q1PPP/R3R1K1 w - - 2 16\",\n        \"r4rk1/1q2bppp/p1p1pn2/1b6/1nBPPB2/1PN2N1P/3Q1PP1/R3R1K1 w - - 1 17\",\n        \"r3r1k1/1q2bppp/p1p1pn2/1b6/1nBPPB2/1PN2N1P/4QPP1/R3R1K1 w - - 3 18\",\n        \"r3r1k1/1q1nbppp/p1p1p3/1b6/1nBPPB2/1PN2N1P/4QPP1/3RR1K1 w - - 5 19\",\n        \"r3rbk1/1q1n1ppp/p1p1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/4R1K1 w - - 7 20\",\n        \"r3rbk1/1q3ppp/pnp1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/4R2K w - - 9 21\",\n        \"2r1rbk1/1q3ppp/pnp1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/1R5K w - - 11 22\",\n        \"2r1rbk1/1q4pp/pnp1pp2/1b6/1nBPPB2/1PN2N1P/4QPP1/1R1R3K w - - 0 23\",\n        \"2r1rbk1/5qpp/pnp1pp2/1b6/1nBPP3/1PN1BN1P/4QPP1/1R1R3K w - - 2 24\",\n        \"2r1rbk1/5qp1/pnp1pp1p/1b6/1nBPP3/1PN1BN1P/4QPP1/1R1R2K1 w - - 0 25\",\n        \"2r1rbk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/n3QPP1/1R1R2K1 w - - 0 26\",\n        \"r3rbk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/Q4PP1/1R1R2K1 w - - 1 27\",\n        \"rr3bk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/Q4PP1/R2R2K1 w - - 3 28\",\n        \"rr2qbk1/6p1/pnp1pp1p/1b6/2BPP3/1P2BN1P/4QPP1/R2R2K1 w - - 5 29\",\n        \"rr2qbk1/6p1/1np1pp1p/pb6/2BPP3/1P1QBN1P/5PP1/R2R2K1 w - - 0 30\",\n        \"rr2qbk1/6p1/1n2pp1p/pp6/3PP3/1P1QBN1P/5PP1/R2R2K1 w - - 0 31\",\n        \"rr2qbk1/6p1/1n2pp1p/1p1P4/p3P3/1P1QBN1P/5PP1/R2R2K1 w - - 0 32\",\n        \"rr2qbk1/3n2p1/3Ppp1p/1p6/p3P3/1P1QBN1P/5PP1/R2R2K1 w - - 1 33\",\n        \"rr3bk1/3n2p1/3Ppp1p/1p5q/pP2P3/3QBN1P/5PP1/R2R2K1 w - - 1 34\",\n        \"rr3bk1/3n2p1/3Ppp1p/1p5q/1P2P3/p2QBN1P/5PP1/2RR2K1 w - - 0 35\",\n        \"1r3bk1/3n2p1/r2Ppp1p/1p5q/1P2P3/pQ2BN1P/5PP1/2RR2K1 w - - 2 36\",\n        \"1r2qbk1/2Rn2p1/r2Ppp1p/1p6/1P2P3/pQ2BN1P/5PP1/3R2K1 w - - 4 37\",\n        \"1r2qbk1/2Rn2p1/r2Ppp1p/1pB5/1P2P3/1Q3N1P/p4PP1/3R2K1 w - - 0 38\",\n        \"1r2q1k1/2Rn2p1/r2bpp1p/1pB5/1P2P3/1Q3N1P/p4PP1/R5K1 w - - 0 39\",\n        \"1r2q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/1Q3N1P/p4PP1/R5K1 w - - 0 40\",\n        \"2r1q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 1 41\",\n        \"1r2q1k1/1R1n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 3 42\",\n        \"2r1q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 5 43\",\n        \"1r2q1k1/1R1n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 7 44\",\n        \"1rq3k1/R2n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 9 45\",\n        \"2q3k1/Rr1n2p1/3rpp1p/1p6/1P2P3/5N1P/4QPP1/R5K1 w - - 11 46\",\n        \"Rrq3k1/3n2p1/3rpp1p/1p6/1P2P3/5N1P/4QPP1/R5K1 w - - 13 47\",\n    },\n    {\n        \"rn1qkb1r/1pp2ppp/p4p2/3p1b2/5P2/1P2PN2/P1PP2PP/RN1QKB1R b KQkq - 1 6\",\n        \"r2qkb1r/1pp2ppp/p1n2p2/3p1b2/3P1P2/1P2PN2/P1P3PP/RN1QKB1R b KQkq - 0 7\",\n        \"r2qkb1r/1pp2ppp/p4p2/3p1b2/1n1P1P2/1P1BPN2/P1P3PP/RN1QK2R b KQkq - 2 8\",\n        \"r2qkb1r/1pp2ppp/p4p2/3p1b2/3P1P2/1P1PPN2/P5PP/RN1QK2R b KQkq - 0 9\",\n        \"r2qk2r/1pp2ppp/p2b1p2/3p1b2/3P1P2/1PNPPN2/P5PP/R2QK2R b KQkq - 2 10\",\n        \"r2qk2r/1p3ppp/p1pb1p2/3p1b2/3P1P2/1PNPPN2/P5PP/R2Q1RK1 b kq - 1 11\",\n        \"r2q1rk1/1p3ppp/p1pb1p2/3p1b2/3P1P2/1PNPPN2/P2Q2PP/R4RK1 b - - 3 12\",\n        \"r2qr1k1/1p3ppp/p1pb1p2/3p1b2/3P1P2/1P1PPN2/P2QN1PP/R4RK1 b - - 5 13\",\n        \"r3r1k1/1p3ppp/pqpb1p2/3p1b2/3P1P2/1P1PPNN1/P2Q2PP/R4RK1 b - - 7 14\",\n        \"r3r1k1/1p3ppp/pqp2p2/3p1b2/1b1P1P2/1P1PPNN1/P1Q3PP/R4RK1 b - - 9 15\",\n        \"r3r1k1/1p1b1ppp/pqp2p2/3p4/1b1P1P2/1P1PPNN1/P4QPP/R4RK1 b - - 11 16\",\n        \"2r1r1k1/1p1b1ppp/pqp2p2/3p4/1b1PPP2/1P1P1NN1/P4QPP/R4RK1 b - - 0 17\",\n        \"2r1r1k1/1p1b1ppp/pq3p2/2pp4/1b1PPP2/PP1P1NN1/5QPP/R4RK1 b - - 0 18\",\n        \"2r1r1k1/1p1b1ppp/pq3p2/2Pp4/4PP2/PPbP1NN1/5QPP/R4RK1 b - - 0 19\",\n        \"2r1r1k1/1p1b1ppp/p4p2/2Pp4/4PP2/PqbP1NN1/5QPP/RR4K1 b - - 1 20\",\n        \"2r1r1k1/1p1b1ppp/p4p2/2Pp4/q3PP2/P1bP1NN1/R4QPP/1R4K1 b - - 3 21\",\n        \"2r1r1k1/1p3ppp/p4p2/1bPP4/q4P2/P1bP1NN1/R4QPP/1R4K1 b - - 0 22\",\n        \"2r1r1k1/1p3ppp/p4p2/2PP4/q4P2/P1bb1NN1/R4QPP/2R3K1 b - - 1 23\",\n        \"2r1r1k1/1p3ppp/p2P1p2/2P5/2q2P2/P1bb1NN1/R4QPP/2R3K1 b - - 0 24\",\n        \"2rr2k1/1p3ppp/p2P1p2/2P5/2q2P2/P1bb1NN1/R4QPP/2R4K b - - 2 25\",\n        \"2rr2k1/1p3ppp/p2P1p2/2Q5/5P2/P1bb1NN1/R5PP/2R4K b - - 0 26\",\n        \"3r2k1/1p3ppp/p2P1p2/2r5/5P2/P1bb1N2/R3N1PP/2R4K b - - 1 27\",\n        \"3r2k1/1p3ppp/p2P1p2/2r5/5P2/P1b2N2/4R1PP/2R4K b - - 0 28\",\n        \"3r2k1/1p3ppp/p2P1p2/2r5/1b3P2/P4N2/4R1PP/3R3K b - - 2 29\",\n        \"3r2k1/1p2Rppp/p2P1p2/b1r5/5P2/P4N2/6PP/3R3K b - - 4 30\",\n        \"3r2k1/1R3ppp/p1rP1p2/b7/5P2/P4N2/6PP/3R3K b - - 0 31\",\n        \"3r2k1/1R3ppp/p2R1p2/b7/5P2/P4N2/6PP/7K b - - 0 32\",\n        \"6k1/1R3ppp/p2r1p2/b7/5P2/P4NP1/7P/7K b - - 0 33\",\n        \"6k1/1R3p1p/p2r1pp1/b7/5P1P/P4NP1/8/7K b - - 0 34\",\n        \"6k1/3R1p1p/pr3pp1/b7/5P1P/P4NP1/8/7K b - - 2 35\",\n        \"6k1/5p2/pr3pp1/b2R3p/5P1P/P4NP1/8/7K b - - 1 36\",\n        \"6k1/5p2/pr3pp1/7p/5P1P/P1bR1NP1/8/7K b - - 3 37\",\n        \"6k1/5p2/p1r2pp1/7p/5P1P/P1bR1NP1/6K1/8 b - - 5 38\",\n        \"6k1/5p2/p1r2pp1/b2R3p/5P1P/P4NP1/6K1/8 b - - 7 39\",\n        \"6k1/5p2/p4pp1/b2R3p/5P1P/P4NPK/2r5/8 b - - 9 40\",\n        \"6k1/2b2p2/p4pp1/7p/5P1P/P2R1NPK/2r5/8 b - - 11 41\",\n        \"6k1/2b2p2/5pp1/p6p/3N1P1P/P2R2PK/2r5/8 b - - 1 42\",\n        \"6k1/2b2p2/5pp1/p6p/3N1P1P/P1R3PK/r7/8 b - - 3 43\",\n        \"6k1/5p2/1b3pp1/p6p/5P1P/P1R3PK/r1N5/8 b - - 5 44\",\n        \"8/5pk1/1bR2pp1/p6p/5P1P/P5PK/r1N5/8 b - - 7 45\",\n        \"3b4/5pk1/2R2pp1/p4P1p/7P/P5PK/r1N5/8 b - - 0 46\",\n        \"8/4bpk1/2R2pp1/p4P1p/6PP/P6K/r1N5/8 b - - 0 47\",\n        \"8/5pk1/2R2pP1/p6p/6PP/b6K/r1N5/8 b - - 0 48\",\n        \"8/6k1/2R2pp1/p6P/7P/b6K/r1N5/8 b - - 0 49\",\n        \"8/6k1/2R2p2/p6p/7P/b5K1/r1N5/8 b - - 1 50\",\n        \"8/8/2R2pk1/p6p/7P/b4K2/r1N5/8 b - - 3 51\",\n        \"8/8/2R2pk1/p6p/7P/4NK2/rb6/8 b - - 5 52\",\n        \"2R5/8/5pk1/7p/p6P/4NK2/rb6/8 b - - 1 53\",\n        \"6R1/8/5pk1/7p/p6P/4NK2/1b6/r7 b - - 3 54\",\n        \"R7/5k2/5p2/7p/p6P/4NK2/1b6/r7 b - - 5 55\",\n        \"R7/5k2/5p2/7p/7P/p3N3/1b2K3/r7 b - - 1 56\",\n        \"8/R4k2/5p2/7p/7P/p3N3/1b2K3/7r b - - 3 57\",\n        \"8/8/5pk1/7p/R6P/p3N3/1b2K3/7r b - - 5 58\",\n        \"8/8/5pk1/7p/R6P/p7/4K3/2bN3r b - - 7 59\",\n        \"8/8/5pk1/7p/R6P/p7/4KN1r/2b5 b - - 9 60\",\n        \"8/8/5pk1/7p/R6P/p3K3/1b3N1r/8 b - - 11 61\",\n        \"8/8/R4pk1/7p/7P/p1b1K3/5N1r/8 b - - 13 62\",\n        \"8/8/5pk1/7p/7P/2b1K3/R4N1r/8 b - - 0 63\",\n        \"8/8/5pk1/7p/3K3P/8/R4N1r/4b3 b - - 2 64\",\n    }\n};\n// clang-format on\n\n}  // namespace\n\nnamespace Stockfish::Benchmark {\n\n// Builds a list of UCI commands to be run by bench. There\n// are five parameters: TT size in MB, number of search threads that\n// should be used, the limit value spent for each position, a file name\n// where to look for positions in FEN format, and the type of the limit:\n// depth, perft, nodes and movetime (in milliseconds). Examples:\n//\n// bench                            : search default positions up to depth 13\n// bench 64 1 15                    : search default positions up to depth 15 (TT = 64MB)\n// bench 64 1 100000 default nodes  : search default positions for 100K nodes each\n// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec\n// bench 16 1 5 blah perft          : run a perft 5 on positions in file \"blah\"\nstd::vector<std::string> setup_bench(const std::string& currentFen, std::istream& is) {\n\n    std::vector<std::string> fens, list;\n    std::string              go, token;\n\n    // Assign default values to missing arguments\n    std::string ttSize    = (is >> token) ? token : \"16\";\n    std::string threads   = (is >> token) ? token : \"1\";\n    std::string limit     = (is >> token) ? token : \"13\";\n    std::string fenFile   = (is >> token) ? token : \"default\";\n    std::string limitType = (is >> token) ? token : \"depth\";\n\n    go = limitType == \"eval\" ? \"eval\" : \"go \" + limitType + \" \" + limit;\n\n    if (fenFile == \"default\")\n        fens = Defaults;\n\n    else if (fenFile == \"current\")\n        fens.push_back(currentFen);\n\n    else\n    {\n        std::string   fen;\n        std::ifstream file(fenFile);\n\n        if (!file.is_open())\n        {\n            std::cerr << \"Unable to open file \" << fenFile << std::endl;\n            exit(EXIT_FAILURE);\n        }\n\n        while (getline(file, fen))\n            if (!fen.empty())\n                fens.push_back(fen);\n\n        file.close();\n    }\n\n    list.emplace_back(\"setoption name Threads value \" + threads);\n    list.emplace_back(\"setoption name Hash value \" + ttSize);\n    list.emplace_back(\"ucinewgame\");\n\n    for (const std::string& fen : fens)\n        if (fen.find(\"setoption\") != std::string::npos)\n            list.emplace_back(fen);\n        else\n        {\n            list.emplace_back(\"position fen \" + fen);\n            list.emplace_back(go);\n        }\n\n    return list;\n}\n\nBenchmarkSetup setup_benchmark(std::istream& is) {\n    // TT_SIZE_PER_THREAD is chosen such that roughly half of the hash is used all positions\n    // for the current sequence have been searched.\n    static constexpr int TT_SIZE_PER_THREAD = 128;\n\n    static constexpr int DEFAULT_DURATION_S = 150;\n\n    BenchmarkSetup setup{};\n\n    // Assign default values to missing arguments\n    int desiredTimeS;\n\n    if (!(is >> setup.threads))\n        setup.threads = int(get_hardware_concurrency());\n    else\n        setup.originalInvocation += std::to_string(setup.threads);\n\n    if (!(is >> setup.ttSize))\n        setup.ttSize = TT_SIZE_PER_THREAD * setup.threads;\n    else\n        setup.originalInvocation += \" \" + std::to_string(setup.ttSize);\n\n    if (!(is >> desiredTimeS))\n        desiredTimeS = DEFAULT_DURATION_S;\n    else\n        setup.originalInvocation += \" \" + std::to_string(desiredTimeS);\n\n    setup.filledInvocation += std::to_string(setup.threads) + \" \" + std::to_string(setup.ttSize)\n                            + \" \" + std::to_string(desiredTimeS);\n\n    auto getCorrectedTime = [&](int ply) {\n        // time per move is fit roughly based on LTC games\n        // seconds = 50/{ply+15}\n        // ms = 50000/{ply+15}\n        // with this fit 10th move gets 2000ms\n        // adjust for desired 10th move time\n        return 50000.0 / (static_cast<double>(ply) + 15.0);\n    };\n\n    float totalTime = 0;\n    for (const auto& game : BenchmarkPositions)\n    {\n        int ply = 1;\n        for (int i = 0; i < static_cast<int>(game.size()); ++i)\n        {\n            const float correctedTime = float(getCorrectedTime(ply));\n            totalTime += correctedTime;\n            ply += 1;\n        }\n    }\n\n    float timeScaleFactor = static_cast<float>(desiredTimeS * 1000) / totalTime;\n\n    for (const auto& game : BenchmarkPositions)\n    {\n        setup.commands.emplace_back(\"ucinewgame\");\n        int ply = 1;\n        for (const std::string& fen : game)\n        {\n            setup.commands.emplace_back(\"position fen \" + fen);\n\n            const int correctedTime = static_cast<int>(getCorrectedTime(ply) * timeScaleFactor);\n            setup.commands.emplace_back(\"go movetime \" + std::to_string(correctedTime));\n\n            ply += 1;\n        }\n    }\n\n    return setup;\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/benchmark.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef BENCHMARK_H_INCLUDED\n#define BENCHMARK_H_INCLUDED\n\n#include <iosfwd>\n#include <string>\n#include <vector>\n\nnamespace Stockfish::Benchmark {\n\nstd::vector<std::string> setup_bench(const std::string&, std::istream&);\n\nstruct BenchmarkSetup {\n    int                      ttSize;\n    int                      threads;\n    std::vector<std::string> commands;\n    std::string              originalInvocation;\n    std::string              filledInvocation;\n};\n\nBenchmarkSetup setup_benchmark(std::istream&);\n\n}  // namespace Stockfish\n\n#endif  // #ifndef BENCHMARK_H_INCLUDED\n"
  },
  {
    "path": "src/bitboard.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"bitboard.h\"\n\n#include <algorithm>\n#include <bitset>\n#include <initializer_list>\n\n#include \"misc.h\"\n\nnamespace Stockfish {\n\nuint8_t PopCnt16[1 << 16];\nuint8_t SquareDistance[SQUARE_NB][SQUARE_NB];\n\nBitboard LineBB[SQUARE_NB][SQUARE_NB];\nBitboard BetweenBB[SQUARE_NB][SQUARE_NB];\nBitboard RayPassBB[SQUARE_NB][SQUARE_NB];\n\nalignas(64) Magic Magics[SQUARE_NB][2];\n\nnamespace {\n\nBitboard RookTable[0x19000];   // To store rook attacks\nBitboard BishopTable[0x1480];  // To store bishop attacks\n\nvoid init_magics(PieceType pt, Bitboard table[], Magic magics[][2]);\n}\n\n// Returns an ASCII representation of a bitboard suitable\n// to be printed to standard output. Useful for debugging.\nstd::string Bitboards::pretty(Bitboard b) {\n\n    std::string s = \"+---+---+---+---+---+---+---+---+\\n\";\n\n    for (Rank r = RANK_8;; --r)\n    {\n        for (File f = FILE_A; f <= FILE_H; ++f)\n            s += b & make_square(f, r) ? \"| X \" : \"|   \";\n\n        s += \"| \" + std::to_string(1 + r) + \"\\n+---+---+---+---+---+---+---+---+\\n\";\n\n        if (r == RANK_1)\n            break;\n    }\n    s += \"  a   b   c   d   e   f   g   h\\n\";\n\n    return s;\n}\n\n\n// Initializes various bitboard tables. It is called at\n// startup and relies on global objects to be already zero-initialized.\nvoid Bitboards::init() {\n\n    for (unsigned i = 0; i < (1 << 16); ++i)\n        PopCnt16[i] = uint8_t(std::bitset<16>(i).count());\n\n    for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)\n        for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)\n            SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));\n\n    init_magics(ROOK, RookTable, Magics);\n    init_magics(BISHOP, BishopTable, Magics);\n\n    for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)\n    {\n        for (PieceType pt : {BISHOP, ROOK})\n            for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)\n            {\n                if (PseudoAttacks[pt][s1] & s2)\n                {\n                    LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;\n                    BetweenBB[s1][s2] =\n                      (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));\n                    RayPassBB[s1][s2] =\n                      attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2);\n                }\n                BetweenBB[s1][s2] |= s2;\n            }\n    }\n}\n\nnamespace {\n// Computes all rook and bishop attacks at startup. Magic\n// bitboards are used to look up attacks of sliding pieces. As a reference see\n// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use\n// the so called \"fancy\" approach.\nvoid init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {\n\n#ifndef USE_PEXT\n    // Optimal PRNG seeds to pick the correct magics in the shortest time\n    int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},\n                            {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};\n\n    Bitboard occupancy[4096];\n    int      epoch[4096] = {}, cnt = 0;\n#endif\n    Bitboard reference[4096];\n    int      size = 0;\n\n    for (Square s = SQ_A1; s <= SQ_H8; ++s)\n    {\n        // Board edges are not considered in the relevant occupancies\n        Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));\n\n        // Given a square 's', the mask is the bitboard of sliding attacks from\n        // 's' computed on an empty board. The index must be big enough to contain\n        // all the attacks for each possible subset of the mask and so is 2 power\n        // the number of 1s of the mask. Hence we deduce the size of the shift to\n        // apply to the 64 or 32 bits word to get the index.\n        Magic& m = magics[s][pt - BISHOP];\n        m.mask   = Bitboards::sliding_attack(pt, s, 0) & ~edges;\n#ifndef USE_PEXT\n        m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);\n#endif\n        // Set the offset for the attacks table of the square. We have individual\n        // table sizes for each square with \"Fancy Magic Bitboards\".\n        m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;\n        size      = 0;\n\n        // Use Carry-Rippler trick to enumerate all subsets of masks[s] and\n        // store the corresponding sliding attack bitboard in reference[].\n        Bitboard b = 0;\n        do\n        {\n#ifndef USE_PEXT\n            occupancy[size] = b;\n#endif\n            reference[size] = Bitboards::sliding_attack(pt, s, b);\n\n            if (HasPext)\n                m.attacks[pext(b, m.mask)] = reference[size];\n\n            size++;\n            b = (b - m.mask) & m.mask;\n        } while (b);\n\n#ifndef USE_PEXT\n        PRNG rng(seeds[Is64Bit][rank_of(s)]);\n\n        // Find a magic for square 's' picking up an (almost) random number\n        // until we find the one that passes the verification test.\n        for (int i = 0; i < size;)\n        {\n            for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)\n                m.magic = rng.sparse_rand<Bitboard>();\n\n            // A good magic must map every possible occupancy to an index that\n            // looks up the correct sliding attack in the attacks[s] database.\n            // Note that we build up the database for square 's' as a side\n            // effect of verifying the magic. Keep track of the attempt count\n            // and save it in epoch[], little speed-up trick to avoid resetting\n            // m.attacks[] after every failed attempt.\n            for (++cnt, i = 0; i < size; ++i)\n            {\n                unsigned idx = m.index(occupancy[i]);\n\n                if (epoch[idx] < cnt)\n                {\n                    epoch[idx]     = cnt;\n                    m.attacks[idx] = reference[i];\n                }\n                else if (m.attacks[idx] != reference[i])\n                    break;\n            }\n        }\n#endif\n    }\n}\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/bitboard.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef BITBOARD_H_INCLUDED\n#define BITBOARD_H_INCLUDED\n\n#include <algorithm>\n#include <cassert>\n#include <cmath>\n#include <cstring>\n#include <cstdint>\n#include <cstdlib>\n#include <string>\n#include <initializer_list>\n#include <array>\n\n#include \"types.h\"\n\nnamespace Stockfish {\n\nnamespace Bitboards {\n\nvoid        init();\nstd::string pretty(Bitboard b);\n\n}  // namespace Stockfish::Bitboards\n\nconstexpr Bitboard FileABB = 0x0101010101010101ULL;\nconstexpr Bitboard FileBBB = FileABB << 1;\nconstexpr Bitboard FileCBB = FileABB << 2;\nconstexpr Bitboard FileDBB = FileABB << 3;\nconstexpr Bitboard FileEBB = FileABB << 4;\nconstexpr Bitboard FileFBB = FileABB << 5;\nconstexpr Bitboard FileGBB = FileABB << 6;\nconstexpr Bitboard FileHBB = FileABB << 7;\n\nconstexpr Bitboard Rank1BB = 0xFF;\nconstexpr Bitboard Rank2BB = Rank1BB << (8 * 1);\nconstexpr Bitboard Rank3BB = Rank1BB << (8 * 2);\nconstexpr Bitboard Rank4BB = Rank1BB << (8 * 3);\nconstexpr Bitboard Rank5BB = Rank1BB << (8 * 4);\nconstexpr Bitboard Rank6BB = Rank1BB << (8 * 5);\nconstexpr Bitboard Rank7BB = Rank1BB << (8 * 6);\nconstexpr Bitboard Rank8BB = Rank1BB << (8 * 7);\n\nextern uint8_t PopCnt16[1 << 16];\nextern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];\n\nextern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];\nextern Bitboard LineBB[SQUARE_NB][SQUARE_NB];\nextern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];\n\n// Magic holds all magic bitboards relevant data for a single square\nstruct Magic {\n    Bitboard  mask;\n    Bitboard* attacks;\n#ifndef USE_PEXT\n    Bitboard magic;\n    unsigned shift;\n#endif\n\n    // Compute the attack's index using the 'magic bitboards' approach\n    unsigned index(Bitboard occupied) const {\n\n#ifdef USE_PEXT\n        return unsigned(pext(occupied, mask));\n#else\n        if (Is64Bit)\n            return unsigned(((occupied & mask) * magic) >> shift);\n\n        unsigned lo = unsigned(occupied) & unsigned(mask);\n        unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);\n        return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;\n#endif\n    }\n\n    Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; }\n};\n\nextern Magic Magics[SQUARE_NB][2];\n\nconstexpr Bitboard square_bb(Square s) {\n    assert(is_ok(s));\n    return 1ULL << s;\n}\n\n\n// Overloads of bitwise operators between a Bitboard and a Square for testing\n// whether a given bit is set in a bitboard, and for setting and clearing bits.\n\nconstexpr Bitboard  operator&(Bitboard b, Square s) { return b & square_bb(s); }\nconstexpr Bitboard  operator|(Bitboard b, Square s) { return b | square_bb(s); }\nconstexpr Bitboard  operator^(Bitboard b, Square s) { return b ^ square_bb(s); }\nconstexpr Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }\nconstexpr Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }\n\nconstexpr Bitboard operator&(Square s, Bitboard b) { return b & s; }\nconstexpr Bitboard operator|(Square s, Bitboard b) { return b | s; }\nconstexpr Bitboard operator^(Square s, Bitboard b) { return b ^ s; }\n\nconstexpr Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }\n\nconstexpr bool more_than_one(Bitboard b) { return b & (b - 1); }\n\n\n// rank_bb() and file_bb() return a bitboard representing all the squares on\n// the given file or rank.\n\nconstexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); }\n\nconstexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); }\n\nconstexpr Bitboard file_bb(File f) { return FileABB << f; }\n\nconstexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); }\n\n\n// Moves a bitboard one or two steps as specified by the direction D\ntemplate<Direction D>\nconstexpr Bitboard shift(Bitboard b) {\n    return D == NORTH         ? b << 8\n         : D == SOUTH         ? b >> 8\n         : D == NORTH + NORTH ? b << 16\n         : D == SOUTH + SOUTH ? b >> 16\n         : D == EAST          ? (b & ~FileHBB) << 1\n         : D == WEST          ? (b & ~FileABB) >> 1\n         : D == NORTH_EAST    ? (b & ~FileHBB) << 9\n         : D == NORTH_WEST    ? (b & ~FileABB) << 7\n         : D == SOUTH_EAST    ? (b & ~FileHBB) >> 7\n         : D == SOUTH_WEST    ? (b & ~FileABB) >> 9\n                              : 0;\n}\n\n\n// Returns the squares attacked by pawns of the given color\n// from the squares in the given bitboard.\ntemplate<Color C>\nconstexpr Bitboard pawn_attacks_bb(Bitboard b) {\n    return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)\n                      : shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);\n}\n\n\n// Returns a bitboard representing an entire line (from board edge\n// to board edge) that intersects the two given squares. If the given squares\n// are not on a same file/rank/diagonal, the function returns 0. For instance,\n// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.\ninline Bitboard line_bb(Square s1, Square s2) {\n\n    assert(is_ok(s1) && is_ok(s2));\n    return LineBB[s1][s2];\n}\n\n\n// Returns a bitboard representing the squares in the semi-open\n// segment between the squares s1 and s2 (excluding s1 but including s2). If the\n// given squares are not on a same file/rank/diagonal, it returns s2. For instance,\n// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but\n// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick\n// allows to generate non-king evasion moves faster: the defending piece must either\n// interpose itself to cover the check or capture the checking piece.\ninline Bitboard between_bb(Square s1, Square s2) {\n\n    assert(is_ok(s1) && is_ok(s2));\n    return BetweenBB[s1][s2];\n}\n\n// distance() functions return the distance between x and y, defined as the\n// number of steps for a king in x to reach y.\n\ntemplate<typename T1 = Square>\ninline int distance(Square x, Square y);\n\ntemplate<>\ninline int distance<File>(Square x, Square y) {\n    return std::abs(file_of(x) - file_of(y));\n}\n\ntemplate<>\ninline int distance<Rank>(Square x, Square y) {\n    return std::abs(rank_of(x) - rank_of(y));\n}\n\ntemplate<>\ninline int distance<Square>(Square x, Square y) {\n    return SquareDistance[x][y];\n}\n\ninline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }\n\n\nconstexpr int constexpr_popcount(Bitboard b) {\n    b = b - ((b >> 1) & 0x5555555555555555ULL);\n    b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL);\n    b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL;\n    return static_cast<int>((b * 0x0101010101010101ULL) >> 56);\n}\n\n// Counts the number of non-zero bits in a bitboard.\ninline int popcount(Bitboard b) {\n\n#ifndef USE_POPCNT\n\n    std::uint16_t indices[4];\n    std::memcpy(indices, &b, sizeof(b));\n    return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]]\n         + PopCnt16[indices[3]];\n\n#elif defined(_MSC_VER)\n\n    return int(_mm_popcnt_u64(b));\n\n#else  // Assumed gcc or compatible compiler\n\n    return __builtin_popcountll(b);\n\n#endif\n}\n\n// Returns the least significant bit in a non-zero bitboard.\ninline Square lsb(Bitboard b) {\n    assert(b);\n\n#if defined(__GNUC__)  // GCC, Clang, ICX\n\n    return Square(__builtin_ctzll(b));\n\n#elif defined(_MSC_VER)\n    #ifdef _WIN64  // MSVC, WIN64\n\n    unsigned long idx;\n    _BitScanForward64(&idx, b);\n    return Square(idx);\n\n    #else  // MSVC, WIN32\n    unsigned long idx;\n\n    if (b & 0xffffffff)\n    {\n        _BitScanForward(&idx, int32_t(b));\n        return Square(idx);\n    }\n    else\n    {\n        _BitScanForward(&idx, int32_t(b >> 32));\n        return Square(idx + 32);\n    }\n    #endif\n#else  // Compiler is neither GCC nor MSVC compatible\n    #error \"Compiler not supported.\"\n#endif\n}\n\n// Returns the most significant bit in a non-zero bitboard.\ninline Square msb(Bitboard b) {\n    assert(b);\n\n#if defined(__GNUC__)  // GCC, Clang, ICX\n\n    return Square(63 ^ __builtin_clzll(b));\n\n#elif defined(_MSC_VER)\n    #ifdef _WIN64  // MSVC, WIN64\n\n    unsigned long idx;\n    _BitScanReverse64(&idx, b);\n    return Square(idx);\n\n    #else  // MSVC, WIN32\n\n    unsigned long idx;\n\n    if (b >> 32)\n    {\n        _BitScanReverse(&idx, int32_t(b >> 32));\n        return Square(idx + 32);\n    }\n    else\n    {\n        _BitScanReverse(&idx, int32_t(b));\n        return Square(idx);\n    }\n    #endif\n#else  // Compiler is neither GCC nor MSVC compatible\n    #error \"Compiler not supported.\"\n#endif\n}\n\n// Returns the bitboard of the least significant\n// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)).\ninline Bitboard least_significant_square_bb(Bitboard b) {\n    assert(b);\n    return b & -b;\n}\n\n// Finds and clears the least significant bit in a non-zero bitboard.\ninline Square pop_lsb(Bitboard& b) {\n    assert(b);\n    const Square s = lsb(b);\n    b &= b - 1;\n    return s;\n}\n\nnamespace Bitboards {\n// Returns the bitboard of target square for the given step\n// from the given square. If the step is off the board, returns empty bitboard.\nconstexpr Bitboard safe_destination(Square s, int step) {\n    constexpr auto abs = [](int v) { return v < 0 ? -v : v; };\n    Square         to  = Square(s + step);\n    return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0);\n}\n\nconstexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {\n    Bitboard  attacks             = 0;\n    Direction RookDirections[4]   = {NORTH, SOUTH, EAST, WEST};\n    Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};\n\n    for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))\n    {\n        Square s = sq;\n        while (safe_destination(s, d))\n        {\n            attacks |= (s += d);\n            if (occupied & s)\n            {\n                break;\n            }\n        }\n    }\n\n    return attacks;\n}\n\nconstexpr Bitboard knight_attack(Square sq) {\n    Bitboard b = {};\n    for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})\n        b |= safe_destination(sq, step);\n    return b;\n}\n\nconstexpr Bitboard king_attack(Square sq) {\n    Bitboard b = {};\n    for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})\n        b |= safe_destination(sq, step);\n    return b;\n}\n\nconstexpr Bitboard pseudo_attacks(PieceType pt, Square sq) {\n    switch (pt)\n    {\n    case PieceType::ROOK :\n    case PieceType::BISHOP :\n        return sliding_attack(pt, sq, 0);\n    case PieceType::QUEEN :\n        return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0);\n    case PieceType::KNIGHT :\n        return knight_attack(sq);\n    case PieceType::KING :\n        return king_attack(sq);\n    default :\n        assert(false);\n        return 0;\n    }\n}\n\n}\n\ninline constexpr auto PseudoAttacks = []() constexpr {\n    std::array<std::array<Bitboard, SQUARE_NB>, PIECE_TYPE_NB> attacks{};\n\n    for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)\n    {\n        attacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));\n        attacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));\n\n        attacks[KING][s1]   = Bitboards::pseudo_attacks(KING, s1);\n        attacks[KNIGHT][s1] = Bitboards::pseudo_attacks(KNIGHT, s1);\n        attacks[QUEEN][s1] = attacks[BISHOP][s1] = Bitboards::pseudo_attacks(BISHOP, s1);\n        attacks[QUEEN][s1] |= attacks[ROOK][s1]  = Bitboards::pseudo_attacks(ROOK, s1);\n    }\n\n    return attacks;\n}();\n\n\n// Returns the pseudo attacks of the given piece type\n// assuming an empty board.\ntemplate<PieceType Pt>\ninline Bitboard attacks_bb(Square s, Color c = COLOR_NB) {\n\n    assert((Pt != PAWN || c < COLOR_NB) && is_ok(s));\n    return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s];\n}\n\n\n// Returns the attacks by the given piece\n// assuming the board is occupied according to the passed Bitboard.\n// Sliding piece attacks do not continue passed an occupied square.\ntemplate<PieceType Pt>\ninline Bitboard attacks_bb(Square s, Bitboard occupied) {\n\n    assert(Pt != PAWN && is_ok(s));\n\n    switch (Pt)\n    {\n    case BISHOP :\n    case ROOK :\n        return Magics[s][Pt - BISHOP].attacks_bb(occupied);\n    case QUEEN :\n        return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);\n    default :\n        return PseudoAttacks[Pt][s];\n    }\n}\n\n// Returns the attacks by the given piece\n// assuming the board is occupied according to the passed Bitboard.\n// Sliding piece attacks do not continue passed an occupied square.\ninline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {\n\n    assert(pt != PAWN && is_ok(s));\n\n    switch (pt)\n    {\n    case BISHOP :\n        return attacks_bb<BISHOP>(s, occupied);\n    case ROOK :\n        return attacks_bb<ROOK>(s, occupied);\n    case QUEEN :\n        return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);\n    default :\n        return PseudoAttacks[pt][s];\n    }\n}\n\ninline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {\n    return type_of(pc) == PAWN ? PseudoAttacks[color_of(pc)][s]\n                               : attacks_bb(type_of(pc), s, occupied);\n}\n\n}  // namespace Stockfish\n\n#endif  // #ifndef BITBOARD_H_INCLUDED\n"
  },
  {
    "path": "src/engine.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"engine.h\"\n\n#include <algorithm>\n#include <cassert>\n#include <deque>\n#include <iosfwd>\n#include <memory>\n#include <ostream>\n#include <sstream>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"evaluate.h\"\n#include \"misc.h\"\n#include \"nnue/network.h\"\n#include \"nnue/nnue_common.h\"\n#include \"nnue/nnue_misc.h\"\n#include \"numa.h\"\n#include \"perft.h\"\n#include \"position.h\"\n#include \"search.h\"\n#include \"shm.h\"\n#include \"syzygy/tbprobe.h\"\n#include \"types.h\"\n#include \"uci.h\"\n#include \"ucioption.h\"\n\nnamespace Stockfish {\n\nnamespace NN = Eval::NNUE;\n\nconstexpr auto StartFEN   = \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\";\nconstexpr int  MaxHashMB  = Is64Bit ? 33554432 : 2048;\nint            MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));\n\n// The default configuration will attempt to group L3 domains up to 32 threads.\n// This size was found to be a good balance between the Elo gain of increased\n// history sharing and the speed loss from more cross-cache accesses (see\n// PR#6526). The user can always explicitly override this behavior.\nconstexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32};\n\nEngine::Engine(std::optional<std::string> path) :\n    binaryDirectory(path ? CommandLine::get_binary_directory(*path) : \"\"),\n    numaContext(NumaConfig::from_system(DefaultNumaPolicy)),\n    states(new std::deque<StateInfo>(1)),\n    threads(),\n    networks(numaContext, get_default_networks()) {\n\n    pos.set(StartFEN, false, &states->back());\n\n    options.add(  //\n      \"Debug Log File\", Option(\"\", [](const Option& o) {\n          start_logger(o);\n          return std::nullopt;\n      }));\n\n    options.add(  //\n      \"NumaPolicy\", Option(\"auto\", [this](const Option& o) {\n          set_numa_config_from_option(o);\n          return numa_config_information_as_string() + \"\\n\"\n               + thread_allocation_information_as_string();\n      }));\n\n    options.add(  //\n      \"Threads\", Option(1, 1, MaxThreads, [this](const Option&) {\n          resize_threads();\n          return thread_allocation_information_as_string();\n      }));\n\n    options.add(  //\n      \"Hash\", Option(16, 1, MaxHashMB, [this](const Option& o) {\n          set_tt_size(o);\n          return std::nullopt;\n      }));\n\n    options.add(  //\n      \"Clear Hash\", Option([this](const Option&) {\n          search_clear();\n          return std::nullopt;\n      }));\n\n    options.add(  //\n      \"Ponder\", Option(false));\n\n    options.add(  //\n      \"MultiPV\", Option(1, 1, MAX_MOVES));\n\n    options.add(\"Skill Level\", Option(20, 0, 20));\n\n    options.add(\"Move Overhead\", Option(10, 0, 5000));\n\n    options.add(\"nodestime\", Option(0, 0, 10000));\n\n    options.add(\"UCI_Chess960\", Option(false));\n\n    options.add(\"UCI_LimitStrength\", Option(false));\n\n    options.add(\"UCI_Elo\",\n                Option(Stockfish::Search::Skill::LowestElo, Stockfish::Search::Skill::LowestElo,\n                       Stockfish::Search::Skill::HighestElo));\n\n    options.add(\"UCI_ShowWDL\", Option(false));\n\n    options.add(  //\n      \"SyzygyPath\", Option(\"\", [](const Option& o) {\n          Tablebases::init(o);\n          return std::nullopt;\n      }));\n\n    options.add(\"SyzygyProbeDepth\", Option(1, 1, 100));\n\n    options.add(\"Syzygy50MoveRule\", Option(true));\n\n    options.add(\"SyzygyProbeLimit\", Option(7, 0, 7));\n\n    options.add(  //\n      \"EvalFile\", Option(EvalFileDefaultNameBig, [this](const Option& o) {\n          load_big_network(o);\n          return std::nullopt;\n      }));\n\n    options.add(  //\n      \"EvalFileSmall\", Option(EvalFileDefaultNameSmall, [this](const Option& o) {\n          load_small_network(o);\n          return std::nullopt;\n      }));\n\n    threads.clear();\n    threads.ensure_network_replicated();\n    resize_threads();\n}\n\nstd::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {\n    verify_networks();\n\n    return Benchmark::perft(fen, depth, isChess960);\n}\n\nvoid Engine::go(Search::LimitsType& limits) {\n    assert(limits.perft == 0);\n    verify_networks();\n\n    threads.start_thinking(options, pos, states, limits);\n}\nvoid Engine::stop() { threads.stop = true; }\n\nvoid Engine::search_clear() {\n    wait_for_search_finished();\n\n    tt.clear(threads);\n    threads.clear();\n\n    // @TODO wont work with multiple instances\n    Tablebases::init(options[\"SyzygyPath\"]);  // Free mapped files\n}\n\nvoid Engine::set_on_update_no_moves(std::function<void(const Engine::InfoShort&)>&& f) {\n    updateContext.onUpdateNoMoves = std::move(f);\n}\n\nvoid Engine::set_on_update_full(std::function<void(const Engine::InfoFull&)>&& f) {\n    updateContext.onUpdateFull = std::move(f);\n}\n\nvoid Engine::set_on_iter(std::function<void(const Engine::InfoIter&)>&& f) {\n    updateContext.onIter = std::move(f);\n}\n\nvoid Engine::set_on_bestmove(std::function<void(std::string_view, std::string_view)>&& f) {\n    updateContext.onBestmove = std::move(f);\n}\n\nvoid Engine::set_on_verify_networks(std::function<void(std::string_view)>&& f) {\n    onVerifyNetworks = std::move(f);\n}\n\nvoid Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); }\n\nstd::optional<PositionSetError> Engine::set_position(const std::string&              fen,\n                                                     const std::vector<std::string>& moves) {\n    // Drop the old state and create a new one\n    states   = StateListPtr(new std::deque<StateInfo>(1));\n    auto err = pos.set(fen, options[\"UCI_Chess960\"], &states->back());\n    if (err.has_value())\n        return err;\n\n    for (const auto& move : moves)\n    {\n        auto m = UCIEngine::to_move(pos, move);\n\n        if (m == Move::none())\n            return PositionSetError(\"Illegal move: \" + move);\n\n        states->emplace_back();\n        pos.do_move(m, states->back());\n    }\n\n    return std::nullopt;\n}\n\n// modifiers\n\nvoid Engine::set_numa_config_from_option(const std::string& o) {\n    if (o == \"auto\" || o == \"system\")\n    {\n        numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy));\n    }\n    else if (o == \"hardware\")\n    {\n        // Don't respect affinity set in the system.\n        numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy, false));\n    }\n    else if (o == \"none\")\n    {\n        numaContext.set_numa_config(NumaConfig{});\n    }\n    else\n    {\n        numaContext.set_numa_config(NumaConfig::from_string(o));\n    }\n\n    // Force reallocation of threads in case affinities need to change.\n    resize_threads();\n    threads.ensure_network_replicated();\n}\n\nvoid Engine::resize_threads() {\n    threads.wait_for_search_finished();\n    threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, networks},\n                updateContext);\n\n    // Reallocate the hash with the new threadpool size\n    set_tt_size(options[\"Hash\"]);\n    threads.ensure_network_replicated();\n}\n\nvoid Engine::set_tt_size(size_t mb) {\n    wait_for_search_finished();\n    tt.resize(mb, threads);\n}\n\nvoid Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }\n\n// network related\n\nvoid Engine::verify_networks() const {\n    networks->big.verify(options[\"EvalFile\"], onVerifyNetworks);\n    networks->small.verify(options[\"EvalFileSmall\"], onVerifyNetworks);\n\n    auto statuses = networks.get_status_and_errors();\n    for (size_t i = 0; i < statuses.size(); ++i)\n    {\n        const auto [status, error] = statuses[i];\n        std::string message        = \"Network replica \" + std::to_string(i + 1) + \": \";\n        if (status == SystemWideSharedConstantAllocationStatus::NoAllocation)\n        {\n            message += \"No allocation.\";\n        }\n        else if (status == SystemWideSharedConstantAllocationStatus::LocalMemory)\n        {\n            message += \"Local memory.\";\n        }\n        else if (status == SystemWideSharedConstantAllocationStatus::SharedMemory)\n        {\n            message += \"Shared memory.\";\n        }\n        else\n        {\n            message += \"Unknown status.\";\n        }\n\n        if (error.has_value())\n        {\n            message += \" \" + *error;\n        }\n\n        onVerifyNetworks(message);\n    }\n}\n\nstd::unique_ptr<Eval::NNUE::Networks> Engine::get_default_networks() const {\n\n    auto networks_ =\n      std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, \"None\", \"\"},\n                                     NN::EvalFile{EvalFileDefaultNameSmall, \"None\", \"\"});\n\n    networks_->big.load(binaryDirectory, \"\");\n    networks_->small.load(binaryDirectory, \"\");\n\n    return networks_;\n}\n\nvoid Engine::load_big_network(const std::string& file) {\n    networks.modify_and_replicate(\n      [this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); });\n    threads.clear();\n    threads.ensure_network_replicated();\n}\n\nvoid Engine::load_small_network(const std::string& file) {\n    networks.modify_and_replicate(\n      [this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); });\n    threads.clear();\n    threads.ensure_network_replicated();\n}\n\nvoid Engine::save_network(const std::pair<std::optional<std::string>, std::string> files[2]) {\n    networks.modify_and_replicate([&files](NN::Networks& networks_) {\n        networks_.big.save(files[0].first);\n        networks_.small.save(files[1].first);\n    });\n}\n\n// utility functions\n\nvoid Engine::trace_eval() const {\n    StateListPtr trace_states(new std::deque<StateInfo>(1));\n    Position     p;\n    p.set(pos.fen(), options[\"UCI_Chess960\"], &trace_states->back());\n\n    verify_networks();\n\n    sync_cout << \"\\n\" << Eval::trace(p, *networks) << sync_endl;\n}\n\nconst OptionsMap& Engine::get_options() const { return options; }\nOptionsMap&       Engine::get_options() { return options; }\n\nstd::string Engine::fen() const { return pos.fen(); }\n\nvoid Engine::flip() { pos.flip(); }\n\nstd::string Engine::visualize() const {\n    std::stringstream ss;\n    ss << pos;\n    return ss.str();\n}\n\nint Engine::get_hashfull(int maxAge) const { return tt.hashfull(maxAge); }\n\nstd::vector<std::pair<size_t, size_t>> Engine::get_bound_thread_count_by_numa_node() const {\n    auto                                   counts = threads.get_bound_thread_count_by_numa_node();\n    const NumaConfig&                      cfg    = numaContext.get_numa_config();\n    std::vector<std::pair<size_t, size_t>> ratios;\n    NumaIndex                              n = 0;\n    for (; n < counts.size(); ++n)\n        ratios.emplace_back(counts[n], cfg.num_cpus_in_numa_node(n));\n    if (!counts.empty())\n        for (; n < cfg.num_numa_nodes(); ++n)\n            ratios.emplace_back(0, cfg.num_cpus_in_numa_node(n));\n    return ratios;\n}\n\nstd::string Engine::get_numa_config_as_string() const {\n    return numaContext.get_numa_config().to_string();\n}\n\nstd::string Engine::numa_config_information_as_string() const {\n    auto cfgStr = get_numa_config_as_string();\n    return \"Available processors: \" + cfgStr;\n}\n\nstd::string Engine::thread_binding_information_as_string() const {\n    auto              boundThreadsByNode = get_bound_thread_count_by_numa_node();\n    std::stringstream ss;\n    if (boundThreadsByNode.empty())\n        return ss.str();\n\n    bool isFirst = true;\n\n    for (auto&& [current, total] : boundThreadsByNode)\n    {\n        if (!isFirst)\n            ss << \":\";\n        ss << current << \"/\" << total;\n        isFirst = false;\n    }\n\n    return ss.str();\n}\n\nstd::string Engine::thread_allocation_information_as_string() const {\n    std::stringstream ss;\n\n    size_t threadsSize = threads.size();\n    ss << \"Using \" << threadsSize << (threadsSize > 1 ? \" threads\" : \" thread\");\n\n    auto boundThreadsByNodeStr = thread_binding_information_as_string();\n    if (boundThreadsByNodeStr.empty())\n        return ss.str();\n\n    ss << \" with NUMA node thread binding: \";\n    ss << boundThreadsByNodeStr;\n\n    return ss.str();\n}\n}\n"
  },
  {
    "path": "src/engine.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef ENGINE_H_INCLUDED\n#define ENGINE_H_INCLUDED\n\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <map>\n#include <memory>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"history.h\"\n#include \"nnue/network.h\"\n#include \"numa.h\"\n#include \"position.h\"\n#include \"search.h\"\n#include \"syzygy/tbprobe.h\"  // for Stockfish::Depth\n#include \"thread.h\"\n#include \"tt.h\"\n#include \"ucioption.h\"\n\nnamespace Stockfish {\n\nclass Engine {\n   public:\n    using InfoShort = Search::InfoShort;\n    using InfoFull  = Search::InfoFull;\n    using InfoIter  = Search::InfoIteration;\n\n    Engine(std::optional<std::string> path = std::nullopt);\n\n    // Cannot be movable due to components holding backreferences to fields\n    Engine(const Engine&)            = delete;\n    Engine(Engine&&)                 = delete;\n    Engine& operator=(const Engine&) = delete;\n    Engine& operator=(Engine&&)      = delete;\n\n    ~Engine() { wait_for_search_finished(); }\n\n    std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960);\n\n    // non blocking call to start searching\n    void go(Search::LimitsType&);\n    // non blocking call to stop searching\n    void stop();\n\n    // blocking call to wait for search to finish\n    void wait_for_search_finished();\n    // set a new position, moves are in UCI format\n    std::optional<PositionSetError> set_position(const std::string&              fen,\n                                                 const std::vector<std::string>& moves);\n\n    // modifiers\n\n    void set_numa_config_from_option(const std::string& o);\n    void resize_threads();\n    void set_tt_size(size_t mb);\n    void set_ponderhit(bool);\n    void search_clear();\n\n    void set_on_update_no_moves(std::function<void(const InfoShort&)>&&);\n    void set_on_update_full(std::function<void(const InfoFull&)>&&);\n    void set_on_iter(std::function<void(const InfoIter&)>&&);\n    void set_on_bestmove(std::function<void(std::string_view, std::string_view)>&&);\n    void set_on_verify_networks(std::function<void(std::string_view)>&&);\n\n    // network related\n\n    void                                  verify_networks() const;\n    std::unique_ptr<Eval::NNUE::Networks> get_default_networks() const;\n    void                                  load_big_network(const std::string& file);\n    void                                  load_small_network(const std::string& file);\n    void save_network(const std::pair<std::optional<std::string>, std::string> files[2]);\n\n    // utility functions\n\n    void trace_eval() const;\n\n    const OptionsMap& get_options() const;\n    OptionsMap&       get_options();\n\n    int get_hashfull(int maxAge = 0) const;\n\n    std::string                            fen() const;\n    void                                   flip();\n    std::string                            visualize() const;\n    std::vector<std::pair<size_t, size_t>> get_bound_thread_count_by_numa_node() const;\n    std::string                            get_numa_config_as_string() const;\n    std::string                            numa_config_information_as_string() const;\n    std::string                            thread_allocation_information_as_string() const;\n    std::string                            thread_binding_information_as_string() const;\n\n   private:\n    const std::string binaryDirectory;\n\n    NumaReplicationContext numaContext;\n\n    Position     pos;\n    StateListPtr states;\n\n    OptionsMap                                         options;\n    ThreadPool                                         threads;\n    TranspositionTable                                 tt;\n    LazyNumaReplicatedSystemWide<Eval::NNUE::Networks> networks;\n\n    Search::SearchManager::UpdateContext  updateContext;\n    std::function<void(std::string_view)> onVerifyNetworks;\n    std::map<NumaIndex, SharedHistories>  sharedHists;\n};\n\n}  // namespace Stockfish\n\n\n#endif  // #ifndef ENGINE_H_INCLUDED\n"
  },
  {
    "path": "src/evaluate.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"evaluate.h\"\n\n#include <algorithm>\n#include <cassert>\n#include <cmath>\n#include <cstdlib>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <tuple>\n\n#include \"nnue/network.h\"\n#include \"nnue/nnue_misc.h\"\n#include \"position.h\"\n#include \"types.h\"\n#include \"uci.h\"\n#include \"nnue/nnue_accumulator.h\"\n\nnamespace Stockfish {\n\n// Returns a static, purely materialistic evaluation of the position from\n// the point of view of the side to move. It can be divided by PawnValue to get\n// an approximation of the material advantage on the board in terms of pawns.\nint Eval::simple_eval(const Position& pos) {\n    Color c = pos.side_to_move();\n    return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c)) + pos.non_pawn_material(c)\n         - pos.non_pawn_material(~c);\n}\n\nbool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; }\n\n// Evaluate is the evaluator for the outer world. It returns a static evaluation\n// of the position from the point of view of the side to move.\nValue Eval::evaluate(const Eval::NNUE::Networks&    networks,\n                     const Position&                pos,\n                     Eval::NNUE::AccumulatorStack&  accumulators,\n                     Eval::NNUE::AccumulatorCaches& caches,\n                     int                            optimism) {\n\n    assert(!pos.checkers());\n\n    bool smallNet           = use_smallnet(pos);\n    auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small)\n                                       : networks.big.evaluate(pos, accumulators, caches.big);\n\n    Value nnue = (125 * psqt + 131 * positional) / 128;\n\n    // Re-evaluate the position when higher eval accuracy is worth the time spent\n    if (smallNet && (std::abs(nnue) < 277))\n    {\n        std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big);\n        nnue                       = (125 * psqt + 131 * positional) / 128;\n        smallNet                   = false;\n    }\n\n    // Blend optimism and eval with nnue complexity\n    int nnueComplexity = std::abs(psqt - positional);\n    optimism += optimism * nnueComplexity / 476;\n    nnue -= nnue * nnueComplexity / 18236;\n\n    int material = 534 * pos.count<PAWN>() + pos.non_pawn_material();\n    int v        = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871;\n\n    // Damp down the evaluation linearly when shuffling\n    v -= v * pos.rule50_count() / 199;\n\n    // Guarantee evaluation does not hit the tablebase range\n    v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);\n\n    return v;\n}\n\n// Like evaluate(), but instead of returning a value, it returns\n// a string (suitable for outputting to stdout) that contains the detailed\n// descriptions and values of each evaluation term. Useful for debugging.\n// Trace scores are from white's point of view\nstd::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {\n\n    if (pos.checkers())\n        return \"Final evaluation: none (in check)\";\n\n    auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>();\n    auto caches       = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);\n\n    std::stringstream ss;\n    ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);\n    ss << '\\n' << NNUE::trace(pos, networks, *caches) << '\\n';\n\n    ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);\n\n    auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big);\n    Value v                 = psqt + positional;\n    v                       = pos.side_to_move() == WHITE ? v : -v;\n    ss << \"NNUE evaluation        \" << 0.01 * UCIEngine::to_cp(v, pos) << \" (white side)\\n\";\n\n    v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO);\n    v = pos.side_to_move() == WHITE ? v : -v;\n    ss << \"Final evaluation       \" << 0.01 * UCIEngine::to_cp(v, pos) << \" (white side)\";\n    ss << \" [with scaled NNUE, ...]\";\n    ss << \"\\n\";\n\n    return ss.str();\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/evaluate.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef EVALUATE_H_INCLUDED\n#define EVALUATE_H_INCLUDED\n\n#include <string>\n\n#include \"types.h\"\n\nnamespace Stockfish {\n\nclass Position;\n\nnamespace Eval {\n\n// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue\n// for the build process (profile-build and fishtest) to work. Do not change the\n// name of the macro or the location where this macro is defined, as it is used\n// in the Makefile/Fishtest.\n#define EvalFileDefaultNameBig \"nn-9a0cc2a62c52.nnue\"\n#define EvalFileDefaultNameSmall \"nn-47fc8b7fff06.nnue\"\n\nnamespace NNUE {\nstruct Networks;\nstruct AccumulatorCaches;\nclass AccumulatorStack;\n}\n\nstd::string trace(Position& pos, const Eval::NNUE::Networks& networks);\n\nint   simple_eval(const Position& pos);\nbool  use_smallnet(const Position& pos);\nValue evaluate(const NNUE::Networks&          networks,\n               const Position&                pos,\n               Eval::NNUE::AccumulatorStack&  accumulators,\n               Eval::NNUE::AccumulatorCaches& caches,\n               int                            optimism);\n}  // namespace Eval\n\n}  // namespace Stockfish\n\n#endif  // #ifndef EVALUATE_H_INCLUDED\n"
  },
  {
    "path": "src/history.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef HISTORY_H_INCLUDED\n#define HISTORY_H_INCLUDED\n\n#include <algorithm>\n#include <array>\n#include <atomic>\n#include <cassert>\n#include <cmath>\n#include <cstdint>\n#include <cstdlib>\n#include <limits>\n#include <type_traits>  // IWYU pragma: keep\n\n#include \"memory.h\"\n#include \"misc.h\"\n#include \"position.h\"\n\nnamespace Stockfish {\n\nconstexpr int PAWN_HISTORY_BASE_SIZE   = 8192;  // has to be a power of 2\nconstexpr int UINT_16_HISTORY_SIZE     = std::numeric_limits<uint16_t>::max() + 1;\nconstexpr int CORRHIST_BASE_SIZE       = UINT_16_HISTORY_SIZE;\nconstexpr int CORRECTION_HISTORY_LIMIT = 1024;\nconstexpr int LOW_PLY_HISTORY_SIZE     = 5;\n\nstatic_assert((PAWN_HISTORY_BASE_SIZE & (PAWN_HISTORY_BASE_SIZE - 1)) == 0,\n              \"PAWN_HISTORY_BASE_SIZE has to be a power of 2\");\n\nstatic_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0,\n              \"CORRHIST_BASE_SIZE has to be a power of 2\");\n\n// StatsEntry is the container of various numerical statistics. We use a class\n// instead of a naked value to directly call history update operator<<() on\n// the entry. The first template parameter T is the base type of the array,\n// and the second template parameter D limits the range of updates in [-D, D]\n// when we update values with the << operator\ntemplate<typename T, int D, bool Atomic = false>\nstruct StatsEntry {\n    static_assert(std::is_arithmetic_v<T>, \"Not an arithmetic type\");\n\n   private:\n    std::conditional_t<Atomic, std::atomic<T>, T> entry;\n\n   public:\n    void operator=(const T& v) {\n        if constexpr (Atomic)\n            entry.store(v, std::memory_order_relaxed);\n        else\n            entry = v;\n    }\n\n    operator T() const {\n        if constexpr (Atomic)\n            return entry.load(std::memory_order_relaxed);\n        else\n            return entry;\n    }\n\n    void operator<<(int bonus) {\n        // Make sure that bonus is in range [-D, D]\n        int clampedBonus = std::clamp(bonus, -D, D);\n        T   val          = *this;\n        *this            = val + clampedBonus - val * std::abs(clampedBonus) / D;\n\n        assert(std::abs(T(*this)) <= D);\n    }\n};\n\nenum StatsType {\n    NoCaptures,\n    Captures\n};\n\ntemplate<typename T, int D, std::size_t... Sizes>\nusing Stats = MultiArray<StatsEntry<T, D>, Sizes...>;\n\ntemplate<typename T, int D, std::size_t... Sizes>\nusing AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;\n\n// DynStats is a dynamically sized array of Stats, used for thread-shared histories\n// which should scale with the total number of threads. The SizeMultiplier gives\n// the per-thread allocation count of T.\ntemplate<typename T, int SizeMultiplier>\nstruct DynStats {\n    explicit DynStats(size_t s) {\n        size = s * SizeMultiplier;\n        data = make_unique_large_page<T[]>(size);\n    }\n    // Sets all values in the range to 0\n    void clear_range(int value, size_t threadIdx, size_t numaTotal) {\n        size_t start = uint64_t(threadIdx) * size / numaTotal;\n        assert(start < size);\n        size_t end = threadIdx + 1 == numaTotal ? size : uint64_t(threadIdx + 1) * size / numaTotal;\n\n        while (start < end)\n            data[start++].fill(value);\n    }\n    size_t get_size() const { return size; }\n    T&     operator[](size_t index) {\n        assert(index < size);\n        return data.get()[index];\n    }\n    const T& operator[](size_t index) const {\n        assert(index < size);\n        return data.get()[index];\n    }\n\n   private:\n    size_t            size;\n    LargePagePtr<T[]> data;\n};\n\n// ButterflyHistory records how often quiet moves have been successful or unsuccessful\n// during the current search, and is used for reduction and move ordering decisions.\n// It uses 2 tables (one for each color) indexed by the move's from and to squares,\n// see https://www.chessprogramming.org/Butterfly_Boards\nusing ButterflyHistory = Stats<std::int16_t, 7183, COLOR_NB, UINT_16_HISTORY_SIZE>;\n\n// LowPlyHistory is addressed by ply and move's from and to squares, used\n// to improve move ordering near the root\nusing LowPlyHistory = Stats<std::int16_t, 7183, LOW_PLY_HISTORY_SIZE, UINT_16_HISTORY_SIZE>;\n\n// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]\nusing CapturePieceToHistory = Stats<std::int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;\n\n// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]\nusing PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;\n\n// ContinuationHistory is the combined history of a given pair of moves, usually\n// the current one given a previous one. The nested history table is based on\n// PieceToHistory instead of ButterflyBoards.\nusing ContinuationHistory = MultiArray<PieceToHistory, PIECE_NB, SQUARE_NB>;\n\n// PawnHistory is addressed by the pawn structure and a move's [piece][to]\nusing PawnHistory =\n  DynStats<AtomicStats<std::int16_t, 8192, PIECE_NB, SQUARE_NB>, PAWN_HISTORY_BASE_SIZE>;\n\n// Correction histories record differences between the static evaluation of\n// positions and their search score. It is used to improve the static evaluation\n// used by some search heuristics.\n// see https://www.chessprogramming.org/Static_Evaluation_Correction_History\nenum CorrHistType {\n    Pawn,          // By color and pawn structure\n    Minor,         // By color and positions of minor pieces (Knight, Bishop)\n    NonPawn,       // By non-pawn material positions and color\n    PieceTo,       // By [piece][to] move\n    Continuation,  // Combined history of move pairs\n};\n\ntemplate<typename T, int D>\nstruct CorrectionBundle {\n    StatsEntry<T, D, true> pawn;\n    StatsEntry<T, D, true> minor;\n    StatsEntry<T, D, true> nonPawnWhite;\n    StatsEntry<T, D, true> nonPawnBlack;\n\n    void operator=(T val) {\n        pawn         = val;\n        minor        = val;\n        nonPawnWhite = val;\n        nonPawnBlack = val;\n    }\n};\n\nnamespace Detail {\n\ntemplate<CorrHistType>\nstruct CorrHistTypedef {\n    using type =\n      DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB>, CORRHIST_BASE_SIZE>;\n};\n\ntemplate<>\nstruct CorrHistTypedef<PieceTo> {\n    using type = Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>;\n};\n\ntemplate<>\nstruct CorrHistTypedef<Continuation> {\n    using type = MultiArray<CorrHistTypedef<PieceTo>::type, PIECE_NB, SQUARE_NB>;\n};\n\ntemplate<>\nstruct CorrHistTypedef<NonPawn> {\n    using type = DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, COLOR_NB>,\n                          CORRHIST_BASE_SIZE>;\n};\n\n}\n\nusing UnifiedCorrectionHistory =\n  DynStats<MultiArray<CorrectionBundle<std::int16_t, CORRECTION_HISTORY_LIMIT>, COLOR_NB>,\n           CORRHIST_BASE_SIZE>;\n\ntemplate<CorrHistType T>\nusing CorrectionHistory = typename Detail::CorrHistTypedef<T>::type;\n\nusing TTMoveHistory = StatsEntry<std::int16_t, 8192>;\n\n// Set of histories shared between groups of threads. To avoid excessive\n// cross-node data transfer, histories are shared only between threads\n// on a given NUMA node. The passed size must be a power of two to make\n// the indexing more efficient.\nstruct SharedHistories {\n    SharedHistories(size_t threadCount) :\n        correctionHistory(threadCount),\n        pawnHistory(threadCount) {\n        assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0);\n        sizeMinus1         = correctionHistory.get_size() - 1;\n        pawnHistSizeMinus1 = pawnHistory.get_size() - 1;\n    }\n\n    size_t get_size() const { return sizeMinus1 + 1; }\n\n    auto& pawn_entry(const Position& pos) {\n        return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];\n    }\n    const auto& pawn_entry(const Position& pos) const {\n        return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];\n    }\n\n    auto& pawn_correction_entry(const Position& pos) {\n        return correctionHistory[pos.pawn_key() & sizeMinus1];\n    }\n    const auto& pawn_correction_entry(const Position& pos) const {\n        return correctionHistory[pos.pawn_key() & sizeMinus1];\n    }\n\n    auto& minor_piece_correction_entry(const Position& pos) {\n        return correctionHistory[pos.minor_piece_key() & sizeMinus1];\n    }\n    const auto& minor_piece_correction_entry(const Position& pos) const {\n        return correctionHistory[pos.minor_piece_key() & sizeMinus1];\n    }\n\n    template<Color c>\n    auto& nonpawn_correction_entry(const Position& pos) {\n        return correctionHistory[pos.non_pawn_key(c) & sizeMinus1];\n    }\n    template<Color c>\n    const auto& nonpawn_correction_entry(const Position& pos) const {\n        return correctionHistory[pos.non_pawn_key(c) & sizeMinus1];\n    }\n\n    UnifiedCorrectionHistory correctionHistory;\n    PawnHistory              pawnHistory;\n\n\n   private:\n    size_t sizeMinus1, pawnHistSizeMinus1;\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef HISTORY_H_INCLUDED\n"
  },
  {
    "path": "src/incbin/UNLICENCE",
    "content": "The file \"incbin.h\" is free and unencumbered software released into\nthe public domain by Dale Weiler, see:\n   <https://github.com/graphitemaster/incbin>\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org/>\n"
  },
  {
    "path": "src/incbin/incbin.h",
    "content": "/**\n * @file incbin.h\n * @author Dale Weiler\n * @brief Utility for including binary files\n *\n * Facilities for including binary files into the current translation unit and\n * making use from them externally in other translation units.\n */\n#ifndef INCBIN_HDR\n#define INCBIN_HDR\n#include <limits.h>\n#if   defined(__AVX512BW__) || \\\n      defined(__AVX512CD__) || \\\n      defined(__AVX512DQ__) || \\\n      defined(__AVX512ER__) || \\\n      defined(__AVX512PF__) || \\\n      defined(__AVX512VL__) || \\\n      defined(__AVX512F__)\n# define INCBIN_ALIGNMENT_INDEX 6\n#elif defined(__AVX__)      || \\\n      defined(__AVX2__)\n# define INCBIN_ALIGNMENT_INDEX 5\n#elif defined(__SSE__)      || \\\n      defined(__SSE2__)     || \\\n      defined(__SSE3__)     || \\\n      defined(__SSSE3__)    || \\\n      defined(__SSE4_1__)   || \\\n      defined(__SSE4_2__)   || \\\n      defined(__neon__)     || \\\n      defined(__ARM_NEON)   || \\\n      defined(__ALTIVEC__)\n# define INCBIN_ALIGNMENT_INDEX 4\n#elif ULONG_MAX != 0xffffffffu\n# define INCBIN_ALIGNMENT_INDEX 3\n# else\n# define INCBIN_ALIGNMENT_INDEX 2\n#endif\n\n/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */\n#define INCBIN_ALIGN_SHIFT_0 1\n#define INCBIN_ALIGN_SHIFT_1 2\n#define INCBIN_ALIGN_SHIFT_2 4\n#define INCBIN_ALIGN_SHIFT_3 8\n#define INCBIN_ALIGN_SHIFT_4 16\n#define INCBIN_ALIGN_SHIFT_5 32\n#define INCBIN_ALIGN_SHIFT_6 64\n\n/* Actual alignment value */\n#define INCBIN_ALIGNMENT \\\n    INCBIN_CONCATENATE( \\\n        INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \\\n        INCBIN_ALIGNMENT_INDEX)\n\n/* Stringize */\n#define INCBIN_STR(X) \\\n    #X\n#define INCBIN_STRINGIZE(X) \\\n    INCBIN_STR(X)\n/* Concatenate */\n#define INCBIN_CAT(X, Y) \\\n    X ## Y\n#define INCBIN_CONCATENATE(X, Y) \\\n    INCBIN_CAT(X, Y)\n/* Deferred macro expansion */\n#define INCBIN_EVAL(X) \\\n    X\n#define INCBIN_INVOKE(N, ...) \\\n    INCBIN_EVAL(N(__VA_ARGS__))\n/* Variable argument count for overloading by arity */\n#define INCBIN_VA_ARG_COUNTER(_1, _2, _3, N, ...) N\n#define INCBIN_VA_ARGC(...) INCBIN_VA_ARG_COUNTER(__VA_ARGS__, 3, 2, 1, 0)\n\n/* Green Hills uses a different directive for including binary data */\n#if defined(__ghs__)\n#  if (__ghs_asm == 2)\n#    define INCBIN_MACRO \".file\"\n/* Or consider the \".myrawdata\" entry in the ld file */\n#  else\n#    define INCBIN_MACRO \"\\tINCBIN\"\n#  endif\n#else\n#  define INCBIN_MACRO \".incbin\"\n#endif\n\n#ifndef _MSC_VER\n#  define INCBIN_ALIGN \\\n    __attribute__((aligned(INCBIN_ALIGNMENT)))\n#else\n#  define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))\n#endif\n\n#if defined(__arm__) || /* GNU C and RealView */ \\\n    defined(__arm) || /* Diab */ \\\n    defined(_ARM) /* ImageCraft */\n#  define INCBIN_ARM\n#endif\n\n#ifdef __GNUC__\n/* Utilize .balign where supported */\n#  define INCBIN_ALIGN_HOST \".balign \" INCBIN_STRINGIZE(INCBIN_ALIGNMENT) \"\\n\"\n#  define INCBIN_ALIGN_BYTE \".balign 1\\n\"\n#elif defined(INCBIN_ARM)\n/*\n * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is\n * the shift count. This is the value passed to `.align'\n */\n#  define INCBIN_ALIGN_HOST \".align \" INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) \"\\n\"\n#  define INCBIN_ALIGN_BYTE \".align 0\\n\"\n#else\n/* We assume other inline assembler's treat `.align' as `.balign' */\n#  define INCBIN_ALIGN_HOST \".align \" INCBIN_STRINGIZE(INCBIN_ALIGNMENT) \"\\n\"\n#  define INCBIN_ALIGN_BYTE \".align 1\\n\"\n#endif\n\n/* INCBIN_CONST is used by incbin.c generated files */\n#if defined(__cplusplus)\n#  define INCBIN_EXTERNAL extern \"C\"\n#  define INCBIN_CONST    extern const\n#else\n#  define INCBIN_EXTERNAL extern\n#  define INCBIN_CONST    const\n#endif\n\n/**\n * @brief Optionally override the linker section into which size and data is\n * emitted.\n * \n * @warning If you use this facility, you might have to deal with\n * platform-specific linker output section naming on your own.\n */\n#if !defined(INCBIN_OUTPUT_SECTION)\n#  if defined(__APPLE__)\n#    define INCBIN_OUTPUT_SECTION \".const_data\"\n#  else\n#    define INCBIN_OUTPUT_SECTION \".rodata\"\n#  endif\n#endif\n\n/**\n * @brief Optionally override the linker section into which data is emitted.\n *\n * @warning If you use this facility, you might have to deal with\n * platform-specific linker output section naming on your own.\n */\n#if !defined(INCBIN_OUTPUT_DATA_SECTION)\n#  define INCBIN_OUTPUT_DATA_SECTION INCBIN_OUTPUT_SECTION\n#endif\n\n/**\n * @brief Optionally override the linker section into which size is emitted.\n *\n * @warning If you use this facility, you might have to deal with\n * platform-specific linker output section naming on your own.\n * \n * @note This is useful for Harvard architectures where program memory cannot\n * be directly read from the program without special instructions. With this you\n * can chose to put the size variable in RAM rather than ROM.\n */\n#if !defined(INCBIN_OUTPUT_SIZE_SECTION)\n#  define INCBIN_OUTPUT_SIZE_SECTION INCBIN_OUTPUT_SECTION\n#endif\n\n#if defined(__APPLE__)\n#  include \"TargetConditionals.h\"\n#  if defined(TARGET_OS_IPHONE) && !defined(INCBIN_SILENCE_BITCODE_WARNING)\n#    warning \"incbin is incompatible with bitcode. Using the library will break upload to App Store if you have bitcode enabled. Add `#define INCBIN_SILENCE_BITCODE_WARNING` before including this header to silence this warning.\"\n#  endif\n/* The directives are different for Apple branded compilers */\n#  define INCBIN_SECTION         INCBIN_OUTPUT_SECTION \"\\n\"\n#  define INCBIN_GLOBAL(NAME)    \".globl \" INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME \"\\n\"\n#  define INCBIN_INT             \".long \"\n#  define INCBIN_MANGLE          \"_\"\n#  define INCBIN_BYTE            \".byte \"\n#  define INCBIN_TYPE(...)\n#else\n#  define INCBIN_SECTION         \".section \" INCBIN_OUTPUT_SECTION \"\\n\"\n#  define INCBIN_GLOBAL(NAME)    \".global \" INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME \"\\n\"\n#  if defined(__ghs__)\n#    define INCBIN_INT           \".word \"\n#  else\n#    define INCBIN_INT           \".int \"\n#  endif\n#  if defined(__USER_LABEL_PREFIX__)\n#    define INCBIN_MANGLE        INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)\n#  else\n#    define INCBIN_MANGLE        \"\"\n#  endif\n#  if defined(INCBIN_ARM)\n/* On arm assemblers, `@' is used as a line comment token */\n#    define INCBIN_TYPE(NAME)    \".type \" INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME \", %object\\n\"\n#  elif defined(__MINGW32__) || defined(__MINGW64__)\n/* Mingw doesn't support this directive either */\n#    define INCBIN_TYPE(NAME)\n#  else\n/* It's safe to use `@' on other architectures */\n#    define INCBIN_TYPE(NAME)    \".type \" INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME \", @object\\n\"\n#  endif\n#  define INCBIN_BYTE            \".byte \"\n#endif\n\n/* List of style types used for symbol names */\n#define INCBIN_STYLE_CAMEL 0\n#define INCBIN_STYLE_SNAKE 1\n\n/**\n * @brief Specify the prefix to use for symbol names.\n *\n * @note By default this is \"g\".\n *\n * @code\n * #define INCBIN_PREFIX incbin\n * #include \"incbin.h\"\n * INCBIN(Foo, \"foo.txt\");\n *\n * // Now you have the following symbols instead:\n * // const unsigned char incbinFoo<data>[];\n * // const unsigned char *const incbinFoo<end>;\n * // const unsigned int incbinFoo<size>;\n * @endcode\n */\n#if !defined(INCBIN_PREFIX)\n#  define INCBIN_PREFIX g\n#endif\n\n/**\n * @brief Specify the style used for symbol names.\n *\n * Possible options are\n * - INCBIN_STYLE_CAMEL \"CamelCase\"\n * - INCBIN_STYLE_SNAKE \"snake_case\"\n *\n * @note By default this is INCBIN_STYLE_CAMEL\n *\n * @code\n * #define INCBIN_STYLE INCBIN_STYLE_SNAKE\n * #include \"incbin.h\"\n * INCBIN(foo, \"foo.txt\");\n *\n * // Now you have the following symbols:\n * // const unsigned char <prefix>foo_data[];\n * // const unsigned char *const <prefix>foo_end;\n * // const unsigned int <prefix>foo_size;\n * @endcode\n */\n#if !defined(INCBIN_STYLE)\n#  define INCBIN_STYLE INCBIN_STYLE_CAMEL\n#endif\n\n/* Style lookup tables */\n#define INCBIN_STYLE_0_DATA Data\n#define INCBIN_STYLE_0_END End\n#define INCBIN_STYLE_0_SIZE Size\n#define INCBIN_STYLE_1_DATA _data\n#define INCBIN_STYLE_1_END _end\n#define INCBIN_STYLE_1_SIZE _size\n\n/* Style lookup: returning identifier */\n#define INCBIN_STYLE_IDENT(TYPE) \\\n    INCBIN_CONCATENATE( \\\n        INCBIN_STYLE_, \\\n        INCBIN_CONCATENATE( \\\n            INCBIN_EVAL(INCBIN_STYLE), \\\n            INCBIN_CONCATENATE(_, TYPE)))\n\n/* Style lookup: returning string literal */\n#define INCBIN_STYLE_STRING(TYPE) \\\n    INCBIN_STRINGIZE( \\\n        INCBIN_STYLE_IDENT(TYPE)) \\\n\n/* Generate the global labels by indirectly invoking the macro with our style\n * type and concatenating the name against them. */\n#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \\\n    INCBIN_INVOKE( \\\n        INCBIN_GLOBAL, \\\n        INCBIN_CONCATENATE( \\\n            NAME, \\\n            INCBIN_INVOKE( \\\n                INCBIN_STYLE_IDENT, \\\n                TYPE))) \\\n    INCBIN_INVOKE( \\\n        INCBIN_TYPE, \\\n        INCBIN_CONCATENATE( \\\n            NAME, \\\n            INCBIN_INVOKE( \\\n                INCBIN_STYLE_IDENT, \\\n                TYPE)))\n\n/**\n * @brief Externally reference binary data included in another translation unit.\n *\n * Produces three external symbols that reference the binary data included in\n * another translation unit.\n *\n * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with\n * \"Data\", as well as \"End\" and \"Size\" after. An example is provided below.\n *\n * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`.\n * @param NAME The name given for the binary data\n *\n * @code\n * INCBIN_EXTERN(Foo);\n *\n * // Now you have the following symbols:\n * // extern const unsigned char <prefix>Foo<data>[];\n * // extern const unsigned char *const <prefix>Foo<end>;\n * // extern const unsigned int <prefix>Foo<size>;\n * @endcode\n * \n * You may specify a custom optional data type as well as the first argument.\n * @code\n * INCBIN_EXTERN(custom_type, Foo);\n * \n * // Now you have the following symbols:\n * // extern const custom_type <prefix>Foo<data>[];\n * // extern const custom_type *const <prefix>Foo<end>;\n * // extern const unsigned int <prefix>Foo<size>;\n * @endcode\n */\n#define INCBIN_EXTERN(...) \\\n    INCBIN_CONCATENATE(INCBIN_EXTERN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__)\n#define INCBIN_EXTERN_1(NAME, ...) \\\n    INCBIN_EXTERN_2(unsigned char, NAME)\n#define INCBIN_EXTERN_2(TYPE, NAME) \\\n    INCBIN_EXTERNAL const INCBIN_ALIGN TYPE \\\n        INCBIN_CONCATENATE( \\\n            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \\\n            INCBIN_STYLE_IDENT(DATA))[]; \\\n    INCBIN_EXTERNAL const INCBIN_ALIGN TYPE *const \\\n    INCBIN_CONCATENATE( \\\n        INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \\\n        INCBIN_STYLE_IDENT(END)); \\\n    INCBIN_EXTERNAL const unsigned int \\\n        INCBIN_CONCATENATE( \\\n            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \\\n            INCBIN_STYLE_IDENT(SIZE))\n\n/**\n * @brief Externally reference textual data included in another translation unit.\n *\n * Produces three external symbols that reference the textual data included in\n * another translation unit.\n *\n * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with\n * \"Data\", as well as \"End\" and \"Size\" after. An example is provided below.\n *\n * @param NAME The name given for the textual data\n *\n * @code\n * INCBIN_EXTERN(Foo);\n *\n * // Now you have the following symbols:\n * // extern const char <prefix>Foo<data>[];\n * // extern const char *const <prefix>Foo<end>;\n * // extern const unsigned int <prefix>Foo<size>;\n * @endcode\n */\n#define INCTXT_EXTERN(NAME) \\\n    INCBIN_EXTERN_2(char, NAME)\n\n/**\n * @brief Include a binary file into the current translation unit.\n *\n * Includes a binary file into the current translation unit, producing three symbols\n * for objects that encode the data and size respectively.\n *\n * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with\n * \"Data\", as well as \"End\" and \"Size\" after. An example is provided below.\n *\n * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`.\n * @param NAME The name to associate with this binary data (as an identifier.)\n * @param FILENAME The file to include (as a string literal.)\n *\n * @code\n * INCBIN(Icon, \"icon.png\");\n *\n * // Now you have the following symbols:\n * // const unsigned char <prefix>Icon<data>[];\n * // const unsigned char *const <prefix>Icon<end>;\n * // const unsigned int <prefix>Icon<size>;\n * @endcode\n * \n * You may specify a custom optional data type as well as the first argument.\n * These macros are specialized by arity.\n * @code\n * INCBIN(custom_type, Icon, \"icon.png\");\n *\n * // Now you have the following symbols:\n * // const custom_type <prefix>Icon<data>[];\n * // const custom_type *const <prefix>Icon<end>;\n * // const unsigned int <prefix>Icon<size>;\n * @endcode\n *\n * @warning This must be used in global scope\n * @warning The identifiers may be different if INCBIN_STYLE is not default\n *\n * To externally reference the data included by this in another translation unit\n * please @see INCBIN_EXTERN.\n */\n#ifdef _MSC_VER\n#  define INCBIN(NAME, FILENAME) \\\n      INCBIN_EXTERN(NAME)\n#else\n#  define INCBIN(...) \\\n     INCBIN_CONCATENATE(INCBIN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__)\n#  if defined(__GNUC__)\n#    define INCBIN_1(...) _Pragma(\"GCC error \\\"Single argument INCBIN not allowed\\\"\")\n#  elif defined(__clang__)\n#    define INCBIN_1(...) _Pragma(\"clang error \\\"Single argument INCBIN not allowed\\\"\")\n#  else\n#    define INCBIN_1(...) /* Cannot do anything here */\n#  endif\n#  define INCBIN_2(NAME, FILENAME) \\\n      INCBIN_3(unsigned char, NAME, FILENAME)\n#  define INCBIN_3(TYPE, NAME, FILENAME) INCBIN_COMMON(TYPE, NAME, FILENAME, /* No terminator for binary data */)\n#  define INCBIN_COMMON(TYPE, NAME, FILENAME, TERMINATOR) \\\n    __asm__(INCBIN_SECTION \\\n            INCBIN_GLOBAL_LABELS(NAME, DATA) \\\n            INCBIN_ALIGN_HOST \\\n            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) \":\\n\" \\\n            INCBIN_MACRO \" \\\"\" FILENAME \"\\\"\\n\" \\\n                TERMINATOR \\\n            INCBIN_GLOBAL_LABELS(NAME, END) \\\n            INCBIN_ALIGN_BYTE \\\n            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) \":\\n\" \\\n                INCBIN_BYTE \"1\\n\" \\\n            INCBIN_GLOBAL_LABELS(NAME, SIZE) \\\n            INCBIN_ALIGN_HOST \\\n            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) \":\\n\" \\\n                INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) \" - \" \\\n                           INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) \"\\n\" \\\n            INCBIN_ALIGN_HOST \\\n            \".text\\n\" \\\n    ); \\\n    INCBIN_EXTERN(TYPE, NAME)\n#endif\n\n/**\n * @brief Include a textual file into the current translation unit.\n * \n * This behaves the same as INCBIN except it produces char compatible arrays\n * and implicitly adds a null-terminator byte, thus the size of data included\n * by this is one byte larger than that of INCBIN.\n *\n * Includes a textual file into the current translation unit, producing three\n * symbols for objects that encode the data and size respectively.\n *\n * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with\n * \"Data\", as well as \"End\" and \"Size\" after. An example is provided below.\n *\n * @param NAME The name to associate with this binary data (as an identifier.)\n * @param FILENAME The file to include (as a string literal.)\n *\n * @code\n * INCTXT(Readme, \"readme.txt\");\n *\n * // Now you have the following symbols:\n * // const char <prefix>Readme<data>[];\n * // const char *const <prefix>Readme<end>;\n * // const unsigned int <prefix>Readme<size>;\n * @endcode\n *\n * @warning This must be used in global scope\n * @warning The identifiers may be different if INCBIN_STYLE is not default\n *\n * To externally reference the data included by this in another translation unit\n * please @see INCBIN_EXTERN.\n */\n#if defined(_MSC_VER)\n#  define INCTXT(NAME, FILENAME) \\\n     INCBIN_EXTERN(NAME)\n#else\n#  define INCTXT(NAME, FILENAME) \\\n     INCBIN_COMMON(char, NAME, FILENAME, INCBIN_BYTE \"0\\n\")\n#endif\n\n#endif"
  },
  {
    "path": "src/main.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include <iostream>\n#include <memory>\n\n#include \"bitboard.h\"\n#include \"misc.h\"\n#include \"position.h\"\n#include \"tune.h\"\n#include \"uci.h\"\n\nusing namespace Stockfish;\n\nint main(int argc, char* argv[]) {\n    std::cout << engine_info() << std::endl;\n\n    Bitboards::init();\n    Position::init();\n\n    auto uci = std::make_unique<UCIEngine>(argc, argv);\n\n    Tune::init(uci->engine_options());\n\n    uci->loop();\n\n    return 0;\n}\n"
  },
  {
    "path": "src/memory.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"memory.h\"\n\n#include <cstdlib>\n\n#if __has_include(\"features.h\")\n    #include <features.h>\n#endif\n\n#if defined(__linux__) && !defined(__ANDROID__)\n    #include <sys/mman.h>\n#endif\n\n#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \\\n  || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \\\n  || defined(__e2k__)\n    #define POSIXALIGNEDALLOC\n    #include <stdlib.h>\n#endif\n\n#ifdef _WIN32\n    #if _WIN32_WINNT < 0x0601\n        #undef _WIN32_WINNT\n        #define _WIN32_WINNT 0x0601  // Force to include needed API prototypes\n    #endif\n\n    #ifndef NOMINMAX\n        #define NOMINMAX\n    #endif\n\n    #include <ios>       // std::hex, std::dec\n    #include <iostream>  // std::cerr\n    #include <ostream>   // std::endl\n    #include <windows.h>\n\n// The needed Windows API for processor groups could be missed from old Windows\n// versions, so instead of calling them directly (forcing the linker to resolve\n// the calls at compile time), try to load them at runtime. To do this we need\n// first to define the corresponding function pointers.\n\n#endif\n\n\nnamespace Stockfish {\n\n// Wrappers for systems where the c++17 implementation does not guarantee the\n// availability of aligned_alloc(). Memory allocated with std_aligned_alloc()\n// must be freed with std_aligned_free().\n\nvoid* std_aligned_alloc(size_t alignment, size_t size) {\n#if defined(_ISOC11_SOURCE)\n    return aligned_alloc(alignment, size);\n#elif defined(POSIXALIGNEDALLOC)\n    void* mem = nullptr;\n    posix_memalign(&mem, alignment, size);\n    return mem;\n#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)\n    return _mm_malloc(size, alignment);\n#elif defined(_WIN32)\n    return _aligned_malloc(size, alignment);\n#else\n    return std::aligned_alloc(alignment, size);\n#endif\n}\n\nvoid std_aligned_free(void* ptr) {\n\n#if defined(POSIXALIGNEDALLOC)\n    free(ptr);\n#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)\n    _mm_free(ptr);\n#elif defined(_WIN32)\n    _aligned_free(ptr);\n#else\n    free(ptr);\n#endif\n}\n\n// aligned_large_pages_alloc() will return suitably aligned memory,\n// if possible using large pages.\n\n#if defined(_WIN32)\n\nstatic void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {\n\n    return windows_try_with_large_page_priviliges(\n      [&](size_t largePageSize) {\n          // Round up size to full pages and allocate\n          allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);\n          return VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,\n                              PAGE_READWRITE);\n      },\n      []() { return (void*) nullptr; });\n}\n\nvoid* aligned_large_pages_alloc(size_t allocSize) {\n\n    // Try to allocate large pages\n    void* mem = aligned_large_pages_alloc_windows(allocSize);\n\n    // Fall back to regular, page-aligned, allocation if necessary\n    if (!mem)\n        mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);\n\n    return mem;\n}\n\n#else\n\nvoid* aligned_large_pages_alloc(size_t allocSize) {\n\n    #if defined(__linux__)\n    constexpr size_t alignment = 2 * 1024 * 1024;  // 2MB page size assumed\n    #else\n    constexpr size_t alignment = 4096;  // small page size assumed\n    #endif\n\n    // Round up to multiples of alignment\n    size_t size = ((allocSize + alignment - 1) / alignment) * alignment;\n    void*  mem  = std_aligned_alloc(alignment, size);\n    #if defined(MADV_HUGEPAGE)\n    madvise(mem, size, MADV_HUGEPAGE);\n    #endif\n    return mem;\n}\n\n#endif\n\nbool has_large_pages() {\n\n#if defined(_WIN32)\n\n    constexpr size_t page_size = 2 * 1024 * 1024;  // 2MB page size assumed\n    void*            mem       = aligned_large_pages_alloc_windows(page_size);\n    if (mem == nullptr)\n    {\n        return false;\n    }\n    else\n    {\n        aligned_large_pages_free(mem);\n        return true;\n    }\n\n#elif defined(__linux__)\n\n    #if defined(MADV_HUGEPAGE)\n    return true;\n    #else\n    return false;\n    #endif\n\n#else\n\n    return false;\n\n#endif\n}\n\n\n// aligned_large_pages_free() will free the previously memory allocated\n// by aligned_large_pages_alloc(). The effect is a nop if mem == nullptr.\n\n#if defined(_WIN32)\n\nvoid aligned_large_pages_free(void* mem) {\n\n    if (mem && !VirtualFree(mem, 0, MEM_RELEASE))\n    {\n        DWORD err = GetLastError();\n        std::cerr << \"Failed to free large page memory. Error code: 0x\" << std::hex << err\n                  << std::dec << std::endl;\n        exit(EXIT_FAILURE);\n    }\n}\n\n#else\n\nvoid aligned_large_pages_free(void* mem) { std_aligned_free(mem); }\n\n#endif\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/memory.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef MEMORY_H_INCLUDED\n#define MEMORY_H_INCLUDED\n\n#include <algorithm>\n#include <cstdint>\n#include <memory>\n#include <new>\n#include <type_traits>\n#include <utility>\n#include <cstring>\n\n#include \"types.h\"\n\n#if defined(_WIN64)\n\n    #if _WIN32_WINNT < 0x0601\n        #undef _WIN32_WINNT\n        #define _WIN32_WINNT 0x0601  // Force to include needed API prototypes\n    #endif\n\n    #if !defined(NOMINMAX)\n        #define NOMINMAX\n    #endif\n    #include <windows.h>\n\n    // Some Windows headers (RPC/old headers) define short macros such\n    // as 'small' expanding to 'char', which breaks identifiers in the code.\n    // Undefine those macros immediately after including <windows.h>.\n    #ifdef small\n        #undef small\n    #endif\n\n    #include <psapi.h>\n\nextern \"C\" {\nusing OpenProcessToken_t      = bool (*)(HANDLE, DWORD, PHANDLE);\nusing LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID);\nusing AdjustTokenPrivileges_t =\n  bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);\n}\n#endif\n\n\nnamespace Stockfish {\n\nvoid* std_aligned_alloc(size_t alignment, size_t size);\nvoid  std_aligned_free(void* ptr);\n\n// Memory aligned by page size, min alignment: 4096 bytes\nvoid* aligned_large_pages_alloc(size_t size);\nvoid  aligned_large_pages_free(void* mem);\n\nbool has_large_pages();\n\n// Frees memory which was placed there with placement new.\n// Works for both single objects and arrays of unknown bound.\ntemplate<typename T, typename FREE_FUNC>\nvoid memory_deleter(T* ptr, FREE_FUNC free_func) {\n    if (!ptr)\n        return;\n\n    // Explicitly needed to call the destructor\n    if constexpr (!std::is_trivially_destructible_v<T>)\n        ptr->~T();\n\n    free_func(ptr);\n}\n\n// Frees memory which was placed there with placement new.\n// Works for both single objects and arrays of unknown bound.\ntemplate<typename T, typename FREE_FUNC>\nvoid memory_deleter_array(T* ptr, FREE_FUNC free_func) {\n    if (!ptr)\n        return;\n\n\n    // Move back on the pointer to where the size is allocated\n    const size_t array_offset = std::max(sizeof(size_t), alignof(T));\n    char*        raw_memory   = reinterpret_cast<char*>(ptr) - array_offset;\n\n    if constexpr (!std::is_trivially_destructible_v<T>)\n    {\n        const size_t size = *reinterpret_cast<size_t*>(raw_memory);\n\n        // Explicitly call the destructor for each element in reverse order\n        for (size_t i = size; i-- > 0;)\n            ptr[i].~T();\n    }\n\n    free_func(raw_memory);\n}\n\n// Allocates memory for a single object and places it there with placement new\ntemplate<typename T, typename ALLOC_FUNC, typename... Args>\ninline std::enable_if_t<!std::is_array_v<T>, T*> memory_allocator(ALLOC_FUNC alloc_func,\n                                                                  Args&&... args) {\n    void* raw_memory = alloc_func(sizeof(T));\n    ASSERT_ALIGNED(raw_memory, alignof(T));\n    return new (raw_memory) T(std::forward<Args>(args)...);\n}\n\n// Allocates memory for an array of unknown bound and places it there with placement new\ntemplate<typename T, typename ALLOC_FUNC>\ninline std::enable_if_t<std::is_array_v<T>, std::remove_extent_t<T>*>\nmemory_allocator(ALLOC_FUNC alloc_func, size_t num) {\n    using ElementType = std::remove_extent_t<T>;\n\n    const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType));\n\n    // Save the array size in the memory location\n    char* raw_memory =\n      reinterpret_cast<char*>(alloc_func(array_offset + num * sizeof(ElementType)));\n    ASSERT_ALIGNED(raw_memory, alignof(T));\n\n    new (raw_memory) size_t(num);\n\n    for (size_t i = 0; i < num; ++i)\n        new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType();\n\n    // Need to return the pointer at the start of the array so that\n    // the indexing in unique_ptr<T[]> works.\n    return reinterpret_cast<ElementType*>(raw_memory + array_offset);\n}\n\n//\n//\n// aligned large page unique ptr\n//\n//\n\ntemplate<typename T>\nstruct LargePageDeleter {\n    void operator()(T* ptr) const { return memory_deleter<T>(ptr, aligned_large_pages_free); }\n};\n\ntemplate<typename T>\nstruct LargePageArrayDeleter {\n    void operator()(T* ptr) const { return memory_deleter_array<T>(ptr, aligned_large_pages_free); }\n};\n\ntemplate<typename T>\nusing LargePagePtr =\n  std::conditional_t<std::is_array_v<T>,\n                     std::unique_ptr<T, LargePageArrayDeleter<std::remove_extent_t<T>>>,\n                     std::unique_ptr<T, LargePageDeleter<T>>>;\n\n// make_unique_large_page for single objects\ntemplate<typename T, typename... Args>\nstd::enable_if_t<!std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(Args&&... args) {\n    static_assert(alignof(T) <= 4096,\n                  \"aligned_large_pages_alloc() may fail for such a big alignment requirement of T\");\n\n    T* obj = memory_allocator<T>(aligned_large_pages_alloc, std::forward<Args>(args)...);\n\n    return LargePagePtr<T>(obj);\n}\n\n// make_unique_large_page for arrays of unknown bound\ntemplate<typename T>\nstd::enable_if_t<std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(size_t num) {\n    using ElementType = std::remove_extent_t<T>;\n\n    static_assert(alignof(ElementType) <= 4096,\n                  \"aligned_large_pages_alloc() may fail for such a big alignment requirement of T\");\n\n    ElementType* memory = memory_allocator<T>(aligned_large_pages_alloc, num);\n\n    return LargePagePtr<T>(memory);\n}\n\n//\n//\n// aligned unique ptr\n//\n//\n\ntemplate<typename T>\nstruct AlignedDeleter {\n    void operator()(T* ptr) const { return memory_deleter<T>(ptr, std_aligned_free); }\n};\n\ntemplate<typename T>\nstruct AlignedArrayDeleter {\n    void operator()(T* ptr) const { return memory_deleter_array<T>(ptr, std_aligned_free); }\n};\n\ntemplate<typename T>\nusing AlignedPtr =\n  std::conditional_t<std::is_array_v<T>,\n                     std::unique_ptr<T, AlignedArrayDeleter<std::remove_extent_t<T>>>,\n                     std::unique_ptr<T, AlignedDeleter<T>>>;\n\n// make_unique_aligned for single objects\ntemplate<typename T, typename... Args>\nstd::enable_if_t<!std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(Args&&... args) {\n    const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); };\n    T*         obj  = memory_allocator<T>(func, std::forward<Args>(args)...);\n\n    return AlignedPtr<T>(obj);\n}\n\n// make_unique_aligned for arrays of unknown bound\ntemplate<typename T>\nstd::enable_if_t<std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(size_t num) {\n    using ElementType = std::remove_extent_t<T>;\n\n    const auto   func   = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); };\n    ElementType* memory = memory_allocator<T>(func, num);\n\n    return AlignedPtr<T>(memory);\n}\n\n\n// Get the first aligned element of an array.\n// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,\n// where N is the number of elements in the array.\ntemplate<uintptr_t Alignment, typename T>\nT* align_ptr_up(T* ptr) {\n    static_assert(alignof(T) < Alignment);\n\n    const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));\n    return reinterpret_cast<T*>(\n      reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));\n}\n\n#if defined(_WIN32)\n\ntemplate<typename FuncYesT, typename FuncNoT>\nauto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, FuncNoT&& fno) {\n\n    #if !defined(_WIN64)\n    return fno();\n    #else\n\n    HANDLE hProcessToken{};\n    LUID   luid{};\n\n    const size_t largePageSize = GetLargePageMinimum();\n    if (!largePageSize)\n        return fno();\n\n    // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges\n\n    HMODULE hAdvapi32 = GetModuleHandle(TEXT(\"advapi32.dll\"));\n\n    if (!hAdvapi32)\n        hAdvapi32 = LoadLibrary(TEXT(\"advapi32.dll\"));\n\n    auto OpenProcessToken_f =\n      OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, \"OpenProcessToken\"));\n    if (!OpenProcessToken_f)\n        return fno();\n    auto LookupPrivilegeValueA_f =\n      LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, \"LookupPrivilegeValueA\"));\n    if (!LookupPrivilegeValueA_f)\n        return fno();\n    auto AdjustTokenPrivileges_f =\n      AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, \"AdjustTokenPrivileges\"));\n    if (!AdjustTokenPrivileges_f)\n        return fno();\n\n    // We need SeLockMemoryPrivilege, so try to enable it for the process\n\n    if (!OpenProcessToken_f(  // OpenProcessToken()\n          GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))\n        return fno();\n\n    if (!LookupPrivilegeValueA_f(nullptr, \"SeLockMemoryPrivilege\", &luid))\n        return fno();\n\n    TOKEN_PRIVILEGES tp{};\n    TOKEN_PRIVILEGES prevTp{};\n    DWORD            prevTpLen = 0;\n\n    tp.PrivilegeCount           = 1;\n    tp.Privileges[0].Luid       = luid;\n    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n    // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges()\n    // succeeds, we still need to query GetLastError() to ensure that the privileges\n    // were actually obtained.\n\n    if (!AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp,\n                                 &prevTpLen)\n        || GetLastError() != ERROR_SUCCESS)\n        return fno();\n\n    auto&& ret = fyes(largePageSize);\n\n    // Privilege no longer needed, restore previous state\n    AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);\n\n    CloseHandle(hProcessToken);\n\n    return std::forward<decltype(ret)>(ret);\n\n    #endif\n}\n\n#endif\n\ntemplate<typename T, typename ByteT>\nT load_as(const ByteT* buffer) {\n    static_assert(std::is_trivially_copyable<T>::value, \"Type must be trivially copyable\");\n    static_assert(sizeof(ByteT) == 1);\n\n    T value;\n    std::memcpy(&value, buffer, sizeof(T));\n\n    return value;\n}\n\n}  // namespace Stockfish\n\n#endif  // #ifndef MEMORY_H_INCLUDED\n"
  },
  {
    "path": "src/misc.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"misc.h\"\n\n#include <array>\n#include <atomic>\n#include <cassert>\n#include <cctype>\n#include <cmath>\n#include <cstdlib>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n#include <limits>\n#include <mutex>\n#include <sstream>\n#include <string_view>\n\n#include \"types.h\"\n\nnamespace Stockfish {\n\nnamespace {\n\n// Version number or dev.\nconstexpr std::string_view version = \"dev\";\n\n// Our fancy logging facility. The trick here is to replace cin.rdbuf() and\n// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We\n// can toggle the logging of std::cout and std::cin at runtime whilst preserving\n// usual I/O functionality, all without changing a single line of code!\n// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81\n\nstruct Tie: public std::streambuf {  // MSVC requires split streambuf for cin and cout\n\n    Tie(std::streambuf* b, std::streambuf* l) :\n        buf(b),\n        logBuf(l) {}\n\n    int sync() override { return logBuf->pubsync(), buf->pubsync(); }\n    int overflow(int c) override { return log(buf->sputc(char(c)), \"<< \"); }\n    int underflow() override { return buf->sgetc(); }\n    int uflow() override { return log(buf->sbumpc(), \">> \"); }\n\n    std::streambuf *buf, *logBuf;\n\n    int log(int c, const char* prefix) {\n\n        static int last = '\\n';  // Single log file\n\n        if (last == '\\n')\n            logBuf->sputn(prefix, 3);\n\n        return last = logBuf->sputc(char(c));\n    }\n};\n\nclass Logger {\n\n    Logger() :\n        in(std::cin.rdbuf(), file.rdbuf()),\n        out(std::cout.rdbuf(), file.rdbuf()) {}\n    ~Logger() { start(\"\"); }\n\n    std::ofstream file;\n    Tie           in, out;\n\n   public:\n    static void start(const std::string& fname) {\n\n        static Logger l;\n\n        if (l.file.is_open())\n        {\n            std::cout.rdbuf(l.out.buf);\n            std::cin.rdbuf(l.in.buf);\n            l.file.close();\n        }\n\n        if (!fname.empty())\n        {\n            l.file.open(fname, std::ifstream::out);\n\n            if (!l.file.is_open())\n            {\n                std::cerr << \"Unable to open debug log file \" << fname << std::endl;\n                exit(EXIT_FAILURE);\n            }\n\n            std::cin.rdbuf(&l.in);\n            std::cout.rdbuf(&l.out);\n        }\n    }\n};\n\n}  // namespace\n\n\n// Returns the full name of the current Stockfish version.\n//\n// For local dev compiles we try to append the commit SHA and\n// commit date from git. If that fails only the local compilation\n// date is set and \"nogit\" is specified:\n//      Stockfish dev-YYYYMMDD-SHA\n//      or\n//      Stockfish dev-YYYYMMDD-nogit\n//\n// For releases (non-dev builds) we only include the version number:\n//      Stockfish version\nstd::string engine_version_info() {\n    std::stringstream ss;\n    ss << \"Stockfish \" << version << std::setfill('0');\n\n    if constexpr (version == \"dev\")\n    {\n        ss << \"-\";\n#ifdef GIT_DATE\n        ss << stringify(GIT_DATE);\n#else\n        constexpr std::string_view months(\"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\");\n\n        std::string       month, day, year;\n        std::stringstream date(__DATE__);  // From compiler, format is \"Sep 21 2008\"\n\n        date >> month >> day >> year;\n        ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4)\n           << std::setw(2) << std::setfill('0') << day;\n#endif\n\n        ss << \"-\";\n\n#ifdef GIT_SHA\n        ss << stringify(GIT_SHA);\n#else\n        ss << \"nogit\";\n#endif\n    }\n\n    return ss.str();\n}\n\nstd::string engine_info(bool to_uci) {\n    return engine_version_info() + (to_uci ? \"\\nid author \" : \" by \")\n         + \"the Stockfish developers (see AUTHORS file)\";\n}\n\n\n// Returns a string trying to describe the compiler we use\nstd::string compiler_info() {\n\n#define make_version_string(major, minor, patch) \\\n    stringify(major) \".\" stringify(minor) \".\" stringify(patch)\n\n    // Predefined macros hell:\n    //\n    // __GNUC__                Compiler is GCC, Clang or ICX\n    // __clang__               Compiler is Clang or ICX\n    // __INTEL_LLVM_COMPILER   Compiler is ICX\n    // _MSC_VER                Compiler is MSVC\n    // _WIN32                  Building on Windows (any)\n    // _WIN64                  Building on Windows 64 bit\n\n    std::string compiler = \"\\nCompiled by                : \";\n\n#if defined(__INTEL_LLVM_COMPILER)\n    compiler += \"ICX \";\n    compiler += stringify(__INTEL_LLVM_COMPILER);\n#elif defined(__clang__)\n    compiler += \"clang++ \";\n    compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);\n#elif _MSC_VER\n    compiler += \"MSVC \";\n    compiler += \"(version \";\n    compiler += stringify(_MSC_FULL_VER) \".\" stringify(_MSC_BUILD);\n    compiler += \")\";\n#elif defined(__e2k__) && defined(__LCC__)\n    #define dot_ver2(n) \\\n        compiler += char('.'); \\\n        compiler += char('0' + (n) / 10); \\\n        compiler += char('0' + (n) % 10);\n\n    compiler += \"MCST LCC \";\n    compiler += \"(version \";\n    compiler += std::to_string(__LCC__ / 100);\n    dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += \")\";\n#elif __GNUC__\n    compiler += \"g++ (GNUC) \";\n    compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);\n#else\n    compiler += \"Unknown compiler \";\n    compiler += \"(unknown version)\";\n#endif\n\n#if defined(__APPLE__)\n    compiler += \" on Apple\";\n#elif defined(__CYGWIN__)\n    compiler += \" on Cygwin\";\n#elif defined(__MINGW64__)\n    compiler += \" on MinGW64\";\n#elif defined(__MINGW32__)\n    compiler += \" on MinGW32\";\n#elif defined(__ANDROID__)\n    compiler += \" on Android\";\n#elif defined(__linux__)\n    compiler += \" on Linux\";\n#elif defined(_WIN64)\n    compiler += \" on Microsoft Windows 64-bit\";\n#elif defined(_WIN32)\n    compiler += \" on Microsoft Windows 32-bit\";\n#else\n    compiler += \" on unknown system\";\n#endif\n\n    compiler += \"\\nCompilation architecture   : \";\n#if defined(ARCH)\n    compiler += stringify(ARCH);\n#else\n    compiler += \"(undefined architecture)\";\n#endif\n\n    compiler += \"\\nCompilation settings       : \";\n    compiler += (Is64Bit ? \"64bit\" : \"32bit\");\n#if defined(USE_AVX512ICL)\n    compiler += \" AVX512ICL\";\n#endif\n#if defined(USE_VNNI)\n    compiler += \" VNNI\";\n#endif\n#if defined(USE_AVX512)\n    compiler += \" AVX512\";\n#endif\n    compiler += (HasPext ? \" BMI2\" : \"\");\n#if defined(USE_AVX2)\n    compiler += \" AVX2\";\n#endif\n#if defined(USE_SSE41)\n    compiler += \" SSE41\";\n#endif\n#if defined(USE_SSSE3)\n    compiler += \" SSSE3\";\n#endif\n#if defined(USE_SSE2)\n    compiler += \" SSE2\";\n#endif\n#if defined(USE_NEON_DOTPROD)\n    compiler += \" NEON_DOTPROD\";\n#elif defined(USE_NEON)\n    compiler += \" NEON\";\n#endif\n    compiler += (HasPopCnt ? \" POPCNT\" : \"\");\n\n#if !defined(NDEBUG)\n    compiler += \" DEBUG\";\n#endif\n\n    compiler += \"\\nCompiler __VERSION__ macro : \";\n#ifdef __VERSION__\n    compiler += __VERSION__;\n#else\n    compiler += \"(undefined macro)\";\n#endif\n\n    compiler += \"\\n\";\n\n    return compiler;\n}\n\n\n// Debug functions used mainly to collect run-time statistics\nconstexpr int MaxDebugSlots = 32;\n\nnamespace {\n\ntemplate<size_t N>\nstruct DebugInfo {\n    std::array<std::atomic<int64_t>, N> data = {0};\n\n    [[nodiscard]] constexpr std::atomic<int64_t>& operator[](size_t index) {\n        assert(index < N);\n        return data[index];\n    }\n\n    constexpr DebugInfo& operator=(const DebugInfo& other) {\n        for (size_t i = 0; i < N; i++)\n            data[i].store(other.data[i].load());\n        return *this;\n    }\n};\n\nstruct DebugExtremes: public DebugInfo<3> {\n    DebugExtremes() {\n        data[1] = std::numeric_limits<int64_t>::min();\n        data[2] = std::numeric_limits<int64_t>::max();\n    }\n};\n\nstd::array<DebugInfo<2>, MaxDebugSlots>  hit;\nstd::array<DebugInfo<2>, MaxDebugSlots>  mean;\nstd::array<DebugInfo<3>, MaxDebugSlots>  stdev;\nstd::array<DebugInfo<6>, MaxDebugSlots>  correl;\nstd::array<DebugExtremes, MaxDebugSlots> extremes;\n\n}  // namespace\n\nvoid dbg_hit_on(bool cond, int slot) {\n\n    ++hit.at(slot)[0];\n    if (cond)\n        ++hit.at(slot)[1];\n}\n\nvoid dbg_mean_of(int64_t value, int slot) {\n\n    ++mean.at(slot)[0];\n    mean.at(slot)[1] += value;\n}\n\nvoid dbg_stdev_of(int64_t value, int slot) {\n\n    ++stdev.at(slot)[0];\n    stdev.at(slot)[1] += value;\n    stdev.at(slot)[2] += value * value;\n}\n\nvoid dbg_extremes_of(int64_t value, int slot) {\n    ++extremes.at(slot)[0];\n\n    int64_t current_max = extremes.at(slot)[1].load();\n    while (current_max < value && !extremes.at(slot)[1].compare_exchange_weak(current_max, value))\n    {}\n\n    int64_t current_min = extremes.at(slot)[2].load();\n    while (current_min > value && !extremes.at(slot)[2].compare_exchange_weak(current_min, value))\n    {}\n}\n\nvoid dbg_correl_of(int64_t value1, int64_t value2, int slot) {\n\n    ++correl.at(slot)[0];\n    correl.at(slot)[1] += value1;\n    correl.at(slot)[2] += value1 * value1;\n    correl.at(slot)[3] += value2;\n    correl.at(slot)[4] += value2 * value2;\n    correl.at(slot)[5] += value1 * value2;\n}\n\nvoid dbg_print() {\n\n    int64_t n;\n    auto    E   = [&n](int64_t x) { return double(x) / n; };\n    auto    sqr = [](double x) { return x * x; };\n\n    for (int i = 0; i < MaxDebugSlots; ++i)\n        if ((n = hit[i][0]))\n            std::cerr << \"Hit #\" << i << \": Total \" << n << \" Hits \" << hit[i][1]\n                      << \" Hit Rate (%) \" << 100.0 * E(hit[i][1]) << std::endl;\n\n    for (int i = 0; i < MaxDebugSlots; ++i)\n        if ((n = mean[i][0]))\n        {\n            std::cerr << \"Mean #\" << i << \": Total \" << n << \" Mean \" << E(mean[i][1]) << std::endl;\n        }\n\n    for (int i = 0; i < MaxDebugSlots; ++i)\n        if ((n = stdev[i][0]))\n        {\n            double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1])));\n            std::cerr << \"Stdev #\" << i << \": Total \" << n << \" Stdev \" << r << std::endl;\n        }\n\n    for (int i = 0; i < MaxDebugSlots; ++i)\n        if ((n = extremes[i][0]))\n        {\n            std::cerr << \"Extremity #\" << i << \": Total \" << n << \" Min \" << extremes[i][2]\n                      << \" Max \" << extremes[i][1] << std::endl;\n        }\n\n    for (int i = 0; i < MaxDebugSlots; ++i)\n        if ((n = correl[i][0]))\n        {\n            double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))\n                     / (sqrt(E(correl[i][2]) - sqr(E(correl[i][1])))\n                        * sqrt(E(correl[i][4]) - sqr(E(correl[i][3]))));\n            std::cerr << \"Correl. #\" << i << \": Total \" << n << \" Coefficient \" << r << std::endl;\n        }\n}\n\nvoid dbg_clear() {\n    hit.fill({});\n    mean.fill({});\n    stdev.fill({});\n    correl.fill({});\n    extremes.fill({});\n}\n\n// Used to serialize access to std::cout\n// to avoid multiple threads writing at the same time.\nstd::ostream& operator<<(std::ostream& os, SyncCout sc) {\n\n    static std::mutex m;\n\n    if (sc == IO_LOCK)\n        m.lock();\n\n    if (sc == IO_UNLOCK)\n        m.unlock();\n\n    return os;\n}\n\nvoid sync_cout_start() { std::cout << IO_LOCK; }\nvoid sync_cout_end() { std::cout << IO_UNLOCK; }\n\n// Hash function based on public domain MurmurHash64A, by Austin Appleby.\nuint64_t hash_bytes(const char* data, size_t size) {\n    const uint64_t m = 0xc6a4a7935bd1e995ull;\n    const int      r = 47;\n\n    uint64_t h = size * m;\n\n    const char* end = data + (size & ~(size_t) 7);\n\n    for (const char* p = data; p != end; p += 8)\n    {\n        uint64_t k;\n        std::memcpy(&k, p, sizeof(k));\n\n        k *= m;\n        k ^= k >> r;\n        k *= m;\n\n        h ^= k;\n        h *= m;\n    }\n\n    if (size & 7)\n    {\n        uint64_t k = 0;\n        for (int i = (size & 7) - 1; i >= 0; i--)\n            k = (k << 8) | (uint64_t) end[i];\n\n        h ^= k;\n        h *= m;\n    }\n\n    h ^= h >> r;\n    h *= m;\n    h ^= h >> r;\n\n    return h;\n}\n\n// Trampoline helper to avoid moving Logger to misc.h\nvoid start_logger(const std::string& fname) { Logger::start(fname); }\n\n\n#ifdef _WIN32\n    #include <direct.h>\n    #define GETCWD _getcwd\n#else\n    #include <unistd.h>\n    #define GETCWD getcwd\n#endif\n\nsize_t str_to_size_t(const std::string& s) {\n    unsigned long long value = std::stoull(s);\n    if (value > std::numeric_limits<size_t>::max())\n        std::exit(EXIT_FAILURE);\n    return static_cast<size_t>(value);\n}\n\nstd::optional<std::string> read_file_to_string(const std::string& path) {\n    std::ifstream f(path, std::ios_base::binary);\n    if (!f)\n        return std::nullopt;\n    return std::string(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>());\n}\n\nvoid remove_whitespace(std::string& s) {\n    s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return std::isspace(c); }), s.end());\n}\n\nbool is_whitespace(std::string_view s) {\n    return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); });\n}\n\nstd::string CommandLine::get_binary_directory(std::string argv0) {\n    std::string pathSeparator;\n\n#ifdef _WIN32\n    pathSeparator = \"\\\\\";\n    #ifdef _MSC_VER\n    // Under windows argv[0] may not have the extension. Also _get_pgmptr() had\n    // issues in some Windows 10 versions, so check returned values carefully.\n    char* pgmptr = nullptr;\n    if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)\n        argv0 = pgmptr;\n    #endif\n#else\n    pathSeparator = \"/\";\n#endif\n\n    // Extract the working directory\n    auto workingDirectory = CommandLine::get_working_directory();\n\n    // Extract the binary directory path from argv0\n    auto   binaryDirectory = argv0;\n    size_t pos             = binaryDirectory.find_last_of(\"\\\\/\");\n    if (pos == std::string::npos)\n        binaryDirectory = \".\" + pathSeparator;\n    else\n        binaryDirectory.resize(pos + 1);\n\n    // Pattern replacement: \"./\" at the start of path is replaced by the working directory\n    if (binaryDirectory.find(\".\" + pathSeparator) == 0)\n        binaryDirectory.replace(0, 1, workingDirectory);\n\n    return binaryDirectory;\n}\n\nstd::string CommandLine::get_working_directory() {\n    std::string workingDirectory = \"\";\n    char        buff[40000];\n    char*       cwd = GETCWD(buff, 40000);\n    if (cwd)\n        workingDirectory = cwd;\n\n    return workingDirectory;\n}\n\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/misc.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef MISC_H_INCLUDED\n#define MISC_H_INCLUDED\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <chrono>\n#include <cstdint>\n#include <cstdio>\n#include <exception>  // IWYU pragma: keep\n// IWYU pragma: no_include <__exception/terminate.h>\n#include <functional>\n#include <iosfwd>\n#include <optional>\n#include <cstring>\n#include <memory>\n#include <string>\n#include <string_view>\n#include <type_traits>\n#include <vector>\n\n#if !defined(NO_PREFETCH) && (defined(_MSC_VER) || defined(__INTEL_COMPILER))\n    #include <immintrin.h>\n#endif\n\n#define stringify2(x) #x\n#define stringify(x) stringify2(x)\n\nnamespace Stockfish {\n\nstd::string engine_version_info();\nstd::string engine_info(bool to_uci = false);\nstd::string compiler_info();\n\n// Prefetch hint enums for explicit call-site control.\nenum class PrefetchRw {\n    READ,\n    WRITE\n};\n\n// NOTE: PrefetchLoc controls locality / cache level, not whether a prefetch\n//       is issued. In particular, PrefetchLoc::NONE maps to a non-temporal /\n//       lowest-locality prefetch (Intel: _MM_HINT_NTA, GCC/Clang: locality = 0)\n//       and therefore still performs a prefetch. To completely disable\n//       prefetching, define NO_PREFETCH so that prefetch() becomes a no-op.\nenum class PrefetchLoc {\n    NONE,      // Non-temporal / no cache locality (still issues a prefetch)\n    LOW,       // Low locality (e.g. T2 / L2)\n    MODERATE,  // Moderate locality (e.g. T1 / L1)\n    HIGH       // High locality (e.g. T0 / closest cache)\n};\n\n// Preloads the given address into cache. This is a non-blocking\n// function that doesn't stall the CPU waiting for data to be loaded from memory,\n// which can be quite slow.\n#ifdef NO_PREFETCH\ntemplate<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>\nvoid prefetch(const void*) {}\n#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)\n\nconstexpr int get_intel_hint(PrefetchRw rw, PrefetchLoc loc) {\n    if (rw == PrefetchRw::WRITE)\n    {\n    #ifdef _MM_HINT_ET0\n        return _MM_HINT_ET0;\n    #else\n        // Fallback when write-prefetch hint is not available: use T0\n        return _MM_HINT_T0;\n    #endif\n    }\n    switch (loc)\n    {\n    case PrefetchLoc::NONE :\n        return _MM_HINT_NTA;\n    case PrefetchLoc::LOW :\n        return _MM_HINT_T2;\n    case PrefetchLoc::MODERATE :\n        return _MM_HINT_T1;\n    case PrefetchLoc::HIGH :\n        return _MM_HINT_T0;\n    default :\n        return _MM_HINT_T0;\n    }\n}\n\ntemplate<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>\nvoid prefetch(const void* addr) {\n    _mm_prefetch(static_cast<const char*>(addr), get_intel_hint(RW, LOC));\n}\n#else\ntemplate<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>\nvoid prefetch(const void* addr) {\n    __builtin_prefetch(addr, static_cast<int>(RW), static_cast<int>(LOC));\n}\n#endif\n\nvoid start_logger(const std::string& fname);\n\nsize_t str_to_size_t(const std::string& s);\n\n#if defined(__linux__)\n\nstruct PipeDeleter {\n    void operator()(FILE* file) const {\n        if (file != nullptr)\n        {\n            pclose(file);\n        }\n    }\n};\n\n#endif\n\n// Reads the file as bytes.\n// Returns std::nullopt if the file does not exist.\nstd::optional<std::string> read_file_to_string(const std::string& path);\n\nvoid dbg_hit_on(bool cond, int slot = 0);\nvoid dbg_mean_of(int64_t value, int slot = 0);\nvoid dbg_stdev_of(int64_t value, int slot = 0);\nvoid dbg_extremes_of(int64_t value, int slot = 0);\nvoid dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);\nvoid dbg_print();\nvoid dbg_clear();\n\nusing TimePoint = std::chrono::milliseconds::rep;  // A value in milliseconds\nstatic_assert(sizeof(TimePoint) == sizeof(int64_t), \"TimePoint should be 64 bits\");\ninline TimePoint now() {\n    return std::chrono::duration_cast<std::chrono::milliseconds>(\n             std::chrono::steady_clock::now().time_since_epoch())\n      .count();\n}\n\ninline std::vector<std::string_view> split(std::string_view s, std::string_view delimiter) {\n    std::vector<std::string_view> res;\n\n    if (s.empty())\n        return res;\n\n    size_t begin = 0;\n    for (;;)\n    {\n        const size_t end = s.find(delimiter, begin);\n        if (end == std::string::npos)\n            break;\n\n        res.emplace_back(s.substr(begin, end - begin));\n        begin = end + delimiter.size();\n    }\n\n    res.emplace_back(s.substr(begin));\n\n    return res;\n}\n\nvoid remove_whitespace(std::string& s);\nbool is_whitespace(std::string_view s);\n\nenum SyncCout {\n    IO_LOCK,\n    IO_UNLOCK\n};\nstd::ostream& operator<<(std::ostream&, SyncCout);\n\n#define sync_cout std::cout << IO_LOCK\n#define sync_endl std::endl << IO_UNLOCK\n\nvoid sync_cout_start();\nvoid sync_cout_end();\n\n// True if and only if the binary is compiled on a little-endian machine\nstatic inline const std::uint16_t Le             = 1;\nstatic inline const bool          IsLittleEndian = *reinterpret_cast<const char*>(&Le) == 1;\n\n\ntemplate<typename T, std::size_t MaxSize>\nclass ValueList {\n\n   public:\n    std::size_t size() const { return size_; }\n    int         ssize() const { return int(size_); }\n    void        push_back(const T& value) {\n        assert(size_ < MaxSize);\n        values_[size_++] = value;\n    }\n    const T* begin() const { return values_; }\n    const T* end() const { return values_ + size_; }\n    const T& operator[](int index) const { return values_[index]; }\n\n    T* make_space(size_t count) {\n        T* result = &values_[size_];\n        size_ += count;\n        assert(size_ <= MaxSize);\n        return result;\n    }\n\n   private:\n    T           values_[MaxSize];\n    std::size_t size_ = 0;\n};\n\n\ntemplate<typename T, std::size_t Size, std::size_t... Sizes>\nclass MultiArray;\n\nnamespace Detail {\n\ntemplate<typename T, std::size_t Size, std::size_t... Sizes>\nstruct MultiArrayHelper {\n    using ChildType = MultiArray<T, Sizes...>;\n};\n\ntemplate<typename T, std::size_t Size>\nstruct MultiArrayHelper<T, Size> {\n    using ChildType = T;\n};\n\ntemplate<typename To, typename From>\nconstexpr bool is_strictly_assignable_v =\n  std::is_assignable_v<To&, From> && (std::is_same_v<To, From> || !std::is_convertible_v<From, To>);\n\n}\n\n// MultiArray is a generic N-dimensional array.\n// The template parameters (Size and Sizes) encode the dimensions of the array.\ntemplate<typename T, std::size_t Size, std::size_t... Sizes>\nclass MultiArray {\n    using ChildType = typename Detail::MultiArrayHelper<T, Size, Sizes...>::ChildType;\n    using ArrayType = std::array<ChildType, Size>;\n    ArrayType data_;\n\n   public:\n    using value_type             = typename ArrayType::value_type;\n    using size_type              = typename ArrayType::size_type;\n    using difference_type        = typename ArrayType::difference_type;\n    using reference              = typename ArrayType::reference;\n    using const_reference        = typename ArrayType::const_reference;\n    using pointer                = typename ArrayType::pointer;\n    using const_pointer          = typename ArrayType::const_pointer;\n    using iterator               = typename ArrayType::iterator;\n    using const_iterator         = typename ArrayType::const_iterator;\n    using reverse_iterator       = typename ArrayType::reverse_iterator;\n    using const_reverse_iterator = typename ArrayType::const_reverse_iterator;\n\n    constexpr auto&       at(size_type index) noexcept { return data_.at(index); }\n    constexpr const auto& at(size_type index) const noexcept { return data_.at(index); }\n\n    constexpr auto&       operator[](size_type index) noexcept { return data_[index]; }\n    constexpr const auto& operator[](size_type index) const noexcept { return data_[index]; }\n\n    constexpr auto&       front() noexcept { return data_.front(); }\n    constexpr const auto& front() const noexcept { return data_.front(); }\n    constexpr auto&       back() noexcept { return data_.back(); }\n    constexpr const auto& back() const noexcept { return data_.back(); }\n\n    auto*       data() { return data_.data(); }\n    const auto* data() const { return data_.data(); }\n\n    constexpr auto begin() noexcept { return data_.begin(); }\n    constexpr auto end() noexcept { return data_.end(); }\n    constexpr auto begin() const noexcept { return data_.begin(); }\n    constexpr auto end() const noexcept { return data_.end(); }\n    constexpr auto cbegin() const noexcept { return data_.cbegin(); }\n    constexpr auto cend() const noexcept { return data_.cend(); }\n\n    constexpr auto rbegin() noexcept { return data_.rbegin(); }\n    constexpr auto rend() noexcept { return data_.rend(); }\n    constexpr auto rbegin() const noexcept { return data_.rbegin(); }\n    constexpr auto rend() const noexcept { return data_.rend(); }\n    constexpr auto crbegin() const noexcept { return data_.crbegin(); }\n    constexpr auto crend() const noexcept { return data_.crend(); }\n\n    constexpr bool      empty() const noexcept { return data_.empty(); }\n    constexpr size_type size() const noexcept { return data_.size(); }\n    constexpr size_type max_size() const noexcept { return data_.max_size(); }\n\n    template<typename U>\n    void fill(const U& v) {\n        static_assert(Detail::is_strictly_assignable_v<T, U>,\n                      \"Cannot assign fill value to entry type\");\n        for (auto& ele : data_)\n        {\n            if constexpr (sizeof...(Sizes) == 0)\n                ele = v;\n            else\n                ele.fill(v);\n        }\n    }\n\n    constexpr void swap(MultiArray<T, Size, Sizes...>& other) noexcept { data_.swap(other.data_); }\n};\n\n\n// xorshift64star Pseudo-Random Number Generator\n// This class is based on original code written and dedicated\n// to the public domain by Sebastiano Vigna (2014).\n// It has the following characteristics:\n//\n//  -  Outputs 64-bit numbers\n//  -  Passes Dieharder and SmallCrush test batteries\n//  -  Does not require warm-up, no zeroland to escape\n//  -  Internal state is a single 64-bit integer\n//  -  Period is 2^64 - 1\n//  -  Speed: 1.60 ns/call (Core i7 @3.40GHz)\n//\n// For further analysis see\n//   <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>\n\nclass PRNG {\n\n    uint64_t s;\n\n    uint64_t rand64() {\n\n        s ^= s >> 12, s ^= s << 25, s ^= s >> 27;\n        return s * 2685821657736338717LL;\n    }\n\n   public:\n    PRNG(uint64_t seed) :\n        s(seed) {\n        assert(seed);\n    }\n\n    template<typename T>\n    T rand() {\n        return T(rand64());\n    }\n\n    // Special generator used to fast init magic numbers.\n    // Output values only have 1/8th of their bits set on average.\n    template<typename T>\n    T sparse_rand() {\n        return T(rand64() & rand64() & rand64());\n    }\n};\n\ninline uint64_t mul_hi64(uint64_t a, uint64_t b) {\n#if defined(__GNUC__) && defined(IS_64BIT)\n    __extension__ using uint128 = unsigned __int128;\n    return (uint128(a) * uint128(b)) >> 64;\n#else\n    uint64_t aL = uint32_t(a), aH = a >> 32;\n    uint64_t bL = uint32_t(b), bH = b >> 32;\n    uint64_t c1 = (aL * bL) >> 32;\n    uint64_t c2 = aH * bL + c1;\n    uint64_t c3 = aL * bH + uint32_t(c2);\n    return aH * bH + (c2 >> 32) + (c3 >> 32);\n#endif\n}\n\nuint64_t hash_bytes(const char*, size_t);\n\ntemplate<typename T>\ninline std::size_t get_raw_data_hash(const T& value) {\n    // We must have no padding bytes because we're reinterpreting as char\n    static_assert(std::has_unique_object_representations<T>());\n\n    return static_cast<std::size_t>(\n      hash_bytes(reinterpret_cast<const char*>(&value), sizeof(value)));\n}\n\ntemplate<typename T>\ninline void hash_combine(std::size_t& seed, const T& v) {\n    std::size_t x;\n    // For primitive types we avoid using the default hasher, which may be\n    // nondeterministic across program invocations\n    if constexpr (std::is_integral<T>())\n        x = v;\n    else\n        x = std::hash<T>{}(v);\n    seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);\n}\n\ninline std::uint64_t hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }\n\ntemplate<std::size_t Capacity>\nclass FixedString {\n   public:\n    FixedString() :\n        length_(0) {\n        data_[0] = '\\0';\n    }\n\n    FixedString(const char* str) {\n        size_t len = std::strlen(str);\n        if (len > Capacity)\n            std::terminate();\n        std::memcpy(data_, str, len);\n        length_        = len;\n        data_[length_] = '\\0';\n    }\n\n    FixedString(const std::string& str) {\n        if (str.size() > Capacity)\n            std::terminate();\n        std::memcpy(data_, str.data(), str.size());\n        length_        = str.size();\n        data_[length_] = '\\0';\n    }\n\n    std::size_t size() const { return length_; }\n    std::size_t capacity() const { return Capacity; }\n\n    const char* c_str() const { return data_; }\n    const char* data() const { return data_; }\n\n    char& operator[](std::size_t i) { return data_[i]; }\n\n    const char& operator[](std::size_t i) const { return data_[i]; }\n\n    FixedString& operator+=(const char* str) {\n        size_t len = std::strlen(str);\n        if (length_ + len > Capacity)\n            std::terminate();\n        std::memcpy(data_ + length_, str, len);\n        length_ += len;\n        data_[length_] = '\\0';\n        return *this;\n    }\n\n    FixedString& operator+=(const FixedString& other) { return (*this += other.c_str()); }\n\n    operator std::string() const { return std::string(data_, length_); }\n\n    operator std::string_view() const { return std::string_view(data_, length_); }\n\n    template<typename T>\n    bool operator==(const T& other) const noexcept {\n        return (std::string_view) (*this) == other;\n    }\n\n    template<typename T>\n    bool operator!=(const T& other) const noexcept {\n        return (std::string_view) (*this) != other;\n    }\n\n    void clear() {\n        length_  = 0;\n        data_[0] = '\\0';\n    }\n\n   private:\n    char        data_[Capacity + 1];  // +1 for null terminator\n    std::size_t length_;\n};\n\nstruct CommandLine {\n   public:\n    CommandLine(int _argc, char** _argv) :\n        argc(_argc),\n        argv(_argv) {}\n\n    static std::string get_binary_directory(std::string argv0);\n    static std::string get_working_directory();\n\n    int    argc;\n    char** argv;\n};\n\nnamespace Utility {\n\ntemplate<typename T, typename Predicate>\nvoid move_to_front(std::vector<T>& vec, Predicate pred) {\n    auto it = std::find_if(vec.begin(), vec.end(), pred);\n\n    if (it != vec.end())\n    {\n        std::rotate(vec.begin(), it, it + 1);\n    }\n}\n}\n\n#if defined(__GNUC__)\n    #define sf_always_inline __attribute__((always_inline))\n#elif defined(_MSC_VER)\n    #define sf_always_inline __forceinline\n#else\n    // do nothing for other compilers\n    #define sf_always_inline\n#endif\n\n#if defined(__clang__)\n    #define sf_assume(cond) __builtin_assume(cond)\n#elif defined(__GNUC__)\n    #if __GNUC__ >= 13\n        #define sf_assume(cond) __attribute__((assume(cond)))\n    #else\n        #define sf_assume(cond) \\\n            do \\\n            { \\\n                if (!(cond)) \\\n                    __builtin_unreachable(); \\\n            } while (0)\n    #endif\n#elif defined(_MSC_VER)\n    #define sf_assume(cond) __assume(cond)\n#else\n    // do nothing for other compilers\n    #define sf_assume(cond)\n#endif\n\n#ifdef __GNUC__\n    #define sf_unreachable() __builtin_unreachable()\n#elif defined(_MSC_VER)\n    #define sf_unreachable() __assume(0)\n#else\n    #define sf_unreachable()\n#endif\n\n}  // namespace Stockfish\n\ntemplate<std::size_t N>\nstruct std::hash<Stockfish::FixedString<N>> {\n    std::size_t operator()(const Stockfish::FixedString<N>& fstr) const noexcept {\n        return Stockfish::hash_bytes(fstr.data(), fstr.size());\n    }\n};\n\n#endif  // #ifndef MISC_H_INCLUDED\n"
  },
  {
    "path": "src/movegen.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"movegen.h\"\n\n#include <cassert>\n#include <initializer_list>\n\n#include \"bitboard.h\"\n#include \"position.h\"\n\n#if defined(USE_AVX512ICL)\n    #include <array>\n    #include <algorithm>\n    #include <immintrin.h>\n#endif\n\nnamespace Stockfish {\n\nnamespace {\n\n#if defined(USE_AVX512ICL)\n\n// clang-format off\nconst __m512i AllSquares = _mm512_set_epi8(\n  63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,\n  40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18,\n  17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n// clang-format on\n\ntemplate<Direction offset>\ninline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {\n    assert(popcount(to_bb) <= 8);  // <= 8 pawns per side\n\n    const __m128i toSquares =\n      _mm_cvtepi8_epi16(_mm512_castsi512_si128(_mm512_maskz_compress_epi8(to_bb, AllSquares)));\n    const __m128i fromSquares = _mm_subs_epi16(toSquares, _mm_set1_epi16(offset));\n    const __m128i moves       = _mm_or_si128(_mm_slli_epi16(fromSquares, Move::FromSqShift),\n                                             _mm_slli_epi16(toSquares, Move::ToSqShift));\n\n    _mm_storeu_si128(reinterpret_cast<__m128i*>(moveList), moves);\n    return moveList + popcount(to_bb);\n}\n\ninline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {\n    assert(popcount(to_bb) <= 32);  // Q can attack up to 27 squares\n\n    const __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());\n    const __m512i toSquares =\n      _mm512_cvtepi8_epi16(_mm512_castsi512_si256(_mm512_maskz_compress_epi8(to_bb, AllSquares)));\n    const __m512i moves = _mm512_or_si512(fromVec, _mm512_slli_epi16(toSquares, Move::ToSqShift));\n\n    _mm512_storeu_si512(moveList, moves);\n    return moveList + popcount(to_bb);\n}\n\n#else\n\ntemplate<Direction offset>\ninline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {\n    while (to_bb)\n    {\n        Square to   = pop_lsb(to_bb);\n        *moveList++ = Move(to - offset, to);\n    }\n    return moveList;\n}\n\ninline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {\n    while (to_bb)\n        *moveList++ = Move(from, pop_lsb(to_bb));\n    return moveList;\n}\n\n#endif\n\ntemplate<GenType Type, Direction D, bool Enemy>\nMove* make_promotions(Move* moveList, [[maybe_unused]] Square to) {\n\n    constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;\n\n    if constexpr (Type == CAPTURES || all)\n        *moveList++ = Move::make<PROMOTION>(to - D, to, QUEEN);\n\n    if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all)\n    {\n        *moveList++ = Move::make<PROMOTION>(to - D, to, ROOK);\n        *moveList++ = Move::make<PROMOTION>(to - D, to, BISHOP);\n        *moveList++ = Move::make<PROMOTION>(to - D, to, KNIGHT);\n    }\n\n    return moveList;\n}\n\n\ntemplate<Color Us, GenType Type>\nMove* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target) {\n\n    constexpr Color     Them     = ~Us;\n    constexpr Bitboard  TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);\n    constexpr Bitboard  TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);\n    constexpr Direction Up       = pawn_push(Us);\n    constexpr Direction UpRight  = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);\n    constexpr Direction UpLeft   = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);\n\n    const Bitboard emptySquares = ~pos.pieces();\n    const Bitboard enemies      = Type == EVASIONS ? pos.checkers() : pos.pieces(Them);\n\n    Bitboard pawnsOn7    = pos.pieces(Us, PAWN) & TRank7BB;\n    Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;\n\n    // Single and double pawn pushes, no promotions\n    if constexpr (Type != CAPTURES)\n    {\n        Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;\n        Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;\n\n        if constexpr (Type == EVASIONS)  // Consider only blocking squares\n        {\n            b1 &= target;\n            b2 &= target;\n        }\n\n        moveList = splat_pawn_moves<Up>(moveList, b1);\n        moveList = splat_pawn_moves<Up + Up>(moveList, b2);\n    }\n\n    // Promotions and underpromotions\n    if (pawnsOn7)\n    {\n        Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;\n        Bitboard b2 = shift<UpLeft>(pawnsOn7) & enemies;\n        Bitboard b3 = shift<Up>(pawnsOn7) & emptySquares;\n\n        if constexpr (Type == EVASIONS)\n            b3 &= target;\n\n        while (b1)\n            moveList = make_promotions<Type, UpRight, true>(moveList, pop_lsb(b1));\n\n        while (b2)\n            moveList = make_promotions<Type, UpLeft, true>(moveList, pop_lsb(b2));\n\n        while (b3)\n            moveList = make_promotions<Type, Up, false>(moveList, pop_lsb(b3));\n    }\n\n    // Standard and en passant captures\n    if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)\n    {\n        Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;\n        Bitboard b2 = shift<UpLeft>(pawnsNotOn7) & enemies;\n\n        moveList = splat_pawn_moves<UpRight>(moveList, b1);\n        moveList = splat_pawn_moves<UpLeft>(moveList, b2);\n\n        if (pos.ep_square() != SQ_NONE)\n        {\n            assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));\n\n            // An en passant capture cannot resolve a discovered check\n            if (Type == EVASIONS && (target & (pos.ep_square() + Up)))\n                return moveList;\n\n            b1 = pawnsNotOn7 & attacks_bb<PAWN>(pos.ep_square(), Them);\n\n            assert(b1);\n\n            while (b1)\n                *moveList++ = Move::make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());\n        }\n    }\n\n    return moveList;\n}\n\n\ntemplate<Color Us, PieceType Pt>\nMove* generate_moves(const Position& pos, Move* moveList, Bitboard target) {\n\n    static_assert(Pt != KING && Pt != PAWN, \"Unsupported piece type in generate_moves()\");\n\n    Bitboard bb = pos.pieces(Us, Pt);\n\n    while (bb)\n    {\n        Square   from = pop_lsb(bb);\n        Bitboard b    = attacks_bb<Pt>(from, pos.pieces()) & target;\n\n        moveList = splat_moves(moveList, from, b);\n    }\n\n    return moveList;\n}\n\n\ntemplate<Color Us, GenType Type>\nMove* generate_all(const Position& pos, Move* moveList) {\n\n    static_assert(Type != LEGAL, \"Unsupported type in generate_all()\");\n\n    const Square ksq = pos.square<KING>(Us);\n    Bitboard     target;\n\n    // Skip generating non-king moves when in double check\n    if (Type != EVASIONS || !more_than_one(pos.checkers()))\n    {\n        target = Type == EVASIONS     ? between_bb(ksq, lsb(pos.checkers()))\n               : Type == NON_EVASIONS ? ~pos.pieces(Us)\n               : Type == CAPTURES     ? pos.pieces(~Us)\n                                      : ~pos.pieces();  // QUIETS\n\n        moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);\n        moveList = generate_moves<Us, KNIGHT>(pos, moveList, target);\n        moveList = generate_moves<Us, BISHOP>(pos, moveList, target);\n        moveList = generate_moves<Us, ROOK>(pos, moveList, target);\n        moveList = generate_moves<Us, QUEEN>(pos, moveList, target);\n    }\n\n    Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);\n\n    moveList = splat_moves(moveList, ksq, b);\n\n    if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))\n        for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE})\n            if (!pos.castling_impeded(cr) && pos.can_castle(cr))\n                *moveList++ = Move::make<CASTLING>(ksq, pos.castling_rook_square(cr));\n\n    return moveList;\n}\n\n}  // namespace\n\n\n// <CAPTURES>     Generates all pseudo-legal captures plus queen promotions\n// <QUIETS>       Generates all pseudo-legal non-captures and underpromotions\n// <EVASIONS>     Generates all pseudo-legal check evasions\n// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures\n//\n// Returns a pointer to the end of the move list.\ntemplate<GenType Type>\nMove* generate(const Position& pos, Move* moveList) {\n\n    static_assert(Type != LEGAL, \"Unsupported type in generate()\");\n    assert((Type == EVASIONS) == bool(pos.checkers()));\n\n    Color us = pos.side_to_move();\n\n    return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)\n                       : generate_all<BLACK, Type>(pos, moveList);\n}\n\n// Explicit template instantiations\ntemplate Move* generate<CAPTURES>(const Position&, Move*);\ntemplate Move* generate<QUIETS>(const Position&, Move*);\ntemplate Move* generate<EVASIONS>(const Position&, Move*);\ntemplate Move* generate<NON_EVASIONS>(const Position&, Move*);\n\n// generate<LEGAL> generates all the legal moves in the given position\n\ntemplate<>\nMove* generate<LEGAL>(const Position& pos, Move* moveList) {\n\n    Color    us     = pos.side_to_move();\n    Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);\n    Square   ksq    = pos.square<KING>(us);\n    Move*    cur    = moveList;\n\n    moveList =\n      pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);\n    while (cur != moveList)\n        if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT)\n            && !pos.legal(*cur))\n            *cur = *(--moveList);\n        else\n            ++cur;\n\n    return moveList;\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/movegen.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef MOVEGEN_H_INCLUDED\n#define MOVEGEN_H_INCLUDED\n\n#include <algorithm>  // IWYU pragma: keep\n#include <cstddef>\n\n#include \"types.h\"\n\nnamespace Stockfish {\n\nclass Position;\n\nenum GenType {\n    CAPTURES,\n    QUIETS,\n    EVASIONS,\n    NON_EVASIONS,\n    LEGAL\n};\n\nstruct ExtMove: public Move {\n    int value;\n\n    void operator=(Move m) { data = m.raw(); }\n\n    // Inhibit unwanted implicit conversions to Move\n    // with an ambiguity that yields to a compile error.\n    operator float() const = delete;\n};\n\ninline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }\n\ntemplate<GenType>\nMove* generate(const Position& pos, Move* moveList);\n\n// The MoveList struct wraps the generate() function and returns a convenient\n// list of moves. Using MoveList is sometimes preferable to directly calling\n// the lower level generate() function.\ntemplate<GenType T>\nstruct MoveList {\n\n    explicit MoveList(const Position& pos) :\n        last(generate<T>(pos, moveList)) {}\n    const Move* begin() const { return moveList; }\n    const Move* end() const { return last; }\n    size_t      size() const { return last - moveList; }\n    bool        contains(Move move) const { return std::find(begin(), end(), move) != end(); }\n\n   private:\n    Move moveList[MAX_MOVES], *last;\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef MOVEGEN_H_INCLUDED\n"
  },
  {
    "path": "src/movepick.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"movepick.h\"\n\n#include <cassert>\n#include <limits>\n#include <utility>\n\n#include \"bitboard.h\"\n#include \"misc.h\"\n#include \"position.h\"\n\nnamespace Stockfish {\n\nnamespace {\n\nenum Stages {\n    // generate main search moves\n    MAIN_TT,\n    CAPTURE_INIT,\n    GOOD_CAPTURE,\n    QUIET_INIT,\n    GOOD_QUIET,\n    BAD_CAPTURE,\n    BAD_QUIET,\n\n    // generate evasion moves\n    EVASION_TT,\n    EVASION_INIT,\n    EVASION,\n\n    // generate probcut moves\n    PROBCUT_TT,\n    PROBCUT_INIT,\n    PROBCUT,\n\n    // generate qsearch moves\n    QSEARCH_TT,\n    QCAPTURE_INIT,\n    QCAPTURE\n};\n\n\n// Sort moves in descending order up to and including a given limit.\n// The order of moves smaller than the limit is left unspecified.\nvoid partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {\n\n    for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)\n        if (p->value >= limit)\n        {\n            ExtMove tmp = *p, *q;\n            *p          = *++sortedEnd;\n            for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q)\n                *q = *(q - 1);\n            *q = tmp;\n        }\n}\n\n}  // namespace\n\n\n// Constructors of the MovePicker class. As arguments, we pass information\n// to decide which class of moves to emit, to help sorting the (presumably)\n// good moves first, and how important move ordering is at the current node.\n\n// MovePicker constructor for the main search and for the quiescence search\nMovePicker::MovePicker(const Position&              p,\n                       Move                         ttm,\n                       Depth                        d,\n                       const ButterflyHistory*      mh,\n                       const LowPlyHistory*         lph,\n                       const CapturePieceToHistory* cph,\n                       const PieceToHistory**       ch,\n                       const SharedHistories*       sh,\n                       int                          pl) :\n    pos(p),\n    mainHistory(mh),\n    lowPlyHistory(lph),\n    captureHistory(cph),\n    continuationHistory(ch),\n    sharedHistory(sh),\n    ttMove(ttm),\n    depth(d),\n    ply(pl) {\n\n    if (pos.checkers())\n        stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm));\n\n    else\n        stage = (depth > 0 ? MAIN_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));\n}\n\n// MovePicker constructor for ProbCut: we generate captures with Static Exchange\n// Evaluation (SEE) greater than or equal to the given threshold.\nMovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) :\n    pos(p),\n    captureHistory(cph),\n    ttMove(ttm),\n    threshold(th) {\n    assert(!pos.checkers());\n\n    stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm));\n}\n\n// Assigns a numerical value to each move in a list, used for sorting.\n// Captures are ordered by Most Valuable Victim (MVV), preferring captures\n// with a good history. Quiets moves are ordered using the history tables.\ntemplate<GenType Type>\nExtMove* MovePicker::score(MoveList<Type>& ml) {\n\n    static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, \"Wrong type\");\n\n    Color us = pos.side_to_move();\n\n    [[maybe_unused]] Bitboard threatByLesser[KING + 1];\n    if constexpr (Type == QUIETS)\n    {\n        threatByLesser[PAWN]   = 0;\n        threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by<PAWN>(~us);\n        threatByLesser[ROOK] =\n          pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatByLesser[KNIGHT];\n        threatByLesser[QUEEN] = pos.attacks_by<ROOK>(~us) | threatByLesser[ROOK];\n        threatByLesser[KING]  = 0;\n    }\n\n    ExtMove* it = cur;\n    for (auto move : ml)\n    {\n        ExtMove& m = *it++;\n        m          = move;\n\n        const Square    from          = m.from_sq();\n        const Square    to            = m.to_sq();\n        const Piece     pc            = pos.moved_piece(m);\n        const PieceType pt            = type_of(pc);\n        const Piece     capturedPiece = pos.piece_on(to);\n\n        if constexpr (Type == CAPTURES)\n            m.value = (*captureHistory)[pc][to][type_of(capturedPiece)]\n                    + 7 * int(PieceValue[capturedPiece]);\n\n        else if constexpr (Type == QUIETS)\n        {\n            // histories\n            m.value = 2 * (*mainHistory)[us][m.raw()];\n            m.value += 2 * sharedHistory->pawn_entry(pos)[pc][to];\n            m.value += (*continuationHistory[0])[pc][to];\n            m.value += (*continuationHistory[1])[pc][to];\n            m.value += (*continuationHistory[2])[pc][to];\n            m.value += (*continuationHistory[3])[pc][to];\n            m.value += (*continuationHistory[5])[pc][to];\n\n            // bonus for checks\n            m.value += (bool(pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384;\n\n            // penalty for moving to a square threatened by a lesser piece\n            // or bonus for escaping an attack by a lesser piece.\n            int v = 20 * (bool(threatByLesser[pt] & from) - bool(threatByLesser[pt] & to));\n            m.value += PieceValue[pt] * v;\n\n\n            if (ply < LOW_PLY_HISTORY_SIZE)\n                m.value += 8 * (*lowPlyHistory)[ply][m.raw()] / (1 + ply);\n        }\n\n        else  // Type == EVASIONS\n        {\n            if (pos.capture_stage(m))\n                m.value = PieceValue[capturedPiece] + (1 << 28);\n            else\n                m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to];\n        }\n    }\n    return it;\n}\n\n// Returns the next move satisfying a predicate function.\n// This never returns the TT move, as it was emitted before.\ntemplate<typename Pred>\nMove MovePicker::select(Pred filter) {\n\n    for (; cur < endCur; ++cur)\n        if (*cur != ttMove && filter())\n            return *cur++;\n\n    return Move::none();\n}\n\n// This is the most important method of the MovePicker class. We emit one\n// new pseudo-legal move on every call until there are no more moves left,\n// picking the move with the highest score from a list of generated moves.\nMove MovePicker::next_move() {\n\n    constexpr int goodQuietThreshold = -14000;\ntop:\n    switch (stage)\n    {\n\n    case MAIN_TT :\n    case EVASION_TT :\n    case QSEARCH_TT :\n    case PROBCUT_TT :\n        ++stage;\n        return ttMove;\n\n    case CAPTURE_INIT :\n    case PROBCUT_INIT :\n    case QCAPTURE_INIT : {\n        MoveList<CAPTURES> ml(pos);\n\n        cur = endBadCaptures = moves;\n        endCur = endCaptures = score<CAPTURES>(ml);\n\n        partial_insertion_sort(cur, endCur, std::numeric_limits<int>::min());\n        ++stage;\n        goto top;\n    }\n\n    case GOOD_CAPTURE :\n        if (select([&]() {\n                if (pos.see_ge(*cur, -cur->value / 18))\n                    return true;\n                std::swap(*endBadCaptures++, *cur);\n                return false;\n            }))\n            return *(cur - 1);\n\n        ++stage;\n        [[fallthrough]];\n\n    case QUIET_INIT :\n        if (!skipQuiets)\n        {\n            MoveList<QUIETS> ml(pos);\n\n            endCur = endGenerated = score<QUIETS>(ml);\n\n            partial_insertion_sort(cur, endCur, -3560 * depth);\n        }\n\n        ++stage;\n        [[fallthrough]];\n\n    case GOOD_QUIET :\n        if (!skipQuiets && select([&]() { return cur->value > goodQuietThreshold; }))\n            return *(cur - 1);\n\n        // Prepare the pointers to loop over the bad captures\n        cur    = moves;\n        endCur = endBadCaptures;\n\n        ++stage;\n        [[fallthrough]];\n\n    case BAD_CAPTURE :\n        if (select([]() { return true; }))\n            return *(cur - 1);\n\n        // Prepare the pointers to loop over quiets again\n        cur    = endCaptures;\n        endCur = endGenerated;\n\n        ++stage;\n        [[fallthrough]];\n\n    case BAD_QUIET :\n        if (!skipQuiets)\n            return select([&]() { return cur->value <= goodQuietThreshold; });\n\n        return Move::none();\n\n    case EVASION_INIT : {\n        MoveList<EVASIONS> ml(pos);\n\n        cur    = moves;\n        endCur = endGenerated = score<EVASIONS>(ml);\n\n        partial_insertion_sort(cur, endCur, std::numeric_limits<int>::min());\n        ++stage;\n        [[fallthrough]];\n    }\n\n    case EVASION :\n    case QCAPTURE :\n        return select([]() { return true; });\n\n    case PROBCUT :\n        return select([&]() { return pos.see_ge(*cur, threshold); });\n    }\n\n    assert(false);\n    return Move::none();  // Silence warning\n}\n\nvoid MovePicker::skip_quiet_moves() { skipQuiets = true; }\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/movepick.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef MOVEPICK_H_INCLUDED\n#define MOVEPICK_H_INCLUDED\n\n#include \"history.h\"\n#include \"movegen.h\"\n#include \"types.h\"\n\nnamespace Stockfish {\n\nclass Position;\n\n// The MovePicker class is used to pick one pseudo-legal move at a time from the\n// current position. The most important method is next_move(), which emits one\n// new pseudo-legal move on every call, until there are no moves left, when\n// Move::none() is returned. In order to improve the efficiency of the alpha-beta\n// algorithm, MovePicker attempts to return the moves which are most likely to get\n// a cut-off first.\nclass MovePicker {\n\n   public:\n    MovePicker(const MovePicker&)            = delete;\n    MovePicker& operator=(const MovePicker&) = delete;\n    MovePicker(const Position&,\n               Move,\n               Depth,\n               const ButterflyHistory*,\n               const LowPlyHistory*,\n               const CapturePieceToHistory*,\n               const PieceToHistory**,\n               const SharedHistories*,\n               int);\n    MovePicker(const Position&, Move, int, const CapturePieceToHistory*);\n    Move next_move();\n    void skip_quiet_moves();\n\n   private:\n    template<typename Pred>\n    Move select(Pred);\n    template<GenType T>\n    ExtMove* score(MoveList<T>&);\n    ExtMove* begin() { return cur; }\n    ExtMove* end() { return endCur; }\n\n    const Position&              pos;\n    const ButterflyHistory*      mainHistory;\n    const LowPlyHistory*         lowPlyHistory;\n    const CapturePieceToHistory* captureHistory;\n    const PieceToHistory**       continuationHistory;\n    const SharedHistories*       sharedHistory;\n    Move                         ttMove;\n    ExtMove *                    cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated;\n    int                          stage;\n    int                          threshold;\n    Depth                        depth;\n    int                          ply;\n    bool                         skipQuiets = false;\n    ExtMove                      moves[MAX_MOVES];\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef MOVEPICK_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/features/full_threats.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n//Definition of input features FullThreats of NNUE evaluation function\n\n#include \"full_threats.h\"\n\n#include <array>\n#include <cstddef>\n#include <cstdint>\n#include <initializer_list>\n#include <utility>\n\n#include \"../../bitboard.h\"\n#include \"../../misc.h\"\n#include \"../../position.h\"\n#include \"../../types.h\"\n#include \"../nnue_common.h\"\n\nnamespace Stockfish::Eval::NNUE::Features {\n\nstruct HelperOffsets {\n    int cumulativePieceOffset, cumulativeOffset;\n};\n\nconstexpr std::array<Piece, 12> AllPieces = {\n  W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,\n  B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,\n};\n\ntemplate<PieceType PT>\nconstexpr auto make_piece_indices_type() {\n    static_assert(PT != PieceType::PAWN);\n\n    std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB> out{};\n\n    for (Square from = SQ_A1; from <= SQ_H8; ++from)\n    {\n        Bitboard attacks = PseudoAttacks[PT][from];\n\n        for (Square to = SQ_A1; to <= SQ_H8; ++to)\n        {\n            out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks);\n        }\n    }\n\n    return out;\n}\n\ntemplate<Piece P>\nconstexpr auto make_piece_indices_piece() {\n    static_assert(type_of(P) == PieceType::PAWN);\n\n    std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB> out{};\n\n    constexpr Color C = color_of(P);\n\n    for (Square from = SQ_A1; from <= SQ_H8; ++from)\n    {\n        Bitboard attacks = PseudoAttacks[C][from];\n\n        for (Square to = SQ_A1; to <= SQ_H8; ++to)\n        {\n            out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks);\n        }\n    }\n\n    return out;\n}\n\nconstexpr auto index_lut2_array() {\n    constexpr auto KNIGHT_ATTACKS = make_piece_indices_type<PieceType::KNIGHT>();\n    constexpr auto BISHOP_ATTACKS = make_piece_indices_type<PieceType::BISHOP>();\n    constexpr auto ROOK_ATTACKS   = make_piece_indices_type<PieceType::ROOK>();\n    constexpr auto QUEEN_ATTACKS  = make_piece_indices_type<PieceType::QUEEN>();\n    constexpr auto KING_ATTACKS   = make_piece_indices_type<PieceType::KING>();\n\n    std::array<std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB>, PIECE_NB> indices{};\n\n    indices[W_PAWN] = make_piece_indices_piece<W_PAWN>();\n    indices[B_PAWN] = make_piece_indices_piece<B_PAWN>();\n\n    indices[W_KNIGHT] = KNIGHT_ATTACKS;\n    indices[B_KNIGHT] = KNIGHT_ATTACKS;\n\n    indices[W_BISHOP] = BISHOP_ATTACKS;\n    indices[B_BISHOP] = BISHOP_ATTACKS;\n\n    indices[W_ROOK] = ROOK_ATTACKS;\n    indices[B_ROOK] = ROOK_ATTACKS;\n\n    indices[W_QUEEN] = QUEEN_ATTACKS;\n    indices[B_QUEEN] = QUEEN_ATTACKS;\n\n    indices[W_KING] = KING_ATTACKS;\n    indices[B_KING] = KING_ATTACKS;\n\n    return indices;\n}\n\nconstexpr auto init_threat_offsets() {\n    std::array<HelperOffsets, PIECE_NB>                    indices{};\n    std::array<std::array<IndexType, SQUARE_NB>, PIECE_NB> offsets{};\n\n    int cumulativeOffset = 0;\n    for (Piece piece : AllPieces)\n    {\n        int pieceIdx              = piece;\n        int cumulativePieceOffset = 0;\n\n        for (Square from = SQ_A1; from <= SQ_H8; ++from)\n        {\n            offsets[pieceIdx][from] = cumulativePieceOffset;\n\n            if (type_of(piece) != PAWN)\n            {\n                Bitboard attacks = PseudoAttacks[type_of(piece)][from];\n                cumulativePieceOffset += constexpr_popcount(attacks);\n            }\n\n            else if (from >= SQ_A2 && from <= SQ_H7)\n            {\n                Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb<WHITE>(square_bb(from))\n                                                  : pawn_attacks_bb<BLACK>(square_bb(from));\n                cumulativePieceOffset += constexpr_popcount(attacks);\n            }\n        }\n\n        indices[pieceIdx] = {cumulativePieceOffset, cumulativeOffset};\n\n        cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset;\n    }\n\n    return std::pair{indices, offsets};\n}\n\nconstexpr auto helper_offsets = init_threat_offsets().first;\n// Lookup array for indexing threats\nconstexpr auto offsets = init_threat_offsets().second;\n\nconstexpr auto init_index_luts() {\n    std::array<std::array<std::array<uint32_t, 2>, PIECE_NB>, PIECE_NB> indices{};\n\n    for (Piece attacker : AllPieces)\n    {\n        for (Piece attacked : AllPieces)\n        {\n            bool      enemy        = (attacker ^ attacked) == 8;\n            PieceType attackerType = type_of(attacker);\n            PieceType attackedType = type_of(attacked);\n\n            int  map           = FullThreats::map[attackerType - 1][attackedType - 1];\n            bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN);\n            IndexType feature  = helper_offsets[attacker].cumulativeOffset\n                              + (color_of(attacked) * (numValidTargets[attacker] / 2) + map)\n                                  * helper_offsets[attacker].cumulativePieceOffset;\n\n            bool excluded                  = map < 0;\n            indices[attacker][attacked][0] = excluded ? FullThreats::Dimensions : feature;\n            indices[attacker][attacked][1] =\n              excluded || semi_excluded ? FullThreats::Dimensions : feature;\n        }\n    }\n\n    return indices;\n}\n\n// The final index is calculated from summing data found in these two LUTs, as well\n// as offsets[attacker][from]\n\n// [attacker][attacked][from < to]\nconstexpr auto index_lut1 = init_index_luts();\n// [attacker][from][to]\nconstexpr auto index_lut2 = index_lut2_array();\n\n// Index of a feature for a given king position and another piece on some square\ninline sf_always_inline IndexType FullThreats::make_index(\n  Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) {\n    const std::int8_t orientation   = OrientTBL[ksq] ^ (56 * perspective);\n    unsigned          from_oriented = uint8_t(from) ^ orientation;\n    unsigned          to_oriented   = uint8_t(to) ^ orientation;\n\n    std::int8_t swap              = 8 * perspective;\n    unsigned    attacker_oriented = attacker ^ swap;\n    unsigned    attacked_oriented = attacked ^ swap;\n\n    return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented]\n         + offsets[attacker_oriented][from_oriented]\n         + index_lut2[attacker_oriented][from_oriented][to_oriented];\n}\n\n// Get a list of indices for active features in ascending order\n\nvoid FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) {\n    Square   ksq      = pos.square<KING>(perspective);\n    Bitboard occupied = pos.pieces();\n\n    for (Color color : {WHITE, BLACK})\n    {\n        for (PieceType pt = PAWN; pt < KING; ++pt)\n        {\n            Color    c        = Color(perspective ^ color);\n            Piece    attacker = make_piece(c, pt);\n            Bitboard bb       = pos.pieces(c, pt);\n\n            if (pt == PAWN)\n            {\n                auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST;\n                auto left  = (c == WHITE) ? NORTH_WEST : SOUTH_EAST;\n                auto attacks_left =\n                  ((c == WHITE) ? shift<NORTH_EAST>(bb) : shift<SOUTH_WEST>(bb)) & occupied;\n                auto attacks_right =\n                  ((c == WHITE) ? shift<NORTH_WEST>(bb) : shift<SOUTH_EAST>(bb)) & occupied;\n\n                while (attacks_left)\n                {\n                    Square    to       = pop_lsb(attacks_left);\n                    Square    from     = to - right;\n                    Piece     attacked = pos.piece_on(to);\n                    IndexType index    = make_index(perspective, attacker, from, to, attacked, ksq);\n\n                    if (index < Dimensions)\n                        active.push_back(index);\n                }\n\n                while (attacks_right)\n                {\n                    Square    to       = pop_lsb(attacks_right);\n                    Square    from     = to - left;\n                    Piece     attacked = pos.piece_on(to);\n                    IndexType index    = make_index(perspective, attacker, from, to, attacked, ksq);\n\n                    if (index < Dimensions)\n                        active.push_back(index);\n                }\n            }\n            else\n            {\n                while (bb)\n                {\n                    Square   from    = pop_lsb(bb);\n                    Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied;\n\n                    while (attacks)\n                    {\n                        Square    to       = pop_lsb(attacks);\n                        Piece     attacked = pos.piece_on(to);\n                        IndexType index =\n                          make_index(perspective, attacker, from, to, attacked, ksq);\n\n                        if (index < Dimensions)\n                            active.push_back(index);\n                    }\n                }\n            }\n        }\n    }\n}\n\n// Get a list of indices for recently changed features\n\nvoid FullThreats::append_changed_indices(Color                   perspective,\n                                         Square                  ksq,\n                                         const DiffType&         diff,\n                                         IndexList&              removed,\n                                         IndexList&              added,\n                                         FusedUpdateData*        fusedData,\n                                         bool                    first,\n                                         const ThreatWeightType* prefetchBase,\n                                         IndexType               prefetchStride) {\n\n    for (const auto& dirty : diff.list)\n    {\n        auto attacker = dirty.pc();\n        auto attacked = dirty.threatened_pc();\n        auto from     = dirty.pc_sq();\n        auto to       = dirty.threatened_sq();\n        auto add      = dirty.add();\n\n        if (fusedData)\n        {\n            if (from == fusedData->dp2removed)\n            {\n                if (add)\n                {\n                    if (first)\n                    {\n                        fusedData->dp2removedOriginBoard |= to;\n                        continue;\n                    }\n                }\n                else if (fusedData->dp2removedOriginBoard & to)\n                    continue;\n            }\n\n            if (to != SQ_NONE && to == fusedData->dp2removed)\n            {\n                if (add)\n                {\n                    if (first)\n                    {\n                        fusedData->dp2removedTargetBoard |= from;\n                        continue;\n                    }\n                }\n                else if (fusedData->dp2removedTargetBoard & from)\n                    continue;\n            }\n        }\n\n        auto&           insert = add ? added : removed;\n        const IndexType index  = make_index(perspective, attacker, from, to, attacked, ksq);\n\n        if (index < Dimensions)\n        {\n            if (prefetchBase)\n                prefetch<PrefetchRw::READ, PrefetchLoc::LOW>(\n                  prefetchBase + static_cast<std::ptrdiff_t>(index) * prefetchStride);\n            insert.push_back(index);\n        }\n    }\n}\n\nbool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {\n    return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100);\n}\n\n}  // namespace Stockfish::Eval::NNUE::Features\n"
  },
  {
    "path": "src/nnue/features/full_threats.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n//Definition of input features Simplified_Threats of NNUE evaluation function\n\n#ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED\n#define NNUE_FEATURES_FULL_THREATS_INCLUDED\n\n#include <cstdint>\n\n#include \"../../misc.h\"\n#include \"../../types.h\"\n#include \"../nnue_common.h\"\n\nnamespace Stockfish {\nclass Position;\n}\n\nnamespace Stockfish::Eval::NNUE::Features {\n\nstatic constexpr int numValidTargets[PIECE_NB] = {0, 6, 10, 8, 8, 10, 0, 0,\n                                                  0, 6, 10, 8, 8, 10, 0, 0};\n\nclass FullThreats {\n   public:\n    // Feature name\n    static constexpr const char* Name = \"Full_Threats(Friend)\";\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t HashValue = 0x8f234cb8u;\n\n    // Number of feature dimensions\n    static constexpr IndexType Dimensions = 60144;\n\n    // clang-format off\n    // Orient a square according to perspective (rotates by 180 for black)\n    static constexpr std::int8_t OrientTBL[SQUARE_NB] = {\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n        SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,\n    };\n\n    static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = {\n      { 0,  1, -1,  2, -1, -1},\n      { 0,  1,  2,  3,  4, -1},\n      { 0,  1,  2,  3, -1, -1},\n      { 0,  1,  2,  3, -1, -1},\n      { 0,  1,  2,  3,  4, -1},\n      {-1, -1, -1, -1, -1, -1}\n    };\n    // clang-format on\n\n    struct FusedUpdateData {\n        Bitboard dp2removedOriginBoard = 0;\n        Bitboard dp2removedTargetBoard = 0;\n\n        Square dp2removed;\n    };\n\n    // Maximum number of simultaneously active features.\n    static constexpr IndexType MaxActiveDimensions = 128;\n    using IndexList                                = ValueList<IndexType, MaxActiveDimensions>;\n    using DiffType                                 = DirtyThreats;\n\n    static IndexType\n    make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq);\n\n    // Get a list of indices for active features\n    static void append_active_indices(Color perspective, const Position& pos, IndexList& active);\n\n    // Get a list of indices for recently changed features\n    static void append_changed_indices(Color                   perspective,\n                                       Square                  ksq,\n                                       const DiffType&         diff,\n                                       IndexList&              removed,\n                                       IndexList&              added,\n                                       FusedUpdateData*        fd             = nullptr,\n                                       bool                    first          = false,\n                                       const ThreatWeightType* prefetchBase   = nullptr,\n                                       IndexType               prefetchStride = 0);\n\n    // Returns whether the change stored in this DirtyPiece means\n    // that a full accumulator refresh is required.\n    static bool requires_refresh(const DiffType& diff, Color perspective);\n};\n\n}  // namespace Stockfish::Eval::NNUE::Features\n\n#endif  // #ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED\n"
  },
  {
    "path": "src/nnue/features/half_ka_v2_hm.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n//Definition of input features HalfKAv2_hm of NNUE evaluation function\n\n#include \"half_ka_v2_hm.h\"\n\n#include \"../../bitboard.h\"\n#include \"../../position.h\"\n#include \"../../types.h\"\n#include \"../nnue_common.h\"\n\nnamespace Stockfish::Eval::NNUE::Features {\n\n#if defined(USE_AVX512ICL)\nvoid HalfKAv2_hm::write_indices(const std::array<Piece, SQUARE_NB>& oldPieces,\n                                const std::array<Piece, SQUARE_NB>& newPieces,\n                                Bitboard                            removedBB,\n                                Bitboard                            addedBB,\n                                Color                               perspective,\n                                Square                              ksq,\n                                IndexList&                          removed,\n                                IndexList&                          added) {\n\n    auto* write_removed = removed.make_space(popcount(removedBB));\n    auto* write_added   = added.make_space(popcount(addedBB));\n\n    const __m512i vecOldPieces = _mm512_loadu_si512(oldPieces.data());\n    const __m512i vecNewPieces = _mm512_loadu_si512(newPieces.data());\n\n    static constexpr uint16_t psiTable[COLOR_NB][32] = {\n      {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,\n       PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,\n       PS_NONE, PS_NONE,   PS_NONE,     PS_NONE,     PS_NONE,   PS_NONE,    PS_NONE, PS_NONE,\n       PS_NONE, PS_NONE,   PS_NONE,     PS_NONE,     PS_NONE,   PS_NONE,    PS_NONE, PS_NONE},\n\n      {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,\n       PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,\n       PS_NONE, PS_NONE,   PS_NONE,     PS_NONE,     PS_NONE,   PS_NONE,    PS_NONE, PS_NONE,\n       PS_NONE, PS_NONE,   PS_NONE,     PS_NONE,     PS_NONE,   PS_NONE,    PS_NONE, PS_NONE}};\n    const __m512i psi = _mm512_loadu_si512(psiTable[perspective]);\n\n    const __m512i allSquares = _mm512_set_epi8(\n      63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,\n      40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18,\n      17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n\n    const uint16_t flip   = 56 * perspective;\n    const __m512i  orient = _mm512_set1_epi16((uint16_t) OrientTBL[ksq] ^ flip);\n    const __m512i  bucket = _mm512_set1_epi16((uint16_t) KingBuckets[int(ksq) ^ flip]);\n\n    __m512i removed_squares       = _mm512_maskz_compress_epi8(removedBB, allSquares);\n    __m512i removed_pieces        = _mm512_permutexvar_epi8(removed_squares, vecOldPieces);\n    removed_squares               = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(removed_squares));\n    removed_pieces                = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(removed_pieces));\n    const __m512i removed_psi     = _mm512_permutexvar_epi16(removed_pieces, psi);\n    __m512i       removed_indices = _mm512_xor_si512(removed_squares, orient);\n    removed_indices               = _mm512_add_epi16(removed_indices, removed_psi);\n    removed_indices               = _mm512_add_epi16(removed_indices, bucket);\n\n    __m512i added_squares       = _mm512_maskz_compress_epi8(addedBB, allSquares);\n    __m512i added_pieces        = _mm512_permutexvar_epi8(added_squares, vecNewPieces);\n    added_squares               = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(added_squares));\n    added_pieces                = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(added_pieces));\n    const __m512i added_psi     = _mm512_permutexvar_epi16(added_pieces, psi);\n    __m512i       added_indices = _mm512_xor_si512(added_squares, orient);\n    added_indices               = _mm512_add_epi16(added_indices, added_psi);\n    added_indices               = _mm512_add_epi16(added_indices, bucket);\n\n    const __m512i removed_indices0 = _mm512_cvtepi16_epi32(_mm512_castsi512_si256(removed_indices));\n    const __m512i removed_indices1 =\n      _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(removed_indices, 1));\n    _mm512_storeu_si512(write_removed, removed_indices0);\n    _mm512_storeu_si512(write_removed + 16, removed_indices1);\n\n    const __m512i added_indices0 = _mm512_cvtepi16_epi32(_mm512_castsi512_si256(added_indices));\n    const __m512i added_indices1 =\n      _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(added_indices, 1));\n    _mm512_storeu_si512(write_added, added_indices0);\n    _mm512_storeu_si512(write_added + 16, added_indices1);\n}\n#endif\n\n// Index of a feature for a given king position and another piece on some square\n\nIndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {\n    const IndexType flip = 56 * perspective;\n    return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[perspective][pc]\n         + KingBuckets[int(ksq) ^ flip];\n}\n\n// Get a list of indices for active features\n\nvoid HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) {\n    Square   ksq = pos.square<KING>(perspective);\n    Bitboard bb  = pos.pieces();\n    while (bb)\n    {\n        Square s = pop_lsb(bb);\n        active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));\n    }\n}\n\n// Get a list of indices for recently changed features\n\nvoid HalfKAv2_hm::append_changed_indices(\n  Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) {\n    removed.push_back(make_index(perspective, diff.from, diff.pc, ksq));\n    if (diff.to != SQ_NONE)\n        added.push_back(make_index(perspective, diff.to, diff.pc, ksq));\n\n    if (diff.remove_sq != SQ_NONE)\n        removed.push_back(make_index(perspective, diff.remove_sq, diff.remove_pc, ksq));\n\n    if (diff.add_sq != SQ_NONE)\n        added.push_back(make_index(perspective, diff.add_sq, diff.add_pc, ksq));\n}\n\nbool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) {\n    return diff.pc == make_piece(perspective, KING);\n}\n\n}  // namespace Stockfish::Eval::NNUE::Features\n"
  },
  {
    "path": "src/nnue/features/half_ka_v2_hm.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n//Definition of input features HalfKP of NNUE evaluation function\n\n#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED\n#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED\n\n#include <cstdint>\n\n#include \"../../misc.h\"\n#include \"../../types.h\"\n#include \"../nnue_common.h\"\n\nnamespace Stockfish {\nclass Position;\n}\n\nnamespace Stockfish::Eval::NNUE::Features {\n\n// Feature HalfKAv2_hm: Combination of the position of own king and the\n// position of pieces. Position mirrored such that king is always on e..h files.\nclass HalfKAv2_hm {\n\n    // Unique number for each piece type on each square\n    enum {\n        PS_NONE     = 0,\n        PS_W_PAWN   = 0,\n        PS_B_PAWN   = 1 * SQUARE_NB,\n        PS_W_KNIGHT = 2 * SQUARE_NB,\n        PS_B_KNIGHT = 3 * SQUARE_NB,\n        PS_W_BISHOP = 4 * SQUARE_NB,\n        PS_B_BISHOP = 5 * SQUARE_NB,\n        PS_W_ROOK   = 6 * SQUARE_NB,\n        PS_B_ROOK   = 7 * SQUARE_NB,\n        PS_W_QUEEN  = 8 * SQUARE_NB,\n        PS_B_QUEEN  = 9 * SQUARE_NB,\n        PS_KING     = 10 * SQUARE_NB,\n        PS_NB       = 11 * SQUARE_NB\n    };\n\n    static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {\n      // Convention: W - us, B - them\n      // Viewed from other side, W and B are reversed\n      {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,\n       PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE},\n      {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,\n       PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};\n\n   public:\n    // Feature name\n    static constexpr const char* Name = \"HalfKAv2_hm(Friend)\";\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t HashValue = 0x7f234cb8u;\n\n    // Number of feature dimensions\n    static constexpr IndexType Dimensions =\n      static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;\n\n#define B(v) (v * PS_NB)\n    // clang-format off\n    static constexpr IndexType KingBuckets[SQUARE_NB] = {\n        B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),\n        B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),\n        B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),\n        B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),\n        B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),\n        B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),\n        B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),\n        B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),\n    };\n    // clang-format on\n#undef B\n    // clang-format off\n    // Orient a square according to perspective (rotates by 180 for black)\n    static constexpr IndexType OrientTBL[SQUARE_NB] = {\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,\n        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 ,\n    };\n    // clang-format on\n\n    // Maximum number of simultaneously active features.\n    static constexpr IndexType MaxActiveDimensions = 32;\n    using IndexList                                = ValueList<IndexType, MaxActiveDimensions>;\n    using DiffType                                 = DirtyPiece;\n\n#if defined(USE_AVX512ICL)\n    // Compute all changed feature indices and write them to the given lists\n    static void write_indices(const std::array<Piece, SQUARE_NB>& oldPieces,\n                              const std::array<Piece, SQUARE_NB>& newPieces,\n                              Bitboard                            removedBB,\n                              Bitboard                            addedBB,\n                              Color                               perspective,\n                              Square                              ksq,\n                              IndexList&                          removed,\n                              IndexList&                          added);\n#endif\n\n    // Index of a feature for a given king position and another piece on some square\n\n    static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);\n\n    // Get a list of indices for active features\n\n    static void append_active_indices(Color perspective, const Position& pos, IndexList& active);\n\n    // Get a list of indices for recently changed features\n    static void append_changed_indices(\n      Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added);\n\n    // Returns whether the change stored in this DirtyPiece means\n    // that a full accumulator refresh is required.\n    static bool requires_refresh(const DiffType& diff, Color perspective);\n};\n\n}  // namespace Stockfish::Eval::NNUE::Features\n\n#endif  // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/layers/affine_transform.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Definition of layer AffineTransform of NNUE evaluation function\n\n#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED\n#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED\n\n#include <cstdint>\n#include <iostream>\n\n#include \"../../memory.h\"\n#include \"../nnue_common.h\"\n#include \"../simd.h\"\n\n/*\n  This file contains the definition for a fully connected layer (aka affine transform).\n\n    - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.\n      - that's why AVX512 is hard to implement\n    - expected use-case is small layers\n    - inputs are processed in chunks of 4, weights are respectively transposed\n    - accumulation happens directly to int32s\n*/\n\nnamespace Stockfish::Eval::NNUE::Layers {\n\n#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD)\n    #define ENABLE_SEQ_OPT\n#endif\n\n// Fallback implementation for older/other architectures.\n// Requires the input to be padded to at least 16 values.\n#ifndef ENABLE_SEQ_OPT\n\ntemplate<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>\nstatic void affine_transform_non_ssse3(std::int32_t*       output,\n                                       const std::int8_t*  weights,\n                                       const std::int32_t* biases,\n                                       const std::uint8_t* input) {\n    #if defined(USE_SSE2) || defined(USE_NEON)\n        #if defined(USE_SSE2)\n    // At least a multiple of 16, with SSE2.\n    constexpr IndexType NumChunks   = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;\n    const __m128i       Zeros       = _mm_setzero_si128();\n    const auto          inputVector = reinterpret_cast<const __m128i*>(input);\n\n        #elif defined(USE_NEON)\n    constexpr IndexType NumChunks   = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;\n    const auto          inputVector = reinterpret_cast<const int8x8_t*>(input);\n        #endif\n\n    for (IndexType i = 0; i < OutputDimensions; ++i)\n    {\n        const IndexType offset = i * PaddedInputDimensions;\n\n        #if defined(USE_SSE2)\n        __m128i    sumLo = _mm_cvtsi32_si128(biases[i]);\n        __m128i    sumHi = Zeros;\n        const auto row   = reinterpret_cast<const __m128i*>(&weights[offset]);\n        for (IndexType j = 0; j < NumChunks; ++j)\n        {\n            __m128i row_j           = _mm_load_si128(&row[j]);\n            __m128i input_j         = _mm_load_si128(&inputVector[j]);\n            __m128i extendedRowLo   = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);\n            __m128i extendedRowHi   = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);\n            __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros);\n            __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros);\n            __m128i productLo       = _mm_madd_epi16(extendedRowLo, extendedInputLo);\n            __m128i productHi       = _mm_madd_epi16(extendedRowHi, extendedInputHi);\n            sumLo                   = _mm_add_epi32(sumLo, productLo);\n            sumHi                   = _mm_add_epi32(sumHi, productHi);\n        }\n        __m128i sum           = _mm_add_epi32(sumLo, sumHi);\n        __m128i sumHigh_64    = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));\n        sum                   = _mm_add_epi32(sum, sumHigh_64);\n        __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));\n        sum                   = _mm_add_epi32(sum, sum_second_32);\n        output[i]             = _mm_cvtsi128_si32(sum);\n\n        #elif defined(USE_NEON)\n\n        int32x4_t  sum = {biases[i]};\n        const auto row = reinterpret_cast<const SIMD::vec_i8x8_t*>(&weights[offset]);\n        for (IndexType j = 0; j < NumChunks; ++j)\n        {\n            int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);\n            product           = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);\n            sum               = vpadalq_s16(sum, product);\n        }\n        output[i] = SIMD::neon_m128_reduce_add_epi32(sum);\n\n        #endif\n    }\n    #else\n    std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);\n\n    // Traverse weights in transpose order to take advantage of input sparsity\n    for (IndexType i = 0; i < InputDimensions; ++i)\n        if (input[i])\n        {\n            const std::int8_t* w  = &weights[i];\n            const int          in = input[i];\n            for (IndexType j = 0; j < OutputDimensions; ++j)\n                output[j] += w[j * PaddedInputDimensions] * in;\n        }\n    #endif\n}\n\n#endif  // !ENABLE_SEQ_OPT\n\ntemplate<IndexType InDims, IndexType OutDims>\nclass AffineTransform {\n   public:\n    // Input/output type\n    using InputType  = std::uint8_t;\n    using OutputType = std::int32_t;\n\n    // Number of input/output dimensions\n    static constexpr IndexType InputDimensions  = InDims;\n    static constexpr IndexType OutputDimensions = OutDims;\n\n    static constexpr IndexType PaddedInputDimensions =\n      ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);\n    static constexpr IndexType PaddedOutputDimensions =\n      ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);\n\n    using OutputBuffer = OutputType[PaddedOutputDimensions];\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {\n        std::uint32_t hashValue = 0xCC03DAE4u;\n        hashValue += OutputDimensions;\n        hashValue ^= prevHash >> 1;\n        hashValue ^= prevHash << 31;\n        return hashValue;\n    }\n\n    static constexpr IndexType get_weight_index_scrambled(IndexType i) {\n        return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4\n             + i / PaddedInputDimensions * 4 + i % 4;\n    }\n\n    static constexpr IndexType get_weight_index(IndexType i) {\n#ifdef ENABLE_SEQ_OPT\n        return get_weight_index_scrambled(i);\n#else\n        return i;\n#endif\n    }\n\n    // Read network parameters\n    bool read_parameters(std::istream& stream) {\n        read_little_endian<BiasType>(stream, biases, OutputDimensions);\n        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)\n            weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);\n\n        return !stream.fail();\n    }\n\n    // Write network parameters\n    bool write_parameters(std::ostream& stream) const {\n        write_little_endian<BiasType>(stream, biases, OutputDimensions);\n\n        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)\n            write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);\n\n        return !stream.fail();\n    }\n\n    std::size_t get_content_hash() const {\n        std::size_t h = 0;\n        hash_combine(h, get_raw_data_hash(biases));\n        hash_combine(h, get_raw_data_hash(weights));\n        hash_combine(h, get_hash_value(0));\n        return h;\n    }\n\n    // Forward propagation\n    void propagate(const InputType* input, OutputType* output) const {\n\n#ifdef ENABLE_SEQ_OPT\n\n        if constexpr (OutputDimensions > 1)\n        {\n    #if defined(USE_AVX512)\n            using vec_t = __m512i;\n        #define vec_set_32 _mm512_set1_epi32\n        #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32\n    #elif defined(USE_AVX2)\n            using vec_t = __m256i;\n        #define vec_set_32 _mm256_set1_epi32\n        #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32\n    #elif defined(USE_SSSE3)\n            using vec_t = __m128i;\n        #define vec_set_32 _mm_set1_epi32\n        #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32\n    #elif defined(USE_NEON_DOTPROD)\n            using vec_t = int32x4_t;\n        #define vec_set_32 vdupq_n_s32\n        #define vec_add_dpbusd_32(acc, a, b) \\\n            SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \\\n                                                vreinterpretq_s8_s32(b))\n    #endif\n\n            static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);\n\n            static_assert(OutputDimensions % OutputSimdWidth == 0);\n\n            constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;\n            constexpr IndexType NumRegs   = OutputDimensions / OutputSimdWidth;\n\n            const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);\n            vec_t        acc[NumRegs];\n            for (IndexType k = 0; k < NumRegs; ++k)\n                acc[k] = biasvec[k];\n\n            for (IndexType i = 0; i < NumChunks; ++i)\n            {\n                const vec_t in0 =\n                  vec_set_32(load_as<std::int32_t>(input + i * sizeof(std::int32_t)));\n                const auto col0 =\n                  reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);\n\n                for (IndexType k = 0; k < NumRegs; ++k)\n                    vec_add_dpbusd_32(acc[k], in0, col0[k]);\n            }\n\n            vec_t* outptr = reinterpret_cast<vec_t*>(output);\n            for (IndexType k = 0; k < NumRegs; ++k)\n                outptr[k] = acc[k];\n\n    #undef vec_set_32\n    #undef vec_add_dpbusd_32\n        }\n        else if constexpr (OutputDimensions == 1)\n        {\n    // We cannot use AVX512 for the last layer because there are only 32 inputs\n    // and the buffer is not padded to 64 elements.\n    #if defined(USE_AVX2)\n            using vec_t = __m256i;\n        #define vec_setzero() _mm256_setzero_si256()\n        #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32\n        #define vec_hadd SIMD::m256_hadd\n    #elif defined(USE_SSSE3)\n            using vec_t = __m128i;\n        #define vec_setzero() _mm_setzero_si128()\n        #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32\n        #define vec_hadd SIMD::m128_hadd\n    #elif defined(USE_NEON_DOTPROD)\n            using vec_t = int32x4_t;\n        #define vec_setzero() vdupq_n_s32(0)\n        #define vec_add_dpbusd_32(acc, a, b) \\\n            SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \\\n                                                vreinterpretq_s8_s32(b))\n        #define vec_hadd SIMD::neon_m128_hadd\n    #endif\n\n            const auto inputVector = reinterpret_cast<const vec_t*>(input);\n\n            static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType);\n\n            static_assert(PaddedInputDimensions % InputSimdWidth == 0);\n\n            constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth;\n            vec_t               sum0      = vec_setzero();\n            const auto          row0      = reinterpret_cast<const vec_t*>(&weights[0]);\n\n            for (int j = 0; j < int(NumChunks); ++j)\n            {\n                const vec_t in = inputVector[j];\n                vec_add_dpbusd_32(sum0, in, row0[j]);\n            }\n            output[0] = vec_hadd(sum0, biases[0]);\n\n    #undef vec_setzero\n    #undef vec_add_dpbusd_32\n    #undef vec_hadd\n        }\n#else\n        // Use old implementation for the other architectures.\n        affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(\n          output, weights, biases, input);\n#endif\n    }\n\n   private:\n    using BiasType   = OutputType;\n    using WeightType = std::int8_t;\n\n    alignas(CacheLineSize) BiasType biases[OutputDimensions];\n    alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];\n};\n\n}  // namespace Stockfish::Eval::NNUE::Layers\n\n#endif  // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/layers/affine_transform_sparse_input.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Definition of layer AffineTransformSparseInput of NNUE evaluation function\n\n#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED\n#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED\n\n#include <algorithm>\n#include <cstddef>\n#include <cstdint>\n#include <iostream>\n\n#include \"../../bitboard.h\"\n#include \"../../memory.h\"\n#include \"../simd.h\"\n#include \"../nnue_common.h\"\n\n/*\n  This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.\n*/\n\nnamespace Stockfish::Eval::NNUE::Layers {\n\n#if (USE_SSSE3 | (USE_NEON >= 8))\nstatic constexpr int lsb_index64[64] = {\n  0,  47, 1,  56, 48, 27, 2,  60, 57, 49, 41, 37, 28, 16, 3,  61, 54, 58, 35, 52, 50, 42,\n  21, 44, 38, 32, 29, 23, 17, 11, 4,  62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43,\n  31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9,  24, 13, 18, 8,  12, 7,  6,  5,  63};\n\nconstexpr int constexpr_lsb(uint64_t bb) {\n    assert(bb != 0);\n    constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL;\n    return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58];\n}\n\nalignas(CacheLineSize) static constexpr struct OffsetIndices {\n\n    std::uint16_t offset_indices[256][8];\n\n    constexpr OffsetIndices() :\n        offset_indices() {\n        for (int i = 0; i < 256; ++i)\n        {\n            std::uint64_t j = i, k = 0;\n            while (j)\n            {\n                offset_indices[i][k++] = constexpr_lsb(j);\n                j &= j - 1;\n            }\n            while (k < 8)\n                offset_indices[i][k++] = 0;\n        }\n    }\n\n} Lookup;\n\n    #if defined(__GNUC__) || defined(__clang__)\n        #define RESTRICT __restrict__\n    #elif defined(_MSC_VER)\n        #define RESTRICT __restrict\n    #else\n        #define RESTRICT\n    #endif\n\n// Find indices of nonzero 32-bit values in a packed byte buffer.\n// The input pointer addresses a sequence of 32-bit blocks stored in a\n// std::uint8_t array.\ntemplate<const IndexType InputDimensions>\nvoid find_nnz(const std::uint8_t* RESTRICT input,\n              std::uint16_t* RESTRICT      out,\n              IndexType&                   count_out) {\n\n    #if defined(USE_AVX512ICL)\n\n    constexpr IndexType SimdWidthIn  = 64;  // 512 bits\n    constexpr IndexType SimdWidthOut = 32;  // 512 bits / 16 bits\n    constexpr IndexType NumChunks    = InputDimensions / SimdWidthOut;\n    const __m512i       increment    = _mm512_set1_epi16(SimdWidthOut);\n    __m512i             base = _mm512_set_epi16(  // Same permute order as _mm512_packus_epi32()\n      31, 30, 29, 28, 15, 14, 13, 12, 27, 26, 25, 24, 11, 10, 9, 8, 23, 22, 21, 20, 7, 6, 5, 4, 19,\n      18, 17, 16, 3, 2, 1, 0);\n\n    IndexType count = 0;\n    for (IndexType i = 0; i < NumChunks; ++i)\n    {\n        const __m512i inputV0 = _mm512_load_si512(input + i * 2 * SimdWidthIn);\n        const __m512i inputV1 = _mm512_load_si512(input + i * 2 * SimdWidthIn + SimdWidthIn);\n\n        // Get a bitmask and gather non zero indices\n        const __m512i   inputV01 = _mm512_packus_epi32(inputV0, inputV1);\n        const __mmask32 nnzMask  = _mm512_test_epi16_mask(inputV01, inputV01);\n\n        // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4\n        __m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base);\n        _mm512_storeu_si512(out + count, nnz);\n\n        count += popcount(nnzMask);\n        base = _mm512_add_epi16(base, increment);\n    }\n    count_out = count;\n\n    #elif defined(USE_AVX512)\n\n    constexpr IndexType SimdWidth = 16;  // 512 bits / 32 bits\n    constexpr IndexType NumChunks = InputDimensions / SimdWidth;\n    const __m512i       increment = _mm512_set1_epi32(SimdWidth);\n    __m512i base = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n\n    IndexType count = 0;\n    for (IndexType i = 0; i < NumChunks; ++i)\n    {\n        const __m512i inputV = _mm512_load_si512(input + i * SimdWidth * sizeof(std::uint32_t));\n\n        // Get a bitmask and gather non zero indices\n        const __mmask16 nnzMask = _mm512_test_epi32_mask(inputV, inputV);\n        const __m512i   nnzV    = _mm512_maskz_compress_epi32(nnzMask, base);\n        _mm512_mask_cvtepi32_storeu_epi16(out + count, 0xFFFF, nnzV);\n        count += popcount(nnzMask);\n        base = _mm512_add_epi32(base, increment);\n    }\n    count_out = count;\n\n    #else\n\n    using namespace SIMD;\n\n    constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t);\n    // Outputs are processed 8 elements at a time, even if the SIMD width is narrower\n    constexpr IndexType ChunkSize      = 8;\n    constexpr IndexType NumChunks      = InputDimensions / ChunkSize;\n    constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth;\n\n    static_assert(InputsPerChunk > 0 && \"SIMD width too wide\");\n\n    const auto     inputVector = reinterpret_cast<const vec_uint_t*>(input);\n    IndexType      count       = 0;\n    vec128_t       base        = vec128_zero;\n    const vec128_t increment   = vec128_set_16(8);\n    for (IndexType i = 0; i < NumChunks; ++i)\n    {\n        // bitmask of nonzero values in this chunk\n        unsigned nnz = 0;\n        for (IndexType j = 0; j < InputsPerChunk; ++j)\n        {\n            const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j];\n            nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);\n        }\n        const vec128_t offsets =\n          vec128_load(reinterpret_cast<const vec128_t*>(&Lookup.offset_indices[nnz]));\n        vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));\n        count += popcount(nnz);\n        base = vec128_add(base, increment);\n    }\n    count_out = count;\n    #endif\n}\n\n#endif\n\n// Sparse input implementation\ntemplate<IndexType InDims, IndexType OutDims>\nclass AffineTransformSparseInput {\n   public:\n    // Input/output type\n    using InputType  = std::uint8_t;\n    using OutputType = std::int32_t;\n\n    // Number of input/output dimensions\n    static constexpr IndexType InputDimensions  = InDims;\n    static constexpr IndexType OutputDimensions = OutDims;\n\n    static_assert(OutputDimensions % 16 == 0,\n                  \"Only implemented for OutputDimensions divisible by 16.\");\n\n    static constexpr IndexType PaddedInputDimensions =\n      ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);\n    static constexpr IndexType PaddedOutputDimensions =\n      ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);\n\n#if (USE_SSSE3 | (USE_NEON >= 8))\n    static constexpr IndexType ChunkSize = 4;\n#else\n    static constexpr IndexType ChunkSize = 1;\n#endif\n\n    using OutputBuffer = OutputType[PaddedOutputDimensions];\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {\n        std::uint32_t hashValue = 0xCC03DAE4u;\n        hashValue += OutputDimensions;\n        hashValue ^= prevHash >> 1;\n        hashValue ^= prevHash << 31;\n        return hashValue;\n    }\n\n    static constexpr IndexType get_weight_index_scrambled(IndexType i) {\n        return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize\n             + i / PaddedInputDimensions * ChunkSize + i % ChunkSize;\n    }\n\n    static constexpr IndexType get_weight_index(IndexType i) {\n#if (USE_SSSE3 | (USE_NEON >= 8))\n        return get_weight_index_scrambled(i);\n#else\n        return i;\n#endif\n    }\n\n    // Read network parameters\n    bool read_parameters(std::istream& stream) {\n        read_little_endian<BiasType>(stream, biases, OutputDimensions);\n        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)\n            weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);\n\n        return !stream.fail();\n    }\n\n    // Write network parameters\n    bool write_parameters(std::ostream& stream) const {\n        write_little_endian<BiasType>(stream, biases, OutputDimensions);\n\n        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)\n            write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);\n\n        return !stream.fail();\n    }\n\n    std::size_t get_content_hash() const {\n        std::size_t h = 0;\n        hash_combine(h, get_raw_data_hash(biases));\n        hash_combine(h, get_raw_data_hash(weights));\n        hash_combine(h, get_hash_value(0));\n        return h;\n    }\n\n    // Forward propagation\n    void propagate(const InputType* input, OutputType* output) const {\n\n#if (USE_SSSE3 | (USE_NEON >= 8))\n    #if defined(USE_AVX512)\n        using invec_t  = __m512i;\n        using outvec_t = __m512i;\n        #define vec_add_32 _mm512_add_epi32\n        #define vec_set_32 _mm512_set1_epi32\n        #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32\n    #elif defined(USE_AVX2)\n        using invec_t  = __m256i;\n        using outvec_t = __m256i;\n        #define vec_add_32 _mm256_add_epi32\n        #define vec_set_32 _mm256_set1_epi32\n        #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32\n    #elif defined(USE_SSSE3)\n        using invec_t  = __m128i;\n        using outvec_t = __m128i;\n        #define vec_set_32 _mm_set1_epi32\n        #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32\n    #elif defined(USE_NEON_DOTPROD)\n        using invec_t  = int8x16_t;\n        using outvec_t = int32x4_t;\n        #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))\n        #define vec_add_dpbusd_32 SIMD::dotprod_m128_add_dpbusd_epi32\n    #elif defined(USE_NEON)\n        using invec_t  = int8x16_t;\n        using outvec_t = int32x4_t;\n        #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))\n        #define vec_add_dpbusd_32 SIMD::neon_m128_add_dpbusd_epi32\n    #endif\n        constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);\n        constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;\n        constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;\n        // If we're using high-latency dot product instructions, split the accumulators\n        // to create 3 separate dependency chains and merge at the end\n        constexpr IndexType NumRegs =\n    #if defined(USE_VNNI)\n          3 * NumAccums;\n    #else\n          NumAccums;\n    #endif\n        std::uint16_t nnz[NumChunks];\n        IndexType     count;\n\n        // Find indices of nonzero 32-bit blocks\n        find_nnz<NumChunks>(input, nnz, count);\n\n        const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(biases);\n        outvec_t        acc[NumRegs];\n        for (IndexType k = 0; k < NumAccums; ++k)\n            acc[k] = biasvec[k];\n\n        const auto* start = nnz;\n        const auto* end   = nnz + count;\n\n        // convince GCC to not do weird pointer arithmetic in the following loop\n        const std::int8_t* weights_cp = weights;\n    #if defined(USE_VNNI)\n        for (IndexType k = NumAccums; k < NumRegs; ++k)\n            acc[k] = vec_zero();\n\n        while (start < end - 2)\n        {\n            const std::ptrdiff_t i0 = *start++;\n            const std::ptrdiff_t i1 = *start++;\n            const std::ptrdiff_t i2 = *start++;\n            const invec_t        in0 =\n              vec_set_32(load_as<std::int32_t>(input + i0 * sizeof(std::int32_t)));\n            const invec_t in1 =\n              vec_set_32(load_as<std::int32_t>(input + i1 * sizeof(std::int32_t)));\n            const invec_t in2 =\n              vec_set_32(load_as<std::int32_t>(input + i2 * sizeof(std::int32_t)));\n            const auto col0 =\n              reinterpret_cast<const invec_t*>(&weights_cp[i0 * OutputDimensions * ChunkSize]);\n            const auto col1 =\n              reinterpret_cast<const invec_t*>(&weights_cp[i1 * OutputDimensions * ChunkSize]);\n            const auto col2 =\n              reinterpret_cast<const invec_t*>(&weights_cp[i2 * OutputDimensions * ChunkSize]);\n            for (IndexType k = 0; k < NumAccums; ++k)\n            {\n                vec_add_dpbusd_32(acc[k], in0, col0[k]);\n                vec_add_dpbusd_32(acc[k + NumAccums], in1, col1[k]);\n                vec_add_dpbusd_32(acc[k + 2 * NumAccums], in2, col2[k]);\n            }\n        }\n        for (IndexType k = 0; k < NumAccums; ++k)\n            acc[k] = vec_add_32(vec_add_32(acc[k], acc[k + NumAccums]), acc[k + 2 * NumAccums]);\n    #endif\n        while (start < end)\n        {\n            const std::ptrdiff_t i = *start++;\n            const invec_t in = vec_set_32(load_as<std::int32_t>(input + i * sizeof(std::int32_t)));\n            const auto    col =\n              reinterpret_cast<const invec_t*>(&weights_cp[i * OutputDimensions * ChunkSize]);\n            for (IndexType k = 0; k < NumAccums; ++k)\n                vec_add_dpbusd_32(acc[k], in, col[k]);\n        }\n\n        outvec_t* outptr = reinterpret_cast<outvec_t*>(output);\n        for (IndexType k = 0; k < NumAccums; ++k)\n            outptr[k] = acc[k];\n\n    #undef vec_set_32\n    #undef vec_add_dpbusd_32\n    #ifdef vec_add_32\n        #undef vec_add_32\n    #endif\n#else\n        // Use dense implementation for the other architectures.\n        affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(\n          output, weights, biases, input);\n#endif\n    }\n\n   private:\n    using BiasType   = OutputType;\n    using WeightType = std::int8_t;\n\n    alignas(CacheLineSize) BiasType biases[OutputDimensions];\n    alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];\n};\n\n}  // namespace Stockfish::Eval::NNUE::Layers\n\n#endif  // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/layers/clipped_relu.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Definition of layer ClippedReLU of NNUE evaluation function\n\n#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED\n#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED\n\n#include <algorithm>\n#include <cstdint>\n#include <iosfwd>\n\n#include \"../nnue_common.h\"\n\nnamespace Stockfish::Eval::NNUE::Layers {\n\n// Clipped ReLU\ntemplate<IndexType InDims>\nclass ClippedReLU {\n   public:\n    // Input/output type\n    using InputType  = std::int32_t;\n    using OutputType = std::uint8_t;\n\n    // Number of input/output dimensions\n    static constexpr IndexType InputDimensions  = InDims;\n    static constexpr IndexType OutputDimensions = InputDimensions;\n    static constexpr IndexType PaddedOutputDimensions =\n      ceil_to_multiple<IndexType>(OutputDimensions, 32);\n\n    using OutputBuffer = OutputType[PaddedOutputDimensions];\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {\n        std::uint32_t hashValue = 0x538D24C7u;\n        hashValue += prevHash;\n        return hashValue;\n    }\n\n    // Read network parameters\n    bool read_parameters(std::istream&) { return true; }\n\n    // Write network parameters\n    bool write_parameters(std::ostream&) const { return true; }\n\n    std::size_t get_content_hash() const {\n        std::size_t h = 0;\n        hash_combine(h, get_hash_value(0));\n        return h;\n    }\n\n    // Forward propagation\n    void propagate(const InputType* input, OutputType* output) const {\n\n#if defined(USE_AVX2)\n        if constexpr (InputDimensions % SimdWidth == 0)\n        {\n            constexpr IndexType NumChunks = InputDimensions / SimdWidth;\n            const __m256i       Offsets   = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);\n            const auto          in        = reinterpret_cast<const __m256i*>(input);\n            const auto          out       = reinterpret_cast<__m256i*>(output);\n            for (IndexType i = 0; i < NumChunks; ++i)\n            {\n                const __m256i words0 =\n                  _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 0]),\n                                                        _mm256_load_si256(&in[i * 4 + 1])),\n                                    WeightScaleBits);\n                const __m256i words1 =\n                  _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 2]),\n                                                        _mm256_load_si256(&in[i * 4 + 3])),\n                                    WeightScaleBits);\n                _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(\n                                              _mm256_packs_epi16(words0, words1), Offsets));\n            }\n        }\n        else\n        {\n            constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);\n            const auto          in        = reinterpret_cast<const __m128i*>(input);\n            const auto          out       = reinterpret_cast<__m128i*>(output);\n            for (IndexType i = 0; i < NumChunks; ++i)\n            {\n                const __m128i words0 = _mm_srli_epi16(\n                  _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),\n                  WeightScaleBits);\n                const __m128i words1 = _mm_srli_epi16(\n                  _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),\n                  WeightScaleBits);\n                _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));\n            }\n        }\n        constexpr IndexType Start = InputDimensions % SimdWidth == 0\n                                    ? InputDimensions / SimdWidth * SimdWidth\n                                    : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);\n\n#elif defined(USE_SSE2)\n        constexpr IndexType NumChunks = InputDimensions / SimdWidth;\n\n    #ifndef USE_SSE41\n        const __m128i k0x80s = _mm_set1_epi8(-128);\n    #endif\n\n        const auto in  = reinterpret_cast<const __m128i*>(input);\n        const auto out = reinterpret_cast<__m128i*>(output);\n        for (IndexType i = 0; i < NumChunks; ++i)\n        {\n    #if defined(USE_SSE41)\n            const __m128i words0 = _mm_srli_epi16(\n              _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),\n              WeightScaleBits);\n            const __m128i words1 = _mm_srli_epi16(\n              _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),\n              WeightScaleBits);\n            _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));\n    #else\n            const __m128i words0 = _mm_srai_epi16(\n              _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),\n              WeightScaleBits);\n            const __m128i words1 = _mm_srai_epi16(\n              _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),\n              WeightScaleBits);\n            const __m128i packedbytes = _mm_packs_epi16(words0, words1);\n            _mm_store_si128(&out[i], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s));\n    #endif\n        }\n        constexpr IndexType Start = NumChunks * SimdWidth;\n\n#elif defined(USE_NEON)\n        constexpr IndexType    NumChunks = InputDimensions / (SimdWidth / 2);\n        const SIMD::vec_i8x8_t Zero      = {0};\n        const auto             in        = reinterpret_cast<const SIMD::vec_i32x4_t*>(input);\n        const auto             out       = reinterpret_cast<SIMD::vec_i8x8_t*>(output);\n        for (IndexType i = 0; i < NumChunks; ++i)\n        {\n            int16x8_t  shifted;\n            const auto pack = reinterpret_cast<int16x4_t*>(&shifted);\n            pack[0]         = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);\n            pack[1]         = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);\n            out[i]          = vmax_s8(vqmovn_s16(shifted), Zero);\n        }\n        constexpr IndexType Start = NumChunks * (SimdWidth / 2);\n#else\n        constexpr IndexType Start = 0;\n#endif\n\n        for (IndexType i = Start; i < InputDimensions; ++i)\n        {\n            output[i] = static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));\n        }\n    }\n};\n\n}  // namespace Stockfish::Eval::NNUE::Layers\n\n#endif  // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/layers/sqr_clipped_relu.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Definition of layer ClippedReLU of NNUE evaluation function\n\n#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED\n#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED\n\n#include <algorithm>\n#include <cstdint>\n#include <iosfwd>\n\n#include \"../nnue_common.h\"\n\nnamespace Stockfish::Eval::NNUE::Layers {\n\n// Clipped ReLU\ntemplate<IndexType InDims>\nclass SqrClippedReLU {\n   public:\n    // Input/output type\n    using InputType  = std::int32_t;\n    using OutputType = std::uint8_t;\n\n    // Number of input/output dimensions\n    static constexpr IndexType InputDimensions  = InDims;\n    static constexpr IndexType OutputDimensions = InputDimensions;\n    static constexpr IndexType PaddedOutputDimensions =\n      ceil_to_multiple<IndexType>(OutputDimensions, 32);\n\n    using OutputBuffer = OutputType[PaddedOutputDimensions];\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {\n        std::uint32_t hashValue = 0x538D24C7u;\n        hashValue += prevHash;\n        return hashValue;\n    }\n\n    // Read network parameters\n    bool read_parameters(std::istream&) { return true; }\n\n    // Write network parameters\n    bool write_parameters(std::ostream&) const { return true; }\n\n    std::size_t get_content_hash() const {\n        std::size_t h = 0;\n        hash_combine(h, get_hash_value(0));\n        return h;\n    }\n\n    // Forward propagation\n    void propagate(const InputType* input, OutputType* output) const {\n\n#if defined(USE_SSE2)\n        constexpr IndexType NumChunks = InputDimensions / 16;\n\n        static_assert(WeightScaleBits == 6);\n        const auto in  = reinterpret_cast<const __m128i*>(input);\n        const auto out = reinterpret_cast<__m128i*>(output);\n        for (IndexType i = 0; i < NumChunks; ++i)\n        {\n            __m128i words0 =\n              _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1]));\n            __m128i words1 =\n              _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3]));\n\n            // We shift by WeightScaleBits * 2 = 12 and divide by 128\n            // which is an additional shift-right of 7, meaning 19 in total.\n            // MulHi strips the lower 16 bits so we need to shift out 3 more to match.\n            words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);\n            words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);\n\n            _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));\n        }\n        constexpr IndexType Start = NumChunks * 16;\n\n#else\n        constexpr IndexType Start = 0;\n#endif\n\n        for (IndexType i = Start; i < InputDimensions; ++i)\n        {\n            output[i] = static_cast<OutputType>(\n              // Really should be /127 but we need to make it fast so we right-shift\n              // by an extra 7 bits instead. Needs to be accounted for in the trainer.\n              std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7)));\n        }\n    }\n};\n\n}  // namespace Stockfish::Eval::NNUE::Layers\n\n#endif  // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/network.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"network.h\"\n\n#include <cstdlib>\n#include <fstream>\n#include <iostream>\n#include <optional>\n#include <type_traits>\n#include <vector>\n\n#define INCBIN_SILENCE_BITCODE_WARNING\n#include \"../incbin/incbin.h\"\n\n#include \"../evaluate.h\"\n#include \"../misc.h\"\n#include \"../position.h\"\n#include \"../types.h\"\n#include \"nnue_architecture.h\"\n#include \"nnue_common.h\"\n#include \"nnue_misc.h\"\n\n// Macro to embed the default efficiently updatable neural network (NNUE) file\n// data in the engine binary (using incbin.h, by Dale Weiler).\n// This macro invocation will declare the following three variables\n//     const unsigned char        gEmbeddedNNUEData[];  // a pointer to the embedded data\n//     const unsigned char *const gEmbeddedNNUEEnd;     // a marker to the end\n//     const unsigned int         gEmbeddedNNUESize;    // the size of the embedded file\n// Note that this does not work in Microsoft Visual Studio.\n#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)\nINCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig);\nINCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);\n#else\nconst unsigned char        gEmbeddedNNUEBigData[1]   = {0x0};\nconst unsigned char* const gEmbeddedNNUEBigEnd       = &gEmbeddedNNUEBigData[1];\nconst unsigned int         gEmbeddedNNUEBigSize      = 1;\nconst unsigned char        gEmbeddedNNUESmallData[1] = {0x0};\nconst unsigned char* const gEmbeddedNNUESmallEnd     = &gEmbeddedNNUESmallData[1];\nconst unsigned int         gEmbeddedNNUESmallSize    = 1;\n#endif\n\nnamespace {\n\nstruct EmbeddedNNUE {\n    EmbeddedNNUE(const unsigned char* embeddedData,\n                 const unsigned char* embeddedEnd,\n                 const unsigned int   embeddedSize) :\n        data(embeddedData),\n        end(embeddedEnd),\n        size(embeddedSize) {}\n    const unsigned char* data;\n    const unsigned char* end;\n    const unsigned int   size;\n};\n\nusing namespace Stockfish::Eval::NNUE;\n\nEmbeddedNNUE get_embedded(EmbeddedNNUEType type) {\n    if (type == EmbeddedNNUEType::BIG)\n        return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize);\n    else\n        return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize);\n}\n\n}\n\n\nnamespace Stockfish::Eval::NNUE {\n\n\nnamespace Detail {\n\n// Read evaluation function parameters\ntemplate<typename T>\nbool read_parameters(std::istream& stream, T& reference) {\n\n    std::uint32_t header;\n    header = read_little_endian<std::uint32_t>(stream);\n    if (!stream || header != T::get_hash_value())\n        return false;\n    return reference.read_parameters(stream);\n}\n\n// Write evaluation function parameters\ntemplate<typename T>\nbool write_parameters(std::ostream& stream, const T& reference) {\n\n    write_little_endian<std::uint32_t>(stream, T::get_hash_value());\n    return reference.write_parameters(stream);\n}\n\n}  // namespace Detail\n\ntemplate<typename Arch, typename Transformer>\nvoid Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {\n#if defined(DEFAULT_NNUE_DIRECTORY)\n    std::vector<std::string> dirs = {\"<internal>\", \"\", rootDirectory,\n                                     stringify(DEFAULT_NNUE_DIRECTORY)};\n#else\n    std::vector<std::string> dirs = {\"<internal>\", \"\", rootDirectory};\n#endif\n\n    if (evalfilePath.empty())\n        evalfilePath = evalFile.defaultName;\n\n    for (const auto& directory : dirs)\n    {\n        if (std::string(evalFile.current) != evalfilePath)\n        {\n            if (directory != \"<internal>\")\n            {\n                load_user_net(directory, evalfilePath);\n            }\n\n            if (directory == \"<internal>\" && evalfilePath == std::string(evalFile.defaultName))\n            {\n                load_internal();\n            }\n        }\n    }\n}\n\n\ntemplate<typename Arch, typename Transformer>\nbool Network<Arch, Transformer>::save(const std::optional<std::string>& filename) const {\n    std::string actualFilename;\n    std::string msg;\n\n    if (filename.has_value())\n        actualFilename = filename.value();\n    else\n    {\n        if (std::string(evalFile.current) != std::string(evalFile.defaultName))\n        {\n            msg = \"Failed to export a net. \"\n                  \"A non-embedded net can only be saved if the filename is specified\";\n\n            sync_cout << msg << sync_endl;\n            return false;\n        }\n\n        actualFilename = evalFile.defaultName;\n    }\n\n    std::ofstream stream(actualFilename, std::ios_base::binary);\n    bool          saved = save(stream, evalFile.current, evalFile.netDescription);\n\n    msg = saved ? \"Network saved successfully to \" + actualFilename : \"Failed to export a net\";\n\n    sync_cout << msg << sync_endl;\n    return saved;\n}\n\n\ntemplate<typename Arch, typename Transformer>\nNetworkOutput\nNetwork<Arch, Transformer>::evaluate(const Position&                         pos,\n                                     AccumulatorStack&                       accumulatorStack,\n                                     AccumulatorCaches::Cache<FTDimensions>& cache) const {\n\n    constexpr uint64_t alignment = CacheLineSize;\n\n    alignas(alignment)\n      TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];\n\n    ASSERT_ALIGNED(transformedFeatures, alignment);\n\n    const int  bucket = (pos.count<ALL_PIECES>() - 1) / 4;\n    const auto psqt =\n      featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);\n    const auto positional = network[bucket].propagate(transformedFeatures);\n    return {static_cast<Value>(psqt / OutputScale), static_cast<Value>(positional / OutputScale)};\n}\n\n\ntemplate<typename Arch, typename Transformer>\nvoid Network<Arch, Transformer>::verify(std::string                                  evalfilePath,\n                                        const std::function<void(std::string_view)>& f) const {\n    if (evalfilePath.empty())\n        evalfilePath = evalFile.defaultName;\n\n    if (std::string(evalFile.current) != evalfilePath)\n    {\n        if (f)\n        {\n            std::string msg1 =\n              \"Network evaluation parameters compatible with the engine must be available.\";\n            std::string msg2 = \"The network file \" + evalfilePath + \" was not loaded successfully.\";\n            std::string msg3 = \"The UCI option EvalFile might need to specify the full path, \"\n                               \"including the directory name, to the network file.\";\n            std::string msg4 = \"The default net can be downloaded from: \"\n                               \"https://tests.stockfishchess.org/api/nn/\"\n                             + std::string(evalFile.defaultName);\n            std::string msg5 = \"The engine will be terminated now.\";\n\n            std::string msg = \"ERROR: \" + msg1 + '\\n' + \"ERROR: \" + msg2 + '\\n' + \"ERROR: \" + msg3\n                            + '\\n' + \"ERROR: \" + msg4 + '\\n' + \"ERROR: \" + msg5 + '\\n';\n\n            f(msg);\n        }\n\n        exit(EXIT_FAILURE);\n    }\n\n    if (f)\n    {\n        size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;\n        f(\"NNUE evaluation using \" + evalfilePath + \" (\" + std::to_string(size / (1024 * 1024))\n          + \"MiB, (\" + std::to_string(featureTransformer.TotalInputDimensions) + \", \"\n          + std::to_string(network[0].TransformedFeatureDimensions) + \", \"\n          + std::to_string(network[0].FC_0_OUTPUTS) + \", \" + std::to_string(network[0].FC_1_OUTPUTS)\n          + \", 1))\");\n    }\n}\n\n\ntemplate<typename Arch, typename Transformer>\nNnueEvalTrace\nNetwork<Arch, Transformer>::trace_evaluate(const Position&                         pos,\n                                           AccumulatorStack&                       accumulatorStack,\n                                           AccumulatorCaches::Cache<FTDimensions>& cache) const {\n\n    constexpr uint64_t alignment = CacheLineSize;\n\n    alignas(alignment)\n      TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];\n\n    ASSERT_ALIGNED(transformedFeatures, alignment);\n\n    NnueEvalTrace t{};\n    t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;\n    for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)\n    {\n        const auto materialist =\n          featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);\n        const auto positional = network[bucket].propagate(transformedFeatures);\n\n        t.psqt[bucket]       = static_cast<Value>(materialist / OutputScale);\n        t.positional[bucket] = static_cast<Value>(positional / OutputScale);\n    }\n\n    return t;\n}\n\n\ntemplate<typename Arch, typename Transformer>\nvoid Network<Arch, Transformer>::load_user_net(const std::string& dir,\n                                               const std::string& evalfilePath) {\n    std::ifstream stream(dir + evalfilePath, std::ios::binary);\n    auto          description = load(stream);\n\n    if (description.has_value())\n    {\n        evalFile.current        = evalfilePath;\n        evalFile.netDescription = description.value();\n    }\n}\n\n\ntemplate<typename Arch, typename Transformer>\nvoid Network<Arch, Transformer>::load_internal() {\n    // C++ way to prepare a buffer for a memory stream\n    class MemoryBuffer: public std::basic_streambuf<char> {\n       public:\n        MemoryBuffer(char* p, size_t n) {\n            setg(p, p, p + n);\n            setp(p, p + n);\n        }\n    };\n\n    const auto embedded = get_embedded(embeddedType);\n\n    MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(embedded.data)),\n                        size_t(embedded.size));\n\n    std::istream stream(&buffer);\n    auto         description = load(stream);\n\n    if (description.has_value())\n    {\n        evalFile.current        = evalFile.defaultName;\n        evalFile.netDescription = description.value();\n    }\n}\n\n\ntemplate<typename Arch, typename Transformer>\nvoid Network<Arch, Transformer>::initialize() {\n    initialized = true;\n}\n\n\ntemplate<typename Arch, typename Transformer>\nbool Network<Arch, Transformer>::save(std::ostream&      stream,\n                                      const std::string& name,\n                                      const std::string& netDescription) const {\n    if (name.empty() || name == \"None\")\n        return false;\n\n    return write_parameters(stream, netDescription);\n}\n\n\ntemplate<typename Arch, typename Transformer>\nstd::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {\n    initialize();\n    std::string description;\n\n    return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt;\n}\n\n\ntemplate<typename Arch, typename Transformer>\nstd::size_t Network<Arch, Transformer>::get_content_hash() const {\n    if (!initialized)\n        return 0;\n\n    std::size_t h = 0;\n    hash_combine(h, featureTransformer);\n    for (auto&& layerstack : network)\n        hash_combine(h, layerstack);\n    hash_combine(h, evalFile);\n    hash_combine(h, static_cast<int>(embeddedType));\n    return h;\n}\n\n// Read network header\ntemplate<typename Arch, typename Transformer>\nbool Network<Arch, Transformer>::read_header(std::istream&  stream,\n                                             std::uint32_t* hashValue,\n                                             std::string*   desc) const {\n    std::uint32_t version, size;\n\n    version    = read_little_endian<std::uint32_t>(stream);\n    *hashValue = read_little_endian<std::uint32_t>(stream);\n    size       = read_little_endian<std::uint32_t>(stream);\n    if (!stream || version != Version)\n        return false;\n    desc->resize(size);\n    stream.read(&(*desc)[0], size);\n    return !stream.fail();\n}\n\n\n// Write network header\ntemplate<typename Arch, typename Transformer>\nbool Network<Arch, Transformer>::write_header(std::ostream&      stream,\n                                              std::uint32_t      hashValue,\n                                              const std::string& desc) const {\n    write_little_endian<std::uint32_t>(stream, Version);\n    write_little_endian<std::uint32_t>(stream, hashValue);\n    write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size()));\n    stream.write(&desc[0], desc.size());\n    return !stream.fail();\n}\n\n\ntemplate<typename Arch, typename Transformer>\nbool Network<Arch, Transformer>::read_parameters(std::istream& stream,\n                                                 std::string&  netDescription) {\n    std::uint32_t hashValue;\n    if (!read_header(stream, &hashValue, &netDescription))\n        return false;\n    if (hashValue != Network::hash)\n        return false;\n    if (!Detail::read_parameters(stream, featureTransformer))\n        return false;\n    for (std::size_t i = 0; i < LayerStacks; ++i)\n    {\n        if (!Detail::read_parameters(stream, network[i]))\n            return false;\n    }\n    return stream && stream.peek() == std::ios::traits_type::eof();\n}\n\n\ntemplate<typename Arch, typename Transformer>\nbool Network<Arch, Transformer>::write_parameters(std::ostream&      stream,\n                                                  const std::string& netDescription) const {\n    if (!write_header(stream, Network::hash, netDescription))\n        return false;\n    if (!Detail::write_parameters(stream, featureTransformer))\n        return false;\n    for (std::size_t i = 0; i < LayerStacks; ++i)\n    {\n        if (!Detail::write_parameters(stream, network[i]))\n            return false;\n    }\n    return bool(stream);\n}\n\n// Explicit template instantiations\n\ntemplate class Network<NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,\n                       FeatureTransformer<TransformedFeatureDimensionsBig>>;\n\ntemplate class Network<NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,\n                       FeatureTransformer<TransformedFeatureDimensionsSmall>>;\n\n}  // namespace Stockfish::Eval::NNUE\n"
  },
  {
    "path": "src/nnue/network.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef NETWORK_H_INCLUDED\n#define NETWORK_H_INCLUDED\n\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <iostream>\n#include <memory>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <tuple>\n\n#include \"../misc.h\"\n#include \"../types.h\"\n#include \"nnue_accumulator.h\"\n#include \"nnue_architecture.h\"\n#include \"nnue_common.h\"\n#include \"nnue_feature_transformer.h\"\n#include \"nnue_misc.h\"\n\nnamespace Stockfish {\nclass Position;\n}\n\nnamespace Stockfish::Eval::NNUE {\n\nenum class EmbeddedNNUEType {\n    BIG,\n    SMALL,\n};\n\nusing NetworkOutput = std::tuple<Value, Value>;\n\n// The network must be a trivial type, i.e. the memory must be in-line.\n// This is required to allow sharing the network via shared memory, as\n// there is no way to run destructors.\ntemplate<typename Arch, typename Transformer>\nclass Network {\n    static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions;\n\n   public:\n    Network(EvalFile file, EmbeddedNNUEType type) :\n        evalFile(file),\n        embeddedType(type) {}\n\n    Network(const Network& other) = default;\n    Network(Network&& other)      = default;\n\n    Network& operator=(const Network& other) = default;\n    Network& operator=(Network&& other)      = default;\n\n    void load(const std::string& rootDirectory, std::string evalfilePath);\n    bool save(const std::optional<std::string>& filename) const;\n\n    std::size_t get_content_hash() const;\n\n    NetworkOutput evaluate(const Position&                         pos,\n                           AccumulatorStack&                       accumulatorStack,\n                           AccumulatorCaches::Cache<FTDimensions>& cache) const;\n\n\n    void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const;\n    NnueEvalTrace trace_evaluate(const Position&                         pos,\n                                 AccumulatorStack&                       accumulatorStack,\n                                 AccumulatorCaches::Cache<FTDimensions>& cache) const;\n\n   private:\n    void load_user_net(const std::string&, const std::string&);\n    void load_internal();\n\n    void initialize();\n\n    bool                       save(std::ostream&, const std::string&, const std::string&) const;\n    std::optional<std::string> load(std::istream&);\n\n    bool read_header(std::istream&, std::uint32_t*, std::string*) const;\n    bool write_header(std::ostream&, std::uint32_t, const std::string&) const;\n\n    bool read_parameters(std::istream&, std::string&);\n    bool write_parameters(std::ostream&, const std::string&) const;\n\n    // Input feature converter\n    Transformer featureTransformer;\n\n    // Evaluation function\n    Arch network[LayerStacks];\n\n    EvalFile         evalFile;\n    EmbeddedNNUEType embeddedType;\n\n    bool initialized = false;\n\n    // Hash value of evaluation function structure\n    static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value();\n\n    template<IndexType Size>\n    friend struct AccumulatorCaches::Cache;\n};\n\n// Definitions of the network types\nusing SmallFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsSmall>;\nusing SmallNetworkArchitecture =\n  NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>;\n\nusing BigFeatureTransformer  = FeatureTransformer<TransformedFeatureDimensionsBig>;\nusing BigNetworkArchitecture = NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>;\n\nusing NetworkBig   = Network<BigNetworkArchitecture, BigFeatureTransformer>;\nusing NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;\n\n\nstruct Networks {\n    Networks(EvalFile bigFile, EvalFile smallFile) :\n        big(bigFile, EmbeddedNNUEType::BIG),\n        small(smallFile, EmbeddedNNUEType::SMALL) {}\n\n    NetworkBig   big;\n    NetworkSmall small;\n};\n\n\n}  // namespace Stockfish\n\ntemplate<typename ArchT, typename FeatureTransformerT>\nstruct std::hash<Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>> {\n    std::size_t operator()(\n      const Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>& network) const noexcept {\n        return network.get_content_hash();\n    }\n};\n\ntemplate<>\nstruct std::hash<Stockfish::Eval::NNUE::Networks> {\n    std::size_t operator()(const Stockfish::Eval::NNUE::Networks& networks) const noexcept {\n        std::size_t h = 0;\n        Stockfish::hash_combine(h, networks.big);\n        Stockfish::hash_combine(h, networks.small);\n        return h;\n    }\n};\n\n#endif\n"
  },
  {
    "path": "src/nnue/nnue_accumulator.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"nnue_accumulator.h\"\n\n#include <cassert>\n#include <cstdint>\n#include <new>\n#include <type_traits>\n\n#include \"../bitboard.h\"\n#include \"../misc.h\"\n#include \"../position.h\"\n#include \"../types.h\"\n#include \"features/half_ka_v2_hm.h\"\n#include \"nnue_architecture.h\"\n#include \"nnue_common.h\"\n#include \"nnue_feature_transformer.h\"  // IWYU pragma: keep\n#include \"simd.h\"\n\nnamespace Stockfish::Eval::NNUE {\n\nusing namespace SIMD;\n\nnamespace {\n\ntemplate<IndexType TransformedFeatureDimensions>\nvoid double_inc_update(Color                                                   perspective,\n                       const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,\n                       const Square                                            ksq,\n                       AccumulatorState<PSQFeatureSet>&                        middle_state,\n                       AccumulatorState<PSQFeatureSet>&                        target_state,\n                       const AccumulatorState<PSQFeatureSet>&                  computed);\n\ntemplate<IndexType TransformedFeatureDimensions>\nvoid double_inc_update(Color                                                   perspective,\n                       const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,\n                       const Square                                            ksq,\n                       AccumulatorState<ThreatFeatureSet>&                     middle_state,\n                       AccumulatorState<ThreatFeatureSet>&                     target_state,\n                       const AccumulatorState<ThreatFeatureSet>&               computed,\n                       const DirtyPiece&                                       dp2);\n\ntemplate<bool Forward, typename FeatureSet, IndexType TransformedFeatureDimensions>\nvoid update_accumulator_incremental(\n  Color                                                   perspective,\n  const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,\n  const Square                                            ksq,\n  AccumulatorState<FeatureSet>&                           target_state,\n  const AccumulatorState<FeatureSet>&                     computed);\n\ntemplate<IndexType Dimensions>\nvoid update_accumulator_refresh_cache(Color                                 perspective,\n                                      const FeatureTransformer<Dimensions>& featureTransformer,\n                                      const Position&                       pos,\n                                      AccumulatorState<PSQFeatureSet>&      accumulatorState,\n                                      AccumulatorCaches::Cache<Dimensions>& cache);\n\ntemplate<IndexType Dimensions>\nvoid update_threats_accumulator_full(Color                                 perspective,\n                                     const FeatureTransformer<Dimensions>& featureTransformer,\n                                     const Position&                       pos,\n                                     AccumulatorState<ThreatFeatureSet>&   accumulatorState);\n}\n\ntemplate<typename T>\nconst AccumulatorState<T>& AccumulatorStack::latest() const noexcept {\n    return accumulators<T>()[size - 1];\n}\n\n// Explicit template instantiations\ntemplate const AccumulatorState<PSQFeatureSet>&    AccumulatorStack::latest() const noexcept;\ntemplate const AccumulatorState<ThreatFeatureSet>& AccumulatorStack::latest() const noexcept;\n\ntemplate<typename T>\nAccumulatorState<T>& AccumulatorStack::mut_latest() noexcept {\n    return mut_accumulators<T>()[size - 1];\n}\n\ntemplate<typename T>\nconst std::array<AccumulatorState<T>, AccumulatorStack::MaxSize>&\nAccumulatorStack::accumulators() const noexcept {\n    static_assert(std::is_same_v<T, PSQFeatureSet> || std::is_same_v<T, ThreatFeatureSet>,\n                  \"Invalid Feature Set Type\");\n\n    if constexpr (std::is_same_v<T, PSQFeatureSet>)\n        return psq_accumulators;\n\n    if constexpr (std::is_same_v<T, ThreatFeatureSet>)\n        return threat_accumulators;\n}\n\ntemplate<typename T>\nstd::array<AccumulatorState<T>, AccumulatorStack::MaxSize>&\nAccumulatorStack::mut_accumulators() noexcept {\n    static_assert(std::is_same_v<T, PSQFeatureSet> || std::is_same_v<T, ThreatFeatureSet>,\n                  \"Invalid Feature Set Type\");\n\n    if constexpr (std::is_same_v<T, PSQFeatureSet>)\n        return psq_accumulators;\n\n    if constexpr (std::is_same_v<T, ThreatFeatureSet>)\n        return threat_accumulators;\n}\n\nvoid AccumulatorStack::reset() noexcept {\n    psq_accumulators[0].reset({});\n    threat_accumulators[0].reset({});\n    size = 1;\n}\n\nstd::pair<DirtyPiece&, DirtyThreats&> AccumulatorStack::push() noexcept {\n    assert(size < MaxSize);\n    auto& dp  = psq_accumulators[size].reset();\n    auto& dts = threat_accumulators[size].reset();\n    new (&dts) DirtyThreats;\n    size++;\n    return {dp, dts};\n}\n\nvoid AccumulatorStack::pop() noexcept {\n    assert(size > 1);\n    size--;\n}\n\ntemplate<IndexType Dimensions>\nvoid AccumulatorStack::evaluate(const Position&                       pos,\n                                const FeatureTransformer<Dimensions>& featureTransformer,\n                                AccumulatorCaches::Cache<Dimensions>& cache) noexcept {\n    constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig);\n\n    evaluate_side<PSQFeatureSet>(WHITE, pos, featureTransformer, cache);\n\n    if (UseThreats)\n        evaluate_side<ThreatFeatureSet>(WHITE, pos, featureTransformer, cache);\n\n    evaluate_side<PSQFeatureSet>(BLACK, pos, featureTransformer, cache);\n\n    if (UseThreats)\n        evaluate_side<ThreatFeatureSet>(BLACK, pos, featureTransformer, cache);\n}\n\ntemplate<typename FeatureSet, IndexType Dimensions>\nvoid AccumulatorStack::evaluate_side(Color                                 perspective,\n                                     const Position&                       pos,\n                                     const FeatureTransformer<Dimensions>& featureTransformer,\n                                     AccumulatorCaches::Cache<Dimensions>& cache) noexcept {\n\n    const auto last_usable_accum =\n      find_last_usable_accumulator<FeatureSet, Dimensions>(perspective);\n\n    if ((accumulators<FeatureSet>()[last_usable_accum].template acc<Dimensions>())\n          .computed[perspective])\n        forward_update_incremental<FeatureSet>(perspective, pos, featureTransformer,\n                                               last_usable_accum);\n\n    else\n    {\n        if constexpr (std::is_same_v<FeatureSet, PSQFeatureSet>)\n            update_accumulator_refresh_cache(perspective, featureTransformer, pos,\n                                             mut_latest<PSQFeatureSet>(), cache);\n        else\n            update_threats_accumulator_full(perspective, featureTransformer, pos,\n                                            mut_latest<ThreatFeatureSet>());\n\n        backward_update_incremental<FeatureSet>(perspective, pos, featureTransformer,\n                                                last_usable_accum);\n    }\n}\n\n// Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator\n// state just before a change that requires full refresh.\ntemplate<typename FeatureSet, IndexType Dimensions>\nstd::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept {\n\n    for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--)\n    {\n        if ((accumulators<FeatureSet>()[curr_idx].template acc<Dimensions>()).computed[perspective])\n            return curr_idx;\n\n        if (FeatureSet::requires_refresh(accumulators<FeatureSet>()[curr_idx].diff, perspective))\n            return curr_idx;\n    }\n\n    return 0;\n}\n\ntemplate<typename FeatureSet, IndexType Dimensions>\nvoid AccumulatorStack::forward_update_incremental(\n  Color                                 perspective,\n  const Position&                       pos,\n  const FeatureTransformer<Dimensions>& featureTransformer,\n  const std::size_t                     begin) noexcept {\n\n    assert(begin < accumulators<FeatureSet>().size());\n    assert((accumulators<FeatureSet>()[begin].template acc<Dimensions>()).computed[perspective]);\n\n    const Square ksq = pos.square<KING>(perspective);\n\n    for (std::size_t next = begin + 1; next < size; next++)\n    {\n        if (next + 1 < size)\n        {\n            DirtyPiece& dp1 = mut_accumulators<PSQFeatureSet>()[next].diff;\n            DirtyPiece& dp2 = mut_accumulators<PSQFeatureSet>()[next + 1].diff;\n\n            auto& accumulators = mut_accumulators<FeatureSet>();\n\n            if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)\n            {\n                if (dp2.remove_sq != SQ_NONE\n                    && (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq)))\n                {\n                    double_inc_update(perspective, featureTransformer, ksq, accumulators[next],\n                                      accumulators[next + 1], accumulators[next - 1], dp2);\n                    next++;\n                    continue;\n                }\n            }\n\n            if constexpr (std::is_same_v<FeatureSet, PSQFeatureSet>)\n            {\n                if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq)\n                {\n                    const Square captureSq = dp1.to;\n                    dp1.to = dp2.remove_sq = SQ_NONE;\n                    double_inc_update(perspective, featureTransformer, ksq, accumulators[next],\n                                      accumulators[next + 1], accumulators[next - 1]);\n                    dp1.to = dp2.remove_sq = captureSq;\n                    next++;\n                    continue;\n                }\n            }\n        }\n\n        update_accumulator_incremental<true>(perspective, featureTransformer, ksq,\n                                             mut_accumulators<FeatureSet>()[next],\n                                             accumulators<FeatureSet>()[next - 1]);\n    }\n\n    assert((latest<PSQFeatureSet>().acc<Dimensions>()).computed[perspective]);\n}\n\ntemplate<typename FeatureSet, IndexType Dimensions>\nvoid AccumulatorStack::backward_update_incremental(\n  Color perspective,\n\n  const Position&                       pos,\n  const FeatureTransformer<Dimensions>& featureTransformer,\n  const std::size_t                     end) noexcept {\n\n    assert(end < accumulators<FeatureSet>().size());\n    assert(end < size);\n    assert((latest<FeatureSet>().template acc<Dimensions>()).computed[perspective]);\n\n    const Square ksq = pos.square<KING>(perspective);\n\n    for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--)\n        update_accumulator_incremental<false>(perspective, featureTransformer, ksq,\n                                              mut_accumulators<FeatureSet>()[next],\n                                              accumulators<FeatureSet>()[next + 1]);\n\n    assert((accumulators<FeatureSet>()[end].template acc<Dimensions>()).computed[perspective]);\n}\n\n// Explicit template instantiations\ntemplate void AccumulatorStack::evaluate<TransformedFeatureDimensionsBig>(\n  const Position&                                            pos,\n  const FeatureTransformer<TransformedFeatureDimensionsBig>& featureTransformer,\n  AccumulatorCaches::Cache<TransformedFeatureDimensionsBig>& cache) noexcept;\ntemplate void AccumulatorStack::evaluate<TransformedFeatureDimensionsSmall>(\n  const Position&                                              pos,\n  const FeatureTransformer<TransformedFeatureDimensionsSmall>& featureTransformer,\n  AccumulatorCaches::Cache<TransformedFeatureDimensionsSmall>& cache) noexcept;\n\n\nnamespace {\n\ntemplate<typename VectorWrapper,\n         IndexType Width,\n         UpdateOperation... ops,\n         typename ElementType,\n         typename... Ts,\n         std::enable_if_t<is_all_same_v<ElementType, Ts...>, bool> = true>\nvoid fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const... rows) {\n    constexpr IndexType size = Width * sizeof(ElementType) / sizeof(typename VectorWrapper::type);\n\n    auto* vecIn  = reinterpret_cast<const typename VectorWrapper::type*>(in);\n    auto* vecOut = reinterpret_cast<typename VectorWrapper::type*>(out);\n\n    for (IndexType i = 0; i < size; ++i)\n        vecOut[i] = fused<VectorWrapper, ops...>(\n          vecIn[i], reinterpret_cast<const typename VectorWrapper::type*>(rows)[i]...);\n}\n\ntemplate<typename FeatureSet, IndexType Dimensions>\nstruct AccumulatorUpdateContext {\n    Color                                 perspective;\n    const FeatureTransformer<Dimensions>& featureTransformer;\n    const AccumulatorState<FeatureSet>&   from;\n    AccumulatorState<FeatureSet>&         to;\n\n    AccumulatorUpdateContext(Color                                 persp,\n                             const FeatureTransformer<Dimensions>& ft,\n                             const AccumulatorState<FeatureSet>&   accF,\n                             AccumulatorState<FeatureSet>&         accT) noexcept :\n        perspective{persp},\n        featureTransformer{ft},\n        from{accF},\n        to{accT} {}\n\n    template<UpdateOperation... ops,\n             typename... Ts,\n             std::enable_if_t<is_all_same_v<IndexType, Ts...>, bool> = true>\n    void apply(const Ts... indices) {\n        auto to_weight_vector = [&](const IndexType index) {\n            return &featureTransformer.weights[index * Dimensions];\n        };\n\n        auto to_psqt_weight_vector = [&](const IndexType index) {\n            return &featureTransformer.psqtWeights[index * PSQTBuckets];\n        };\n\n        fused_row_reduce<Vec16Wrapper, Dimensions, ops...>(\n          (from.template acc<Dimensions>()).accumulation[perspective].data(),\n          (to.template acc<Dimensions>()).accumulation[perspective].data(),\n          to_weight_vector(indices)...);\n\n        fused_row_reduce<Vec32Wrapper, PSQTBuckets, ops...>(\n          (from.template acc<Dimensions>()).psqtAccumulation[perspective].data(),\n          (to.template acc<Dimensions>()).psqtAccumulation[perspective].data(),\n          to_psqt_weight_vector(indices)...);\n    }\n\n    void apply(const typename FeatureSet::IndexList& added,\n               const typename FeatureSet::IndexList& removed) {\n        const auto& fromAcc = from.template acc<Dimensions>().accumulation[perspective];\n        auto&       toAcc   = to.template acc<Dimensions>().accumulation[perspective];\n\n        const auto& fromPsqtAcc = from.template acc<Dimensions>().psqtAccumulation[perspective];\n        auto&       toPsqtAcc   = to.template acc<Dimensions>().psqtAccumulation[perspective];\n\n#ifdef VECTOR\n        using Tiling = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;\n        vec_t      acc[Tiling::NumRegs];\n        psqt_vec_t psqt[Tiling::NumPsqtRegs];\n\n        const auto* threatWeights = &featureTransformer.threatWeights[0];\n\n        for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j)\n        {\n            auto* fromTile = reinterpret_cast<const vec_t*>(&fromAcc[j * Tiling::TileHeight]);\n            auto* toTile   = reinterpret_cast<vec_t*>(&toAcc[j * Tiling::TileHeight]);\n\n            for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                acc[k] = fromTile[k];\n\n            for (int i = 0; i < removed.ssize(); ++i)\n            {\n                size_t       index  = removed[i];\n                const size_t offset = Dimensions * index;\n                auto*        column = reinterpret_cast<const vec_i8_t*>(&threatWeights[offset]);\n\n    #ifdef USE_NEON\n                for (IndexType k = 0; k < Tiling::NumRegs; k += 2)\n                {\n                    acc[k]     = vec_sub_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2])));\n                    acc[k + 1] = vec_sub_16(acc[k + 1], vmovl_high_s8(column[k / 2]));\n                }\n    #else\n                for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                    acc[k] = vec_sub_16(acc[k], vec_convert_8_16(column[k]));\n    #endif\n            }\n\n            for (int i = 0; i < added.ssize(); ++i)\n            {\n                size_t       index  = added[i];\n                const size_t offset = Dimensions * index;\n                auto*        column = reinterpret_cast<const vec_i8_t*>(&threatWeights[offset]);\n\n    #ifdef USE_NEON\n                for (IndexType k = 0; k < Tiling::NumRegs; k += 2)\n                {\n                    acc[k]     = vec_add_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2])));\n                    acc[k + 1] = vec_add_16(acc[k + 1], vmovl_high_s8(column[k / 2]));\n                }\n    #else\n                for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                    acc[k] = vec_add_16(acc[k], vec_convert_8_16(column[k]));\n    #endif\n            }\n\n            for (IndexType k = 0; k < Tiling::NumRegs; k++)\n                vec_store(&toTile[k], acc[k]);\n\n            threatWeights += Tiling::TileHeight;\n        }\n\n        for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j)\n        {\n            auto* fromTilePsqt =\n              reinterpret_cast<const psqt_vec_t*>(&fromPsqtAcc[j * Tiling::PsqtTileHeight]);\n            auto* toTilePsqt =\n              reinterpret_cast<psqt_vec_t*>(&toPsqtAcc[j * Tiling::PsqtTileHeight]);\n\n            for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n                psqt[k] = fromTilePsqt[k];\n\n            for (int i = 0; i < removed.ssize(); ++i)\n            {\n                size_t       index      = removed[i];\n                const size_t offset     = PSQTBuckets * index + j * Tiling::PsqtTileHeight;\n                auto*        columnPsqt = reinterpret_cast<const psqt_vec_t*>(\n                  &featureTransformer.threatPsqtWeights[offset]);\n\n                for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)\n                    psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);\n            }\n\n            for (int i = 0; i < added.ssize(); ++i)\n            {\n                size_t       index      = added[i];\n                const size_t offset     = PSQTBuckets * index + j * Tiling::PsqtTileHeight;\n                auto*        columnPsqt = reinterpret_cast<const psqt_vec_t*>(\n                  &featureTransformer.threatPsqtWeights[offset]);\n\n                for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)\n                    psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);\n            }\n\n            for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n                vec_store_psqt(&toTilePsqt[k], psqt[k]);\n        }\n\n#else\n\n        toAcc     = fromAcc;\n        toPsqtAcc = fromPsqtAcc;\n\n        for (const auto index : removed)\n        {\n            const IndexType offset = Dimensions * index;\n\n            for (IndexType j = 0; j < Dimensions; ++j)\n                toAcc[j] -= featureTransformer.threatWeights[offset + j];\n\n            for (std::size_t k = 0; k < PSQTBuckets; ++k)\n                toPsqtAcc[k] -= featureTransformer.threatPsqtWeights[index * PSQTBuckets + k];\n        }\n\n        for (const auto index : added)\n        {\n            const IndexType offset = Dimensions * index;\n\n            for (IndexType j = 0; j < Dimensions; ++j)\n                toAcc[j] += featureTransformer.threatWeights[offset + j];\n\n            for (std::size_t k = 0; k < PSQTBuckets; ++k)\n                toPsqtAcc[k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k];\n        }\n\n#endif\n    }\n};\n\ntemplate<typename FeatureSet, IndexType Dimensions>\nauto make_accumulator_update_context(Color                                 perspective,\n                                     const FeatureTransformer<Dimensions>& featureTransformer,\n                                     const AccumulatorState<FeatureSet>&   accumulatorFrom,\n                                     AccumulatorState<FeatureSet>&         accumulatorTo) noexcept {\n    return AccumulatorUpdateContext<FeatureSet, Dimensions>{perspective, featureTransformer,\n                                                            accumulatorFrom, accumulatorTo};\n}\n\ntemplate<IndexType TransformedFeatureDimensions>\nvoid double_inc_update(Color                                                   perspective,\n                       const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,\n                       const Square                                            ksq,\n                       AccumulatorState<PSQFeatureSet>&                        middle_state,\n                       AccumulatorState<PSQFeatureSet>&                        target_state,\n                       const AccumulatorState<PSQFeatureSet>&                  computed) {\n\n    assert(computed.acc<TransformedFeatureDimensions>().computed[perspective]);\n    assert(!middle_state.acc<TransformedFeatureDimensions>().computed[perspective]);\n    assert(!target_state.acc<TransformedFeatureDimensions>().computed[perspective]);\n\n    PSQFeatureSet::IndexList removed, added;\n    PSQFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added);\n    // you can't capture a piece that was just involved in castling since the rook ends up\n    // in a square that the king passed\n    assert(added.size() < 2);\n    PSQFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added);\n\n    [[maybe_unused]] const int addedSize   = added.ssize();\n    [[maybe_unused]] const int removedSize = removed.ssize();\n\n    assert(addedSize == 1);\n    assert(removedSize == 2 || removedSize == 3);\n\n    // Workaround compiler warning for uninitialized variables, replicated on\n    // profile builds on windows with gcc 14.2.0.\n    // Also helps with optimizations on some compilers.\n\n    sf_assume(addedSize == 1);\n    sf_assume(removedSize == 2 || removedSize == 3);\n\n    auto updateContext =\n      make_accumulator_update_context(perspective, featureTransformer, computed, target_state);\n\n    if (removedSize == 2)\n    {\n        updateContext.template apply<Add, Sub, Sub>(added[0], removed[0], removed[1]);\n    }\n    else\n    {\n        updateContext.template apply<Add, Sub, Sub, Sub>(added[0], removed[0], removed[1],\n                                                         removed[2]);\n    }\n\n    target_state.acc<TransformedFeatureDimensions>().computed[perspective] = true;\n}\n\ntemplate<IndexType TransformedFeatureDimensions>\nvoid double_inc_update(Color                                                   perspective,\n                       const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,\n                       const Square                                            ksq,\n                       AccumulatorState<ThreatFeatureSet>&                     middle_state,\n                       AccumulatorState<ThreatFeatureSet>&                     target_state,\n                       const AccumulatorState<ThreatFeatureSet>&               computed,\n                       const DirtyPiece&                                       dp2) {\n\n    assert(computed.acc<TransformedFeatureDimensions>().computed[perspective]);\n    assert(!middle_state.acc<TransformedFeatureDimensions>().computed[perspective]);\n    assert(!target_state.acc<TransformedFeatureDimensions>().computed[perspective]);\n\n    ThreatFeatureSet::FusedUpdateData fusedData;\n\n    fusedData.dp2removed = dp2.remove_sq;\n\n    ThreatFeatureSet::IndexList removed, added;\n    const auto*                 pfBase   = &featureTransformer.threatWeights[0];\n    auto                        pfStride = static_cast<IndexType>(TransformedFeatureDimensions);\n    ThreatFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added,\n                                             &fusedData, true, pfBase, pfStride);\n    ThreatFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added,\n                                             &fusedData, false, pfBase, pfStride);\n\n    auto updateContext =\n      make_accumulator_update_context(perspective, featureTransformer, computed, target_state);\n\n    updateContext.apply(added, removed);\n\n    target_state.acc<TransformedFeatureDimensions>().computed[perspective] = true;\n}\n\ntemplate<bool Forward, typename FeatureSet, IndexType TransformedFeatureDimensions>\nvoid update_accumulator_incremental(\n  Color                                                   perspective,\n  const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,\n  const Square                                            ksq,\n  AccumulatorState<FeatureSet>&                           target_state,\n  const AccumulatorState<FeatureSet>&                     computed) {\n\n    assert((computed.template acc<TransformedFeatureDimensions>()).computed[perspective]);\n    assert(!(target_state.template acc<TransformedFeatureDimensions>()).computed[perspective]);\n\n    // The size must be enough to contain the largest possible update.\n    // That might depend on the feature set and generally relies on the\n    // feature set's update cost calculation to be correct and never allow\n    // updates with more added/removed features than MaxActiveDimensions.\n    // In this case, the maximum size of both feature addition and removal\n    // is 2, since we are incrementally updating one move at a time.\n    typename FeatureSet::IndexList removed, added;\n    if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)\n    {\n        const auto* pfBase   = &featureTransformer.threatWeights[0];\n        auto        pfStride = static_cast<IndexType>(TransformedFeatureDimensions);\n        if constexpr (Forward)\n            FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added,\n                                               nullptr, false, pfBase, pfStride);\n        else\n            FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed,\n                                               nullptr, false, pfBase, pfStride);\n    }\n    else\n    {\n        if constexpr (Forward)\n            FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added);\n        else\n            FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed);\n    }\n\n    auto updateContext =\n      make_accumulator_update_context(perspective, featureTransformer, computed, target_state);\n\n    if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)\n        updateContext.apply(added, removed);\n    else\n    {\n        [[maybe_unused]] const int addedSize   = added.ssize();\n        [[maybe_unused]] const int removedSize = removed.ssize();\n\n        assert(addedSize == 1 || addedSize == 2);\n        assert(removedSize == 1 || removedSize == 2);\n        assert((Forward && addedSize <= removedSize) || (!Forward && addedSize >= removedSize));\n\n        // Workaround compiler warning for uninitialized variables, replicated\n        // on profile builds on windows with gcc 14.2.0.\n        // Also helps with optimizations on some compilers.\n\n        sf_assume(addedSize == 1 || addedSize == 2);\n        sf_assume(removedSize == 1 || removedSize == 2);\n\n        if (!(removedSize == 1 || removedSize == 2) || !(addedSize == 1 || addedSize == 2))\n            sf_unreachable();\n\n        if ((Forward && removedSize == 1) || (!Forward && addedSize == 1))\n        {\n            assert(addedSize == 1 && removedSize == 1);\n            updateContext.template apply<Add, Sub>(added[0], removed[0]);\n        }\n        else if (Forward && addedSize == 1)\n        {\n            assert(removedSize == 2);\n            updateContext.template apply<Add, Sub, Sub>(added[0], removed[0], removed[1]);\n        }\n        else if (!Forward && removedSize == 1)\n        {\n            assert(addedSize == 2);\n            updateContext.template apply<Add, Add, Sub>(added[0], added[1], removed[0]);\n        }\n        else\n        {\n            assert(addedSize == 2 && removedSize == 2);\n            updateContext.template apply<Add, Add, Sub, Sub>(added[0], added[1], removed[0],\n                                                             removed[1]);\n        }\n    }\n\n    (target_state.template acc<TransformedFeatureDimensions>()).computed[perspective] = true;\n}\n\nBitboard get_changed_pieces(const std::array<Piece, SQUARE_NB>& oldPieces,\n                            const std::array<Piece, SQUARE_NB>& newPieces) {\n#if defined(USE_AVX512) || defined(USE_AVX2)\n    static_assert(sizeof(Piece) == 1);\n    Bitboard sameBB = 0;\n\n    for (int i = 0; i < 64; i += 32)\n    {\n        const __m256i old_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(&oldPieces[i]));\n        const __m256i new_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(&newPieces[i]));\n        const __m256i cmpEqual        = _mm256_cmpeq_epi8(old_v, new_v);\n        const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual);\n        sameBB |= static_cast<Bitboard>(equalMask) << i;\n    }\n    return ~sameBB;\n#elif defined(USE_NEON)\n    uint8x16x4_t old_v = vld4q_u8(reinterpret_cast<const uint8_t*>(oldPieces.data()));\n    uint8x16x4_t new_v = vld4q_u8(reinterpret_cast<const uint8_t*>(newPieces.data()));\n    auto         cmp   = [=](const int i) { return vceqq_u8(old_v.val[i], new_v.val[i]); };\n\n    uint8x16_t cmp0_1 = vsriq_n_u8(cmp(1), cmp(0), 1);\n    uint8x16_t cmp2_3 = vsriq_n_u8(cmp(3), cmp(2), 1);\n    uint8x16_t merged = vsriq_n_u8(cmp2_3, cmp0_1, 2);\n    merged            = vsriq_n_u8(merged, merged, 4);\n    uint8x8_t sameBB  = vshrn_n_u16(vreinterpretq_u16_u8(merged), 4);\n\n    return ~vget_lane_u64(vreinterpret_u64_u8(sameBB), 0);\n#else\n    Bitboard changed = 0;\n\n    for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq)\n        changed |= static_cast<Bitboard>(oldPieces[sq] != newPieces[sq]) << sq;\n\n    return changed;\n#endif\n}\n\ntemplate<IndexType Dimensions>\nvoid update_accumulator_refresh_cache(Color                                 perspective,\n                                      const FeatureTransformer<Dimensions>& featureTransformer,\n                                      const Position&                       pos,\n                                      AccumulatorState<PSQFeatureSet>&      accumulatorState,\n                                      AccumulatorCaches::Cache<Dimensions>& cache) {\n\n    using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;\n\n    const Square             ksq   = pos.square<KING>(perspective);\n    auto&                    entry = cache[ksq][perspective];\n    PSQFeatureSet::IndexList removed, added;\n\n    const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array());\n    Bitboard       removedBB = changedBB & entry.pieceBB;\n    Bitboard       addedBB   = changedBB & pos.pieces();\n\n#if defined(USE_AVX512ICL)\n    PSQFeatureSet::write_indices(entry.pieces, pos.piece_array(), removedBB, addedBB, perspective,\n                                 ksq, removed, added);\n#else\n    while (removedBB)\n    {\n        Square sq = pop_lsb(removedBB);\n        removed.push_back(PSQFeatureSet::make_index(perspective, sq, entry.pieces[sq], ksq));\n    }\n    while (addedBB)\n    {\n        Square sq = pop_lsb(addedBB);\n        added.push_back(PSQFeatureSet::make_index(perspective, sq, pos.piece_on(sq), ksq));\n    }\n#endif\n\n    entry.pieceBB = pos.pieces();\n    entry.pieces  = pos.piece_array();\n\n    auto& accumulator                 = accumulatorState.acc<Dimensions>();\n    accumulator.computed[perspective] = true;\n\n#ifdef VECTOR\n    vec_t      acc[Tiling::NumRegs];\n    psqt_vec_t psqt[Tiling::NumPsqtRegs];\n\n    const auto* weights = &featureTransformer.weights[0];\n\n    for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j)\n    {\n        auto* accTile =\n          reinterpret_cast<vec_t*>(&accumulator.accumulation[perspective][j * Tiling::TileHeight]);\n        auto* entryTile = reinterpret_cast<vec_t*>(&entry.accumulation[j * Tiling::TileHeight]);\n\n        for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n            acc[k] = entryTile[k];\n\n        int i = 0;\n        for (; i < std::min(removed.ssize(), added.ssize()); ++i)\n        {\n            size_t       indexR  = removed[i];\n            const size_t offsetR = Dimensions * indexR;\n            auto*        columnR = reinterpret_cast<const vec_t*>(&weights[offsetR]);\n            size_t       indexA  = added[i];\n            const size_t offsetA = Dimensions * indexA;\n            auto*        columnA = reinterpret_cast<const vec_t*>(&weights[offsetA]);\n\n            for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                acc[k] = fused<Vec16Wrapper, Add, Sub>(acc[k], columnA[k], columnR[k]);\n        }\n        for (; i < removed.ssize(); ++i)\n        {\n            size_t       index  = removed[i];\n            const size_t offset = Dimensions * index;\n            auto*        column = reinterpret_cast<const vec_t*>(&weights[offset]);\n\n            for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                acc[k] = vec_sub_16(acc[k], column[k]);\n        }\n        for (; i < added.ssize(); ++i)\n        {\n            size_t       index  = added[i];\n            const size_t offset = Dimensions * index;\n            auto*        column = reinterpret_cast<const vec_t*>(&weights[offset]);\n\n            for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                acc[k] = vec_add_16(acc[k], column[k]);\n        }\n\n        for (IndexType k = 0; k < Tiling::NumRegs; k++)\n            vec_store(&entryTile[k], acc[k]);\n        for (IndexType k = 0; k < Tiling::NumRegs; k++)\n            vec_store(&accTile[k], acc[k]);\n\n        weights += Tiling::TileHeight;\n    }\n\n    for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j)\n    {\n        auto* accTilePsqt = reinterpret_cast<psqt_vec_t*>(\n          &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]);\n        auto* entryTilePsqt =\n          reinterpret_cast<psqt_vec_t*>(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]);\n\n        for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n            psqt[k] = entryTilePsqt[k];\n\n        for (int i = 0; i < removed.ssize(); ++i)\n        {\n            size_t       index  = removed[i];\n            const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;\n            auto*        columnPsqt =\n              reinterpret_cast<const psqt_vec_t*>(&featureTransformer.psqtWeights[offset]);\n\n            for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)\n                psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);\n        }\n        for (int i = 0; i < added.ssize(); ++i)\n        {\n            size_t       index  = added[i];\n            const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;\n            auto*        columnPsqt =\n              reinterpret_cast<const psqt_vec_t*>(&featureTransformer.psqtWeights[offset]);\n\n            for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)\n                psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);\n        }\n\n        for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n            vec_store_psqt(&entryTilePsqt[k], psqt[k]);\n        for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n            vec_store_psqt(&accTilePsqt[k], psqt[k]);\n    }\n\n#else\n\n    for (const auto index : removed)\n    {\n        const IndexType offset = Dimensions * index;\n        for (IndexType j = 0; j < Dimensions; ++j)\n            entry.accumulation[j] -= featureTransformer.weights[offset + j];\n\n        for (std::size_t k = 0; k < PSQTBuckets; ++k)\n            entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k];\n    }\n    for (const auto index : added)\n    {\n        const IndexType offset = Dimensions * index;\n        for (IndexType j = 0; j < Dimensions; ++j)\n            entry.accumulation[j] += featureTransformer.weights[offset + j];\n\n        for (std::size_t k = 0; k < PSQTBuckets; ++k)\n            entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k];\n    }\n\n    // The accumulator of the refresh entry has been updated.\n    // Now copy its content to the actual accumulator we were refreshing.\n    accumulator.accumulation[perspective]     = entry.accumulation;\n    accumulator.psqtAccumulation[perspective] = entry.psqtAccumulation;\n#endif\n}\n\ntemplate<IndexType Dimensions>\nvoid update_threats_accumulator_full(Color                                 perspective,\n                                     const FeatureTransformer<Dimensions>& featureTransformer,\n                                     const Position&                       pos,\n                                     AccumulatorState<ThreatFeatureSet>&   accumulatorState) {\n    using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;\n\n    ThreatFeatureSet::IndexList active;\n    ThreatFeatureSet::append_active_indices(perspective, pos, active);\n\n    auto& accumulator                 = accumulatorState.acc<Dimensions>();\n    accumulator.computed[perspective] = true;\n\n#ifdef VECTOR\n    vec_t      acc[Tiling::NumRegs];\n    psqt_vec_t psqt[Tiling::NumPsqtRegs];\n\n    const auto* threatWeights = &featureTransformer.threatWeights[0];\n\n    for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j)\n    {\n        auto* accTile =\n          reinterpret_cast<vec_t*>(&accumulator.accumulation[perspective][j * Tiling::TileHeight]);\n\n        for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n            acc[k] = vec_zero();\n\n        int i = 0;\n\n        for (; i < active.ssize(); ++i)\n        {\n            size_t       index  = active[i];\n            const size_t offset = Dimensions * index;\n            auto*        column = reinterpret_cast<const vec_i8_t*>(&threatWeights[offset]);\n\n    #ifdef USE_NEON\n            for (IndexType k = 0; k < Tiling::NumRegs; k += 2)\n            {\n                acc[k]     = vec_add_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2])));\n                acc[k + 1] = vec_add_16(acc[k + 1], vmovl_high_s8(column[k / 2]));\n            }\n    #else\n            for (IndexType k = 0; k < Tiling::NumRegs; ++k)\n                acc[k] = vec_add_16(acc[k], vec_convert_8_16(column[k]));\n    #endif\n        }\n\n        for (IndexType k = 0; k < Tiling::NumRegs; k++)\n            vec_store(&accTile[k], acc[k]);\n\n        threatWeights += Tiling::TileHeight;\n    }\n\n    for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j)\n    {\n        auto* accTilePsqt = reinterpret_cast<psqt_vec_t*>(\n          &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]);\n\n        for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n            psqt[k] = vec_zero_psqt();\n\n        for (int i = 0; i < active.ssize(); ++i)\n        {\n            size_t       index  = active[i];\n            const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;\n            auto*        columnPsqt =\n              reinterpret_cast<const psqt_vec_t*>(&featureTransformer.threatPsqtWeights[offset]);\n\n            for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)\n                psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);\n        }\n\n        for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)\n            vec_store_psqt(&accTilePsqt[k], psqt[k]);\n    }\n\n#else\n\n    for (IndexType j = 0; j < Dimensions; ++j)\n        accumulator.accumulation[perspective][j] = 0;\n\n    for (std::size_t k = 0; k < PSQTBuckets; ++k)\n        accumulator.psqtAccumulation[perspective][k] = 0;\n\n    for (const auto index : active)\n    {\n        const IndexType offset = Dimensions * index;\n\n        for (IndexType j = 0; j < Dimensions; ++j)\n            accumulator.accumulation[perspective][j] +=\n              featureTransformer.threatWeights[offset + j];\n\n        for (std::size_t k = 0; k < PSQTBuckets; ++k)\n            accumulator.psqtAccumulation[perspective][k] +=\n              featureTransformer.threatPsqtWeights[index * PSQTBuckets + k];\n    }\n\n#endif\n}\n\n}\n\n}\n"
  },
  {
    "path": "src/nnue/nnue_accumulator.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Class for difference calculation of NNUE evaluation function\n\n#ifndef NNUE_ACCUMULATOR_H_INCLUDED\n#define NNUE_ACCUMULATOR_H_INCLUDED\n\n#include <array>\n#include <cstddef>\n#include <cstdint>\n#include <cstring>\n#include <utility>\n\n#include \"../types.h\"\n#include \"nnue_architecture.h\"\n#include \"nnue_common.h\"\n\nnamespace Stockfish {\nclass Position;\n}\n\nnamespace Stockfish::Eval::NNUE {\n\ntemplate<IndexType Size>\nstruct alignas(CacheLineSize) Accumulator;\n\ntemplate<IndexType TransformedFeatureDimensions>\nclass FeatureTransformer;\n\n// Class that holds the result of affine transformation of input features\ntemplate<IndexType Size>\nstruct alignas(CacheLineSize) Accumulator {\n    std::array<std::array<std::int16_t, Size>, COLOR_NB>        accumulation;\n    std::array<std::array<std::int32_t, PSQTBuckets>, COLOR_NB> psqtAccumulation;\n    std::array<bool, COLOR_NB>                                  computed = {};\n};\n\n\n// AccumulatorCaches struct provides per-thread accumulator caches, where each\n// cache contains multiple entries for each of the possible king squares.\n// When the accumulator needs to be refreshed, the cached entry is used to more\n// efficiently update the accumulator, instead of rebuilding it from scratch.\n// This idea, was first described by Luecx (author of Koivisto) and\n// is commonly referred to as \"Finny Tables\".\nstruct AccumulatorCaches {\n\n    template<typename Networks>\n    AccumulatorCaches(const Networks& networks) {\n        clear(networks);\n    }\n\n    template<IndexType Size>\n    struct alignas(CacheLineSize) Cache {\n\n        struct alignas(CacheLineSize) Entry {\n            std::array<BiasType, Size>              accumulation;\n            std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;\n            std::array<Piece, SQUARE_NB>            pieces;\n            Bitboard                                pieceBB;\n\n            // To initialize a refresh entry, we set all its bitboards empty,\n            // so we put the biases in the accumulation, without any weights on top\n            void clear(const std::array<BiasType, Size>& biases) {\n                accumulation = biases;\n                std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation),\n                            0, sizeof(Entry) - offsetof(Entry, psqtAccumulation));\n            }\n        };\n\n        template<typename Network>\n        void clear(const Network& network) {\n            for (auto& entries1D : entries)\n                for (auto& entry : entries1D)\n                    entry.clear(network.featureTransformer.biases);\n        }\n\n        std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }\n\n        std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;\n    };\n\n    template<typename Networks>\n    void clear(const Networks& networks) {\n        big.clear(networks.big);\n        small.clear(networks.small);\n    }\n\n    Cache<TransformedFeatureDimensionsBig>   big;\n    Cache<TransformedFeatureDimensionsSmall> small;\n};\n\n\ntemplate<typename FeatureSet>\nstruct AccumulatorState {\n    Accumulator<TransformedFeatureDimensionsBig>   accumulatorBig;\n    Accumulator<TransformedFeatureDimensionsSmall> accumulatorSmall;\n    typename FeatureSet::DiffType                  diff;\n\n    template<IndexType Size>\n    auto& acc() noexcept {\n        static_assert(Size == TransformedFeatureDimensionsBig\n                        || Size == TransformedFeatureDimensionsSmall,\n                      \"Invalid size for accumulator\");\n\n        if constexpr (Size == TransformedFeatureDimensionsBig)\n            return accumulatorBig;\n        else if constexpr (Size == TransformedFeatureDimensionsSmall)\n            return accumulatorSmall;\n    }\n\n    template<IndexType Size>\n    const auto& acc() const noexcept {\n        static_assert(Size == TransformedFeatureDimensionsBig\n                        || Size == TransformedFeatureDimensionsSmall,\n                      \"Invalid size for accumulator\");\n\n        if constexpr (Size == TransformedFeatureDimensionsBig)\n            return accumulatorBig;\n        else if constexpr (Size == TransformedFeatureDimensionsSmall)\n            return accumulatorSmall;\n    }\n\n    void reset(const typename FeatureSet::DiffType& dp) noexcept {\n        diff = dp;\n        accumulatorBig.computed.fill(false);\n        accumulatorSmall.computed.fill(false);\n    }\n\n    typename FeatureSet::DiffType& reset() noexcept {\n        accumulatorBig.computed.fill(false);\n        accumulatorSmall.computed.fill(false);\n        return diff;\n    }\n};\n\nclass AccumulatorStack {\n   public:\n    static constexpr std::size_t MaxSize = MAX_PLY + 1;\n\n    template<typename T>\n    [[nodiscard]] const AccumulatorState<T>& latest() const noexcept;\n\n    void                                  reset() noexcept;\n    std::pair<DirtyPiece&, DirtyThreats&> push() noexcept;\n    void                                  pop() noexcept;\n\n    template<IndexType Dimensions>\n    void evaluate(const Position&                       pos,\n                  const FeatureTransformer<Dimensions>& featureTransformer,\n                  AccumulatorCaches::Cache<Dimensions>& cache) noexcept;\n\n   private:\n    template<typename T>\n    [[nodiscard]] AccumulatorState<T>& mut_latest() noexcept;\n\n    template<typename T>\n    [[nodiscard]] const std::array<AccumulatorState<T>, MaxSize>& accumulators() const noexcept;\n\n    template<typename T>\n    [[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept;\n\n    template<typename FeatureSet, IndexType Dimensions>\n    void evaluate_side(Color                                 perspective,\n                       const Position&                       pos,\n                       const FeatureTransformer<Dimensions>& featureTransformer,\n                       AccumulatorCaches::Cache<Dimensions>& cache) noexcept;\n\n    template<typename FeatureSet, IndexType Dimensions>\n    [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept;\n\n    template<typename FeatureSet, IndexType Dimensions>\n    void forward_update_incremental(Color                                 perspective,\n                                    const Position&                       pos,\n                                    const FeatureTransformer<Dimensions>& featureTransformer,\n                                    const std::size_t                     begin) noexcept;\n\n    template<typename FeatureSet, IndexType Dimensions>\n    void backward_update_incremental(Color                                 perspective,\n                                     const Position&                       pos,\n                                     const FeatureTransformer<Dimensions>& featureTransformer,\n                                     const std::size_t                     end) noexcept;\n\n    std::array<AccumulatorState<PSQFeatureSet>, MaxSize>    psq_accumulators;\n    std::array<AccumulatorState<ThreatFeatureSet>, MaxSize> threat_accumulators;\n    std::size_t                                             size = 1;\n};\n\n}  // namespace Stockfish::Eval::NNUE\n\n#endif  // NNUE_ACCUMULATOR_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/nnue_architecture.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Input features and network structure used in NNUE evaluation function\n\n#ifndef NNUE_ARCHITECTURE_H_INCLUDED\n#define NNUE_ARCHITECTURE_H_INCLUDED\n\n#include <cstdint>\n#include <cstring>\n#include <iosfwd>\n\n#include \"features/half_ka_v2_hm.h\"\n#include \"features/full_threats.h\"\n#include \"layers/affine_transform.h\"\n#include \"layers/affine_transform_sparse_input.h\"\n#include \"layers/clipped_relu.h\"\n#include \"layers/sqr_clipped_relu.h\"\n#include \"nnue_common.h\"\n\nnamespace Stockfish::Eval::NNUE {\n\n// Input features used in evaluation function\nusing ThreatFeatureSet = Features::FullThreats;\nusing PSQFeatureSet    = Features::HalfKAv2_hm;\n\n// Number of input feature dimensions after conversion\nconstexpr IndexType TransformedFeatureDimensionsBig = 1024;\nconstexpr int       L2Big                           = 31;\nconstexpr int       L3Big                           = 32;\n\nconstexpr IndexType TransformedFeatureDimensionsSmall = 128;\nconstexpr int       L2Small                           = 15;\nconstexpr int       L3Small                           = 32;\n\nconstexpr IndexType PSQTBuckets = 8;\nconstexpr IndexType LayerStacks = 8;\n\n// If vector instructions are enabled, we update and refresh the\n// accumulator tile by tile such that each tile fits in the CPU's\n// vector registers.\nstatic_assert(PSQTBuckets % 8 == 0,\n              \"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.\");\n\ntemplate<IndexType L1, int L2, int L3>\nstruct NetworkArchitecture {\n    static constexpr IndexType TransformedFeatureDimensions = L1;\n    static constexpr int       FC_0_OUTPUTS                 = L2;\n    static constexpr int       FC_1_OUTPUTS                 = L3;\n\n    Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;\n    Layers::SqrClippedReLU<FC_0_OUTPUTS + 1>                                           ac_sqr_0;\n    Layers::ClippedReLU<FC_0_OUTPUTS + 1>                                              ac_0;\n    Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS>                            fc_1;\n    Layers::ClippedReLU<FC_1_OUTPUTS>                                                  ac_1;\n    Layers::AffineTransform<FC_1_OUTPUTS, 1>                                           fc_2;\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t get_hash_value() {\n        // input slice hash\n        std::uint32_t hashValue = 0xEC42E90Du;\n        hashValue ^= TransformedFeatureDimensions * 2;\n\n        hashValue = decltype(fc_0)::get_hash_value(hashValue);\n        hashValue = decltype(ac_0)::get_hash_value(hashValue);\n        hashValue = decltype(fc_1)::get_hash_value(hashValue);\n        hashValue = decltype(ac_1)::get_hash_value(hashValue);\n        hashValue = decltype(fc_2)::get_hash_value(hashValue);\n\n        return hashValue;\n    }\n\n    // Read network parameters\n    bool read_parameters(std::istream& stream) {\n        return fc_0.read_parameters(stream) && ac_0.read_parameters(stream)\n            && fc_1.read_parameters(stream) && ac_1.read_parameters(stream)\n            && fc_2.read_parameters(stream);\n    }\n\n    // Write network parameters\n    bool write_parameters(std::ostream& stream) const {\n        return fc_0.write_parameters(stream) && ac_0.write_parameters(stream)\n            && fc_1.write_parameters(stream) && ac_1.write_parameters(stream)\n            && fc_2.write_parameters(stream);\n    }\n\n    std::int32_t propagate(const TransformedFeatureType* transformedFeatures) const {\n        struct alignas(CacheLineSize) Buffer {\n            alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out;\n            alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType\n              ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];\n            alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out;\n            alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out;\n            alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out;\n            alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out;\n\n            Buffer() { std::memset(this, 0, sizeof(*this)); }\n        };\n\n#if defined(__clang__) && (__APPLE__)\n        // workaround for a bug reported with xcode 12\n        static thread_local auto tlsBuffer = std::make_unique<Buffer>();\n        // Access TLS only once, cache result.\n        Buffer& buffer = *tlsBuffer;\n#else\n        alignas(CacheLineSize) static thread_local Buffer buffer;\n#endif\n\n        fc_0.propagate(transformedFeatures, buffer.fc_0_out);\n        ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out);\n        ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);\n        std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out,\n                    FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType));\n        fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);\n        ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);\n        fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);\n\n        // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in\n        // quantized form, but we want 1.0 to be equal to 600*OutputScale\n        std::int32_t fwdOut =\n          (buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));\n        std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;\n\n        return outputValue;\n    }\n\n    std::size_t get_content_hash() const {\n        std::size_t h = 0;\n        hash_combine(h, fc_0.get_content_hash());\n        hash_combine(h, ac_sqr_0.get_content_hash());\n        hash_combine(h, ac_0.get_content_hash());\n        hash_combine(h, fc_1.get_content_hash());\n        hash_combine(h, ac_1.get_content_hash());\n        hash_combine(h, fc_2.get_content_hash());\n        hash_combine(h, get_hash_value());\n        return h;\n    }\n};\n\n}  // namespace Stockfish::Eval::NNUE\n\ntemplate<Stockfish::Eval::NNUE::IndexType L1, int L2, int L3>\nstruct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>> {\n    std::size_t\n    operator()(const Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>& arch) const noexcept {\n        return arch.get_content_hash();\n    }\n};\n\n#endif  // #ifndef NNUE_ARCHITECTURE_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/nnue_common.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Constants used in NNUE evaluation function\n\n#ifndef NNUE_COMMON_H_INCLUDED\n#define NNUE_COMMON_H_INCLUDED\n\n#include <algorithm>\n#include <cassert>\n#include <cstdint>\n#include <cstring>\n#include <iostream>\n#include <type_traits>\n\n#include \"../misc.h\"\n\n#if defined(USE_AVX2)\n    #include <immintrin.h>\n\n#elif defined(USE_SSE41)\n    #include <smmintrin.h>\n\n#elif defined(USE_SSSE3)\n    #include <tmmintrin.h>\n\n#elif defined(USE_SSE2)\n    #include <emmintrin.h>\n\n#elif defined(USE_NEON)\n    #include <arm_neon.h>\n#endif\n\nnamespace Stockfish::Eval::NNUE {\n\nusing BiasType         = std::int16_t;\nusing ThreatWeightType = std::int8_t;\nusing WeightType       = std::int16_t;\nusing PSQTWeightType   = std::int32_t;\nusing IndexType        = std::uint32_t;\n\n// Version of the evaluation file\nconstexpr std::uint32_t Version = 0x7AF32F20u;\n\n// Constant used in evaluation value calculation\nconstexpr int OutputScale     = 16;\nconstexpr int WeightScaleBits = 6;\n\n// Size of cache line (in bytes)\nconstexpr std::size_t CacheLineSize = 64;\n\nconstexpr const char        Leb128MagicString[]   = \"COMPRESSED_LEB128\";\nconstexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;\n\n// SIMD width (in bytes)\n#if defined(USE_AVX2)\nconstexpr std::size_t SimdWidth = 32;\n\n#elif defined(USE_SSE2)\nconstexpr std::size_t SimdWidth = 16;\n\n#elif defined(USE_NEON)\nconstexpr std::size_t SimdWidth = 16;\n#endif\n\nconstexpr std::size_t MaxSimdWidth = 32;\n\n// Type of input feature after conversion\nusing TransformedFeatureType = std::uint8_t;\n\n// Round n up to be a multiple of base\ntemplate<typename IntType>\nconstexpr IntType ceil_to_multiple(IntType n, IntType base) {\n    return (n + base - 1) / base * base;\n}\n\n\n// Utility to read an integer (signed or unsigned, any size)\n// from a stream in little-endian order. We swap the byte order after the read if\n// necessary to return a result with the byte ordering of the compiling machine.\ntemplate<typename IntType>\ninline IntType read_little_endian(std::istream& stream) {\n    IntType result;\n\n    if (IsLittleEndian)\n        stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));\n    else\n    {\n        std::uint8_t                  u[sizeof(IntType)];\n        std::make_unsigned_t<IntType> v = 0;\n\n        stream.read(reinterpret_cast<char*>(u), sizeof(IntType));\n        for (std::size_t i = 0; i < sizeof(IntType); ++i)\n            v = (v << 8) | u[sizeof(IntType) - i - 1];\n\n        std::memcpy(&result, &v, sizeof(IntType));\n    }\n\n    return result;\n}\n\n\n// Utility to write an integer (signed or unsigned, any size)\n// to a stream in little-endian order. We swap the byte order before the write if\n// necessary to always write in little-endian order, independently of the byte\n// ordering of the compiling machine.\ntemplate<typename IntType>\ninline void write_little_endian(std::ostream& stream, IntType value) {\n\n    if (IsLittleEndian)\n        stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));\n    else\n    {\n        std::uint8_t                  u[sizeof(IntType)];\n        std::make_unsigned_t<IntType> v = value;\n\n        std::size_t i = 0;\n        // if constexpr to silence the warning about shift by 8\n        if constexpr (sizeof(IntType) > 1)\n        {\n            for (; i + 1 < sizeof(IntType); ++i)\n            {\n                u[i] = std::uint8_t(v);\n                v >>= 8;\n            }\n        }\n        u[i] = std::uint8_t(v);\n\n        stream.write(reinterpret_cast<char*>(u), sizeof(IntType));\n    }\n}\n\n\n// Read integers in bulk from a little-endian stream.\n// This reads N integers from stream s and puts them in array out.\ntemplate<typename IntType>\ninline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {\n    if (IsLittleEndian)\n        stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);\n    else\n        for (std::size_t i = 0; i < count; ++i)\n            out[i] = read_little_endian<IntType>(stream);\n}\n\n\n// Write integers in bulk to a little-endian stream.\n// This takes N integers from array values and writes them on stream s.\ntemplate<typename IntType>\ninline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {\n    if (IsLittleEndian)\n        stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);\n    else\n        for (std::size_t i = 0; i < count; ++i)\n            write_little_endian<IntType>(stream, values[i]);\n}\n\n// Read N signed integers from the stream s, putting them in the array out.\n// The stream is assumed to be compressed using the signed LEB128 format.\n// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.\ntemplate<typename BufType, typename IntType, std::size_t Count>\ninline void read_leb_128_detail(std::istream&               stream,\n                                std::array<IntType, Count>& out,\n                                std::uint32_t&              bytes_left,\n                                BufType&                    buf,\n                                std::uint32_t&              buf_pos) {\n\n    static_assert(std::is_signed_v<IntType>, \"Not implemented for unsigned types\");\n    static_assert(sizeof(IntType) <= 4, \"Not implemented for types larger than 32 bit\");\n\n    IntType result = 0;\n    size_t  shift = 0, i = 0;\n    while (i < Count)\n    {\n        if (buf_pos == buf.size())\n        {\n            stream.read(reinterpret_cast<char*>(buf.data()),\n                        std::min(std::size_t(bytes_left), buf.size()));\n            buf_pos = 0;\n        }\n\n        std::uint8_t byte = buf[buf_pos++];\n        --bytes_left;\n        result |= (byte & 0x7f) << (shift % 32);\n        shift += 7;\n\n        if ((byte & 0x80) == 0)\n        {\n            out[i++] = (shift >= 32 || (byte & 0x40) == 0) ? result : result | ~((1 << shift) - 1);\n            result   = 0;\n            shift    = 0;\n        }\n    }\n}\n\ntemplate<typename... Arrays>\ninline void read_leb_128(std::istream& stream, Arrays&... outs) {\n    // Check the presence of our LEB128 magic string\n    char leb128MagicString[Leb128MagicStringSize];\n    stream.read(leb128MagicString, Leb128MagicStringSize);\n    assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);\n\n    auto                           bytes_left = read_little_endian<std::uint32_t>(stream);\n    std::array<std::uint8_t, 8192> buf;\n    std::uint32_t                  buf_pos = std::uint32_t(buf.size());\n\n    (read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...);\n\n    assert(bytes_left == 0);\n}\n\n\n// Write signed integers to a stream with LEB128 compression.\n// This takes N integers from array values, compresses them with\n// the LEB128 algorithm and writes the result on the stream s.\n// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.\ntemplate<typename IntType, std::size_t Count>\ninline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {\n\n    // Write our LEB128 magic string\n    stream.write(Leb128MagicString, Leb128MagicStringSize);\n\n    static_assert(std::is_signed_v<IntType>, \"Not implemented for unsigned types\");\n\n    std::uint32_t byte_count = 0;\n    for (std::size_t i = 0; i < Count; ++i)\n    {\n        IntType      value = values[i];\n        std::uint8_t byte;\n        do\n        {\n            byte = value & 0x7f;\n            value >>= 7;\n            ++byte_count;\n        } while ((byte & 0x40) == 0 ? value != 0 : value != -1);\n    }\n\n    write_little_endian(stream, byte_count);\n\n    const std::uint32_t BUF_SIZE = 4096;\n    std::uint8_t        buf[BUF_SIZE];\n    std::uint32_t       buf_pos = 0;\n\n    auto flush = [&]() {\n        if (buf_pos > 0)\n        {\n            stream.write(reinterpret_cast<char*>(buf), buf_pos);\n            buf_pos = 0;\n        }\n    };\n\n    auto write = [&](std::uint8_t b) {\n        buf[buf_pos++] = b;\n        if (buf_pos == BUF_SIZE)\n            flush();\n    };\n\n    for (std::size_t i = 0; i < Count; ++i)\n    {\n        IntType value = values[i];\n        while (true)\n        {\n            std::uint8_t byte = value & 0x7f;\n            value >>= 7;\n            if ((byte & 0x40) == 0 ? value == 0 : value == -1)\n            {\n                write(byte);\n                break;\n            }\n            write(byte | 0x80);\n        }\n    }\n\n    flush();\n}\n\n}  // namespace Stockfish::Eval::NNUE\n\n#endif  // #ifndef NNUE_COMMON_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/nnue_feature_transformer.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// A class that converts the input features of the NNUE evaluation function\n\n#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED\n#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED\n\n#include <algorithm>\n#include <cstdint>\n#include <cstring>\n#include <iosfwd>\n#include <iterator>\n\n#include \"../position.h\"\n#include \"../types.h\"\n#include \"nnue_accumulator.h\"\n#include \"nnue_architecture.h\"\n#include \"nnue_common.h\"\n#include \"simd.h\"\n\nnamespace Stockfish::Eval::NNUE {\n\n// Returns the inverse of a permutation\ntemplate<std::size_t Len>\nconstexpr std::array<std::size_t, Len>\ninvert_permutation(const std::array<std::size_t, Len>& order) {\n    std::array<std::size_t, Len> inverse{};\n    for (std::size_t i = 0; i < order.size(); i++)\n        inverse[order[i]] = i;\n    return inverse;\n}\n\n// Divide a byte region of size TotalSize to chunks of size\n// BlockSize, and permute the blocks by a given order\ntemplate<std::size_t BlockSize, typename T, std::size_t N, std::size_t OrderSize>\nvoid permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& order) {\n    constexpr std::size_t TotalSize = N * sizeof(T);\n\n    static_assert(TotalSize % (BlockSize * OrderSize) == 0,\n                  \"ChunkSize * OrderSize must perfectly divide TotalSize\");\n\n    constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize;\n\n    std::array<std::byte, ProcessChunkSize> buffer{};\n\n    std::byte* const bytes = reinterpret_cast<std::byte*>(data.data());\n\n    for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize)\n    {\n        std::byte* const values = &bytes[i];\n\n        for (std::size_t j = 0; j < OrderSize; j++)\n        {\n            auto* const buffer_chunk = &buffer[j * BlockSize];\n            auto* const value_chunk  = &values[order[j] * BlockSize];\n\n            std::copy(value_chunk, value_chunk + BlockSize, buffer_chunk);\n        }\n\n        std::copy(std::begin(buffer), std::end(buffer), values);\n    }\n}\n\n// Input feature converter\ntemplate<IndexType TransformedFeatureDimensions>\nclass FeatureTransformer {\n    static constexpr bool UseThreats =\n      (TransformedFeatureDimensions == TransformedFeatureDimensionsBig);\n    // Number of output dimensions for one side\n    static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;\n\n   public:\n    // Output type\n    using OutputType = TransformedFeatureType;\n\n    // Number of input/output dimensions\n    static constexpr IndexType InputDimensions       = PSQFeatureSet::Dimensions;\n    static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions;\n    static constexpr IndexType TotalInputDimensions =\n      InputDimensions + (UseThreats ? ThreatInputDimensions : 0);\n    static constexpr IndexType OutputDimensions = HalfDimensions;\n\n    // Size of forward propagation buffer\n    static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType);\n\n    // Store the order by which 128-bit blocks of a 1024-bit data must\n    // be permuted so that calling packus on adjacent vectors of 16-bit\n    // integers loaded from the data results in the pre-permutation order\n    static constexpr auto PackusEpi16Order = []() -> std::array<std::size_t, 8> {\n#if defined(USE_AVX512)\n        // _mm512_packus_epi16 after permutation:\n        // |   0   |   2   |   4   |   6   | // Vector 0\n        // |   1   |   3   |   5   |   7   | // Vector 1\n        // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result\n        return {0, 2, 4, 6, 1, 3, 5, 7};\n#elif defined(USE_AVX2)\n        // _mm256_packus_epi16 after permutation:\n        // |   0   |   2   |  |   4   |   6   | // Vector 0, 2\n        // |   1   |   3   |  |   5   |   7   | // Vector 1, 3\n        // | 0 | 1 | 2 | 3 |  | 4 | 5 | 6 | 7 | // Packed Result\n        return {0, 2, 1, 3, 4, 6, 5, 7};\n#else\n        return {0, 1, 2, 3, 4, 5, 6, 7};\n#endif\n    }();\n\n    static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order);\n\n    static constexpr std::uint32_t combine_hash(std::initializer_list<std::uint32_t> hashes) {\n        std::uint32_t hash = 0;\n        for (const auto component_hash : hashes)\n        {\n            hash = (hash << 1) | (hash >> 31);\n            hash ^= component_hash;\n        }\n        return hash;\n    }\n\n    // Hash value embedded in the evaluation file\n    static constexpr std::uint32_t get_hash_value() {\n        return (UseThreats ? combine_hash({ThreatFeatureSet::HashValue, PSQFeatureSet::HashValue})\n                           : PSQFeatureSet::HashValue)\n             ^ (OutputDimensions * 2);\n    }\n\n    void permute_weights() {\n        permute<16>(biases, PackusEpi16Order);\n        permute<16>(weights, PackusEpi16Order);\n\n        if constexpr (UseThreats)\n            permute<8>(threatWeights, PackusEpi16Order);\n    }\n\n    void unpermute_weights() {\n        permute<16>(biases, InversePackusEpi16Order);\n        permute<16>(weights, InversePackusEpi16Order);\n\n        if constexpr (UseThreats)\n            permute<8>(threatWeights, InversePackusEpi16Order);\n    }\n\n    // Read network parameters\n    bool read_parameters(std::istream& stream) {\n        read_leb_128(stream, biases);\n\n        if constexpr (UseThreats)\n        {\n            read_little_endian<ThreatWeightType>(stream, threatWeights.data(),\n                                                 ThreatInputDimensions * HalfDimensions);\n            read_leb_128(stream, weights);\n\n            read_leb_128(stream, threatPsqtWeights, psqtWeights);\n        }\n        else\n        {\n            read_leb_128(stream, weights);\n            read_leb_128(stream, psqtWeights);\n        }\n\n        permute_weights();\n\n        return !stream.fail();\n    }\n\n    // Write network parameters\n    bool write_parameters(std::ostream& stream) const {\n        std::unique_ptr<FeatureTransformer> copy = std::make_unique<FeatureTransformer>(*this);\n\n        copy->unpermute_weights();\n\n        write_leb_128<BiasType>(stream, copy->biases);\n\n        if constexpr (UseThreats)\n        {\n            write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(),\n                                                  ThreatInputDimensions * HalfDimensions);\n            write_leb_128<WeightType>(stream, copy->weights);\n\n            auto combinedPsqtWeights =\n              std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>();\n\n            std::copy(std::begin(copy->threatPsqtWeights),\n                      std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets,\n                      combinedPsqtWeights->begin());\n\n            std::copy(std::begin(copy->psqtWeights),\n                      std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets,\n                      combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets);\n\n            write_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);\n        }\n        else\n        {\n            write_leb_128<WeightType>(stream, copy->weights);\n            write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);\n        }\n\n        return !stream.fail();\n    }\n\n    std::size_t get_content_hash() const {\n        std::size_t h = 0;\n\n        hash_combine(h, get_raw_data_hash(biases));\n        hash_combine(h, get_raw_data_hash(weights));\n        hash_combine(h, get_raw_data_hash(psqtWeights));\n\n        if constexpr (UseThreats)\n        {\n            hash_combine(h, get_raw_data_hash(threatWeights));\n            hash_combine(h, get_raw_data_hash(threatPsqtWeights));\n        }\n\n        hash_combine(h, get_hash_value());\n\n        return h;\n    }\n\n    // Convert input features\n    std::int32_t transform(const Position&                           pos,\n                           AccumulatorStack&                         accumulatorStack,\n                           AccumulatorCaches::Cache<HalfDimensions>& cache,\n                           OutputType*                               output,\n                           int                                       bucket) const {\n\n        using namespace SIMD;\n        accumulatorStack.evaluate(pos, *this, cache);\n        const auto& accumulatorState       = accumulatorStack.latest<PSQFeatureSet>();\n        const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>();\n\n        const Color perspectives[2]  = {pos.side_to_move(), ~pos.side_to_move()};\n        const auto& psqtAccumulation = (accumulatorState.acc<HalfDimensions>()).psqtAccumulation;\n        auto        psqt =\n          (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);\n\n        if constexpr (UseThreats)\n        {\n            const auto& threatPsqtAccumulation =\n              (threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation;\n            psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket]\n                    - threatPsqtAccumulation[perspectives[1]][bucket])\n                 / 2;\n        }\n        else\n            psqt /= 2;\n\n        const auto& accumulation = (accumulatorState.acc<HalfDimensions>()).accumulation;\n        const auto& threatAccumulation =\n          (threatAccumulatorState.acc<HalfDimensions>()).accumulation;\n\n        for (IndexType p = 0; p < 2; ++p)\n        {\n            const IndexType offset = (HalfDimensions / 2) * p;\n\n#if defined(VECTOR)\n\n            constexpr IndexType OutputChunkSize = MaxChunkSize;\n            static_assert((HalfDimensions / 2) % OutputChunkSize == 0);\n            constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;\n\n            const vec_t Zero = vec_zero();\n            const vec_t One  = vec_set_16(255);\n\n            const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));\n            const vec_t* in1 =\n              reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));\n            vec_t* out = reinterpret_cast<vec_t*>(output + offset);\n\n            // Per the NNUE architecture, here we want to multiply pairs of\n            // clipped elements and divide the product by 128. To do this,\n            // we can naively perform min/max operation to clip each of the\n            // four int16 vectors, mullo pairs together, then pack them into\n            // one int8 vector. However, there exists a faster way.\n\n            // The idea here is to use the implicit clipping from packus to\n            // save us two vec_max_16 instructions. This clipping works due\n            // to the fact that any int16 integer below zero will be zeroed\n            // on packus.\n\n            // Consider the case where the second element is negative.\n            // If we do standard clipping, that element will be zero, which\n            // means our pairwise product is zero. If we perform packus and\n            // remove the lower-side clip for the second element, then our\n            // product before packus will be negative, and is zeroed on pack.\n            // The two operation produce equivalent results, but the second\n            // one (using packus) saves one max operation per pair.\n\n            // But here we run into a problem: mullo does not preserve the\n            // sign of the multiplication. We can get around this by doing\n            // mulhi, which keeps the sign. But that requires an additional\n            // tweak.\n\n            // mulhi cuts off the last 16 bits of the resulting product,\n            // which is the same as performing a rightward shift of 16 bits.\n            // We can use this to our advantage. Recall that we want to\n            // divide the final product by 128, which is equivalent to a\n            // 7-bit right shift. Intuitively, if we shift the clipped\n            // value left by 9, and perform mulhi, which shifts the product\n            // right by 16 bits, then we will net a right shift of 7 bits.\n            // However, this won't work as intended. Since we clip the\n            // values to have a maximum value of 127, shifting it by 9 bits\n            // might occupy the signed bit, resulting in some positive\n            // values being interpreted as negative after the shift.\n\n            // There is a way, however, to get around this limitation. When\n            // loading the network, scale accumulator weights and biases by\n            // 2. To get the same pairwise multiplication result as before,\n            // we need to divide the product by 128 * 2 * 2 = 512, which\n            // amounts to a right shift of 9 bits. So now we only have to\n            // shift left by 7 bits, perform mulhi (shifts right by 16 bits)\n            // and net a 9 bit right shift. Since we scaled everything by\n            // two, the values are clipped at 127 * 2 = 254, which occupies\n            // 8 bits. Shifting it by 7 bits left will no longer occupy the\n            // signed bit, so we are safe.\n\n            // Note that on NEON processors, we shift left by 6 instead\n            // because the instruction \"vqdmulhq_s16\" also doubles the\n            // return value after the multiplication, adding an extra shift\n            // to the left by 1, so we compensate by shifting less before\n            // the multiplication.\n\n            constexpr int shift =\n    #if defined(USE_SSE2)\n              7;\n    #else\n              6;\n    #endif\n            if constexpr (UseThreats)\n            {\n                const vec_t* tin0 =\n                  reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));\n                const vec_t* tin1 = reinterpret_cast<const vec_t*>(\n                  &(threatAccumulation[perspectives[p]][HalfDimensions / 2]));\n                for (IndexType j = 0; j < NumOutputChunks; ++j)\n                {\n                    const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]);\n                    const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]);\n                    const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]);\n                    const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]);\n\n                    const vec_t sum0a =\n                      vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift);\n                    const vec_t sum0b =\n                      vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift);\n                    const vec_t sum1a = vec_min_16(acc1a, One);\n                    const vec_t sum1b = vec_min_16(acc1b, One);\n\n                    const vec_t pa = vec_mulhi_16(sum0a, sum1a);\n                    const vec_t pb = vec_mulhi_16(sum0b, sum1b);\n\n                    out[j] = vec_packus_16(pa, pb);\n                }\n            }\n            else\n            {\n                for (IndexType j = 0; j < NumOutputChunks; ++j)\n                {\n                    const vec_t sum0a =\n                      vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift);\n                    const vec_t sum0b =\n                      vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift);\n                    const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One);\n                    const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One);\n\n                    const vec_t pa = vec_mulhi_16(sum0a, sum1a);\n                    const vec_t pb = vec_mulhi_16(sum0b, sum1b);\n\n                    out[j] = vec_packus_16(pa, pb);\n                }\n            }\n\n#else\n\n            for (IndexType j = 0; j < HalfDimensions / 2; ++j)\n            {\n                BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];\n                BiasType sum1 =\n                  accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];\n\n                if constexpr (UseThreats)\n                {\n                    sum0 += threatAccumulation[static_cast<int>(perspectives[p])][j + 0];\n                    sum1 +=\n                      threatAccumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];\n                }\n\n                sum0 = std::clamp<BiasType>(sum0, 0, 255);\n                sum1 = std::clamp<BiasType>(sum1, 0, 255);\n\n                output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 512);\n            }\n\n#endif\n        }\n\n        return psqt;\n    }  // end of function transform()\n\n    alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases;\n    alignas(CacheLineSize) std::array<WeightType, HalfDimensions * InputDimensions> weights;\n    alignas(CacheLineSize)\n      std::array<ThreatWeightType,\n                 UseThreats ? HalfDimensions * ThreatInputDimensions : 0> threatWeights;\n    alignas(CacheLineSize) std::array<PSQTWeightType, InputDimensions * PSQTBuckets> psqtWeights;\n    alignas(CacheLineSize)\n      std::array<PSQTWeightType,\n                 UseThreats ? ThreatInputDimensions * PSQTBuckets : 0> threatPsqtWeights;\n};\n\n}  // namespace Stockfish::Eval::NNUE\n\n\ntemplate<Stockfish::Eval::NNUE::IndexType TransformedFeatureDimensions>\nstruct std::hash<Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>> {\n    std::size_t\n    operator()(const Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>& ft)\n      const noexcept {\n        return ft.get_content_hash();\n    }\n};\n\n#endif  // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/nnue_misc.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Code for calculating NNUE evaluation function\n\n#include \"nnue_misc.h\"\n\n#include <cmath>\n#include <cstdlib>\n#include <cstring>\n#include <iomanip>\n#include <iosfwd>\n#include <iostream>\n#include <sstream>\n#include <string_view>\n#include <tuple>\n\n#include \"../position.h\"\n#include \"../types.h\"\n#include \"../uci.h\"\n#include \"network.h\"\n#include \"nnue_accumulator.h\"\n\nnamespace Stockfish::Eval::NNUE {\n\n\nconstexpr std::string_view PieceToChar(\" PNBRQK  pnbrqk\");\n\n\nnamespace {\n// Converts a Value into (centi)pawns and writes it in a buffer.\n// The buffer must have capacity for at least 5 chars.\nvoid format_cp_compact(Value v, char* buffer, const Position& pos) {\n\n    buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');\n\n    int cp = std::abs(UCIEngine::to_cp(v, pos));\n    if (cp >= 10000)\n    {\n        buffer[1] = '0' + cp / 10000;\n        cp %= 10000;\n        buffer[2] = '0' + cp / 1000;\n        cp %= 1000;\n        buffer[3] = '0' + cp / 100;\n        buffer[4] = ' ';\n    }\n    else if (cp >= 1000)\n    {\n        buffer[1] = '0' + cp / 1000;\n        cp %= 1000;\n        buffer[2] = '0' + cp / 100;\n        cp %= 100;\n        buffer[3] = '.';\n        buffer[4] = '0' + cp / 10;\n    }\n    else\n    {\n        buffer[1] = '0' + cp / 100;\n        cp %= 100;\n        buffer[2] = '.';\n        buffer[3] = '0' + cp / 10;\n        cp %= 10;\n        buffer[4] = '0' + cp / 1;\n    }\n}\n\n\n// Converts a Value into pawns, always keeping two decimals\nvoid format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) {\n\n    const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos));\n\n    stream << (v < 0   ? '-'\n               : v > 0 ? '+'\n                       : ' ')\n           << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns;\n}\n}\n\n\n// Returns a string with the value of each piece on a board,\n// and a table for (PSQT, Layers) values bucket by bucket.\nstd::string\ntrace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) {\n\n    std::stringstream ss;\n\n    char board[3 * 8 + 1][8 * 8 + 2];\n    std::memset(board, ' ', sizeof(board));\n    for (int row = 0; row < 3 * 8 + 1; ++row)\n        board[row][8 * 8 + 1] = '\\0';\n\n    // A lambda to output one box of the board\n    auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) {\n        const int x = int(file) * 8;\n        const int y = (7 - int(rank)) * 3;\n        for (int i = 1; i < 8; ++i)\n            board[y][x + i] = board[y + 3][x + i] = '-';\n        for (int i = 1; i < 3; ++i)\n            board[y + i][x] = board[y + i][x + 8] = '|';\n        board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+';\n        if (pc != NO_PIECE)\n            board[y + 1][x + 4] = PieceToChar[pc];\n        if (is_valid(value))\n            format_cp_compact(value, &board[y + 2][x + 2], pos);\n    };\n\n    auto accumulators = std::make_unique<AccumulatorStack>();\n\n    // We estimate the value of each piece by doing a differential evaluation from\n    // the current base eval, simulating the removal of the piece from its square.\n    auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big);\n    Value base              = psqt + positional;\n    base                    = pos.side_to_move() == WHITE ? base : -base;\n\n    for (File f = FILE_A; f <= FILE_H; ++f)\n        for (Rank r = RANK_1; r <= RANK_8; ++r)\n        {\n            Square sq = make_square(f, r);\n            Piece  pc = pos.piece_on(sq);\n            Value  v  = VALUE_NONE;\n\n            if (pc != NO_PIECE && type_of(pc) != KING)\n            {\n                pos.remove_piece(sq);\n\n                accumulators->reset();\n                std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big);\n                Value eval                 = psqt + positional;\n                eval                       = pos.side_to_move() == WHITE ? eval : -eval;\n                v                          = base - eval;\n\n                pos.put_piece(pc, sq);\n            }\n\n            writeSquare(f, r, pc, v);\n        }\n\n    ss << \" NNUE derived piece values:\\n\";\n    for (int row = 0; row < 3 * 8 + 1; ++row)\n        ss << board[row] << '\\n';\n    ss << '\\n';\n\n    accumulators->reset();\n    auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big);\n\n    ss << \" NNUE network contributions \"\n       << (pos.side_to_move() == WHITE ? \"(White to move)\" : \"(Black to move)\") << std::endl\n       << \"+------------+------------+------------+------------+\\n\"\n       << \"|   Bucket   |  Material  | Positional |   Total    |\\n\"\n       << \"|            |   (PSQT)   |  (Layers)  |            |\\n\"\n       << \"+------------+------------+------------+------------+\\n\";\n\n    for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)\n    {\n        ss << \"|  \" << bucket << \"        \"  //\n           << \" |  \";\n        format_cp_aligned_dot(t.psqt[bucket], ss, pos);\n        ss << \"  \"  //\n           << \" |  \";\n        format_cp_aligned_dot(t.positional[bucket], ss, pos);\n        ss << \"  \"  //\n           << \" |  \";\n        format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos);\n        ss << \"  \"  //\n           << \" |\";\n        if (bucket == t.correctBucket)\n            ss << \" <-- this bucket is used\";\n        ss << '\\n';\n    }\n\n    ss << \"+------------+------------+------------+------------+\\n\";\n\n    return ss.str();\n}\n\n\n}  // namespace Stockfish::Eval::NNUE\n"
  },
  {
    "path": "src/nnue/nnue_misc.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef NNUE_MISC_H_INCLUDED\n#define NNUE_MISC_H_INCLUDED\n\n#include <cstddef>\n#include <memory>\n#include <string>\n\n#include \"../misc.h\"\n#include \"../types.h\"\n#include \"nnue_architecture.h\"\n\nnamespace Stockfish {\n\nclass Position;\n\nnamespace Eval::NNUE {\n\n// EvalFile uses fixed string types because it's part of the network structure which must be trivial.\nstruct EvalFile {\n    // Default net name, will use one of the EvalFileDefaultName* macros defined\n    // in evaluate.h\n    FixedString<256> defaultName;\n    // Selected net name, either via uci option or default\n    FixedString<256> current;\n    // Net description extracted from the net file\n    FixedString<256> netDescription;\n};\n\nstruct NnueEvalTrace {\n    static_assert(LayerStacks == PSQTBuckets);\n\n    Value       psqt[LayerStacks];\n    Value       positional[LayerStacks];\n    std::size_t correctBucket;\n};\n\nstruct Networks;\nstruct AccumulatorCaches;\n\nstd::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches);\n\n}  // namespace Stockfish::Eval::NNUE\n}  // namespace Stockfish\n\ntemplate<>\nstruct std::hash<Stockfish::Eval::NNUE::EvalFile> {\n    std::size_t operator()(const Stockfish::Eval::NNUE::EvalFile& evalFile) const noexcept {\n        std::size_t h = 0;\n        Stockfish::hash_combine(h, evalFile.defaultName);\n        Stockfish::hash_combine(h, evalFile.current);\n        Stockfish::hash_combine(h, evalFile.netDescription);\n        return h;\n    }\n};\n\n#endif  // #ifndef NNUE_MISC_H_INCLUDED\n"
  },
  {
    "path": "src/nnue/simd.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef NNUE_SIMD_H_INCLUDED\n#define NNUE_SIMD_H_INCLUDED\n\n#if defined(USE_AVX2)\n    #include <immintrin.h>\n\n#elif defined(USE_SSE41)\n    #include <smmintrin.h>\n\n#elif defined(USE_SSSE3)\n    #include <tmmintrin.h>\n\n#elif defined(USE_SSE2)\n    #include <emmintrin.h>\n\n#elif defined(USE_NEON)\n    #include <arm_neon.h>\n#endif\n\n#include \"../types.h\"\n#include \"nnue_common.h\"\n\nnamespace Stockfish::Eval::NNUE::SIMD {\n\n// If vector instructions are enabled, we update and refresh the\n// accumulator tile by tile such that each tile fits in the CPU's\n// vector registers.\n#define VECTOR\n\n#ifdef USE_AVX512\nusing vec_t      = __m512i;\nusing vec_i8_t   = __m256i;\nusing vec128_t   = __m128i;\nusing psqt_vec_t = __m256i;\nusing vec_uint_t = __m512i;\n    #define vec_load(a) _mm512_load_si512(a)\n    #define vec_store(a, b) _mm512_store_si512(a, b)\n    #define vec_convert_8_16(a) _mm512_cvtepi8_epi16(a)\n    #define vec_add_16(a, b) _mm512_add_epi16(a, b)\n    #define vec_sub_16(a, b) _mm512_sub_epi16(a, b)\n    #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b)\n    #define vec_zero() _mm512_setzero_epi32()\n    #define vec_set_16(a) _mm512_set1_epi16(a)\n    #define vec_max_16(a, b) _mm512_max_epi16(a, b)\n    #define vec_min_16(a, b) _mm512_min_epi16(a, b)\n    #define vec_slli_16(a, b) _mm512_slli_epi16(a, b)\n    // Inverse permuted at load time\n    #define vec_packus_16(a, b) _mm512_packus_epi16(a, b)\n    #define vec_load_psqt(a) _mm256_load_si256(a)\n    #define vec_store_psqt(a, b) _mm256_store_si256(a, b)\n    #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)\n    #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)\n    #define vec_zero_psqt() _mm256_setzero_si256()\n\n    #ifdef USE_SSSE3\n        #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())\n    #endif\n\n    #define vec128_zero _mm_setzero_si128()\n    #define vec128_set_16(a) _mm_set1_epi16(a)\n    #define vec128_load(a) _mm_load_si128(a)\n    #define vec128_storeu(a, b) _mm_storeu_si128(a, b)\n    #define vec128_add(a, b) _mm_add_epi16(a, b)\n    #define NumRegistersSIMD 16\n    #define MaxChunkSize 64\n\n#elif USE_AVX2\nusing vec_t      = __m256i;\nusing vec_i8_t   = __m128i;\nusing vec128_t   = __m128i;\nusing psqt_vec_t = __m256i;\nusing vec_uint_t = __m256i;\n    #define vec_load(a) _mm256_load_si256(a)\n    #define vec_store(a, b) _mm256_store_si256(a, b)\n    #define vec_convert_8_16(a) _mm256_cvtepi8_epi16(a)\n    #define vec_add_16(a, b) _mm256_add_epi16(a, b)\n    #define vec_sub_16(a, b) _mm256_sub_epi16(a, b)\n    #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b)\n    #define vec_zero() _mm256_setzero_si256()\n    #define vec_set_16(a) _mm256_set1_epi16(a)\n    #define vec_max_16(a, b) _mm256_max_epi16(a, b)\n    #define vec_min_16(a, b) _mm256_min_epi16(a, b)\n    #define vec_slli_16(a, b) _mm256_slli_epi16(a, b)\n    // Inverse permuted at load time\n    #define vec_packus_16(a, b) _mm256_packus_epi16(a, b)\n    #define vec_load_psqt(a) _mm256_load_si256(a)\n    #define vec_store_psqt(a, b) _mm256_store_si256(a, b)\n    #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)\n    #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)\n    #define vec_zero_psqt() _mm256_setzero_si256()\n\n    #ifdef USE_SSSE3\n        #if defined(USE_VNNI) && !defined(USE_AVXVNNI)\n            #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())\n        #else\n            #define vec_nnz(a) \\\n                _mm256_movemask_ps( \\\n                  _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))\n        #endif\n    #endif\n\n    #define vec128_zero _mm_setzero_si128()\n    #define vec128_set_16(a) _mm_set1_epi16(a)\n    #define vec128_load(a) _mm_load_si128(a)\n    #define vec128_storeu(a, b) _mm_storeu_si128(a, b)\n    #define vec128_add(a, b) _mm_add_epi16(a, b)\n\n    #define NumRegistersSIMD 12\n    #define MaxChunkSize 32\n\n#elif USE_SSE2\nusing vec_t      = __m128i;\nusing vec_i8_t   = std::uint64_t;  // for the correct size -- will be loaded into an xmm reg\nusing vec128_t   = __m128i;\nusing psqt_vec_t = __m128i;\nusing vec_uint_t = __m128i;\n    #define vec_load(a) (*(a))\n    #define vec_store(a, b) *(a) = (b)\n    #define vec_add_16(a, b) _mm_add_epi16(a, b)\n    #define vec_sub_16(a, b) _mm_sub_epi16(a, b)\n    #define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b)\n    #define vec_zero() _mm_setzero_si128()\n    #define vec_set_16(a) _mm_set1_epi16(a)\n    #define vec_max_16(a, b) _mm_max_epi16(a, b)\n    #define vec_min_16(a, b) _mm_min_epi16(a, b)\n    #define vec_slli_16(a, b) _mm_slli_epi16(a, b)\n    #define vec_packus_16(a, b) _mm_packus_epi16(a, b)\n    #define vec_load_psqt(a) (*(a))\n    #define vec_store_psqt(a, b) *(a) = (b)\n    #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b)\n    #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b)\n    #define vec_zero_psqt() _mm_setzero_si128()\n\n    #ifdef USE_SSSE3\n        #define vec_nnz(a) \\\n            _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))\n    #endif\n\n    #ifdef __i386__\ninline __m128i _mm_cvtsi64_si128(int64_t val) {\n    return _mm_loadl_epi64(reinterpret_cast<const __m128i*>(&val));\n}\n    #endif\n\n    #ifdef USE_SSE41\n        #define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast<int64_t>(a)))\n    #else\n// Credit: Yoshie2000\ninline __m128i vec_convert_8_16(uint64_t x) {\n    __m128i v8   = _mm_cvtsi64_si128(static_cast<int64_t>(x));\n    __m128i sign = _mm_cmpgt_epi8(_mm_setzero_si128(), v8);\n    return _mm_unpacklo_epi8(v8, sign);\n}\n    #endif\n\n    #define vec128_zero _mm_setzero_si128()\n    #define vec128_set_16(a) _mm_set1_epi16(a)\n    #define vec128_load(a) _mm_load_si128(a)\n    #define vec128_storeu(a, b) _mm_storeu_si128(a, b)\n    #define vec128_add(a, b) _mm_add_epi16(a, b)\n\n    #define NumRegistersSIMD (Is64Bit ? 12 : 6)\n    #define MaxChunkSize 16\n\n#elif USE_NEON\nusing vec_i8x8_t __attribute__((may_alias))  = int8x8_t;\nusing vec_i16x8_t __attribute__((may_alias)) = int16x8_t;\nusing vec_i8x16_t __attribute__((may_alias)) = int8x16_t;\nusing vec_u16x8_t __attribute__((may_alias)) = uint16x8_t;\nusing vec_i32x4_t __attribute__((may_alias)) = int32x4_t;\n\nusing vec_t __attribute__((may_alias))      = int16x8_t;\nusing vec_i8_t __attribute__((may_alias))   = int8x16_t;\nusing psqt_vec_t __attribute__((may_alias)) = int32x4_t;\nusing vec128_t __attribute__((may_alias))   = uint16x8_t;\nusing vec_uint_t __attribute__((may_alias)) = uint32x4_t;\n    #define vec_load(a) (*(a))\n    #define vec_store(a, b) *(a) = (b)\n    #define vec_add_16(a, b) vaddq_s16(a, b)\n    #define vec_sub_16(a, b) vsubq_s16(a, b)\n    #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b)\n    #define vec_zero() vec_t{0}\n    #define vec_set_16(a) vdupq_n_s16(a)\n    #define vec_max_16(a, b) vmaxq_s16(a, b)\n    #define vec_min_16(a, b) vminq_s16(a, b)\n    #define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b))\n    #define vec_packus_16(a, b) reinterpret_cast<vec_t>(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b)))\n    #define vec_load_psqt(a) (*(a))\n    #define vec_store_psqt(a, b) *(a) = (b)\n    #define vec_add_psqt_32(a, b) vaddq_s32(a, b)\n    #define vec_sub_psqt_32(a, b) vsubq_s32(a, b)\n    #define vec_zero_psqt() psqt_vec_t{0}\n\nstatic constexpr std::uint32_t Mask[4] = {1, 2, 4, 8};\n    #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))\n    #define vec128_zero vdupq_n_u16(0)\n    #define vec128_set_16(a) vdupq_n_u16(a)\n    #define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))\n    #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)\n    #define vec128_add(a, b) vaddq_u16(a, b)\n\n    #define NumRegistersSIMD 16\n    #define MaxChunkSize 16\n\n    #ifndef __aarch64__\n// Single instruction doesn't exist on 32-bit ARM\ninline int16x8_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); }\n    #endif\n\n#else\n    #undef VECTOR\n\n#endif\n\nstruct Vec16Wrapper {\n#ifdef VECTOR\n    using type = vec_t;\n    static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); }\n    static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); }\n#else\n    using type = BiasType;\n    static type add(const type& lhs, const type& rhs) { return lhs + rhs; }\n    static type sub(const type& lhs, const type& rhs) { return lhs - rhs; }\n#endif\n};\n\nstruct Vec32Wrapper {\n#ifdef VECTOR\n    using type = psqt_vec_t;\n    static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); }\n    static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); }\n#else\n    using type = PSQTWeightType;\n    static type add(const type& lhs, const type& rhs) { return lhs + rhs; }\n    static type sub(const type& lhs, const type& rhs) { return lhs - rhs; }\n#endif\n};\n\nenum UpdateOperation {\n    Add,\n    Sub\n};\n\ntemplate<typename VecWrapper,\n         UpdateOperation... ops,\n         std::enable_if_t<sizeof...(ops) == 0, bool> = true>\ntypename VecWrapper::type fused(const typename VecWrapper::type& in) {\n    return in;\n}\n\ntemplate<typename VecWrapper,\n         UpdateOperation update_op,\n         UpdateOperation... ops,\n         typename T,\n         typename... Ts,\n         std::enable_if_t<is_all_same_v<typename VecWrapper::type, T, Ts...>, bool> = true,\n         std::enable_if_t<sizeof...(ops) == sizeof...(Ts), bool>                    = true>\ntypename VecWrapper::type\nfused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) {\n    switch (update_op)\n    {\n    case Add :\n        return fused<VecWrapper, ops...>(VecWrapper::add(in, operand), operands...);\n    case Sub :\n        return fused<VecWrapper, ops...>(VecWrapper::sub(in, operand), operands...);\n    default :\n        static_assert(update_op == Add || update_op == Sub,\n                      \"Only Add and Sub are currently supported.\");\n        return typename VecWrapper::type();\n    }\n}\n\n#if defined(USE_AVX512)\n\n[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {\n    return _mm512_reduce_add_epi32(sum) + bias;\n}\n\n[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) {\n\n    #if defined(USE_VNNI)\n    acc = _mm512_dpbusd_epi32(acc, a, b);\n    #else\n    __m512i product0 = _mm512_maddubs_epi16(a, b);\n    product0         = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));\n    acc              = _mm512_add_epi32(acc, product0);\n    #endif\n}\n\n#endif\n\n#if defined(USE_AVX2)\n\n[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {\n    __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));\n    sum128         = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));\n    sum128         = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));\n    return _mm_cvtsi128_si32(sum128) + bias;\n}\n\n[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {\n\n    #if defined(USE_VNNI)\n    acc = _mm256_dpbusd_epi32(acc, a, b);\n    #else\n    __m256i product0 = _mm256_maddubs_epi16(a, b);\n    product0         = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));\n    acc              = _mm256_add_epi32(acc, product0);\n    #endif\n}\n\n#endif\n\n#if defined(USE_SSSE3)\n\n[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {\n    sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E));  //_MM_PERM_BADC\n    sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1));  //_MM_PERM_CDAB\n    return _mm_cvtsi128_si32(sum) + bias;\n}\n\n[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {\n\n    __m128i product0 = _mm_maddubs_epi16(a, b);\n    product0         = _mm_madd_epi16(product0, _mm_set1_epi16(1));\n    acc              = _mm_add_epi32(acc, product0);\n}\n\n#endif\n\n#if defined(USE_NEON_DOTPROD)\n\n[[maybe_unused]] static void\ndotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {\n\n    acc = vdotq_s32(acc, a, b);\n}\n#endif\n\n#if defined(USE_NEON)\n\n[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {\n    #if USE_NEON >= 8\n    return vaddvq_s32(s);\n    #else\n    return s[0] + s[1] + s[2] + s[3];\n    #endif\n}\n\n[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {\n    return neon_m128_reduce_add_epi32(sum) + bias;\n}\n\n#endif\n\n#if USE_NEON >= 8\n[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {\n\n    int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b));\n    int16x8_t product1 = vmull_high_s8(a, b);\n    int16x8_t sum      = vpaddq_s16(product0, product1);\n    acc                = vpadalq_s16(acc, sum);\n}\n#endif\n\n\n// Compute optimal SIMD register count for feature transformer accumulation.\ntemplate<IndexType TransformedFeatureWidth, IndexType HalfDimensions, IndexType PSQTBuckets>\nclass SIMDTiling {\n#ifdef VECTOR\n        // We use __m* types as template arguments, which causes GCC to emit warnings\n        // about losing some attribute information. This is irrelevant to us as we\n        // only take their size, so the following pragma are harmless.\n    #if defined(__GNUC__)\n        #pragma GCC diagnostic push\n        #pragma GCC diagnostic ignored \"-Wignored-attributes\"\n    #endif\n\n    template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>\n    static constexpr int BestRegisterCount() {\n        constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType);\n        constexpr std::size_t LaneSize     = sizeof(LaneType);\n\n        static_assert(RegisterSize >= LaneSize);\n        static_assert(MaxRegisters <= NumRegistersSIMD);\n        static_assert(MaxRegisters > 0);\n        static_assert(NumRegistersSIMD > 0);\n        static_assert(RegisterSize % LaneSize == 0);\n        static_assert((NumLanes * LaneSize) % RegisterSize == 0);\n\n        const int ideal = (NumLanes * LaneSize) / RegisterSize;\n        if (ideal <= MaxRegisters)\n            return ideal;\n\n        // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters\n        for (int divisor = MaxRegisters; divisor > 1; --divisor)\n            if (ideal % divisor == 0)\n                return divisor;\n\n        return 1;\n    }\n\n    #if defined(__GNUC__)\n        #pragma GCC diagnostic pop\n    #endif\n\n   public:\n    static constexpr int NumRegs =\n      BestRegisterCount<vec_t, WeightType, TransformedFeatureWidth, NumRegistersSIMD>();\n    static constexpr int NumPsqtRegs =\n      BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();\n\n    static constexpr IndexType TileHeight     = NumRegs * sizeof(vec_t) / 2;\n    static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;\n\n    static_assert(HalfDimensions % TileHeight == 0, \"TileHeight must divide HalfDimensions\");\n    static_assert(PSQTBuckets % PsqtTileHeight == 0, \"PsqtTileHeight must divide PSQTBuckets\");\n#endif\n};\n}\n\n#endif\n"
  },
  {
    "path": "src/numa.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef NUMA_H_INCLUDED\n#define NUMA_H_INCLUDED\n\n#include <algorithm>\n#include <atomic>\n#include <cstdint>\n#include <cstdlib>\n#include <functional>\n#include <iostream>\n#include <limits>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <set>\n#include <sstream>\n#include <string>\n#include <thread>\n#include <utility>\n#include <vector>\n#include <cstring>\n\n#include \"shm.h\"\n\n// We support linux very well, but we explicitly do NOT support Android,\n// because there is no affected systems, not worth maintaining.\n#if defined(__linux__) && !defined(__ANDROID__)\n    #if !defined(_GNU_SOURCE)\n        #define _GNU_SOURCE\n    #endif\n    #include <sched.h>\n#elif defined(_WIN64)\n\n    #if _WIN32_WINNT < 0x0601\n        #undef _WIN32_WINNT\n        #define _WIN32_WINNT 0x0601  // Force to include needed API prototypes\n    #endif\n\n// On Windows each processor group can have up to 64 processors.\n// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups\nstatic constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64;\n\n    #if !defined(NOMINMAX)\n        #define NOMINMAX\n    #endif\n    #include <windows.h>\n    #if defined small\n        #undef small\n    #endif\n\n// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadselectedcpusetmasks\nusing SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT);\n\n// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadselectedcpusetmasks\nusing GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT, PUSHORT);\n\n#endif\n\n#include \"misc.h\"\n\nnamespace Stockfish {\n\nusing CpuIndex  = size_t;\nusing NumaIndex = size_t;\n\ninline CpuIndex get_hardware_concurrency() {\n    CpuIndex concurrency = std::thread::hardware_concurrency();\n\n    // Get all processors across all processor groups on windows, since\n    // hardware_concurrency() only returns the number of processors in\n    // the first group, because only these are available to std::thread.\n#ifdef _WIN64\n    concurrency = std::max<CpuIndex>(concurrency, GetActiveProcessorCount(ALL_PROCESSOR_GROUPS));\n#endif\n\n    return concurrency;\n}\n\ninline const CpuIndex SYSTEM_THREADS_NB = std::max<CpuIndex>(1, get_hardware_concurrency());\n\n#if defined(_WIN64)\n\nstruct WindowsAffinity {\n    std::optional<std::set<CpuIndex>> oldApi;\n    std::optional<std::set<CpuIndex>> newApi;\n\n    // We also provide diagnostic for when the affinity is set to nullopt\n    // whether it was due to being indeterminate. If affinity is indeterminate\n    // it is best to assume it is not set at all, so consistent with the meaning\n    // of the nullopt affinity.\n    bool isNewDeterminate = true;\n    bool isOldDeterminate = true;\n\n    std::optional<std::set<CpuIndex>> get_combined() const {\n        if (!oldApi.has_value())\n            return newApi;\n        if (!newApi.has_value())\n            return oldApi;\n\n        std::set<CpuIndex> intersect;\n        std::set_intersection(oldApi->begin(), oldApi->end(), newApi->begin(), newApi->end(),\n                              std::inserter(intersect, intersect.begin()));\n        return intersect;\n    }\n\n    // Since Windows 11 and Windows Server 2022 thread affinities can span\n    // processor groups and can be set as such by a new WinAPI function. However,\n    // we may need to force using the old API if we detect that the process has\n    // affinity set by the old API already and we want to override that. Due to the\n    // limitations of the old API we cannot detect its use reliably. There will be\n    // cases where we detect not use but it has actually been used and vice versa.\n\n    bool likely_used_old_api() const { return oldApi.has_value() || !isOldDeterminate; }\n};\n\ninline std::pair<BOOL, std::vector<USHORT>> get_process_group_affinity() {\n\n    // GetProcessGroupAffinity requires the GroupArray argument to be\n    // aligned to 4 bytes instead of just 2.\n    static constexpr size_t GroupArrayMinimumAlignment = 4;\n    static_assert(GroupArrayMinimumAlignment >= alignof(USHORT));\n\n    // The function should succeed the second time, but it may fail if the group\n    // affinity has changed between GetProcessGroupAffinity calls. In such case\n    // we consider this a hard error, as we Cannot work with unstable affinities\n    // anyway.\n    static constexpr int MAX_TRIES  = 2;\n    USHORT               GroupCount = 1;\n    for (int i = 0; i < MAX_TRIES; ++i)\n    {\n        auto GroupArray = std::make_unique<USHORT[]>(\n          GroupCount + (GroupArrayMinimumAlignment / alignof(USHORT) - 1));\n\n        USHORT* GroupArrayAligned = align_ptr_up<GroupArrayMinimumAlignment>(GroupArray.get());\n\n        const BOOL status =\n          GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArrayAligned);\n\n        if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n        {\n            break;\n        }\n\n        if (status != 0)\n        {\n            return std::make_pair(status,\n                                  std::vector(GroupArrayAligned, GroupArrayAligned + GroupCount));\n        }\n    }\n\n    return std::make_pair(0, std::vector<USHORT>());\n}\n\n// On Windows there are two ways to set affinity, and therefore 2 ways to get it.\n// These are not consistent, so we have to check both. In some cases it is actually\n// not possible to determine affinity. For example when two different threads have\n// affinity on different processor groups, set using SetThreadAffinityMask, we cannot\n// retrieve the actual affinities.\n// From documentation on GetProcessAffinityMask:\n//     > If the calling process contains threads in multiple groups,\n//     > the function returns zero for both affinity masks.\n// In such cases we just give up and assume we have affinity for all processors.\n// nullopt means no affinity is set, that is, all processors are allowed\ninline WindowsAffinity get_process_affinity() {\n    HMODULE k32                            = GetModuleHandle(TEXT(\"Kernel32.dll\"));\n    auto    GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t(\n      (void (*)()) GetProcAddress(k32, \"GetThreadSelectedCpuSetMasks\"));\n\n    BOOL status = 0;\n\n    WindowsAffinity affinity;\n\n    if (GetThreadSelectedCpuSetMasks_f != nullptr)\n    {\n        USHORT RequiredMaskCount;\n        status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount);\n\n        // We expect ERROR_INSUFFICIENT_BUFFER from GetThreadSelectedCpuSetMasks,\n        // but other failure is an actual error.\n        if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n        {\n            affinity.isNewDeterminate = false;\n        }\n        else if (RequiredMaskCount > 0)\n        {\n            // If RequiredMaskCount then these affinities were never set, but it's\n            // not consistent so GetProcessAffinityMask may still return some affinity.\n            auto groupAffinities = std::make_unique<GROUP_AFFINITY[]>(RequiredMaskCount);\n\n            status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(),\n                                                    RequiredMaskCount, &RequiredMaskCount);\n\n            if (status == 0)\n            {\n                affinity.isNewDeterminate = false;\n            }\n            else\n            {\n                std::set<CpuIndex> cpus;\n\n                for (USHORT i = 0; i < RequiredMaskCount; ++i)\n                {\n                    const size_t procGroupIndex = groupAffinities[i].Group;\n\n                    for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)\n                    {\n                        if (groupAffinities[i].Mask & (KAFFINITY(1) << j))\n                            cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);\n                    }\n                }\n\n                affinity.newApi = std::move(cpus);\n            }\n        }\n    }\n\n    // NOTE: There is no way to determine full affinity using the old API if\n    //       individual threads set affinity on different processor groups.\n\n    DWORD_PTR proc, sys;\n    status = GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys);\n\n    // If proc == 0 then we cannot determine affinity because it spans processor groups.\n    // On Windows 11 and Server 2022 it will instead\n    //     > If, however, hHandle specifies a handle to the current process, the function\n    //     > always uses the calling thread's primary group (which by default is the same\n    //     > as the process' primary group) in order to set the\n    //     > lpProcessAffinityMask and lpSystemAffinityMask.\n    // So it will never be indeterminate here. We can only make assumptions later.\n    if (status == 0 || proc == 0)\n    {\n        affinity.isOldDeterminate = false;\n        return affinity;\n    }\n\n    // If SetProcessAffinityMask was never called the affinity must span\n    // all processor groups, but if it was called it must only span one.\n\n    std::vector<USHORT> groupAffinity;  // We need to capture this later and capturing\n                                        // from structured bindings requires c++20.\n\n    std::tie(status, groupAffinity) = get_process_group_affinity();\n    if (status == 0)\n    {\n        affinity.isOldDeterminate = false;\n        return affinity;\n    }\n\n    if (groupAffinity.size() == 1)\n    {\n        // We detect the case when affinity is set to all processors and correctly\n        // leave affinity.oldApi as nullopt.\n        if (GetActiveProcessorGroupCount() != 1 || proc != sys)\n        {\n            std::set<CpuIndex> cpus;\n\n            const size_t procGroupIndex = groupAffinity[0];\n\n            const uint64_t mask = static_cast<uint64_t>(proc);\n            for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)\n            {\n                if (mask & (KAFFINITY(1) << j))\n                    cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);\n            }\n\n            affinity.oldApi = std::move(cpus);\n        }\n    }\n    else\n    {\n        // If we got here it means that either SetProcessAffinityMask was never set\n        // or we're on Windows 11/Server 2022.\n\n        // Since Windows 11 and Windows Server 2022 the behaviour of\n        // GetProcessAffinityMask changed:\n        //     > If, however, hHandle specifies a handle to the current process,\n        //     > the function always uses the calling thread's primary group\n        //     > (which by default is the same as the process' primary group)\n        //     > in order to set the lpProcessAffinityMask and lpSystemAffinityMask.\n        // In which case we can actually retrieve the full affinity.\n\n        if (GetThreadSelectedCpuSetMasks_f != nullptr)\n        {\n            std::thread th([&]() {\n                std::set<CpuIndex> cpus;\n                bool               isAffinityFull = true;\n\n                for (auto procGroupIndex : groupAffinity)\n                {\n                    const int numActiveProcessors =\n                      GetActiveProcessorCount(static_cast<WORD>(procGroupIndex));\n\n                    // We have to schedule to two different processors\n                    // and & the affinities we get. Otherwise our processor\n                    // choice could influence the resulting affinity.\n                    // We assume the processor IDs within the group are\n                    // filled sequentially from 0.\n                    uint64_t procCombined = std::numeric_limits<uint64_t>::max();\n                    uint64_t sysCombined  = std::numeric_limits<uint64_t>::max();\n\n                    for (int i = 0; i < std::min(numActiveProcessors, 2); ++i)\n                    {\n                        GROUP_AFFINITY GroupAffinity;\n                        std::memset(&GroupAffinity, 0, sizeof(GROUP_AFFINITY));\n                        GroupAffinity.Group = static_cast<WORD>(procGroupIndex);\n\n                        GroupAffinity.Mask = static_cast<KAFFINITY>(1) << i;\n\n                        status =\n                          SetThreadGroupAffinity(GetCurrentThread(), &GroupAffinity, nullptr);\n                        if (status == 0)\n                        {\n                            affinity.isOldDeterminate = false;\n                            return;\n                        }\n\n                        SwitchToThread();\n\n                        DWORD_PTR proc2, sys2;\n                        status = GetProcessAffinityMask(GetCurrentProcess(), &proc2, &sys2);\n                        if (status == 0)\n                        {\n                            affinity.isOldDeterminate = false;\n                            return;\n                        }\n\n                        procCombined &= static_cast<uint64_t>(proc2);\n                        sysCombined &= static_cast<uint64_t>(sys2);\n                    }\n\n                    if (procCombined != sysCombined)\n                        isAffinityFull = false;\n\n                    for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)\n                    {\n                        if (procCombined & (KAFFINITY(1) << j))\n                            cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);\n                    }\n                }\n\n                // We have to detect the case where the affinity was not set,\n                // or is set to all processors so that we correctly produce as\n                // std::nullopt result.\n                if (!isAffinityFull)\n                {\n                    affinity.oldApi = std::move(cpus);\n                }\n            });\n\n            th.join();\n        }\n    }\n\n    return affinity;\n}\n\n// Type machinery used to emulate Cache->GroupCount\n\ntemplate<typename T, typename = void>\nstruct HasGroupCount: std::false_type {};\n\ntemplate<typename T>\nstruct HasGroupCount<T, std::void_t<decltype(std::declval<T>().Cache.GroupCount)>>: std::true_type {\n};\n\ntemplate<typename T, typename Pred, std::enable_if_t<HasGroupCount<T>::value, bool> = true>\nstd::set<CpuIndex> readCacheMembers(const T* info, Pred&& is_cpu_allowed) {\n    std::set<CpuIndex> cpus;\n    // On Windows 10 this will read a 0 because GroupCount doesn't exist\n    int groupCount = std::max(info->Cache.GroupCount, WORD(1));\n    for (WORD procGroup = 0; procGroup < groupCount; ++procGroup)\n    {\n        for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)\n        {\n            WORD           groupNumber = info->Cache.GroupMasks[procGroup].Group;\n            const CpuIndex c = static_cast<CpuIndex>(groupNumber) * WIN_PROCESSOR_GROUP_SIZE\n                             + static_cast<CpuIndex>(number);\n            if (!(info->Cache.GroupMasks[procGroup].Mask & (1ULL << number)) || !is_cpu_allowed(c))\n                continue;\n            cpus.insert(c);\n        }\n    }\n    return cpus;\n}\n\ntemplate<typename T, typename Pred, std::enable_if_t<!HasGroupCount<T>::value, bool> = true>\nstd::set<CpuIndex> readCacheMembers(const T* info, Pred&& is_cpu_allowed) {\n    std::set<CpuIndex> cpus;\n    for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)\n    {\n        WORD           groupNumber = info->Cache.GroupMask.Group;\n        const CpuIndex c           = static_cast<CpuIndex>(groupNumber) * WIN_PROCESSOR_GROUP_SIZE\n                         + static_cast<CpuIndex>(number);\n        if (!(info->Cache.GroupMask.Mask & (1ULL << number)) || !is_cpu_allowed(c))\n            continue;\n        cpus.insert(c);\n    }\n    return cpus;\n}\n\n#endif\n\n#if defined(__linux__) && !defined(__ANDROID__)\n\ninline std::set<CpuIndex> get_process_affinity() {\n\n    std::set<CpuIndex> cpus;\n\n    // For unsupported systems, or in case of a soft error, we may assume\n    // all processors are available for use.\n    [[maybe_unused]] auto set_to_all_cpus = [&]() {\n        for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)\n            cpus.insert(c);\n    };\n\n    // cpu_set_t by default holds 1024 entries. This may not be enough soon,\n    // but there is no easy way to determine how many threads there actually\n    // is. In this case we just choose a reasonable upper bound.\n    static constexpr CpuIndex MaxNumCpus = 1024 * 64;\n\n    cpu_set_t* mask = CPU_ALLOC(MaxNumCpus);\n    if (mask == nullptr)\n        std::exit(EXIT_FAILURE);\n\n    const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus);\n\n    CPU_ZERO_S(masksize, mask);\n\n    const int status = sched_getaffinity(0, masksize, mask);\n\n    if (status != 0)\n    {\n        CPU_FREE(mask);\n        std::exit(EXIT_FAILURE);\n    }\n\n    for (CpuIndex c = 0; c < MaxNumCpus; ++c)\n        if (CPU_ISSET_S(c, masksize, mask))\n            cpus.insert(c);\n\n    CPU_FREE(mask);\n\n    return cpus;\n}\n\n#endif\n\n#if defined(__linux__) && !defined(__ANDROID__)\n\ninline static const auto STARTUP_PROCESSOR_AFFINITY = get_process_affinity();\n\n#elif defined(_WIN64)\n\ninline static const auto STARTUP_PROCESSOR_AFFINITY = get_process_affinity();\ninline static const auto STARTUP_USE_OLD_AFFINITY_API =\n  STARTUP_PROCESSOR_AFFINITY.likely_used_old_api();\n\n#endif\n\n// We want to abstract the purpose of storing the numa node index somewhat.\n// Whoever is using this does not need to know the specifics of the replication\n// machinery to be able to access NUMA replicated memory.\nclass NumaReplicatedAccessToken {\n   public:\n    NumaReplicatedAccessToken() :\n        n(0) {}\n\n    explicit NumaReplicatedAccessToken(NumaIndex idx) :\n        n(idx) {}\n\n    NumaIndex get_numa_index() const { return n; }\n\n   private:\n    NumaIndex n;\n};\n\nstruct L3Domain {\n    NumaIndex          systemNumaIndex{};\n    std::set<CpuIndex> cpus{};\n};\n\n// Use system NUMA nodes\nstruct SystemNumaPolicy {};\n// Use system-reported L3 domains\nstruct L3DomainsPolicy {};\n// Group system-reported L3 domains until they reach bundleSize\nstruct BundledL3Policy {\n    size_t bundleSize;\n};\n\nusing NumaAutoPolicy = std::variant<SystemNumaPolicy, L3DomainsPolicy, BundledL3Policy>;\n\n// Designed as immutable, because there is no good reason to alter an already\n// existing config in a way that doesn't require recreating it completely, and\n// it would be complex and expensive to maintain class invariants.\n// The CPU (processor) numbers always correspond to the actual numbering used\n// by the system. The NUMA node numbers MAY NOT correspond to the system's\n// numbering of the NUMA nodes. In particular, by default, if the processor has\n// non-uniform cache access within a NUMA node (i.e., a non-unified L3 cache structure),\n// then L3 domains within a system NUMA node will be used to subdivide it\n// into multiple logical NUMA nodes in the config. Additionally, empty nodes may\n// be removed, or the user may create custom nodes.\n//\n// As a special case, when performing system-wide replication of read-only data\n// (i.e., LazyNumaReplicatedSystemWide), the system NUMA node is used, rather than\n// custom or L3-aware nodes. See that class's get_discriminator() function.\n//\n// It is guaranteed that NUMA nodes are NOT empty: every node exposed by NumaConfig\n// has at least one processor assigned.\n//\n// We use startup affinities so as not to modify its own behaviour in time.\n//\n// Since Stockfish doesn't support exceptions all places where an exception\n// should be thrown are replaced by std::exit.\nclass NumaConfig {\n   public:\n    NumaConfig() :\n        highestCpuIndex(0),\n        customAffinity(false) {\n        const auto numCpus = SYSTEM_THREADS_NB;\n        add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1);\n    }\n\n    // This function gets a NumaConfig based on the system's provided information.\n    // The available policies are documented above.\n    static NumaConfig from_system([[maybe_unused]] const NumaAutoPolicy& policy,\n                                  bool respectProcessAffinity = true) {\n        NumaConfig cfg = empty();\n\n#if !((defined(__linux__) && !defined(__ANDROID__)) || defined(_WIN64))\n        // Fallback for unsupported systems.\n        for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)\n            cfg.add_cpu_to_node(NumaIndex{0}, c);\n#else\n\n    #if defined(_WIN64)\n\n        std::optional<std::set<CpuIndex>> allowedCpus;\n\n        if (respectProcessAffinity)\n            allowedCpus = STARTUP_PROCESSOR_AFFINITY.get_combined();\n\n        // The affinity cannot be determined in all cases on Windows,\n        // but we at least guarantee that the number of allowed processors\n        // is >= number of processors in the affinity mask. In case the user\n        // is not satisfied they must set the processor numbers explicitly.\n        auto is_cpu_allowed = [&allowedCpus](CpuIndex c) {\n            return !allowedCpus.has_value() || allowedCpus->count(c) == 1;\n        };\n\n    #elif defined(__linux__) && !defined(__ANDROID__)\n\n        std::set<CpuIndex> allowedCpus;\n\n        if (respectProcessAffinity)\n            allowedCpus = STARTUP_PROCESSOR_AFFINITY;\n\n        auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) {\n            return !respectProcessAffinity || allowedCpus.count(c) == 1;\n        };\n\n    #endif\n\n        bool l3Success = false;\n        if (!std::holds_alternative<SystemNumaPolicy>(policy))\n        {\n            size_t l3BundleSize = 0;\n            if (const auto* v = std::get_if<BundledL3Policy>(&policy))\n            {\n                l3BundleSize = v->bundleSize;\n            }\n            if (auto l3Cfg =\n                  try_get_l3_aware_config(respectProcessAffinity, l3BundleSize, is_cpu_allowed))\n            {\n                cfg       = std::move(*l3Cfg);\n                l3Success = true;\n            }\n        }\n        if (!l3Success)\n            cfg = from_system_numa(respectProcessAffinity, is_cpu_allowed);\n\n    #if defined(_WIN64)\n        // Split the NUMA nodes to be contained within a group if necessary.\n        // This is needed between Windows 10 Build 20348 and Windows 11, because\n        // the new NUMA allocation behaviour was introduced while there was\n        // still no way to set thread affinity spanning multiple processor groups.\n        // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support\n        // We also do this is if need to force old API for some reason.\n        //\n        // 2024-08-26: It appears that we need to actually always force this behaviour.\n        // While Windows allows this to work now, such assignments have bad interaction\n        // with the scheduler - in particular it still prefers scheduling on the thread's\n        // \"primary\" node, even if it means scheduling SMT processors first.\n        // See https://github.com/official-stockfish/Stockfish/issues/5551\n        // See https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups\n        //\n        //     Each process is assigned a primary group at creation, and by default all\n        //     of its threads' primary group is the same. Each thread's ideal processor\n        //     is in the thread's primary group, so threads will preferentially be\n        //     scheduled to processors on their primary group, but they are able to\n        //     be scheduled to processors on any other group.\n        //\n        // used to be guarded by if (STARTUP_USE_OLD_AFFINITY_API)\n        {\n            NumaConfig splitCfg = empty();\n\n            NumaIndex splitNodeIndex = 0;\n            for (const auto& cpus : cfg.nodes)\n            {\n                if (cpus.empty())\n                    continue;\n\n                size_t lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE;\n                for (CpuIndex c : cpus)\n                {\n                    const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;\n                    if (procGroupIndex != lastProcGroupIndex)\n                    {\n                        splitNodeIndex += 1;\n                        lastProcGroupIndex = procGroupIndex;\n                    }\n                    splitCfg.add_cpu_to_node(splitNodeIndex, c);\n                }\n                splitNodeIndex += 1;\n            }\n\n            cfg = std::move(splitCfg);\n        }\n    #endif\n\n#endif\n\n        // We have to ensure no empty NUMA nodes persist.\n        cfg.remove_empty_numa_nodes();\n\n        // If the user explicitly opts out from respecting the current process affinity\n        // then it may be inconsistent with the current affinity (obviously), so we\n        // consider it custom.\n        if (!respectProcessAffinity)\n            cfg.customAffinity = true;\n\n        return cfg;\n    }\n\n    // ':'-separated numa nodes\n    // ','-separated cpu indices\n    // supports \"first-last\" range syntax for cpu indices\n    // For example \"0-15,128-143:16-31,144-159:32-47,160-175:48-63,176-191\"\n    static NumaConfig from_string(const std::string& s) {\n        NumaConfig cfg = empty();\n\n        NumaIndex n = 0;\n        for (auto&& nodeStr : split(s, \":\"))\n        {\n            auto indices = indices_from_shortened_string(std::string(nodeStr));\n            if (!indices.empty())\n            {\n                for (auto idx : indices)\n                {\n                    if (!cfg.add_cpu_to_node(n, CpuIndex(idx)))\n                        std::exit(EXIT_FAILURE);\n                }\n\n                n += 1;\n            }\n        }\n\n        cfg.customAffinity = true;\n\n        return cfg;\n    }\n\n    NumaConfig(const NumaConfig&)            = delete;\n    NumaConfig(NumaConfig&&)                 = default;\n    NumaConfig& operator=(const NumaConfig&) = delete;\n    NumaConfig& operator=(NumaConfig&&)      = default;\n\n    bool is_cpu_assigned(CpuIndex n) const { return nodeByCpu.count(n) == 1; }\n\n    NumaIndex num_numa_nodes() const { return nodes.size(); }\n\n    CpuIndex num_cpus_in_numa_node(NumaIndex n) const {\n        assert(n < nodes.size());\n        return nodes[n].size();\n    }\n\n    CpuIndex num_cpus() const { return nodeByCpu.size(); }\n\n    bool requires_memory_replication() const { return customAffinity || nodes.size() > 1; }\n\n    std::string to_string() const {\n        std::string str;\n\n        bool isFirstNode = true;\n        for (auto&& cpus : nodes)\n        {\n            if (!isFirstNode)\n                str += \":\";\n\n            bool isFirstSet = true;\n            auto rangeStart = cpus.begin();\n            for (auto it = cpus.begin(); it != cpus.end(); ++it)\n            {\n                auto next = std::next(it);\n                if (next == cpus.end() || *next != *it + 1)\n                {\n                    // cpus[i] is at the end of the range (may be of size 1)\n                    if (!isFirstSet)\n                        str += \",\";\n\n                    const CpuIndex last = *it;\n\n                    if (it != rangeStart)\n                    {\n                        const CpuIndex first = *rangeStart;\n\n                        str += std::to_string(first);\n                        str += \"-\";\n                        str += std::to_string(last);\n                    }\n                    else\n                        str += std::to_string(last);\n\n                    rangeStart = next;\n                    isFirstSet = false;\n                }\n            }\n\n            isFirstNode = false;\n        }\n\n        return str;\n    }\n\n    bool suggests_binding_threads(CpuIndex numThreads) const {\n        // If we can reasonably determine that the threads cannot be contained\n        // by the OS within the first NUMA node then we advise distributing\n        // and binding threads. When the threads are not bound we can only use\n        // NUMA memory replicated objects from the first node, so when the OS\n        // has to schedule on other nodes we lose performance. We also suggest\n        // binding if there's enough threads to distribute among nodes with minimal\n        // disparity. We try to ignore small nodes, in particular the empty ones.\n\n        // If the affinity set by the user does not match the affinity given by\n        // the OS then binding is necessary to ensure the threads are running on\n        // correct processors.\n        if (customAffinity)\n            return true;\n\n        // We obviously cannot distribute a single thread, so a single thread\n        // should never be bound.\n        if (numThreads <= 1)\n            return false;\n\n        size_t largestNodeSize = 0;\n        for (auto&& cpus : nodes)\n            if (cpus.size() > largestNodeSize)\n                largestNodeSize = cpus.size();\n\n        auto is_node_small = [largestNodeSize](const std::set<CpuIndex>& node) {\n            static constexpr double SmallNodeThreshold = 0.6;\n            return static_cast<double>(node.size()) / static_cast<double>(largestNodeSize)\n                <= SmallNodeThreshold;\n        };\n\n        size_t numNotSmallNodes = 0;\n        for (auto&& cpus : nodes)\n            if (!is_node_small(cpus))\n                numNotSmallNodes += 1;\n\n        return (numThreads > largestNodeSize / 2 || numThreads >= numNotSmallNodes * 4)\n            && nodes.size() > 1;\n    }\n\n    std::vector<NumaIndex> distribute_threads_among_numa_nodes(CpuIndex numThreads) const {\n        std::vector<NumaIndex> ns;\n\n        if (nodes.size() == 1)\n        {\n            // Special case for when there's no NUMA nodes. This doesn't buy us\n            // much, but let's keep the default path simple.\n            ns.resize(numThreads, NumaIndex{0});\n        }\n        else\n        {\n            std::vector<size_t> occupation(nodes.size(), 0);\n            for (CpuIndex c = 0; c < numThreads; ++c)\n            {\n                NumaIndex bestNode{0};\n                float     bestNodeFill = std::numeric_limits<float>::max();\n                for (NumaIndex n = 0; n < nodes.size(); ++n)\n                {\n                    float fill =\n                      static_cast<float>(occupation[n] + 1) / static_cast<float>(nodes[n].size());\n                    // NOTE: Do we want to perhaps fill the first available node\n                    //       up to 50% first before considering other nodes?\n                    //       Probably not, because it would interfere with running\n                    //       multiple instances. We basically shouldn't favor any\n                    //       particular node.\n                    if (fill < bestNodeFill)\n                    {\n                        bestNode     = n;\n                        bestNodeFill = fill;\n                    }\n                }\n                ns.emplace_back(bestNode);\n                occupation[bestNode] += 1;\n            }\n        }\n\n        return ns;\n    }\n\n    NumaReplicatedAccessToken bind_current_thread_to_numa_node(NumaIndex n) const {\n        if (n >= nodes.size() || nodes[n].size() == 0)\n            std::exit(EXIT_FAILURE);\n\n#if defined(__linux__) && !defined(__ANDROID__)\n\n        cpu_set_t* mask = CPU_ALLOC(highestCpuIndex + 1);\n        if (mask == nullptr)\n            std::exit(EXIT_FAILURE);\n\n        const size_t masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1);\n\n        CPU_ZERO_S(masksize, mask);\n\n        for (CpuIndex c : nodes[n])\n            CPU_SET_S(c, masksize, mask);\n\n        const int status = sched_setaffinity(0, masksize, mask);\n\n        CPU_FREE(mask);\n\n        if (status != 0)\n            std::exit(EXIT_FAILURE);\n\n        // We yield this thread just to be sure it gets rescheduled.\n        // This is defensive, allowed because this code is not performance critical.\n        sched_yield();\n\n#elif defined(_WIN64)\n\n        // Requires Windows 11. No good way to set thread affinity spanning\n        // processor groups before that.\n        HMODULE k32                            = GetModuleHandle(TEXT(\"Kernel32.dll\"));\n        auto    SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t(\n          (void (*)()) GetProcAddress(k32, \"SetThreadSelectedCpuSetMasks\"));\n\n        // We ALWAYS set affinity with the new API if available, because\n        // there's no downsides, and we forcibly keep it consistent with\n        // the old API should we need to use it. I.e. we always keep this\n        // as a superset of what we set with SetThreadGroupAffinity.\n        if (SetThreadSelectedCpuSetMasks_f != nullptr)\n        {\n            // Only available on Windows 11 and Windows Server 2022 onwards\n            const USHORT numProcGroups = USHORT(\n              ((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE);\n            auto groupAffinities = std::make_unique<GROUP_AFFINITY[]>(numProcGroups);\n            std::memset(groupAffinities.get(), 0, sizeof(GROUP_AFFINITY) * numProcGroups);\n            for (WORD i = 0; i < numProcGroups; ++i)\n                groupAffinities[i].Group = i;\n\n            for (CpuIndex c : nodes[n])\n            {\n                const size_t procGroupIndex     = c / WIN_PROCESSOR_GROUP_SIZE;\n                const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;\n                groupAffinities[procGroupIndex].Mask |= KAFFINITY(1) << idxWithinProcGroup;\n            }\n\n            HANDLE hThread = GetCurrentThread();\n\n            const BOOL status =\n              SetThreadSelectedCpuSetMasks_f(hThread, groupAffinities.get(), numProcGroups);\n            if (status == 0)\n                std::exit(EXIT_FAILURE);\n\n            // We yield this thread just to be sure it gets rescheduled.\n            // This is defensive, allowed because this code is not performance critical.\n            SwitchToThread();\n        }\n\n        // Sometimes we need to force the old API, but do not use it unless necessary.\n        if (SetThreadSelectedCpuSetMasks_f == nullptr || STARTUP_USE_OLD_AFFINITY_API)\n        {\n            // On earlier windows version (since windows 7) we cannot run a single thread\n            // on multiple processor groups, so we need to restrict the group.\n            // We assume the group of the first processor listed for this node.\n            // Processors from outside this group will not be assigned for this thread.\n            // Normally this won't be an issue because windows used to assign NUMA nodes\n            // such that they cannot span processor groups. However, since Windows 10\n            // Build 20348 the behaviour changed, so there's a small window of versions\n            // between this and Windows 11 that might exhibit problems with not all\n            // processors being utilized.\n            //\n            // We handle this in NumaConfig::from_system by manually splitting the\n            // nodes when we detect that there is no function to set affinity spanning\n            // processor nodes. This is required because otherwise our thread distribution\n            // code may produce suboptimal results.\n            //\n            // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support\n            GROUP_AFFINITY affinity;\n            std::memset(&affinity, 0, sizeof(GROUP_AFFINITY));\n            // We use an ordered set to be sure to get the smallest cpu number here.\n            const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE;\n            affinity.Group                    = static_cast<WORD>(forcedProcGroupIndex);\n            for (CpuIndex c : nodes[n])\n            {\n                const size_t procGroupIndex     = c / WIN_PROCESSOR_GROUP_SIZE;\n                const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;\n                // We skip processors that are not in the same processor group.\n                // If everything was set up correctly this will never be an issue,\n                // but we have to account for bad NUMA node specification.\n                if (procGroupIndex != forcedProcGroupIndex)\n                    continue;\n\n                affinity.Mask |= KAFFINITY(1) << idxWithinProcGroup;\n            }\n\n            HANDLE hThread = GetCurrentThread();\n\n            const BOOL status = SetThreadGroupAffinity(hThread, &affinity, nullptr);\n            if (status == 0)\n                std::exit(EXIT_FAILURE);\n\n            // We yield this thread just to be sure it gets rescheduled. This is\n            // defensive, allowed because this code is not performance critical.\n            SwitchToThread();\n        }\n\n#endif\n\n        return NumaReplicatedAccessToken(n);\n    }\n\n    template<typename FuncT>\n    void execute_on_numa_node(NumaIndex n, FuncT&& f) const {\n        std::thread th([this, &f, n]() {\n            bind_current_thread_to_numa_node(n);\n            std::forward<FuncT>(f)();\n        });\n\n        th.join();\n    }\n\n    std::vector<std::set<CpuIndex>> nodes;\n    std::map<CpuIndex, NumaIndex>   nodeByCpu;\n\n   private:\n    CpuIndex highestCpuIndex;\n\n    bool customAffinity;\n\n    static NumaConfig empty() { return NumaConfig(EmptyNodeTag{}); }\n\n    struct EmptyNodeTag {};\n\n    NumaConfig(EmptyNodeTag) :\n        highestCpuIndex(0),\n        customAffinity(false) {}\n\n    void remove_empty_numa_nodes() {\n        std::vector<std::set<CpuIndex>> newNodes;\n        for (auto&& cpus : nodes)\n            if (!cpus.empty())\n                newNodes.emplace_back(std::move(cpus));\n        nodes = std::move(newNodes);\n    }\n\n    // Returns true if successful\n    // Returns false if failed, i.e. when the cpu is already present\n    //                          strong guarantee, the structure remains unmodified\n    bool add_cpu_to_node(NumaIndex n, CpuIndex c) {\n        if (is_cpu_assigned(c))\n            return false;\n\n        while (nodes.size() <= n)\n            nodes.emplace_back();\n\n        nodes[n].insert(c);\n        nodeByCpu[c] = n;\n\n        if (c > highestCpuIndex)\n            highestCpuIndex = c;\n\n        return true;\n    }\n\n    // Returns true if successful\n    // Returns false if failed, i.e. when any of the cpus is already present\n    //                          strong guarantee, the structure remains unmodified\n    bool add_cpu_range_to_node(NumaIndex n, CpuIndex cfirst, CpuIndex clast) {\n        for (CpuIndex c = cfirst; c <= clast; ++c)\n            if (is_cpu_assigned(c))\n                return false;\n\n        while (nodes.size() <= n)\n            nodes.emplace_back();\n\n        for (CpuIndex c = cfirst; c <= clast; ++c)\n        {\n            nodes[n].insert(c);\n            nodeByCpu[c] = n;\n        }\n\n        if (clast > highestCpuIndex)\n            highestCpuIndex = clast;\n\n        return true;\n    }\n\n    static std::vector<size_t> indices_from_shortened_string(const std::string& s) {\n        std::vector<size_t> indices;\n\n        if (s.empty())\n            return indices;\n\n        for (const auto& ss : split(s, \",\"))\n        {\n            if (ss.empty())\n                continue;\n\n            auto parts = split(ss, \"-\");\n            if (parts.size() == 1)\n            {\n                const CpuIndex c = CpuIndex{str_to_size_t(std::string(parts[0]))};\n                indices.emplace_back(c);\n            }\n            else if (parts.size() == 2)\n            {\n                const CpuIndex cfirst = CpuIndex{str_to_size_t(std::string(parts[0]))};\n                const CpuIndex clast  = CpuIndex{str_to_size_t(std::string(parts[1]))};\n                for (size_t c = cfirst; c <= clast; ++c)\n                {\n                    indices.emplace_back(c);\n                }\n            }\n        }\n\n        return indices;\n    }\n\n    // This function queries the system for the mapping of processors to NUMA nodes.\n    // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA\n    // node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see\n    // comment for Windows implementation of get_process_affinity.\n    template<typename Pred>\n    static NumaConfig from_system_numa([[maybe_unused]] bool   respectProcessAffinity,\n                                       [[maybe_unused]] Pred&& is_cpu_allowed) {\n        NumaConfig cfg = empty();\n\n#if defined(__linux__) && !defined(__ANDROID__)\n\n        // On Linux things are straightforward, since there's no processor groups and\n        // any thread can be scheduled on all processors.\n        // We try to gather this information from the sysfs first\n        // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node\n\n        bool useFallback = false;\n        auto fallback    = [&]() {\n            useFallback = true;\n            cfg         = empty();\n        };\n\n        // /sys/devices/system/node/online contains information about active NUMA nodes\n        auto nodeIdsStr = read_file_to_string(\"/sys/devices/system/node/online\");\n        if (!nodeIdsStr.has_value() || nodeIdsStr->empty())\n        {\n            fallback();\n        }\n        else\n        {\n            remove_whitespace(*nodeIdsStr);\n            for (size_t n : indices_from_shortened_string(*nodeIdsStr))\n            {\n                // /sys/devices/system/node/node.../cpulist\n                std::string path =\n                  std::string(\"/sys/devices/system/node/node\") + std::to_string(n) + \"/cpulist\";\n                auto cpuIdsStr = read_file_to_string(path);\n                // Now, we only bail if the file does not exist. Some nodes may be\n                // empty, that's fine. An empty node still has a file that appears\n                // to have some whitespace, so we need to handle that.\n                if (!cpuIdsStr.has_value())\n                {\n                    fallback();\n                    break;\n                }\n                else\n                {\n                    remove_whitespace(*cpuIdsStr);\n                    for (size_t c : indices_from_shortened_string(*cpuIdsStr))\n                    {\n                        if (is_cpu_allowed(c))\n                            cfg.add_cpu_to_node(n, c);\n                    }\n                }\n            }\n        }\n\n        if (useFallback)\n        {\n            for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)\n                if (is_cpu_allowed(c))\n                    cfg.add_cpu_to_node(NumaIndex{0}, c);\n        }\n\n#elif defined(_WIN64)\n\n        WORD numProcGroups = GetActiveProcessorGroupCount();\n        for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup)\n        {\n            for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)\n            {\n                PROCESSOR_NUMBER procnum;\n                procnum.Group    = procGroup;\n                procnum.Number   = number;\n                procnum.Reserved = 0;\n                USHORT nodeNumber;\n\n                const BOOL     status = GetNumaProcessorNodeEx(&procnum, &nodeNumber);\n                const CpuIndex c      = static_cast<CpuIndex>(procGroup) * WIN_PROCESSOR_GROUP_SIZE\n                                 + static_cast<CpuIndex>(number);\n                if (status != 0 && nodeNumber != std::numeric_limits<USHORT>::max()\n                    && is_cpu_allowed(c))\n                {\n                    cfg.add_cpu_to_node(nodeNumber, c);\n                }\n            }\n        }\n\n#else\n\n        abort();  // should not reach here\n\n#endif\n\n        return cfg;\n    }\n\n    template<typename Pred>\n    static std::optional<NumaConfig> try_get_l3_aware_config(\n      bool respectProcessAffinity, size_t bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) {\n        // Get the normal system configuration so we know to which NUMA node\n        // each L3 domain belongs.\n        NumaConfig systemConfig =\n          NumaConfig::from_system(SystemNumaPolicy{}, respectProcessAffinity);\n        std::vector<L3Domain> l3Domains;\n\n#if defined(__linux__) && !defined(__ANDROID__)\n\n        std::set<CpuIndex> seenCpus;\n        auto               nextUnseenCpu = [&seenCpus]() {\n            for (CpuIndex i = 0;; ++i)\n                if (!seenCpus.count(i))\n                    return i;\n        };\n\n        while (true)\n        {\n            CpuIndex next = nextUnseenCpu();\n            auto     siblingsStr =\n              read_file_to_string(\"/sys/devices/system/cpu/cpu\" + std::to_string(next)\n                                  + \"/cache/index3/shared_cpu_list\");\n\n            if (!siblingsStr.has_value() || siblingsStr->empty())\n            {\n                break;  // we have read all available CPUs\n            }\n\n            L3Domain domain;\n            for (size_t c : indices_from_shortened_string(*siblingsStr))\n            {\n                if (is_cpu_allowed(c))\n                {\n                    domain.systemNumaIndex = systemConfig.nodeByCpu.at(c);\n                    domain.cpus.insert(c);\n                }\n                seenCpus.insert(c);\n            }\n            if (!domain.cpus.empty())\n            {\n                l3Domains.emplace_back(std::move(domain));\n            }\n        }\n\n#elif defined(_WIN64)\n\n        DWORD bufSize = 0;\n        GetLogicalProcessorInformationEx(RelationCache, nullptr, &bufSize);\n        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n            return std::nullopt;\n\n        std::vector<char> buffer(bufSize);\n        auto info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(buffer.data());\n        if (!GetLogicalProcessorInformationEx(RelationCache, info, &bufSize))\n            return std::nullopt;\n\n        while (reinterpret_cast<char*>(info) < buffer.data() + bufSize)\n        {\n            info = std::launder(info);\n            if (info->Relationship == RelationCache && info->Cache.Level == 3)\n            {\n                L3Domain domain{};\n                domain.cpus = readCacheMembers(info, is_cpu_allowed);\n                if (!domain.cpus.empty())\n                {\n                    domain.systemNumaIndex = systemConfig.nodeByCpu.at(*domain.cpus.begin());\n                    l3Domains.push_back(std::move(domain));\n                }\n            }\n            // Variable length data structure, advance to next\n            info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(\n              reinterpret_cast<char*>(info) + info->Size);\n        }\n#endif\n\n        if (!l3Domains.empty())\n            return {NumaConfig::from_l3_info(std::move(l3Domains), bundleSize)};\n\n        return std::nullopt;\n    }\n\n\n    static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, size_t bundleSize) {\n        assert(!domains.empty());\n\n        std::map<NumaIndex, std::vector<L3Domain>> list;\n        for (auto& d : domains)\n            list[d.systemNumaIndex].emplace_back(std::move(d));\n\n        NumaConfig cfg = empty();\n        NumaIndex  n   = 0;\n        for (auto& [_, ds] : list)\n        {\n            bool changed;\n            // Scan through pairs and merge them. With roughly equal L3 sizes, should give\n            // a decent distribution.\n            do\n            {\n                changed = false;\n                for (size_t j = 0; j + 1 < ds.size(); ++j)\n                {\n                    if (ds[j].cpus.size() + ds[j + 1].cpus.size() <= bundleSize)\n                    {\n                        changed = true;\n                        ds[j].cpus.merge(ds[j + 1].cpus);\n                        ds.erase(ds.begin() + j + 1);\n                    }\n                }\n                // ds.size() has decreased if changed is true, so this loop will terminate\n            } while (changed);\n            for (const L3Domain& d : ds)\n            {\n                const NumaIndex dn = n++;\n                for (CpuIndex cpu : d.cpus)\n                {\n                    cfg.add_cpu_to_node(dn, cpu);\n                }\n            }\n        }\n        return cfg;\n    }\n};\n\nclass NumaReplicationContext;\n\n// Instances of this class are tracked by the NumaReplicationContext instance.\n// NumaReplicationContext informs all tracked instances when NUMA configuration changes.\nclass NumaReplicatedBase {\n   public:\n    NumaReplicatedBase(NumaReplicationContext& ctx);\n\n    NumaReplicatedBase(const NumaReplicatedBase&) = delete;\n    NumaReplicatedBase(NumaReplicatedBase&& other) noexcept;\n\n    NumaReplicatedBase& operator=(const NumaReplicatedBase&) = delete;\n    NumaReplicatedBase& operator=(NumaReplicatedBase&& other) noexcept;\n\n    virtual void on_numa_config_changed() = 0;\n    virtual ~NumaReplicatedBase();\n\n    const NumaConfig& get_numa_config() const;\n\n   private:\n    NumaReplicationContext* context;\n};\n\n// We force boxing with a unique_ptr. If this becomes an issue due to added\n// indirection we may need to add an option for a custom boxing type. When the\n// NUMA config changes the value stored at the index 0 is replicated to other nodes.\ntemplate<typename T>\nclass NumaReplicated: public NumaReplicatedBase {\n   public:\n    using ReplicatorFuncType = std::function<T(const T&)>;\n\n    NumaReplicated(NumaReplicationContext& ctx) :\n        NumaReplicatedBase(ctx) {\n        replicate_from(T{});\n    }\n\n    NumaReplicated(NumaReplicationContext& ctx, T&& source) :\n        NumaReplicatedBase(ctx) {\n        replicate_from(std::move(source));\n    }\n\n    NumaReplicated(const NumaReplicated&) = delete;\n    NumaReplicated(NumaReplicated&& other) noexcept :\n        NumaReplicatedBase(std::move(other)),\n        instances(std::exchange(other.instances, {})) {}\n\n    NumaReplicated& operator=(const NumaReplicated&) = delete;\n    NumaReplicated& operator=(NumaReplicated&& other) noexcept {\n        NumaReplicatedBase::operator=(*this, std::move(other));\n        instances = std::exchange(other.instances, {});\n\n        return *this;\n    }\n\n    NumaReplicated& operator=(T&& source) {\n        replicate_from(std::move(source));\n\n        return *this;\n    }\n\n    ~NumaReplicated() override = default;\n\n    const T& operator[](NumaReplicatedAccessToken token) const {\n        assert(token.get_numa_index() < instances.size());\n        return *(instances[token.get_numa_index()]);\n    }\n\n    const T& operator*() const { return *(instances[0]); }\n\n    const T* operator->() const { return instances[0].get(); }\n\n    template<typename FuncT>\n    void modify_and_replicate(FuncT&& f) {\n        auto source = std::move(instances[0]);\n        std::forward<FuncT>(f)(*source);\n        replicate_from(std::move(*source));\n    }\n\n    void on_numa_config_changed() override {\n        // Use the first one as the source. It doesn't matter which one we use,\n        // because they all must be identical, but the first one is guaranteed to exist.\n        auto source = std::move(instances[0]);\n        replicate_from(std::move(*source));\n    }\n\n   private:\n    std::vector<std::unique_ptr<T>> instances;\n\n    void replicate_from(T&& source) {\n        instances.clear();\n\n        const NumaConfig& cfg = get_numa_config();\n        if (cfg.requires_memory_replication())\n        {\n            for (NumaIndex n = 0; n < cfg.num_numa_nodes(); ++n)\n            {\n                cfg.execute_on_numa_node(\n                  n, [this, &source]() { instances.emplace_back(std::make_unique<T>(source)); });\n            }\n        }\n        else\n        {\n            assert(cfg.num_numa_nodes() == 1);\n            // We take advantage of the fact that replication is not required\n            // and reuse the source value, avoiding one copy operation.\n            instances.emplace_back(std::make_unique<T>(std::move(source)));\n        }\n    }\n};\n\n// We force boxing with a unique_ptr. If this becomes an issue due to added\n// indirection we may need to add an option for a custom boxing type.\ntemplate<typename T>\nclass LazyNumaReplicated: public NumaReplicatedBase {\n   public:\n    using ReplicatorFuncType = std::function<T(const T&)>;\n\n    LazyNumaReplicated(NumaReplicationContext& ctx) :\n        NumaReplicatedBase(ctx) {\n        prepare_replicate_from(T{});\n    }\n\n    LazyNumaReplicated(NumaReplicationContext& ctx, T&& source) :\n        NumaReplicatedBase(ctx) {\n        prepare_replicate_from(std::move(source));\n    }\n\n    LazyNumaReplicated(const LazyNumaReplicated&) = delete;\n    LazyNumaReplicated(LazyNumaReplicated&& other) noexcept :\n        NumaReplicatedBase(std::move(other)),\n        instances(std::exchange(other.instances, {})) {}\n\n    LazyNumaReplicated& operator=(const LazyNumaReplicated&) = delete;\n    LazyNumaReplicated& operator=(LazyNumaReplicated&& other) noexcept {\n        NumaReplicatedBase::operator=(*this, std::move(other));\n        instances = std::exchange(other.instances, {});\n\n        return *this;\n    }\n\n    LazyNumaReplicated& operator=(T&& source) {\n        prepare_replicate_from(std::move(source));\n\n        return *this;\n    }\n\n    ~LazyNumaReplicated() override = default;\n\n    const T& operator[](NumaReplicatedAccessToken token) const {\n        assert(token.get_numa_index() < instances.size());\n        ensure_present(token.get_numa_index());\n        return *(instances[token.get_numa_index()]);\n    }\n\n    const T& operator*() const { return *(instances[0]); }\n\n    const T* operator->() const { return instances[0].get(); }\n\n    template<typename FuncT>\n    void modify_and_replicate(FuncT&& f) {\n        auto source = std::move(instances[0]);\n        std::forward<FuncT>(f)(*source);\n        prepare_replicate_from(std::move(*source));\n    }\n\n    void on_numa_config_changed() override {\n        // Use the first one as the source. It doesn't matter which one we use,\n        // because they all must be identical, but the first one is guaranteed to exist.\n        auto source = std::move(instances[0]);\n        prepare_replicate_from(std::move(*source));\n    }\n\n   private:\n    mutable std::vector<std::unique_ptr<T>> instances;\n    mutable std::mutex                      mutex;\n\n    void ensure_present(NumaIndex idx) const {\n        assert(idx < instances.size());\n\n        if (instances[idx] != nullptr)\n            return;\n\n        assert(idx != 0);\n\n        std::unique_lock<std::mutex> lock(mutex);\n        // Check again for races.\n        if (instances[idx] != nullptr)\n            return;\n\n        const NumaConfig& cfg = get_numa_config();\n        cfg.execute_on_numa_node(\n          idx, [this, idx]() { instances[idx] = std::make_unique<T>(*instances[0]); });\n    }\n\n    void prepare_replicate_from(T&& source) {\n        instances.clear();\n\n        const NumaConfig& cfg = get_numa_config();\n        if (cfg.requires_memory_replication())\n        {\n            assert(cfg.num_numa_nodes() > 0);\n\n            // We just need to make sure the first instance is there.\n            // Note that we cannot move here as we need to reallocate the data\n            // on the correct NUMA node.\n            cfg.execute_on_numa_node(\n              0, [this, &source]() { instances.emplace_back(std::make_unique<T>(source)); });\n\n            // Prepare others for lazy init.\n            instances.resize(cfg.num_numa_nodes());\n        }\n        else\n        {\n            assert(cfg.num_numa_nodes() == 1);\n            // We take advantage of the fact that replication is not required\n            // and reuse the source value, avoiding one copy operation.\n            instances.emplace_back(std::make_unique<T>(std::move(source)));\n        }\n    }\n};\n\n// Utilizes shared memory.\ntemplate<typename T>\nclass LazyNumaReplicatedSystemWide: public NumaReplicatedBase {\n   public:\n    using ReplicatorFuncType = std::function<T(const T&)>;\n\n    LazyNumaReplicatedSystemWide(NumaReplicationContext& ctx) :\n        NumaReplicatedBase(ctx) {\n        prepare_replicate_from(std::make_unique<T>());\n    }\n\n    LazyNumaReplicatedSystemWide(NumaReplicationContext& ctx, std::unique_ptr<T>&& source) :\n        NumaReplicatedBase(ctx) {\n        prepare_replicate_from(std::move(source));\n    }\n\n    LazyNumaReplicatedSystemWide(const LazyNumaReplicatedSystemWide&) = delete;\n    LazyNumaReplicatedSystemWide(LazyNumaReplicatedSystemWide&& other) noexcept :\n        NumaReplicatedBase(std::move(other)),\n        instances(std::exchange(other.instances, {})) {}\n\n    LazyNumaReplicatedSystemWide& operator=(const LazyNumaReplicatedSystemWide&) = delete;\n    LazyNumaReplicatedSystemWide& operator=(LazyNumaReplicatedSystemWide&& other) noexcept {\n        NumaReplicatedBase::operator=(*this, std::move(other));\n        instances = std::exchange(other.instances, {});\n\n        return *this;\n    }\n\n    LazyNumaReplicatedSystemWide& operator=(std::unique_ptr<T>&& source) {\n        prepare_replicate_from(std::move(source));\n\n        return *this;\n    }\n\n    ~LazyNumaReplicatedSystemWide() override = default;\n\n    const T& operator[](NumaReplicatedAccessToken token) const {\n        assert(token.get_numa_index() < instances.size());\n        ensure_present(token.get_numa_index());\n        return *(instances[token.get_numa_index()]);\n    }\n\n    const T& operator*() const { return *(instances[0]); }\n\n    const T* operator->() const { return &*instances[0]; }\n\n    std::vector<std::pair<SystemWideSharedConstantAllocationStatus, std::optional<std::string>>>\n    get_status_and_errors() const {\n        std::vector<std::pair<SystemWideSharedConstantAllocationStatus, std::optional<std::string>>>\n          status;\n        status.reserve(instances.size());\n\n        for (const auto& instance : instances)\n        {\n            status.emplace_back(instance.get_status(), instance.get_error_message());\n        }\n\n        return status;\n    }\n\n    template<typename FuncT>\n    void modify_and_replicate(FuncT&& f) {\n        auto source = std::make_unique<T>(*instances[0]);\n        std::forward<FuncT>(f)(*source);\n        prepare_replicate_from(std::move(source));\n    }\n\n    void on_numa_config_changed() override {\n        // Use the first one as the source. It doesn't matter which one we use,\n        // because they all must be identical, but the first one is guaranteed to exist.\n        auto source = std::make_unique<T>(*instances[0]);\n        prepare_replicate_from(std::move(source));\n    }\n\n   private:\n    mutable std::vector<SystemWideSharedConstant<T>> instances;\n    mutable std::mutex                               mutex;\n\n    std::size_t get_discriminator(NumaIndex idx) const {\n        const NumaConfig& cfg     = get_numa_config();\n        const NumaConfig& cfg_sys = NumaConfig::from_system(SystemNumaPolicy{}, false);\n        // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to\n        CpuIndex    cpu     = *cfg.nodes[idx].begin();  // get a CpuIndex from NumaIndex\n        NumaIndex   sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0;\n        std::string s       = cfg_sys.to_string() + \"$\" + std::to_string(sys_idx);\n        return static_cast<std::size_t>(hash_string(s));\n    }\n\n    void ensure_present(NumaIndex idx) const {\n        assert(idx < instances.size());\n\n        if (instances[idx] != nullptr)\n            return;\n\n        assert(idx != 0);\n\n        std::unique_lock<std::mutex> lock(mutex);\n        // Check again for races.\n        if (instances[idx] != nullptr)\n            return;\n\n        const NumaConfig& cfg = get_numa_config();\n        cfg.execute_on_numa_node(idx, [this, idx]() {\n            instances[idx] = SystemWideSharedConstant<T>(*instances[0], get_discriminator(idx));\n        });\n    }\n\n    void prepare_replicate_from(std::unique_ptr<T>&& source) {\n        instances.clear();\n\n        const NumaConfig& cfg = get_numa_config();\n        // We just need to make sure the first instance is there.\n        // Note that we cannot move here as we need to reallocate the data\n        // on the correct NUMA node.\n        // Even in the case of a single NUMA node we have to copy since it's shared memory.\n        if (cfg.requires_memory_replication())\n        {\n            assert(cfg.num_numa_nodes() > 0);\n\n            cfg.execute_on_numa_node(0, [this, &source]() {\n                instances.emplace_back(SystemWideSharedConstant<T>(*source, get_discriminator(0)));\n            });\n\n            // Prepare others for lazy init.\n            instances.resize(cfg.num_numa_nodes());\n        }\n        else\n        {\n            assert(cfg.num_numa_nodes() == 1);\n            instances.emplace_back(SystemWideSharedConstant<T>(*source, get_discriminator(0)));\n        }\n    }\n};\n\nclass NumaReplicationContext {\n   public:\n    NumaReplicationContext(NumaConfig&& cfg) :\n        config(std::move(cfg)) {}\n\n    NumaReplicationContext(const NumaReplicationContext&) = delete;\n    NumaReplicationContext(NumaReplicationContext&&)      = delete;\n\n    NumaReplicationContext& operator=(const NumaReplicationContext&) = delete;\n    NumaReplicationContext& operator=(NumaReplicationContext&&)      = delete;\n\n    ~NumaReplicationContext() {\n        // The context must outlive replicated objects\n        if (!trackedReplicatedObjects.empty())\n            std::exit(EXIT_FAILURE);\n    }\n\n    void attach(NumaReplicatedBase* obj) {\n        assert(trackedReplicatedObjects.count(obj) == 0);\n        trackedReplicatedObjects.insert(obj);\n    }\n\n    void detach(NumaReplicatedBase* obj) {\n        assert(trackedReplicatedObjects.count(obj) == 1);\n        trackedReplicatedObjects.erase(obj);\n    }\n\n    // oldObj may be invalid at this point\n    void move_attached([[maybe_unused]] NumaReplicatedBase* oldObj, NumaReplicatedBase* newObj) {\n        assert(trackedReplicatedObjects.count(oldObj) == 1);\n        assert(trackedReplicatedObjects.count(newObj) == 0);\n        trackedReplicatedObjects.erase(oldObj);\n        trackedReplicatedObjects.insert(newObj);\n    }\n\n    void set_numa_config(NumaConfig&& cfg) {\n        config = std::move(cfg);\n        for (auto&& obj : trackedReplicatedObjects)\n            obj->on_numa_config_changed();\n    }\n\n    const NumaConfig& get_numa_config() const { return config; }\n\n   private:\n    NumaConfig config;\n\n    // std::set uses std::less by default, which is required for pointer comparison\n    std::set<NumaReplicatedBase*> trackedReplicatedObjects;\n};\n\ninline NumaReplicatedBase::NumaReplicatedBase(NumaReplicationContext& ctx) :\n    context(&ctx) {\n    context->attach(this);\n}\n\ninline NumaReplicatedBase::NumaReplicatedBase(NumaReplicatedBase&& other) noexcept :\n    context(std::exchange(other.context, nullptr)) {\n    context->move_attached(&other, this);\n}\n\ninline NumaReplicatedBase& NumaReplicatedBase::operator=(NumaReplicatedBase&& other) noexcept {\n    context = std::exchange(other.context, nullptr);\n\n    context->move_attached(&other, this);\n\n    return *this;\n}\n\ninline NumaReplicatedBase::~NumaReplicatedBase() {\n    if (context != nullptr)\n        context->detach(this);\n}\n\ninline const NumaConfig& NumaReplicatedBase::get_numa_config() const {\n    return context->get_numa_config();\n}\n\n}  // namespace Stockfish\n\n\n#endif  // #ifndef NUMA_H_INCLUDED\n"
  },
  {
    "path": "src/perft.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef PERFT_H_INCLUDED\n#define PERFT_H_INCLUDED\n\n#include <cstdint>\n\n#include \"movegen.h\"\n#include \"position.h\"\n#include \"types.h\"\n#include \"uci.h\"\n\nnamespace Stockfish::Benchmark {\n\n// Utility to verify move generation. All the leaf nodes up\n// to the given depth are generated and counted, and the sum is returned.\ntemplate<bool Root>\nuint64_t perft(Position& pos, Depth depth) {\n\n    StateInfo st;\n\n    uint64_t   cnt, nodes = 0;\n    const bool leaf = (depth == 2);\n\n    for (const auto& m : MoveList<LEGAL>(pos))\n    {\n        if (Root && depth <= 1)\n            cnt = 1, nodes++;\n        else\n        {\n            pos.do_move(m, st);\n            cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - 1);\n            nodes += cnt;\n            pos.undo_move(m);\n        }\n        if (Root)\n            sync_cout << UCIEngine::move(m, pos.is_chess960()) << \": \" << cnt << sync_endl;\n    }\n    return nodes;\n}\n\ninline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) {\n    StateInfo st;\n    Position  p;\n    p.set(fen, isChess960, &st);\n\n    return perft<true>(p, depth);\n}\n}\n\n#endif  // PERFT_H_INCLUDED\n"
  },
  {
    "path": "src/position.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"position.h\"\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cctype>\n#include <cstddef>\n#include <cstring>\n#include <initializer_list>\n#include <iomanip>\n#include <iostream>\n#include <sstream>\n#include <string_view>\n#include <utility>\n\n#include \"bitboard.h\"\n#include \"history.h\"\n#include \"misc.h\"\n#include \"movegen.h\"\n#include \"syzygy/tbprobe.h\"\n#include \"tt.h\"\n#include \"uci.h\"\n\nusing std::string;\n\nnamespace Stockfish {\n\nnamespace Zobrist {\n\nKey psq[PIECE_NB][SQUARE_NB];\nKey enpassant[FILE_NB];\nKey castling[CASTLING_RIGHT_NB];\nKey side, noPawns;\n\n}\n\nnamespace {\n\nconstexpr std::string_view PieceToChar(\" PNBRQK  pnbrqk\");\n\nstatic constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,\n                                   B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING};\n}  // namespace\n\n\n// Returns an ASCII representation of the position\nstd::ostream& operator<<(std::ostream& os, const Position& pos) {\n\n    os << \"\\n +---+---+---+---+---+---+---+---+\\n\";\n\n    for (Rank r = RANK_8;; --r)\n    {\n        for (File f = FILE_A; f <= FILE_H; ++f)\n            os << \" | \" << PieceToChar[pos.piece_on(make_square(f, r))];\n\n        os << \" | \" << (1 + r) << \"\\n +---+---+---+---+---+---+---+---+\\n\";\n\n        if (r == RANK_1)\n            break;\n    }\n\n    os << \"   a   b   c   d   e   f   g   h\\n\"\n       << \"\\nFen: \" << pos.fen() << \"\\nKey: \" << std::hex << std::uppercase << std::setfill('0')\n       << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << \"\\nCheckers: \";\n\n    for (Bitboard b = pos.checkers(); b;)\n        os << UCIEngine::square(pop_lsb(b)) << \" \";\n\n    if (Tablebases::MaxCardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))\n    {\n        StateInfo st;\n\n        Position p;\n        p.set(pos.fen(), pos.is_chess960(), &st);\n        Tablebases::ProbeState s1, s2;\n        Tablebases::WDLScore   wdl = Tablebases::probe_wdl(p, &s1);\n        int                    dtz = Tablebases::probe_dtz(p, &s2);\n        os << \"\\nTablebases WDL: \" << std::setw(4) << wdl << \" (\" << s1 << \")\"\n           << \"\\nTablebases DTZ: \" << std::setw(4) << dtz << \" (\" << s2 << \")\";\n    }\n\n    return os;\n}\n\n\n// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions\n// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes\n// to allow fast detection of recurring positions. For details see:\n// http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf\n\n// First and second hash functions for indexing the cuckoo tables\ninline int H1(Key h) { return h & 0x1fff; }\ninline int H2(Key h) { return (h >> 16) & 0x1fff; }\n\n// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves\nstd::array<Key, 8192>  cuckoo;\nstd::array<Move, 8192> cuckooMove;\n\n// Initializes at startup the various arrays used to compute hash keys\nvoid Position::init() {\n\n    PRNG rng(1070372);\n\n    for (Piece pc : Pieces)\n        for (Square s = SQ_A1; s <= SQ_H8; ++s)\n            Zobrist::psq[pc][s] = rng.rand<Key>();\n    // pawns on these squares will promote\n    std::fill_n(Zobrist::psq[W_PAWN] + SQ_A8, 8, 0);\n    std::fill_n(Zobrist::psq[B_PAWN], 8, 0);\n\n    for (File f = FILE_A; f <= FILE_H; ++f)\n        Zobrist::enpassant[f] = rng.rand<Key>();\n\n    for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)\n        Zobrist::castling[cr] = rng.rand<Key>();\n\n    Zobrist::side    = rng.rand<Key>();\n    Zobrist::noPawns = rng.rand<Key>();\n\n    // Prepare the cuckoo tables\n    cuckoo.fill(0);\n    cuckooMove.fill(Move::none());\n    [[maybe_unused]] int count = 0;\n    for (Piece pc : Pieces)\n        for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)\n            for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)\n                if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2))\n                {\n                    Move move = Move(s1, s2);\n                    Key  key  = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;\n                    int  i    = H1(key);\n                    while (true)\n                    {\n                        std::swap(cuckoo[i], key);\n                        std::swap(cuckooMove[i], move);\n                        if (move == Move::none())  // Arrived at empty slot?\n                            break;\n                        i = (i == H1(key)) ? H2(key) : H1(key);  // Push victim to alternative slot\n                    }\n                    count++;\n                }\n    assert(count == 3668);\n}\n\n\n// Initializes the position object with the given FEN string.\n// The FEN string is strictly validated; if it is invalid or inconsistent,\n// a PositionSetError describing the problem is returned, otherwise std::nullopt.\nstd::optional<PositionSetError>\nPosition::set(const string& fenStr, bool isChess960, StateInfo* si) {\n    /*\n   A FEN string defines a particular position using only the ASCII character set.\n\n   A FEN string contains six fields separated by a space. The fields are:\n\n   1) Piece placement (from white's perspective). Each rank is described, starting\n      with rank 8 and ending with rank 1. Within each rank, the contents of each\n      square are described from file A through file H. Following the Standard\n      Algebraic Notation (SAN), each piece is identified by a single letter taken\n      from the standard English names. White pieces are designated using upper-case\n      letters (\"PNBRQK\") whilst Black uses lowercase (\"pnbrqk\"). Blank squares are\n      noted using digits 1 through 8 (the number of blank squares), and \"/\"\n      separates ranks.\n\n   2) Active color. \"w\" means white moves next, \"b\" means black.\n\n   3) Castling availability. If neither side can castle, this is \"-\". Otherwise,\n      this has one or more letters: \"K\" (White can castle kingside), \"Q\" (White\n      can castle queenside), \"k\" (Black can castle kingside), and/or \"q\" (Black\n      can castle queenside).\n\n   4) En passant target square (in algebraic notation). If there's no en passant\n      target square, this is \"-\". If a pawn has just made a 2-square move, this\n      is the position \"behind\" the pawn. Following X-FEN standard, this is recorded\n      only if there is a pawn in position to make an en passant capture, and if\n      there really is a pawn that might have advanced two squares.\n\n   5) Halfmove clock. This is the number of halfmoves since the last pawn advance\n      or capture. This is used to determine if a draw can be claimed under the\n      fifty-move rule.\n\n   6) Fullmove number. The number of the full move. It starts at 1, and is\n      incremented after Black's move.\n*/\n\n    unsigned char      token;\n    std::istringstream ss(fenStr);\n\n    std::memset(reinterpret_cast<char*>(this), 0, sizeof(Position));\n    std::memset(si, 0, sizeof(StateInfo));\n    st = si;\n\n    ss >> std::noskipws;\n\n    int numPieces = 0;\n    int file      = FILE_A;\n    int rank      = RANK_8;\n\n    // 1. Piece placement\n    for (;;)\n    {\n        if (!(ss >> token))\n            return PositionSetError(\"Invalid FEN. Unexpected end of stream.\");\n\n        if (isspace(token))\n            break;\n\n        if (isdigit(token))\n        {\n            const int diff = (token - '0');\n            if (diff < 1 || diff > 8)\n                return PositionSetError(\"Invalid FEN. Invalid number of squares to skip.\");\n\n            file += diff;\n            if (file > FILE_NB)\n                return PositionSetError(\"Invalid FEN. Invalid file reached.\");\n        }\n        else if (token == '/')\n        {\n            if (file != FILE_NB)\n                return PositionSetError(\n                  \"Invalid FEN. Trying to end rank when not at the end of it.\");\n\n            --rank;\n            file = FILE_A;\n\n            if (rank < RANK_1)\n                return PositionSetError(\"Invalid FEN. Invalid rank reached.\");\n        }\n        else\n        {\n            if (file >= FILE_NB)\n                return PositionSetError(\"Invalid FEN. Invalid file reached.\");\n\n            const size_t idx = PieceToChar.find(token);\n            if (idx == string::npos)\n                return PositionSetError(std::string(\"Invalid FEN. Invalid piece: \")\n                                        + std::string(1, token));\n\n            if (++numPieces > 32)\n                return PositionSetError(\"Invalid FEN. More than 32 pieces on the board.\");\n\n            const Square sq = make_square(File(file), Rank(rank));\n            put_piece(Piece(idx), sq);\n\n            ++file;\n        }\n    }\n\n    if (rank != RANK_1 || file != FILE_NB)\n        return PositionSetError(\"Invalid FEN. Board state encoding ended but cursor not at end.\");\n\n    if (pieces(PAWN) & (RANK_1 | RANK_8))\n        return PositionSetError(\"Unsupported position. Pawns on the first or eighth rank.\");\n\n    if (count<KING>(WHITE) != 1 || count<KING>(BLACK) != 1)\n        return PositionSetError(\"Unsupported position. Incorrect number of kings.\");\n\n    const int wPawns = count<PAWN>(WHITE);\n    const int bPawns = count<PAWN>(BLACK);\n    if (wPawns > 8)\n        return PositionSetError(\"Unsupported position. WHITE has more than 8 pawns.\");\n    if (bPawns > 8)\n        return PositionSetError(\"Unsupported position. BLACK has more than 8 pawns.\");\n\n    const int wAdditionalKnights = std::max((int) count<KNIGHT>(WHITE) - 2, 0);\n    const int bAdditionalKnights = std::max((int) count<KNIGHT>(BLACK) - 2, 0);\n    const int wAdditionalBishops = std::max((int) count<BISHOP>(WHITE) - 2, 0);\n    const int bAdditionalBishops = std::max((int) count<BISHOP>(BLACK) - 2, 0);\n    const int wAdditionalRooks   = std::max((int) count<ROOK>(WHITE) - 2, 0);\n    const int bAdditionalRooks   = std::max((int) count<ROOK>(BLACK) - 2, 0);\n    const int wAdditionalQueens  = std::max((int) count<QUEEN>(WHITE) - 1, 0);\n    const int bAdditionalQueens  = std::max((int) count<QUEEN>(BLACK) - 1, 0);\n    if (wAdditionalKnights + wAdditionalBishops + wAdditionalRooks + wAdditionalQueens > 8 - wPawns)\n        return PositionSetError(\"Unsupported position. Too many major pieces for WHITE.\");\n    if (bAdditionalKnights + bAdditionalBishops + bAdditionalRooks + bAdditionalQueens > 8 - bPawns)\n        return PositionSetError(\"Unsupported position. Too many major pieces for BLACK.\");\n\n    // 2. Active color\n    if (!(ss >> token))\n        return PositionSetError(\"Invalid FEN. Unexpected end of stream.\");\n    if (token != 'w' && token != 'b')\n        return PositionSetError(std::string(\"Invalid FEN. Invalid side to move: \")\n                                + std::string(1, token));\n    sideToMove = (token == 'w' ? WHITE : BLACK);\n    if (!(ss >> token) || !isspace(token) || ss.eof())\n        return PositionSetError(\"Invalid FEN. Expected whitespace after side to move.\");\n\n    // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,\n    // Shredder-FEN that uses the letters of the columns on which the rooks began\n    // the game instead of KQkq and also X-FEN standard that, in case of Chess960,\n    // if an inner rook is associated with the castling right, the castling tag is\n    // replaced by the file letter of the involved rook, as for the Shredder-FEN.\n    //\n    // NOTE: Due to the prevalnce of incorrect (or missing) castling rights the\n    // validation is less strict. However, incorrect castling rights are still sanitized.\n    int num_castling_rights = 0;\n    for (;;)\n    {\n        if (!(ss >> token))\n            break;\n\n        if (isspace(token))\n            break;\n\n        if (num_castling_rights == 0 && token == '-')\n        {\n            ss >> std::ws;\n            break;\n        }\n\n        if (++num_castling_rights > 4)\n            return PositionSetError(\"Invalid FEN. Maximum of 4 castling rights can be specified.\");\n\n        Square rsq  = SQ_NONE;\n        Square ksq  = SQ_NONE;\n        Color  c    = islower(token) ? BLACK : WHITE;\n        Piece  rook = make_piece(c, ROOK);\n        Piece  king = make_piece(c, KING);\n\n        token = char(toupper(token));\n\n        if (token == 'K' || token == 'Q')\n        {\n            const int dir = token == 'K' ? -1 : 1;\n            Square    sq  = relative_square(c, token == 'K' ? SQ_H1 : SQ_A1);\n            // Look for a rook and a king for the castling. King must come later.\n            // Only the first rook is noted.\n            // If the castling rights are available the king must always be between files 2 and 7 inclusive\n            // so there is no need to check the last square.\n            for (int i = 0; i < 7; ++i, sq = Square(sq + dir))\n            {\n                const Piece pc = piece_on(sq);\n                if (pc == king)\n                {\n                    ksq = sq;\n                    break;\n                }\n                else if (pc == rook && rsq == SQ_NONE)\n                {\n                    rsq = sq;\n                }\n            }\n        }\n        else if (token >= 'A' && token <= 'H')\n        {\n            const Square rsqCandidate = make_square(File(token - 'A'), relative_rank(c, RANK_1));\n            ;\n            if (piece_on(rsqCandidate) == rook)\n                rsq = rsqCandidate;\n\n            // If the castling rights are available the king must always be between files 2 and 7 inclusive.\n            Square sq = relative_square(c, SQ_B1);\n            for (int i = 0; i < 6; ++i, ++sq)\n            {\n                if (piece_on(sq) == king)\n                    ksq = sq;\n            }\n        }\n        else\n        {\n            return PositionSetError(std::string(\"Invalid FEN. Expected castling rights. Got: \")\n                                    + std::string(1, token));\n        }\n\n        // Only apply castling rights if they can be valid.\n        if (ksq != SQ_NONE && rsq != SQ_NONE)\n            set_castling_right(c, rsq);\n    }\n\n    // 4. En passant square.\n    // Ignore if square is invalid or not on side to move relative rank 6.\n    bool          enpassant = false, legalEP = false;\n    unsigned char col = '-', row;\n    ss >> col;\n    if (col != '-')\n    {\n        if (!(ss >> row))\n            return PositionSetError(\"Invalid FEN. Unexpected end of stream.\");\n\n        if ((col >= 'a' && col <= 'h') && (row == (sideToMove == WHITE ? '6' : '3')))\n        {\n            st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));\n\n            Bitboard pawns = attacks_bb<PAWN>(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN);\n            Bitboard target = (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)));\n            Bitboard occ    = pieces() ^ target ^ st->epSquare;\n\n            // En passant square will be considered only if\n            // a) side to move have a pawn threatening epSquare\n            // b) there is an enemy pawn in front of epSquare\n            // c) there is no piece on epSquare or behind epSquare\n            enpassant = pawns && target\n                     && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));\n\n            // If no pawn can execute the en passant capture without leaving the king in check, don't record the epSquare\n            while (pawns)\n                legalEP |= !(attackers_to(square<KING>(sideToMove), occ ^ pop_lsb(pawns))\n                             & pieces(~sideToMove) & ~target);\n        }\n        else\n            return PositionSetError(\"Invalid FEN. Invalid en-passant square.\");\n    }\n\n    if (!enpassant || !legalEP)\n        st->epSquare = SQ_NONE;\n\n    // 5-6. Halfmove clock and fullmove number\n    ss >> std::skipws >> st->rule50 >> gamePly;\n\n    // Normally values larger than 99 would be pointless but we do support ignoring 50 move rule for TB purposes.\n    // Limit at 2**15 as it's used multiplicativly with position evaluation during search.\n    if (st->rule50 < 0 || st->rule50 > 32767)\n        return PositionSetError(\"Unsupported position. Rule50 counter out of range.\");\n\n    if (gamePly < 0 || gamePly > 100000)\n        return PositionSetError(\"Unsupported position. Game ply out of range.\");\n\n    // Convert from fullmove starting from 1 to gamePly starting from 0,\n    // handle also common incorrect FEN with fullmove = 0.\n    gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);\n\n    chess960 = isChess960;\n    set_state();\n\n    if (attackers_to_exist(square<KING>(~sideToMove), pieces(), sideToMove))\n        return PositionSetError(\"Unsupported position. King can be captured.\");\n\n    assert(pos_is_ok());\n\n    return std::nullopt;\n}\n\n\n// Helper function used to set castling\n// rights given the corresponding color and the rook starting square.\nvoid Position::set_castling_right(Color c, Square rfrom) {\n\n    Square         kfrom = square<KING>(c);\n    CastlingRights cr    = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE);\n\n    st->castlingRights |= cr;\n    castlingRightsMask[kfrom] |= cr;\n    castlingRightsMask[rfrom] |= cr;\n    castlingRookSquare[cr] = rfrom;\n\n    Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);\n    Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);\n\n    castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom);\n}\n\n\n// Sets king attacks to detect if a move gives check\nvoid Position::set_check_info() const {\n\n    update_slider_blockers(WHITE);\n    update_slider_blockers(BLACK);\n\n    Square ksq = square<KING>(~sideToMove);\n\n    st->checkSquares[PAWN]   = attacks_bb<PAWN>(ksq, ~sideToMove);\n    st->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);\n    st->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());\n    st->checkSquares[ROOK]   = attacks_bb<ROOK>(ksq, pieces());\n    st->checkSquares[QUEEN]  = st->checkSquares[BISHOP] | st->checkSquares[ROOK];\n    st->checkSquares[KING]   = 0;\n}\n\n\n// Computes the hash keys of the position, and other\n// data that once computed is updated incrementally as moves are made.\n// The function is only used when a new position is set up\nvoid Position::set_state() const {\n\n    st->key               = 0;\n    st->minorPieceKey     = 0;\n    st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0;\n    st->pawnKey                                   = Zobrist::noPawns;\n    st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;\n    st->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);\n\n    set_check_info();\n\n    for (Bitboard b = pieces(); b;)\n    {\n        Square s  = pop_lsb(b);\n        Piece  pc = piece_on(s);\n        st->key ^= Zobrist::psq[pc][s];\n\n        if (type_of(pc) == PAWN)\n            st->pawnKey ^= Zobrist::psq[pc][s];\n\n        else\n        {\n            st->nonPawnKey[color_of(pc)] ^= Zobrist::psq[pc][s];\n\n            if (type_of(pc) != KING)\n            {\n                st->nonPawnMaterial[color_of(pc)] += PieceValue[pc];\n\n                if (type_of(pc) <= BISHOP)\n                    st->minorPieceKey ^= Zobrist::psq[pc][s];\n            }\n        }\n    }\n\n    if (st->epSquare != SQ_NONE)\n        st->key ^= Zobrist::enpassant[file_of(st->epSquare)];\n\n    if (sideToMove == BLACK)\n        st->key ^= Zobrist::side;\n\n    st->key ^= Zobrist::castling[st->castlingRights];\n    st->materialKey = compute_material_key();\n}\n\nKey Position::compute_material_key() const {\n    Key k = 0;\n    for (Piece pc : Pieces)\n        for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)\n            k ^= Zobrist::psq[pc][8 + cnt];\n    return k;\n}\n\n\n// Overload to initialize the position object with the given endgame code string\n// like \"KBPKN\". It's mainly a helper to get the material key out of an endgame code.\nstd::optional<PositionSetError> Position::set(const string& code, Color c, StateInfo* si) {\n\n    assert(code[0] == 'K');\n\n    string sides[] = {code.substr(code.find('K', 1)),                                // Weak\n                      code.substr(0, std::min(code.find('v'), code.find('K', 1)))};  // Strong\n\n    assert(sides[0].length() > 0 && sides[0].length() < 8);\n    assert(sides[1].length() > 0 && sides[1].length() < 8);\n\n    std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);\n\n    string fenStr = \"8/\" + sides[0] + char(8 - sides[0].length() + '0') + \"/8/8/8/8/\" + sides[1]\n                  + char(8 - sides[1].length() + '0') + \"/8 w - - 0 10\";\n\n    return set(fenStr, false, si);\n}\n\n\n// Returns a FEN representation of the position. In case of\n// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.\nstring Position::fen() const {\n\n    int                emptyCnt;\n    std::ostringstream ss;\n\n    for (Rank r = RANK_8;; --r)\n    {\n        for (File f = FILE_A; f <= FILE_H; ++f)\n        {\n            for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f)\n                ++emptyCnt;\n\n            if (emptyCnt)\n                ss << emptyCnt;\n\n            if (f <= FILE_H)\n                ss << PieceToChar[piece_on(make_square(f, r))];\n        }\n\n        if (r == RANK_1)\n            break;\n        ss << '/';\n    }\n\n    ss << (sideToMove == WHITE ? \" w \" : \" b \");\n\n    if (can_castle(WHITE_OO))\n        ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K');\n\n    if (can_castle(WHITE_OOO))\n        ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');\n\n    if (can_castle(BLACK_OO))\n        ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k');\n\n    if (can_castle(BLACK_OOO))\n        ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');\n\n    if (!can_castle(ANY_CASTLING))\n        ss << '-';\n\n    ss << (ep_square() == SQ_NONE ? \" - \" : \" \" + UCIEngine::square(ep_square()) + \" \")\n       << st->rule50 << \" \" << 1 + (gamePly - (sideToMove == BLACK)) / 2;\n\n    return ss.str();\n}\n\n// Calculates st->blockersForKing[c] and st->pinners[~c],\n// which store respectively the pieces preventing king of color c from being in check\n// and the slider pieces of color ~c pinning pieces of color c to the king.\nvoid Position::update_slider_blockers(Color c) const {\n\n    Square ksq = square<KING>(c);\n\n    st->blockersForKing[c] = 0;\n    st->pinners[~c]        = 0;\n\n    // Snipers are sliders that attack 's' when a piece and other snipers are removed\n    Bitboard snipers = ((attacks_bb<ROOK>(ksq) & pieces(QUEEN, ROOK))\n                        | (attacks_bb<BISHOP>(ksq) & pieces(QUEEN, BISHOP)))\n                     & pieces(~c);\n    Bitboard occupancy = pieces() ^ snipers;\n\n    while (snipers)\n    {\n        Square   sniperSq = pop_lsb(snipers);\n        Bitboard b        = between_bb(ksq, sniperSq) & occupancy;\n\n        if (b && !more_than_one(b))\n        {\n            st->blockersForKing[c] |= b;\n            if (b & pieces(c))\n                st->pinners[~c] |= sniperSq;\n        }\n    }\n}\n\n\n// Computes a bitboard of all pieces which attack a given square.\n// Slider attacks use the occupied bitboard to indicate occupancy.\nBitboard Position::attackers_to(Square s, Bitboard occupied) const {\n\n    return (attacks_bb<ROOK>(s, occupied) & pieces(ROOK, QUEEN))\n         | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))\n         | (attacks_bb<PAWN>(s, BLACK) & pieces(WHITE, PAWN))\n         | (attacks_bb<PAWN>(s, WHITE) & pieces(BLACK, PAWN))\n         | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT)) | (attacks_bb<KING>(s) & pieces(KING));\n}\n\nbool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const {\n\n    return (attacks_bb<ROOK>(s, occupied) & pieces(c, ROOK, QUEEN))\n        || (attacks_bb<BISHOP>(s, occupied) & pieces(c, BISHOP, QUEEN))\n        || (attacks_bb<PAWN>(s, ~c) & pieces(c, PAWN))\n        || (attacks_bb<KNIGHT>(s) & pieces(c, KNIGHT)) || (attacks_bb<KING>(s) & pieces(c, KING));\n}\n\n// Tests whether a pseudo-legal move is legal\nbool Position::legal(Move m) const {\n\n    assert(m.is_ok());\n\n    Color  us   = sideToMove;\n    Square from = m.from_sq();\n    Square to   = m.to_sq();\n\n    assert(color_of(moved_piece(m)) == us);\n    assert(piece_on(square<KING>(us)) == make_piece(us, KING));\n\n    // En passant captures are a tricky special case. Because they are rather\n    // uncommon, we do it simply by testing whether the king is attacked after\n    // the move is made.\n    if (m.type_of() == EN_PASSANT)\n    {\n        Square   ksq      = square<KING>(us);\n        Square   capsq    = to - pawn_push(us);\n        Bitboard occupied = (pieces() ^ from ^ capsq) | to;\n\n        assert(to == ep_square());\n        assert(moved_piece(m) == make_piece(us, PAWN));\n        assert(piece_on(capsq) == make_piece(~us, PAWN));\n        assert(piece_on(to) == NO_PIECE);\n\n        return !(attacks_bb<ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))\n            && !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));\n    }\n\n    // Castling moves generation does not check if the castling path is clear of\n    // enemy attacks, it is delayed at a later time: now!\n    if (m.type_of() == CASTLING)\n    {\n        // After castling, the rook and king final positions are the same in\n        // Chess960 as they would be in standard chess.\n        to             = relative_square(us, to > from ? SQ_G1 : SQ_C1);\n        Direction step = to > from ? WEST : EAST;\n\n        for (Square s = to; s != from; s += step)\n            if (attackers_to_exist(s, pieces(), ~us))\n                return false;\n\n        // In case of Chess960, verify if the Rook blocks some checks.\n        // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.\n        return !chess960 || !(blockers_for_king(us) & m.to_sq());\n    }\n\n    // If the moving piece is a king, check whether the destination square is\n    // attacked by the opponent.\n    if (type_of(piece_on(from)) == KING)\n        return !(attackers_to_exist(to, pieces() ^ from, ~us));\n\n    // A non-king move is legal if and only if it is not pinned or it\n    // is moving along the ray towards or away from the king.\n    return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING);\n}\n\n\n// Takes a random move and tests whether the move is\n// pseudo-legal. It is used to validate moves from TT that can be corrupted\n// due to SMP concurrent access or hash position key aliasing.\nbool Position::pseudo_legal(const Move m) const {\n\n    Color  us   = sideToMove;\n    Square from = m.from_sq();\n    Square to   = m.to_sq();\n    Piece  pc   = moved_piece(m);\n\n    // Use a slower but simpler function for uncommon cases\n    // yet we skip the legality check of MoveList<LEGAL>().\n    if (m.type_of() != NORMAL)\n        return checkers() ? MoveList<EVASIONS>(*this).contains(m)\n                          : MoveList<NON_EVASIONS>(*this).contains(m);\n\n    // Is not a promotion, so the promotion piece must be empty\n    assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE);\n\n    // If the 'from' square is not occupied by a piece belonging to the side to\n    // move, the move is obviously not legal.\n    if (pc == NO_PIECE || color_of(pc) != us)\n        return false;\n\n    // The destination square cannot be occupied by a friendly piece\n    if (pieces(us) & to)\n        return false;\n\n    // Handle the special case of a pawn move\n    if (type_of(pc) == PAWN)\n    {\n        // We have already handled promotion moves, so destination cannot be on the 8th/1st rank\n        if ((Rank8BB | Rank1BB) & to)\n            return false;\n\n        // Check if it's a valid capture, single push, or double push\n        const bool isCapture    = bool(attacks_bb<PAWN>(from, us) & pieces(~us) & to);\n        const bool isSinglePush = (from + pawn_push(us) == to) && empty(to);\n        const bool isDoublePush = (from + 2 * pawn_push(us) == to)\n                               && (relative_rank(us, from) == RANK_2) && empty(to)\n                               && empty(to - pawn_push(us));\n\n        if (!(isCapture || isSinglePush || isDoublePush))\n            return false;\n    }\n    else if (!(attacks_bb(type_of(pc), from, pieces()) & to))\n        return false;\n\n    // Evasions generator already takes care to avoid some kind of illegal moves\n    // and legal() relies on this. We therefore have to take care that the same\n    // kind of moves are filtered out here.\n    if (checkers())\n    {\n        if (type_of(pc) != KING)\n        {\n            // Double check? In this case, a king move is required\n            if (more_than_one(checkers()))\n                return false;\n\n            // Our move must be a blocking interposition or a capture of the checking piece\n            if (!(between_bb(square<KING>(us), lsb(checkers())) & to))\n                return false;\n        }\n        // In case of king moves under check we have to remove the king so as to catch\n        // invalid moves like b1a1 when opposite queen is on c1.\n        else if (attackers_to_exist(to, pieces() ^ from, ~us))\n            return false;\n    }\n\n    return true;\n}\n\n\n// Tests whether a pseudo-legal move gives a check\nbool Position::gives_check(Move m) const {\n\n    assert(m.is_ok());\n    assert(color_of(moved_piece(m)) == sideToMove);\n\n    Square from = m.from_sq();\n    Square to   = m.to_sq();\n\n    // Is there a direct check?\n    if (check_squares(type_of(piece_on(from))) & to)\n        return true;\n\n    // Is there a discovered check?\n    if (blockers_for_king(~sideToMove) & from)\n        return !(line_bb(from, to) & pieces(~sideToMove, KING)) || m.type_of() == CASTLING;\n\n    switch (m.type_of())\n    {\n    case NORMAL :\n        return false;\n\n    case PROMOTION :\n        return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING);\n\n    // En passant capture with check? We have already handled the case of direct\n    // checks and ordinary discovered check, so the only case we need to handle\n    // is the unusual case of a discovered check through the captured pawn.\n    case EN_PASSANT : {\n        Square   capsq = make_square(file_of(to), rank_of(from));\n        Bitboard b     = (pieces() ^ from ^ capsq) | to;\n\n        return (attacks_bb<ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))\n             | (attacks_bb<BISHOP>(square<KING>(~sideToMove), b)\n                & pieces(sideToMove, QUEEN, BISHOP));\n    }\n    default :  //CASTLING\n    {\n        // Castling is encoded as 'king captures the rook'\n        Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);\n\n        return check_squares(ROOK) & rto;\n    }\n    }\n}\n\n\n// Makes a move, and saves all information necessary\n// to a StateInfo object. The move is assumed to be legal. Pseudo-legal\n// moves should be filtered out before this function is called.\n// If a pointer to the TT table is passed, the entry for the new position\n// will be prefetched, and likewise for shared history.\nvoid Position::do_move(Move                      m,\n                       StateInfo&                newSt,\n                       bool                      givesCheck,\n                       DirtyPiece&               dp,\n                       DirtyThreats&             dts,\n                       const TranspositionTable* tt      = nullptr,\n                       const SharedHistories*    history = nullptr) {\n\n    assert(m.is_ok());\n    assert(&newSt != st);\n\n    Key k = st->key ^ Zobrist::side;\n\n    // Copy some fields of the old state to our new StateInfo object except the\n    // ones which are going to be recalculated from scratch anyway and then switch\n    // our state pointer to point to the new (ready to be updated) state.\n    std::memcpy(&newSt, st, offsetof(StateInfo, key));\n    newSt.previous = st;\n    st             = &newSt;\n\n    // Increment ply counters. In particular, rule50 will be reset to zero later on\n    // in case of a capture or a pawn move.\n    ++gamePly;\n    ++st->rule50;\n    ++st->pliesFromNull;\n\n    Color  us       = sideToMove;\n    Color  them     = ~us;\n    Square from     = m.from_sq();\n    Square to       = m.to_sq();\n    Piece  pc       = piece_on(from);\n    Piece  captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to);\n\n    dp.pc             = pc;\n    dp.from           = from;\n    dp.to             = to;\n    dp.add_sq         = SQ_NONE;\n    dts.us            = us;\n    dts.prevKsq       = square<KING>(us);\n    dts.threatenedSqs = dts.threateningSqs = 0;\n\n    assert(color_of(pc) == us);\n    assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us));\n    assert(type_of(captured) != KING);\n\n    if (m.type_of() == CASTLING)\n    {\n        assert(pc == make_piece(us, KING));\n        assert(captured == make_piece(us, ROOK));\n\n        Square rfrom, rto;\n        do_castling<true>(us, from, to, rfrom, rto, &dts, &dp);\n\n        k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];\n        st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];\n        captured = NO_PIECE;\n    }\n    else if (captured)\n    {\n        Square capsq = to;\n\n        // If the captured piece is a pawn, update pawn hash key, otherwise\n        // update non-pawn material.\n        if (type_of(captured) == PAWN)\n        {\n            if (m.type_of() == EN_PASSANT)\n            {\n                capsq -= pawn_push(us);\n\n                assert(pc == make_piece(us, PAWN));\n                assert(to == st->epSquare);\n                assert(relative_rank(us, to) == RANK_6);\n                assert(piece_on(to) == NO_PIECE);\n                assert(piece_on(capsq) == make_piece(them, PAWN));\n\n                // Update board and piece lists in ep case, normal captures are updated later\n                remove_piece(capsq, &dts);\n            }\n\n            st->pawnKey ^= Zobrist::psq[captured][capsq];\n        }\n        else\n        {\n            st->nonPawnMaterial[them] -= PieceValue[captured];\n            st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq];\n\n            if (type_of(captured) <= BISHOP)\n                st->minorPieceKey ^= Zobrist::psq[captured][capsq];\n        }\n\n        dp.remove_pc = captured;\n        dp.remove_sq = capsq;\n\n        k ^= Zobrist::psq[captured][capsq];\n        st->materialKey ^=\n          Zobrist::psq[captured][8 + pieceCount[captured] - (m.type_of() != EN_PASSANT)];\n\n        // Reset rule 50 counter\n        st->rule50 = 0;\n    }\n    else\n        dp.remove_sq = SQ_NONE;\n\n    // Update hash key\n    k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];\n\n    // Reset en passant square\n    if (st->epSquare != SQ_NONE)\n    {\n        k ^= Zobrist::enpassant[file_of(st->epSquare)];\n        st->epSquare = SQ_NONE;\n    }\n\n    // Update castling rights.\n    k ^= Zobrist::castling[st->castlingRights];\n    st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);\n    k ^= Zobrist::castling[st->castlingRights];\n\n    // Move the piece. The tricky Chess960 castling is handled earlier\n    if (m.type_of() != CASTLING)\n    {\n        if (captured && m.type_of() != EN_PASSANT)\n        {\n            remove_piece(from, &dts);\n            swap_piece(to, pc, &dts);\n        }\n        else\n            move_piece(from, to, &dts);\n    }\n\n    // If the moving piece is a pawn do some special extra work\n    if (type_of(pc) == PAWN)\n    {\n        // Check if the en passant square needs to be set. Accurate e.p. info is needed\n        // for correct zobrist key generation and 3-fold checking.\n        if ((int(to) ^ int(from)) == 16)\n        {\n            Square   epSquare = to - pawn_push(us);\n            Bitboard pawns    = attacks_bb<PAWN>(epSquare, us) & pieces(them, PAWN);\n\n            // If there are no pawns attacking the ep square, ep is not possible.\n            if (pawns)\n            {\n                Square   ksq         = square<KING>(them);\n                Bitboard notBlockers = ~st->previous->blockersForKing[them];\n                bool     noDiscovery = (from & notBlockers) || file_of(from) == file_of(ksq);\n\n                // If the pawn gives discovered check, ep is never legal. Else, if at least one\n                // pawn was not a blocker for the enemy king or lies on the same line as the\n                // enemy king and en passant square, a legal capture exists.\n                if (noDiscovery && (pawns & (notBlockers | line_bb(epSquare, ksq))))\n                {\n                    st->epSquare = epSquare;\n                    k ^= Zobrist::enpassant[file_of(epSquare)];\n                }\n            }\n        }\n\n        else if (m.type_of() == PROMOTION)\n        {\n            Piece     promotion     = make_piece(us, m.promotion_type());\n            PieceType promotionType = type_of(promotion);\n\n            assert(relative_rank(us, to) == RANK_8);\n            assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);\n\n            swap_piece(to, promotion, &dts);\n\n            dp.add_pc = promotion;\n            dp.add_sq = to;\n            dp.to     = SQ_NONE;\n\n            // Update hash keys\n            // Zobrist::psq[pc][to] is zero, so we don't need to clear it\n            k ^= Zobrist::psq[promotion][to];\n            st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1]\n                             ^ Zobrist::psq[pc][8 + pieceCount[pc]];\n            st->nonPawnKey[us] ^= Zobrist::psq[promotion][to];\n\n            if (promotionType <= BISHOP)\n                st->minorPieceKey ^= Zobrist::psq[promotion][to];\n\n            // Update material\n            st->nonPawnMaterial[us] += PieceValue[promotion];\n        }\n\n        // Update pawn hash key\n        st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];\n\n        // Reset rule 50 draw counter\n        st->rule50 = 0;\n    }\n\n    else\n    {\n        st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];\n\n        if (type_of(pc) <= BISHOP)\n            st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];\n    }\n\n    // Update the key with the final value\n    st->key = k;\n    if (tt)\n        prefetch(tt->first_entry(key()));\n\n    if (history)\n    {\n        prefetch(&history->pawn_entry(*this)[pc][to]);\n        prefetch(&history->pawn_correction_entry(*this));\n        prefetch(&history->minor_piece_correction_entry(*this));\n        prefetch(&history->nonpawn_correction_entry<WHITE>(*this));\n        prefetch(&history->nonpawn_correction_entry<BLACK>(*this));\n    }\n\n    // Set capture piece\n    st->capturedPiece = captured;\n\n    // Calculate checkers bitboard (if move gives check)\n    st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;\n\n    sideToMove = ~sideToMove;\n\n    // Update king attacks used for fast check detection\n    set_check_info();\n\n    // Calculate the repetition info. It is the ply distance from the previous\n    // occurrence of the same position, negative in the 3-fold case, or zero\n    // if the position was not repeated.\n    st->repetition = 0;\n    int end        = std::min(st->rule50, st->pliesFromNull);\n    if (end >= 4)\n    {\n        StateInfo* stp = st->previous->previous;\n        for (int i = 4; i <= end; i += 2)\n        {\n            stp = stp->previous->previous;\n            if (stp->key == st->key)\n            {\n                st->repetition = stp->repetition ? -i : i;\n                break;\n            }\n        }\n    }\n\n    dts.ksq = square<KING>(us);\n\n    assert(pos_is_ok());\n\n    assert(dp.pc != NO_PIECE);\n    assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE));\n    assert(dp.from != SQ_NONE);\n    assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING));\n}\n\n\n// Unmakes a move. When it returns, the position should\n// be restored to exactly the same state as before the move was made.\nvoid Position::undo_move(Move m) {\n\n    assert(m.is_ok());\n\n    sideToMove = ~sideToMove;\n\n    Color  us   = sideToMove;\n    Square from = m.from_sq();\n    Square to   = m.to_sq();\n    Piece  pc   = piece_on(to);\n\n    assert(empty(from) || m.type_of() == CASTLING);\n    assert(type_of(st->capturedPiece) != KING);\n\n    if (m.type_of() == PROMOTION)\n    {\n        assert(relative_rank(us, to) == RANK_8);\n        assert(type_of(pc) == m.promotion_type());\n        assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);\n\n        pc = make_piece(us, PAWN);\n        swap_piece(to, pc);\n    }\n\n    if (m.type_of() == CASTLING)\n    {\n        Square rfrom, rto;\n        do_castling<false>(us, from, to, rfrom, rto);\n    }\n    else\n    {\n        move_piece(to, from);  // Put the piece back at the source square\n\n        if (st->capturedPiece)\n        {\n            Square capsq = to;\n\n            if (m.type_of() == EN_PASSANT)\n            {\n                capsq -= pawn_push(us);\n\n                assert(type_of(pc) == PAWN);\n                assert(to == st->previous->epSquare);\n                assert(relative_rank(us, to) == RANK_6);\n                assert(piece_on(capsq) == NO_PIECE);\n                assert(st->capturedPiece == make_piece(~us, PAWN));\n            }\n\n            put_piece(st->capturedPiece, capsq);  // Restore the captured piece\n        }\n    }\n\n    // Finally point our state pointer back to the previous state\n    st = st->previous;\n    --gamePly;\n\n    assert(pos_is_ok());\n}\n\ntemplate<bool PutPiece>\ninline void add_dirty_threat(\n  DirtyThreats* const dts, Piece pc, Piece threatened, Square s, Square threatenedSq) {\n    if (PutPiece)\n    {\n        dts->threatenedSqs |= threatenedSq;\n        dts->threateningSqs |= s;\n    }\n\n    dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece});\n}\n\n#ifdef USE_AVX512ICL\n// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats\n// present at the given bitboard.\ntemplate<int SqShift, int PcShift>\nvoid write_multiple_dirties(const Position& p,\n                            Bitboard        mask,\n                            DirtyThreat     dt_template,\n                            DirtyThreats*   dts) {\n    static_assert(sizeof(DirtyThreat) == 4);\n\n    const __m512i board      = _mm512_loadu_si512(p.piece_array().data());\n    const __m512i AllSquares = _mm512_set_epi8(\n      63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,\n      40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18,\n      17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n\n    const int dt_count = popcount(mask);\n    assert(dt_count <= 16);\n\n    const __m512i template_v = _mm512_set1_epi32(dt_template.raw());\n    auto*         write      = dts->list.make_space(dt_count);\n\n    // Extract the list of squares and upconvert to 32 bits. There are never more than 16\n    // incoming threats so this is sufficient.\n    __m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares);\n    threat_squares         = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares));\n\n    __m512i threat_pieces =\n      _mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board);\n\n    // Shift the piece and square into place\n    threat_squares = _mm512_slli_epi32(threat_squares, SqShift);\n    threat_pieces  = _mm512_slli_epi32(threat_pieces, PcShift);\n\n    const __m512i dirties =\n      _mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */);\n    _mm512_storeu_si512(write, dirties);\n}\n#endif\n\ntemplate<bool PutPiece, bool ComputeRay>\nvoid Position::update_piece_threats(Piece                     pc,\n                                    Square                    s,\n                                    DirtyThreats* const       dts,\n                                    [[maybe_unused]] Bitboard noRaysContaining) const {\n    const Bitboard occupied     = pieces();\n    const Bitboard rookQueens   = pieces(ROOK, QUEEN);\n    const Bitboard bishopQueens = pieces(BISHOP, QUEEN);\n    const Bitboard rAttacks     = attacks_bb<ROOK>(s, occupied);\n    const Bitboard bAttacks     = attacks_bb<BISHOP>(s, occupied);\n    const Bitboard kings        = pieces(KING);\n    Bitboard       occupiedNoK  = occupied ^ kings;\n\n    Bitboard sliders         = (rookQueens & rAttacks) | (bishopQueens & bAttacks);\n    auto     process_sliders = [&](bool addDirectAttacks) {\n        while (sliders)\n        {\n            Square sliderSq = pop_lsb(sliders);\n            Piece  slider   = piece_on(sliderSq);\n\n            const Bitboard ray        = RayPassBB[sliderSq][s];\n            const Bitboard discovered = ray & (rAttacks | bAttacks) & occupiedNoK;\n\n            assert(!more_than_one(discovered));\n            if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining)\n            {\n                const Square threatenedSq = lsb(discovered);\n                const Piece  threatenedPc = piece_on(threatenedSq);\n                add_dirty_threat<!PutPiece>(dts, slider, threatenedPc, sliderSq, threatenedSq);\n            }\n\n            if (addDirectAttacks)\n                add_dirty_threat<PutPiece>(dts, slider, pc, sliderSq, s);\n        }\n    };\n\n    if (type_of(pc) == KING)\n    {\n        if constexpr (ComputeRay)\n            process_sliders(false);\n        return;\n    }\n\n\n    const Bitboard knights    = pieces(KNIGHT);\n    const Bitboard whitePawns = pieces(WHITE, PAWN);\n    const Bitboard blackPawns = pieces(BLACK, PAWN);\n\n\n    Bitboard threatened = attacks_bb(pc, s, occupied) & occupiedNoK;\n    Bitboard incoming_threats =\n      (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb<PAWN>(s, WHITE) & blackPawns)\n      | (attacks_bb<PAWN>(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings);\n\n#ifdef USE_AVX512ICL\n    if constexpr (PutPiece)\n    {\n        dts->threatenedSqs |= threatened;\n        // A bit may only be set if that square actually produces a threat, so we\n        // must guard setting the square accordingly\n        dts->threateningSqs |= Bitboard(bool(threatened)) << s;\n    }\n\n    DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece};\n    write_multiple_dirties<DirtyThreat::ThreatenedSqOffset, DirtyThreat::ThreatenedPcOffset>(\n      *this, threatened, dt_template, dts);\n\n    Bitboard all_attackers = sliders | incoming_threats;\n\n    if constexpr (PutPiece)\n    {\n        dts->threatenedSqs |= Bitboard(bool(all_attackers)) << s;  // same as above\n        dts->threateningSqs |= all_attackers;\n    }\n\n    dt_template = {NO_PIECE, pc, Square(0), s, PutPiece};\n    write_multiple_dirties<DirtyThreat::PcSqOffset, DirtyThreat::PcOffset>(*this, all_attackers,\n                                                                           dt_template, dts);\n#else\n    while (threatened)\n    {\n        Square threatenedSq = pop_lsb(threatened);\n        Piece  threatenedPc = piece_on(threatenedSq);\n\n        assert(threatenedSq != s);\n        assert(threatenedPc);\n\n        add_dirty_threat<PutPiece>(dts, pc, threatenedPc, s, threatenedSq);\n    }\n#endif\n\n    if constexpr (ComputeRay)\n    {\n#ifndef USE_AVX512ICL\n        process_sliders(true);\n#else  // for ICL, direct threats were processed earlier (all_attackers)\n        process_sliders(false);\n#endif\n    }\n    else\n    {\n        incoming_threats |= sliders;\n    }\n\n#ifndef USE_AVX512ICL\n    while (incoming_threats)\n    {\n        Square srcSq = pop_lsb(incoming_threats);\n        Piece  srcPc = piece_on(srcSq);\n\n        assert(srcSq != s);\n        assert(srcPc != NO_PIECE);\n\n        add_dirty_threat<PutPiece>(dts, srcPc, pc, srcSq, s);\n    }\n#endif\n}\n\n// Helper used to do/undo a castling move. This is a bit\n// tricky in Chess960 where from/to squares can overlap.\ntemplate<bool Do>\nvoid Position::do_castling(Color               us,\n                           Square              from,\n                           Square&             to,\n                           Square&             rfrom,\n                           Square&             rto,\n                           DirtyThreats* const dts,\n                           DirtyPiece* const   dp) {\n\n    bool kingSide = to > from;\n    rfrom         = to;  // Castling is encoded as \"king captures friendly rook\"\n    rto           = relative_square(us, kingSide ? SQ_F1 : SQ_D1);\n    to            = relative_square(us, kingSide ? SQ_G1 : SQ_C1);\n\n    assert(!Do || dp);\n\n    if (Do)\n    {\n        dp->to        = to;\n        dp->remove_pc = dp->add_pc = make_piece(us, ROOK);\n        dp->remove_sq              = rfrom;\n        dp->add_sq                 = rto;\n    }\n\n    // Remove both pieces first since squares could overlap in Chess960\n    remove_piece(Do ? from : to, dts);\n    remove_piece(Do ? rfrom : rto, dts);\n    put_piece(make_piece(us, KING), Do ? to : from, dts);\n    put_piece(make_piece(us, ROOK), Do ? rto : rfrom, dts);\n}\n\n\n// Used to do a \"null move\": it flips\n// the side to move without executing any move on the board.\nvoid Position::do_null_move(StateInfo& newSt) {\n\n    assert(!checkers());\n    assert(&newSt != st);\n\n    std::memcpy(&newSt, st, sizeof(StateInfo));\n\n    newSt.previous = st;\n    st             = &newSt;\n\n    if (st->epSquare != SQ_NONE)\n    {\n        st->key ^= Zobrist::enpassant[file_of(st->epSquare)];\n        st->epSquare = SQ_NONE;\n    }\n\n    st->key ^= Zobrist::side;\n\n    st->pliesFromNull = 0;\n\n    sideToMove = ~sideToMove;\n\n    set_check_info();\n\n    st->repetition = 0;\n\n    assert(pos_is_ok());\n}\n\n\n// Must be used to undo a \"null move\"\nvoid Position::undo_null_move() {\n\n    assert(!checkers());\n\n    st         = st->previous;\n    sideToMove = ~sideToMove;\n}\n\n\n// Tests if the SEE (Static Exchange Evaluation)\n// value of move is greater or equal to the given threshold. We'll use an\n// algorithm similar to alpha-beta pruning with a null window.\nbool Position::see_ge(Move m, int threshold) const {\n\n    assert(m.is_ok());\n\n    // Only deal with normal moves, assume others pass a simple SEE\n    if (m.type_of() != NORMAL)\n        return VALUE_ZERO >= threshold;\n\n    Square from = m.from_sq(), to = m.to_sq();\n\n    assert(piece_on(from) != NO_PIECE);\n\n    int swap = PieceValue[piece_on(to)] - threshold;\n    if (swap < 0)\n        return false;\n\n    swap = PieceValue[piece_on(from)] - swap;\n    if (swap <= 0)\n        return true;\n\n    assert(color_of(piece_on(from)) == sideToMove);\n    Bitboard occupied  = pieces() ^ from ^ to;  // xoring to is important for pinned piece logic\n    Color    stm       = sideToMove;\n    Bitboard attackers = attackers_to(to, occupied);\n    Bitboard stmAttackers, bb;\n    int      res = 1;\n\n    while (true)\n    {\n        stm = ~stm;\n        attackers &= occupied;\n\n        // If stm has no more attackers then give up: stm loses\n        if (!(stmAttackers = attackers & pieces(stm)))\n            break;\n\n        // Don't allow pinned pieces to attack as long as there are\n        // pinners on their original square.\n        if (pinners(~stm) & occupied)\n        {\n            stmAttackers &= ~blockers_for_king(stm);\n\n            if (!stmAttackers)\n                break;\n        }\n\n        res ^= 1;\n\n        // Locate and remove the next least valuable attacker, and add to\n        // the bitboard 'attackers' any X-ray attackers behind it.\n        if ((bb = stmAttackers & pieces(PAWN)))\n        {\n            if ((swap = PawnValue - swap) < res)\n                break;\n            occupied ^= least_significant_square_bb(bb);\n\n            attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);\n        }\n\n        else if ((bb = stmAttackers & pieces(KNIGHT)))\n        {\n            if ((swap = KnightValue - swap) < res)\n                break;\n            occupied ^= least_significant_square_bb(bb);\n        }\n\n        else if ((bb = stmAttackers & pieces(BISHOP)))\n        {\n            if ((swap = BishopValue - swap) < res)\n                break;\n            occupied ^= least_significant_square_bb(bb);\n\n            attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);\n        }\n\n        else if ((bb = stmAttackers & pieces(ROOK)))\n        {\n            if ((swap = RookValue - swap) < res)\n                break;\n            occupied ^= least_significant_square_bb(bb);\n\n            attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);\n        }\n\n        else if ((bb = stmAttackers & pieces(QUEEN)))\n        {\n            swap = QueenValue - swap;\n            //  implies that the previous recapture was done by a higher rated piece than a Queen (King is excluded)\n            assert(swap >= res);\n            occupied ^= least_significant_square_bb(bb);\n\n            attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))\n                       | (attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN));\n        }\n\n        else  // KING\n              // If we \"capture\" with the king but the opponent still has attackers,\n              // reverse the result.\n            return (attackers & ~pieces(stm)) ? res ^ 1 : res;\n    }\n\n    return bool(res);\n}\n\n// Tests whether the position is drawn by 50-move rule\n// or by repetition. It does not detect stalemates.\nbool Position::is_draw(int ply) const {\n\n    if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))\n        return true;\n\n    return is_repetition(ply);\n}\n\n// Return a draw score if a position repeats once earlier but strictly\n// after the root, or repeats twice before or at the root.\nbool Position::is_repetition(int ply) const { return st->repetition && st->repetition < ply; }\n\n// Tests whether there has been at least one repetition\n// of positions since the last capture or pawn move.\nbool Position::has_repeated() const {\n\n    StateInfo* stc = st;\n    int        end = std::min(st->rule50, st->pliesFromNull);\n    while (end-- >= 4)\n    {\n        if (stc->repetition)\n            return true;\n\n        stc = stc->previous;\n    }\n    return false;\n}\n\n\n// Tests if the position has a move which draws by repetition.\n// This function accurately matches the outcome of is_draw() over all legal moves.\nbool Position::upcoming_repetition(int ply) const {\n\n    int j;\n\n    int end = std::min(st->rule50, st->pliesFromNull);\n\n    if (end < 3)\n        return false;\n\n    Key        originalKey = st->key;\n    StateInfo* stp         = st->previous;\n    Key        other       = originalKey ^ stp->key ^ Zobrist::side;\n\n    for (int i = 3; i <= end; i += 2)\n    {\n        stp = stp->previous;\n        other ^= stp->key ^ stp->previous->key ^ Zobrist::side;\n        stp = stp->previous;\n\n        if (other != 0)\n            continue;\n\n        Key moveKey = originalKey ^ stp->key;\n        if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey))\n        {\n            Move   move = cuckooMove[j];\n            Square s1   = move.from_sq();\n            Square s2   = move.to_sq();\n\n            if (!((between_bb(s1, s2) ^ s2) & pieces()))\n            {\n                if (ply > i)\n                    return true;\n\n                // For nodes before or at the root, check that the move is a\n                // repetition rather than a move to the current position.\n                if (stp->repetition)\n                    return true;\n            }\n        }\n    }\n    return false;\n}\n\n\n// Flips position with the white and black sides reversed. This\n// is only useful for debugging e.g. for finding evaluation symmetry bugs.\nvoid Position::flip() {\n\n    string            f, token;\n    std::stringstream ss(fen());\n\n    for (Rank r = RANK_8;; --r)  // Piece placement\n    {\n        std::getline(ss, token, r > RANK_1 ? '/' : ' ');\n        f.insert(0, token + (f.empty() ? \" \" : \"/\"));\n\n        if (r == RANK_1)\n            break;\n    }\n\n    ss >> token;                        // Active color\n    f += (token == \"w\" ? \"B \" : \"W \");  // Will be lowercased later\n\n    ss >> token;  // Castling availability\n    f += token + \" \";\n\n    std::transform(f.begin(), f.end(), f.begin(),\n                   [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); });\n\n    ss >> token;  // En passant square\n    f += (token == \"-\" ? token : token.replace(1, 1, token[1] == '3' ? \"6\" : \"3\"));\n\n    std::getline(ss, token);  // Half and full moves\n    f += token;\n\n    set(f, is_chess960(), st);\n\n    assert(pos_is_ok());\n}\n\n\nbool Position::material_key_is_ok() const { return compute_material_key() == st->materialKey; }\n\n\n// Performs some consistency checks for the position object\n// and raise an assert if something wrong is detected.\n// This is meant to be helpful when debugging.\nbool Position::pos_is_ok() const {\n\n    constexpr bool Fast = true;  // Quick (default) or full check?\n\n    if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square<KING>(WHITE)) != W_KING\n        || piece_on(square<KING>(BLACK)) != B_KING\n        || (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6))\n        assert(0 && \"pos_is_ok: Default\");\n\n    if (Fast)\n        return true;\n\n    if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1\n        || attackers_to_exist(square<KING>(~sideToMove), pieces(), sideToMove))\n        assert(0 && \"pos_is_ok: Kings\");\n\n    if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8)\n        assert(0 && \"pos_is_ok: Pawns\");\n\n\n    if (ep_square() != SQ_NONE)\n    {\n        Square ksq = square<KING>(sideToMove);\n\n        Bitboard captured = (ep_square() + pawn_push(~sideToMove)) & pieces(~sideToMove, PAWN);\n        Bitboard pawns    = attacks_bb<PAWN>(ep_square(), ~sideToMove) & pieces(sideToMove, PAWN);\n        Bitboard potentialCheckers = pieces(~sideToMove) ^ captured;\n\n        if (!captured || !pawns\n            || ((attackers_to(ksq, pieces() ^ captured ^ ep_square() ^ lsb(pawns))\n                 & potentialCheckers)\n                && (attackers_to(ksq, pieces() ^ captured ^ ep_square() ^ msb(pawns))\n                    & potentialCheckers)))\n            assert(0 && \"pos_is_ok: En passant square\");\n    }\n\n    if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces()\n        || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16)\n        assert(0 && \"pos_is_ok: Bitboards\");\n\n    for (PieceType p1 = PAWN; p1 <= KING; ++p1)\n        for (PieceType p2 = PAWN; p2 <= KING; ++p2)\n            if (p1 != p2 && (pieces(p1) & pieces(p2)))\n                assert(0 && \"pos_is_ok: Bitboards\");\n\n\n    for (Piece pc : Pieces)\n        if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))\n            || pieceCount[pc] != std::count(board.begin(), board.end(), pc))\n            assert(0 && \"pos_is_ok: Pieces\");\n\n    for (Color c : {WHITE, BLACK})\n        for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})\n        {\n            if (!can_castle(cr))\n                continue;\n\n            if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)\n                || castlingRightsMask[castlingRookSquare[cr]] != cr\n                || (castlingRightsMask[square<KING>(c)] & cr) != cr)\n                assert(0 && \"pos_is_ok: Castling\");\n        }\n\n    assert(material_key_is_ok() && \"pos_is_ok: materialKey\");\n\n    return true;\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/position.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef POSITION_H_INCLUDED\n#define POSITION_H_INCLUDED\n\n#include <array>\n#include <cassert>\n#include <deque>\n#include <iosfwd>\n#include <memory>\n#include <new>\n#include <optional>\n#include <stdexcept>\n#include <string>\n\n#include \"bitboard.h\"\n#include \"types.h\"\n\nnamespace Stockfish {\n\nclass TranspositionTable;\nstruct SharedHistories;\n\n// StateInfo struct stores information needed to restore a Position object to\n// its previous state when we retract a move. Whenever a move is made on the\n// board (by calling Position::do_move), a StateInfo object must be passed.\n\nstruct StateInfo {\n\n    // Copied when making a move\n    Key    materialKey;\n    Key    pawnKey;\n    Key    minorPieceKey;\n    Key    nonPawnKey[COLOR_NB];\n    Value  nonPawnMaterial[COLOR_NB];\n    int    castlingRights;\n    int    rule50;\n    int    pliesFromNull;\n    Square epSquare;\n\n    // Not copied when making a move (will be recomputed anyhow)\n    Key        key;\n    Bitboard   checkersBB;\n    StateInfo* previous;\n    Bitboard   blockersForKing[COLOR_NB];\n    Bitboard   pinners[COLOR_NB];\n    Bitboard   checkSquares[PIECE_TYPE_NB];\n    Piece      capturedPiece;\n    int        repetition;\n};\n\n\n// A list to keep track of the position states along the setup moves (from the\n// start position to the position just before the search starts). Needed by\n// 'draw by repetition' detection. Use a std::deque because pointers to\n// elements are not invalidated upon list resizing.\nusing StateListPtr = std::unique_ptr<std::deque<StateInfo>>;\n\n// This error should be used whenever a position is suspected to be unsupported\n// by the engine. In particular positions that may cause hard errors like segmentation fault.\nstruct PositionSetError: std::runtime_error {\n    using std::runtime_error::runtime_error;\n};\n\n// Position class stores information regarding the board representation as\n// pieces, side to move, hash keys, castling info, etc. Important methods are\n// do_move() and undo_move(), used by the search to update node info when\n// traversing the search tree.\nclass Position {\n   public:\n    static void init();\n\n    Position()                           = default;\n    Position(const Position&)            = delete;\n    Position& operator=(const Position&) = delete;\n\n    // FEN string input/output\n    std::optional<PositionSetError> set(const std::string& fenStr, bool isChess960, StateInfo* si);\n    std::optional<PositionSetError> set(const std::string& code, Color c, StateInfo* si);\n    std::string                     fen() const;\n\n    // Position representation\n    Bitboard pieces() const;  // All pieces\n    template<typename... PieceTypes>\n    Bitboard pieces(PieceTypes... pts) const;\n    Bitboard pieces(Color c) const;\n    template<typename... PieceTypes>\n    Bitboard                            pieces(Color c, PieceTypes... pts) const;\n    Piece                               piece_on(Square s) const;\n    const std::array<Piece, SQUARE_NB>& piece_array() const;\n    Square                              ep_square() const;\n    bool                                empty(Square s) const;\n    template<PieceType Pt>\n    int count(Color c) const;\n    template<PieceType Pt>\n    int count() const;\n    template<PieceType Pt>\n    Square square(Color c) const;\n\n    // Castling\n    bool   can_castle(CastlingRights cr) const;\n    bool   castling_impeded(CastlingRights cr) const;\n    Square castling_rook_square(CastlingRights cr) const;\n\n    // Checking\n    Bitboard checkers() const;\n    Bitboard blockers_for_king(Color c) const;\n    Bitboard check_squares(PieceType pt) const;\n    Bitboard pinners(Color c) const;\n\n    // Attacks to/from a given square\n    Bitboard attackers_to(Square s) const;\n    Bitboard attackers_to(Square s, Bitboard occupied) const;\n    bool     attackers_to_exist(Square s, Bitboard occupied, Color c) const;\n    void     update_slider_blockers(Color c) const;\n    template<PieceType Pt>\n    Bitboard attacks_by(Color c) const;\n\n    // Properties of moves\n    bool  legal(Move m) const;\n    bool  pseudo_legal(const Move m) const;\n    bool  capture(Move m) const;\n    bool  capture_stage(Move m) const;\n    bool  gives_check(Move m) const;\n    Piece moved_piece(Move m) const;\n    Piece captured_piece() const;\n\n    // Doing and undoing moves\n    void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt);\n    void do_move(Move                      m,\n                 StateInfo&                newSt,\n                 bool                      givesCheck,\n                 DirtyPiece&               dp,\n                 DirtyThreats&             dts,\n                 const TranspositionTable* tt,\n                 const SharedHistories*    worker);\n    void undo_move(Move m);\n    void do_null_move(StateInfo& newSt);\n    void undo_null_move();\n\n    // Static Exchange Evaluation\n    bool see_ge(Move m, int threshold = 0) const;\n\n    // Accessing hash keys\n    Key key() const;\n    Key material_key() const;\n    Key pawn_key() const;\n    Key minor_piece_key() const;\n    Key non_pawn_key(Color c) const;\n\n    // Other properties of the position\n    Color side_to_move() const;\n    int   game_ply() const;\n    bool  is_chess960() const;\n    bool  is_draw(int ply) const;\n    bool  is_repetition(int ply) const;\n    bool  upcoming_repetition(int ply) const;\n    bool  has_repeated() const;\n    int   rule50_count() const;\n    Value non_pawn_material(Color c) const;\n    Value non_pawn_material() const;\n\n    // Position consistency check, for debugging\n    bool pos_is_ok() const;\n    bool material_key_is_ok() const;\n    void flip();\n\n    StateInfo* state() const;\n\n    void put_piece(Piece pc, Square s, DirtyThreats* const dts = nullptr);\n    void remove_piece(Square s, DirtyThreats* const dts = nullptr);\n    void swap_piece(Square s, Piece pc, DirtyThreats* const dts = nullptr);\n\n   private:\n    // Initialization helpers (used while setting up a position)\n    void set_castling_right(Color c, Square rfrom);\n    Key  compute_material_key() const;\n    void set_state() const;\n    void set_check_info() const;\n\n    // Other helpers\n    template<bool PutPiece, bool ComputeRay = true>\n    void update_piece_threats(Piece               pc,\n                              Square              s,\n                              DirtyThreats* const dts,\n                              Bitboard            noRaysContaining = -1ULL) const;\n    void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr);\n    template<bool Do>\n    void do_castling(Color               us,\n                     Square              from,\n                     Square&             to,\n                     Square&             rfrom,\n                     Square&             rto,\n                     DirtyThreats* const dts = nullptr,\n                     DirtyPiece* const   dp  = nullptr);\n    Key  adjust_key50(Key k) const;\n\n    // Data members\n    std::array<Piece, SQUARE_NB>        board;\n    std::array<Bitboard, PIECE_TYPE_NB> byTypeBB;\n    std::array<Bitboard, COLOR_NB>      byColorBB;\n\n    int          pieceCount[PIECE_NB];\n    int          castlingRightsMask[SQUARE_NB];\n    Square       castlingRookSquare[CASTLING_RIGHT_NB];\n    Bitboard     castlingPath[CASTLING_RIGHT_NB];\n    StateInfo*   st;\n    int          gamePly;\n    Color        sideToMove;\n    bool         chess960;\n    DirtyPiece   scratch_dp;\n    DirtyThreats scratch_dts;\n};\n\nstd::ostream& operator<<(std::ostream& os, const Position& pos);\n\ninline Color Position::side_to_move() const { return sideToMove; }\n\ninline Piece Position::piece_on(Square s) const {\n    assert(is_ok(s));\n    return board[s];\n}\n\ninline const std::array<Piece, SQUARE_NB>& Position::piece_array() const { return board; }\n\ninline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }\n\ninline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); }\n\ninline Bitboard Position::pieces() const { return byTypeBB[ALL_PIECES]; }\n\ntemplate<typename... PieceTypes>\ninline Bitboard Position::pieces(PieceTypes... pts) const {\n    return (byTypeBB[pts] | ...);\n}\n\ninline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }\n\ntemplate<typename... PieceTypes>\ninline Bitboard Position::pieces(Color c, PieceTypes... pts) const {\n    return pieces(c) & pieces(pts...);\n}\n\ntemplate<PieceType Pt>\ninline int Position::count(Color c) const {\n    return pieceCount[make_piece(c, Pt)];\n}\n\ntemplate<PieceType Pt>\ninline int Position::count() const {\n    return count<Pt>(WHITE) + count<Pt>(BLACK);\n}\n\ntemplate<PieceType Pt>\ninline Square Position::square(Color c) const {\n    assert(count<Pt>(c) == 1);\n    return lsb(pieces(c, Pt));\n}\n\ninline Square Position::ep_square() const { return st->epSquare; }\n\ninline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; }\n\ninline bool Position::castling_impeded(CastlingRights cr) const {\n    assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);\n    return pieces() & castlingPath[cr];\n}\n\ninline Square Position::castling_rook_square(CastlingRights cr) const {\n    assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);\n    return castlingRookSquare[cr];\n}\n\ninline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); }\n\ntemplate<PieceType Pt>\ninline Bitboard Position::attacks_by(Color c) const {\n\n    if constexpr (Pt == PAWN)\n        return c == WHITE ? pawn_attacks_bb<WHITE>(pieces(WHITE, PAWN))\n                          : pawn_attacks_bb<BLACK>(pieces(BLACK, PAWN));\n    else\n    {\n        Bitboard threats   = 0;\n        Bitboard attackers = pieces(c, Pt);\n        while (attackers)\n            threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());\n        return threats;\n    }\n}\n\ninline Bitboard Position::checkers() const { return st->checkersBB; }\n\ninline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; }\n\ninline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }\n\ninline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; }\n\ninline Key Position::key() const { return adjust_key50(st->key); }\n\ninline Key Position::adjust_key50(Key k) const {\n    return st->rule50 < 14 ? k : k ^ make_key((st->rule50 - 14) / 8);\n}\n\ninline Key Position::pawn_key() const { return st->pawnKey; }\n\ninline Key Position::material_key() const { return st->materialKey; }\n\ninline Key Position::minor_piece_key() const { return st->minorPieceKey; }\n\ninline Key Position::non_pawn_key(Color c) const { return st->nonPawnKey[c]; }\n\ninline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; }\n\ninline Value Position::non_pawn_material() const {\n    return non_pawn_material(WHITE) + non_pawn_material(BLACK);\n}\n\ninline int Position::game_ply() const { return gamePly; }\n\ninline int Position::rule50_count() const { return st->rule50; }\n\ninline bool Position::is_chess960() const { return chess960; }\n\ninline bool Position::capture(Move m) const {\n    assert(m.is_ok());\n    return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT;\n}\n\n// Returns true if a move is generated from the capture stage, having also\n// queen promotions covered, i.e. consistency with the capture stage move\n// generation is needed to avoid the generation of duplicate moves.\ninline bool Position::capture_stage(Move m) const {\n    assert(m.is_ok());\n    return capture(m) || m.promotion_type() == QUEEN;\n}\n\ninline Piece Position::captured_piece() const { return st->capturedPiece; }\n\ninline void Position::put_piece(Piece pc, Square s, DirtyThreats* const dts) {\n    board[s] = pc;\n    byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;\n    byColorBB[color_of(pc)] |= s;\n    pieceCount[pc]++;\n    pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;\n\n    if (dts)\n        update_piece_threats<true>(pc, s, dts);\n}\n\ninline void Position::remove_piece(Square s, DirtyThreats* const dts) {\n    Piece pc = board[s];\n\n    if (dts)\n        update_piece_threats<false>(pc, s, dts);\n\n    byTypeBB[ALL_PIECES] ^= s;\n    byTypeBB[type_of(pc)] ^= s;\n    byColorBB[color_of(pc)] ^= s;\n    board[s] = NO_PIECE;\n    pieceCount[pc]--;\n    pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;\n}\n\ninline void Position::move_piece(Square from, Square to, DirtyThreats* const dts) {\n    Piece    pc     = board[from];\n    Bitboard fromTo = from | to;\n\n    if (dts)\n        update_piece_threats<false>(pc, from, dts, fromTo);\n\n    byTypeBB[ALL_PIECES] ^= fromTo;\n    byTypeBB[type_of(pc)] ^= fromTo;\n    byColorBB[color_of(pc)] ^= fromTo;\n    board[from] = NO_PIECE;\n    board[to]   = pc;\n\n    if (dts)\n        update_piece_threats<true>(pc, to, dts, fromTo);\n}\n\ninline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {\n    Piece old = board[s];\n\n    remove_piece(s);\n\n    if (dts)\n        update_piece_threats<false, false>(old, s, dts);\n\n    put_piece(pc, s);\n\n    if (dts)\n        update_piece_threats<true, false>(pc, s, dts);\n}\n\ninline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) {\n    new (&scratch_dts) DirtyThreats;\n    do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt, nullptr);\n}\n\ninline StateInfo* Position::state() const { return st; }\n\n}  // namespace Stockfish\n\n#endif  // #ifndef POSITION_H_INCLUDED\n"
  },
  {
    "path": "src/score.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"score.h\"\n\n#include <cassert>\n#include <cmath>\n#include <cstdlib>\n\n#include \"uci.h\"\n\nnamespace Stockfish {\n\nScore::Score(Value v, const Position& pos) {\n    assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);\n\n    if (!is_decisive(v))\n    {\n        score = InternalUnits{UCIEngine::to_cp(v, pos)};\n    }\n    else if (std::abs(v) <= VALUE_TB)\n    {\n        auto distance = VALUE_TB - std::abs(v);\n        score         = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false};\n    }\n    else\n    {\n        auto distance = VALUE_MATE - std::abs(v);\n        score         = (v > 0) ? Mate{distance} : Mate{-distance};\n    }\n}\n\n}"
  },
  {
    "path": "src/score.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef SCORE_H_INCLUDED\n#define SCORE_H_INCLUDED\n\n#include <variant>\n#include <utility>\n\n#include \"types.h\"\n\nnamespace Stockfish {\n\nclass Position;\n\nclass Score {\n   public:\n    struct Mate {\n        int plies;\n    };\n\n    struct Tablebase {\n        int  plies;\n        bool win;\n    };\n\n    struct InternalUnits {\n        int value;\n    };\n\n    Score() = default;\n    Score(Value v, const Position& pos);\n\n    template<typename T>\n    bool is() const {\n        return std::holds_alternative<T>(score);\n    }\n\n    template<typename T>\n    T get() const {\n        return std::get<T>(score);\n    }\n\n    template<typename F>\n    decltype(auto) visit(F&& f) const {\n        return std::visit(std::forward<F>(f), score);\n    }\n\n   private:\n    std::variant<Mate, Tablebase, InternalUnits> score;\n};\n\n}\n\n#endif  // #ifndef SCORE_H_INCLUDED\n"
  },
  {
    "path": "src/search.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"search.h\"\n\n#include <algorithm>\n#include <array>\n#include <atomic>\n#include <cassert>\n#include <chrono>\n#include <cmath>\n#include <cstdint>\n#include <cstdlib>\n#include <initializer_list>\n#include <iostream>\n#include <list>\n#include <ratio>\n#include <string>\n#include <utility>\n\n#include \"bitboard.h\"\n#include \"evaluate.h\"\n#include \"history.h\"\n#include \"misc.h\"\n#include \"movegen.h\"\n#include \"movepick.h\"\n#include \"nnue/network.h\"\n#include \"nnue/nnue_accumulator.h\"\n#include \"position.h\"\n#include \"syzygy/tbprobe.h\"\n#include \"thread.h\"\n#include \"timeman.h\"\n#include \"tt.h\"\n#include \"types.h\"\n#include \"uci.h\"\n#include \"ucioption.h\"\n\nnamespace Stockfish {\n\nnamespace TB = Tablebases;\n\nvoid syzygy_extend_pv(const OptionsMap&            options,\n                      const Search::LimitsType&    limits,\n                      Stockfish::Position&         pos,\n                      Stockfish::Search::RootMove& rootMove,\n                      Value&                       v);\n\nusing namespace Search;\n\nnamespace {\n\nconstexpr int SEARCHEDLIST_CAPACITY = 32;\nusing SearchedList                  = ValueList<Move, SEARCHEDLIST_CAPACITY>;\n\n// (*Scalers):\n// The values with Scaler asterisks have proven non-linear scaling.\n// They are optimized to time controls of 180 + 1.8 and longer,\n// so changing them or adding conditions that are similar requires\n// tests at these types of time controls.\n\n// (*Scaler) All tuned parameters at time controls shorter than\n// optimized for require verifications at longer time controls\n\nint correction_value(const Worker& w, const Position& pos, const Stack* const ss) {\n    const Color us     = pos.side_to_move();\n    const auto  m      = (ss - 1)->currentMove;\n    const auto& shared = w.sharedHistory;\n    const int   pcv    = shared.pawn_correction_entry(pos).at(us).pawn;\n    const int   micv   = shared.minor_piece_correction_entry(pos).at(us).minor;\n    const int   wnpcv  = shared.nonpawn_correction_entry<WHITE>(pos).at(us).nonPawnWhite;\n    const int   bnpcv  = shared.nonpawn_correction_entry<BLACK>(pos).at(us).nonPawnBlack;\n    const int   cntcv =\n      m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]\n                    + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]\n                  : 8;\n\n    return 12153 * pcv + 8620 * micv + 12355 * (wnpcv + bnpcv) + 7982 * cntcv;\n}\n\n// Add correctionHistory value to raw staticEval and guarantee evaluation\n// does not hit the tablebase range.\nValue to_corrected_static_eval(const Value v, const int cv) {\n    return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);\n}\n\nvoid update_correction_history(const Position& pos,\n                               Stack* const    ss,\n                               Search::Worker& workerThread,\n                               const int       bonus) {\n    const Move  m  = (ss - 1)->currentMove;\n    const Color us = pos.side_to_move();\n\n    constexpr int nonPawnWeight = 187;\n    auto&         shared        = workerThread.sharedHistory;\n\n    shared.pawn_correction_entry(pos).at(us).pawn << bonus;\n    shared.minor_piece_correction_entry(pos).at(us).minor << bonus * 153 / 128;\n    shared.nonpawn_correction_entry<WHITE>(pos).at(us).nonPawnWhite << bonus * nonPawnWeight / 128;\n    shared.nonpawn_correction_entry<BLACK>(pos).at(us).nonPawnBlack << bonus * nonPawnWeight / 128;\n\n    // Branchless: use mask to zero bonus when move is not ok\n    const int    mask   = int(m.is_ok());\n    const Square to     = m.to_sq_unchecked();\n    const Piece  pc     = pos.piece_on(to);\n    const int    bonus2 = (bonus * 126 / 128) * mask;\n    const int    bonus4 = (bonus * 63 / 128) * mask;\n    (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus2;\n    (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus4;\n}\n\n// Add a small random component to draw evaluations to avoid 3-fold blindness\nValue value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }\nValue value_to_tt(Value v, int ply);\nValue value_from_tt(Value v, int ply, int r50c);\nvoid  update_pv(Move* pv, Move move, const Move* childPv);\nvoid  update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);\nvoid  update_quiet_histories(\n   const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);\nvoid update_all_stats(const Position& pos,\n                      Stack*          ss,\n                      Search::Worker& workerThread,\n                      Move            bestMove,\n                      Square          prevSq,\n                      SearchedList&   quietsSearched,\n                      SearchedList&   capturesSearched,\n                      Depth           depth,\n                      Move            ttMove);\n\nbool is_shuffling(Move move, Stack* const ss, const Position& pos) {\n    if (pos.capture_stage(move) || pos.rule50_count() < 11)\n        return false;\n    if (pos.state()->pliesFromNull <= 6 || ss->ply < 18)\n        return false;\n    return move.from_sq() == (ss - 2)->currentMove.to_sq()\n        && (ss - 2)->currentMove.from_sq() == (ss - 4)->currentMove.to_sq();\n}\n\n}  // namespace\n\nSearch::Worker::Worker(SharedState&                    sharedState,\n                       std::unique_ptr<ISearchManager> sm,\n                       size_t                          threadId,\n                       size_t                          numaThreadId,\n                       size_t                          numaTotalThreads,\n                       NumaReplicatedAccessToken       token) :\n    // Unpack the SharedState struct into member variables\n    sharedHistory(sharedState.sharedHistories.at(token.get_numa_index())),\n    threadIdx(threadId),\n    numaThreadIdx(numaThreadId),\n    numaTotal(numaTotalThreads),\n    numaAccessToken(token),\n    manager(std::move(sm)),\n    options(sharedState.options),\n    threads(sharedState.threads),\n    tt(sharedState.tt),\n    networks(sharedState.networks),\n    refreshTable(networks[token]) {\n    clear();\n}\n\nvoid Search::Worker::ensure_network_replicated() {\n    // Access once to force lazy initialization.\n    // We do this because we want to avoid initialization during search.\n    (void) (networks[numaAccessToken]);\n}\n\nvoid Search::Worker::start_searching() {\n\n    accumulatorStack.reset();\n    lastIterationPV.clear();\n\n    // Non-main threads go directly to iterative_deepening()\n    if (!is_mainthread())\n    {\n        iterative_deepening();\n        return;\n    }\n\n    main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options,\n                            main_manager()->originalTimeAdjust);\n    tt.new_search();\n\n    if (rootMoves.empty())\n    {\n        rootMoves.emplace_back(Move::none());\n        main_manager()->updates.onUpdateNoMoves(\n          {0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}});\n    }\n    else\n    {\n        threads.start_searching();  // start non-main threads\n        iterative_deepening();      // main thread start searching\n    }\n\n    // When we reach the maximum depth, we can arrive here without a raise of\n    // threads.stop. However, if we are pondering or in an infinite search,\n    // the UCI protocol states that we shouldn't print the best move before the\n    // GUI sends a \"stop\" or \"ponderhit\" command. We therefore simply wait here\n    // until the GUI sends one of those commands.\n    while (!threads.stop && (main_manager()->ponder || limits.infinite))\n    {}  // Busy wait for a stop or a ponder reset\n\n    // Stop the threads if not already stopped (also raise the stop if\n    // \"ponderhit\" just reset threads.ponder)\n    threads.stop = true;\n\n    // Wait until all threads have finished\n    threads.wait_for_search_finished();\n\n    // When playing in 'nodes as time' mode, subtract the searched nodes from\n    // the available ones before exiting.\n    if (limits.npmsec)\n        main_manager()->tm.advance_nodes_time(threads.nodes_searched()\n                                              - limits.inc[rootPos.side_to_move()]);\n\n    Worker* bestThread = this;\n    Skill   skill =\n      Skill(options[\"Skill Level\"], options[\"UCI_LimitStrength\"] ? int(options[\"UCI_Elo\"]) : 0);\n\n    if (int(options[\"MultiPV\"]) == 1 && !limits.depth && !skill.enabled()\n        && rootMoves[0].pv[0] != Move::none())\n        bestThread = threads.get_best_thread()->worker.get();\n\n    main_manager()->bestPreviousScore        = bestThread->rootMoves[0].score;\n    main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore;\n\n    // Send again PV info if we have a new best thread\n    if (bestThread != this)\n        main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth);\n\n    std::string ponder;\n\n    if (bestThread->rootMoves[0].pv.size() > 1\n        || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos))\n        ponder = UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());\n\n    auto bestmove = UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());\n    main_manager()->updates.onBestmove(bestmove, ponder);\n}\n\n// Main iterative deepening loop. It calls search()\n// repeatedly with increasing depth until the allocated thinking time has been\n// consumed, the user stops the search, or the maximum search depth is reached.\nvoid Search::Worker::iterative_deepening() {\n\n    SearchManager* mainThread = (is_mainthread() ? main_manager() : nullptr);\n\n    Move pv[MAX_PLY + 1];\n\n    Depth             lastBestMoveDepth = 0;\n    Value             lastBestScore     = -VALUE_INFINITE;\n    std::vector<Move> lastBestPV;\n\n    Value  alpha, beta;\n    Value  bestValue     = -VALUE_INFINITE;\n    Color  us            = rootPos.side_to_move();\n    double timeReduction = 1, totBestMoveChanges = 0;\n    int    delta, iterIdx                        = 0;\n\n    // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):\n    // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),\n    // (ss + 2) is needed for initialization of cutOffCnt.\n    Stack  stack[MAX_PLY + 10] = {};\n    Stack* ss                  = stack + 7;\n\n    for (int i = 7; i > 0; --i)\n    {\n        (ss - i)->continuationHistory =\n          &continuationHistory[0][0][NO_PIECE][0];  // Use as a sentinel\n        (ss - i)->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0];\n        (ss - i)->staticEval                    = VALUE_NONE;\n    }\n\n    for (int i = 0; i <= MAX_PLY + 2; ++i)\n        (ss + i)->ply = i;\n\n    ss->pv = pv;\n\n    if (mainThread)\n    {\n        if (mainThread->bestPreviousScore == VALUE_INFINITE)\n            mainThread->iterValue.fill(VALUE_ZERO);\n        else\n            mainThread->iterValue.fill(mainThread->bestPreviousScore);\n    }\n\n    size_t multiPV = size_t(options[\"MultiPV\"]);\n    Skill skill(options[\"Skill Level\"], options[\"UCI_LimitStrength\"] ? int(options[\"UCI_Elo\"]) : 0);\n\n    // When playing with strength handicap enable MultiPV search that we will\n    // use behind-the-scenes to retrieve a set of possible moves.\n    if (skill.enabled())\n        multiPV = std::max(multiPV, size_t(4));\n\n    multiPV = std::min(multiPV, rootMoves.size());\n\n    int searchAgainCounter = 0;\n\n    lowPlyHistory.fill(98);\n\n    for (Color c : {WHITE, BLACK})\n        for (int i = 0; i < UINT_16_HISTORY_SIZE; i++)\n            mainHistory[c][i] = mainHistory[c][i] * 820 / 1024;\n\n    // Iterative deepening loop until requested to stop or the target depth is reached\n    while (++rootDepth < MAX_PLY && !threads.stop\n           && !(limits.depth && mainThread && rootDepth > limits.depth))\n    {\n        // Age out PV variability metric\n        if (mainThread)\n            totBestMoveChanges /= 2;\n\n        // Save the last iteration's scores before the first PV line is searched and\n        // all the move scores except the (new) PV are set to -VALUE_INFINITE.\n        for (RootMove& rm : rootMoves)\n            rm.previousScore = rm.score;\n\n        size_t pvFirst = 0;\n        pvLast         = 0;\n\n        if (!threads.increaseDepth)\n            searchAgainCounter++;\n\n        // MultiPV loop. We perform a full root search for each PV line\n        for (pvIdx = 0; pvIdx < multiPV; ++pvIdx)\n        {\n            if (pvIdx == pvLast)\n            {\n                pvFirst = pvLast;\n                for (pvLast++; pvLast < rootMoves.size(); pvLast++)\n                    if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank)\n                        break;\n            }\n\n            // Reset UCI info selDepth for each depth and each PV line\n            selDepth = 0;\n\n            // Reset aspiration window starting size\n            delta     = 5 + threadIdx % 8 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 10208;\n            Value avg = rootMoves[pvIdx].averageScore;\n            alpha     = std::max(avg - delta, -VALUE_INFINITE);\n            beta      = std::min(avg + delta, VALUE_INFINITE);\n\n            // Adjust optimism based on root move's averageScore\n            optimism[us]  = 144 * avg / (std::abs(avg) + 91);\n            optimism[~us] = -optimism[us];\n\n            // Start with a small aspiration window and, in the case of a fail\n            // high/low, re-search with a bigger window until we don't fail\n            // high/low anymore.\n            int failedHighCnt = 0;\n            while (true)\n            {\n                // Adjust the effective depth searched, but ensure at least one\n                // effective increment for every four searchAgain steps (see issue #2717).\n                Depth adjustedDepth =\n                  std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);\n                rootDelta = beta - alpha;\n                bestValue = search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);\n\n                // Bring the best move to the front. It is critical that sorting\n                // is done with a stable algorithm because all the values but the\n                // first and eventually the new best one is set to -VALUE_INFINITE\n                // and we want to keep the same order for all the moves except the\n                // new PV that goes to the front. Note that in the case of MultiPV\n                // search the already searched PV lines are preserved.\n                std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast);\n\n                // If search has been stopped, we break immediately. Sorting is\n                // safe because RootMoves is still valid, although it refers to\n                // the previous iteration.\n                if (threads.stop)\n                    break;\n\n                // When failing high/low give some update before a re-search. To avoid\n                // excessive output that could hang GUIs like Fritz 19, only start\n                // at nodes > 10M (rather than depth N, which can be reached quickly)\n                if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta)\n                    && nodes > 10000000)\n                    main_manager()->pv(*this, threads, tt, rootDepth);\n\n                // In case of failing low/high increase aspiration window and re-search,\n                // otherwise exit the loop.\n                if (bestValue <= alpha)\n                {\n                    beta  = alpha;\n                    alpha = std::max(bestValue - delta, -VALUE_INFINITE);\n\n                    failedHighCnt = 0;\n                    if (mainThread)\n                        mainThread->stopOnPonderhit = false;\n                }\n                else if (bestValue >= beta)\n                {\n                    alpha = std::max(beta - delta, alpha);\n                    beta  = std::min(bestValue + delta, VALUE_INFINITE);\n                    ++failedHighCnt;\n                }\n                else\n                    break;\n\n                delta += delta / 3;\n\n                assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);\n            }\n\n            // Sort the PV lines searched so far and update the GUI\n            std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);\n\n            if (mainThread\n                && (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000)\n                // A thread that aborted search can have a mated-in/TB-loss score and\n                // PV that cannot be trusted, i.e. it can be delayed or refuted if we\n                // would have had time to fully search other root-moves. Thus here we\n                // suppress any exact mated-in/TB loss output and, if we do, below pick\n                // the score/PV from the previously completed iteration with the most\n                // recent bestmove change.\n                && !(threads.stop && is_loss(rootMoves[0].uciScore)\n                     && rootMoves[0].score == rootMoves[0].uciScore))\n                main_manager()->pv(*this, threads, tt, rootDepth);\n\n            if (threads.stop)\n                break;\n        }\n\n        if (!threads.stop)\n        {\n            completedDepth  = rootDepth;\n            lastIterationPV = rootMoves[0].pv;\n        }\n\n        // We make sure not to pick an unproven mated-in score,\n        // in case this thread prematurely stopped search (aborted-search).\n        if (completedDepth != rootDepth && rootMoves[0].score != -VALUE_INFINITE\n            && is_loss(rootMoves[0].score))\n        {\n            // Bring the last best move to the front for best thread selection.\n            // For an aborted d1 search we label the loss score as inexact.\n            if (!lastBestPV.empty())\n            {\n                Utility::move_to_front(rootMoves,\n                                       [&lastBestPV = std::as_const(lastBestPV)](const auto& rm) {\n                                           return rm == lastBestPV[0];\n                                       });\n                rootMoves[0].pv    = lastBestPV;\n                rootMoves[0].score = rootMoves[0].uciScore = lastBestScore;\n            }\n            else\n            {\n                if (!rootMoves[0].scoreLowerbound)\n                    rootMoves[0].scoreUpperbound = true;\n                if (mainThread)\n                    main_manager()->pv(*this, threads, tt, rootDepth);\n            }\n        }\n        else if (lastBestPV.empty() || rootMoves[0].pv[0] != lastBestPV[0])\n        {\n            lastBestPV        = rootMoves[0].pv;\n            lastBestScore     = rootMoves[0].score;\n            lastBestMoveDepth = rootDepth;\n        }\n\n        // Have we found a \"mate in x\" after a completed iteration?\n        if (limits.mate && !threads.stop\n            && ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY\n                 && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate)\n                || (rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY\n                    && VALUE_MATE + rootMoves[0].score <= 2 * limits.mate)))\n            threads.stop = true;\n\n        if (!mainThread)\n            continue;\n\n        // If the skill level is enabled and time is up, pick a sub-optimal best move\n        if (skill.enabled() && skill.time_to_pick(rootDepth))\n            skill.pick_best(rootMoves, multiPV);\n\n        // Use part of the gained time from a previous stable move for the current move\n        for (auto&& th : threads)\n        {\n            totBestMoveChanges += th->worker->bestMoveChanges;\n            th->worker->bestMoveChanges = 0;\n        }\n\n        // Do we have time for the next iteration? Can we stop searching now?\n        if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit)\n        {\n            uint64_t nodesEffort =\n              rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes));\n\n            double fallingEval = (12.44 + 2.318 * (mainThread->bestPreviousAverageScore - bestValue)\n                                  + 0.95 * (mainThread->iterValue[iterIdx] - bestValue))\n                               / 100.0;\n            fallingEval = std::clamp(fallingEval, 0.581, 1.655);\n\n            // If the bestMove is stable over several iterations, reduce time accordingly\n            double k      = 0.476;\n            double center = lastBestMoveDepth + 11.565;\n\n            timeReduction = 0.64 + 0.93 / (0.953 + std::exp(-k * (completedDepth - center)));\n\n            double reduction = (1.5 + mainThread->previousTimeReduction) / (2.255 * timeReduction);\n\n            double bestMoveInstability = 1.088 + 2.315 * totBestMoveChanges / threads.size();\n\n            double highBestMoveEffort = nodesEffort > 86000 ? 0.74 : 0.96;\n\n            double totalTime = mainThread->tm.optimum() * fallingEval * reduction\n                             * bestMoveInstability * highBestMoveEffort;\n\n            // Cap used time in case of a single legal move for a better viewer experience\n            if (rootMoves.size() == 1)\n                totalTime = std::min(504.4, totalTime);\n\n            auto elapsedTime = elapsed();\n\n            // Stop the search if we have exceeded the totalTime or maximum\n            if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum())))\n            {\n                // If we are allowed to ponder do not stop the search now but\n                // keep pondering until the GUI sends \"ponderhit\" or \"stop\".\n                if (mainThread->ponder)\n                    mainThread->stopOnPonderhit = true;\n                else\n                    threads.stop = true;\n            }\n            else\n                threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.50;\n        }\n\n        mainThread->iterValue[iterIdx] = bestValue;\n        iterIdx                        = (iterIdx + 1) & 3;\n    }\n\n    if (!mainThread)\n        return;\n\n    mainThread->previousTimeReduction = timeReduction;\n\n    // If the skill level is enabled, swap the best PV line with the sub-optimal one\n    if (skill.enabled())\n        std::swap(rootMoves[0],\n                  *std::find(rootMoves.begin(), rootMoves.end(),\n                             skill.best ? skill.best : skill.pick_best(rootMoves, multiPV)));\n}\n\n\nvoid Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss) {\n    do_move(pos, move, st, pos.gives_check(move), ss);\n}\n\nvoid Search::Worker::do_move(\n  Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) {\n    bool capture = pos.capture_stage(move);\n    // Preferable over fetch_add to avoid locking instructions\n    nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);\n\n    auto [dirtyPiece, dirtyThreats] = accumulatorStack.push();\n    pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt, &sharedHistory);\n\n    if (ss != nullptr)\n    {\n        ss->currentMove = move;\n        ss->continuationHistory =\n          &continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()];\n        ss->continuationCorrectionHistory =\n          &continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()];\n    }\n}\n\nvoid Search::Worker::do_null_move(Position& pos, StateInfo& st, Stack* const ss) {\n    pos.do_null_move(st);\n    ss->currentMove                   = Move::null();\n    ss->continuationHistory           = &continuationHistory[0][0][NO_PIECE][0];\n    ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0];\n}\n\nvoid Search::Worker::undo_move(Position& pos, const Move move) {\n    pos.undo_move(move);\n    accumulatorStack.pop();\n}\n\nvoid Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); }\n\n\n// Reset histories, usually before a new game\nvoid Search::Worker::clear() {\n    mainHistory.fill(0);\n    captureHistory.fill(-678);\n\n    // Each thread is responsible for clearing their part of shared history\n    sharedHistory.correctionHistory.clear_range(0, numaThreadIdx, numaTotal);\n    sharedHistory.pawnHistory.clear_range(-1238, numaThreadIdx, numaTotal);\n\n    ttMoveHistory = 0;\n\n    for (auto& to : continuationCorrectionHistory)\n        for (auto& h : to)\n            h.fill(6);\n\n    for (bool inCheck : {false, true})\n        for (StatsType c : {NoCaptures, Captures})\n            for (auto& to : continuationHistory[inCheck][c])\n                for (auto& h : to)\n                    h.fill(-523);\n\n    for (size_t i = 1; i < reductions.size(); ++i)\n        reductions[i] = int(2763 / 128.0 * std::log(i));\n\n    refreshTable.clear(networks[numaAccessToken]);\n}\n\n\n// Main search function for both PV and non-PV nodes\ntemplate<NodeType nodeType>\nValue Search::Worker::search(\n  Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {\n\n    constexpr bool PvNode   = nodeType != NonPV;\n    constexpr bool rootNode = nodeType == Root;\n    const bool     allNode  = !(PvNode || cutNode);\n\n    // Dive into quiescence search when the depth reaches zero\n    if (depth <= 0)\n        return qsearch<PvNode ? PV : NonPV>(pos, ss, alpha, beta);\n\n    // Limit the depth if extensions made it too large\n    depth = std::min(depth, MAX_PLY - 1);\n\n    // Check if we have an upcoming move that draws by repetition\n    if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))\n    {\n        alpha = value_draw(nodes);\n        if (alpha >= beta)\n            return alpha;\n    }\n\n    assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);\n    assert(PvNode || (alpha == beta - 1));\n    assert(0 < depth && depth < MAX_PLY);\n    assert(!(PvNode && cutNode));\n\n    Move      pv[MAX_PLY + 1];\n    StateInfo st;\n\n    Key   posKey;\n    Move  move, excludedMove, bestMove;\n    Depth extension, newDepth;\n    Value bestValue, value, eval, maxValue, probCutBeta;\n    bool  givesCheck, improving, priorCapture, opponentWorsening;\n    bool  capture, ttCapture;\n    int   priorReduction;\n    Piece movedPiece;\n\n    SearchedList capturesSearched;\n    SearchedList quietsSearched;\n\n    // Step 1. Initialize node\n    ss->inCheck   = pos.checkers();\n    priorCapture  = pos.captured_piece();\n    Color us      = pos.side_to_move();\n    ss->moveCount = 0;\n    bestValue     = -VALUE_INFINITE;\n    maxValue      = VALUE_INFINITE;\n\n    ss->followPV = rootNode\n                || ((ss - 1)->followPV && static_cast<size_t>(ss->ply - 1) < lastIterationPV.size()\n                    && (ss - 1)->currentMove == lastIterationPV[ss->ply - 1]);\n\n    // Check for the available remaining time\n    if (is_mainthread())\n        main_manager()->check_time(*this);\n\n    // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)\n    if (PvNode && selDepth < ss->ply + 1)\n        selDepth = ss->ply + 1;\n\n    if (!rootNode)\n    {\n        // Step 2. Check for aborted search and immediate draw\n        if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply)\n            || ss->ply >= MAX_PLY)\n            return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : value_draw(nodes);\n\n        // Step 3. Mate distance pruning. Even if we mate at the next move our score\n        // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because\n        // a shorter mate was found upward in the tree then there is no need to search\n        // because we will never beat the current alpha. Same logic but with reversed\n        // signs apply also in the opposite condition of being mated instead of giving\n        // mate. In this case, return a fail-high score.\n        alpha = std::max(mated_in(ss->ply), alpha);\n        beta  = std::min(mate_in(ss->ply + 1), beta);\n        if (alpha >= beta)\n            return alpha;\n    }\n\n    assert(0 <= ss->ply && ss->ply < MAX_PLY);\n\n    Square prevSq  = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;\n    bestMove       = Move::none();\n    priorReduction = (ss - 1)->reduction;\n    (ss - 1)->reduction = 0;\n    ss->statScore       = 0;\n    (ss + 2)->cutoffCnt = 0;\n\n    // Step 4. Transposition table lookup\n    excludedMove                   = ss->excludedMove;\n    posKey                         = pos.key();\n    auto [ttHit, ttData, ttWriter] = tt.probe(posKey);\n    // Need further processing of the saved data\n    ss->ttHit    = ttHit;\n    ttData.move  = rootNode ? rootMoves[pvIdx].pv[0] : ttHit ? ttData.move : Move::none();\n    ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE;\n    ss->ttPv     = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv);\n    ttCapture    = ttData.move && pos.capture_stage(ttData.move);\n\n    // Step 6. Static evaluation of the position\n    Value      unadjustedStaticEval = VALUE_NONE;\n    const auto correctionValue      = correction_value(*this, pos, ss);\n    // Skip early pruning when in check\n    if (ss->inCheck)\n        ss->staticEval = eval = (ss - 2)->staticEval;\n    else if (excludedMove)\n        unadjustedStaticEval = eval = ss->staticEval;\n    else if (ss->ttHit)\n    {\n        // Never assume anything about values stored in TT\n        unadjustedStaticEval = ttData.eval;\n        if (!is_valid(unadjustedStaticEval))\n            unadjustedStaticEval = evaluate(pos);\n\n        ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);\n\n        // ttValue can be used as a better position evaluation\n        if (is_valid(ttData.value)\n            && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER)))\n            eval = ttData.value;\n    }\n    else\n    {\n        unadjustedStaticEval = evaluate(pos);\n        ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);\n\n        // Static evaluation is saved as it was before adjustment by correction history\n        ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(),\n                       unadjustedStaticEval, tt.generation());\n    }\n\n    // Set up the improving flag, which is true if current static evaluation is\n    // bigger than the previous static evaluation at our turn (if we were in\n    // check at our previous move we go back until we weren't in check) and is\n    // false otherwise. The improving flag is used in various pruning heuristics.\n    // Similarly, opponentWorsening is true if our static evaluation is better\n    // for us than at the last ply.\n    improving         = ss->staticEval > (ss - 2)->staticEval;\n    opponentWorsening = ss->staticEval > -(ss - 1)->staticEval;\n\n    // Hindsight adjustment of reductions based on static evaluation difference.\n    if (priorReduction >= 3 && !opponentWorsening)\n        depth++;\n    if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 195)\n        depth--;\n\n    // At non-PV nodes we check for an early TT cutoff\n    if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta)\n        && is_valid(ttData.value)  // Can happen when !ttHit or when access race in probe()\n        && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))\n        && (cutNode == (ttData.value >= beta) || depth > 5))\n    {\n        // If ttMove is quiet, update move sorting heuristics on TT hit\n        if (ttData.move && ttData.value >= beta)\n        {\n            // Bonus for a quiet ttMove that fails high\n            if (!ttCapture)\n                update_quiet_histories(pos, ss, *this, ttData.move,\n                                       std::min(119 * depth - 74, 855));\n\n            // Extra penalty for early quiet moves of the previous ply\n            if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture)\n                update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2014);\n        }\n\n        // Partial workaround for the graph history interaction problem\n        // For high rule50 counts don't produce transposition table cutoffs.\n        if (pos.rule50_count() < 96)\n        {\n            if (depth >= 7 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move)\n                && !is_decisive(ttData.value))\n            {\n                pos.do_move(ttData.move, st);\n                Key nextPosKey                             = pos.key();\n                auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey);\n                pos.undo_move(ttData.move);\n\n                // Check that the ttValue after the tt move would also trigger a cutoff\n                if (!is_valid(ttDataNext.value))\n                    return ttData.value;\n\n                if ((ttData.value >= beta) == (-ttDataNext.value >= beta))\n                    return ttData.value;\n            }\n            else\n                return ttData.value;\n        }\n    }\n\n    // Step 5. Tablebases probe\n    if (!rootNode && !excludedMove && tbConfig.cardinality)\n    {\n        int piecesCount = pos.count<ALL_PIECES>();\n\n        if (piecesCount <= tbConfig.cardinality\n            && (piecesCount < tbConfig.cardinality || depth >= tbConfig.probeDepth)\n            && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING))\n        {\n            TB::ProbeState err;\n            TB::WDLScore   wdl = Tablebases::probe_wdl(pos, &err);\n\n            // Force check of time on the next occasion\n            if (is_mainthread())\n                main_manager()->callsCnt = 0;\n\n            if (err != TB::ProbeState::FAIL)\n            {\n                // Preferable over fetch_add to avoid locking instructions\n                tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);\n\n                int drawScore = tbConfig.useRule50 ? 1 : 0;\n\n                Value tbValue = VALUE_TB - ss->ply;\n\n                // Use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score\n                value = wdl < -drawScore ? -tbValue\n                      : wdl > drawScore  ? tbValue\n                                         : VALUE_DRAW + 2 * wdl * drawScore;\n\n                Bound b = wdl < -drawScore ? BOUND_UPPER\n                        : wdl > drawScore  ? BOUND_LOWER\n                                           : BOUND_EXACT;\n\n                if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha))\n                {\n                    ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, b,\n                                   std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE,\n                                   tt.generation());\n\n                    return value;\n                }\n\n                if (PvNode)\n                {\n                    if (b == BOUND_LOWER)\n                        bestValue = value, alpha = std::max(alpha, bestValue);\n                    else\n                        maxValue = value;\n                }\n            }\n        }\n    }\n\n    if (ss->inCheck)\n        goto moves_loop;\n\n    // Use static evaluation difference to improve quiet move ordering\n    if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)\n    {\n        int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -214, 171) + 60;\n        mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 10;\n        if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN\n            && ((ss - 1)->currentMove).type_of() != PROMOTION)\n            sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << evalDiff * 12;\n    }\n\n\n    // Step 7. Razoring\n    // If eval is really low, skip search entirely and return the qsearch value.\n    // For PvNodes, we must have a guard against mates being returned.\n    if (!PvNode && eval < alpha - 502 - 306 * depth * depth)\n        return qsearch<NonPV>(pos, ss, alpha, beta);\n\n    // Step 8. Futility pruning: child node\n    // The depth condition is important for mate finding.\n    {\n        auto futility_margin = [&](Depth d) {\n            Value futilityMult = 76 - 21 * !ss->ttHit;\n\n            return futilityMult * d\n                 - (2686 * improving + 362 * opponentWorsening) * futilityMult / 1024  //\n                 + std::abs(correctionValue) / 180600;\n        };\n\n        if (!ss->ttPv && depth < 15 && eval - futility_margin(depth) >= beta && eval >= beta\n            && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval))\n            return (2 * beta + eval) / 3;\n    }\n\n    // Step 9. Null move search with verification search\n    if (cutNode && ss->staticEval >= beta - 16 * depth - 53 * improving + 378 && !excludedMove\n        && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta))\n    {\n        assert((ss - 1)->currentMove != Move::null());\n\n        // Null move dynamic reduction based on depth\n        Depth R = 7 + depth / 3;\n        do_null_move(pos, st, ss);\n\n        Value nullValue = -search<NonPV>(pos, ss + 1, -beta, -beta + 1, depth - R, false);\n\n        undo_null_move(pos);\n\n        // Do not return unproven mate or TB scores\n        if (nullValue >= beta && !is_win(nullValue))\n        {\n            if (nmpMinPly || depth < 16)\n                return nullValue;\n\n            assert(!nmpMinPly);  // Recursive verification is not allowed\n\n            // Do verification search at high depths, with null move pruning disabled\n            // until ply exceeds nmpMinPly.\n            nmpMinPly = ss->ply + 3 * (depth - R) / 4;\n\n            Value v = search<NonPV>(pos, ss, beta - 1, beta, depth - R, false);\n\n            nmpMinPly = 0;\n\n            if (v >= beta)\n                return nullValue;\n        }\n    }\n\n    improving |= ss->staticEval >= beta;\n\n    // Step 10. Internal iterative reductions\n    // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove.\n    // (*Scaler) Making IIR more aggressive scales poorly.\n    if (!ss->followPV && !allNode && depth >= 6 && !ttData.move && priorReduction <= 3)\n        depth--;\n\n    // Step 11. ProbCut\n    // If we have a good enough capture (or queen promotion) and a reduced search\n    // returns a value much above beta, we can (almost) safely prune the previous move.\n    probCutBeta = beta + 224 - 61 * improving;\n    if (depth >= 3\n        && !is_decisive(beta)\n        // If value from transposition table is lower than probCutBeta, don't attempt\n        // probCut there\n        && !(is_valid(ttData.value) && ttData.value < probCutBeta))\n    {\n        assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);\n\n        MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory);\n        Depth      probCutDepth = depth - 4;\n\n        while ((move = mp.next_move()) != Move::none())\n        {\n            assert(move.is_ok());\n\n            if (move == excludedMove || !pos.legal(move))\n                continue;\n\n            assert(pos.capture_stage(move));\n\n            do_move(pos, move, st, ss);\n\n            // Perform a preliminary qsearch to verify that the move holds\n            value = -qsearch<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1);\n\n            // If the qsearch held, perform the regular search\n            if (value >= probCutBeta && probCutDepth > 0)\n                value = -search<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth,\n                                       !cutNode);\n\n            undo_move(pos, move);\n\n            if (value >= probCutBeta)\n            {\n                // Save ProbCut data into transposition table\n                ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER,\n                               probCutDepth + 1, move, unadjustedStaticEval, tt.generation());\n\n                if (!is_decisive(value))\n                    return value - (probCutBeta - beta);\n            }\n        }\n    }\n\nmoves_loop:  // When in check, search starts here\n\n    // Step 12. A small Probcut idea\n    probCutBeta = beta + 416;\n    if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta\n        && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value))\n        return probCutBeta;\n\n    const PieceToHistory* contHist[] = {\n      (ss - 1)->continuationHistory, (ss - 2)->continuationHistory, (ss - 3)->continuationHistory,\n      (ss - 4)->continuationHistory, (ss - 5)->continuationHistory, (ss - 6)->continuationHistory};\n\n\n    MovePicker mp(pos, ttData.move, depth, &mainHistory, &lowPlyHistory, &captureHistory, contHist,\n                  &sharedHistory, ss->ply);\n\n    value = bestValue;\n\n    int moveCount = 0;\n\n    // Step 13. Loop through all pseudo-legal moves until no moves remain\n    // or a beta cutoff occurs.\n    while ((move = mp.next_move()) != Move::none())\n    {\n        assert(move.is_ok());\n\n        if (move == excludedMove)\n            continue;\n\n        // Check for legality\n        if (!pos.legal(move))\n            continue;\n\n        // At root obey the \"searchmoves\" option and skip moves not listed in Root\n        // Move List. In MultiPV mode we also skip PV moves that have been already\n        // searched and those of lower \"TB rank\" if we are in a TB root position.\n        if (rootNode && !std::count(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast, move))\n            continue;\n\n        ss->moveCount = ++moveCount;\n\n        if (rootNode && is_mainthread() && nodes > 10000000)\n        {\n            main_manager()->updates.onIter(\n              {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + pvIdx});\n        }\n        if (PvNode)\n            (ss + 1)->pv = nullptr;\n\n        extension  = 0;\n        capture    = pos.capture_stage(move);\n        movedPiece = pos.moved_piece(move);\n        givesCheck = pos.gives_check(move);\n\n        // Calculate new depth for this move\n        newDepth = depth - 1;\n\n        int delta = beta - alpha;\n\n        Depth r = reduction(improving, depth, moveCount, delta);\n\n        // Increase reduction for ttPv nodes (*Scaler)\n        // Larger values scale well\n        if (ss->ttPv)\n            r += 1013;\n\n        // Step 14. Pruning at shallow depths.\n        // Depth conditions are important for mate finding.\n        if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue))\n        {\n            // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold\n            if (moveCount >= (3 + depth * depth) / (2 - improving))\n                mp.skip_quiet_moves();\n\n            // Reduced depth of the next LMR search\n            int lmrDepth = newDepth - r / 1024;\n\n            if (capture || givesCheck)\n            {\n                Piece capturedPiece = pos.piece_on(move.to_sq());\n                int   captHist = captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)];\n\n                // Futility pruning for captures\n                if (!givesCheck && lmrDepth < 7)\n                {\n                    Value futilityValue = ss->staticEval + 218 + 223 * lmrDepth\n                                        + PieceValue[capturedPiece] + 131 * captHist / 1024;\n\n                    if (futilityValue <= alpha)\n                        continue;\n                }\n\n                // SEE based pruning for captures and checks\n                // Avoid pruning sacrifices of our last piece for stalemate\n                int margin = std::max(167 * depth + captHist * 34 / 1024, 0);\n                if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece])\n                    && !pos.see_ge(move, -margin))\n                    continue;\n            }\n            else if (!ss->followPV || !PvNode)\n            {\n                int history = (*contHist[0])[movedPiece][move.to_sq()]\n                            + (*contHist[1])[movedPiece][move.to_sq()]\n                            + sharedHistory.pawn_entry(pos)[movedPiece][move.to_sq()];\n\n                // Continuation history based pruning\n                if (history < -4097 * depth)\n                    continue;\n\n                history += 71 * mainHistory[us][move.raw()] / 32;\n\n                // (*Scaler): Generally, lower divisors scales well\n                lmrDepth += history / 2995;\n\n                Value futilityValue = ss->staticEval + 42 + 151 * !bestMove + 120 * lmrDepth\n                                    + 86 * (ss->staticEval > alpha);\n\n                // Futility pruning: parent node\n                // (*Scaler): Generally, more frequent futility pruning\n                // scales well\n                if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha)\n                {\n                    if (bestValue <= futilityValue && !is_decisive(bestValue)\n                        && !is_win(futilityValue))\n                        bestValue = futilityValue;\n                    continue;\n                }\n\n                lmrDepth = std::max(lmrDepth, 0);\n\n                // Prune moves with negative SEE\n                if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth))\n                    continue;\n            }\n        }\n\n        // Step 15. Extensions\n        // Singular extension search. If all moves but one\n        // fail low on a search of (alpha-s, beta-s), and just one fails high on\n        // (alpha, beta), then that move is singular and should be extended. To\n        // verify this we do a reduced search on the position excluding the ttMove\n        // and if the result is lower than ttValue minus a margin, then we will\n        // extend the ttMove. Recursive singular search is avoided.\n\n        // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue)\n        // and lower extension margins scale well.\n        if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv\n            && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER)\n            && ttData.depth >= depth - 3 && !is_shuffling(move, ss, pos))\n        {\n            Value singularBeta  = ttData.value - (60 + 66 * (ss->ttPv && !PvNode)) * depth / 55;\n            Depth singularDepth = newDepth / 2;\n\n            ss->excludedMove = move;\n            value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);\n            ss->excludedMove = Move::none();\n\n            if (value < singularBeta)\n            {\n                int corrValAdj   = std::abs(correctionValue) / 210590;\n                int doubleMargin = -4 + 212 * PvNode - 182 * !ttCapture - corrValAdj\n                                 - 906 * ttMoveHistory / 116517 - (ss->ply > rootDepth) * 44;\n                int tripleMargin = 73 + 320 * PvNode - 218 * !ttCapture + 92 * ss->ttPv - corrValAdj\n                                 - (ss->ply > rootDepth) * 45;\n\n                extension =\n                  1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin);\n\n                depth++;\n            }\n\n            // Multi-cut pruning\n            // Our ttMove is assumed to fail high based on the bound of the TT entry,\n            // and if after excluding the ttMove with a reduced search we fail high\n            // over the original beta, we assume this expected cut-node is not\n            // singular (multiple moves fail high), and we can prune the whole\n            // subtree by returning a softbound.\n            else if (value >= beta && !is_decisive(value))\n            {\n                ttMoveHistory << std::max(-424 - 107 * depth, -3375);\n                return value;\n            }\n\n            // Negative extensions\n            // If other moves failed high over (ttValue - margin) without the\n            // ttMove on a reduced search, but we cannot do multi-cut because\n            // (ttValue - margin) is lower than the original beta, we do not know\n            // if the ttMove is singular or can do a multi-cut, so we reduce the\n            // ttMove in favor of other moves based on some conditions:\n\n            // If the ttMove is assumed to fail high over current beta\n            else if (ttData.value >= beta)\n                extension = -3;\n\n            // If we are on a cutNode but the ttMove is not assumed to fail high\n            // over current beta\n            else if (cutNode)\n                extension = -2;\n        }\n\n        // Step 16. Make the move\n        do_move(pos, move, st, givesCheck, ss);\n\n        // Add extension to new depth\n        newDepth += extension;\n        uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0;\n\n        // Decrease reduction for PvNodes (*Scaler)\n        if (ss->ttPv)\n            r -= 2819 + PvNode * 973 + (ttData.value > alpha) * 905\n               + (ttData.depth >= depth) * (935 + cutNode * 959);\n\n        r += 691;  // Base reduction offset to compensate for other tweaks\n        r -= moveCount * 65;\n        r -= std::abs(correctionValue) / 25600;\n\n        // Increase reduction for cut nodes\n        if (cutNode)\n            r += 3611 + 985 * !ttData.move;\n\n        // Increase reduction if ttMove is a capture\n        if (ttCapture)\n            r += 1054;\n\n        // Increase reduction if next ply has a lot of fail high\n        if ((ss + 1)->cutoffCnt > 1)\n            r += 251 + 1124 * ((ss + 1)->cutoffCnt > 2) + 1042 * allNode;\n\n        // For first picked move (ttMove) reduce reduction\n        if (move == ttData.move)\n            r -= 2239;\n\n        if (capture)\n            ss->statScore = 863 * int(PieceValue[pos.captured_piece()]) / 128\n                          + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())];\n        else\n            ss->statScore = 2 * mainHistory[us][move.raw()]\n                          + (*contHist[0])[movedPiece][move.to_sq()]\n                          + (*contHist[1])[movedPiece][move.to_sq()];\n\n        // Decrease/increase reduction for moves with a good/bad history\n        r -= ss->statScore * 428 / 4096;\n\n        // Scale up reductions for expected ALL nodes\n        if (allNode)\n            r += r * 273 / (256 * depth + 260);\n\n        // Step 17. Late moves reduction / extension (LMR)\n        if (depth >= 2 && moveCount > 1)\n        {\n            // In general we want to cap the LMR depth search at newDepth, but when\n            // reduction is negative, we allow this move a limited search extension\n            // beyond the first move depth.\n            // To prevent problems when the max value is less than the min value,\n            // std::clamp has been replaced by a more robust implementation.\n            Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + 2)) + PvNode;\n\n            ss->reduction = newDepth - d;\n            value         = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);\n            ss->reduction = 0;\n\n            // Do a full-depth search when reduced LMR search fails high\n            // (*Scaler) Shallower searches here don't scale well\n            if (value > alpha)\n            {\n                // Adjust full-depth search based on LMR results - if the result was\n                // good enough search deeper, if it was bad enough search shallower.\n                const bool doDeeperSearch    = d < newDepth && value > bestValue + 48;\n                const bool doShallowerSearch = value < bestValue + 9;\n\n                newDepth += doDeeperSearch - doShallowerSearch;\n\n                if (newDepth > d)\n                    value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);\n\n                // Post LMR continuation history updates\n                update_continuation_histories(ss, movedPiece, move.to_sq(), 1426);\n            }\n        }\n\n        // Step 18. Full-depth search when LMR is skipped\n        else if (!PvNode || moveCount > 1)\n        {\n            // Increase reduction if ttMove is not present\n            if (!ttData.move)\n                r += 1057;\n\n            // Note that if expected reduction is high, we reduce search depth here\n            value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha,\n                                   newDepth - (r > 4628) - (r > 5772 && newDepth > 2), !cutNode);\n        }\n\n        // For PV nodes only, do a full PV search on the first move or after a fail high,\n        // otherwise let the parent node fail low with value <= alpha and try another move.\n        if (PvNode && (moveCount == 1 || value > alpha))\n        {\n            (ss + 1)->pv    = pv;\n            (ss + 1)->pv[0] = Move::none();\n\n            // Extend move from transposition table if we are about to dive into qsearch.\n            // decisive score handling improves mate finding and retrograde analysis.\n            if (move == ttData.move\n                && ((is_valid(ttData.value) && is_decisive(ttData.value) && ttData.depth > 0)\n                    || ttData.depth > 1))\n                newDepth = std::max(newDepth, 1);\n\n            value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);\n        }\n\n        // Step 19. Undo move\n        undo_move(pos, move);\n\n        assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);\n\n        // Step 20. Check for a new best move\n        // Finished searching the move. If a stop occurred, the return value of\n        // the search cannot be trusted, and we return immediately without updating\n        // best move, principal variation nor transposition table.\n        if (threads.stop.load(std::memory_order_relaxed))\n            return VALUE_ZERO;\n\n        if (rootNode)\n        {\n            RootMove& rm = *std::find(rootMoves.begin(), rootMoves.end(), move);\n\n            rm.effort += nodes - nodeCount;\n\n            rm.averageScore =\n              rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value;\n\n            rm.meanSquaredScore = rm.meanSquaredScore != -VALUE_INFINITE * VALUE_INFINITE\n                                  ? (value * std::abs(value) + rm.meanSquaredScore) / 2\n                                  : value * std::abs(value);\n\n            // PV move or new best move?\n            if (moveCount == 1 || value > alpha)\n            {\n                rm.score = rm.uciScore = value;\n                rm.selDepth            = selDepth;\n                rm.scoreLowerbound = rm.scoreUpperbound = false;\n\n                if (value >= beta)\n                {\n                    rm.scoreLowerbound = true;\n                    rm.uciScore        = beta;\n                }\n                else if (value <= alpha)\n                {\n                    rm.scoreUpperbound = true;\n                    rm.uciScore        = alpha;\n                }\n\n                rm.pv.resize(1);\n\n                assert((ss + 1)->pv);\n\n                for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m)\n                    rm.pv.push_back(*m);\n\n                // We record how often the best move has been changed in each iteration.\n                // This information is used for time management. In MultiPV mode,\n                // we must take care to only do this for the first PV line.\n                if (moveCount > 1 && !pvIdx)\n                    ++bestMoveChanges;\n            }\n            else\n                // All other moves but the PV, are set to the lowest value: this\n                // is not a problem when sorting because the sort is stable and the\n                // move position in the list is preserved - just the PV is pushed up.\n                rm.score = -VALUE_INFINITE;\n        }\n\n        // In case we have an alternative move equal in eval to the current bestmove,\n        // promote it to bestmove by pretending it just exceeds alpha (but not beta).\n        int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 14) == 0\n                   && !is_win(std::abs(value) + 1));\n\n        if (value + inc > bestValue)\n        {\n            bestValue = value;\n\n            if (value + inc > alpha)\n            {\n                bestMove = move;\n\n                if (PvNode && !rootNode)  // Update pv even in fail-high case\n                    update_pv(ss->pv, move, (ss + 1)->pv);\n\n                if (value >= beta)\n                {\n                    // (*Scaler) Infrequent and small updates scale well\n                    ss->cutoffCnt += (extension < 2) || PvNode;\n                    assert(value >= beta);  // Fail high\n                    break;\n                }\n\n                // Reduce other moves if we have found at least one score improvement\n                if (depth > 2 && depth < 14 && !is_decisive(value))\n                    depth -= 2;\n\n                assert(depth > 0);\n                alpha = value;  // Update alpha! Always alpha < beta\n            }\n        }\n\n        // If the move is worse than some previously searched move,\n        // remember it, to update its stats later.\n        if (move != bestMove && moveCount <= SEARCHEDLIST_CAPACITY)\n        {\n            if (capture)\n                capturesSearched.push_back(move);\n            else\n                quietsSearched.push_back(move);\n        }\n    }\n\n    // Step 21. Check for mate and stalemate\n    // All legal moves have been searched and if there are no legal moves, it\n    // must be a mate or a stalemate. If we are in a singular extension search then\n    // return a fail low score.\n\n    assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());\n\n    // Adjust best value for fail high cases\n    if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(alpha))\n        bestValue = (bestValue * depth + beta) / (depth + 1);\n\n    if (!moveCount)\n        bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW;\n\n    // If there is a move that produces search value greater than alpha,\n    // we update the stats of searched moves.\n    else if (bestMove)\n    {\n        update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth,\n                         ttData.move);\n        if (!PvNode)\n            ttMoveHistory << (bestMove == ttData.move ? 805 : -787);\n    }\n\n    // Bonus for prior quiet countermove that caused the fail low\n    else if (!priorCapture && prevSq != SQ_NONE)\n    {\n        int bonusScale = -232;\n        bonusScale -= (ss - 1)->statScore / 108;\n        bonusScale += std::min(59 * depth, 454);\n        bonusScale += 169 * ((ss - 1)->moveCount > 8);\n        bonusScale += 145 * (!ss->inCheck && bestValue <= ss->staticEval - 110);\n        bonusScale += 154 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 73);\n\n        bonusScale = std::max(bonusScale, 0);\n\n        // scaledBonus ranges from 0 to roughly 2.3M, overflows happen for multipliers larger than 900\n        const int scaledBonus = std::min(135 * depth - 80, 1400) * bonusScale;\n\n        update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,\n                                      scaledBonus * 221 / 16384);\n\n        mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 235 / 32768;\n\n        if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)\n            sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << scaledBonus * 290 / 8192;\n    }\n\n    // Bonus for prior capture countermove that caused the fail low\n    else if (priorCapture && prevSq != SQ_NONE)\n    {\n        Piece capturedPiece = pos.captured_piece();\n        assert(capturedPiece != NO_PIECE);\n        captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1018;\n    }\n\n    if (PvNode)\n        bestValue = std::min(bestValue, maxValue);\n\n    // If no good move is found and the previous position was ttPv, then the previous\n    // opponent move is probably good and the new position is added to the search tree.\n    if (bestValue <= alpha)\n        ss->ttPv = ss->ttPv || (ss - 1)->ttPv;\n\n    // Write gathered information in transposition table. Note that the\n    // static evaluation is saved as it was before correction history.\n    if (!excludedMove && !(rootNode && pvIdx))\n        ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv,\n                       bestValue >= beta    ? BOUND_LOWER\n                       : PvNode && bestMove ? BOUND_EXACT\n                                            : BOUND_UPPER,\n                       moveCount != 0 ? depth : std::min(MAX_PLY - 1, depth + 6), bestMove,\n                       unadjustedStaticEval, tt.generation());\n\n    // Adjust correction history if the best move is not a capture\n    // and the error direction matches whether we are above/below bounds.\n    if (!ss->inCheck && !(bestMove && pos.capture(bestMove))\n        && (bestValue > ss->staticEval) == bool(bestMove))\n    {\n        auto bonus =\n          std::clamp(int(bestValue - ss->staticEval) * depth * (bestMove ? 12 : 17) / 128,\n                     -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);\n        update_correction_history(pos, ss, *this, 1069 * bonus / 1024);\n    }\n\n    assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);\n\n    return bestValue;\n}\n\n\n// Quiescence search function, which is called by the main search function with\n// depth zero, or recursively with further decreasing depth. With depth <= 0, we\n// \"should\" be using static eval only, but tactical moves may confuse the static eval.\n// To fight this horizon effect, we implement this qsearch of tactical moves.\n// See https://www.chessprogramming.org/Horizon_Effect\n// and https://www.chessprogramming.org/Quiescence_Search\ntemplate<NodeType nodeType>\nValue Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) {\n\n    static_assert(nodeType != Root);\n    constexpr bool PvNode = nodeType == PV;\n\n    assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);\n    assert(PvNode || (alpha == beta - 1));\n\n    // Check if we have an upcoming move that draws by repetition\n    if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))\n    {\n        alpha = value_draw(nodes);\n        if (alpha >= beta)\n            return alpha;\n    }\n\n    Move      pv[MAX_PLY + 1];\n    StateInfo st;\n\n    Key   posKey;\n    Move  move, bestMove;\n    Value bestValue, value, futilityBase;\n    bool  pvHit, givesCheck, capture;\n    int   moveCount;\n\n    // Step 1. Initialize node\n    if (PvNode)\n    {\n        (ss + 1)->pv = pv;\n        ss->pv[0]    = Move::none();\n    }\n\n    bestMove    = Move::none();\n    ss->inCheck = pos.checkers();\n    moveCount   = 0;\n\n    // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)\n    if (PvNode && selDepth < ss->ply + 1)\n        selDepth = ss->ply + 1;\n\n    // Step 2. Check for an immediate draw or maximum ply reached\n    if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY)\n        return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;\n\n    assert(0 <= ss->ply && ss->ply < MAX_PLY);\n\n    // Step 3. Transposition table lookup\n    posKey                         = pos.key();\n    auto [ttHit, ttData, ttWriter] = tt.probe(posKey);\n    // Need further processing of the saved data\n    ss->ttHit    = ttHit;\n    ttData.move  = ttHit ? ttData.move : Move::none();\n    ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE;\n    pvHit        = ttHit && ttData.is_pv;\n\n    // At non-PV nodes we check for an early TT cutoff\n    if (!PvNode && ttData.depth >= DEPTH_QS\n        && is_valid(ttData.value)  // Can happen when !ttHit or when access race in probe()\n        && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))\n        return ttData.value;\n\n    // Step 4. Static evaluation of the position\n    Value unadjustedStaticEval = VALUE_NONE;\n    if (ss->inCheck)\n        bestValue = futilityBase = -VALUE_INFINITE;\n    else\n    {\n        const auto correctionValue = correction_value(*this, pos, ss);\n\n        if (ss->ttHit)\n        {\n            // Never assume anything about values stored in TT\n            unadjustedStaticEval = ttData.eval;\n\n            if (!is_valid(unadjustedStaticEval))\n                unadjustedStaticEval = evaluate(pos);\n\n            ss->staticEval = bestValue =\n              to_corrected_static_eval(unadjustedStaticEval, correctionValue);\n\n            // ttValue can be used as a better position evaluation\n            if (is_valid(ttData.value) && !is_decisive(ttData.value)\n                && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER)))\n                bestValue = ttData.value;\n        }\n        else\n        {\n            unadjustedStaticEval = evaluate(pos);\n            ss->staticEval       = bestValue =\n              to_corrected_static_eval(unadjustedStaticEval, correctionValue);\n        }\n\n        // Stand pat. Return immediately if static value is at least beta\n        if (bestValue >= beta)\n        {\n            if (!is_decisive(bestValue))\n                bestValue = (bestValue + beta) / 2;\n\n            if (!ss->ttHit)\n                ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,\n                               DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval,\n                               tt.generation());\n            return bestValue;\n        }\n\n        if (bestValue > alpha)\n            alpha = bestValue;\n\n        futilityBase = ss->staticEval + 328;\n    }\n\n    const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory};\n\n    Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;\n\n    // Initialize a MovePicker object for the current position, and prepare to search\n    // the moves. We presently use two stages of move generator in quiescence search:\n    // captures, or evasions only when in check.\n    MovePicker mp(pos, ttData.move, DEPTH_QS, &mainHistory, &lowPlyHistory, &captureHistory,\n                  contHist, &sharedHistory, ss->ply);\n\n    // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta\n    // cutoff occurs.\n    while ((move = mp.next_move()) != Move::none())\n    {\n        assert(move.is_ok());\n\n        if (!pos.legal(move))\n            continue;\n\n        givesCheck = pos.gives_check(move);\n        capture    = pos.capture_stage(move);\n\n        moveCount++;\n\n        // Step 6. Pruning\n        if (!is_loss(bestValue))\n        {\n            // Futility pruning and moveCount pruning\n            if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase)\n                && move.type_of() != PROMOTION)\n            {\n                if (moveCount > 2)\n                    continue;\n\n                Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())];\n\n                // If static eval + value of piece we are going to capture is\n                // much lower than alpha, we can prune this move.\n                if (futilityValue <= alpha)\n                {\n                    bestValue = std::max(bestValue, futilityValue);\n                    continue;\n                }\n\n                // If static exchange evaluation is low enough\n                // we can prune this move.\n                if (!pos.see_ge(move, alpha - futilityBase))\n                {\n                    bestValue = std::max(bestValue, std::min(alpha, futilityBase));\n                    continue;\n                }\n            }\n\n            // Skip non-captures\n            if (!capture)\n                continue;\n\n            // Do not search moves with bad enough SEE values\n            if (!pos.see_ge(move, -73))\n                continue;\n        }\n\n        // Step 7. Make and search the move\n        do_move(pos, move, st, givesCheck, ss);\n\n        value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha);\n        undo_move(pos, move);\n\n        assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);\n\n        // Step 8. Check for a new best move\n        if (value > bestValue)\n        {\n            bestValue = value;\n\n            if (value > alpha)\n            {\n                bestMove = move;\n\n                if (PvNode)  // Update pv even in fail-high case\n                    update_pv(ss->pv, move, (ss + 1)->pv);\n\n                if (value < beta)  // Update alpha here!\n                    alpha = value;\n                else\n                    break;  // Fail high\n            }\n        }\n    }\n\n    // Step 9. Check for mate\n    // All legal moves have been searched. A special case: if we are\n    // in check and no legal moves were found, it is checkmate.\n    if (ss->inCheck && bestValue == -VALUE_INFINITE)\n    {\n        assert(!MoveList<LEGAL>(pos).size());\n        return mated_in(ss->ply);  // Plies to mate from the root\n    }\n\n    if (!is_decisive(bestValue) && bestValue > beta)\n        bestValue = (bestValue + beta) / 2;\n\n    Color us = pos.side_to_move();\n    if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us)\n        && type_of(pos.captured_piece()) >= ROOK)\n    {\n        if (!((us == WHITE ? shift<NORTH>(pos.pieces(us, PAWN))\n                           : shift<SOUTH>(pos.pieces(us, PAWN)))\n              & ~pos.pieces()))  // no pawn pushes available\n        {\n            pos.state()->checkersBB = Rank1BB;  // search for legal king-moves only\n            if (!MoveList<LEGAL>(pos).size())   // stalemate\n                bestValue = VALUE_DRAW;\n            pos.state()->checkersBB = 0;\n        }\n    }\n\n    // Save gathered info in transposition table. The static evaluation\n    // is saved as it was before adjustment by correction history.\n    ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit,\n                   bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, DEPTH_QS, bestMove,\n                   unadjustedStaticEval, tt.generation());\n\n    assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);\n\n    return bestValue;\n}\n\nDepth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {\n    int reductionScale = reductions[d] * reductions[mn];\n    return reductionScale - delta * 585 / rootDelta + !i * reductionScale * 206 / 512 + 1133;\n}\n\n// elapsed() returns the time elapsed since the search started. If the\n// 'nodestime' option is enabled, it will return the count of nodes searched\n// instead. This function is called to check whether the search should be\n// stopped based on predefined thresholds like time limits or nodes searched.\n//\n// elapsed_time() returns the actual time elapsed since the start of the search.\n// This function is intended for use only when printing PV outputs, and not used\n// for making decisions within the search algorithm itself.\nTimePoint Search::Worker::elapsed() const {\n    return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); });\n}\n\nTimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); }\n\nValue Search::Worker::evaluate(const Position& pos) {\n    return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable,\n                          optimism[pos.side_to_move()]);\n}\n\nnamespace {\n// Adjusts a mate or TB score from \"plies to mate from the root\" to\n// \"plies to mate from the current position\". Standard scores are unchanged.\n// The function is called before storing a value in the transposition table.\nValue value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; }\n\n\n// Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition\n// table (which refers to the plies to mate/be mated from current position) to\n// \"plies to mate/be mated (TB win/loss) from the root\". However, to avoid\n// potentially false mate or TB scores related to the 50 moves rule and the\n// graph history interaction, we return the highest non-TB score instead.\nValue value_from_tt(Value v, int ply, int r50c) {\n\n    if (!is_valid(v))\n        return VALUE_NONE;\n\n    // handle TB win or better\n    if (is_win(v))\n    {\n        // Downgrade a potentially false mate score\n        if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c)\n            return VALUE_TB_WIN_IN_MAX_PLY - 1;\n\n        // Downgrade a potentially false TB score.\n        if (VALUE_TB - v > 100 - r50c)\n            return VALUE_TB_WIN_IN_MAX_PLY - 1;\n\n        return v - ply;\n    }\n\n    // handle TB loss or worse\n    if (is_loss(v))\n    {\n        // Downgrade a potentially false mate score.\n        if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c)\n            return VALUE_TB_LOSS_IN_MAX_PLY + 1;\n\n        // Downgrade a potentially false TB score.\n        if (VALUE_TB + v > 100 - r50c)\n            return VALUE_TB_LOSS_IN_MAX_PLY + 1;\n\n        return v + ply;\n    }\n\n    return v;\n}\n\n\n// Adds current move and appends child pv[]\nvoid update_pv(Move* pv, Move move, const Move* childPv) {\n\n    for (*pv++ = move; childPv && *childPv != Move::none();)\n        *pv++ = *childPv++;\n    *pv = Move::none();\n}\n\n\n// Updates stats at the end of search() when a bestMove is found\nvoid update_all_stats(const Position& pos,\n                      Stack*          ss,\n                      Search::Worker& workerThread,\n                      Move            bestMove,\n                      Square          prevSq,\n                      SearchedList&   quietsSearched,\n                      SearchedList&   capturesSearched,\n                      Depth           depth,\n                      Move            ttMove) {\n\n    CapturePieceToHistory& captureHistory = workerThread.captureHistory;\n    Piece                  movedPiece     = pos.moved_piece(bestMove);\n    PieceType              capturedPiece;\n\n    int bonus =\n      std::min(128 * depth - 77, 1529) + 353 * (bestMove == ttMove) + (ss - 1)->statScore / 32;\n    int malus = std::min(882 * depth - 204, 2122);\n\n    if (!pos.capture_stage(bestMove))\n    {\n        update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 806 / 1024);\n\n        int actualMalus = malus * 1113 / 1024;\n        // Decrease stats for all non-best quiet moves\n        for (Move move : quietsSearched)\n        {\n            actualMalus = actualMalus * 977 / 1024;\n            update_quiet_histories(pos, ss, workerThread, move, -actualMalus);\n        }\n    }\n    else\n    {\n        // Increase stats for the best move in case it was a capture move\n        capturedPiece = type_of(pos.piece_on(bestMove.to_sq()));\n        captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1286 / 1024;\n    }\n\n    // Extra penalty for a quiet early move that was not a TT move in\n    // previous ply when it gets refuted.\n    if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece())\n        update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 616 / 1024);\n\n    // Decrease stats for all non-best capture moves\n    for (Move move : capturesSearched)\n    {\n        movedPiece    = pos.moved_piece(move);\n        capturedPiece = type_of(pos.piece_on(move.to_sq()));\n        captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1559 / 1024;\n    }\n}\n\n\n// Updates histories of the move pairs formed by moves\n// at ply -1, -2, -3, -4, and -6 with current move.\nvoid update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {\n    static constexpr std::array<ConthistBonus, 6> conthist_bonuses = {\n      {{1, 1071}, {2, 753}, {3, 329}, {4, 539}, {5, 124}, {6, 434}}};\n\n    // Multipliers for positive history consistency\n    constexpr int CMHCMultipliers[] = {96, 100, 100, 100, 115, 118, 129};\n    int           positiveCount     = 0;\n\n    for (const auto [i, weight] : conthist_bonuses)\n    {\n        // Only update the first 2 continuation histories if we are in check\n        if (ss->inCheck && i > 2)\n            break;\n\n        if (((ss - i)->currentMove).is_ok())\n        {\n            auto& historyEntry = (*(ss - i)->continuationHistory)[pc][to];\n            if (historyEntry > 0)\n                positiveCount++;\n\n            int multiplier = CMHCMultipliers[positiveCount];\n            historyEntry << (bonus * weight * multiplier / 131072) + 73 * (i < 2);\n        }\n    }\n}\n\n// Updates move sorting heuristics\n\nvoid update_quiet_histories(\n  const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {\n\n    Color us = pos.side_to_move();\n    workerThread.mainHistory[us][move.raw()] << bonus;  // Untuned to prevent duplicate effort\n\n    if (ss->ply < LOW_PLY_HISTORY_SIZE)\n        workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 682 / 1024;\n\n    update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 894 / 1024);\n\n    workerThread.sharedHistory.pawn_entry(pos)[pos.moved_piece(move)][move.to_sq()]\n      << bonus * (bonus > 0 ? 974 : 543) / 1024;\n}\n\n}\n\n// When playing with strength handicap, choose the best move among a set of\n// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.\nMove Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) {\n    static PRNG rng(now());  // PRNG sequence should be non-deterministic\n\n    // RootMoves are already sorted by score in descending order\n    Value  topScore = rootMoves[0].score;\n    int    delta    = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue));\n    int    maxScore = -VALUE_INFINITE;\n    double weakness = 120 - 2 * level;\n\n    // Choose best move. For each move score we add two terms, both dependent on\n    // weakness. One is deterministic and bigger for weaker levels, and one is\n    // random. Then we choose the move with the resulting highest score.\n    for (size_t i = 0; i < multiPV; ++i)\n    {\n        // This is our magic formula\n        int push = int(weakness * int(topScore - rootMoves[i].score)\n                       + delta * (rng.rand<unsigned>() % int(weakness)))\n                 / 128;\n\n        if (rootMoves[i].score + push >= maxScore)\n        {\n            maxScore = rootMoves[i].score + push;\n            best     = rootMoves[i].pv[0];\n        }\n    }\n\n    return best;\n}\n\n// Used to print debug info and, more importantly, to detect\n// when we are out of available time and thus stop the search.\nvoid SearchManager::check_time(Search::Worker& worker) {\n    if (--callsCnt > 0)\n        return;\n\n    // When using nodes, ensure checking rate is not lower than 0.1% of nodes\n    callsCnt = worker.limits.nodes ? std::min(512, int(worker.limits.nodes / 1024)) : 512;\n\n    static TimePoint lastInfoTime = now();\n\n    TimePoint elapsed = tm.elapsed([&worker]() { return worker.threads.nodes_searched(); });\n    TimePoint tick    = worker.limits.startTime + elapsed;\n\n    if (tick - lastInfoTime >= 1000)\n    {\n        lastInfoTime = tick;\n        dbg_print();\n    }\n\n    // We should not stop pondering until told so by the GUI\n    if (ponder)\n        return;\n\n    if (\n      // Later we rely on the fact that we can at least use the mainthread previous\n      // root-search score and PV in a multithreaded environment to prove mated-in scores.\n      worker.completedDepth >= 1\n      && ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit))\n          || (worker.limits.movetime && elapsed >= worker.limits.movetime)\n          || (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes)))\n        worker.threads.stop = true;\n}\n\n// Used to correct and extend PVs for moves that have a TB (but not a mate) score.\n// Keeps the search based PV for as long as it is verified to maintain the game\n// outcome, truncates afterwards. Finally, extends to mate the PV, providing a\n// possible continuation (but not a proven mating line).\nvoid syzygy_extend_pv(const OptionsMap&         options,\n                      const Search::LimitsType& limits,\n                      Position&                 pos,\n                      RootMove&                 rootMove,\n                      Value&                    v) {\n\n    auto t_start      = std::chrono::steady_clock::now();\n    int  moveOverhead = int(options[\"Move Overhead\"]);\n    bool rule50       = bool(options[\"Syzygy50MoveRule\"]);\n\n    // Do not use more than moveOverhead / 2 time, if time management is active\n    auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool {\n        auto t_end = std::chrono::steady_clock::now();\n        return limits.use_time_management()\n            && 2 * std::chrono::duration<double, std::milli>(t_end - t_start).count()\n                 > moveOverhead;\n    };\n\n    std::list<StateInfo> sts;\n\n    // Step 0, do the rootMove, no correction allowed, as needed for MultiPV in TB.\n    auto& stRoot = sts.emplace_back();\n    pos.do_move(rootMove.pv[0], stRoot);\n    int ply = 1;\n\n    // Step 1, walk the PV to the last position in TB with correct decisive score\n    while (size_t(ply) < rootMove.pv.size())\n    {\n        Move& pvMove = rootMove.pv[ply];\n\n        RootMoves legalMoves;\n        for (const auto& m : MoveList<LEGAL>(pos))\n            legalMoves.emplace_back(m);\n\n        Tablebases::Config config =\n          Tablebases::rank_root_moves(options, pos, legalMoves, false, time_abort);\n        RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove);\n\n        if (legalMoves[0].tbRank != rm.tbRank)\n            break;\n\n        ply++;\n\n        auto& st = sts.emplace_back();\n        pos.do_move(pvMove, st);\n\n        // Do not allow for repetitions or drawing moves along the PV in TB regime\n        if (config.rootInTB && ((rule50 && pos.is_draw(ply)) || pos.is_repetition(ply)))\n        {\n            pos.undo_move(pvMove);\n            ply--;\n            break;\n        }\n\n        // Full PV shown will thus be validated and end in TB.\n        // If we cannot validate the full PV in time, we do not show it.\n        if (config.rootInTB && time_abort())\n            break;\n    }\n\n    // Resize the PV to the correct part\n    rootMove.pv.resize(ply);\n\n    // Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info\n    // using top ranked moves (minimal DTZ), which gives optimal mates only for simple\n    // endgames e.g. KRvK.\n    while (!(rule50 && pos.is_draw(0)))\n    {\n        if (time_abort())\n            break;\n\n        RootMoves legalMoves;\n        for (const auto& m : MoveList<LEGAL>(pos))\n        {\n            auto&     rm = legalMoves.emplace_back(m);\n            StateInfo tmpSI;\n            pos.do_move(m, tmpSI);\n            // Give a score of each move to break DTZ ties restricting opponent mobility,\n            // but not giving the opponent a capture.\n            for (const auto& mOpp : MoveList<LEGAL>(pos))\n                rm.tbRank -= pos.capture(mOpp) ? 100 : 1;\n            pos.undo_move(m);\n        }\n\n        // Mate found\n        if (legalMoves.size() == 0)\n            break;\n\n        // Sort moves according to their above assigned rank.\n        // This will break ties for moves with equal DTZ in rank_root_moves.\n        std::stable_sort(\n          legalMoves.begin(), legalMoves.end(),\n          [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });\n\n        // The winning side tries to minimize DTZ, the losing side maximizes it\n        Tablebases::Config config =\n          Tablebases::rank_root_moves(options, pos, legalMoves, true, time_abort);\n\n        // If DTZ is not available we might not find a mate, so we bail out\n        if (!config.rootInTB || config.cardinality > 0)\n            break;\n\n        ply++;\n\n        Move& pvMove = legalMoves[0].pv[0];\n        rootMove.pv.push_back(pvMove);\n        auto& st = sts.emplace_back();\n        pos.do_move(pvMove, st);\n    }\n\n    // Finding a draw in this function is an exceptional case, that cannot happen when rule50 is false or\n    // during engine game play, since we have a winning score, and play correctly\n    // with TB support. However, it can be that a position is draw due to the 50 move\n    // rule if it has been been reached on the board with a non-optimal 50 move counter\n    // (e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 ) which TB with dtz counter rounding\n    // cannot always correctly rank. See also\n    // https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495\n    // We adjust the score to match the found PV. Note that a TB loss score can be\n    // displayed if the engine did not find a drawing move yet, but eventually search\n    // will figure it out (e.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 )\n    if (pos.is_draw(0))\n        v = VALUE_DRAW;\n\n    // Undo the PV moves\n    for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it)\n        pos.undo_move(*it);\n\n    // Inform if we couldn't get a full extension in time\n    if (time_abort())\n        sync_cout\n          << \"info string Syzygy based PV extension requires more time, increase Move Overhead as needed.\"\n          << sync_endl;\n}\n\nvoid SearchManager::pv(Search::Worker&           worker,\n                       const ThreadPool&         threads,\n                       const TranspositionTable& tt,\n                       Depth                     depth) {\n\n    const auto nodes     = threads.nodes_searched();\n    auto&      rootMoves = worker.rootMoves;\n    auto&      pos       = worker.rootPos;\n    size_t     pvIdx     = worker.pvIdx;\n    size_t     multiPV   = std::min(size_t(worker.options[\"MultiPV\"]), rootMoves.size());\n    uint64_t   tbHits    = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);\n\n    for (size_t i = 0; i < multiPV; ++i)\n    {\n        bool updated = rootMoves[i].score != -VALUE_INFINITE;\n\n        if (depth == 1 && !updated && i > 0)\n            continue;\n\n        Depth d = updated ? depth : std::max(1, depth - 1);\n        Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore;\n\n        if (v == -VALUE_INFINITE)\n            v = VALUE_ZERO;\n\n        bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB;\n        v       = tb ? rootMoves[i].tbScore : v;\n\n        bool isExact = i != pvIdx || tb || !updated;  // tablebase- and previous-scores are exact\n\n        // Potentially correct and extend the PV, and in exceptional cases v\n        if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY\n            && ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact))\n            syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v);\n\n        std::string pv;\n        for (Move m : rootMoves[i].pv)\n            pv += UCIEngine::move(m, pos.is_chess960()) + \" \";\n\n        // Remove last whitespace\n        if (!pv.empty())\n            pv.pop_back();\n\n        auto wdl   = worker.options[\"UCI_ShowWDL\"] ? UCIEngine::wdl(v, pos) : \"\";\n        auto bound = rootMoves[i].scoreLowerbound\n                     ? \"lowerbound\"\n                     : (rootMoves[i].scoreUpperbound ? \"upperbound\" : \"\");\n\n        InfoFull info;\n\n        info.depth    = d;\n        info.selDepth = rootMoves[i].selDepth;\n        info.multiPV  = i + 1;\n        info.score    = {v, pos};\n        info.wdl      = wdl;\n\n        if (!isExact)\n            info.bound = bound;\n\n        TimePoint time = std::max(TimePoint(1), tm.elapsed_time());\n        info.timeMs    = time;\n        info.nodes     = nodes;\n        info.nps       = nodes * 1000 / time;\n        info.tbHits    = tbHits;\n        info.pv        = pv;\n        info.hashfull  = tt.hashfull();\n\n        updates.onUpdateFull(info);\n    }\n}\n\n// Called in case we have no ponder move before exiting the search,\n// for instance, in case we stop the search during a fail high at root.\n// We try hard to have a ponder move to return to the GUI,\n// otherwise in case of 'ponder on' we have nothing to think about.\nbool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) {\n\n    StateInfo st;\n\n    assert(pv.size() == 1);\n    if (pv[0] == Move::none())\n        return false;\n\n    pos.do_move(pv[0], st, &tt);\n\n    auto [ttHit, ttData, ttWriter] = tt.probe(pos.key());\n    if (ttHit)\n    {\n        if (MoveList<LEGAL>(pos).contains(ttData.move))\n            pv.push_back(ttData.move);\n    }\n\n    pos.undo_move(pv[0]);\n    return pv.size() > 1;\n}\n\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/search.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef SEARCH_H_INCLUDED\n#define SEARCH_H_INCLUDED\n\n#include <algorithm>\n#include <array>\n#include <atomic>\n#include <cassert>\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <map>\n#include <memory>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"history.h\"\n#include \"misc.h\"\n#include \"nnue/network.h\"\n#include \"nnue/nnue_accumulator.h\"\n#include \"numa.h\"\n#include \"position.h\"\n#include \"score.h\"\n#include \"syzygy/tbprobe.h\"\n#include \"timeman.h\"\n#include \"types.h\"\n\nnamespace Stockfish {\n\n// Different node types, used as a template parameter\nenum NodeType {\n    NonPV,\n    PV,\n    Root\n};\n\nclass TranspositionTable;\nclass ThreadPool;\nclass OptionsMap;\n\nnamespace Search {\n\n// Stack struct keeps track of the information we need to remember from nodes\n// shallower and deeper in the tree during the search. Each search thread has\n// its own array of Stack objects, indexed by the current ply.\nstruct Stack {\n    Move*                       pv;\n    PieceToHistory*             continuationHistory;\n    CorrectionHistory<PieceTo>* continuationCorrectionHistory;\n    int                         ply;\n    Move                        currentMove;\n    Move                        excludedMove;\n    Value                       staticEval;\n    int                         statScore;\n    int                         moveCount;\n    bool                        inCheck;\n    bool                        ttPv;\n    bool                        ttHit;\n    bool                        followPV;\n    int                         cutoffCnt;\n    int                         reduction;\n};\n\n\n// RootMove struct is used for moves at the root of the tree. For each root move\n// we store a score and a PV (really a refutation in the case of moves which\n// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.\nstruct RootMove {\n\n    explicit RootMove(Move m) :\n        pv(1, m) {}\n    bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);\n    bool operator==(const Move& m) const { return pv[0] == m; }\n    // Sort in descending order\n    bool operator<(const RootMove& m) const {\n        return m.score != score ? m.score < score : m.previousScore < previousScore;\n    }\n\n    uint64_t          effort           = 0;\n    Value             score            = -VALUE_INFINITE;\n    Value             previousScore    = -VALUE_INFINITE;\n    Value             averageScore     = -VALUE_INFINITE;\n    Value             meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;\n    Value             uciScore         = -VALUE_INFINITE;\n    bool              scoreLowerbound  = false;\n    bool              scoreUpperbound  = false;\n    int               selDepth         = 0;\n    int               tbRank           = 0;\n    Value             tbScore;\n    std::vector<Move> pv;\n};\n\nusing RootMoves = std::vector<RootMove>;\n\n\n// LimitsType struct stores information sent by the caller about the analysis required.\nstruct LimitsType {\n\n    // Init explicitly due to broken value-initialization of non POD in MSVC\n    LimitsType() {\n        time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);\n        movestogo = depth = mate = perft = infinite = 0;\n        nodes                                       = 0;\n        ponderMode                                  = false;\n    }\n\n    bool use_time_management() const { return time[WHITE] || time[BLACK]; }\n\n    std::vector<std::string> searchmoves;\n    TimePoint                time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;\n    int                      movestogo, depth, mate, perft, infinite;\n    uint64_t                 nodes;\n    bool                     ponderMode;\n};\n\n\n// The UCI stores the uci options, thread pool, and transposition table.\n// This struct is used to easily forward data to the Search::Worker class.\nstruct SharedState {\n    SharedState(const OptionsMap&                                         optionsMap,\n                ThreadPool&                                               threadPool,\n                TranspositionTable&                                       transpositionTable,\n                std::map<NumaIndex, SharedHistories>&                     sharedHists,\n                const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& nets) :\n        options(optionsMap),\n        threads(threadPool),\n        tt(transpositionTable),\n        sharedHistories(sharedHists),\n        networks(nets) {}\n\n    const OptionsMap&                                         options;\n    ThreadPool&                                               threads;\n    TranspositionTable&                                       tt;\n    std::map<NumaIndex, SharedHistories>&                     sharedHistories;\n    const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;\n};\n\nclass Worker;\n\n// Null Object Pattern, implement a common interface for the SearchManagers.\n// A Null Object will be given to non-mainthread workers.\nclass ISearchManager {\n   public:\n    virtual ~ISearchManager() {}\n    virtual void check_time(Search::Worker&) = 0;\n};\n\nstruct InfoShort {\n    int   depth;\n    Score score;\n};\n\nstruct InfoFull: InfoShort {\n    int              selDepth;\n    size_t           multiPV;\n    std::string_view wdl;\n    std::string_view bound;\n    size_t           timeMs;\n    size_t           nodes;\n    size_t           nps;\n    size_t           tbHits;\n    std::string_view pv;\n    int              hashfull;\n};\n\nstruct InfoIteration {\n    int              depth;\n    std::string_view currmove;\n    size_t           currmovenumber;\n};\n\n// Skill structure is used to implement strength limit. If we have a UCI_Elo,\n// we convert it to an appropriate skill level, anchored to the Stash engine.\n// This method is based on a fit of the Elo results for games played between\n// Stockfish at various skill levels and various versions of the Stash engine.\n// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately\n// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2\nstruct Skill {\n    // Lowest and highest Elo ratings used in the skill level calculation\n    constexpr static int LowestElo  = 1320;\n    constexpr static int HighestElo = 3190;\n\n    Skill(int skill_level, int uci_elo) {\n        if (uci_elo)\n        {\n            double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo);\n            level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);\n        }\n        else\n            level = double(skill_level);\n    }\n    bool enabled() const { return level < 20.0; }\n    bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }\n    Move pick_best(const RootMoves&, size_t multiPV);\n\n    double level;\n    Move   best = Move::none();\n};\n\n// SearchManager manages the search from the main thread. It is responsible for\n// keeping track of the time, and storing data strictly related to the main thread.\nclass SearchManager: public ISearchManager {\n   public:\n    using UpdateShort    = std::function<void(const InfoShort&)>;\n    using UpdateFull     = std::function<void(const InfoFull&)>;\n    using UpdateIter     = std::function<void(const InfoIteration&)>;\n    using UpdateBestmove = std::function<void(std::string_view, std::string_view)>;\n\n    struct UpdateContext {\n        UpdateShort    onUpdateNoMoves;\n        UpdateFull     onUpdateFull;\n        UpdateIter     onIter;\n        UpdateBestmove onBestmove;\n    };\n\n\n    SearchManager(const UpdateContext& updateContext) :\n        updates(updateContext) {}\n\n    void check_time(Search::Worker& worker) override;\n\n    void pv(Search::Worker&           worker,\n            const ThreadPool&         threads,\n            const TranspositionTable& tt,\n            Depth                     depth);\n\n    Stockfish::TimeManagement tm;\n    double                    originalTimeAdjust;\n    int                       callsCnt;\n    std::atomic_bool          ponder;\n\n    std::array<Value, 4> iterValue;\n    double               previousTimeReduction;\n    Value                bestPreviousScore;\n    Value                bestPreviousAverageScore;\n    bool                 stopOnPonderhit;\n\n    size_t id;\n\n    const UpdateContext& updates;\n};\n\nclass NullSearchManager: public ISearchManager {\n   public:\n    void check_time(Search::Worker&) override {}\n};\n\n// Search::Worker is the class that does the actual search.\n// It is instantiated once per thread, and it is responsible for keeping track\n// of the search history, and storing data required for the search.\nclass Worker {\n   public:\n    Worker(SharedState&,\n           std::unique_ptr<ISearchManager>,\n           size_t,\n           size_t,\n           size_t,\n           NumaReplicatedAccessToken);\n\n    // Called at instantiation to initialize reductions tables.\n    // Reset histories, usually before a new game.\n    void clear();\n\n    // Called when the program receives the UCI 'go' command.\n    // It searches from the root position and outputs the \"bestmove\".\n    void start_searching();\n\n    bool is_mainthread() const { return threadIdx == 0; }\n\n    void ensure_network_replicated();\n\n    // Public because they need to be updatable by the stats\n    ButterflyHistory mainHistory;\n    LowPlyHistory    lowPlyHistory;\n\n    CapturePieceToHistory           captureHistory;\n    ContinuationHistory             continuationHistory[2][2];\n    CorrectionHistory<Continuation> continuationCorrectionHistory;\n\n    TTMoveHistory    ttMoveHistory;\n    SharedHistories& sharedHistory;\n\n   private:\n    void iterative_deepening();\n\n    void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss);\n    void\n    do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss);\n    void do_null_move(Position& pos, StateInfo& st, Stack* const ss);\n    void undo_move(Position& pos, const Move move);\n    void undo_null_move(Position& pos);\n\n    // This is the main search function, for both PV and non-PV nodes\n    template<NodeType nodeType>\n    Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);\n\n    // Quiescence search function, which is called by the main search\n    template<NodeType nodeType>\n    Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta);\n\n    Depth reduction(bool i, Depth d, int mn, int delta) const;\n\n    // Pointer to the search manager, only allowed to be called by the main thread\n    SearchManager* main_manager() const {\n        assert(threadIdx == 0);\n        return static_cast<SearchManager*>(manager.get());\n    }\n\n    TimePoint elapsed() const;\n    TimePoint elapsed_time() const;\n\n    Value evaluate(const Position&);\n\n    LimitsType limits;\n\n    size_t                pvIdx, pvLast;\n    std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;\n    int                   selDepth, nmpMinPly;\n\n    Value optimism[COLOR_NB];\n\n    Position  rootPos;\n    StateInfo rootState;\n    RootMoves rootMoves;\n    Depth     rootDepth, completedDepth;\n    Value     rootDelta;\n\n    std::vector<Move> lastIterationPV;\n\n    size_t                    threadIdx, numaThreadIdx, numaTotal;\n    NumaReplicatedAccessToken numaAccessToken;\n\n    // Reductions lookup table initialized at startup\n    std::array<int, MAX_MOVES> reductions;  // [depth or moveNumber]\n\n    // The main thread has a SearchManager, the others have a NullSearchManager\n    std::unique_ptr<ISearchManager> manager;\n\n    Tablebases::Config tbConfig;\n\n    const OptionsMap&                                         options;\n    ThreadPool&                                               threads;\n    TranspositionTable&                                       tt;\n    const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;\n\n    // Used by NNUE\n    Eval::NNUE::AccumulatorStack  accumulatorStack;\n    Eval::NNUE::AccumulatorCaches refreshTable;\n\n    friend class Stockfish::ThreadPool;\n    friend class SearchManager;\n};\n\nstruct ConthistBonus {\n    int index;\n    int weight;\n};\n\n\n}  // namespace Search\n\n}  // namespace Stockfish\n\n#endif  // #ifndef SEARCH_H_INCLUDED\n"
  },
  {
    "path": "src/shm.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef SHM_H_INCLUDED\n#define SHM_H_INCLUDED\n\n#include <algorithm>\n#include <cinttypes>\n#include <cstddef>\n#include <cstdint>\n#include <cstring>\n#include <functional>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <new>\n#include <optional>\n#include <sstream>\n#include <string>\n#include <type_traits>\n#include <utility>\n#include <variant>\n\n#if defined(__linux__) && !defined(__ANDROID__)\n    #include \"shm_linux.h\"\n#endif\n\n#if defined(__ANDROID__)\n    #include <limits.h>\n    #define SF_MAX_SEM_NAME_LEN NAME_MAX\n#endif\n\n#include \"types.h\"\n\n#include \"memory.h\"\n\n#if defined(_WIN32)\n\n    #if _WIN32_WINNT < 0x0601\n        #undef _WIN32_WINNT\n        #define _WIN32_WINNT 0x0601  // Force to include needed API prototypes\n    #endif\n\n    #if !defined(NOMINMAX)\n        #define NOMINMAX\n    #endif\n    #include <windows.h>\n#elif defined(__linux__)\n    #include <cstring>\n    #include <fcntl.h>\n    #include <pthread.h>\n    #include <semaphore.h>\n    #include <sys/mman.h>\n    #include <sys/stat.h>\n    #include <unistd.h>\n#endif\n\n\n#if defined(__APPLE__)\n    #include <mach-o/dyld.h>\n    #include <sys/syslimits.h>\n\n#elif defined(__sun)\n    #include <stdlib.h>\n\n#elif defined(__FreeBSD__)\n    #include <sys/sysctl.h>\n    #include <sys/types.h>\n    #include <unistd.h>\n\n#elif defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__)\n    #include <limits.h>\n    #include <unistd.h>\n#endif\n\n\nnamespace Stockfish {\n\n// argv[0] CANNOT be used because we need to identify the executable.\n// argv[0] contains the command used to invoke it, which does not involve the full path.\n// Just using a path is not fully resilient either, as the executable could\n// have changed if it wasn't locked by the OS. Ideally we would hash the executable\n// but it's not really that important at this point.\n// If the path is longer than 4095 bytes the hash will be computed from an unspecified\n// amount of bytes of the path; in particular it can a hash of an empty string.\n\ninline std::string getExecutablePathHash() {\n    char        executable_path[4096] = {0};\n    std::size_t path_length           = 0;\n\n#if defined(_WIN32)\n    path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path));\n\n#elif defined(__APPLE__)\n    uint32_t size = sizeof(executable_path);\n    if (_NSGetExecutablePath(executable_path, &size) == 0)\n    {\n        path_length = std::strlen(executable_path);\n    }\n\n#elif defined(__sun)  // Solaris\n    const char* path = getexecname();\n    if (path)\n    {\n        std::strncpy(executable_path, path, sizeof(executable_path) - 1);\n        path_length = std::strlen(executable_path);\n    }\n\n#elif defined(__FreeBSD__)\n    size_t size   = sizeof(executable_path);\n    int    mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};\n    if (sysctl(mib, 4, executable_path, &size, NULL, 0) == 0)\n    {\n        path_length = std::strlen(executable_path);\n    }\n\n#elif defined(__NetBSD__) || defined(__DragonFly__)\n    ssize_t len = readlink(\"/proc/curproc/exe\", executable_path, sizeof(executable_path) - 1);\n    if (len >= 0)\n    {\n        executable_path[len] = '\\0';\n        path_length          = len;\n    }\n\n#elif defined(__linux__)\n    ssize_t len = readlink(\"/proc/self/exe\", executable_path, sizeof(executable_path) - 1);\n    if (len >= 0)\n    {\n        executable_path[len] = '\\0';\n        path_length          = len;\n    }\n\n#endif\n\n    // In case of any error the path will be empty.\n    return std::string(executable_path, path_length);\n}\n\nenum class SystemWideSharedConstantAllocationStatus {\n    NoAllocation,\n    LocalMemory,\n    SharedMemory\n};\n\n#if defined(_WIN32)\n\ninline std::string GetLastErrorAsString(DWORD error) {\n    //Get the error message ID, if any.\n    DWORD errorMessageID = error;\n    if (errorMessageID == 0)\n    {\n        return std::string();  //No error message has been recorded\n    }\n\n    LPSTR messageBuffer = nullptr;\n\n    //Ask Win32 to give us the string version of that message ID.\n    //The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).\n    size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM\n                                   | FORMAT_MESSAGE_IGNORE_INSERTS,\n                                 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n                                 (LPSTR) &messageBuffer, 0, NULL);\n\n    //Copy the error message into a std::string.\n    std::string message(messageBuffer, size);\n\n    //Free the Win32's string's buffer.\n    LocalFree(messageBuffer);\n\n    return message;\n}\n\n// Utilizes shared memory to store the value. It is deduplicated system-wide (for the single user).\ntemplate<typename T>\nclass SharedMemoryBackend {\n   public:\n    enum class Status {\n        Success,\n        LargePageAllocationError,\n        FileMappingError,\n        MapViewError,\n        MutexCreateError,\n        MutexWaitError,\n        MutexReleaseError,\n        NotInitialized\n    };\n\n    static constexpr DWORD IS_INITIALIZED_VALUE = 1;\n\n    SharedMemoryBackend() :\n        status(Status::NotInitialized) {};\n\n    SharedMemoryBackend(const std::string& shm_name, const T& value) :\n        status(Status::NotInitialized) {\n\n        initialize(shm_name, value);\n    }\n\n    bool is_valid() const { return status == Status::Success; }\n\n    std::optional<std::string> get_error_message() const {\n        switch (status)\n        {\n        case Status::Success :\n            return std::nullopt;\n        case Status::LargePageAllocationError :\n            return \"Failed to allocate large page memory\";\n        case Status::FileMappingError :\n            return \"Failed to create file mapping: \" + last_error_message;\n        case Status::MapViewError :\n            return \"Failed to map view: \" + last_error_message;\n        case Status::MutexCreateError :\n            return \"Failed to create mutex: \" + last_error_message;\n        case Status::MutexWaitError :\n            return \"Failed to wait on mutex: \" + last_error_message;\n        case Status::MutexReleaseError :\n            return \"Failed to release mutex: \" + last_error_message;\n        case Status::NotInitialized :\n            return \"Not initialized\";\n        default :\n            return \"Unknown error\";\n        }\n    }\n\n    void* get() const { return is_valid() ? pMap : nullptr; }\n\n    ~SharedMemoryBackend() { cleanup(); }\n\n    SharedMemoryBackend(const SharedMemoryBackend&)            = delete;\n    SharedMemoryBackend& operator=(const SharedMemoryBackend&) = delete;\n\n    SharedMemoryBackend(SharedMemoryBackend&& other) noexcept :\n        pMap(other.pMap),\n        hMapFile(other.hMapFile),\n        status(other.status),\n        last_error_message(std::move(other.last_error_message)) {\n\n        other.pMap     = nullptr;\n        other.hMapFile = 0;\n        other.status   = Status::NotInitialized;\n    }\n\n    SharedMemoryBackend& operator=(SharedMemoryBackend&& other) noexcept {\n        if (this != &other)\n        {\n            cleanup();\n            pMap               = other.pMap;\n            hMapFile           = other.hMapFile;\n            status             = other.status;\n            last_error_message = std::move(other.last_error_message);\n\n            other.pMap     = nullptr;\n            other.hMapFile = 0;\n            other.status   = Status::NotInitialized;\n        }\n        return *this;\n    }\n\n    SystemWideSharedConstantAllocationStatus get_status() const {\n        return status == Status::Success ? SystemWideSharedConstantAllocationStatus::SharedMemory\n                                         : SystemWideSharedConstantAllocationStatus::NoAllocation;\n    }\n\n   private:\n    void initialize(const std::string& shm_name, const T& value) {\n        const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);\n\n        // Try allocating with large pages first.\n        hMapFile = windows_try_with_large_page_priviliges(\n          [&](size_t largePageSize) {\n              const size_t total_size_aligned =\n                (total_size + largePageSize - 1) / largePageSize * largePageSize;\n\n    #if defined(_WIN64)\n              DWORD total_size_low  = total_size_aligned & 0xFFFFFFFFu;\n              DWORD total_size_high = total_size_aligned >> 32u;\n    #else\n              DWORD total_size_low  = total_size_aligned;\n              DWORD total_size_high = 0;\n    #endif\n\n              return CreateFileMappingA(INVALID_HANDLE_VALUE, NULL,\n                                        PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES,\n                                        total_size_high, total_size_low, shm_name.c_str());\n          },\n          []() { return (void*) nullptr; });\n\n        // Fallback to normal allocation if no large pages available.\n        if (!hMapFile)\n        {\n            hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,\n                                          static_cast<DWORD>(total_size), shm_name.c_str());\n        }\n\n        if (!hMapFile)\n        {\n            const DWORD err    = GetLastError();\n            last_error_message = GetLastErrorAsString(err);\n            status             = Status::FileMappingError;\n            return;\n        }\n\n        pMap = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, total_size);\n        if (!pMap)\n        {\n            const DWORD err    = GetLastError();\n            last_error_message = GetLastErrorAsString(err);\n            status             = Status::MapViewError;\n            cleanup_partial();\n            return;\n        }\n\n        // Use named mutex to ensure only one initializer\n        std::string mutex_name = shm_name + \"$mutex\";\n        HANDLE      hMutex     = CreateMutexA(NULL, FALSE, mutex_name.c_str());\n        if (!hMutex)\n        {\n            const DWORD err    = GetLastError();\n            last_error_message = GetLastErrorAsString(err);\n            status             = Status::MutexCreateError;\n            cleanup_partial();\n            return;\n        }\n\n        DWORD wait_result = WaitForSingleObject(hMutex, INFINITE);\n        if (wait_result != WAIT_OBJECT_0)\n        {\n            const DWORD err    = GetLastError();\n            last_error_message = GetLastErrorAsString(err);\n            status             = Status::MutexWaitError;\n            CloseHandle(hMutex);\n            cleanup_partial();\n            return;\n        }\n\n        // Crucially, we place the object first to ensure alignment.\n        volatile DWORD* is_initialized =\n          std::launder(reinterpret_cast<DWORD*>(reinterpret_cast<char*>(pMap) + sizeof(T)));\n        T* object = std::launder(reinterpret_cast<T*>(pMap));\n\n        if (*is_initialized != IS_INITIALIZED_VALUE)\n        {\n            // First time initialization, message for debug purposes\n            new (object) T{value};\n            *is_initialized = IS_INITIALIZED_VALUE;\n        }\n\n        BOOL release_result = ReleaseMutex(hMutex);\n        CloseHandle(hMutex);\n\n        if (!release_result)\n        {\n            const DWORD err    = GetLastError();\n            last_error_message = GetLastErrorAsString(err);\n            status             = Status::MutexReleaseError;\n            cleanup_partial();\n            return;\n        }\n\n        status = Status::Success;\n    }\n\n    void cleanup_partial() {\n        if (pMap != nullptr)\n        {\n            UnmapViewOfFile(pMap);\n            pMap = nullptr;\n        }\n        if (hMapFile)\n        {\n            CloseHandle(hMapFile);\n            hMapFile = 0;\n        }\n    }\n\n    void cleanup() {\n        if (pMap != nullptr)\n        {\n            UnmapViewOfFile(pMap);\n            pMap = nullptr;\n        }\n        if (hMapFile)\n        {\n            CloseHandle(hMapFile);\n            hMapFile = 0;\n        }\n    }\n\n    void*       pMap     = nullptr;\n    HANDLE      hMapFile = 0;\n    Status      status   = Status::NotInitialized;\n    std::string last_error_message;\n};\n\n#elif defined(__linux__) && !defined(__ANDROID__)\n\ntemplate<typename T>\nclass SharedMemoryBackend {\n   public:\n    SharedMemoryBackend() = default;\n\n    SharedMemoryBackend(const std::string& shm_name, const T& value) :\n        shm1(shm::create_shared<T>(shm_name, value)) {}\n\n    void* get() const {\n        const T* ptr = &shm1->get();\n        return reinterpret_cast<void*>(const_cast<T*>(ptr));\n    }\n\n    bool is_valid() const { return shm1 && shm1->is_open() && shm1->is_initialized(); }\n\n    SystemWideSharedConstantAllocationStatus get_status() const {\n        return is_valid() ? SystemWideSharedConstantAllocationStatus::SharedMemory\n                          : SystemWideSharedConstantAllocationStatus::NoAllocation;\n    }\n\n    std::optional<std::string> get_error_message() const {\n        if (!shm1)\n            return \"Shared memory not initialized\";\n\n        if (!shm1->is_open())\n            return \"Shared memory is not open\";\n\n        if (!shm1->is_initialized())\n            return \"Not initialized\";\n\n        return std::nullopt;\n    }\n\n   private:\n    std::optional<shm::SharedMemory<T>> shm1;\n};\n\n#else\n\n// For systems that don't have shared memory, or support is troublesome.\n// The way fallback is done is that we need a dummy backend.\n\ntemplate<typename T>\nclass SharedMemoryBackend {\n   public:\n    SharedMemoryBackend() = default;\n\n    SharedMemoryBackend([[maybe_unused]] const std::string& shm_name,\n                        [[maybe_unused]] const T&           value) {}\n\n    void* get() const { return nullptr; }\n\n    bool is_valid() const { return false; }\n\n    SystemWideSharedConstantAllocationStatus get_status() const {\n        return SystemWideSharedConstantAllocationStatus::NoAllocation;\n    }\n\n    std::optional<std::string> get_error_message() const { return \"Dummy SharedMemoryBackend\"; }\n};\n\n#endif\n\ntemplate<typename T>\nstruct SharedMemoryBackendFallback {\n    SharedMemoryBackendFallback() = default;\n\n    SharedMemoryBackendFallback(const std::string&, const T& value) :\n        fallback_object(make_unique_large_page<T>(value)) {}\n\n    void* get() const { return fallback_object.get(); }\n\n    SharedMemoryBackendFallback(const SharedMemoryBackendFallback&)            = delete;\n    SharedMemoryBackendFallback& operator=(const SharedMemoryBackendFallback&) = delete;\n\n    SharedMemoryBackendFallback(SharedMemoryBackendFallback&& other) noexcept :\n        fallback_object(std::move(other.fallback_object)) {}\n\n    SharedMemoryBackendFallback& operator=(SharedMemoryBackendFallback&& other) noexcept {\n        fallback_object = std::move(other.fallback_object);\n        return *this;\n    }\n\n    SystemWideSharedConstantAllocationStatus get_status() const {\n        return fallback_object == nullptr ? SystemWideSharedConstantAllocationStatus::NoAllocation\n                                          : SystemWideSharedConstantAllocationStatus::LocalMemory;\n    }\n\n    std::optional<std::string> get_error_message() const {\n        if (fallback_object == nullptr)\n            return \"Not initialized\";\n\n        return \"Shared memory not supported by the OS. Local allocation fallback.\";\n    }\n\n   private:\n    LargePagePtr<T> fallback_object;\n};\n\n// Platform-independent wrapper\ntemplate<typename T>\nstruct SystemWideSharedConstant {\n   private:\n    static std::string createHashString(const std::string& input) {\n        char buf[1024];\n        std::snprintf(buf, sizeof(buf), \"%016\" PRIx64, hash_string(input));\n        return buf;\n    }\n\n   public:\n    // We can't run the destructor because it may be in a completely different process.\n    // The object stored must also be obviously in-line but we can't check for that, other than some basic checks that cover most cases.\n    static_assert(std::is_trivially_destructible_v<T>);\n    static_assert(std::is_trivially_move_constructible_v<T>);\n    static_assert(std::is_trivially_copy_constructible_v<T>);\n\n    SystemWideSharedConstant() = default;\n\n\n    // Content is addressed by its hash. An additional discriminator can be added to account for differences\n    // that are not present in the content, for example NUMA node allocation.\n    SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {\n        std::size_t content_hash    = std::hash<T>{}(value);\n        std::size_t executable_hash = hash_string(getExecutablePathHash());\n\n        char buf[1024];\n        std::snprintf(buf, sizeof(buf), \"Local\\\\sf_%zu$%zu$%zu\", content_hash, executable_hash,\n                      discriminator);\n        std::string shm_name = buf;\n\n#if defined(__linux__) && !defined(__ANDROID__)\n        // POSIX shared memory names must start with a slash\n        shm_name = \"/sf_\" + createHashString(shm_name);\n\n        // hash name and make sure it is not longer than SF_MAX_SEM_NAME_LEN\n        if (shm_name.size() > SF_MAX_SEM_NAME_LEN)\n        {\n            shm_name = shm_name.substr(0, SF_MAX_SEM_NAME_LEN - 1);\n        }\n#endif\n\n        SharedMemoryBackend<T> shm_backend(shm_name, value);\n\n        if (shm_backend.is_valid())\n        {\n            backend = std::move(shm_backend);\n        }\n        else\n        {\n            backend = SharedMemoryBackendFallback<T>(shm_name, value);\n        }\n    }\n\n    SystemWideSharedConstant(const SystemWideSharedConstant&)            = delete;\n    SystemWideSharedConstant& operator=(const SystemWideSharedConstant&) = delete;\n\n    SystemWideSharedConstant(SystemWideSharedConstant&& other) noexcept :\n        backend(std::move(other.backend)) {}\n\n    SystemWideSharedConstant& operator=(SystemWideSharedConstant&& other) noexcept {\n        backend = std::move(other.backend);\n        return *this;\n    }\n\n    const T& operator*() const { return *std::launder(reinterpret_cast<const T*>(get_ptr())); }\n\n    bool operator==(std::nullptr_t) const noexcept { return get_ptr() == nullptr; }\n\n    bool operator!=(std::nullptr_t) const noexcept { return get_ptr() != nullptr; }\n\n    SystemWideSharedConstantAllocationStatus get_status() const {\n        return std::visit(\n          [](const auto& end) -> SystemWideSharedConstantAllocationStatus {\n              if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)\n              {\n                  return SystemWideSharedConstantAllocationStatus::NoAllocation;\n              }\n              else\n              {\n                  return end.get_status();\n              }\n          },\n          backend);\n    }\n\n    std::optional<std::string> get_error_message() const {\n        return std::visit(\n          [](const auto& end) -> std::optional<std::string> {\n              if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)\n              {\n                  return std::nullopt;\n              }\n              else\n              {\n                  return end.get_error_message();\n              }\n          },\n          backend);\n    }\n\n   private:\n    auto get_ptr() const {\n        return std::visit(\n          [](const auto& end) -> void* {\n              if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)\n              {\n                  return nullptr;\n              }\n              else\n              {\n                  return end.get();\n              }\n          },\n          backend);\n    }\n\n    std::variant<std::monostate, SharedMemoryBackend<T>, SharedMemoryBackendFallback<T>> backend;\n};\n\n\n}  // namespace Stockfish\n\n#endif  // #ifndef SHM_H_INCLUDED\n"
  },
  {
    "path": "src/shm_linux.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef SHM_LINUX_H_INCLUDED\n#define SHM_LINUX_H_INCLUDED\n\n#if !defined(__linux__) || defined(__ANDROID__)\n    #error shm_linux.h should not be included on this platform.\n#endif\n\n#include <atomic>\n#include <cassert>\n#include <cerrno>\n#include <cstdlib>\n#include <cstring>\n#include <cstdio>\n#include <dirent.h>\n#include <mutex>\n#include <new>\n#include <optional>\n#include <pthread.h>\n#include <string>\n#include <inttypes.h>\n#include <type_traits>\n\n#include <fcntl.h>\n#include <signal.h>\n#include <sys/file.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <limits.h>\n#define SF_MAX_SEM_NAME_LEN NAME_MAX\n\n#include \"misc.h\"\n\nnamespace Stockfish::shm {\n\nnamespace detail {\n\nstruct ShmHeader {\n    static constexpr uint32_t SHM_MAGIC = 0xAD5F1A12;\n    pthread_mutex_t           mutex;\n    std::atomic<uint32_t>     ref_count{0};\n    std::atomic<bool>         initialized{false};\n    uint32_t                  magic = SHM_MAGIC;\n};\n\nclass SharedMemoryBase {\n   public:\n    virtual ~SharedMemoryBase()                                        = default;\n    virtual void               close(bool skip_unmap = false) noexcept = 0;\n    virtual const std::string& name() const noexcept                   = 0;\n};\n\nclass SharedMemoryRegistry {\n   private:\n    static std::mutex                     registry_mutex_;\n    static std::vector<SharedMemoryBase*> active_instances_;\n\n   public:\n    static void register_instance(SharedMemoryBase* instance) {\n        std::scoped_lock lock(registry_mutex_);\n        active_instances_.push_back(instance);\n    }\n\n    static void unregister_instance(SharedMemoryBase* instance) {\n        std::scoped_lock lock(registry_mutex_);\n        active_instances_.erase(\n          std::remove(active_instances_.begin(), active_instances_.end(), instance),\n          active_instances_.end());\n    }\n\n    static void cleanup_all(bool skip_unmap = false) noexcept {\n        std::scoped_lock lock(registry_mutex_);\n        for (auto* instance : active_instances_)\n            instance->close(skip_unmap);\n        active_instances_.clear();\n    }\n};\n\ninline std::mutex                     SharedMemoryRegistry::registry_mutex_;\ninline std::vector<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;\n\nclass CleanupHooks {\n   private:\n    static std::once_flag register_once_;\n\n    static void handle_signal(int sig) noexcept {\n        // Search threads may still be running, so skip munmap (but still perform\n        // other cleanup actions). The memory mappings will be released on exit.\n        SharedMemoryRegistry::cleanup_all(true);\n\n        // Invoke the default handler, which will exit\n        struct sigaction sa;\n        sa.sa_handler = SIG_DFL;\n        sigemptyset(&sa.sa_mask);\n        sa.sa_flags = 0;\n        if (sigaction(sig, &sa, nullptr) == -1)\n            _Exit(128 + sig);\n\n        raise(sig);\n    }\n\n    static void register_signal_handlers() noexcept {\n        std::atexit([]() { SharedMemoryRegistry::cleanup_all(true); });\n\n        constexpr int signals[] = {SIGHUP,  SIGINT,  SIGQUIT, SIGILL, SIGABRT, SIGFPE,\n                                   SIGSEGV, SIGTERM, SIGBUS,  SIGSYS, SIGXCPU, SIGXFSZ};\n\n        struct sigaction sa;\n        sa.sa_handler = handle_signal;\n        sigemptyset(&sa.sa_mask);\n        sa.sa_flags = 0;\n\n        for (int sig : signals)\n            sigaction(sig, &sa, nullptr);\n    }\n\n   public:\n    static void ensure_registered() noexcept {\n        std::call_once(register_once_, register_signal_handlers);\n    }\n};\n\ninline std::once_flag CleanupHooks::register_once_;\n\n\ninline int portable_fallocate(int fd, off_t offset, off_t length) {\n#ifdef __APPLE__\n    fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, offset, length, 0};\n    int      ret   = fcntl(fd, F_PREALLOCATE, &store);\n    if (ret == -1)\n    {\n        store.fst_flags = F_ALLOCATEALL;\n        ret             = fcntl(fd, F_PREALLOCATE, &store);\n    }\n    if (ret != -1)\n        ret = ftruncate(fd, offset + length);\n    return ret;\n#else\n    return posix_fallocate(fd, offset, length);\n#endif\n}\n\n}  // namespace detail\n\ntemplate<typename T>\nclass SharedMemory: public detail::SharedMemoryBase {\n    static_assert(std::is_trivially_copyable_v<T>, \"T must be trivially copyable\");\n    static_assert(!std::is_pointer_v<T>, \"T cannot be a pointer type\");\n\n   private:\n    std::string        name_;\n    int                fd_         = -1;\n    void*              mapped_ptr_ = nullptr;\n    T*                 data_ptr_   = nullptr;\n    detail::ShmHeader* header_ptr_ = nullptr;\n    size_t             total_size_ = 0;\n    std::string        sentinel_base_;\n    std::string        sentinel_path_;\n\n    static constexpr size_t calculate_total_size() noexcept {\n        return sizeof(T) + sizeof(detail::ShmHeader);\n    }\n\n    static std::string make_sentinel_base(const std::string& name) {\n        char buf[32];\n        // Using std::to_string here causes non-deterministic PGO builds.\n        // snprintf, being part of libc, is insensitive to the formatted values.\n        std::snprintf(buf, sizeof(buf), \"sfshm_%016\" PRIu64, hash_string(name));\n        return buf;\n    }\n\n   public:\n    explicit SharedMemory(const std::string& name) noexcept :\n        name_(name),\n        total_size_(calculate_total_size()),\n        sentinel_base_(make_sentinel_base(name)) {}\n\n    ~SharedMemory() noexcept override {\n        detail::SharedMemoryRegistry::unregister_instance(this);\n        close();\n    }\n\n    SharedMemory(const SharedMemory&)            = delete;\n    SharedMemory& operator=(const SharedMemory&) = delete;\n\n    SharedMemory(SharedMemory&& other) noexcept :\n        name_(std::move(other.name_)),\n        fd_(other.fd_),\n        mapped_ptr_(other.mapped_ptr_),\n        data_ptr_(other.data_ptr_),\n        header_ptr_(other.header_ptr_),\n        total_size_(other.total_size_),\n        sentinel_base_(std::move(other.sentinel_base_)),\n        sentinel_path_(std::move(other.sentinel_path_)) {\n\n        detail::SharedMemoryRegistry::unregister_instance(&other);\n        detail::SharedMemoryRegistry::register_instance(this);\n        other.reset();\n    }\n\n    SharedMemory& operator=(SharedMemory&& other) noexcept {\n        if (this != &other)\n        {\n            detail::SharedMemoryRegistry::unregister_instance(this);\n            close();\n\n            name_          = std::move(other.name_);\n            fd_            = other.fd_;\n            mapped_ptr_    = other.mapped_ptr_;\n            data_ptr_      = other.data_ptr_;\n            header_ptr_    = other.header_ptr_;\n            total_size_    = other.total_size_;\n            sentinel_base_ = std::move(other.sentinel_base_);\n            sentinel_path_ = std::move(other.sentinel_path_);\n\n            detail::SharedMemoryRegistry::unregister_instance(&other);\n            detail::SharedMemoryRegistry::register_instance(this);\n\n            other.reset();\n        }\n        return *this;\n    }\n\n    [[nodiscard]] bool open(const T& initial_value) noexcept {\n        detail::CleanupHooks::ensure_registered();\n\n        bool retried_stale = false;\n\n        while (true)\n        {\n            if (is_open())\n                return false;\n\n            bool created_new = false;\n            fd_              = shm_open(name_.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666);\n\n            if (fd_ == -1)\n            {\n                fd_ = shm_open(name_.c_str(), O_RDWR, 0666);\n                if (fd_ == -1)\n                    return false;\n            }\n            else\n                created_new = true;\n\n            if (!lock_file(LOCK_EX))\n            {\n                ::close(fd_);\n                reset();\n                return false;\n            }\n\n            bool invalid_header = false;\n            bool success =\n              created_new ? setup_new_region(initial_value) : setup_existing_region(invalid_header);\n\n            if (!success)\n            {\n                if (created_new || invalid_header)\n                    shm_unlink(name_.c_str());\n                if (mapped_ptr_)\n                    unmap_region();\n                unlock_file();\n                ::close(fd_);\n                reset();\n\n                if (!created_new && invalid_header && !retried_stale)\n                {\n                    retried_stale = true;\n                    continue;\n                }\n                return false;\n            }\n\n            if (!lock_shared_mutex())\n            {\n                if (created_new)\n                    shm_unlink(name_.c_str());\n                if (mapped_ptr_)\n                    unmap_region();\n                unlock_file();\n                ::close(fd_);\n                reset();\n\n                if (!created_new && !retried_stale)\n                {\n                    retried_stale = true;\n                    continue;\n                }\n                return false;\n            }\n\n            if (!create_sentinel_file_locked())\n            {\n                unlock_shared_mutex();\n                unmap_region();\n                if (created_new)\n                    shm_unlink(name_.c_str());\n                unlock_file();\n                ::close(fd_);\n                reset();\n                return false;\n            }\n\n            header_ptr_->ref_count.fetch_add(1, std::memory_order_acq_rel);\n\n            unlock_shared_mutex();\n            unlock_file();\n            detail::SharedMemoryRegistry::register_instance(this);\n            return true;\n        }\n    }\n\n    void close(bool skip_unmap = false) noexcept override {\n        if (fd_ == -1 && mapped_ptr_ == nullptr)\n            return;\n\n        bool remove_region = false;\n        bool file_locked   = lock_file(LOCK_EX);\n        bool mutex_locked  = false;\n\n        if (file_locked && header_ptr_ != nullptr)\n            mutex_locked = lock_shared_mutex();\n\n        if (mutex_locked)\n        {\n            if (header_ptr_)\n            {\n                header_ptr_->ref_count.fetch_sub(1, std::memory_order_acq_rel);\n            }\n            remove_sentinel_file();\n            remove_region = !has_other_live_sentinels_locked();\n            unlock_shared_mutex();\n        }\n        else\n        {\n            remove_sentinel_file();\n            decrement_refcount_relaxed();\n        }\n\n        if (skip_unmap)\n            mapped_ptr_ = nullptr;\n        else\n            unmap_region();\n\n        if (remove_region)\n            shm_unlink(name_.c_str());\n\n        if (file_locked)\n            unlock_file();\n\n        if (fd_ != -1)\n        {\n            ::close(fd_);\n            fd_ = -1;\n        }\n\n        if (!skip_unmap)\n            reset();\n    }\n\n    const std::string& name() const noexcept override { return name_; }\n\n    [[nodiscard]] bool is_open() const noexcept { return fd_ != -1 && mapped_ptr_ && data_ptr_; }\n\n    [[nodiscard]] const T& get() const noexcept { return *data_ptr_; }\n\n    [[nodiscard]] const T* operator->() const noexcept { return data_ptr_; }\n\n    [[nodiscard]] const T& operator*() const noexcept { return *data_ptr_; }\n\n    [[nodiscard]] uint32_t ref_count() const noexcept {\n        return header_ptr_ ? header_ptr_->ref_count.load(std::memory_order_acquire) : 0;\n    }\n\n    [[nodiscard]] bool is_initialized() const noexcept {\n        return header_ptr_ ? header_ptr_->initialized.load(std::memory_order_acquire) : false;\n    }\n\n    static void cleanup_all_instances() noexcept { detail::SharedMemoryRegistry::cleanup_all(); }\n\n   private:\n    void reset() noexcept {\n        fd_         = -1;\n        mapped_ptr_ = nullptr;\n        data_ptr_   = nullptr;\n        header_ptr_ = nullptr;\n        sentinel_path_.clear();\n    }\n\n    void unmap_region() noexcept {\n        if (mapped_ptr_)\n        {\n            munmap(mapped_ptr_, total_size_);\n            mapped_ptr_ = nullptr;\n            data_ptr_   = nullptr;\n            header_ptr_ = nullptr;\n        }\n    }\n\n    [[nodiscard]] bool lock_file(int operation) noexcept {\n        if (fd_ == -1)\n            return false;\n\n        while (flock(fd_, operation) == -1)\n        {\n            if (errno == EINTR)\n                continue;\n            return false;\n        }\n        return true;\n    }\n\n    void unlock_file() noexcept {\n        if (fd_ == -1)\n            return;\n\n        while (flock(fd_, LOCK_UN) == -1)\n        {\n            if (errno == EINTR)\n                continue;\n            break;\n        }\n    }\n\n    std::string sentinel_full_path(pid_t pid) const {\n        char buf[1024];\n        // See above snprintf comment\n        std::snprintf(buf, sizeof(buf), \"/dev/shm/%s.%ld\", sentinel_base_.c_str(), long(pid));\n        return buf;\n    }\n\n    void decrement_refcount_relaxed() noexcept {\n        if (!header_ptr_)\n            return;\n\n        uint32_t expected = header_ptr_->ref_count.load(std::memory_order_relaxed);\n        while (expected != 0\n               && !header_ptr_->ref_count.compare_exchange_weak(\n                 expected, expected - 1, std::memory_order_acq_rel, std::memory_order_relaxed))\n        {}\n    }\n\n    bool create_sentinel_file_locked() noexcept {\n        if (!header_ptr_)\n            return false;\n\n        const pid_t self_pid = getpid();\n        sentinel_path_       = sentinel_full_path(self_pid);\n\n        for (int attempt = 0; attempt < 2; ++attempt)\n        {\n            int fd = ::open(sentinel_path_.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);\n            if (fd != -1)\n            {\n                ::close(fd);\n                return true;\n            }\n\n            if (errno == EEXIST)\n            {\n                ::unlink(sentinel_path_.c_str());\n                decrement_refcount_relaxed();\n                continue;\n            }\n\n            break;\n        }\n\n        sentinel_path_.clear();\n        return false;\n    }\n\n    void remove_sentinel_file() noexcept {\n        if (!sentinel_path_.empty())\n        {\n            ::unlink(sentinel_path_.c_str());\n            sentinel_path_.clear();\n        }\n    }\n\n    static bool pid_is_alive(pid_t pid) noexcept {\n        if (pid <= 0)\n            return false;\n\n        if (kill(pid, 0) == 0)\n            return true;\n\n        return errno == EPERM;\n    }\n\n    [[nodiscard]] bool initialize_shared_mutex() noexcept {\n        if (!header_ptr_)\n            return false;\n\n        pthread_mutexattr_t attr;\n        if (pthread_mutexattr_init(&attr) != 0)\n            return false;\n\n        bool success = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) == 0;\n#if _POSIX_C_SOURCE >= 200809L\n        if (success)\n            success = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) == 0;\n#endif\n\n        if (success)\n            success = pthread_mutex_init(&header_ptr_->mutex, &attr) == 0;\n\n        pthread_mutexattr_destroy(&attr);\n        return success;\n    }\n\n    [[nodiscard]] bool lock_shared_mutex() noexcept {\n        if (!header_ptr_)\n            return false;\n\n        while (true)\n        {\n            int rc = pthread_mutex_lock(&header_ptr_->mutex);\n            if (rc == 0)\n                return true;\n\n#if _POSIX_C_SOURCE >= 200809L\n            if (rc == EOWNERDEAD)\n            {\n                if (pthread_mutex_consistent(&header_ptr_->mutex) == 0)\n                    return true;\n                return false;\n            }\n#endif\n\n            if (rc == EINTR)\n                continue;\n\n            return false;\n        }\n    }\n\n    void unlock_shared_mutex() noexcept {\n        if (header_ptr_)\n            pthread_mutex_unlock(&header_ptr_->mutex);\n    }\n\n    bool has_other_live_sentinels_locked() const noexcept {\n        DIR* dir = opendir(\"/dev/shm\");\n        if (!dir)\n            return false;\n\n        std::string prefix = sentinel_base_ + \".\";\n        bool        found  = false;\n\n        while (dirent* entry = readdir(dir))\n        {\n            std::string name = entry->d_name;\n            if (name.rfind(prefix, 0) != 0)\n                continue;\n\n            auto  pid_str = name.substr(prefix.size());\n            char* end     = nullptr;\n            long  value   = std::strtol(pid_str.c_str(), &end, 10);\n            if (!end || *end != '\\0')\n                continue;\n\n            pid_t pid = static_cast<pid_t>(value);\n            if (pid_is_alive(pid))\n            {\n                found = true;\n                break;\n            }\n\n            std::string stale_path = std::string(\"/dev/shm/\") + name;\n            ::unlink(stale_path.c_str());\n            const_cast<SharedMemory*>(this)->decrement_refcount_relaxed();\n        }\n\n        closedir(dir);\n        return found;\n    }\n\n    [[nodiscard]] bool setup_new_region(const T& initial_value) noexcept {\n        if (ftruncate(fd_, static_cast<off_t>(total_size_)) == -1)\n            return false;\n\n        if (detail::portable_fallocate(fd_, 0, static_cast<off_t>(total_size_)) != 0)\n            return false;\n\n        mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);\n        if (mapped_ptr_ == MAP_FAILED)\n        {\n            mapped_ptr_ = nullptr;\n            return false;\n        }\n\n        data_ptr_ = static_cast<T*>(mapped_ptr_);\n        header_ptr_ =\n          reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T));\n\n        new (header_ptr_) detail::ShmHeader{};\n        new (data_ptr_) T{initial_value};\n\n        if (!initialize_shared_mutex())\n            return false;\n\n        header_ptr_->ref_count.store(0, std::memory_order_release);\n        header_ptr_->initialized.store(true, std::memory_order_release);\n        return true;\n    }\n\n    [[nodiscard]] bool setup_existing_region(bool& invalid_header) noexcept {\n        invalid_header = false;\n\n        struct stat st;\n        fstat(fd_, &st);\n        if (static_cast<size_t>(st.st_size) < total_size_)\n        {\n            invalid_header = true;\n            return false;\n        }\n\n        mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);\n        if (mapped_ptr_ == MAP_FAILED)\n        {\n            mapped_ptr_ = nullptr;\n            return false;\n        }\n\n        data_ptr_   = static_cast<T*>(mapped_ptr_);\n        header_ptr_ = std::launder(\n          reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T)));\n\n        if (!header_ptr_->initialized.load(std::memory_order_acquire)\n            || header_ptr_->magic != detail::ShmHeader::SHM_MAGIC)\n        {\n            invalid_header = true;\n            unmap_region();\n            return false;\n        }\n\n        return true;\n    }\n};\n\ntemplate<typename T>\n[[nodiscard]] std::optional<SharedMemory<T>> create_shared(const std::string& name,\n                                                           const T& initial_value) noexcept {\n    SharedMemory<T> shm(name);\n    if (shm.open(initial_value))\n        return shm;\n    return std::nullopt;\n}\n\n}  // namespace Stockfish::shm\n\n#endif  // #ifndef SHM_LINUX_H_INCLUDED\n"
  },
  {
    "path": "src/syzygy/tbprobe.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"tbprobe.h\"\n\n#include <algorithm>\n#include <atomic>\n#include <cassert>\n#include <cstdint>\n#include <cstdlib>\n#include <cstring>\n#include <deque>\n#include <fstream>\n#include <initializer_list>\n#include <iostream>\n#include <mutex>\n#include <optional>\n#include <sstream>\n#include <string_view>\n#include <sys/stat.h>\n#include <type_traits>\n#include <utility>\n#include <vector>\n#include <array>\n\n#include \"../bitboard.h\"\n#include \"../misc.h\"\n#include \"../movegen.h\"\n#include \"../position.h\"\n#include \"../search.h\"\n#include \"../types.h\"\n#include \"../ucioption.h\"\n\n#ifndef _WIN32\n    #include <fcntl.h>\n    #include <sys/mman.h>\n    #include <unistd.h>\n#else\n    #define WIN32_LEAN_AND_MEAN\n    #ifndef NOMINMAX\n        #define NOMINMAX  // Disable macros min() and max()\n    #endif\n    #include <windows.h>\n#endif\n\nusing namespace Stockfish::Tablebases;\n\nint Stockfish::Tablebases::MaxCardinality;\n\nnamespace Stockfish {\n\nnamespace {\n\nconstexpr int TBPIECES = 7;  // Max number of supported pieces\nconstexpr int MAX_DTZ =\n  1 << 18;  // Max DTZ supported times 2, large enough to deal with the syzygy TB limit.\n\nenum {\n    BigEndian,\n    LittleEndian\n};\nenum TBType {\n    WDL,\n    DTZ\n};  // Used as template parameter\n\n// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables\nenum TBFlag {\n    STM         = 1,\n    Mapped      = 2,\n    WinPlies    = 4,\n    LossPlies   = 8,\n    Wide        = 16,\n    SingleValue = 128\n};\n\ninline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }\ninline Square   operator^(Square s, int i) { return Square(int(s) ^ i); }\n\nconstexpr std::string_view PieceToChar = \" PNBRQK  pnbrqk\";\n\nint MapPawns[SQUARE_NB];\nint MapB1H1H7[SQUARE_NB];\nint MapA1D1D4[SQUARE_NB];\nint MapKK[10][SQUARE_NB];  // [MapA1D1D4][SQUARE_NB]\n\nint Binomial[6][SQUARE_NB];     // [k][n] k elements from a set of n elements\nint LeadPawnIdx[6][SQUARE_NB];  // [leadPawnsCnt][SQUARE_NB]\nint LeadPawnsSize[6][4];        // [leadPawnsCnt][FILE_A..FILE_D]\n\n// Comparison function to sort leading pawns in ascending MapPawns[] order\nbool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }\nint  off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); }\n\nconstexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW,\n                                  VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1};\n\ntemplate<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>\ninline void swap_endian(T& x) {\n    static_assert(std::is_unsigned_v<T>, \"Argument of swap_endian not unsigned\");\n\n    uint8_t tmp, *c = (uint8_t*) &x;\n    for (int i = 0; i < Half; ++i)\n        tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;\n}\ntemplate<>\ninline void swap_endian<uint8_t>(uint8_t&) {}\n\ntemplate<typename T, int LE>\nT number(void* addr) {\n    T v;\n\n    if (uintptr_t(addr) & (alignof(T) - 1))  // Unaligned pointer (very rare)\n        std::memcpy(&v, addr, sizeof(T));\n    else\n        v = *((T*) addr);\n\n    if (LE != IsLittleEndian)\n        swap_endian(v);\n    return v;\n}\n\n// DTZ tables don't store valid scores for moves that reset the rule50 counter\n// like captures and pawn moves but we can easily recover the correct dtz of the\n// previous move if we know the position's WDL score.\nint dtz_before_zeroing(WDLScore wdl) {\n    return wdl == WDLWin         ? 1\n         : wdl == WDLCursedWin   ? 101\n         : wdl == WDLBlessedLoss ? -101\n         : wdl == WDLLoss        ? -1\n                                 : 0;\n}\n\n// Return the sign of a number (-1, 0, 1)\ntemplate<typename T>\nint sign_of(T val) {\n    return (T(0) < val) - (val < T(0));\n}\n\n// Numbers in little-endian used by sparseIndex[] to point into blockLength[]\nstruct SparseEntry {\n    char block[4];   // Number of block\n    char offset[2];  // Offset within the block\n};\n\nstatic_assert(sizeof(SparseEntry) == 6, \"SparseEntry must be 6 bytes\");\n\nusing Sym = uint16_t;  // Huffman symbol\n\nstruct LR {\n    enum Side {\n        Left,\n        Right\n    };\n\n    uint8_t lr[3];  // The first 12 bits is the left-hand symbol, the second 12\n                    // bits is the right-hand symbol. If the symbol has length 1,\n                    // then the left-hand symbol is the stored value.\n    template<Side S>\n    Sym get() {\n        return S == Left  ? ((lr[1] & 0xF) << 8) | lr[0]\n             : S == Right ? (lr[2] << 4) | (lr[1] >> 4)\n                          : (assert(false), Sym(-1));\n    }\n};\n\nstatic_assert(sizeof(LR) == 3, \"LR tree entry must be 3 bytes\");\n\n// Tablebases data layout is structured as following:\n//\n//  TBFile:   memory maps/unmaps the physical .rtbw and .rtbz files\n//  TBTable:  one object for each file with corresponding indexing information\n//  TBTables: has ownership of TBTable objects, keeping a list and a hash\n\n// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are\n// memory mapped for best performance. Files are mapped at first access: at init\n// time only existence of the file is checked.\nclass TBFile: public std::ifstream {\n\n    std::string fname;\n\n   public:\n    // Look for and open the file among the Paths directories where the .rtbw\n    // and .rtbz files can be found. Multiple directories are separated by \";\"\n    // on Windows and by \":\" on Unix-based operating systems.\n    //\n    // Example:\n    // C:\\tb\\wdl345;C:\\tb\\wdl6;D:\\tb\\dtz345;D:\\tb\\dtz6\n    static std::string Paths;\n\n    TBFile(const std::string& f) {\n\n#ifndef _WIN32\n        constexpr char SepChar = ':';\n#else\n        constexpr char SepChar = ';';\n#endif\n        std::stringstream ss(Paths);\n        std::string       path;\n\n        while (std::getline(ss, path, SepChar))\n        {\n            fname = path + \"/\" + f;\n            std::ifstream::open(fname);\n            if (is_open())\n                return;\n        }\n    }\n\n    // Memory map the file and check it.\n    uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {\n        if (is_open())\n            close();  // Need to re-open to get native file descriptor\n\n#ifndef _WIN32\n        struct stat statbuf;\n        int         fd = ::open(fname.c_str(), O_RDONLY);\n\n        if (fd == -1)\n            return *baseAddress = nullptr, nullptr;\n\n        fstat(fd, &statbuf);\n\n        if (statbuf.st_size % 64 != 16)\n        {\n            std::cerr << \"Corrupt tablebase file \" << fname << std::endl;\n            exit(EXIT_FAILURE);\n        }\n\n        *mapping     = statbuf.st_size;\n        *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);\n    #if defined(MADV_RANDOM)\n        madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);\n    #endif\n        ::close(fd);\n\n        if (*baseAddress == MAP_FAILED)\n        {\n            std::cerr << \"Could not mmap() \" << fname << std::endl;\n            exit(EXIT_FAILURE);\n        }\n#else\n        // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.\n        HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,\n                                OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);\n\n        if (fd == INVALID_HANDLE_VALUE)\n            return *baseAddress = nullptr, nullptr;\n\n        DWORD size_high;\n        DWORD size_low = GetFileSize(fd, &size_high);\n\n        if (size_low % 64 != 16)\n        {\n            std::cerr << \"Corrupt tablebase file \" << fname << std::endl;\n            exit(EXIT_FAILURE);\n        }\n\n        HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr);\n        CloseHandle(fd);\n\n        if (!mmap)\n        {\n            std::cerr << \"CreateFileMapping() failed\" << std::endl;\n            exit(EXIT_FAILURE);\n        }\n\n        *mapping     = uint64_t(mmap);\n        *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);\n\n        if (!*baseAddress)\n        {\n            std::cerr << \"MapViewOfFile() failed, name = \" << fname\n                      << \", error = \" << GetLastError() << std::endl;\n            exit(EXIT_FAILURE);\n        }\n#endif\n        uint8_t* data = (uint8_t*) *baseAddress;\n\n        constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};\n\n        if (memcmp(data, Magics[type == WDL], 4))\n        {\n            std::cerr << \"Corrupted table in file \" << fname << std::endl;\n            unmap(*baseAddress, *mapping);\n            return *baseAddress = nullptr, nullptr;\n        }\n\n        return data + 4;  // Skip Magics's header\n    }\n\n    static void unmap(void* baseAddress, uint64_t mapping) {\n\n#ifndef _WIN32\n        munmap(baseAddress, mapping);\n#else\n        UnmapViewOfFile(baseAddress);\n        CloseHandle((HANDLE) mapping);\n#endif\n    }\n};\n\nstd::string TBFile::Paths;\n\n// struct PairsData contains low-level indexing information to access TB data.\n// There are 8, 4, or 2 PairsData records for each TBTable, according to the type\n// of table and if positions have pawns or not. It is populated at first access.\nstruct PairsData {\n    uint8_t   flags;            // Table flags, see enum TBFlag\n    uint8_t   maxSymLen;        // Maximum length in bits of the Huffman symbols\n    uint8_t   minSymLen;        // Minimum length in bits of the Huffman symbols\n    uint32_t  blocksNum;        // Number of blocks in the TB file\n    size_t    sizeofBlock;      // Block size in bytes\n    size_t    span;             // About every span values there is a SparseIndex[] entry\n    Sym*      lowestSym;        // lowestSym[l] is the symbol of length l with the lowest value\n    LR*       btree;            // btree[sym] stores the left and right symbols that expand sym\n    uint16_t* blockLength;      // Number of stored positions (minus one) for each block: 1..65536\n    uint32_t  blockLengthSize;  // Size of blockLength[] table: padded so it's bigger than blocksNum\n    SparseEntry* sparseIndex;   // Partial indices into blockLength[]\n    size_t       sparseIndexSize;  // Size of SparseIndex[] table\n    uint8_t*     data;             // Start of Huffman compressed data\n    std::vector<uint64_t>\n      base64;  // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l\n    std::vector<uint8_t>\n             symlen;  // Number of values (-1) represented by a given Huffman symbol: 1..256\n    Piece    pieces[TBPIECES];        // Position pieces: the order of pieces defines the groups\n    uint64_t groupIdx[TBPIECES + 1];  // Start index used for the encoding of the group's pieces\n    int      groupLen[TBPIECES + 1];  // Number of pieces in a given group: KRKN -> (3, 1)\n    uint16_t map_idx[4];              // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)\n};\n\n// struct TBTable contains indexing information to access the corresponding TBFile.\n// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable\n// is populated at init time but the nested PairsData records are populated at\n// first access, when the corresponding file is memory mapped.\ntemplate<TBType Type>\nstruct TBTable {\n    using Ret = std::conditional_t<Type == WDL, WDLScore, int>;\n\n    static constexpr int Sides = Type == WDL ? 2 : 1;\n\n    std::atomic_bool ready;\n    void*            baseAddress;\n    uint8_t*         map;\n    uint64_t         mapping;\n    Key              key;\n    Key              key2;\n    int              pieceCount;\n    bool             hasPawns;\n    bool             hasUniquePieces;\n    uint8_t          pawnCount[2];     // [Lead color / other color]\n    PairsData        items[Sides][4];  // [wtm / btm][FILE_A..FILE_D or 0]\n\n    PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; }\n\n    TBTable() :\n        ready(false),\n        baseAddress(nullptr) {}\n    explicit TBTable(const std::string& code);\n    explicit TBTable(const TBTable<WDL>& wdl);\n\n    ~TBTable() {\n        if (baseAddress)\n            TBFile::unmap(baseAddress, mapping);\n    }\n};\n\ntemplate<>\nTBTable<WDL>::TBTable(const std::string& code) :\n    TBTable() {\n\n    StateInfo st;\n    Position  pos;\n\n    auto err = pos.set(code, WHITE, &st);\n    // IMPORTANT: We cannot assert here because it WILL produce validation errors\n    // on some TB7 and higher positions due to the black king being attacked\n    // while white is to move. This is not fixable without significant changes.\n    // As using pos.set here is already a very hacky way to achieve the desired\n    // result here so we leave it for now. The validation checks that fail are\n    // done after the position is fully set up, so it's fine for now.\n    // assert(!err.has_value());\n    (void) err;\n    key        = pos.material_key();\n    pieceCount = pos.count<ALL_PIECES>();\n    hasPawns   = pos.pieces(PAWN);\n\n    hasUniquePieces = false;\n    for (Color c : {WHITE, BLACK})\n        for (PieceType pt = PAWN; pt < KING; ++pt)\n            if (popcount(pos.pieces(c, pt)) == 1)\n                hasUniquePieces = true;\n\n    // Set the leading color. In case both sides have pawns the leading color\n    // is the side with fewer pawns because this leads to better compression.\n    bool c = !pos.count<PAWN>(BLACK)\n          || (pos.count<PAWN>(WHITE) && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));\n\n    pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);\n    pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);\n\n    err = pos.set(code, BLACK, &st);\n    // IMPORTANT: We cannot assert here because it WILL produce validation errors\n    // on some TB7 and higher positions due to the black king being attacked\n    // while white is to move. This is not fixable without significant changes.\n    // As using pos.set here is already a very hacky way to achieve the desired\n    // result here so we leave it for now. The validation checks that fail are\n    // done after the position is fully set up, so it's fine for now.\n    // assert(!err.has_value());\n    (void) err;\n    key2 = pos.material_key();\n}\n\ntemplate<>\nTBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) :\n    TBTable() {\n\n    // Use the corresponding WDL table to avoid recalculating all from scratch\n    key             = wdl.key;\n    key2            = wdl.key2;\n    pieceCount      = wdl.pieceCount;\n    hasPawns        = wdl.hasPawns;\n    hasUniquePieces = wdl.hasUniquePieces;\n    pawnCount[0]    = wdl.pawnCount[0];\n    pawnCount[1]    = wdl.pawnCount[1];\n}\n\n// class TBTables creates and keeps ownership of the TBTable objects, one for\n// each TB file found. It supports a fast, hash-based, table lookup. Populated\n// at init time, accessed at probe time.\nclass TBTables {\n\n    struct Entry {\n        Key           key;\n        TBTable<WDL>* wdl;\n        TBTable<DTZ>* dtz;\n\n        template<TBType Type>\n        TBTable<Type>* get() const {\n            return (TBTable<Type>*) (Type == WDL ? (void*) wdl : (void*) dtz);\n        }\n    };\n\n    static constexpr int Size     = 1 << 12;  // 4K table, indexed by key's 12 lsb\n    static constexpr int Overflow = 1;  // Number of elements allowed to map to the last bucket\n\n    Entry hashTable[Size + Overflow];\n\n    std::deque<TBTable<WDL>> wdlTable;\n    std::deque<TBTable<DTZ>> dtzTable;\n    size_t                   foundDTZFiles = 0;\n    size_t                   foundWDLFiles = 0;\n\n    void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {\n        uint32_t homeBucket = uint32_t(key) & (Size - 1);\n        Entry    entry{key, wdl, dtz};\n\n        // Ensure last element is empty to avoid overflow when looking up\n        for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)\n        {\n            Key otherKey = hashTable[bucket].key;\n            if (otherKey == key || !hashTable[bucket].get<WDL>())\n            {\n                hashTable[bucket] = entry;\n                return;\n            }\n\n            // Robin Hood hashing: If we've probed for longer than this element,\n            // insert here and search for a new spot for the other element instead.\n            uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);\n            if (otherHomeBucket > homeBucket)\n            {\n                std::swap(entry, hashTable[bucket]);\n                key        = otherKey;\n                homeBucket = otherHomeBucket;\n            }\n        }\n        std::cerr << \"TB hash table size too low!\" << std::endl;\n        exit(EXIT_FAILURE);\n    }\n\n   public:\n    template<TBType Type>\n    TBTable<Type>* get(Key key) {\n        for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)\n        {\n            if (entry->key == key || !entry->get<Type>())\n                return entry->get<Type>();\n        }\n    }\n\n    void clear() {\n        memset(hashTable, 0, sizeof(hashTable));\n        wdlTable.clear();\n        dtzTable.clear();\n        foundDTZFiles = 0;\n        foundWDLFiles = 0;\n    }\n\n    void info() const {\n        sync_cout << \"info string Found \" << foundWDLFiles << \" WDL and \" << foundDTZFiles\n                  << \" DTZ tablebase files (up to \" << MaxCardinality << \"-man).\" << sync_endl;\n    }\n\n    void add(const std::vector<PieceType>& pieces);\n};\n\nTBTables TBTables;\n\n// If the corresponding file exists two new objects TBTable<WDL> and TBTable<DTZ>\n// are created and added to the lists and hash table. Called at init time.\nvoid TBTables::add(const std::vector<PieceType>& pieces) {\n\n    std::string code;\n\n    for (PieceType pt : pieces)\n        code += PieceToChar[pt];\n    code.insert(code.find('K', 1), \"v\");\n\n    TBFile file_dtz(code + \".rtbz\");  // KRK -> KRvK\n    if (file_dtz.is_open())\n    {\n        file_dtz.close();\n        foundDTZFiles++;\n    }\n\n    TBFile file(code + \".rtbw\");  // KRK -> KRvK\n\n    if (!file.is_open())  // Only WDL file is checked\n        return;\n\n    file.close();\n    foundWDLFiles++;\n\n    MaxCardinality = std::max(int(pieces.size()), MaxCardinality);\n\n    wdlTable.emplace_back(code);\n    dtzTable.emplace_back(wdlTable.back());\n\n    // Insert into the hash keys for both colors: KRvK with KR white and black\n    insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back());\n    insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());\n}\n\n// TB tables are compressed with canonical Huffman code. The compressed data is divided into\n// blocks of size d->sizeofBlock, and each block stores a variable number of symbols.\n// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols\n// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536\n// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after\n// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most\n// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly\n// of draws or mostly of wins, but such tables are actually quite common. In principle, the\n// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for\n// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so\n// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long.\n// The generator picks the size that leads to the smallest table. The \"book\" of symbols and\n// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file\n// will have one table for wtm and one for btm, a TB file with pawns will have tables per\n// file a,b,c,d also, in this case, one set for wtm and one for btm.\nint decompress_pairs(PairsData* d, uint64_t idx) {\n\n    // Special case where all table positions store the same value\n    if (d->flags & TBFlag::SingleValue)\n        return d->minSymLen;\n\n    // First we need to locate the right block that stores the value at index \"idx\".\n    // Because each block n stores blockLength[n] + 1 values, the index i of the block\n    // that contains the value at position idx is:\n    //\n    //                    for (i = -1, sum = 0; sum <= idx; i++)\n    //                        sum += blockLength[i + 1] + 1;\n    //\n    // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that\n    // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry\n    // that stores the blockLength[] index and the offset within that block of the value\n    // with index I(k), where:\n    //\n    //       I(k) = k * d->span + d->span / 2      (1)\n\n    // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)\n    uint32_t k = uint32_t(idx / d->span);\n\n    // Then we read the corresponding SparseIndex[] entry\n    uint32_t block  = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);\n    int      offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);\n\n    // Now compute the difference idx - I(k). From the definition of k, we know that\n    //\n    //       idx = k * d->span + idx % d->span    (2)\n    //\n    // So from (1) and (2) we can compute idx - I(K):\n    int diff = int(idx % d->span - d->span / 2);\n\n    // Sum the above to offset to find the offset corresponding to our idx\n    offset += diff;\n\n    // Move to the previous/next block, until we reach the correct block that contains idx,\n    // that is when 0 <= offset <= d->blockLength[block]\n    while (offset < 0)\n        offset += d->blockLength[--block] + 1;\n\n    while (offset > d->blockLength[block])\n        offset -= d->blockLength[block++] + 1;\n\n    // Finally, we find the start address of our block of canonical Huffman symbols\n    uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock));\n\n    // Read the first 64 bits in our block, this is a (truncated) sequence of\n    // unknown number of symbols of unknown length but we know the first one\n    // is at the beginning of this 64-bit sequence.\n    uint64_t buf64 = number<uint64_t, BigEndian>(ptr);\n    ptr += 2;\n    int buf64Size = 64;\n    Sym sym;\n\n    while (true)\n    {\n        int len = 0;  // This is the symbol length - d->min_sym_len\n\n        // Now get the symbol length. For any symbol s64 of length l right-padded\n        // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we\n        // can find the symbol length iterating through base64[].\n        while (buf64 < d->base64[len])\n            ++len;\n\n        // All the symbols of a given length are consecutive integers (numerical\n        // sequence property), so we can compute the offset of our symbol of\n        // length len, stored at the beginning of buf64.\n        sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));\n\n        // Now add the value of the lowest symbol of length len to get our symbol\n        sym += number<Sym, LittleEndian>(&d->lowestSym[len]);\n\n        // If our offset is within the number of values represented by symbol sym,\n        // we are done.\n        if (offset < d->symlen[sym] + 1)\n            break;\n\n        // ...otherwise update the offset and continue to iterate\n        offset -= d->symlen[sym] + 1;\n        len += d->minSymLen;  // Get the real length\n        buf64 <<= len;        // Consume the just processed symbol\n        buf64Size -= len;\n\n        if (buf64Size <= 32)\n        {  // Refill the buffer\n            buf64Size += 32;\n            buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);\n        }\n    }\n\n    // Now we have our symbol that expands into d->symlen[sym] + 1 symbols.\n    // We binary-search for our value recursively expanding into the left and\n    // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1\n    // that will store the value we need.\n    while (d->symlen[sym])\n    {\n        Sym left = d->btree[sym].get<LR::Left>();\n\n        // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and\n        // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then\n        // we know that, for instance, the tenth value (offset = 10) will be on\n        // the left side because in Recursive Pairing child symbols are adjacent.\n        if (offset < d->symlen[left] + 1)\n            sym = left;\n        else\n        {\n            offset -= d->symlen[left] + 1;\n            sym = d->btree[sym].get<LR::Right>();\n        }\n    }\n\n    return d->btree[sym].get<LR::Left>();\n}\n\nbool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }\n\nbool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {\n\n    auto flags = entry->get(stm, f)->flags;\n    return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns);\n}\n\n// DTZ scores are sorted by frequency of occurrence and then assigned the\n// values 0, 1, 2, ... in order of decreasing frequency. This is done for each\n// of the four WDLScore values. The mapping information necessary to reconstruct\n// the original values are stored in the TB file and read during map[] init.\nWDLScore map_score(TBTable<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }\n\nint map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {\n\n    constexpr int WDLMap[] = {1, 3, 0, 2, 0};\n\n    auto flags = entry->get(0, f)->flags;\n\n    uint8_t*  map = entry->map;\n    uint16_t* idx = entry->get(0, f)->map_idx;\n    if (flags & TBFlag::Mapped)\n    {\n        if (flags & TBFlag::Wide)\n            value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value];\n        else\n            value = map[idx[WDLMap[wdl + 2]] + value];\n    }\n\n    // DTZ tables store distance to zero in number of moves or plies. We\n    // want to return plies, so we have to convert to plies when needed.\n    if ((wdl == WDLWin && !(flags & TBFlag::WinPlies))\n        || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin\n        || wdl == WDLBlessedLoss)\n        value *= 2;\n\n    return value + 1;\n}\n\n// A temporary fix for the compiler bug with vectorization. (#4450)\n#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15\n    #define DISABLE_CLANG_LOOP_VEC _Pragma(\"clang loop vectorize(disable)\")\n#else\n    #define DISABLE_CLANG_LOOP_VEC\n#endif\n\n// Compute a unique index out of a position and use it to probe the TB file. To\n// encode k pieces of the same type and color, first sort the pieces by square in\n// ascending order s1 <= s2 <= ... <= sk then compute the unique index as:\n//\n//      idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]\n//\ntemplate<typename T, typename Ret = typename T::Ret>\nRet do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {\n\n    Square     squares[TBPIECES];\n    Piece      pieces[TBPIECES];\n    uint64_t   idx;\n    int        next = 0, size = 0, leadPawnsCnt = 0;\n    PairsData* d;\n    Bitboard   b, leadPawns = 0;\n    File       tbFile = FILE_A;\n\n    // A given TB entry like KRK has associated two material keys: KRvk and Kvkr.\n    // If both sides have the same pieces keys are equal. In this case TB tables\n    // only stores the 'white to move' case, so if the position to lookup has black\n    // to move, we need to switch the color and flip the squares before to lookup.\n    bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move());\n\n    // TB files are calculated for white as the stronger side. For instance, we\n    // have KRvK, not KvKR. A position where the stronger side is white will have\n    // its material key == entry->key, otherwise we have to switch the color and\n    // flip the squares before to lookup.\n    bool blackStronger = (pos.material_key() != entry->key);\n\n    int flipColor   = (symmetricBlackToMove || blackStronger) * 8;\n    int flipSquares = (symmetricBlackToMove || blackStronger) * 56;\n    int stm         = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move();\n\n    // For pawns, TB files store 4 separate tables according if leading pawn is on\n    // file a, b, c or d after reordering. The leading pawn is the one with maximum\n    // MapPawns[] value, that is the one most toward the edges and with lowest rank.\n    if (entry->hasPawns)\n    {\n\n        // In all the 4 tables, pawns are at the beginning of the piece sequence and\n        // their color is the reference one. So we just pick the first one.\n        Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor);\n\n        assert(type_of(pc) == PAWN);\n\n        leadPawns = b = pos.pieces(color_of(pc), PAWN);\n        do\n            squares[size++] = pop_lsb(b) ^ flipSquares;\n        while (b);\n\n        leadPawnsCnt = size;\n\n        std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));\n\n        tbFile = File(edge_distance(file_of(squares[0])));\n    }\n\n    // DTZ tables are one-sided, i.e. they store positions only for white to\n    // move or only for black to move, so check for side to move to be stm,\n    // early exit otherwise.\n    if (!check_dtz_stm(entry, stm, tbFile))\n        return *result = CHANGE_STM, Ret();\n\n    // Now we are ready to get all the position pieces (but the lead pawns) and\n    // directly map them to the correct color and square.\n    b = pos.pieces() ^ leadPawns;\n    do\n    {\n        Square s       = pop_lsb(b);\n        squares[size]  = s ^ flipSquares;\n        pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);\n    } while (b);\n\n    assert(size >= 2);\n\n    d = entry->get(stm, tbFile);\n\n    // Then we reorder the pieces to have the same sequence as the one stored\n    // in pieces[i]: the sequence that ensures the best compression.\n    for (int i = leadPawnsCnt; i < size - 1; ++i)\n        for (int j = i + 1; j < size; ++j)\n            if (d->pieces[i] == pieces[j])\n            {\n                std::swap(pieces[i], pieces[j]);\n                std::swap(squares[i], squares[j]);\n                break;\n            }\n\n    // Now we map again the squares so that the square of the lead piece is in\n    // the triangle A1-D1-D4.\n    if (file_of(squares[0]) > FILE_D)\n    {\n        DISABLE_CLANG_LOOP_VEC\n        for (int i = 0; i < size; ++i)\n            squares[i] = flip_file(squares[i]);\n    }\n\n    // Encode leading pawns starting with the one with minimum MapPawns[] and\n    // proceeding in ascending order.\n    if (entry->hasPawns)\n    {\n        idx = LeadPawnIdx[leadPawnsCnt][squares[0]];\n\n        std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);\n\n        for (int i = 1; i < leadPawnsCnt; ++i)\n            idx += Binomial[i][MapPawns[squares[i]]];\n\n        goto encode_remaining;  // With pawns we have finished special treatments\n    }\n\n    // In positions without pawns, we further flip the squares to ensure leading\n    // piece is below RANK_5.\n    if (rank_of(squares[0]) > RANK_4)\n    {\n        DISABLE_CLANG_LOOP_VEC\n        for (int i = 0; i < size; ++i)\n            squares[i] = flip_rank(squares[i]);\n    }\n\n    // Look for the first piece of the leading group not on the A1-D4 diagonal\n    // and ensure it is mapped below the diagonal.\n    DISABLE_CLANG_LOOP_VEC\n    for (int i = 0; i < d->groupLen[0]; ++i)\n    {\n        if (!off_A1H8(squares[i]))\n            continue;\n\n        if (off_A1H8(squares[i]) > 0)  // A1-H8 diagonal flip: SQ_A3 -> SQ_C1\n        {\n            DISABLE_CLANG_LOOP_VEC\n            for (int j = i; j < size; ++j)\n                squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);\n        }\n        break;\n    }\n\n    // Encode the leading group.\n    //\n    // Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR\n    // and bK (each 0...63). The simplest way to map this position to an index\n    // is like this:\n    //\n    //   index = wK * 64 * 64 + wR * 64 + bK;\n    //\n    // But this way the TB is going to have 64*64*64 = 262144 positions, with\n    // lots of positions being equivalent (because they are mirrors of each\n    // other) and lots of positions being invalid (two pieces on one square,\n    // adjacent kings, etc.).\n    // Usually the first step is to take the wK and bK together. There are just\n    // 462 ways legal and not-mirrored ways to place the wK and bK on the board.\n    // Once we have placed the wK and bK, there are 62 squares left for the wR\n    // Mapping its square from 0..63 to available squares 0..61 can be done like:\n    //\n    //   wR -= (wR > wK) + (wR > bK);\n    //\n    // In words: if wR \"comes later\" than wK, we deduct 1, and the same if wR\n    // \"comes later\" than bK. In case of two same pieces like KRRvK we want to\n    // place the two Rs \"together\". If we have 62 squares left, we can place two\n    // Rs \"together\" in 62 * 61 / 2 ways (we divide by 2 because rooks can be\n    // swapped and still get the same position.)\n    //\n    // In case we have at least 3 unique pieces (including kings) we encode them\n    // together.\n    if (entry->hasUniquePieces)\n    {\n\n        int adjust1 = squares[1] > squares[0];\n        int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]);\n\n        // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3\n        // triangle to 0...5. There are 63 squares for second piece and 62\n        // (mapped to 0...61) for the third.\n        if (off_A1H8(squares[0]))\n            idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2;\n\n        // First piece is on a1-h8 diagonal, second below: map this occurrence to\n        // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal\n        // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.\n        else if (off_A1H8(squares[1]))\n            idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2]\n                - adjust2;\n\n        // First two pieces are on a1-h8 diagonal, third below\n        else if (off_A1H8(squares[2]))\n            idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28\n                + (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]];\n\n        // All 3 pieces on the diagonal a1-h8\n        else\n            idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6\n                + (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2);\n    }\n    else\n        // We don't have at least 3 unique pieces, like in KRRvKBB, just map\n        // the kings.\n        idx = MapKK[MapA1D1D4[squares[0]]][squares[1]];\n\nencode_remaining:\n    idx *= d->groupIdx[0];\n    Square* groupSq = squares + d->groupLen[0];\n\n    // Encode remaining pawns and then pieces according to square, in ascending order\n    bool remainingPawns = entry->hasPawns && entry->pawnCount[1];\n\n    while (d->groupLen[++next])\n    {\n        std::stable_sort(groupSq, groupSq + d->groupLen[next]);\n        uint64_t n = 0;\n\n        // Map down a square if \"comes later\" than a square in the previous\n        // groups (similar to what was done earlier for leading group pieces).\n        for (int i = 0; i < d->groupLen[next]; ++i)\n        {\n            auto f      = [&](Square s) { return groupSq[i] > s; };\n            auto adjust = std::count_if(squares, groupSq, f);\n            n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];\n        }\n\n        remainingPawns = false;\n        idx += n * d->groupIdx[next];\n        groupSq += d->groupLen[next];\n    }\n\n    // Now that we have the index, decompress the pair and get the score\n    return map_score(entry, tbFile, decompress_pairs(d, idx), wdl);\n}\n\n// Group together pieces that will be encoded together. The general rule is that\n// a group contains pieces of the same type and color. The exception is the leading\n// group that, in case of positions without pawns, can be formed by 3 different\n// pieces (default) or by the king pair when there is not a unique piece apart\n// from the kings. When there are pawns, pawns are always first in pieces[].\n//\n// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K\n//\n// The actual grouping depends on the TB generator and can be inferred from the\n// sequence of pieces in piece[] array.\ntemplate<typename T>\nvoid set_groups(T& e, PairsData* d, int order[], File f) {\n\n    int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2;\n    d->groupLen[n] = 1;\n\n    // Number of pieces per group is stored in groupLen[], for instance in KRKN\n    // the encoder will default on '111', so groupLen[] will be (3, 1).\n    for (int i = 1; i < e.pieceCount; ++i)\n        if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1])\n            d->groupLen[n]++;\n        else\n            d->groupLen[++n] = 1;\n\n    d->groupLen[++n] = 0;  // Zero-terminated\n\n    // The sequence in pieces[] defines the groups, but not the order in which\n    // they are encoded. If the pieces in a group g can be combined on the board\n    // in N(g) different ways, then the position encoding will be of the form:\n    //\n    //           g1 * N(g2) * N(g3) + g2 * N(g3) + g3\n    //\n    // This ensures unique encoding for the whole position. The order of the\n    // groups is a per-table parameter and could not follow the canonical leading\n    // pawns/pieces -> remaining pawns -> remaining pieces. In particular the\n    // first group is at order[0] position and the remaining pawns, when present,\n    // are at order[1] position.\n    bool     pp          = e.hasPawns && e.pawnCount[1];  // Pawns on both sides\n    int      next        = pp ? 2 : 1;\n    int      freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);\n    uint64_t idx         = 1;\n\n    for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)\n        if (k == order[0])  // Leading pawns or pieces\n        {\n            d->groupIdx[0] = idx;\n            idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462;\n        }\n        else if (k == order[1])  // Remaining pawns\n        {\n            d->groupIdx[1] = idx;\n            idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];\n        }\n        else  // Remaining pieces\n        {\n            d->groupIdx[next] = idx;\n            idx *= Binomial[d->groupLen[next]][freeSquares];\n            freeSquares -= d->groupLen[next++];\n        }\n\n    d->groupIdx[n] = idx;\n}\n\n// In Recursive Pairing each symbol represents a pair of children symbols. So\n// read d->btree[] symbols data and expand each one in his left and right child\n// symbol until reaching the leaves that represent the symbol value.\nuint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {\n\n    visited[s] = true;  // We can set it now because tree is acyclic\n    Sym sr     = d->btree[s].get<LR::Right>();\n\n    if (sr == 0xFFF)\n        return 0;\n\n    Sym sl = d->btree[s].get<LR::Left>();\n\n    if (!visited[sl])\n        d->symlen[sl] = set_symlen(d, sl, visited);\n\n    if (!visited[sr])\n        d->symlen[sr] = set_symlen(d, sr, visited);\n\n    return d->symlen[sl] + d->symlen[sr] + 1;\n}\n\nuint8_t* set_sizes(PairsData* d, uint8_t* data) {\n\n    d->flags = *data++;\n\n    if (d->flags & TBFlag::SingleValue)\n    {\n        d->blocksNum = d->blockLengthSize = 0;\n        d->span = d->sparseIndexSize = 0;        // Broken MSVC zero-init\n        d->minSymLen                 = *data++;  // Here we store the single value\n        return data;\n    }\n\n    // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]\n    // element stores the biggest index that is the tb size.\n    uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];\n\n    d->sizeofBlock     = 1ULL << *data++;\n    d->span            = 1ULL << *data++;\n    d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span);  // Round up\n    auto padding       = number<uint8_t, LittleEndian>(data++);\n    d->blocksNum       = number<uint32_t, LittleEndian>(data);\n    data += sizeof(uint32_t);\n    d->blockLengthSize = d->blocksNum + padding;  // Padded to ensure SparseIndex[]\n                                                  // does not point out of range.\n    d->maxSymLen = *data++;\n    d->minSymLen = *data++;\n    d->lowestSym = (Sym*) data;\n    d->base64.resize(d->maxSymLen - d->minSymLen + 1);\n\n    // See https://en.wikipedia.org/wiki/Huffman_coding\n    // The canonical code is ordered such that longer symbols (in terms of\n    // the number of bits of their Huffman code) have a lower numeric value,\n    // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian).\n    // Starting from this we compute a base64[] table indexed by symbol length\n    // and containing 64 bit values so that d->base64[i] >= d->base64[i+1].\n\n    // Implementation note: we first cast the unsigned size_t \"base64.size()\"\n    // to a signed int \"base64_size\" variable and then we are able to subtract 2,\n    // avoiding unsigned overflow warnings.\n\n    int base64_size = static_cast<int>(d->base64.size());\n    for (int i = base64_size - 2; i >= 0; --i)\n    {\n        d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])\n                        - number<Sym, LittleEndian>(&d->lowestSym[i + 1]))\n                     / 2;\n\n        assert(d->base64[i] * 2 >= d->base64[i + 1]);\n    }\n\n    // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more\n    // than d->base64[i+1] and given the above assert condition, we ensure that\n    // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i\n    // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i].\n    for (int i = 0; i < base64_size; ++i)\n        d->base64[i] <<= 64 - i - d->minSymLen;  // Right-padding to 64 bits\n\n    data += base64_size * sizeof(Sym);\n    d->symlen.resize(number<uint16_t, LittleEndian>(data));\n    data += sizeof(uint16_t);\n    d->btree = (LR*) data;\n\n    // The compression scheme used is \"Recursive Pairing\", that replaces the most\n    // frequent adjacent pair of symbols in the source message by a new symbol,\n    // reevaluating the frequencies of all of the symbol pairs with respect to\n    // the extended alphabet, and then repeating the process.\n    // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf\n    std::vector<bool> visited(d->symlen.size());\n\n    for (Sym sym = 0; sym < d->symlen.size(); ++sym)\n        if (!visited[sym])\n            d->symlen[sym] = set_symlen(d, sym, visited);\n\n    return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);\n}\n\nuint8_t* set_dtz_map(TBTable<WDL>&, uint8_t* data, File) { return data; }\n\nuint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {\n\n    e.map = data;\n\n    for (File f = FILE_A; f <= maxFile; ++f)\n    {\n        auto flags = e.get(0, f)->flags;\n        if (flags & TBFlag::Mapped)\n        {\n            if (flags & TBFlag::Wide)\n            {\n                data += uintptr_t(data) & 1;  // Word alignment, we may have a mixed table\n                for (int i = 0; i < 4; ++i)\n                {  // Sequence like 3,x,x,x,1,x,0,2,x,x\n                    e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1);\n                    data += 2 * number<uint16_t, LittleEndian>(data) + 2;\n                }\n            }\n            else\n            {\n                for (int i = 0; i < 4; ++i)\n                {\n                    e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1);\n                    data += *data + 1;\n                }\n            }\n        }\n    }\n\n    return data += uintptr_t(data) & 1;  // Word alignment\n}\n\n// Populate entry's PairsData records with data from the just memory-mapped file.\n// Called at first access.\ntemplate<typename T>\nvoid set(T& e, uint8_t* data) {\n\n    PairsData* d;\n\n    enum {\n        Split    = 1,\n        HasPawns = 2\n    };\n\n    assert(e.hasPawns == bool(*data & HasPawns));\n    assert((e.key != e.key2) == bool(*data & Split));\n\n    data++;  // First byte stores flags\n\n    const int  sides   = T::Sides == 2 && (e.key != e.key2) ? 2 : 1;\n    const File maxFile = e.hasPawns ? FILE_D : FILE_A;\n\n    bool pp = e.hasPawns && e.pawnCount[1];  // Pawns on both sides\n\n    assert(!pp || e.pawnCount[0]);\n\n    for (File f = FILE_A; f <= maxFile; ++f)\n    {\n\n        for (int i = 0; i < sides; i++)\n            *e.get(i, f) = PairsData();\n\n        int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF},\n                          {*data >> 4, pp ? *(data + 1) >> 4 : 0xF}};\n        data += 1 + pp;\n\n        for (int k = 0; k < e.pieceCount; ++k, ++data)\n            for (int i = 0; i < sides; i++)\n                e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);\n\n        for (int i = 0; i < sides; ++i)\n            set_groups(e, e.get(i, f), order[i], f);\n    }\n\n    data += uintptr_t(data) & 1;  // Word alignment\n\n    for (File f = FILE_A; f <= maxFile; ++f)\n        for (int i = 0; i < sides; i++)\n            data = set_sizes(e.get(i, f), data);\n\n    data = set_dtz_map(e, data, maxFile);\n\n    for (File f = FILE_A; f <= maxFile; ++f)\n        for (int i = 0; i < sides; i++)\n        {\n            (d = e.get(i, f))->sparseIndex = (SparseEntry*) data;\n            data += d->sparseIndexSize * sizeof(SparseEntry);\n        }\n\n    for (File f = FILE_A; f <= maxFile; ++f)\n        for (int i = 0; i < sides; i++)\n        {\n            (d = e.get(i, f))->blockLength = (uint16_t*) data;\n            data += d->blockLengthSize * sizeof(uint16_t);\n        }\n\n    for (File f = FILE_A; f <= maxFile; ++f)\n        for (int i = 0; i < sides; i++)\n        {\n            data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F);  // 64 byte alignment\n            (d = e.get(i, f))->data = data;\n            data += d->blocksNum * d->sizeofBlock;\n        }\n}\n\n// If the TB file corresponding to the given position is already memory-mapped\n// then return its base address, otherwise, try to memory map and init it. Called\n// at every probe, memory map, and init only at first access. Function is thread\n// safe and can be called concurrently.\ntemplate<TBType Type>\nvoid* mapped(TBTable<Type>& e, const Position& pos) {\n\n    static std::mutex mutex;\n    // Because TB is the only usage of materialKey, check it here in debug mode\n    assert(pos.material_key_is_ok());\n\n    // Use 'acquire' to avoid a thread reading 'ready' == true while\n    // another is still working. (compiler reordering may cause this).\n    if (e.ready.load(std::memory_order_acquire))\n        return e.baseAddress;  // Could be nullptr if file does not exist\n\n    std::scoped_lock<std::mutex> lk(mutex);\n\n    if (e.ready.load(std::memory_order_relaxed))  // Recheck under lock\n        return e.baseAddress;\n\n    // Pieces strings in decreasing order for each color, like (\"KPP\",\"KR\")\n    std::string fname, w, b;\n    for (PieceType pt = KING; pt >= PAWN; --pt)\n    {\n        w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]);\n        b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]);\n    }\n\n    fname =\n      (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? \".rtbw\" : \".rtbz\");\n\n    uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);\n\n    if (data)\n        set(e, data);\n\n    e.ready.store(true, std::memory_order_release);\n    return e.baseAddress;\n}\n\ntemplate<TBType Type, typename Ret = typename TBTable<Type>::Ret>\nRet probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {\n\n    if (pos.count<ALL_PIECES>() == 2)  // KvK\n        return Ret(WDLDraw);\n\n    TBTable<Type>* entry = TBTables.get<Type>(pos.material_key());\n\n    if (!entry || !mapped(*entry, pos))\n        return *result = FAIL, Ret();\n\n    return do_probe_table(pos, entry, wdl, result);\n}\n\n// For a position where the side to move has a winning capture it is not necessary\n// to store a winning value so the generator treats such positions as \"don't care\"\n// and tries to assign to it a value that improves the compression ratio. Similarly,\n// if the side to move has a drawing capture, then the position is at least drawn.\n// If the position is won, then the TB needs to store a win value. But if the\n// position is drawn, the TB may store a loss value if that is better for compression.\n// All of this means that during probing, the engine must look at captures and probe\n// their results and must probe the position itself. The \"best\" result of these\n// probes is the correct result for the position.\n// DTZ tables do not store values when a following move is a zeroing winning move\n// (winning capture or winning pawn move). Also, DTZ store wrong values for positions\n// where the best move is an ep-move (even if losing). So in all these cases set\n// the state to ZEROING_BEST_MOVE.\ntemplate<bool CheckZeroingMoves>\nWDLScore search(Position& pos, ProbeState* result) {\n\n    WDLScore  value, bestValue = WDLLoss;\n    StateInfo st;\n\n    auto   moveList   = MoveList<LEGAL>(pos);\n    size_t totalCount = moveList.size(), moveCount = 0;\n\n    for (const Move move : moveList)\n    {\n        if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))\n            continue;\n\n        moveCount++;\n\n        pos.do_move(move, st);\n        value = -search<false>(pos, result);\n        pos.undo_move(move);\n\n        if (*result == FAIL)\n            return WDLDraw;\n\n        if (value > bestValue)\n        {\n            bestValue = value;\n\n            if (value >= WDLWin)\n            {\n                *result = ZEROING_BEST_MOVE;  // Winning DTZ-zeroing move\n                return value;\n            }\n        }\n    }\n\n    // In case we have already searched all the legal moves we don't have to probe\n    // the TB because the stored score could be wrong. For instance TB tables\n    // do not contain information on position with ep rights, so in this case\n    // the result of probe_wdl_table is wrong. Also in case of only capture\n    // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to\n    // return with ZEROING_BEST_MOVE set.\n    bool noMoreMoves = (moveCount && moveCount == totalCount);\n\n    if (noMoreMoves)\n        value = bestValue;\n    else\n    {\n        value = probe_table<WDL>(pos, result);\n\n        if (*result == FAIL)\n            return WDLDraw;\n    }\n\n    // DTZ stores a \"don't care\" value if bestValue is a win\n    if (bestValue >= value)\n        return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;\n\n    return *result = OK, value;\n}\n\n}  // namespace\n\n\n// Called at startup and after every change to\n// \"SyzygyPath\" UCI option to (re)create the various tables. It is not thread\n// safe, nor it needs to be.\nvoid Tablebases::init(const std::string& paths) {\n\n    TBTables.clear();\n    MaxCardinality = 0;\n    TBFile::Paths  = paths;\n\n    if (paths.empty())\n        return;\n\n    // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27\n    int code = 0;\n    for (Square s = SQ_A1; s <= SQ_H8; ++s)\n        if (off_A1H8(s) < 0)\n            MapB1H1H7[s] = code++;\n\n    // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9\n    std::vector<Square> diagonal;\n    code = 0;\n    for (Square s = SQ_A1; s <= SQ_D4; ++s)\n        if (off_A1H8(s) < 0 && file_of(s) <= FILE_D)\n            MapA1D1D4[s] = code++;\n\n        else if (!off_A1H8(s) && file_of(s) <= FILE_D)\n            diagonal.push_back(s);\n\n    // Diagonal squares are encoded as last ones\n    for (auto s : diagonal)\n        MapA1D1D4[s] = code++;\n\n    // MapKK[] encodes all the 462 possible legal positions of two kings where\n    // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4\n    // diagonal, the other one shall not be above the a1-h8 diagonal.\n    std::vector<std::pair<int, Square>> bothOnDiagonal;\n    code = 0;\n    for (int idx = 0; idx < 10; idx++)\n        for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1)\n            if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1))  // SQ_B1 is mapped to 0\n            {\n                for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)\n                    if ((PseudoAttacks[KING][s1] | s1) & s2)\n                        continue;  // Illegal position\n\n                    else if (!off_A1H8(s1) && off_A1H8(s2) > 0)\n                        continue;  // First on diagonal, second above\n\n                    else if (!off_A1H8(s1) && !off_A1H8(s2))\n                        bothOnDiagonal.emplace_back(idx, s2);\n\n                    else\n                        MapKK[idx][s2] = code++;\n            }\n\n    // Legal positions with both kings on a diagonal are encoded as last ones\n    for (auto p : bothOnDiagonal)\n        MapKK[p.first][p.second] = code++;\n\n    // Binomial[] stores the Binomial Coefficients using Pascal rule. There\n    // are Binomial[k][n] ways to choose k elements from a set of n elements.\n    Binomial[0][0] = 1;\n\n    for (int n = 1; n < 64; n++)               // Squares\n        for (int k = 0; k < 6 && k <= n; ++k)  // Pieces\n            Binomial[k][n] =\n              (k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0);\n\n    // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible\n    // available squares when the leading one is in 's'. Moreover the pawn with\n    // highest MapPawns[] is the leading pawn, the one nearest the edge, and\n    // among pawns with the same file, the one with the lowest rank.\n    int availableSquares = 47;  // Available squares when lead pawn is in a2\n\n    // Init the tables for the encoding of leading pawns group: with 7-men TB we\n    // can have up to 5 leading pawns (KPPPPPK).\n    for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)\n        for (File f = FILE_A; f <= FILE_D; ++f)\n        {\n            // Restart the index at every file because TB table is split\n            // by file, so we can reuse the same index for different files.\n            int idx = 0;\n\n            // Sum all possible combinations for a given file, starting with\n            // the leading pawn on rank 2 and increasing the rank.\n            for (Rank r = RANK_2; r <= RANK_7; ++r)\n            {\n                Square sq = make_square(f, r);\n\n                // Compute MapPawns[] at first pass.\n                // If sq is the leading pawn square, any other pawn cannot be\n                // below or more toward the edge of sq. There are 47 available\n                // squares when sq = a2 and reduced by 2 for any rank increase\n                // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45\n                if (leadPawnsCnt == 1)\n                {\n                    MapPawns[sq]            = availableSquares--;\n                    MapPawns[flip_file(sq)] = availableSquares--;\n                }\n                LeadPawnIdx[leadPawnsCnt][sq] = idx;\n                idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];\n            }\n            // After a file is traversed, store the cumulated per-file index\n            LeadPawnsSize[leadPawnsCnt][f] = idx;\n        }\n\n    // Add entries in TB tables if the corresponding \".rtbw\" file exists\n    for (PieceType p1 = PAWN; p1 < KING; ++p1)\n    {\n        TBTables.add({KING, p1, KING});\n\n        for (PieceType p2 = PAWN; p2 <= p1; ++p2)\n        {\n            TBTables.add({KING, p1, p2, KING});\n            TBTables.add({KING, p1, KING, p2});\n\n            for (PieceType p3 = PAWN; p3 < KING; ++p3)\n                TBTables.add({KING, p1, p2, KING, p3});\n\n            for (PieceType p3 = PAWN; p3 <= p2; ++p3)\n            {\n                TBTables.add({KING, p1, p2, p3, KING});\n\n                for (PieceType p4 = PAWN; p4 <= p3; ++p4)\n                {\n                    TBTables.add({KING, p1, p2, p3, p4, KING});\n\n                    for (PieceType p5 = PAWN; p5 <= p4; ++p5)\n                        TBTables.add({KING, p1, p2, p3, p4, p5, KING});\n\n                    for (PieceType p5 = PAWN; p5 < KING; ++p5)\n                        TBTables.add({KING, p1, p2, p3, p4, KING, p5});\n                }\n\n                for (PieceType p4 = PAWN; p4 < KING; ++p4)\n                {\n                    TBTables.add({KING, p1, p2, p3, KING, p4});\n\n                    for (PieceType p5 = PAWN; p5 <= p4; ++p5)\n                        TBTables.add({KING, p1, p2, p3, KING, p4, p5});\n                }\n            }\n\n            for (PieceType p3 = PAWN; p3 <= p1; ++p3)\n                for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4)\n                    TBTables.add({KING, p1, p2, KING, p3, p4});\n        }\n    }\n\n    TBTables.info();\n}\n\n// Probe the WDL table for a particular position.\n// If *result != FAIL, the probe was successful.\n// The return value is from the point of view of the side to move:\n// -2 : loss\n// -1 : loss, but draw under 50-move rule\n//  0 : draw\n//  1 : win, but draw under 50-move rule\n//  2 : win\nWDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {\n\n    *result = OK;\n    return search<false>(pos, result);\n}\n\n// Probe the DTZ table for a particular position.\n// If *result != FAIL, the probe was successful.\n// The return value is from the point of view of the side to move:\n//         n < -100 : loss, but draw under 50-move rule\n// -100 <= n < -1   : loss in n ply (assuming 50-move counter == 0)\n//        -1        : loss, the side to move is mated\n//         0        : draw\n//     1 < n <= 100 : win in n ply (assuming 50-move counter == 0)\n//   100 < n        : win, but draw under 50-move rule\n//\n// The return value n can be off by 1: a return value -n can mean a loss\n// in n+1 ply and a return value +n can mean a win in n+1 ply. This\n// cannot happen for tables with positions exactly on the \"edge\" of\n// the 50-move rule.\n//\n// This implies that if dtz > 0 is returned, the position is certainly\n// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine\n// picks moves that preserve dtz + 50-move-counter <= 99.\n//\n// If n = 100 immediately after a capture or pawn move, then the position\n// is also certainly a win, and during the whole phase until the next\n// capture or pawn move, the inequality to be preserved is\n// dtz + 50-move-counter <= 100.\n//\n// In short, if a move is available resulting in dtz + 50-move-counter <= 99,\n// then do not accept moves leading to dtz + 50-move-counter == 100.\nint Tablebases::probe_dtz(Position& pos, ProbeState* result) {\n\n    *result      = OK;\n    WDLScore wdl = search<true>(pos, result);\n\n    if (*result == FAIL || wdl == WDLDraw)  // DTZ tables don't store draws\n        return 0;\n\n    // DTZ stores a 'don't care value in this case, or even a plain wrong\n    // one as in case the best move is a losing ep, so it cannot be probed.\n    if (*result == ZEROING_BEST_MOVE)\n        return dtz_before_zeroing(wdl);\n\n    int dtz = probe_table<DTZ>(pos, result, wdl);\n\n    if (*result == FAIL)\n        return 0;\n\n    if (*result != CHANGE_STM)\n        return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl);\n\n    // DTZ stores results for the other side, so we need to do a 1-ply search and\n    // find the winning move that minimizes DTZ.\n    StateInfo st;\n    int       minDTZ = 0xFFFF;\n\n    for (const Move move : MoveList<LEGAL>(pos))\n    {\n        bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;\n\n        pos.do_move(move, st);\n\n        // For zeroing moves we want the dtz of the move _before_ doing it,\n        // otherwise we will get the dtz of the next move sequence. Search the\n        // position after the move to get the score sign (because even in a\n        // winning position we could make a losing capture or go for a draw).\n        dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result)) : -probe_dtz(pos, result);\n\n        // If the move mates, force minDTZ to 1\n        if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)\n            minDTZ = 1;\n\n        // Convert result from 1-ply search. Zeroing moves are already accounted\n        // by dtz_before_zeroing() that returns the DTZ of the previous move.\n        if (!zeroing)\n            dtz += sign_of(dtz);\n\n        // Skip the draws and if we are winning only pick positive dtz\n        if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl))\n            minDTZ = dtz;\n\n        pos.undo_move(move);\n\n        if (*result == FAIL)\n            return 0;\n    }\n\n    // When there are no legal moves, the position is mate: we return -1\n    return minDTZ == 0xFFFF ? -1 : minDTZ;\n}\n\n\n// Use the DTZ tables to rank root moves.\n//\n// A return value false indicates that not all probes were successful.\nbool Tablebases::root_probe(Position&                    pos,\n                            Search::RootMoves&           rootMoves,\n                            bool                         rule50,\n                            bool                         rankDTZ,\n                            const std::function<bool()>& time_abort) {\n\n    ProbeState result = OK;\n    StateInfo  st;\n\n    // Obtain 50-move counter for the root position\n    int cnt50 = pos.rule50_count();\n\n    // Check whether a position was repeated since the last zeroing move.\n    bool rep = pos.has_repeated();\n\n    int dtz, bound = rule50 ? (MAX_DTZ / 2 - 100) : 1;\n\n    // Probe and rank each move\n    for (auto& m : rootMoves)\n    {\n        pos.do_move(m.pv[0], st);\n\n        // Calculate dtz for the current move counting from the root position\n        if (pos.rule50_count() == 0)\n        {\n            // In case of a zeroing move, dtz is one of -101/-1/0/1/101\n            WDLScore wdl = -probe_wdl(pos, &result);\n            dtz          = dtz_before_zeroing(wdl);\n        }\n        else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1))\n        {\n            // In case a root move leads to a draw by repetition or 50-move rule,\n            // we set dtz to zero. Note: since we are only 1 ply from the root,\n            // this must be a true 3-fold repetition inside the game history.\n            dtz = 0;\n        }\n        else\n        {\n            // Otherwise, take dtz for the new position and correct by 1 ply\n            dtz = -probe_dtz(pos, &result);\n            dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz;\n        }\n\n        // Make sure that a mating move is assigned a dtz value of 1\n        if (pos.checkers() && dtz == 2 && MoveList<LEGAL>(pos).size() == 0)\n            dtz = 1;\n\n        pos.undo_move(m.pv[0]);\n\n        if (time_abort() || result == FAIL)\n            return false;\n\n        // Better moves are ranked higher. Certain wins are ranked equally.\n        // Losing moves are ranked equally unless a 50-move draw is in sight.\n        int r    = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ - (rankDTZ ? dtz : 0)\n                                                        : MAX_DTZ / 2 - (dtz + cnt50))\n                 : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ - (rankDTZ ? dtz : 0)\n                                                     : -MAX_DTZ / 2 + (-dtz + cnt50))\n                           : 0;\n        m.tbRank = r;\n\n        // Determine the score to be displayed for this move. Assign at least\n        // 1 cp to cursed wins and let it grow to 49 cp as the positions gets\n        // closer to a real win.\n        m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1\n                  : r > 0  ? Value((std::max(3, r - (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)\n                  : r == 0 ? VALUE_DRAW\n                  : r > -bound\n                    ? Value((std::min(-3, r + (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)\n                    : -VALUE_MATE + MAX_PLY + 1;\n    }\n\n    return true;\n}\n\n\n// Use the WDL tables to rank root moves.\n// This is a fallback for the case that some or all DTZ tables are missing.\n//\n// A return value false indicates that not all probes were successful.\nbool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) {\n\n    static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};\n\n    ProbeState result = OK;\n    StateInfo  st;\n    WDLScore   wdl;\n\n\n    // Probe and rank each move\n    for (auto& m : rootMoves)\n    {\n        pos.do_move(m.pv[0], st);\n\n        if (pos.is_draw(1))\n            wdl = WDLDraw;\n        else\n            wdl = -probe_wdl(pos, &result);\n\n        pos.undo_move(m.pv[0]);\n\n        if (result == FAIL)\n            return false;\n\n        m.tbRank = WDL_to_rank[wdl + 2];\n\n        if (!rule50)\n            wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw;\n        m.tbScore = WDL_to_value[wdl + 2];\n    }\n\n    return true;\n}\n\nConfig Tablebases::rank_root_moves(const OptionsMap&            options,\n                                   Position&                    pos,\n                                   Search::RootMoves&           rootMoves,\n                                   bool                         rankDTZ,\n                                   const std::function<bool()>& time_abort) {\n    Config config;\n\n    if (rootMoves.empty())\n        return config;\n\n    config.rootInTB    = false;\n    config.useRule50   = bool(options[\"Syzygy50MoveRule\"]);\n    config.probeDepth  = int(options[\"SyzygyProbeDepth\"]);\n    config.cardinality = int(options[\"SyzygyProbeLimit\"]);\n\n    bool dtz_available = true;\n\n    // Tables with fewer pieces than SyzygyProbeLimit are searched with\n    // probeDepth == DEPTH_ZERO\n    if (config.cardinality > MaxCardinality)\n    {\n        config.cardinality = MaxCardinality;\n        config.probeDepth  = 0;\n    }\n\n    if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))\n    {\n        // Rank moves using DTZ tables, bail out if time_abort flags zeitnot\n        config.rootInTB =\n          root_probe(pos, rootMoves, options[\"Syzygy50MoveRule\"], rankDTZ, time_abort);\n\n        if (!config.rootInTB && !time_abort())\n        {\n            // DTZ tables are missing; try to rank moves using WDL tables\n            dtz_available   = false;\n            config.rootInTB = root_probe_wdl(pos, rootMoves, options[\"Syzygy50MoveRule\"]);\n        }\n    }\n\n    if (config.rootInTB)\n    {\n        // Sort moves according to TB rank\n        std::stable_sort(\n          rootMoves.begin(), rootMoves.end(),\n          [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });\n\n        // Probe during search only if DTZ is not available and we are winning\n        if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)\n            config.cardinality = 0;\n    }\n    else\n    {\n        // Clean up if root_probe() and root_probe_wdl() have failed\n        for (auto& m : rootMoves)\n            m.tbRank = 0;\n    }\n\n    return config;\n}\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/syzygy/tbprobe.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef TBPROBE_H\n#define TBPROBE_H\n\n#include <functional>\n#include <string>\n#include <vector>\n\n\nnamespace Stockfish {\nclass Position;\nclass OptionsMap;\n\nusing Depth = int;\n\nnamespace Search {\nstruct RootMove;\nusing RootMoves = std::vector<RootMove>;\n}\n}\n\nnamespace Stockfish::Tablebases {\n\nstruct Config {\n    int   cardinality = 0;\n    bool  rootInTB    = false;\n    bool  useRule50   = false;\n    Depth probeDepth  = 0;\n};\n\nenum WDLScore {\n    WDLLoss        = -2,  // Loss\n    WDLBlessedLoss = -1,  // Loss, but draw under 50-move rule\n    WDLDraw        = 0,   // Draw\n    WDLCursedWin   = 1,   // Win, but draw under 50-move rule\n    WDLWin         = 2,   // Win\n};\n\n// Possible states after a probing operation\nenum ProbeState {\n    FAIL              = 0,   // Probe failed (missing file table)\n    OK                = 1,   // Probe successful\n    CHANGE_STM        = -1,  // DTZ should check the other side\n    ZEROING_BEST_MOVE = 2    // Best move zeroes DTZ (capture or pawn move)\n};\n\nextern int MaxCardinality;\n\n\nvoid     init(const std::string& paths);\nWDLScore probe_wdl(Position& pos, ProbeState* result);\nint      probe_dtz(Position& pos, ProbeState* result);\nbool     root_probe(Position&                    pos,\n                    Search::RootMoves&           rootMoves,\n                    bool                         rule50,\n                    bool                         rankDTZ,\n                    const std::function<bool()>& time_abort);\nbool     root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);\nConfig   rank_root_moves(\n    const OptionsMap&            options,\n    Position&                    pos,\n    Search::RootMoves&           rootMoves,\n    bool                         rankDTZ    = false,\n    const std::function<bool()>& time_abort = []() { return false; });\n\n}  // namespace Stockfish::Tablebases\n\n#endif\n"
  },
  {
    "path": "src/thread.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"thread.h\"\n\n#include <algorithm>\n#include <cassert>\n#include <deque>\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"bitboard.h\"\n#include \"history.h\"\n#include \"memory.h\"\n#include \"movegen.h\"\n#include \"search.h\"\n#include \"syzygy/tbprobe.h\"\n#include \"timeman.h\"\n#include \"types.h\"\n#include \"uci.h\"\n#include \"ucioption.h\"\n\nnamespace Stockfish {\n\n// Constructor launches the thread and waits until it goes to sleep\n// in idle_loop(). Note that 'searching' and 'exit' should be already set.\nThread::Thread(Search::SharedState&                    sharedState,\n               std::unique_ptr<Search::ISearchManager> sm,\n               size_t                                  n,\n               size_t                                  numaN,\n               size_t                                  totalNumaCount,\n               OptionalThreadToNumaNodeBinder          binder) :\n    idx(n),\n    idxInNuma(numaN),\n    totalNuma(totalNumaCount),\n    nthreads(sharedState.options[\"Threads\"]),\n    stdThread(&Thread::idle_loop, this) {\n\n    wait_for_search_finished();\n\n    run_custom_job([this, &binder, &sharedState, &sm, n]() {\n        // Use the binder to [maybe] bind the threads to a NUMA node before doing\n        // the Worker allocation. Ideally we would also allocate the SearchManager\n        // here, but that's minor.\n        this->numaAccessToken = binder();\n        this->worker          = make_unique_large_page<Search::Worker>(\n          sharedState, std::move(sm), n, idxInNuma, totalNuma, this->numaAccessToken);\n    });\n\n    wait_for_search_finished();\n}\n\n\n// Destructor wakes up the thread in idle_loop() and waits\n// for its termination. Thread should be already waiting.\nThread::~Thread() {\n\n    assert(!searching);\n\n    exit = true;\n    start_searching();\n    stdThread.join();\n}\n\n// Wakes up the thread that will start the search\nvoid Thread::start_searching() {\n    assert(worker != nullptr);\n    run_custom_job([this]() { worker->start_searching(); });\n}\n\n// Clears the histories for the thread worker (usually before a new game)\nvoid Thread::clear_worker() {\n    assert(worker != nullptr);\n    run_custom_job([this]() { worker->clear(); });\n}\n\n// Blocks on the condition variable until the thread has finished searching\nvoid Thread::wait_for_search_finished() {\n\n    std::unique_lock<std::mutex> lk(mutex);\n    cv.wait(lk, [&] { return !searching; });\n}\n\n// Launching a function in the thread\nvoid Thread::run_custom_job(std::function<void()> f) {\n    {\n        std::unique_lock<std::mutex> lk(mutex);\n        cv.wait(lk, [&] { return !searching; });\n        jobFunc   = std::move(f);\n        searching = true;\n    }\n    cv.notify_one();\n}\n\nvoid Thread::ensure_network_replicated() { worker->ensure_network_replicated(); }\n\n// Thread gets parked here, blocked on the condition variable\n// when the thread has no work to do.\n\nvoid Thread::idle_loop() {\n    while (true)\n    {\n        std::unique_lock<std::mutex> lk(mutex);\n        searching = false;\n        cv.notify_one();  // Wake up anyone waiting for search finished\n        cv.wait(lk, [&] { return searching; });\n\n        if (exit)\n            return;\n\n        std::function<void()> job = std::move(jobFunc);\n        jobFunc                   = nullptr;\n\n        lk.unlock();\n\n        if (job)\n            job();\n    }\n}\n\nSearch::SearchManager* ThreadPool::main_manager() { return main_thread()->worker->main_manager(); }\n\nuint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }\nuint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }\n\nstatic size_t next_power_of_two(uint64_t count) { return count > 1 ? (2ULL << msb(count - 1)) : 1; }\n\n// Creates/destroys threads to match the requested number.\n// Created and launched threads will immediately go to sleep in idle_loop.\n// Upon resizing, threads are recreated to allow for binding if necessary.\nvoid ThreadPool::set(const NumaConfig&                           numaConfig,\n                     Search::SharedState                         sharedState,\n                     const Search::SearchManager::UpdateContext& updateContext) {\n\n    if (threads.size() > 0)  // destroy any existing thread(s)\n    {\n        main_thread()->wait_for_search_finished();\n\n        threads.clear();\n\n        boundThreadToNumaNode.clear();\n    }\n\n    const size_t requested = sharedState.options[\"Threads\"];\n\n    if (requested > 0)  // create new thread(s)\n    {\n        // Binding threads may be problematic when there's multiple NUMA nodes and\n        // multiple Stockfish instances running. In particular, if each instance\n        // runs a single thread then they would all be mapped to the first NUMA node.\n        // This is undesirable, and so the default behaviour (i.e. when the user does not\n        // change the NumaConfig UCI setting) is to not bind the threads to processors\n        // unless we know for sure that we span NUMA nodes and replication is required.\n        const std::string numaPolicy(sharedState.options[\"NumaPolicy\"]);\n        const bool        doBindThreads = [&]() {\n            if (numaPolicy == \"none\")\n                return false;\n\n            if (numaPolicy == \"auto\")\n                return numaConfig.suggests_binding_threads(requested);\n\n            // numaPolicy == \"system\", or explicitly set by the user\n            return true;\n        }();\n\n        std::map<NumaIndex, size_t> counts;\n        boundThreadToNumaNode = doBindThreads\n                                ? numaConfig.distribute_threads_among_numa_nodes(requested)\n                                : std::vector<NumaIndex>{};\n\n        if (boundThreadToNumaNode.empty())\n            counts[0] = requested;  // Pretend all threads are part of numa node 0\n        else\n        {\n            for (size_t i = 0; i < boundThreadToNumaNode.size(); ++i)\n                counts[boundThreadToNumaNode[i]]++;\n        }\n\n        sharedState.sharedHistories.clear();\n        for (auto pair : counts)\n        {\n            NumaIndex numaIndex = pair.first;\n            uint64_t  count     = pair.second;\n            auto      f         = [&]() {\n                sharedState.sharedHistories.try_emplace(numaIndex, next_power_of_two(count));\n            };\n            if (doBindThreads)\n                numaConfig.execute_on_numa_node(numaIndex, f);\n            else\n                f();\n        }\n\n        auto threadsPerNode = counts;\n        counts.clear();\n\n        while (threads.size() < requested)\n        {\n            const size_t    threadId      = threads.size();\n            const NumaIndex numaId        = doBindThreads ? boundThreadToNumaNode[threadId] : 0;\n            auto            create_thread = [&]() {\n                auto manager = threadId == 0\n                                          ? std::unique_ptr<Search::ISearchManager>(\n                                   std::make_unique<Search::SearchManager>(updateContext))\n                                          : std::make_unique<Search::NullSearchManager>();\n\n                // When not binding threads we want to force all access to happen\n                // from the same NUMA node, because in case of NUMA replicated memory\n                // accesses we don't want to trash cache in case the threads get scheduled\n                // on the same NUMA node.\n                auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId)\n                                                       : OptionalThreadToNumaNodeBinder(numaId);\n\n                threads.emplace_back(std::make_unique<Thread>(sharedState, std::move(manager),\n                                                                         threadId, counts[numaId]++,\n                                                                         threadsPerNode[numaId], binder));\n            };\n\n            // Ensure the worker thread inherits the intended NUMA affinity at creation.\n            if (doBindThreads)\n                numaConfig.execute_on_numa_node(numaId, create_thread);\n            else\n                create_thread();\n        }\n\n        clear();\n\n        main_thread()->wait_for_search_finished();\n    }\n}\n\n\n// Sets threadPool data to initial values\nvoid ThreadPool::clear() {\n    if (threads.size() == 0)\n        return;\n\n    for (auto&& th : threads)\n        th->clear_worker();\n\n    for (auto&& th : threads)\n        th->wait_for_search_finished();\n\n    // These two affect the time taken on the first move of a game:\n    main_manager()->bestPreviousAverageScore = VALUE_INFINITE;\n    main_manager()->previousTimeReduction    = 0.85;\n\n    main_manager()->callsCnt           = 0;\n    main_manager()->bestPreviousScore  = VALUE_INFINITE;\n    main_manager()->originalTimeAdjust = -1;\n    main_manager()->tm.clear();\n}\n\nvoid ThreadPool::run_on_thread(size_t threadId, std::function<void()> f) {\n    assert(threads.size() > threadId);\n    threads[threadId]->run_custom_job(std::move(f));\n}\n\nvoid ThreadPool::wait_on_thread(size_t threadId) {\n    assert(threads.size() > threadId);\n    threads[threadId]->wait_for_search_finished();\n}\n\nsize_t ThreadPool::num_threads() const { return threads.size(); }\n\n\n// Wakes up main thread waiting in idle_loop() and returns immediately.\n// Main thread will wake up other threads and start the search.\nvoid ThreadPool::start_thinking(const OptionsMap&  options,\n                                Position&          pos,\n                                StateListPtr&      states,\n                                Search::LimitsType limits) {\n\n    main_thread()->wait_for_search_finished();\n\n    main_manager()->stopOnPonderhit = stop = false;\n    main_manager()->ponder                 = limits.ponderMode;\n\n    increaseDepth = true;\n\n    Search::RootMoves rootMoves;\n    const auto        legalmoves = MoveList<LEGAL>(pos);\n\n    for (const auto& uciMove : limits.searchmoves)\n    {\n        auto move = UCIEngine::to_move(pos, uciMove);\n\n        if (std::find(legalmoves.begin(), legalmoves.end(), move) != legalmoves.end())\n            rootMoves.emplace_back(move);\n    }\n\n    if (rootMoves.empty())\n        for (const auto& m : legalmoves)\n            rootMoves.emplace_back(m);\n\n    Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves);\n\n    // After ownership transfer 'states' becomes empty, so if we stop the search\n    // and call 'go' again without setting a new position states.get() == nullptr.\n    assert(states.get() || setupStates.get());\n\n    if (states.get())\n        setupStates = std::move(states);  // Ownership transfer, states is now empty\n\n    // We use Position::set() to set root position across threads. But there are\n    // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot\n    // be deduced from a fen string, so set() clears them and they are set from\n    // setupStates->back() later. The rootState is per thread, earlier states are\n    // shared since they are read-only.\n    for (auto&& th : threads)\n    {\n        th->run_custom_job([&]() {\n            th->worker->limits = limits;\n            th->worker->nodes = th->worker->tbHits = th->worker->bestMoveChanges = 0;\n            th->worker->nmpMinPly                                                = 0;\n            th->worker->rootDepth = th->worker->completedDepth = 0;\n            th->worker->rootMoves                              = rootMoves;\n            th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState);\n            th->worker->rootState = setupStates->back();\n            th->worker->tbConfig  = tbConfig;\n        });\n    }\n\n    for (auto&& th : threads)\n        th->wait_for_search_finished();\n\n    main_thread()->start_searching();\n}\n\nThread* ThreadPool::get_best_thread() const {\n\n    Thread* bestThread = threads.front().get();\n    Value   minScore   = VALUE_NONE;\n\n    std::unordered_map<Move, int64_t, Move::MoveHash> votes(\n      2 * std::min(size(), bestThread->worker->rootMoves.size()));\n\n    // Find the minimum score of all threads\n    for (auto&& th : threads)\n        minScore = std::min(minScore, th->worker->rootMoves[0].score);\n\n    // Vote according to score and depth, and select the best thread\n    auto thread_voting_value = [minScore](Thread* th) {\n        return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);\n    };\n\n    for (auto&& th : threads)\n        votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th.get());\n\n    auto has_bound = [](const Thread* th) {\n        return th->worker->rootMoves[0].scoreLowerbound || th->worker->rootMoves[0].scoreUpperbound;\n    };\n\n    for (auto&& th : threads)\n    {\n        const auto bestThreadScore = bestThread->worker->rootMoves[0].score;\n        const auto newThreadScore  = th->worker->rootMoves[0].score;\n\n        const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv;\n        const auto& newThreadPV  = th->worker->rootMoves[0].pv;\n\n        const auto bestThreadMoveVote = votes[bestThreadPV[0]];\n        const auto newThreadMoveVote  = votes[newThreadPV[0]];\n\n        // Aborted searches may lead to inexact win scores.\n        const bool bestThreadInProvenWin = is_win(bestThreadScore) && !has_bound(bestThread);\n        const bool newThreadInProvenWin  = is_win(newThreadScore) && !has_bound(th.get());\n\n        // Loss scores may be inexact only for aborted d1 searches.\n        const bool bestThreadInProvenLoss =\n          bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore) && !has_bound(bestThread);\n        const bool newThreadInProvenLoss =\n          newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore) && !has_bound(th.get());\n\n        // We make sure not to pick a thread with truncated principal variation\n        const bool betterVotingValue =\n          thread_voting_value(th.get()) * int(newThreadPV.size() > 2)\n          > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2);\n\n        if (bestThreadInProvenWin)\n        {\n            // Make sure we pick the shortest mate / TB conversion\n            if (newThreadInProvenWin && newThreadScore > bestThreadScore)\n                bestThread = th.get();\n        }\n        else if (bestThreadInProvenLoss)\n        {\n            // Make sure we pick the shortest mated / TB conversion\n            if (newThreadInProvenLoss && newThreadScore < bestThreadScore)\n                bestThread = th.get();\n        }\n        else if (newThreadInProvenWin || newThreadInProvenLoss\n                 || (!is_loss(newThreadScore)\n                     && (newThreadMoveVote > bestThreadMoveVote\n                         || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))\n            bestThread = th.get();\n    }\n\n    return bestThread;\n}\n\n\n// Start non-main threads.\n// Will be invoked by main thread after it has started searching.\nvoid ThreadPool::start_searching() {\n\n    for (auto&& th : threads)\n        if (th != threads.front())\n            th->start_searching();\n}\n\n\n// Wait for non-main threads\nvoid ThreadPool::wait_for_search_finished() const {\n\n    for (auto&& th : threads)\n        if (th != threads.front())\n            th->wait_for_search_finished();\n}\n\nstd::vector<size_t> ThreadPool::get_bound_thread_count_by_numa_node() const {\n    std::vector<size_t> counts;\n\n    if (!boundThreadToNumaNode.empty())\n    {\n        NumaIndex highestNumaNode = 0;\n        for (NumaIndex n : boundThreadToNumaNode)\n            if (n > highestNumaNode)\n                highestNumaNode = n;\n\n        counts.resize(highestNumaNode + 1, 0);\n\n        for (NumaIndex n : boundThreadToNumaNode)\n            counts[n] += 1;\n    }\n\n    return counts;\n}\n\nvoid ThreadPool::ensure_network_replicated() {\n    for (auto&& th : threads)\n        th->ensure_network_replicated();\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/thread.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef THREAD_H_INCLUDED\n#define THREAD_H_INCLUDED\n\n#include <atomic>\n#include <condition_variable>\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <vector>\n\n#include \"memory.h\"\n#include \"numa.h\"\n#include \"position.h\"\n#include \"search.h\"\n#include \"thread_win32_osx.h\"\n\nnamespace Stockfish {\n\n\nclass OptionsMap;\nusing Value = int;\n\n// Sometimes we don't want to actually bind the threads, but the recipient still\n// needs to think it runs on *some* NUMA node, such that it can access structures\n// that rely on NUMA node knowledge. This class encapsulates this optional process\n// such that the recipient does not need to know whether the binding happened or not.\nclass OptionalThreadToNumaNodeBinder {\n   public:\n    OptionalThreadToNumaNodeBinder(NumaIndex n) :\n        numaConfig(nullptr),\n        numaId(n) {}\n\n    OptionalThreadToNumaNodeBinder(const NumaConfig& cfg, NumaIndex n) :\n        numaConfig(&cfg),\n        numaId(n) {}\n\n    NumaReplicatedAccessToken operator()() const {\n        if (numaConfig != nullptr)\n            return numaConfig->bind_current_thread_to_numa_node(numaId);\n        else\n            return NumaReplicatedAccessToken(numaId);\n    }\n\n   private:\n    const NumaConfig* numaConfig;\n    NumaIndex         numaId;\n};\n\n// Abstraction of a thread. It contains a pointer to the worker and a native thread.\n// After construction, the native thread is started with idle_loop()\n// waiting for a signal to start searching.\n// When the signal is received, the thread starts searching and when\n// the search is finished, it goes back to idle_loop() waiting for a new signal.\nclass Thread {\n   public:\n    Thread(Search::SharedState&,\n           std::unique_ptr<Search::ISearchManager>,\n           size_t,\n           size_t,\n           size_t,\n           OptionalThreadToNumaNodeBinder);\n    virtual ~Thread();\n\n    void idle_loop();\n    void start_searching();\n    void clear_worker();\n    void run_custom_job(std::function<void()> f);\n\n    void ensure_network_replicated();\n\n    // Thread has been slightly altered to allow running custom jobs, so\n    // this name is no longer correct. However, this class (and ThreadPool)\n    // require further work to make them properly generic while maintaining\n    // appropriate specificity regarding search, from the point of view of an\n    // outside user, so renaming of this function is left for whenever that happens.\n    void   wait_for_search_finished();\n    size_t id() const { return idx; }\n\n    LargePagePtr<Search::Worker> worker;\n    std::function<void()>        jobFunc;\n\n   private:\n    std::mutex                mutex;\n    std::condition_variable   cv;\n    size_t                    idx, idxInNuma, totalNuma, nthreads;\n    bool                      exit = false, searching = true;  // Set before starting std::thread\n    NativeThread              stdThread;\n    NumaReplicatedAccessToken numaAccessToken;\n};\n\n\n// ThreadPool struct handles all the threads-related stuff like init, starting,\n// parking and, most importantly, launching a thread. All the access to threads\n// is done through this class.\nclass ThreadPool {\n   public:\n    ThreadPool() {}\n\n    ~ThreadPool() {\n        // destroy any existing thread(s)\n        if (threads.size() > 0)\n        {\n            main_thread()->wait_for_search_finished();\n\n            threads.clear();\n        }\n    }\n\n    ThreadPool(const ThreadPool&) = delete;\n    ThreadPool(ThreadPool&&)      = delete;\n\n    ThreadPool& operator=(const ThreadPool&) = delete;\n    ThreadPool& operator=(ThreadPool&&)      = delete;\n\n    void   start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);\n    void   run_on_thread(size_t threadId, std::function<void()> f);\n    void   wait_on_thread(size_t threadId);\n    size_t num_threads() const;\n    void   clear();\n    void   set(const NumaConfig& numaConfig,\n               Search::SharedState,\n               const Search::SearchManager::UpdateContext&);\n\n    Search::SearchManager* main_manager();\n    Thread*                main_thread() const { return threads.front().get(); }\n    uint64_t               nodes_searched() const;\n    uint64_t               tb_hits() const;\n    Thread*                get_best_thread() const;\n    void                   start_searching();\n    void                   wait_for_search_finished() const;\n\n    std::vector<size_t> get_bound_thread_count_by_numa_node() const;\n\n    void ensure_network_replicated();\n\n    std::atomic_bool stop, increaseDepth;\n\n    auto cbegin() const noexcept { return threads.cbegin(); }\n    auto begin() noexcept { return threads.begin(); }\n    auto end() noexcept { return threads.end(); }\n    auto cend() const noexcept { return threads.cend(); }\n    auto size() const noexcept { return threads.size(); }\n    auto empty() const noexcept { return threads.empty(); }\n\n   private:\n    StateListPtr                         setupStates;\n    std::vector<std::unique_ptr<Thread>> threads;\n    std::vector<NumaIndex>               boundThreadToNumaNode;\n\n    uint64_t accumulate(std::atomic<uint64_t> Search::Worker::* member) const {\n\n        uint64_t sum = 0;\n        for (auto&& th : threads)\n            sum += (th->worker.get()->*member).load(std::memory_order_relaxed);\n        return sum;\n    }\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef THREAD_H_INCLUDED\n"
  },
  {
    "path": "src/thread_win32_osx.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef THREAD_WIN32_OSX_H_INCLUDED\n#define THREAD_WIN32_OSX_H_INCLUDED\n\n#include <thread>\n\n// On OSX threads other than the main thread are created with a reduced stack\n// size of 512KB by default, this is too low for deep searches, which require\n// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.\n// The implementation calls pthread_create() with the stack size parameter\n// equal to the Linux 8MB default, on platforms that support it.\n\n#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)\n\n    #include <pthread.h>\n    #include <functional>\n\nnamespace Stockfish {\n\nclass NativeThread {\n    pthread_t thread;\n\n    static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;\n\n   public:\n    template<class Function, class... Args>\n    explicit NativeThread(Function&& fun, Args&&... args) {\n        auto func = new std::function<void()>(\n          std::bind(std::forward<Function>(fun), std::forward<Args>(args)...));\n\n        pthread_attr_t attr_storage, *attr = &attr_storage;\n        pthread_attr_init(attr);\n        pthread_attr_setstacksize(attr, TH_STACK_SIZE);\n\n        auto start_routine = [](void* ptr) -> void* {\n            auto f = reinterpret_cast<std::function<void()>*>(ptr);\n            // Call the function\n            (*f)();\n            delete f;\n            return nullptr;\n        };\n\n        pthread_create(&thread, attr, start_routine, func);\n    }\n\n    void join() { pthread_join(thread, nullptr); }\n};\n\n}  // namespace Stockfish\n\n#else  // Default case: use STL classes\n\nnamespace Stockfish {\n\nusing NativeThread = std::thread;\n\n}  // namespace Stockfish\n\n#endif\n\n#endif  // #ifndef THREAD_WIN32_OSX_H_INCLUDED\n"
  },
  {
    "path": "src/timeman.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"timeman.h\"\n\n#include <algorithm>\n#include <cassert>\n#include <cmath>\n#include <cstdint>\n\n#include \"search.h\"\n#include \"ucioption.h\"\n\nnamespace Stockfish {\n\nTimePoint TimeManagement::optimum() const { return optimumTime; }\nTimePoint TimeManagement::maximum() const { return maximumTime; }\n\nvoid TimeManagement::clear() {\n    availableNodes = -1;  // When in 'nodes as time' mode\n}\n\nvoid TimeManagement::advance_nodes_time(std::int64_t nodes) {\n    assert(useNodesTime);\n    availableNodes = std::max(int64_t(0), availableNodes - nodes);\n}\n\n// Called at the beginning of the search and calculates\n// the bounds of time allowed for the current game ply. We currently support:\n//      1) x basetime (+ z increment)\n//      2) x moves in y seconds (+ z increment)\nvoid TimeManagement::init(Search::LimitsType& limits,\n                          Color               us,\n                          int                 ply,\n                          const OptionsMap&   options,\n                          double&             originalTimeAdjust) {\n    TimePoint npmsec = TimePoint(options[\"nodestime\"]);\n\n    // If we have no time, we don't need to fully initialize TM.\n    // startTime is used by movetime and useNodesTime is used in elapsed calls.\n    startTime    = limits.startTime;\n    useNodesTime = npmsec != 0;\n\n    if (limits.time[us] == 0)\n        return;\n\n    TimePoint moveOverhead = TimePoint(options[\"Move Overhead\"]);\n\n    // optScale is a percentage of available time to use for the current move.\n    // maxScale is a multiplier applied to optimumTime.\n    double optScale, maxScale;\n\n    // If we have to play in 'nodes as time' mode, then convert from time\n    // to nodes, and use resulting values in time management formulas.\n    // WARNING: to avoid time losses, the given npmsec (nodes per millisecond)\n    // must be much lower than the real engine speed.\n    if (useNodesTime)\n    {\n        if (availableNodes == -1)                       // Only once at game start\n            availableNodes = npmsec * limits.time[us];  // Time is in msec\n\n        // Convert from milliseconds to nodes\n        limits.time[us] = TimePoint(availableNodes);\n        limits.inc[us] *= npmsec;\n        limits.npmsec = npmsec;\n        moveOverhead *= npmsec;\n    }\n\n    // These numbers are used where multiplications, divisions or comparisons\n    // with constants are involved.\n    const int64_t   scaleFactor = useNodesTime ? npmsec : 1;\n    const TimePoint scaledTime  = limits.time[us] / scaleFactor;\n\n    // Maximum move horizon\n    int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051;\n\n    // If less than one second, gradually reduce mtg\n    if (scaledTime < 1000)\n        centiMTG = int(scaledTime * 5.051);\n\n    // Make sure timeLeft is > 0 since we may use it as a divisor\n    TimePoint timeLeft =\n      std::max(TimePoint(1),\n               limits.time[us]\n                 + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100);\n\n    // x basetime (+ z increment)\n    // If there is a healthy increment, timeLeft can exceed the actual available\n    // game time for the current move, so also cap to a percentage of available game time.\n    if (limits.movestogo == 0)\n    {\n        // Extra time according to timeLeft\n        if (originalTimeAdjust < 0)\n            originalTimeAdjust = 0.3272 * std::log10(timeLeft) - 0.4141;\n\n        // Calculate time constants based on current time left.\n        double logTimeInSec = std::log10(scaledTime / 1000.0);\n        double optConstant  = std::min(0.0029869 + 0.00033554 * logTimeInSec, 0.004905);\n        double maxConstant  = std::max(3.3744 + 3.0608 * logTimeInSec, 3.1441);\n\n        optScale = std::min(0.012112 + std::pow(ply + 3.22713, 0.46866) * optConstant,\n                            0.19404 * limits.time[us] / timeLeft)\n                 * originalTimeAdjust;\n\n        maxScale = std::min(6.873, maxConstant + ply / 12.352);\n    }\n\n    // x moves in y seconds (+ z increment)\n    else\n    {\n        optScale =\n          std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft);\n        maxScale = 1.3 + 0.11 * (centiMTG / 100.0);\n    }\n\n    // Limit the maximum possible time for this move\n    optimumTime = TimePoint(std::max(1.0, optScale * timeLeft));\n    maximumTime =\n      TimePoint(std::max(double(optimumTime), std::min(0.8097 * limits.time[us] - moveOverhead,\n                                                       maxScale * optimumTime)));\n\n    if (options[\"Ponder\"])\n        optimumTime += optimumTime / 4;\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/timeman.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef TIMEMAN_H_INCLUDED\n#define TIMEMAN_H_INCLUDED\n\n#include <cstdint>\n\n#include \"misc.h\"\n\nnamespace Stockfish {\n\nclass OptionsMap;\nenum Color : uint8_t;\n\nnamespace Search {\nstruct LimitsType;\n}\n\n// The TimeManagement class computes the optimal time to think depending on\n// the maximum available time, the game move number, and other parameters.\nclass TimeManagement {\n   public:\n    void init(Search::LimitsType& limits,\n              Color               us,\n              int                 ply,\n              const OptionsMap&   options,\n              double&             originalTimeAdjust);\n\n    TimePoint optimum() const;\n    TimePoint maximum() const;\n    template<typename FUNC>\n    TimePoint elapsed(FUNC nodes) const {\n        return useNodesTime ? TimePoint(nodes()) : elapsed_time();\n    }\n    TimePoint elapsed_time() const { return now() - startTime; };\n\n    void clear();\n    void advance_nodes_time(std::int64_t nodes);\n\n   private:\n    TimePoint startTime;\n    TimePoint optimumTime;\n    TimePoint maximumTime;\n\n    std::int64_t availableNodes = -1;     // When in 'nodes as time' mode\n    bool         useNodesTime   = false;  // True if we are in 'nodes as time' mode\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef TIMEMAN_H_INCLUDED\n"
  },
  {
    "path": "src/tt.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"tt.h\"\n\n#include <cassert>\n#include <cstdint>\n#include <cstdlib>\n#include <cstring>\n#include <iostream>\n\n#include \"memory.h\"\n#include \"misc.h\"\n#include \"syzygy/tbprobe.h\"\n#include \"thread.h\"\n\nnamespace Stockfish {\n\n\n// TTEntry struct is the 10 bytes transposition table entry, defined as below:\n//\n// key        16 bit\n// depth       8 bit\n// generation  5 bit\n// pv node     1 bit\n// bound type  2 bit\n// move       16 bit\n// value      16 bit\n// evaluation 16 bit\n//\n// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially.\n// Equally, the store order in save() matches this order.\n\nstruct TTEntry {\n\n    // Convert internal bitfields to external types\n    TTData read() const {\n        return TTData{Move(move16),           Value(value16),\n                      Value(eval16),          Depth(depth8 + DEPTH_ENTRY_OFFSET),\n                      Bound(genBound8 & 0x3), bool(genBound8 & 0x4)};\n    }\n\n    bool is_occupied() const;\n    void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);\n    // The returned age is a multiple of TranspositionTable::GENERATION_DELTA\n    uint8_t relative_age(const uint8_t generation8) const;\n\n   private:\n    friend class TranspositionTable;\n\n    uint16_t key16;\n    uint8_t  depth8;\n    uint8_t  genBound8;\n    Move     move16;\n    int16_t  value16;\n    int16_t  eval16;\n};\n\n// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits\n// and 3 trailing miscellaneous bits.\n\n// These bits are reserved for other things.\nstatic constexpr unsigned GENERATION_BITS = 3;\n// increment for generation field\nstatic constexpr int GENERATION_DELTA = (1 << GENERATION_BITS);\n// cycle length\nstatic constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA;\n// mask to pull out generation number\nstatic constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;\n\n// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but\n// 2) we need to store negative depths for QS. (`depth8` is the only field with \"spare bits\":\n// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.)\nbool TTEntry::is_occupied() const { return bool(depth8); }\n\n// Populates the TTEntry with a new node's data, possibly\n// overwriting an old position. The update is not atomic and can be racy.\nvoid TTEntry::save(\n  Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {\n\n    // Preserve the old ttmove if we don't have a new one\n    if (m || uint16_t(k) != key16)\n        move16 = m;\n\n    // Overwrite less valuable entries (cheapest checks first)\n    if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4\n        || relative_age(generation8))\n    {\n        assert(d > DEPTH_ENTRY_OFFSET);\n        assert(d < 256 + DEPTH_ENTRY_OFFSET);\n\n        key16     = uint16_t(k);\n        depth8    = uint8_t(d - DEPTH_ENTRY_OFFSET);\n        genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);\n        value16   = int16_t(v);\n        eval16    = int16_t(ev);\n    }\n}\n\n\nuint8_t TTEntry::relative_age(const uint8_t generation8) const {\n    // Due to our packed storage format for generation and its cyclic\n    // nature we add GENERATION_CYCLE (256 is the modulus, plus what\n    // is needed to keep the unrelated lowest n bits from affecting\n    // the result) to calculate the entry age correctly even after\n    // generation8 overflows into the next cycle.\n    return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK;\n}\n\n\n// TTWriter is but a very thin wrapper around the pointer\nTTWriter::TTWriter(TTEntry* tte) :\n    entry(tte) {}\n\nvoid TTWriter::write(\n  Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {\n    entry->save(k, v, pv, b, d, m, ev, generation8);\n}\n\n\n// A TranspositionTable is an array of Cluster, of size clusterCount. Each cluster consists of ClusterSize number\n// of TTEntry. Each non-empty TTEntry contains information on exactly one position. The size of a Cluster should\n// divide the size of a cache line for best performance, as the cacheline is prefetched when possible.\n\nstatic constexpr int ClusterSize = 3;\n\nstruct Cluster {\n    TTEntry entry[ClusterSize];\n    char    padding[2];  // Pad to 32 bytes\n};\n\nstatic_assert(sizeof(Cluster) == 32, \"Suboptimal Cluster size\");\n\n\n// Sets the size of the transposition table,\n// measured in megabytes. Transposition table consists\n// of clusters and each cluster consists of ClusterSize number of TTEntry.\nvoid TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {\n    aligned_large_pages_free(table);\n\n    clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);\n\n    table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));\n\n    if (!table)\n    {\n        std::cerr << \"Failed to allocate \" << mbSize << \"MB for transposition table.\" << std::endl;\n        exit(EXIT_FAILURE);\n    }\n\n    clear(threads);\n}\n\n\n// Initializes the entire transposition table to zero,\n// in a multi-threaded way.\nvoid TranspositionTable::clear(ThreadPool& threads) {\n    generation8              = 0;\n    const size_t threadCount = threads.num_threads();\n\n    for (size_t i = 0; i < threadCount; ++i)\n    {\n        threads.run_on_thread(i, [this, i, threadCount]() {\n            // Each thread will zero its part of the hash table\n            const size_t stride = clusterCount / threadCount;\n            const size_t start  = stride * i;\n            const size_t len    = i + 1 != threadCount ? stride : clusterCount - start;\n\n            std::memset(&table[start], 0, len * sizeof(Cluster));\n        });\n    }\n\n    for (size_t i = 0; i < threadCount; ++i)\n        threads.wait_on_thread(i);\n}\n\n\n// Returns an approximation of the hashtable\n// occupation during a search. The hash is x permill full, as per UCI protocol.\n// Only counts entries which match the current generation.\nint TranspositionTable::hashfull(int maxAge) const {\n    int maxAgeInternal = maxAge << GENERATION_BITS;\n    int cnt            = 0;\n    for (int i = 0; i < 1000; ++i)\n        for (int j = 0; j < ClusterSize; ++j)\n            cnt += table[i].entry[j].is_occupied()\n                && table[i].entry[j].relative_age(generation8) <= maxAgeInternal;\n\n    return cnt / ClusterSize;\n}\n\n\nvoid TranspositionTable::new_search() {\n    // increment by delta to keep lower bits as is\n    generation8 += GENERATION_DELTA;\n}\n\n\nuint8_t TranspositionTable::generation() const { return generation8; }\n\n\n// Looks up the current position in the transposition\n// table. It returns true if the position is found.\n// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry\n// to be replaced later. The replace value of an entry is calculated as its depth\n// minus 8 times its relative age. TTEntry t1 is considered more valuable than\n// TTEntry t2 if its replace value is greater than that of t2.\nstd::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) const {\n\n    TTEntry* const tte   = first_entry(key);\n    const uint16_t key16 = uint16_t(key);  // Use the low 16 bits as key inside the cluster\n\n    for (int i = 0; i < ClusterSize; ++i)\n        if (tte[i].key16 == key16)\n            // This gap is the main place for read races.\n            // After `read()` completes that copy is final, but may be self-inconsistent.\n            return {tte[i].is_occupied(), tte[i].read(), TTWriter(&tte[i])};\n\n    // Find an entry to be replaced according to the replacement strategy\n    TTEntry* replace = tte;\n    for (int i = 1; i < ClusterSize; ++i)\n        if (replace->depth8 - replace->relative_age(generation8)\n            > tte[i].depth8 - tte[i].relative_age(generation8))\n            replace = &tte[i];\n\n    return {false,\n            TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false},\n            TTWriter(replace)};\n}\n\n\nTTEntry* TranspositionTable::first_entry(const Key key) const {\n    return &table[mul_hi64(key, clusterCount)].entry[0];\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/tt.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef TT_H_INCLUDED\n#define TT_H_INCLUDED\n\n#include <cstddef>\n#include <cstdint>\n#include <tuple>\n\n#include \"memory.h\"\n#include \"types.h\"\n\nnamespace Stockfish {\n\nclass ThreadPool;\nstruct TTEntry;\nstruct Cluster;\n\n// There is only one global hash table for the engine and all its threads. For chess in particular, we even allow racy\n// updates between threads to and from the TT, as taking the time to synchronize access would cost thinking time and\n// thus elo. As a hash table, collisions are possible and may cause chess playing issues (bizarre blunders, faulty mate\n// reports, etc). Fixing these also loses elo; however such risk decreases quickly with larger TT size.\n//\n// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of:\n//   1) whether the entry already has this position\n//   2) a copy of the prior data (if any) (may be inconsistent due to read races)\n//   3) a writer object to this entry\n// The copied data and the writer are separated to maintain clear boundaries between local vs global objects.\n\n\n// A copy of the data already in the entry (possibly collided). `probe` may be racy, resulting in inconsistent data.\nstruct TTData {\n    Move  move;\n    Value value, eval;\n    Depth depth;\n    Bound bound;\n    bool  is_pv;\n\n    TTData() = delete;\n\n    // clang-format off\n    TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) :\n        move(m),\n        value(v),\n        eval(ev),\n        depth(d),\n        bound(b),\n        is_pv(pv) {};\n    // clang-format on\n};\n\n\n// This is used to make racy writes to the global TT.\nstruct TTWriter {\n   public:\n    void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);\n\n   private:\n    friend class TranspositionTable;\n    TTEntry* entry;\n    TTWriter(TTEntry* tte);\n};\n\n\nclass TranspositionTable {\n\n   public:\n    ~TranspositionTable() { aligned_large_pages_free(table); }\n\n    void resize(size_t mbSize, ThreadPool& threads);  // Set TT size\n    void clear(ThreadPool& threads);                  // Re-initialize memory, multithreaded\n    int  hashfull(int maxAge = 0)\n      const;  // Approximate what fraction of entries (permille) have been written to during this root search\n\n    void\n    new_search();  // This must be called at the beginning of each root search to track entry aging\n    uint8_t generation() const;  // The current age, used when writing new data to the TT\n    std::tuple<bool, TTData, TTWriter>\n    probe(const Key key) const;  // The main method, whose retvals separate local vs global objects\n    TTEntry* first_entry(const Key key)\n      const;  // This is the hash function; its only external use is memory prefetching.\n\n   private:\n    friend struct TTEntry;\n\n    size_t   clusterCount;\n    Cluster* table = nullptr;\n\n    uint8_t generation8 = 0;  // Size must be not bigger than TTEntry::genBound8\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef TT_H_INCLUDED\n"
  },
  {
    "path": "src/tune.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"tune.h\"\n\n#include <algorithm>\n#include <iostream>\n#include <map>\n#include <optional>\n#include <sstream>\n#include <string>\n\n#include \"ucioption.h\"\n\nusing std::string;\n\nnamespace Stockfish {\n\nbool          Tune::update_on_last;\nconst Option* LastOption = nullptr;\nOptionsMap*   Tune::options;\nnamespace {\nstd::map<std::string, int> TuneResults;\n\nstd::optional<std::string> on_tune(const Option& o) {\n\n    if (!Tune::update_on_last || LastOption == &o)\n        Tune::read_options();\n\n    return std::nullopt;\n}\n}\n\nvoid Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& r) {\n\n    // Do not generate option when there is nothing to tune (ie. min = max)\n    if (r(v).first == r(v).second)\n        return;\n\n    if (TuneResults.count(n))\n        v = TuneResults[n];\n\n    opts->add(n, Option(v, r(v).first, r(v).second, on_tune));\n    LastOption = &((*opts)[n]);\n\n    // Print formatted parameters, ready to be copy-pasted in Fishtest\n    std::cout << n << \",\"                                  //\n              << v << \",\"                                  //\n              << r(v).first << \",\"                         //\n              << r(v).second << \",\"                        //\n              << (r(v).second - r(v).first) / 20.0 << \",\"  //\n              << \"0.0020\" << std::endl;\n}\n\nstring Tune::next(string& names, bool pop) {\n\n    string name;\n\n    do\n    {\n        string token = names.substr(0, names.find(','));\n\n        if (pop)\n            names.erase(0, token.size() + 1);\n\n        std::stringstream ws(token);\n        name += (ws >> token, token);  // Remove trailing whitespace\n\n    } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')'));\n\n    return name;\n}\n\n\ntemplate<>\nvoid Tune::Entry<int>::init_option() {\n    make_option(options, name, value, range);\n}\n\ntemplate<>\nvoid Tune::Entry<int>::read_option() {\n    if (options->count(name))\n        value = int((*options)[name]);\n}\n\n// Instead of a variable here we have a PostUpdate function: just call it\ntemplate<>\nvoid Tune::Entry<Tune::PostUpdate>::init_option() {}\ntemplate<>\nvoid Tune::Entry<Tune::PostUpdate>::read_option() {\n    value();\n}\n\n}  // namespace Stockfish\n\n\n// Init options with tuning session results instead of default values. Useful to\n// get correct bench signature after a tuning session or to test tuned values.\n// Just copy fishtest tuning results in a result.txt file and extract the\n// values with:\n//\n// cat results.txt | sed 's/^param: \\([^,]*\\), best: \\([^,]*\\).*/  TuneResults[\"\\1\"] = int(round(\\2));/'\n//\n// Then paste the output below, as the function body\n\n\nnamespace Stockfish {\n\nvoid Tune::read_results() { /* ...insert your values here... */ }\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/tune.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef TUNE_H_INCLUDED\n#define TUNE_H_INCLUDED\n\n#include <cstddef>\n#include <memory>\n#include <string>\n#include <type_traits>  // IWYU pragma: keep\n#include <utility>\n#include <vector>\n\nnamespace Stockfish {\n\nclass OptionsMap;\n\nusing Range    = std::pair<int, int>;  // Option's min-max values\nusing RangeFun = Range(int);\n\n// Default Range function, to calculate Option's min-max values\ninline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); }\n\nstruct SetRange {\n    explicit SetRange(RangeFun f) :\n        fun(f) {}\n    SetRange(int min, int max) :\n        fun(nullptr),\n        range(min, max) {}\n    Range operator()(int v) const { return fun ? fun(v) : range; }\n\n    RangeFun* fun;\n    Range     range;\n};\n\n#define SetDefaultRange SetRange(default_range)\n\n\n// Tune class implements the 'magic' code that makes the setup of a fishtest tuning\n// session as easy as it can be. Mainly you have just to remove const qualifiers\n// from the variables you want to tune and flag them for tuning, so if you have:\n//\n//   const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };\n//\n// If you have a my_post_update() function to run after values have been updated,\n// and a my_range() function to set custom Option's min-max values, then you just\n// remove the 'const' qualifiers and write somewhere below in the file:\n//\n//   TUNE(SetRange(my_range), myValue, my_post_update);\n//\n// You can also set the range directly, and restore the default at the end\n//\n//   TUNE(SetRange(-100, 100), myValue, SetDefaultRange);\n//\n// In case update function is slow and you have many parameters, you can add:\n//\n//   UPDATE_ON_LAST();\n//\n// And the values update, including post update function call, will be done only\n// once, after the engine receives the last UCI option, that is the one defined\n// and created as the last one, so the GUI should send the options in the same\n// order in which have been defined.\n\nclass Tune {\n\n    using PostUpdate = void();  // Post-update function\n\n    Tune() { read_results(); }\n    Tune(const Tune&)           = delete;\n    void operator=(const Tune&) = delete;\n    void read_results();\n\n    static Tune& instance() {\n        static Tune t;\n        return t;\n    }  // Singleton\n\n    // Use polymorphism to accommodate Entry of different types in the same vector\n    struct EntryBase {\n        virtual ~EntryBase()       = default;\n        virtual void init_option() = 0;\n        virtual void read_option() = 0;\n    };\n\n    template<typename T>\n    struct Entry: public EntryBase {\n\n        static_assert(!std::is_const_v<T>, \"Parameter cannot be const!\");\n\n        static_assert(std::is_same_v<T, int> || std::is_same_v<T, PostUpdate>,\n                      \"Parameter type not supported!\");\n\n        Entry(const std::string& n, T& v, const SetRange& r) :\n            name(n),\n            value(v),\n            range(r) {}\n        void operator=(const Entry&) = delete;  // Because 'value' is a reference\n        void init_option() override;\n        void read_option() override;\n\n        std::string name;\n        T&          value;\n        SetRange    range;\n    };\n\n    // Our facility to fill the container, each Entry corresponds to a parameter\n    // to tune. We use variadic templates to deal with an unspecified number of\n    // entries, each one of a possible different type.\n    static std::string next(std::string& names, bool pop = true);\n\n    int add(const SetRange&, std::string&&) { return 0; }\n\n    template<typename T, typename... Args>\n    int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {\n        list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));\n        return add(range, std::move(names), args...);\n    }\n\n    // Template specialization for arrays: recursively handle multi-dimensional arrays\n    template<typename T, size_t N, typename... Args>\n    int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {\n        for (size_t i = 0; i < N; i++)\n            add(range, next(names, i == N - 1) + \"[\" + std::to_string(i) + \"]\", value[i]);\n        return add(range, std::move(names), args...);\n    }\n\n    // Template specialization for SetRange\n    template<typename... Args>\n    int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {\n        return add(value, (next(names), std::move(names)), args...);\n    }\n\n    static void make_option(OptionsMap* options, const std::string& n, int v, const SetRange& r);\n\n    std::vector<std::unique_ptr<EntryBase>> list;\n\n   public:\n    template<typename... Args>\n    static int add(const std::string& names, Args&&... args) {\n        return instance().add(SetDefaultRange, names.substr(1, names.size() - 2),\n                              args...);  // Remove trailing parenthesis\n    }\n    static void init(OptionsMap& o) {\n        options = &o;\n        for (auto& e : instance().list)\n            e->init_option();\n        read_options();\n    }  // Deferred, due to UCIEngine::Options access\n    static void read_options() {\n        for (auto& e : instance().list)\n            e->read_option();\n    }\n\n    static bool        update_on_last;\n    static OptionsMap* options;\n};\n\ntemplate<typename... Args>\nconstexpr void tune_check_args(Args&&...) {\n    static_assert((!std::is_fundamental_v<Args> && ...), \"TUNE macro arguments wrong\");\n}\n\n// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()\n#define STRINGIFY(x) #x\n#define UNIQUE2(x, y) x##y\n#define UNIQUE(x, y) UNIQUE2(x, y)  // Two indirection levels to expand __LINE__\n#define TUNE(...) \\\n    int UNIQUE(p, __LINE__) = []() -> int { \\\n        tune_check_args(__VA_ARGS__); \\\n        return Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \\\n    }();\n\n#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true\n\n}  // namespace Stockfish\n\n#endif  // #ifndef TUNE_H_INCLUDED\n"
  },
  {
    "path": "src/types.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef TYPES_H_INCLUDED\n    #define TYPES_H_INCLUDED\n\n// When compiling with provided Makefile (e.g. for Linux and OSX), configuration\n// is done automatically. To get started type 'make help'.\n//\n// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches\n// need to be set manually:\n//\n// -DNDEBUG      | Disable debugging mode. Always use this for release.\n//\n// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to\n//               | run on some very old machines.\n//\n// -DUSE_POPCNT  | Add runtime support for use of popcnt asm-instruction. Works\n//               | only in 64-bit mode and requires hardware with popcnt support.\n//\n// -DUSE_PEXT    | Add runtime support for use of pext asm-instruction. Works\n//               | only in 64-bit mode and requires hardware with pext support.\n\n    #include <cassert>\n    #include <cstddef>\n    #include <cstdint>\n    #include <type_traits>\n    #include \"misc.h\"\n\n    #if defined(_MSC_VER)\n        // Disable some silly and noisy warnings from MSVC compiler\n        #pragma warning(disable: 4127)  // Conditional expression is constant\n        #pragma warning(disable: 4146)  // Unary minus operator applied to unsigned type\n        #pragma warning(disable: 4800)  // Forcing value to bool 'true' or 'false'\n    #endif\n\n// Predefined macros hell:\n//\n// __GNUC__                Compiler is GCC, Clang or ICX\n// __clang__               Compiler is Clang or ICX\n// __INTEL_LLVM_COMPILER   Compiler is ICX\n// _MSC_VER                Compiler is MSVC\n// _WIN32                  Building on Windows (any)\n// _WIN64                  Building on Windows 64 bit\n\n// Enforce minimum GCC version\n    #if defined(__GNUC__) && !defined(__clang__) \\\n      && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ < 3))\n        #error \"Stockfish requires GCC 9.3 or later for correct compilation\"\n    #endif\n\n    // Enforce minimum Clang version\n    #if defined(__clang__) && (__clang_major__ < 10)\n        #error \"Stockfish requires Clang 10.0 or later for correct compilation\"\n    #endif\n\n    #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)\n\n    #if defined(_WIN64) && defined(_MSC_VER)  // No Makefile used\n        #include <intrin.h>                   // Microsoft header for _BitScanForward64()\n        #define IS_64BIT\n    #endif\n\n    #if defined(USE_POPCNT) && defined(_MSC_VER)\n        #include <nmmintrin.h>  // Microsoft header for _mm_popcnt_u64()\n    #endif\n\n    #if !defined(NO_PREFETCH) && defined(_MSC_VER)\n        #include <xmmintrin.h>  // Microsoft header for _mm_prefetch()\n    #endif\n\n    #if defined(USE_PEXT)\n        #include <immintrin.h>  // Header for _pext_u64() intrinsic\n        #define pext(b, m) _pext_u64(b, m)\n    #else\n        #define pext(b, m) 0\n    #endif\n\nnamespace Stockfish {\n\n    #ifdef USE_POPCNT\nconstexpr bool HasPopCnt = true;\n    #else\nconstexpr bool HasPopCnt = false;\n    #endif\n\n    #ifdef USE_PEXT\nconstexpr bool HasPext = true;\n    #else\nconstexpr bool HasPext = false;\n    #endif\n\n    #ifdef IS_64BIT\nconstexpr bool Is64Bit = true;\n    #else\nconstexpr bool Is64Bit = false;\n    #endif\n\nusing Key      = uint64_t;\nusing Bitboard = uint64_t;\n\nconstexpr int MAX_MOVES = 256;\nconstexpr int MAX_PLY   = 246;\n\nenum Color : uint8_t {\n    WHITE,\n    BLACK,\n    COLOR_NB = 2\n};\n\nenum CastlingRights : uint8_t {\n    NO_CASTLING,\n    WHITE_OO,\n    WHITE_OOO = WHITE_OO << 1,\n    BLACK_OO  = WHITE_OO << 2,\n    BLACK_OOO = WHITE_OO << 3,\n\n    KING_SIDE      = WHITE_OO | BLACK_OO,\n    QUEEN_SIDE     = WHITE_OOO | BLACK_OOO,\n    WHITE_CASTLING = WHITE_OO | WHITE_OOO,\n    BLACK_CASTLING = BLACK_OO | BLACK_OOO,\n    ANY_CASTLING   = WHITE_CASTLING | BLACK_CASTLING,\n\n    CASTLING_RIGHT_NB = 16\n};\n\nenum Bound : uint8_t {\n    BOUND_NONE,\n    BOUND_UPPER,\n    BOUND_LOWER,\n    BOUND_EXACT = BOUND_UPPER | BOUND_LOWER\n};\n\n// Value is used as an alias for int, this is done to differentiate between a search\n// value and any other integer value. The values used in search are always supposed\n// to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range.\nusing Value = int;\n\nconstexpr Value VALUE_ZERO     = 0;\nconstexpr Value VALUE_DRAW     = 0;\nconstexpr Value VALUE_NONE     = 32002;\nconstexpr Value VALUE_INFINITE = 32001;\n\nconstexpr Value VALUE_MATE             = 32000;\nconstexpr Value VALUE_MATE_IN_MAX_PLY  = VALUE_MATE - MAX_PLY;\nconstexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY;\n\nconstexpr Value VALUE_TB                 = VALUE_MATE_IN_MAX_PLY - 1;\nconstexpr Value VALUE_TB_WIN_IN_MAX_PLY  = VALUE_TB - MAX_PLY;\nconstexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY;\n\n\nconstexpr bool is_valid(Value value) { return value != VALUE_NONE; }\n\nconstexpr bool is_win(Value value) {\n    assert(is_valid(value));\n    return value >= VALUE_TB_WIN_IN_MAX_PLY;\n}\n\nconstexpr bool is_loss(Value value) {\n    assert(is_valid(value));\n    return value <= VALUE_TB_LOSS_IN_MAX_PLY;\n}\n\nconstexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); }\n\n// In the code, we make the assumption that these values\n// are such that non_pawn_material() can be used to uniquely\n// identify the material on the board.\nconstexpr Value PawnValue   = 208;\nconstexpr Value KnightValue = 781;\nconstexpr Value BishopValue = 825;\nconstexpr Value RookValue   = 1276;\nconstexpr Value QueenValue  = 2538;\n\n\n// clang-format off\nenum PieceType : std::uint8_t {\n    NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,\n    ALL_PIECES = 0,\n    PIECE_TYPE_NB = 8\n};\n\nenum Piece : std::uint8_t {\n    NO_PIECE,\n    W_PAWN = PAWN,     W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,\n    B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,\n    PIECE_NB = 16\n};\n// clang-format on\n\nconstexpr Value PieceValue[PIECE_NB] = {\n  VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO,\n  VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO};\n\nusing Depth = int;\n\n// The following DEPTH_ constants are used for transposition table entries\n// and quiescence search move generation stages. In regular search, the\n// depth stored in the transposition table is literal: the search depth\n// (effort) used to make the corresponding transposition table value. In\n// quiescence search, however, the transposition table entries only store\n// the current quiescence move generation stage (which should thus compare\n// lower than any regular search depth).\nconstexpr Depth DEPTH_QS = 0;\n// For transposition table entries where no searching at all was done\n// (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus\n// compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET\n// is used only for the transposition table entry occupancy check (see tt.cpp),\n// and should thus be lower than DEPTH_UNSEARCHED.\nconstexpr Depth DEPTH_UNSEARCHED   = -2;\nconstexpr Depth DEPTH_ENTRY_OFFSET = -3;\n\n// clang-format off\nenum Square : uint8_t {\n    SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,\n    SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,\n    SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3,\n    SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4,\n    SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5,\n    SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6,\n    SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7,\n    SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,\n    SQ_NONE,\n\n    SQUARE_ZERO = 0,\n    SQUARE_NB   = 64\n};\n// clang-format on\n\nenum Direction : int8_t {\n    NORTH = 8,\n    EAST  = 1,\n    SOUTH = -NORTH,\n    WEST  = -EAST,\n\n    NORTH_EAST = NORTH + EAST,\n    SOUTH_EAST = SOUTH + EAST,\n    SOUTH_WEST = SOUTH + WEST,\n    NORTH_WEST = NORTH + WEST\n};\n\nenum File : uint8_t {\n    FILE_A,\n    FILE_B,\n    FILE_C,\n    FILE_D,\n    FILE_E,\n    FILE_F,\n    FILE_G,\n    FILE_H,\n    FILE_NB\n};\n\nenum Rank : uint8_t {\n    RANK_1,\n    RANK_2,\n    RANK_3,\n    RANK_4,\n    RANK_5,\n    RANK_6,\n    RANK_7,\n    RANK_8,\n    RANK_NB\n};\n\n// Keep track of what a move changes on the board (used by NNUE)\nstruct DirtyPiece {\n    Piece  pc;        // this is never allowed to be NO_PIECE\n    Square from, to;  // to should be SQ_NONE for promotions\n\n    // if {add,remove}_sq is SQ_NONE, {add,remove}_pc is allowed to be\n    // uninitialized\n    // castling uses add_sq and remove_sq to remove and add the rook\n    Square remove_sq, add_sq;\n    Piece  remove_pc, add_pc;\n};\n\n// Keep track of what threats change on the board (used by NNUE)\nstruct DirtyThreat {\n    static constexpr int PcSqOffset         = 0;\n    static constexpr int ThreatenedSqOffset = 8;\n    static constexpr int ThreatenedPcOffset = 16;\n    static constexpr int PcOffset           = 20;\n\n    DirtyThreat() { /* don't initialize data */ }\n    DirtyThreat(uint32_t raw) :\n        data(raw) {}\n    DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) {\n        data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset)\n             | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset);\n    }\n\n    Piece  pc() const { return static_cast<Piece>(data >> PcOffset & 0xf); }\n    Piece  threatened_pc() const { return static_cast<Piece>(data >> ThreatenedPcOffset & 0xf); }\n    Square threatened_sq() const { return static_cast<Square>(data >> ThreatenedSqOffset & 0xff); }\n    Square pc_sq() const { return static_cast<Square>(data >> PcSqOffset & 0xff); }\n    bool   add() const { return data >> 31; }\n    uint32_t raw() const { return data; }\n\n   private:\n    uint32_t data;\n};\n\n// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks.\n// Moving a piece also can reveal at most 8 discovered attacks.\n// This implies that a non-castling move can change at most (8 + 16) * 3 + 8 = 80 features.\n// By similar logic, a castling move can change at most (5 + 1 + 3 + 9) * 2 = 36 features.\n// Thus, 80 should work as an upper bound. Finally, 16 entries are added to accommodate\n// unmasked vector stores near the end of the list.\n\nusing DirtyThreatList = ValueList<DirtyThreat, 96>;\n\nstruct DirtyThreats {\n    DirtyThreatList list;\n    Color           us;\n    Square          prevKsq, ksq;\n\n    Bitboard threatenedSqs, threateningSqs;\n};\n\n    #define ENABLE_INCR_OPERATORS_ON(T) \\\n        constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \\\n        constexpr T& operator--(T& d) { return d = T(int(d) - 1); }\n\nENABLE_INCR_OPERATORS_ON(PieceType)\nENABLE_INCR_OPERATORS_ON(Square)\nENABLE_INCR_OPERATORS_ON(File)\nENABLE_INCR_OPERATORS_ON(Rank)\n\n    #undef ENABLE_INCR_OPERATORS_ON\n\nconstexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); }\nconstexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); }\n\n// Additional operators to add a Direction to a Square\nconstexpr Square  operator+(Square s, Direction d) { return Square(int(s) + int(d)); }\nconstexpr Square  operator-(Square s, Direction d) { return Square(int(s) - int(d)); }\nconstexpr Square& operator+=(Square& s, Direction d) { return s = s + d; }\nconstexpr Square& operator-=(Square& s, Direction d) { return s = s - d; }\n\n// Toggle color\nconstexpr Color operator~(Color c) { return Color(c ^ BLACK); }\n\n// Swap A1 <-> A8\nconstexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); }\n\n// Swap A1 <-> H1\nconstexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); }\n\n// Swap color of piece B_KNIGHT <-> W_KNIGHT\nconstexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); }\n\nconstexpr CastlingRights operator&(Color c, CastlingRights cr) {\n    return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);\n}\n\nconstexpr Value mate_in(int ply) { return VALUE_MATE - ply; }\n\nconstexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }\n\nconstexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); }\n\nconstexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }\n\nconstexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); }\n\nconstexpr Color color_of(Piece pc) {\n    assert(pc != NO_PIECE);\n    return Color(pc >> 3);\n}\n\nconstexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; }\n\nconstexpr File file_of(Square s) { return File(s & 7); }\n\nconstexpr Rank rank_of(Square s) { return Rank(s >> 3); }\n\nconstexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); }\n\nconstexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); }\n\nconstexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); }\n\nconstexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }\n\n\n// Based on a congruential pseudo-random number generator\nconstexpr Key make_key(uint64_t seed) {\n    return seed * 6364136223846793005ULL + 1442695040888963407ULL;\n}\n\n\nenum MoveType : uint16_t {\n    NORMAL,\n    PROMOTION  = 1 << 14,\n    EN_PASSANT = 2 << 14,\n    CASTLING   = 3 << 14\n};\n\n// A move needs 16 bits to be stored\n//\n// bit  0- 5: destination square (from 0 to 63)\n// bit  6-11: origin square (from 0 to 63)\n// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2)\n// bit 14-15: special move flag: promotion (1), en passant (2), castling (3)\n// NOTE: en passant bit is set only when a pawn can be captured\n//\n// Special cases are Move::none() and Move::null(). We can sneak these in because\n// in any normal move the destination square and origin square are always different,\n// but Move::none() and Move::null() have the same origin and destination square.\n\nclass Move {\n   public:\n    Move() = default;\n    constexpr explicit Move(std::uint16_t d) :\n        data(d) {}\n\n    constexpr Move(Square from, Square to) :\n        data((from << 6) + to) {}\n\n    template<MoveType T>\n    static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {\n        return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);\n    }\n\n    constexpr Square from_sq() const {\n        assert(is_ok());\n        return Square((data >> 6) & 0x3F);\n    }\n\n    constexpr Square to_sq() const {\n        assert(is_ok());\n        return Square(data & 0x3F);\n    }\n\n    // Same as to_sq() but without assertion, for branchless code paths\n    // where the result is masked/ignored when move is not ok\n    constexpr Square to_sq_unchecked() const { return Square(data & 0x3F); }\n\n    constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); }\n\n    constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); }\n\n    constexpr bool is_ok() const { return none().data != data && null().data != data; }\n\n    static constexpr Move null() { return Move(65); }\n    static constexpr Move none() { return Move(0); }\n\n    constexpr bool operator==(const Move& m) const { return data == m.data; }\n    constexpr bool operator!=(const Move& m) const { return data != m.data; }\n\n    constexpr explicit operator bool() const { return data != 0; }\n\n    constexpr std::uint16_t raw() const { return data; }\n\n    struct MoveHash {\n        std::size_t operator()(const Move& m) const { return make_key(m.data); }\n    };\n\n    static constexpr int FromSqShift = 6;\n    static constexpr int ToSqShift   = 0;\n\n   protected:\n    std::uint16_t data;\n};\n\ntemplate<typename T, typename... Ts>\nstruct is_all_same {\n    static constexpr bool value = (std::is_same_v<T, Ts> && ...);\n};\n\ntemplate<typename... Ts>\nconstexpr auto is_all_same_v = is_all_same<Ts...>::value;\n\n}  // namespace Stockfish\n\n#endif  // #ifndef TYPES_H_INCLUDED\n\n#include \"tune.h\"  // Global visibility to tuning setup\n"
  },
  {
    "path": "src/uci.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"uci.h\"\n\n#include <algorithm>\n#include <cctype>\n#include <cmath>\n#include <cstdint>\n#include <cstdlib>\n#include <iterator>\n#include <optional>\n#include <sstream>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"benchmark.h\"\n#include \"engine.h\"\n#include \"memory.h\"\n#include \"movegen.h\"\n#include \"position.h\"\n#include \"score.h\"\n#include \"search.h\"\n#include \"types.h\"\n#include \"ucioption.h\"\n\nnamespace Stockfish {\n\nconstexpr auto BenchmarkCommand = \"speedtest\";\n\nconstexpr auto StartFEN = \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\";\ntemplate<typename... Ts>\nstruct overload: Ts... {\n    using Ts::operator()...;\n};\n\ntemplate<typename... Ts>\noverload(Ts...) -> overload<Ts...>;\n\nvoid UCIEngine::print_info_string(std::string_view str) {\n    sync_cout_start();\n    for (auto& line : split(str, \"\\n\"))\n    {\n        if (!is_whitespace(line))\n        {\n            std::cout << \"info string \" << line << '\\n';\n        }\n    }\n    sync_cout_end();\n}\n\nUCIEngine::UCIEngine(int argc, char** argv) :\n    engine(argv[0]),\n    cli(argc, argv) {\n\n    engine.get_options().add_info_listener([](const std::optional<std::string>& str) {\n        if (str.has_value())\n            print_info_string(*str);\n    });\n\n    init_search_update_listeners();\n}\n\nvoid UCIEngine::init_search_update_listeners() {\n    engine.set_on_iter([](const auto& i) { on_iter(i); });\n    engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); });\n    engine.set_on_update_full(\n      [this](const auto& i) { on_update_full(i, engine.get_options()[\"UCI_ShowWDL\"]); });\n    engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); });\n    engine.set_on_verify_networks([](const auto& s) { print_info_string(s); });\n}\n\nvoid UCIEngine::loop() {\n    std::string token, cmd;\n\n    for (int i = 1; i < cli.argc; ++i)\n        cmd += std::string(cli.argv[i]) + \" \";\n\n    do\n    {\n        if (cli.argc == 1\n            && !getline(std::cin, cmd))  // Wait for an input or an end-of-file (EOF) indication\n            cmd = \"quit\";\n\n        std::istringstream is(cmd);\n\n        token.clear();  // Avoid a stale if getline() returns nothing or a blank line\n        is >> std::skipws >> token;\n\n        if (token == \"quit\" || token == \"stop\")\n            engine.stop();\n\n        // The GUI sends 'ponderhit' to tell that the user has played the expected move.\n        // So, 'ponderhit' is sent if pondering was done on the same move that the user\n        // has played. The search should continue, but should also switch from pondering\n        // to the normal search.\n        else if (token == \"ponderhit\")\n            engine.set_ponderhit(false);\n\n        else if (token == \"uci\")\n        {\n            sync_cout << \"id name \" << engine_info(true) << \"\\n\"\n                      << engine.get_options() << sync_endl;\n\n            sync_cout << \"uciok\" << sync_endl;\n        }\n\n        else if (token == \"setoption\")\n            setoption(is);\n        else if (token == \"go\")\n        {\n            // send info strings after the go command is sent for old GUIs and python-chess\n            print_info_string(engine.numa_config_information_as_string());\n            print_info_string(engine.thread_allocation_information_as_string());\n            go(is);\n        }\n        else if (token == \"position\")\n            position(is);\n        else if (token == \"ucinewgame\")\n            engine.search_clear();\n        else if (token == \"isready\")\n            sync_cout << \"readyok\" << sync_endl;\n\n        // Add custom non-UCI commands, mainly for debugging purposes.\n        // These commands must not be used during a search!\n        else if (token == \"flip\")\n            engine.flip();\n        else if (token == \"bench\")\n            bench(is);\n        else if (token == BenchmarkCommand)\n            benchmark(is);\n        else if (token == \"d\")\n            sync_cout << engine.visualize() << sync_endl;\n        else if (token == \"eval\")\n            engine.trace_eval();\n        else if (token == \"compiler\")\n            sync_cout << compiler_info() << sync_endl;\n        else if (token == \"export_net\")\n        {\n            std::pair<std::optional<std::string>, std::string> files[2];\n\n            if (is >> std::skipws >> files[0].second)\n                files[0].first = files[0].second;\n\n            if (is >> std::skipws >> files[1].second)\n                files[1].first = files[1].second;\n\n            engine.save_network(files);\n        }\n        else if (token == \"--help\" || token == \"help\" || token == \"--license\" || token == \"license\")\n            sync_cout\n              << \"\\nStockfish is a powerful chess engine for playing and analyzing.\"\n                 \"\\nIt is released as free software licensed under the GNU GPLv3 License.\"\n                 \"\\nStockfish is normally used with a graphical user interface (GUI) and implements\"\n                 \"\\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc.\"\n                 \"\\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme\"\n                 \"\\nor read the corresponding README.md and Copying.txt files distributed along with this program.\\n\"\n              << sync_endl;\n        else if (!token.empty() && token[0] != '#')\n            sync_cout << \"Unknown command: '\" << cmd << \"'. Type help for more information.\"\n                      << sync_endl;\n\n    } while (token != \"quit\" && cli.argc == 1);  // The command-line arguments are one-shot\n}\n\nSearch::LimitsType UCIEngine::parse_limits(std::istream& is) {\n    Search::LimitsType limits;\n    std::string        token;\n\n    limits.startTime = now();  // The search starts as early as possible\n\n    while (is >> token)\n        if (token == \"searchmoves\")  // Needs to be the last command on the line\n            while (is >> token)\n                limits.searchmoves.push_back(to_lower(token));\n\n        else if (token == \"wtime\")\n            is >> limits.time[WHITE];\n        else if (token == \"btime\")\n            is >> limits.time[BLACK];\n        else if (token == \"winc\")\n            is >> limits.inc[WHITE];\n        else if (token == \"binc\")\n            is >> limits.inc[BLACK];\n        else if (token == \"movestogo\")\n            is >> limits.movestogo;\n        else if (token == \"depth\")\n            is >> limits.depth;\n        else if (token == \"nodes\")\n            is >> limits.nodes;\n        else if (token == \"movetime\")\n            is >> limits.movetime;\n        else if (token == \"mate\")\n            is >> limits.mate;\n        else if (token == \"perft\")\n            is >> limits.perft;\n        else if (token == \"infinite\")\n            limits.infinite = 1;\n        else if (token == \"ponder\")\n            limits.ponderMode = true;\n\n    return limits;\n}\n\nvoid UCIEngine::go(std::istringstream& is) {\n\n    Search::LimitsType limits = parse_limits(is);\n\n    if (limits.perft)\n        perft(limits);\n    else\n        engine.go(limits);\n}\n\nvoid UCIEngine::bench(std::istream& args) {\n    std::string token;\n    uint64_t    num, nodes = 0, cnt = 1;\n    uint64_t    nodesSearched = 0;\n    const auto& options       = engine.get_options();\n\n    engine.set_on_update_full([&](const auto& i) {\n        nodesSearched = i.nodes;\n        on_update_full(i, options[\"UCI_ShowWDL\"]);\n    });\n\n    std::vector<std::string> list = Benchmark::setup_bench(engine.fen(), args);\n\n    num = count_if(list.begin(), list.end(),\n                   [](const std::string& s) { return s.find(\"go \") == 0 || s.find(\"eval\") == 0; });\n\n    TimePoint elapsed = now();\n\n    for (const auto& cmd : list)\n    {\n        std::istringstream is(cmd);\n        is >> std::skipws >> token;\n\n        if (token == \"go\" || token == \"eval\")\n        {\n            std::cerr << \"\\nPosition: \" << cnt++ << '/' << num << \" (\" << engine.fen() << \")\"\n                      << std::endl;\n            if (token == \"go\")\n            {\n                Search::LimitsType limits = parse_limits(is);\n\n                if (limits.perft)\n                    nodesSearched = perft(limits);\n                else\n                {\n                    engine.go(limits);\n                    engine.wait_for_search_finished();\n                }\n\n                nodes += nodesSearched;\n                nodesSearched = 0;\n            }\n            else\n                engine.trace_eval();\n        }\n        else if (token == \"setoption\")\n            setoption(is);\n        else if (token == \"position\")\n            position(is);\n        else if (token == \"ucinewgame\")\n        {\n            engine.search_clear();  // search_clear may take a while\n            elapsed = now();\n        }\n    }\n\n    elapsed = now() - elapsed + 1;  // Ensure positivity to avoid a 'divide by zero'\n\n    dbg_print();\n\n    std::cerr << \"\\n===========================\"    //\n              << \"\\nTotal time (ms) : \" << elapsed  //\n              << \"\\nNodes searched  : \" << nodes    //\n              << \"\\nNodes/second    : \" << 1000 * nodes / elapsed << std::endl;\n\n    // reset callback, to not capture a dangling reference to nodesSearched\n    engine.set_on_update_full([&](const auto& i) { on_update_full(i, options[\"UCI_ShowWDL\"]); });\n}\n\nvoid UCIEngine::benchmark(std::istream& args) {\n    // Probably not very important for a test this long, but include for completeness and sanity.\n    static constexpr int NUM_WARMUP_POSITIONS = 3;\n\n    std::string token;\n    uint64_t    nodes = 0, cnt = 1;\n    uint64_t    nodesSearched = 0;\n\n    engine.set_on_update_full([&](const Engine::InfoFull& i) { nodesSearched = i.nodes; });\n\n    engine.set_on_iter([](const auto&) {});\n    engine.set_on_update_no_moves([](const auto&) {});\n    engine.set_on_bestmove([](const auto&, const auto&) {});\n    engine.set_on_verify_networks([](const auto&) {});\n\n    Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args);\n\n    const auto numGoCommands = count_if(setup.commands.begin(), setup.commands.end(),\n                                        [](const std::string& s) { return s.find(\"go \") == 0; });\n\n    TimePoint totalTime = 0;\n\n    // Set options once at the start.\n    auto ss = std::istringstream(\"name Threads value \" + std::to_string(setup.threads));\n    setoption(ss);\n    ss = std::istringstream(\"name Hash value \" + std::to_string(setup.ttSize));\n    setoption(ss);\n    ss = std::istringstream(\"name UCI_Chess960 value false\");\n    setoption(ss);\n\n    // Warmup\n    for (const auto& cmd : setup.commands)\n    {\n        std::istringstream is(cmd);\n        is >> std::skipws >> token;\n\n        if (token == \"go\")\n        {\n            // One new line is produced by the search, so omit it here\n            std::cerr << \"\\rWarmup position \" << cnt++ << '/' << NUM_WARMUP_POSITIONS;\n\n            Search::LimitsType limits = parse_limits(is);\n\n            // Run with silenced network verification\n            engine.go(limits);\n            engine.wait_for_search_finished();\n        }\n        else if (token == \"position\")\n            position(is);\n        else if (token == \"ucinewgame\")\n        {\n            engine.search_clear();  // search_clear may take a while\n        }\n\n        if (cnt > NUM_WARMUP_POSITIONS)\n            break;\n    }\n\n    std::cerr << \"\\n\";\n\n    cnt   = 1;\n    nodes = 0;\n\n    int           numHashfullReadings = 0;\n    constexpr int hashfullAges[]      = {0, 999};  // Only normal hashfull and touched hash.\n    constexpr int hashfullAgeCount    = std::size(hashfullAges);\n    int           totalHashfull[hashfullAgeCount] = {0};\n    int           maxHashfull[hashfullAgeCount]   = {0};\n\n    auto updateHashfullReadings = [&]() {\n        numHashfullReadings += 1;\n\n        for (int i = 0; i < hashfullAgeCount; ++i)\n        {\n            const int hashfull = engine.get_hashfull(hashfullAges[i]);\n            maxHashfull[i]     = std::max(maxHashfull[i], hashfull);\n            totalHashfull[i] += hashfull;\n        }\n    };\n\n    engine.search_clear();  // search_clear may take a while\n\n    for (const auto& cmd : setup.commands)\n    {\n        std::istringstream is(cmd);\n        is >> std::skipws >> token;\n\n        if (token == \"go\")\n        {\n            // One new line is produced by the search, so omit it here\n            std::cerr << \"\\rPosition \" << cnt++ << '/' << numGoCommands;\n\n            Search::LimitsType limits = parse_limits(is);\n\n            nodesSearched     = 0;\n            TimePoint elapsed = now();\n\n            // Run with silenced network verification\n            engine.go(limits);\n            engine.wait_for_search_finished();\n\n            totalTime += now() - elapsed;\n\n            updateHashfullReadings();\n\n            nodes += nodesSearched;\n        }\n        else if (token == \"position\")\n            position(is);\n        else if (token == \"ucinewgame\")\n        {\n            engine.search_clear();  // search_clear may take a while\n        }\n    }\n\n    totalTime = std::max<TimePoint>(totalTime, 1);  // Ensure positivity to avoid a 'divide by zero'\n\n    dbg_print();\n\n    std::cerr << \"\\n\";\n\n    static_assert(\n      std::size(hashfullAges) == 2 && hashfullAges[0] == 0 && hashfullAges[1] == 999,\n      \"Hardcoded for display. Would complicate the code needlessly in the current state.\");\n\n    std::string threadBinding = engine.thread_binding_information_as_string();\n    if (threadBinding.empty())\n        threadBinding = \"none\";\n\n    // clang-format off\n\n    std::cerr << \"===========================\"\n              << \"\\nVersion                    : \"\n              << engine_version_info()\n              // \"\\nCompiled by                : \"\n              << compiler_info()\n              << \"Large pages                : \" << (has_large_pages() ? \"yes\" : \"no\")\n              << \"\\nUser invocation            : \" << BenchmarkCommand << \" \"\n              << setup.originalInvocation << \"\\nFilled invocation          : \" << BenchmarkCommand\n              << \" \" << setup.filledInvocation\n              << \"\\nAvailable processors       : \" << engine.get_numa_config_as_string()\n              << \"\\nThread count               : \" << setup.threads\n              << \"\\nThread binding             : \" << threadBinding\n              << \"\\nTT size [MiB]              : \" << setup.ttSize\n              << \"\\nHash max, avg [per mille]  : \"\n              << \"\\n    single search          : \" << maxHashfull[0] << \", \"\n              << totalHashfull[0] / numHashfullReadings\n              << \"\\n    single game            : \" << maxHashfull[1] << \", \"\n              << totalHashfull[1] / numHashfullReadings\n              << \"\\nTotal nodes searched       : \" << nodes\n              << \"\\nTotal search time [s]      : \" << totalTime / 1000.0\n              << \"\\nNodes/second               : \" << 1000 * nodes / totalTime << std::endl;\n\n    // clang-format on\n\n    init_search_update_listeners();\n}\n\nvoid UCIEngine::setoption(std::istringstream& is) {\n    engine.wait_for_search_finished();\n    engine.get_options().setoption(is);\n}\n\nstd::uint64_t UCIEngine::perft(const Search::LimitsType& limits) {\n    auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()[\"UCI_Chess960\"]);\n    sync_cout << \"\\nNodes searched: \" << nodes << \"\\n\" << sync_endl;\n    return nodes;\n}\n\nvoid UCIEngine::position(std::istringstream& is) {\n    const std::string fullCommand = is.str();\n\n    std::string token, fen;\n\n    is >> token;\n\n    if (token == \"startpos\")\n    {\n        fen = StartFEN;\n        is >> token;  // Consume the \"moves\" token, if any\n    }\n    else if (token == \"fen\")\n        while (is >> token && token != \"moves\")\n            fen += token + \" \";\n    else\n        return;\n\n    std::vector<std::string> moves;\n\n    while (is >> token)\n    {\n        moves.push_back(token);\n    }\n\n    auto err = engine.set_position(fen, moves);\n    if (err.has_value())\n    {\n        terminate_on_critical_error(fullCommand, err->what());\n    }\n}\n\nnamespace {\n\nstruct WinRateParams {\n    double a;\n    double b;\n};\n\nWinRateParams win_rate_params(const Position& pos) {\n\n    int material = pos.count<PAWN>() + 3 * pos.count<KNIGHT>() + 3 * pos.count<BISHOP>()\n                 + 5 * pos.count<ROOK>() + 9 * pos.count<QUEEN>();\n\n    // The fitted model only uses data for material counts in [17, 78], and is anchored at count 58.\n    double m = std::clamp(material, 17, 78) / 58.0;\n\n    // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model\n    constexpr double as[] = {-72.32565836, 185.93832038, -144.58862193, 416.44950446};\n    constexpr double bs[] = {83.86794042, -136.06112997, 69.98820887, 47.62901433};\n\n    double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];\n    double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];\n\n    return {a, b};\n}\n\n// The win rate model is 1 / (1 + exp((a - eval) / b)), where a = p_a(material) and b = p_b(material).\n// It fits the LTC fishtest statistics rather accurately.\nint win_rate_model(Value v, const Position& pos) {\n\n    auto [a, b] = win_rate_params(pos);\n\n    // Return the win rate in per mille units, rounded to the nearest integer.\n    return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b)));\n}\n}\n\nstd::string UCIEngine::format_score(const Score& s) {\n    constexpr int TB_CP = 20000;\n    const auto    format =\n      overload{[](Score::Mate mate) -> std::string {\n                   auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2;\n                   return std::string(\"mate \") + std::to_string(m);\n               },\n               [](Score::Tablebase tb) -> std::string {\n                   return std::string(\"cp \")\n                        + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies));\n               },\n               [](Score::InternalUnits units) -> std::string {\n                   return std::string(\"cp \") + std::to_string(units.value);\n               }};\n\n    return s.visit(format);\n}\n\n// Turns a Value to an integer centipawn number,\n// without treatment of mate and similar special scores.\nint UCIEngine::to_cp(Value v, const Position& pos) {\n\n    // In general, the score can be defined via the WDL as\n    // (log(1/L - 1) - log(1/W - 1)) / (log(1/L - 1) + log(1/W - 1)).\n    // Based on our win_rate_model, this simply yields v / a.\n\n    auto [a, b] = win_rate_params(pos);\n\n    return int(std::round(100 * int(v) / a));\n}\n\nstd::string UCIEngine::wdl(Value v, const Position& pos) {\n    std::stringstream ss;\n\n    int wdl_w = win_rate_model(v, pos);\n    int wdl_l = win_rate_model(-v, pos);\n    int wdl_d = 1000 - wdl_w - wdl_l;\n    ss << wdl_w << \" \" << wdl_d << \" \" << wdl_l;\n\n    return ss.str();\n}\n\nstd::string UCIEngine::square(Square s) {\n    return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};\n}\n\nstd::string UCIEngine::move(Move m, bool chess960) {\n    if (m == Move::none())\n        return \"(none)\";\n\n    if (m == Move::null())\n        return \"0000\";\n\n    Square from = m.from_sq();\n    Square to   = m.to_sq();\n\n    if (m.type_of() == CASTLING && !chess960)\n        to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));\n\n    std::string move = square(from) + square(to);\n\n    if (m.type_of() == PROMOTION)\n        move += \" pnbrqk\"[m.promotion_type()];\n\n    return move;\n}\n\n\nstd::string UCIEngine::to_lower(std::string str) {\n    std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); });\n\n    return str;\n}\n\nMove UCIEngine::to_move(const Position& pos, std::string str) {\n    str = to_lower(str);\n\n    for (const auto& m : MoveList<LEGAL>(pos))\n        if (str == move(m, pos.is_chess960()))\n            return m;\n\n    return Move::none();\n}\n\nvoid UCIEngine::on_update_no_moves(const Engine::InfoShort& info) {\n    sync_cout << \"info depth \" << info.depth << \" score \" << format_score(info.score) << sync_endl;\n}\n\nvoid UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) {\n    std::stringstream ss;\n\n    ss << \"info\";\n    ss << \" depth \" << info.depth                 //\n       << \" seldepth \" << info.selDepth           //\n       << \" multipv \" << info.multiPV             //\n       << \" score \" << format_score(info.score);  //\n\n    if (!info.bound.empty())\n        ss << \" \" << info.bound;\n\n    if (showWDL)\n        ss << \" wdl \" << info.wdl;\n\n    ss << \" nodes \" << info.nodes        //\n       << \" nps \" << info.nps            //\n       << \" hashfull \" << info.hashfull  //\n       << \" tbhits \" << info.tbHits      //\n       << \" time \" << info.timeMs        //\n       << \" pv \" << info.pv;             //\n\n    sync_cout << ss.str() << sync_endl;\n}\n\nvoid UCIEngine::on_iter(const Engine::InfoIter& info) {\n    std::stringstream ss;\n\n    ss << \"info\";\n    ss << \" depth \" << info.depth                     //\n       << \" currmove \" << info.currmove               //\n       << \" currmovenumber \" << info.currmovenumber;  //\n\n    sync_cout << ss.str() << sync_endl;\n}\n\nvoid UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder) {\n    sync_cout << \"bestmove \" << bestmove;\n    if (!ponder.empty())\n        std::cout << \" ponder \" << ponder;\n    std::cout << sync_endl;\n}\n\nvoid UCIEngine::terminate_on_critical_error(const std::string& fullCommand,\n                                            const std::string& message) {\n    sync_cout << \"info string CRITICAL ERROR: Command `\" << fullCommand\n              << \"` failed. Reason: \" << message << '\\n'\n              << sync_endl;\n    std::exit(1);\n}\n\n}  // namespace Stockfish\n"
  },
  {
    "path": "src/uci.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef UCI_H_INCLUDED\n#define UCI_H_INCLUDED\n\n#include <cstdint>\n#include <iostream>\n#include <string>\n#include <string_view>\n\n#include \"engine.h\"\n#include \"misc.h\"\n#include \"search.h\"\n\nnamespace Stockfish {\n\nclass Position;\nclass Move;\nclass Score;\nenum Square : uint8_t;\nusing Value = int;\n\nclass UCIEngine {\n   public:\n    UCIEngine(int argc, char** argv);\n\n    void loop();\n\n    static int         to_cp(Value v, const Position& pos);\n    static std::string format_score(const Score& s);\n    static std::string square(Square s);\n    static std::string move(Move m, bool chess960);\n    static std::string wdl(Value v, const Position& pos);\n    static std::string to_lower(std::string str);\n    static Move        to_move(const Position& pos, std::string str);\n\n    static Search::LimitsType parse_limits(std::istream& is);\n\n    auto& engine_options() { return engine.get_options(); }\n\n   private:\n    Engine      engine;\n    CommandLine cli;\n\n    static void print_info_string(std::string_view str);\n\n    void          go(std::istringstream& is);\n    void          bench(std::istream& args);\n    void          benchmark(std::istream& args);\n    void          position(std::istringstream& is);\n    void          setoption(std::istringstream& is);\n    std::uint64_t perft(const Search::LimitsType&);\n\n    static void on_update_no_moves(const Engine::InfoShort& info);\n    static void on_update_full(const Engine::InfoFull& info, bool showWDL);\n    static void on_iter(const Engine::InfoIter& info);\n    static void on_bestmove(std::string_view bestmove, std::string_view ponder);\n\n    void init_search_update_listeners();\n\n    [[noreturn]] void terminate_on_critical_error(const std::string& fullCommand,\n                                                  const std::string& message);\n};\n\n}  // namespace Stockfish\n\n#endif  // #ifndef UCI_H_INCLUDED\n"
  },
  {
    "path": "src/ucioption.cpp",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"ucioption.h\"\n\n#include <algorithm>\n#include <cassert>\n#include <cctype>\n#include <cstdlib>\n#include <iostream>\n#include <sstream>\n#include <utility>\n\n#include \"misc.h\"\n\nnamespace Stockfish {\n\nbool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const {\n\n    return std::lexicographical_compare(\n      s1.begin(), s1.end(), s2.begin(), s2.end(),\n      [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });\n}\n\nvoid OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); }\n\nvoid OptionsMap::setoption(std::istringstream& is) {\n    std::string token, name, value;\n\n    is >> token;  // Consume the \"name\" token\n\n    // Read the option name (can contain spaces)\n    while (is >> token && token != \"value\")\n        name += (name.empty() ? \"\" : \" \") + token;\n\n    // Read the option value (can contain spaces)\n    while (is >> token)\n        value += (value.empty() ? \"\" : \" \") + token;\n\n    if (options_map.count(name))\n        options_map[name] = value;\n    else\n        sync_cout << \"No such option: \" << name << sync_endl;\n}\n\nconst Option& OptionsMap::operator[](const std::string& name) const {\n    auto it = options_map.find(name);\n    assert(it != options_map.end());\n    return it->second;\n}\n\n// Inits options and assigns idx in the correct printing order\nvoid OptionsMap::add(const std::string& name, const Option& option) {\n    if (!options_map.count(name))\n    {\n        static size_t insert_order = 0;\n\n        options_map[name] = option;\n\n        options_map[name].parent = this;\n        options_map[name].idx    = insert_order++;\n    }\n    else\n    {\n        std::cerr << \"Option \\\"\" << name << \"\\\" was already added!\" << std::endl;\n        std::exit(EXIT_FAILURE);\n    }\n}\n\n\nstd::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }\n\nOption::Option(const OptionsMap* map) :\n    parent(map) {}\n\nOption::Option(const char* v, OnChange f) :\n    type(\"string\"),\n    min(0),\n    max(0),\n    on_change(std::move(f)) {\n    defaultValue = currentValue = v;\n}\n\nOption::Option(bool v, OnChange f) :\n    type(\"check\"),\n    min(0),\n    max(0),\n    on_change(std::move(f)) {\n    defaultValue = currentValue = (v ? \"true\" : \"false\");\n}\n\nOption::Option(OnChange f) :\n    type(\"button\"),\n    min(0),\n    max(0),\n    on_change(std::move(f)) {}\n\nOption::Option(int v, int minv, int maxv, OnChange f) :\n    type(\"spin\"),\n    min(minv),\n    max(maxv),\n    on_change(std::move(f)) {\n    defaultValue = currentValue = std::to_string(v);\n}\n\nOption::Option(const char* v, const char* cur, OnChange f) :\n    type(\"combo\"),\n    min(0),\n    max(0),\n    on_change(std::move(f)) {\n    defaultValue = v;\n    currentValue = cur;\n}\n\nOption::operator int() const {\n    assert(type == \"check\" || type == \"spin\");\n    return (type == \"spin\" ? std::stoi(currentValue) : currentValue == \"true\");\n}\n\nOption::operator std::string() const {\n    assert(type == \"string\");\n    return currentValue;\n}\n\nbool Option::operator==(const char* s) const {\n    assert(type == \"combo\");\n    return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue);\n}\n\nbool Option::operator!=(const char* s) const { return !(*this == s); }\n\n\n// Updates currentValue and triggers on_change() action. It's up to\n// the GUI to check for option's limits, but we could receive the new value\n// from the user by console window, so let's check the bounds anyway.\nOption& Option::operator=(const std::string& v) {\n\n    assert(!type.empty());\n\n    if ((type != \"button\" && type != \"string\" && v.empty())\n        || (type == \"check\" && v != \"true\" && v != \"false\")\n        || (type == \"spin\" && (std::stoi(v) < min || std::stoi(v) > max)))\n        return *this;\n\n    if (type == \"combo\")\n    {\n        OptionsMap         comboMap;  // To have case insensitive compare\n        std::string        token;\n        std::istringstream ss(defaultValue);\n        while (ss >> token)\n            comboMap.add(token, Option());\n        if (!comboMap.count(v) || v == \"var\")\n            return *this;\n    }\n\n    if (type == \"string\")\n        currentValue = v == \"<empty>\" ? \"\" : v;\n    else if (type != \"button\")\n        currentValue = v;\n\n    if (on_change)\n    {\n        const auto ret = on_change(*this);\n\n        if (ret && parent != nullptr && parent->info != nullptr)\n            parent->info(ret);\n    }\n\n    return *this;\n}\n\nstd::ostream& operator<<(std::ostream& os, const OptionsMap& om) {\n    for (size_t idx = 0; idx < om.options_map.size(); ++idx)\n        for (const auto& it : om.options_map)\n            if (it.second.idx == idx)\n            {\n                const Option& o = it.second;\n                os << \"\\noption name \" << it.first << \" type \" << o.type;\n\n                if (o.type == \"check\" || o.type == \"combo\")\n                    os << \" default \" << o.defaultValue;\n\n                else if (o.type == \"string\")\n                {\n                    std::string defaultValue = o.defaultValue.empty() ? \"<empty>\" : o.defaultValue;\n                    os << \" default \" << defaultValue;\n                }\n\n                else if (o.type == \"spin\")\n                    os << \" default \" << stoi(o.defaultValue) << \" min \" << o.min << \" max \"\n                       << o.max;\n\n                break;\n            }\n\n    return os;\n}\n}\n"
  },
  {
    "path": "src/ucioption.h",
    "content": "/*\n  Stockfish, a UCI chess playing engine derived from Glaurung 2.1\n  Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)\n\n  Stockfish is free software: you can redistribute it and/or modify\n  it under the terms of the GNU General Public License as published by\n  the Free Software Foundation, either version 3 of the License, or\n  (at your option) any later version.\n\n  Stockfish is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n  GNU General Public License for more details.\n\n  You should have received a copy of the GNU General Public License\n  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef UCIOPTION_H_INCLUDED\n#define UCIOPTION_H_INCLUDED\n\n#include <cstddef>\n#include <functional>\n#include <iosfwd>\n#include <map>\n#include <optional>\n#include <string>\n\nnamespace Stockfish {\n// Define a custom comparator, because the UCI options should be case-insensitive\nstruct CaseInsensitiveLess {\n    bool operator()(const std::string&, const std::string&) const;\n};\n\nclass OptionsMap;\n\n// The Option class implements each option as specified by the UCI protocol\nclass Option {\n   public:\n    using OnChange = std::function<std::optional<std::string>(const Option&)>;\n\n    Option(const OptionsMap*);\n    Option(OnChange = nullptr);\n    Option(bool v, OnChange = nullptr);\n    Option(const char* v, OnChange = nullptr);\n    Option(int v, int minv, int maxv, OnChange = nullptr);\n    Option(const char* v, const char* cur, OnChange = nullptr);\n\n    Option& operator=(const std::string&);\n    operator int() const;\n    operator std::string() const;\n    bool operator==(const char*) const;\n    bool operator!=(const char*) const;\n\n    friend std::ostream& operator<<(std::ostream&, const OptionsMap&);\n\n    int operator<<(const Option&) = delete;\n\n   private:\n    friend class OptionsMap;\n    friend class Engine;\n    friend class Tune;\n\n\n    std::string       defaultValue, currentValue, type;\n    int               min, max;\n    size_t            idx;\n    OnChange          on_change;\n    const OptionsMap* parent = nullptr;\n};\n\nclass OptionsMap {\n   public:\n    using InfoListener = std::function<void(std::optional<std::string>)>;\n\n    OptionsMap()                             = default;\n    OptionsMap(const OptionsMap&)            = delete;\n    OptionsMap(OptionsMap&&)                 = delete;\n    OptionsMap& operator=(const OptionsMap&) = delete;\n    OptionsMap& operator=(OptionsMap&&)      = delete;\n\n    void add_info_listener(InfoListener&&);\n\n    void setoption(std::istringstream&);\n\n    const Option& operator[](const std::string&) const;\n\n    void add(const std::string&, const Option& option);\n\n    std::size_t count(const std::string&) const;\n\n   private:\n    friend class Engine;\n    friend class Option;\n\n    friend std::ostream& operator<<(std::ostream&, const OptionsMap&);\n\n    // The options container is defined as a std::map\n    using OptionsStore = std::map<std::string, Option, CaseInsensitiveLess>;\n\n    OptionsStore options_map;\n    InfoListener info;\n};\n\n}\n#endif  // #ifndef UCIOPTION_H_INCLUDED\n"
  },
  {
    "path": "tests/.gitattributes",
    "content": "*.sh text eol=lf\n"
  },
  {
    "path": "tests/instrumented.py",
    "content": "import argparse\nimport re\nimport sys\nimport subprocess\nimport pathlib\nimport os\nimport fnmatch\n\nfrom testing import (\n    EPD,\n    TSAN,\n    Stockfish as Engine,\n    MiniTestFramework,\n    OrderedClassMembers,\n    Valgrind,\n    Syzygy,\n)\n\nPATH = pathlib.Path(__file__).parent.resolve()\nCWD = os.getcwd()\n\n\ndef get_prefix():\n    if args.valgrind:\n        return Valgrind.get_valgrind_command()\n    if args.valgrind_thread:\n        return Valgrind.get_valgrind_thread_command()\n\n    return []\n\n\ndef get_threads():\n    if args.valgrind_thread or args.sanitizer_thread:\n        return 2\n    return 1\n\n\ndef get_path():\n    return os.path.abspath(os.path.join(CWD, args.stockfish_path))\n\n\ndef postfix_check(output):\n    if args.sanitizer_undefined:\n        for idx, line in enumerate(output):\n            if \"runtime error:\" in line:\n                # print next possible 50 lines\n                for i in range(50):\n                    debug_idx = idx + i\n                    if debug_idx < len(output):\n                        print(output[debug_idx])\n                return False\n\n    if args.sanitizer_thread:\n        for idx, line in enumerate(output):\n            if \"WARNING: ThreadSanitizer:\" in line:\n                # print next possible 50 lines\n                for i in range(50):\n                    debug_idx = idx + i\n                    if debug_idx < len(output):\n                        print(output[debug_idx])\n                return False\n\n    return True\n\n\ndef Stockfish(*args, **kwargs):\n    return Engine(get_prefix(), get_path(), *args, **kwargs)\n\n\nclass TestCLI(metaclass=OrderedClassMembers):\n    def beforeAll(self):\n        pass\n\n    def afterAll(self):\n        pass\n\n    def beforeEach(self):\n        self.stockfish = None\n\n    def afterEach(self):\n        assert postfix_check(self.stockfish.get_output()) == True\n        self.stockfish.clear_output()\n\n    def test_eval(self):\n        self.stockfish = Stockfish(\"eval\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_nodes_1000(self):\n        self.stockfish = Stockfish(\"go nodes 1000\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_depth_10(self):\n        self.stockfish = Stockfish(\"go depth 10\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_perft_4(self):\n        self.stockfish = Stockfish(\"go perft 4\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_movetime_1000(self):\n        self.stockfish = Stockfish(\"go movetime 1000\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_wtime_8000_btime_8000_winc_500_binc_500(self):\n        self.stockfish = Stockfish(\n            \"go wtime 8000 btime 8000 winc 500 binc 500\".split(\" \"),\n            True,\n        )\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_wtime_1000_btime_1000_winc_0_binc_0(self):\n        self.stockfish = Stockfish(\n            \"go wtime 1000 btime 1000 winc 0 binc 0\".split(\" \"),\n            True,\n        )\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_wtime_1000_btime_1000_winc_0_binc_0_movestogo_5(self):\n        self.stockfish = Stockfish(\n            \"go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5\".split(\" \"),\n            True,\n        )\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_movetime_200(self):\n        self.stockfish = Stockfish(\"go movetime 200\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_go_nodes_20000_searchmoves_e2e4_d2d4(self):\n        self.stockfish = Stockfish(\n            \"go nodes 20000 searchmoves e2e4 d2d4\".split(\" \"), True\n        )\n        assert self.stockfish.process.returncode == 0\n\n    def test_bench_128_threads_8_default_depth(self):\n        self.stockfish = Stockfish(\n            f\"bench 128 {get_threads()} 8 default depth\".split(\" \"),\n            True,\n        )\n        assert self.stockfish.process.returncode == 0\n\n    def test_bench_128_threads_3_bench_tmp_epd_depth(self):\n        self.stockfish = Stockfish(\n            f\"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth\".split(\n                \" \"\n            ),\n            True,\n        )\n        assert self.stockfish.process.returncode == 0\n\n    def test_d(self):\n        self.stockfish = Stockfish(\"d\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_compiler(self):\n        self.stockfish = Stockfish(\"compiler\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_license(self):\n        self.stockfish = Stockfish(\"license\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_uci(self):\n        self.stockfish = Stockfish(\"uci\".split(\" \"), True)\n        assert self.stockfish.process.returncode == 0\n\n    def test_export_net_verify_nnue(self):\n        current_path = os.path.abspath(os.getcwd())\n        self.stockfish = Stockfish(\n            f\"export_net {os.path.join(current_path, 'verify.nnue')}\".split(\" \"), True\n        )\n        assert self.stockfish.process.returncode == 0\n\n    # verify the generated net equals the base net\n\n    def test_network_equals_base(self):\n        self.stockfish = Stockfish(\n            [\"uci\"],\n            True,\n        )\n\n        output = self.stockfish.process.stdout\n\n        # find line\n        for line in output.split(\"\\n\"):\n            if \"option name EvalFile type string default\" in line:\n                network = line.split(\" \")[-1]\n                break\n\n        # find network file in src dir\n        network = os.path.join(PATH.parent.resolve(), \"src\", network)\n\n        if not os.path.exists(network):\n            print(\n                f\"Network file {network} not found, please download the network file over the make command.\"\n            )\n            assert False\n\n        diff = subprocess.run([\"diff\", network, f\"verify.nnue\"])\n\n        assert diff.returncode == 0\n\n\nclass TestInteractive(metaclass=OrderedClassMembers):\n    def beforeAll(self):\n        self.stockfish = Stockfish()\n\n    def afterAll(self):\n        self.stockfish.quit()\n        assert self.stockfish.close() == 0\n\n    def afterEach(self):\n        assert postfix_check(self.stockfish.get_output()) == True\n        self.stockfish.clear_output()\n\n    def test_startup_output(self):\n        self.stockfish.starts_with(\"Stockfish\")\n\n    def test_uci_command(self):\n        self.stockfish.send_command(\"uci\")\n        self.stockfish.equals(\"uciok\")\n\n    def test_set_threads_option(self):\n        self.stockfish.send_command(f\"setoption name Threads value {get_threads()}\")\n\n    def test_ucinewgame_and_startpos_nodes_1000(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position startpos\")\n        self.stockfish.send_command(\"go nodes 1000\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_ucinewgame_and_startpos_moves(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position startpos moves e2e4 e7e6\")\n        self.stockfish.send_command(\"go nodes 1000\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_1(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\")\n        self.stockfish.send_command(\"go nodes 1000\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_2_flip(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\")\n        self.stockfish.send_command(\"flip\")\n        self.stockfish.send_command(\"go nodes 1000\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_depth_5_with_callback(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position startpos\")\n        self.stockfish.send_command(\"go depth 5\")\n\n        def callback(output):\n            regex = r\"info depth \\d+ seldepth \\d+ multipv \\d+ score cp -?\\d+ nodes \\d+ nps \\d+ hashfull \\d+ tbhits \\d+ time \\d+ pv\"\n            if output.startswith(\"info depth\") and not re.match(regex, output):\n                assert False\n            if output.startswith(\"bestmove\"):\n                return True\n            return False\n\n        self.stockfish.check_output(callback)\n\n    def test_ucinewgame_and_go_depth_9(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"setoption name UCI_ShowWDL value true\")\n        self.stockfish.send_command(\"position startpos\")\n        self.stockfish.send_command(\"go depth 9\")\n\n        depth = 1\n\n        def callback(output):\n            nonlocal depth\n\n            regex = rf\"info depth {depth} seldepth \\d+ multipv \\d+ score cp -?\\d+ wdl \\d+ \\d+ \\d+ nodes \\d+ nps \\d+ hashfull \\d+ tbhits \\d+ time \\d+ pv\"\n\n            if output.startswith(\"info depth\"):\n                if not re.match(regex, output):\n                    assert False\n                depth += 1\n\n            if output.startswith(\"bestmove\"):\n                assert depth == 10\n                return True\n\n            return False\n\n        self.stockfish.check_output(callback)\n\n    def test_clear_hash(self):\n        self.stockfish.send_command(\"setoption name Clear Hash\")\n\n    def test_fen_position_mate_1(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6\"\n        )\n        self.stockfish.send_command(\"go depth 18\")\n\n        self.stockfish.expect(\"* score mate 1 * pv d5e6\")\n        self.stockfish.equals(\"bestmove d5e6\")\n\n    def test_fen_position_mate_minus_1(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -\"\n        )\n        self.stockfish.send_command(\"go depth 18\")\n        self.stockfish.expect(\"* score mate -1 *\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_fixed_node(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 5K2/8/2P1P1Pk/6pP/3p2P1/1P6/3P4/8 w - - 0 1\"\n        )\n        self.stockfish.send_command(\"go nodes 500000\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_with_mate_go_depth(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\"\n        )\n        self.stockfish.send_command(\"go depth 18 searchmoves c6d7\")\n        self.stockfish.expect(\"* score mate 2 * pv c6d7 * f7f5\")\n\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_with_mate_go_mate(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\"\n        )\n        self.stockfish.send_command(\"go mate 2 searchmoves c6d7\")\n        self.stockfish.expect(\"* score mate 2 * pv c6d7 *\")\n\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_with_mate_go_nodes(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\"\n        )\n        self.stockfish.send_command(\"go nodes 500000 searchmoves c6d7\")\n        self.stockfish.expect(\"* score mate 2 * pv c6d7 * f7f5\")\n\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_depth_27(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen r1b2r1k/pp1p2pp/2p5/2B1q3/8/8/P1PN2PP/R4RK1 w - - 0 18\"\n        )\n        self.stockfish.send_command(\"go\")\n        self.stockfish.contains(\"score mate 1\")\n\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_with_mate_go_depth_and_promotion(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q\"\n        )\n        self.stockfish.send_command(\"go depth 18\")\n        self.stockfish.expect(\"* score mate 1 * pv f7f5\")\n        self.stockfish.starts_with(\"bestmove f7f5\")\n\n    def test_fen_position_with_mate_go_depth_and_searchmoves(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\"\n        )\n        self.stockfish.send_command(\"go depth 18 searchmoves c6d7\")\n        self.stockfish.expect(\"* score mate 2 * pv c6d7 * f7f5\")\n\n        self.stockfish.starts_with(\"bestmove c6d7\")\n\n    def test_fen_position_with_moves_with_mate_go_depth_and_searchmoves(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\n            \"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7\"\n        )\n        self.stockfish.send_command(\"go depth 18 searchmoves e3e2\")\n        self.stockfish.expect(\"* score mate -1 * pv e3e2 f7f5\")\n        self.stockfish.starts_with(\"bestmove e3e2\")\n\n    def test_verify_nnue_network(self):\n        current_path = os.path.abspath(os.getcwd())\n        Stockfish(\n            f\"export_net {os.path.join(current_path, 'verify.nnue')}\".split(\" \"), True\n        )\n\n        self.stockfish.send_command(\"setoption name EvalFile value verify.nnue\")\n        self.stockfish.send_command(\"position startpos\")\n        self.stockfish.send_command(\"go depth 5\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_multipv_setting(self):\n        self.stockfish.send_command(\"setoption name MultiPV value 4\")\n        self.stockfish.send_command(\"position startpos\")\n        self.stockfish.send_command(\"go depth 5\")\n        self.stockfish.starts_with(\"bestmove\")\n\n    def test_fen_position_with_skill_level(self):\n        self.stockfish.send_command(\"setoption name Skill Level value 10\")\n        self.stockfish.send_command(\"position startpos\")\n        self.stockfish.send_command(\"go depth 5\")\n        self.stockfish.starts_with(\"bestmove\")\n\n        self.stockfish.send_command(\"setoption name Skill Level value 20\")\n\n\nclass TestSyzygy(metaclass=OrderedClassMembers):\n    def beforeAll(self):\n        self.stockfish = Stockfish()\n\n    def afterAll(self):\n        self.stockfish.quit()\n        assert self.stockfish.close() == 0\n\n    def afterEach(self):\n        assert postfix_check(self.stockfish.get_output()) == True\n        self.stockfish.clear_output()\n\n    def test_syzygy_setup(self):\n        self.stockfish.starts_with(\"Stockfish\")\n        self.stockfish.send_command(\"uci\")\n        self.stockfish.send_command(\n            f\"setoption name SyzygyPath value {os.path.join(PATH, 'syzygy')}\"\n        )\n        self.stockfish.expect(\n            \"info string Found 35 WDL and 35 DTZ tablebase files (up to 4-man).\"\n        )\n\n    def test_syzygy_bench(self):\n        self.stockfish.send_command(\"bench 128 1 8 default depth\")\n        self.stockfish.expect(\"Nodes searched  :*\")\n\n    def test_syzygy_position(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\")\n        self.stockfish.send_command(\"go depth 5\")\n\n        def check_output(output):\n            if \"score cp 20000\" in output or \"score mate\" in output:\n                return True\n\n        self.stockfish.check_output(check_output)\n        self.stockfish.expect(\"bestmove *\")\n\n    def test_syzygy_position_2(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\")\n        self.stockfish.send_command(\"go depth 5\")\n\n        def check_output(output):\n            if \"score cp 20000\" in output or \"score mate\" in output:\n                return True\n\n        self.stockfish.check_output(check_output)\n        self.stockfish.expect(\"bestmove *\")\n\n    def test_syzygy_position_3(self):\n        self.stockfish.send_command(\"ucinewgame\")\n        self.stockfish.send_command(\"position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1\")\n        self.stockfish.send_command(\"go depth 5\")\n\n        def check_output(output):\n            if \"score cp -20000\" in output or \"score mate -\" in output:\n                return True\n\n        self.stockfish.check_output(check_output)\n        self.stockfish.expect(\"bestmove *\")\n\nclass TestEnPassantSanitization(metaclass=OrderedClassMembers):\n    def beforeAll(self):\n        self.stockfish = Stockfish()\n\n    def afterAll(self):\n        self.stockfish.quit()\n        assert self.stockfish.close() == 0\n\n    def afterEach(self):\n        assert postfix_check(self.stockfish.get_output()) == True\n        self.stockfish.clear_output()\n\n    def test_position_1(self):\n        self.stockfish.send_command(\"position fen rnbqkbnr/ppp1p1pp/5p2/3pP3/8/8/PPPP1PPP/RNBQKBNR w kq d6 0 3\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*rnbqkbnr/ppp1p1pp/5p2/3pP3/8/8/PPPP1PPP/RNBQKBNR w kq d6 0 3*\")\n\n    def test_position_2(self):\n        self.stockfish.send_command(\"position fen k7/8/8/1pP5/2K5/8/8/8 w - b6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k7/8/8/1pP5/2K5/8/8/8 w - b6 0 1*\")\n\n    def test_position_3(self):\n        self.stockfish.send_command(\"position fen k1r5/8/8/1pP5/2K5/8/8/8 w - b6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k1r5/8/8/1pP5/2K5/8/8/8 w - - 0 1*\")\n\n    def test_position_4(self):\n        self.stockfish.send_command(\"position fen k1r5/8/8/1pP5/8/2K5/8/8 w - b6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k1r5/8/8/1pP5/8/2K5/8/8 w - - 0 1*\")\n\n    def test_position_5(self):\n        self.stockfish.send_command(\"position fen k1r5/8/8/PpP5/8/2K5/8/8 w - b6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k1r5/8/8/PpP5/8/2K5/8/8 w - b6 0 1*\")\n\n    def test_position_6(self):\n        self.stockfish.send_command(\"position fen k1r5/8/8/PpP5/2K5/8/8/8 w - b6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k1r5/8/8/PpP5/2K5/8/8/8 w - b6 0 1*\")\n\n    def test_position_7(self):\n        self.stockfish.send_command(\"position fen k7/4b3/8/PpP5/1K6/8/8/8 w - b6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k7/4b3/8/PpP5/1K6/8/8/8 w - b6 0 1*\")\n\n    def test_position_8(self):\n        self.stockfish.send_command(\"position fen k7/b5b1/8/2PpP3/3K4/8/8/8 w - d6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k7/b5b1/8/2PpP3/3K4/8/8/8 w - - 0 1*\")\n\n    def test_position_9(self):\n        self.stockfish.send_command(\"position fen k7/8/8/r2pPK2/8/8/8/8 w - d6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k7/8/8/r2pPK2/8/8/8/8 w - - 0 1*\")\n\n    def test_position_10(self):\n        self.stockfish.send_command(\"position fen k7/8/8/r1PpPK2/8/8/8/8 w - d6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*k7/8/8/r1PpPK2/8/8/8/8 w - d6 0 1*\")\n\n    def test_position_11(self):\n        self.stockfish.send_command(\"position fen kb6/8/8/3pP3/5K2/8/8/8 w - d6 0 1\")\n        self.stockfish.send_command(\"d\")\n\n        self.stockfish.expect_for_line_matching(\"Fen*\", \"*kb6/8/8/3pP3/5K2/8/8/8 w - d6 0 1*\")\n\n    def test_position_find_draw(self):\n        self.stockfish.send_command(\"position fen q4kb1/3Q2nq/8/r3PpK1/2n5/7q/8/q7 w - f6 0 1 moves d7c8 f8f7 c8d7 f7f8 d7d8 f8f7\")\n        self.stockfish.send_command(\"go nodes 10000\")\n\n        def check_output(output):\n            if fnmatch.fnmatch(output, \"* score cp 0 * pv d8d7*\"):\n                return True\n        \n        self.stockfish.check_output(check_output)\n        self.stockfish.expect(\"bestmove d8d7*\")\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description=\"Run Stockfish with testing options\")\n    parser.add_argument(\"--valgrind\", action=\"store_true\", help=\"Run valgrind testing\")\n    parser.add_argument(\n        \"--valgrind-thread\", action=\"store_true\", help=\"Run valgrind-thread testing\"\n    )\n    parser.add_argument(\n        \"--sanitizer-undefined\",\n        action=\"store_true\",\n        help=\"Run sanitizer-undefined testing\",\n    )\n    parser.add_argument(\n        \"--sanitizer-thread\", action=\"store_true\", help=\"Run sanitizer-thread testing\"\n    )\n\n    parser.add_argument(\n        \"--none\", action=\"store_true\", help=\"Run without any testing options\"\n    )\n    parser.add_argument(\"stockfish_path\", type=str, help=\"Path to Stockfish binary\")\n\n    return parser.parse_args()\n\n\nif __name__ == \"__main__\":\n    args = parse_args()\n\n    EPD.create_bench_epd()\n    TSAN.set_tsan_option()\n    Syzygy.download_syzygy()\n\n    framework = MiniTestFramework()\n\n    # Each test suite will be run inside a temporary directory\n    framework.run([TestCLI, TestInteractive, TestSyzygy, TestEnPassantSanitization])\n\n    EPD.delete_bench_epd()\n    TSAN.unset_tsan_option()\n\n    if framework.has_failed():\n        sys.exit(1)\n\n    sys.exit(0)\n"
  },
  {
    "path": "tests/perft.sh",
    "content": "#!/bin/bash\n# verify perft numbers (positions from https://www.chessprogramming.org/Perft_Results)\n\nTESTS_FAILED=0\n\nerror()\n{\n  echo \"perft testing failed on line $1\"\n  exit 1\n}\ntrap 'error ${LINENO}' ERR\n\necho \"perft testing started\"\n\nEXPECT_SCRIPT=$(mktemp)\n\ncat << 'EOF' > $EXPECT_SCRIPT\n#!/usr/bin/expect -f\nset timeout 120\nlassign [lrange $argv 0 4] pos depth result chess960 logfile\nlog_file -noappend $logfile\nspawn ./stockfish\nif {$chess960 == \"true\"} {\n  send \"setoption name UCI_Chess960 value true\\n\"\n}\nsend \"position $pos\\ngo perft $depth\\n\"\nexpect {\n  \"Nodes searched: $result\" {}\n  timeout {puts \"TIMEOUT: Expected $result nodes\"; exit 1}\n  eof {puts \"EOF: Stockfish crashed\"; exit 2}\n}\nsend \"quit\\n\"\nexpect eof\nEOF\n\nchmod +x $EXPECT_SCRIPT\n\nrun_test() {\n  local pos=\"$1\"\n  local depth=\"$2\"\n  local expected=\"$3\"\n  local chess960=\"$4\"\n  local tmp_file=$(mktemp)\n\n  echo -n \"Testing depth $depth: ${pos:0:40}... \"\n\n  if $EXPECT_SCRIPT \"$pos\" \"$depth\" \"$expected\" \"$chess960\" \"$tmp_file\" > /dev/null 2>&1; then\n    echo \"OK\"\n    rm -f \"$tmp_file\"\n  else\n    local exit_code=$?\n    echo \"FAILED (exit code: $exit_code)\"\n    echo \"===== Output for failed test =====\"\n    cat \"$tmp_file\"\n    echo \"==================================\"\n    rm -f \"$tmp_file\"\n    TESTS_FAILED=1\n  fi\n}\n\n# standard positions\n\nrun_test \"startpos\" 7 3195901860 \"false\"\nrun_test \"fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -\" 5 193690690 \"false\"\nrun_test \"fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -\" 7 178633661 \"false\"\nrun_test \"fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1\" 6 706045033 \"false\"\nrun_test \"fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8\" 5 89941194 \"false\"\nrun_test \"fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10\" 5 164075551 \"false\"\nrun_test \"fen r7/4p3/5p1q/3P4/4pQ2/4pP2/6pp/R3K1kr w Q - 1 3\" 5 11609488 \"false\"\n\n# chess960 positions\n\nrun_test \"fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1\" 6 119060324 \"true\"\nrun_test \"fen 1rqbkrbn/1ppppp1p/1n6/p1N3p1/8/2P4P/PP1PPPP1/1RQBKRBN w FBfb - 0 9\" 6 191762235 \"true\"\nrun_test \"fen rbbqn1kr/pp2p1pp/6n1/2pp1p2/2P4P/P7/BP1PPPP1/R1BQNNKR w HAha - 0 9\" 6 924181432 \"true\"\nrun_test \"fen rqbbknr1/1ppp2pp/p5n1/4pp2/P7/1PP5/1Q1PPPPP/R1BBKNRN w GAga - 0 9\" 6 308553169 \"true\"\nrun_test \"fen 4rrb1/1kp3b1/1p1p4/pP1Pn2p/5p2/1PR2P2/2P1NB1P/2KR1B2 w D - 0 21\" 6 872323796 \"true\"\nrun_test \"fen 1rkr3b/1ppn3p/3pB1n1/6q1/R2P4/4N1P1/1P5P/2KRQ1B1 b Dbd - 0 14\" 6 2678022813 \"true\"\nrun_test \"fen qbbnrkr1/p1pppppp/1p4n1/8/2P5/6N1/PPNPPPPP/1BRKBRQ1 b FCge - 1 3\" 6 521301336 \"true\"\nrun_test \"fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2R2R b E - 1 20\" 2 1438 \"true\"\nrun_test \"fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 w E - 0 20\" 3 37340 \"true\"\nrun_test \"fen rr6/2kpp3/1ppnb1p1/p2Q1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 b E - 2 19\" 4 2237725 \"true\"\nrun_test \"fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19\" 4 2098209 \"true\"\nrun_test \"fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19\" 5 79014522 \"true\"\nrun_test \"fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19\" 6 2998685421 \"true\"\n\nrm -f $EXPECT_SCRIPT\necho \"perft testing completed\"\n\nif [ $TESTS_FAILED -ne 0 ]; then\n  echo \"Some tests failed\"\n  exit 1\nfi\n"
  },
  {
    "path": "tests/reprosearch.sh",
    "content": "#!/bin/bash\n# verify reproducible search\n\nerror()\n{\n  echo \"reprosearch testing failed on line $1\"\n  exit 1\n}\ntrap 'error ${LINENO}' ERR\n\necho \"reprosearch testing started\"\n\n# repeat two short games, separated by ucinewgame.\n# with go nodes $nodes they should result in exactly\n# the same node count for each iteration.\ncat << EOF > repeat.exp\n set timeout 10\n spawn ./stockfish\n lassign \\$argv nodes\n\n send \"uci\\n\"\n expect \"uciok\"\n\n send \"ucinewgame\\n\"\n send \"position startpos\\n\"\n send \"go nodes \\$nodes\\n\"\n expect \"bestmove\"\n\n send \"position startpos moves e2e4 e7e6\\n\"\n send \"go nodes \\$nodes\\n\"\n expect \"bestmove\"\n\n send \"ucinewgame\\n\"\n send \"position startpos\\n\"\n send \"go nodes \\$nodes\\n\"\n expect \"bestmove\"\n\n send \"position startpos moves e2e4 e7e6\\n\"\n send \"go nodes \\$nodes\\n\"\n expect \"bestmove\"\n\n send \"quit\\n\"\n expect eof\nEOF\n\n# to increase the likelihood of finding a non-reproducible case,\n# the allowed number of nodes are varied systematically\nfor i in `seq 1 20`\ndo\n\n  nodes=$((100*3**i/2**i))\n  echo \"reprosearch testing with $nodes nodes\"\n\n  # each line should appear exactly an even number of times\n  expect repeat.exp $nodes 2>&1 | grep -o \"nodes [0-9]*\" | sort | uniq -c | awk '{if ($1%2!=0) exit(1)}'\n\ndone\n\nrm repeat.exp\n\necho \"reprosearch testing OK\"\n"
  },
  {
    "path": "tests/signature.sh",
    "content": "#!/bin/bash\n# obtain and optionally verify Bench / signature\n# if no reference is given, the output is deliberately limited to just the signature\n\nSTDOUT_FILE=$(mktemp)\nSTDERR_FILE=$(mktemp)\n\nerror()\n{\n  echo \"running bench for signature failed on line $1\"\n  echo \"===== STDOUT =====\"\n  cat \"$STDOUT_FILE\"\n  echo \"===== STDERR =====\"\n  cat \"$STDERR_FILE\"\n  rm -f \"$STDOUT_FILE\" \"$STDERR_FILE\"\n  exit 1\n}\ntrap 'error ${LINENO}' ERR\n\n# obtain\neval \"$RUN_PREFIX ./stockfish bench\" > \"$STDOUT_FILE\" 2> \"$STDERR_FILE\" || error ${LINENO}\nsignature=$(grep \"Nodes searched  : \" \"$STDERR_FILE\" | awk '{print $4}')\n\nrm -f \"$STDOUT_FILE\" \"$STDERR_FILE\"\n\nif [ $# -gt 0 ]; then\n   # compare to given reference\n   if [ \"$1\" != \"$signature\" ]; then\n      if [ -z \"$signature\" ]; then\n         echo \"No signature obtained from bench. Code crashed or assert triggered ?\"\n      else\n         echo \"signature mismatch: reference $1 obtained: $signature .\"\n      fi\n      exit 1\n   else\n      echo \"signature OK: $signature\"\n   fi\nelse\n   # just report signature\n   echo $signature\nfi"
  },
  {
    "path": "tests/testing.py",
    "content": "import subprocess\nfrom typing import List\nimport os\nimport collections\nimport time\nimport sys\nimport traceback\nimport fnmatch\nfrom functools import wraps\nfrom contextlib import redirect_stdout\nimport io\nimport tarfile\nimport pathlib\nimport concurrent.futures\nimport tempfile\nimport shutil\nimport requests\n\nCYAN_COLOR = \"\\033[36m\"\nGRAY_COLOR = \"\\033[2m\"\nRED_COLOR = \"\\033[31m\"\nGREEN_COLOR = \"\\033[32m\"\nRESET_COLOR = \"\\033[0m\"\nWHITE_BOLD = \"\\033[1m\"\n\nMAX_TIMEOUT = 60 * 5\n\nPATH = pathlib.Path(__file__).parent.resolve()\n\n\nclass Valgrind:\n    @staticmethod\n    def get_valgrind_command():\n        return [\n            \"valgrind\",\n            \"--error-exitcode=42\",\n            \"--errors-for-leak-kinds=all\",\n            \"--leak-check=full\",\n        ]\n\n    @staticmethod\n    def get_valgrind_thread_command():\n        return [\"valgrind\", \"--error-exitcode=42\", \"--fair-sched=try\"]\n\n\nclass TSAN:\n    @staticmethod\n    def set_tsan_option():\n        with open(f\"tsan.supp\", \"w\") as f:\n            f.write(\n                \"\"\"\nrace:Stockfish::TTEntry::read\nrace:Stockfish::TTEntry::save\nrace:Stockfish::TranspositionTable::probe\nrace:Stockfish::TranspositionTable::hashfull\n\"\"\"\n            )\n\n        os.environ[\"TSAN_OPTIONS\"] = \"suppressions=./tsan.supp\"\n\n    @staticmethod\n    def unset_tsan_option():\n        os.environ.pop(\"TSAN_OPTIONS\", None)\n        os.remove(f\"tsan.supp\")\n\n\nclass EPD:\n    @staticmethod\n    def create_bench_epd():\n        with open(f\"{os.path.join(PATH,'bench_tmp.epd')}\", \"w\") as f:\n            f.write(\n                \"\"\"\nRn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26\nrnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3\n3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28\nr4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13\n\"\"\"\n            )\n\n    @staticmethod\n    def delete_bench_epd():\n        os.remove(f\"{os.path.join(PATH,'bench_tmp.epd')}\")\n\n\nclass Syzygy:\n    @staticmethod\n    def get_syzygy_path():\n        return os.path.abspath(\"syzygy\")\n\n    @staticmethod\n    def download_syzygy():\n        if not os.path.isdir(os.path.join(PATH, \"syzygy\")):\n            url = \"https://api.github.com/repos/niklasf/python-chess/tarball/9b9aa13f9f36d08aadfabff872882f4ab1494e95\"\n            file = \"niklasf-python-chess-9b9aa13\"\n\n            with tempfile.TemporaryDirectory() as tmpdirname:\n                tarball_path = os.path.join(tmpdirname, f\"{file}.tar.gz\")\n\n                response = requests.get(url, stream=True)\n                with open(tarball_path, \"wb\") as f:\n                    for chunk in response.iter_content(chunk_size=8192):\n                        f.write(chunk)\n\n                with tarfile.open(tarball_path, \"r:gz\") as tar:\n                    tar.extractall(tmpdirname)\n\n                shutil.move(\n                    os.path.join(tmpdirname, file), os.path.join(PATH, \"syzygy\")\n                )\n\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n\n    def __new__(self, name, bases, classdict):\n        classdict[\"__ordered__\"] = [\n            key for key in classdict.keys() if key not in (\"__module__\", \"__qualname__\")\n        ]\n        return type.__new__(self, name, bases, classdict)\n\n\nclass TimeoutException(Exception):\n    def __init__(self, message: str, timeout: int):\n        self.message = message\n        self.timeout = timeout\n\nclass UnexpectedOutputException(Exception):\n    def __init__(self, actual: str, expected: str):\n        self.actual   = actual\n        self.expected = expected\n\n\ndef timeout_decorator(timeout: float):\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            with concurrent.futures.ThreadPoolExecutor() as executor:\n                future = executor.submit(func, *args, **kwargs)\n                try:\n                    result = future.result(timeout=timeout)\n                except concurrent.futures.TimeoutError:\n                    raise TimeoutException(\n                        f\"Function {func.__name__} timed out after {timeout} seconds\",\n                        timeout,\n                    )\n            return result\n\n        return wrapper\n\n    return decorator\n\n\nclass MiniTestFramework:\n    def __init__(self):\n        self.passed_test_suites = 0\n        self.failed_test_suites = 0\n        self.passed_tests = 0\n        self.failed_tests = 0\n        self.stop_on_failure = True\n\n    def has_failed(self) -> bool:\n        return self.failed_test_suites > 0\n\n    def run(self, classes: List[type]) -> bool:\n        self.start_time = time.time()\n\n        for test_class in classes:\n            with tempfile.TemporaryDirectory() as tmpdirname:\n                original_cwd = os.getcwd()\n                os.chdir(tmpdirname)\n\n                try:\n                    if self.__run(test_class):\n                        self.failed_test_suites += 1\n                    else:\n                        self.passed_test_suites += 1\n                except Exception as e:\n                    self.failed_test_suites += 1\n                    print(f\"\\n{RED_COLOR}Error: {e}{RESET_COLOR}\")\n                finally:\n                    os.chdir(original_cwd)\n\n        self.__print_summary(round(time.time() - self.start_time, 2))\n        return self.has_failed()\n\n    def __run(self, test_class) -> bool:\n        test_instance = test_class()\n        test_name = test_instance.__class__.__name__\n        test_methods = [m for m in test_instance.__ordered__ if m.startswith(\"test_\")]\n\n        print(f\"\\nTest Suite: {test_name}\")\n\n        if hasattr(test_instance, \"beforeAll\"):\n            test_instance.beforeAll()\n\n        fails = 0\n\n        for method in test_methods:\n            fails += self.__run_test_method(test_instance, method)\n\n        if hasattr(test_instance, \"afterAll\"):\n            test_instance.afterAll()\n\n        self.failed_tests += fails\n\n        return fails > 0\n\n    def __run_test_method(self, test_instance, method: str) -> int:\n        print(f\"    Running {method}... \\r\", end=\"\", flush=True)\n\n        buffer = io.StringIO()\n        fails = 0\n\n        try:\n            t0 = time.time()\n\n            with redirect_stdout(buffer):\n                if hasattr(test_instance, \"beforeEach\"):\n                    test_instance.beforeEach()\n\n                getattr(test_instance, method)()\n\n                if hasattr(test_instance, \"afterEach\"):\n                    test_instance.afterEach()\n\n            duration = time.time() - t0\n\n            self.print_success(f\" {method} ({duration * 1000:.2f}ms)\")\n            self.passed_tests += 1\n        except Exception as e:\n            if isinstance(e, TimeoutException):\n                self.print_failure(\n                    f\" {method} (hit execution limit of {e.timeout} seconds)\"\n                )\n\n            if isinstance(e, UnexpectedOutputException):\n                self.print_failure(\n                    f\" {method} encountered unexpected output: \\\"{e.actual}\\\" when output matching \\\"{e.expected}\\\" was expected\"\n                )\n\n            if isinstance(e, AssertionError):\n                self.__handle_assertion_error(t0, method)\n\n            if self.stop_on_failure:\n                self.__print_buffer_output(buffer)\n                raise e\n\n            fails += 1\n        finally:\n            self.__print_buffer_output(buffer)\n\n        return fails\n\n    def __handle_assertion_error(self, start_time, method: str):\n        duration = time.time() - start_time\n        self.print_failure(f\" {method} ({duration * 1000:.2f}ms)\")\n        traceback_output = \"\".join(traceback.format_tb(sys.exc_info()[2]))\n\n        colored_traceback = \"\\n\".join(\n            f\"  {CYAN_COLOR}{line}{RESET_COLOR}\"\n            for line in traceback_output.splitlines()\n        )\n\n        print(colored_traceback)\n\n    def __print_buffer_output(self, buffer: io.StringIO):\n        output = buffer.getvalue()\n        if output:\n            indented_output = \"\\n\".join(f\"    {line}\" for line in output.splitlines())\n            print(f\"    {RED_COLOR}⎯⎯⎯⎯⎯OUTPUT⎯⎯⎯⎯⎯{RESET_COLOR}\")\n            print(f\"{GRAY_COLOR}{indented_output}{RESET_COLOR}\")\n            print(f\"    {RED_COLOR}⎯⎯⎯⎯⎯OUTPUT⎯⎯⎯⎯⎯{RESET_COLOR}\")\n\n    def __print_summary(self, duration: float):\n        print(f\"\\n{WHITE_BOLD}Test Summary{RESET_COLOR}\\n\")\n        print(\n            f\"    Test Suites: {GREEN_COLOR}{self.passed_test_suites} passed{RESET_COLOR}, {RED_COLOR}{self.failed_test_suites} failed{RESET_COLOR}, {self.passed_test_suites + self.failed_test_suites} total\"\n        )\n        print(\n            f\"    Tests:       {GREEN_COLOR}{self.passed_tests} passed{RESET_COLOR}, {RED_COLOR}{self.failed_tests} failed{RESET_COLOR}, {self.passed_tests + self.failed_tests} total\"\n        )\n        print(f\"    Time:        {duration}s\\n\")\n\n    def print_failure(self, add: str):\n        print(f\"    {RED_COLOR}✗{RESET_COLOR}{add}\", flush=True)\n\n    def print_success(self, add: str):\n        print(f\"    {GREEN_COLOR}✓{RESET_COLOR}{add}\", flush=True)\n\n\nclass Stockfish:\n    def __init__(\n        self,\n        prefix: List[str],\n        path: str,\n        args: List[str] = [],\n        cli: bool = False,\n    ):\n        self.path = path\n        self.process = None\n        self.args = args\n        self.cli = cli\n        self.prefix = prefix\n        self.output = []\n\n        self.start()\n\n    def _check_process_alive(self):\n        if not self.process or self.process.poll() is not None:\n            print(\"\\n\".join(self.output))\n            raise RuntimeError(\"Stockfish process has terminated\")\n\n    def start(self):\n        if self.cli:\n            self.process = subprocess.run(\n                self.prefix + [self.path] + self.args,\n                capture_output=True,\n                text=True,\n            )\n\n            if self.process.returncode != 0:\n                print(self.process.stdout)\n                print(self.process.stderr)\n                print(f\"Process failed with return code {self.process.returncode}\")\n\n            return\n\n        self.process = subprocess.Popen(\n            self.prefix + [self.path] + self.args,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            universal_newlines=True,\n            bufsize=1,\n        )\n\n    def setoption(self, name: str, value: str):\n        self.send_command(f\"setoption name {name} value {value}\")\n\n    def send_command(self, command: str):\n        if not self.process:\n            raise RuntimeError(\"Stockfish process is not started\")\n\n        self._check_process_alive()\n\n        self.process.stdin.write(command + \"\\n\")\n        self.process.stdin.flush()\n\n    @timeout_decorator(MAX_TIMEOUT)\n    def equals(self, expected_output: str):\n        for line in self.readline():\n            if line == expected_output:\n                return\n\n    @timeout_decorator(MAX_TIMEOUT)\n    def expect(self, expected_output: str):\n        for line in self.readline():\n            if fnmatch.fnmatch(line, expected_output):\n                return\n\n    @timeout_decorator(MAX_TIMEOUT)\n    def contains(self, expected_output: str):\n        for line in self.readline():\n            if expected_output in line:\n                return\n\n    @timeout_decorator(MAX_TIMEOUT)\n    def starts_with(self, expected_output: str):\n        for line in self.readline():\n            if line.startswith(expected_output):\n                return\n\n    @timeout_decorator(MAX_TIMEOUT)\n    def check_output(self, callback):\n        if not callback:\n            raise ValueError(\"Callback function is required\")\n\n        for line in self.readline():\n            if callback(line) == True:\n                return\n\n    @timeout_decorator(MAX_TIMEOUT)    \n    def expect_for_line_matching(self, line_match: str, expected: str):\n        for line in self.readline():\n            if fnmatch.fnmatch(line, line_match):\n                if fnmatch.fnmatch(line, expected):\n                    break\n                else:\n                    raise UnexpectedOutputException(line, expected)\n\n    def readline(self):\n        if not self.process:\n            raise RuntimeError(\"Stockfish process is not started\")\n\n        while True:\n            self._check_process_alive()\n            line = self.process.stdout.readline().strip()\n            self.output.append(line)\n\n            yield line\n\n    def clear_output(self):\n        self.output = []\n\n    def get_output(self) -> List[str]:\n        return self.output\n\n    def quit(self):\n        self.send_command(\"quit\")\n\n    def close(self):\n        if self.process:\n            self.process.stdin.close()\n            self.process.stdout.close()\n            return self.process.wait()\n\n        return 0\n"
  }
]