Merge official-stockfish/master
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 18 Oct 2020 18:40:07 +0000 (20:40 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 18 Oct 2020 18:40:07 +0000 (20:40 +0200)
- Updates language standard to C++17.
- Includes merge of NNUE code.
  - NNUE embedding is disabled by default
  - Variant name is matched against NN file name to detect support

bench: 4170106

41 files changed:
1  2 
.travis.yml
README.md
appveyor.yml
appveyor_python.yml
setup.py
src/Makefile
src/benchmark.cpp
src/bitbase.cpp
src/bitboard.cpp
src/bitboard.h
src/endgame.cpp
src/endgame.h
src/evaluate.cpp
src/evaluate.h
src/main.cpp
src/material.cpp
src/material.h
src/misc.cpp
src/misc.h
src/movegen.cpp
src/movepick.cpp
src/movepick.h
src/pawns.cpp
src/position.cpp
src/position.h
src/psqt.cpp
src/search.cpp
src/search.h
src/syzygy/tbprobe.cpp
src/thread.cpp
src/thread.h
src/timeman.cpp
src/timeman.h
src/tt.cpp
src/tt.h
src/types.h
src/uci.cpp
src/uci.h
src/ucioption.cpp
tests/instrumented.sh
tests/js/README.md

diff --cc .travis.yml
@@@ -31,23 -29,23 +29,28 @@@ matrix
          - COMP=gcc
  
      - os: osx
+       osx_image: xcode12
        compiler: clang
        env:
-         - COMPILER=clang++ V='Apple LLVM 9.4.1' # Apple LLVM version 9.1.0 (clang-902.0.39.2)
+         - COMPILER=clang++
          - COMP=clang
  
 -branches:
 -  only:
 -   - master
 -
  before_script:
    - cd src
  
  script:
 -  # Download net
++  # Download net and test nnue
+   - make net
++  - make clean && make -j2 ARCH=x86-64 nnue=yes debug=yes build && ./stockfish bench > /dev/null 2>&1
++
 +  # Check perft of large-board version
 +  - make clean && make -j2 ARCH=x86-64 largeboards=yes all=yes debug=yes build && ../tests/perft.sh largeboard
 +  - ./stockfish bench xiangqi > /dev/null 2>&1
 +  - ./stockfish bench shogi > /dev/null 2>&1
 +  - ./stockfish bench capablanca > /dev/null 2>&1
 +  - ./stockfish bench sittuyin > /dev/null 2>&1
-   - make clean && make -j2 ARCH=x86-32 largeboards=yes all=yes debug=yes build && ../tests/perft.sh largeboard
-   #
++  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 largeboards=yes all=yes debug=yes build && ../tests/perft.sh largeboard; fi
    # Obtain bench reference from git log
    - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
    - export benchref=$(cat git_sig)
  
    #
    # Check perft and reproducible search
-   - export CXXFLAGS="-Werror"
-   - make clean && make -j2 ARCH=x86-64 build
-   - ../tests/protocol.sh
+   - make clean && make -j2 ARCH=x86-64-modern build
    - ../tests/perft.sh
    - ../tests/reprosearch.sh
++  - ../tests/protocol.sh
    #
    # Valgrind
    #
diff --cc README.md
Simple merge
diff --cc appveyor.yml
@@@ -4,11 -4,9 +4,11 @@@ clone_depth: 5
  branches:
    only:
      - master
 +    - merge
 +    - appveyor
  
  # Operating system (build VM template)
- os: Visual Studio 2017
+ os: Visual Studio 2019
  
  # Build platform, i.e. x86, x64, AnyCPU. This setting is optional.
  platform:
index 7bc6d5e,0000000..e8eae81
mode 100644,000000..100644
--- /dev/null
@@@ -1,50 -1,0 +1,51 @@@
++image:
++  - Visual Studio 2019
 +environment:
 +  matrix:
 +    - PYTHON: "C:\\Python36-x64"
 +    - PYTHON: "C:\\Python37-x64"
 +    - PYTHON: "C:\\Python38-x64"
 +
 +init:
 +  - "ECHO %PYTHON% %PYTHON_ARCH% %MSVC_VERSION%"
 +
 +install:
 +  - ps: |
 +      if (-not (Test-Path $env:PYTHON)) {
 +        curl -o install_python.ps1 https://raw.githubusercontent.com/matthew-brett/multibuild/11a389d78892cf90addac8f69433d5e22bfa422a/install_python.ps1
 +        .\install_python.ps1
 +      }
 +  - ps: if (-not (Test-Path $env:PYTHON)) { throw "No $env:PYTHON" }
 +  - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
 +  - python --version
 +
 +  # We need wheel installed to build wheels
 +  - "%PYTHON%\\python.exe -m pip install wheel"
 +
 +build: off
 +
 +test_script:
 +  # Put your test command here.
 +  # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4,
 +  # you can remove "build.cmd" from the front of the command, as it's
 +  # only needed to support those cases.
 +  # Note that you must use the environment variable %PYTHON% to refer to
 +  # the interpreter you're using - Appveyor does not do anything special
 +  # to put the Python version you want to use on PATH.
 +  - "%PYTHON%\\python.exe setup.py test"
 +
 +after_test:
 +  # This step builds your wheels.
 +  # Again, you only need build.cmd if you're building C extensions for
 +  # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct
 +  # interpreter
 +  - "%PYTHON%\\python.exe setup.py bdist_wheel"
 +
 +artifacts:
 +  # bdist_wheel puts your built wheel in the dist directory
 +  - path: dist\*
 +
 +#on_success:
 +#  You can use this step to upload your artifacts to a public website.
 +#  See Appveyor's documentation for more details. Or you can simply
 +#  access your wheels from the Appveyor "artifacts" tab for your build.
diff --cc setup.py
index 96761c3,0000000..6a25f81
mode 100644,000000..100644
--- /dev/null
+++ b/setup.py
@@@ -1,51 -1,0 +1,53 @@@
 +# -*- coding: utf-8 -*-
 +
 +from setuptools import setup, Extension
 +from glob import glob
 +import platform
 +import io
 +import os
 +
 +
- args = ["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-flto", "-std=c++11"]
++if platform.python_compiler().startswith("MSC"):
++    args = ["/std:c++17"]
++else:
++    args = ["-std=c++17", "-flto", "-Wno-date-time"]
 +
- if not platform.python_compiler().startswith("MSC"):
-     args.append("-Wno-date-time")
++args.extend(["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"])
 +
 +if "64bit" in platform.architecture():
 +    args.append("-DIS_64BIT")
 +
 +CLASSIFIERS = [
 +    "Development Status :: 3 - Alpha",
 +    "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
 +    "Programming Language :: Python :: 3",
 +    "Operating System :: OS Independent",
 +]
 +
- with io.open("Readme.md", "r", encoding="utf8") as fh:
++with io.open("README.md", "r", encoding="utf8") as fh:
 +    long_description = fh.read().strip()
 +
- sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp")
++sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp") + glob("src/nnue/*.cpp") + glob("src/nnue/features/*.cpp")
 +ffish_source_file = os.path.normcase("src/ffishjs.cpp")
 +try:
 +    sources.remove(ffish_source_file)
 +except ValueError:
 +    print(f"ffish_source_file {ffish_source_file} was not found in sources {sources}.")
 +
 +pyffish_module = Extension(
 +    "pyffish",
 +    sources=sources,
 +    extra_compile_args=args)
 +
 +setup(name="pyffish", version="0.0.51",
 +      description="Fairy-Stockfish Python wrapper",
 +      long_description=long_description,
 +      long_description_content_type="text/markdown",
 +      author="Bajusz Tamás",
 +      author_email="gbtami@gmail.com",
 +      license="GPL3",
 +      classifiers=CLASSIFIERS,
 +      url="https://github.com/gbtami/Fairy-Stockfish",
 +      python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
 +      ext_modules=[pyffish_module]
 +      )
diff --cc src/Makefile
@@@ -37,8 -37,9 +37,10 @@@ PGOBENCH = ./$(EXE) benc
  
  ### Source and object files
  SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
-       material.cpp misc.cpp movegen.cpp movepick.cpp partner.cpp parser.cpp pawns.cpp piece.cpp position.cpp psqt.cpp \
-       search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp variant.cpp xboard.cpp tune.cpp syzygy/tbprobe.cpp
+       material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
+       search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
 -      nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp
++      nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp \
++      partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp
  
  OBJS = $(notdir $(SRCS:.cpp=.o))
  
@@@ -74,9 -84,23 +85,27 @@@ endi
  # at the end of the line for flag values.
  
  ### 2.1. General and architecture defaults
 +largeboards = no
 +all = no
 +precomputedmagics = yes
++nnue = no
+ ifeq ($(ARCH),)
+    ARCH = x86-64-modern
+    help_skip_sanity = yes
+ endif
+ # explicitly check for the list of supported architectures (as listed with make help),
+ # the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
+ ifeq ($(ARCH), $(filter $(ARCH), \
+                  x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \
+                  x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
+                  x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \
+                  armv7 armv7-neon armv8 apple-silicon general-64 general-32))
+    SUPPORTED_ARCH=true
+ else
+    SUPPORTED_ARCH=false
+ endif
  optimize = yes
  debug = no
  sanitize = no
@@@ -153,29 -289,17 +294,43 @@@ ifeq ($(ARCH),ppc-64
        prefetch = yes
  endif
  
++# Disable precomputed magics when 64-bit PEXT is available
++ifeq ($(pext),yes)
++      precomputedmagics = no
++endif
++
+ endif
  ### ==========================================================================
  ### Section 3. Low-level Configuration
  ### ==========================================================================
  
  ### 3.1 Selecting compiler (default = gcc)
- CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS)
- DEPENDFLAGS += -std=c++11
+ CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
+ DEPENDFLAGS += -std=c++17
  LDFLAGS += $(EXTRALDFLAGS)
  
 +# Compile version with support for large board variants
 +# Use precomputed magics by default if pext is not available
 +ifneq ($(largeboards),no)
 +      CXXFLAGS += -DLARGEBOARDS
 +      ifeq ($(precomputedmagics),yes)
 +              CXXFLAGS += -DPRECOMPUTED_MAGICS
 +      endif
 +endif
 +
++# Embed and enable NNUE
++ifeq ($(nnue),yes)
++      CXXFLAGS += -DUSE_NNUE
++else
++      CXXFLAGS += -DNNUE_EMBEDDING_OFF
++endif
++
 +# Enable all variants, even heavyweight ones like amazons
 +ifneq ($(all),no)
 +      CXXFLAGS += -DALLVARS
 +endif
 +
  ifeq ($(COMP),)
        COMP=gcc
  endif
  ifeq ($(COMP),gcc)
        comp=gcc
        CXX=g++
 -      CXXFLAGS += -pedantic -Wextra -Wshadow
 +      CXXFLAGS += -Wextra -Wshadow
 +      ifeq ($(largeboards),no)
 +              CXXFLAGS += -pedantic
 +      endif
  
-       ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
+       ifeq ($(arch),$(filter $(arch),armv7 armv8))
                ifeq ($(OS),Android)
                        CXXFLAGS += -m$(bits)
                        LDFLAGS += -m$(bits)
@@@ -430,25 -688,26 +722,40 @@@ help
        @echo ""
        @echo "Simple examples. If you don't know what to do, you likely want to run: "
        @echo ""
-       @echo "make build ARCH=x86-64    (This is for 64-bit systems)"
-       @echo "make build ARCH=x86-32    (This is for 32-bit systems)"
+       @echo "make -j build ARCH=x86-64  (A portable, slow compile for 64-bit systems)"
+       @echo "make -j build ARCH=x86-32  (A portable, slow compile for 32-bit systems)"
        @echo ""
-       @echo "Advanced examples, for experienced users: "
+       @echo "Advanced examples, for experienced users looking for performance: "
        @echo ""
-       @echo "make build ARCH=x86-64 COMP=clang"
-       @echo "make profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8"
+       @echo "make    help  ARCH=x86-64-bmi2"
+       @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0"
+       @echo "make -j build ARCH=x86-64-ssse3 COMP=clang"
        @echo ""
+       @echo "-------------------------------"
+ ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true)
+       @echo "The selected architecture $(ARCH) will enable the following configuration: "
+       @$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity
+ else
+       @echo "Specify a supported architecture with the ARCH option for more details"
+       @echo ""
++      @echo "-------------------------------"
++      @echo "Embed and enable NNUE by default: "
++      @echo ""
++      @echo "make build ARCH=x86-64 nnue=yes"
++      @echo ""
++      @echo "-------------------------------"
 +      @echo "Version for large boards: "
 +      @echo ""
 +      @echo "make build ARCH=x86-64 COMP=gcc largeboards=yes"
 +      @echo ""
-       @echo "Include all variants: "
++      @echo "Include all variants (adds Game of the Amazons): "
 +      @echo ""
 +      @echo "make build ARCH=x86-64 largeboards=yes all=yes"
 +      @echo ""
+ endif
  
  
- .PHONY: help build profile-build strip install clean objclean profileclean \
+ .PHONY: help build profile-build strip install clean net objclean profileclean \
          config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
          clang-profile-use clang-profile-make
  
@@@ -132,6 -116,7 +131,11 @@@ vector<string> setup_bench(const Positi
    string limit     = (is >> token) ? token : "13";
    string fenFile   = (is >> token) ? token : "default";
    string limitType = (is >> token) ? token : "depth";
++#ifdef USE_NNUE
+   string evalType  = (is >> token) ? token : "mixed";
++#else
++  string evalType  = (is >> token) ? token : "classic";
++#endif
  
    go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
  
  
    list.emplace_back("setoption name Threads value " + threads);
    list.emplace_back("setoption name Hash value " + ttSize);
 +  list.emplace_back("setoption name UCI_Variant value " + varname);
    list.emplace_back("ucinewgame");
  
+   size_t posCounter = 0;
    for (const string& fen : fens)
        if (fen.find("setoption") != string::npos)
            list.emplace_back(fen);
        else
        {
+           if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0))
+               list.emplace_back("setoption name Use NNUE value false");
+           else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0))
+               list.emplace_back("setoption name Use NNUE value true");
            list.emplace_back("position fen " + fen);
            list.emplace_back(go);
+           ++posCounter;
        }
  
++#ifdef USE_NNUE
+   list.emplace_back("setoption name Use NNUE value true");
++#else
++  list.emplace_back("setoption name Use NNUE value false");
++#endif
    return list;
  }
diff --cc src/bitbase.cpp
Simple merge
@@@ -50,104 -35,20 +48,114 @@@ Magic JanggiElephantMagics[SQUARE_NB]
  
  namespace {
  
 -  Bitboard RookTable[0x19000];  // To store rook attacks
 +#ifdef LARGEBOARDS
 +  Bitboard RookTableH[0x11800];  // To store horizontalrook attacks
 +  Bitboard RookTableV[0x4800];  // To store vertical rook attacks
 +  Bitboard BishopTable[0x33C00]; // To store bishop attacks
 +  Bitboard CannonTableH[0x11800];  // To store horizontal cannon attacks
 +  Bitboard CannonTableV[0x4800];  // To store vertical cannon attacks
 +  Bitboard HorseTable[0x500];  // To store horse attacks
 +  Bitboard ElephantTable[0x400];  // To store elephant attacks
 +  Bitboard JanggiElephantTable[0x1C000];  // To store janggi elephant attacks
 +#else
 +  Bitboard RookTableH[0xA00];  // To store horizontal rook attacks
 +  Bitboard RookTableV[0xA00];  // To store vertical rook attacks
    Bitboard BishopTable[0x1480]; // To store bishop attacks
 +  Bitboard CannonTableH[0xA00];  // To store horizontal cannon attacks
 +  Bitboard CannonTableV[0xA00];  // To store vertical cannon attacks
 +  Bitboard HorseTable[0x240];  // To store horse attacks
 +  Bitboard ElephantTable[0x1A0];  // To store elephant attacks
 +  Bitboard JanggiElephantTable[0x5C00];  // To store janggi elephant attacks
 +#endif
 +
 +  enum MovementType { RIDER, HOPPER, LAME_LEAPER };
 +
 +  template <MovementType MT>
 +#ifdef PRECOMPUTED_MAGICS
 +  void init_magics(Bitboard table[], Magic magics[], std::vector<Direction> directions, Bitboard magicsInit[]);
 +#else
 +  void init_magics(Bitboard table[], Magic magics[], std::vector<Direction> directions);
 +#endif
 +
 +  template <MovementType MT>
 +  Bitboard sliding_attack(std::vector<Direction> directions, Square sq, Bitboard occupied, Color c = WHITE) {
 +    assert(MT != LAME_LEAPER);
 +
 +    Bitboard attack = 0;
 +
 +    for (Direction d : directions)
 +    {
 +        bool hurdle = false;
 +        for (Square s = sq + (c == WHITE ? d : -d);
 +             is_ok(s) && distance(s, s - (c == WHITE ? d : -d)) == 1;
 +             s += (c == WHITE ? d : -d))
 +        {
 +            if (MT != HOPPER || hurdle)
 +                attack |= s;
 +
 +            if (occupied & s)
 +            {
 +                if (MT == HOPPER && !hurdle)
 +                    hurdle = true;
 +                else
 +                    break;
 +            }
 +        }
 +    }
 +
 +    return attack;
 +  }
 +
 +  Bitboard lame_leaper_path(Direction d, Square s) {
 +    Direction dr = d > 0 ? NORTH : SOUTH;
 +    Direction df = (std::abs(d % NORTH) < NORTH / 2 ? d % NORTH : -(d % NORTH)) < 0 ? WEST : EAST;
 +    Square to = s + d;
 +    Bitboard b = 0;
 +    if (!is_ok(to) || distance(s, to) >= 4)
 +        return b;
 +    while (s != to)
 +    {
 +        int diff = std::abs(file_of(to) - file_of(s)) - std::abs(rank_of(to) - rank_of(s));
 +        if (diff > 0)
 +            s += df;
 +        else if (diff < 0)
 +            s += dr;
 +        else
 +            s += df + dr;
 +
 +        if (s != to)
 +            b |= s;
 +    }
 +    return b;
 +  }
  
 -  void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
 +  Bitboard lame_leaper_path(std::vector<Direction> directions, Square s) {
 +    Bitboard b = 0;
 +    for (Direction d : directions)
 +        b |= lame_leaper_path(d, s);
 +    return b;
 +  }
 +
 +  Bitboard lame_leaper_attack(std::vector<Direction> directions, Square s, Bitboard occupied) {
 +    Bitboard b = 0;
 +    for (Direction d : directions)
 +    {
 +        Square to = s + d;
 +        if (is_ok(to) && distance(s, to) < 4 && !(lame_leaper_path(d, s) & occupied))
 +            b |= to;
 +    }
 +    return b;
 +  }
+ }
+ /// safe_destination() returns the bitboard of target square for the given step
+ /// from the given square. If the step is off the board, returns empty bitboard.
+ inline Bitboard safe_destination(Square s, int step) {
+     Square to = Square(s + step);
 -    return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
++    return is_ok(to) && distance(s, to) <= 3 ? square_bb(to) : Bitboard(0);
  }
  
  
diff --cc src/bitboard.h
@@@ -373,34 -275,8 +371,25 @@@ template<> inline int distance<File>(Sq
  template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
  template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
  
 -inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
 -inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
 +inline int edge_distance(File f, File maxFile = FILE_H) { return std::min(f, File(maxFile - f)); }
 +inline int edge_distance(Rank r, Rank maxRank = RANK_8) { return std::min(r, Rank(maxRank - r)); }
 +
 +
- /// safe_destination() returns the bitboard of target square for the given step
- /// from the given square. If the step is off the board, returns empty bitboard.
- inline Bitboard safe_destination(Square s, int step)
- {
-     Square to = Square(s + step);
-     return is_ok(to) && distance(s, to) <= 3 ? square_bb(to) : Bitboard(0);
- }
 +template<RiderType R>
 +inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) {
 +
 +  assert(R == RIDER_BISHOP || R == RIDER_ROOK_H || R == RIDER_ROOK_V || R == RIDER_CANNON_H || R == RIDER_CANNON_V
 +         || R == RIDER_HORSE || R == RIDER_ELEPHANT || R == RIDER_JANGGI_ELEPHANT);
 +  const Magic& m =  R == RIDER_ROOK_H ? RookMagicsH[s]
 +                  : R == RIDER_ROOK_V ? RookMagicsV[s]
 +                  : R == RIDER_CANNON_H ? CannonMagicsH[s]
 +                  : R == RIDER_CANNON_V ? CannonMagicsV[s]
 +                  : R == RIDER_HORSE ? HorseMagics[s]
 +                  : R == RIDER_ELEPHANT ? ElephantMagics[s]
 +                  : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s]
 +                  : BishopMagics[s];
 +  return m.attacks[m.index(occupied)];
 +}
  
  
  /// attacks_bb(Square) returns the pseudo attacks of the give piece type
diff --cc src/endgame.cpp
Simple merge
diff --cc src/endgame.h
Simple merge
  #include "pawns.h"
  #include "thread.h"
  #include "uci.h"
+ #include "incbin/incbin.h"
+ // Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler).
+ // This macro invocation will declare the following three variables
+ //     const unsigned char        gEmbeddedNNUEData[];  // a pointer to the embedded data
+ //     const unsigned char *const gEmbeddedNNUEEnd;     // a marker to the end
+ //     const unsigned int         gEmbeddedNNUESize;    // the size of the embedded file
+ // Note that this does not work in Microsof Visual Studio.
+ #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
+   INCBIN(EmbeddedNNUE, EvalFileDefaultName);
+ #else
+   const unsigned char        gEmbeddedNNUEData[1] = {0x0};
++  [[maybe_unused]]
+   const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
+   const unsigned int         gEmbeddedNNUESize = 1;
+ #endif
+ using namespace std;
+ using namespace Eval::NNUE;
+ namespace Eval {
+   bool useNNUE;
+   string eval_file_loaded = "None";
+   /// NNUE::init() tries to load a nnue network at startup time, or when the engine
+   /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
+   /// The name of the nnue network is always retrieved from the EvalFile option.
+   /// We search the given network in three locations: internally (the default
+   /// network may be embedded in the binary), in the active working directory and
+   /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
+   /// variable to have the engine search in a special directory in their distro.
+   void NNUE::init() {
 -    useNNUE = Options["Use NNUE"];
++    string eval_file = string(Options["EvalFile"]);
++
++    useNNUE = Options["Use NNUE"]
++             && (   eval_file.find(string(Options["UCI_Variant"])) != string::npos
++                 || (Options["UCI_Variant"] == "chess" && eval_file.rfind("nn-", 0) != string::npos)) // restrict NNUE usage to corresponding variant
++             && RANK_MAX == RANK_8; // TODO: fix for large boards
+     if (!useNNUE)
+         return;
 -    string eval_file = string(Options["EvalFile"]);
 -
+     #if defined(DEFAULT_NNUE_DIRECTORY)
+     #define stringify2(x) #x
+     #define stringify(x) stringify2(x)
+     vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
+     #else
+     vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
+     #endif
+     for (string directory : dirs)
+         if (eval_file_loaded != eval_file)
+         {
+             if (directory != "<internal>")
+             {
+                 ifstream stream(directory + eval_file, ios::binary);
+                 if (load_eval(eval_file, stream))
+                     eval_file_loaded = eval_file;
+             }
+             if (directory == "<internal>" && eval_file == EvalFileDefaultName)
+             {
+                 // C++ way to prepare a buffer for a memory stream
+                 class MemoryBuffer : public basic_streambuf<char> {
+                     public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
+                 };
+                 MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
+                                     size_t(gEmbeddedNNUESize));
+                 istream stream(&buffer);
+                 if (load_eval(eval_file, stream))
+                     eval_file_loaded = eval_file;
+             }
+         }
+   }
+   /// NNUE::verify() verifies that the last net used was loaded successfully
+   void NNUE::verify() {
+     string eval_file = string(Options["EvalFile"]);
+     if (useNNUE && eval_file_loaded != eval_file)
+     {
+         UCI::OptionsMap defaults;
+         UCI::init(defaults);
+         string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
+         string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
+         string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
+         string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]);
+         string msg5 = "The engine will be terminated now.";
+         sync_cout << "info string ERROR: " << msg1 << sync_endl;
+         sync_cout << "info string ERROR: " << msg2 << sync_endl;
+         sync_cout << "info string ERROR: " << msg3 << sync_endl;
+         sync_cout << "info string ERROR: " << msg4 << sync_endl;
+         sync_cout << "info string ERROR: " << msg5 << sync_endl;
+         exit(EXIT_FAILURE);
+     }
+     if (useNNUE)
+         sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
+     else
+         sync_cout << "info string classical evaluation enabled" << sync_endl;
+   }
+ }
  
  namespace Trace {
  
@@@ -75,40 -187,40 +191,42 @@@ using namespace Trace
  namespace {
  
    // Threshold for lazy and space evaluation
-   constexpr Value LazyThreshold1  = Value(1400);
-   constexpr Value LazyThreshold2  = Value(1300);
+   constexpr Value LazyThreshold1 =  Value(1400);
+   constexpr Value LazyThreshold2 =  Value(1300);
    constexpr Value SpaceThreshold = Value(12222);
+   constexpr Value NNUEThreshold1 =   Value(550);
+   constexpr Value NNUEThreshold2 =   Value(150);
  
    // KingAttackWeights[PieceType] contains king attack weights by piece type
 -  constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
 +  constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10, 40 };
  
    // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
    // higher if multiple safe checks are possible for that piece type.
    constexpr int SafeCheck[][2] = {
-       {}, {600, 600}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119}, {600, 900}
 -      {}, {}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132}
++      {}, {600, 600}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132}, {600, 900}
    };
  
  #define S(mg, eg) make_score(mg, eg)
  
    // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
    // indexed by piece type and number of attacked squares in the mobility area.
 -  constexpr Score MobilityBonus[][32] = {
 +  constexpr Score MobilityBonus[][4 * RANK_NB] = {
-     { S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S(  3,  5), S( 13, 11), // Knight
-       S( 22, 17), S( 28, 20), S( 33, 25) },
-     { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop
-       S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
-       S( 91, 88), S( 98, 97) },
-     { S(-60,-78), S(-20,-17), S(  2, 23), S(  3, 39), S(  3, 70), S( 11, 99), // Rook
-       S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164),
-       S( 57,168), S( 57,169), S( 62,172) },
-     { S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen
-       S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100),
-       S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141),
-       S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171),
-       S(110,182), S(114,182), S(114,192), S(116,219) }
+     { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S(  3,  7), S( 12, 13), // Knight
+       S( 21, 16), S( 28, 21), S( 37, 26) },
+     { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop
+       S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87),
+       S( 91, 88), S( 96, 98) },
+     { S(-61,-82), S(-20,-17), S(  2, 23) ,S(  3, 40), S(  4, 72), S( 11,100), // Rook
+       S( 22,104), S( 31,120), S( 39,134), S(40 ,138), S( 41,158), S( 47,163),
+       S( 59,168), S( 60,169), S( 64,173) },
+     { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen
+       S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101),
+       S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140),
+       S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171),
+       S(112,178), S(114,185), S(114,187), S(119,221) }
    };
 +  constexpr Score MaxMobility  = S(150, 200);
 +  constexpr Score DropMobility = S(10, 10);
  
    // KingProtector[knight/bishop] contains penalty for each distance unit to own king
    constexpr Score KingProtector[] = { S(8, 9), S(6, 9) };
  
      // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king
      // or controlled by enemy pawns are excluded from the mobility area.
 -    mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them));
 +    if (pos.must_capture())
 +        mobilityArea[Us] = AllSquares;
 +    else
 +        mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them)
 +                               | (pos.pieces(Us, SHOGI_PAWN) & shift<Down>(pos.pieces(Us)))
 +                               | shift<Down>(pos.pieces(Them, SHOGI_PAWN, SOLDIER))
 +                               | shift<EAST>(pos.promoted_soldiers(Them))
 +                               | shift<WEST>(pos.promoted_soldiers(Them)));
  
      // Initialize attackedBy[] for king and pawns
 -    attackedBy[Us][KING] = attacks_bb<KING>(ksq);
 +    attackedBy[Us][KING] = pos.count<KING>(Us) ? pos.attacks_from(Us, KING, ksq) : Bitboard(0);
      attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
 -    attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
 -    attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
 +    attackedBy[Us][SHOGI_PAWN] = shift<Up>(pos.pieces(Us, SHOGI_PAWN));
 +    attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN] | attackedBy[Us][SHOGI_PAWN];
 +    attackedBy2[Us]            =  (attackedBy[Us][KING] & attackedBy[Us][PAWN])
 +                                | (attackedBy[Us][KING] & attackedBy[Us][SHOGI_PAWN])
 +                                | (attackedBy[Us][PAWN] & attackedBy[Us][SHOGI_PAWN])
 +                                | dblAttackByPawn;
  
      // Init our king safety tables
 -    Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G),
 -                           std::clamp(rank_of(ksq), RANK_2, RANK_7));
 -    kingRing[Us] = attacks_bb<KING>(s) | s;
 +    if (!pos.count<KING>(Us))
 +        kingRing[Us] = Bitboard(0);
 +    else
 +    {
-         Square s = make_square(Utility::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)),
-                                Utility::clamp(rank_of(ksq), RANK_2, Rank(pos.max_rank() - 1)));
++        Square s = make_square(std::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)),
++                               std::clamp(rank_of(ksq), RANK_2, Rank(pos.max_rank() - 1)));
 +        kingRing[Us] = attacks_bb<KING>(s) | s;
 +    }
  
      kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
      kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
          {
              // Penalty if any relative pin or discovered attack against the queen
              Bitboard queenPinners;
 -            if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
 +            if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners, Them))
                  score -= WeakQueen;
-             // Bonus for queen on weak square in enemy camp
-             if (relative_rank(Us, s) > RANK_4 && (~pe->pawn_attacks_span(Them) & s))
-                 score += QueenInfiltration;
          }
      }
      if (T)
    template<Tracing T>
    Value Evaluation<T>::winnable(Score score) const {
  
 -    int outflanking =  distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
 -                     - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
 +    // No initiative bonus for extinction variants
 +    int complexity = 0;
++    bool pawnsOnBothFlanks = true;
 +    if (pos.extinction_value() == VALUE_NONE && !pos.captures_to_hand() && !pos.connect_n())
 +    {
 +    int outflanking = !pos.count<KING>(WHITE) || !pos.count<KING>(BLACK) ? 0
 +                     :  distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
 +                      - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
  
--    bool pawnsOnBothFlanks =   (pos.pieces(PAWN) & QueenSide)
++        pawnsOnBothFlanks =   (pos.pieces(PAWN) & QueenSide)
                              && (pos.pieces(PAWN) & KingSide);
  
      bool almostUnwinnable =   outflanking < 0
              sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
                                                          : pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
          else
-             sf = std::min(sf, 36 + 7 * (pos.count<PAWN>(strongSide) + pos.count<SOLDIER>(strongSide)));
 -            sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
++            sf = std::min(sf, 36 + 7 * (pos.count<PAWN>(strongSide) + pos.count<SOLDIER>(strongSide))) - 4 * !pawnsOnBothFlanks;
+       
+         sf -= 4 * !pawnsOnBothFlanks;
      }
  
      // Interpolate between the middlegame and (scaled by 'sf') endgame score
@@@ -1280,16 -999,8 +1392,8 @@@ make_v
      v = (v / 16) * 16;
  
      // Side to move point of view
 -    v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
 +    v = (pos.side_to_move() == WHITE ? v : -v) + Eval::tempo_value(pos);
  
-     // Damp down the evaluation linearly when shuffling
-     if (pos.n_move_rule())
-     {
-         v = v * (2 * pos.n_move_rule() - pos.rule50_count()) / (2 * pos.n_move_rule());
-         if (pos.material_counting())
-             v += pos.material_counting_result() / (10 * std::max(2 * pos.n_move_rule() - pos.rule50_count(), 1));
-     }
      return v;
    }
  
@@@ -1307,9 -1011,46 +1411,51 @@@ Value Eval::tempo_value(const Position
  /// evaluation of the position from the point of view of the side to move.
  
  Value Eval::evaluate(const Position& pos) {
-   return Evaluation<NO_TRACE>(pos).value();
- }
  
+   Value v;
+   if (!Eval::useNNUE)
+       v = Evaluation<NO_TRACE>(pos).value();
+   else
+   {
+       // Scale and shift NNUE for compatibility with search and classical evaluation
+       auto  adjusted_NNUE = [&](){
+          int mat = pos.non_pawn_material() + PieceValue[MG][PAWN] * pos.count<PAWN>();
+          return NNUE::evaluate(pos) * (720 + mat / 32) / 1024 + Tempo;
+       };
+       // If there is PSQ imbalance use classical eval, with small probability if it is small
+       Value psq = Value(abs(eg_value(pos.psq_score())));
+       int   r50 = 16 + pos.rule50_count();
+       bool  largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50;
+       bool  classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
+       v = classical ? Evaluation<NO_TRACE>(pos).value() : adjusted_NNUE();
+       // If the classical eval is small and imbalance large, use NNUE nevertheless.
+       // For the case of opposite colored bishops, switch to NNUE eval with
+       // small probability if the classical eval is less than the threshold.
+       if (   largePsq
+           && (abs(v) * 16 < NNUEThreshold2 * r50
+           || (   pos.opposite_bishops()
+               && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50
+               && !(pos.this_thread()->nodes & 0xB))))
+           v = adjusted_NNUE();
+   }
+   // Damp down the evaluation linearly when shuffling
 -  v = v * (100 - pos.rule50_count()) / 100;
++  if (pos.n_move_rule())
++  {
++      v = v * (2 * pos.n_move_rule() - pos.rule50_count()) / (2 * pos.n_move_rule());
++      if (pos.material_counting())
++          v += pos.material_counting_result() / (10 * std::max(2 * pos.n_move_rule() - pos.rule50_count(), 1));
++  }
+   // Guarantee evaluation does not hit the tablebase range
+   v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
+   return v;
+ }
  
  /// trace() is like evaluate(), but instead of returning a value, it returns
  /// a string (suitable for outputting to stdout) that contains the detailed
diff --cc src/evaluate.h
@@@ -29,10 -27,26 +27,27 @@@ class Position
  
  namespace Eval {
  
- std::string trace(const Position& pos);
++  Value tempo_value(const Position& pos);
+   std::string trace(const Position& pos);
+   Value evaluate(const Position& pos);
  
- Value tempo_value(const Position& pos);
- Value evaluate(const Position& pos);
- }
+   extern bool useNNUE;
+   extern std::string eval_file_loaded;
+   // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
+   // for the build process (profile-build and fishtest) to work. Do not change the
+   // name of the macro, as it is used in the Makefile.
+   #define EvalFileDefaultName   "nn-04cf2b4ed1da.nnue"
+   namespace NNUE {
+     Value evaluate(const Position& pos);
+     bool load_eval(std::string name, std::istream& stream);
+     void init();
+     void verify();
+   } // namespace NNUE
+ } // namespace Eval
  
  #endif // #ifndef EVALUATE_H_INCLUDED
diff --cc src/main.cpp
@@@ -39,11 -35,10 +37,12 @@@ int main(int argc, char* argv[]) 
  
    std::cout << engine_info() << std::endl;
  
 +  pieceMap.init();
 +  variants.init();
+   CommandLine::init(argc, argv);
    UCI::init(Options);
    Tune::init();
 -  PSQT::init();
 +  PSQT::init(variants.find(Options["UCI_Variant"])->second);
    Bitboards::init();
    Position::init();
    Bitbases::init();
@@@ -141,23 -130,11 +139,23 @@@ Entry* probe(const Position& pos) 
  
    Value npm_w = pos.non_pawn_material(WHITE);
    Value npm_b = pos.non_pawn_material(BLACK);
-   Value npm   = Utility::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
+   Value npm   = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
  
    // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
 -  e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
 +  if (pos.captures_to_hand() || pos.two_boards())
 +  {
 +      Value npm2 = VALUE_ZERO;
 +      for (PieceType pt : pos.piece_types())
 +          npm2 += (pos.count_in_hand(WHITE, pt) + pos.count_in_hand(BLACK, pt)) * PieceValue[MG][make_piece(WHITE, pt)];
 +      e->gamePhase = Phase(PHASE_MIDGAME * npm / std::max(int(npm + npm2), 1));
 +      int countAll = pos.count_with_hand(WHITE, ALL_PIECES) + pos.count_with_hand(BLACK, ALL_PIECES);
 +      e->materialDensity = (npm + npm2 + pos.count<PAWN>() * PawnValueMg) * countAll / ((pos.max_file() + 1) * (pos.max_rank() + 1));
 +  }
 +  else
 +      e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
  
 +  if (pos.endgame_eval())
 +  {
    // Let's look if we have a specialized evaluation function for this particular
    // material configuration. Firstly we look for a fixed configuration one, then
    // for a generic one if the previous search failed.
diff --cc src/material.h
Simple merge
diff --cc src/misc.cpp
@@@ -147,16 -152,8 +152,13 @@@ const string engine_info(bool to_uci, b
        ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
    }
  
 -  ss << (to_uci  ? "\nid author ": " by ")
 -     << "the Stockfish developers (see AUTHORS file)";
 +#ifdef LARGEBOARDS
 +  ss << " LB";
 +#endif
 +
-   ss << (Is64Bit ? " 64" : "")
-      << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : ""));
 +  if (!to_xboard)
 +    ss << (to_uci  ? "\nid author ": " by ")
 +       << "Fabian Fichter";
  
    return ss.str();
  }
@@@ -300,25 -330,38 +335,38 @@@ void prefetch(void* addr) 
  #endif
  
  
- /// aligned_ttmem_alloc() will return suitably aligned memory, and if possible use large pages.
- /// The returned pointer is the aligned one, while the mem argument is the one that needs
- /// to be passed to free. With c++17 some of this functionality could be simplified.
+ /// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
+ /// does not guarantee the availability of aligned_alloc(). Memory allocated with
+ /// std_aligned_alloc() must be freed with std_aligned_free().
  
- #if defined(__linux__) && !defined(__ANDROID__)
+ void* std_aligned_alloc(size_t alignment, size_t size) {
  
- void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+ #if defined(POSIXALIGNEDALLOC)
+   void *mem;
+   return posix_memalign(&mem, alignment, size) ? nullptr : mem;
+ #elif defined(_WIN32)
+   return _mm_malloc(size, alignment);
+ #else
 -  return std::aligned_alloc(alignment, size);
++  return aligned_alloc(alignment, size);
+ #endif
+ }
  
-   constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes
-   size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment
-   if (posix_memalign(&mem, alignment, size))
-      mem = nullptr;
-   madvise(mem, allocSize, MADV_HUGEPAGE);
-   return mem;
+ void std_aligned_free(void* ptr) {
+ #if defined(POSIXALIGNEDALLOC)
+   free(ptr);
+ #elif defined(_WIN32)
+   _mm_free(ptr);
+ #else
+   free(ptr);
+ #endif
  }
  
- #elif defined(_WIN64)
+ /// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
+ #if defined(_WIN32)
  
- static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
+ static void* aligned_large_pages_alloc_win(size_t allocSize) {
  
    HANDLE hProcessToken { };
    LUID luid { };
diff --cc src/misc.h
Simple merge
diff --cc src/movegen.cpp
@@@ -363,67 -233,24 +361,67 @@@ namespace 
          default:
              static_assert(true, "Unsupported type in generate_all()");
      }
 +    target &= pos.board_bb();
  
      moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
 -    moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
 -    moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
 -    moveList = generate_moves<Us,   ROOK, Checks>(pos, moveList, target);
 -    moveList = generate_moves<Us,  QUEEN, Checks>(pos, moveList, target);
 -
 -    if (Type != QUIET_CHECKS && Type != EVASIONS)
 +    for (PieceType pt : pos.piece_types())
 +        if (pt != PAWN && pt != KING)
 +            moveList = generate_moves<Us, Checks>(pos, moveList, pt, target);
 +    // generate drops
 +    if (pos.piece_drops() && Type != CAPTURES && pos.count_in_hand(Us, ALL_PIECES))
 +        for (PieceType pt : pos.piece_types())
 +            moveList = generate_drops<Us, Checks>(pos, moveList, pt, target & ~pos.pieces(~Us));
 +
 +    if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count<KING>(Us))
      {
          Square ksq = pos.square<KING>(Us);
 -        Bitboard b = attacks_bb<KING>(ksq) & target;
 +        Bitboard b = (  (pos.attacks_from(Us, KING, ksq) & pos.pieces())
 +                      | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & target;
          while (b)
 -            *moveList++ = make_move(ksq, pop_lsb(&b));
 +            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
 +
 +        // Passing move by king
 +        if (pos.pass())
 +            *moveList++ = make<SPECIAL>(ksq, ksq);
  
          if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING))
-             for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
+             for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
                  if (!pos.castling_impeded(cr) && pos.can_castle(cr))
 -                    *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
 +                    moveList = make_move_and_gating<CASTLING>(pos, moveList, Us,ksq, pos.castling_rook_square(cr));
 +    }
 +    // Workaround for passing: Execute a non-move with any piece
 +    else if (pos.pass() && !pos.count<KING>(Us) && pos.pieces(Us))
 +        *moveList++ = make<SPECIAL>(lsb(pos.pieces(Us)), lsb(pos.pieces(Us)));
 +
 +    // Castling with non-king piece
 +    if (!pos.count<KING>(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING))
 +    {
 +        Square from = make_square(FILE_E, pos.castling_rank(Us));
 +        for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
 +            if (!pos.castling_impeded(cr) && pos.can_castle(cr))
 +                moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(cr));
 +    }
 +
 +    // Special moves
 +    if (pos.cambodian_moves() && pos.gates(Us))
 +    {
 +        if (Type != CAPTURES && Type != EVASIONS && (pos.pieces(Us, KING) & pos.gates(Us)))
 +        {
 +            Square from = pos.square<KING>(Us);
 +            Bitboard b = PseudoAttacks[WHITE][KNIGHT][from] & rank_bb(rank_of(from + (Us == WHITE ? NORTH : SOUTH)))
 +                        & target & ~pos.pieces();
 +            while (b)
 +                moveList = make_move_and_gating<SPECIAL>(pos, moveList, Us, from, pop_lsb(&b));
 +        }
 +
 +        Bitboard b = pos.pieces(Us, FERS) & pos.gates(Us);
 +        while (b)
 +        {
 +            Square from = pop_lsb(&b);
 +            Square to = from + 2 * (Us == WHITE ? NORTH : SOUTH);
 +            if (is_ok(to) && (target & to))
 +                moveList = make_move_and_gating<SPECIAL>(pos, moveList, Us, from, to);
 +        }
      }
  
      return moveList;
Simple merge
diff --cc src/movepick.h
@@@ -86,13 -84,13 +84,13 @@@ enum StatsType { NoCaptures, Captures }
  /// unsuccessful during the current search, and is used for reduction and move
  /// ordering decisions. It uses 2 tables (one for each color) indexed by
  /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
 -typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
 +typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB + 1) * int(1 << SQUARE_BITS)> ButterflyHistory;
  
- /// At higher depths LowPlyHistory records successful quiet moves near the root and quiet
- /// moves which are/were in the PV (ttPv)
- /// It is cleared with each new search and filled during iterative deepening
+ /// At higher depths LowPlyHistory records successful quiet moves near the root
+ /// and quiet moves which are/were in the PV (ttPv). It is cleared with each new
+ /// search and filled during iterative deepening.
  constexpr int MAX_LPH = 4;
 -typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
 +typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB + 1) * int(1 << SQUARE_BITS)> LowPlyHistory;
  
  /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
  /// move, see www.chessprogramming.org/Countermove_Heuristic
diff --cc src/pawns.cpp
@@@ -32,14 -30,14 +30,14 @@@ namespace 
    #define S(mg, eg) make_score(mg, eg)
  
    // Pawn penalties
-   constexpr Score Backward      = S( 9, 24);
-   constexpr Score Doubled       = S(11, 56);
-   constexpr Score Isolated      = S( 5, 15);
-   constexpr Score WeakLever     = S( 0, 56);
-   constexpr Score WeakUnopposed = S(13, 27);
+   constexpr Score Backward      = S( 8, 25);
+   constexpr Score Doubled       = S(10, 55);
+   constexpr Score Isolated      = S( 3, 15);
+   constexpr Score WeakLever     = S( 3, 55);
+   constexpr Score WeakUnopposed = S(13, 25);
  
    // Bonus for blocked pawns at 5th or 6th rank
-   constexpr Score BlockedPawn[RANK_NB - 5] = { S(-11, -4), S(-3, 4) };
 -  constexpr Score BlockedPawn[2] = { S(-13, -4), S(-5, 2) };
++  constexpr Score BlockedPawn[RANK_NB - 5] = { S(-13, -4), S(-5, 2) };
  
    constexpr Score BlockedStorm[RANK_NB] = {
      S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2)
              e->passedPawns[Us] |= s;
  
          // Score this pawn
 -        if (support | phalanx)
 +        if ((support | phalanx) && (r < pos.promotion_rank() || !pos.mandatory_pawn_promotion()))
          {
 -            int v =  Connected[r] * (2 + bool(phalanx) - bool(opposed))
 +            int v =  Connected[r] * (2 + bool(phalanx) - bool(opposed)) * (r == RANK_2 && pos.captures_to_hand() ? 3 : 1)
-                    + 21 * popcount(support);
+                    + 22 * popcount(support);
 +            if (r >= RANK_4 && pos.count<PAWN>(Us) > popcount(pos.board_bb()) / 4)
 +                v = popcount(support | phalanx) * 50 / (opposed ? 2 : 1);
  
              score += make_score(v, v * (r - 2) / 4);
          }
@@@ -244,7 -219,7 +242,7 @@@ Score Entry::evaluate_shelter(const Pos
  
    Score bonus = make_score(5, 5);
  
-   File center = Utility::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1));
 -  File center = std::clamp(file_of(ksq), FILE_B, FILE_G);
++  File center = std::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1));
    for (File f = File(center - 1); f <= File(center + 1); ++f)
    {
        b = ourPawns & file_bb(f);
@@@ -1206,9 -701,12 +1204,14 @@@ void Position::do_move(Move m, StateInf
    ++gamePly;
    ++st->rule50;
    ++st->pliesFromNull;
 +  if (st->countingLimit)
 +      ++st->countingPly;
  
+   // Used by NNUE
+   st->accumulator.computed_accumulation = false;
+   auto& dp = st->dirtyPiece;
+   dp.dirty_num = 1;
    Color us = sideToMove;
    Color them = ~us;
    Square from = from_sq(m);
        else
            st->nonPawnMaterial[them] -= PieceValue[MG][captured];
  
+       if (Eval::useNNUE)
+       {
+           dp.dirty_num = 2;  // 1 piece moved, 1 piece captured
+           dp.piece[1] = captured;
+           dp.from[1] = capsq;
+           dp.to[1] = SQ_NONE;
+       }
        // Update board and piece lists
 +      bool capturedPromoted = is_promoted(capsq);
 +      Piece unpromotedCaptured = unpromoted_piece_on(capsq);
        remove_piece(capsq);
 -
        if (type_of(m) == ENPASSANT)
            board[capsq] = NO_PIECE;
 +      if (captures_to_hand())
 +      {
 +          Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured
 +                             : unpromotedCaptured ? ~unpromotedCaptured
 +                                                  : make_piece(~color_of(captured), PAWN);
 +          add_to_hand(pieceToHand);
 +          k ^=  Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1]
 +              ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]];
 +      }
  
        // Update material hash key and prefetch access to materialTable
        k ^= Zobrist::psq[captured][capsq];
        k ^= Zobrist::castling[st->castlingRights];
    }
  
 +  // Flip enclosed pieces
 +  st->flippedPieces = 0;
 +  if (flip_enclosed_pieces() && !is_pass(m))
 +  {
 +      // Find end of rows to be flipped
 +      if (flip_enclosed_pieces() == REVERSI)
 +      {
 +          Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us);
 +          while(b)
 +              st->flippedPieces |= between_bb(to, pop_lsb(&b));
 +      }
 +      else
 +      {
 +          assert(flip_enclosed_pieces() == ATAXX);
 +          st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us);
 +      }
 +
 +      // Flip pieces
 +      Bitboard to_flip = st->flippedPieces;
 +      while(to_flip)
 +      {
 +          Square s = pop_lsb(&to_flip);
 +          Piece flipped = piece_on(s);
 +          Piece resulting = ~flipped;
 +
 +          // remove opponent's piece
 +          remove_piece(s);
 +          k ^= Zobrist::psq[flipped][s];
 +          st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]];
 +          st->nonPawnMaterial[them] -= PieceValue[MG][flipped];
 +
 +          // add our piece
 +          put_piece(resulting, s);
 +          k ^= Zobrist::psq[resulting][s];
 +          st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1];
 +          st->nonPawnMaterial[us] += PieceValue[MG][resulting];
 +      }
 +  }
 +
    // Move the piece. The tricky Chess960 castling is handled earlier
 -  if (type_of(m) != CASTLING)
 +  if (type_of(m) == DROP)
 +  {
++      if (Eval::useNNUE)
++      {
++          // Add drop piece
++          dp.piece[dp.dirty_num] = pc;
++          dp.from[dp.dirty_num] = SQ_NONE;
++          dp.to[dp.dirty_num] = to;
++          dp.dirty_num++;
++      }
++
 +      drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to);
 +      st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1];
 +      if (type_of(pc) != PAWN)
 +          st->nonPawnMaterial[us] += PieceValue[MG][pc];
 +      // Set castling rights for dropped king or rook
 +      if (castling_dropped_piece() && rank_of(to) == castling_rank(us))
 +      {
 +          if (type_of(pc) == KING && file_of(to) == FILE_E)
 +          {
 +              Bitboard castling_rooks =  pieces(us, castling_rook_piece())
 +                                       & rank_bb(castling_rank(us))
 +                                       & (file_bb(FILE_A) | file_bb(max_file()));
 +              while (castling_rooks)
 +                  set_castling_right(us, pop_lsb(&castling_rooks));
 +          }
 +          else if (type_of(pc) == castling_rook_piece())
 +          {
 +              if (   (file_of(to) == FILE_A || file_of(to) == max_file())
 +                  && piece_on(make_square(FILE_E, castling_rank(us))) == make_piece(us, KING))
 +                  set_castling_right(us, to);
 +          }
 +      }
 +  }
 +  else if (type_of(m) != CASTLING)
+   {
+       if (Eval::useNNUE)
+       {
+           dp.piece[0] = pc;
+           dp.from[0] = from;
+           dp.to[0] = to;
+       }
        move_piece(from, to);
+   }
  
    // If the moving piece is a pawn do some special extra work
    if (type_of(pc) == PAWN)
            k ^= Zobrist::enpassant[file_of(st->epSquare)];
        }
  
 -      else if (type_of(m) == PROMOTION)
 +      else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION)
        {
 -          Piece promotion = make_piece(us, promotion_type(m));
 +          Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(PAWN));
  
 -          assert(relative_rank(us, to) == RANK_8);
 -          assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
 +          assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion());
 +          assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING);
  
            remove_piece(to);
 -          put_piece(promotion, to);
 +          put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE);
  
+           if (Eval::useNNUE)
+           {
+               // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
+               dp.to[0] = SQ_NONE;
+               dp.piece[dp.dirty_num] = promotion;
+               dp.from[dp.dirty_num] = SQ_NONE;
+               dp.to[dp.dirty_num] = to;
+               dp.dirty_num++;
+           }
            // Update hash keys
            k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
            st->pawnKey ^= Zobrist::psq[pc][to];
        // Reset rule 50 draw counter
        st->rule50 = 0;
    }
 +  else if (type_of(m) == PIECE_PROMOTION)
 +  {
 +      Piece promotion = make_piece(us, promoted_piece_type(type_of(pc)));
 +
 +      remove_piece(to);
 +      put_piece(promotion, to, true, pc);
 +
++      if (Eval::useNNUE)
++      {
++          // Promoting piece to SQ_NONE, promoted piece from SQ_NONE
++          dp.to[0] = SQ_NONE;
++          dp.piece[dp.dirty_num] = promotion;
++          dp.from[dp.dirty_num] = SQ_NONE;
++          dp.to[dp.dirty_num] = to;
++          dp.dirty_num++;
++      }
++
 +      // Update hash keys
 +      k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
 +      st->materialKey ^=  Zobrist::psq[promotion][pieceCount[promotion]-1]
 +                        ^ Zobrist::psq[pc][pieceCount[pc]];
 +
 +      // Update material
 +      st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc];
 +  }
 +  else if (type_of(m) == PIECE_DEMOTION)
 +  {
 +      Piece demotion = unpromoted_piece_on(to);
 +
 +      remove_piece(to);
 +      put_piece(demotion, to);
 +
++      if (Eval::useNNUE)
++      {
++          // Demoting piece to SQ_NONE, demoted piece from SQ_NONE
++          dp.to[0] = SQ_NONE;
++          dp.piece[dp.dirty_num] = demotion;
++          dp.from[dp.dirty_num] = SQ_NONE;
++          dp.to[dp.dirty_num] = to;
++          dp.dirty_num++;
++      }
++
 +      // Update hash keys
 +      k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[demotion][to];
 +      st->materialKey ^=  Zobrist::psq[demotion][pieceCount[demotion]-1]
 +                        ^ Zobrist::psq[pc][pieceCount[pc]];
 +
 +      // Update material
 +      st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc];
 +  }
  
    // Set capture piece
    st->capturedPiece = captured;
  
 +  // Add gating piece
 +  if (is_gating(m))
 +  {
 +      Square gate = gating_square(m);
 +      Piece gating_piece = make_piece(us, gating_type(m));
++
 +      put_piece(gating_piece, gate);
 +      remove_from_hand(gating_piece);
++
++      if (Eval::useNNUE)
++      {
++          // Add gating piece
++          dp.piece[dp.dirty_num] = gating_piece;
++          dp.from[dp.dirty_num] = SQ_NONE;
++          dp.to[dp.dirty_num] = gate;
++          dp.dirty_num++;
++      }
++
 +      st->gatesBB[us] ^= gate;
 +      k ^= Zobrist::psq[gating_piece][gate];
 +      st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]];
 +      st->nonPawnMaterial[us] += PieceValue[MG][gating_piece];
 +  }
 +
 +  // Remove gates
 +  if (gating())
 +  {
 +      if (is_ok(from) && (gates(us) & from))
 +          st->gatesBB[us] ^= from;
 +      if (type_of(m) == CASTLING && (gates(us) & to_sq(m)))
 +          st->gatesBB[us] ^= to_sq(m);
 +      if (gates(them) & to)
 +          st->gatesBB[them] ^= to;
 +      if (seirawan_gating() && !count_in_hand(us, ALL_PIECES) && !captures_to_hand())
 +          st->gatesBB[us] = 0;
 +  }
 +
    // Update the key with the final value
    st->key = k;
 -
    // Calculate checkers bitboard (if move gives check)
 -  st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
 +  st->checkersBB = givesCheck ? attackers_to(square<KING>(them), us) & pieces(us) : Bitboard(0);
  
    sideToMove = ~sideToMove;
  
@@@ -1643,17 -964,27 +1713,30 @@@ void Position::do_castling(Color us, Sq
  
    bool kingSide = to > from;
    rfrom = to; // Castling is encoded as "king captures friendly rook"
 -  rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
 -  to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
 +  to = make_square(kingSide ? castling_kingside_file() : castling_queenside_file(), castling_rank(us));
 +  rto = to + (kingSide ? WEST : EAST);
 +
-   // Remove both pieces first since squares could overlap in Chess960
 +  Piece castlingKingPiece = piece_on(Do ? from : to);
 +  Piece castlingRookPiece = piece_on(Do ? rfrom : rto);
+   if (Do && Eval::useNNUE)
+   {
+       auto& dp = st->dirtyPiece;
 -      dp.piece[0] = make_piece(us, KING);
++      dp.piece[0] = castlingKingPiece;
+       dp.from[0] = from;
+       dp.to[0] = to;
 -      dp.piece[1] = make_piece(us, ROOK);
++      dp.piece[1] = castlingRookPiece;
+       dp.from[1] = rfrom;
+       dp.to[1] = rto;
+       dp.dirty_num = 2;
+   }
+   // Remove both pieces first since squares could overlap in Chess960
    remove_piece(Do ? from : to);
    remove_piece(Do ? rfrom : rto);
 -  board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us
 -  put_piece(make_piece(us, KING), Do ? to : from);
 -  put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
 +  board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us
 +  put_piece(castlingKingPiece, Do ? to : from);
 +  put_piece(castlingRookPiece, Do ? rto : rfrom);
  }
  
  
diff --cc src/position.h
  #include <deque>
  #include <memory> // For std::unique_ptr
  #include <string>
 +#include <functional>
  
  #include "bitboard.h"
+ #include "evaluate.h"
  #include "types.h"
 +#include "variant.h"
  
+ #include "nnue/nnue_accumulator.h"
  
  /// StateInfo struct stores information needed to restore a Position object to
  /// its previous state when we retract a move. Whenever a move is made on the
@@@ -60,12 -54,11 +61,16 @@@ struct StateInfo 
    Bitboard   blockersForKing[COLOR_NB];
    Bitboard   pinners[COLOR_NB];
    Bitboard   checkSquares[PIECE_TYPE_NB];
 +  Bitboard   flippedPieces;
 +  bool       capturedpromoted;
 +  bool       shak;
 +  bool       bikjang;
 +  bool       pass;
    int        repetition;
+   // Used by NNUE
+   Eval::NNUE::Accumulator accumulator;
+   DirtyPiece dirtyPiece;
  };
  
  
@@@ -1127,75 -439,9 +1140,82 @@@ inline void Position::do_move(Move m, S
    do_move(m, newSt, gives_check(m));
  }
  
+ inline StateInfo* Position::state() const {
+   return st;
+ }
++// Variant-specific
++
 +inline int Position::count_in_hand(Color c, PieceType pt) const {
 +  return pieceCountInHand[c][pt];
 +}
 +
 +inline int Position::count_with_hand(Color c, PieceType pt) const {
 +  return pieceCount[make_piece(c, pt)] + pieceCountInHand[c][pt];
 +}
 +
 +inline bool Position::bikjang() const {
 +  return st->bikjang;
 +}
 +
 +inline Value Position::material_counting_result() const {
 +  auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); };
 +  int materialCount;
 +  Value result;
 +  switch (var->materialCounting)
 +  {
 +  case JANGGI_MATERIAL:
 +      materialCount =  weigth_count(ROOK, 13)
 +                     + weigth_count(JANGGI_CANNON, 7)
 +                     + weigth_count(HORSE, 5)
 +                     + weigth_count(JANGGI_ELEPHANT, 3)
 +                     + weigth_count(WAZIR, 3)
 +                     + weigth_count(SOLDIER, 2)
 +                     - 1;
 +      result = materialCount > 0 ? VALUE_MATE : -VALUE_MATE;
 +      break;
 +  case UNWEIGHTED_MATERIAL:
 +      result =  count(WHITE, ALL_PIECES) > count(BLACK, ALL_PIECES) ?  VALUE_MATE
 +              : count(WHITE, ALL_PIECES) < count(BLACK, ALL_PIECES) ? -VALUE_MATE
 +                                                                    :  VALUE_DRAW;
 +      break;
 +  case WHITE_DRAW_ODDS:
 +      result = VALUE_MATE;
 +      break;
 +  case BLACK_DRAW_ODDS:
 +      result = -VALUE_MATE;
 +      break;
 +  default:
 +      assert(false);
 +      result = VALUE_DRAW;
 +  }
 +  return sideToMove == WHITE ? result : -result;
 +}
 +
 +inline void Position::add_to_hand(Piece pc) {
 +  pieceCountInHand[color_of(pc)][type_of(pc)]++;
 +  pieceCountInHand[color_of(pc)][ALL_PIECES]++;
 +  psq += PSQT::psq[pc][SQ_NONE];
 +}
 +
 +inline void Position::remove_from_hand(Piece pc) {
 +  pieceCountInHand[color_of(pc)][type_of(pc)]--;
 +  pieceCountInHand[color_of(pc)][ALL_PIECES]--;
 +  psq -= PSQT::psq[pc][SQ_NONE];
 +}
 +
 +inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s) {
 +  assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]);
 +  put_piece(pc_drop, s, pc_drop != pc_hand, pc_drop != pc_hand ? pc_hand : NO_PIECE);
 +  remove_from_hand(pc_hand);
 +}
 +
 +inline void Position::undrop_piece(Piece pc_hand, Square s) {
 +  remove_piece(s);
 +  board[s] = NO_PIECE;
 +  add_to_hand(pc_hand);
 +  assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]);
 +}
 +
  #endif // #ifndef POSITION_H_INCLUDED
diff --cc src/psqt.cpp
@@@ -109,112 -103,19 +107,112 @@@ Score psq[PIECE_NB][SQUARE_NB + 1]
  // PSQT::init() initializes piece-square tables: the white halves of the tables are
  // copied from Bonus[] and PBonus[], adding the piece value, then the black halves of
  // the tables are initialized by flipping and changing the sign of the white scores.
 -void init() {
 +void init(const Variant* v) {
 +
 +  PieceType strongestPiece = NO_PIECE_TYPE;
 +  for (PieceType pt : v->pieceTypes)
 +      if (PieceValue[MG][pt] > PieceValue[MG][strongestPiece])
 +          strongestPiece = pt;
 +
 +  Value maxPromotion = VALUE_ZERO;
 +  for (PieceType pt : v->promotionPieceTypes)
 +      maxPromotion = std::max(maxPromotion, PieceValue[EG][pt]);
  
 -  for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING})
 +  for (PieceType pt = PAWN; pt <= KING; ++pt)
    {
 +      Piece pc = make_piece(WHITE, pt);
 +
        Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
  
 -      for (Square s = SQ_A1; s <= SQ_H8; ++s)
 +      // Consider promotion types in pawn score
 +      if (pt == PAWN)
 +          score -= make_score(0, (QueenValueEg - maxPromotion) / 100);
 +
 +      // Scale slider piece values with board size
 +      const PieceInfo* pi = pieceMap.find(pt)->second;
 +      bool isSlider = pi->sliderQuiet.size() || pi->sliderCapture.size() || pi->hopperQuiet.size() || pi->hopperCapture.size();
 +      bool isPawn = !isSlider && pi->stepsQuiet.size() && !std::any_of(pi->stepsQuiet.begin(), pi->stepsQuiet.end(), [](Direction d) { return d < SOUTH / 2; });
 +      bool isSlowLeaper = !isSlider && !std::any_of(pi->stepsQuiet.begin(), pi->stepsQuiet.end(), [](Direction d) { return dist(d) > 1; });
 +
 +      if (isSlider)
 +      {
 +          constexpr int lc = 5;
 +          constexpr int rm = 5;
 +          constexpr int r0 = rm + RANK_8;
 +          int r1 = rm + (v->maxRank + v->maxFile) / 2;
 +          int leaper = pi->stepsQuiet.size() + pi->stepsCapture.size();
 +          int slider = pi->sliderQuiet.size() + pi->sliderCapture.size() + pi->hopperQuiet.size() + pi->hopperCapture.size();
 +          score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider),
 +                             eg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider));
 +      }
 +
 +      // Increase leapers' value in makpong
 +      if (v->makpongRule)
 +      {
 +          if (std::any_of(pi->stepsCapture.begin(), pi->stepsCapture.end(), [](Direction d) { return dist(d) > 1; })
 +                  && !pi->lameLeaper)
 +              score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)),
 +                                 eg_value(score) * 4700 / (3500 + mg_value(score)));
 +      }
 +
 +      // For drop variants, halve the piece values
 +      if (v->capturesToHand)
 +          score = make_score(mg_value(score) * 3500 / (7000 + mg_value(score)),
 +                             eg_value(score) * 3500 / (7000 + eg_value(score)));
 +      else if (!v->checking)
 +          score = make_score(mg_value(score) * 2000 / (3500 + mg_value(score)),
 +                             eg_value(score) * 2200 / (3500 + eg_value(score)));
 +      else if (v->twoBoards)
 +          score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)),
 +                             eg_value(score) * 7000 / (7000 + eg_value(score)));
 +      else if (v->checkCounting)
 +          score = make_score(mg_value(score) * (40000 + mg_value(score)) / 41000,
 +                             eg_value(score) * (30000 + eg_value(score)) / 31000);
 +      else if (pt == strongestPiece)
 +              score += make_score(std::max(QueenValueMg - PieceValue[MG][pt], VALUE_ZERO) / 20,
 +                                  std::max(QueenValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20);
 +
 +      // For antichess variants, use negative piece values
 +      if (   v->extinctionValue == VALUE_MATE
 +          && v->extinctionPieceTypes.find(ALL_PIECES) != v->extinctionPieceTypes.end())
 +          score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->sliderCapture.size()));
 +
 +      // Determine pawn rank
 +      std::istringstream ss(v->startFen);
 +      unsigned char token;
 +      Rank rc = v->maxRank;
 +      Rank pawnRank = RANK_2;
 +      while ((ss >> token) && !isspace(token))
 +      {
 +          if (token == '/')
 +              --rc;
 +          else if (token == v->pieceToChar[PAWN] || token == v->pieceToChar[SHOGI_PAWN])
 +              pawnRank = rc;
 +      }
 +
 +      for (Square s = SQ_A1; s <= SQ_MAX; ++s)
        {
 -          File f = File(edge_distance(file_of(s)));
 -          psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
 -                                                     : Bonus[pc][rank_of(s)][f]);
 -          psq[~pc][flip_rank(s)] = -psq[pc][s];
 +          File f = std::max(File(edge_distance(file_of(s), v->maxFile)), FILE_A);
 +          Rank r = rank_of(s);
 +          psq[ pc][s] = score + (  pt == PAWN  ? PBonus[std::min(r, RANK_8)][std::min(file_of(s), FILE_H)]
-                                  : pt == KING  ? KingBonus[Utility::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand)
++                                 : pt == KING  ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand)
 +                                 : pt <= QUEEN ? Bonus[pc][std::min(r, RANK_8)][std::min(f, FILE_D)]
 +                                 : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)]
 +                                 : isSlider    ? make_score(5, 5) * (2 * f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile - 1)
 +                                 : isPawn      ? make_score(5, 5) * (2 * f - v->maxFile)
 +                                               : make_score(10, 10) * (1 + isSlowLeaper) * (f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile / 2));
 +          if (pt == SOLDIER && r < v->soldierPromotionRank)
 +              psq[pc][s] -= score * (v->soldierPromotionRank - r) / (4 + f);
 +          if (v->enclosingDrop == REVERSI)
 +          {
 +              if (f == FILE_A && (r == RANK_1 || r == v->maxRank))
 +                  psq[pc][s] += make_score(1000, 1000);
 +          }
 +          psq[~pc][rank_of(s) <= v->maxRank ? flip_rank(s, v->maxRank) : s] = -psq[pc][s];
        }
 +      // pieces in pocket
 +      psq[ pc][SQ_NONE] = score + make_score(35, 10) * (1 + !isSlider);
 +      psq[~pc][SQ_NONE] = -psq[pc][SQ_NONE];
    }
  }
  
diff --cc src/search.cpp
@@@ -226,28 -222,16 +224,30 @@@ void MainThread::search() 
    }
  
    Color us = rootPos.side_to_move();
 -  Time.init(Limits, us, rootPos.game_ply());
 +  Time.init(rootPos, Limits, us, rootPos.game_ply());
    TT.new_search();
  
+   Eval::NNUE::verify();
 -  if (rootMoves.empty())
 +  if (rootMoves.empty() || (Options["Protocol"] == "xboard" && rootPos.is_optional_game_end()))
    {
        rootMoves.emplace_back(MOVE_NONE);
 +      Value variantResult;
 +      Value result =  rootPos.is_game_end(variantResult) ? variantResult
 +                    : rootPos.checkers()                 ? rootPos.checkmate_value()
 +                                                         : rootPos.stalemate_value();
 +      if (Options["Protocol"] == "xboard")
 +      {
 +          // rotate MOVE_NONE to front (for optional game end)
 +          std::rotate(rootMoves.rbegin(), rootMoves.rbegin() + 1, rootMoves.rend());
 +          sync_cout << (  result == VALUE_DRAW ? "1/2-1/2 {Draw}"
 +                        : (rootPos.side_to_move() == BLACK ? -result : result) == VALUE_MATE ? "1-0 {White wins}"
 +                        : "0-1 {Black wins}")
 +                    << sync_endl;
 +      }
 +      else
        sync_cout << "info depth 0 score "
 -                << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)
 +                << UCI::value(result)
                  << sync_endl;
    }
    else
@@@ -433,7 -403,7 +433,7 @@@ void Thread::search() 
            if (rootDepth >= 4)
            {
                Value prev = rootMoves[pvIdx].previousScore;
-               delta = Value(19 * (1 + rootPos.captures_to_hand()));
 -              delta = Value(17);
++              delta = Value(17 * (1 + rootPos.captures_to_hand()));
                alpha = std::max(prev - delta,-VALUE_INFINITE);
                beta  = std::min(prev + delta, VALUE_INFINITE);
  
@@@ -856,15 -791,11 +853,11 @@@ namespace 
      else
      {
          if ((ss-1)->currentMove != MOVE_NULL)
-         {
-             int bonus = -(ss-1)->statScore / 512;
-             ss->staticEval = eval = evaluate(pos) + bonus;
-         }
+             ss->staticEval = eval = evaluate(pos);
          else
 -            ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo;
 +            ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::tempo_value(pos);
  
-         tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+         tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
      }
  
      // Step 7. Razoring (~1 Elo)
          &&  eval <= alpha - RazorMargin)
          return qsearch<NT>(pos, ss, alpha, beta);
  
-     improving =  (ss-2)->staticEval == VALUE_NONE ? (ss->staticEval > (ss-4)->staticEval
-               || (ss-4)->staticEval == VALUE_NONE) : ss->staticEval > (ss-2)->staticEval;
+     improving =  (ss-2)->staticEval == VALUE_NONE
+                ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE
+                : ss->staticEval > (ss-2)->staticEval;
  
 +    // Skip early pruning in case of mandatory capture
 +    if (pos.must_capture() && MoveList<CAPTURES>(pos).size())
 +        goto moves_loop;
 +
      // Step 8. Futility pruning: child node (~50 Elo)
      if (   !PvNode
-         &&  depth < 6
+         &&  depth < 8
 -        &&  eval - futility_margin(depth, improving) >= beta
 +        && !(   pos.extinction_value() == -VALUE_MATE
 +             && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end())
 +        && !(pos.capture_the_flag_piece() && !pos.checking_permitted())
 +        &&  eval - futility_margin(depth, improving) * (1 + pos.check_counting() + 2 * pos.must_capture()) >= beta
          &&  eval < VALUE_KNOWN_WIN) // Do not return unproven wins
          return eval;
  
      // Step 9. Null move search with verification search (~40 Elo)
      if (   !PvNode
          && (ss-1)->currentMove != MOVE_NULL
-         && (ss-1)->statScore < 23824
+         && (ss-1)->statScore < 22977
          &&  eval >= beta
          &&  eval >= ss->staticEval
-         &&  ss->staticEval >= beta - 33 * depth - 33 * improving + 112 * ttPv + 311
+         &&  ss->staticEval >= beta - 30 * depth - 28 * improving + 84 * ss->ttPv + 182
          && !excludedMove
          &&  pos.non_pawn_material(us)
 +        &&  pos.count<ALL_PIECES>(~us) != pos.count<PAWN>(~us)
 +        && !pos.flip_enclosed_pieces()
          && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
      {
          assert(eval - beta >= 0);
  
          // Null move dynamic reduction based on depth and value
-         Depth R = (737 - 150 * !pos.checking_permitted() + 77 * depth) / 246 + std::min(int(eval - beta) / 192, 3);
 -        Depth R = (982 + 85 * depth) / 256 + std::min(int(eval - beta) / 192, 3);
++        Depth R = (982 - 150 * !pos.checking_permitted() + 85 * depth) / 256 + std::min(int(eval - beta) / 192, 3);
  
          ss->currentMove = MOVE_NULL;
          ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
          }
      }
  
-     probcutBeta = beta + (176 + 20 * !!pos.capture_the_flag_piece()) * (1 + pos.check_counting() + (pos.extinction_value() != VALUE_NONE)) - 49 * improving;
 -    probCutBeta = beta + 176 - 49 * improving;
++    probCutBeta = beta + (176 + 20 * !!pos.capture_the_flag_piece()) * (1 + pos.check_counting() + (pos.extinction_value() != VALUE_NONE)) - 49 * improving;
  
      // Step 10. ProbCut (~10 Elo)
      // If we have a good enough capture and a reduced search returns a value
@@@ -1060,11 -986,15 +1060,15 @@@ moves_loop: // When in check, search st
                                    thisThread->rootMoves.begin() + thisThread->pvLast, move))
            continue;
  
+       // Check for legality
+       if (!rootNode && !pos.legal(move))
+           continue;
        ss->moveCount = ++moveCount;
  
 -      if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
 +      if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000 && Options["Protocol"] != "xboard")
            sync_cout << "info depth " << depth
 -                    << " currmove " << UCI::move(move, pos.is_chess960())
 +                    << " currmove " << UCI::move(pos, move)
                      << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
        if (PvNode)
            (ss+1)->pv = nullptr;
                    continue;
  
                // Futility pruning: parent node (~5 Elo)
-               if (   lmrDepth < 6
+               if (   lmrDepth < 7
                    && !ss->inCheck
 -                  && ss->staticEval + 283 + 170 * lmrDepth <= alpha
 -                  &&  (*contHist[0])[movedPiece][to_sq(move)]
 -                    + (*contHist[1])[movedPiece][to_sq(move)]
 -                    + (*contHist[3])[movedPiece][to_sq(move)]
 -                    + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 27376)
 +                  && !(   pos.extinction_value() == -VALUE_MATE
 +                       && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end())
-                   && ss->staticEval + (284 + 188 * lmrDepth) * (1 + pos.check_counting()) <= alpha
++                  && ss->staticEval + (283 + 170 * lmrDepth) * (1 + pos.check_counting()) <= alpha
 +                  &&  (*contHist[0])[history_slot(movedPiece)][to_sq(move)]
 +                    + (*contHist[1])[history_slot(movedPiece)][to_sq(move)]
 +                    + (*contHist[3])[history_slot(movedPiece)][to_sq(move)]
-                     + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 2 < 28388)
++                    + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 2 < 27376)
                    continue;
  
                // Prune moves with negative SEE (~20 Elo)
-               if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 17) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth)))
 -              if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
++              if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth)))
                    continue;
            }
 -          else
 +          else if (!pos.must_capture())
            {
                // Capture history based pruning when the move doesn't give check
                if (   !givesCheck
                    && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0)
                    continue;
  
-               // Futility pruning for captures
-               if (   !givesCheck
-                   && lmrDepth < 6
-                   && !(PvNode && abs(bestValue) < 2)
-                   && PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))]
-                   && !ss->inCheck
-                   && ss->staticEval + 267 + 391 * lmrDepth
-                      + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha)
-                   continue;
                // See based pruning
-               if (!pos.see_ge(move, Value(-202 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo)
 -              if (!pos.see_ge(move, Value(-221) * depth)) // (~25 Elo)
++              if (!pos.see_ge(move, Value(-221 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo)
                    continue;
            }
        }
                 && pos.non_pawn_material() <= 2 * RookValueMg)
            extension = 1;
  
-       // Castling extension
-       if (type_of(move) == CASTLING)
-           extension = 1;
+       // Late irreversible move extension
+       if (   move == ttMove
+           && pos.rule50_count() > 80
+           && (captureOrPromotion || type_of(movedPiece) == PAWN))
+           extension = 2;
  
 +      // Losing chess capture extension
 +      else if (    pos.must_capture()
 +               &&  pos.capture(move)
 +               &&  MoveList<CAPTURES>(pos).size() == 1)
 +          extension = 1;
 +
        // Add extension to new depth
        newDepth += extension;
  
                || moveCountPruning
                || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
                || cutNode
-               || thisThread->ttHitAverage < 415 * TtHitAverageResolution * TtHitAverageWindow / 1024)
 -              || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024))
++              || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024)
 +          && !(pos.must_capture() && MoveList<CAPTURES>(pos).size()))
        {
            Depth r = reduction(improving, depth, moveCount);
  
                // hence break make_move(). (~2 Elo)
                else if (    type_of(move) == NORMAL
                         && !pos.see_ge(reverse_move(move)))
-                   r -= 2 + ttPv - (type_of(movedPiece) == PAWN);
+                   r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN);
  
                ss->statScore =  thisThread->mainHistory[us][from_to(move)]
 -                             + (*contHist[0])[movedPiece][to_sq(move)]
 -                             + (*contHist[1])[movedPiece][to_sq(move)]
 -                             + (*contHist[3])[movedPiece][to_sq(move)]
 +                             + (*contHist[0])[history_slot(movedPiece)][to_sq(move)]
 +                             + (*contHist[1])[history_slot(movedPiece)][to_sq(move)]
 +                             + (*contHist[3])[history_slot(movedPiece)][to_sq(move)]
-                              - 4826;
+                              - 5287;
  
                // Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
-               if (ss->statScore >= -100 && (ss-1)->statScore < -112)
+               if (ss->statScore >= -106 && (ss-1)->statScore < -104)
                    r--;
  
-               else if ((ss-1)->statScore >= -125 && ss->statScore < -138)
+               else if ((ss-1)->statScore >= -119 && ss->statScore < -140)
                    r++;
  
                // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
-               r -= ss->statScore / (14615 - 4434 * pos.captures_to_hand());
 -              r -= ss->statScore / 14884;
++              r -= ss->statScore / (14884 - 4434 * pos.captures_to_hand());
            }
            else
            {
        ss->currentMove = move;
        ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
                                                                  [captureOrPromotion]
 -                                                                [pos.moved_piece(move)]
 +                                                                [history_slot(pos.moved_piece(move))]
                                                                  [to_sq(move)];
  
+       // CounterMove based pruning
+       if (  !captureOrPromotion
+           && moveCount
 -          && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold
 -          && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold)
++          && (*contHist[0])[history_slot(pos.moved_piece(move))][to_sq(move)] < CounterMovePruneThreshold
++          && (*contHist[1])[history_slot(pos.moved_piece(move))][to_sq(move)] < CounterMovePruneThreshold)
+           continue;
        // Make and search the move
        pos.do_move(move, st, givesCheck);
        value = -qsearch<NT>(pos, ss+1, -beta, -alpha, depth - 1);
diff --cc src/search.h
Simple merge
Simple merge
diff --cc src/thread.cpp
@@@ -216,11 -201,10 +201,10 @@@ void ThreadPool::start_thinking(Positio
        th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
        th->rootDepth = th->completedDepth = 0;
        th->rootMoves = rootMoves;
-       th->rootPos.set(pos.variant(), pos.fen(), pos.is_chess960(), &setupStates->back(), th);
 -      th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
++      th->rootPos.set(pos.variant(), pos.fen(), pos.is_chess960(), &th->rootState, th);
+       th->rootState = setupStates->back();
    }
  
-   setupStates->back() = tmp;
    main()->start_searching();
  }
  
diff --cc src/thread.h
Simple merge
diff --cc src/timeman.cpp
Simple merge
diff --cc src/timeman.h
Simple merge
diff --cc src/tt.cpp
@@@ -37,20 -35,21 +35,21 @@@ void TTEntry::save(Key k, Value v, boo
  
    // Preserve any existing move for the same position
    if (m || (uint16_t)k != key16)
 -      move16 = (uint16_t)m;
 +      move32 = (uint32_t)m;
  
-   // Overwrite less valuable entries
-   if ((uint16_t)k != key16
-       || d - DEPTH_OFFSET > depth8 - 4
-       || b == BOUND_EXACT)
+   // Overwrite less valuable entries (cheapest checks first)
+   if (b == BOUND_EXACT
+       || (uint16_t)k != key16
+       || d - DEPTH_OFFSET > depth8 - 4)
    {
-       assert(d >= DEPTH_OFFSET);
+       assert(d > DEPTH_OFFSET);
+       assert(d < 256 + DEPTH_OFFSET);
  
        key16     = (uint16_t)k;
+       depth8    = (uint8_t)(d - DEPTH_OFFSET);
+       genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
        value16   = (int16_t)v;
        eval16    = (int16_t)ev;
-       genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
-       depth8    = (uint8_t)(d - DEPTH_OFFSET);
    }
  }
  
diff --cc src/tt.h
+++ b/src/tt.h
  #include "misc.h"
  #include "types.h"
  
 -/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
 +/// TTEntry struct is the 12 bytes transposition table entry, defined as below:
  ///
- /// move       32 bit
  /// key        16 bit
- /// value      16 bit
- /// eval value 16 bit
+ /// depth       8 bit
  /// generation  5 bit
  /// pv node     1 bit
  /// bound type  2 bit
- /// depth       8 bit
 -/// move       16 bit
++/// move       32 bit (official SF: 16 bit)
+ /// value      16 bit
+ /// eval value 16 bit
  
  struct TTEntry {
  
  private:
    friend class TranspositionTable;
  
-   uint32_t move32;
    uint16_t key16;
+   uint8_t  depth8;
+   uint8_t  genBound8;
 -  uint16_t move16;
++  uint32_t move32;
    int16_t  value16;
    int16_t  eval16;
-   uint8_t  genBound8;
-   uint8_t  depth8;
  };
  
  
@@@ -69,13 -67,13 +67,13 @@@ class TranspositionTable 
  
    struct Cluster {
      TTEntry entry[ClusterSize];
 -    char padding[2]; // Pad to 32 bytes
 +    char padding[4]; // Pad to 64 bytes
    };
  
 -  static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
 +  static_assert(sizeof(Cluster) == 64, "Unexpected Cluster size");
  
  public:
-  ~TranspositionTable() { aligned_ttmem_free(mem); }
+  ~TranspositionTable() { aligned_large_pages_free(table); }
    void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound
    TTEntry* probe(const Key key, bool& found) const;
    int hashfull() const;
diff --cc src/types.h
@@@ -494,17 -229,10 +493,18 @@@ enum Square : int 
    SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6,
    SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7,
    SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,
 +#endif
    SQ_NONE,
  
+   SQUARE_ZERO = 0,
 -  SQUARE_NB   = 64
 +#ifdef LARGEBOARDS
 +  SQUARE_NB = 120,
 +  SQUARE_BIT_MASK = 127,
 +#else
 +  SQUARE_NB = 64,
 +  SQUARE_BIT_MASK = 63,
 +#endif
 +  SQ_MAX = SQUARE_NB - 1
  };
  
  enum Direction : int {
@@@ -534,15 -252,24 +534,30 @@@ enum File : int 
  };
  
  enum Rank : int {
 -  RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
 +#ifdef LARGEBOARDS
 +  RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10,
 +#else
 +  RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8,
 +#endif
 +  RANK_NB,
 +  RANK_MAX = RANK_NB - 1
  };
  
+ // Keep track of what a move changes on the board (used by NNUE)
+ struct DirtyPiece {
+   // Number of changed pieces
+   int dirty_num;
+   // Max 3 pieces can change in one move. A promotion with capture moves
+   // both the pawn and the captured piece to SQ_NONE and the piece promoted
+   // to from SQ_NONE to the capture square.
+   Piece piece[3];
+   // From and to squares, which may be SQ_NONE
+   Square from[3];
+   Square to[3];
+ };
  
  /// Score enum stores a middlegame and an endgame value in a single integer (enum).
  /// The least significant 16 bits are used to store the middlegame value and the
@@@ -567,20 -294,11 +582,20 @@@ inline Value mg_value(Score s) 
    return Value(mg.s);
  }
  
 +#define ENABLE_BIT_OPERATORS_ON(T)                                    \
 +inline T operator~ (T d) { return (T)~(int)d; }                       \
 +inline T operator| (T d1, T d2) { return (T)((int)d1 | (int)d2); }        \
 +inline T operator& (T d1, T d2) { return (T)((int)d1 & (int)d2); }        \
 +inline T operator^ (T d1, T d2) { return (T)((int)d1 ^ (int)d2); }        \
 +inline T& operator|= (T& d1, T d2) { return (T&)((int&)d1 |= (int)d2); }  \
 +inline T& operator&= (T& d1, T d2) { return (T&)((int&)d1 &= (int)d2); }  \
 +inline T& operator^= (T& d1, T d2) { return (T&)((int&)d1 ^= (int)d2); }
 +
  #define ENABLE_BASE_OPERATORS_ON(T)                                \
- constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
- constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
+ constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); }    \
+ constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); }    \
  constexpr T operator-(T d) { return T(-int(d)); }                  \
- inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; }         \
+ inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; }       \
  inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
  
  #define ENABLE_INCR_OPERATORS_ON(T)                                \
diff --cc src/uci.cpp
@@@ -77,6 -76,20 +75,20 @@@ namespace 
      }
    }
  
+   // trace_eval() prints the evaluation for the current position, consistent with the UCI
+   // options set so far.
+   void trace_eval(Position& pos) {
+     StateListPtr states(new std::deque<StateInfo>(1));
+     Position p;
 -    p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
++    p.set(pos.variant(), pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
+     Eval::NNUE::verify();
+     sync_cout << "\n" << Eval::trace(p) << sync_endl;
+   }
  
    // setoption() is called when engine receives the "setoption" UCI command. The
    // function updates the UCI option ("name") to the given value ("value").
@@@ -321,10 -273,8 +333,10 @@@ void UCI::loop(int argc, char* argv[]) 
        else if (token == "flip")     pos.flip();
        else if (token == "bench")    bench(pos, is, states);
        else if (token == "d")        sync_cout << pos << sync_endl;
-       else if (token == "eval")     sync_cout << Eval::trace(pos) << sync_endl;
+       else if (token == "eval")     trace_eval(pos);
        else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
 +      else if (token == "load")     { load(is); argc = 1; } // continue reading stdin
 +      else if (token == "check")    check(is);
        else
            sync_cout << "Unknown command: " << cmd << sync_endl;
  
diff --cc src/uci.h
Simple merge
  #include <cassert>
  #include <ostream>
  #include <sstream>
 +#include <iostream>
  
+ #include "evaluate.h"
  #include "misc.h"
 +#include "piece.h"
  #include "search.h"
  #include "thread.h"
  #include "tt.h"
@@@ -56,74 -41,9 +55,81 @@@ void on_hash_size(const Option& o) { TT
  void on_logger(const Option& o) { start_logger(o); }
  void on_threads(const Option& o) { Threads.set(size_t(o)); }
  void on_tb_path(const Option& o) { Tablebases::init(o); }
++
+ void on_use_NNUE(const Option& ) { Eval::NNUE::init(); }
+ void on_eval_file(const Option& ) { Eval::NNUE::init(); }
 +void on_variant_path(const Option& o) { variants.parse<false>(o); Options["UCI_Variant"].set_combo(variants.get_keys()); }
 +void on_variant_change(const Option &o) {
++    // Re-initialize NNUE
++    Eval::NNUE::init();
++
 +    const Variant* v = variants.find(o)->second;
 +    PSQT::init(v);
 +    // Do not send setup command for known variants
 +    if (standard_variants.find(o) != standard_variants.end())
 +        return;
 +    int pocketsize = v->pieceDrops ? (v->pocketSize ? v->pocketSize : v->pieceTypes.size()) : 0;
 +    if (Options["Protocol"] == "xboard")
 +    {
 +        // Send setup command
 +        sync_cout << "setup (" << v->pieceToCharTable << ") "
 +                  << v->maxFile + 1 << "x" << v->maxRank + 1
 +                  << "+" << pocketsize << "_" << v->variantTemplate
 +                  << " " << v->startFen
 +                  << sync_endl;
 +        // Send piece command with Betza notation
 +        // https://www.gnu.org/software/xboard/Betza.html
 +        for (PieceType pt : v->pieceTypes)
 +        {
 +            string suffix =   pt == PAWN && v->doubleStep     ? "ifmnD"
 +                            : pt == KING && v->cambodianMoves ? "ismN"
 +                            : pt == FERS && v->cambodianMoves ? "ifD"
 +                                                              : "";
 +            // Janggi palace moves
 +            if (v->diagonalLines)
 +            {
 +                PieceType pt2 = pt == KING ? v->kingType : pt;
 +                if (pt2 == WAZIR)
 +                    suffix += "F";
 +                else if (pt2 == SOLDIER)
 +                    suffix += "fF";
 +                else if (pt2 == ROOK)
 +                    suffix += "B";
 +                else if (pt2 == JANGGI_CANNON)
 +                    suffix += "pB";
 +            }
 +            // Castling
 +            if (pt == KING && v->castling)
 +                 suffix += "O" + std::to_string((v->castlingKingsideFile - v->castlingQueensideFile) / 2);
 +            // Drop region
 +            if (v->pieceDrops)
 +            {
 +                if (pt == PAWN && !v->firstRankPawnDrops)
 +                    suffix += "j";
 +                else if (pt == SHOGI_PAWN && !v->shogiDoubledPawn)
 +                    suffix += "f";
 +                else if (pt == BISHOP && v->dropOppositeColoredBishop)
 +                    suffix += "s";
 +                suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops ? v->promotionRank : v->maxRank + 1);
 +            }
 +            sync_cout << "piece " << v->pieceToChar[pt] << "& " << pieceMap.find(pt == KING ? v->kingType : pt)->second->betza << suffix << sync_endl;
 +            PieceType promType = v->promotedPieceType[pt];
 +            if (promType)
 +                sync_cout << "piece +" << v->pieceToChar[pt] << "& " << pieceMap.find(promType)->second->betza << sync_endl;
 +        }
 +    }
 +    else
 +        sync_cout << "info string variant " << (std::string)o
 +                << " files " << v->maxFile + 1
 +                << " ranks " << v->maxRank + 1
 +                << " pocket " << pocketsize
 +                << " template " << v->variantTemplate
 +                << " startpos " << v->startFen
 +                << sync_endl;
 +}
 +
 +
  /// Our case insensitive less() function as required by UCI protocol
  bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
  
@@@ -161,8 -79,8 +167,14 @@@ void init(OptionsMap& o) 
    o["SyzygyProbeDepth"]      << Option(1, 1, 100);
    o["Syzygy50MoveRule"]      << Option(true);
    o["SyzygyProbeLimit"]      << Option(7, 0, 7);
++#ifdef USE_NNUE
+   o["Use NNUE"]              << Option(true, on_use_NNUE);
++#else
++  o["Use NNUE"]              << Option(false, on_use_NNUE);
++#endif
+   o["EvalFile"]              << Option(EvalFileDefaultName, on_eval_file);
 +  o["TsumeMode"]             << Option(false);
 +  o["VariantPath"]           << Option("<empty>", on_variant_path);
  }
  
  
Simple merge
index 2d370d5,0000000..6a7ed3e
mode 100644,000000..100644
--- /dev/null
@@@ -1,272 -1,0 +1,276 @@@
 +<h2 align="center">ffish.js</h2>
 +
 +<p align="center">
 +<a href="https://img.shields.io/badge/-ffish.js-green"><img src="https://img.shields.io/badge/-ffish.js-green" alt="Package"></a>
 +<a href="https://npmcharts.com/compare/ffish?minimal=true"><img src="https://img.shields.io/npm/dm/ffish.svg?sanitize=true" alt="Downloads"></a>
 +<a href="https://www.npmjs.com/package/ffish"><img src="https://img.shields.io/npm/v/ffish.svg?sanitize=true" alt="Version"></a>
 +</p>
 +
 +<p align="center">
 +<a href="https://img.shields.io/badge/-ffish--es6.js-green"><img src="https://img.shields.io/badge/-ffish--es6.js-green" alt="Package-ES6"></a>
 +<a href="https://npmcharts.com/compare/ffish-es6?minimal=true"><img src="https://img.shields.io/npm/dm/ffish-es6.svg?sanitize=true" alt="Downloads-ES6"></a>
 +<a href="https://www.npmjs.com/package/ffish-es6"><img src="https://img.shields.io/npm/v/ffish-es6.svg?sanitize=true" alt="Version-ES6"></a>
 +</p>
 +
 +
 +The package **ffish.js** is a high performance WebAssembly chess variant library based on [_Fairy-Stockfish_](https://github.com/ianfab/Fairy-Stockfish).
 +
 +It is available as a [standard module](https://www.npmjs.com/package/ffish), as an [ES6 module](https://www.npmjs.com/package/ffish-es6) and aims to have a syntax similar to [python-chess](https://python-chess.readthedocs.io/en/latest/index.html).
 +
 +## Install instructions
 +
 +### Standard module
 +
 +```bash
 +npm install ffish
 +```
 +
 +### ES6 module
 +```bash
 +npm install ffish-es6
 +```
 +
 +## Examples
 +
 +Load the API in JavaScript:
 +
 +### Standard module
 +
 +```javascript
 +const ffish = require('ffish');
 +```
 +
 +### ES6 module
 +
 +```javascript
 +import Module from 'ffish-es6';
 +let ffish = null;
 +
 +new Module().then(loadedModule => {
 +    ffish = loadedModule;
 +    console.log(`initialized ${ffish} ${loadedModule}`);
 +    }
 +});
 +```
 +
 +### Available variants
 +
 +Show all available variants supported by _Fairy-Stockfish_ and **ffish.js**.
 +
 +```javascript
 +ffish.variants()
 +```
 +```
 +>> 3check 5check ai-wok almost amazon antichess armageddon asean ataxx breakthrough bughouse cambodian\
 +capablanca capahouse caparandom centaur chancellor chess chessgi chigorin clobber clobber10 codrus courier\
 +crazyhouse dobutsu embassy euroshogi extinction fairy fischerandom gardner giveaway gorogoro gothic grand\
 +hoppelpoppel horde janggi janggicasual janggimodern janggitraditional janus jesonmor judkins karouk kinglet\
 +kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk manchu micro mini minishogi\
 +minixiangqi modern newzealand nocastle normal placement pocketknight racingkings seirawan shako shatar\
 +shatranj shogi shouse sittuyin suicide supply threekings xiangqi
 +```
 +
 +## Custom variants
 +
 +Fairy-Stockfish also allows defining custom variants by loading a configuration file.
 +See e.g. the confiugration for connect4, tictactoe or janggihouse in [variants.ini](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini).
 +```javascript
 +fs = require('fs');
 +let configFilePath = './variants.ini';
 + fs.readFile(configFilePath, 'utf8', function (err,data) {
 +   if (err) {
 +     return console.log(err);
 +   }
 +   ffish.loadVariantConfig(data)
 +   let board = new ffish.Board("tictactoe");
 +   board.delete();
 + });
 +```
 +
 +### Board object
 +
 +Create a new variant board from its default starting position.
 +The event `onRuntimeInitialized` ensures that the wasm file was properly loaded.
 +
 +```javascript
 +ffish['onRuntimeInitialized'] = () => {
 +  let board = new ffish.Board("chess");
 +}
 +```
 +
 +Set a custom fen position:
 +```javascript
 +board.setFen("rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3");
 +```
 +
 +Alternatively, you can initialize a board with a custom FEN directly:
 +```javascript
 +let board2 = new ffish.Board("crazyhouse", "rnb1kb1r/ppp2ppp/4pn2/8/3P4/2N2Q2/PPP2PPP/R1B1KB1R/QPnp b KQkq - 0 6");
 +```
 +
 +Add a new move:
 +```javascript
 +board.push("g2g4");
 +```
 +
 +Generate all legal moves in UCI and SAN notation:
 +```javascript
 +let legalMoves = board.legalMoves().split(" ");
 +let legalMovesSan = board.legalMovesSan().split(" ");
 +
 +for (var i = 0; i < legalMovesSan.length; i++) {
 +    console.log(`${i}: ${legalMoves[i]}, ${legalMoves})
 +}
 +```
 +
 +Unfortunately, it is impossible for Emscripten to call the destructors on C++ object.
 +Therefore, you need to call `.delete()` to free the heap memory of an object.
 +```javascript
 +board.delete();
 +```
 +
 +## PGN parsing
 +
 +Read a string from a file and parse it as a single PGN game.
 +
 +```javascript
 +fs = require('fs');
 +let pgnFilePath = "data/pgn/kasparov-deep-blue-1997.pgn"
 +
 +fs.readFile(pgnFilePath, 'utf8', function (err,data) {
 +  if (err) {
 +    return console.log(err);
 +  }
 +  game = ffish.readGamePGN(data);
 +  console.log(game.headerKeys());
 +  console.log(game.headers("White"));
 +  console.log(game.mainlineMoves())
 +
 +  let board = new ffish.Board(game.headers("Variant").toLowerCase());
 +  board.pushMoves(game.mainlineMoves());
 +
 +  let finalFen = board.fen();
 +  board.delete();
 +  game.delete();
 +}
 +```
 +
 +## Remaining features
 +
 +For an example of each available function see [test.js](https://github.com/ianfab/Fairy-Stockfish/blob/master/tests/js/test.js).
 +
 +## Build instuctions
 +
 +It is built using emscripten/Embind from C++ source code.
 +
 +* https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
 +
 +
 +If you want to disable variants with a board greater than 8x8,
 + you can remove the flags `-s TOTAL_MEMORY=67108864 -s
 +  ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648
 +   -DLARGEBOARDS -DPRECOMPUTED_MAGICS`.
 +
 +The pre-compiled wasm binary is built with `-DLARGEBOARDS`.
 +
 +It is recommended to set `-s ASSERTIONS=1 -s SAFE_HEAP=1` before running tests.
 +
 +
 +### Compile as standard module
 +
 +```bash
 +cd Fairy-Stockfish/src
 +```
 +```bash
- emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNO_THREADS \
++emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNNUE_EMBEDDING_OFF -DNO_THREADS \
 + -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 \
 + -s ASSERTIONS=0 -s SAFE_HEAP=0 \
 + -DNO_THREADS -DLARGEBOARDS -DPRECOMPUTED_MAGICS \
 +ffishjs.cpp \
 +benchmark.cpp \
 +bitbase.cpp \
 +bitboard.cpp \
 +endgame.cpp \
 +evaluate.cpp \
 +material.cpp \
 +misc.cpp \
 +movegen.cpp \
 +movepick.cpp \
 +parser.cpp \
 +partner.cpp \
 +pawns.cpp \
 +piece.cpp \
 +position.cpp \
 +psqt.cpp \
 +search.cpp \
 +thread.cpp \
 +timeman.cpp \
 +tt.cpp \
 +uci.cpp \
- syzygy/tbprobe.cpp \
 +ucioption.cpp \
 +variant.cpp \
 +xboard.cpp \
++nnue/*.cpp \
++nnue/features/*.cpp \
++syzygy/*.cpp \
 +-o ../tests/js/ffish.js
 +```
 +
 +### Compile as ES6/ES2015 module
 +
 +Some environments such as [vue-js](https://vuejs.org/) may require the library to be exported
 +  as a ES6/ES2015 module.
 +
 +```bash
 +cd Fairy-Stockfish/src
 +```
 +```bash
- emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNO_THREADS \
++emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNNUE_EMBEDDING_OFF -DNO_THREADS \
 + -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 \
 + -s ASSERTIONS=0 -s SAFE_HEAP=0 \
 + -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 \
 +ffishjs.cpp \
 +benchmark.cpp \
 +bitbase.cpp \
 +bitboard.cpp \
 +endgame.cpp \
 +evaluate.cpp \
 +material.cpp \
 +misc.cpp \
 +movegen.cpp \
 +movepick.cpp \
 +parser.cpp \
 +partner.cpp \
 +pawns.cpp \
 +piece.cpp \
 +position.cpp \
 +psqt.cpp \
 +search.cpp \
 +thread.cpp \
 +timeman.cpp \
 +tt.cpp \
 +uci.cpp \
- syzygy/tbprobe.cpp \
 +ucioption.cpp \
 +variant.cpp \
 +xboard.cpp \
++nnue/*.cpp \
++nnue/features/*.cpp \
++syzygy/*.cpp \
 +-o ../tests/js/ffish.js
 +```
 +
 +Reference: [emscripten/#10114](https://github.com/emscripten-core/emscripten/issues/10114)
 +
 +## Instructions to run the tests
 +```bash
 +npm install
 +npm test
 +```
 +
 +## Instructions to run the example server
 +```bash
 +npm install
 +```
 +```bash
 +node index.js
 +```