Merge official-stockfish/master
authorFabian Fichter <ianfab@users.noreply.github.com>
Sat, 26 Jun 2021 18:26:01 +0000 (20:26 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sat, 26 Jun 2021 20:09:09 +0000 (22:09 +0200)
1  2 
src/misc.h
src/nnue/features/half_kp.cpp
src/nnue/features/half_kp.h
src/nnue/features/half_kp_shogi.cpp
src/nnue/features/half_kp_shogi.h
src/nnue/features/half_kp_variants.cpp
src/nnue/features/half_kp_variants.h
src/nnue/nnue_architecture.h
src/nnue/nnue_feature_transformer.h
src/position.cpp
src/types.h

diff --cc src/misc.h
Simple merge
  
  namespace Stockfish::Eval::NNUE::Features {
  
 +  // Map square to numbering on 8x8 board
 +  constexpr Square to_chess_square(Square s) {
 +    return Square(s - rank_of(s) * (FILE_MAX - FILE_H));
 +  }
 +
    // Orient a square according to perspective (rotates by 180 for black)
-   inline Square orient(Color perspective, Square s) {
 -  inline Square HalfKP::orient(Color perspective, Square s) {
 -    return Square(int(s) ^ (bool(perspective) * 63));
++  inline Square HalfKPChess::orient(Color perspective, Square s) {
 +    return Square(int(to_chess_square(s)) ^ (bool(perspective) * 63));
    }
  
    // Index of a feature for a given king position and another piece on some square
-   inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) {
 -  inline IndexType HalfKP::make_index(Color perspective, Square s, Piece pc, Square ksq) {
++  inline IndexType HalfKPChess::make_index(Color perspective, Square s, Piece pc, Square ksq) {
      return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
    }
  
    // Get a list of indices for active features
-   template <Side AssociatedKing>
-   void HalfKPChess<AssociatedKing>::append_active_indices(
-       const Position& pos, Color perspective, IndexList* active) {
-     Square ksq = orient(perspective, pos.square<KING>(perspective));
-     Bitboard bb = pos.pieces() & ~pos.pieces(KING);
 -  void HalfKP::append_active_indices(
++  void HalfKPChess::append_active_indices(
+     const Position& pos,
+     Color perspective,
+     ValueListInserter<IndexType> active
+   ) {
 -    Square ksq = orient(perspective, pos.square<KING>(perspective));
 -    Bitboard bb = pos.pieces() & ~pos.pieces(KING);
++    Square ksq = orient(perspective, pos.square(perspective, pos.nnue_king()));
++    Bitboard bb = pos.pieces() & ~pos.pieces(pos.nnue_king());
      while (bb)
      {
        Square s = pop_lsb(bb);
  
    // append_changed_indices() : get a list of indices for recently changed features
  
-   // IMPORTANT: The `pos` in this function is pretty much useless as it
-   // is not always the position the features are updated to. The feature
-   // transformer code right now can update multiple accumulators per move,
-   // but since Stockfish only keeps the full state of the current leaf
-   // search position it is not possible to always pass here the position for
-   // which the accumulator is being updated. Therefore the only thing that
-   // can be reliably extracted from `pos` is the king square for the king
-   // of the `perspective` color (note: not even the other king's square will
-   // match reality in all cases, this is also the reason why `dp` is passed
-   // as a parameter and not extracted from pos.state()). This is of particular
-   // problem for future nets with other feature sets, where updating the active
-   // feature might require more information from the intermediate positions. In
-   // this case the only easy solution is to remove the multiple updates from
-   // the feature transformer update code and only update the accumulator for
-   // the current leaf position (the position after the move).
-   template <Side AssociatedKing>
-   void HalfKPChess<AssociatedKing>::append_changed_indices(
-       const Position& pos, const DirtyPiece& dp, Color perspective,
-       IndexList* removed, IndexList* added) {
-     Square ksq = orient(perspective, pos.square<KING>(perspective));
 -  void HalfKP::append_changed_indices(
++  void HalfKPChess::append_changed_indices(
+     Square ksq,
+     StateInfo* st,
+     Color perspective,
+     ValueListInserter<IndexType> removed,
 -    ValueListInserter<IndexType> added
++    ValueListInserter<IndexType> added,
++    const Position& pos
+   ) {
+     const auto& dp = st->dirtyPiece;
+     Square oriented_ksq = orient(perspective, ksq);
      for (int i = 0; i < dp.dirty_num; ++i) {
        Piece pc = dp.piece[i];
--      if (type_of(pc) == KING) continue;
++      if (type_of(pc) == pos.nnue_king()) continue;
        if (dp.from[i] != SQ_NONE)
-         removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
+         removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
        if (dp.to[i] != SQ_NONE)
-         added->push_back(make_index(perspective, dp.to[i], pc, ksq));
+         added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq));
      }
    }
  
-   template class HalfKPChess<Side::Friend>;
 -  int HalfKP::update_cost(StateInfo* st) {
++  int HalfKPChess::update_cost(StateInfo* st) {
+     return st->dirtyPiece.dirty_num;
+   }
 -  int HalfKP::refresh_cost(const Position& pos) {
++  int HalfKPChess::refresh_cost(const Position& pos) {
+     return pos.count<ALL_PIECES>() - 2;
+   }
 -  bool HalfKP::requires_refresh(StateInfo* st, Color perspective) {
 -    return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
++  bool HalfKPChess::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
++    return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king());
+   }
  
  }  // namespace Stockfish::Eval::NNUE::Features
@@@ -28,30 -34,75 +34,117 @@@ namespace Stockfish::Eval::NNUE::Featur
  
    // Feature HalfKP: Combination of the position of own king
    // and the position of pieces other than kings
-   template <Side AssociatedKing>
 -  class HalfKP {
 +  class HalfKPChess {
  
+     // unique number for each piece type on each square
+     enum {
+       PS_NONE     =  0,
+       PS_W_PAWN   =  1,
 -      PS_B_PAWN   =  1 * SQUARE_NB + 1,
 -      PS_W_KNIGHT =  2 * SQUARE_NB + 1,
 -      PS_B_KNIGHT =  3 * SQUARE_NB + 1,
 -      PS_W_BISHOP =  4 * SQUARE_NB + 1,
 -      PS_B_BISHOP =  5 * SQUARE_NB + 1,
 -      PS_W_ROOK   =  6 * SQUARE_NB + 1,
 -      PS_B_ROOK   =  7 * SQUARE_NB + 1,
 -      PS_W_QUEEN  =  8 * SQUARE_NB + 1,
 -      PS_B_QUEEN  =  9 * SQUARE_NB + 1,
 -      PS_NB = 10 * SQUARE_NB + 1
++      PS_B_PAWN   =  1 * SQUARE_NB_CHESS + 1,
++      PS_W_KNIGHT =  2 * SQUARE_NB_CHESS + 1,
++      PS_B_KNIGHT =  3 * SQUARE_NB_CHESS + 1,
++      PS_W_BISHOP =  4 * SQUARE_NB_CHESS + 1,
++      PS_B_BISHOP =  5 * SQUARE_NB_CHESS + 1,
++      PS_W_ROOK   =  6 * SQUARE_NB_CHESS + 1,
++      PS_B_ROOK   =  7 * SQUARE_NB_CHESS + 1,
++      PS_W_QUEEN  =  8 * SQUARE_NB_CHESS + 1,
++      PS_B_QUEEN  =  9 * SQUARE_NB_CHESS + 1,
++      PS_NB       = 10 * SQUARE_NB_CHESS + 1,
+     };
 -    static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
++    static constexpr uint32_t PieceSquareIndex[COLOR_NB][PIECE_NB] = {
+       // convention: W - us, B - them
+       // viewed from other side, W and B are reversed
 -      { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE,
 -        PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE },
 -      { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE,
 -        PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE }
++      {
++        PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++        PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++        PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++        PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++        PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++        PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++      },
++
++      {
++        PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++        PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++        PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++        PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++        PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++        PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++      }
+     };
++    // Check that the fragile array definition is correct
++    static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, PAWN)] == PS_W_PAWN);
++    static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, KING)] == PS_NONE);
++    static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, PAWN)] == PS_B_PAWN);
++    static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, KING)] == PS_NONE);
++
+     // Orient a square according to perspective (rotates by 180 for black)
+     static Square orient(Color perspective, Square s);
+     // Index of a feature for a given king position and another piece on some square
+     static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
     public:
      // Feature name
      static constexpr const char* Name = "HalfKP(Friend)";
      // Hash value embedded in the evaluation file
-     static constexpr std::uint32_t HashValue =
-         0x5D69D5B9u ^ (AssociatedKing == Side::Friend);
+     static constexpr std::uint32_t HashValue = 0x5D69D5B8u;
      // Number of feature dimensions
      static constexpr IndexType Dimensions =
 -        static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB);
 +        static_cast<IndexType>(SQUARE_NB_CHESS) * static_cast<IndexType>(PS_NB);
-     // Maximum number of simultaneously active features
-     static constexpr IndexType MaxActiveDimensions = 30; // Kings don't count
-     // Trigger for full calculation instead of difference calculation
-     static constexpr TriggerEvent RefreshTrigger = TriggerEvent::FriendKingMoved;
+     // Maximum number of simultaneously active features. 30 because kins are not included.
+     static constexpr IndexType MaxActiveDimensions = 30;
  
      // Get a list of indices for active features
-     static void append_active_indices(const Position& pos, Color perspective,
-                                       IndexList* active);
+     static void append_active_indices(
+       const Position& pos,
+       Color perspective,
+       ValueListInserter<IndexType> active);
  
      // Get a list of indices for recently changed features
-     static void append_changed_indices(const Position& pos, const DirtyPiece& dp, Color perspective,
-                                        IndexList* removed, IndexList* added);
+     static void append_changed_indices(
+       Square ksq,
+       StateInfo* st,
+       Color perspective,
+       ValueListInserter<IndexType> removed,
 -      ValueListInserter<IndexType> added);
++      ValueListInserter<IndexType> added,
++      const Position& pos);
+     // Returns the cost of updating one perspective, the most costly one.
+     // Assumes no refresh needed.
+     static int update_cost(StateInfo* st);
+     static int refresh_cost(const Position& pos);
+     // Returns whether the change stored in this StateInfo means that
+     // a full accumulator refresh is required.
 -    static bool requires_refresh(StateInfo* st, Color perspective);
++    static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos);
    };
  
  }  // namespace Stockfish::Eval::NNUE::Features
index fbadfe5,0000000..0d7e2fa
mode 100644,000000..100644
--- /dev/null
@@@ -1,98 -1,0 +1,116 @@@
 +/*
 +  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 +  Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
 +
 +  Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +*/
 +
 +//Definition of input features HalfKP of NNUE evaluation function
 +
 +#include "half_kp_shogi.h"
- #include "index_list.h"
++
++#include "../../position.h"
 +
 +namespace Stockfish::Eval::NNUE::Features {
 +
 +  constexpr Square rotate(Square s) {
 +    return Square(SQUARE_NB_SHOGI - 1 - int(s));
 +  }
 +
 +  constexpr Square to_shogi_square(Square s) {
 +    return Square((8 - s % 12) * 9 + 8 - s / 12);
 +  }
 +
 +  // Orient a square according to perspective (rotates by 180 for black)
-   inline Square orient(Color perspective, Square s) {
++  inline Square HalfKPShogi::orient(Color perspective, Square s) {
 +    return perspective == WHITE ? to_shogi_square(s) : rotate(to_shogi_square(s));
 +  }
 +
 +  // Index of a feature for a given king position and another piece on some square
-   inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) {
++  inline IndexType HalfKPShogi::make_index(Color perspective, Square s, Piece pc, Square ksq) {
 +    return IndexType(orient(perspective, s) + PieceSquareIndexShogi[perspective][pc] + SHOGI_PS_END * ksq);
 +  }
 +
 +  // Index of a feature for a given king position and hand piece
-   inline IndexType make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq) {
++  inline IndexType HalfKPShogi::make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq) {
 +    Color color = (c == perspective) ? WHITE : BLACK;
 +    return IndexType(hand_index + PieceSquareIndexShogiHand[color][pt] + SHOGI_PS_END * ksq);
 +  }
 +
 +  // Get a list of indices for active features
-   template <Side AssociatedKing>
-   void HalfKPShogi<AssociatedKing>::append_active_indices(
-       const Position& pos, Color perspective, IndexList* active) {
++  void HalfKPShogi::append_active_indices(
++    const Position& pos,
++    Color perspective,
++    ValueListInserter<IndexType> active
++  ) {
 +    Square ksq = orient(perspective, pos.square<KING>(perspective));
 +    Bitboard bb = pos.pieces() & ~pos.pieces(KING);
 +    while (bb) {
 +      Square s = pop_lsb(bb);
-       active->push_back(make_index(perspective, s, pos.piece_on(s), ksq));
++      active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
 +    }
 +
 +    // Indices for pieces in hand
 +    for (Color c : {WHITE, BLACK})
 +        for (PieceType pt : pos.piece_types())
 +            for (int i = 0; i < pos.count_in_hand(c, pt); i++)
-                 active->push_back(make_index(perspective, c, i, pt, ksq));
++                active.push_back(make_index(perspective, c, i, pt, ksq));
 +  }
 +
-   // Get a list of indices for recently changed features
-   template <Side AssociatedKing>
-   void HalfKPShogi<AssociatedKing>::append_changed_indices(
-       const Position& pos, const DirtyPiece& dp, Color perspective,
-       IndexList* removed, IndexList* added) {
-     Square ksq = orient(perspective, pos.square<KING>(perspective));
++  // append_changed_indices() : get a list of indices for recently changed features
++
++  void HalfKPShogi::append_changed_indices(
++    Square ksq,
++    StateInfo* st,
++    Color perspective,
++    ValueListInserter<IndexType> removed,
++    ValueListInserter<IndexType> added,
++    const Position& pos
++  ) {
++    const auto& dp = st->dirtyPiece;
++    Square oriented_ksq = orient(perspective, ksq);
 +    for (int i = 0; i < dp.dirty_num; ++i) {
 +      Piece pc = dp.piece[i];
-       if (type_of(pc) == KING) continue;
++      if (type_of(pc) == pos.nnue_king()) continue;
 +      if (dp.from[i] != SQ_NONE)
-         removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
++        removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
 +      else if (dp.dirty_num == 1)
 +      {
 +        Piece handPc = dp.handPiece[i];
-         removed->push_back(make_index(perspective, color_of(handPc), pos.count_in_hand(color_of(handPc), type_of(handPc)), type_of(handPc), ksq));
++        removed.push_back(make_index(perspective, color_of(handPc), dp.handCount[i], type_of(handPc), oriented_ksq));
 +      }
 +      if (dp.to[i] != SQ_NONE)
-         added->push_back(make_index(perspective, dp.to[i], pc, ksq));
++        added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq));
 +      else if (i == 1)
 +      {
 +        Piece handPc = dp.handPiece[i];
-         added->push_back(make_index(perspective, color_of(handPc), pos.count_in_hand(color_of(handPc), type_of(handPc)) - 1, type_of(handPc), ksq));
++        added.push_back(make_index(perspective, color_of(handPc), dp.handCount[i] - 1, type_of(handPc), oriented_ksq));
 +      }
 +    }
 +  }
 +
-   template class HalfKPShogi<Side::Friend>;
++  int HalfKPShogi::update_cost(StateInfo* st) {
++    return st->dirtyPiece.dirty_num;
++  }
++
++  int HalfKPShogi::refresh_cost(const Position& pos) {
++    return pos.count<ALL_PIECES>() - 2;
++  }
++
++  bool HalfKPShogi::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
++    return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king());
++  }
++
 +
 +}  // namespace Stockfish::Eval::NNUE::Features
index 343d2d1,0000000..f6033b0
mode 100644,000000..100644
--- /dev/null
@@@ -1,59 -1,0 +1,209 @@@
 +/*
 +  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 +  Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
 +
 +  Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +*/
 +
 +//Definition of input features HalfKP of NNUE evaluation function
 +
 +#ifndef NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED
 +#define NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED
 +
++#include "../nnue_common.h"
++
 +#include "../../evaluate.h"
- #include "features_common.h"
++#include "../../misc.h"
++
++namespace Stockfish {
++  struct StateInfo;
++}
 +
 +namespace Stockfish::Eval::NNUE::Features {
 +
 +  // Feature HalfKP: Combination of the position of own king
 +  // and the position of pieces other than kings
-   template <Side AssociatedKing>
 +  class HalfKPShogi {
 +
++    enum {
++      PS_NONE             =  0,
++      SHOGI_HAND_W_PAWN   =   1,
++      SHOGI_HAND_B_PAWN   =  20,
++      SHOGI_HAND_W_LANCE  =  39,
++      SHOGI_HAND_B_LANCE  =  44,
++      SHOGI_HAND_W_KNIGHT =  49,
++      SHOGI_HAND_B_KNIGHT =  54,
++      SHOGI_HAND_W_SILVER =  59,
++      SHOGI_HAND_B_SILVER =  64,
++      SHOGI_HAND_W_GOLD   =  69,
++      SHOGI_HAND_B_GOLD   =  74,
++      SHOGI_HAND_W_BISHOP =  79,
++      SHOGI_HAND_B_BISHOP =  82,
++      SHOGI_HAND_W_ROOK   =  85,
++      SHOGI_HAND_B_ROOK   =  88,
++      SHOGI_HAND_END      =  90,
++
++      SHOGI_PS_W_PAWN     =  SHOGI_HAND_END,
++      SHOGI_PS_B_PAWN     =  1 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_LANCE    =  2 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_LANCE    =  3 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_KNIGHT   =  4 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_KNIGHT   =  5 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_SILVER   =  6 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_SILVER   =  7 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_GOLD     =  8 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_GOLD     =  9 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_BISHOP   = 10 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_BISHOP   = 11 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_HORSE    = 12 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_HORSE    = 13 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_ROOK     = 14 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_ROOK     = 15 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_DRAGON   = 16 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_B_DRAGON   = 17 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_W_KING     = 18 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_END        = SHOGI_PS_W_KING, // pieces without kings (pawns included)
++      SHOGI_PS_B_KING     = 19 * SQUARE_NB_SHOGI + SHOGI_HAND_END,
++      SHOGI_PS_END2       = 20 * SQUARE_NB_SHOGI + SHOGI_HAND_END
++    };
++
++    static constexpr uint32_t PieceSquareIndexShogi[COLOR_NB][PIECE_NB] = {
++      // convention: W - us, B - them
++      // viewed from other side, W and B are reversed
++      {
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_BISHOP, SHOGI_PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, SHOGI_PS_W_SILVER, PS_NONE, SHOGI_PS_W_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_PAWN, SHOGI_PS_W_LANCE, SHOGI_PS_W_KNIGHT, SHOGI_PS_W_GOLD, SHOGI_PS_W_HORSE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_KING,
++
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_BISHOP, SHOGI_PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, SHOGI_PS_B_SILVER, PS_NONE, SHOGI_PS_B_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_PAWN, SHOGI_PS_B_LANCE, SHOGI_PS_B_KNIGHT, SHOGI_PS_B_GOLD, SHOGI_PS_B_HORSE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_KING
++      },
++
++      {
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_BISHOP, SHOGI_PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, SHOGI_PS_B_SILVER, PS_NONE, SHOGI_PS_B_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_PAWN, SHOGI_PS_B_LANCE, SHOGI_PS_B_KNIGHT, SHOGI_PS_B_GOLD, SHOGI_PS_B_HORSE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_B_KING,
++
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_BISHOP, SHOGI_PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, SHOGI_PS_W_SILVER, PS_NONE, SHOGI_PS_W_DRAGON, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_PAWN, SHOGI_PS_W_LANCE, SHOGI_PS_W_KNIGHT, SHOGI_PS_W_GOLD, SHOGI_PS_W_HORSE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, SHOGI_PS_W_KING
++      }
++    };
++    static_assert(PieceSquareIndexShogi[WHITE][make_piece(WHITE, SHOGI_PAWN)] == SHOGI_PS_W_PAWN);
++    static_assert(PieceSquareIndexShogi[WHITE][make_piece(WHITE, KING)] == SHOGI_PS_W_KING);
++    static_assert(PieceSquareIndexShogi[WHITE][make_piece(BLACK, SHOGI_PAWN)] == SHOGI_PS_B_PAWN);
++    static_assert(PieceSquareIndexShogi[WHITE][make_piece(BLACK, KING)] == SHOGI_PS_B_KING);
++
++    static constexpr uint32_t PieceSquareIndexShogiHand[COLOR_NB][PIECE_TYPE_NB] = {
++      // convention: W - us, B - them
++      // viewed from other side, W and B are reversed
++      {
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_W_BISHOP, SHOGI_HAND_W_ROOK, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, SHOGI_HAND_W_SILVER, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_W_PAWN, SHOGI_HAND_W_LANCE, SHOGI_HAND_W_KNIGHT, SHOGI_HAND_W_GOLD, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE
++      },
++
++      {
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_B_BISHOP, SHOGI_HAND_B_ROOK, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, SHOGI_HAND_B_SILVER, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, SHOGI_HAND_B_PAWN, SHOGI_HAND_B_LANCE, SHOGI_HAND_B_KNIGHT, SHOGI_HAND_B_GOLD, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE
++      }
++    };
++    static_assert(PieceSquareIndexShogiHand[WHITE][SHOGI_PAWN] == SHOGI_HAND_W_PAWN);
++    static_assert(PieceSquareIndexShogiHand[WHITE][GOLD] == SHOGI_HAND_W_GOLD);
++    static_assert(PieceSquareIndexShogiHand[BLACK][SHOGI_PAWN] == SHOGI_HAND_B_PAWN);
++    static_assert(PieceSquareIndexShogiHand[BLACK][GOLD] == SHOGI_HAND_B_GOLD);
++
++    // Orient a square according to perspective (rotates by 180 for black)
++    static Square orient(Color perspective, Square s);
++
++    // Index of a feature for a given king position and another piece on some square
++    static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
++
++    // Index of a feature for a given king position and a piece in hand
++    static IndexType make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq);
++
 +   public:
 +    // Feature name
 +    static constexpr const char* Name = "HalfKP(Friend)";
++
 +    // Hash value embedded in the evaluation file
-     static constexpr std::uint32_t HashValue =
-         0x5D69D5B9u ^ (AssociatedKing == Side::Friend);
++    static constexpr std::uint32_t HashValue = 0x5D69D5B8u;
++
 +    // Number of feature dimensions
 +    static constexpr IndexType Dimensions =
 +        static_cast<IndexType>(SQUARE_NB_SHOGI) * static_cast<IndexType>(SHOGI_PS_END);
-     // Maximum number of simultaneously active features
-     static constexpr IndexType MaxActiveDimensions = 38; // Kings don't count
-     // Trigger for full calculation instead of difference calculation
-     static constexpr TriggerEvent RefreshTrigger = TriggerEvent::FriendKingMoved;
++
++    // Maximum number of simultaneously active features. 38 because kins are not included.
++    static constexpr IndexType MaxActiveDimensions = 38;
 +
 +    // Get a list of indices for active features
-     static void append_active_indices(const Position& pos, Color perspective,
-                                       IndexList* active);
++    static void append_active_indices(
++      const Position& pos,
++      Color perspective,
++      ValueListInserter<IndexType> active);
 +
 +    // Get a list of indices for recently changed features
-     static void append_changed_indices(const Position& pos, const DirtyPiece& dp, Color perspective,
-                                        IndexList* removed, IndexList* added);
++    static void append_changed_indices(
++      Square ksq,
++      StateInfo* st,
++      Color perspective,
++      ValueListInserter<IndexType> removed,
++      ValueListInserter<IndexType> added,
++      const Position& pos);
++
++    // Returns the cost of updating one perspective, the most costly one.
++    // Assumes no refresh needed.
++    static int update_cost(StateInfo* st);
++    static int refresh_cost(const Position& pos);
++
++    // Returns whether the change stored in this StateInfo means that
++    // a full accumulator refresh is required.
++    static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos);
 +  };
 +
 +}  // namespace Stockfish::Eval::NNUE::Features
 +
 +#endif // #ifndef NNUE_FEATURES_HALF_KP_SHOGI_H_INCLUDED
index a149823,0000000..d93107f
mode 100644,000000..100644
--- /dev/null
@@@ -1,96 -1,0 +1,112 @@@
 +/*
 +  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 +  Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
 +
 +  Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +*/
 +
 +//Definition of input features HalfKP of NNUE evaluation function
 +
 +#include "half_kp_variants.h"
- #include "index_list.h"
 +
 +#ifdef LARGEBOARDS
 +#include "half_kp_shogi.h"
 +#endif
 +
++#include "../../position.h"
++
 +namespace Stockfish::Eval::NNUE::Features {
 +
 +  // Map square to numbering on 8x8 board
 +  constexpr Square to_chess_square(Square s) {
 +    return Square(s - rank_of(s) * (FILE_MAX - FILE_H));
 +  }
 +
 +  // Orient a square according to perspective (rotates by 180 for black)
-   inline Square orient(const Position& pos, Color perspective, Square s) {
++  inline Square HalfKPVariants::orient(Color perspective, Square s, const Position& pos) {
 +    return to_chess_square(  perspective == WHITE || (pos.capture_the_flag(BLACK) & Rank8BB) ? s
 +                           : flip_rank(flip_file(s, pos.max_file()), pos.max_rank()));
 +  }
 +
 +  // Index of a feature for a given king position and another piece on some square
-   inline IndexType make_index(const Position& pos, Color perspective, Square s, Piece pc, Square ksq) {
-     return IndexType(orient(pos, perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
++  inline IndexType HalfKPVariants::make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos) {
++    return IndexType(orient(perspective, s, pos) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
 +  }
 +
 +  // Get a list of indices for active features
-   template <Side AssociatedKing>
-   void HalfKPVariants<AssociatedKing>::append_active_indices(
-       const Position& pos, Color perspective, IndexList* active) {
++  void HalfKPVariants::append_active_indices(
++    const Position& pos,
++    Color perspective,
++    ValueListInserter<IndexType> active
++  ) {
 +    // Re-route to shogi features
 +#ifdef LARGEBOARDS
 +    if (currentNnueFeatures == NNUE_SHOGI)
 +    {
-         assert(HalfKPShogi<AssociatedKing>::Dimensions <= Dimensions);
-         return HalfKPShogi<AssociatedKing>::append_active_indices(pos, perspective, active);
++        assert(HalfKPShogi::Dimensions <= Dimensions);
++        return HalfKPShogi::append_active_indices(pos, perspective, active);
 +    }
 +#endif
 +
-     Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king()));
++    Square oriented_ksq = orient(perspective, pos.square(perspective, pos.nnue_king()), pos);
 +    Bitboard bb = pos.pieces() & ~pos.pieces(pos.nnue_king());
 +    while (bb) {
 +      Square s = pop_lsb(bb);
-       active->push_back(make_index(pos, perspective, s, pos.piece_on(s), ksq));
++      active.push_back(make_index(perspective, s, pos.piece_on(s), oriented_ksq, pos));
 +    }
 +  }
 +
-   // Get a list of indices for recently changed features
-   template <Side AssociatedKing>
-   void HalfKPVariants<AssociatedKing>::append_changed_indices(
-       const Position& pos, const DirtyPiece& dp, Color perspective,
-       IndexList* removed, IndexList* added) {
++  // append_changed_indices() : get a list of indices for recently changed features
 +
++  void HalfKPVariants::append_changed_indices(
++    Square ksq,
++    StateInfo* st,
++    Color perspective,
++    ValueListInserter<IndexType> removed,
++    ValueListInserter<IndexType> added,
++    const Position& pos
++  ) {
 +    // Re-route to shogi features
 +#ifdef LARGEBOARDS
 +    if (currentNnueFeatures == NNUE_SHOGI)
 +    {
-         assert(HalfKPShogi<AssociatedKing>::Dimensions <= Dimensions);
-         return HalfKPShogi<AssociatedKing>::append_changed_indices(pos, dp, perspective, removed, added);
++        assert(HalfKPShogi::Dimensions <= Dimensions);
++        return HalfKPShogi::append_changed_indices(ksq, st, perspective, removed, added, pos);
 +    }
 +#endif
-     Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king()));
++    const auto& dp = st->dirtyPiece;
++    Square oriented_ksq = orient(perspective, ksq, pos);
 +    for (int i = 0; i < dp.dirty_num; ++i) {
 +      Piece pc = dp.piece[i];
 +      if (type_of(pc) == pos.nnue_king()) continue;
 +      if (dp.from[i] != SQ_NONE)
-         removed->push_back(make_index(pos, perspective, dp.from[i], pc, ksq));
++        removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq, pos));
 +      if (dp.to[i] != SQ_NONE)
-         added->push_back(make_index(pos, perspective, dp.to[i], pc, ksq));
++        added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq, pos));
 +    }
 +  }
 +
-   template class HalfKPVariants<Side::Friend>;
++  int HalfKPVariants::update_cost(StateInfo* st) {
++    return st->dirtyPiece.dirty_num;
++  }
++
++  int HalfKPVariants::refresh_cost(const Position& pos) {
++    return pos.count<ALL_PIECES>() - 2;
++  }
++
++  bool HalfKPVariants::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
++    return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king());
++  }
 +
 +}  // namespace Stockfish::Eval::NNUE::Features
index f5720d4,0000000..f095737
mode 100644,000000..100644
--- /dev/null
@@@ -1,63 -1,0 +1,164 @@@
 +/*
 +  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 +  Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
 +
 +  Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +*/
 +
 +//Definition of input features HalfKP of NNUE evaluation function
 +
 +#ifndef NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED
 +#define NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED
 +
++#include "../nnue_common.h"
++
 +#include "../../evaluate.h"
- #include "features_common.h"
++#include "../../misc.h"
++
++#include "half_kp_shogi.h"
++#include "half_kp.h"
++
++namespace Stockfish {
++  struct StateInfo;
++}
 +
 +namespace Stockfish::Eval::NNUE::Features {
 +
 +  // Feature HalfKP: Combination of the position of own king
 +  // and the position of pieces other than kings
-   template <Side AssociatedKing>
 +  class HalfKPVariants {
 +
++    // unique number for each piece type on each square
++    enum {
++      PS_NONE     =  0,
++      PS_W_PAWN   =  1,
++      PS_B_PAWN   =  1 * SQUARE_NB_CHESS + 1,
++      PS_W_KNIGHT =  2 * SQUARE_NB_CHESS + 1,
++      PS_B_KNIGHT =  3 * SQUARE_NB_CHESS + 1,
++      PS_W_BISHOP =  4 * SQUARE_NB_CHESS + 1,
++      PS_B_BISHOP =  5 * SQUARE_NB_CHESS + 1,
++      PS_W_ROOK   =  6 * SQUARE_NB_CHESS + 1,
++      PS_B_ROOK   =  7 * SQUARE_NB_CHESS + 1,
++      PS_W_QUEEN  =  8 * SQUARE_NB_CHESS + 1,
++      PS_B_QUEEN  =  9 * SQUARE_NB_CHESS + 1,
++      PS_NB       = 10 * SQUARE_NB_CHESS + 1,
++    };
++
++    static constexpr uint32_t PieceSquareIndex[COLOR_NB][PIECE_NB] = {
++      // convention: W - us, B - them
++      // viewed from other side, W and B are reversed
++      {
++        PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++        PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++        PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++        PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++        PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++        PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++      },
++
++      {
++        PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_QUEEN, PS_B_BISHOP,
++        PS_B_BISHOP, PS_B_BISHOP, PS_B_QUEEN, PS_B_QUEEN, PS_NONE, PS_NONE, PS_B_QUEEN, PS_B_KNIGHT,
++        PS_B_BISHOP, PS_B_KNIGHT, PS_B_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_B_BISHOP, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++
++        PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_QUEEN, PS_W_BISHOP,
++        PS_W_BISHOP, PS_W_BISHOP, PS_W_QUEEN, PS_W_QUEEN, PS_NONE, PS_NONE, PS_W_QUEEN, PS_W_KNIGHT,
++        PS_W_BISHOP, PS_W_KNIGHT, PS_W_ROOK, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_W_BISHOP, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++        PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE,
++      }
++    };
++    // Check that the fragile array definition is correct
++    static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, PAWN)] == PS_W_PAWN);
++    static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, KING)] == PS_NONE);
++    static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, PAWN)] == PS_B_PAWN);
++    static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, KING)] == PS_NONE);
++
++    // Orient a square according to perspective (rotates by 180 for black)
++    static Square orient(Color perspective, Square s, const Position& pos);
++
++    // Index of a feature for a given king position and another piece on some square
++    static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos);
++
 +   public:
 +    // Feature name
 +    static constexpr const char* Name = "HalfKP(Friend)";
++
 +    // Hash value embedded in the evaluation file
-     static constexpr std::uint32_t HashValue =
-         0x5D69D5B9u ^ (AssociatedKing == Side::Friend);
++    static constexpr std::uint32_t HashValue = 0x5D69D5B8u;
++
 +    // Number of feature dimensions
 +    static constexpr IndexType Dimensions =
 +#ifdef LARGEBOARDS
-         static_cast<IndexType>(SQUARE_NB_SHOGI) * static_cast<IndexType>(SHOGI_PS_END);
++        HalfKPShogi::Dimensions;
 +#else
-         static_cast<IndexType>(SQUARE_NB_CHESS) * static_cast<IndexType>(PS_NB);
++        HalfKPChess::Dimensions;
 +#endif
-     // Maximum number of simultaneously active features
-     static constexpr IndexType MaxActiveDimensions = 64; // Kings don't count
-     // Trigger for full calculation instead of difference calculation
-     static constexpr TriggerEvent RefreshTrigger = TriggerEvent::FriendKingMoved;
++
++    static IndexType get_dimensions() {
++      return  currentNnueFeatures == NNUE_SHOGI ? HalfKPShogi::Dimensions
++            : currentNnueFeatures == NNUE_CHESS ? HalfKPChess::Dimensions
++                                                : SQUARE_NB_CHESS * PS_NB;
++    }
++
++    // Maximum number of simultaneously active features. 30 because kins are not included.
++    static constexpr IndexType MaxActiveDimensions = 64;
 +
 +    // Get a list of indices for active features
-     static void append_active_indices(const Position& pos, Color perspective,
-                                       IndexList* active);
++    static void append_active_indices(
++      const Position& pos,
++      Color perspective,
++      ValueListInserter<IndexType> active);
 +
 +    // Get a list of indices for recently changed features
-     static void append_changed_indices(const Position& pos, const DirtyPiece& dp, Color perspective,
-                                        IndexList* removed, IndexList* added);
++    static void append_changed_indices(
++      Square ksq,
++      StateInfo* st,
++      Color perspective,
++      ValueListInserter<IndexType> removed,
++      ValueListInserter<IndexType> added,
++      const Position& pos);
++
++    // Returns the cost of updating one perspective, the most costly one.
++    // Assumes no refresh needed.
++    static int update_cost(StateInfo* st);
++    static int refresh_cost(const Position& pos);
++
++    // Returns whether the change stored in this StateInfo means that
++    // a full accumulator refresh is required.
++    static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos);
 +  };
 +
 +}  // namespace Stockfish::Eval::NNUE::Features
 +
 +#endif // #ifndef NNUE_FEATURES_HALF_KP_VARIANTS_H_INCLUDED
  #ifndef NNUE_ARCHITECTURE_H_INCLUDED
  #define NNUE_ARCHITECTURE_H_INCLUDED
  
- // Defines the network structure
- // #include "architectures/halfkp_256x2-32-32.h"
- // #include "architectures/halfkp_shogi_256x2-32-32.h"
- #include "architectures/halfkp_variants_256x2-32-32.h"
+ #include "nnue_common.h"
 -#include "features/half_kp.h"
++//#include "features/half_kp.h"
++#include "features/half_kp_variants.h"
+ #include "layers/input_slice.h"
+ #include "layers/affine_transform.h"
+ #include "layers/clipped_relu.h"
  
  namespace Stockfish::Eval::NNUE {
  
+   // Input features used in evaluation function
 -  using FeatureSet = Features::HalfKP;
++  //using FeatureSet = Features::HalfKPChess;
++  using FeatureSet = Features::HalfKPVariants;
+   // Number of input feature dimensions after conversion
+   constexpr IndexType TransformedFeatureDimensions = 256;
+   namespace Layers {
+     // Define network structure
+     using InputLayer = InputSlice<TransformedFeatureDimensions * 2>;
+     using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
+     using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
+     using OutputLayer = AffineTransform<HiddenLayer2, 1>;
+   }  // namespace Layers
+   using Network = Layers::OutputLayer;
    static_assert(TransformedFeatureDimensions % MaxSimdWidth == 0, "");
    static_assert(Network::OutputDimensions == 1, "");
    static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
@@@ -110,12 -111,9 +111,10 @@@ namespace Stockfish::Eval::NNUE 
  
      // Read network parameters
      bool read_parameters(std::istream& stream) {
 +
        for (std::size_t i = 0; i < HalfDimensions; ++i)
          biases[i] = read_little_endian<BiasType>(stream);
-       for (std::size_t i = 0; i < HalfDimensions * (  currentNnueFeatures == NNUE_SHOGI ? SQUARE_NB_SHOGI * SHOGI_PS_END
-                                                     : currentNnueFeatures == NNUE_CHESS ? SQUARE_NB_CHESS * PS_NB
-                                                                                         : SQUARE_NB_CHESS * PS_NB); ++i)
 -      for (std::size_t i = 0; i < HalfDimensions * InputDimensions; ++i)
++      for (std::size_t i = 0; i < HalfDimensions * FeatureSet::get_dimensions(); ++i)
          weights[i] = read_little_endian<WeightType>(stream);
        return !stream.fail();
      }
        // Look for a usable accumulator of an earlier position. We keep track
        // of the estimated gain in terms of features to be added/subtracted.
        StateInfo *st = pos.state(), *next = nullptr;
-       int gain = pos.count<ALL_PIECES>() - 2;
-       while (st->accumulator.state[c] == EMPTY)
+       int gain = FeatureSet::refresh_cost(pos);
+       while (st->accumulator.state[perspective] == EMPTY)
        {
-         auto& dp = st->dirtyPiece;
-         // The first condition tests whether an incremental update is
-         // possible at all: if this side's king has moved, it is not possible.
-         static_assert(std::is_same_v<RawFeatures::SortedTriggerSet,
-               Features::CompileTimeList<Features::TriggerEvent, Features::TriggerEvent::FriendKingMoved>>,
-               "Current code assumes that only FriendlyKingMoved refresh trigger is being used.");
-         if (   dp.piece[0] == make_piece(c, pos.nnue_king())
-             || (gain -= dp.dirty_num + 1) < 0)
+         // This governs when a full feature refresh is needed and how many
+         // updates are better than just one full refresh.
 -        if (   FeatureSet::requires_refresh(st, perspective)
++        if (   FeatureSet::requires_refresh(st, perspective, pos)
+             || (gain -= FeatureSet::update_cost(st) + 1) < 0)
            break;
          next = st;
          st = st->previous;
          // Update incrementally in two steps. First, we update the "next"
          // accumulator. Then, we update the current accumulator (pos.state()).
  
-         // Gather all features to be updated. This code assumes HalfKP features
-         // only and doesn't support refresh triggers.
-         static_assert(std::is_same_v<Features::FeatureSet<Features::HalfKP<Features::Side::Friend>>,
-                                      RawFeatures>);
-         Features::IndexList removed[2], added[2];
-         Features::HalfKP<Features::Side::Friend>::append_changed_indices(pos,
-             next->dirtyPiece, c, &removed[0], &added[0]);
+         // Gather all features to be updated.
 -        const Square ksq = pos.square<KING>(perspective);
++        const Square ksq = pos.square(perspective, pos.nnue_king());
+         IndexList removed[2], added[2];
+         FeatureSet::append_changed_indices(
 -          ksq, next, perspective, removed[0], added[0]);
++          ksq, next, perspective, removed[0], added[0], pos);
          for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
-           Features::HalfKP<Features::Side::Friend>::append_changed_indices(pos,
-               st2->dirtyPiece, c, &removed[1], &added[1]);
+           FeatureSet::append_changed_indices(
 -            ksq, st2, perspective, removed[1], added[1]);
++            ksq, st2, perspective, removed[1], added[1], pos);
  
          // Mark the accumulators as computed.
-         next->accumulator.state[c] = COMPUTED;
-         pos.state()->accumulator.state[c] = COMPUTED;
+         next->accumulator.state[perspective] = COMPUTED;
+         pos.state()->accumulator.state[perspective] = COMPUTED;
  
-         // Now update the accumulators listed in info[], where the last element is a sentinel.
-         StateInfo *info[3] =
+         // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+         StateInfo *states_to_update[3] =
            { next, next == pos.state() ? nullptr : pos.state(), nullptr };
    #ifdef VECTOR
          for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
@@@ -1417,20 -767,6 +1417,23 @@@ void Position::do_move(Move m, StateInf
  
        if (type_of(m) == EN_PASSANT)
            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)]];
 +
 +          if (Eval::useNNUE)
++          {
 +              dp.handPiece[1] = pieceToHand;
++              dp.handCount[1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)];
++          }
 +      }
 +      else
 +          dp.handPiece[1] = NO_PIECE;
  
        // 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[0] = pc;
 +          dp.handPiece[0] = make_piece(us, in_hand_piece_type(m));
++          dp.handCount[0] = pieceCountInHand[us][in_hand_piece_type(m)];
 +          dp.from[0] = SQ_NONE;
 +          dp.to[0] = to;
 +      }
 +
 +      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) == castling_king_piece() && file_of(to) == castling_king_file())
 +          {
 +              st->castlingKingSquare[us] = to;
 +              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(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece()))
 +              {
 +                  st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us));
 +                  set_castling_right(us, to);
 +              }
 +          }
 +      }
 +  }
 +  else if (type_of(m) != CASTLING)
    {
        if (Eval::useNNUE)
        {
    // 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.handPiece[dp.dirty_num] = gating_piece;
++          dp.handCount[dp.dirty_num] = pieceCountInHand[us][gating_type(m)];
 +          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) == 0 && !captures_to_hand())
 +          st->gatesBB[us] = 0;
 +  }
 +
 +  // Remove the blast pieces
 +  if (captured && blast_on_capture())
 +  {
 +      std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch));
 +      st->demotedBycatch = st->promotedBycatch = 0;
 +      Bitboard blast = (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to;
 +      while (blast)
 +      {
 +          Square bsq = pop_lsb(blast);
 +          Piece bpc = piece_on(bsq);
 +          Color bc = color_of(bpc);
 +          if (type_of(bpc) != PAWN)
 +              st->nonPawnMaterial[bc] -= PieceValue[MG][bpc];
 +
 +          if (Eval::useNNUE)
 +          {
 +              dp.piece[dp.dirty_num] = bpc;
 +              dp.handPiece[dp.dirty_num] = NO_PIECE;
 +              dp.from[dp.dirty_num] = bsq;
 +              dp.to[dp.dirty_num] = SQ_NONE;
 +              dp.dirty_num++;
 +          }
 +
 +          // Update board and piece lists
 +          // In order to not have to store the values of both board and unpromotedBoard,
 +          // demote promoted pieces, but keep promoted pawns as promoted,
 +          // and store demotion/promotion bitboards to disambiguate the piece state
 +          bool capturedPromoted = is_promoted(bsq);
 +          Piece unpromotedCaptured = unpromoted_piece_on(bsq);
 +          st->unpromotedBycatch[bsq] = unpromotedCaptured ? unpromotedCaptured : bpc;
 +          if (unpromotedCaptured)
 +              st->demotedBycatch |= bsq;
 +          else if (capturedPromoted)
 +              st->promotedBycatch |= bsq;
 +          remove_piece(bsq);
 +          board[bsq] = NO_PIECE;
 +          if (captures_to_hand())
 +          {
 +              Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc
 +                                 : unpromotedCaptured ? ~unpromotedCaptured
 +                                                      : make_piece(~color_of(bpc), 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)]];
 +
 +              if (Eval::useNNUE)
++              {
 +                  dp.handPiece[dp.dirty_num - 1] = pieceToHand;
++                  dp.handCount[dp.dirty_num - 1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)];
++              }
 +          }
 +
 +          // Update material hash key
 +          k ^= Zobrist::psq[bpc][bsq];
 +          st->materialKey ^= Zobrist::psq[bpc][pieceCount[bpc]];
 +          if (type_of(bpc) == PAWN)
 +              st->pawnKey ^= Zobrist::psq[bpc][bsq];
 +
 +          // Update castling rights if needed
 +          if (st->castlingRights && castlingRightsMask[bsq])
 +          {
 +              int cr = castlingRightsMask[bsq];
 +              k ^= Zobrist::castling[st->castlingRights & cr];
 +              st->castlingRights &= ~cr;
 +          }
 +      }
 +  }
 +
    // 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;
  
diff --cc src/types.h
@@@ -535,18 -272,11 +535,19 @@@ struct DirtyPiece 
    // 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];
 +  Piece piece[12];
 +  Piece handPiece[12];
++  int handCount[12];
  
    // From and to squares, which may be SQ_NONE
 -  Square from[3];
 -  Square to[3];
 +  Square from[12];
 +  Square to[12];
 +};
 +
 +enum NnueFeatures {
 +  NNUE_CHESS,
 +  NNUE_SHOGI,
 +  NNUE_VARIANT,
  };
  
  /// Score enum stores a middlegame and an endgame value in a single integer (enum).