Kalah TDD study · Part 17 of 29 · Designs

Annealed refactoring

Uphill refactors allowed early on a fixed cooling schedule.

Source run full-1 at tdd-experiment c3dd988

Contents

Step size

What it changes: Accepts worse designs early then cools.

  • Silver Fewest errors E 4

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: Step size. Idea: Uphill refactors allowed early on a fixed cooling schedule. What it changes in the search: Accepts worse designs early then cools.

throughout:2. Hot may move nouns and verbs. Warm moves verbs. Cold renames and extracts.3. Timebox and allowed rise: hot 45 min 20%. Warm 15 min 5%. Cold 5 min 0%.repeat per feature until none is open:1. Run classic TDD. Log energy per commit: PMD warnings plus jdeps class edges.4. After each green try at most one allowed move on a throwaway branch.5. Pick the move after which the next test needs the fewest changed lines to pass.6. Merge if energy does not rise. Merge a rise within budget with a one-line reason.7. Revert any move still red or over budget when its timebox ends.8. Cool one phase per third of the original test list. Stay cold after.

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 17.1

Stage flow of Annealed refactoring

Stage flow of Annealed refactoring2 stages in run order across the lanes Throughout, Agent. Each loop repeats its stage per unit.ThroughoutAgentSteps 2 and 3Steps 1, 4 to 8repeat per feature
  1. Steps 2 and 3. Lane: Throughout.
  2. Steps 1, 4 to 8. Lane: Agent.

    Loop: repeat per feature, back to Steps 1, 4 to 8.

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 Annealed refactoring
  1. Steps 2 and 3. Lane: Throughout.
  2. Steps 1, 4 to 8. Lane: Agent.

    Loop: repeat per feature, back to Steps 1, 4 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
Steps 2 and 3throughout:2. Hot may move nouns and verbs. Warm moves verbs. Cold renames and extracts.
3. Timebox and allowed rise: hot 45 min 20%. Warm 15 min 5%. Cold 5 min 0%.
noneThe timeboxes are red-green cycles: hot 9, warm 3 and cold 1.
Steps 1, 4 to 8repeat per feature until none is open:1. Run classic TDD. Log energy per commit: PMD warnings plus jdeps class edges.
4. After each green try at most one allowed move on a throwaway branch.
5. Pick the move after which the next test needs the fewest changed lines to pass.
6. Merge if energy does not rise. Merge a rise within budget with a one-line reason.
7. Revert any move still red or over budget when its timebox ends.
8. Cool one phase per third of the original test list. Stay cold after.
unit featureIf TESTS.md is missing, first write the original test list to it with one line per planned test and commit it. Step 8 counts thirds of that list. Energy comes from ./run.sh lint and ./run.sh deps. A move is tried uncommitted in place of a throwaway branch and reverted with git restore.

Survey build

One build in run full-1.

15/15Functional mark
4E
910,035Cost to completion
625,663M
557,219M_io
2PMD findings
noAlignment

Verdict

One survey build. It passed every hidden test with 1 class, 2 PMD violations and adjusted mutation 0.983. Later commits rewrote 35 percent of the src lines it added, the second highest share of the survey after functions-first. One build cannot show whether that rewriting is typical of the method. Tag: recomputed.

Report summary

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

Functional mark /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Strict PMD compliancefail (2 violations)
Mutation score115/123, 0 not run, adjusted 115/117 (2 of 8 survivors fail hidden tests)
Classes1
Simple Constructs /54
Minimal Duplication /54
Modular Structure /53
Problem Alignment (tentative) /53
Weighted tokens958,093
Bench median ms526

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.9835th highest of 11 single survey builds, one per method
Classes1joint 10th highest of 11 single survey builds, one per method
Cost to completion910,0354th lowest of 11 single survey builds, one per method
Strict PMD violations24th 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)

Buildannealed
Adjusted mutation0.983
Classes1
Cost to completion910,035
Strict PMD violations2

Figure 17.2

Class diagram of method/annealed

kalahKalah

1 class and 0 links. Tag: recomputed.

Text version: Class diagram of method/annealed
  1. Package kalah: Kalah

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.

Annealed refactoring: functional mark 15/15, strict PMD fail (2 violations), classes 1, weighted tokens 958,093

Annealed refactoring

Branch method/annealed. 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 (2 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 lines227 (src 176, own tests 51)recomputed
Lines of code181 (src 143, own tests 38)recomputed
Classes1recomputed
Methods16recomputed
Fields3recomputed
Bench median ms526recomputed
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 score115/123, 0 not run, adjusted 115/117 (2 of 8 survivors fail hidden tests)recomputed
PMD lines1 (2 violations)recomputed
Dependency edges0recomputed
Review scores (superseded)Alterability 4.5, Code quality principles 3.9, Test quality 3unchecked
Tokens7,926,085 (input 384, cache creation 383,456, cache read 7,521,785, output 20,460)recomputed
Weighted tokens958,093recomputed
Wall time0:26:32recomputed
Agent time0:25:47recomputed
Agent calls8recomputed
Audit flags0 high, 13 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: 4.

Reason: Sowing walks one fixed lap and the direction is only a mirror of the printed board, so the change touches just printBoard. That method has two clockwise branches, so the variant still needs edits in two places.

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

Reason: The variant is set by two ternaries for houseCount and seedCount. Every index, loop, border and capture formula comes from houseCount, so nothing else changes.

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

Reason: Formatting and indentation are consistent and the methods are short. The placement is a little loose: the static constant sits after the instance fields, and the helpers are not grouped by role (rules versus printing).

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

Reason: Names such as sow, capture, storeIndex and seedsInHouses are descriptive, and the short comments explain the lap layout. Bare idioms like 3 - player and 2 * houseCount - last have no named constant or helper.

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

Reason: Players and houses are 1-based throughout and are accessed uniformly through index/storeIndex. printBoard mixes that convention with direct pits[houseCount] and pits[pits.length - 1] lookups for the stores.

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

Reason: Every field, constant and helper is used and no dead code remains. The starter's leftover stub output was removed.

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

Reason: Control flow is a simple loop with early returns, and the methods are small. The while(true) loop with continue and the long compound capture condition take a little effort to follow.

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

Reason: Helpers like repeat, houses, pit and store remove most repetition. printBoard still repeats the store-plus-houses row assembly in two mirrored if/else blocks.

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

Reason: All game state, rules and rendering sit in one class around a raw int array, with no board or player abstraction. Functions are single-purpose, but presentation and rules are not split into modules.

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

Reason: Domain terms like sow, capture, store and house appear in the code. Players are bare ints 1 and 2, the board is an index-arithmetic array, and clockwise exists only as a display flag, which hides the domain model.

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

Reason: Eleven clearly named, independent spec-driven tests pin the core rules: empty house, extra turn, skipping the opponent store, capture, no capture, game end and tie. Almost all run only on anticlockwise 6-4, and 7-5 plus clockwise are covered mostly by quit-at-start, so the variants are pinned only thinly.

Audit flags

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

2026-10-15T23:55:34Z. Step: L1,4-8 iteration 1. Unit: Board display and quit at start (anti-clockwise 6-4). Tests added: 1. Cycles: 1. Sha: 754d0b7. Agent: annealed:L1,4-8#1. Tokens: 1300447. Seconds: 153.33.

Actions: Wrote UNITS.md (7 units) and TESTS.md (12 tests, hot 1-4). Red KalahTest.testQuitAtStartAnticlockwise64 then green by rendering the board and quitting. Energy at green 0 (PMD 0 + jdeps edges 0). Phase hot (test 1 of 12). One move tried: noun move to a single circular pits array, chosen because sowing for the next test becomes an index walk. A Board class was not tried because its edge would rise energy from 0, beyond any percentage budget. Move green, energy 0 to 0, merged. Ticked unit. Example tests left red as expected until later units.

2026-10-15T23:57:32Z. Step: L1,4-8 iteration 2. Unit: Sowing a move and switching turns. Tests added: 1. Cycles: 1. Sha: 12f2307. Agent: annealed:L1,4-8#2. Tokens: 807950. Seconds: 117.015.

Actions: Red KalahTest.testOneMovePassesTurnToP2 (spec test/specs/STA_6_4/one_move.txt) then green with a turn loop and an index walk sow. Energy at green 0 (PMD 0 + jdeps edges 0). Phase hot (test 2 of 12). One move tried: extract a houses(pits, player, descending) row verb from printBoard, chosen because the clockwise board in test 3 then only reorders rows. First try raised energy 0 to 3 (LineLength). Wrapped the line within the timebox and energy returned to 0, merged. Ticked unit. Conflict logged: Write and Edit strip trailing spaces that spec prompt lines need, so one sed call added them against the no-sed edit rule.

2026-10-16T00:00:25Z. Step: L1,4-8 iteration 3. Unit: Variant layouts (clockwise board and 7 houses with 5 seeds). Tests added: 2. Cycles: 2. Sha: dd5d790. Agent: annealed:L1,4-8#3. Tokens: 1498292. Seconds: 174.332.

Actions: Cycle 1: red KalahTest.testQuitAtStartClockwise64 (spec test/specs/STC_6_4/quit_at_start.txt) then green with a clockwise branch in printBoard. Energy 0 (PMD 0 + jdeps edges 0). Phase hot (test 3 of 12). Move tried: noun move of HOUSES and SEEDS constants to houseCount and seedCount instance fields, chosen because test 4 then only sets two fields. First try rose 0 to 5 (LineLength), wrapped within the timebox, energy 0, merged. Cycle 2: red KalahTest.testQuitAtStartAnticlockwise75 (spec test/specs/STA_7_5/quit_at_start.txt) then green by setting the counts from sevenfive. Energy 0. Example testSimpleStart75 also turned green. First third done so cooled to warm. Move tried: extract index(player, house) verb shared by play and houses, chosen because test 5 then checks the chosen pit in one line. Energy 0 to 0, merged. Ticked unit. Spec files were made with cp and Edit on board lines only, which kept the prompt trailing space without sed.

2026-10-16T00:09:13Z. Step: L1,4-8 iteration 4. Unit: Choosing an empty house. Tests added: 1. Cycles: 1. Sha: 5d035ce. Agent: annealed:L1,4-8#4. Tokens: 673124. Seconds: 578.848.

Actions: Red KalahTest.testEmptyHouseAsksAgain (spec test/specs/STA_6_4/empty_house.txt) failing at the House is empty line, then green by checking the chosen pit and continuing the turn loop. Example testUseEmpty also turned green. Energy at green 0 (PMD 0 + jdeps edges 0). Phase warm (test 5 of 12). Move tried: verb change so sow returns the last sown pit, chosen because test 6 then needs only a store check on that pit. Energy 0 to 0, merged. Ticked unit. Conflict logged: Edit stripped trailing spaces on new prompt lines, so one sed call restored them in the spec, and one sed call ticked UNITS.md, both against the no-sed edit rule.

2026-10-16T00:13:26Z. Step: L1,4-8 iteration 5. Unit: Extra turn on the own store and wrapping past the opponent store. Tests added: 2. Cycles: 2. Sha: 085e054. Agent: annealed:L1,4-8#5. Tokens: 1388762. Seconds: 191.958.

Actions: Cycle 1: red KalahTest.testLastSeedInOwnStoreMovesAgain (spec test/specs/STA_6_4/extra_turn.txt) then green by keeping the turn when sow ends in the own store. Energy 0 (PMD 0 + jdeps edges 0). Phase warm (test 6 of 12). Move tried: verb change so sow takes player and house, chosen because test 7 then needs one skip check inside sow. Energy 0 to 0, merged. Cycle 2: red KalahTest.testSowingSkipsOpponentStore (spec test/specs/STA_6_4/wrap_past_store.txt, the opening of examples exactlapemptycapture.txt up to the 9-seed wrap, then a quit) then green by skipping the opponent store in sow. Energy 0. Phase warm (test 7 of 12). Move tried: extract a storeIndex(player) verb, chosen because the capture in test 8 then adds seeds to the store in one call. Energy rose 0 to 1 (TooManyMethods) against a 0 budget, reverted with git restore. Ticked unit. Conflict logged: the wrap spec was built with head and printf appends because Edit strips trailing prompt spaces, against the no-shell edit rule.

2026-10-16T00:16:24Z. Step: L1,4-8 iteration 6. Unit: Capture including the exact lap. Tests added: 2. Cycles: 2. Sha: 5fb7173. Agent: annealed:L1,4-8#6. Tokens: 1158027. Seconds: 180.59.

Actions: Cycle 1: red KalahTest.testLastSeedInEmptyOwnHouseCaptures (spec test/specs/STA_6_4/capture.txt, the README capture after P1 6 and P2 4) then green with a capture in play. Example testExactLapEmptyCapture also turned green. Energy rose 0 to 2 at green (PMD NcssCount and CyclomaticComplexity on play, jdeps edges 0), a TDD step and no move. Phase warm (test 8 of 12). Move tried: extract a capture(pits, player, last) verb, chosen because test 9 then adds one condition inside it. Energy 2 to 2 (CyclomaticComplexity gone, TooManyMethods in), merged. Cycle 2: red KalahTest.testNoCaptureWhenOppositeHouseEmpty (spec test/specs/STA_6_4/no_capture_opposite_empty.txt) then green by requiring seeds in the opposite house. Energy 2. Phase cold (test 9 of 12, second third done). Move tried: extract storeIndex(player), chosen because the game end scores in test 10 then read each store in one call. Energy 2 to 2, merged. Ticked unit. Deviation: TESTS.md test 9 says no capture after an exact lap, but an exact lap always seeds the opposite house, so the test checks the rule without a lap and the example covers the lap. Conflict logged: Write strips trailing prompt spaces, so one sed call per spec restored them and one sed call ticked UNITS.md, against the no-sed edit rule.

2026-10-16T00:18:32Z. Step: L1,4-8 iteration 7. Unit: Game end with scores and winner or tie. Tests added: 2. Cycles: 2. Sha: 69dd694. Agent: annealed:L1,4-8#7. Tokens: 963555. Seconds: 121.044.

Actions: Cycle 1: red KalahTest.testGameEndsWhenPlayerToMoveHasNoSeeds (spec test/specs/STA_6_4/game_end_winner.txt, copied from examples full_game2.txt because only a full game empties one side) then green by ending the game when the player to move has no seeds in houses and printing scores and the winner. Example testFullGame2 also turned green. Energy rose 2 to 3 at green (PMD class NcssCount, play NcssCount and TooManyMethods, jdeps edges 0), a TDD step and no move. Phase cold (test 10 of 12). Move tried: extract a printResult(io, pits) verb, chosen because the tie in test 11 then changes only the winner line. Energy 3 to 2, merged. Cycle 2: red KalahTest.testGameEndsInATie (spec test/specs/STC_6_4/game_end_tie.txt, copied from examples full_game_tie.txt) then green with a tie branch in printResult. Example testFullGameTie also turned green, so all 17 tests pass. Energy 2. Phase cold (test 11 of 12). Move tried: extract a printGameOver(io, pits, clockwise) verb shared by quit and the game end, chosen because test 12 then needs no game over change. Energy 2 to 2, merged. Ticked unit, no units left. Test 12 (one clockwise move on 7-5) has no unit and was not written.

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: annealed refactoring

Sources are LOG.md rows 1 to 7 and git log from 46b06f8 to c0d87dd. GAP.md and ATTEMPTS.md do not exist in this clone. Energy is PMD violations under kalah_pmd.xml plus jdeps package edges, as recorded at each green and after each move. No check was rerun for this file.

## Energy per commit

| sha | kind | subject | energy |
|---|---|---|---|
| 540ed36 | green | Show the anti-clockwise 6-4 board and quit at start | 0 |
| c40e6e8 | move | Hold the board as one lap of pits | 0 |
| f7b9ae1 | green | Sow a chosen house and pass the turn | 0 |
| a918b2c | move | Extract the house row | 0 |
| e390b80 | green | Show the clockwise board | 0 |
| 1e72cc4 | move | Hold house and seed counts per game | 0 |
| ba5291b | green | Size the board from sevenfive | 0 |
| 3c81f86 | move | Extract the pit index | 0 |
| 9d12dcb | green | Ask again when the chosen house is empty | 0 |
| 840b308 | move | Return the last sown pit | 0 |
| b0b5cd3 | green | Give another move in the own store | 0 |
| e80f960 | move | Let sow take the player | 0 |
| bb69aa7 | green | Skip the opponent store when sowing wraps | 0 |
| f097997 | green | Capture the opposite seeds | 2 |
| fab8881 | move | Extract the capture | 2 |
| 69668af | green | Skip the capture when the opposite house is empty | 2 |
| 97025e1 | move | Extract the store index | 2 |
| 0db4474 | green | End the game and name the winner | 3 |
| 248d7f9 | move | Extract the result printing | 2 |
| fc063c1 | green | Announce a tie | 2 |
| 406ff81 | move | Extract the game over printing | 2 |

Starter kit, plan, tick and log commits change no code under src and carry the energy of the commit before them. Both rises (0 to 2 and 2 to 3) came from green steps. No committed move raised energy.

## Uphill moves

| measure | count |
|---|---|
| moves tried | 11 |
| moves merged | 10 |
| uphill on first try | 3 |
| uphill kept | 0 |
| uphill repaired to flat within the timebox and merged | 2 (a918b2c 0 to 3, 1e72cc4 0 to 5, both LineLength) |
| uphill reverted | 1 (storeIndex in iteration 5, 0 to 1 TooManyMethods, git restore) |
| downhill moves | 1 (248d7f9, 3 to 2) |

The schedule cooled from hot to warm after test 4 and to cold after test 9. The only uphill try in a warm phase was reverted against a 0 budget. The same storeIndex move merged flat in iteration 6 once energy was already 2.

## Final energy against the classic baseline

Final energy at 406ff81 is 2 (recorded in LOG.md iteration 7). The classic baseline is not present in this clone, so the comparison is made by the harness across arms. 11 of 12 planned tests were written. Test 12 has no unit. All 17 tests passed at the last green.

## Credits and time

Credits and wall time are measured by the harness. LOG.md timestamps span 2026-10-15T23:55:34Z to 2026-10-16T00:18:32Z over 11 red-green cycles.

Terms and columns

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