Backgammon AI Play
This page was mostly written by CLAUDE AI, with the instructions to be technical and include all information required to program this software yourself. It is the technical companion to the User's Guide, which
describes the controls. Several of those controls — the depth menus, the brain editor,
EVOLVE, LEARN, COMPETE and TOURNAMENT — only make sense once you know what is
behind them. Where an algorithm is easier to state as a program than as prose it is given as pseudocode.
There are two kinds of artificial player here, developed one after the other and both still in use.
The older kind judges a position by adding up a couple of dozen hand-chosen features with weights found by an
evolutionary search; the newer kind judges it with a neural network that taught itself by playing against
itself. They share the same search, the same rules and the same tournament machinery, and they can play each
other. Sections 2 to 4 describe the first kind,
section 5 the second, and section 6 how either is measured.
The Table of Contents below takes you to any part of this page. Clicking on a symbol like this
⇧ anywhere on the page
brings you back here.
Table of Contents
1. Two Kinds of Brain⇧
1.1. What both kinds share
A brain is whatever answers one question: given a position, how good is it? Everything else
— how the legal moves are generated, how far ahead the game is searched, how a match is scored, how a
tournament is run — is the same for both kinds and is described in section 2.
That is not a tidy coincidence but a deliberate design: the two kinds of brain meet the rest of the program
through a single function, so a net can be dropped into any seat a parameter AI can occupy, and the two can
play each other in the same match.
Both kinds work on the same board model. The points are numbered 1 to 24 in a fixed
absolute frame. White moves from 24 towards 1 and bears off from points 1–6; Red moves
from 1 towards 24 and bears off from 19–24. The bar is treated as a virtual point one step
behind each player's entry edge: point 25 for White, point 0 for Red. A position is therefore three things:
the 24 points, each holding a count and an owner; the two bar counts; and the two borne-off counts.
Both kinds also answer in a single currency and from a single point of view. Every value in the
search is stated from White's point of view: positive is good for White, negative is good for Red.
White maximizes it and Red minimizes it. The two kinds do not use the same units — that is the
one place they differ visibly, and section 1.2 says what the units are — but
within one search there is only ever one brain's currency, because a search is one player deciding.
1.2. What tells them apart
| Parameter AI | Judges a position by measuring about twenty features of it — the race, blots, primes, made points — and adding them up with 24 weights, of which 22 score the board and 2 govern the doubling cube. The result is an integer in a band of roughly ±1000 with no probabilistic meaning: it is a comparison device, not a prediction. You can read every weight, edit it, export it and evolve it. Section 3. |
| Net | Judges a position with a neural network that predicts the probability of each of the six ways the game can end. Its value is therefore an equity: the expected number of points, between −3 and +3. Its knowledge is spread over tens of thousands of numbers that mean nothing individually; it cannot be edited or evolved, only trained and measured. Section 5. |
The consequence you see on screen is that the two report differently. A parameter AI's judgment of a
position appears as a band number such as 332; a net's appears as a probability, 55%, or as an
equity, +0.298. They are deliberately not rescaled onto a common axis, because there is no
honest conversion between them: one is a heuristic ordering and the other is a prediction.
2. Choosing a Move: the Search⇧
2.1. Legal complete turns
A backgammon turn is not one move but a sequence: two moves for an ordinary roll, four for a double, and
fewer only when the rules leave no choice. The rules also require you to use as much of the roll as you can
— both dice if any legal sequence plays both, the higher die if only one can be played, as many as
possible for a double. So the unit the AI chooses between is not a single checker move but a complete
turn: an entire legal, maximum-usage sequence, together with the position it leads to.
The generator therefore produces every distinct complete turn from a position and a roll, de-duplicated by
the resulting position rather than by the sequence of moves, since the same position can usually be
reached by playing the dice in either order and there is no sense in evaluating it twice. An opening
position offers roughly 15 to 20 distinct complete turns for a typical roll; a double with the whole army in
play can offer many more. If a roll admits no legal play at all, the generator returns the position
unchanged as a single "turn", so a dance needs no special case anywhere in the search.
2.2. How deep: the depth menus
The search depth, set in the depth menus of the control column, is measured in plies. A
ply is one player's turn — a half move.
- Depth 0 — no judgment at all: the first legal complete turn the generator produced is
played. It is not a way to play; it exists as a floor to measure against.
- Depth 1 — apply the static judgment to every position reachable with the current
roll and take the best. No future roll is considered. This is the default and it is instant.
- Depth 2 — for each of my candidate turns, consider what the opponent would do with each of
the 21 distinct rolls he might get, average those values, and choose the candidate with the best
average.
- Depth 3 — the same, one turn deeper: my move, his roll and reply, my next roll and reply.
⚠ The numbering here is one higher than the literature's. What this page calls depth 1 —
evaluate every legal turn, take the best, look no further — is what GNU Backgammon and the TD-Gammon
papers call 0-ply, because no future roll is averaged over. So depth 2 here is 1-ply there, and
depth 3 is 2-ply. Depth 0 has no counterpart anywhere else. When comparing figures on this page with
published ones, line them up that way.
2.3. Why expectimax and not minimax
The minimax search used for chess, and for the Mill and Reversi games elsewhere on
this site, assumes the opponent moves next and chooses. In backgammon he does not: the dice choose first.
The correct search alternates two kinds of node.
| decision | A player is on roll with a known roll and picks a complete turn. White takes the maximum of the values below, Red the minimum — one currency, two directions. |
| chance | A roll is about to happen. The value is the average over the 21 distinct rolls, each weighted by how often it occurs: a non-double two ways out of 36, a double one way. The weights sum to 36, so a chance node is $\left(\sum_r w_r\,V_r\right)/36$. |
This is expectimax. Note that averaging happens over the dice, never over the opponent's
choices: given his roll he is assumed to play his best, in the same currency, which is the usual zero-sum
assumption. It is an approximation whenever the two players are different brains, since the search models
the opponent as a copy of the deciding brain — but it is the only assumption available, and the error is
second order.
Alpha-beta pruning, which makes minimax practical in chess, helps very little here: a chance node has to
inspect essentially all of its children to know their average, so the pruning that makes deep chess search
possible has almost nothing to bite on. Sharper methods exist for expectimax trees (the *-minimax family)
and none is implemented; the cost figures in section 2.7 are therefore the cost of a
full search.
2.4. The algorithm
The whole search is three short functions. leafValue is the seam described in section
1.1: it is the only place a brain is consulted, and it is where the two kinds of
brain differ.
Choosing a move at the root
function bestTurn(position, player, roll, depth, brain):
turns = generateCompleteTurns(position, player, roll)
if turns is empty: return none
if depth == 0: return turns[0] // depth 0 plays the first one
opponent = other(player)
best = none
bestValue = (player == White) ? -infinity : +infinity
for turn in turns:
v = terminalValue(turn, brain) // null unless the game just ended
if v == null:
if depth == 1: v = leafValue(turn, brain, onRoll = opponent)
else: v = expecti(turn, opponent, depth - 1, brain)
v = v + rootBonus(turn, brain, player) // section 2.6; zero for a net
if (player == White) ? (v > bestValue) : (v < bestValue):
bestValue = v; best = turn
return best
A chance node: average over the 21 rolls
function expecti(position, player, plies, brain):
v = terminalValue(position, brain)
if v != null: return v
total = 0
for (d1, d2, weight) in DICE: // 21 entries, weights sum to 36
dice = (d1 == d2) ? [d1,d1,d1,d1] : [d1,d2]
total = total + weight * rollValue(position, player, dice, plies, brain)
return total / 36
A decision node: the best reply to one known roll
function rollValue(position, player, dice, plies, brain):
opponent = other(player)
best = (player == White) ? -infinity : +infinity
for turn in generateCompleteTurns(position, player, dice):
v = terminalValue(turn, brain)
if v == null:
if plies <= 1: v = leafValue(turn, brain, onRoll = opponent)
else: v = expecti(turn, opponent, plies - 1, brain)
best = (player == White) ? max(best, v) : min(best, v)
return best
Three details are worth keeping if you reimplement this. The turn generator must accept an
arbitrary position, not just the current one, since the search asks it about positions that were never
played. A roll with no legal play must yield one "turn" equal to the position itself, so that a dance costs
no special case. And leafValue is told who is on roll at the leaf: a parameter AI
ignores that argument, but a net's answer depends on it entirely (section 5.6).
2.5. Positions that are already decided
A position in which one side has borne off all fifteen checkers is not judged, it is known, and the
two kinds of brain express that differently.
A parameter AI uses a sentinel: a win is worth $10^9$ and a loss $-10^9$, so that a certain win outranks
every heuristic score. A net uses the exact equity of the finished game — ±1 for a
single, ±2 for a gammon, ±3 for a backgammon — because that is the scale everything else
it says is on. The side effect was accepted deliberately: for a net, a certain single win (+1) does
not outrank every alternative, so a position it reads at +1.4 can be preferred to winning at once.
That is correct play, not a bug — it is the search declining to take a single when a gammon is nearly
certain.
2.6. Two adjustments made only at the root
Two terms are added when the AI actually chooses a move, and are not part of any position's stored
value. Both apply to parameter AIs only.
- The disengagement bonus, DE. One of the 22 weights. It is added to a candidate turn that breaks
all contact while the mover is ahead in the race: $+\mathrm{DE}$ for White, $-\mathrm{DE}$ for Red.
A positive DE says "race away when you are ahead", a negative one says "keep the contact". It is
deliberately excluded from the search interior: inside the tree it would be counted repeatedly for the
same disengagement.
- The race tie-break. Once contact is broken the static score ties for almost every legal turn
— see section 3.8 for why — and something has to decide. The tie-break
prefers the turn that leaves the mover with fewer pips still to travel before every checker is home:
$\sum_{p>6}\,\mathrm{count}_p\cdot(p-6)$ for White, the mirror for Red, with a checker on the bar counted
as 19. It is scaled by $\varepsilon = 0.001$, and its largest possible value is $15\cdot19\cdot\varepsilon
= 0.285$, safely under the half-unit that separates two different integer scores. So it breaks ties and
can never overrule a real difference.
A net gets neither. DE is a weight it does not have, and its equity is of order 1 rather than of order
1000, so an $\varepsilon$ of 0.001 would be a real perturbation rather than a tie-break. It needs neither:
it does not tie, and it prices a race directly.
2.7. What a ply costs
Each extra ply multiplies the work by the number of dice outcomes times the number of replies to each:
21 rolls by about 18 to 20 complete turns, so roughly 400. That is not a rule of thumb, it is what
the measurement says. The figures below are single-threaded, on one core of a 32-thread desktop, and you can
reproduce the ratio (though not the exact numbers, which are your machine's) by running COMPETE at two
depths and comparing how long each takes.
| depth | time per move, one core | times the previous ply |
| 0 | 0.14 ms | — |
| 1 | 0.58 ms | 4 |
| 2 | 245 ms | 419 |
| 3 | ~89 s | 364 |
Divide the two large factors by 21 and you get 20.0 and 17.3 — the average number of legal complete
turns, exactly as the arithmetic predicts, with nothing unexplained hiding in the constant. Depth 0 to
1 is only a factor of 4 because depth 0 does no evaluation at all.
The practical consequences run through the whole design. Depth 2 is comfortable for interactive play and
affordable for a tournament. Depth 3 is a fine setting for a person taking their time over one move, and it
is not a tournament setting: a single depth-3 decision is tens of seconds, so one match is hours.
2.8. Running the search in parallel
The game uses as many Workers — independent processes in your browser — as row 1 of the
control column allows, and it splits the work two different ways depending on what is being done.
| batch | A tournament, a competition, an evolution run or a training run gives each worker one whole match (or one whole self-play game). The jobs are independent and there is nothing to combine, so throughput is very nearly the number of workers. |
| interactive | One move at depth 2 or more is split into one job per (my candidate turn × opponent's roll) pair — a few hundred jobs — and the results are recombined exactly as the serial search would: sum $w_r V_r/36$ within a candidate, then take the maximum for White or the minimum for Red. |
The parallel decomposition returns the same move as the serial search, not merely a similar one;
that equality has been verified position by position, including at depth 3, against an independent serial
implementation. That matters because it means the depth menus mean the same thing whether or not your
browser gave the game any workers.
Two consequences of the batch split are worth knowing when you time something. A run of one match
uses exactly one worker however many you have, because the unit of work is a whole match. And a run of
exactly as many matches as workers is the worst case for measuring speedup, since the wall clock then collapses
to the single longest match: to see the real throughput, give the pool several times as many matches as it has
workers.
3. The Parameter AI⇧
3.1. The static evaluation
The static evaluation takes a position and returns one number, the score, saying how good it is for
White. Every feature is measured for both sides and entered as a difference, White minus Red. With
$w_i$ for the weights and $f_i$ for the features,
$ \displaystyle S \;=\; \sum_i w_i\,\bigl(f_i^{\,\text{White}} - f_i^{\,\text{Red}}\bigr). $
Writing it as a difference buys an exact property, and the property is worth more than the tidiness:
the score is anti-symmetric. Mirror a position — swap the colors, reflect point $i$ to
$25-i$, swap the bars and the trays — and the score must come out exactly negated. That is the
strongest cheap correctness check available for an evaluation function, since a feature that scans the board
in one direction for White and the other for Red is easy to get subtly wrong, and two real bugs were caught
this way. It is tested by mirroring some twenty thousand positions from real games after every change.
The score is rounded to a whole number, rounding halves away from zero rather than JavaScript's
default of rounding towards $+\infty$, since the default would break the anti-symmetry on positions landing
exactly on a half.
There are 22 such weights, plus 2 more for the cube (section 3.10), which is
why the parameter grid in the control column has 24 fields. Several of the features are divided by a
constant before being weighted — the pip count by 20, borne-off checkers by 15, the encumbrance counts by
36 — purely so that every weight lives in the same numeric band. Those divisors are a representation
choice and nothing else; when the pip divisor was changed from 167 to 20, every brain's PC weight was rescaled
by the same factor and no brain's play changed at all.
3.2. The race: PC and BO
| PC | Pip count. How far a side still has to travel: $\sum_p \mathrm{count}_p \cdot \mathrm{distance}_p$, with a checker on the bar counted from its entry edge. Entered as $(\mathrm{pip}_W - \mathrm{pip}_R)/20$ and carried with a negative weight, so that having fewer pips left is good. |
| BO | Borne off. $(\mathrm{off}_W - \mathrm{off}_R)/15$, positive weight. It exists because the pip count cannot tell bearing a checker off from stacking it deeper in the home board — both reduce the pip count by the same amount — and without it brains piled checkers on the 1-point instead of taking them off. |
⚠ The pip count is nearly blind to move choice, and this shapes the whole roster. Whatever you
do with a roll, your own pip count falls by exactly the dice total. So PC does not distinguish one legal turn
from another except through hits, which put the opponent back, and through the efficiency of a bear-off.
Section 4.6 returns to this: it is the reason a "pure racer" still hits, and hits
soundly.
3.3. Mobility: EC1 and EC0
Both count over the 36 ordered dice rolls, weighted by frequency, how badly a side would be stuck. For
each roll the generator is asked how many dice could be used at all.
| EC1 | Encumbrance, one die. The number of the 36 rolls that would let the side use exactly one die. Divided by 36, negative weight. |
| EC0 | Encumbrance, no die. The number of the 36 rolls that would let it use none — a dance. Divided by 36, negative weight. |
These are expensive features: computing them means asking, 21 times per side, how much of a roll is
playable. They are also the two weights evolution has been most consistent about, with EC0 pinned near the
bottom of the band in almost every brain in the roster.
3.4. Containment: the escape count and F0–F5
Six weights grade how trapped a side's checkers are. They rest on one primitive, the escape count
of a single checker: how many of the six die-values would take it past the front of the wall in front of
it. The wall matters, not the next square: a checker with an open point immediately ahead but a five-prime
beyond it is trapped, and the escape count says so.
The escape count of one checker (White; Red is the mirror)
function escapeCount(points, p, me): // p = 25 means a checker on the bar
opponent = other(me)
made(t) = (t is on the board) and points[t].owner == opponent and points[t].count >= 2
// find the first opposing point ahead of us, and the far end of the block it belongs to
back = the largest t < p with made(t), or none
if back == none: // nothing ahead: count open landings
return #{ d in 1..6 : p-d < 1 or not made(p-d) }
front = back; while made(front - 1): front = front - 1
if back - front + 1 == 1: // a LONE point: weave around it
return #{ d in 1..6 : p-d < 1 or not made(p-d) }
return #{ d in 1..6 : p-d < 1 or (p-d < front and not made(p-d)) }
Landing beyond the edge of the board — bearing off — counts as an escape. The result is 0 for
a checker that is sealed in and 6 for one that is free.
| F0 | Sealed. No die frees the checker: entombed behind a full prime, or on the bar against a closed board. The worst place a checker can be. |
| F1 … F5 | One, two, … five of the six dice free it. |
Each side's checkers are counted into these six buckets — a checker on the bar included, as a virtual
checker at point 25 or 0 — and the term is $\sum_{n=0}^{5} w_{Fn}\,(\mathrm{White}_n - \mathrm{Red}_n)$.
There is deliberately no F6. A completely free checker carries no information: the count of free
checkers is 15 minus the others and minus those borne off, so a sixth weight would be a linear combination of
the ones already there, and it would overlap with BO.
3.5. The bar: BE
A checker on the bar is worse than a checker on a point with the same escape count, because it freezes the
whole army: nothing else may move until it enters. BE prices that at one weight per expected
frozen turn, added on top of the checker's ordinary F-term.
If a bar checker enters on $e$ of the six die-values, both dice miss with probability $\left(\frac{6-e}{6}
\right)^{2}$, and the expected number of frozen turns is the geometric sum $\frac{q}{1-q}$ with that $q$:
| escape count | 1 | 2 | 3 | 4 | 5 | 6 |
| expected frozen turns | 2.27 | 0.80 | 0.33 | 0.125 | 0.029 | 0 |
A sealed bar checker (escape count 0) is the interesting case, because how long it stays there is
not a dice question but a question about the opponent: he must break his closed board when he runs out of spare
pips. A bare closed board is twelve checkers on six points, or 42 pips, and about 8 pips are played per turn,
so the freeze is priced at $(\mathrm{opponent's\ pips} - 42)/8$, floored at 0 and capped at 8 turns.
The term is $\mathrm{BE}\cdot(\sum_{\text{Red}} \mathrm{freeze} - \sum_{\text{White}} \mathrm{freeze})$: a
White checker on the bar hurts White, a Red one helps him. Note that the sealed case reads the opponent's
pip count, which makes this a direction-dependent feature and therefore one to re-check against the mirror test
after any change.
3.6. Made points: HB, G5, G7, G4, AN, GA
A point is made when a side has two or more checkers on it. Not all made points are worth the
same, so the valuable ones carry their own weights and the rest are counted in bulk. All of these are
owner-relative: White's $n$-point is board point $n$, Red's is board point $25-n$, which is what keeps
them anti-symmetric.
| G5 | The golden point — the owner's 5-point. The single most valuable point on the board. |
| G7 | The bar point — the owner's 7-point. |
| G4 | The owner's 4-point. With G5 and G7 it completes the prime-building trio. |
| HB | Home-board points, excluding the 4- and 5-points, which have their own weights above: so it counts the owner's 1-, 2-, 3- and 6-points. |
| GA | The golden anchor — the owner's 20-point, which is the opponent's 5-point, held from the back. Counted only while the owner is behind in the race, since an anchor is a defensive asset. |
| AN | Anchors: points made in the opponent's home board, excluding the golden anchor. Same behind-in-the-race gate. |
The exclusions matter: HB and AN cover exactly the points the four golden weights do not, so nothing is
counted twice. The same board point can read as White's G5 and as Red's GA — point 5 is White's 5-point
and Red's 20-point — but only one of them owns it, so there is no conflict.
These six weights were the change that broke a long-standing deadlock. Before they existed, evolution
converged on pure racing every time; with them, and with the race terms deliberately reined in so they no
longer dominated the scale, a positional brain that prizes the 5-point finally won the roster.
3.7. Blots: DO, IO, DP, IP
A blot is a lone checker, which may be hit. For every blot on the board the evaluation asks
whether the opponent can hit it with a single die (direct) or with a combination of dice landing
somewhere legal on the way (indirect), and whether the blot stands in the near half of its owner's
board or the far half. That is four weights.
| DP / IP | Direct / indirect shot at a blot in the near half — points 1–12 for White, 13–24 for Red. |
| DO / IO | Direct / indirect shot at a blot in the far half. |
Each threatened blot contributes its weight once, signed so that a blot belonging to Red is good for
White. The same number therefore describes both an exposure and a threat: they are the same event seen from
two sides, which is why four weights suffice where an earlier version of the evaluation had eight.
One simplification is deliberate: when the hitter has a checker on the bar, only hits that enter from the
bar — either directly, or entering and continuing with the other die — are counted. That
under-counts a few shots and never over-counts.
Two sides are in contact while the rearmost White checker is still behind the rearmost Red one, so
that a hit is possible; a checker on the bar always counts as in contact. Once contact is broken the game is
a pure race, and most of the evaluation is then measuring things that cannot happen: a made point blocks
nobody, an anchor traps nobody, a blot cannot be hit. So HB, G5, G7, G4, AN and GA are all switched off
when there is no contact, and the blot and containment terms fall to zero on their own.
This is a symmetric $0/1$ scalar multiplying anti-symmetric terms, so it leaves the mirror property intact.
⚠ It also leaves the evaluation with almost nothing to say. In a pure race PC is constant
across move choices, BO is unchanged until you start bearing off, and everything else is gated off — so
the integer score is frequently identical for every legal turn, and the tie-break of section
2.6 decides. That is a known weakness of this evaluation, not a subtlety: in the race
the tie-break is effectively the whole policy. The net has no such gap, which is one reason it is stronger.
3.9. Normalization
Only the proportions between the weights affect play: multiplying every weight by a positive
constant multiplies every score by it and changes no comparison. Brains are therefore normalized so
that the largest evaluation weight in absolute value is exactly 1000.
The rule has two details that matter. The scale factor is chosen from the 22 evaluation weights
only — the two cube thresholds do not get a vote — but it is then applied to all 24,
because DT and AT are compared against scores: if every score doubles, the thresholds must double too or the
doubling behavior changes. And a weight that lands exactly on zero is set to $\pm 1$ rather than left at
zero, so that no feature can vanish from a brain irrecoverably.
3.10. The cube: DT and AT
A parameter AI's score has no probabilistic meaning — there is no way to derive a take point from
"$-603$ times a pip difference" — so its cube policy is two more evolvable numbers, in the same units as
the score. Let the own score of a player be the static score seen from that player's side (the score
for White, its negation for Red).
| DT | Double threshold. Offer a double, or redouble, when own score $>$ DT. |
| AT | Accept threshold. Accept an offered double unless own score $< -$AT. |
A sensible take window needs AT larger than DT. Evolved brains vary, and a few invert it — an
aggressive doubler that is also a tight taker. Note that this rule has no upper end: there is no "too good to
double", so a parameter AI will double away a position it should have played on for the gammon. The net's
cube (section 5.11) does not have that defect, and gets it for free rather than
by a special rule.
3.11. The current roster
The site ships with eight named brains kept in order of playing strength, Arwen strongest, and re-sorted
after each tournament, plus Origin, the hand-guessed historic baseline, which never changes and is
always listed last. The table below is a snapshot; the live values are always in the parameter grid of the
control column, and can be exported from there.
| Brain |
PC | BO | EC1 | EC0 | F0 | F1 | F2 | F3 | F4 | F5 | BE | HB | G5 | G7 | G4 | AN | GA | DO | IO | DP | IP | DE | DT | AT |
| Arwen | -603 | 1000 | -70 | -970 | -16 | -19 | -20 | -6 | -7 | -1 | 8 | 29 | 291 | 13 | 38 | -68 | -47 | 10 | 10 | 290 | 59 | 4 | 308 | 735 |
| Bilbo | -435 | 633 | -62 | -1000 | -13 | -14 | -21 | -9 | -5 | -1 | 9 | 28 | 228 | 6 | 31 | -46 | -41 | 6 | 11 | 205 | 60 | 3 | 241 | 836 |
| Celebrian | -412 | 738 | -60 | -1000 | -11 | -12 | -19 | -8 | -4 | -1 | 7 | 23 | 190 | 6 | 25 | -41 | -33 | 6 | 10 | 168 | 58 | 3 | 236 | 686 |
| Dwalin | -778 | 834 | -184 | -1000 | -13 | -13 | -22 | -3 | -10 | -2 | 10 | 66 | 382 | 17 | 26 | -32 | -43 | 18 | 9 | 373 | 22 | 4 | 400 | 999 |
| Eowyn | -433 | 22 | -140 | -1000 | -8 | -15 | -22 | -6 | -8 | -2 | 7 | 66 | 181 | 21 | 30 | -19 | -80 | 19 | 12 | 267 | 32 | 4 | 330 | 588 |
| Frodo | -903 | 32 | -153 | -1000 | -8 | -16 | -25 | -12 | -10 | -6 | 16 | 58 | 281 | 18 | 44 | -47 | -61 | 18 | 13 | 469 | 46 | 6 | 398 | 1009 |
| Galadriel | -1000 | 492 | -118 | -771 | -7 | -8 | -25 | -6 | -9 | -2 | 11 | 27 | 227 | 12 | 23 | -46 | -52 | 9 | 13 | 298 | 45 | 4 | 322 | 716 |
| Hamfast | -978 | 351 | -117 | -1000 | -73 | -57 | -7 | -20 | -21 | -4 | 14 | 268 | 277 | 105 | 39 | -3 | -9 | 9 | 4 | 347 | 25 | 67 | 220 | 857 |
| Origin | -80 | 500 | -133 | -400 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 133 | 133 | 0 | 133 | 200 | 200 | 1000 | 600 | 500 | 300 | 667 | 100 | 200 |
A glance down the table tells a story. Origin, the hand-tuned baseline, pours its weight into
hitting (DO = 1000), knows nothing of containment (F0–F5 and BE are all zero, since those features did
not exist when it was written), and doubles on very little (DT = 100). The evolved brains do the opposite:
they hold hitting to almost nothing, prize the race (PC), bearing off (BO) and the golden 5-point, and double
far later. That is why they beat the original so decisively.
4. Evolution: Where the Numbers Come From⇧
None of the values in the table above were chosen by hand — except Origin's, which were guessed
at the start of the project and never touched since. All the others were evolved, by the EVOLVE
button in row 14 of the control column.
The difficulty is that there is no formula telling us whether one set of 24 numbers is better than another.
The only honest test is to let two brains play and see which wins more — and because backgammon is a dice
game, even that answer is noisy: a weaker brain wins a short series often enough. So the search has to be
driven entirely by match results and has to be skeptical of them. What follows is a hill climber: it
keeps a single parent, tries a random variation, and promotes it only if it survives two increasingly demanding
tests.
4.1. What you control
| Brain | The brain selected in the editor menu (row 13) is the starting parent. Evolution never modifies it — results arrive as downloaded files. |
| Generations | How many rounds to attempt before stopping. You can stop earlier at any time with the same button, which reads Stop E while a run is going, or with STOP in row 5. |
| Matches | Matches per sample: how many matches each individual comparison is decided by. Larger is slower and less noisy. |
| R % | The mutation rate: the largest random change, as a percentage, that any single weight may undergo in one step. |
| D: | The look-ahead depth used for every game played during the run. Depth 1 is the practical setting; see section 2.7. |
4.2. One generation
- Mutation. Every one of the 24 numbers — the 22 evaluation weights plus the two cube
thresholds — is nudged independently by a random amount of up to R % of its own current value:
$ \delta_i = \operatorname{round}\!\left( w_i \cdot \frac{R}{100} \cdot u \right), \qquad u \ \text{uniform on}\ (-1,1). $
Because the change is proportional to the weight itself, a single mutation can never flip a sign, and
large weights move in large absolute steps while small ones move in small steps — which is what you
want when the weights span three orders of magnitude. (If by chance nothing changed at all, one weight
is nudged by 1 so the generation is not wasted.)
- Normalization. The mutant is rescaled as in section 3.9, so that every
candidate is measured on the same scale as its parent.
- The scout. The mutant plays the parent over the chosen number of matches, colors alternating so
that neither side keeps any advantage of moving first. If it does not come out ahead it is discarded
and the generation ends here. Most generations end here, and that is the point: the scout is the cheap
filter that lets a bad idea die quickly.
- The line search. A promising mutation tells us not only that a point is better but which
direction was better. So instead of accepting the mutant as it stands, the algorithm treats the
difference between mutant and parent as a direction vector and walks along it: it tries a step further
on, and a step back, keeps whichever wins, and doubles the step while it keeps improving. When a
step overshoots, the step is halved instead. Because the weights are integers, repeated halving
reaches zero exactly, so the walk always terminates — and in a few dozen matches it can travel much
further than a single mutation ever could. This is also the only place a weight can change sign, by
being extrapolated through zero.
- The gauntlet. The survivor faces a much longer series — ten times the sample size —
against a fixed baseline brain, and is crowned a champion only if it beats that baseline by a wider
margin than any previous candidate in this run. Testing against a fixed opponent rather than against the
current parent matters: it is a common yardstick, so successive champions are measured against each other
and not merely against whatever the parent happened to be at the time.
- Sweeps. If a champion beats the baseline in every single match, the baseline has become too
weak to distinguish candidates. The new champion replaces it, the record resets, and the gauntlet
length doubles — so the standard of proof rises as the brains improve.
4.3. A caveat on granularity
Because weights are whole numbers and the mutation is proportional, a weight can only move if
$|w|\cdot R/100$ can reach one half — that is, if $|w| > 50/R$. At the default R = 50 %
a weight of ±1 is therefore frozen: every proposed change rounds back to zero. Lowering R to
search more finely freezes more of them (at R = 10 %, everything up to ±5). Such a
weight is not trapped forever — re-normalization can lift it when some larger weight shrinks — but
it escapes only slowly, and only upward in magnitude. Since a weight of 1 against a scale of 1000 has almost
no effect on play this costs little strength, but it does mean a feature crushed to ±1 early in a
lineage is hard to rediscover later.
4.4. Matches, not points
Every comparison above is decided by matches won, never by points scored. This is deliberate, and
it was learned the hard way: when brains were scored on total points, evolution discovered it could inflate its
score by escalating the doubling cube without limit, and optimized for that instead of for playing well. One
game worth a million points swamped a tournament of thousands of games. Counting matches removes the
incentive entirely — a match is a match however extravagantly it was won. Section
6.1 describes the match rules this rests on.
4.5. Nothing is installed automatically
Each champion is downloaded as a small file the moment it is crowned, and the parameter grid updates to
show it, but the roster is never changed behind your back. Evolution runs produce candidates; deciding
which candidates deserve a place among the nine brains is a separate, human decision, made by importing a
champion (row 13) and running a tournament to see where it really belongs. Since results over a few dozen
matches are noisy, several hundred matches per pairing are worth the wait before trusting any re-ordering
— see section 6.3.
4.6. What evolution has found
Evolution beats hand-tuning, decisively. Every evolved brain outplays the hand-tuned
Origin, and by a wide margin. Hand-tuning two dozen interacting numbers turns out to be something
humans are simply bad at.
It compounds. Evolving a brain that is itself the product of an earlier evolution keeps yielding
improvements, several generations deep, although the margins narrow.
It found the racer — and then found its way past it. For most of the project every run
converged on nearly the same strategy: pour everything into the race and almost nothing into hitting. That is
less strange than it sounds, and section 3.2 has the reason. Your own pip count falls
by the dice total whatever you do, so the race term barely distinguishes one legal play from another —
except through hits, which set the opponent back, and through efficient bearing off. A "pure racer"
therefore still hits, and hits soundly, for reasons of the race; whereas an explicit hitting weight tempts a
brain into hits its blunt evaluation cannot properly judge. Only after the golden-point weights were added,
and the race terms deliberately reined in so that they no longer dominated the scale, did a genuinely
positional brain — one that prizes the 5-point — finally win the roster.
And there is a ceiling. Gauntlet margins have been shrinking for some generations, and the whole
of the parameter roster is now beaten comfortably by a net trained for a few minutes. That is the honest
summary of what a hand-designed feature set can reach here, and it is why the second kind of brain exists.
5. The Net⇧
A net replaces the twenty-two hand-designed features with a neural network that is shown nothing but
the raw position and taught by playing against itself. It is never given a human game, and nobody ever tells
it what a prime or an anchor is. The design follows Gerald Tesauro's TD-Gammon, with the differences noted
where they occur.
5.1. Four decisions taken before any code
- The encoding is mover-relative. Every position is presented from the point of view of the side
on roll; if that is Red, the board is mirrored first. Color symmetry then becomes a property of the
encoder, exact to the last bit, instead of something the network has to learn — and every
training game teaches both colors at once. One consequence: Tesauro's two turn-indicator inputs are
constant here and are omitted, so the input is 196 wide rather than 198.
- The output is a distribution over six outcomes, not a score: my single, gammon and backgammon,
and the opponent's three. Six units rather than five, although one is redundant: softmax is invariant
to a constant shift, so the redundancy is harmless, and keeping all six is what makes the output layer
symmetric under the mirror.
- The cube is not an input. The net predicts the cubeless outcome distribution, and every
doubling decision is computed from those probabilities afterwards (section
5.11).
- No genetic algorithm. Self-play learning replaces it entirely. A net cannot be evolved and the
EVOLVE button refuses one.
5.2. The encoding
The 196 inputs are laid out as follows. Remember that the position has already been mirrored if
necessary, so the mover is always the first side.
| 0–95 | The mover's 24 points, four units each. |
| 96–191 | The opponent's 24 points, four units each. |
| 192, 193 | The mover's and the opponent's checkers on the bar. |
| 194, 195 | The mover's and the opponent's checkers borne off. |
The four units of a point encode how many checkers of that owner stand on it, in truncated unary:
the first three are 1 when the count is at least 1, 2 and 3 respectively, and the fourth carries the excess,
$(n-3)/2$. So one checker reads $(1,0,0,0)$, three read $(1,1,1,0)$, and five read $(1,1,1,1)$ with the last
unit at 1. The point is that "a blot", "a made point" and "a stack" are distinct features the first
layer can weight separately, rather than one number the network would have to learn to threshold.
Bar and borne-off counts are compressed as $\sqrt{n}$, which keeps them in the same band as the
indicator units. Tesauro's original is reported variously as $n/2$ and $\sqrt{n}$; the choice here is one
line and has never been measured against the alternative.
The encoding is sparse by construction: at most thirty checkers are on the board, so at most about
thirty of the 196 inputs are ever nonzero — the measured median is 28. Both the forward pass and the
first layer's gradient iterate over the nonzero entries only, which is roughly seven times less work than a
dense pass and is the reason a wide input encoding costs memory rather than time.
Encoding a position
function encode(points, bar, off, mover):
if mover == Red: (points, bar, off) = mirror(points, bar, off)
x = 196 zeros
for side in {mover: 0, opponent: 96}:
for p in 1..24:
n = number of that side's checkers on point p
b = side_base + (p-1)*4
if n >= 1: x[b] = 1
if n >= 2: x[b+1] = 1
if n >= 3: x[b+2] = 1
if n > 3: x[b+3] = (n-3)/2
x[192] = sqrt(bar[mover]); x[193] = sqrt(bar[opponent])
x[194] = sqrt(off[mover]); x[195] = sqrt(off[opponent])
return x
5.3. The shape of the network
The network is a plain feed-forward stack: 196 inputs, one or more hidden layers of equal width, and six
outputs. The width and the number of layers are the W and L fields of the LEARN row; the
activation function $\varphi$ applied at every hidden unit is the menu beside them.
| sigmoid | $1/(1+e^{-z})$. What TD-Gammon used, and measurably the best of these on this encoding. |
| tanh | $\tanh z$. Note $\tanh z = 2\,\mathrm{sigmoid}(2z) - 1$, so a tanh net and a sigmoid net represent exactly the same function class; they differ only in parameterization and in gradient scale. |
| lrelu / relu | $\max(z,0)$, with leaky ReLU passing $0.01z$ instead of 0 below zero. |
| arctan | $\arctan z$: the same odd saturating shape as tanh but with polynomial rather than exponential tails, so a hard-driven unit keeps passing a usable gradient. |
| ident | $z$ — a control, not a brain. A composition of affine maps is affine, so an identity network of any depth collapses to a single $196 \to 6$ linear map with a softmax: multinomial logistic regression on this encoding. It answers "how much of the net's strength is the nonlinearity rather than the representation?", and the layer menu is dimmed when it is chosen because depth is meaningless for it. |
Weights are initialized from a Gaussian with standard deviation $\sqrt{g/\mathrm{fan\_in}}$, where $g$ is
2 for the ReLU family and 1 otherwise, and biases at zero.
⚠ The first layer's fan-in is the number of ACTIVE inputs, not the input width. The textbook
rule uses $1/196$, which assumes every input carries signal; here about 86% of them are zero, so $1/196$ would
start every hidden unit in the near-linear region doing nothing at all, and the net would crawl for thousands
of games before anything happened. The expected active count, 27, is used instead. The output
layer's weights are additionally scaled by 0.1: large early logits saturate the softmax, and a net that starts
out confident spends its first thousands of games unlearning a confidence it had no reason to have.
5.4. The forward pass
Each layer computes $z = Wa + b$ and then $a = \varphi(z)$, except the last, which applies a
softmax:
$ \displaystyle p_k = \frac{e^{z_k}}{\sum_{j} e^{z_j}}, \qquad k = 1 \ldots 6. $
In practice the maximum $z$ is subtracted before exponentiating, which softmax is invariant to and which
keeps the exponentials from overflowing. The six outputs are then a probability distribution over the six
ways the game can end, from the mover's point of view.
The equity — the expected number of points — is a fixed dot product with no free
parameters at all:
$ \displaystyle E \;=\; p_1 + 2p_2 + 3p_3 - p_4 - 2p_5 - 3p_6. $
That is the whole reason for predicting a distribution rather than a score. The evaluation has to learn
probabilities, which are meaningful and checkable; how much a gammon is worth is arithmetic, not a parameter
somebody has to tune. The same six numbers also carry the cube decision (section
5.11) and the win probability shown in the move list.
One more operation is needed everywhere: the same distribution seen from the other side of the table
is $(p_4,p_5,p_6,p_1,p_2,p_3)$. The search needs it because a child position is the opponent's to move, and
the learning rule needs it for the same reason.
5.5. Positions the net is never asked about
If the game is over, the answer is known exactly and the network is not consulted: a finished position is
encoded directly as a one-hot outcome. The loser's borne-off count decides between a single and a gammon; a
gammon becomes a backgammon if the loser still has a checker on the bar or in the winner's home board. Asking
the network there would train it against its own noise on the one class of position where the truth is free.
5.6. How a net chooses a move
Exactly as in section 2: the search is the same code. The only difference is
what leafValue does, and it has one wrinkle worth stating carefully.
A net answers from the point of view of the side on roll at that position. After I play a
candidate turn it is the opponent's turn, so the equity the net reports for the resulting position is
the opponent's, and mine is its negative. Getting this wrong does not crash anything; it produces a net that
plays plausibly and badly. The same reasoning explains why the seam is told who is on roll: for a parameter
AI that argument is ignored, and for a net it decides the sign.
function leafValue(position, brain, onRoll):
if brain is a parameter AI: return evaluate(position, brain.weights) // White's view already
e = equity(net(position, mover = onRoll)) // the ON-ROLL side's view
return (onRoll == White) ? e : -e // into White's view
5.7. Learning from self-play
Training is a loop over complete games the net plays against itself, at depth 1, cubeless, with seeded
dice. Each game produces a trace: the sequence of positions at which a side was about to roll,
which is exactly the situation the net is asked about when a search reaches a leaf. The two sides alternate
strictly — a turn with no legal move still ends the turn — and the trainer asserts that, because
the learning rule depends on position $t+1$ belonging to the other player.
One self-play game
function selfPlayGame(net, seed):
g = new game with dice seeded from `seed`
g.rollForFirstTurn()
trace = [ (encode(g, mover = g.player), g.player) ]
while not g.over:
turn = argmax over legal complete turns of -equity(net(result, mover = other))
play turn
if g.over: break
g.endTurn()
trace.append( (encode(g, mover = g.player), g.player) )
g.rollDice()
return trace, terminalOutcome(g)
5.8. The λ-return
The question every learning rule has to answer is what each position in the trace should be trained
towards. Two extremes are obvious and both are bad.
| λ = 1 | Monte Carlo. Every position in the game is labeled with the outcome that actually happened. Unbiased and simple — but one game then delivers some sixty perfectly correlated gradient steps, which forces the learning rate down and, at any rate high enough to learn quickly, walks backwards. |
| λ = 0 | TD(0). A position is trained towards its successor's current estimate. Only the last position of a game carries any truth, and the net collapses towards the base rate: near-uniform predictions and games that get longer rather than shorter. |
The lambda-return interpolates between them. Walking the trace backwards, with $V$ the
network's own estimate and $\mathrm{swap}$ the outcome swap of section 5.4:
$ \displaystyle G_t \;=\; \operatorname{swap}\Bigl( (1-\lambda)\,V(s_{t+1}) \;+\; \lambda\,G_{t+1} \Bigr), \qquad G_{\text{last}} = \text{the actual outcome}. $
Both terms inside the swap are in the successor's frame, which is why they can be blended directly
and the swap happens once, at the end. Walking backwards propagates the outcome the whole length of the game
in a single sweep rather than one step per visit. At $\lambda = 1$ this reduces exactly to Monte Carlo and at
$\lambda = 0$ exactly to TD(0), which is worth checking in an implementation because it is easy to get the
frames wrong and hard to notice afterwards.
λ = 0.7 is the default and it is the largest single effect measured in this project —
larger than the width, the depth, or the choice of activation, by an order of magnitude. Section
6.4 has the numbers.
5.9. Backpropagation
The loss is cross-entropy between the target distribution and the network's output,
$L = -\sum_k t_k \log p_k$. Pairing a softmax output with cross-entropy has a convenient consequence: the
gradient with respect to the output logits is exactly
$ \delta^{\text{out}} = p - t, $
with no quotient rule and nothing that can blow up. From there it is the ordinary chain rule: for each
earlier layer, $\delta^{(l-1)} = \bigl(W^{(l)\top}\delta^{(l)}\bigr)\odot\varphi'(z^{(l-1)})$, the weight
gradient of a layer is $\delta$ times the incoming activation, and the bias gradient is $\delta$ itself. The
first layer's weight gradient is nonzero only in the roughly thirty active input columns, so the backward pass
through the largest matrix touches those and no others.
The update is plain gradient descent, $w \leftarrow w - \eta\,\partial L/\partial w$, applied once per
position in game order. There is no momentum, no weight decay and no adaptive rate. The learning rate is
fixed at $\eta = 0.01$ and there is deliberately no control for it: a wrong value ruins a run silently and
cannot be told apart afterwards from a bad idea, whereas the value that is there has evidence behind it.
Two checks are worth building if you reimplement this, because between them they catch nearly every
mistake. Compare the analytic gradient against a central finite difference — but require agreement in
absolute or relative terms, since wherever the true gradient is tiny the relative error is meaningless
roundoff. And overfit a single position: cross-entropy should fall to nearly zero. That second test, not
the gradient check, is what catches a sign error in the update.
5.10. Training in parallel
Self-play looks inherently serial — game $n+1$ is played by the net that game $n$ produced —
but a single game barely moves the weights: about 78 positions at a rate of 0.01, and each move is an argmax
over some twenty candidates. The net that plays game 1001 is, for practical purposes, the net that played
game 1000.
So training goes in rounds. The weights are frozen, a round of games is generated in parallel
across the workers, and then every update is applied on one thread in game order, exactly as a serial
trainer would. The learning code does not change at all; the only approximation is that a round's games
were played by a net one round stale instead of one game stale.
The round size is set by bandwidth, not by staleness: broadcasting the weights costs about 130 kB
per worker per round, so one game per round would spend more time copying than playing. Eight games per
worker puts a round at 256 games on 32 workers — a quarter of one percent of a 100,000-game run.
⚠ A seed reproduces a run only at the same number of workers, since the round size, and with it
the staleness boundary, is the worker count times eight. The same seed on 32 and on 16 workers gives two
different training runs. That is why the worker count is recorded in a net's file and displayed beside its
name.
Generation is about seven-eighths of the work, so parallelizing only the generation captures nearly all of
the available speedup. Measured on a 32-thread desktop: about 390 games per second, so a hundred
thousand games is a few minutes and a million is under an hour.
⚠ A run is written out only when it finishes. There is no checkpointing, so closing the tab
part-way through a long run loses all of it.
5.11. The cube: Janowski's formula
Because a net predicts the whole outcome distribution, its cube decisions are computed rather than
tuned: it has no DT and no AT. The model is Rick Janowski's (1993). Reduce the position, from the relevant
side's point of view, to three numbers:
| p | The probability of winning at all, $p_1+p_2+p_3$. |
| W | The average value of the games won: $(p_1 + 2p_2 + 3p_3)/p$. |
| L | The average value of the games lost: $(p_4 + 2p_5 + 3p_6)/(1-p)$. |
A dead cube (never usable again) and a perfectly live one (recubed at exactly the right moment) are both
solvable, and real play lies between them. One cube-life index $x \in [0,1]$ interpolates:
$ \displaystyle \mathrm{TP} = \frac{L - 0.5}{W + L + 0.5x}, \qquad
E_{\text{own}} = p\,(W + L + 0.5x) - L, $
$ \displaystyle E_{\text{opp}} = E_{\text{own}} - 0.5x, \qquad
E_{\text{centre}} = \frac{4}{4-x}\Bigl( p\,(W+L+0.5x) - L - 0.25x \Bigr). $
All four are per unit of the current cube value, so the cube value cancels out of every comparison.
Owning the cube is worth exactly $0.5x$. The take point TP is the classic 25% at $x = 0$ and 20% at $x = 1$
when $W = L = 1$, which is the check to write first.
The decisions follow directly:
| double | Offer or redouble when $\min(2E_{\text{opp}},\,1) > E_{\text{hold}}$, where $E_{\text{hold}}$ is $E_{\text{own}}$ or $E_{\text{centre}}$ according to who holds the cube. Doubling hands the cube to the opponent at twice the stake and he takes only if that beats dropping, so my equity is the smaller of the two. |
| take | Take when $p \ge \mathrm{TP}$, with $p$, $W$ and $L$ read from the taker's side. |
"Too good to double" needs no rule of its own. When gammons are heavy, $E_{\text{hold}}$ exceeds 1
by itself and the comparison declines the double. That falls out of the arithmetic, and it is exactly the
case the two-threshold rule of section 3.10 gets wrong.
Two details are easy to get wrong and both were. The take is priced with the DOUBLER on roll —
he doubles, then rolls — so the taker's distribution is the doubler's, swapped. Evaluating as though the
taker were on roll overstates his position by the value of a roll. And $x$ is forced to 0 when a
redouble is impossible, that is when four times the current cube value exceeds what the match still allows: a
live-cube model on a dead cube doubles far too readily. Otherwise $x$ is 0.7 in contact and 0.6 in a race.
⚠ This is money-game theory. It is a good approximation early in a match and wrong near the
end, where the right answer needs a match-equity table. That is a known and deliberate limitation.
5.12. What is in a net file
A net is a JSON file of about a third of a megabyte: a spec recording the recipe — hidden
layer widths, activation, seed, $\lambda$, learning rate, worker count — the number of games it was
trained on, and then the layers, each an array of weights and one of biases. It is validated on import:
shapes must agree with one another and with this build's 196 inputs and 6 outputs, and every number must be
finite. A malformed file is refused with a reason, since a brain full of NaNs would poison every score
derived from it and be far harder to notice later.
A trained net's systematic name spells the recipe out: N80-L2-sigmoid-800k-lam07 is two hidden
layers of eighty units, sigmoid, 800,000 training games, $\lambda = 0.7$. The nets that ship with the game
carry mythological names instead, kept in order of strength; row 18 of the control column shows the recipe of
whichever is selected.
⚠ A shipped net's NAME is a slot, not an identity. The roster has twice been rebuilt and the
names reassigned wholesale, so a claim about "Apollo" is only meaningful with a date attached. Identify a net
by its recipe and seed, which is what the field beside the menu is for.
6. Playing and Measuring⇧
6.1. Matches, the Crawford rule and the dead cube
Everything the AIs do against one another is scored in matches won, for the reason given in section
4.4. A match is a series of games, colors alternating, played until one
side reaches an agreed point total X — 11 by default, odd so that it cannot be tied. A match win
counts as one win however lopsided the games were.
Two standard rules are implemented, and both matter to the AIs' cube decisions.
| Crawford | The single game played immediately after either side first reaches $X-1$ is played without doubling. Without it the trailing player would double at once, having nothing to lose. |
| dead cube | The cube is never raised beyond the points the trailing player still needs: $\mathrm{maxCube} = X - \min(\mathrm{score}_A, \mathrm{score}_B)$. Anything above that cannot change who wins the match. |
At $X = 1$ the cap makes the cube dead altogether, which is why a match length of 1 gives plain single
games. Since version 153 the cube can also be switched off independently of the match length, from the
Doubling menu of row 20 — so a cubeless match to 11 is possible, which it was not before. The
difference is large: measured in the app, 13.6 games per match cubeless against 6.2 with the cube.
A match to 11 needs at least 11 games if every game is worth one point and can run to 21, but the measured
average is about 5 to 6, because of gammons and the cube.
6.2. Duplicate play
Backgammon results are noisy, and most of the noise is the dice rather than the play. Duplicate
play, borrowed from duplicate bridge, cancels some of it: every match is played twice, the second
time with the same dice and the two brains exchanged between the seats. Each brain then receives, in the
second half, exactly what its opponent received in the first.
The mechanism is cheap because the batch runners already alternated seats: the mirror of a match is the
same call with the arguments swapped and the same seed. Mirroring is at the match level, so the whole
match — Crawford, the cube cap, the ending — is mirrored, and every game within it is still paired
with its counterpart by index. The dice come from a seeded generator (a mulberry32, so it is bit-identical in
the browser, in a worker and in Node), with each game's seed derived from the match seed and the game index.
One consequence is exact rather than statistical: two brains that play identically now tie exactly.
With equal weights the two halves are the same computation, so the same seat wins both, and since the brains
change seats the pair cancels to zero. Not approximately — zero. That is what makes duplicate play
valuable during evolution, where a mutant is compared with a parent it differs from barely at all: a
behaviorally identical mutant nets 0 instead of a couple of matches of noise, and can no longer be crowned by
luck.
⚠ Between brains that play differently the gain is slight, and the reason is worth understanding.
Backgammon's randomness is interleaved with the decisions, so the dice are genuinely shared only up to the first
divergence: after that the same number arrives at a different board. Two roster brains playing the same match
diverge on average by turn 11. Longer matches make it worse. Measured effective sample multipliers in
tournaments between real, differing brains have been 1.01 to 1.08 — that is, almost nothing.
The exported spreadsheet reports the multiplier as the ratio of an independent-play baseline to the
observed rate of sweeps, a sweep being the same brain winning both halves of a pair. The baseline is
not one half. Two independent matches between brains with win probability $p$ sweep with probability
$p^2 + (1-p)^2$, which is one half only for an even pairing and rises towards 1 as the field spreads out; a
98%-vs-2% pairing sweeps 96% of the time with no pairing at all. The baseline is therefore computed as the
mean of $p^2+(1-p)^2$ over the pairings actually played.
6.3. How many matches are enough
This is the question that decides whether any of the numbers on this page mean anything, so it is worth
stating plainly. For a head-to-head of $n$ matches with a true probability near one half, the standard error
of the observed share is $1/(2\sqrt{n})$: about 1.6 percentage points at 1,000 matches and 0.5 at 10,000.
Every result on this page is therefore quoted with a z-score: the number of standard errors
separating the observed result from a dead heat. For a head-to-head it has a particularly simple form.
With $n$ matches played and a margin of $d$ matches (the winner's total minus the loser's),
$ \displaystyle z \;=\; \frac{d}{\sqrt{n}}. $
So a 572–428 result over 1,000 matches is $z = 144/\sqrt{1000} = 4.6$, and over 10,000 matches the
rule of thumb is simply $z = d/100$. Where the statistic is a mean rather than a count — the millipoint
figures of sections 6.5 and 8 — $z$ is the same idea:
the measured difference divided by its own standard error.
What the number means: if the two players were in truth exactly equal, chance alone would produce
$|z| \ge 2$ about one time in twenty, $|z| \ge 3$ about one time in 370, and $|z| \ge 4$ about one time in
16,000. So $z$ below about 2 means the run did not settle the question, whatever the percentages
look like; $z$ above 4 means it did. The convention throughout is that a positive $z$ favours the player
named first.
| matches per pairing | standard error | what it can resolve |
| 50 | 7 points | nothing in a tight field — the order swings by ±30 matches run to run |
| 1,000 | 1.6 points | a real difference of about 5 points |
| 10,000 | 0.5 points | a difference of about 1.5 points |
Two warnings, both learned by getting them wrong. Do not read a running total. A watched score
crosses two standard errors far more often than 5% of the time; one comparison that finished at 58.3% stood at
64.6% after 65 matches. Decide the threshold before the run and read only the final number. And a
measurement is not the only source of noise: two nets trained to the same recipe with different seeds differ
by about a point, so at 10,000 matches per pairing the training seed, not the match count, is what limits how
finely two recipes can be told apart.
6.4. What has been measured
All of the following come from the tournament and competition machinery in the control column, and can be
re-run there. Since the dice are not seeded across runs, re-running gives a statistically equivalent answer
rather than the same number, which is why sample sizes are quoted.
Search depth pays, and it is the biggest single dial after λ. The strongest net at depth 1
against the same net at depth 2, 1,000 matches to 11: depth 2 won 583–417, that is 58.3%,
z = 5.25, and +0.22 game points per game. The control — the same net at the same depth on both sides
— returned exactly 500–500 with duplicate play on, which is what says the harness is sound.
What each net parameter is worth. Ten nets, each differing from a common reference in exactly one
respect, played a round robin of 10,000 matches per pairing at depth 1 — 450,000 matches, about 71
minutes. The reference is two hidden layers of 80, sigmoid, λ = 0.7, 800,000 training games, all
trained from one seed.
| the one change | against the reference | z |
| twice the training (1.6M games) | 53.4% | +6.7 |
| twice the width (160) | 50.8% | +1.7 |
| a third hidden layer | 49.8% | −0.4 |
| leaky ReLU instead of sigmoid | 42.9% | −14.1 |
| tanh | 41.5% | −17.0 |
| ReLU | 39.3% | −21.5 |
| arctan | 37.8% | −24.4 |
| λ = 1 (Monte-Carlo targets) | 8.9% | −82 |
| no nonlinearity at all (the control) | 1.6% | −97 |
Read from the bottom up, that table is the argument of this whole page. The learning rule matters
most by a distance: Monte-Carlo targets cost more than every architecture choice put together, and get you
barely more than logistic regression. The activation matters next, and sigmoid wins decisively —
which is striking, since tanh is the same function class rescaled, so the difference is gradient scale
rather than what the network can represent. Training volume is the most reliable positive dial and has
not begun to saturate. Width helps only once there is enough training to support it: the same
comparison at 100,000 games measured 48.3%, that is, harmful. A third layer buys nothing at any volume
tried, while costing about 1.5 times the training time.
⚠ Every one of those figures was measured at depth 1, and the architecture effects are already
known to interact with the activation. None of them should be quoted as unconditional.
Gammons are a third of the stake. Over 3,000 cubeless self-play games: 69.0% single, 29.9%
gammon, 1.1% backgammon, so the average cubeless game is worth 1.32 points rather than 1. And they change the
policy, not just the score: ranking moves by full equity rather than by win probability alone picks a
different move 16.9% of the time. That is a large part of why a net beats a parameter AI, which has no
representation of a gammon at all.
6.5. An outside yardstick
Everything above is self-relative: this net beats that one, which beats Arwen, which beats Origin.
A chain of relative wins carries no absolute scale, so it says nothing about how the play here compares with
serious backgammon. For that, the AIs were scored against GNU Backgammon, a mature open-source engine
whose evaluation is the product of far more work than this project, and which prices every legal move.
⚠ The measurement below is NOT reproducible from this web page. It was made with an external
program, on a fixed corpus of 2,400 decisions taken from 160 games of GNU Backgammon's own play, each scored by
how much equity the choice gave away against that engine's best move. The unit is the millipoint per
decision (mpr) — thousandths of a point — and it is the standard yardstick for rating backgammon
play. Lower is better.
| player | plays the reference engine's top move | mean error |
| the net, depth 1 | 64.4% | 12.6 mpr |
| the net, depth 2 | 69.8% | 6.9 mpr |
| the net, depth 3 | 75.6% | 4.9 mpr |
| GNU Backgammon, at its shallowest setting | — | 2.0 mpr |
Two things follow, and they point in opposite directions.
All three rows are the same net, and it is the champion of section
7.1 — 80+80 sigmoid, 1,600,000 training games — measured at every depth
over the same 2,400 decisions. Section 8 takes the depth comparison further and
gives the error bars.
Every ply pays, and the returns diminish smoothly. The three depths are 12.6, 6.9 and 4.9 mpr;
each extra ply removes roughly 45% and then 29% of the remaining error. This is an independent confirmation
of the 583–417 match result above, by a completely different instrument, and it is the measurement that
justifies playing at depth 2 or 3 when you have the patience.
But the like-for-like comparison is humbling. At the same search depth, the net gives away
12.6 mpr where the reference engine gives away 2.0 — a factor of six. The net needs two extra plies,
and seconds per move rather than milliseconds, to reach 4.9, which is still nearly two and a half times the reference at
its cheapest setting. The gap is in the evaluation, not the search, and that is exactly what the raw
196-input encoding predicts: the reference engine's inputs include hand-designed features for containment,
timing and distribution which this encoding must discover for itself and largely does not.
The same conclusion arrives from the other direction. Doubling the training volume is worth about 0.66
mpr, or 5% of the error — and a constant fraction per doubling, at both depths. Closing a factor of six
that way would take on the order of $10^{11}$ self-play games. Training volume is the cheapest and most
reliable dial available here, and it is not the one that reaches a modern engine. Features, phase-split
networks and rollout targets are; none of them is implemented.
For orientation, the reference engine sorts play into named bands by exactly this error rate: under about
2 mpr it calls "supernatural", 2 to 4 "world class", and around 6 "expert". Those names describe checker play
only, cubeless, and they are that program's nomenclature rather than a claim about tournament results against
human champions.
7. Three Runs, in Full⇧
The figures quoted earlier on this page are summaries. This section gives three complete runs, made on the
3rd of September 2026 with version 154, as they came out of the TOURNAMENT and COMPETE buttons.
All three used 10,000 matches per pairing (1,000 for the competition), matches to 11, search
depth 1 unless stated, the money cube, and DUPLO on. Between them they took a little over four
and a half hours on a 32-thread desktop.
Everything here is reproducible from the control column, which is the point of showing it. The dice are
not seeded across runs, so repeating one of these gives a statistically equivalent answer rather than the same
number — which is exactly what makes the comparison in section 7.4 worth
making.
7.1. The nets, with the best parameter AI among them
All ten nets plus Arwen, the strongest of the parameter AIs: 11 players, 55 pairings, 550,000
matches and 2.4 million games in 1 h 53 m. Each player therefore played 100,000 matches.
| rank | player | what it is | matches won of 100,000 | share |
| 1 | Apollo | 80+80 sigmoid, 1,600,000 games | 68,229 | 68.2% |
| 2 | Belona | 160+160, 800,000 games | 67,196 | 67.2% |
| 3 | Castor | 80+80, 800,000 games — the reference | 65,677 | 65.7% |
| 4 | Diana | 80+80+80, 800,000 games | 65,234 | 65.2% |
| 5 | Eros | leaky ReLU | 58,927 | 58.9% |
| 6 | Flora | tanh | 57,956 | 58.0% |
| 7 | Ganymede | ReLU | 55,758 | 55.8% |
| 8 | Hera | arctan | 54,140 | 54.1% |
| 9 | Arwen | the best parameter AI | 33,299 | 33.3% |
| 10 | Icarus | λ = 1 | 18,948 | 18.9% |
| 11 | Juno | no nonlinearity — the control | 4,636 | 4.6% |
The headline is where Arwen finishes. The best brain the genetic algorithm has produced —
the top of a roster that took months of evolution — comes ninth of eleven, below every net that
has a working activation function, and above only the two deliberately crippled controls. Head to head it
loses to Apollo 83.8% of the time, and to Hera — the worst of the four real activations,
a net built to lose — still 71.6% of the time. It beats Icarus, the λ = 1 net, 69.5%, and
Juno 96.5%.
Read down the standings and the shape of section 6.4 is there again: training
volume at the top, then width, then the third layer buying nothing, then a five-point gap down to the
non-sigmoid activations, then a chasm to the two controls.
7.2. The parameter roster, with one net among them
The nine parameter AIs plus Apollo: 10 players, 45 pairings, 450,000 matches in 1 h 54 m.
Each player played 90,000 matches.
| rank | player | matches won of 90,000 | share | net matches over the next one down |
| 1 | Apollo (the net) | 76,819 | 85.4% | 6,756 |
| 2 | Arwen | 49,283 | 54.8% | 84 |
| 3 | Bilbo | 48,265 | 53.6% | 104 |
| 4 | Celebrian | 48,140 | 53.5% | 490 |
| 5 | Dwalin | 47,038 | 52.3% | 240 |
| 6 | Eowyn | 46,865 | 52.1% | 540 |
| 7 | Frodo | 44,526 | 49.5% | 498 |
| 8 | Galadriel | 42,956 | 47.7% | 402 |
| 9 | Hamfast | 42,303 | 47.0% | 9,088 |
| 10 | Origin | 3,805 | 4.2% | — |
Three things are worth pulling out.
The roster is in very nearly the right order, and the top of it is a three-way tie. The last column
is the head-to-head margin over the next brain down, and at 10,000 matches a margin of $d$ carries
$z = d/100$. So Arwen over Bilbo (84, $z = 0.8$) and Bilbo over Celebrian (104, $z = 1.0$) are not
resolved — those three are indistinguishable even at ten thousand matches each. Every pairing
further down is resolved, at $z$ between 2.4 and 5.4. There are two small inversions in the full
head-to-head table as well: Arwen beats Galadriel by more than it beats Hamfast, and Bilbo beats Dwalin by more
than it beats Eowyn, though in both cases the standings put those opponents the other way round. Both gaps
are under one standard error, so they are noise rather than a genuine circle of superiority.
Origin is a different order of thing. The hand-guessed baseline wins 4.2% of its matches and loses
to Arwen 95.9%. Whatever else these numbers say, they say that hand-tuning two dozen interacting weights is
hopeless, and that the genetic algorithm of section 4 has done a great deal of
real work.
And a net beats all of it comfortably. Apollo took 85.4% of its 90,000 matches, beating every
parameter AI between 83.2% and 84.5% individually. Note the shape of that: the whole nine-brain roster, from
Arwen down to Hamfast, spans about eight percentage points, while the gap from Arwen up to Apollo is more than
thirty.
7.3. One net against itself, one ply apart
Apollo in both seats, one searching at depth 1 and the other at depth 2, 1,000 matches to 11 — the
cleanest possible measurement of what a ply is worth, since the two players are the same evaluation
function and nothing but the search differs.
| seat | depth | matches won | game points |
| Red | 2 | 572 | 9,251 |
| White | 1 | 428 | 7,931 |
Depth 2 wins 572–428 — 57.2%, $z = 4.6$, with a 95% interval of 54.1% to 60.3% —
and takes +0.21 game points per game over the 6,437 games played. An earlier run of the same
comparison finished 583–417; pooled, that is 1,155 of 2,000 matches, 57.8% at $z = 6.9$.
The price is the whole story of why depth 1 is the default. The tournament of section
7.1 played 550,000 matches in 113 minutes. This competition played 1,000
matches in 56 minutes — and only one of the two seats was searching deeper.
That gives a clean check on the estimate in section 2.7 that a ply costs a factor of
about four hundred. Per move, and in the same units, the depth-1 tournament cost 1.8 and this competition
361.8. Half the moves here are depth 1, so the depth-2 moves cost about $2 \times 361.8 - 1.8 = 722$, and
$722 / 1.8 = 401$. The measured factor is 401 against a predicted 400, from two runs made for entirely
different reasons.
The 9/2 measurements reproduced. Section 6.4 reports a ten-net
tournament of the same design run the previous day. Scoring every net in 7.1 against the same reference
(Castor) gives 53.3, 52.2, 50.7, 42.4, 41.0, 39.4, 36.7, 9.1 and 1.6 per cent, against 53.4, 50.8, 49.8, 42.9,
41.5, 39.3, 37.8, 8.9 and 1.6 the day before. Every one of the nine agrees within 1.4 points, and the
standard error on a difference of two independent 10,000-match measurements is 0.7, so the whole table
reproduces within about two standard errors. Nine independent agreements is a much stronger statement than
any single one of them.
Search and evaluation are separate axes, and the second is where the distance is. One extra ply
of search buys 57.2% head to head; being a net rather than the best parameter AI buys 83.8%. The two are not
in competition — section 6.4 found the training gain survives search almost
undiminished — but if you are choosing where to spend effort, the evaluation function is worth more than
the search, and it is also the cheaper of the two at run time.
Duplicate play did nothing measurable in any of the three. The multipliers came out 1.01, 1.04 and
0.97 — the last of them below 1, which simply means the sweep rate fell a little the wrong side of its
baseline by chance. That is the expected result between brains that play differently, for the reason given in
section 6.2, and it is not an argument for turning DUPLO off: it costs nothing, and
the case it is there for is evolution, where a mutant and its parent play almost identically.
A weak opponent is cheap to beat, which shows up in the game counts. A match against Juno lasted
2.4 games on average and one against Origin 2.8, against tournament-wide averages of 4.4 and 4.8. A match
ends when someone reaches eleven points, so a hopeless opponent concedes it in two or three gammons.
That is worth knowing when you plan a run: adding a very weak player to a tournament costs far less time than
adding a strong one.
⚠ All three runs are at depth 1 and at one training seed. Two nets built to the same recipe
with different seeds differ by about a point, so at 10,000 matches per pairing the seed, not the sample size,
is what limits how finely two recipes can be told apart — and 7.2's three-way tie at the top of the
parameter roster would need a different instrument, not merely more matches, to break.
8. Depth 2 against Depth 3⇧
Section 7.3 settled depth 1 against depth 2 by playing a thousand matches.
The next question is the obvious one — is a third ply worth having? — and it is answered here by a
different instrument, for reasons section 8.3 explains.
8.1. The measurement
The same corpus as section 6.5: 2,400 positions drawn from 160 games of
GNU Backgammon's own play, each one scored by how much equity the net's chosen move gives away against that
engine's best. The player is the strongest net on the roster — 80+80 sigmoid, 1,600,000 training games
— run at each depth over the same positions, so the three rows are paired: the same 2,400
decisions, three times.
| search depth | the reference's own name for it | plays the top move | mean error | median | p90 |
| 1 | 0-ply | 64.4% | 12.62 ± 0.84 mpr | 0.0 | 35 |
| 2 | 1-ply | 69.8% | 6.89 ± 0.55 mpr | 0.0 | 18 |
| 3 | 2-ply | 75.6% | 4.89 ± 0.40 mpr | 0.0 | 10 |
Paired, depth 2 against depth 3: +1.99 ± 0.57 mpr in favour of depth 3, z = 3.5, over the
full 2,400 decisions at every depth. The two depths choose the same move 87.8% of the time; all of the
difference comes from the 293 decisions where they part company.
The rig was rebuilt from scratch for this run and checked before any of these numbers were believed.
Scoring the previous champion — the same recipe at 800,000 training games — reproduced its
recorded figures exactly at both depth 1 (62.3%, 13.28 mpr) and depth 2 (68.0%, 7.30 mpr), and every gnubg move
on every position mapped to a distinct move of our own generator, 2,400 positions with nothing unmatched. That
net measures 5.4 mpr at depth 3, so the champion's 4.89 is a real improvement on it rather than a change of
instrument.
The second column is worth a moment. Because this page numbers depths one higher than the literature
does (section 2.2), depth 3 here is the reference engine's 2-ply — the
very setting used to score it. So the last row is a like-for-like comparison at equal nominal depth, and it
is the harshest reading available.
8.2. What it says
The third ply is real, and it is worth about two thirds of what the second was. Depth 1 to 2
removes 45% of the error; depth 2 to 3 removes a further 29%. The agreement rate tells the same story from
another angle — 64.4%, 69.8%, 75.6% — and so does the tail, which is where the damage in backgammon
actually lives: the 90th-percentile error falls 35, 18, 10 millipoints, and the worst single decision improves
from 464 to 308. The median is 0 at every depth, because most decisions are not close.
It is worth having, and it is not free. A depth-3 decision takes tens of seconds
(section 2.7), so this is a setting for a considered game or for examining one position
with MOV, and not one for a tournament.
⚠ An early version of this measurement said the opposite, and the reason is instructive. A
first pass over 24 positions put depth 3 behind depth 2 by 4.6 mpr — wrong sign, and it was flagged
at the time as meaningless at that sample size (z = 1.5). The full corpus reversed it at z = 4.0. A
backgammon measurement that is not significant is not weak evidence for its own sign; it is no evidence at all.
⚠ And a corpus of the net's own games flatters it. The 800,000-game net was scored both
ways: on positions from its own play it measured 4.9 mpr at depth 3, against 5.4 on the reference engine's
positions — about half a millipoint of self-flattery. Small, but in the direction you would expect, and
the reason every figure on this page comes from a corpus the player did not generate.
⚠ One measurement, one seed, one net. These are three depths of a single trained net on a
single corpus. The depth ordering is now solid — it has been reproduced on two different nets —
but the exact millipoint figures carry the training seed's noise as well as the corpus's, and should be read to
about a tenth of a millipoint, not better.
8.3. Why this one is not a competition
Depth 1 against depth 2 was settled by playing matches. The same approach at depth 3 is not
impractical by a small margin, it is out of reach, and the arithmetic is worth setting down because it applies
to any future question of this kind.
A depth-3 decision costs roughly 460 core-seconds, and a batch match runs on a single worker. One
cubeless game asks the deep side for about 27 decisions, so a single game costs some 3.4 core-hours; with 32
workers busy that is about nine matches an hour. The effect to be detected is small — if the head-to-head
advantage scales with the error reduction above, depth 3 should win a single game about 51.5 to 52% of the time
— and separating that from a coin needs on the order of four thousand matches. That is two to three
weeks of continuous running, for a result the corpus delivers in an afternoon at z = 4.
⚠ And a shorter match does not help, which is the part that surprises people. It is tempting
to play to 1 point instead of 11 on the grounds that matches are then six times cheaper. They are — but
a match to $X$ points amplifies a small per-game edge in proportion to the square root of the games it
contains. Writing $\varepsilon$ for the per-game edge and $g$ for the games per match, the significance of
$N$ matches goes as
$ \displaystyle z \;\propto\; \varepsilon\,\sqrt{g}\,\sqrt{N} \;=\; \varepsilon\,\sqrt{gN} \;=\; \varepsilon\,\sqrt{G}, $
where $G$ is the total number of games played. The match length cancels exactly: a short match is
cheap and carries proportionally less information. What match length changes is what is measured
— at a match length of 1 the cube is dead, so the comparison covers checker play alone — and how
well duplicate play works, since two brains diverge less within one game than across eleven points. Neither
of those is a reason to expect more significance per hour.
← Back to the User's Guide