A worked demonstration

Building a Backgammon Engine in Shoddy

Thirty-one steps from nothing to a game you can sit down and play, against an opponent that taught itself — written by a model, specified and verified by you. Every prompt is here, ready to copy.

Before you start — three things

Untested This is the least verified thing on this site, by a distance. Everything else here ships code that runs. The machines, the mills and the first tutorial's spirograph all have tests that execute on every build, so the code on those pages is code that still works. This page is different. It describes a program that has not been built end to end. The steps have been reasoned through and ordered with care. They have not been run.

So expect to find faults, and treat finding them as part of the exercise. You might meet a requirement that turns out ambiguous. You might meet a gate (the list of things a step must prove true) that cannot be met as written. A step's scope may be wrong, two steps may sit in the wrong order, or a design constraint may rule out the good answer along with the bad one.

There is no need to report any of it. Working out what a step should have said is the exercise, not a defect to file.
Cost This will consume a meaningful quantity of tokens or credits, and the bill is yours. Tokens are the small pieces of text an AI service counts and charges for. Thirty-one steps at up to three replies each is ninety-odd exchanges, before a single repair cycle. Every exchange carries the standing grounding, plus that step's requirement and its list of what already exists. The later steps — the network, the learning, the two-ply search — produce the longest replies.

Don't estimate from this page; measure. Run step 1 and look at what your provider says you spent. Multiply that by thirty-one, then add half again for repairs. That is the only estimate worth having, and it costs you one step to get it. Watch your usage as you go. If your quota runs out mid-build you can recover — the log and the accepted designs are the state — but only if you have been keeping them.

On choosing a model. This method asks for something specific. The model must hold a three-phase protocol. It must propose a design and write no code when told not to. It must argue about a representation (the way the data is laid out) rather than reaching for the keyboard. Smaller and faster models tend to collapse the phases and hand you an implementation in the design turn. A long context window (the amount of text a model can keep in mind at once) earns its keep as "what already exists" grows. The difficulty is not evenly spread — a model that sails through step 1 may stall at step 21, so judge it late rather than early. A reasonable split is a strong model for the design turns and the back half, a cheaper one for the steps marked extends.

The usual cautions, which all apply here. Generated code can be confidently and fluently wrong — run it, never read it and assume. Don't paste secrets, keys or personal data into a prompt. Depending on the terms you agreed to, your prompts and the model's replies may be retained or used to improve a service. Check those terms if that matters to you. The provenance of generated code (where it really came from) and its licensing are yours to satisfy yourself about. Two runs of the same prompt will not match, so a step that worked for someone else may not work identically for you. Nothing here is affiliated with or endorsed by any model provider.
Scope Two phases, thirty-one steps, and it is not an afternoon. Phase 1 is steps 1–22: the engine, which never learns that a screen exists. It covers the rules, the doubling cube, judgement, and a network that learns to play from self-play. Phase 2 is steps 23–31: the board you can sit down at. The phases are separable, and phase 1 alone is a finished thing.

Each step is up to three replies from the model plus your two reviews, so the work is bursty. A settled step passes in minutes. A step whose design you reject three times can take an evening on its own. Order of magnitude: tens of hours, spread over weeks rather than done in a sitting. Take that as arithmetic from the step count rather than a measurement (see the first note). Expect the back half of phase 1 to be slower than the front.

If you want a smaller commitment, there are two natural places to stop. Step 13 leaves you with a rules-complete game that plays itself start to finish: legal throughout, scored at the end, though its chooser picks at random. Step 22 ends phase 1 with an opponent that taught itself from nothing. Both are worth having on their own.
On this page: Before you start  |  What this is  |  The method  |  Standing grounding  |  The thirty-one steps  |  Deliberately excluded  |  Things to watch

What this is

This builds a complete backgammon game in two phases. Phase 1 is the engine: the rules, the doubling cube (the marker that records what a game is worth), judgement, and finally a neural network (a program that learns from experience rather than from written rules) that learns to play from nothing but self-play. Phase 2 is the game itself: a graphical board you move checkers on with mouse or keyboard, undo, hints drawn from the engine, and dice that roll. The code is written by an AI model. You never write a line of it.

The two phases are separable. Phase 1 ends with something thoroughly tested that nobody can play. Phase 2 ends with something anybody can. Each is worth doing alone. The seam between them is deliberate — twenty-two steps of engine never learn that a screen exists.

You write no Shoddy at all — not the engine, not the game, and not the tests. What you write is English: what must be true, and what would prove it. That division is the subject. The engine is the evidence.

Why Shoddy makes this a fair test

Ask a model to write a chess engine in C (a widely used programming language) and you cannot tell engineering from recall. Stockfish and Sunfish, two well-known chess engines, sit in its training data (the text the model learned from), along with a thousand tutorials about them. The result may be excellent while proving nothing.

There is no backgammon engine in Shoddy to remember. Every line the model produces has to come from the requirement, the language reference, and nothing else. That makes the requirement the independent variable (the one input the experiment turns on), which is what a demonstration of requirements-driven development needs.

Backgammon supplies the second necessary property: small enough to finish, and intricate enough that a plausible-looking implementation is wrong in several specific, catchable ways.

The objection, answered first

If the model writes the tests too, what is left to stop it agreeing with itself? The gate at the end of every step below — a list, in English, of what must be true, written before the model has seen anything. The assertions (the checks the tests make) transcribe it. The gate is the contract. Writing the assertions by hand would buy nothing the gate has not already bought. It would cost the thing this method is for: an implementation cheap enough to delete and regenerate, with a test suite that regenerates alongside it.

The risk is real all the same, and it has a name: a requirement misread once can be encoded twice, in the code and in the test, and pass. Four things make that expensive:

Things to watch returns to it, because it is the one failure this method cannot design away.

Prerequisites You need three things: a Shoddy toolchain that runs (mill run), a model you can hold a conversation with, and somewhere to keep files. Nothing else.

The method

Six steps, not five

Tests call functions by name, and names come from a design. So the cycle has a design gate before the test gate:

  1. Requirement — rules, required behaviour, design constraints. No design.
  2. Design proposal — the model proposes an interface and a representation, and implements nothing.
  3. Design review — accept, redirect, or reject against criteria written in advance.
  4. Tests — written by the model, from the step's gate, against the accepted interface, in their own turn, before any implementation exists.
  5. Test review — one question: what must be true that nothing here checks?
  6. Implementation.
  7. Run and repair — on failure, fix the requirement or the design. Never the code, and never the test alone.

Tests still precede implementation, and you still validate twice — once on the shape, once on the coverage. What you do not do is type. You write no Shoddy in this build: not the engine, not the game, and not the tests.

Why this is safe The contract was never the assertion — it is the gate stated at the end of every step below, in English, before the model has done anything. Step 1's is "starting layout exact, point by point; both totals 15; bar and off zero; equality distinguishes boards that differ by one checker." Turning that into Assert calls is transcription. Doing the transcribing yourself would buy you nothing the gate did not already buy. It would cost you the ability to throw the whole implementation away and regenerate it.

Constraints on the design, not the design

You do not say "represent the board from the mover's perspective and flip it to pass the turn." That is a design, and handing it over makes the model a typist. You say:

No function may need to know which player is moving in order to decide how a checker travels.

That is a property any acceptable design must have. It rules out the colour flag without naming the alternative. If the model finds the perspective-flip on its own, that is a result. If it does not, you reject and restate — and that exchange is worth more than the answer would have been.

Three rules that do not bend

Never let one reply contain both the tests and the implementation. The model writes the tests, which is what keeps your hands off the keyboard. But a model that has already written the implementation will write tests that agree with it, fluently, proving nothing. Ask for the tests, read them, then ask for the code. The separation does the work that secrecy used to do, and it does it without you typing an assertion.

Do not hand-patch generated code. When something fails, the question is not what is wrong with the code but what did my requirement fail to say? Every hand-patch is a defect that returns the next time you regenerate.

Scope the context to the step. Hand over the rules that step needs, not the rulebook. A model told the deduplication rule (the removal of duplicate plays) at step 6 will apply it at step 6, and step 7's finding never happens.

The three deliverables

Each step produces three artifacts (three separate pieces of work) in three separate replies. You review each at its own moment, and each review looks for a different thing.

D1 — DesignD2 — TestsD3 — Implementation
Arrivesafter the requirementafter the design is acceptedafter the tests are accepted
Containstypes, signatures, rationale, questionstest.shoddy: one assertion per gate clauseShoddy source
Contains noimplementation, not even a sketchimplementation, and no test for anything out of scopenew public interfaces, no test changes
You askis this the right shape?what would pass this that shouldn't?does it pass?
Reviewed byreading and arguingreading the assertion messagesrunning the tests

Steps marked extends below skip D1 — the interface is already settled. No step skips D2.

Every step below states its deliverables explicitly: whether a design document is expected, and which files the tests and the implementation must produce. A step that produces something not on that list has exceeded its scope, however good the extra code is.

D2 is where the gate stops being prose. That is why the design arrives first: nothing can assert against a function whose name has not been settled. And it is why the implementation arrives last: assertions written after the code that will satisfy them are a description of the answer, not a test of it.

Reviewing tests without writing them

You will not read D2 line by line — you do not have to. The assertion messages are sentences, and they are what you read. Three questions, in order:

What you find goes back as a sentence — a new gate clause, or a correction to the requirement. Never send it back as an assertion you wrote yourself. The moment you hand-write one, you own it, and it stops being regenerable with everything else.

What a design deliverable contains

Accept, redirect, or reject

Three responses, and the difference matters. Accept and proceed to tests. Redirect when the shape is wrong but your requirement was adequate — the model's miss. Reject and rewrite the requirement when the requirement permitted the wrong design — yours, and the more valuable finding. A record in which every design issue turns out to be a redirect is a record that has been flattering its author.

Keep a log

Write one entry per cycle, as it happens. Each entry records:

Write it live. A log reconstructed afterwards comes out tidier than the truth, and the tidiness shows.

The most instructive artifact this produces is the diff (the line-by-line comparison) between a step's first requirement and its last. Everyone publishes the final, clean version. Nobody publishes how it got there.

Standing grounding

Give the model this once, at the start of every session, before anything else. Grounding is the background text that tells the model how to work. This block is the only grounding that stays constant — the backgammon rules and the list of what already exists arrive fresh with each step.

# Backgammon in Shoddy

You are writing Shoddy. One file per step, named as the step tells you.

## The language

Read these before writing anything. Do not summarise them back to me.
Three trees, all of them fair game:

- `docs/*` — the documentation. `quickref.html` first (every builtin word,
  type and syntax form on one page), then `spec.html` for the grammar and
  the semantics, `errors.html` for every error with its causes and fixes,
  and `machines/*.html` for a word reference per machine.
- `machines/*` — the standard library in readable Shoddy. The definitive
  answer on any library word's name, arguments and types, and the house
  style you should be writing in. Read the machine rather than guessing.
- `mills/*` — complete programs built the way this one is: a pure core, a
  thin shell, a headless test. Worth reading before phase 2 in particular,
  where a window and an event loop arrive.

## Constraints that will bite

Verify each against the docs; do not trust this list.

- **Everything is immutable.** There is no assignment. `SetNth` returns a copy.
- **`Number` is an IEEE double and the only numeric type.** No integers, and
  **no bitwise operators** — bit work is `bool.shoddy`'s words (`BoolBitAnd`,
  `BoolShl`, `BoolMask`), never `&` or `<<`. If you want the operators
  themselves, the design is wrong for Shoddy.
- The network is gated behind `--allow-net`. A plain socket never blocks; a
  secured socket blocks on `Recv` and refuses `Ready`.
- **A picture does not need a screen.** `ScribblerSave(sc, path)` writes the
  buffer to a PNG with no window involved; `mill run --no-window` opens every
  scribbler hidden.
- **Indexes are 1-based.**
- **Arrays** are fixed-length with O(1) `Nth`; **lists** are cons cells for
  recursion. Index into arrays, recurse over lists.
- **Records** compare structurally with `=`. `With(r, F = v)` copies.
- **Self-tail-recursion compiles to a loop.** Mutual recursion consumes stack.
- **No exceptions.** `Error(msg)` aborts. Failure must be a returned value.
- Word names are case-insensitive; string contents are exact.

## Machines you may include

`seq`, `str`, `math`, `random`, `file`. Ask before any other.

## How we work

Each step has three phases, in three separate replies. Do not run them
together, and do not start the next one until I accept the last.

**Phase 1 — design.** You propose. Give me:
  - the types you will introduce, and what each field means
  - the signature of every function you will expose, one line on each
  - the representation choices you made and **why**, especially where you
    considered an alternative and rejected it
  - anything in the requirement you found ambiguous

  **Write no implementation in phase 1.** Not a sketch, not an example body.

**Phase 2 — tests.** Only after I accept the design. Write `test.shoddy`
against the accepted interface, from the GATE at the end of the step:
  - one assertion per clause of the gate, in the gate's order
  - each message a sentence saying what is true, so a failure names the
    requirement it broke and not the function it called
  - where the requirement holds for every input and not just the examples,
    assert the property, not three cases of it
  - nothing for behaviour this step does not own

  **Write no implementation in phase 2**, and do not tell me how you intend
  to make these pass.

**Phase 3 — implementation.** Only after I accept the tests. Implement exactly
what was designed. If implementing reveals the design was wrong, stop and say
so rather than quietly changing it.

## Rules of engagement

- Build only what the step asks. If you can see what the next step needs,
  **do not build it.** Out-of-scope code is a defect even when correct.
- Anything under "What already exists" is fixed. Call it. Do not reimplement
  it or change its signature.
- **Never change a test to make the implementation pass.** If a test looks
  wrong, stop and say which requirement you think it has misread. A test
  changes only when I change the gate it came from.
- **If the requirement is ambiguous, stop and ask.** Do not choose and proceed.
  An ambiguity you surface is worth more than a guess that happens to be right.
Note The last rule earns its place several times over. Every question the model asks is a hole in your requirement, found before any code exists.

The steps

Phase 1 — The engine

Phase 2 — The game

Each step gives the prompt to send, what to look for in the design, and the gate the implementation must pass. Where a step says extends, there is no design proposal — the interface is settled and the implementation comes directly.

Step 1 · design proposal

The board

### What already exists
Nothing. This is the first step.

### The game
Backgammon is a game for two players. It is played on a board of 24 points,
with fifteen checkers each, TWO DICE, and a DOUBLING CUBE that records what
the game is currently worth. A player wins by bearing all fifteen checkers off
the board; how much that win is worth depends on how badly the loser was beaten
and on the cube.

This step is only the board. Dice, movement and the cube come later.

### The rules
The board has 24 points, numbered 1 to 24. Each player has 15 checkers.

The two players move in opposite directions. Numbering the points from one
player's side, that player travels from 24 down toward 1; the other travels the
other way, so what is point 24 to one player is point 1 to the other.

A point may hold any number of checkers of one colour. It never holds both
colours at once.

A checker that has been hit sits on the bar, off the board, until it re-enters.
A checker that has completed its journey is borne off and takes no further part.

The starting position, described from one player's side: 2 checkers on point
24, 5 on point 13, 3 on point 8, 5 on point 6. The other player's checkers are
the mirror image.

### What the program must be able to do
1. Represent any legal board state, including checkers on the bar and borne off.
2. Produce the starting position.
3. Report, for either player, the total checkers they have anywhere — on points,
   on the bar, and borne off. For a legal position this is always 15.
4. Compare two board states for equality.

### Design constraints
- **No function may need to be told which player is moving in order to decide
  how a checker travels.** Direction must fall out of the representation, not
  out of a conditional in every function.
- A point's occupancy is a single value, not two counts. Both colours cannot
  occupy a point.
- The bar and the borne-off checkers are not points and must not be stored as
  though they were. Both players can have checkers on the bar at once.
- Shoddy is immutable: every operation returns a new board. Choose a
  representation whose copy is cheap.

### Out of scope
Dice, moves, legality, display, passing the turn, the cube.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document. Types, interface, rationale, questions. No code.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — board.shoddy: the board type, a constructor for the starting position, two total-checker functions, and an equality function.
Review
The board type with every field explained; how a point's occupancy is stored; where bar and off live; the constructor for the start; count and equality signatures. The rationale must address how direction is represented — that is the review.
Reject if
A colour or side-to-move flag other functions must consult for direction. Separate white and black storage. Bar or off stored as points 0 and 25 — that breaks the moment both players are on the bar. Ask what a copy costs.
Gate
Starting layout exact, point by point; both totals 15; bar and off zero; equality distinguishes boards that differ by one checker.
What a design document actually looks like — step 1, worked
## Types

Board
  Points    Array Of Number   26 slots. Slots 1-24 are the points; 0 and 25 are
                              unused padding so point N sits at index N.
                              Positive = the player to move; negative = the
                              opponent; zero = empty. Never both signs at once.
  BarMine   Number            Checkers of the player to move on the bar. >= 0.
  BarTheirs Number            The opponent's. >= 0.
  OffMine   Number            Borne off by the player to move. >= 0.
  OffTheirs Number            The opponent's. >= 0.

## Interface

StartBoard()          As Board     The opening position.
CheckersMine(b)       As Number    Total for the player to move: 15 when legal.
CheckersTheirs(b)     As Number    Total for the opponent.
SameBoard(a, b)       As Boolean   Structural equality.

## Rationale

Direction. The requirement forbids any function needing to be told whose turn it
is in order to know how a checker travels. I considered an absolute board with a
side-to-move field, but every movement function would then branch on it, which is
what the constraint rules out. Instead the board is always recorded from the point
of view of the player to move: that player always travels from 24 toward 1, and
their home is always 1-6. Direction is therefore a property of the representation,
not a parameter. Handing over the turn will need an operation that re-reads the
board from the other side; I have not built it, as it is out of scope here.

Sign, not two counts. The requirement states a point never holds both colours, so
one signed number suffices and makes the impossible state unrepresentable. Two
counts would permit a board that cannot exist.

Padding. Slots 0 and 25 are unused so that point N is at index N. It costs two
numbers and removes an off-by-one from every future function.

Bar and off as fields. I considered storing them in the unused slots 0 and 25.
Rejected: both players can be on the bar simultaneously, and two players' bars
cannot share one signed slot.

Copy cost. A board is 26 numbers plus four, so a copy is about 30 numbers. Cheap
enough that immutability costs nothing here.

## Questions

1. "A point may hold any number of checkers of one colour" — is fifteen a hard
   ceiling I should assert, or genuinely unbounded? I have assumed unbounded.
2. Should equality distinguish two boards differing only in whose turn it is?
   With this representation the question does not arise, but it would under an
   absolute-board design, and I want to confirm it is not a requirement I have
   designed away.

Three things to notice. The bar rationale names the failure it avoids rather than restating the constraint — that is understanding rather than compliance. The direction rationale ends by declining to build step 2, which is the scope discipline holding. And both questions are real holes in the requirement, found before a line of code exists.

What good looks like A rationale that names the failure it is avoiding rather than restating the constraint. For the bar: "both players can be on the bar simultaneously, and two bars cannot share one signed slot." That is understanding. "Separate fields are cleaner" is compliance.

Step 2 · extends

Passing the turn

### What already exists
[ board type, starting position, checker counts, equality ]

### The rules
When a turn ends, the other player moves. The board does not change; only whose
turn it is changes. What was one player's point 24 is the other's point 1.

### What the program must be able to do
1. Take a board and produce the same board as the other player now sees it.
2. Doing that twice returns exactly the original, for every legal position.
3. The starting position is unchanged by it, being symmetric.

### Design constraints
- Total: defined for every legal board, with no failure case.
- Loses no information. Everything recoverable before is recoverable after.

### Out of scope
Dice, moves, legality, display, the cube.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — board.shoddy extended with one turn-passing function. No new types, no other additions.
Gate
Applying it twice returns the original, over 1,000 constructed positions; the starting position is a fixed point.
Watch If step 1's design was right, this is four lines. If it needs more, step 1 was wrong, and this is the cheapest moment you will ever have to find out. Going back here is not failure — it is the finding.

Step 3 · extends

Seeing the board

### What already exists
[ board type, starting position, turn-passing, counts, equality ]

### The rules
None beyond the layout already described.

### What the program must be able to do
Render any board as text so a reader can identify which points hold which
player's checkers and how many, how many each player has on the bar, and how
many each has borne off.

This exists to be printed inside a failing assertion. Legibility is the only
requirement.

### Design constraints
- Pure: returns text, prints nothing.
- Rendering from the other player's side produces the mirror image, with no
  separate code path.

### Out of scope
Colour, terminal control, interactivity, anything a player would use.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — show.shoddy: one function returning a board as text.
Gate
Starting position renders four correct stacks; rendering after passing the turn is the mirror.
Why now Build the diagnostic before you need it. Every later step debugs faster because a failing assertion can print the board that caused it. It costs twenty minutes here and saves hours by step 7.

Step 4 · design proposal

One checker, one die

### What already exists
[ board type, starting position, turn-passing, rendering, counts, equality ]

### The rules
A turn in backgammon is played by rolling TWO DICE. Each die shows 1 to 6, and
each die value moves ONE checker that many points, in the moving player's
direction of travel. A player who rolls two different numbers makes two moves,
one of each value; a player who rolls the same number twice makes four moves of
that value.

**This step is about a single move by a single die value only** — the building
block. How the two dice combine into a whole turn, and which combinations are
legal, come in later steps. Build the piece, not the turn.

A point holding two or more of the opponent's checkers is blocked; a checker
may not land there.

A point holding exactly one opponent checker is a blot. Landing on it hits that
checker, which goes to the bar; the arriving checker takes the point.

A point that is empty, or holds any number of the moving player's own checkers,
is open. There is no limit on how many may stack on a point.

A checker on the bar re-enters that many points from the far end of the board:
a 1 enters on the farthest point from home, a 6 on the sixth-farthest. Entry is
subject to the same blocking and hitting rules.

While a player has any checker on the bar, they may move nothing else. Every
checker on the bar must re-enter first.

### What the program must be able to do
1. Report where the moving player could legally move a checker from.
2. Given a board, a source, and one die value, either produce the resulting
   board together with whether a checker was hit, or report the move illegal.

Shoddy has no exceptions, so illegality must be a value you return.

### Design constraints
- Moving from the bar and moving on the board are the same operation from the
  caller's point of view. Do not expose two functions.
- Whether a move is legal and what board it produces must be answered by one
  call, not two.
- The result must compose: later steps will chain these moves to build whole
  turns, so the output of one must be a valid input to the next with nothing
  in between.

### Out of scope
Bearing off — a move carrying a checker past the end of the board is simply
illegal at this step. Rolling dice. Combining two dice. Whole turns. The cube.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on how illegality is represented.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — move.shoddy: a source-enumeration function and a single-die move function returning either the resulting board with a hit flag, or the illegal outcome.
Review
The move signature; how illegality is represented as a value; how bar entry and board movement are unified; whether results chain without unwrapping.
Reject if
Separate bar-entry and on-board functions. A design requiring test-then-apply. An illegality representation that can be ignored by accident. Ask whether composing two moves needs glue code — if it does, the design will fight step 6.
Gate
One assertion per rule; the opponent's bar count rises by exactly the number of hits recorded.
Watch The visual convention of five checkers to a point is everywhere in the training data. The requirement says there is no limit; check that the design agrees.

Step 5 · extends

Bearing off

### What already exists
[ board type, turn-passing, rendering, source enumeration, single-die moves ]

### The rules
The last six points in a player's direction of travel are their home board.

Bearing a checker off — removing it as finished — is legal only when all fifteen
of that player's checkers are in their home board or already borne off. A
checker on the bar disqualifies the player entirely.

Given that, for a checker whose remaining distance is exactly the die value,
that checker bears off.

For a die larger than the checker's remaining distance, the checker bears off
only if that player has no checker further from home. If they do, the move is
illegal.

For a die smaller than the remaining distance, this is an ordinary move within
the home board. It is never a bear-off, and a player is never obliged to bear
off when an ordinary move is available.

### What the program must be able to do
1. Report whether a player is in a position to bear off at all.
2. Extend single-die movement so bear-offs are produced under the three rules.

### Design constraints
- The single-die move signature does not change. Bearing off is a kind of move.
- The "no checker further from home" test is a property of the whole board, not
  of the source point. Do not answer it locally.

### Out of scope
Whole turns, rolling dice, the cube.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — move.shoddy extended: a bear-off eligibility test, and the existing move function extended to produce bear-offs. Its signature does not change.
Gate
Each of the three rules asserted separately; a checker on the bar disqualifies; bear-off is never forced when an ordinary move exists.
The lesson This step contrasts local rules with global preconditions (conditions the whole board must meet first). Whether a move is legal usually depends on source and destination. Bear-off depends on the entire board. A requirement phrased as a local rule produces code that checks locally.

Step 6 · design proposal

A whole turn

### What already exists
[ board type, turn-passing, rendering, source enumeration, single-die moves
  including bear-off, the bear-off eligibility test ]

### The rules
A turn is played by rolling two dice.

If the dice show different values, the player makes two moves, one of each
value. If both show the same value, the player makes four moves of that value.

The dice may be used in either order, and the order can matter: a play reachable
one way round may be unreachable the other.

Within a turn, moves may use different checkers or move the same checker more
than once.

### What the program must be able to do
Given a board and two die values, produce every complete way the turn could be
played, each paired with the board it produces.

A way of playing is complete when no further move is legal with any die still
unused.

### Design constraints
- **Correctness matters and speed does not.** This will later be the reference
  a faster version is checked against, so it must be short enough that a reader
  can convince themselves it is right by reading it. Do not prune. Do not exit
  early. Do not optimise anything.
- Every result must record how many dice it used.
- The recursive structure should be visible in the code, because the rule is
  recursive.

### Out of scope
Removing duplicate results. Discarding results using fewer dice. Any rule about
which die must be played. Choosing between plays. Rolling the dice — the values
are given to you. **Return everything you generate, unfiltered.**

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on how a turn is represented.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — turn.shoddy: one generator returning every complete way to play a roll, each with its resulting board and the number of dice it used. Unfiltered.
Review
How a turn is represented — that decision determines step 7. The shape of the recursion. Where the dice-used count is recorded.
Reject if
Anything prunes or short-circuits. Only one die order is generated — that silently loses plays reachable the other way.
Gate
Raw counts on three small positions; on every move the remaining distance drops by exactly the die used, except where an over-large die bore a checker off.
The lesson Write the recurrence (the rule stated in terms of smaller cases of itself), not the prose. Models implement stated recurrences accurately and narrative descriptions loosely.

Step 7 · design proposal

When two turns are the same turn

### What already exists
[ everything through whole-turn generation, unfiltered ]

### The rules
Two ways of playing a turn that leave the board in the same state are the same
play. How the checkers got there does not matter.

Moving one checker from point 13 to 7 and then on to 2 leaves the same board as
moving it 13 to 8 and then 8 to 2. One play, not two.

It also leaves the same board as moving one checker from 13 to 8 while a
different checker goes from 8 to 2. Still one play.

### What the program must be able to do
Reduce the generated results so at most one remains per distinct resulting
board.

### Design constraints
- The generator can produce on the order of 50,000 candidates for a doubles
  roll, reducing to a few hundred distinct boards. **A design comparing each
  candidate against every result kept so far performs tens of millions of
  comparisons for one roll.** Choose a design whose cost grows better than that.
- Sameness is a property of the resulting board and nothing else. Do not compare
  the moves.

### Out of scope
Any rule about how many dice must be used, or which die must be played.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, including the expected cost on a doubles roll.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — turn.shoddy extended: an identity key for a board, a reduction function, and the generator now returning reduced results.
Review
The identity key and whether it is injective. The reduction strategy, and its expected cost on a doubles roll, stated before you accept.
Reject if
The quadratic scan. If the proposal is a sort, ask what it sorts on and whether two different boards can produce the same key.
Gate
From the starting position: 6 and 5 gives exactly 7 plays; 2 and 1 gives exactly 15; double 5 gives exactly 4. Plus a timing assertion on a worst-case doubles roll.
Watch The hardest step to review. If the counts are right on the first cycle, find out why before believing it — the likeliest explanation is that step 6 was already dropping results it should have kept. And time it: a linear scan (comparing each candidate against every result kept so far) is correct and unusably slow, so every assertion goes green while the design is wrong. A performance defect that passes its tests is the sneakiest failure in the project.

The 6-and-5 case is worth working by hand before you run it. Most people count 8 and have to be argued down to 7.

Step 8 · extends

Rules that constrain the whole turn

### What already exists
[ whole-turn generation with duplicates removed ]

### The rules
Two rules constrain a turn as a whole rather than any individual move.

A player must use as many dice as it is legally possible to use. If some way of
playing uses both dice, the player may not choose one that uses only one.

If it is possible to use only one die, and either die could be the one used,
the player must use the higher.

If no move at all is legal, the player forfeits the turn and play passes.

### What the program must be able to do
Apply both rules so only genuinely legal plays are returned, and report the
forfeited case distinctly from an error.

### Design constraints
- **Both rules are properties of the complete set of possibilities and cannot be
  decided while generating.** A way of playing beginning with the lower die may
  permit both dice where beginning with the higher does not. Any design deciding
  which die to use before generating everything will be wrong on exactly the
  positions where the rule matters.
- The forfeited turn is a legal outcome of the rules, not a failure.

### Out of scope
Choosing among the surviving plays.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — turn.shoddy extended: the two whole-turn filters applied after generation, and a distinct forfeited-turn result.
Gate
Every returned play uses the same number of dice; a position with every entry point blocked yields nothing for all 21 rolls; a constructed one-die position forces the higher die.
Experiment The design constraint is the whole lesson. Try removing that paragraph once, deliberately, and record what comes back. Expect a greedy implementation, one that plays the higher die first. It will be wrong precisely on the positions where the rule exists to matter.

Step 9 · design proposal

A fast generator, and the harness that trusts neither

### What already exists
[ the complete, correct, slow turn generator; board type; turn-passing;
  rendering; board equality ]

### The rules
None new. The fast generator must produce exactly what the slow one produces.

### What the program must be able to do
1. Generate the legal plays for a board and a roll, substantially faster, by any
   means preserving the result.
2. Report whether the two generators produce the same SET of resulting boards
   for a given board and roll.
3. Play a specified number of games choosing uniformly at random among legal
   plays, collecting every board visited, reproducibly from a seed.

### Design constraints
- The slow generator is the definition of correct. Where they differ the fast
  one is wrong. Do not modify the slow one to agree.
- Set comparison, not sequence comparison. Order is not significant.
- The harness must run thousands of boards without accumulating state that
  changes its behaviour.

### Out of scope
Evaluation, opponents, choosing a play, the cube.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, listing each pruning and its justification.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — turn-fast.shoddy and harness.shoddy: the fast generator, an agreement check between the two generators, and a seeded random-play harness that collects every board visited.
Review
Each pruning claimed safe, with a one-sentence justification for each. Any pruning the model cannot justify in a sentence is a defect waiting for the harness to find.
Gate
The two generators agree on every board from a 400-game random run across all 21 rolls; board invariants hold at every ply.
The technique Write the version you can prove by reading, then the version that is fast, then prove they agree. That gives you two independent derivations of the same fact, cross-checked at volume, with no artifact from outside the language.
Milestone The engine is now correct in a way you can defend to a stranger.

Step 10 · extends

Winning, and what a win is worth

### What already exists
[ the complete move generator, board type, turn-passing, rendering, the harness ]

### The rules
A player wins by bearing off all fifteen checkers.

If the loser has borne off at least one checker, the win is a plain win and
counts 1.

If the loser has borne off none, it is a gammon and counts 2.

If the loser has borne off none and additionally still has a checker on the bar
or anywhere in the winner's home board, it is a backgammon and counts 3.

These counts are the base value of the game. The doubling cube multiplies them,
and comes in the next step.

### What the program must be able to do
1. Determine whether a board is a finished game and, if so, which of the three
   outcomes it is.
2. Report the base value of that outcome.

### Design constraints
- The three outcomes are decided by one rule set, not three independent checks.
- The base value and the cube's multiplier are separate concerns. Do not
  anticipate the cube here, but do not design something the cube cannot
  multiply.

### Out of scope
The cube. Notation. The game loop.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — outcome.shoddy: a finished-game test, the outcome, and its base value.
Gate
One constructed terminal position per outcome, in both directions; base values exactly 1, 2, 3.
Watch The backgammon condition is a disjunction — on the bar or in the winner's home board. It is routinely reduced to one half. Assert both.

Step 11 · design proposal

The doubling cube

### What already exists
[ the complete move generator, outcome determination and base values, board
  type, turn-passing, rendering ]

### The rules
Alongside the two dice, a game is played with a doubling cube: a marker showing
2, 4, 8, 16, 32, 64, recording what the game is currently worth.

At the start the cube is worth 1, sits in the middle, and belongs to neither
player. Either may be the first to double.

A player may offer to double the stakes only when it is their turn and only
BEFORE they roll. They may not double after seeing their dice.

The opponent, offered a double, either:
  - PASSES, and immediately loses the game at its value BEFORE the double; or
  - TAKES, and the game continues at twice the previous value, with the cube now
    belonging to the taker.

Only the owner of the cube may offer the next double. While it sits in the
middle either player may. Once a player owns it, their opponent cannot double
until they have taken a double themselves and so gained ownership.

A cube at 64 cannot be doubled further.

The value of a completed game is the cube's value multiplied by the base value
of the outcome — 1 for a plain win, 2 for a gammon, 3 for a backgammon.

### What the program must be able to do
1. Represent the cube: its current value, and who if anyone owns it.
2. Report whether a given player may offer a double at a given moment.
3. Resolve an offered double, given the opponent's answer, producing either a
   finished game with its value or a continuing game with the cube updated.
4. Score a finished game as the cube's value times the outcome's base value.

### Design constraints
- Cube ownership has THREE states — neither player, one player, the other. Not
  two, and not a boolean with a special case.
- Whether a double is legal depends on ownership and on where you are within the
  turn. It is not a property of the board.
- **A player who passes loses the value BEFORE the double, not after.** This is
  the most commonly implemented-wrong rule in the game.
- The existing outcome rules must not be rewritten. The cube multiplies a
  result; it does not change what the result is.

### Out of scope
Whether doubling is a good idea, and whether to take — those need judgement and
come much later. Automatic doubles, beavers, raccoons, the Jacoby rule, match
play and the Crawford rule. This is money play with a plain cube.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the three ownership states.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — cube.shoddy: the cube type, a may-double predicate, a resolution function for take and pass, and a game-scoring function combining cube value with base value.
Review
The cube type with three ownership states; the legality predicate and what it depends on; the resolution signature; how scoring composes with the existing outcome rules without rewriting them.
Reject if
A boolean for ownership. A cube stored inside the board. Ask directly: the cube is at 4, a player doubles to 8, the opponent passes — what is scored? The answer must be 4.
Gate
Cube starts at 1, owned by neither, either may double. After a take it is twice its previous value and owned by the taker. The doubler may not double again until they re-take ownership. Pass at 4 doubled to 8 scores 4. Gammon at cube 4 scores 8; backgammon at cube 2 scores 6. No double after a roll, none from 64. Hand-worked five-turn sequences with the cube changing hands twice.
Ask this too What does this imply for the game loop? If the answer is "it needs a second kind of decision," the model is thinking two steps ahead correctly — and step 13 will be easier for it.

Step 12 · extends

Writing it down

### What already exists
[ move generator, outcome determination, base values, the cube and its rules,
  rendering ]

### The rules
Moves are written as source and destination separated by a slash: a move from
point 8 to point 5 is "8/5". A checker entering from the bar onto point 22 is
"bar/22". A checker borne off from point 6 is "6/off". A move that hits carries
a trailing asterisk: "8/5*". A turn is its moves in the order played, separated
by spaces.

Cube actions are recorded too: a player offering a double, and the opponent's
take or pass, are each part of the record of a game.

### What the program must be able to do
1. Render a play in the notation above.
2. Render a cube action — an offer, a take, a pass — distinguishably.
3. Take a notation string and a list of legal plays, and identify which play it
   denotes, or report that it denotes none of them.

### Design constraints
- Notation must round-trip: rendering a play and reading it back identifies that
  same play, for every play the generator produces.
- Reading notation must not re-derive any rule. It matches against plays the
  generator already produced.
- A cube action and a checker play are different kinds of thing. Do not force
  them into one representation to share a parser.

### Out of scope
The game loop, input handling, opponents.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — notation.shoddy: render a play, render a cube action, and match a string against a list of legal plays.
Gate
Round-trip across every play generated from every test position; cube actions render unambiguously.
The lesson Round-trip properties are the cheapest strong test available. One property (render a play, parse it back in, and identify the same play) covers a page of examples.

Step 13 · design proposal

A game, and a first opponent

### What already exists
[ move generator, outcomes and base values, the cube and its rules, notation
  both ways, rendering, turn-passing ]

### The rules
To begin, each player rolls one die. The higher roll moves first and plays that
pair of dice as their opening turn — the dice are not re-rolled. Equal rolls are
re-rolled.

After that, a turn has TWO decision points. First, before rolling, the player on
turn may offer to double if the cube rules permit; if they do, the opponent must
take or pass, and a pass ends the game. Then the player rolls two dice and plays
a legal turn. A player with no legal play forfeits the turn.

The game ends when either player bears off all fifteen checkers, or when a
double is passed.

### What the program must be able to do
1. Determine who moves first and with what dice.
2. Run a complete game: offer the cube decision, then roll, offer the legal
   plays, apply the chosen one, test for a finished game, pass the turn.
3. Choose uniformly at random among legal plays, reproducibly from a seed.
4. Accept a play or a cube action proposed by a human and either apply it or
   reject it.
5. Make a forfeited turn visible rather than silently skipping it.
6. Report the final value of a game, cube included.

### Design constraints
- **A proposed play is legal if and only if it is among the plays the generator
  produced.** No rule of backgammon may be re-checked at this layer. There is
  exactly one rules engine in this program and it already exists.
- **A turn contains two independent decisions — whether to double, and how to
  play.** Both must be supplied to the loop from outside, and either must be
  replaceable without touching the loop or each other. Later steps supply better
  versions of both.
- A game can end without any checker being borne off. Do not assume the terminal
  condition is a board state.

### Out of scope
Judgement of any kind. Any opponent that is not random, and any doubling policy
beyond "never double, always take."

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the loop taking two decisions from outside.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — game.shoddy: the opening roll, the turn loop, a random chooser, a never-double policy, human-proposal validation, and final scoring.
Review
The loop's shape, and specifically that it takes two decisions from outside; how a human proposal is validated; how a game can end without the board being terminal.
Reject if
Any rule logic in the input path. A loop that names its opponent rather than taking one. A loop with only one decision point. Ask what would have to change to add a cube policy — if the answer touches the loop, reject.
Gate
100 scripted games complete without error; every input either matches a legal option or is rejected; forfeits are reported; a game ended by a pass scores correctly.
Milestone A complete game exists and can be driven from end to end, cube included — every decision point is there and every rule is enforced. Nothing yet renders it or reads a keystroke. That is Phase 2.

Step 14 · extends

Counting the race

### What already exists
[ move generator, the cube, the game loop, outcomes, turn-passing, rendering ]

### The rules
A player's pip count is the total distance their checkers must still travel to
be borne off. A checker needs as many pips as its distance from bearing off. A
checker on the bar must travel the whole board and counts 25.

Contact is broken when the two players' checkers have passed each other so
completely that neither can ever hit the other again, whatever is rolled. From
that moment the game is a pure race: no hitting or blocking can occur and only
distance matters.

A player on roll has an advantage worth roughly one move.

### What the program must be able to do
1. Report either player's pip count.
2. Report whether contact is broken.
3. Given a board with contact broken, estimate the probability the player on
   roll wins.

### Design constraints
- The race estimate is a probability between 0 and 1. Not a score, not a pip
  difference.
- Constants it uses must be named values changeable in one place; they will be
  tuned.
- Contact detection must be exact, not a heuristic. Either a hit is possible or
  it is not.

### Out of scope
Positions where contact still exists. Choosing plays. The cube.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — race.shoddy: two pip counts, an exact contact test, and a race win-probability estimate with its constants named.
Gate
Pip counts exact against hand-computed values on six positions; contact detection correct on constructed pairs that differ by one checker.
Watch A checker on the bar is 25 pips, not zero. Omitting it is the reliable defect here.

Step 15 · design proposal

Judging a position

### What already exists
[ pip counts, contact detection, race estimation, move generator, the cube, the
  game loop, turn-passing ]

### The rules
A point holding exactly one checker is a blot and can be hit. A point holding
two or more is made and is safe. Consecutive made points form a prime; six in a
row cannot be passed by any roll. A made point inside the opponent's home board
is an anchor and gives a player somewhere safe to wait.

A blot is hit directly, by a single die, when an opponent checker is one to six
points away. It is hit indirectly by two dice combined — but only when the
intermediate landing point is not blocked.

A game does not simply end in a win or a loss. It ends in one of six results: a
plain win, a gammon win, a backgammon win, and the three corresponding losses.
A position where the opponent has borne off nothing is very different from one
where they are nearly home, even at the same winning chances.

### What the program must be able to do
1. Report the probability a given blot is hit by the opponent's next roll.
2. Estimate, for any board, the probabilities of ALL SIX RESULTS from the point
   of view of the player on roll. They describe one game and must be consistent
   with that.
3. Use the race estimate when contact is broken and the contact estimate
   otherwise.

### Design constraints
- **Hit probability must be computed by enumerating all 36 outcomes of two dice
  and asking whether each admits a hitting reply.** Do not derive or approximate
  a formula. The enumeration is the definition.
- Estimating a single winning chance is NOT sufficient. Later steps need gammon
  and backgammon chances to value the cube, and adding them afterwards would
  mean redesigning this.
- Every weight or coefficient must be a separately named value.
- The estimate must be consistent under a change of viewpoint: a board judged
  from one side and the same board judged from the other must agree about who is
  winning and by how much.

### Out of scope
Turning these probabilities into points. Choosing plays. Cube decisions.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the six-result output and the relationship among them.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — judge.shoddy: a blot hit-probability function by enumeration, a six-result distribution for any board, and the race/contact switch. Every weight a named value.
Review
That the output is the full six-result distribution, not a winning chance. What relationship holds among the six, and whether the design enforces it. That hit probability is an enumeration. How the weights are exposed for tuning.
Reject if
A single win-probability output. Any hit probability that is not an enumeration. Weights as literals inside expressions.
Gate
Hit probabilities against exhaustive enumeration on 20 constructed positions; the six probabilities consistent on every sampled board; a board and its viewpoint-reversed twin give mirrored distributions.
Watch This is where the cube's dependency lands. Nothing needs the full distribution until step 16, so there is every temptation to accept a single number and retrofit later. Retrofitting means redesigning. This is the clearest case in the project of a requirement that must anticipate three steps ahead.
The assertion that matters The assertion that matters is the viewpoint check. A viewpoint error is invisible in self-play, because both sides make it identically and the games look fine.

Step 16 · extends

Equity

### What already exists
[ the six result probabilities for any board, race and contact estimation, the
  cube and its rules, the game loop ]

### The rules
The value of a position is measured in points, expected. That is its equity.

Ignoring the cube, equity is the sum over every possible result of its point
value times its probability: a plain win is +1, a gammon +2, a backgammon +3,
and the corresponding losses are negative.

Equity is what makes cube decisions computable, and it is also the right measure
for choosing a play, because a play that raises your winning chances while
losing gammon chances may be worse than it looks.

### What the program must be able to do
1. Compute the equity of a board in points, from the six result probabilities.
2. Multiply an equity by a cube value to give the equity of a game played at
   those stakes.

### Design constraints
- **Equity is in points and is not bounded by 0 and 1.** It ranges from -3 to
  +3. Do not reuse the probability type for it. Confusing the two is the defect
  this step exists to catch.
- Equity from the other player's viewpoint is the negative of equity from this
  one. That must hold exactly, not approximately.

### Out of scope
Cube decisions. Choosing plays. Searching.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — equity.shoddy: equity in points from a result distribution, and equity at a given cube value.
Gate
A certain plain win is exactly +1, a certain gammon +2, a certain backgammon +3, and the negatives; a 50% position with no gammons is exactly 0; equity plus its viewpoint reversal is exactly 0; five hand-computed distributions.

Step 17 · extends

Choosing a play

### What already exists
[ equity, the six result probabilities, the move generator, the game loop with
  pluggable decisions, random choice ]

### The rules
None new.

### What the program must be able to do
1. Given a board and a roll, choose among the legal plays by judging the
   position each leaves, and pick the best.
2. Offer at least three strengths of opponent: random, one judging only by the
   race, and one using the full judgement.
3. Play a specified number of games between any two opponents and report the
   result as points per game, alternating who moves first.

### Design constraints
- **A play is judged by the position it leaves the OPPONENT.** A high equity for
  them is a low one for you. Getting this backwards produces an opponent that
  plays the worst available move, and it will look like weak judgement rather
  than an inverted comparison.
- Ties break deterministically, so a match is reproducible from a seed.
- Opponents are interchangeable without the game loop changing.

### Out of scope
Cube decisions. Looking more than one move ahead.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — choose.shoddy: a one-ply chooser, three opponent strengths, and a seeded match runner reporting points per game.
Gate
Race-only beats random in at least 70% of 1,000 games; full judgement beats race-only in at least 65%.
Worth knowing Measured over 12,264 positions from real play, there are 20.5 legal plays per roll on average, against 21 distinct rolls. Each further ply (one more move of look-ahead) costs a factor of about 420. At a dice node there is nothing to prune, because every outcome contributes to an average. The original TD-Gammon, the 1990s program that first learned backgammon from self-play, searched one ply. Nearly all the strength is in the judgement, not the search — which is why step 15 comes before this one.

Step 18 · design proposal

When to double, and when to take

### What already exists
[ equity, the six result probabilities, the cube and its rules, the game loop
  with a pluggable cube decision, one-ply play selection, match play ]

### The rules
Facing a double, a player has two choices with computable values. Passing loses
the current stake exactly. Taking means playing on for twice the stake, and is
worth twice the equity of the resulting position.

In the simplest case — where the taker will never get to double back — taking is
correct precisely when it is no worse than passing. Writing p for the taker's
winning chances, with no gammons:

    pass  = -1
    take  = 2 x (p - (1 - p)) = 4p - 2
    take when 4p - 2 >= -1,  that is, p >= 0.25

A quarter is the take point. Below it, pass; above it, take.

Owning the cube after taking is worth something, because it permits doubling
back later. That advantage lowers the take point below a quarter — to roughly a
fifth in practice. How much is a matter of estimate, not arithmetic.

A player may also be TOO GOOD to double: so far ahead that playing on for a
gammon is worth more than the opponent's certain pass.

### What the program must be able to do
1. Given a board and a cube state, decide whether to offer a double.
2. Given an offered double, decide whether to take or pass.
3. Play matches between a cube-aware opponent and one that never doubles and
   always takes, reporting points per game.

### Design constraints
- **The take point with no recube possible is exact arithmetic, not an estimate,
  and your implementation must reproduce it exactly.** The adjustment for owning
  the cube is an estimate and must be a separately named value.
- Whether to double and whether to take are different questions with different
  answers. Do not implement one as a threshold on the other.
- Gammon chances change both answers. Use the full result distribution, not the
  winning chance alone.
- The decision must be replaceable in the game loop without touching play
  selection.

### Out of scope
Match play, match equity tables, the Crawford rule. Recube vantage beyond a
single named constant.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, including the model's own derivation of the take point.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — cube-policy.shoddy: a should-double decision and a take-or-pass decision, with the recube adjustment as a named constant.
Review
Ask the model to derive the take point in its own words before you accept anything. If it cannot reproduce those four lines, it has hard-coded 0.25 without understanding it, and every neighbouring decision will be subtly wrong.
Reject if
Double and take share a threshold. The recube adjustment is buried rather than named.
Gate
The dead-cube take point is exactly 0.25, asserted from both sides of the boundary. A position too good to double is not doubled. A cube-aware opponent beats a never-double-always-take opponent over 1,000 games by a positive, stable margin.
Why this step matters most This is the only gate in the project that checks a judgement against exact arithmetic. Every other gate checks a mechanic against the rules, or a strength claim against a tournament. Four lines of algebra produce a decision that is either right or wrong, with no argument available. That is what it looks like when a requirement is precise enough that the correct answer is provable.

Step 19 · extends

Looking one move further

### What already exists
[ one-ply play selection, cube decisions, equity, the result distribution,
  match play, the move generator ]

### The rules
Two dice produce 21 distinct rolls: 15 unequal pairs, each occurring twice in
36, and 6 doubles, each once in 36.

### What the program must be able to do
Choose a play by considering, for each candidate, what the opponent would do
with each of the 21 possible replies, and preferring the candidate with the best
average outcome weighted by how likely each reply is.

### Design constraints
- The average over replies must be weighted by actual probability. The 21 rolls
  are not equally likely, and treating them as such is wrong by a factor of two
  on most of them.
- **Cutting off branches by comparing against a best-so-far is invalid where the
  value is an average**, because every outcome contributes. Do not do it.
- Considering every candidate costs roughly 8,000 judgements per decision. If
  you reduce the candidates before expanding them, **report how many were
  dropped.** A silent reduction reads as full coverage.

### Out of scope
Three plies. At about 20 plays and 21 rolls per level it is 3.5 million
judgements per decision.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — choose.shoddy extended: a two-ply chooser weighting all 21 rolls, reporting how many candidates it dropped.
Gate
Beats one-ply on the same judgement in at least 52% of 1,000 games; the truncation count appears in the log.
The lesson Put the cost in the requirement, and log every truncation. A silent top-k cut (keeping only the k best-looking candidates) reads as full coverage. And 52% is what a real improvement looks like at this level. That is worth saying out loud, because it is easy to mistake for noise.

Step 20 · extends

Describing a board to a network

### What already exists
[ board type, turn-passing, the result distribution, equity, the move generator ]

### The rules
None from backgammon. This is a data format, given exactly, to be implemented as
given rather than improved.

### What the program must be able to do
Turn any board into exactly 198 numbers:

- For each of the 24 points, for each of the two players, four numbers
  describing that player's checker count n on that point:
    the first is 1 if n is at least 1, otherwise 0
    the second is 1 if n is at least 2, otherwise 0
    the third is 1 if n is at least 3, otherwise 0
    the fourth is (n - 3) / 2 if n is more than 3, otherwise 0
  That is 24 x 2 x 4 = 192 numbers.
- Two numbers for the bar: each player's count divided by 2.
- Two numbers for borne-off checkers: each player's count divided by 15.
- Two numbers indicating which player is to move.

### Design constraints
- Implement this format. Do not substitute a one-hot encoding, a raw count, or
  anything that seems more natural. The four-number scheme is deliberate: the
  first three mark thresholds and the fourth carries the excess.
- Exactly 198 numbers for every board.

### Out of scope
The network. Training. The cube — it is not part of this description.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — encode.shoddy: one function turning a board into exactly 198 numbers.
Gate
Length exactly 198; every value in range; viewpoint-reversed boards give mirrored vectors; three hand-computed positions match exactly.
The lesson Some requirements are just data layouts, and they must be exact. The threshold-plus-excess scheme is not something a model derives; it has to be written out number by number. Expect it to want to improve on it.

Step 21 · design proposal

Learning, checked against an answer you can compute

### What already exists
[ the board description; the neural machine — feed-forward networks with one
  tanh hidden layer, gradient descent with momentum, binary save and load ]

### The rules
Nothing from backgammon. **This step does not touch the game.**

### What the program must be able to do
1. Learn to predict the value of a state from experience, using temporal
   differences: after each transition, adjust the prediction toward the next
   prediction, and credit that adjustment not only to the state just left but to
   states visited earlier in the episode, with credit decaying the further back
   they are.

2. Demonstrate this on a problem whose answer is computable by hand: five states
   in a line, an end at each side worth 0 and 1, moving left or right with equal
   probability. The true value of each state can be worked out on paper.

3. Show that the learned values converge to the computed ones.

### Design constraints
- **The decaying credit must persist across transitions within an episode and
  reset between episodes.** An implementation adjusting only the most recent
  state is ordinary gradient descent toward a moving target — it will look
  similar, learn far more slowly, and present as a tuning problem for weeks. If
  nothing in your design survives from one transition to the next, it is wrong.
- The decay rate and the step size must be named values.
- **Do not connect this to backgammon in this step.** The entire point is to be
  wrong somewhere you can prove it.

### Out of scope
Backgammon. Self-play. The 198-number description.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on what persists between transitions.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — td.shoddy and randomwalk.shoddy: the learner with eligibility traces, the five-state walk, and a convergence demonstration.
Review
Ask what state persists between transitions. If the answer is "none," reject — that is the defect this step exists to catch, and it is far easier to see in a design than in code.
Gate
Learned values within tolerance of the hand-computed solution on the random walk.
The most transferable lesson here Test the learning algorithm on something you can verify before pointing it at the problem. Debug both at once and you debug neither. These failures are silent: for weeks they look like nothing worse than bad hyperparameters, the tuning knobs of a learner.

Step 22 · extends

Learning to play by playing itself

### What already exists
[ the 198-number board description; temporal-difference learning verified on the
  random walk; equity; the six result probabilities; one-ply play selection;
  cube decisions; match play; the full-judgement opponent as a fixed reference ]

### The rules
A game does not end in a win or a loss but in one of six results, and the cube
multiplies whichever occurs. Correct play and correct cube decisions both depend
on the whole distribution, not on the chance of winning alone.

### What the program must be able to do
1. Judge a board with a network producing enough numbers to give the full result
   distribution for the player on roll — from which equity, play selection, and
   cube decisions all follow.
2. Play against itself, choosing plays and cube actions by its own current
   judgement, and learn from every move by temporal difference. No human games,
   no opening book, no exploration rule — **the dice supply all the exploration
   this game needs.**
3. Save the network at intervals so training can resume and past versions be
   retained.
4. Report strength as points per game over at least 1,000 games.

### Design constraints
- The network's outputs are not independent probabilities. State what
  relationship holds among them and enforce it.
- The terminal signal must reflect what actually happened: a game ending in a
  gammon trains a different target from one ending plainly, and a game ended by
  a passed double is different again.
- **Play selection and cube decisions use the same network.** Do not train two.
- **Strength must be measured against a FIXED opponent that does not change.**
  Measuring against the current network shows improvement by construction and
  proves nothing.
- One thousand games is not optional. Backgammon variance is enormous and a
  hundred-game result is noise that will look like signal.

### Out of scope
Match play. Match equity tables. Terminal display.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — selfplay.shoddy: the network, the self-play training loop, checkpoint save and load, and a strength report against the fixed reference.
Gate
The outputs satisfy the stated relationship on every sampled board; equity computed from them matches hand-computed values on constructed terminal positions; each checkpoint beats the previous one and beats the fixed reference by a widening margin.
Expectations The original TD-Gammon plateaued at roughly 1.5 million self-play games. Recognisable competence arrives far earlier — in the low tens of thousands. Checkpoint often and be patient.
Milestone It taught itself — checkers and cube both. No opening book, no human games, no supervision.

Phase 2 — The game

Phase 1 produced something thoroughly tested that nobody can play. Phase 2 wraps it in a window: a board you move checkers on, dice that roll, undo, and hints taken from the engine itself. Nine steps.

The method does not change, and neither does the engine. No step in Phase 2 may add, alter, or re-decide a rule of backgammon. Every question about what is legal is answered by asking Phase 1 what it already worked out. That is what keeps a graphical program from quietly growing a second, disagreeing rules engine inside its interface.

A graphical program is more testable than it looks Three of the four layers here have exact gates. The layout is arithmetic. The interaction is a value changed by a function, so a scripted sequence of intents (the player's meanings, such as "pick up point 8") can be asserted without a window. The same sequence delivered by mouse and by keyboard must produce identical state. Even the drawing can be checked, because the surface can be read back a pixel at a time. Only aesthetics need eyes.

Step 23 · design proposal

The layout

### What already exists
[ the complete engine: board type, turn-passing, the move generator, the cube,
  outcomes, notation, the game loop, judgement, opponents ]

### What this phase is
Phase 1 built a backgammon engine that nobody can play. Phase 2 builds the game
around it: a window, a board you move checkers on with mouse or keyboard, dice
that roll, undo, and hints taken from the engine itself.

**The engine does not change.** Nothing in Phase 2 may add, alter, or re-decide
a rule. Every question about legality is answered by asking the engine what it
already knows.

### The rules
None. This step is arithmetic.

### What the program must be able to do
Given the width and height of a drawing surface, describe where everything sits:

1. The rectangle occupied by each of the 24 points, laid out as a real board —
   two rows of twelve, split by a bar down the middle, with the player's home
   quadrant nearest them.
2. The position of the Nth checker resting on a given point, so that checkers
   stack and a point holding many checkers still fits its rectangle.
3. The rectangles for the bar, for each player's borne-off tray, for the cube,
   and for the area where the dice are shown.
4. Given a coordinate on the surface, which of those regions it falls in — a
   point number, the bar, a tray, the cube, the dice, or nothing at all.

### Design constraints
- **This step touches no drawing surface and opens no window.** It is arithmetic
  over a width and a height, and every function in it is pure.
- The region lookup must be the exact inverse of the layout: the centre of the
  rectangle computed for point N must map back to point N, for all 24.
- The layout must be computed from the surface size, not written as a table of
  fixed pixel coordinates. A different window size must produce a proportionate
  board, not a broken one.
- Checkers on a heavily loaded point must remain inside that point's rectangle.
  Decide how — overlap them, shrink them, or show a count — and say which.
- Nothing here may consult the engine. This describes a board, not a position.

### Out of scope
Drawing anything. Colour. Events. Any board state at all.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on how the layout scales with the surface and how crowded points are handled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — layout.shoddy: a layout descriptor from a width and height, a rectangle per point, a checker position function, rectangles for bar, trays, cube and dice, and a coordinate-to-region lookup.
Review
How the layout is derived from the surface size. What happens to a point holding six or more checkers. Whether the region lookup covers every pixel or leaves gaps.
Reject if
Hard-coded pixel tables. A lookup that cannot return nothing. Any function here that takes a board.
Gate
The centre of each point's rectangle maps back to that point, for all 24. No two point rectangles overlap. Bar, trays, cube and dice regions are disjoint from the points and from each other. Fifteen checkers on one point all fall inside its rectangle. The whole thing recomputed at a different surface size still satisfies every one of these.
Why this comes first Geometry is the one part of a graphical program that has exact answers, and it is where the irritating bugs live. Settling it before anything is drawn means every later step can assume the board is where it says it is.

Step 24 · extends

Drawing the board

### What already exists
[ the engine; the layout — rectangles per point, checker positions, region
  rectangles, coordinate lookup ]

### The rules
None.

### What the program must be able to do
Draw a complete board onto a drawing surface from a position and a layout:

1. The board itself — points alternating in two colours, the bar, the two trays.
2. Every checker, in the colour of its owner, stacked on its point.
3. Checkers on the bar and checkers borne off.
4. The doubling cube, showing its current value, positioned to show who owns it
   — one side, the other, or the middle.
5. The dice, when there are dice to show.
6. Whose turn it is, and the score if there is one.

### Design constraints
- **This step draws and decides nothing else.** It takes a position and a layout
  and produces pixels. It must not consult the engine, test legality, or hold
  any state of its own.
- Draw the whole frame, then present it once. Do not present a partly drawn
  board.
- Every coordinate must come from the layout. No drawing function may compute a
  position of its own.
- It must draw a position handed to it, not "the current position" — including
  positions that could not arise in play, because tests will hand it those.

### Out of scope
Events, input, animation, highlighting, selection, the game loop.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — draw.shoddy: one function drawing a whole frame from a position, a cube state, an optional pair of dice, and a layout; plus whatever private helpers it needs.
Gate
Drawn into a buffer and read back pixel by pixel: the centre of a point holding checkers is the owner's colour; the centre of an empty point is not; a point holding one checker has exactly one checker drawn and the second stack position is empty; the bar shows the right count; the cube renders on the correct side for each of its three ownership states. A position with fifteen checkers on one point draws without any pixel landing outside that point's rectangle.
Drawing is more testable than it looks The surface can be read back a pixel at a time, so "the centre of point 6 is dark after drawing a position with checkers there" is an ordinary assertion. It will not catch an ugly board. It catches the off-by-one error (a position wrong by one place) that puts a checker in the wrong quadrant — which is the bug you would otherwise find by squinting.
Watch These tests need a real window, so they run differently from the Phase 1 suite. Keep them in a separate file and say so, rather than letting the main suite quietly depend on a display.

Step 25 · design proposal

What can move where

### What already exists
[ the engine — in particular the generator that returns every legal play for a
  board and a roll; the layout; drawing ]

### The rules
A player with a roll has a set of legal plays, and the engine already computes
it. A play is a sequence of individual checker moves.

A person does not choose a play from a list. They pick up one checker at a time.
So the game must work out, at each moment, which checkers may be picked up and
where each may be put down — such that whatever the player builds can always be
completed into a legal play.

This matters more than it sounds. A player who moves a checker somewhere that
happens to be legal on its own, but leaves no legal way to use the remaining
dice, has been led into a position the rules do not allow. The game must never
offer that move.

### What the program must be able to do
Given a board, a roll, and the sequence of moves the player has committed so far
this turn:

1. Report which points hold a checker that may be picked up.
2. Given a chosen source, report every destination it may be put down on.
3. Report whether what the player has committed so far is a complete turn.
4. Report the board as it currently appears, part-way through the turn.

### Design constraints
- **Nothing here decides what is legal.** Every answer must be derived from the
  set of legal plays the engine already produced for this board and roll, by
  keeping only those beginning with the moves the player has committed. A source
  is available when some remaining play moves a checker from it; a destination is
  available when some remaining play moves this checker there.
- It follows that a partially built turn can always be completed. If your design
  can strand a player, it is wrong.
- The engine is asked for the legal plays **once per roll**, not once per click.
- This layer holds no pixels and no coordinates. It speaks in point numbers.

### Out of scope
Mouse, keyboard, drawing, highlighting, undo, the cube.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on how the committed prefix filters the legal plays.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — select.shoddy: available sources, available destinations for a source, a completeness test, and the board as it currently stands mid-turn.
Review
That every answer is a filter over the engine's play list, not a fresh legality test. Where the play list is held so it is computed once per roll. How a prefix is compared against a play.
Reject if
Any re-implementation of a movement rule. Anything that checks a single move in isolation — that is exactly the design that strands players.
Gate
In a position where a checker can legally move but doing so leaves the second die unplayable, that destination is not offered. On a roll with exactly one legal play, the sources list holds exactly one point. On a forfeited turn the sources list is empty. After committing every move of some legal play, the completeness test is true and the resulting board equals the board that play produces.
The idea Phase 2 turns on The interface never knows a rule. It filters a list the engine already made. Everything downstream — highlighting, keyboard control, undo, hints — is a different way of walking that same filtered list. That is why none of them can disagree with the engine or with each other.

Step 26 · design proposal

Building a turn

### What already exists
[ the engine; the layout; drawing; available sources, available destinations,
  the completeness test, the mid-turn board ]

### The rules
A turn is built one checker at a time. The player picks up a checker, puts it
down on one of the places it may go, and repeats until the turn is complete.

They may change their mind about a checker they have picked up but not yet put
down.

### What the program must be able to do
Hold everything about the turn in progress, and change it in response to what the
player intends:

1. Pick up a checker from a point.
2. Put the held checker down on a destination.
3. Put down a held checker without moving it — change of mind.
4. Report what should be shown: which point is held, which destinations are
   available, and the board as it currently stands.
5. Report when the turn is complete and what play was built.

Intent must be described in the game's own terms — pick up point 8, put down on
point 5 — not in terms of buttons or keys.

### Design constraints
- **The state must be a value, changed by a function of the current state and one
  intent.** No hidden state, no partial updates. This is what makes it testable
  without a window and identical under mouse and keyboard.
- Picking up a checker from a point with no available destination must be
  refused, not accepted and then undone.
- A forfeited turn — no legal play at all — must be a state the player is told
  about, not a hang.
- When exactly one legal play exists, say so, so the interface can offer to play
  it rather than making the player reconstruct the only option.

### Out of scope
Mouse, keyboard, drawing, undo, the cube, the opponent.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the turn-in-progress state and the intents that change it.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — turnstate.shoddy: the state type, a constructor for a new roll, one function applying an intent, and readers for what to display and whether the turn is done.
Review
That state changes only through one function taking one intent. What the intents are, and that they name points rather than devices. How a forfeited turn is represented.
Reject if
State scattered across several values. An intent vocabulary that mentions clicks, buttons, or keys. Any path that mutates the board directly rather than through a committed move.
Gate
A scripted sequence of intents from a known roll produces the expected play. Picking up a checker with no destinations is refused. Putting down on an unavailable destination is refused. A completed sequence yields a play the engine also lists as legal — assert that against the generator directly.

Step 27 · extends

Undo

### What already exists
[ the engine; the turn-in-progress state and its intents; the game loop from
  Phase 1 ]

### The rules
A player may take back the last checker they moved this turn, and go on taking
back until the turn is as it was when they rolled.

A player may also take back their whole last turn, along with the opponent's
reply to it, returning the game to the moment before they moved.

### What the program must be able to do
1. Take back the last committed move of the turn in progress.
2. Take back every move of the turn in progress at once.
3. Take back the last completed turn together with the opponent's reply, so the
   player is on roll again with the dice they had.

### Design constraints
- **Do not compute an inverse move.** There is no such thing as un-hitting a
  checker by reasoning backwards. Everything in this program is immutable, so
  the earlier state still exists — keep it and return to it. If your design
  contains a function that reverses a move, it is the wrong design.
- Taking back must restore everything that changed, not only the board: the
  dice, whose turn it is, the cube, and the moves available.
- Taking back a completed turn must restore the dice that were rolled for it, so
  the player replays the same roll rather than getting a new one.
- The limit of how far back one can go must be stated, not implicit.

### Out of scope
Redo. Saving a game. Taking back the opponent's decision to double.

### Phase
Extends the accepted design — no new design. Tests first, then implementation.
Deliverables
No design document — the interface is settled.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — undo.shoddy: a history value, a function to record a state onto it, and three take-back operations — last move, whole turn, last completed exchange.
Gate
Committing a move then taking it back yields a state identical to the one before it, including dice and available destinations. Taking back a whole turn from any point in it equals the state at the roll. Taking back a completed exchange restores the same dice. Taking back at the start of a game is refused rather than crashing. Assert identity with the engine's own equality, not field by field.
Nearly free here Undo is usually the hardest feature in a board game, because it means inverting every effect. In a language where nothing is ever modified, the old state was never destroyed — undo is a list and a pointer. This is the clearest payoff in the whole project for a property chosen back at step 1.

Step 28 · design proposal

The keyboard

### What already exists
[ the turn-in-progress state and its intents; undo; the layout; drawing ]

### The rules
None. This is about control.

### What the program must be able to do
Let a player build a turn entirely from the keyboard, with no pointing device:

1. Move a highlight between the points that currently hold a movable checker.
2. Pick up the highlighted checker.
3. Move a highlight between the destinations available to it.
4. Put it down, or change their mind.
5. Reach the bar and the borne-off tray the same way.

And, separately, let a player do all of the same things with a pointing device:
pressing on a checker picks it up, releasing on a destination puts it down, and
releasing anywhere else is a change of mind.

### Design constraints
- **Both devices must produce the same intents.** Neither may reach the turn
  state through a path the other does not use, and no rule or availability check
  may live in either. They translate; they do not decide.
- The keyboard must be able to reach every region the pointer can, including the
  bar and the trays.
- Highlight movement follows the board's geometry, so that moving the highlight
  in a direction goes somewhere a person would expect.
- Nothing may require a device the player does not have. A player with only a
  keyboard must be able to complete a game.

### Out of scope
Commands beyond building a turn. Animation. The cube.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the shared intent vocabulary and how each device reaches it.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — control.shoddy: a highlight position within the turn state, a translator from key events to intents, and a translator from pointer events to intents.
Review
That both translators emit the same intents and nothing else. How the highlight moves across a board whose two halves run in opposite directions. Whether the bar and trays are reachable by keyboard.
Reject if
Either translator testing availability or legality itself. Any intent only one device can produce. A highlight that can rest on a point holding nothing movable.
Gate
The same scripted sequence of intents, delivered once as pointer events and once as key events, produces identical turn state at every step. A complete game is playable from the keyboard alone. Releasing the pointer away from any destination leaves the state as it was before the checker was picked up.
The gate that matters Two devices producing byte-identical state is a strong, cheap, entirely automatable assertion about an interactive program. It is only possible because step 26 made the state a value changed by one function. Interaction bugs are famously hard to test. This one is not.

Step 29 · design proposal

Commands, and the hint

### What already exists
[ the engine, including judgement and the opponents at each difficulty; the turn
  state; undo; both control translators; drawing ]

### The rules
A game needs more than moving checkers: starting a new one, giving up, changing
who you are playing against, offering the cube, answering a double.

And a player who is stuck should be able to ask what the engine would do.

### What the program must be able to do
1. Recognise a set of commands and carry them out: start a new game, take back
   (each of the three kinds), ask for a hint, offer a double, take, pass, choose
   an opponent strength, and quit.
2. Refuse a command that does not apply right now, with a reason, rather than
   doing something surprising. Offering a double after the dice are rolled is a
   refusal, not an error.
3. Produce a hint: the play the engine would choose for this board and roll, and
   the cube decision it would make when one is due.

### Design constraints
- **The hint must be produced by asking the current opponent what it would play.**
  Not a simplified version, not a separate weaker path, not a heuristic written
  for the purpose. A hint from the strongest opponent and that opponent's own
  move must be the same play, always.
- A hint is shown, never played. The player remains the one who moves.
- Whether a command applies is decided in one place, so that every way of
  invoking it agrees.
- Commands and turn-building intents are different things. Do not force them
  into one vocabulary.

### Out of scope
Menus, buttons, decoration, help text. This step decides what the commands are
and what they do, not how they are offered.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on how a hint is obtained from the existing opponent.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — command.shoddy: the command type, an applicability test, an execution function returning a new game state or a refusal with a reason, and a hint function returning a play and, when due, a cube decision.
Review
That the hint calls the same chooser the opponent uses at the current strength. Where applicability is decided. That refusals carry a reason.
Reject if
A hint computed by any code the opponent does not also use. Applicability decided in more than one place. A command that silently does nothing when it does not apply.
Gate
For a hundred sampled boards and rolls, the hint equals the play the opponent at that strength makes for the same board and roll. Every command is refused with a reason in a state where it does not apply — doubling after the roll, taking with no double offered, taking back at the start of a game. A new game returns the starting position with the cube in the middle.

Step 30 · design proposal

Rolling the dice

### What already exists
[ the engine; the turn state; commands; drawing; the layout's dice region ]

### The rules
Dice are rolled, and a player likes to watch them roll. What they show when they
stop is what was rolled.

### What the program must be able to do
1. Show dice tumbling briefly and settling on the values that were rolled.
2. Show the settled values for as long as the turn lasts.

### Design constraints
- **The values are decided before the animation begins.** The animation displays
  a result; it never produces one. Nothing about how it runs, how long it lasts,
  or whether it runs at all may change what was rolled.
- What is shown at any moment must be a function of the rolled values and how
  much time has passed since the roll — nothing else. Given the same values and
  the same elapsed time it must show the same thing.
- At the end of the animation the display must equal the rolled values exactly.
- **The animation must be skippable and interruptible.** Any input during it ends
  it immediately, showing the settled values, and the game continues from a state
  identical to the one it would have reached had the animation finished.
- The game must remain playable if frames are slow or dropped. Elapsed time
  drives it, not a count of frames drawn.
- Keep it plain: a short tumble and a settle. No physics, no bouncing, no
  shadows.

### Out of scope
Sound. Moving checkers with animation. Anything else that moves.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the animation being a function of values and elapsed time.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — dice.shoddy: a function from rolled values and elapsed time to what should be shown, a settled test, a skip operation, and the drawing of a die face.
Review
That the display function takes elapsed time and returns what to show, with no state of its own. That skipping and finishing reach the same state. How long the tumble lasts and where that number lives.
Reject if
Any design where the animation holds the dice values, generates them, or can be observed to change them. A frame counter instead of elapsed time. An animation that must complete before the player may act.
Gate
At elapsed time past the tumble, the display equals the rolled values exactly. The same values and elapsed time always give the same display. Skipping at any moment gives the same state as letting it finish. A roll whose animation is never drawn at all still yields the same play list from the engine.
Why an animation gets a gate The requirement makes the animation a pure function of two inputs: what it shows depends on those inputs and nothing else. That is why it can be asserted like anything else. Most animation is untestable because it is written as a mutable loop (one that changes state in place) owning its own clock. Requiring it to be a function of elapsed time costs nothing and moves it inside the test suite.

Step 31 · design proposal

The game

### What already exists
[ everything: the engine and its opponents; the layout; drawing; the turn state;
  undo; both control translators; commands and hints; the dice animation ]

### The rules
None. This is assembly.

### What the program must be able to do
Be a game a person can start and play:

1. Open a window and set a steady frame rate.
2. Let the player choose an opponent strength before starting, and load the
   trained network for the strongest one.
3. Run the game: show the board, take the player's input, run the opponent's
   turns, roll and animate dice, apply the cube rules, and declare the result.
4. Show the player what they need — whose turn it is, the dice, the cube, the
   score, which checker is held, where it may go, and any hint they asked for.
5. Close cleanly when asked, and when the window is closed.

### Design constraints
- **This program contains no rule and no judgement.** It translates events into
  intents, hands them to the pieces already built, and draws what comes back. If
  a rule of backgammon appears anywhere in it, the design is wrong and one of the
  earlier layers is missing something.
- The opponent must not block the window. A player must be able to close the game
  while the opponent is thinking.
- Draw the whole frame and present it once.
- Everything the player needs must be visible or reachable without instructions
  printed elsewhere.
- The strongest opponent must load the trained network at startup. A player must
  never face an untrained one by accident.

### Out of scope
Saving and loading games. Match play. Sound. Settings that persist.

### Phase
Design proposal first. Then tests. Then implementation. Three replies.
Deliverables
D1 — design document, centred on the loop's shape and how it stays free of rules.
D2 — test.shoddy: one assertion per clause of the gate below, each message a sentence.
D3 — backgammon.shoddy: the entry point, opponent selection, network loading, the event loop, and the frame draw. Plus a short note on how to run it.
Review
That the loop only translates, dispatches and draws. Where the opponent's turn happens and whether it can block. What is loaded at startup and what happens if the trained network is missing.
Reject if
Any legality test, any move construction, any scoring in this file. A loop that cannot be closed while the opponent thinks. Silent fallback to an untrained opponent.
Gate
A complete game played through by mouse alone, and again by keyboard alone. Every command reachable and every refusal explained. The window closes cleanly from the command and from the window itself. Started against the strongest opponent, the trained network is in use — assert it, do not assume it.
Milestone A game. Someone who has never heard of any of this can sit down and play backgammon against an opponent that taught itself, and the thirty-one steps behind it never once let a rule leak out of the engine.
The closing argument Twenty-two steps of engine were written without any knowledge that a screen exists, and nine steps of game were written without any knowledge of a rule. That seam is what made every gate in Phase 1 possible and every gate in Phase 2 cheap. It was chosen at step 1 and never crossed.

Deliberately excluded

These are stated so a knowledgeable reader can see they were decisions rather than omissions.

ExcludedWhy
Match play, match equity tablesCube decisions in a match depend on the score, needing a match equity table — a large body of tabulated external data and a different decision theory. A second project.
The Crawford ruleMatch play only.
Jacoby ruleA money-play convention: gammons count single until the cube is turned. Nearly free to add and it changes cube strategy substantially. The one most worth adding back — as its own step with its own gate, never folded quietly into step 11.
Beavers, raccoonsOptional money-play conventions, not part of the game proper.
Automatic doublesA house convention on the opening roll.

Things to watch

Ordered by how much damage they do if missed.

  1. Review the design against criteria written before it arrived. Otherwise you will rationalise whatever shows up — it will be plausible, articulate, and have a reason for everything. Write the rejection criteria first, before the design exists, for the same reason the gate is written before the tests.
  2. The tests and the code come from the same mind. This is the price of not typing, and it is the failure mode to keep in view: a requirement misread once can be encoded twice and go green. Four things make that expensive, and all four are in the method. The tests arrive in their own reply before any implementation exists. They transcribe a gate you wrote. A property constrains every input where an example constrains one. And a fresh session given only the gate will tell you what it would have tested. What none of it fixes is a gate that is wrong in the same way your own understanding is wrong. That is the residual risk of the whole build. The answer to it is to spend your time on the gates, which is where you were going to spend it anyway.
  3. Never hand-patch generated code. Fix the requirement or the design and regenerate. Every hand-patch returns on the next regeneration and silently invalidates the cycle counts you are recording. When you do patch by hand — you will — record it as a workflow violation rather than quietly.
  4. Keep the finished implementation out of the model's reach. Once it can see the answer it stops deriving it, and nothing downstream means anything.
  5. Performance defects pass their tests. Step 7 is the case in point: the quadratic reduction (one whose work grows with the square of its input) is correct, so every assertion goes green while the design is unusable. Time each gate and record the number.
  6. Watch for the model building the next step. If whole-turn generation arrives with duplicate removal already in it, step 7's finding is gone. It will happen — the model can see where this is going. Reject and regenerate rather than accepting a helpful bonus.
  7. A design needing more code than expected is a signal about the previous design. Step 2 is the canary, the early warning. If passing the turn is not nearly trivial, step 1 was wrong. Going back is not failure. It is the finding.
  8. Step 15 is the cube's real dependency. If the position judgement produces only a winning chance, steps 16 and 18 cannot be built without redesigning it. Hold the line on the full result distribution even though nothing needs it yet.
  9. Every question the model asks is a hole in your requirement. They arrive before any code exists and cost nothing. Record them verbatim.
  10. Never weaken a test to make it pass. On a repair cycle the temptation is to soften an assertion that "isn't really the point." And since the model wrote the tests, it can do it to itself in the same reply that fixes the code, without mentioning it. Diff test.shoddy on every repair. A test changes only after the gate changes, and both go in the log with a reason.
  11. Do not skip step 21's random walk because the learning code looks obviously right.
  12. Record the uneventful steps too. A step that passed on the first cycle in four minutes is data. A record containing only the interesting failures has been curated, and a careful reader will assume the worst about what was left out.