* draw_waves Usage advertised [--no-main], which argparse never defined (the flags are
--main / --only-main, and main is excluded by default), and omitted --exclude-file,
which is now a PREREQUISITE that refuses a stale list.
* jr_isolate's STATUS block still declared the tool BLOCKED on split_src_region with the
blocker unbuilt. Five defects were fixed this session and it runs the full chain to
completion; what remains is a duplicate-definition class at assembly. Says so, and
points at §431 as the cheaper route than finishing the item model.
* ld_interleave's layout diagram — the first thing anyone reads — showed the pre-S72
three-piece island with 6324C.data.o. main's island is SEVEN pieces driven by --order;
--front/--tail is the overlay form now.
* jtbl_rodata_pads described a stored-spec-only filter and advertised guards that no
longer all exist; --derive serves main since S72.
My own regression from the same session: exclude_audit.parse now returns 4-tuples (it
carries the WALL pin and each entry's note), and draw_waves built `skip` straight from
them, so every membership test against a 2-tuple missed and the exclude list had no
effect at all — while the run reported success.
Caught by MEASURING the pool rather than trusting the run: it came back 88 non-main + 45
main = the full frontier, when a 25-entry list should have reduced it. Now 69 and 41,
which reconciles exactly (88 - 16 carve-blocked - 3 non-main walls; 45 - 4 open main
walls, PopMatrix/PushMatrix being linked and already refused).
The silently-narrowed-scope shape again, and the third time this session that counting
the RESULT rather than trusting the REPORT is what caught it.
An exclude list records what the TOOLING could not do, then gets treated as a property of
the FUNCTIONS. Nothing re-examined it, so every tool fix left behind a population that is
now tractable and still marked impossible — invisible, because the draw filters it out
before anything measures it.
MEASURED one day after .run/S71_exclude.txt was written: 88 of its 107 entries were
stale — 28 already banked, 14 linked PsyQ symbols that were never targets, and 46 whose
blocker had since been fixed. Those 46 are 12,750 instructions of open, drawable work
including main:SaveLoadRoutine (1,165), the largest function left in main.
* tools/exclude_audit.py (NEW) — classifies each entry by its CURRENT blocker
(BANKED / LINKED / RE-PROBE / CARVE-BLOCKED / WALL), regenerates keeping only the
still-valid classes, and --assert-fresh exits 3 on staleness.
* draw_waves --exclude-file — runs that audit and REFUSES to draw on a stale list, naming
the counts and the regenerate command. --exclude-stale-ok still draws but prints what it
ignores: skipping is possible, never silent. Also fixes the old --exclude parsing, which
could not survive a '#' comment.
* .run/S72_exclude.txt — the regenerated list: 19 entries (16 CARVE-BLOCKED + 3 WALL),
each carrying its reason, down from 107.
Verified in all three directions: stale refuses rc=1, fresh proceeds rc=0, override
proceeds and announces. The parser's own report-don't-drop design caught a bug I
introduced in it (comma-splitting before comment-stripping).
Drew, 2026-09-02: "use opus mainly and only escalate the difficult ones to fable".
The S69 table that set the old 150 line actually puts opus's cliff at ~350:
m1 opus 191-347 ins 10/15 MATCH 1,291 tok/matched-ins <-- best measured
m2 opus 347-670 ins 1/9 MATCH 7,158 <-- the cliff
So 150 was handing opus's STRONGEST band to Fable. Fable is now reserved for >340
instructions and for arm_from_history's compiler-internal residual signal (§413) at any
size. On the current pool that moves three functions back to opus while two stay on
Fable because their history names a scheduling/regalloc residual - escalation by
difficulty rather than length, which is the point.
Retries default back to opus: one failure is not evidence of a wall, and the history
signal lifts a target on its own if the notes justify it.
Measured on S71's own wave: wall-clock tracks iteration count, and iteration count
tracks the residual class, not size. A 26-instruction function took 18 min / 31 tool
calls (regalloc, finished NEAR); a 122-instruction one took 80 s / 10. The 20-33 min
runs were all compiler-internal residuals — scheduling ties, birthing boost, register
colouring, LUID order — where every hypothesis costs a compile-and-measure cycle.
arm_for keys on nins alone, so a 47-instruction regalloc wall could not be drawn at
the higher tier and nothing escalates mid-run. arm_from_history() now reads the
function's own journal notes at draw time and returns fable when they name one of
those classes; it never downgrades the size ladder's choice.
R39 control over 3,147 functions with history x 3 bands = 9,441 decisions:
4,020 upgrades (43%), 0 downgrades.
The control's FIRST form passed over an empty set — it keyed on journal rows carrying
a binary, and there are none: the agent verdict schema never had that field, so every
historical note is name-keyed and the same name is a different function in another
overlay (§238). claude_wave_draft.js's VERDICT now requires `binary`, so new rows are
exact. Cookbook §413.
Measured over 129 drafting agents in one session, per MATCHED instruction (the
only cost that matters, since a failed agent is billed in full):
sonnet 105 agents, 57 MATCH 4,289 tok/matched-ins (flat ~47% above 30 ins)
opus 24 agents, 11 MATCH 2,083 (m1 191-347: 1,291, 67%)
opus at 347-670: 1/9 7,158 <- the cliff, 2.92M tokens for ONE bank
fable escalation: 3/4 closed at ~1/3 the cost of the attempt it rescued
Sonnet's per-agent price was never the cost that mattered; cost per BANK is, and
it lost on that by 2.1x. The m2 wave should have been fable from the start.
Escalating SOONER is the standing finding — higher models crack harder functions
in fewer tokens. Tested twice now (S68 A/B, S69 measurement); do not re-derive a
cheap-tier argument from per-agent price a third time.
draw_waves.py gains --only-main (the main lane draws main and nothing else; implies --main so the
LINKED refusal still applies). Progress/backlog regenerated: fleet 99.2% instruction-weighted,
98.1% distinct.
- match_one.py: a CPP/CC1/MASPSX/AS failure printed bare text and exited 1 even under --json, so
every programmatic caller got json.loads of a non-JSON line. claude_wave_packs swallowed 4 of 19
prior drafts as 'residual not measured: Expecting value' — the most actionable datum a pack can
carry (the draft does not COMPILE, here is the error) was the one it dropped. Negative control:
a near draft still measures identically (closeness 2, same residual rows); human mode unchanged.
- claude_wave_packs.py: renders that verdict, pointing the agent at the card's decl_prior block.
Coverage on wave r1 went 15/19 -> 19/19 packs carrying a measured verdict.
- wave_args.py (new): emits the claude_wave_draft.js args from <wave>/targets.json, asserting the
.s exists, that sub is exactly its parent dir, and that the pack exists. Written because I
hand-typed sub as 'ov_SC03_112/jr_80181D08' when the truth is
'asm/ov_SC03_112/nonmatchings/ov_SC03_112_jr_801817E0' (a stub's asm dir is named for its jr-carve
block, not itself) — all 19 agent oracles would have failed identically and read as a model
failure. Negative control: that exact string is REFUSED.
- draw_waves.py (new): draws waves off corpus.stubs cheapest-first, ledger-filtered, with
name-collision deferral (packs are name-keyed and refuse a duplicate). Re-measured the frontier:
the S65 tier map's '~557 cheap singletons (3-17 ins)' conflated one-member FAMILIES with small
functions — only 28 undrawn non-main stubs are <=17 ins; the bulk is 51-120 (258) and >120 (245).