Running an experiment #
One idea, one branch, one comparison in the pull request.
git checkout -b claude/flank-weighting origin/main
# change the bot
./scripts/build.sh
./sds.sh bench --games 60 --against main
The last command prints a comparison and writes comparison.md into the run
directory. That file is the PR body.
The baseline #
baselines/*.json are committed. They hold an aggregate — games, decided, wins,
BV left, the commit they were measured at — and never the match files, which are
large, derived, and regenerated by any run.
Record one when main moves:
./sds.sh bench --games 60 \
--save-baseline main --notes "after the terrain-cost fix"
Then commit it. A baseline that lives on one machine cannot be compared against in a review.
What the comparison refuses to do #
Compare across different rules. Scenario, victory condition, round limit and opponent travel with the baseline, and a mismatch is an error rather than a warning. A baseline recorded at a 50% BV threshold and a run at 70% produce numbers of the same shape that mean different things, and nothing in a diff would show it.
Read a difference that is not there. Every comparison carries a 95% interval on the difference in win rate — Newcombe's method, which stays sane at rates near 0 and 1 where the normal approximation does not — and states the verdict in a sentence:
The interval spans zero, so this run does not show a difference. An effect this size would need roughly 380 decided games per side to separate from noise.
That sentence is the point of the whole apparatus. 12–8 against 10–10 looks like progress and is nothing, and a table of numbers invites reading the one you hoped for.
How many games #
Rough, for a mirror match:
| difference you want to see | decided games per side |
|---|---|
| 20 points | ~25 |
| 10 points | ~100 |
| 5 points | ~385 |
A match is 1–4 minutes, and --jobs matches run at once. Sixty games is about
half an hour at --jobs 3 and is enough to catch a large change; a subtle one
needs an overnight run and there is no way around that.
Watch the undecided count. Games that hit the round limit are excluded from the rate, so 60 games with 20 undecided is a 40-game experiment.
What the units actually did #
python3 -m sds.cli moves runs/<run>
A win rate says which side won. It cannot say that our machines spent 44% of their unit-rounds in the hex they started in, which was the state of the bot when this was first measured. "Did not change hex" is not one thing, and the report splits it: moved, held position, stood up, moved while prone, stayed prone, went prone, shut down, crew out, immobile. Princess is the column beside it, over the same suite.
The last three are not decisions. A shut-down machine, one whose crew is dead
or unconscious, and one MegaMek calls immobile all took no turn at all, so they
are counted apart and a second table repeats the choices over the decisions
alone. Two more states cannot be seen in a position log and are printed from
the seat's own movement tally: illegal and failed are orders the host
refused, defaulted is the bot failing to answer, and all three stand the unit
still in a hex it did not choose to stay in.
A run recorded before immobile reached the round snapshot cannot be split
fully. The report says so and marks the affected rows as ceilings rather than
printing a number that is quietly too high.
Before you believe any of it #
./sds.sh control --games 40
Princess against itself must come out near 50/50. If it does not, the harness is biased and every number it has printed is suspect. Run it after touching the harness, not after being surprised by a result.
What belongs in the PR #
comparison.md, as the body.- What was changed and why you expected it to help — before the numbers, so the reader can tell a prediction from a rationalisation.
- The
answered/decisionssplit if the bot's own counters moved. A change that improves the win rate by making the bot answer more of its decisions is a bug fix wearing a strategy's clothes, and worth naming as one. - Any run that was thrown away, and why. A benchmark you can re-roll is not a benchmark.