…t value
A search's fail soft answer (the window as it stands, the best score and
the move that scored it, the taint, the count of moves searched, and
whether alpha rose) was written three times. `Node` held it with `absorb`
and `raised_alpha`. Quiescence kept six locals for it and repeated
`absorb` by hand in its capture loop. `search_root` kept seven and did the
same in its move loop, with `alpha != opening_alpha` as whether an aborted
iteration may answer.
The answer half of `Node` is now its own value, `FailSoft`, which `Node`
holds as `answer`. It has `open`, `absorb` (which still moves alpha and the
root bounds together at a rise) and `raised_alpha`, and a `stand_pat` step
for quiescence that sets the best score and alpha from the static score
with no move and no count. Quiescence opens one before its stand pat test,
absorbs each capture, reads a side in check with no legal move off a
searched count of zero, and stores as before (its standing evaluation with
the entry) with `raised_alpha` from the answer. The root opens one at the
aspiration window with both root bounds, absorbs each move, stops at beta,
answers with the best move and score, and reads whether an aborted
iteration may answer off `raised_alpha`. The late
move rules, the census, the effort instrument and the ledger read the
bounds and the count through `node.answer`, a field path that costs
nothing over the old one. `Node::open` takes the opened answer, and its
`too_many_arguments` allowance goes. The name keeps clear of the protocol's
`Answer` and the effort instrument's `Answered`, which are finished
answers rather than one being built.
`absorb` asks beta only of a score above alpha, as quiescence and the root
did, where `Node` asked beta first. With alpha under beta the two give the
same answer, and a move that fails low (most of them) asks one question.
A debug assertion holds the window open. Asking beta first measured
206,734 more instructions over the depth 5 bench (0.1%), most of it in
quiescence.
The root's store keeps its exemption from the taint policy, now as
`store_root_answer` with the reason beside it: the reported line is read
back from the root's slot, so the root stores whatever its taint, and
under the skip policy the rare tainted cutoff that slot offers is refused
as under refuse. No test held the exemption, so one now searches a root a
half move short of the fifty move draw under the skip policy and reads
that its one tainted store landed.
The answer's tests move to the new value, and two more read it with no
board: the stand pat sets the best score and alpha and counts no move, and
a fresh answer counts nothing searched until a move is absorbed. The bench
and a fixed depth search open the root at the full window, so no test
reached the root's ceiling or its abort below alpha. Two more call the
root at a window no move beats. A depth five search of the opening at 500
to 560 answers a ceiling, and the same search stopped one node short
answers nothing rather than the closest move. A root whose every move
scores exactly alpha answers a ceiling too, since meeting alpha does not
raise it.
The tree is unchanged. The bench counts 5,965,973, and its per position
rows print as on the parent under each of the four taint policies, with
the time masked. Every instrument's rows at depth 5 print as on the
parent: cutoffs, reductions, effort with no switch off and with null_move
off, residuals, and forced with every kind and with null_move,
reverse_futility, and skip and trusted_scout from depth 2 alone, at their
default sampling and at every 1. The info lines of `go depth 12` from the
start position through the protocol match the parent's, with the time
masked.
Callgrind over the depth 5 bench counts 194,139,961 instructions on the
parent and 194,078,291 here, -61,670 (-0.03%). By symbol, summing each
function's two copies, alpha_beta is 180,733 fewer and search_root 21,250,
windowed is unchanged, and quiescence is 140,252 more (1.2% of its own).
Quiescence's own lines in engine.rs lost 228,276 and the integer
comparisons inlined into it gained 267,714, much of that the same
comparisons credited to the operator's line rather than the caller's. Its
mate test asks the searched count before the check flag. Written the
other way round, the compiler kept the flag on the stack rather than in a
register, every test of it read memory, and quiescence ran 45,834 more.
Bench: 5965973
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A search's fail soft answer (the window as it stands, the best score and its
move, the taint, the count searched, whether alpha rose) was written three
times:
Nodeheld it, quiescence kept six locals and repeatedabsorbbyhand, and
search_rootkept seven and did the same. It is now one value,FailSoft, whichNodeholds and quiescence and the root absorb through.Quiescence's stand pat is a named step on it, and the root's store is a named
store_root_answerwith its documented exemption from the taint policystated where it happens.
What a reviewer should know
arche benchcounts 5,965,973 on the base and thebranch, and its per position rows are identical under each of the four taint
policies. Every instrument's rows at depth 5 are identical at the default
sampling and at
every 1: cutoffs, reductions, effort with and withoutnull_moveoff, residuals, and the new forced instrument with every kindand three selections of kinds. Every
infoline ofgo depth 12from thestart position through the protocol matches. A second review's own builds
also held
bench 7, the reference configuration at depths 7 and 8, thegames suite under three policies, seven switches off, and node limit
sessions including an aborted iteration reported as a floor.
bench 5against the parent: -61,670 instructions (-0.03%).alpha_betaand the root fell; quiescence rose 1.2% of its own cost, whichis integer comparisons credited to the operator's line rather than the
caller's. Two shapes were measured on the way:
absorbasking beta firstcost 0.1% more, almost all in quiescence, so it asks beta only of a score
above alpha, as quiescence and the root did before; and quiescence's mate
test written flag first made the compiler spill the check flag to the
stack, seen in the disassembly, so the count is asked first. A speed round
on an idle machine read -0.7% with a 95% interval from -2.9% to +0.6%, not
resolved, as a change this size should read. A first round under load was
set aside.
case (the stand pat's baseline, the order of the beta test, the best move
and the store, the abort rule, the bound, the game over read, the taint)
and found no behaviour defect. It found the root's abort rule had no test
off the full window, since the bench and a fixed depth search open the root
at the full window. Two tests now drive the root at an aspiration window no
move beats: the search answers a ceiling, the same search stopped one node
short answers nothing, and a root whose every move scores exactly alpha
answers a ceiling.
documented on
TaintPolicy::Skip; a test now holds it.Tests go from 784 release and 786 debug in the workspace to 789 and 791.
🤖 Generated with Claude Code