Training: for Black-to-move positions, mirror the board (ranks flipped,
colours swapped) before feature extraction so the model always sees the
position from the side-to-move's perspective. Eval label is negated to
match. Implemented in fen_to_features (board.mirror()) and __getitem__
(' b ' check in FEN string).
Inference (legacy evaluate()): applies the same flip for Black so the
model receives features in the format it was trained on. The
scoreFromOutput negation converts back to White's absolute perspective.
Incremental accumulator path is unchanged — it uses the raw HalfKP
features with the existing sign-negation at output; the quality gain comes
from the richer training distribution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace absolute 768-feature encoding with dual-perspective king-relative
encoding (HalfKP style): each piece is encoded from both the white king's
and the black king's reference frame, yielding 98304 input features
(2 × 64 king-squares × 12 piece-types × 64 squares).
Key changes:
- NNUE.scala: featureIdxWhite/featureIdxBlack replace featureIndex;
pushAccumulator now accepts childBoard and recomputes on king moves
(castle or normal king move) instead of using stale incremental state;
non-king moves update both perspectives incrementally (~4 column ops).
- EvaluationNNUE.scala: pass child.board to pushAccumulator.
- python/src/train.py: fen_to_features produces 98304-dim HalfKP vector;
NNUE model input size updated to INPUT_SIZE (98304); DEFAULT_HIDDEN_SIZES
reduced to [512, 256, 128] appropriate for sparse high-dim input.
- nnue_weights.nbai: replaced with placeholder 98304→16→8→1 model so
tests compile and run; replace with a retrained model via Colab notebook.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>