Kalah TDD study · Part 20 of 29 · Designs

Property-based TDD

State rules over input regions. Generators sample and shrink.

Source run full-1 at tdd-experiment c3dd988

Contents

Search and validation

What it changes: The check. One test covers an input region.

  • Bronze Fewest errors E 5

One method of the study, from runs full-1. The survey build is the method's single build in run full-1. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.

How it builds code

The procedure supplied to the agents. Indented lines run under the line above them.

Family: Search and validation. Idea: State rules over input regions. Generators sample and shrink. What it changes in the search: The check. One test covers an input region.

repeat per requirement until none is open:1. Phrase the next requirement as an invariant, round trip or model comparison.2. Write a generator for a narrow slice of valid inputs.3. Run the property and watch it fail with a shrunk counterexample.4. Make the smallest change that makes the property pass.5. Widen the generator toward all valid inputs and repeat until green.6. Save each shrunk counterexample as a fixed example test.7. Refactor with the properties as the safety net.once:8. In CI log the seed and replay saved failures before fresh random cases.

Once means one agent runs the steps. Repeat per unit means one agent per unit until no unit is open. Throughout means the rule joins every prompt. For up to n starts means separate attempts with the best kept. The harness does means the harness runs the step without an agent. If names the event that starts a stage, and with adds a rule borrowed from another method. Steps are quoted from args.json with their numbers. Tag: verified.

Figure 20.1

Stage flow of Property-based TDD

Stage flow of Property-based TDD2 stages in run order across the lanes Agent. Each loop repeats its stage per unit.AgentSteps 1 to 7Once: step 8repeat per requirement
  1. Steps 1 to 7. Lane: Agent.

    Next: Once: step 8.

    Loop: repeat per requirement, back to Steps 1 to 7.

  2. Once: step 8. Lane: Agent.

2 stages from the run facts, each checked against its block of the steps above. Details and notes are in the stage table. Tag: recomputed.

Text version: Stage flow of Property-based TDD
  1. Steps 1 to 7. Lane: Agent.

    Next: Once: step 8.

    Loop: repeat per requirement, back to Steps 1 to 7.

  2. Once: step 8. Lane: Agent.

Stages

Each stage as the run facts list it. Runs as and Note are quoted verbatim and Steps repeats the steps above. Tag: verified.

StageRuns asStepsDetailsNote
Steps 1 to 7repeat per requirement until none is open:1. Phrase the next requirement as an invariant, round trip or model comparison.
2. Write a generator for a narrow slice of valid inputs.
3. Run the property and watch it fail with a shrunk counterexample.
4. Make the smallest change that makes the property pass.
5. Widen the generator toward all valid inputs and repeat until green.
6. Save each shrunk counterexample as a fixed example test.
7. Refactor with the properties as the safety net.
unit requirementNo property library is on the classpath. Write generators over java.util.Random with a logged seed.
Once: step 8once:8. In CI log the seed and replay saved failures before fresh random cases.nonenone

Survey build

One build in run full-1.

15/15Functional mark
5E
1,171,669Cost to completion
823,020M
703,501M_io
5PMD findings
noAlignment

Verdict

One survey build. It passed every hidden test with adjusted mutation 1.000, 4 classes and 5 PMD violations. It gained 52 of its 76 hidden tests in one step and rewrote 1 percent of the src lines it added. Tag: recomputed.

Report summary

The report's class diagram for this design is identical to Figure 20.2 below, so it is shown once. Tag: recomputed.

Functional mark /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Strict PMD compliancefail (5 violations)
Mutation score116/123, 0 not run, adjusted 116/116 (0 of 7 survivors fail hidden tests)
Classes4
Simple Constructs /54
Minimal Duplication /53
Modular Structure /54
Problem Alignment (tentative) /54
Weighted tokens1,234,954
Bench median ms553

Score card

Survey tiles place the survey build among the single survey builds. One build per method describes that build and does not rank the methods. Contrast tiles are medians over the replicates. Tag: recomputed.

Adjusted mutation1.000joint 1st highest of 11 single survey builds, one per method
Classes45th highest of 11 single survey builds, one per method
Cost to completion1,171,6697th lowest of 11 single survey builds, one per method
Strict PMD violations5joint 6th lowest of 11 single survey builds, one per method

Builds

Best, median and worst order a method's replicates by adjusted mutation, highest first, then lower cost to completion. The median build is the representative. Tag: recomputed.

Survey build (full-1)

Buildproperty-based
Adjusted mutation1.000
Classes4
Cost to completion1,171,669
Strict PMD violations5

Figure 20.2

Class diagram of method/property-based

kalahBoardBoardViewKalahMatch

4 classes and 6 links. Tag: recomputed.

Text version: Class diagram of method/property-based
  1. Package kalah: Board, BoardView, Kalah, Match
  2. BoardView uses Board
  3. Kalah uses Board
  4. Kalah uses BoardView
  5. Kalah uses Match
  6. Match uses Board
  7. Match holds Board

Same family comes from the methods doc. Nearest and farthest average the rank differences over adjusted mutation, classes and cost to completion of the survey builds, over the metrics both methods have. Anchor Engineering has no survey build, so the replicate medians of adjusted mutation and classes stand in. Cost to completion leaves out such a method, since costs of a different batch do not compare. Tag: recomputed.

Records

Clone reports

Each clone's own report, as in its REPORT.md.

Property-based TDD: functional mark 15/15, strict PMD fail (5 violations), classes 4, weighted tokens 1,234,954

Property-based TDD

Branch method/property-based. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.

Headline
Functional mark /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancefail (5 violations)

Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.

Rubric setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines1562 (src 234, own tests 1328)recomputed
Lines of code1237 (src 175, own tests 1062)recomputed
Classes4recomputed
Methods22recomputed
Fields5recomputed
Bench median ms553recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.

Quality, costs and audit
MetricValueTag
Mutation score116/123, 0 not run, adjusted 116/116 (0 of 7 survivors fail hidden tests)recomputed
PMD lines5 (5 violations)recomputed
Dependency edges5recomputed
Review scores (superseded)Alterability 5, Code quality principles 4.2, Test quality 4unchecked
Tokens7,851,434 (input 396, cache creation 554,603, cache read 7,260,782, output 35,653)recomputed
Weighted tokens1,234,954recomputed
Wall time0:29:48recomputed
Agent time0:28:44recomputed
Agent calls12recomputed
Audit flags0 high, 8 lowrecomputed

PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.

Review scores

A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 5.

Reason: Direction is handled only in BoardView. Board sowing works the same whatever the direction, so the change sits in one small file plus the flag passed through Kalah.

Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 5.

Reason: The variant appears only as two ternaries in play() that size the Board. Board, BoardView and Match all work from houseCount(), so no other code changes.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

Reason: Classes and methods are short and laid out cleanly. The sow loop uses a pit = -1 reset and side/player index juggling that takes some effort to follow.

Code quality principles. Item: 2. Explanatory Language. Score /5: 4.

Reason: Names such as sow, capture, houseSeeds, score and isOver match the domain, and the Javadoc explains why. Arithmetic like 3 - player and 1 - side encodes the opponent without a named helper.

Code quality principles. Item: 3. Consistent Code. Score /5: 4.

Reason: Players are 1-based through the public API and 0-based inside, and that holds everywhere. Kalah.java mixes tab indentation from the starter with spaces in the new code.

Code quality principles. Item: 4. Used Content. Score /5: 4.

Reason: Nearly everything is used by play(). Board.total() exists only for the tests.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

Reason: Plain arrays, loops and a null-returning message keep it simple. Returning a nullable String from move() as a signal and the sentinel-driven sow loop are slightly awkward.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 4.

Reason: There is little repetition. total() repeats the house summing that houseSeeds() already does, and the sevenfive ternary is evaluated twice.

Code quality principles. Item: 7. Modular Structure. Score /5: 5.

Reason: Board (state and rules), Match (turn), BoardView (rendering) and Kalah (I/O loop) each have one clear job with narrow interfaces.

Code quality principles. Item: 8. Problem Alignment. Score /5: 5.

Reason: The structure follows the game's concepts of houses, stores, sowing, capture and turns. Treating direction as a matter of presentation reflects the symmetry of the board.

Test quality. Item: Test quality. Score /5: 4.

Reason: Property tests with independent reference models pin sowing, capture, rendering, game end and full spec replays across all four variants, with named regression cases. The suite is large and its files depend on each other's helpers (SowPropertyTest.Game). Comments record red-run history, and the prompt model reuses the production BoardView.

Audit flags

0 high and 8 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.

Step log

Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.

Step log, 11 rows and 10 columns

2026-10-15T23:54:47Z. Step: L1-7 iteration 1. Unit: Initial board. Tests added: 4. Cycles: 1. Sha: 95ad92f. Agent: property-based:L1-7#1. Tokens: 634376. Seconds: 100.857.

Actions: Wrote UNITS.md (10 units). Added seeded property runner test/kalah/Property.java and Board. Narrow generator (6,4) went red with shrunk counterexample (houses=1, seeds=1), stub fixed, generator widened to houses 1-10 seeds 0-11 green, counterexample saved as fixed test, refactored Board to pit arrays under the property. Checks: ./run.sh test BoardPropertyTest only, as the step names no other check.

2026-10-15T23:56:28Z. Step: L1-7 iteration 2. Unit: Seed conservation. Tests added: 3. Cycles: 1. Sha: 987a997. Agent: property-based:L1-7#2. Tokens: 592947. Seconds: 103.491.

Actions: Added SowPropertyTest over replayed move sequences (illegal moves skipped) and Board.sow. Narrow generator (6-4, one move) went red against an emptying stub with shrunk counterexample (houses=1, seeds=1, P1:1). Implemented sowing along own houses, own store, opponent houses, skipping opponent store (direction affects rendering only, per the anticlockwise specs). Widened to 1-10 houses, 0-11 seeds, up to 40 moves, green. Saved counterexample plus a 13-seed full-lap case as fixed tests. No refactor needed. Checks: ./run.sh test SowPropertyTest only, as the step names no other check.

2026-10-15T23:58:16Z. Step: L1-7 iteration 3. Unit: Sowing order. Tests added: 4. Cycles: 1. Sha: 80c3010. Agent: property-based:L1-7#3. Tokens: 467867. Seconds: 102.89.

Actions: Added SowingOrderPropertyTest with a reference that walks the on-screen board ring per direction (layout from the example specs) and skips the opponent store. Narrow generator (6-4 and 7-5 single move, both directions) was green on the first run because Board.sow from iteration 2 already follows the cycle, so no production change was needed. To see step 3 fail, an opponent-store fault was injected into Board.sow: the widened property went red with shrunk counterexample clockwise (houses=2, seeds=4, P1:2), then git restore made it green. Narrow test folded into the widened property (1-10 houses, 0-11 seeds, up to 40 moves). Saved the counterexample plus the 7-5 clockwise simple_start move and a 13-seed anti-clockwise lap as fixed tests. Checks: ./run.sh test SowingOrderPropertyTest only, as the step names no other check.

2026-10-15T23:59:24Z. Step: L1-7 iteration 4. Unit: Extra turn. Tests added: 3. Cycles: 1. Sha: b5772b3. Agent: property-based:L1-7#4. Tokens: 422817. Seconds: 64.47.

Actions: Added ExtraTurnPropertyTest with an oracle that counts pits along the 2 x houses + 1 sowing cycle (store is houses - house + 1 pits after the chosen house). Board.sow now returns boolean, first as a false stub. Narrow generator (6-4 start, one move) went red with shrunk counterexample (houses=6, seeds=4, P1:3). Fixed by returning whether sowing ended at the store position. Widened to the shared game generator (1-10 houses, 0-11 seeds, up to 40 moves), green; narrow generator folded into it. Saved the counterexample plus a one-short and a 13-seed lap case as fixed tests. No refactor needed. Checks: ./run.sh test ExtraTurnPropertyTest only, as the step names no other check.

2026-10-16T00:08:50Z. Step: L1-7 iteration 5. Unit: Capture. Tests added: 3. Cycles: 2. Sha: bf2f3e5. Agent: property-based:L1-7#5. Tokens: 794533. Seconds: 565.752.

Actions: Added CapturePropertyTest with a ring reference (own houses, own store, opponent houses) that sows then captures when the last seed ends in an own house holding 1 seed and the facing house (houses + 1 - house) is non-empty. Narrow generator (6-4, up to 10 moves) went red with shrunk counterexample (houses=6, seeds=4, P1:6 P1:2), the README capture example. Added Board.capture after sowing, green. Widened to the shared game generator (1-10 houses, 0-11 seeds, up to 40 moves), green; narrow generator folded into it. Saved the counterexample and an empty-opposite no-capture case as fixed tests. Exact-lap capture checked against examples exactlapemptycapture.txt. Refactor safety-net run of ./run.sh test turned SowingOrderPropertyTest red because its reference had no capture (conflicting test), so its reference now applies the same capture step, all five property classes green; the six example tests stay red until play() exists. Checks: ./run.sh test CapturePropertyTest, then ./run.sh test for the property safety net.

2026-10-16T00:10:59Z. Step: L1-7 iteration 6. Unit: Empty house. Tests added: 3. Cycles: 1. Sha: 5d88a4a. Agent: property-based:L1-7#6. Tokens: 416422. Seconds: 125.738.

Actions: Added Match (board plus whose turn, P1 first) with move(house) returning the message to print, and EmptyHousePropertyTest asserting that choosing an empty house returns "House is empty. Move again." and leaves board snapshot and turn unchanged while legal moves return null. Stub that always sowed went red on the narrow generator (6-4, up to 6 choices) with shrunk counterexample (houses=5, seeds=4, P1:2 P1:2), an extra turn followed by the emptied house. Fixed with an empty-house check before sowing, green. Widened to the shared game generator (1-10 houses, 0-11 seeds, up to 40 moves), green; narrow generator folded into it. Saved the counterexample and the useempty.txt sequence as fixed tests. No refactor needed. Printing via IO waits for play(). Checks: ./run.sh test EmptyHousePropertyTest only, as the step names no other check.

2026-10-16T00:13:02Z. Step: L1-7 iteration 7. Unit: Board rendering. Tests added: 5. Cycles: 2. Sha: 34235c3. Agent: property-based:L1-7#7. Tokens: 871807. Seconds: 125.695.

Actions: Added BoardView.lines(board, clockwise) and RenderPropertyTest, whose parser reads each row's P1 or P2 label so it does not depend on direction, plus a layout check against the screen geometry of SowingOrderPropertyTest.ring. Narrow generator (anti-clockwise 6-4, up to 30 moves) went red against an empty stub with shrunk counterexample anti-clockwise (houses=1, seeds=0, no moves). Implemented the anti-clockwise layout, green. Widened to all four variants (6-4 and 7-5, both directions), red with shrunk counterexample clockwise (houses=1, seeds=0, no moves). Added the mirrored clockwise layout, green. Saved both counterexamples plus exact-text tests for the anti-clockwise 6-4 simple_start board after P1:1 and the clockwise 7-5 start board. Refactor was an Arrays import only. Checks: ./run.sh test RenderPropertyTest only, as the step names no other check.

2026-10-16T00:15:45Z. Step: L1-7 iteration 8. Unit: Prompt and quit. Tests added: 5. Cycles: 3. Sha: 696f171. Agent: property-based:L1-7#8. Tokens: 1131808. Seconds: 157.525.

Actions: Added PromptPropertyTest with a ScriptedIO (records prompts and printed lines, answers readInteger from a script ending in q) and a model built from Match plus BoardView in the example spec layout: start board, then per choice the mover's prompt, any message and the board, then prompt, "Game over" and the board. Choices stop before a move that would end the game, as game end is a later unit. Narrow generator (anti-clockwise 6-4, quit at once) went red against the stub with shrunk counterexample anti-clockwise (houses=6, seeds=4, no moves). Added board, one prompt and Game over, green. Widened to up to 30 choices, red with shrunk counterexample (houses=6, seeds=4, P1:1). Added the readInteger loop over Match.move, green. Widened to all four variants, red with shrunk counterexample anti-clockwise (houses=7, seeds=5, no moves). Sized the board from sevenfive, green. Saved the three counterexamples plus the useempty sequence as fixed tests. No refactor needed. Checks: ./run.sh test PromptPropertyTest, then ./run.sh test as the refactor safety net: all property classes green, quit, exactlapemptycapture, useempty and simple_start specs now pass, full_game2 and full_game_tie stay red at game end (later unit).

2026-10-16T00:17:20Z. Step: L1-7 iteration 9. Unit: Game end and scores. Tests added: 3. Cycles: 1. Sha: 7cff8f0. Agent: property-based:L1-7#9. Tokens: 572565. Seconds: 92.044.

Actions: Added GameEndPropertyTest: a case is a variant plus picks, each pick choosing among the mover's non-empty houses and the lowest one after the picks run out, so every case plays to the end (capped at 5000 moves). The invariant: scores (houses plus store) sum to the starting total, and play() output ends with the last board, then with no further prompt "Game over", the board, a tab-indented score line per player and "Player N wins!" or "A tie!" for the higher score. Narrow generator (anti-clockwise 6-4, up to 60 picks) went red with shrunk counterexample anti-clockwise 6-4 picks=[]. Added Board.houseSeeds and Board.score, Match.isOver and the result lines in play(), green. Widened to all four variants, green on first run. Saved the counterexample plus clockwise 7-5 lowest-house as fixed tests. Refactor merged the quit and game-end exits into one ending. Checks: ./run.sh test GameEndPropertyTest, then ./run.sh test as the refactor safety net: all 11 classes green, full_game2 and full_game_tie specs now pass.

2026-10-16T00:19:43Z. Step: L1-7 iteration 10. Unit: Full games. Tests added: 4. Cycles: 1. Sha: 6c2df1f. Agent: property-based:L1-7#10. Tokens: 1061181. Seconds: 147.799.

Actions: Added FullGamePropertyTest: a case is an example spec, a cut and a mirror flag. The derived spec keeps the spec's lines up to input number cut, turns that input into q and appends "Game over" and the last board (a cut past the last input keeps the whole spec). Mirroring reverses the cells of each house row, which draws the same counts in the other direction, so clockwise 7-5 simple_start also covers the anti-clockwise 7-5 variant that has no spec. play() runs on a ScriptedIO fed the derived inputs and its output must equal the derived output lines. Narrow generator (each spec in full) was green on the first run, then widened to any cut and then to mirroring, green each time, because play() already passed all six specs after iteration 9. Conflict with step 3 (watch it fail): no production change could be made smaller, so the red came from a temporary fault, capture disabled in Board, which went red with shrunk counterexample full_game2.txt cut=12 (quit just after the first capture); Board was restored with git restore, green. Saved that counterexample plus mirrored simple_start (anti-clockwise 7-5) and mirrored full_game_tie as fixed tests. No refactor needed. Checks: ./run.sh specs once at the start to see spec status (6 of 6 pass), then ./run.sh test FullGamePropertyTest for each run.

2026-10-16T00:21:15Z. Step: s8. Unit: CI seed logging and replay of saved failures. Tests added: 4. Cycles: 2. Sha: b38c93c. Agent: property-based:s8. Tokens: 596470. Seconds: 87.549.

Actions: The runner already printed one seed per property. Added test/kalah/property-failures.txt (lines of seed and property name) and Property.savedSeeds, and forAll now runs every saved seed for the property, logged as "replay seed=N", before the fresh run logged as "seed=N" (still overridable with -Dkalah.seed). A failure message names which seed failed, so its seed can be added to the file. Cycle 1: PropertyTest (saved seed read for its own property only, first case drawn from the saved seed, replay plus fresh run, no saved seeds means fresh only) red at compile, then green. Cycle 2: replay failure names "replay seed=42", red with "failed with seed=42", fixed, green. The file holds only the self-test seed 42 because earlier iterations did not record their failing seeds; their shrunk counterexamples are already fixed example tests. Checks: ./run.sh test PropertyTest per cycle, then ./run.sh test once as CI: 13 classes, 0 failed.

Raw inputs
  • metrics.json
  • summary.json
  • grade.tsv
  • costs.json
  • steps.csv
  • audit.json
  • review.json
  • LOG.md
  • MEASURE.md
  • classdiagram.uml
  • classdiagram.png
  • classdiagram.svg
  • collect.tsv
  • rating.json
MEASURE.md

The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.

# Measure

Method: Property-based TDD. Sources: LOG.md, git log (95ad92f to 4eef089) and the test sources. GAP.md and ATTEMPTS.md do not exist in this clone. Credits and time are measured by the harness and are not recorded here.

## Properties per verb

Ten domain properties over five production verbs, plus one runner self-test property. Each property is one `Property.forAll` call. `recomputed` from the test sources.

| verb | properties | names |
|---|---|---|
| `new Board` | 1 | initial board |
| `Board.sow` | 4 | seed conservation, sowing order, extra turn, capture |
| `Match.move` | 1 | empty house |
| `BoardView.lines` | 1 | board rendering |
| `Kalah.play` | 3 | prompt and quit, game end and scores, full games |
| `Property.forAll` (runner) | 1 | runner self-test |

Mean 2.0 domain properties per verb. `Board.sow` carries the most because sowing, extra turn and capture all live in it. `Board.houseSeeds`, `Board.score` and `Match.isOver` have no property of their own. They are covered through the game end property.

## Defects caught that example tests missed

Zero recorded. `recomputed` from LOG.md.

| source of red | count | would the example specs also fail |
|---|---|---|
| red against a stub or missing feature (iterations 1, 2, 4 to 9) | 11 | yes. The specs stayed red until play() existed in iterations 8 and 9 |
| injected fault: sowing into the opponent store (iteration 3) | 1 | likely through the lap specs. `unchecked` |
| injected fault: capture disabled (iteration 10) | 1 | yes. full_game2 contains a capture |
| conflicting test: the sowing order reference lacked capture (iteration 5) | 1 | not a production defect |

Every property red came from a stub, a missing feature or a deliberate fault. None came from code that already passed the six example specs. The properties do reach inputs the specs never show, such as one-house boards, house counts 1 to 10, seed counts 0 to 11 and the anti-clockwise 7-5 variant, which has no spec. Iteration 8 first saw the sevenfive sizing gap on that variant. The clockwise 7-5 simple_start spec exercises the same gap, so it does not count as a miss.

## Failures that do not replay from their seed

Zero observed. `recomputed` from LOG.md and test/kalah/property-failures.txt.

- Iterations 1 to 10 logged shrunk counterexamples and saved each one as a fixed test. They did not record the failing seeds, so seed replay of those failures was never exercised.
- Step s8 added seed logging and replay of saved seeds. The only saved seed is the runner self-test seed 42, and its replay passed in PropertyTest.
- Replay determinism therefore rests on one checked seed.

Terms and columns

See Methodology: measures and the Glossary for the terms and table columns this page uses.