cc65-Chess versus Sargon II
This directory contains the repeatable match harness and the operational notes
for playing the native cc65-Chess engine against Apple II Sargon II under
a2m-v2. The emulator stays visible and runs at maximum turbo so a person can
follow the games.
Nothing in this workflow modifies the source Sargon disk. Match artifacts go
under scratch/, and the harness is resumable after an interruption or bridge
failure.
Files
match.pydrives Sargon through the a2m-v2 control port, drives the exact cc65-Chess engine throughtests/uci, referees games with python-chess, and writes CSV, PGN, JSON summaries, and failure logs.levelcost.pyanswers "how slow is each Sargon level" in six minutes, which is the question that decides whether a match at some level is an afternoon or a fortnight. It shares the boot choreography, the control port and the one-harness-at-a-time rule withmatch.py.analyse.pyruns Stockfish over a finished PGN and attributes every large evaluation swing to the side that played it.piececost.pyanswers "what does moving each kind of piece cost, per move, by phase" - the third statistic indoc/strength.md§5.2a, and the one that found the first two were measuring something else.- This file is the runbook. Update it whenever a new Sargon display or emulator behavior is discovered.
Required local layout
The harness derives these paths from the cc65-Chess repository root:
cc65-Chess/
sargon/match.py
sargon/Sargon-trimmed.dsk
tests/uci
../a2m-v2/
build/a2m-v2
tools/a2m_control_client.py
The disk image is not in this repository
Sargon II is a commercial program from 1978 and its disk image is nobody's here
to redistribute, so .gitignore excludes sargon/*.dsk while tracking the rest
of this directory. Supply your own copy at:
sargon/Sargon-trimmed.dsk
It must be exactly 143,360 bytes - a standard DOS 3.3 image - with SHA-256:
f0ad0bd2c4b162f5a410f4b2ddb4e6a0e70e4a0bbadd247c604f80e0cd47e1b8
shasum -a 256 sargon/Sargon-trimmed.dsk
The copy these results were produced with came from:
https://www.myabandonware.com/game/sargon-ii-54m#Apple%20II
Recorded as provenance, not as a recommendation or a guarantee - check the hash above rather than trusting any particular download, since what is served under one name changes over time.
Two things that image is not. The commonly circulated Sargon.dsk is 153,394
bytes: the same 143,360-byte image followed by a 10,034-byte trailer, and a2m-v2
will not boot the oversized raw file - trimming it to the first 143,360 bytes
produces the hash above. And do not substitute the protected
Sargon II.nib; that image was observed announcing a false mate from the
initial position.
match.py validates the size, copies the file to
/private/tmp/cc65-sargon-match.dsk, and mounts that disposable working copy,
so nothing in this workflow can write to your original.
Python needs the chess package. Build the native UCI adapter before a run:
make -C tests -B uci
The repository test suite can be rebuilt and run with:
make -C tests test
Exact Sargon boot choreography
The compilation disk is not directly bootable into the game. The reliable sequence is:
- Launch a2m-v2 with the trimmed disk and a control port. Do not use
--headless. - Wait for the
SARGON ][catalog/menu screen. - Send uppercase
R, then uppercaseH, as individual keys. Do not send Return with either key. - Ignore the transient
INSERT COPY DISK & HIT RETURNscreen. It clears by itself. Pressing Return there drops into the monitor. - Wait for the
NEW GAMEprompt. - Send
Gfollowed by Return. - At
YOUR COLOR, sendWorBfollowed by Return. - At
LEVEL OF PLAY, send0through6followed by Return.
R and H must be uppercase. The distinction between the no-Return loader
keys and the later choice-plus-Return prompts is important.
After boot, send the control command set-turbo max. a2m-v2 does run at max,
but its window title currently does not update when speed is changed through
the control port.
Reset behavior
The a2m-v2 reset control command returns this Sargon disk to the reusable
NEW GAME state. This was verified both textually and graphically:
- initial board frame SHA-256:
c8a566b8c37bddd5bdb5d23add6aefa2113cecc86753d58f86ae8d8c1a688f20 - after
E2-E4 C7-C5:2275ee882c95532bcab053fbb194490ef8e9dd13a286cde2fe9d09a87e0e1d04 - after reset and starting another game, the frame returned byte-for-byte to the initial hash.
Apple II snapshots are exposed by a2m-v2 but currently time out and are not usable. Reset is therefore the per-game restoration mechanism.
cc65-Chess side
tests/uci uses the same engine and four shipped skill budgets as the Apple II
game:
| UCI skill | Menu name | Nodes |
|---|---|---|
| 1 | Very Easy | 400 |
| 2 | Easy | 1,200 |
| 3 | Harder | 15,000 |
| 4 | Very Hard | 60,000 |
The shipping 8-bit front end bypasses normal search for the first move of a game - four entries as White, and five entries of two replies each as Black.
match.py used to reproduce the White table in Python, because the adapter
called the search directly and could not reach cpu.c at all. It no longer
does: tests/uci has OwnBook and BookSeed, UCIEngine sets both, and the
harness asks the engine what it plays. --no-own-book turns the tables off,
which is the pre-2026 behaviour and the control for anything measured with them
on.
Sargon opening randomization
A scripted reset otherwise repeats Sargon's opening choice. Sargon's keyboard
wait loop increments zero-page $4E, which supplies its opening entropy. At
the level prompt the harness:
- pauses the emulator;
- writes a deterministic byte to
$004E; - queues the level and Return while paused;
- resumes execution.
Color-swapped game pairs receive the same seed, and consecutive pairs advance
the low bits because Sargon's picker consumes only a few of them. The exact
seed is recorded implicitly by the batch name, game index, and retry number in
seed_for().
cc65's own opening variety is small and bounded: four first moves as White, and
two replies to each of five White first moves as Black. Do not treat a handful
of repeated cc65 openers as a bridge failure - the rest of the diversity comes
from Sargon's book, Sargon's colour, and later play. Summaries count distinct
six-ply prefixes, and doc/strength.md §5.1.7 is why that count matters more
than the score.
Text screen layout and move parsing
The Apple II text page is read from $0400-$07ff and decoded using the normal
24-row interleave. Sargon's move table has fixed columns:
- move number in the first ten columns;
- Player/White cell at columns 10-17;
- Sargon/Black cell at columns 23-30.
Which column belongs to Sargon depends on the selected color. Legal move forms observed so far are:
E2-E4 ordinary move
F5XD3 capture
0-0 king-side castle
0-0-0 queen-side castle
PXPEP en passant
Coordinates are translated against python-chess's current legal move list,
which also resolves promotion to a queen. CHECK is a nonterminal annotation
after a coordinate move. CHECKMATE and STALEMATE are terminal displays;
on those screens Sargon may not put the normal player cursor in its usual
column.
MATE IN n is status, not a move
Sargon can display MATE IN 1 (and similar forecasts) for a long time while
searching. It can also put the forecast in the next move row after its real
coordinate move, for example:
52 ... C1-D2
53 ` MATE IN 1
The forecast must never be fed to the referee as a move. When the input
cursor has returned, the bridge compares the new table with the pre-move
snapshot and selects the newest newly-added valid coordinate token, ignoring
later status cells. Do not press a key merely because MATE IN n persists;
some Level 1 searches have taken more than a minute at emulator max.
Move 100 is printed as :0
Sargon has only a two-character move-number field and does not implement a hundreds digit. It advances the tens character through ASCII instead:
99, :0 (100), :1, ... ;0 (110), ... ?0 (150)
The parser decodes 0 through ? as digit values 0 through 15. ?0 is full
move 150, matching the harness's 300-ply safety cap. If this rollover is not
recognized, the bridge cannot see the player cursor at move 100 and appears to
hang.
Referee and game policy
python-chess is an independent referee. It validates every coordinate move, maintains the authoritative position, and determines game outcomes. Draw claims are enabled, including threefold repetition and the fifty-move rule. Games also have a 300-ply safety cap. Colors alternate every game.
Every completed game is appended immediately, so Ctrl-C is safe. Rerunning the same command resumes at the next CSV index. A failed game is retried up to three times; the retry changes its seed so one broken position cannot wedge an entire batch. Failures are retained rather than silently discarded.
Each batch writes:
<batch>.csv
<batch>.pgn
<batch>-summary.json
<batch>-failures.log # only when a failure occurred
The CSV contains the full UCI move list as well as result, cc65 color, draw or mate reason, ply count, duration, and six-ply opening prefix. The PGN is the best starting point for reproducing suspected cc65 late-endgame defects.
Running screens and matches
Use a scratch output directory so generated games remain outside the shipped source tree:
python3 sargon/match.py \
--only calibration \
--calibration-games 64 \
--cc-skill 2 \
--output scratch/sargon-vs-cc65-YYYYMMDD
The emulator window remains visible. Sargon defaults to L1 for calibration. To run a 512-game measured match explicitly:
python3 sargon/match.py \
--only match \
--match-games 512 \
--cc-skill 2 \
--sargon-level 1 \
--output scratch/sargon-vs-cc65-YYYYMMDD
A four-game bridge smoke test is:
python3 sargon/match.py \
--only smoke \
--smoke-games 4 \
--cc-skill 1 \
--sargon-level 1 \
--output scratch/sargon-smoke-YYYYMMDD
--only calibration plays Sargon level 1 whatever --sargon-level says.
The calibration phase is the fixed screen from the original study - level 1,
raise cc65 - and it hard-codes the level and the batch name to match. Any
other level is --only match --sargon-level N --match-games N, which is also
what names the output match-ccX-sargonY instead of calibration-....
A colour-split screen is two runs, and they want separate output
directories: --cc-color does not reach the batch name, so both halves would
otherwise resume into one CSV and the second half would start at the first
half's game index and inherit its seeds.
python3 sargon/match.py --only match --match-games 32 \
--cc-skill 3 --sargon-level 3 --cc-color white --move-timeout 300 \
--output scratch/sargon-l3-cc3-white-YYYYMMDD
And the cost of any of this before starting it:
python3 sargon/levelcost.py --output scratch/sargon-levelcost-YYYYMMDD
Do not run more than one harness on the default control port 6511. A second read-only control client may also block because the active harness polls the same emulator continuously.
Calibration policy used for the first study
The initial idea was Very Easy versus Sargon L1, raising Sargon only if cc65 dominated. The first screen showed the mismatch was in the opposite direction: Very Easy scored 0 wins, 7 losses, and 5 draws (20.8%) in 12 games.
The revised ladder keeps Sargon at L1 and raises cc65 through the shipped levels:
- Very Easy, 400 nodes;
- Easy, 1,200 nodes;
- Harder, 15,000 nodes;
- Very Hard, 60,000 nodes.
Run 64-game screens. A score below 35% advances to the next cc65 tier. A score in the 35%-65% band is a candidate for the 512-game measured match. If a tier jumps above the band, compare it with the previous tier and use the one closest to 50%. If Very Hard is still clearly below Sargon L1, report that; Sargon L0 is a separate experiment rather than silently changing the question.
The Easy screen immediately produced much longer endings than Very Easy, including fifty-move draws, insufficient-material draws, and games beyond move 100. These retained games may expose a cc65 late-game defect and should be mined as regression positions after the match data is collected.
They did, and they were. The defect was that nothing in the evaluation
preferred one bare-king position to another, so a won ending had no gradient to
climb. Game 0 of the baseline is now tests/c64drive.c, replayed on a real
C64 to price the fix; the king-and-rook position from move 66 is the first
entry in chesstest convert.
Completed pre-fix baseline (2026-08-10)
The Easy-versus-Sargon-L1 screen was deliberately stopped at 64 games because a cc65-Chess late-game fix was already being prepared. The completed baseline is:
cc65 Easy: 10 wins, 19 losses, 35 draws
score: 27.5 / 64 = 42.96875%
openings: 17 distinct six-ply prefixes
plies: 8,207 total, 128.23 mean, 230 maximum
By cc65 color:
White: 9 wins, 3 losses, 20 draws
Black: 1 win, 16 losses, 15 draws
Terminations:
checkmate 29
threefold repetition 18
fifty-move rule 15
insufficient material 2
The large color split is important and must be retained when evaluating the fix. The result artifacts are:
scratch/sargon-vs-cc65-20260809/calibration-cc2-sargon1.csv
scratch/sargon-vs-cc65-20260809/calibration-cc2-sargon1.pgn
scratch/sargon-vs-cc65-20260809/calibration-cc2-sargon1-summary.json
scratch/sargon-vs-cc65-20260809/calibration-cc2-sargon1-failures.log
All 64 CSV rows are present and all 64 PGNs parse without errors. The one
failure-log entry is the bridge discovery where row 52 contained Sargon's real
C1-D2 move and row 53 contained MATE IN 1; the game was retried and the
newest-valid-coordinate selection fix was applied before the remaining run.
As a rough unpaired summary, 42.97% corresponds to about -49 Elo and a normal 95% score interval of about 34.8%-51.1%. This is not a publishable strength estimate: the engine is deterministic, the opening set is small and repeated, 64 games is a screen rather than the prescribed 512, and the suspected cc65 late-game defect means this run is intentionally a pre-fix baseline.
Completed post-fix run (2026-08-10)
The late-game fix landed as two commits — the mate drive itself, and then a second gate on it that this rig is the reason for. The same screen was re-run at the same settings, 64 games, cc65 Easy versus Sargon L1:
cc65 Easy: 27 wins, 20 losses, 17 draws
score: 35.5 / 64 = 55.47% (pre-fix 27.5 / 64 = 42.97%)
openings: 16 distinct six-ply prefixes
plies: 8,028 total, 125.4 mean, 193 maximum
By cc65 color, which is where the change is visible:
White: 25 wins, 2 losses, 5 draws (pre-fix 9W 3L 20D)
Black: 2 wins, 18 losses, 12 draws (pre-fix 1W 16L 15D)
Terminations:
checkmate 47 (pre-fix 29)
threefold repetition 13 (pre-fix 18)
fifty-move rule 2 (pre-fix 15)
insufficient material 2 (pre-fix 2)
And on the measure the fix was for — a clear piece up for ten plies or more:
converted: 26 of 28 (92%) (pre-fix 6 of 23, 26%)
drew still a piece up: 0 (pre-fix 15)
gave the material back: 2 (pre-fix 2)
The fifteen games that ended still holding a rook against a bare king are gone.
Do not quote the percentage on its own. Naive 95% intervals are 30.8%-55.1% and 43.3%-67.6% and they overlap; the effective sample is 16-17 distinct games, not 64. The conversion count and the fifty-move column are what carry the result, because they are categorical outcomes tied to the mechanism.
What this rig found that nothing in the repository could
The first version of the fix passed every test in tests/, and scored better
than the shipped version on the internal self-play conversion metric — 85%
against 81%. Played here it took 51.6% to 31.2% over 32 games. Its gate was
loose enough to change moves in middlegames, and no internal instrument could
see that: the endgame tests contain only bare-king positions, and in self-play
both sides carry the same harm so it cancels.
That is the third time a defect in this engine has been found by playing rather than by measuring, and it is the argument for keeping this rig alive.
Two properties of this rig that the study has to live with
Sargon is not reproducible. The $4E seed is written before the level is
typed, but the keyboard-wait counter keeps advancing afterwards, so book choices
depend on host timing. The same seed opened 1.d4 Nf6 in one run and
1.d4 d5 in the next with cc65's own moves identical. Paired replay of a game
index against an earlier run is therefore impossible; all comparisons here are
unpaired.
64 games is not 64 samples. The pre-fix baseline holds 17 distinct move lists and the post-fix run 16; on the Black side it is four or five. The fifteen pre-fix fifty-move draws were one game played fifteen times. Scores from this rig move in large steps and should be read that way.
cc65 had an opening book only as White, four entries deep, while Sargon has one on both sides. That was written here as the most likely cause of the sixty-point gap between cc65's White score (85.9%) and its Black score (25.0%).
It was checked, and it is wrong in its mechanism. doc/strength.md §5.1.7 has
the whole of it; the short version is that all eighteen Black losses came from
two openings, the fourteen 1.e4 Nc6 games were one identical 103-ply game,
and outside those two lines Black scored 57% and did not lose. Stockfish says
neither losing game was lost in the opening — the 1.e4 game is winning for
Black on move 13 and thrown on move 17. Played from tests/book.epd where no
book fires for either side, the engine scores the same with both colours.
The gap is a weighting, not a strength difference: White's four-entry table gave 32 games twelve distinct openings, and Black's absence of one gave 32 games five. A book helps by spreading the sample, and one bad game counted fourteen times is what 25.0% mostly is.
Black now has a reply table too, and this rig is what confirms it.
Two things about this harness changed with that
The opening tables are the engine's, not this script's. match.py used to
carry a Python copy of the White table, because tests/uci called the search
directly and could not reach cpu.c at all. The adapter now has OwnBook and
BookSeed, UCIEngine sets both, and the harness asks the engine what it plays.
A copy of a table is a copy of what it was believed to contain, which is exactly
the thing a rig for finding wrong beliefs should not have.
--cc-color pins the colour for a whole run. A colour split is measured
with half the games, and the Black side is where the open questions are: 32
alternating games gave five distinct Black openings. --cc-color black spends
all 64 on the side being asked about, and the per-game seed advances every game
rather than every pair, because there is no colour-swapped partner to hold it
constant for.
python3 sargon/match.py \
--only calibration --calibration-games 64 \
--cc-skill 2 --cc-color black \
--output scratch/sargon-blackbook-YYYYMMDD
--no-own-book turns the tables off, which is the pre-2026 behaviour and the
control for anything measured with them on.
Sargon's deeper levels, and what they cost
Every level has now been timed. levelcost.py plays the opening of one
throwaway game per level and times Sargon's replies; all seven levels take six
minutes of emulator time.
level opening replies (s) median per Sargon move 64 games 512 games
0 0.1 0.1 0.1 0.1 0.07 ~0.3 s 20 min 2.5 h
1 0.3 0.3 0.4 0.4 0.34 0.6 s * 40 min 5 h
2 0.3 0.4 0.5 0.8 0.42 ~0.7 s 45 min 6.0 h
3 1.0 1.3 1.8 2.6 1.54 1.50 s * 1.6 h 13 h
4 4.2 4.8 5.4 4.79 3.79 s * 4.3 h 34 h
5 7.3 8.9 16.5 8.95 7 to 17 s 8 to 18 h 65-145 h
6 44.7 48.6 191.0 48.59 90.7 s * 93 h 31 days
* measured over whole games: the 64-game level-1 screen, the 64-game level-3
screen below, the 16-game level-4 probe below that, and the six-game level-6
run. Level 5 is a range on purpose, and the reason is the useful part.
A one-factor model fitted two anchors and was wrong in the middle. With only levels 1 and 6 measured, one constant - the median opening reply times 1.80 - reproduced both to within 4% across a range spanning a factor of 150. It predicted 2.8 s a move at level 3. The level-3 screen then measured 1.50, so the tidy model over-predicted by 87% at the one point between its two anchors. Two anchors and a straight line through them is not a model, and this one agreed at both ends for different reasons at each end.
What the three measured levels actually say about the probe:
level 1 probe 0.34 whole game 0.6 1.76x - but see the overhead below
level 3 probe 1.54 whole game 1.50 0.97x
level 4 probe 4.79 whole game 3.79 0.79x
level 6 probe 48.6 whole game 90.7 1.87x
So the opening is representative through level 4 - slightly pessimistic, if anything - and only half the story at level 6, where the direction flips. Use the probe as-is up to level 4, expect up to double it at 5 and 6, and check the first few games of any long run against the estimate rather than trusting it. Every estimate made here before it was measured came out high, twice by a lot, which is the safe direction but not an accurate one.
The band the study needed is levels 2 to 5, and the shape of it is that the wall between an afternoon and a fortnight sits between level 4 and level 6 - level 3 is under two hours for 64 games, level 4 an afternoon, and level 6 four days.
Three things this corrected, two of them numbers that were written down here.
"45 seconds per Sargon move" was seconds per ply. The level-6 run is 8.39 hours over 666 plies, and only half of those plies are Sargon's; cc65 answers in 8 ms at Very Hard on this host, measured. Per Sargon move it is 90.7 seconds, 73 to 137 across the six games. The 84-minutes-a-game figure was right and this one was out by two, which matters because the per-move number is the one that gets multiplied when a match is being sized.
"An early move is the expensive case" holds at the shallow levels and inverts at the deepest. This file used to state it flatly, off Sargon's cost tracking the piece count. At level 6 a whole game costs 1.9x its opening per move, which is the opposite; at level 3 the opening is if anything slightly dearer, which is the original claim. Neither is wrong, and the flip is somewhere between - deep searches in complicated middlegames are where level 6's time goes, and that is not a piece-count story.
Read the cheap end carefully, because at level 1 this harness is a large part
of its own measurement: player_move types six keys at KEY_DELAY, which is
0.15s before Sargon has thought at all, and the screen poll adds another 25ms.
That is arithmetic off the constants rather than a measurement, and it is
nearly a third of the level-1 figure against 0.2% of the level-6 one. Anything
inferred from the ratio between a probe and a game mean at the cheap levels is
partly measuring the bridge.
And the host matters, which is worth 11%. The level-3 screen ran its White half while a 6-core Stockfish gauntlet had the machine and its Black half with the machine mostly idle: 1.67 s a move against 1.50. Neither result changed - both programs are node limited, which is the whole reason this rig is allowed to share a host - but a timing figure taken under load is 11% pessimistic.
What actually broke the sixty-hour prediction was variance, not phase. One
level-6 move in three took 191 seconds against a 48.6-second median for its own
level - 4x. That is the number to size MOVE_TIMEOUT against, because a
search that overruns it is scored as a bridge failure and the game is retried
rather than waited for, which loses the game rather than the move. Allow about
20x the projected per-move cost: the 120s default is ample through level 3, 300
at level 4, 600 at level 5, and 1200 was used for level 6.
Completed level-3 screen (2026-08-11)
The first use of the numbers above. cc65 Harder against Sargon level 3, 64 games, run as two single-colour halves of 32 so the colour split is measured rather than halved:
cc65 Harder: 42 wins, 12 losses, 10 draws
score: 47.0 / 64 = 73.4%
openings: 27 distinct six-ply prefixes, most-repeated game 6x
plies: 118 mean
cost: 1.6 hours for the 64 games
By colour, and terminations:
White: 26W 4L 2D = 84.4% 12 distinct in 32
Black: 16W 8L 8D = 62.5% 15 distinct in 32
checkmate 54, threefold 8, fifty-move 1, stalemate 1
Two of these are the best numbers this rig has produced and neither is the score. 27 distinct games in 64, against 16 to 17 in the level-1 study, with the worst repeat down from fourteen to six - so for the first time the effective sample is most of the games played. And one fifty-move draw where the pre-fix baseline had fifteen, which is the endgame work holding at a level it had never been played at. Black at 62.5% against 45.3% at level 1 is the reply table and the deeper search together.
73.4% is above the 35%-65% band, so level 3 is not the pairing, and by the policy the next screen is level 4.
Completed level-4 screen (2026-08-11)
The sixteen-game cost probe was resumed in the same output directory to the full 64 games, with cc65 Harder against Sargon level 4:
cc65 Harder: 22 wins, 27 losses, 15 draws
score: 29.5 / 64 = 46.1%
openings: 28 distinct six-ply prefixes, most-repeated game 6x
plies: 121 mean
cost: 4.27 hours, 4.00 minutes a game
By colour, and terminations:
White: 17W 12L 3D = 57.8% 17 distinct in 32, worst repeat 5x
Black: 5W 15L 12D = 34.4% 11 distinct in 32, worst repeat 6x
checkmate 49, threefold 15, fifty-move 0, stalemate 0
Level 4 is the calibrated pairing. The aggregate 46.1% is inside the 35%-65% competitive band, and 28 distinct openings is marginally better than level 3's 27. More important for future candidate checks, zero fifty-move draws says the endgame work still holds at this new level.
The colour split is also part of the baseline, not something to average away. Black is half a point below the nominal band and draws twelve games; a future candidate must not turn that existing weakness into a categorical collapse. Comparisons remain unpaired because Sargon is not reproducible, so the score's honest interval is still roughly +-12 points. What this screen buys is an outside opponent at the right aggregate strength and baseline categories to compare: colour, distinct games, fifty-move draws and termination causes.
The levels do not sit on one scale, and this is where that shows
Before the screen the levels were placed by interpolating between cc65 Easy at
55.5% against level 1 and cc65 Very Hard at 41.7% against level 6: about 123
rating points a level, which the timing appeared to corroborate at 2.7x more
time per level and doc/strength.md §4.2's 60 points a doubling. That
predicted 58% for this screen. The answer was 73.4%.
The two ends will not reconcile, either. Reading down from the level-3 result puts the levels about 44 points apart and level 6 near 1615 - at which cc65 Very Hard, rated 1946, should have scored about 90% there instead of 41.7%. Six games cannot carry that weight; 64 games at level 3 cannot be waved away either.
The honest reading is §4.3's non-transitivity arriving again: Sargon's levels are not a ladder that can be interpolated, and cost per level is not strength per level. Level 6 costs sixty times level 3 in time and is plainly not sixty times anything in strength. The band has to be walked one 64-game screen at a time, and the cost table is what says which rungs can be afforded.
What that means for a match
A 64-game screen costs 40 minutes at level 1, 1.6 hours at level 3, 4.3 hours at level 4 and 93 at level 6. A 512-game measured match costs 13 hours at level 3 and four to six days at level 5.
Read the calibration policy's "advance to a 512-game measured match" as
retired above level 3 rather than inherited. At 64 games this rig now yields
28 distinct games; at 512 it would yield perhaps 120, for a fortnight, at the
levels that are actually competitive. The statistics come from Stockfish and
the confirmation comes from Sargon; doc/strength.md §5.1.7 is where a
Stockfish ranking failed to transfer here on both of the measures tried, and
§5.2b is the first time that order ran in the cheap direction - a candidate
closed by forty minutes of Stockfish never cost this rig a game.
Reading the games afterwards
analyse.py runs Stockfish over a PGN and prints, per game, every move that
moved the evaluation by more than a threshold, attributed to the side that
played it:
python3 sargon/analyse.py scratch/<run>/smoke.pgn --depth 18 --engine cc65
It is what found that cc65 reaches move 13 winning in the games it loses to
level 1 (doc/strength.md §5.1.7) and behind in every game against level 6.
Three cautions, all of them earned:
- Stockfish judges by a standard neither program can play to. A position
it calls equal can be lost for a 60,000-node engine. Sargon's
11.Nxf7in the §5.1.7 game is worth −139 to Sargon and won the game anyway. - Mate scores clamp at ±10000, so walking into a forced mate and failing to deliver one both print as ~9000cp swings. They are opposite errors and the blunder counts do not tell them apart.
- And what that clamp does to an average is worse. This caution used to
stop at blunder counts. Over 21 games, five clamped moves in 231 turned the
mean cost of a middlegame pawn move from -27.9 into +166.1 - a pawn move
reading as a large gain - and two in 94 turned an opening knight move from
-23.3 into -226.7. Any mean over evaluation deltas needs the mate scores
dropped and a median printed beside it, which is what
piececost.pydoes and whatdoc/strength.md§5.2a did not. That table is now believed to be mostly this artifact; §5.2b has the replication. - A per-game count is not a per-move cost. Counting cc65's queen moves per
game correlated beautifully with the result and was close to meaningless,
because a losing position invites queen moves. Cost per move, split by game
phase, said something different and better -
doc/strength.md§5.2a has the whole sequence.
Known a2m-v2 limitations and cautions
- Do not use
--headless; the visible window is part of this study. set-turbo maxworks, but the window title does not reflect the control-port speed change.- Apple II snapshots currently time out. Use verified reset instead.
- Do not send Return during the
R,Hboot-loader sequence. - Do not type into the emulator window while automation owns focus; host keystrokes can be consumed by the Apple II.
- Do not assume
MATE IN nmeans the program is finished. - Do not assume decimal-only move numbers after move 99.