/** Copyright (c) Scott Gasch Module Name: nnuefeatures.c Abstract: Standalone reference implementation of the HalfKAv2_hm feature index formula, used ONLY to cross-check against the Python reference encoder (annotate_pgns/nnue_feature_spec.py) -- see docs/NNUE.md's "Canonical HalfKAv2_hm feature-indexing spec" section for the formulas this file implements, sourced verbatim from Stockfish's src/nnue/features/half_ka_v2_hm.{h,cpp} (git tag sf_16). This is deliberately NOT wired into search/eval -- no accumulator, no incremental maintenance, nothing performance-sensitive. It exists purely so `dump nnuefeatures` can print the exact same active-feature-index set the real C inference code produces, so a Python/C cross-checker has a C-side ground truth to diff against. The actual feature-index formula (NnueFeatureIndex) now lives in chess.h, shared verbatim with the real MakeMove/UnmakeMove accumulator maintenance (move.c) -- this file only walks the board and calls it, so the two can never silently drift apart. Author: Scott Gasch (scott.gasch@gmail.com) 15 Oct 2026 **/ #include "chess.h" static int CompareInts(const void *a, const void *b) { int x = *(const int *)a; int y = *(const int *)b; return (x > y) - (x < y); } // // Fill pIndices (caller-allocated, at least 32 ints) with the sorted // set of active HalfKAv2_hm feature indices for perspective // uPerspective at position pos. Returns the count (<=32). // static int ActiveFeatureIndices(POSITION *pos, ULONG uPerspective, int *pIndices) { int f, r; COOR cKing = ILLEGAL_COOR; int n = 0; // // Find this perspective's own king square. // for (f = 0; f < 8; f++) { for (r = 1; r <= 8; r++) { COOR c = FILE_RANK_TO_COOR(f, r); PIECE p = pos->rgSquare[c].pPiece; if (!IS_EMPTY(p) && (PIECE_TYPE(p) == KING) && (PIECE_COLOR(p) == uPerspective)) { cKing = c; } } } ASSERT(cKing != ILLEGAL_COOR); for (f = 0; f < 8; f++) { for (r = 1; r <= 8; r++) { COOR c = FILE_RANK_TO_COOR(f, r); PIECE p = pos->rgSquare[c].pPiece; if (IS_EMPTY(p)) { continue; } ASSERT(n < 32); pIndices[n++] = NnueFeatureIndex(uPerspective, cKing, c, p); } } qsort(pIndices, n, sizeof(int), CompareInts); return n; } void DumpNnueFeatures(POSITION *pos) /** Routine description: Prints the sorted set of active HalfKAv2_hm feature indices for both perspectives at the current position, one line per perspective, in a format trivial for a Python cross-checker to parse: NNUE_FEATURES white ... NNUE_FEATURES black ... Parameters: POSITION *pos : position to dump Return value: void **/ { int rgIndices[32]; int n; int i; n = ActiveFeatureIndices(pos, WHITE, rgIndices); Trace("NNUE_FEATURES white"); for (i = 0; i < n; i++) { Trace(" %d", rgIndices[i]); } Trace("\n"); n = ActiveFeatureIndices(pos, BLACK, rgIndices); Trace("NNUE_FEATURES black"); for (i = 0; i < n; i++) { Trace(" %d", rgIndices[i]); } Trace("\n"); }