docs(phase-q): fold Q-W0 sign-off into PLAN/CONTEXT — all 59 dispositions settled; Q-W2v wave, T4-18 VST placement, ~600 ceiling + structural heuristics, W3 riders (wav_codec, ICaptureBackend, Q-9)

This commit is contained in:
2026-07-28 18:44:48 -04:00
parent a4571cc074
commit 78a214b247
2 changed files with 431 additions and 105 deletions
+248 -65
View File
@@ -220,8 +220,21 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that
> downstream Q-W1..Q-W6 points; fixes that Q-W0 classifies fix-now are remediated in Q-W0 (or folded
> into the wave that already touches the file), **not** deferred silently into the structural waves.
>
> **Q-W0 SIGN-OFF: COMPLETE (Daniel, 2026-07-28).** The audit ran as four parallel tracks (T1 DSP,
> T2 architecture, T3 env-coupled constants, T4 sizing/placement — **59 findings**; report
> `docs/product/code-quality-audit.md`, appendices `docs/product/audit-notes/`), and **all 59
> findings' dispositions are approved as proposed.** The Q-W1 sub-gate is satisfied **once the six
> approved fix-now remediations land** (in flight on branch `pq-w0-fixes`, Q-W0-scoped): T1-01,
> T1-03, T1-09, T2-01(a), T3-01, T3-03. The audit's §3 plan reshape is **folded into the waves
> below** (Q-W2 6→8 seams; NEW wave **Q-W2v** parallel with Q-W2; Q-W3 3→4 hoists + riders; Q-W5
> + the ext-state-loop dedupe), and its §4 decision list is settled — see the settlement block
> below. The Q-11 question is answered by the audit: the SOLA pitch engine is **sound — no
> technique replacement warranted**; every pitch finding is a bounded in-technique fix or a
> documented operating limit.
>
> **Settled (Q-1, this-doc):** the phase is **`Q` (Quality)**; point-id family `Q1..Qn`, wave
> prefixes `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg).
> prefixes `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg; **+
> `Q-W2v`**, the VST god-module wave added at the Q-W0 sign-off, 2026-07-28).
> **Settled (Q-10/Q-11, Daniel 2026-07-27):** Q-10 audit-report home = a **committed doc**
> (`docs/product/code-quality-audit.md`, not a tracked issue list); Q-11 pitch-remediation depth =
> **defer to findings** (default document-and-defer; weigh a bounded OLA fix before a technique
@@ -238,6 +251,25 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that
> `BankModel`; the JSON `Parser`→`json::Reader`/`Writer`), leave the merely-quirky (rec);
> Q-9 align the `capture_realtime`/`realtime_record` shell↔core word order during W3 (rec: yes).**
>
> **SETTLED (Daniel, 2026-07-28 — with the Q-W0 sign-off; the REC record above kept as history):**
> **Q-5 SETTLED** — split to **seams-by-responsibility with the T4 seam lists adopted**
> (`bank_panel` 6→8 seams adding `panel_layout` + `panel_drag`, T4-01; `capture_orchestrator`
> further split with `capture_batch`, T4-02), and the **~600-line file ceiling is an acceptance
> criterion on every split wave** — seams are the method, the ceiling is the bar; arbitrary
> bisection to hit the number is rejected. **Q-6 SETTLED: in scope, last wave, as planned.**
> **Q-8 SETTLED: both renames** — `BankIndex`→`BankModel` (W1) and the JSON parser minted as
> `json::Reader`/`json::Writer` (W1); additionally from the audit, **`ICaptureBackend` is deleted
> in Q-W3** (T4-26 — one deriver, zero polymorphic call sites; the CLAUDE.md/CONTEXT "two
> backends behind one interface" correction **rides Q-W3 itself**, recorded as a rider — the docs
> are not edited before that wave). **Q-9 SETTLED: yes** — align to stem `capture_realtime`,
> shell suffixed, during W3. **VST placement (audit §4a) SETTLED: T4-18** — `src/vst/` integrates
> into the single `core/`/`shell/` top split as `core/instrument/{engine,map,ui}` +
> `shell/instrument/` (Q-3 directory map updated in CONTEXT.md §Phase Q). **WAV/RIFF
> consolidation (audit §4e) SETTLED:** a named rider on **Q-W3** — one pure **`wav_codec`** owner
> (walker + layout + build + patch), absorbing the T4-10 ingest extraction. **Q-W2v scheduling
> (audit §4f) SETTLED: parallel with Q-W2** (different artifact, zero file overlap; the serial
> "Q-W7" alternative set aside).
>
> **HARD CONSTRAINT — performance (see CONTEXT.md §Phase Q, `docs/product/code-organization.md`
> §3).** The reorg must cost **zero runtime.** On the three hot paths — `peaks` envelope
> compute, audition/preview, the realtime-capture tick — **no added virtual dispatch, no
@@ -247,6 +279,21 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that
> an acceptance criterion on every point: *a split that would add a hot-path indirection is out
> of scope — rework it or drop it.*
>
> **STRUCTURAL HEURISTICS (Daniel, 2026-07-28 — acceptance criteria on every wave; these
> *generalize* the three-hot-path guardrail above, they do not replace it):**
> (1) **More directories is a must, more files is good, ~600-line file ceiling** — SRP applies to
> namespaces, encapsulation, and file organization alike. The ceiling is the *bar*, the audit's
> named seams are the *method*: a file landing over ~600 needs a responsibility seam, not an
> arbitrary bisection; a documented hot-path exception (`sampler_core.cpp`, T4-14/T4-27) is
> legitimate, silent overshoot is not.
> (2) **Templates are the right tool for compile-time dedup — use them where earned** (the LE
> byte codec `putLE`/`readLE`, T4-20), not for name-only unification (the rect family is one
> **concrete** `ui::Rect`, NOT a template — T4-21's ruling).
> (3) **SOLID is great but saved CPU is better** — no dispatch-stack blowouts *anywhere*, not
> just the three named hot paths; prefer static polymorphism where types are compile-time-known
> (T4-27's warning is the canonical case: a by-class `sampler_core` split would put virtual
> envelope `tick()`s on the per-voice-per-sample path).
>
> **NAMING dimension (added 2026-07-27; grep-verified audit in `docs/product/code-organization.md`
> §2b).** Beyond giving symbols a directory + namespace *home* (Q-3/Q-4), Phase Q also gives
> poorly/inconsistently-named symbols a consistent *name*, against the same Vital bar. The audit
@@ -265,9 +312,14 @@ panel adopts knob deck + curve popup, `param_slider` slider rows retired on that
> per-module static-lib + per-module test-executable seams already draw the module boundaries;
> a file move + namespace change is mechanically verifiable — `ctest --test-dir build` is green
> or it isn't. **Green-CTest-at-every-point is an acceptance criterion.** Big-bang is rejected;
> the reorg is risk-ordered waves (W1 safe opener → W2W5 god-module splits → W6 OCP finish).
> the reorg is risk-ordered waves (W1 safe opener → W2/W2vW5 god-module splits → W6 OCP finish).
## Q-W0 — pre-restructure functional + DSP quality audit (runs FIRST; gates Q-W1)
**STATUS (2026-07-28): audit COMPLETE, triage COMPLETE, sign-off COMPLETE.** The findings report
is committed (`docs/product/code-quality-audit.md`; track appendices in
`docs/product/audit-notes/` — T1 DSP, T2 architecture, T3 env-constants, T4 sizing/placement; 59
findings). Daniel approved every disposition 2026-07-28. **The Q-W1 sub-gate is satisfied once
the six approved fix-now remediations land** (final point below; in flight on `pq-w0-fixes`).
**Goal:** Before a single structural point moves, perform a **thorough static/functional audit** of
the codebase and produce a **written, triaged findings report**. This is the *functional-correctness
and algorithm-quality* complement to the grep-verified SOLID/naming audit that already grounds
@@ -322,19 +374,33 @@ before/after listening or null check. **The gate to Q-W1 is: triage complete + D
are a decision, not an omission.
- [ ] **Sign-off gate.** Daniel reviews the triage and signs off on each disposition. Q-W1 does not
begin until this is done; fold any new/reshaped downstream points the audit surfaces into
Q-W1..Q-W6 before starting them.
Q-W1..Q-W6 before starting them. **DONE (Daniel, 2026-07-28): all 59 dispositions approved as
proposed; the §3 plan reshape and §4 decisions are folded into Q-W1..Q-W6 + Q-W2v below.**
- [ ] **Fix-now remediations (approved 2026-07-28; Q-W0-scoped; in flight on branch
`pq-w0-fixes`):** T1-01 linked-lag stereo splice alignment, T1-03 Preserve prime bound +
immediate `freezeTail()`, T1-09 `declickR_` dead-state removal (rider on the `sampler_core`
edits), T2-01(a) provenance wire-cursor hardening backport, T3-01 gain-ramp seconds, T3-03
fade-ceiling seconds. Each lands with its module's CTest target green; the audible DSP fixes
with a stated before/after listening check. **Landing these closes Q-W0 and opens Q-W1.**
## Q-W1 — safe opener: extract `core/json` + impose the directory/namespace layout on clean modules
**Goal:** The zero-god-module-risk opener. Two moves: (1) extract a pure **`core/json`** module
(parser + serializer) and **delete the four hand-rolled `Parser`s** in `bank_model` /
`bank_book` / `view_mode_model` / `owned_manifest` (the single largest DRY+SRP violation, and
entirely off the hot paths); (2) impose the settled `core/`/`shell/`/`app/` directory layout +
sub-namespaces (`reasampler::model`/`view`/`capture`/`audio`/`ui`/`reclaim`/`version`/`json`) on
the **30 clean pure libs + the clean shells that need no splitting** — pure relocation, no logic
change. Proves the wave discipline (relocate + encapsulate, CTest-green) before any god-module
surgery. CONTEXT.md §Phase Q (json extraction; directory + namespace map).
**Verify:** CTest green at every commit. The four duplicate `Parser`s are gone, replaced by one
`core/json` consumed by all four models; round-trip serialization is byte-identical to before
(parser + serializer) and **delete the five hand-rolled JSON decoders** — the four `Parser`s in
`bank_model` / `bank_book` / `view_mode_model` / `owned_manifest` **plus `tail_control`'s fifth
decoder** (T2-02, Q-W0's undercount fix — the wave's one-JSON-path goal is not met without it) —
the single largest DRY+SRP violation, entirely off the hot paths; (2) impose the settled
`core/`/`shell/`/`app/` directory layout + sub-namespaces
(`reasampler::model`/`view`/`capture`/`audio`/`ui`/`reclaim`/`version`/`json` + the `instrument`
family) on the **30 clean pure libs + the clean shells that need no splitting + the ~20 clean
VST pure libs** (T4 §1.5, under the settled T4-18 `core/instrument/{engine,map,ui}` +
`shell/instrument/` shape) — pure relocation, no logic change. **Q-W0 additions (SETTLED
2026-07-28):** the shared `readFileBytes` pure helper (T2-03); the wire-`Cursor` collapse into
one shared codec beside `core/json` (T2-01(b)); the rect unification — one **concrete**
`ui::Rect` + `contains()` + per-role aliases, NOT a template (T2-05 ≡ T4-21). Proves the wave
discipline (relocate + encapsulate, CTest-green) before any god-module surgery. CONTEXT.md
§Phase Q (json extraction; directory + namespace map).
**Verify:** CTest green at every commit. The five duplicate JSON decoders are gone, replaced by
one `core/json` consumed by all five consumers; round-trip serialization is byte-identical to before
(no format change — a *structural* dedupe, not a behavior change). Every relocated clean module
compiles and its test executable passes unmoved. `Sample` (model) vs `AudioSample` (audio) vs
unified `Parser` (json) do not collide once sub-namespaced. No REAPER type crosses into any
@@ -345,81 +411,174 @@ any fix-now findings the audit assigned to Q-W1 folded in). First structural wav
- [ ] Extract `core/json` (pure parser + serializer: parseString/parseInt/parseKey/skipValue +
escape, plus emit helpers); unify under `reasampler::json`; guard the `Parser` name against
cross-lib collision. Off all hot paths — safe to abstract freely.
- [ ] Rewire `bank_model`, `bank_book`, `view_mode_model`, `owned_manifest` onto `core/json`;
**delete the four duplicate `Parser`s.** Round-trip output byte-identical (dedupe, not
reformat).
- [ ] Rewire `bank_model`, `bank_book`, `view_mode_model`, `owned_manifest`, **and
`tail_control` (T2-02)** onto `core/json`; **delete the five duplicate decoders.** Round-trip
output byte-identical (dedupe, not reformat).
- [ ] Add the shared `readFileBytes` pure helper to the `core/` utility home; both artifacts
link it (T2-03).
- [ ] Collapse the length-prefixed wire-`Cursor` family into one shared wire codec beside
`core/json`, consumed by `provenance` / `assignment_request` / `sample_usage` /
`parseBankGeneration` (T2-01(b) — the structural half; the hardening backport lands in Q-W0).
- [ ] Riders on files this wave already opens: `slot_map` extraction from `bank_book` (T4-05);
`view_mode_model` planner split (T4-06 — optional if the wave wants to stay minimal);
`view_lanes` split only if relocation touches `view.cpp` anyway (T4-09).
- [ ] Relocate the 30 clean pure libs into `core/{model,view,capture,audio,ui,reclaim,version,
json}/` and the clean shells into `shell/{capture,panel,view,persist,actions}/`; move
`main.cpp` to `app/`. Update `CMakeLists.txt` `src/` paths only (no target-graph change).
json}/` **+ the ~20 clean VST pure libs into `core/instrument/{engine,map,ui}/` (T4-18
SETTLED, Daniel 2026-07-28)**, and the clean shells into
`shell/{capture,panel,view,persist,actions,instrument}/`; move `main.cpp` to `app/`. Update
`CMakeLists.txt` `src/` paths only (no target-graph change).
- [ ] Apply sub-namespaces matching the directories on every relocated *clean* module (the
god-modules re-namespace their own new TUs as they split, W2W5). Resolve `Sample`/
god-modules re-namespace their own new TUs as they split, W2/W2vW5). Resolve `Sample`/
`AudioSample`/`Parser` homes. **This alone resolves the naming *collisions*** (§2b.2): the
shared pure-UI rect types (`FooterRect`/`ButtonRect`/`Selection`/`CellRect`) get one `ui::`
owner — retire the hand-collision "NAME NOTE" in `footer_bar.h`.
- [ ] **Naming riders (Q-8, if settled):** rename the survivor JSON parser to `json::Parser`
(or `json::Reader`/`json::Writer`); if Daniel takes the `BankIndex`→`BankModel` rename, land
it here (mechanical class rename, verified by `bank_model_tests`). No rename on a file this
wave isn't already relocating (Q-7).
owner — one **concrete** `ui::Rect` + `contains()` + per-role aliases, retiring the
XYWH-vs-LTRB fork and folding in `editor_geometry`'s `Rect` (T2-05 ≡ T4-21 — explicitly NOT a
template); retire the hand-collision "NAME NOTE" in `footer_bar.h`. Riders: `clamp01` dedup
(T4-24); the `hitIndex` hit-test template only as an opportunistic follow-on once the rect
unification lands (T4-22).
- [ ] **Naming riders (Q-8 — SETTLED, Daniel 2026-07-28: both renames):** the survivor JSON
parser is minted as **`json::Reader`/`json::Writer`**; land **`BankIndex`→`BankModel`** here
(mechanical class rename, verified by `bank_model_tests`). No rename on a file this wave isn't
already relocating (Q-7).
- [ ] Confirm CTest green + no hot-path change: `peaks`/audition/realtime-tick untouched by this
wave (pure relocation of clean modules; `peaks` stays a free function).
## Q-W2 — split `bank_panel.cpp` (the biggest god-module, 2424 LOC)
**Goal:** Split the largest god-module (8+ responsibilities) along the audit's named seams:
## Q-W2 — split `bank_panel.cpp` (the biggest extension god-module — 3459 LOC at the Q-W0 census)
**Goal:** Split the largest extension god-module (8+ responsibilities) along the audit's named
seams — **eight TUs (Q-5 SETTLED with the T4-01 reshape, Daniel 2026-07-28)**:
`panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` / `panel_bank_ops` /
`panel_window`. Split the fat `bank_panel.h` alongside (Interface Segregation). **Preserve the
`panel_window` **+ `panel_layout` (toolbar/footer/menu rects + row/cluster builders + region
geometry glue) + `panel_drag` (the card-drag/hover state machine — it already has a pure mirror,
`card_drag`)** — without the two new seams, `panel_render` (~700) and `panel_input` (~800) would
ship over the ~600 ceiling on day one. Split the fat `bank_panel.h` alongside (Interface
Segregation). **Preserve the
audition hot path as a direct call-through, never virtual.** `panel_bank_ops` becomes the single
home for the bank-CRUD verbs that W4 will dedupe `actions.cpp` against. CONTEXT.md §Phase Q
(bank_panel split seams; hot-path audition guardrail). See `docs/product/code-organization.md`
§2.1, §5.
**Verify:** CTest green at every commit. Each seam is its own TU under `shell/panel/`; the panel
**Verify:** CTest green at every commit. Each seam is its own TU under `shell/panel/` and
**every TU lands under the ~600-line ceiling** (the Q-5 acceptance bar); the panel
draws, thumbnails, auditions, handles input, does bank ops, and manages its window exactly as
before (no behavior change — verify in DAW that the panel is visually and interactively
unchanged). Audition/preview call path stays a **direct call-through** (no virtual dispatch, no
added header→TU indirection on the preview path). The ~20-function public API is now segmented
across the split headers.
**Depends on:** Q-W1 (directory/namespace layout established). Independently landable.
**Parallel-safe with Q-W2v** (different artifact, zero file overlap — §4f SETTLED).
- [ ] Split rendering (`draw*`/`paint*`) → `panel_render`; thumbnail compute+cache →
`panel_thumbnails`.
`panel_thumbnails`; toolbar/footer/menu rect + row/cluster builders + region geometry →
**`panel_layout`** (new seam, T4-01).
- [ ] Split the audio audition/preview engine → `panel_audition` — **direct call-through, not
virtual; preview idle path unchanged.**
- [ ] Split input handling (mouse/key/wheel) + new-content detection → `panel_input`; window
lifecycle + OS drag-out/drop-target → `panel_window`.
lifecycle + OS drag-out/drop-target → `panel_window`; the card-drag/hover state machine →
**`panel_drag`** (new seam, T4-01). Per-mouse-move work stays plain free-function calls
(T4-28).
- [ ] Extract bank-CRUD verbs → `panel_bank_ops` (the future single owner; W4 dedupes
`actions.cpp` against it). Split `bank_panel.h` into per-seam headers (I).
- [ ] Verify in DAW: panel unchanged; CTest green; no hot-path indirection added.
- [ ] Verify in DAW: panel unchanged; CTest green; no hot-path indirection added; all eight TUs
under the ~600 ceiling.
## Q-W2v — split the VST god-modules (NEW wave — Q-W0 T4 §1.5; runs parallel with Q-W2)
**Goal:** Close the audit's structural scope gap: the VST artifact's god-modules had no owning
wave, and `reasampler_editor.cpp` (3065 LOC) is the largest file in the repo. Split the editor
into **eight TUs along the Sample/Browse/Zone face axis** (T4-11): `editor_session` /
`editor_controls` / `editor_layout` (**pure-candidate hoist** into the existing pure homes —
`editor_geometry` is the named owner; this discharges T2-06's stranded-layout-math finding) /
`editor_paint_sample` / `editor_paint_browse_zone` / `editor_input_sample` /
`editor_input_browse_zone` / `editor_platform`. Split `reasampler_processor.cpp` (1164 LOC) into
**three TUs** (T4-12): `processor_state` / `processor_reload` / lifecycle+`process()` kept
whole. Split `sample_map` into resolution core vs the **`component_state_io`** binary codec +
matching header split (T4-13 ≡ T2-07 — the codec grows every envelope bump; the extension stops
linking the whole voice engine to serialize one preset blob). **`sampler_core.cpp` stays whole
(968 LOC) — a DOCUMENTED hot-path exception to the ~600 ceiling** (T4-14/T4-27: envelope
`tick()`s run per-voice-per-sample; same-TU definition is what lets the compiler inline the
stack, no LTO in the build; a by-class split is the exact heuristic-(3) dispatch blowout); its
header splits into `zone_params.h` + `sampler_core.h`. The `core/wire` LE byte-codec template
(`putLE`/`readLE`, T4-20) lands here with its biggest consumer. CONTEXT.md §Phase Q (VST split
seams; `sampler_core` exception).
**Verify:** CTest green at every commit. Editor and processor behave identically in DAW (visual
+ interactive parity across all three faces; `process()` audio unchanged). Every new TU lands
under the ~600-line ceiling **except the one documented `sampler_core.cpp` exception**.
`process()` + its per-block helpers stay one TU; the atomic-pointer-swap reload pattern gains
**no virtual seam** (T4-29); no dispatch-stack blowout anywhere (heuristic 3).
**Depends on:** Q-W1 (layout + the T4-18 `instrument/` placement established). **Parallel-safe
with Q-W2** (different artifact, zero file overlap — §4f SETTLED, Daniel 2026-07-28; the serial
"Q-W7" alternative was set aside).
- [ ] Split `reasampler_editor.cpp` → the eight face-axis TUs; hoist `editor_layout`'s pure
geometry into the existing pure homes (`editor_geometry` — discharges T2-06).
- [ ] Split `reasampler_processor.cpp` → `processor_state` / `processor_reload` /
lifecycle+`process()` whole; **no virtual seam on the atomic-swap pattern** (T4-29).
- [ ] Split `sample_map` → resolution core + `component_state_io` codec (+ header split); the
extension's preset-blob path stops linking the voice engine (T4-13 ≡ T2-07).
- [ ] `sampler_core`: split `zone_params.h` out of the header; **TU stays whole — documented
exception** (T4-14/T4-27), recorded in the wave brief so nobody "fixes" it later.
- [ ] Land the `core/wire` LE byte-codec template (`putLE`/`readLE`, T4-20) with
`component_state_io`; other consumers rewire opportunistically.
- [ ] Rider: adopt the pure `ThumbnailKey` on the VST editor side (T2-10).
- [ ] Verify in DAW: editor + processor unchanged; CTest green; ceiling met (one documented
exception); no added dispatch.
## Q-W3 — split `main.cpp` (hoist orchestration; leave main = pointers + entry + dispatch)
**Goal:** Reduce `main.cpp` (1762 LOC) to its actual job — API pointers + `ReaperPluginEntry` +
dispatch (~the owns-pointers ~120 lines) — by hoisting: `capture_orchestrator` (`RunCapture` /
`captureAndIndexOne` / `renderOffline` / batch/recapture/realtime `Run*`), `scope_resolve`
**Goal:** Reduce `main.cpp` (1897 LOC at the Q-W0 census) to its actual job — API pointers +
`ReaperPluginEntry` + dispatch — by hoisting **four** TUs (T4-02 reshape, SETTLED with Q-5,
Daniel 2026-07-28 — the planned three left `capture_orchestrator` at ~885, over the ceiling):
`capture_orchestrator` (`RunCapture` / `captureAndIndexOne` / `renderOffline` / single-capture +
realtime/insert action bodies — lands ~450), **`capture_batch`** (the batch family +
`RunRecaptureFromSource` + the two RAII selection guards — recapture is planner-driven like
batch and shares the guard machinery), `scope_resolve`
(`resolveRange`/`resolveRazorRange`/`collectSelectedTracks` + provenance assembly inputs), and
`realtime_lifecycle` (the realtime-capture state machine + globals + selection guards).
`realtime_lifecycle` (the realtime-capture state machine + globals).
**`FxBypassGuard` moves out but stays a stack RAII object (precision-critical); the realtime idle
tick stays a single pointer test.** CONTEXT.md §Phase Q (main split seams; FxBypassGuard +
tick stays a single pointer test.** **Q-W0 riders owned by this wave (all SETTLED 2026-07-28):**
delete `ICaptureBackend` (T4-26 — one deriver, zero polymorphic call sites; `OfflineRenderBackend`
becomes concrete; the CLAUDE.md/CONTEXT "two backends behind one interface" correction **rides
this wave's own commit**, not earlier); the shared `stampCaptureSample` capture-epilogue dedupe
(T2-09); the `capture_realtime_finalize` split riding the Q-9 rename (T4-08); the `makeUniqueTag`
per-session monotonic-counter fix (T1-11); and the **WAV/RIFF consolidation (audit §4e)** — one
pure **`wav_codec`** owner (walker + layout + build + patch), absorbing `ingest.cpp`'s pure WAV
build helpers (T2-08 / T4-23 / T4-10). CONTEXT.md §Phase Q (main split seams; FxBypassGuard +
realtime-tick guardrails). See `docs/product/code-organization.md` §2.1, §3.
**Verify:** CTest green at every commit. Capture (offline + realtime + batch + recapture) behaves
identically in DAW; the null test still nulls, bit-identical repeats still match (the precision
invariants `FxBypassGuard` protects are unchanged); capture ≠ placement holds (no hoisted `Run*`
path gains an `InsertMedia` call). The realtime idle fast-path is still a single pointer test.
`main.cpp` is now pointers + entry + dispatch only.
**Depends on:** Q-W1. Independent of Q-W2.
`main.cpp` is now pointers + entry + dispatch only. The four hoisted TUs + `wav_codec` land
under the ~600 ceiling; the WAV/RIFF layout has **one** pure owner (the dedup-by-hash and
null-test invariants now rest on one implementation); `ICaptureBackend` is gone with no behavior
change and the CLAUDE.md/CONTEXT description is corrected in the same commit.
**Depends on:** Q-W1. Independent of Q-W2/Q-W2v.
- [ ] Hoist capture orchestration → `capture_orchestrator` (`shell/capture/`); keep
`FxBypassGuard` a **stack RAII** object as it moves (precision-invariant-critical).
- [ ] Hoist the batch family + `RunRecaptureFromSource` + the two RAII selection guards →
**`capture_batch`** (fourth hoist, T4-02) so `capture_orchestrator` lands ~450.
- [ ] Hoist scope/source resolution + provenance assembly inputs → `scope_resolve`.
- [ ] Hoist the realtime-capture lifecycle state machine + globals + the two RAII selection
guards → `realtime_lifecycle`; **idle tick stays a single pointer test.**
- [ ] Hoist the realtime-capture lifecycle state machine + globals → `realtime_lifecycle`;
**idle tick stays a single pointer test.**
- [ ] Leave `main.cpp` = API-pointer ownership + `ReaperPluginEntry` + dispatch; move to `app/`.
- [ ] **Naming rider (Q-9, if settled):** align the `capture_realtime` (shell) / `realtime_record`
(pure) word-order inversion to the house shell↔core convention (rec: stem `capture_realtime`,
shell suffixed) — a free rider since W3 already hoists the realtime lifecycle. No rename on a
file this wave isn't already touching (Q-7).
- [ ] **Naming rider (Q-9 — SETTLED, Daniel 2026-07-28: yes):** align the `capture_realtime`
(shell) / `realtime_record` (pure) word-order inversion to the house shell↔core convention
the pure module takes the stem `capture_realtime`, the shell takes the suffix
(`drag_out`↔`drag_out_win` is the model). Split `capture_realtime_finalize` (async lifecycle
vs file-side finalize) in the same surgery (T4-08). No rename on a file this wave isn't
already touching (Q-7).
- [ ] Delete `ICaptureBackend` (T4-26): `OfflineRenderBackend` becomes concrete; correct the
CLAUDE.md/CONTEXT "two backends behind one interface" description **in the same commit**.
- [ ] Dedupe the capture-stamp epilogue → shared `stampCaptureSample` (T2-09 — the divergent
bits stay in the realtime caller); fix `makeUniqueTag` with a per-session monotonic counter,
both call sites (T1-11 — same-second batch captures currently collide silently).
- [ ] **WAV/RIFF consolidation rider (audit §4e — SETTLED, Daniel 2026-07-28):** one pure
`wav_codec` owner (chunk walker + layout + build + patch), absorbing `ingest.cpp`'s pure
WAV/PCM build (T4-10 — the ingest shell drops to ~500 and the WAV build gains a test target).
- [ ] Verify in DAW: null test nulls, bit-identical repeats match, capture≠placement holds;
CTest green; no realtime-tick branch-shape change.
## Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops`
**Goal:** Split the two unrelated command-id families in one TU (981 LOC) into
**Goal:** Split the two unrelated command-id families in one TU (1016 LOC at the Q-W0 census —
T4-03: the planned seams still land sub-600, no reshape) into
`design_view_actions` / `bank_actions` / `prune_action`, and **dedupe** `actions.cpp`'s own
`promptText`/`mintBankId` and bank verbs against the `panel_bank_ops` single-owner established in
Q-W2. `prune_action` keeps the `doBankPruneFolder` deletion authority contract intact (routes to
@@ -442,12 +601,16 @@ contract — a reorg must not touch a shipped command id).
no-undo/no-ext-state; CTest green.
## Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`)
**Goal:** Split `persist.cpp` (766 LOC, 5 responsibilities) into `session` (lifecycle+poll,
`BeginLoadProjectState` reload hook), `ext_state_io` (the ext-state ↔ JSON serialization bridge +
GUID minting + folder relocation), and **`prune_fs`** (prune scanning + `deleteOrphanFile` via
`SHFileOperationW`). The split **concentrates** the byte-deleting authority into one obvious
module — it must never spread it. CONTEXT.md §Phase Q (persist split seams; deletion-authority
isolation). See `docs/product/code-organization.md` §2.1, §7.
**Goal:** Split `persist.cpp` (852 LOC at the Q-W0 census — T4-04: seams unchanged; the
pS-usage growth landed exactly where this wave isolates it; 5 responsibilities) into `session`
(lifecycle+poll, `BeginLoadProjectState` reload hook), `ext_state_io` (the ext-state ↔ JSON
serialization bridge + GUID minting + folder relocation), and **`prune_fs`** (prune scanning +
`deleteOrphanFile` via `SHFileOperationW`). The split **concentrates** the byte-deleting
authority into one obvious module — it must never spread it. **Q-W0 rider (T2-04, SETTLED
2026-07-28):** generalize the `GetProjExtState` grow-loop retry policy into `bridge_marshal`'s
pure decode home (or its `core/` successor) and rewire all three hand-rolled copies —
`usage_scan`'s prune-safety-adjacent copy included. CONTEXT.md §Phase Q (persist split seams;
deletion-authority isolation). See `docs/product/code-organization.md` §2.1, §7.
**Verify:** CTest green at every commit. Session save/load/undo-reload, ext-state round-trip,
folder relocation, and prune deletion all behave identically in DAW. **File deletion lives in
exactly one module (`prune_fs`)** — the single-file-deletion-authority invariant is *improved*
@@ -459,6 +622,9 @@ independently landable.
(serialization bridge + GUID minting + folder relocation).
- [ ] Isolate prune scanning + `deleteOrphanFile` (`SHFileOperationW`) → **`prune_fs`** — the
one file-deletion module; nothing else may delete bytes.
- [ ] Dedupe the `GetProjExtState` grow-loop ×3 (T2-04): one retry policy generalized from
`bridge_marshal`; rewire `usage_scan`'s prune-safety-adjacent copy with `sample_usage_tests`
green.
- [ ] Verify in DAW: save/load/undo-reload/relocation/prune unchanged; deletion authority is one
module; relative-paths-only holds; CTest green.
@@ -466,7 +632,9 @@ independently landable.
**Goal:** Close the last SOLID wart: replace the ~350-line hand-written **non-table** action
registration blocks (now isolated in `app/main.cpp` after Q-W3) with a **registration table**, so
adding an action edits one place, not four parallel ones (OCP). Split any remaining fat headers
(`capture.h`/`persist.h`) not already resolved by their TU splits (I). CONTEXT.md §Phase Q (OCP
(`capture.h`/`persist.h`) not already resolved by their TU splits (I). (Q-W0: no reshape
T4-02 notes the ~385-line registration residue left in `app/main.cpp` after Q-W3 shrinks
further under the table.) CONTEXT.md §Phase Q (OCP
registration-table). See `docs/product/code-organization.md` §2.3, §6 (Q-6).
**Verify:** CTest green at every commit. Every action still registers, appears in the Actions
list, and fires via `hookcommand` exactly as before; command-id + display strings unchanged
@@ -488,27 +656,42 @@ GATE: Phase S + Phase L L3 merged to dev (D2 complete, M9 abandoned) — tree qu
("when Phase S and L3 are finished" — L1/L2/L3/L4L7 all landed — GATE SATISFIED)
Q-W0 (pre-restructure functional + DSP quality audit — findings report + triage)
│ ── SUB-GATE: triage complete + Daniel signed off on every disposition ──
▼ (fix-now findings remediated/assigned; downstream Q-W1..W6 reshaped as needed)
Q-W1 (safe opener: core/json extract + directory/namespace layout on clean modules)
├─► Q-W2 (split bank_panel) ──► Q-W4 (split actions + dedupe bank verbs vs panel_bank_ops)
├─► Q-W3 (split main.cpp; hoist orchestration) ──► Q-W6 (OCP registration-table + I splits)
─► Q-W5 (split persist; isolate prune_fs) [best after Q-W4]
Q-W0 (audit + triage + report — COMPLETE; all 59 dispositions signed off 2026-07-28)
│ ── SUB-GATE: satisfied once the six approved fix-now remediations land ──
▼ (T1-01 T1-03 T1-09 T2-01a T3-01 T3-03 — in flight on pq-w0-fixes)
Q-W1 (safe opener: core/json ×5 + wire codec + rect unification + relocation incl. ~20 VST
│ pure libs under core/instrument/{engine,map,ui} + riders)
├─► Q-W2 (split bank_panel — 8 seams) ──► Q-W4 (split actions + dedupe vs panel_bank_ops)
─► Q-W2v (NEW: VST god-modules — editor 8 TUs / processor 3 TUs / component_state_io;
│ sampler_core TU whole — documented exception) [parallel with Q-W2: zero overlap]
├─► Q-W3 (split main — 4 hoists incl. capture_batch; + wav_codec, ICaptureBackend deletion,
│ stamp dedupe, T1-11, capture_realtime_finalize) ──► Q-W6 (OCP registration-table)
└─► Q-W5 (split persist; + ext-state-loop dedupe) [best after Q-W4]
```
Q-W0 is the **entry point** — the functional/DSP audit runs FIRST and gates Q-W1 (no structural
point begins until its triage closes and Daniel signs off). W1 is then the safe, high-leverage
structural opener (all later waves assume the layout it establishes). The four god-module splits
(W2W5) are risk-ordered and mostly parallel-safe; W4 depends on W2's `panel_bank_ops`, W6 depends
on W3's isolated registration code. Big-bang is rejected — every wave is independently landable and
CTest-green.
Q-W0 has run and is signed off (2026-07-28); its sub-gate closes when the six fix-now
remediations land. W1 is the safe, high-leverage structural opener (all later waves assume the
layout — including the T4-18 `instrument/` placement — it establishes). The god-module splits
(W2, W2v, W3, W5) are risk-ordered and mostly parallel-safe; **Q-W2v runs parallel with Q-W2**
(different artifact, zero file overlap — audit §4f SETTLED); W4 depends on W2's
`panel_bank_ops`, W6 depends on W3's isolated registration code. Big-bang is rejected — every
wave is independently landable and CTest-green.
## Phase Q — must-verify-before-build
- **Q-W0 closed before any structural point** — the functional/DSP audit's findings report exists,
every finding is triaged (fix-now vs. document-and-defer, each with rationale), fix-now findings
are remediated or assigned to the wave that opens their file, and **Daniel has signed off on every
disposition.** Q-W1 does not begin otherwise. (CONTEXT.md §Phase Q Q-W0; naming/DSP smell
categories §2c of `docs/product/code-organization.md`.)
categories §2c of `docs/product/code-organization.md`.) **Status 2026-07-28: triage + sign-off
COMPLETE (all 59 dispositions); the sub-gate closes when the six fix-now remediations land
(`pq-w0-fixes`).**
- **~600-line ceiling on every split wave** — every TU a split wave ships lands under ~600 LOC,
with `sampler_core.cpp` the single documented exception (T4-14/T4-27). Seams are the method,
the ceiling is the bar; arbitrary bisection to hit the number is rejected (Q-5 settlement,
2026-07-28).
- **No dispatch-stack blowouts anywhere** — heuristic (3) generalizes the hot-path guardrail
beyond the three named paths: prefer static polymorphism where types are compile-time-known;
templates only where earned for compile-time dedup (T4-20 yes; T4-21's rect NO-template
ruling).
- **Hot-path call/inline shape** — before landing each split, confirm no virtual dispatch and no
header→TU indirection was added on `peaks` envelope compute, audition/preview, or the realtime
tick. `computeEnvelope` stays a free function on `const std::vector<float>&`;