Commit Graph

2924 Commits

Author SHA1 Message Date
Drew T 348eb2808a feat(decomp): main lane m45a — 3 banked
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.

  func_800189A8
  func_80028E68
  func_8005D0F8
2026-08-25 18:50:13 -06:00
Drew T af33c35957 chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 18:49:12 -06:00
Drew T 68da52e73d feat(decomp): ox wave ei overlays — 34 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 18:48:44 -06:00
Drew T 7a23245e87 chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 18:41:49 -06:00
Drew T c8c697a0a9 feat(decomp): ox wave eo overlays — 3 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 18:40:57 -06:00
Drew T b4f91e532f chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 18:20:32 -06:00
Drew T 2094ee04a0 docs(cookbook): §283-§292 — the 36-wave batch distilled, 1,216 candidates reviewed
Five reviewers on disjoint wave groups (D1 dd/de/df · D2 dg-dm · D3 cg-cm · D4 cn-cw ·
D5 cx-dt): 859 COVERED · 46 ADDENDUM · 9 NEW · 289 REJECT. Index now 888 sections.

  §283  the 46 sharpenings, one block per target section
  §284  combine can reassociate two sequential bitwise-AND masks against the PRE-mask value;
        an asm fence at the mask's definition point stops it
  §285  pre-initializing a variable with a shared constant BEFORE a branch makes both arms of
        the following if/else destructively reuse one register
  §286  fold a statement's side effect into a comma-expression in an argument position to
        place its RTL relative to a call's own delay slot
  §287  a stack-frame hole below two address-taken aggregate locals is a LEADING PAD MEMBER of
        one combined struct, not separate locals
  §288  a register pin declared UNINITIALIZED and assigned only at its late sole use still
        forces the fixed register's save/restore
  §289  an array local's decayed base keeps EVERY element's store alive under DSE, though only
        one pointer value escapes
  §290  one strength-reduced giv can drive stores to several distinct relocatable symbols,
        each keeping its own %hi/%lo anchor
  §291  the delay-slot false-value: a conditional branch's zero arm must be a fall-through
        adjacent block ending in an explicit goto
  §292  declaring a symbol upstream of an already-banked sibling that relies on its implicit
        (K&R) declaration silently reprototypes the sibling's call site

VERIFICATION CAUGHT SIX BAD CLAIMS, recorded in §283 as refuted rather than laundered in:
three independent "the harvester leaked an unbanked NEAR into a banked-only harvest" reports
(all three functions are genuinely banked per corpus.stubs — the notes predate their gate and
read as harvester bugs hours later); a "match_one resolves targets by bare symbol name" claim
(it resolves an explicit path, and api_draft always passes it — the real hazard is its
resident-defaulting --asm-subdir, hardened separately in commit:2917); and two idiom claims
whose mechanism is absent from the banked code. §284's fence was described as NON-volatile
and the banked code uses `__asm__ volatile` — corrected in the text, since that is a detail
readers copy verbatim.

THE STRONGEST SIGNAL IS NOT A SECTION: eight cards across four waves independently
re-derived that the whole-object gate needs every sibling matched. It is implicit in the
corpus and has never been stated as its own law. Recorded in §283 as the batch's clearest
missing-section signal.

PROCESS, for the next batch: forked sub-reviewers exceeded their brief in three of five
groups — one re-derived five waves it was not assigned and self-merged over the shared output
path, one silently dropped 11 rows including a whole function, one produced nothing. Each
parent caught its own fork. Tell forks not to spawn forks (they infer it from the parent's
inherited context) and give each a private output path.
2026-08-25 18:10:29 -06:00
Drew T 5fae3f5094 feat(decomp): ox wave en overlays — 14 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 18:09:50 -06:00
Drew T 3a4de071d9 fix(match_one): warn when --asm-subdir is defaulted — the default targets resident
A distill reviewer reported "match_one resolves targets by bare symbol name, not
(binary, address)" from 6+ observed target-confusion instances. VERIFIED AND THE CLAIM DOES
NOT HOLD as stated: match_one resolves '%s/%s.s' % (asm_subdir, fn) — an explicit path — and
the drafting path is safe because api_draft.match_one() always passes
dirname(card['asm']). The 675 "match_one MATCH but the whole-binary gate rejected" rows today
keep their real explanation: they landed in the window when config/overlays.mk was empty and
NOTHING could build.

The narrower hazard behind the report is real. --asm-subdir defaults to
asm/resident/nonmatchings/resident, function names are ADDRESS-DERIVED, and overlays share
the address space — so the same name is routinely a DIFFERENT function in another binary
(§238 homonym trap). Any caller that omits the flag gets a confident verdict about the wrong
target, and the failure is silent because the file exists.

It now warns loudly on stderr when the default is used, naming the fn and the directory, and
stays silent when the flag is passed (controlled both ways). A warning rather than a refusal:
resident-era callers legitimately rely on the default, and R43's "refuse what you cannot
handle" does not apply to a tool that CAN handle the input — it applies to one that cannot
tell whether the input is what the caller meant.
2026-08-25 18:01:14 -06:00
Drew T 4fc7eb6555 chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 17:49:27 -06:00
Drew T d801b8f177 fix(config): the registry wiper found — four truncating writes, now atomic and collapse-refusing
ROOT CAUSE of both wipes today. config/overlays.mk was rewritten in four places with

    open(mk, "w").write(txt)

(jr_isolate_all.py:593, jtbl_carve.py:1077/1118/1165) — which TRUNCATES to zero first and only
then writes. Three ways that loses the registry: the process dies between truncate and write
(empty file); another process reads inside that window (sees an empty registry); two writers
interleave (a partial line lands after the last good one — this morning's file ended in a stray
`uto.txt` fragment, exactly that fingerprint). The jtbl carve automation runs AT THE GATE, which
is when all three wipes happened, and ONE_PER_GID=0 made it far likelier by putting many more
carve members in every wave.

BLAST RADIUS, measured twice: with no binaries registered, main's object glob sweeps every
overlay's nonmatchings/*.s into MAIN's OBJS and assembles them standalone, so main cannot build,
the main lane correctly refuses against a RED baseline, and every overlay gate rejects every
draft. Waves dn/do banked 0/224 and 0/236; waves ei..em banked 2 of ~1,100 with 675 backlog rows
reading "match_one MATCH but the whole-binary gate rejected" — the local oracle proving the
drafts were byte-correct while the tree could not build them.

tools/mk_write.py is now the only writer: atomic (tmp + fsync + os.replace, so no reader ever
sees a partial file and a crash leaves the original intact), collapse-refusing (a rewrite below
80% of the current line count raises), and flock-serialized.

TWO HONEST LIMITS, recorded rather than papered over:
  * Callers still READ outside the lock, so two concurrent carves can each read-edit-write and
    the second drops the first's line. That is a LOST UPDATE — a missing line, not a wiped file —
    caught downstream by the fleet check and jtbl_pads_fix. Closing it means holding the lock
    across read-modify-write in every caller.
  * The guard now also refuses when the CURRENT file is under 100 lines. That case cost me
    directly: my own verification control overwrote a registry a carve had truncated seconds
    earlier, because the collapse check was skipped when the old file was empty. A control must
    assert its precondition; mine did not, and now the tool enforces it instead.
2026-08-25 17:48:45 -06:00
Drew T c271c499fc feat(decomp): main lane m43aaa — 1 banked
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.

  func_80028EAC
2026-08-25 17:46:03 -06:00
Drew T 3251a06003 fix(config): RESTORE overlays.mk — deleted AGAIN by wave ei's gate commit
Second registry wipe today. commit:2911 ('ox wave ei overlays — 1 banked') committed
config/overlays.mk as a zero-line file, exactly as commit:2863 did this morning, and the
consequence was identical: no overlay can build, so every gate rejects every draft. Waves
ei/ej/ek/el/em gated ~1,100 drafts and banked 2 between them, with 675 backlog rows reading
'match_one MATCH but the whole-binary gate rejected' — the local oracle proving the drafts
were byte-correct while the tree could not build them.

WHY config_sane() DID NOT FIRE. I added it at 13:0x. The gater process has been running since
midnight and executes the code it loaded at startup — Python does not reload, and a
long-running lane never re-invokes its own module. The runbook already states this ('tool code
next invocation, lane args/env only on a fresh shell') and I applied it to the drafter and the
maintenance lane today while leaving the gater untouched. A guard that is not running is not a
guard.

Restored from commit:2911^ (5,083 lines, 141 binaries). The gater is restarted in the same
change so the protection is actually live.
2026-08-25 17:43:55 -06:00
Drew T 434d8c0976 feat(decomp): ox wave em overlays — 1 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 17:41:12 -06:00
Drew T dc70f53205 feat(decomp): ox wave ei overlays — 1 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 17:14:33 -06:00
Drew T eebe792a0b feat(decomp): ox wave eh overlays — 129 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 17:10:42 -06:00
Drew T f0450910dd feat(decomp): ox wave ee overlays — 4 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 16:37:01 -06:00
Drew T fe97918979 feat(decomp): free A-prop sibling lane (maintenance pass) — zero model tokens 2026-08-25 16:26:03 -06:00
Drew T 84706c9860 feat(draw): draft EVERY instance, siblings included (ONE_PER_GID=0) + K attempts per card
Two ways to spend a free drafting window on a tail that is 92% walls.

ONE_PER_GID=0 — the sibling collapse exists because a same-gid sibling banks by mechanical
remap once its exemplar cracks, so drafting it pays for what the remap does free. That prices
AGENT TOKENS as the scarce resource. On the free ox window they are not, and the collapse is
what makes 3,271 open crackable functions look like 334 drawable skeletons — of which 308 are
gen6+ walls whose exemplars have already refused six waves each. A sibling drafted directly
can crack on its OWN terms instead of waiting on an exemplar that never will.

Measured on a live draw rather than argued:
    uncollapsed  627 cards / 44,403 ins / 160 binaries / 215 gate groups = 2.9 drafts per rebuild
    collapsed    334 cards / 30,926 ins / 104 binaries / 127 gate groups = 2.6 drafts per rebuild
The gate cost is per (binary, TU) group and chunked, so siblings landing in binaries the wave
already touches are close to free at the gate — the card count nearly doubles and the gate gets
MORE efficient per build, not less.

ATTEMPTS=K — K independent shots at each card. The gate cost does not multiply: reloc_filter
keys by fn and staging writes <binary>/<fn>.c, so a function still gets exactly one whole-binary
build per wave; the attempts compete to BE that build, ranked by match_one, which is local and
needs no build. Alternates stay on disk for a later recovery pass. Default 1 (no-op).

A BUG I CAUGHT IN MY OWN SELECTOR before it shipped: it passed binof[fn] (a BINARY NAME) where
match_one wants --asm-subdir (an asm DIRECTORY). Every attempt would have scored identically at
infinity and the picker would have silently degraded to first-seen while appearing to rank —
the same "true number about the wrong thing" class as the day's other defects. Fixed with a
subof map, and a missing subdir now returns neutral instead of a fake score.
2026-08-25 16:18:43 -06:00
Drew T 78047a0fc8 chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 16:18:37 -06:00
Drew T 2b3a2674cf feat(decomp): ox wave ec overlays — 10 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 16:18:25 -06:00
Drew T 274932d209 chore(decomp): commit in-tree banked work before the serial lane 2026-08-25 16:06:04 -06:00
Drew T 2c3201b2fd feat(decomp): ox wave eb overlays — 5 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 16:00:57 -06:00
Drew T b604fc1e4e feat(decomp): ox wave dy overlays — 12 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 15:50:19 -06:00
Drew T bd50b9191c chore(decomp): commit in-tree banked work before the serial lane 2026-08-25 15:42:21 -06:00
Drew T 201015c0d1 feat(decomp): ox wave dx overlays — 21 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 15:38:29 -06:00
Drew T 9e45486282 docs(phase-31): S60 checkpoint — five harness defects, the registry rescue, and the endgame reframed
1,947 banked today. Throughput went 65 -> 341 req/min peak and gates 63 -> 39 min, all by
removing harness defects rather than changing models. The registry incident (config/overlays.mk
committed EMPTY, taking main and every overlay gate down) is written up with its blast radius
and the config_sane guard that now prevents it.

The strategic finding is the part that matters for planning: 3,652 open crackable functions
collapse to 334 DRAWABLE skeletons, of which 308 are gen6+ walls — the ~2,900 untouched
functions sit behind those skeletons and bank by mechanical remap, not by drafting. Wide
drafting now converts at 5%. main is 327 crackable, not 1,288.

Four rule candidates for PhaseEnd (R51-R54), each earned by a defect that fired today.
2026-08-25 15:34:50 -06:00
Drew T 7d4c717cae feat(decomp): serial idiom lane — func_8017D1C0 (118 ins, extend-tell, group worth 681) 2026-08-25 15:29:06 -06:00
Drew T 9d6a748387 feat(decomp): ox wave du overlays — 3 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 15:23:00 -06:00
Drew T 3b90d2db39 feat(decomp): main lane m40bba — 1 banked
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.

  SYS_OBJ_21A4
2026-08-25 15:22:34 -06:00
Drew T 389c0c1065 feat(decomp): main lane m40a — 3 banked
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.

  func_80028EF0
  func_80031CC8
  func_8005D118
2026-08-25 15:20:38 -06:00
Drew T 317d31d18a feat(decomp): ox wave dw overlays — 33 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 15:04:19 -06:00
Drew T 849020a536 chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 15:02:44 -06:00
Drew T 09b7f71a19 fix(serial lane): it was dead at turn 0 — a family-card file fed to a per-function consumer
idiom_serial passed `--cards .run/aprop_cards.json` to api_agent. That file is a FAMILY-card
file — rows are {family, members, seed, cls, reach}, with no per-function key — while
api_agent's --cards wants per-function wave cards and built its map with `c['fn']`. Result:
KeyError at line 647, the agent died before its first turn, and every target logged
"no-draft". The lane read as a model failure; its ledger holds 8 rows total and the tells
lane had, literally, "never yet run".

TWO FIXES, one on each side of the contract:

  * api_agent REFUSES, never crashes (R43). It accepts 'fn' or 'name', skips rows with
    neither, and says how many it skipped. The R32 warning two lines below — "ZERO matched —
    wrong card file?" — existed to catch exactly this and was unreachable behind the crash. A
    guard downstream of the failure is not a guard.
  * idiom_serial passes no --cards at all. Its fuel is the --brief: the target plus every
    idiom distilled so far in the run, which IS the compounding channel the 2,000-way fan-out
    lacks. It never needed a wave card.

Verified: the lane now reaches `api_agent: stealth/ox-alpha -> 1 target(s), max 60 turns` and
drafts, instead of exiting in 0.1 s.
2026-08-25 15:00:31 -06:00
Drew T 0a2bc5c7f7 feat(decomp): ox wave dm overlays — 3 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 15:00:06 -06:00
Drew T ade4bd0bbe chore(decomp): commit in-tree banked work before the serial lane 2026-08-25 14:59:09 -06:00
Drew T ebb83dd9ab chore(decomp): commit in-tree banked work before the serial lane 2026-08-25 14:57:13 -06:00
Drew T b0587c573e fix(idiom_serial): main is never a target, and a contended index is not a dirty tree
The serial lane's first-ever run on tells died 0 seconds in, on both counts:

  [1/6] func_8017D1C0 @ ov_SC06_027 · 118 ins  -> drafting
  [2/6] func_8001382C @ main · 103 ins         -> could not commit — refusing on a dirty tree

MAIN IS NOT A SERIAL-LANE TARGET. main gates through gate_main on the main lane's own cadence
(a clean whole-EXE rebuild with bisection), so this lane cannot bank it however good the draft
is — a main target burns a slot to learn that. md_* was already excluded for jtbl; main never
was, because the lane predates main being in the atlas at all.

A CONTENDED INDEX IS NOT A DIRTY TREE. Six campaign lanes plus the re-gate runner commit
continuously, so `git commit` here loses the index.lock race routinely — twice today it was a
STALE lock blocking every lane for 8 and 21 minutes. The lane refused over a tree that was
fine. It now retries the commit six times at 5 s before concluding, and the refusal is
reserved for FOREIGN DIRT it must not adopt — which is the property that rule was protecting.

Verified: pick_targets('extend-tell', 8, 80) now returns 8 overlay targets, no main.

Also in this commit: MAX_BINS 160 -> 50 in the drafter shell. Wave size was the right lever at
50% conversion (dd: 217 banked of 422 gated in 39 min); at 5% it is dead weight — dq banked 8
of 167 gated and took 98 MINUTES, while four drafted waves queued and the free-ox fleet sat at
14 agents / 10 req/min. At this conversion a 150-card wave banks what a 430-card wave banks,
in a third of the gate.
2026-08-25 14:57:13 -06:00
Drew T a372b648ef chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 14:51:13 -06:00
Drew T 04bb99d370 feat(decomp): ox wave dq overlays — 8 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 14:50:48 -06:00
Drew T 98085269b1 docs(cookbook): §278-§282 — wave cf distilled, 4 new laws from the post-429-fix wave
34 candidates: 13 COVERED · 8 ADDENDUM · 4 NEW · 9 REJECT.

  §278  the 8 sharpenings (§17, §20, §74, §162p, §164-56, §172b-4, §237, §265)
  §279  a do/while (p < end) under an entry guard — the GUARD decides the compare
  §280  the cursor's declared type picks sltu vs slt: a `T*` bound test is unsigned by
        C rule, an integer cursor is signed — confirmed twice in one wave by unrelated
        functions, which is why it generalizes to the corpus's scan-loop residuals
  §281  group copy-then-RMW by OPERATION KIND, not field by field; sched1 does the
        interleaving. §213 explicitly disclaimed this shared-pseudo case — a named gap now filled
  §282  gcc's own loop reversal places the reversed counter's init AFTER the hoisted
        movables, a position no hand-written down-count loop can reach — resolves a class of
        "prologue order" residuals that looked unfixable from the C side

VERIFICATION, both directions (R14/G3). Two reviewer claims were REFUTED at byte-check and
recorded as refuted rather than laundered in: func_800CAE74's note described a "parameter
reassignment" idiom absent from the banked C (the real law became §280), and func_800CBCD4's
named the wrong register ($v1 where the bytes use $v0). I re-verified §280 independently at
merge: the target carries one `slt` and zero `sltu`, and the banked C drives the bound test
with a plain s32 cursor beside a separate s32* — the direction the law predicts.

Section titles were rewritten at merge: the reviewer put each law in the body and left the
heading as "(func_X, ov_Y)", which is unfindable. A section is written to be GREPPED, so the
title states the law.

Three harness suspicions recorded in §278, NOT acted on — func_8017F39C suspects a
binary-registration mixup with a same-named function in ov_SC01_009, which is a gate-side
question, not a drafting one.

Index: 824 sections. Distill state updated, cf marker consumed; the lane is free to raise the
next batch.
2026-08-25 14:42:01 -06:00
Drew T 9b407f5441 feat(decomp): main lane m37a — 1 banked
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.

  func_80028F78
2026-08-25 14:37:29 -06:00
Drew T f96ad0bd6d chore(decomp): commit in-tree banked work before the serial lane 2026-08-25 14:35:50 -06:00
Drew T 9b74262697 chore(decomp): commit in-tree banked work before the serial lane 2026-08-25 14:35:50 -06:00
Drew T e7353d63de feat(tells 6.3): put the TELL COUNTS on the card and in the prompt
A tell-lever card named a lever and then made the agent go find its sites: it said
"extend-tell" and nothing about where or how many. atlas_features already counts the
detectors per function into .run/feat.<bin>.jsonl at atlas time, so this is a JOIN, not a
computation — one dict load per binary in the wave.

build_wave_atlas attaches 'tells' {extpair, dupselect, magic_div, sign_lh, sign_lb} to every
card that has a nonzero one — not just tell-lever cards, because a sll/sra pair site or a
repeated select is worth knowing whatever lever drew the card. Measured on a live draw: 85 of
185 cards carry counts.

api_agent._fuel renders them as a CHECKLIST rather than a hint, which is the point: the
counts come from the TARGET's own bytes, so a draft emitting fewer has provably missed sites
and should go looking before spending a turn elsewhere.

Verified: a card with {extpair 3, dupselect 2, sign_lh 1} renders all three with the zero
fields omitted; NEGATIVE CONTROL — a card with no tells renders no TELLS line at all.

Takes effect on the next draw + the next shard (api_agent is spawned per draft).
2026-08-25 14:35:36 -06:00
Drew T 98f7205a3a feat(decomp): ox wave dl overlays — 12 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 14:34:35 -06:00
Drew T d0da3f24f7 feat(decomp): main lane m36 — 4 banked
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.

  SYS_OBJ_1F64
  SsGetMute
  func_80028F34
  func_80031DEC
2026-08-25 14:23:25 -06:00
Drew T 073cf446ce feat(draw): generational TOP-OFF — every wave fills from the lowest generation up
Neither extreme was right. Ignoring generations lets a card that has failed five waves
compete with one nobody has ever drafted; filtering to a single generation starved the fleet
to 46 cards. So generation becomes the PRIMARY ORDERING and the existing mass/count criterion
breaks ties, at both levels of the assembly:

  * gate groups holding never-drafted cards rank ahead of all-retry groups (before the
    --max-bins truncation, so an untouched group is never cut for a fat retry group);
  * within a group, untouched cards are taken before retries.

Wave SIZE is untouched — only the order changes — so the fleet stays full while the scarce
never-drafted work always goes out first.

Verified on a live draw: gen0 2 · gen1 2 · gen2 14 · gen3 2 · gen4 3 · gen5+ 340. It took
EVERY card below generation 5 (all 23 available) and filled the remaining 340 slots from the
5+ pile, which is the whole point.

The gen0 count is 2 because wave dw drew the last 51 untouched skeletons an hour ago. That is
the campaign's real state: essentially everything drawable has now been drafted at least
once, and ~635 distinct skeletons have refused. The remaining work is levers, not draws.
2026-08-25 14:22:59 -06:00
Drew T 70c9fc13f5 chore(decomp): commit in-tree banked work before the next gate
Uncommitted src/ changes found at gate entry. These are banked functions from a lane that gates with commit=False, not residue — preserved, not reverted. Top-level src/*.c (main TUs) are excluded by construction (S59).
2026-08-25 14:22:54 -06:00
Drew T f9e5b1d865 feat(decomp): ox wave dt overlays — 9 banked
Committed before the main batch: gate_main reverts on failure and would take these with it.
2026-08-25 14:22:29 -06:00