curate: Migration: memories routed

This commit is contained in:
Drew T
2026-09-29 18:59:02 -06:00
parent b8a6cbc548
commit 376335fa04
16 changed files with 3527 additions and 13 deletions
+3
View File
@@ -0,0 +1,3 @@
# Memory Index
- [Generation legacy memories](genlegacy.md) — 83 entries
File diff suppressed because it is too large Load Diff
+47 -13
View File
@@ -5,23 +5,57 @@
"installed_at": "2026-09-30T00:53:41Z",
"lifetime_since": null,
"phase_ends_dir": "phase-ends",
"handoff_ctx": { "expert": 350000, "coder": 300000 },
"handoff_ctx": {
"expert": 350000,
"coder": 300000
},
"toast": "waiting",
"warmer": "on",
"follow_agents": "off",
"discord_webhook": null,
"rhythm": "autonomous",
"tier": "max5",
"legacy": { "phase_naming": "dotted" },
"credit": { "spill_chars": 20000, "note_max_age_s": 3600 },
"audit": { "file_chars_flag": 500000, "growth_flag": 0.2,
"noise_patterns": ["warning: in the working copy of", "CRLF will be replaced",
"usage: ", "No such file or directory"],
"router_ctx_flag": 300000 },
"install": { "source": "https://github.com/Druthulu/ProjectArchitect.git", "clone_dir": "pa3-src",
"update_check_hours": 24, "fetch_timeout_s": 8 },
"card": { "max_chars": 7000 },
"guard": { "spilled_read": true, "whole_plan_roles": ["review", "critic"], "tool_source_roles": ["expert"],
"whole_read_chars": 20000 },
"bench": { "window_max_pct": 80 }
"preset": "max20",
"legacy": {
"phase_naming": "dotted"
},
"credit": {
"spill_chars": 20000,
"note_max_age_s": 3600
},
"audit": {
"file_chars_flag": 500000,
"growth_flag": 0.2,
"noise_patterns": [
"warning: in the working copy of",
"CRLF will be replaced",
"usage: ",
"No such file or directory"
],
"router_ctx_flag": 300000
},
"install": {
"source": "https://github.com/Druthulu/ProjectArchitect.git",
"clone_dir": "pa3-src",
"update_check_hours": 24,
"fetch_timeout_s": 8
},
"card": {
"max_chars": 7000
},
"guard": {
"spilled_read": true,
"whole_plan_roles": [
"review",
"critic"
],
"tool_source_roles": [
"expert"
],
"whole_read_chars": 20000
},
"bench": {
"window_max_pct": 80
},
"memory_routed": "3.14"
}
+1
View File
@@ -14,6 +14,7 @@ Who: <!-- TODO DEVELOPER_NAME: no source found -->. Experience: <!-- TODO DEVELO
Preferences: recommendations, not questions. Plain-English recaps at phase end. A notification
whenever anything waits on them. Autonomy: <!-- TODO AUTONOMY_POSTURE: no source found -->. Notification channel: <!-- TODO NOTIFY_CHANNEL: no source found -->.
The developer pushes; agents never do. They ratify rules at the next planner session.
- drew-working-preferences: Drew's working style on BFM-decomp — autonomous-within-phases, ultracode effort, accepts recommended options, wants WSL/tooling decisions surfaced plainly
## Rhythm <!-- roles: router planner review discuss auditor curator -->
autonomous <!-- autonomous: the router runs the phase end to end, stopping only at the two gates.
+34
View File
@@ -0,0 +1,34 @@
# C0509 — fleet-tool-parallelism-defaults
tags: legacy,memory · date: 2026-09-29 · phase/task: - · origin: legacy memory fleet-tool-parallelism-defaults.md
Drew (2026-08-07): "if these work successfully I want a new memory and defaults created so we forever
use these speedup techniques." Measured on `dedup_propagate` (S46): **24 min → 11.4 min, same 29
functions, +62 MORE member instances** (R22 213/213 both ways).
**The four defaults for any fleet-wide tool in this repo:**
1. **Return EVERY verdict a sweep already computed.** `gate_all` byte-gated all 141 overlays and
returned only the first failure, so the recovery loop paid a full sweep to rediscover each of the
next 137. Batching them took convergence from ~138 rounds to 1–3. Same builds, same determinism.
2. **PROCESSES for CPU-bound work; threads ONLY for subprocess waits.** A `ThreadPoolExecutor` over
138 "independent" searches kept **0–4 builds alive at load 3** — the work was regex over 15k-line
files, so every thread queued on the GIL. The same code in a `ProcessPoolExecutor`: **14–29 builds,
load 34.75** on 32 cores. Threads are right for the byte-gate (each is a `subprocess.run`), wrong
for anything that parses or rewrites source.
3. **Longest-first scheduling.** `ex.map` starts work in list order, so the giant overlays landing last
left 31 cores watching one build for ~25 s of every 56 s sweep. Sort by source size descending;
re-sort results into the caller's order so the verdict stays bit-identical.
4. **Per-item search beats lock-step sweeps** when items are independent — and *say why they are*.
Here: the shared header carries every macro regardless, so writing it once up front leaves each
overlay owning only its own `.c` files and `build/<bin>/`. That cuts BUILDS, not just overlap.
**Two traps this exposed, both worth checking in any tool about to run parallel:**
- **A fixed temp path** (`.run/dpcc/t.c`) is a correctness bug the day something runs concurrently —
the same fake-isolation class as `match_one`'s shared `--work` dir. Make it per-call.
- **A pool submitted all at once shares no learning.** Every worker got an empty suspect list and paid
a full bisection. Seed with one item in-process first, then fan out with the result.
**Prove it, don't assume it:** the acceptance test was a *regression*, not a stopwatch — revert to the
pre-run state, re-run the identical command, and require the same functions and a byte-verified fleet
(R22). It came back faster AND with 62 more instances banked, which is how the over-exclusion in the
old path was found at all. See [[decomp-accelerator-ledger]] (A8) and `docs/accelerators.md`.
+1
View File
@@ -958,3 +958,4 @@
- **§498** — ★★★ — A CARVE THAT DROPS THE BINARY'S `--pre` CLAUSE, AND A GATE THAT IGNORED THE EXTRACT'S EXIT CODE: HOW A BYTE-CORRECT DRAFT WAS BOOKED "DIFF" (P32 T1a; resident `func_800D128C` BANKED 243 ins after two instrument fixes) <sub>L36728</sub>
- **§499** — ★★★ — A NEVER-ONBOARDED PAYLOAD PINS ITS OWN BASE STATICALLY, AND THE FIRST BUILD CANNOT (P32 T2a/T2b: the parked five onboarded, 213 → 218 binaries, 0 UNCLAIMED) <sub>L36758</sub>
- **§500** — ★★★ — THE T3 WAVE HARVEST (P32 T3, 2026-09-05): 31 one-agent-per-function drafters → 20 MATCH / 9 NEAR / 2 FAIL, every closer, two NEW named gcc mechanisms, and the wave-process defects <sub>L36799</sub>
C0509 | fleet-tool-parallelism-defaults | legacy,memory | 2026-09-29 | - | legacy memory fleet-tool-parallelism-defaults.md
+6
View File
@@ -45,3 +45,9 @@ p37-s106-2026-09-12-the-type-census-and-the-struct-map-phase-37-t1.md | §P37 S1
p37-s106-2026-09-12-the-probe-and-the-rewrite-engines-first-form-phase-37-t2.md | §P37 S106 — 2026-09-12: the probe and the rewrite engine's first form (Phase 37 T2) | 24
p37-s107-2026-09-12-the-rewrite-engines-full-form-the-layout-library-and-the-linked-oracle-phase-37-t3.md | §P37 S107 — 2026-09-12: the rewrite engine's full form, the layout library and the linked oracle (Phase 37 T3) | 51
retired-tools-toolssunset-phase-335.md | Retired tools (`tools/sunset/`, Phase 33.5) | 198
SETUP.preamble.md | SETUP.md — Environment Setup & Daily Operations Reference | 8
disc-dump-location.md | disc-dump-location | 9
gate-main-only-with-gate-main.md | gate-main-only-with-gate-main | 71
mcp-renames-dont-persist-use-applysymbols.md | mcp-renames-dont-persist-use-applysymbols | 21
ops-setup-decomp.md | extract the medium and verify against the committed manifest | 68
wsl-disk-capped-75gb.md | wsl-disk-capped-75gb | 34
+9
View File
@@ -0,0 +1,9 @@
# disc-dump-location
The legally-owned BFM USA BIN/CUE dump is at `/mnt/z/Storage/git/BFM-decomp/Brave Fencer Musashi (USA)/` (Windows `Z:\Storage\git\BFM-decomp\Brave Fencer Musashi (USA)`). Drew corrected me here on 2026-06-13 after I guessed a wrong `/mnt/z/Games/Emu/...` copy.
- **Track 1** `...(Track 1).bin` = 364,846,944 B — the MODE2/2352 data track holding the ISO9660 volume + every `.CD` file.
- Tracks 2–4 are CD-DA AUDIO (each `(Track N).bin` = 150-sector INDEX 00→01 pregap + audio). The 3 `.DA` ISO files (ST01_13A.DA=Track2, ST01_13B.DA=Track3, DUMMY_DA.DA=Track4) point into them.
- **Scope change 2026-06-13 (Drew):** stage ALL 4 tracks + the `.cue` to ext4 `disks/`, and extract the `.DA` files too. `extract.py` extracts them as raw 2352-byte/sector audio (sizes differ from the 2048-based ISO entry). Supersedes the earlier "Track 1 only / .DA out of scope" plan.
- Per H2 the dump is read once, **not built against** — copied to ext4 `disks/` (gitignored) for fast iteration instead of reading repeatedly over the slow 9P `/mnt` bridge.
- The raw dump is never committed (gitignore firewall, survives even the H1 relaxation in [[rom-content-git-policy]]).
+71
View File
@@ -0,0 +1,71 @@
# gate-main-only-with-gate-main
**`main` is gated ONLY by `tools/gate_main.py`** — baseline assert → substitute the whole slate →
`make extract` → `make build` → compare SHA1, with a bisect when the batch fails. `parallel_gate` now
REFUSES `binary == 'main'` in code, so this cannot be re-learned by accident.
**Why:** main's `make extract` runs the EXE-only `psyq_integrate` + `ld_interleave` steps, which
REWRITE the linker script. `gate_stage` (and therefore `parallel_gate`, whose worker *is*
`gate_stage`) builds incrementally, so it re-runs that on an already-rewritten `.ld`. S58 recorded the
false-DIFF direction (105 competent main drafts thrown away). S71 hit the **false-PASS** direction,
which is worse: `parallel_gate` reported "11 banked" on main, the merge was committed, and R22 came
back 212/213 — the tree did not compile from clean, and once the declarations were reconciled it was
still not byte-identical. All 11 re-gated individually: **11 of 11 REJECTED**.
**How to apply:**
* Any main draft → `gate_main.py`. Run `--assert-baseline` first; it is cheap and it separates "my
draft is wrong" from "the tree was already red".
* **Count banks from the SOURCE** (the `INCLUDE_ASM` stub is gone), never from the tool's slate.
`gate_main` printed "BANKED 5 of 6" when 4 had applied — `len(good)` is *what we decided to keep*,
not *what was substituted*. Fixed, but the principle is general: this was the 4th tool in one
session reporting a derived number as a measured one.
* A draft that **contains its own `INCLUDE_ASM`** is a silent no-op — substituting it restores the
stub, the build is trivially byte-identical, and it counts as a bank. `gate_main` refuses these now.
* **A tool that wraps another tool inherits its refusals.** Every constraint documented on
`gate_stage` binds `parallel_gate`, `harvest_verify`, and anything else that shells it — encode it
as a refusal in the WRAPPER, not a paragraph in the callee.
**CORRECTED S72 (2026-09-02) — that "11 PROVEN gate-rejects" line was WRONG; NONE of them is a body
reject.** The S71 re-gate ran through an ad-hoc script (`.run/S71_main_bisect.py`), not `gate_main.py`,
so the decl pre-check never ran — and **all 11 are switch functions**. main had carried exactly ONE
`.rodata` carve since Phase 7, so a drafted switch DOUBLE-EMITS its jump table, the image grows
(+28/+52/+76/+84 measured) and ~332 symbols shift. Extending the carve to the contiguous span
`0x80072A38-0x80072C70` banked `func_8001A114`, `func_8001AAD0`, `func_8001AF34` **byte-identical in
14 s**; the other 8 sit in uncarved spans B/C and need `src/800.c` split. Cookbook §426/§427.
**S75 CORRECTION (2026-09-02) — THAT VERDICT LINE HAD A CLASS THAT COULD NOT FIRE.**
`main_diff_locate.classify()` gained a `TABLE REJECT` case in S72 precisely because BODY/PLUMBING had
mislabelled a table failure. On main it was **unreachable by construction**: it summed bytes whose
object string contains `(.rodata)`, but main's `section_order` is `[.rodata, .text, .data, .bss]` — its
rodata sits BELOW `.text` and its jump tables live in **`.data`** objects
(`build/asm/data/63C4C.data.o(.data)`). It also demanded PURITY (`ro == outside`), so a few bytes of
perturbed code dropped the verdict through to PLUMBING anyway.
Cost: **`SaveLoadRoutine` (1,165 ins — the largest open function in the project, 9.2% of all remaining
work, carried as the §434 WALL) has a BYTE-IDENTICAL body.** Gated alone, twice, the tool said
"PLUMBING REJECT … route to fix_arity_callers -> cast_self_callers"; the chain was run twice and fixed
nothing, because the real split is **3,787 of 3,989 bytes (94.9%) in `.data` jump tables vs 202 (5.1%)
in `.text`**, and the built image is **4 bytes SHORTER than retail** (§446's first diagnostic).
It is a CARVE — and it sits in `src/800_b.c`, i.e. **span B, exactly where the S72 note above predicted
the remaining 8 would sit.** Fixed: table bytes counted in `(.data)` OR `(.rodata)`, and the test is
DOMINANCE (>=60%) not purity, naming which part is carve and which is declaration. NC: 5 of 6
pre-existing verdict shapes unchanged. Cookbook §447.
**THE LAW, and it generalizes past this tool: a verdict class that CANNOT FIRE is worse than one that
does not exist** — it converts "I don't know" into confident, specific, wrong advice that then
consumes sessions. When a verdict names a subsystem, check that subsystem owns the MAJORITY OF THE
BYTES before acting on it.
**ALSO S75: gate main with `--no-propagate`.** `gate_stage`'s tail runs
`dedup_propagate --auto-from <bin>`, which sweeps the WHOLE binary rather than the function just
banked; a binary with an unswept pile stalls the gate 30+ minutes (`ov_SC01_005` held 557). Drew's
decision 2026-09-02: leave the ~12,000-copy hygiene backlog (all already matched, orthogonal to
completion %) and gate `--no-propagate` by default. See [[dedup-backlog-leave-it]].
**A main gate now says WHERE, not just that.** `gate_main` preserves the red image + map under
`.run/gate_main_fail/` BEFORE the R40 baseline control rebuilds over them (that ordering bug is why
nobody could ever localize a main failure), then prints **BODY REJECT / PLUMBING REJECT / MIXED** via
`tools/main_diff_locate.py`. **Read that line before recording any main verdict.**
Related: [[standalone-match-is-not-bankable]] · [[verify-blast-radius-not-just-defect]] ·
[[silently-narrowed-tool-scope]] · [[check-against-a-known-true-case]] (cookbook §414)
@@ -0,0 +1,21 @@
# mcp-renames-dont-persist-use-applysymbols
**Fact (2026-09-04, P31 S78):** 47 renames made through the headless GhidrAssistMCP server
(`batch_rename`/`rename_symbol`, all reported success) were GONE after `tools/ghidra_mcp_stop.sh`
("Save succeeded", DB grew 9.9→10.4 MB). R9's read-only re-open caught it. Cause not isolated (single
instance, normal stop sequence; Phase 3 had verified the same flow). The fix that worked first time:
`tools/ghidra_apply_symbols.sh [PROG] [symbols…]` → `tools/ghidra_scripts/ApplySymbols.java`, a headless
postScript that mirrors `config/symbols.us.txt` into the program with a real save (73 renamed,
R9-verified ×4). Also: gate worktrees carry no `.run/obj40`, so main's LINKED (SDK-object) build path is
only ever checked by an in-tree `make build BINARY=main` — it had been red at HEAD for a day unnoticed.
**Why:** the curated text file is the source of truth (R15); an MCP write that never persisted looks
identical to one that did until a fresh open reads the DB (R9). A gate that always takes the fallback
path is blind to the path the contract cares about (R34).
**How to apply:** for symbol renames, edit `config/symbols.us.txt` (no `name:` tokens in comments —
splat parses them as attributes), run `lint_symbol_refs.py` and read its WHOLE output (verbatim
`__asm__` bodies spell `\tfunc_X`), then `ghidra_mcp_stop.sh` → `ghidra_apply_symbols.sh` →
`ghidra_mcp_verify.sh <addr> <name>`. After any change to `psyq_identify`/`psyq_integrate`/the splat
yaml: in-tree `make build BINARY=main` WITH `.run/obj40`, then the fresh-extract fallback WITHOUT it.
See [[exonerate-the-instrument]], [[check-against-a-known-true-case]].
+34
View File
@@ -0,0 +1,34 @@
# wsl-disk-capped-75gb
The WSL2 disk (`/dev/sdd`, `ext4.vhdx`) was resized from WSL's 1 TB default down to
**75 GB (73 GiB usable)** on 2026-08-29. As of then: 38 GB live, ~32 GB free.
This is a HARD ceiling — ext4 cannot grow past it.
**Why it was done:** the vhdx had bloated to 111.6 GiB while holding only 38 GB of
live data, leaving C: with 11 GB free. Root cause: WSL creates a 1 TB ext4 filesystem
inside a dynamically-growing vhdx, and ext4's Orlov allocator deliberately places each
new *directory* in a block group with above-average free space. The `.run/wave_*`
pattern (hundreds of new top-level dirs) scattered data across 8192 block groups —
live data was smeared over 820 GiB of address space in 1363 groups. A dynamic vhdx
can only release whole 32 MB blocks, and nearly every touched block still held some
live data, so `compact vdisk` reclaimed ~7 GB of a possible 66. Zero-fill and sparse
VHD both fail or are unsafe here. `wsl --manage <distro> --resize` was the fix:
resize2fs relocated 33 GB down from above the boundary and packed data into 553 groups.
Result: vhdx 111.6 -> 53.3 GiB, C: 11.2 -> 69.9 GB free.
**Why it matters:** `.run/` is ~20 GB and grows every wave. With only ~32 GB of
headroom, a big fleet run can now fail with ENOSPC instead of the disk silently
growing. That failure will look like a mysterious mid-wave build error.
**How to apply:**
- Watch `df -h /` before launching large waves; prune old `.run/wave_*` when tight.
- The vhdx will still drift 53 -> 75 GiB over time (spreading is confined, not fixed).
That is expected, not a new problem.
- Reclaim recipe (works now that data is packed): `sudo fstrim -av` -> `wsl --shutdown`
-> diskpart `compact vdisk`. Do NOT bother with zero-fill.
- Need more room? GROWING is the supported direction and is safe:
`wsl --shutdown` then `wsl --manage Ubuntu-24.04 --resize 150GB`.
- Sparse VHD (`--set-sparse`) is gated behind `--allow-unsafe` by Microsoft for
data-corruption risk — do not use it.
Related: [[no-tmp-project-local-data]] (.run/ is the gitignored runtime scratch dir).
+8
View File
@@ -119,3 +119,11 @@
<!-- G63 | Outward text is written by a person | | active | | | source: decomp-architect -->
<!-- G64 | No automated traffic against community infrastructure | | active | | | source: decomp-architect -->
<!-- G65 | Agents assist; a person owns | | active | | | source: decomp-architect -->
L1 | offline-tooling-first | legacy,memory | active | legacy memory offline-tooling-first.md
L2 | lever-removal-is-a-tracked-series | legacy,memory | active | legacy memory lever-removal-is-a-tracked-series.md
L3 | decomp-accelerator-ledger | legacy,memory | active | legacy memory decomp-accelerator-ledger.md
L4 | verify-blast-radius-not-just-defect | legacy,memory | active | legacy memory verify-blast-radius-not-just-defect.md
+30
View File
@@ -0,0 +1,30 @@
# L1 — offline-tooling-first
id: L1 · group: - · status: active · tags: legacy,memory · origin: legacy memory offline-tooling-first.md · added: 2026-09-29
**Standing goal (Drew, 2026-07-21): get as much as possible working as offline tooling.** When a
recovery, diagnosis, or integration step is *computable*, it belongs in a deterministic tool that runs
with zero tokens on every future draft — not in an agent prompt, and not in a search.
**Why:** the measured economics keep pointing the same way. Phase 15: a 50-agent wave added +0.36% while
deterministic recovery added +2.67% for ~0 agent tokens. Phase 29 (2026-07-21): the permuter's problem was
*targeting*, not a missing transform — ~92% of its CPU was aimed at residuals a search provably cannot
close, fixed for free by a deterministic classifier ([[matching-is-solved-integration-is-the-bottleneck]]).
Every hour of agent drafting is spent once; every ladder stage is spent once and paid forever.
**The two engines — keep them separate:**
1. **Deterministic recovery** (`tools/gate_stage.py`'s ladder): computable fixes — decl/arity/cast
reconciliation, type-lift, jtbl isolate+re-carve. Applied always, free, byte-gate arbitrated.
2. **Search** (decomp-permuter / ILS): only for residuals that are NOT computable — regalloc and
schedule permutations where the answer must be explored.
Putting a computable fix into the search is a category error: it burns CPU rediscovering a derivable
answer. And per cookbook §60b, raising a search-closer's yield is at least as often about *refusing it
unreachable work* as widening its mutation set.
**How to apply:** when a draft fails to bank, ask "is this residual computable?" before reaching for
agents or the permuter. If yes → a ladder stage (and check whether the logic already exists elsewhere —
`normalize_self_decls` and jtbl auto-isolate both existed in `family_sweep`/`jtbl_family_bank` while
`gate_stage` lacked them; R33 says one implementation, two callers). Any ladder stage that mutates SHARED
state must undo by snapshot-restore, never an inverse transform, and be verified fleet-wide (R22) — a
single-binary gate cannot validate a fleet-wide edit (cookbook §61; it cost 138 broken binaries once).
Track the LLM-free fraction and make raising it the objective (`docs/hindsight-study.md` §7,
`tools/burndown.py`).
+23
View File
@@ -0,0 +1,23 @@
# L2 — lever-removal-is-a-tracked-series
id: L2 · group: - · status: active · tags: legacy,memory · origin: legacy memory lever-removal-is-a-tracked-series.md · added: 2026-09-29
Drew (2026-09-09, mid-Phase-36): the pins and compiler hints are not just work to finish — **their count over time is a
deliverable**. Keep `docs/levers.md` current after **every task that changes the count**, with
`tools/lever_progress.py --snapshot "<task>"` (appends a milestone row to `docs/lever-progress.tsv` and re-renders the
document's generated block; `--check` refuses a stale series). Mirror the story-relevant numbers into
`phase-ends/CURRENT_PHASE.md` as the phase goes, so the retrospective is built from the record and not from memory.
Four audiences, all named by Drew: the **post-100% chart**, the **project story**, the **wiki** (a Levers page), and the
**`decomp-architect/` package** — which needs the taxonomy plus an answer to *"what should we have done from day one to stop
this creeping up on us post-100%, or is leaving it to a post-100% cleanup actually optimal?"*
**Why:** the only phase that ever counts the levers is the phase that removes them, so if the series is not captured while
the work happens it cannot be reconstructed afterwards — a census is a moment. The measured answer so far (P36): **38% of
the class A/B population came off with no understanding at all** (strip, compile, compare), which is the evidence for the
day-one rule *ban the silence, not the lever* — a lever is allowed but is a marked, ledgered, published debt from the first
bank, with a one-compile bank-time trial that would have refused a third of them where the context was still hot.
**How to apply:** at every task close run the census then `lever_progress --snapshot`; keep §5 of `docs/levers.md` (the
prevent-vs-defer argument) written from the generated numbers, never typed; feed each new rung/recipe and each measured
yield into §4. Related: [[matching-cookbook]] (§454 carries the mechanism), [[decomp-accelerator-ledger]],
[[phaseend-verbosity-for-the-retrospective]], [[project-endgame-deliverables]].
+20
View File
@@ -0,0 +1,20 @@
# L3 — decomp-accelerator-ledger
id: L3 · group: - · status: active · tags: legacy,memory · origin: legacy memory decomp-accelerator-ledger.md · added: 2026-09-29
Drew (2026-08-07): we are building a **Claude Code decomp workflow** to reuse after BFM ships. So
whenever something is found that would have made a lot of *previous* work much faster had we known it
sooner, record it — what it is, when we found it, when we *could* have, and what it would have saved —
in `docs/accelerators.md`. A new decomp project should get that wisdom on day one instead of at phase 23.
**Why:** this project repeatedly found its biggest levers late (the byte-gate harvest at phase 12, dedup
propagation at 11–15, the gcc codegen map at 23, the tracker's addressing blind spot at 30). The
per-phase PhaseEnds record *what happened*; they do not answer "what should phase 1 of the NEXT game
do differently." That is a separate, deliberately-maintained artifact.
**How to apply:** when a discovery lands, ask "would this have changed earlier work?" If yes, add an
entry the same session (R30 timing). Distinguish honestly between a lever that was *available* earlier
and one that structurally could not exist yet (needed the fleet onboarded, the compiler pinned, etc.) —
the second kind belongs in the ledger too, marked, because its *prerequisite* is the real advice.
Feeds [[project-endgame-deliverables]] (the public "how to AI-decomp" wiki) alongside
`docs/decision-log.md` (R31, the why-behind-pivots).
+29
View File
@@ -0,0 +1,29 @@
# L4 — verify-blast-radius-not-just-defect
id: L4 · group: - · status: active · tags: legacy,memory · origin: legacy memory verify-blast-radius-not-just-defect.md · added: 2026-09-29
**Verify the BLAST RADIUS, not just the DEFECT.** (Phase 26, 2026-07-14 — I got this wrong in front of Drew.)
A coverage audit reported that `progress.py` under-counted ~243k instructions because `classify()` reads a K&R
definition as a forward declaration. I did the R14 thing — reproduced the mechanism against the bytes, confirmed
it was real, measured 400 banked instances in that shape — and then told Drew our headline numbers had been
under-reporting our progress.
**Wrong.** The headline metrics come from `weighted_metrics()`, which never calls `classify()` at all: it tests
"is this function still an `INCLUDE_ASM` stub?", so it is structurally immune to the bug. The published
instruction-weighted and distinct-code numbers were correct all along; only a secondary function-count report was
wrong.
**Why:** *"this tool is broken"* and *"this number is wrong"* are different claims requiring different evidence.
A confirmed mechanism proves nothing about consequence. Before reporting impact, trace the defect to the actual
consumer and check whether that consumer is even on the affected path.
**How to apply:**
- After confirming a defect, ask *"who consumes this, and does the consumer use this code path?"* — then verify
THAT, not the defect, before quoting an impact number to the owner.
- **A null result where you predicted a large effect is a refutation — chase it, do not wave it off.** The fix
moved the numbers by +376 instructions when I had predicted +190,000. That gap was the whole story and it
would have been trivially easy to dismiss as noise (or worse, to report as "the fix worked, the numbers moved").
- Do not amplify a sub-agent's impact claim (R14) — the auditor conflated "classify() is blind" with "the metrics
are wrong", and I propagated it as fact while lecturing about unverified oracles.
Related: [[derive-from-invariants-not-reparsing]].