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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
/**
Copyright (c) Scott Gasch
Module Name:
dumptree.c
Abstract:
TREEDUMP: a flat, line-oriented, path-keyed node/move trace of the
search tree, meant to be diffed against an identically-instrumented
Stockfish build on the same FEN. See TREEDUMP_FORMAT.md for the
output format and chess.h's TREEDUMP comment block for the calling
convention.
Build-time-only ("gmake TREEDUMP=1 ..."), always-on-when-built --
there is no runtime toggle/filter. Keep a dump small by using a
shallow "sd N"/"go depth N", not by restricting what gets written.
This replaces an earlier (2004), long-unused tree-dump mechanism
that wrote XML for a Windows GUI (typhoonui.exe) long since gone;
that code and its DUMP_TREE build flag have been removed outright
rather than kept alongside this one, on the assumption that a
mechanism nothing has exercised in two decades is broken.
Author:
Scott Gasch ([email protected]) 17 Jul 2004, replaced 2026-10-22
**/
#ifdef TREEDUMP
#include "chess.h"
static FILE *g_pfTreeDump = NULL;
void
TDOpen(void)
/**
Routine description:
Open (truncate) the fixed-name TREEDUMP output file. Call once at
startup when built with TREEDUMP -- there is no runtime toggle, so
this always runs and the file always gets every node/move of every
search performed. fflush() after every single line (unbuffered)
since TREEDUMP runs are expected to be small (a shallow sd/go
depth), not performance sensitive, and a flushed file is
immediately diffable even if the engine is killed mid-run or
crashes.
Parameters:
void
Return value:
void
**/
{
g_pfTreeDump = fopen("treedump.log", "w");
if (NULL == g_pfTreeDump)
{
UtilPanic(FATAL_ACCESS_DENIED, NULL, "treedump.log", "write",
"NULL", __FILE__, __LINE__);
}
}
void
TDClose(void)
/**
Routine description:
Flush and close the TREEDUMP output file.
Parameters:
void
Return value:
void
**/
{
if (NULL != g_pfTreeDump)
{
fflush(g_pfTreeDump);
fclose(g_pfTreeDump);
g_pfTreeDump = NULL;
}
}
// Formats a move as a UCI/ICS-style coordinate string ("e2e4"), or the
// literal "0000" for a null move (MOVE{0}, same sentinel as NULLMOVE in
// data.c) -- matches Stockfish's UCIEngine::move()'s spelling for
// Move::null() exactly, so the two engines' logs tokenize identically.
// Not thread safe (static buffer, same caveat as MoveToIcs itself,
// which this wraps) -- TREEDUMP builds are meant for single-threaded
// diagnostic runs only, matching this project's own comparison
// methodology (see CLAUDE.md's "--cpus 1" convention).
static CHAR *
_TDMoveToStr(MOVE mv)
{
static CHAR szNull[] = "0000";
CHAR *p;
if (0 == mv.uMove)
{
return(szNull);
}
p = MoveToIcs(mv);
ASSERT(p);
return(p);
}
// Builds the path of moves from the root up to and including ctx's
// current ply's own incoming move -- i.e. ctx->sPlyInfo[0..ctx->uPly-1].
// MakeMove() (move.c) stores each move at the OLD ply index before
// incrementing ctx->uPly, so this naturally includes:
// - the move just made by a caller logging from inside SearchOneMove
// (called after MakeMove, before UnmakeMove -- ctx->uPly is already
// bumped to the child ply, so sPlyInfo[childPly - 1] == that move), or
// - only the moves leading INTO this node, with none of this node's
// own not-yet-tried moves, for a caller logging a node's own RESULT
// from its own ply (e.g. search.c's "end:").
// Same role as the Stockfish side's TreeDump::path() (search.cpp) --
// see TREEDUMP_FORMAT.md. Not thread safe (static buffer); see
// _TDMoveToStr's comment.
static CHAR *
_TDPath(SEARCHER_THREAD_CONTEXT *ctx)
{
static CHAR szPath[2048];
ULONG p;
szPath[0] = '\0';
for (p = 0; p < ctx->uPly; p++)
{
if ('\0' != szPath[0])
{
strncat(szPath, " ", ARRAY_LENGTH(szPath) - strlen(szPath) - 1);
}
strncat(szPath, _TDMoveToStr(ctx->sPlyInfo[p].mv),
ARRAY_LENGTH(szPath) - strlen(szPath) - 1);
}
return(szPath);
}
void
TDMove(SEARCHER_THREAD_CONTEXT *ctx,
ULONG uDepth,
SCORE iAlpha,
SCORE iBeta,
CHAR *szKind,
CHAR *szAct,
double dReducedPlies,
double dExtendedPlies,
SCORE iScore,
FLAG fSkipped)
/**
Routine description:
Log one MOVE line: a decision about the move already made at
ctx's current (child) ply -- see this file's header comment for
the calling convention (after MakeMove, before UnmakeMove).
Parameters:
SEARCHER_THREAD_CONTEXT *ctx : current searcher thread context,
already past MakeMove for the move being logged.
ULONG uDepth : remaining depth (in internal ONE_PLY ticks) at
the PARENT node, before this move's own extension/
reduction.
SCORE iAlpha, iBeta : the parent node's alpha/beta.
CHAR *szKind : move classification, e.g. "FIRST", "CAP", "QUIET",
"NULL", freeform.
CHAR *szAct : what happened to it, e.g. "FULL", "LMR", "LMP",
"NMP", freeform.
double dReducedPlies, dExtendedPlies : magnitude, in whole plies,
of any reduction/extension applied (0.0 if none).
SCORE iScore : the score returned by searching this move;
ignored (but still required as a parameter for a
uniform call shape) when fSkipped is TRUE.
FLAG fSkipped : TRUE if this move was pruned/skipped without
being searched at all (e.g. LMP) -- logs
"score=SKIPPED" instead of a numeric score.
Return value:
void
**/
{
if (NULL == g_pfTreeDump)
{
return;
}
if (TRUE == fSkipped)
{
fprintf(g_pfTreeDump,
"MOVE path=\"%s\" depth=%.2f alpha=%d beta=%d kind=%s "
"act=%s r=%.2f ext=%.2f score=SKIPPED\n",
_TDPath(ctx), (double)uDepth / ONE_PLY, iAlpha, iBeta,
szKind, szAct, dReducedPlies, dExtendedPlies);
}
else
{
fprintf(g_pfTreeDump,
"MOVE path=\"%s\" depth=%.2f alpha=%d beta=%d kind=%s "
"act=%s r=%.2f ext=%.2f score=%d\n",
_TDPath(ctx), (double)uDepth / ONE_PLY, iAlpha, iBeta,
szKind, szAct, dReducedPlies, dExtendedPlies, iScore);
}
fflush(g_pfTreeDump);
}
void
TDResult(SEARCHER_THREAD_CONTEXT *ctx,
ULONG uDepth,
SCORE iAlpha,
SCORE iBeta,
SCORE iEval,
SCORE iBestScore)
/**
Routine description:
Log one RESULT line: this node's own final outcome. Call from the
node's own ply (i.e. before any further move has been made) --
see this file's header comment.
Parameters:
SEARCHER_THREAD_CONTEXT *ctx : current searcher thread context, at
the node whose result is being logged.
ULONG uDepth : remaining depth (internal ONE_PLY ticks) this
node was searched to.
SCORE iAlpha, iBeta : this node's ORIGINAL (pre-widening) alpha/beta
-- callers should pass iInitialAlpha, not whatever
iAlpha was mutated to internally, so the bound
classification below means what it says.
SCORE iEval : this node's static eval (0/stale if the node
returned before Eval() ran -- diagnostic only).
SCORE iBestScore : this node's final score.
Return value:
void
**/
{
CHAR *szBound;
if (NULL == g_pfTreeDump)
{
return;
}
if (iBestScore >= iBeta)
{
szBound = "LOWER";
}
else if (iBestScore <= iAlpha)
{
szBound = "UPPER";
}
else
{
szBound = "EXACT";
}
fprintf(g_pfTreeDump,
"RESULT path=\"%s\" depth=%.2f alpha=%d beta=%d eval=%d "
"bound=%s score=%d\n",
_TDPath(ctx), (double)uDepth / ONE_PLY, iAlpha, iBeta, iEval,
szBound, iBestScore);
fflush(g_pfTreeDump);
}
#endif // TREEDUMP
|