The pin the whole history now encodes becomes the primary route: the
submodule default replaces FetchContent and the unpinned forge fallback,
LIBAVR_ROOT stays as the tandem-development override, the presets already
take the toolchain file from the submodule, and the Studio projects anchor
their include path there — correct by construction. The version tags and
the one-command historical build are documented beside the version map.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The size matrix proves every image fits its slot and says nothing about the
numbers the README prints. Those drift silently: caller_page() took eight bytes
off every build at once, so all fifteen rows went stale together and no test
noticed, because nothing was over budget. sizes.py check-readme compares the
table against the built images; sizes.py max reports the largest image per chip
and anything over its slot.
It is a check rather than a generator, so the table stays prose someone can
write. No chip geometry lives here either: the image/budget pairs come out of
each build's own CTestTestfile.cmake, which is what the gate checks, so a chip
added or a budget changed needs no edit. Only trees a preset still owns are
read — a stale directory answers with a size that was true once.
16243 images across 37 chips today, none over budget, tightest tsb_asm at 510
of 512.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check.sh proves the protocol under simavr on every chip; it cannot prove a
board. Two things live only on silicon — an RC oscillator that is not on its
nominal, and a reset edge that has to come from somewhere — and until now the
scripts that reached them were per-session scratch on the machine holding the
programmer, which is where the ATtiny13A run's findings nearly stayed.
pbrig.py is the primitives, knowing nothing per-board: every deployment fact is
a flag or a PUREBOOT_* variable. Two rig facts are encoded in it because neither
is guessable and each cost a session to learn: an ISP access *is* the reset edge
where the adapter's DTR is unwired, so a session begins with an ISP touch and
knocks immediately after; and avrdude splits -U on colons, so a Windows drive
letter breaks the spec and every file goes as a bare name with avrdude run in
its own directory. Its `rate` subcommand is the one that turns "the loader is
silent, so the wiring must be wrong" into a number, by sweeping the host rate
against a fixed cycles-per-bit transmitter — PUREBOOT_HEARTBEAT makes the
existing fixture into one, software link only, since the hardware-link idle owes
the self-update tests its command loop.
pbhw.py takes every bound from the info block the loader reports, so one run
covers a 1 KiB tiny and a 128 KiB mega alike. Both are exercised on an ATtiny13A:
backup verified against a known-good capture, the clock measured at 9.048 MHz
against a 9.6 MHz nominal, and the suite 11/11.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every plausible oscillator against every rate it reaches against every
backend, on one chip per size-bearing class, under --full only. The baud
ladder becomes a reachability predicate the enumeration filters on, so an
unreachable point drops out instead of aborting the configure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tools/make_presets.py emits the uniform pipeline the hand-grown file had
drifted from — generated configure/build/test presets and workflows for
all 37 chips, reflect configure/build for libavr's 12-chip spot set —
and tools/check.sh runs every chip's workflow (--full adds the reflect
spot) as the port's gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>