1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
/**
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 ([email protected]) 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 <idx> <idx> ...
NNUE_FEATURES black <idx> <idx> ...
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");
}
|