Kalah TDD study · Part 13 of 29 · Designs

Mapped-path TDD

Walk the map leaves first toward one acceptance test at a time.

Source run full-1 at tdd-experiment c3dd988

Contents

Structure first

What it changes: The map. Tests follow a planned route on it.

  • Silver Best overall rank sum 13.0
  • Gold Fewest errors E 2
  • Gold Best CQP/PMD alignment mean rating median 4.0

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: Structure first. Idea: Walk the map leaves first toward one acceptance test at a time. What it changes in the search: The map. Tests follow a planned route on it.

once:1. Build and check the map using steps 2 to 5 of noun-verb.with Noun-verb map step 2. Extract every noun and verb as Noun: -verb lists. Trim to the domain.with Noun-verb map step 3. Mark parents as Noun(Parent). Split nouns over 4 verbs. Apply Clean Code names.with Noun-verb map step 4. Replace duplicate verbs with references written as verb1(Noun2.verb3).with Noun-verb map step 5. Script-check for cycles, dangling references and nouns over 4 verbs. Fix each.once:2. Read each verb1(Noun2.verb3) reference as a call edge between verbs.repeat per feature until none is open:3. Write one failing end-to-end acceptance test for the next spec feature.4. List the verbs that test reaches. Order them leaves first.5. Per verb write the smallest failing unit test then the smallest passing change.6. Run all tests and lint after each verb. Refactor only while green.7. Add any verb the route lacks to the map first. Then re-route.8. Repeat from step 3 until every feature passes. Then diff the map.

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 13.1

Stage flow of Mapped-path TDD

Stage flow of Mapped-path TDD3 stages in run order across the lanes Agent. Each loop repeats its stage per unit.AgentOnce: step 1, withnoun-verb steps 2 to 5Once: step 2Steps 3 to 8repeat per feature
  1. Once: step 1, with noun-verb steps 2 to 5. Lane: Agent.

    Next: Once: step 2.

  2. Once: step 2. Lane: Agent.

    Next: Steps 3 to 8.

  3. Steps 3 to 8. Lane: Agent.

    Loop: repeat per feature, back to Steps 3 to 8.

3 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 Mapped-path TDD
  1. Once: step 1, with noun-verb steps 2 to 5. Lane: Agent.

    Next: Once: step 2.

  2. Once: step 2. Lane: Agent.

    Next: Steps 3 to 8.

  3. Steps 3 to 8. Lane: Agent.

    Loop: repeat per feature, back to Steps 3 to 8.

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
Once: step 1, with noun-verb steps 2 to 5once:1. Build and check the map using steps 2 to 5 of noun-verb.
with Noun-verb map step 2. Extract every noun and verb as Noun: -verb lists. Trim to the domain.
with Noun-verb map step 3. Mark parents as Noun(Parent). Split nouns over 4 verbs. Apply Clean Code names.
with Noun-verb map step 4. Replace duplicate verbs with references written as verb1(Noun2.verb3).
with Noun-verb map step 5. Script-check for cycles, dangling references and nouns over 4 verbs. Fix each.
with noun-verb steps 2 to 5none
Once: step 2once:2. Read each verb1(Noun2.verb3) reference as a call edge between verbs.nonenone
Steps 3 to 8repeat per feature until none is open:3. Write one failing end-to-end acceptance test for the next spec feature.
4. List the verbs that test reaches. Order them leaves first.
5. Per verb write the smallest failing unit test then the smallest passing change.
6. Run all tests and lint after each verb. Refactor only while green.
7. Add any verb the route lacks to the map first. Then re-route.
8. Repeat from step 3 until every feature passes. Then diff the map.
unit featurenone

Survey build

One build in run full-1.

15/15Functional mark
2E
2,058,511Cost to completion
1,475,658M
1,386,111M_io
0PMD findings
yesAlignment

Verdict

One survey build. It passed strict PMD compliance with 16 classes, the most of the survey, and adjusted mutation 0.982. It used the most weighted tokens of the survey, 2126219, and ran the PMD linter 68 times during development. Tag: recomputed.

Report summary

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

Functional mark /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Strict PMD compliancepass
Mutation score107/113, 0 not run, adjusted 107/109 (2 of 6 survivors fail hidden tests)
Classes16
Simple Constructs /54
Minimal Duplication /54
Modular Structure /54
Problem Alignment (tentative) /54
Weighted tokens2,126,219
Bench median ms520

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 mutation0.9826th highest of 11 single survey builds, one per method
Classes161st highest of 11 single survey builds, one per method
Cost to completion2,058,51111th lowest of 11 single survey builds, one per method
Strict PMD violations0joint 1st 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)

Buildmapped-path
Adjusted mutation0.982
Classes16
Cost to completion2,058,511
Strict PMD violations0

Figure 13.2

Class diagram of method/mapped-path

kalahBoardBoardViewCaptureCommandGameHouseKalahMessageMovePitPlayerRingScoreStoreTurnVariant

16 classes and 67 links. Tag: recomputed.

Text version: Class diagram of method/mapped-path
  1. Package kalah: Board, BoardView, Capture, Command, Game, House, Kalah, Message, Move, Pit, Player, Ring, Score, Store, Turn, Variant
  2. Board uses House
  3. Board holds House
  4. Board uses Player
  5. Board uses Store
  6. Board holds Store
  7. BoardView uses Board
  8. BoardView holds Board
  9. BoardView uses House
  10. BoardView uses Player
  11. BoardView uses Store
  12. BoardView uses Variant
  13. BoardView holds Variant
  14. Capture uses Board
  15. Capture holds Board
  16. Capture uses Pit
  17. Capture uses Player
  18. Capture uses Ring
  19. Capture holds Ring
  20. Game uses BoardView
  21. Game holds BoardView
  22. Game uses Message
  23. Game uses Player
  24. Game uses Score
  25. Game holds Score
  26. Game uses Turn
  27. Game holds Turn
  28. House uses Pit
  29. House extends Pit
  30. Kalah uses Board
  31. Kalah uses BoardView
  32. Kalah uses Game
  33. Kalah uses Move
  34. Kalah uses Ring
  35. Kalah uses Score
  36. Kalah uses Turn
  37. Kalah uses Variant
  38. Message uses Player
  39. Message uses Score
  40. Move uses Board
  41. Move holds Board
  42. Move uses Capture
  43. Move holds Capture
  44. Move uses House
  45. Move uses Pit
  46. Move uses Player
  47. Move uses Ring
  48. Move holds Ring
  49. Ring uses Board
  50. Ring uses Pit
  51. Ring uses Player
  52. Ring uses Store
  53. Score uses Board
  54. Score holds Board
  55. Score uses House
  56. Score uses Player
  57. Score uses Store
  58. Store uses Pit
  59. Store extends Pit
  60. Store uses Player
  61. Store holds Player
  62. Turn uses BoardView
  63. Turn holds BoardView
  64. Turn uses Command
  65. Turn uses Message
  66. Turn uses Move
  67. Turn holds Move
  68. Turn uses Player

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.

Mapped-path TDD: functional mark 15/15, strict PMD pass, classes 16, weighted tokens 2,126,219

Mapped-path TDD

Branch method/mapped-path. 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 compliancepass

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 lines1244 (src 541, own tests 703)recomputed
Lines of code975 (src 409, own tests 566)recomputed
Classes16recomputed
Methods67recomputed
Fields30recomputed
Bench median ms520recomputed
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 score107/113, 0 not run, adjusted 107/109 (2 of 6 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges52recomputed
Review scores (superseded)Alterability 5, Code quality principles 4.4, Test quality 4unchecked
Tokens19,389,975 (input 728, cache creation 865,780, cache read 18,499,812, output 23,655)recomputed
Weighted tokens2,126,219recomputed
Wall time0:54:34recomputed
Agent time0:53:37recomputed
Agent calls14recomputed
Audit flags0 high, 6 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: Sowing order is fixed in Ring and direction affects only drawing, so the clockwise flag reaches one predicate, BoardView.isLabelFirst, and Variant. Any combination with the 7-5 layout works without further changes.

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

Reason: House and seed counts come from Variant.fromFlags and flow to Board, Ring, Score and BoardView as parameters. No rule code hard-codes 6 or 4.

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

Reason: Small files with consistent indentation and short methods. Each class opens with a one-line purpose comment and related methods sit together.

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

Reason: Names such as Ring, Capture, nextPit and isLabelFirst are descriptive and comments give rationale. Comments about keeping coupling at 5 record process more than intent, and null as a quit and tie signal stays implicit.

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

Reason: Classes follow the same final, package-private, constructor-injection style. Board uses a static factory while siblings use constructors, and Ring and Score take houseCount separately where BoardView takes the Variant.

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

Reason: Nearly everything is used. The House subclass adds nothing over Pit and Board.create only wraps a private constructor.

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

Reason: Control flow is flat with small loops. Ring relies on index arithmetic for opposite houses and own-house checks, and null return values signal quit and tie.

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

Reason: Little repetition. Score.total and isSideEmpty repeat the same house loop, and Capture adds seeds one at a time in a loop instead of a bulk add.

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

Reason: Responsibilities split cleanly across Board, Ring, Move, Capture, Turn, Game, Score, BoardView and Message with narrow dependencies. Kalah.play only wires the parts.

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

Reason: Classes map directly to domain terms such as house, store, pit, move, capture, turn and score. The variant concept is explicit in its own type.

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

Reason: Unit tests per class have clear behaviour names and independent setUp fixtures, and acceptance tests replay spec files for sowing, extra move, capture, tie and empty house. The 7-5 variant is pinned only by a quit-board test and Ring and Move tests cover the 6-4 layout only.

Audit flags

0 high and 6 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, 13 rows and 10 columns

2026-10-15T23:54:36Z. Step: s1. Unit: noun-verb map. Tests added: 0. Cycles: 0. Sha: 23297a0. Agent: mapped-path:s1. Tokens: 387027. Seconds: 90.877.

Actions: Built MAP.md from README.md and docs/SPEC.md with 15 nouns. Split Board, Game and Player, which had over 4 verbs. Added parents House(Pit) and Store(Pit) and verb1(Noun2.verb3) references. Added tools/check_map.py and confirmed it flags an over-4 noun, a dangling parent, a dangling reference and a call cycle on a scratch map. The final map has 0 problems. No ./run.sh call because the step names none.

2026-10-15T23:55:32Z. Step: s2. Unit: map call edges. Tests added: 0. Cycles: 0. Sha: 0b81ee0. Agent: mapped-path:s2. Tokens: 318251. Seconds: 50.58.

Actions: Read each verb1(Noun2.verb3) reference as an edge Noun.verb1 -> Noun2.verb3, with inherited verbs resolved to Pit. Extracted call_edges in tools/check_map.py and added --edges to print them. Added a Call edges section to MAP.md with 64 edges from 24 callers and 19 leaf verbs. Map check still reports 0 problems. No ./run.sh call because the step names none.

2026-10-16T00:11:43Z. Step: L3-8 iteration 1. Unit: Quit before any move shows the anti-clockwise 6-4 board, the P1 prompt and Game over. Tests added: 25. Cycles: 15. Sha: e3a3c87. Agent: mapped-path:L3-8#1. Tokens: 4565339. Seconds: 978.58.

Actions: Wrote UNITS.md with 11 features. Acceptance test AcceptanceTest.testQuitBeforeAnyMoveAnticlockwise plays examples quit.txt and failed first. Route leaves first: Variant.fromFlags, Player.label, Pit.seedCount/isEmpty, Board.create/house/store, Board.isSideEmpty, BoardView.drawBorder, drawStore, drawSide, draw, Message.prompt/gameOver, Command.read/isQuit, Turn.take, Game.isOver, Game.quit, Game.play. Each verb got a failing unit test then the smallest change, then ./run.sh test and ./run.sh lint. Trivial getters shared a cycle with their verb to fit the 15-cycle limit. Re-routed in MAP.md first: Board.create takes counts and a new Kalah.play entry builds Variant, Board and Game, both to pass PMD CouplingBetweenObjects 5. Wiring Kalah.play closed the acceptance test without its own unit cycle. Added test helper ScriptedIO because the Write tool strips the trailing space spec prompts need. Acceptance and ExampleAnticlockwiseTest.testQuit pass. Lint clean. Five example tests stay red for later features. Conflict with standing guidance: one sed edit to BoardTest.java where Edit was required.

2026-10-16T00:14:02Z. Step: L3-8 iteration 2. Unit: Clockwise board layout with stores and labels swapped. Tests added: 4. Cycles: 3. Sha: 260c048. Agent: mapped-path:L3-8#2. Tokens: 1394665. Seconds: 137.902.

Actions: Acceptance test AcceptanceTest.testQuitBeforeAnyMoveClockwise plays Kalah.play(clockwise 6-4) with ScriptedIO and asserts the board lines from useempty.txt, because no clockwise quit spec exists. It failed first on the anti-clockwise layout. Route leaves first: Variant.isClockwise (existing, tested), BoardView.drawSide, BoardView.draw. Added edges BoardView.draw -> Variant.isClockwise and Player.label to MAP.md first. Map check 0 problems, 67 edges. Cycles: clockwise P2 houses count up, clockwise P1 houses count down (refactored into private isLabelFirst), draw swaps stores and labels through private drawRow. ./run.sh test and ./run.sh lint after each verb. Acceptance passes and lint is clean. Three clockwise example tests now fail at line 11 after the first move instead of line 2. Five example tests stay red for later features. Map diff: only the two new BoardView.draw edges.

2026-10-16T00:15:01Z. Step: L3-8 iteration 3. Unit: 7 houses with 5 seeds board. Tests added: 1. Cycles: 0. Sha: f3257c0. Agent: mapped-path:L3-8#3. Tokens: 376246. Seconds: 48.17.

Actions: Acceptance test AcceptanceTest.testQuitBeforeAnyMoveSevenFive plays Kalah.play(anti-clockwise 7-5) with ScriptedIO and asserts the board lines from the 7-5 simple_start.txt layout. It passed on first run, so step 3 saw no red. The quit assertion moved into a shared private assertQuitShows. Route leaves first: Variant.fromFlags (7-5 branch already unit tested), Board.create(7, 5) (already unit tested), BoardView.drawBorder, drawSide, draw, which all take the house count from Variant. No verb needed a change and the route lacked no verb, so 0 red-green cycles and no unit tests. ./run.sh test: acceptance passes, the same five example tests stay red for later features. ./run.sh lint clean. Map diff: none.

2026-10-16T00:20:11Z. Step: L3-8 iteration 4. Unit: Sow a move on the player's own side and pass the turn. Tests added: 11. Cycles: 10. Sha: f75c088. Agent: mapped-path:L3-8#4. Tokens: 3035994. Seconds: 317.437.

Actions: Acceptance test AcceptanceTest.testSowOnOwnSidePassesTurn plays Kalah.play(anti-clockwise 6-4) with inputs 1 then q and asserts P1 houses 2 to 5 at 5 seeds and the P2 prompt. It failed first. Route leaves first: Pit.takeAll, Pit.addSeed, Command.houseNumber, Player.next, Ring.nextPit, Move.sow, Move.nextPlayer, Move.apply, Turn.take, Game.play. Re-routed MAP.md first: Ring.nextPit calls Variant.houseCount in place of Variant.isClockwise because the specs sow house n to n+1 in both directions and only the drawing mirrors. Map check 0 problems, still 67 edges. Each verb got a failing unit test then the smallest change, then ./run.sh test and ./run.sh lint. Ring holds only houses so far and Move.nextPlayer always passes the turn; stores and extra moves wait for later features. Turn.take returns the next player or null on quit. Game takes its Turn and Kalah.play builds Ring, Move and Turn inline because declared locals pushed Kalah to PMD coupling 6. Acceptance passes, lint clean. ExampleClockwiseTest.testSimpleStart75 now passes. Four example tests stay red for later features. Map diff: only the Ring.nextPit edge.

2026-10-16T00:24:14Z. Step: L3-8 iteration 5. Unit: Sow into the player's own store, skip the opponent's store and wrap around. Tests added: 5. Cycles: 3. Sha: e5d0366. Agent: mapped-path:L3-8#5. Tokens: 2044031. Seconds: 240.993.

Actions: Acceptance test AcceptanceTest.testSowIntoOwnStoreSkipOpponentStoreAndWrap replays the first nine moves of exactlapemptycapture.txt, then q. The prefix has no capture and no extra move. Moves 3 to 8 sow into the own store and P1's ninth move skips P2's store and wraps to P1 house 1. It failed first. Route leaves first: Store.owner, Ring.nextPit (own store, then skip and wrap), Move.sow. Each verb got a failing unit test then the smallest change, then ./run.sh test and ./run.sh lint. Move.sow only passes the sowing player to Ring.nextPit, so it got no own unit cycle. A first instanceof check broke PMD InstanceOfUse and CastUse and a Store list then pushed Ring to coupling 6. Re-routed MAP.md first: Ring takes a house count from Kalah.play and drops its Variant.houseCount edges. Map check 0 problems, 65 edges. Acceptance passes and lint is clean. Four example tests stay red for later features. Map diff: the two Variant.houseCount edges left Ring.

2026-10-16T00:25:51Z. Step: L3-8 iteration 6. Unit: Last seed in the player's own store gives another move. Tests added: 3. Cycles: 1. Sha: 74399f4. Agent: mapped-path:L3-8#6. Tokens: 865298. Seconds: 91.31.

Actions: Acceptance test AcceptanceTest.testLastSeedInOwnStoreGivesAnotherMove replays full_game2.txt to line 31, then q. P2's moves 3 and 2 end in P2's store, so P2 is asked twice in a row. It failed first. The prefix replay moved into a shared private assertSpecPrefixThenQuit. Route leaves first: Board.store, Player.next (both existing and tested), Move.nextPlayer, Move.apply. Re-routed MAP.md first: Move.nextPlayer calls Board.store in place of Store.owner and compares the last pit with the mover's store, because a type check on Pit breaks PMD InstanceOfUse. Map check 0 problems, still 65 edges. One red-green cycle covered Move.nextPlayer and Move.apply together, because the new nextPlayer(player, last) signature forces apply to pass the sown pit in the same change. MoveTest got two new tests and its old apply test moved to house 1, since house 3 now ends in the store. ./run.sh test and ./run.sh lint after the verb. Acceptance passes and lint is clean. Four example tests stay red for later features, now at lines 111, 90, 22 and 26. Map diff: Move.nextPlayer -> Store.owner became Move.nextPlayer -> Board.store.

2026-10-16T00:27:43Z. Step: L3-8 iteration 7. Unit: Choosing an empty house reports it and asks again. Tests added: 4. Cycles: 3. Sha: 4ffd6df. Agent: mapped-path:L3-8#7. Tokens: 968742. Seconds: 106.242.

Actions: Acceptance test AcceptanceTest.testEmptyHouseIsReportedAndAskedAgain plays useempty.txt (clockwise 6-4) through ExampleSpec.play. It failed first at the line after P1 picks the empty house 1. Route leaves first: Pit.isEmpty, Board.house (both existing and tested), Message.emptyHouse, Move.isLegal, Turn.take. The map already held every verb and edge, so no re-route. Each verb got a failing unit test then the smallest change, then ./run.sh test and ./run.sh lint. Turn.take prints the message and returns the same player, so Game.play redraws the board and asks again. Acceptance passes and lint is clean. ExampleClockwiseTest.testUseEmpty now passes and testFullGameTie moved from line 26 to 203. Three example tests stay red for later features. Map diff: none.

2026-10-16T00:31:10Z. Step: L3-8 iteration 8. Unit: Capture when the last seed lands in an empty own house opposite a non-empty house. Tests added: 10. Cycles: 6. Sha: a1fd096. Agent: mapped-path:L3-8#8. Tokens: 2412141. Seconds: 212.014.

Actions: Acceptance test AcceptanceTest.testCaptureFromTheOppositeHouse replays full_game2.txt to line 94, then q. P2's move 2 ends in the empty house 4 facing P1's house 3 with 7 seeds, so P2's store goes from 6 to 14. It failed first. Route leaves first: Ring.oppositeHouse, Ring.isOwnHouse, Capture.applies, Capture.collect, Capture.tryCapture, Move.apply. Re-routed MAP.md first: House.owner left the map and Ring gained -isOwnHouse, because reading an owner from the last Pit would need a cast that breaks PMD. Ring.oppositeHouse and Ring.isOwnHouse read the pit order and became leaves. Capture.collect gained the Ring.oppositeHouse edge. Map check 0 problems, still 65 edges, now 23 calling verbs and 21 leaves. Each verb got a failing unit test then the smallest change, then ./run.sh test and ./run.sh lint. Move builds its own Capture so Kalah.play stays within PMD coupling. Acceptance passes and lint is clean. ExampleAnticlockwiseTest.testExactLapEmptyCapture now passes. testFullGame2 and testFullGameTie stay red at the game end. Map diff: House.owner removed, Ring.isOwnHouse added, Ring.oppositeHouse -> Board.house removed, Capture.applies -> House.owner became Capture.applies -> Ring.isOwnHouse, Capture.collect -> Ring.oppositeHouse added.

2026-10-16T00:32:28Z. Step: L3-8 iteration 9. Unit: No capture when the opposite house is empty, including an exact lap. Tests added: 1. Cycles: 0. Sha: efe6caa. Agent: mapped-path:L3-8#9. Tokens: 453639. Seconds: 67.521.

Actions: Acceptance test AcceptanceTest.testNoCaptureWhenTheOppositeHouseIsEmpty replays full_game2.txt to line 166, then q. P1 move 1 ends in the empty house 2 facing the empty P2 house 5, so no seeds move to a store and P2 moves next. Conflict with step 3: the test passed on first run and was never red, because iteration 8 built the empty-opposite check into Capture.applies with CaptureTest.testDoesNotApplyWhenTheOppositeHouseIsEmpty. The exact lap half is already pinned by ExampleAnticlockwiseTest.testExactLapEmptyCapture, which passes. Route leaves first: Pit.isEmpty, Ring.oppositeHouse, Ring.isOwnHouse, Capture.applies, Capture.tryCapture, Move.apply. Every verb already had a unit test covering this path, so no unit red-green cycle ran and no production code changed. ./run.sh test and ./run.sh lint: acceptance passes, lint clean, ExampleAnticlockwiseTest.testFullGame2 and ExampleClockwiseTest.testFullGameTie stay red at the game end. Map diff: none.

2026-10-16T00:36:19Z. Step: L3-8 iteration 10. Unit: Game ends when the player to move has no seeds, with scores and the winner. Tests added: 6. Cycles: 5. Sha: 7d881d6. Agent: mapped-path:L3-8#10. Tokens: 1715620. Seconds: 228.851.

Actions: Acceptance test AcceptanceTest.testGameEndsWithScoresAndWinner plays full_game2.txt through ExampleSpec.play. It failed first at line 217, where P1 has no seeds after P2's last move. Route leaves first: Pit.seedCount, Board.house, Board.store (existing), Score.total, Score.winner, Message.result, Game.finish, Game.play. Re-routed MAP.md first twice. Game.finish gained a BoardView.draw edge because no turn draws the board after the last move. Then Game hit PMD CouplingBetweenObjects 6 with a Score field, so Board.isSideEmpty moved to Score.isSideEmpty and Game holds a Score in place of a Board. Its two BoardTest tests moved to ScoreTest. Map check 0 problems, 66 edges, 23 calling verbs and 21 leaves. Each verb got a failing unit test then the smallest change, then ./run.sh test and ./run.sh lint. Score.winner returns null on a tie and Message.result only prints a winner line, so the tie stays for the next feature. Acceptance passes and lint is clean. ExampleAnticlockwiseTest.testFullGame2 now passes. ExampleClockwiseTest.testFullGameTie stays red with a null winner at the result line. Map diff: Board.isSideEmpty removed, Score.isSideEmpty added, Game.isOver -> Board.isSideEmpty became Game.isOver -> Score.isSideEmpty, Game.finish -> BoardView.draw added.

2026-10-16T00:37:37Z. Step: L3-8 iteration 11. Unit: A tie result when the scores are equal. Tests added: 2. Cycles: 1. Sha: 608f2bf. Agent: mapped-path:L3-8#11. Tokens: 614240. Seconds: 597.695.

Actions: Acceptance test AcceptanceTest.testTieWhenTheScoresAreEqual plays full_game_tie.txt (clockwise 6-4) through ExampleSpec.play. It failed first with a NullPointerException in Message.result on the null winner. Route leaves first: Pit.seedCount, Board.house, Board.store, Score.total, Score.winner (existing, ScoreTest.testNoWinnerOnATie already pins null on a tie), Message.result. The map already held every verb and edge, so no re-route. One red-green cycle: MessageTest.testResultReportsATie failed, then Message.result prints "A tie!" when the winner is null. ./run.sh test and ./run.sh lint after the verb. Lint flagged line length on the new ternary, so it was split while green. All 16 test classes pass, including ExampleClockwiseTest.testFullGameTie, and lint is clean. Map diff: none.

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: Mapped-path TDD. Sources are LOG.md, `git log` and MAP.md. GAP.md and ATTEMPTS.md do not exist in this clone.

## Answer

All 11 features went from a red acceptance test to green within one call each, at 47 red-green cycles over 51 verb changes. Credits per verb and time are measured by the harness and are not in the repository. The comparison with the A02 test-fix share of 71 percent therefore waits on harness credits. The log proxy below suggests test-fix work was a small share here.

## Credits per verb against the A02 test-fix share of 71 percent

- Credits and time per call are measured in the harness. Credits per verb is harness credits for each L3-8 call divided by the verbs changed in that call (table below).
- Log proxy for the test-fix share (`unchecked` against credits). 3 of 11 feature calls reworked an existing test. Iteration 1 edited BoardTest once. Iteration 6 moved one MoveTest apply test. Iteration 10 moved two BoardTest tests to ScoreTest. Iteration 11 split one line for lint while green. No call logged a red test that needed a fix after the green step.

## Prompts per verb from red to green

Each L3-8 call worked one feature from a failing acceptance test to green. Prompt counts inside a call are measured in the harness. Per call the log gives this (`recomputed` from LOG.md).

| iteration | feature | verbs changed | cycles | tests added | prompts per verb |
|---|---|---|---|---|---|
| 1 | quit anti-clockwise 6-4 | 20 plus Kalah.play wiring | 15 | 25 | 0.05 |
| 2 | clockwise layout | 2 | 3 | 4 | 0.50 |
| 3 | 7-5 board | 0 | 0 | 1 | n/a, green on first run |
| 4 | sow and pass | 10 | 10 | 11 | 0.10 |
| 5 | own store, skip, wrap | 2 | 3 | 5 | 0.50 |
| 6 | extra move | 2 | 1 | 3 | 0.50 |
| 7 | empty house | 3 | 3 | 4 | 0.33 |
| 8 | capture | 6 | 6 | 10 | 0.17 |
| 9 | no capture on empty opposite | 0 | 0 | 1 | n/a, green on first run |
| 10 | game end and winner | 5 | 5 | 6 | 0.20 |
| 11 | tie | 1 | 1 | 2 | 1.00 |
| total | 11 features | 51 | 47 | 72 | 11 calls / 51 verbs = 0.22 |

The final map holds 44 verbs. Two features (3 and 9) were never red because earlier verbs already covered them.

## Route replans and map edits

`recomputed` from LOG.md and `git log -- MAP.md`.

| measure | count | where |
|---|---|---|
| route replans | 7 | iterations 1, 4, 5, 6, 8 and twice in 10 |
| map edits without a replan | 1 | iteration 2 added two BoardView.draw edges |
| feature calls that touched MAP.md | 7 of 11 | iterations 1, 2, 4, 5, 6, 8 and 10 |
| commits touching MAP.md after the s1 and s2 map build | 8 | |
| final map | 44 verbs, 66 edges, 0 problems | `python3 tools/check_map.py MAP.md` |

Cause of replans. Five of the seven came from PMD rules met during coding. These were CouplingBetweenObjects in iterations 1, 5 and 10 and InstanceOfUse or CastUse in iterations 6 and 8. One came from the spec sowing order in iteration 4. One came from the board needing a final draw in iteration 10.

Terms and columns

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