# PLAN

The post-1.0 roadmap. Seventeen queued items consolidated into overlapping areas and
sequenced into a Phase → Wave → Track hierarchy that implementation specialists can be
dispatched against directly — **plus Phase Γ**, which did not come from those seventeen
(it came from a direct interview, 2026-08-01) and is scoped in
`docs/product/instrument-control-surface.md`, **and Phase Ψ**, which likewise did not
come from the seventeen: it came from a direct list of seven defects and refinements
(Daniel, 2026-08-01); it has landed, and now that its section here is retired, the list
is carried verbatim in `docs/COMPLETED.md` — there is no backing product doc for it,
**and Phase Ε**, which likewise did not come from the
seventeen: it came from a direct request (Daniel, 2026-08-02) and is scoped in
`docs/product/bank-package.md`, **and Phase Ρ**, likewise a direct request (Daniel,
2026-08-02), scoped in `docs/product/render-in-place.md`, **and Phase Λ** — the Linux port
of both artifacts — likewise a direct request (Daniel, 2026-08-02), scoped in
`docs/product/linux-readiness.md`, **and Phase Ω**, which likewise did not come from the
seventeen: it came from a direct list of seven defects and refinements (Daniel,
2026-08-03), and like Ψ it has **no backing product doc of its own** — the seven are
carried verbatim in its phase header below as Ω.1…Ω.7. It does amend two clauses of
`docs/product/instrument-control-surface.md`, which are edited there rather than merely
contradicted here.

## What this doc is, and how it relates to the others

- **`docs/PLAN.md`** (this file) — the active on-deck specification list. Each track is
  written so a specialist brief is writable from this file alone: goal, consolidated
  source items, surface boundary, behavior, acceptance criteria, open questions,
  prerequisites.
- **`docs/TODO-1.0.md`** — retained as the **verbatim-provenance appendix**. It holds
  Daniel's raw asks and every answer round, unedited; where this plan compresses a
  behavior bullet, that file is the backing record. Items are cited here by number
  (e.g. "consolidates items 1, 8, 14"). It is not a work queue any more; this file is.
- **`docs/TODO.md`** — deferred follow-ups with recorded rationale, unrelated to the
  seventeen (with one flagged intersection: see "Flagged for awareness" below).
- **`docs/COMPLETED.md`** / **`docs/ARCHIVE.md`** — doc-keeper's. When a track here
  finishes, its point is removed from this file and appended to `COMPLETED.md` with any
  deviation between landed code and spec noted.
- **`docs/product/`** — the product-design reasoning behind prior phases. Grep for a
  cited section rather than reading a file whole.

**Worktree slug convention:** `p<phase>-w<wave>-t<track>-<slug>`. Greek phase letters
transliterate: **Θ → `th`**, **Ξ → `xi`**, **Γ → `g`**, **Ψ → `psi`**, **Ε → `e`**, **Ρ → `r`**,
**Λ → `l`**, **Ω → `om`**. So Θ-W1-T1 dispatches into `pth-w1-t1-zone-retirement`, Γ-W1-T1 into
`pg-w1-t1-knob-interaction-law`, Ψ-W1-T1 into `ppsi-w1-t1-capture-range-exactness`,
Λ-W2-T1 into `pl-w2-t1-linux-compile-blockers` (Phase Λ's two audit tracks already ran
under `pl-w1-t1-build-toolchain-audit` and `pl-w1-t2-source-runtime-audit`), and Ω-W1-T1
into `pom-w1-t1-mode-switch-responsiveness`.

## Decision state

Everything carried forward from `TODO-1.0.md` is classified **[verify]** (answerable by
reading code or running the DAW) or **[propose]** (a design call made at implementation
review with a proposal, not a Daniel call); that classification is preserved per
question, attached to the track that will answer it.

**Read the Phase Λ paragraph below before relying on the plan-wide "nothing is unanswered"
claim the Γ, Ε and Ρ paragraphs make.** Λ (added 2026-08-02) carries **four open
[Daniel]-class forks**, so that claim is no longer true of the plan as a whole; it is true
of every phase except Λ, and Λ's paragraph states exactly which of its four forks gate
anything.

Θ-W3-T1's two genuine **[Daniel]** questions — which no amount of code-reading could
answer — are both ruled on and the track has landed; see `docs/COMPLETED.md` for the
full narrative. **Reload tier = Grouping B** (continuous knobs live: filter cutoff/Q/
morph/drive/mod amount/key-track, every envelope stage time and level; root note, loop
span, and start frame still trigger a full reload). **Mid-stage rule = candidate (iv),
hold normalized stage position** (φ = elapsed/duration held fixed across a duration
change, then advancing at 1/newDuration). **Phase Γ opened six [Daniel]-class forks
(Γ-F1…Γ-F6) and all six are ruled** (Daniel, 2026-08-01) — the rulings are folded into the
tracks below and indexed in `docs/product/instrument-control-surface.md` §8. **Γ-F6 closed
with a correction to the analysis, not merely a ruling**: dynamic reported latency is routine
for VST3 instruments and REAPER handles it as a matter of course; what makes the mandated
restart expensive *here* was self-inflicted (`setActive(true)` called `reloadInstrument`), so
the cost was ours to reduce. **That reduction landed in Γ-W3** — see `docs/COMPLETED.md`.

**Γ-F3 was subsequently REVERSED and a seventh fork opened AND CLOSED, all by Daniel's later
rulings of 2026-08-01.** Γ-F3 (*"the stage-time ceiling stays 2.0 s"*) is replaced by *"extend
the stage lengths to 10s"* — the ceiling moves in Γ-W1-T1. **Γ-F7** (the VST3 parameter
*order*) is **RULED: signal flow** — *"signal flow order."* **There is now NO unanswered
[Daniel]-class question anywhere in this plan.**

**Phase Ε (added 2026-08-02) opened three more [Daniel]-class forks and ALL THREE ARE
RULED**, same day (Daniel, 2026-08-02): **Ε-F1** container format — *"proprietary
container"*, the hand-rolled `RSBK`; **Ε-F2** import target — *"always lands as a new bank,
with an auto suffix if name collision"*; **Ε-F3** import under a degraded tracking ledger —
*"refuse mismatched import."* Ε-W1 through Ε-W3 have landed; see `docs/COMPLETED.md` for
the full narrative, and the rulings remain indexed at `docs/product/bank-package.md`
§"Rulings". **Two of the three landed somewhere other than
the framing recommendation:** Ε-F2 dropped the proposed rename prompt in favour of a
deterministic suffix, and Ε-F3 reversed allow-with-confirm to refuse — carried by the
framing's own counter-argument, that the accepted tracking residual contemplates *one*
untracked capture while a bulk import strands hundreds in a single gesture. **The plan-wide
claim above therefore holds unqualified — no unanswered [Daniel]-class question remains
anywhere in this plan, Phase Ε included — and no track in this plan is gated on a
decision.**

**Phase Ρ (added 2026-08-02) opened three more [Daniel]-class forks and ALL THREE ARE
RULED**, same day (Daniel, 2026-08-02): **Ρ-F1** multi-track — *"refuse"*, one track per
fire, a settled non-goal rather than a deferral; **Ρ-F2** the result track's mode —
*"for this action which is not a capture, the result track should always go to
arrange"*; **Ρ-F3** tail — *"follow panel tail settings."* Ρ-W1-T1 has landed; see
`docs/COMPLETED.md` for the full narrative, and the rulings remain indexed at
`docs/product/render-in-place.md` §"Rulings". **Ρ-F2
overrode the request's own original wording** ("stays in whatever mode was active") and
is the only one of the three that changed the spec: the result track is now an Arrange
member unconditionally, the A/B-on-the-bench behaviour mode-following would have enabled
is gone, and the ruling pulls in a two-line fix to the panel's auto-tag detector that
would otherwise reverse it on the next timer tick. **The plan-wide claim above therefore
still holds unqualified — no unanswered [Daniel]-class question remains anywhere in this
plan, Phase Ρ included — and no track in this plan is gated on a decision.**

**Phase Λ (added 2026-08-02) breaks that unqualified claim, and it is corrected here rather
than left to be discovered.** The claim as written in the three paragraphs above — *"no
unanswered [Daniel]-class question remains anywhere in this plan"* and *"no track in this
plan is gated on a decision"* — is now **scoped to Θ, Ξ, Γ, Ψ, Ε and Ρ.** It is still true
of all six. It is **not** true of Λ. **Λ opened four [Daniel]-class forks and NONE of them
is ruled** (Λ-F1 CI; Λ-F2 the dialog-resource route; Λ-F3 the copy-only drag-out invariant's
wording; Λ-F4 the declared support floor). Each is stated with the evidence for both sides
at `docs/product/linux-readiness.md` §"Open forks — Daniel's", and the Phase Λ section below
carries them as a table. What they gate is narrow and is not a matter of judgement:

- **Λ-F2 is the only one that gates a dispatch** — Λ-W2-T3 `panel-dialog-resource` cannot be
  briefed until the route is chosen, because the two routes own different files. Leaving it
  open does not stall the phase; it slips that one track to Λ-W4 and **splits Λ-W3's
  verification sweep into two Linux sessions**, which is the fork's actual price.
- **Λ-F4 gates the ship wave, not a dispatch.** Λ-W2 can record a floor and widen it; Λ-W5
  cannot ship an artifact that does not say what it runs on. Answerable any time before
  Λ-W5-T1 is briefed.
- **Λ-F1 and Λ-F3 gate nothing at all.** Λ-F1 (CI) decides only which paragraph Λ-W5-T1
  writes; Λ-F3 decides one sentence's wording inside an edit Λ-W4-T3 makes either way.
- **The other eleven Λ tracks are dispatchable against open forks**, in the sense that none
  of them waits on a decision. What every Λ track waits on instead is a Linux box — a
  different kind of unknown, and the reason Λ-W3 exists.

**Λ's six [Daniel] rulings of 2026-08-02 are settled** and are recorded in the phase section
below in the same shape as Γ's: the instrument is in scope; SWELL is reached by
`dlopen`ing REAPER's own `libSwell.so` rather than vendoring it; the editor is REAPER-only
but every other host must degrade safely; macOS is out; the Linux artifact is shipped rather
than developer-only; and a hard `unlink` prune is acceptable with a platform-aware
confirmation. **Do not re-litigate them.**

**Phase Ω (added 2026-08-03) opened six [Daniel]-class forks and ALL SIX ARE RULED**, the
same day the phase was framed (Daniel, 2026-08-03): **Ω-F1** the marker-alignment scope —
*"widen to the correct scope"*, the whole overlay↔waveform mapping rather than the START
mark; **Ω-F2** the loop marks — hidden outright in Trigger, drawn **grey-disabled and still
grabbable** in Gate with loop off; **Ω-F3** the single-button toggle rule extends to Filter,
Loop and Mono|Stereo, the last as a **mode selector** with no off state; **Ω-F4** the
active/focus colour is **`AccentPrimary`**, not `AccentHot`; **Ω-F5** the mode-switch symptom
is *"nothing for 2 seconds, then everything"* on **both** entry surfaces; **Ω-F6** the FX
parking **separates UI from processing** — visibility/routing/FX-enable apply synchronously,
per-FX offline/online defers. **Phase Ω therefore carries ZERO unanswered [Daniel]-class
questions and no Ω track is gated on a decision.** Two of the six moved the spec rather than
merely confirming it: Ω-F2 reverses `docs/product/instrument-control-surface.md`'s
"Disabled rather than hidden" marker principle in the Trigger case and retires its
`LOOP — GATE ONLY` caption, and Ω-F6 amends the accepted FX-parking invariant in
`src/shell/view/CLAUDE.md`. Both amendments are named track deliverables, not footnotes.

**The Λ carve-out above is unchanged and is NOT weakened by this phase.** Λ remains the one
phase in this plan with unanswered [Daniel]-class questions; the enumeration in Λ's paragraph
predates Ω, so read *"scoped to Θ, Ξ, Γ, Ψ, Ε and Ρ"* as **Θ, Ξ, Γ, Ψ, Ε, Ρ and Ω** — seven
phases, all of them fully ruled, and Λ alone excepted.

**Ruling 3 (Daniel, 2026-08-01) — real units at the host boundary.** *"The parameter values
exposed to the VST host should be in real units, such that the host automation lanes report
usable values."* Satisfied through VST3's **plain-value layer**, not its wire format (which is
normalized and cannot be otherwise): `toPlain`/`toNormalized`, `getParamStringByValue` and
`ParameterInfo::units`. Specified at **`docs/product/parameter-automation.md` §6.7** — the
per-category unit/precision table, the one-formatter invariant, the `stepCount` sweep, and the
resolution of the apparent conflict with the filter's re-taper prohibition. It lands entirely
in Γ-W4-T1 and changes no wave boundary.

### Flagged for awareness — not blocking, but decision-grade

1. **Item 15's cross-artifact seam is RESOLVED — this is no longer an unknown.** The
   instrument is a read-only bank consumer by invariant (`src/shell/instrument/CLAUDE.md`),
   and the one previous attempt at an instrument→extension relay (S13) closed with a
   **DEGRADED** spike verdict and was deferred (`docs/TODO.md`). Resample required that
   crossing. Ξ-W2-T1 ratified Decision 1 = (1b): the editor invokes the extension's bake
   action directly over the VST-host bridge (`NamedCommandLookup`/`Main_OnCommandEx`), no
   poller, no nonce — dissolving the S13 DEGRADED verdict rather than re-litigating it. The
   read-only bank invariant held: the crossing is a bridge call, not a shell-side bank
   write. See `docs/COMPLETED.md` for the full narrative.

2. **The "Γ before Ξ-W2" ordering is VIOLATED, it was never Daniel's choice, and Γ now owns
   the correction.** Daniel, 2026-08-01: *"xi was started before I spun you up, we'll have to
   correct phase xi inside gamma. wasn't a choice."* Ξ-W2-T1 (`resample-bake-chain`) ran
   ahead of this plan's sequencing claim, so the bake's settled reset scope — which
   enumerates parameters **by name** — ships incomplete: it cannot name rate, pitch offset or
   the limiter flag, none of which existed when it was written.

   **This is no longer a scheduling constraint to honour. It is a correction obligation with
   a named owner: Γ-W3-T2 `bake-reset-amendment`.** The classification costs no Daniel
   decision — `docs/product/instrument-control-surface.md` §3.4 pre-classifies all three
   against Ξ-W2's own ratified rule (all **reset**) — but the amendment must be written
   **against what Ξ-W2-T1 actually shipped, not against what this plan predicted it would
   ship.** Ruling 1 adds a second correction of the same shape, homed on Γ-W4-T1 rather than
   here: see item 3.

3. **The one-way doors are now IN-PHASE, and the sweep for them is a delivered artifact.**
   Ruling 1 (Daniel, 2026-08-01) schedules VST3 parameter reporting **inside Phase Γ**, as
   Γ-W4-T1. Everything that participates in a parameter's normalization therefore freezes at
   the end of this phase rather than at the start of some later one, and anything that ought
   to move must move first.

   - **The taper** (Γ-W1-T1) — known, and the reason this phase was ordered as it was.
   - **The stage-time ceiling 2.0 → 10.0 s** (Γ-W1-T1) — Γ-F3 **reversed** by Daniel's
     *"extend the stage lengths to 10s."* A range endpoint is normalization exactly as much
     as the curve between the endpoints is.
   - **Two further doors that need action, both new**, both landing on Γ-W1-T1: every
     default must have an **exact normalized preimage** (a host's reset-to-default has no
     `resetDeckParam` bypass to use), and the filter's four `*Norm` controls **must not be
     re-tapered** (their laws are already wire-frozen in payload v9).
   - **Six more constants freeze without needing to change**, and three are already frozen
     for unrelated reasons; the complete sweep, with dispositions and with what was checked,
     is `docs/product/parameter-automation.md` §8. **That doc is no longer scoping-only —
     §§6–10 are the specification Γ-W4-T1 is built from.**

## Phase-wide acceptance criteria

These bind every track in this plan and are stated once here rather than repeated
per track. **Phase Λ adds a set of its own**, stated in its phase header.

### Structural (root `CLAUDE.md`, Daniel 2026-07-28)

- **More directories is a must; more files is good; ~600-line file ceiling.** The
  ceiling is the *bar*; a responsibility seam is the *method*. Bisection-to-hit-the-
  number is rejected. `sampler_core.cpp` (956 lines) is the standing documented
  hot-path exception — Θ-W1-T1 re-seams it, and any surviving over-ceiling TU must
  carry the same explicit justification.
- **Templates where earned** — compile-time dedup with zero runtime cost, off the hot
  paths. Not for types that differ in name only.
- **SOLID is great, but saved CPU is better.** No dispatch-stack blowouts anywhere;
  prefer static polymorphism where the types are compile-time-known.

### Performance guardrails

Root `CLAUDE.md`'s five extension-side guardrails (peaks envelope compute, audition,
realtime-capture tick, JSON, `FxBypassGuard`) are unchanged by this plan; no track here
touches them. The instrument adds a sixth surface that binds every Θ track:

- **`process()` — the per-voice-per-sample path — takes no new indirection.** The
  filter tick, the envelope evaluation (staged *and* spline), the pitch-shift read, and
  the loop read all sit on it. Concrete, inlineable types only: **no `IEnvelope`, no
  `IFilter`, no virtual per-voice `tick()`**. A filter with two modes is a
  branch-predictable switch or a compile-time-known dispatch, never a vtable. Spline
  evaluation is a binary search over a point array, not a polymorphic curve object.
- **No allocation, no file I/O, no bridge call in `process()`.** The off-audio-thread
  `reloadInstrument` + atomic pointer swap stays the only way new state reaches the
  audio thread. Every new parameter this plan adds follows that path.
- **A split that would add a hot-path indirection is out of scope — rework it or drop
  it.**

### Product invariants

- **Capture and placement are separate acts.** No track here may place a timeline item.
  Item 15's bake explicitly must not, and one of its candidate architectures uses a
  *temporary* arrange mutation — that candidate must leave the arrange byte-identical.
- **Prune is the single, exclusive file-deletion authority.** Item 15's "replace" never
  deletes bytes; item 17's consolidation may not weaken any protection prune has today.
- **The instrument never writes the bank.** Item 15 is the first feature that needs to,
  and it resolves that by *asking the extension*, not by breaching the invariant.
- **Migration bar: a project saved before a change reopens sounding identical.** Holds
  everywhere except item 16's genuinely-multi-zone case, where Daniel deliberately
  relaxed it.
- **Every pure module gets a `<module>_tests` target** that runs without REAPER or a
  DAW. New pure modules in this plan are not optional-test.

---

## Phase Ω — Responsiveness, and the control surface's second pass

**Ships:** A mode switch that draws its result at once instead of two seconds later; a master
meter that moves at 60 FPS instead of two; a deck whose two-state toggles are single buttons
and whose envelope decks are their own overlay targets; loop marks that tell the truth about
the mode they are in; and one coordinate mapping shared by everything that rides the waveform,
so an overlay lands on the frame it names.

**Consolidates: none of the seventeen.** Phase Ω came from a direct list of seven defects and
refinements (Daniel, 2026-08-03) and has **no backing product doc of its own** — the Ψ
precedent. The seven are its provenance and are recorded here verbatim in substance:

- **Ω.1** — the ReaSampler 9000 master meter renders at 0.5–1 FPS. *(instrument)*
- **Ω.2** — Filter Mod belongs on the filter's envelope deck, and the Trigger face's knob
  spacing is wrong. *(instrument)*
- **Ω.3** — a two-state toggle should be ONE button, not two segments. *(instrument)*
- **Ω.4** — the whole envelope deck should be the overlay's click target, not a 12 px corner
  square. *(instrument)*
- **Ω.5** — the loop marks are meaningless in Trigger and unreadable in Gate with loop off.
  *(instrument)*
- **Ω.6** — the START marker does not land on the sample position it names — **widened by Ω-F1
  to the overlay↔waveform coordinate mapping as a whole.** *(instrument)*
- **Ω.7** — the arrange⇄design switch takes 1–2 s and shows nothing until it completes.
  *(extension)*

**This phase is small tidy work sequenced BEFORE Λ, and it is not another Λ.** Everything here
is verifiable on the current Windows box except the DAW-observable half, which has one track of
its own (Ω-W2-T3) rather than a wave. It **supersedes nothing** and adds no format, no wire
version, no parameter id, and no new artifact.

**Two invariant amendments are track deliverables** (the Ψ/Ρ precedent), because two settled
statements become untrue the moment this phase lands:

- `docs/product/instrument-control-surface.md` §6.3(d) and §6.4 — the marks' "Disabled rather
  than hidden" principle and the `LOOP — GATE ONLY` caption, reversed in the Trigger case by
  Ω-F2. **Owner: Ω-W1-T5.** *(Amended in that doc as part of this phase's authoring; the track
  verifies the code agrees with the amended text.)*
- `src/shell/view/CLAUDE.md`'s FX-parking caveat — today it accepts a "possible load hitch" on
  the return to the active mode as the cost of the CPU reclaim. Ω-F6 splits that cost off the
  synchronous path entirely. **Owner: Ω-W1-T1.**

### Rulings — Daniel's, 2026-08-03. Six, settled.

**Do not re-litigate these.** There is no product doc to index them in; this table is their one
home, and a brief for any Ω track is written against it.

| Ruling | What it settled | Bound into |
|---|---|---|
| **Ω-F1** | **Widen the marker-alignment work to the correct scope.** The defect is the overlay↔waveform coordinate mapping as a whole, not the START mark — a class defect with four independent causes, inherited by every overlay | Ω-W1-T5 |
| **Ω-F2** | **Trigger ⇒ the loop marks are HIDDEN outright. Gate with loop off ⇒ they are still drawn and still grabbable, styled GREY DISABLED rather than dim teal** — *"that was not intuitive to me."* The drag-to-set-loop gesture survives; `LOOP — GATE ONLY` goes | Ω-W1-T5 |
| **Ω-F3** | **The single-button toggle rule extends to `Filter` and chrome `Loop`** (Primary when active, **dim gray** when off) **and to `Mono\|Stereo`, which is a MODE SELECTOR** — its label reads the current mode and it has **no** dim-gray state | Ω-W1-T4 |
| **Ω-F4** | **The active/focus colour is `AccentPrimary`** — both the active single-button state and the focused envelope deck's border. **Not `AccentHot`**, which stays the hover cue | Ω-W1-T4 |
| **Ω-F5** | **The mode-switch symptom is both entry surfaces**, and it is *"nothing for 2 seconds, then everything switches"* — nothing renders until the whole synchronous body completes | Ω-W1-T1, Ω-W1-T2 |
| **Ω-F6** | **ACCEPTED, and it is a spec amendment: separate the FX parking's UI half from its processing half.** Visibility / routing / FX-enable apply **synchronously**; per-FX offline/online **defers to a later idle tick** — *"the switch doesn't happen during playback anyway… the UI is the priority when toggling modes"* | Ω-W1-T1 |

**Verified by Daniel, not a fork:** the deck knob-spacing defect is in the **Trigger** face.
**Gate is correct and must stay pixel-identical** — an acceptance criterion of Ω-W1-T4, not a
hope.

### Phase-Ω acceptance criteria

These bind every track in this phase, in addition to the plan-wide set above.

- **No behaviour changes outside the named defect.** Every track carries that criterion
  explicitly. The Gate deck face, the zero-crossing snap, the ballistics constants, the 500 ms
  bank/bake poll body and the audio thread are each named below as things that must come out
  the other side identical.
- **Nothing is added to any hot path.** No track here touches `process()`, the `peaks` envelope
  compute, audition, or the realtime-capture tick. Ω-W1-T3's work is entirely UI-thread paint
  cost; Ω-W1-T1's is entirely main-thread REAPER API cost.
- **`reasampler_editor.h` is at 564 lines against the ~600 ceiling** (`docs/TODO.md`, "The
  editor's drag state machine has no seam"), and **three W1 tracks add state to it.** The
  budget is ~36 lines for the whole wave. **The drag-state seam that TODO entry names is OUT OF
  SCOPE for Ω** — a track that finds itself over the ceiling stops and flags rather than
  bisecting the header under time pressure, which is exactly the failure that entry predicts.
- **The shell layers this phase touches have no CTest coverage** — `editor_paint_*`,
  `editor_input_*` and `shell/view` have no test targets. Every track therefore states, as a
  criterion, **what it pushed down into a pure module and asserted there**; "verified by eye in
  the DAW" is the fallback, not the plan.
- **Measurement before and after is a deliverable, not a nicety**, for the two performance
  items (Ω.1, Ω.7). A track that improves a number it never measured has no acceptance
  criterion, only an opinion.

---

### Ω-W1 — The five defects, in parallel

**Depends on:** nothing. **Five tracks, disjoint at the file level.** Items 2, 3 and 4 are ONE
track on purpose: they land in the same deck modules and the same pinned-geometry test files,
so splitting them means re-deriving `test_deck_groups_measured.cpp` three times. Items 5 and 6
are one track because they overlap inside `paintWaveform`'s mark-draw loop — one deletes and
gates it, the other changes the `mx` it computes.

| Track | Owns |
|---|---|
| **T1** `mode-switch-responsiveness` | `shell/view/view.{h,cpp}` + one new deferred-park TU + its drain in `app/main.cpp` |
| **T2** `mode-switch-persist` | `shell/actions/design_view_actions.cpp` + `shell/persist/ext_state_io.cpp` — dirty-key gating |
| **T3** `meter-rate` | the editor's timer/paint path + the hero-envelope cache |
| **T4** `deck-reflow-focus-toggles` | Ω.2 + Ω.3 + Ω.4 — the deck's composition, layout, paint, input and pinned tests |
| **T5** `overlay-mapping` | Ω.5 + Ω.6 — the waveform overlay's marks and its one coordinate mapping |

**Shared files in the wave, named rather than discovered at merge.** All textual adjacency
unless marked otherwise.

| File | Tracks | Nature |
|---|---|---|
| `src/shell/instrument/reasampler_editor.h` | T3 (meter clock + timer members), T4 (deck focus state), T5 (nothing new expected) | **The one to watch.** Additive member declarations in different sections of a header with ~36 lines of ceiling margin between them. Whichever lands second rebases AND re-checks the line count |
| `src/shell/instrument/editor_paint_waveform.cpp` | T3 (the envelope-cache call site at `paintWaveform`'s bin/`computeEnvelope` block), T5 (the mark, caption and mapping regions below it) | Disjoint regions of one function's body; whichever lands second rebases |
| `src/shell/instrument/editor_controls.cpp` | T4 (`faceLayout`'s `deckDescs` build), T5 (`waveMarksFor` / `grabbableMarks`) | Different functions, no shared state |
| `src/app/CMakeLists.txt` | T1 (one `target_sources` line for the new park-queue TU) | Ω's only CMake edit. **Cross-phase adjacency:** Λ-W2-T2, Λ-W2-T3, Λ-W4-T3 and Λ-W5-T1 all own regions of this file. Ω lands first; Λ rebases |
| `src/app/main.cpp` | T1 (the drain call inside `OnTimer`) | **Cross-phase adjacency:** Λ-W2-T1 owns this file's `REAPERAPI_LoadAPI` failure branch — a different region |

#### Ω-W1-T1 — `mode-switch-responsiveness`

**Goal.** A mode switch redraws immediately. The expensive half of the switch still happens; it
just stops being the thing the user waits for.

**Spec:** Ω.7 and rulings Ω-F5, Ω-F6 above; no product doc.

**Surface boundary — owns:** `src/shell/view/view.cpp` (the `applyMode` body — the undo block,
the park/restore loops, the parent-visibility loop, `resolve`), `src/shell/view/view.h`, ONE
new TU pair beside them for the deferred queue, one `target_sources` line in
`src/app/CMakeLists.txt`, the drain call in `src/app/main.cpp`'s `OnTimer`, and
`src/shell/view/CLAUDE.md`'s FX-parking caveat. **Does not own:** `core/view` (that is
Ω-W2-T1's), `design_view_actions.cpp` or `ext_state_io.cpp` (T2's), `panel_input.cpp`
(Ω-W2-T2's), or `view_solo.cpp`.

**The solo surface index is EXONERATED.** It costs ~2T extra API calls, sub-millisecond. Say so
in the brief so it is not re-litigated: the magnitude is two older O(project) costs that are
invisible on a scratch project and quadratic-feeling on a real one.

**Behavior.**
- **The undo mask (`view.cpp:563`).** `Undo_EndBlock2(proj, undoLabel.c_str(), -1)` — `-1` is
  `UNDO_STATE_ALL` (`reaper_plugin.h:1540`), which includes `UNDO_STATE_FX` (`:1542`). REAPER
  marshals **every plugin's state chunk in the project** into the undo record, on every switch
  **and** every tag/untag reapply. The honest mask is
  `UNDO_STATE_TRACKCFG|UNDO_STATE_ITEMS|UNDO_STATE_MISCCFG`, with `UNDO_STATE_FX` OR'd in only
  when the park/restore loop actually toggled an offline state this apply. The second
  `-1` at `:607` (`mintManagedLanes`) is a separate call site — assess it, do not assume it.
- **Unconditional `TrackFX_SetOffline`.** `parkFxOffline` (`:190-195`) calls it on **every FX of
  every parked track**, including tracks already parked and already offline — unloading and
  re-instantiating plugins for no state change. `restoreFxOffline` (`:200-204`) writes verbatim
  from the snapshot the same way. Both compare before writing.
- **`PreventUIRefresh` is used NOWHERE in `src/`** (verified: zero matches across the tree)
  though the SDK exports it (`reaper_plugin_functions.h:5583`). ~10T flag writes land with UI
  refresh live. Bracket the write phase, and keep the existing
  `TrackList_AdjustWindows(false)` + `UpdateArrange()` as the one deliberate rebuild after the
  bracket closes. **Balanced pairing is mandatory** — an early return between the two calls
  leaves REAPER's UI frozen.
- **Compare before write.** `applyFlags` (`:182-186`) and the parent-visibility loop
  (`:521-531`) write unconditionally, so a reapply that changed nothing still pays every write.
- **`resolve` is a linear scan** (`:122-128`) called once per parked / restored / parent / lane
  track — O(T²). `readFolderEntries` (`:103-120`) already builds `handleByGuid` and can hand
  back a hash map instead of a vector of pairs.
- **Ω-F6, and it is the headline.** Split the synchronous apply from the deferred park:
  visibility, routing and FX-enable apply **inside** `applyMode`; per-FX offline/online is
  enqueued and drained on a later idle tick. **The spec must state what the queue does when a
  second switch arrives before the first's park has run** — the answer is that a queue entry is
  keyed by track GUID and the LATEST intent wins (an enqueued park superseded by a restore
  cancels, not stacks), because the two are inverses and replaying both is both slower and
  observably wrong. A project close, a track deletion, or a project switch drains the queue
  without applying it.
- **Where the queue lives.** `src/shell/view/view.cpp` measures **642 lines** today and is
  already over the ~600 ceiling with its seam blocked (`docs/TODO.md`). The queue is therefore
  a **new TU beside it**, not more of `view.cpp` — `src/shell/view/` has no `CMakeLists.txt` of
  its own, so this costs exactly one `target_sources` line in `src/app/CMakeLists.txt` (where
  `view.cpp` and `view_solo.cpp` are already listed). This is the one structural decision in
  the track and it moves `view.cpp` toward the ceiling rather than further past it.
- **The invariant amendment.** `src/shell/view/CLAUDE.md`'s "Documented caveat" bullet accepts a
  load hitch on the return to the active mode. After this track the hitch is still real but no
  longer sits on the switch's synchronous path — the caveat must say so, and must say what the
  deferral does NOT change (the plugin still re-instantiates; un-persisted internal state is
  still lost).

**Acceptance criteria.**
- On a project with enough tracks and FX to reproduce it, the mode switch **paints its new
  state before the park work runs**, and the wall-clock to first paint is recorded before and
  after. `[verify — DAW]`, discharged by Ω-W2-T3.
- A tag/untag reapply on an unchanged project performs **zero** `TrackFX_SetOffline` calls and
  **zero** redundant flag writes.
- The undo record for one mode switch no longer carries FX state when no offline state changed;
  **one switch is still one Ctrl-Z**, and Ctrl-Z still restores what it restored before.
- The park/restore round trip — the phase's trust anchor — still returns every driven flag to
  its captured value, including when the deferral means the park lands on a later tick.
- Two switches in rapid succession leave every track in the state the SECOND switch specifies,
  with no track left offline that should be online.
- `PreventUIRefresh` is balanced on every exit path from `applyMode`, including the two early
  returns already there.
- `view.cpp` is no longer than it is today.

**Prerequisites.** None; concurrent with T2–T5. **Discharges:** Ω.7 (the structural half),
Ω-F5, Ω-F6.

#### Ω-W1-T2 — `mode-switch-persist`

**Goal.** A mode switch stops rewriting the whole session to record that one key changed.

**Spec:** Ω.7's lower-priority half; ruling Ω-F5 (both entry surfaces).

**Surface boundary — owns:** `src/shell/actions/design_view_actions.cpp` (`persistViewState` and
the two callers `doActivateMode` / `doToggleMode`) and `src/shell/persist/ext_state_io.cpp`
(`saveToActiveProject`). **Does not own:** `view.cpp`, `session.cpp`, or any panel TU.

**Behavior.**
- The panel-click path routes through `Main_OnCommand` → `doActivateMode`
  (`panel_input.cpp:330-334`), which calls `persistViewState()` →
  `saveToActiveProject()` — a **seven-key** ext-state write (`ext_state_io.cpp:140-190`: banks,
  the legacy-key clear, view, tail, owned_files, version stamp, bank generation) plus a
  `MarkProjectDirty`, every switch, when only `view_state` changed. Serializing the bank book
  and the tracking ledger is the expensive part and neither moved.
- **Gate by dirty key, not by call site.** A per-key dirty flag on the session, or a
  `saveViewStateOnly` entry point beside the full save — the track picks, and states why. The
  full save must remain the default for every other caller.
- **`saveToActiveProject`'s return contract must not change.** `shell/persist/CLAUDE.md` is
  explicit: `false` means *no active/saved project and NOTHING was written*, and it may never
  grow an observational third failure mode. A narrowed write returns on the same rule.
- The degraded-ledger suppression (`tracking::ledgerDegraded`) and the version stamp both stay
  exactly where they are on the full path.

**Acceptance criteria.**
- A mode switch on a saved project writes `view_state` and nothing else; a capture, a bank op
  and a project save each still write everything they wrote before.
- `MarkProjectDirty` still fires on the narrowed path — the state DID change.
- Both entry surfaces (the footer segment and the bindable action) take the same path.
- The write-count difference is measured and recorded.

**Prerequisites.** None; concurrent with T1. **Discharges:** Ω.7 (the persist half).

#### Ω-W1-T3 — `meter-rate`

**Goal.** The master meter moves at 60 FPS, and a paint costs what one frame should.

**Spec:** Ω.1; no product doc. `src/shell/instrument/CLAUDE.md` already records that "a
meter-rate timer remains a separate change and is not in" — this is that change.

**Surface boundary — owns:** `src/shell/instrument/editor_platform.cpp` (the timer set-up and
the `WM_PAINT`/`invalidate` path), `editor_session.cpp` (the meter block currently inside
`onSyncTimer`), `editor_paint.cpp` (the paint entry's bitmap and dirty-rect handling), the
hero-envelope cache's accessor in `editor_paint_waveform.cpp`, and `reasampler_editor.h`'s
meter/timer/cache members. **Does not own:** `meter_ballistics.h`, `master_meter`, the
processor, any deck or overlay module.

**Ballistics need no rework.** Every constant in `core/instrument/engine/meter_ballistics.h` is
per-second, and elapsed time is measured rather than assumed — the rate is a UI-cadence
problem, not a ballistics one. **Nothing changes on the audio thread.**

**Behavior.**
- **The cause.** There is exactly ONE timer in the tree: `kSyncTimerIntervalMs = 500`
  (`editor_platform.cpp:33`), the bank/bake change-detection poll. The meter's advance +
  `invalidate` sit inside `onSyncTimer` (`editor_session.cpp:113-134`), so 2 FPS is the
  ceiling, and `WM_TIMER` coalescing under host load takes it below that.
- **The recommended shape: a SECOND timer id on the same child window**, running only the meter
  drain, advance and invalidate. **The 500 ms poll body is untouched** — `pollBankSync`,
  `bakeAvailable`, `resolveBakeHoldNeeded` and the bake run must not speed up (a bridge read
  plus a bank parse each), and the tick-counted UI decays (`kBakeMessageTicks`,
  `dropHintTicks_`) keep their meaning for free. **The rejected alternative** is raising the
  shared interval and converting those decays to elapsed time: it is one fewer timer and it
  silently shortens two user-visible banners by ~15× if the conversion is missed anywhere.
- **`masterBusMeter()` is a CONSUMING drain** (exchange-to-identity) and must keep **exactly
  ONE caller**, or the two clocks steal windows from each other. Moving it to the meter timer
  keeps that; leaving a copy behind in `onSyncTimer` breaks it.
- **The meter block must stay AHEAD of the in-flight-drag guard.** On its own timer this is
  structural rather than positional — state it, because the reason (a drag suppresses the
  reload poll but the bus keeps sounding) survives the move.
- **The clock resolution is a real hazard at 16 ms.** Today the elapsed measure is
  `GetTickCount64` (`editor_session.cpp:114`), whose granularity is ~15.6 ms — at 60 FPS the
  measured delta quantizes to 0 or 16 and a zero-delta advance does nothing but burn a frame.
  Move the meter clock to a high-resolution source off the audio thread, and skip the advance
  on a zero delta rather than passing it in.
- **Three paint costs, all of which the higher rate exposes:**
  1. `invalidate()` (`editor_platform.cpp:65-66`) dirties the **whole client area**
     (`InvalidateRect(hwnd, nullptr, FALSE)`) and `paint` never reads `ps.rcPaint`
     (`:149-155`). The meter needs its own rect invalidated and the paint needs to honour the
     dirty rect.
  2. `paint` allocates a **full-size `LICE_SysBitmap` per paint** (`editor_paint.cpp:28`).
     Hoist it to a member resized on `WM_SIZE`, where `thumbCache_.clear()` already lives.
  3. `paintWaveform` recomputes `computeEnvelope` over the **entire decoded PCM every paint**
     (`editor_paint_waveform.cpp:204-212`). The decode is cached (`channelPcmFor` /
     `monoPcmFor`); the hero envelope is not. Cache it keyed on sample id + bin count + lane
     count, busted by `refreshFromBank` and by a resize — the same discipline `marksCache_` and
     `thumbCache_` already use.
- **Target 60 FPS.** 30 is the fallback **only** if the paint-cost work cannot get there, and
  choosing it requires the measurement that justifies it.

**Acceptance criteria.**
- The meter visibly tracks transients at the shipped rate; the measured frame interval is
  recorded, with the rate chosen and why. `[verify — DAW]`, discharged by Ω-W2-T3.
- A meter-only frame does **not** repaint the deck, chrome or waveform bands.
- No allocation in the steady-state paint path.
- With no editor open, nothing changed: the meter timer is bound to the child window exactly as
  the sync timer is.
- The bake affordance, the Hold applicability, the bake message and the drop-hint banner all
  behave exactly as before — same cadence, same durations.
- `masterBusMeter()` has exactly one call site.
- The first meter read is still DISCARDED (session history, not a window) and the clip latch is
  still NOT discarded with it.

**Prerequisites.** None; concurrent with T1, T2, T4, T5. **Discharges:** Ω.1.

#### Ω-W1-T4 — `deck-reflow-focus-toggles`

**Goal.** Filter Mod sits with the envelope it modulates, the Trigger face's knobs sit at their
natural pitch, a two-state toggle is one button, and clicking an envelope deck anywhere selects
its overlay.

**Spec:** Ω.2, Ω.3, Ω.4 and rulings Ω-F3, Ω-F4. Deck composition law:
`src/core/instrument/ui/CLAUDE.md`'s `knob_deck` and `deck_groups` bullets.

**Surface boundary — owns:** `src/core/instrument/ui/deck_groups.{h,cpp}`,
`knob_deck.{h,cpp}`, `deck_values.cpp` (the toggle commit seam only),
`src/core/instrument/ui/sample_chrome.{h,cpp}`, `src/shell/instrument/editor_paint_deck.cpp`,
`editor_input_deck.cpp`, `editor_paint_chrome.cpp`, `editor_input_chrome.cpp`,
`editor_input.cpp` (the focus-clear arm of the mouse-down dispatch only), `editor_controls.cpp`
(`faceLayout`'s `deckDescs` build only), and `tests/test_deck_groups.cpp`,
`tests/test_deck_groups_measured.cpp`, `tests/test_knob_deck.cpp`. **Does not own:**
`waveform_view`, `envelope_overlay`, `spline_edit`, or any `editor_*_waveform` TU (T5's), and
**no `param/` file whatsoever**.

**Behavior — Ω.2, the move.**
- `sampleDeckGroups` (`deck_groups.cpp`) is the sole deck-composition authority. Move
  `kFilterModAmt` out of FILTER's `cellIds` (`:62-68`) into FILTER ENV's (`:80-89`), **in both
  faces**, landing **last in the run** — the `kPitchEnvDepth` precedent (`:50-53`).
- **NO VST3 parameter-id change.** `kFilterModAmt` keeps its frozen id (`kParamFilterModAmount`
  = 1240) and its unit; ids are keyed on `DeckParam`, never on deck group. `param_id.h`'s
  header comment says the blocks run "per deck group in SIGNAL-FLOW order" (`:34`) — that
  becomes untrue for exactly one row. **Annotate it; never renumber.** The frozen-table rule is
  the whole reason the annotation is the correct fix.
- **This move costs the filter tie-line, and that cost is stated rather than discovered.**
  Today `test_deck_groups_measured.cpp` pins both rows' filter groups to one right edge (x=640
  block-relative) — the only reason the row block was widened 1020→1028. After the move the
  Sound row loses 60 px and the Contour row gains 60, and with the two filter groups then equal
  in width the tie is **unreachable at any block width** under the space-between law. The
  alignment goes; the ruling stands. Re-derive that fixture from the new contract, record the
  loss in it, and **do not change `kDeckRowBlockW` or the editor floor to chase it** — revisiting
  the floor is a separate proposal, not this track's to make.

**Behavior — Ω.2, the spacing.**
- The defect is a **layout-algorithm artifact, not a constant**. A group's cell run is a
  reserved width, and a `-1` reserve donates its slot to the surviving cells
  (`knob_deck.cpp:123-138`), so Trigger's FILTER ENV gets ~100 px cells and AMP ENV ~75 against
  the standard 60. Gate is uniform at 60 because it has no reserves.
- **The law: cells stay at `kDeckCellW`; the run is CENTRED in the reserve.** It is the only
  candidate satisfying both of Daniel's constraints — group width stays stable across a mode
  flip (which is why the reserves exist) and knobs keep their natural pitch. Gate is then
  pixel-identical by construction, because with no reserves the centred run and the divided run
  are the same run.
- **The two rejected laws, recorded so they are not re-proposed:** *(a) drop the reserves and
  let the group narrow in Trigger* — reflows every neighbour on a mode flip, which is the thing
  the reserves were introduced to prevent; *(b) keep the division but cap the cell width* —
  leaves an asymmetric residue at one end and re-introduces the dead-slot look the division law
  replaced.
- A consequence worth stating: once the run is centred, **where a `-1` sits in `cellIds` stops
  mattering** — only the count does. The reserves become pure width accounting.

**Behavior — Ω.3, single buttons.**
- Today every deck toggle is structurally two segments (`DeckToggleDesc` `knob_deck.h:69-72`,
  `placeToggle` `knob_deck.cpp:82-94`, `hitTestDeck`'s seg0/seg1 `:259-268`, `drawToggle`
  `editor_paint_deck.cpp:117-137`). **Two variants replace it:**
  - **Enable** — one button, label names what it controls, **`AccentPrimary` when active and
    dim gray when off** (Ω-F3, Ω-F4). Applies to: the envelope enable (`kPitchEnvEnable`,
    today `Off|On`) → **"Envelope"**; `kFilterEnable` → **"Filter"**; `kLimiterEnable` →
    **"Limiter"** (it currently names nothing it controls); and the chrome row's
    `Loop Off|Loop On` → **"Loop"**.
  - **Mode selector** — one button whose **label reads the current mode**, with **no dim-gray
    state** (Ω-F3). Applies to: the three `Stg|Spl` env-mode toggles → **"Stage"/"Spline"**,
    and the chrome row's `Mono|Stereo`.
  - **A third visual state survives and must not be collapsed into "off":** a control refused
    by the mode (the chrome Loop enable in Trigger, `kFilterLaw` while the filter is off) draws
    **Disabled and inert**. Off is live and clickable; Disabled is not.
- **Explicitly NOT converted, and that is a boundary rather than an oversight:** `kPlayMode`
  (`Gate|Trigger`), `kPitchEngine` (`Varisp|Presrv`), `kVoiceMode` (`Poly|Mono`), `kFilterLaw`
  (`Band|Notch`) and `kMonoTrigger` (`Retrig|Legato`) keep their two-segment form. Daniel named
  a set; this track ships that set. The mode-selector variant generalizes to them the day he
  asks.
- **Three load-bearing risks the implementation must answer:**
  1. **A single button carries no `segment`**, so the commit must derive the NEXT state from the
     current one. `setDeckParam`'s `segment == 1` contract (`deck_values.cpp:135-162`) is the
     seam; `nextOverlaySelection` (`deck_groups.cpp:298-302`) is the existing precedent for
     computing a next discrete state purely.
  2. **`kLimiterEnable`'s split commit must survive VERBATIM** (`editor_input_deck.cpp:67-79`):
     it commits the parameter set and calls `setLimiterEnabled`, which only ARMS the host's
     `restartComponent(kLatencyChanged)` for the sync tick to deliver. Nothing on that path may
     start calling into the host from inside a mouse handler.
  3. **MASTER has ZERO caption slack** — its caption row (46 + toggle + radio) and its knob row
     (`kDeckCellW` + `kDeckColumnGap` + `kMeterColumnW`) both measure 130, which is exactly
     `kDeckSpanningW` − 2·`kDeckGroupPadX`. **So the "Limiter" button must be ≤ 64 px** (what
     the two 32 px segments occupy today). Wider and `kDeckSpanningW` grows, which pushes
     `kEditorMinWidth` into the 82 px budget against `kEditorCeilingWidth`. **Every other group
     absorbs a wider button free** — verified: each is knob-row-bound with 50+ px of caption
     headroom.
- **`hitTestKnobFace` runs no toggle-precedence pass** (`knob_deck.h:217-224`) and is only
  correct while no toggle rect overlaps a knob circle. Any button that grows must be re-checked
  against that, or given the same precedence order.

**Behavior — Ω.4, the deck as the overlay target.**
- Remove the three overlay radios from the env groups' descriptors. **`DeckRadioDesc` itself
  must SURVIVE** — MASTER uses a passive one for the GR lamp (`deck_groups.cpp:140`), and the
  `passive` skip in `hitTestDeck` (`:255-258`) is what keeps a readout from growing a gesture.
- Clicking anywhere in an envelope deck — panel background, knob, or button — focuses that
  envelope's overlay. The focused deck takes an **`AccentPrimary`** border (Ω-F4). Focus clears
  when the user clicks a control surface outside it; **the overlay itself is excluded from
  stealing or clearing focus**, so editing the thing you selected does not deselect it.
- **Five design constraints, each of which breaks something if missed:**
  1. The focus write must land **before** the `hit.kind` switch in `mouseDownDeck`
     (`editor_input_deck.cpp:31-37`) and must **not consume the click**, or every knob grab and
     every toggle commit on a focused deck dies.
  2. **`DeckHit` carries no group id** (`knob_deck.h:190-195`). Either the shell scans
     `dl.groups` for containment or `DeckHit` gains one — the latter is the smaller lie.
  3. **`hitTestDeck` returns a miss for a click on group padding** (`knob_deck.cpp:252-278`),
     which is exactly the "deck panel background" Daniel wants to hit. Containment against
     `g.box` is the test, not the hit kind.
  4. **A deck click SETS focus idempotently; it does not toggle to none.** Otherwise every knob
     tweak on the focused deck unfocuses it. This retires `nextOverlaySelection`'s
     re-click-clears branch (`deck_groups.cpp:301`) and replaces the radio-id map with a
     group-id one.
  5. **Refuse focus changes while a drag is in flight**, and keep `overlayEnv_` transient view
     state with **no persistence path** — it is what is drawn, not what is played.

**Acceptance criteria.**
- **The Gate deck face is pixel-identical to today**, group boxes and cell rects both, asserted
  in `test_deck_groups_measured.cpp` — except for FILTER and FILTER ENV, whose widths move by
  exactly one cell in opposite directions.
- Trigger's FILTER ENV and AMP ENV cells measure `kDeckCellW`, centred in their reserves, and
  each group's box width is **identical in both modes**.
- `kFilterModAmt`'s `ParamId` is unchanged; `exposedParams()` returns the same list in the same
  order; no `param/` file is edited.
- Every converted control's commit produces the same audible result as the segment it replaced,
  including the limiter's latency-restart arm.
- `kEditorMinWidth` and `kEditorCeilingWidth` are unchanged, and the 82 px budget is unspent.
- Clicking a knob, a button and the bare panel of an envelope deck all focus that overlay; the
  first two still perform their own action; clicking the overlay does not change focus.
- The deck's pure fixtures cover the new composition, the centred run and the focus map — the
  shell layer is not where any of the three is asserted.

**Prerequisites.** None; concurrent with T1, T2, T3, T5. **Discharges:** Ω.2, Ω.3, Ω.4, Ω-F3,
Ω-F4.

#### Ω-W1-T5 — `overlay-mapping`

**Goal.** The loop marks say what the mode means, and every overlay that rides the waveform
lands on the frame it names.

**Spec:** Ω.5, Ω.6 and rulings Ω-F1, Ω-F2; `docs/product/instrument-control-surface.md`
§6.3(d) and §6.4 **as amended by Ω-F2**.

**Surface boundary — owns:** `src/core/instrument/ui/waveform_view.{h,cpp}`,
`src/shell/instrument/editor_paint_waveform.cpp` (the mark, caption and overlay regions),
`editor_input_waveform.cpp`, `editor_controls.cpp` (`waveMarksFor` / `grabbableMarks` only),
`src/core/instrument/ui/spline_edit.h`'s `splineOverlayBox` and `envelope_overlay`, and
`tests/test_waveform_view.cpp` + `tests/test_component_geometry.cpp`. **Does not own:** any
deck module or `editor_*_deck` TU (T4's), the envelope-cache region of
`editor_paint_waveform.cpp` (T3's), or — **without an explicit proposal** —
`src/shell/panel/draw_kit.cpp` and `src/core/ui/component_geometry.cpp`.

**Behavior — Ω.5, the marks.**
- **Trigger ⇒ the loop pair and the crossfade mark are HIDDEN entirely.** Not Disabled, not
  dim: absent. **Gate with loop off ⇒ they are still drawn and still grabbable**, styled **grey
  disabled** rather than today's dim teal at `kMarkAlphaDisabled`
  (`editor_paint_waveform.cpp:51-53, 285-305`). The drag-to-set-loop gesture survives, which is
  the whole reason they stay grabbable.
- **The `"LOOP \xe2\x80\x94 GATE ONLY"` caption (`editor_paint_waveform.cpp:254`) is removed
  unconditionally** — in Trigger there is no span left to centre it in.
- **The two sibling captions SURVIVE** (`:255`): `DRAG TO SET LOOP` when the pair is parked and
  `LOOP OFF` when a span is retained. Their box derives from the marks' span, which still
  exists in Gate, and they carry the one thing the grey styling cannot — *which* off state you
  are in, and that dragging will fix it. **This is a [propose]-class call made here rather than
  left open**; reversing it means deleting information, not styling.
- **The seams.** `waveMarksFor`'s `present[]` (`editor_controls.cpp:145`) is the correct place
  to suppress — it also skips label layout. `grabbableMarks` (`:162`) is the separate
  grabbability fold and must NOT follow it into hiding for the Gate-off case.
- **Warning to the track:** `resolveWaveformClaim`'s smallest-area arbitration and `capAtPoint`'s
  reverse-order resolve (`editor_input_waveform.cpp:65, 89, 111, 212`) are both sensitive to
  marks entering and leaving the set — `capAtPoint`'s reverse order exists precisely so a
  coincident pair stays separable and the crossfade (the one mark with no column) can never be
  shadowed (`core/instrument/CLAUDE.md`, `waveform_view`). Changing which marks are in the set
  changes what those two resolve, which is the regression risk in this half of the track.

**Behavior — Ω.6, the mapping. This is a CLASS defect, not a START-marker instance.**

Two chains that do not agree, verified this pass:

| | Marker / overlay chain | Waveform draw chain |
|---|---|---|
| Area | `waveformOverlayArea` returns the **FULL band, no inset** (`waveform_view.cpp:23-25`) | insets **2 px each side**; `waveformColumnCount` = `width − 4` (`component_geometry.cpp:95-98`), columns start at `box.x + 2` (`draw_kit.cpp:292, 311`) |
| Domain | `[0, N]` **closed** (`clampFrame`, `waveform_view.cpp:15-19`) | `[0, N)` **half-open** (`columnMinMax`, `peaks.cpp:69-71`) |
| Rounding | **round-half-up** (`frameToX`, `:51-58`) | **truncation** |
| Phase | a 2 px marker line centred on an integer (`editor_paint_waveform.cpp:298, :301`) | a 1 px column centred at +0.5 |

Four independent discrepancies. The 2 px inset dominates — frame 0's marker sits 2 px left of
the first column and the error reverses sign toward the end — and the closed domain puts the
last frame one pixel outside the band. **Everything riding the overlay inherits all four:** the
loop span fill, the crossfade wedges, the staged envelope polyline, and the spline contour —
whose `spline_edit.h` guarantee that it uses "the FULL area, no inset, so the drawn contour
stays 1:1 with the sample's time axis" **is currently false by 2 px.**

- **Mandate ONE mapping both chains read**, so the drift cannot recur. Two routes:
  - **Inset the overlay** (recommended) — instrument-only, and the overlay is already the
    distinct `OverlayArea` type, so the change has one construction site.
  - **De-inset the waveform** — `[propose]`, **requires justification before it is taken**: it
    touches the shared `draw_kit`, which also serves the docked bank panel and the browse-card
    thumbnails, so its blast radius is three surfaces rather than one.
- **The zero-crossing snap must be preserved EXACTLY.** `nearestZeroCrossing`
  (`waveform_view.cpp:170-199`) fans out symmetrically, probing `t−d` before `t+d` so an
  equidistant tie resolves to the lower frame; it runs on the mono cache at drag time and is
  exempt for the crossfade handle. **Behaviour-identical after the change** is an acceptance
  criterion, not an assumption.
- **One question the track must ANSWER, not dodge:** what "sample-accurate" means when
  `frames < columns` and one frame spans many pixel columns. A marker must land somewhere
  inside its own frame's column span; which end, and whether the inverse `xToFrame`
  (`:60-70`) still round-trips, are the track's to specify.
- **The two pinned fixtures are re-DERIVED from the new contract, never re-baselined.**
  `test_waveform_view.cpp` and `test_component_geometry.cpp` pin the current mapping by
  construction; a test updated to match whatever the code now prints has stopped being a test.

**Acceptance criteria.**
- One mapping function serves both chains; a second one anywhere in the tree fails review.
- Every mark, the loop span fill, both crossfade wedges, the staged polyline and the spline
  contour land on the same pixel as the waveform column for the frame they name — at frame 0,
  at the last frame, and at an interior frame, asserted in the pure fixtures.
- `frames < columns` and `frames > columns` are both covered.
- The zero-crossing snap returns the identical frame for the identical input, ties included.
- Trigger shows no loop marks and no loop caption; Gate-with-loop-off shows grey marks that
  still drag, and dragging one still turns the enable on.
- `docs/product/instrument-control-surface.md` §6.3(d) and §6.4 agree with the shipped
  behaviour.

**Prerequisites.** None; concurrent with T1–T4. **Discharges:** Ω.5, Ω.6, Ω-F1, Ω-F2.

---

### Ω-W2 — What the measurements say, and what only a DAW can

**Depends on:** Ω-W1 in full. **Three tracks.** T1 is **measurement-gated** — it may turn out
not to be worth doing, and that is a legitimate outcome. T3's deliverable is a **RECORD, not a
fix** (the Λ-W3 precedent).

| Track | Owns |
|---|---|
| **T1** `view-topology-cache` | `src/core/view/` — tree / `parentOf` / handle-map memoization |
| **T2** `content-detect-throttle` | `src/shell/panel/panel_input.cpp` — the 30 Hz detector's cadence |
| **T3** `omega-verification-sweep` | `docs/VERIFICATION.md` — the DAW record for all of Ω-W1 |

**Shared files in the wave:** none. The three tracks touch three disjoint directories.

#### Ω-W2-T1 — `view-topology-cache` *(measurement-gated on Ω-W1-T1's result)*

**Goal.** Stop rebuilding the project's folder topology from scratch on every apply — if, after
Ω-W1-T1, that is still a cost worth paying for.

**Spec:** Ω.7's residual.

**Surface boundary — owns:** `src/core/view/` (the folder tree, `parentOf`, and the
GUID→handle map's shape). **Does not own:** `shell/view/view.cpp` — Ω-W1-T1 owns it, and this
track lands after.

**Behavior.** `readFolderEntries` + `buildFolderTree` run on every apply, and `visibleTracks`
walks the tree per parent. **The gate:** Ω-W1-T1 removes the undo-mask cost, the redundant FX
writes and the O(T²) resolve; if the remaining apply time is already inside the frame budget on
the measured project, **this track is not dispatched** and its finding is recorded instead.
`docs/TODO.md` already carries `view_mode_model.cpp` at 815 lines with a named codec seam — do
not take that seam here, and do not grow that file.

**Acceptance criteria.**
- A before/after measurement on the same project decides the track, and the number is recorded
  either way.
- If taken: membership, reconcile and the park/restore round trip are all unchanged, and the
  cache is invalidated by every path that can change track topology.
- `core/view` stays pure and CTest-covered.

**Prerequisites.** Ω-W1-T1 landed and measured. **Discharges:** Ω.7's residual, or records it
as not worth taking.

#### Ω-W2-T2 — `content-detect-throttle`

**Goal.** The auto-tag detector stops spending main-thread time 30 times a second on exactly
the projects where the switch is slowest.

**Spec:** Ω.7's third cost.

**Surface boundary — owns:** `src/shell/panel/panel_input.cpp` (`detectNewContent` /
`enumerateLiveGuids` and their cadence inside `bankPanelRefresh`). **Does not own:** any other
panel TU, `view.cpp`, or the auto-tag RULES — only how often they are asked.

**Behavior.** `detectNewContent` (`panel_input.cpp:146`) runs `enumerateLiveGuids` (`:106-135`)
every tick of REAPER's `"timer"` registration (`main.cpp:341`, ~30/sec), regardless of panel
state — O(T + I) REAPER string calls plus ~4(T+I) allocations per tick, on every project. Throttle
the ENUMERATION, not the detection semantics: the same GUIDs must still be detected, the
first-poll and `reloadPending` re-baseline guards must still fire before any diff, and the
lane-minting pass must still run only on a tick that tagged. A slower cadence is a latency
change to an invisible background tag, and the track states the chosen interval and why.

**Acceptance criteria.**
- New tracks and items are still auto-tagged into the active mode, and a project reload still
  re-baselines rather than mass-tagging.
- The Ρ-W1-T1 rule survives: an added GUID that already carries a membership record is still
  dropped, so a render-in-place result is not re-tagged to Design.
- The per-tick allocation count is measured before and after.
- The panel's own repaint cadence is unaffected.

**Prerequisites.** None beyond Ω-W1. **Discharges:** Ω.7's polling cost.

#### Ω-W2-T3 — `omega-verification-sweep`

**Goal.** The DAW-observable half of this phase is recorded rather than assumed.

**Spec:** every `[verify — DAW]` criterion in Ω-W1.

**Surface boundary — owns:** `docs/VERIFICATION.md` (a Phase-Ω section). **Does not own:** any
source file. **A fix found here opens a track; it is not applied inside this one.**

**Behavior.** One session, on a project big enough to reproduce Ω.7 (the track records how big
that turned out to be, because that number is itself a finding). Covers: switch-to-first-paint
before and after; two switches in rapid succession; the park/restore round trip after
deferral; Ctrl-Z after a switch; the meter's frame interval and its behaviour under host load;
the Gate deck face against a pre-Ω screenshot; the Trigger face's spacing; every converted
button in all three states; deck-click overlay focus; the marks in Trigger and in
Gate-with-loop-off; and a marker aligned against the waveform at both ends of a sample.

**Acceptance criteria.**
- Every `[verify — DAW]` mark in Ω-W1 is discharged by a recorded observation or explicitly
  carried forward as unrun. **Inspection is not an observation.**
- The two performance numbers are recorded with the machine and project they were measured on.

**Prerequisites.** All of Ω-W1 landed. **Discharges:** Ω's DAW criteria.

### Shared files across Ω's waves, named rather than discovered at merge

All textual adjacency unless marked otherwise. Within Ω-W1 the wave's own table above is the
authority; this one covers the cross-wave and cross-phase cases.

| File | Tracks | Nature |
|---|---|---|
| `src/shell/view/view.cpp` | Ω-W1-T1 alone | **Deliberately not split.** Already 642 lines and over the ceiling with its seam blocked; T1's new code goes in a new TU beside it rather than into it |
| `src/core/view/*` | Ω-W2-T1 alone | Different wave from T1's shell edits, and gated on them |
| `src/app/CMakeLists.txt` / `src/app/main.cpp` | Ω-W1-T1 | **Cross-phase:** Λ owns other regions of both (Λ-W2-T1/T2/T3, Λ-W4-T3, Λ-W5-T1). Ω lands first; Λ rebases |
| `src/shell/panel/panel_input.cpp` | Ω-W2-T2 | **Cross-phase:** Ρ-W1-T1's `detectNewContent` edit is landed and is the same function — read what shipped, not what was specced |
| `docs/VERIFICATION.md` | Ω-W2-T3 | Λ-W3-T1 writes its own Linux record into a different section |

---

## Phase Λ — ReaSampler on Linux: both artifacts, shipped

**Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented
on Linux. The extension compiles under GCC/Clang, builds optimized by default, docks its
panel, writes a bank index that survives a comma-decimal locale, and prunes without lying
about what it reclaimed. The instrument loads, scans, instantiates and processes audio in
**any** Linux host, and opens its editor under **REAPER only** — a stated contract, not a
shortfall. macOS is out.

**Four named exceptions to platform parity**, stated up front rather than discovered:
prune deletes permanently (no trash); the OS drag-out's copy-only guarantee is conventional
rather than structural, because SWELL's file-list drag takes no effect mask; the type faces
are DejaVu rather than Segoe UI and Consolas; and the ReaSampler 9000 editor comes up under
REAPER only, with every other host getting its generic parameter UI.

**Consolidates: none of the seventeen.** Phase Λ came from a direct request (Daniel,
2026-08-02) and is scoped in `docs/product/linux-readiness.md`, which is itself downstream of
two landed audits: `docs/product/audit-notes/lambda-w1-t1-build-toolchain.md` (findings
Λ-01…Λ-10, verify items V1…V10, forks D1…D7 — build system, toolchain, vendored deps,
resources, test harness, packaging) and
`docs/product/audit-notes/lambda-w1-t2-source-runtime.md` (L2-01…L2-12 — source portability
and runtime behaviour). **Grep those for a cited finding number; do not read either whole.**
This phase **supersedes nothing**, and corrects one standing promise rather than inheriting
it: `docs/product/versioning-and-release.md` already commits in writing to "three platform
artifacts per channel per release" (T1 §1e), which Λ-D4 makes **two** for now, with macOS
named as deferred rather than silently dropped.

**Nothing in this phase has been verified on a Linux machine.** Every `[verify — Linux]`
mark below is load-bearing and none may be laundered into settled voice. The structural
consequence is stated once here because it shapes the whole wave order: **Λ-W2's edits are
authored blind from the audits' citations, and Λ-W3 is the session that discharges Λ-W2's
acceptance criteria.** Λ-W2 is not "done" in the usual sense until Λ-W3 runs, and a track
that reports otherwise has laundered unverified work into landed work — which is exactly
what the audits' `[verify]` discipline exists to prevent. Both audits state their own effort
bands as provisional until the sweep runs; this plan does not restate a band the audits did
not give.

### Rulings — Daniel's, 2026-08-02. Six, settled.

Full statements with the reasoning Daniel gave: `docs/product/linux-readiness.md`
§"Settled decisions (Daniel, 2026-08-02)". **Do not re-litigate these.**

| Ruling | What it settled | Specified in | Bound into |
|---|---|---|---|
| **Λ-D1** | **The VST3 instrument is in scope** (audit fork D1 = **Fork B**). Both artifacts ship, not just the extension. **Reverses D5's platform clause in writing** — "Windows-only, VST3-only, REAPER-only" becomes "Windows and Linux; the editor is REAPER-hosted"; the VST3-only and REAPER-only clauses survive verbatim | §Λ-D1 | Λ-W6…Λ-W8; the D5 rewrite is Λ-W6-T1's first act |
| **Λ-D2** | **SWELL is reached by `dlopen`ing REAPER's own `libSwell.so`** (route **B3a**). Route B3b — building SWELL into the module, and with it GDK/GTK3, FreeType, Fontconfig, OpenGL — is **rejected**, not deferred | §Λ-D2 | Λ-W7-T1 |
| **Λ-D3** | **Non-REAPER hosts must be protected** — *"REAPER-only is good for now, but we need to protext other hosts."* A first-class acceptance criterion of two tracks, not a footnote on one | §"The non-REAPER-host safety criterion" | Λ-W6-T2 (contract), Λ-W7-T1 (runtime), Λ-W8-T1 (regression) |
| **Λ-D4** | **macOS is out** (D3 = **out**) — *"I don't have a mac to compile on."* Shared macOS/Linux edits are still made **in their shared form and noted as shared**; what is out is macOS as a deliverable, any macOS verification, signing/notarization, and L2-11's APFS half. **Do not "fix macOS while you're in there"** — an unverifiable edit to the APPLE branch is worse than none, because it looks tested | §Λ-D4 | phase-wide |
| **Λ-D5** | **The Linux artifact is shipped, not developer-only** (D6 = **shipped**), which promotes Λ-02 (a real optimized build) and Λ-08 (a documented install path) to must-fix. **And a hard `unlink` prune is acceptable**, with a **platform-aware confirmation string** saying so | §Λ-D5 | Λ-W2-T2, Λ-W4-T1, Λ-W5-T1 |
| **Λ-D6** | **The X11 editor is real work and is sequenced last.** Not a separate ruling — the direct consequence of D1+D2+D3. Everything before it ships something; halting Λ-W8 still leaves a shipped extension and a loading, processing, generic-UI instrument | §Λ-D6 | the wave order itself |

### Open forks — four, NONE ruled

Stated with the evidence for both sides at `docs/product/linux-readiness.md` §"Open forks —
Daniel's". This is the one phase in this plan with unanswered [Daniel]-class questions; see
"Decision state" above for how that reconciles against the plan-wide claim.

| Fork | Question | Recommendation | What it blocks |
|---|---|---|---|
| **Λ-F1** | Does CI get built in this phase, and on what runner? (audit D4) | **No recommendation — genuinely a resourcing call.** For: two toolchains and one is not on the developer's machine, so every Windows-only commit becomes a latent Linux regression. Against: CI is infrastructure, its value is highest *after* the first Linux build works, and a runner forces Λ-F4 immediately | **Nothing.** Decides only whether Λ-W5-T1 writes a pipeline paragraph or a "deferred to dev-ops" one |
| **Λ-F2** | The dialog-resource route: **resource-id-0** (SWELL's documented escape hatch, `swell-functions.h:606–608`; deletes the whole resgen pipeline from non-Windows builds) or **wire up resgen properly** (PHP, `add_custom_command`, an include-shim TU — the M end of Λ-01's band)? (audit D7) | **No clean recommendation; the asymmetry neither audit stated is the reason it is worth ruling.** Route A is cheaper and structurally simpler and stakes the panel's file-drop ingest on an *unverified* `ChildWindowFromPoint` descent, because with no resource there is no `WS_EX_ACCEPTFILES` bit to set (L2-07). Route B can set that bit explicitly. **A third option, if the cheap route is wanted without the exposure:** take Route A and make the file-drop check a hard gate in Λ-W3's sweep, with Route B as an additive follow-up track rather than a rewrite — it spends a second Linux session in the bad case | **Λ-W2-T3's dispatch — the ONLY fork here that gates a track.** Unruled, that track slips to Λ-W4 and Λ-W3's sweep splits into two Linux sessions |
| **Λ-F3** | Does "copy-only is structural" survive as a shipped invariant? (T2 [Daniel] 4) | **Keep the invariant, scope the *enforcement* claim to Windows** — "structurally enforced on Windows via `DoDragDrop`'s copy-only mask; advertised, not enforced, on SWELL, which takes no effect mask." The real counter: an invariant one platform cannot enforce is arguably not an invariant, and weakening the global wording would also correctly warn a Windows reader off relying on it in shared code | **Nothing.** Λ-W4-T3 makes the edit either way; only the sentence changes. Answerable at implementation review |
| **Λ-F4** | What is the declared support floor? (new — the audits raised the inputs Λ-07, V8, V10, not the decision) | **Declare a narrow floor — current-stable-distro glibc, GCC ≥ 9, `x86_64-linux` only — and widen it on request.** Counter: REAPER's Linux reputation is partly built on modest and non-x86 hardware, `aarch64` is no longer exotic, and adding it later means a second bundle directory and a second verification pass on every release | **The ship wave, not a dispatch.** Non-gating for Λ-W2 (a recorded floor can be widened); **gating for Λ-W5-T1** — a shipped artifact has to say what it runs on. Also feeds Λ-W6-T1's bundle directory set and Λ-F1's runner image |

### Phase-Λ acceptance criteria

These bind every track in this phase, in addition to the plan-wide set above.

- **Windows behaviour does not change. Every track carries that criterion explicitly.** This
  is a port; a port that improves Windows by accident has also changed Windows by accident.
- **`[verify — Linux]` is a status, not a decoration.** A criterion carrying it is discharged
  by a recorded observation in Λ-W3's verification record — never by inspection, never by
  "should work". A track whose Linux criteria are all unrun is not landed; it is authored.
- **No vendored file is patched.** `git status` under `vendor/` stays clean through the whole
  phase, including the SWELL bootstrap — which is precisely the constraint that shapes it.
- **No new third-party dependency surface.** Λ-D2 rejected route B3b for this reason: no
  vendored SWELL build, no GDK/GTK3, no FreeType, no Fontconfig, no OpenGL, no `pkg-config`
  in this tree. **A track that finds itself reaching for one of those has drifted and stops.**
- **Shared macOS/Linux edits are made in shared form and labelled shared** (Λ-D4). Λ-01,
  Λ-03, Λ-04, Λ-07, Λ-09 and L2-09 are all shared by the audits' own marking; an
  `#ifdef _WIN32` / `#else` that is right for both costs nothing and needs no mac. No macOS
  verification is claimed for any of them.
- **`core/` stays pure and stays platform-neutral.** Every `#include` under `src/core/**` is
  a `core/` sibling, one of 26 standard headers, or the generated `version_generated.h` (T2
  §1.1) — the audits verified this rather than assuming it, and no Λ track may be the one
  that breaks it. The one platform fork in the whole directory
  (`capture_paths.cpp:18–20`, the Windows case-fold) is already correct for Linux with both
  branches asserted by `tests/test_capture_paths.cpp`.
- **Every pure module gets a `<module>_tests` target** that runs without REAPER or a DAW.
  Λ-W2-T4 is the phase's proof that this is not ceremonial: it is a `core/`-only fix, fully
  testable on the current Windows box, and it lands before any Linux session.
- **The ~600-line ceiling and the structural heuristics bind unchanged.** Λ-W8-T1 is the
  track most likely to strain them; its own criteria name the seam vocabulary to reuse.

**Performance posture.** No named hot path is touched by any track in this phase — not the
`peaks` envelope compute, not audition, not the realtime-capture tick's single-pointer-test
idle fast path, not the instrument's `process()`. Two consequences are stated as criteria
rather than left implicit: **nothing is added to `process()`** (no `dlopen`, no probe, no
platform branch on the per-voice-per-sample path — the SWELL availability probe is computed
once, lazily, off the audio thread), and **Λ-W2-T4's locale fix stays on the JSON/persist
path**, which root `CLAUDE.md` already declares off all hot paths. Λ-02 is the phase's one
genuine performance item and it runs the other way: today a Linux build carries **no `-O`
flag at all**, on a tree whose `peaks` path is documented as presuming an optimizing build.

### The non-REAPER-host safety contract

Λ-D3 stated as something a person can check. **This is an observable contract, and it is the
specification the two instrument waves are graded against** — the full argument is
`docs/product/linux-readiness.md` §"The non-REAPER-host safety criterion", which a brief for
Λ-W6-T2, Λ-W7-T1 or Λ-W8-T1 must be written against.

**The finding that makes it urgent, and it is a fact rather than a risk.** The vendored
`vendor/WDL/WDL/swell/swell-modstub-generic.cpp` declares a **file-scope static** at `:125`,
so its constructor runs when our `.so` is `dlopen`ed — i.e. **during the host's plugin
scan** — and `:102` calls **`exit(2)`** when `dlopen` of `libSwell.so` fails, `:117`
**`exit(1)`** on an incomplete API table. Compiling the vendored `SWELL_LOAD_SWELL_DYLIB`
path unmodified therefore means **a Bitwig or Ardour plugin scan on a machine without
`libSwell.so` terminates the host process** — a killed DAW mid-scan, with the user's
session. The mitigating detail that shapes the fix: `doinit` substitutes a zero-returning
`dummyFunc` for each unresolved name, so a *partial* table degrades rather than crashes. A
partial load is survivable; `exit()` is not.

**In any Linux host, with or without `libSwell.so`:** the module scans and enumerates its one
class with no crash, hang, process exit or blacklist entry; it instantiates, produces audio,
plays MIDI, and round-trips component state byte-identically with the Windows build; when no
usable platform surface exists `isPlatformTypeSupported` returns `kResultFalse` for **every**
type — including `kPlatformTypeX11EmbedWindowID` — and `createView(kEditor)` returns
**`nullptr`**, never a view that then fails to attach and never one that draws nothing; the
host falls back to its generic parameter UI; exactly **one** diagnostic line per process
names why the editor is unavailable, through the SDK's logging or stderr, never a modal;
teardown crashes nothing and leaves no partially-initialised SWELL table reachable.

**Verified with a harness that is already on disk and that neither audit named**, because
neither swept the SDK's `samples/` tree:
`vendor/vst3sdk/public.sdk/samples/vst-hosting/validator/` is a scriptable, headless,
REAPER-free host, under `public.sdk/` and therefore pulled by the documented narrow submodule
init. The four checks, all `[verify — Linux]`: `validator` completes with **no `libSwell.so`
on the filesystem** (the direct negation of the `exit(2)` finding, and the load-bearing one);
a real Ardour or Bitwig scan reaching browser, instantiation and generic UI; a REAPER-on-Linux
load where the editor opens; and a **negative control** — rename `libSwell.so` beside a Linux
REAPER and confirm the generic-UI fallback plus the single diagnostic line.

### Cross-phase boundary — Γ-W4-T1, and the frozen parameter table

**Λ must never register a VST3 parameter.** Γ-W4-T1's `ParamID` table is FOREVER-FROZEN from
the moment it ships, on the same footing as the command-id strings and the class UIDs. A
parameter minted in Λ to make a Linux fallback look better would collide with a table Λ does
not own. **This is a hard boundary and it is the only Γ↔Λ interaction that could actually go
wrong.**

Neither phase blocks the other. The host's fallback for a plug-in reporting no usable editor
is its generic parameter UI, and today the plugin registers zero parameters — so "no editor"
currently degrades to *nothing* rather than to *controls*. **Λ-W6-T2's acceptance criteria are
written to pass with zero parameters registered**: an empty generic UI is a pass. Once
Γ-W4-T1 lands its 44 derived parameters the identical Λ code path degrades to a usable
generic UI instead — a better result, not a different criterion. **Λ assumes no schedule for
Γ**; a Λ track needing a Γ fact reads `dev` at dispatch time.

**Concurrency.** Λ is the widest phase in this plan by file surface, and it is disjoint from
the others by *kind* rather than by directory: its edits are platform guards, CMake, docs and
one new `shell/instrument/` TU. Γ owns `core/instrument/` + `shell/instrument/` **sources**
(Λ-W6…Λ-W8 own that directory's CMake, its `CLAUDE.md` files, and `editor_platform`'s
non-Windows branch); Ε lands in the new `core/package/` + `shell/package/`; Ρ touches
`core/capture`, one new `shell/capture` TU, `panel_input.cpp` and `main.cpp`. **The genuine
adjacency to watch is CMake**: Ε appends two `add_subdirectory` lines to the root
`CMakeLists.txt` while Λ-W2-T2 owns that file's language and target settings — append-only
lines against property blocks, textual adjacency rather than semantic contention. Λ's own
shared files are named in the wave sections below.

---

### Λ-W1 — The audits *(complete)*

Two tracks, both landed: **T1 `build-toolchain-audit`** (Λ-01…Λ-10, V1…V10, D1…D7) and
**T2 `source-runtime-audit`** (L2-01…L2-12). Recorded so the wave numbering matches the audit
filenames and branch names. Nothing to re-spec; nothing to dispatch.

---

### Λ-W2 — Make it buildable, and make it honest

**Depends on:** nothing. **Four tracks, disjoint at the file level.** Three are authored
blind against the audits and verified in Λ-W3; **T4 is the one track fully verifiable on the
current Windows box.**

| Track | Owns |
|---|---|
| **T1** `linux-compile-blockers` | two source blockers + `main.cpp`'s API-load failure branch |
| **T2** `toolchain-floor` | the CMake files' language, property and platform blocks |
| **T3** `panel-dialog-resource` | `panel_window.cpp`'s dialog creation, `resource.rc` / `resource.h` — **gated on Λ-F2** |
| **T4** `locale-independent-numerics` | the number codec in four `core/` TUs |

**Shared file in the wave, named rather than discovered at merge:**
`src/app/CMakeLists.txt` — T2 takes the property and platform blocks, T3 takes **one
`target_sources` line under the resgen route only**. Disjoint regions; whichever lands second
rebases. The wave's severity question is dissolved rather than adjudicated: T2 grades L2-03 a
Blocker on failure-mode quality with no direct evidence and L2-04 a Major with a confirmed
mechanism, and flags its own inconsistency — both land here, in different tracks, so the
relative grade never has to be settled.

#### Λ-W2-T1 — `linux-compile-blockers`

**Goal.** The extension compiles and links under GCC/Clang, and when it refuses to load it
says why instead of vanishing.

**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T1; findings L2-01, L2-02, L2-03.

**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp` (`loadFont`, `:70–77`),
`src/shell/actions/instrument_drop_win.cpp` (`writeTempPreset`, `:50–61`), `src/app/main.cpp`
(**the `REAPERAPI_LoadAPI` failure branch only**). **Does not own:** any `CMakeLists.txt`,
`panel_window.cpp`, or any `core/` file.

**Behavior.**
- **`FF_DONTCARE` (L2-01).** `draw_kit.cpp:73` passes `DEFAULT_PITCH | FF_DONTCARE` to
  `CreateFont`; the symbol has **zero occurrences anywhere in `vendor/WDL/`**, and the file is
  not platform-guarded, only its include is. `draw_kit` links into **both** modules, so
  nothing builds until this is fixed. Drop the term or define it locally in the non-Windows
  include branch. **Do not add `windows.h`** — L2-01's stated direction. The family bits are
  advisory to Windows' font mapper and meaningless to fontconfig.
- **`GetCurrentProcessId()` (L2-02).** `instrument_drop_win.cpp:59` calls it with no platform
  branch anywhere in the TU; SWELL exports `GetCurrentThreadId` and not this. The PID exists
  only to keep two concurrent REAPER instances from colliding in the shared temp dir, and the
  atomic counter at `:52` already carries the intra-process half. Replace with a
  platform-neutral uniqueness source behind a guard.
- **The silent load failure (L2-03).** Either switch `main.cpp` to `REAPERAPI_MINIMAL` plus an
  explicit `WANT` list — the pattern `panel_window.cpp` and `panel_audition.cpp` already use,
  and the honest inventory of what this extension actually needs — or keep the full load and
  print the failure count via `rec->GetFunc("ShowConsoleMsg")` before returning 0.

**Acceptance criteria.**
- `cmake -B build -S . -G Ninja && cmake --build build` produces `build/reaper_reasampler.so`
  with no errors. `[verify — Linux]` = **V1**.
- `ctest --test-dir build --output-on-failure` passes all 91 test targets, **no `-C` flag
  needed** on a single-config generator. `[verify — Linux]` = **V2**.
- A deliberately misspelled `WANT` entry (or a forced non-zero `failcnt`) produces a visible
  REAPER console line naming the count, not a silent refusal.
- Windows build and `ctest` unchanged.

**Prerequisites.** None. **Discharges:** L2-01, L2-02, L2-03; enables V1, V2, V3.

#### Λ-W2-T2 — `toolchain-floor`

**Goal.** The Linux build is optimized when asked, links what it uses, hides what it does not
export, and reports diagnostics no one has seen yet.

**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T2; findings Λ-02, Λ-03, Λ-04, Λ-05, Λ-07,
Λ-09, Λ-10.

**Surface boundary — owns:** root `CMakeLists.txt`, `src/app/CMakeLists.txt` (**the target
property and platform blocks; NOT the source list — that is Λ-W4-T3's**),
`cmake/reasampler_targets.cmake`, `src/shell/instrument/CMakeLists.txt` (**thread linkage
only; the `WIN32` gate is Λ-W6-T1's**), `README.md`, and root `CLAUDE.md` §"Build and test" /
§"Install / reload" / §"One-time submodule setup". **Does not own:** any `.cpp` or `.h`.

**Behavior.**
- **Λ-02** — root `CMakeLists.txt:28–30` is the *complete* list of language settings: no
  `CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no IPO/LTO, no `target_compile_options` anywhere
  in the tree. Default `CMAKE_BUILD_TYPE` when neither it nor `CMAKE_CONFIGURATION_TYPES` is
  set, and correct the docs' ship instruction: `--config Release` is *accepted and ignored*
  by Ninja and Make, and the README currently sends a Linux user to `build/Release/`, which
  does not exist there — the module lands at `build/reaper_reasampler.so`.
- **Λ-03** — `CXX_VISIBILITY_PRESET hidden` + `VISIBILITY_INLINES_HIDDEN` on both module
  targets. SWELL's own build already uses `-fvisibility=hidden`, and the symbols that must
  stay exported carry their own `visibility("default")` attributes
  (`reaper_plugin.h`'s `REAPER_PLUGIN_DLL_EXPORT`, `fplatform.h`'s `SMTG_EXPORT_SYMBOL`,
  `swell-modstub-generic.cpp:135`'s `SWELL_dllMain`).
- **Λ-04** — `find_package(Threads REQUIRED)` + `Threads::Threads`. Correct on all three
  platforms, costs nothing on Windows.
- **Λ-09** — `-Wall -Wextra` and `CMAKE_CXX_EXTENSIONS OFF`. **No `-Werror` in this change**
  (the audit is explicit): the diagnostic-set size over this tree is not estimable from
  Windows. Separately `-Wl,--no-undefined` on the module targets, restoring the
  fail-at-link-time behaviour MSVC gives and GNU `ld` does not — **or** a recorded reason why
  `SWELL_PROVIDED_BY_APP`'s function-pointer design makes the gap moot.
- **Λ-05** — pin the `reaper_plugin.h` → `../WDL/swell/swell.h` include coincidence with a
  comment or an `INTERFACE` target carrying both include dirs as one unit. Invisible on
  Windows, load-bearing off it.
- **Λ-07** — record the compiler floor (**Λ-F4's input**), or add `-lstdc++fs` and document
  why.
- **Λ-10** — one sentence in the platform-support docs: on Linux `vendor/vst3sdk` is optional
  until Λ-W6, so `git submodule update --init vendor/reaper-sdk vendor/WDL` is the complete
  extension-only prerequisite.

**Acceptance criteria.**
- `compile_commands.json` or a verbose build log shows an explicit `-O` flag on a bare
  `cmake --build build`. `[verify — Linux]`.
- `nm -D --defined-only reaper_reasampler.so | grep -E 'ReaperPluginEntry|SWELL_dllMain'`
  finds **both** after the visibility preset. `[verify — Linux]` = **V6**.
- The extension links with `Threads::Threads` **removed** — proving the include-only pthread
  dependency needs no flag — or the symbol forcing it is named. `[verify — Linux]` = **V5**.
- `capture_paths_tests` links without an explicit `-lstdc++fs` on the declared floor, or the
  flag is added and the floor documented. `[verify — Linux]` = **V8**.
- **The warning count from the first `-Wall -Wextra` build is recorded, not fixed**, and
  handed to Λ-W4 as an input.
- Windows build unchanged; `--config Release` still behaves as documented there.

**Prerequisites.** None; concurrent with T1, T3, T4. **Discharges:** Λ-02, Λ-03, Λ-04, Λ-05,
Λ-07, Λ-09, Λ-10; enables V5, V6, V8.

#### Λ-W2-T3 — `panel-dialog-resource` *(GATED on Λ-F2 — do not dispatch until it is ruled)*

**Goal.** The docked bank panel opens on Linux, and if it ever fails to, it says so.

**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T3 and §Λ-F2; findings Λ-01, L2-06, L2-07.

**The defect.** `panel_window.cpp:169` is `CreateDialogParam(g_hInst,
MAKEINTRESOURCE(IDD_BANK_PANEL), …)`, which SWELL resolves out of a per-module registry
populated by a **resgen-generated source file that is not in the Linux target**:
`src/app/CMakeLists.txt:109` has the `target_sources` line commented out (`:98` for macOS).
The registry head stays null, `SWELL_CreateDialog` returns null, `:171` returns, and the
toggle action is a **silent no-op** — no console line, no Actions-list checkmark. Three
defects stack inside the commented-out instructions themselves: the script named at `:108`
(`mac_resgen.php`) does not exist, the output filename is wrong, and the output is an
`#include`-only artifact that cannot be a `target_sources` entry at all.

**Surface boundary — owns:** `src/shell/panel/panel_window.cpp` (the `CreateDialogParam` call
at `:169–171`, the dialog proc's platform contract, the drop-accept opt-in at `:179–184`),
`src/resource.rc`, `src/resource.h`, and — **under the resgen route only** — one
`target_sources` line in `src/app/CMakeLists.txt`'s `else()` branch plus a new include-shim
TU. **Does not own:** any other panel TU, `draw_kit`, or any CMake target property.
`panel_window.cpp` is **deliberately not split** across tracks: the L2-06 diagnostic and the
Λ-01 resource route are the same function, and under the id-0 route the same *line*.

**Behavior.** Whichever route Λ-F2 picks, plus — **unconditionally, and on both platforms
rather than behind a guard** — a one-line `ShowConsoleMsg` on the `!g_panel.hwnd` path naming
the missing dialog resource (L2-06). That single line converts a mystery into a two-minute
diagnosis and is worth having on Windows too. The fix is shared macOS/Linux either way
(Λ-D4: made in shared form, verified on Linux only).

**Acceptance criteria.**
- The panel toggle action docks a visible, LICE-drawn bank panel in a Linux REAPER.
  `[verify — Linux]` = **V7**.
- With the resource deliberately unavailable, the toggle prints one console line rather than
  doing nothing. **Verifiable on Windows by forcing the branch.**
- Dragging a WAV from the file manager onto the docked panel ingests it — or, if it does not,
  the failure is understood rather than mysterious. `[verify — Linux]`, T2 §5 item 3.
  **This criterion's difficulty depends on the Λ-F2 route** and is the fork's substance.
- Windows panel behaviour byte-for-byte unchanged.

**Prerequisites.** **Λ-F2 must be ruled before dispatch.** If it is not, this track slips to
Λ-W4 and Λ-W3's sweep splits into a panel-independent half (run early) and a panel-dependent
half (run after this lands) — a second Linux session, which is the cost of leaving the fork
open. **Discharges:** Λ-01, L2-06, L2-07; enables V7.

#### Λ-W2-T4 — `locale-independent-numerics`

**Goal.** A persisted float round-trips identically regardless of process locale, so a bank
index written on a comma-decimal machine is not written unparseable.

**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T4; finding L2-04.

**Why it is pulled forward, ahead of everything it looks like it should follow:** it is the
phase's one substantial fix that **needs no Linux box at all** — two writers and three
readers, all in `core/`, all unit-testable on Windows today — and its failure mode is that
the bank index is written unparseable and **the project's whole bank is lost on reload.** It
should land before any Linux user saves a project.

**Surface boundary — owns:** `src/core/json/json.cpp` (the `%.17g` writer and the `strtod`
reader), `src/core/model/provenance.cpp` (its own `%.17g`), `src/core/wire/wire.cpp`
(`Cursor::fieldDouble`), `src/core/capture/render_settings.cpp` (the `std::stod` over
REAPER's `P_RAZOREDITS`), and the corresponding `tests/`. **Does not own:** any shell TU, any
CMake file, `wav_codec` (its byte-order handling is already explicit and correct).

**Behavior.** Make the number codec locale-independent at its two writers and three readers —
`std::to_chars`/`std::from_chars`, or a `std::locale::classic()`-bound stream. **Do not "fix"
this by calling `setlocale`**; an extension must not mutate the host's locale. Why it is not
paranoia: on Windows the CRT starts in the `"C"` locale and nothing here calls `setlocale`,
which is why it has never fired; on Linux SWELL's GDK backend calls
`gtk_init_check`/`gdk_init_check` and never calls `gtk_disable_setlocale`, and any GTK or Qt
plugin in the same process can do the same. The readers are honestly fail-closed — they
require whole-token consumption — so the failure is "the field disappears", not "the field is
silently wrong".

**Acceptance criteria.**
- New pure tests pass **on Windows today**, under a forced comma-decimal `LC_NUMERIC` set
  inside the test. **This is the one Λ-W2 track that does not wait for Λ-W3.**
- The bank index, view model, tracking ledger, provenance blob and tail setting all round-trip
  a fractional value under that forced locale.
- **Byte-for-byte identical output to today's writer under the `"C"` locale** — this is a
  persisted format, and a changed representation is a compatibility event.
- Nothing on a hot path is touched; the JSON/persist path is declared off all hot paths in
  root `CLAUDE.md` and stays there.

**Prerequisites.** None. **Discharges:** L2-04.
**Note:** T2 §5 item 1 (read `LC_NUMERIC` inside a running REAPER-Linux process) stays in the
Λ-W3 sweep, but only to record how urgent this *was* — the work is not gated on it.

---

### Λ-W3 — First light, and the verification sweep

**Depends on:** Λ-W2. **One track, deliberately** — this is a person at a Linux box working a
checklist where each answer reprices the next; splitting it across specialists buys no
concurrency and loses the thread. Precedent: Ρ-W1 and Γ-W4 are both single-track waves for
the same reason.

#### Λ-W3-T1 — `linux-verification-sweep`

**Goal.** Discharge every acceptance criterion Λ-W2 could not check from Windows, answer every
`[verify — Linux]` item in both audits, and reprice the remaining waves against what is
actually true.

**Spec:** `docs/product/linux-readiness.md` §Λ-W3-T1 and §"Why this sequence".

**Surface boundary — owns: no source file and no CMake file.** Its deliverable is a
**verification record** at `docs/product/audit-notes/lambda-w3-verification.md`, one entry per
item: the exact check run, the observed result, and what it changes. **Any fix the sweep
motivates is filed to Λ-W4, not made here.** The one exception: a defect that blocks further
sweeping (the build does not link at all) is fixed in place and recorded as a deviation,
because the alternative is a wasted session.

**Behavior — the sweep set.**

| From | Items |
|---|---|
| T1 | **V1** compile, **V2** ctest, **V3** does REAPER's Linux build call `SWELL_dllMain` and populate the API table, **V4** where `UserPlugins/` actually is and whether `reaper_*.so` is the right glob, **V5** pthread link flag, **V6** visibility vs. the two exported symbols, **V7** the docked panel, **V8** `-lstdc++fs`, **V9** does `libSwell.so` sit beside REAPER's executable, **V10** which `uname -m` values the VST3 bundle must carry |
| T2 | process `LC_NUMERIC`; `REAPERAPI_LoadAPI`'s actual return value and the name of any gap; panel file-drop routing; which SWELL GDI/locale build REAPER ships; fontconfig's substitution for "Consolas"; `SWELL_InitiateDragDropOfFileList` acceptance by common targets, and whether its 500 ms no-motion timeout cancels a slow gesture; prune against an in-use file |
| New | the `-Wall -Wextra` diagnostic set, counted and categorised; whether `-Wl,--no-undefined` links clean or names an undefined set; whether the four-TU LICE slice links without `lice_colorspace.cpp` (T1 §3 leaves this an unreconciled inference) |

**Three items are load-bearing beyond their own answer and must be run FIRST:** **V1**
(nothing else is observable until it passes); **V9** (a negative answer **voids Λ-D2's route
and needs a Daniel re-ruling** before Λ-W6 is dispatched — the entire `dlopen`-REAPER's-SWELL
design rests on it, and it is a one-line `ls`); and the `REAPERAPI_LoadAPI` count (a non-zero
result promotes L2-03 from a diagnostic to a real Blocker and names the gap).

**Acceptance criteria.**
- Every item above has a recorded answer or an explicit "could not determine, because X".
  **A blank is a failure of this track, not a deferral.**
- Λ-W2's four tracks each have their `[verify — Linux]` criteria marked discharged or failed,
  **by name**.
- Λ-W4 and Λ-W5's scope is restated against the answers, with any effort band that moved
  called out. **The audits' bands are provisional by their own statement; this is where they
  stop being.**
- Λ-W6's prerequisites (V9, V10) are answered, or Λ-W6 is explicitly blocked pending a Daniel
  re-ruling on Λ-D2.

**Prerequisites.** Λ-W2-T1, T2, T4. **T3 if Λ-F2 was ruled** — otherwise the panel-dependent
items (V7, file-drop routing, any font check needing a rendered panel) defer to a second
session and that deferral is recorded. **Discharges:** V1–V10 and T2 §5's seven items, **as
answers rather than as fixes**.

---

### Λ-W4 — Correctness and safety, repriced

**Depends on:** Λ-W3-T1. **Three tracks, disjoint by directory.** Everything here is known
work whose *size* the sweep may have moved.

#### Λ-W4-T1 — `prune-deletion-safety`

**Goal.** Prune on Linux deletes only what it means to, tells the truth about what it
reclaimed, and tells the user the deletion is permanent.

**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T1; finding L2-05 (both halves) plus the
symlink row of T2 §4.

**Surface boundary — owns:** `src/shell/persist/prune_fs.cpp` (the deletion authority's
non-Windows branch and the reclaim scan) and the prune confirmation text wherever it is
composed (`shell/actions/prune_action` / `shell/panel/panel_bank_ops`). **Does not own:**
`core/reclaim/`'s orphan computation (pure, portable, unaffected), or any other persist TU.

**Behavior.**
- **The confirmation string becomes platform-aware** (Λ-D5). On Linux it states the deletion
  is permanent and there is no Recycle Bin; on Windows it says what it says today. **Same code
  path, one platform-dependent phrase — not a second dialog.**
- **The dead "locked" branch is reckoned with.** `if (ec) return false; // real failure
  (locked/permission) -> skip` encodes Windows file-sharing semantics; on Linux `unlink` of an
  open file **succeeds**, the audio keeps playing from the open fd, and the bytes are gone
  when it closes — so the branch never fires. Either it is documented as Windows-only in
  place, or the Linux path acquires an equivalent guard. **What it must not do is stay
  silently asymmetric**: the recovery floor root `CLAUDE.md` §"The resample bake" relies on
  ("the superseded file survives on disk until a prune reclaims it") otherwise has nothing
  under it on Linux. **This half is not covered by Λ-D5's trash ruling** — it is not a trash
  question.
- **The symlink hazard is fixed.** `fs::directory_iterator` + `is_regular_file()` follows
  symlinks under C++17; size is read from the target via `file_size()` but `fs::remove`
  deletes the **link**, not the target — so prune reports N bytes reclaimed and reclaims
  zero. Symlinked media folders are far more idiomatic on Linux than on Windows. **This is a
  reporting lie, not a cosmetic issue.**

**Acceptance criteria.**
- A Linux prune of a bank file currently playing behaves as recorded in Λ-W3's sweep, and the
  behaviour matches what the confirmation promised. `[verify — Linux]`.
- A symlinked bank file is either skipped or deleted with its target, and the reclaimed-byte
  figure matches what actually left the disk in **both** cases. Unit-testable for the
  computation; `[verify — Linux]` for the filesystem half.
- The Windows path — `SHFileOperationW` + `FOF_ALLOWUNDO`, Recycle-Bin recoverable — is
  bit-for-bit unchanged.
- **Prune remains the only file-deletion path in the system** (plan-wide product invariant).

**Prerequisites.** Λ-W3-T1 (the in-use-file check). **Discharges:** L2-05 both halves, and the
symlink row of T2 §4.

#### Λ-W4-T2 — `linux-font-faces`

**Goal.** The kit asks for faces that exist on a stock Linux distro, so type is **chosen**
rather than substituted.

**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T2 and §"Calls made here"; finding L2-09.
**This is a product-designer call, not a fork:** silent fontconfig substitution is not
acceptable because the kit's type is part of a deliberate visual identity
(`docs/product/visual-design-language.md`), and the palette-role discipline exists precisely
so a visual direction is a single-file change rather than an emergent property of the host.
The cost is five string literals. Contradict it in review with an argument.

**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp`'s five `loadFont` call sites
(`:154–158`) and the face constants in `draw_kit.h`. **Does not own:** `loadFont` itself
beyond the literals, the palette, any geometry, or the two WCAG `static_assert`s — which are
on pixel height and weight, not on the face, and hold regardless.

**Behavior.** A platform face list at the five call sites: **DejaVu Sans / DejaVu Sans Mono**
as the Linux defaults, following SWELL's own no-fontconfig fallback list
(LiberationSans/DejaVuSans, LiberationMono/DejaVuSansMono) as precedent. **One code path**,
shared with macOS's eventual San Francisco/Menlo (Λ-D4: made in shared form, not verified).
The subtlety to carry into the work: `draw_kit.cpp:74`'s `if (!hf) return` guard does **not**
catch this failure mode — SWELL's `CreateFont` always returns a non-null handle even when the
face never resolved, recording the failure as a null `typedata` internally. **A wrong or
missing face is not observable at the call site, only in the rendering.**

**Acceptance criteria.**
- Every kit string renders in the intended face on a stock distro, and the numeric readouts
  are tabular. `[verify — Linux]`.
- Windows renders Segoe UI and Consolas exactly as today.
- Row heights, ellipsis points and label truncation are unchanged on Windows; on Linux they
  are **measured** rather than assumed correct.

**Prerequisites.** Λ-W3-T1's fontconfig answer (T2 §5 item 5), which decides whether this is
cosmetic or a readability regression. **Discharges:** L2-09.

#### Λ-W4-T3 — `source-partition-and-invariants`

**Goal.** The build's source list stops relying on every TU's own `#ifdef` discipline, and the
invariants Linux weakens are stated where a reviewer will read them.

**Spec:** `docs/product/linux-readiness.md` §Λ-W4-T3 and §Λ-F3; findings Λ-06, L2-10, L2-11.

**Surface boundary — owns:** the `target_sources` list in `src/app/CMakeLists.txt:8–55` (**the
list; the property blocks are Λ-W2-T2's**), any new platform-sibling TU the sweep showed was
needed, `src/shell/actions/drag_out_win.h`'s invariant comment, and the corresponding
`src/shell/**/CLAUDE.md` invariant passages. **Does not own:** any behaviour change in a
shipped code path.

**Behavior.**
- **Λ-06** — partition the source list where the Λ-W3 diagnostic set says a TU needs it.
  `drag_out_win.cpp` is the model (a real `#ifdef _WIN32` / `#else` split);
  `arrange_drop_win.cpp` and `instrument_drop_win.cpp` are `_win`-suffixed for the **surface**
  they serve, not for a platform dependency, and the audits found them portable by inspection
  — **confirm against the actual compile rather than re-inspecting.**
- **L2-10 — the copy-only invariant. Λ-F3 rules the wording; this track makes the edit either
  way.** Make `drag_out_win.h:7–11` the doc a Linux reviewer is pointed at, and treat "MOVE is
  structurally impossible" as a Windows-scoped claim: SWELL's file-list drag takes no effect
  mask, so nothing at the API level forbids a target from treating the drag as a move.
- **L2-11 — no Linux action.** If the predicate is touched at all it becomes
  "case-insensitive filesystem", not "Windows" — but Λ-D4 puts macOS out, so the right move is
  **a comment recording the known macOS defect, not a speculative fix.**

**Acceptance criteria.**
- No TU compiles on Linux only because of an `#ifdef` that happens to be complete; every
  platform-specific TU is either partitioned in CMake or carries a deliberate, commented
  guard.
- The copy-only invariant's text says **the same thing** in `drag_out_win.h`, the owning
  `CLAUDE.md`, and any spec text that cites it.
- Zero behaviour change on Windows.

**Prerequisites.** Λ-W3-T1. **Λ-F3 for the L2-10 wording only — non-gating:** the track can
land the partition and leave the sentence for a follow-up. **Discharges:** Λ-06, L2-10,
L2-11's Linux half.

---

### Λ-W5 — Ship the extension

**Depends on:** Λ-W3-T1 (V4) and **all three Λ-W4 tracks** — shipping means the correctness
fixes are in. **One track. Gated on Λ-F4** (a shipped artifact has to say what it runs on).

#### Λ-W5-T1 — `linux-packaging-and-install`

**Goal.** A Linux user can install a correctly-built `reaper_reasampler.so` by following a
document, and a pipeline can install it by following a rule.

**Spec:** `docs/product/linux-readiness.md` §Λ-W5-T1; finding Λ-08 plus Λ-02's shipped half.

**Surface boundary — owns:** an `install()` rule in `src/app/CMakeLists.txt` — **the first in
the tree** — `README.md`'s platform-support and install sections, root `CLAUDE.md`
§"Install / reload", and `docs/product/versioning-and-release.md`'s artifact and pipeline
paragraphs. **Does not own:** any source file, the toolchain properties (Λ-W2-T2's), or CI
(Λ-F1).

**Behavior.**
- Document the Linux `UserPlugins/` root **as V4 actually found it**, and the
  single-config-generator output path (`build/reaper_reasampler.so`) that no doc currently
  names.
- Add the `install()` rule for **both channels**. The channel fork is platform-independent by
  construction — `REASAMPLER_CHANNEL` threads through `configure_file` into names only — so
  `reaper_reasampler_beta.so` needs no separate mechanism, only a separate destination check.
- **Amend `versioning-and-release.md`**, which already promises three platform artifacts per
  channel per release and mentions no Linux install path, no signing and no CI. Λ-D4 removes
  macOS from the near-term promise: that document should say **two** platform artifacts per
  channel for now, **with macOS named as deferred rather than silently dropped.**
- **The beta channel on Linux is uncosted in both audits.** Almost certainly free — the fork
  is name-only — but "almost certainly" is not a ship criterion. Build and install both
  channels side by side once.
- **Λ-F1 decides only which paragraph gets written here** — a pipeline paragraph, or an
  explicit "deferred to dev-ops". Until it is ruled, **Λ ships nothing that presumes a
  runner.**

**Acceptance criteria.**
- A clean-machine walkthrough **following only the README**: clone, narrow submodule init,
  configure, build, install, restart REAPER, actions present, panel docks. `[verify — Linux]`.
- `reaper_reasampler.so` and `reaper_reasampler_beta.so` coexist in one REAPER with separate
  ext-state namespaces, command ids and dock idents. `[verify — Linux]`.
- The **installed** binary shows an explicit `-O` flag in its build log — Λ-02's criterion,
  re-checked on the artifact that actually ships.
- `versioning-and-release.md` no longer promises an artifact this phase does not produce.
- The declared support floor (Λ-F4) appears in the README and matches what was built.

**Prerequisites.** Λ-W3-T1 (V4), Λ-W4 (all three tracks), **Λ-F4 ruled**. **Discharges:**
Λ-08, and Λ-02's shipped half.

---

### Λ-W6 — The instrument module, and the host-safety contract

**Depends on:** Λ-W2-T2 (thread linkage, visibility, warnings) and Λ-W3-T1 (V10, plus V9 if
Λ-D2 is to survive). **Λ-W6 may run CONCURRENTLY with Λ-W4 and Λ-W5** — it touches
`src/shell/instrument/` and that directory's CMake and `CLAUDE.md` files plus
`src/core/instrument/CLAUDE.md`, none of which Λ-W4 or Λ-W5 opens; the only reason to
serialise is attention, not contention.

**Two tracks — one build + docs, one source — and they are `T1 before T2 — serial`**, the
same shape this plan already records for Γ-W3. They are file-disjoint but not order-free:
there must be a module before it can refuse to show a view.

**At the end of this wave the Linux instrument loads in any host, processes audio, and has NO
EDITOR AT ALL** — a defined, verifiable, shippable state, not a half-done one.

#### Λ-W6-T1 — `vst-linux-module`

**Goal.** `reasampler_9000.vst3` configures, builds and installs on Linux as the directory
bundle a Linux host expects, and the decision that forbade it is reversed in writing.

**Spec:** `docs/product/linux-readiness.md` §Λ-W6-T1; audit findings B1, B2, B5.

**Surface boundary — owns:** `src/shell/instrument/CMakeLists.txt` (the gate, the entry-point
source selection, the bundle POST_BUILD and install rule, `SWELL_PROVIDED_BY_APP`, the modstub
TU) and the three D5 passages in `src/core/instrument/CLAUDE.md`,
`src/shell/instrument/CLAUDE.md` and `src/shell/panel/CLAUDE.md`. **Does not own:** any `.cpp`
or `.h` under `shell/instrument/`.

**Behavior.**
- **B5 first, as a documentation act.** Rewrite the three D5 passages: the platform clause
  becomes "Windows and Linux; the editor is REAPER-hosted"; **VST3-only and REAPER-only
  survive verbatim.** `src/core/instrument/CLAUDE.md` carries it twice (Invariants and
  Non-goals), `src/shell/instrument/CLAUDE.md` once (Non-goals), and
  `src/shell/panel/CLAUDE.md` once (the font/GDI clause). **Nothing else in this wave may land
  before this does** — the invariant files are what a future implementer reads.
- **B1** — swap `public.sdk/source/main/dllmain.cpp` for `linuxmain.cpp` on Linux. `dllmain`
  includes `<windows.h>` with no `SMTG_OS_*` guard; `linuxmain.cpp` exports `ModuleEntry` and
  `ModuleExit`, **both mandatory** — the SDK's own loader refuses the module without either.
  Both files are already vendored; this is a source swap plus a platform `if()`.
- **Split the `WIN32 AND EXISTS` conjunction** at `src/shell/instrument/CMakeLists.txt:9`.
  Today it is a conjunction, so a Linux configure silently omits `reasampler_vst` **even with
  the submodule slice fully initialised.** The `EXISTS` half stays — a fresh clone with no
  VST3 slice must still configure — and the `WIN32` half becomes a Windows-or-Linux predicate.
- **B2** — the artifact becomes a **directory bundle**:
  `reasampler_9000.vst3/Contents/<uname -m>-linux/reasampler_9000.so`, per V10's answer and
  Λ-F4's floor. `Contents/Resources/moduleinfo.json` is **optional** — the SDK's
  `getModuleInfoPath` returns empty when absent rather than failing — **so do not author
  one.** Install roots: `$HOME/.vst3/`, `/usr/lib/vst3/`, `/usr/local/lib/vst3/`,
  `$APPFOLDER/vst3/`.
- **`SWELL_PROVIDED_BY_APP` + the modstub TU** are added to this target in the **default
  (non-`SWELL_LOAD_SWELL_DYLIB`) branch** — the same branch the extension already uses
  (`src/app/CMakeLists.txt:103–104`). The whole modstub file is inside
  `#ifdef SWELL_PROVIDED_BY_APP`, so the VST3 target must define that symbol; today it does
  not. **The `dlopen` itself is Λ-W7's**; what this track lands is the compiled-in, inert
  table plus the exported `SWELL_dllMain`.
- **The channel fork applies:** `reasampler_9000_beta.vst3` with its own class UID. **The UID
  pair is FOREVER-FROZEN and must not change** — a Linux build is a new platform, not a new
  identity, and a saved project rebinds by UID.

**Acceptance criteria.**
- A Linux configure produces the `reasampler_vst` target; a Windows configure is unchanged.
- The built bundle's directory shape matches what the SDK's `module_linux.cpp` opens, and the
  SDK's own `validator` loads it. `[verify — Linux]`.
- Both channels build and install side by side, and **the two class UIDs are byte-identical to
  the Windows build's.**
- The three `CLAUDE.md` files no longer claim Windows-only, and **still** claim VST3-only and
  REAPER-only.
- **No VST3 parameter is registered by this track or any other in this phase** — see the
  Γ-W4-T1 boundary above.

**Prerequisites.** Λ-W2-T2, Λ-W3-T1 (V10; V9 if Λ-D2 is to survive). **Discharges:** B1, B2,
B5; and Λ-10's "vst3sdk is optional on Linux" caveat becomes conditional.

#### Λ-W6-T2 — `vst-host-safety-contract`

**Goal.** The plugin's editor surface refuses **cleanly** on every Linux host, so that adding
a real editor later is a change of branch taken and not a change of contract.

**Spec:** `docs/product/linux-readiness.md` §Λ-W6-T2 and §"The non-REAPER-host safety
criterion"; Λ-D3, L2-08's fallback half.

**Surface boundary — owns:** `src/shell/instrument/editor_platform.cpp` (the
`isPlatformTypeSupported` / `createView` decision and the non-Windows stubs),
`reasampler_processor.cpp`'s `createView` site, `reasampler_embed.cpp`'s
`REAPER_FXEMBED_WM_IS_SUPPORTED`, and a new availability-probe seam under
`shell/instrument/`. **Does not own:** any CMake file, the paint or input families (untouched
— they stay whole-region-guarded), or any pure `core/instrument/` module.

**Behavior.**
- The editor's availability becomes a **tri-state probe** — untried / available / unavailable
  — computed **once, lazily, off the audio thread**. In this wave the Linux answer is
  unconditionally *unavailable*; **Λ-W7 gives it a real computation without changing a single
  call site.**
- `isPlatformTypeSupported` returns `kResultFalse` for **every** type when unavailable;
  `createView(kEditor)` returns `nullptr`. **Never a view that fails to attach.**
- **One** diagnostic line per process, naming the reason. Never a modal.
- `reasampler_embed`'s `REAPER_FXEMBED_WM_IS_SUPPORTED` continues to return 0 off Windows.
  That is correct and stays correct — the TCP/MCP embed strip on Linux is an explicit
  non-goal of this phase.
- **The Windows path is not restructured to accommodate this.** The probe is a Linux branch on
  an existing decision, **not a new abstraction over both.**

**Acceptance criteria.** The full observable contract above, verified by **all four** of its
checks: `validator` with no `libSwell.so` present; a real Ardour or Bitwig scan; a
REAPER-on-Linux load; and the renamed-`libSwell.so` negative control. `[verify — Linux]`.
Plus:
- Windows editor behaviour bit-for-bit unchanged — same window class, same `wndProc`, same
  `CS_DBLCLKS` fall-through.
- **Nothing added to `process()`** — no `dlopen`, no probe, no branch on the
  per-voice-per-sample path.
- **The criteria pass with ZERO VST3 parameters registered.** An empty generic UI is a pass;
  Γ-W4-T1 improves it and Λ must not.

**Prerequisites.** Λ-W6-T1 (there must be a module to load). **Discharges:** Λ-D3's Λ-W6 half;
L2-08's fallback half.

---

### Λ-W7 — The SWELL bootstrap

**Depends on:** Λ-W6-T1, Λ-W6-T2, and **V9 answered affirmatively.** If V9 is negative, **this
track does not exist and Λ-D2 needs a re-ruling** — which is why V9 runs first in Λ-W3 rather
than being discovered here.

#### Λ-W7-T1 — `swell-dylib-bootstrap`

**Goal.** The plugin acquires a working SWELL function table under REAPER on Linux, without
the vendored stub's `exit()` behaviour and without vendoring SWELL.

**Spec:** `docs/product/linux-readiness.md` §Λ-W7-T1 and §"The implementation shape this
obliges"; Λ-D2 (route B3a), audit finding B3.

**Surface boundary — owns:** a new `src/shell/instrument/swell_bootstrap.{h,cpp}`, the
availability-probe computation Λ-W6-T2 left stubbed, and the `SWELLAppMain`-shaped callback.
**Does not own:** any vendored file (**nothing under `vendor/` is patched**), the CMake source
list beyond adding one TU, or the editor.

**Behavior — exactly the four-point shape in the product doc, and it is not a re-litigation of
Λ-D2 but the specification of it.**
1. **Never define `SWELL_LOAD_SWELL_DYLIB`.** Compile `swell-modstub-generic.cpp` in its
   default branch, which exports `SWELL_dllMain(hInst, callMode, GetFunc)` and calls `doinit`
   on the pointer it is handed.
2. **Own the load.** Resolve the host executable's directory, `dlopen` `libSwell.so`, `dlsym`
   `SWELLAPI_GetFunc`, `dlsym` and call `SWELL_set_app_main`, then call our own exported
   `SWELL_dllMain(hinst, DLL_PROCESS_ATTACH, getfunc)` — **using only exported surface, with
   every failure returning a recorded state instead of exiting.** This also dissolves the
   `SWELLAppMain`-as-a-link-requirement noted in T1 B3a: we pass an app-main because we choose
   to, not because the linker demands one. `[verify — Linux]` whether a minimal app-main
   suffices for a plugin that only ever creates child windows inside a host-supplied X11
   window.
3. **Probe the host, not just the library.** A non-REAPER host has no SWELL message loop —
   REAPER's own is what pumps SWELL windows — so even a successful `dlopen` in Ardour would
   produce a window nothing drives. **Gate on the host name via `IHostApplication::getName()`
   AND on the `dlopen` succeeding**; either failing means no editor.
4. **Probe once, lazily, off the audio thread.** Tri-state, computed on first `createView` and
   never recomputed. No `dlopen` from `process()`, no per-`createView` retry, **no
   static-constructor work.**

**The failure taxonomy the probe must distinguish**, because they need different messages:
host is not REAPER / executable path unresolvable / `libSwell.so` not present at the resolved
path / `SWELLAPI_GetFunc` missing or version-mismatched (the stub checks
`SWELLAPI_GetFunc(NULL)==(void*)0x100`) / API table incomplete. **The last is not fatal** —
`doinit` substitutes a zero-returning `dummyFunc` per miss — and whether to accept a partial
table or refuse is **[propose at review]**, with *"refuse if any name the editor actually
calls is missing"* as the starting proposal.

**Acceptance criteria.**
- Under REAPER on Linux the probe reports *available* and the SWELL table resolves with **zero
  misses**. `[verify — Linux]`.
- Under `validator` with **no `libSwell.so` anywhere**, the probe reports *unavailable*, the
  process **exits normally**, and one diagnostic line is printed. **This is the direct
  negation of the `exit(2)` finding and is the track's headline criterion.**
  `[verify — Linux]`.
- Under Ardour or Bitwig **with** a `libSwell.so` reachable, the host-name gate **still
  refuses** — proving the gate is on the host, not only on the library. `[verify — Linux]`.
- No vendored file is modified; `git status` under `vendor/` is clean.
- Windows build unaffected — the whole TU is behind a platform guard.

**Prerequisites.** Λ-W6-T1, Λ-W6-T2, V9 affirmative. **Discharges:** B3 via route B3a; Λ-D3's
runtime half.

---

### Λ-W8 — The X11 editor

**Depends on:** Λ-W7-T1. **One track, and it is the phase's only L-band source item** (L2-08;
T1 B4 costs it "build S, source L"). Λ-D6 sequences it last on purpose: **halting this wave
still leaves a shipped extension and a loading, processing, generic-UI instrument** — a real
product, not a stub.

#### Λ-W8-T1 — `x11-embed-view`

**Goal.** The ReaSampler 9000 editor opens, draws and responds under REAPER on Linux.

**Spec:** `docs/product/linux-readiness.md` §Λ-W8-T1; audit findings B4, L2-08.

**Surface boundary — owns:** a **Linux sibling** to `src/shell/instrument/editor_platform.cpp`
(window creation and parenting, the run-loop timer, the event-driven input path), and the
`#ifdef _WIN32` region **boundaries** in `reasampler_editor.h` and the eight
`editor_input_*` / `editor_paint*` TUs — **boundaries only, not their contents.**
**Does not own:** `draw_kit`, any pure `core/instrument/` module, any painter's drawing logic,
`instrument_bake`, or the processor.

**Behavior.** The audits call this **a new competence rather than a port.** The build-side cost
is nil — the Linux IIDs are already in the vendored slice (`commoniids.cpp` defines
`Linux::IEventHandler`, `Linux::ITimerHandler`, `Linux::IRunLoop` under `#if SMTG_OS_LINUX`,
and that file is already in the `vst3_sdk` source list). What must be written:
- `kPlatformTypeX11EmbedWindowID` instead of `kPlatformTypeHWND`; `attachedToParent` receiving
  an X11 window id.
- A `Linux::IRunLoop`-driven timer replacing `SetTimer`/`WM_TIMER` — **including the editor's
  sync tick, which the bake's arm-then-run discipline rides on.**
- An **event-driven input path** replacing the `wndProc` switch. `RegisterClassW` /
  `CreateWindowExW` / `DefWindowProcW` have **no SWELL analogue at all** — SWELL has no
  window-class model, only `SWELL_CreateDialog` and raw `HWND__` construction.
- Substitutions the audit already resolved by name: `MoveWindow` → `SetWindowPos`;
  `GetWindowLongPtr`/`SetWindowLongPtr` → the non-`Ptr` forms returning `LONG_PTR`;
  `GetKeyState` → `GetAsyncKeyState`; `TrackMouseEvent`/`WM_MOUSELEAVE` → **nothing**, so
  hover-leave needs its own derivation (the panel layer already documents the same gap);
  `DragAcceptFiles`/`DragQueryFileW` → the drop path Λ-W2-T3 settled for the panel.

**A worked reference implementation is already on disk and neither audit named it.**
`vendor/vst3sdk/public.sdk/samples/vst-hosting/editorhost/source/platform/linux/` contains
`window.cpp` (returning `{kPlatformTypeX11EmbedWindowID, …}` and answering
`Linux::IRunLoop::iid` from `queryInterface`), `runloop.cpp`, and `irunloopimpl.h` (a
`RunLoopImpl` implementing `registerEventHandler` / `registerTimer` and their unregisters). It
is the **host** side of the contract rather than the plug-in side, which makes it a precise
specification of what our plug-in side must satisfy, and the documented narrow submodule init
already pulls it. **This does not shrink the L band** — it is a reading input, not a library;
it removes the "we are guessing at the contract" risk from the estimate.

**Acceptance criteria.**
- The editor opens, draws every face identically to the Windows build (compare screenshots at
  the same window size), and every drag, click, wheel and keyboard interaction behaves the
  same. `[verify — Linux]`.
- Hover-leave is correct on every hover surface, without `TrackMouseEvent`.
- **The bake's arm-then-run tick fires under `IRunLoop`, and a bake completes end-to-end on
  Linux:** staged file, extension action invoked over the VST3 host bridge, outcome read back,
  adopt and reset. `[verify — Linux]`.
- **Λ-W6-T2's contract still holds** — a non-REAPER host still gets `nullptr` from
  `createView`. **Re-run all four checks; this is a regression criterion, not a new one.**
- **Nothing added to `process()`**; no dispatch added to any per-sample path.
- Every new file lands under the ~600-line ceiling **with a responsibility seam, not a
  bisection.** The editor's existing band-axis split (`_chrome` / `_waveform` / `_deck` /
  `_browse` / `_curve`) is the seam vocabulary to reuse — the Linux platform TU is a **sibling
  to `editor_platform`**, per L2-08's own direction: *"it needs a sibling, not a rewrite."*
- Windows editor behaviour bit-for-bit unchanged.

**Prerequisites.** Λ-W7-T1. **Discharges:** B4, L2-08.

---

### Shared files across Λ's waves, named rather than discovered at merge

All textual adjacency, not semantic contention, unless marked otherwise.

| File | Tracks | Nature |
|---|---|---|
| `src/app/CMakeLists.txt` | Λ-W2-T2 (property + platform blocks), Λ-W2-T3 (one `target_sources` line, **resgen route only**), Λ-W4-T3 (the source list), Λ-W5-T1 (the `install()` rule) | Four disjoint regions of one file. Λ-W2-T2 and Λ-W2-T3 are the only pair in the same wave; one line each |
| `src/shell/panel/draw_kit.cpp` | Λ-W2-T1 (`loadFont`'s `CreateFont` args, `:73`), Λ-W4-T2 (the five call sites, `:154–158`) | Different waves |
| `src/shell/instrument/CMakeLists.txt` | Λ-W2-T2 (thread linkage), Λ-W6-T1 (gate, entry point, bundle, install), Λ-W7-T1 (one added TU) | Different waves |
| `src/shell/panel/panel_window.cpp` | Λ-W2-T3 alone | **Deliberately not split.** The L2-06 diagnostic and the Λ-01 resource route are the same function — under the id-0 route the same *line*. Splitting them would be semantic contention |
| `src/shell/instrument/editor_platform.cpp` | Λ-W6-T2 (the refusal branch), Λ-W8-T1 (the real branch) | Different waves; the second replaces the first's computation **without touching its call sites** |

---

## Traceability — all seventeen items

The check that nothing was dropped. Every row points at a track. All seventeen items are
Θ or Ξ work, and both phases have since landed in full — their track sections are retired
from this file; see `docs/COMPLETED.md` for each track's full narrative.

| # | Item (short) | Phase-Wave-Track | Worktree slug |
|---|---|---|---|
| 1 | Envelope editor: radio switch, curve dials, overlay recolor | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
| 2 | MM preamp Filter — resonant HP/LP stage | Θ-W1-T3 (DSP) **+** Θ-W2-T1 (integration) | `pth-w1-t3-filter-dsp-port`, `pth-w2-t1-filter-voice-path` |
| 3 | Alternative Spline EGs | Θ-W5-T1 | `pth-w5-t1-spline-egs` |
| 4 | Bug: end-of-sample click, Trigger × Preserve | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
| 5 | Bug: drag-out sometimes lands without audio | Θ-W1-T2 | `pth-w1-t2-capture-handoff-bugs` |
| 6 | Bug: FX-container drop loses the capture | Θ-W1-T2 | `pth-w1-t2-capture-handoff-bugs` |
| 7 | Stereo waveform shows both channels | Θ-W2-T2 | `pth-w2-t2-stereo-waveform-lanes` |
| 8 | Release anchoring; the Pitch AD becomes AHD | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
| 9 | Loop points — regression + Gate loop-sustain | Θ-W4-T1 | `pth-w4-t1-gate-loop-sustain` |
| 10 | Knob/label sizing, ms units, double-click reset | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
| 11 | Preview glyph; VELOCITY deck; bipolar curves | Θ-W4-T2 | `pth-w4-t2-velocity-deck-and-bipolar-curves` |
| 12 | Toolbar cleanup; full-width piano strip; tooltips | Θ-W2-T3 | `pth-w2-t3-toolbar-and-piano-strip` |
| 13 | Antialiased rendering audit for high-DPI | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
| 14 | Trigger amp/filter fade → AHD consolidation | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
| 15 | One-click in-sampler resample | Ξ-W1-T2 (note model) **+** Ξ-W2-T1 (bake chain) **+** Ξ-W3-T1 (popup abandoned by ruling; window derives itself) | `pxi-w1-t2-note-program-model`, `pxi-w2-t1-resample-bake-chain`, `pxi-w3-t1-capture-signal-popup` |
| 16 | Retire the zone mapping system | Θ-W1-T1 | `pth-w1-t1-zone-retirement` |
| 17 | Consolidate provenance/usage tracking | Ξ-W1-T1 | `pxi-w1-t1-tracking-consolidation` |

### Work in this plan that is not one of the seventeen

The table above is a completeness proof over `TODO-1.0.md` — every row points at a track,
so nothing from the source was dropped. It is deliberately **not** an index of the plan:
work that did not come from the source doc has no row, and inventing one would weaken the
proof it exists to give.

- **Θ-W3-T1 — `live-parameter-delivery`** (`pth-w3-t1-live-parameter-delivery`). Arose
  from Θ-W2-T1's implementation review, not from `TODO-1.0.md`. The first such track in
  this plan; see Θ-W7-T1 below for the second.
- **Θ-W7-T1 — `arc-and-spline-aa`** (`pth-w7-t1-arc-and-spline-aa`). Opened after
  Θ-W6-T1 shipped, when Daniel found two rendering defects by eye in the editor — not
  from `TODO-1.0.md`, and not a track this plan originally scoped. The second such track
  in this plan today; if others appear, they belong on this list rather than in the
  table.
- **All of Phase Γ** (`pg-*`). **Thirteen tracks across four waves** (W1 seven, W2 two, W3
  three, W4 one), from a direct interview with Daniel (2026-08-01) and his four later rulings
  the same day, not from `TODO-1.0.md`. Listed
  here as a block rather than per track, because the whole phase is outside the source doc;
  the product reasoning lives in `docs/product/instrument-control-surface.md` and the
  parameter system's in `docs/product/parameter-automation.md` §§6–10. **Two `docs/TODO.md`
  entries are discharged by this phase, not deferred again:** Γ-W3-T1 discharges the
  deck-rework entry (whose original "one row of taller decks with within-deck stacking" shape
  Daniel explicitly superseded), and **Γ-W1-T1 discharges "Raise the stage-time ceiling above
  2 s"** (Γ-F3 reversed).
- **All of Phase Ψ** (`ppsi-*`). **Seven tracks across three waves**, from a direct list
  of seven defects and refinements (Daniel, 2026-08-01), not from `TODO-1.0.md`. Listed
  here as a block, like Γ; unlike Γ it has no backing product doc — the seven are
  recorded verbatim in the phase header as its provenance (Ψ.1–Ψ.7). Phase Ψ has since
  landed in full and its `PLAN.md` section is retired; the design content and the
  Ψ.1–Ψ.7 list now live in `docs/COMPLETED.md`. The seventh track, Ψ-W3-T1, is not one of
  the seven defects/refinements itself — it came from a review finding mid-phase; see
  `docs/COMPLETED.md` for detail.
- **Γ-W3-T2 `bake-reset-amendment` is a CORRECTION, not a feature**, and belongs on this list
  for a different reason from the others: it exists only because Ξ-W2-T1 shipped ahead of this
  plan's sequencing claim. If more corrections of this shape appear, they belong here rather
  than in the table — the table is a completeness proof over `TODO-1.0.md`, and a correction
  has no source row to point at.
- **Γ-W3-T3 `bake-prints-limiter` is also a CORRECTION, not a feature**, and for the same
  reason as T2: it exists only because T2's own finding disproved the premise §3.4's reset
  classification rested on. Unlike the twelve above, it does not come from the 2026-08-01
  interview or that day's four rulings — it is a separate ruling, one day later (2026-08-02),
  opened after T2 landed and found the gap.
- **All of Phase Ε** (`pe-*`). **Six tracks across three waves**, from a direct request
  (Daniel, 2026-08-02), not from `TODO-1.0.md`. Listed here as a block, like Γ and Ψ; the
  product reasoning lives in `docs/product/bank-package.md`. It **supersedes nothing** —
  neither `docs/TODO.md` nor `docs/TODO-1.0.md` records export, import, or a package format,
  so there is no deferred entry to absorb or contradict. Its three [Daniel]-class forks
  (Ε-F1/F2/F3) were opened and ruled the same day it was framed, so no track here is gated on
  a decision; see "Decision state" above.
- **All of Phase Ρ** (`pr-*`). **One track in one wave**, from a direct request (Daniel,
  2026-08-02), not from `TODO-1.0.md`; the product reasoning lives in
  `docs/product/render-in-place.md`. It **supersedes nothing** — a sweep of `docs/TODO.md`
  and `docs/TODO-1.0.md` for `render.in.place|render to new track|preserve.source` returns
  nothing. The smallest phase in this plan, deliberately: it is a thin third verb composed
  out of machinery that already exists, and the burden was on any new machinery to prove it
  unavoidable. Its three [Daniel]-class forks (Ρ-F1/F2/F3) were opened and ruled the same
  day it was framed, so the track here is not gated on a decision; see "Decision state"
  above. **Ρ-F2 was the one that moved the spec** — the result track goes to Arrange
  unconditionally rather than following the active mode, which is also the only reason the
  phase touches `panel_input.cpp` at all. **Ρ-W1-T1 has landed**; see `docs/COMPLETED.md`
  for the full narrative.
- **All of Phase Λ** (`pl-*`). **Thirteen pending tracks across seven waves** (Λ-W2…Λ-W8),
  plus Λ-W1's two audit tracks, which are landed. From a direct request (Daniel, 2026-08-02),
  not from `TODO-1.0.md`; the product reasoning lives in `docs/product/linux-readiness.md`
  and the evidence in the two audit notes it cites. It **supersedes nothing** and **corrects
  one standing promise** rather than inheriting it: `docs/product/versioning-and-release.md`
  commits to three platform artifacts per channel per release, and Λ-D4 (macOS out) makes
  that two for now, with macOS named as deferred. **Λ is the one phase in this plan with
  unanswered [Daniel]-class questions** — four forks, none ruled, of which only Λ-F2 gates a
  dispatch and only Λ-F4 gates a wave; see "Decision state" above. It is also the one phase
  whose acceptance criteria cannot be checked from the current box at all, which is why
  Λ-W3 exists as a wave rather than as a checklist at the end.
- **All of Phase Ω** (`pom-*`). **Eight tracks across two waves** (W1 five, W2 three), from a
  direct list of seven defects and refinements (Daniel, 2026-08-03), not from `TODO-1.0.md`.
  Listed here as a block, like Γ, Ψ, Ε, Ρ and Λ; **like Ψ it has no backing product doc** — the
  seven are recorded in its phase header as Ω.1…Ω.7, and its six rulings are indexed in the
  table there rather than in `docs/product/`. It **supersedes nothing** but **amends two
  settled statements**, which is what distinguishes it from the other doc-less phase: Ω-F2
  reverses the marker "Disabled rather than hidden" principle in
  `docs/product/instrument-control-surface.md` §6.3(d)/§6.4 for the Trigger case, and Ω-F6
  amends `src/shell/view/CLAUDE.md`'s accepted FX-parking load hitch. Its six [Daniel]-class
  forks were opened and ruled the same day it was framed, so no track here is gated on a
  decision; see "Decision state" above. **Two `docs/TODO.md` entries constrain it without being
  discharged by it:** `reasampler_editor.h`'s ceiling margin (three W1 tracks add state to it,
  and the drag-state seam is explicitly out of scope) and `view.cpp`'s blocked split (Ω-W1-T1
  adds a TU beside it rather than growing it).

### Deliberate compressions

Recorded so a reader of `TODO-1.0.md` can see what this plan did to the source, rather
than discovering it later:

- **Item 2 is split across two waves.** The DSP port (Θ-W1-T3) is deliberately separated
  from the integration (Θ-W2-T1) so the external-input dependency on Daniel's Cortex-M4
  code sits on a standalone, disjoint track instead of blocking a wave. Item 2's
  acceptance criteria are split accordingly — the range/resonance assertions land in
  W1-T3's tests, the audible/pipeline/deck criteria in W2-T1.
- **Item 2's filter envelope ships twice.** W2-T1 ships it in the existing staged AHDSR
  shape; W3-T2 gives it the curve treatment and the mode-driven Gate→AHDSR /
  Trigger→AHD shape. This is deliberate: waiting would put the filter behind the whole
  envelope system.
- **Item 4 lands in Θ-W3, not Θ-W1.** Its fix region is the region item 14 retires, and
  the only earlier instrument-side track owns the entire engine. Its acceptance gate is
  stated as the post-consolidation gate. It is not gated behind the *whole* editor chain
  — three waves of six — and the source doc itself requires re-verification under the
  surviving mechanism either way.
- **Item 11's filter velocity curve is split from item 2's filter velocity
  modulation.** W2-T1 wires the modulation path (following the amp/pitch precedents);
  W4-T2 sets the curve's bipolar domain and default and homes its button. Neither track
  can do the other's half.
- **Item 15's undo/recovery is carried as a note, not a criterion** — Daniel set it at
  exactly "a plus." The guaranteed recovery floor (the superseded file surviving until
  prune) is a criterion.
- **Nothing else was compressed.** Every other item's behavior bullets and acceptance
  criteria are carried at full strength into the track that owns it.

---

## Outline at a glance

```
Phase Θ — ReaSampler 9000: one parameter set, filter, shapeable envelopes, legible editor
  [LANDED — all seven waves (W1-W7); see docs/COMPLETED.md; full detail section removed from this file]
  W1  Collapse and re-seam
      T1 zone-retirement ......................... 16
      T2 capture-handoff-bugs .................... 5, 6
      T3 filter-dsp-port ......................... 2 (DSP)      [needs Daniel's M4 code]
  W2  Filter in the voice path; waveform and chrome bands
      T1 filter-voice-path ....................... 2 (integration)
      T2 stereo-waveform-lanes ................... 7
      T3 toolbar-and-piano-strip ................. 12
  W3  Live parameters, then the staged envelope system      [T1 before T2 — serial]
      T1 live-parameter-delivery ................. (not one of the seventeen)
      T2 staged-envelope-curves .................. 1, 8, 14, 4
  W4  Loop sustain and the velocity deck
      T1 gate-loop-sustain ....................... 9
      T2 velocity-deck-and-bipolar-curves ........ 11
  W5  Spline EGs
      T1 spline-egs .............................. 3
  W6  Editor legibility pass
      T1 legibility-and-antialiasing ............. 10, 13
  W7  Arc-and-spline antialiasing fix
      T1 arc-and-spline-aa ....................... (not one of the seventeen)

Phase Ξ — The resample loop            (W1 concurrency-safe with Θ from Θ-W2 onward)
  [LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file]
  W1  Consolidated tracking, and the programmed-note model
      T1 tracking-consolidation .................. 17
      T2 note-program-model ...................... 15 (model)
  W2  The bake chain            [ran AHEAD of Γ; its reset list is corrected by Γ-W3-T2]
      T1 resample-bake-chain ..................... 15 (chain)
  W3  The capture-signal popup      [popup abandoned by ruling; bake window derives itself]
      T1 capture-signal-popup .................... 15 (popup abandoned; window derives)

Phase Γ — The instrument's control surface      (none of the seventeen; ends with VST3 params)
  [LANDED — all four waves (W1-W4); see docs/COMPLETED.md; full detail section removed from this file]
  W1  Foundations — landed                   [seven tracks, disjoint by surface]
      T1 knob-interaction-law ....... modifiers + ONE taper module + reset bypass
                                      + 10 s ceiling + AHDSR schematic axis   [Ruling 2]
      T2 master-bus-audio ........... limiter + meter ballistics + dynamic PDC [rung 1]
      T3 contour-trace-curves ....... staged traces draw curved, knot on its trace
      T4 editor-floor-and-row-law ... floor 1190x680 (W3-T1: 1198) + budget constants + row predicate
      T5 preserve-time-stretch ...... real stretcher      [measure-and-report gate]
      T6 exhaustive-switch gate on pure libraries ... /we4062, -Werror=switch on
                                      pure libraries   [no PLAN entry — see COMPLETED.md]
      T7 psola-preserve ............. PSOLA-aligned splice jump; period_detect
                                      [cadence closure — see COMPLETED.md]
  W2  New controls, and the overlay's marks — landed  [2 tracks]
      T1 pitch-rate-deck ............ Rate + Pitch, Varisp/Presrv compounding  [rung 2]
      T2 loop-crossfade-ux .......... four-mark grammar; fade painted where it is heard
  W3  The reflow, and the bake correction — landed    [3 tracks]
      T1 deck-reflow ................ two rows + double-height MASTER, by construction
      T2 bake-reset-amendment ....... the Xi correction Gamma owns
      T3 bake-prints-limiter ........ landed — bake prints the limiter through
                                      the master stage
  W4  VST3 parameters — landed               [1 track]
      T1 vst3-parameter-set ......... 44 derived params, frozen id table     [Ruling 1]
                                      [rung 3 RESERVED, unspent]
                                      [Gamma-F7 RULED: signal flow order]

  Resequenced 2026-08-01, three times: the reflow split canvas (W1-T4) from arrangement
  (W3-T1); preserve-time-stretch moved W4 -> W1-T5, retiring Rate's interim stand-in; then
  Ruling 1 added W4 and Ruling 2 grew W1-T1.
  Payload rungs are RELATIVE, not absolute. Both are spent — kParamsPayloadVersion == 15
  (Γ-W1-T2, landed, above) and v16 (Γ-W2-T1, stated there) — and the third remains RESERVED
  as v17, unspent: Γ-W4-T1's storage verification did not force it.
  Shared files, named: engine/CMakeLists.txt (W1-T2 | W1-T5), ui/CMakeLists.txt
  (W1-T1 | W1-T3), editor_session.cpp (W2-T1 | W2-T2) — all textual adjacency, not
  semantic contention. W3-T2's disjointness from W3-T1, once a CONDITIONAL risk to confirm
  against what Xi-W2-T1 shipped, is resolved: both tracks landed — see docs/COMPLETED.md.

Phase Psi — The extension trust pass          (none of the seventeen; a direct list of seven)
  [LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file]
  W1  Exact bounds, disciplined switches, reachable actions, resolved drops  [4 tracks]
      T1 capture-range-exactness ..... Psi.7   [opens with a DAW repro matrix]
      T2 mode-switch-discipline ...... Psi.2 + Psi.3   [one chokepoint: applyMode]
      T3 media-explorer-section ...... Psi.4   [BOTH sections; new FOREVER-STABLE id]
      T4 drop-target-resolution ...... Psi.5   [gesture law, not a patch]
  W2  Names and channels, over the settled render block   [gated on W1-T1's render block]
      T1 capture-naming .............. Psi.1   [+ the card shows the name]
      T2 mono-collapse ............... Psi.6   [lossless only; ingest excluded]

  Three invariant amendments are track deliverables: never-touch-solo (W1-T2, three
  files), the action-registration contract (W1-T3, root CLAUDE.md), channel-count-
  preserved (W2-T2, root CLAUDE.md:215).
  Shared files, named: panel_input.cpp (W1-T2 footer block | W1-T4 drag-arm block);
  capture.cpp + capture_realtime_finalize.cpp (W2-T1 naming lines | W2-T2 channel
  lines) — all textual adjacency, not semantic contention. main.cpp is W1-T3's
  exclusively.

Phase Epsilon — The bank package        (none of the seventeen; a direct request 2026-08-02)
  [LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file]
  W1  The contract, the filesystem, and the ledger's new kind   [3 tracks, disjoint by dir]
      T1 package-format .............. core/package: framing + TWO version ints
                                       [E-F1 RULED: proprietary RSBK. No ZIP, no zlib]
      T2 package-fs-shell ............ shell/package: atomic write, streaming, pickers
                                       [ONE picker: REAPER GetUserFileName, mode 0/1]
      T3 import-origin-kind .......... OriginKind::PackageImport = 5, append-only
  W2  The two verbs                    [2 tracks; disjointness CONDITIONAL — see below]
      T1 bank-export ................. export_plan + verb + action; project untouched
      T2 bank-import ................. import_plan + verb + action + .rsbank drop
                                       [E-F2 RULED: always a NEW bank, auto-suffix, no
                                        merge. E-F3 RULED: REFUSE on degraded ledger]
  W3  The compatibility fixtures       [1 track]
      T1 package-compat-fixtures ..... frozen bytes prove BOTH version directions

  Version policy, both directions: older package in newer build ALWAYS imports (additive
  fields default, unknown keys skipped); newer package in older build REFUSES WHOLE with a
  three-part message (package needs / this build reads / writer semver). The gate is
  minReaderVersion <= kPackageFormatVersion — formatVersion is for the message, not the gate.
  All three E-forks were ruled 2026-08-02, the day the phase was framed: NO track here is
  gated on a decision. Import name collision = first free of seed, "seed 2", "seed 3", ...
  seed never re-parsed, fold is BankBook's own.
  Shared files, named: root CMakeLists.txt add_subdirectory list (W1-T1 | W1-T2, one
  append-only line each); main.cpp + the panel bank menu (W2-T1 | W2-T2, one registration
  line and one menu row each); bank_book.{h,cpp} is W2-T2's alone (one additive public const
  member). W1's three tracks are unconditionally disjoint; W2's two are
  textually adjacent only — serialize T2 behind T1 if zero contention is wanted.

Phase Rho — Render in place             (none of the seventeen; a direct request 2026-08-02)
  [LANDED — Ρ-W1-T1; see docs/COMPLETED.md; full detail section removed from this file]
  W1  The verb                                          [ONE track, deliberately]
      T1 render-in-place ............. render selected track -> new sibling track,
                                       item placed at the exact render position,
                                       colour + "Capture " name cloned, source -> Design,
                                       result track -> ARRANGE always. Never the bank.
                                       [R-F1 RULED: refuse multi-track, settled non-goal.
                                        R-F2 RULED: result track ALWAYS Arrange, never
                                        mode-following. R-F3 RULED: follow panel tail]

  The THIRD VERB. Capture = arrange -> bank (writes it). Placement = bank -> arrange
  (reads it). Render in place = arrange -> arrange (bank on neither side). That is why the
  load-bearing capture/placement rule survives it; the full argument is in the phase header
  and in docs/product/render-in-place.md.
  Structural boundary, not a convention: the render destination reaches the backend as a
  TWO-VALUED ENUM (Bank | ProjectMedia), never a caller-supplied path — so no Rho caller can
  name the bank folder. A renderDir string on CaptureRequest IS the drift.
  Genuinely new: ONE pure sibling-placement function (folder-parent and last-in-folder are
  both audibly wrong if inserted at srcIdx+1), an idempotent "Capture " prefix, a
  deriveRenderPaths sibling, and CaptureResult::absolutePath. Everything else composes.
  R-F2 fallout, the ruling's only cost: the result track AND its item are tagged
  kArrangeModeId explicitly, and detectNewContent must drop added GUIDs that already carry
  a membership record — the auto-tag detector otherwise re-tags a Design-fired result to
  Design on its next tick and reverses the ruling. Firing from Design is therefore
  deliberately invisible: the render waits in Arrange. No A/B-on-the-bench behaviour exists.
  Four invariant amendments are track deliverables (the Psi precedent): shell/capture and
  shell/actions CLAUDE.md placing-path claims, ONE sentence in root CLAUDE.md naming
  the third verb without softening the prohibition, and core/view CLAUDE.md's "new tracks
  are tagged to the active mode" made conditional on carrying no membership record.
  Shared files, named: capture.{h,cpp} (destination enum + result field), main.cpp (one
  ActionTableRow), and panel_input.cpp (detectNewContent only; Psi's two named regions in
  that file are landed and are other functions) — the only pre-existing shell files
  touched. Disjoint from Gamma (core+shell/instrument) and Epsilon (core+shell/package).

Phase Omega — Responsiveness + control surface  (none of the seventeen; a direct list of seven)
  W1  The five defects, in parallel                     [5 tracks, file-disjoint]
      T1 mode-switch-responsiveness .. Omega.7 structural: undo mask (-1 = UNDO_STATE_ALL,
                                       pulls every FX chunk), unconditional SetOffline,
                                       PreventUIRefresh (used NOWHERE in src today),
                                       compare-before-write, resolve() map, and the
                                       F6 split: UI/routing sync, per-FX park DEFERRED.
                                       New TU beside view.cpp (642 lines, over ceiling).
      T2 mode-switch-persist ......... Omega.7 persist: a switch rewrites all SEVEN
                                       ext-state keys when only view_state moved.
      T3 meter-rate .................. Omega.1: ONE 500ms timer today -> second timer id
                                       for the meter; 500ms poll body UNCHANGED.
                                       Paint: dirty rect, hoisted bitmap, hero-envelope
                                       cache. 60 FPS target, 30 the measured fallback.
      T4 deck-reflow-focus-toggles ... Omega.2 + Omega.3 + Omega.4. Filter Mod -> FILTER
                                       ENV last-in-run (id 1240 FROZEN, annotate only);
                                       run CENTRED at kDeckCellW; single buttons in two
                                       variants (enable = AccentPrimary|dim gray, mode
                                       selector = reads current mode, no off state);
                                       whole env deck = overlay target, radios gone but
                                       DeckRadioDesc survives for MASTER's passive lamp.
                                       [F3 + F4 RULED. Gate face stays PIXEL-IDENTICAL.
                                        COST: the filter tie-line is unreachable after
                                        the move — stated, not discovered]
      T5 overlay-mapping ............. Omega.5 + Omega.6. Trigger = marks HIDDEN; Gate
                                       loop-off = GREY DISABLED and still grabbable;
                                       GATE ONLY caption gone, the other two survive.
                                       ONE mapping for both chains — four discrepancies
                                       (2px inset, round vs truncate, closed vs half-open,
                                       half-pixel phase). Overlay-inset route recommended;
                                       draw_kit route is [propose]. Zero-crossing snap
                                       behaviour-identical. Fixtures RE-DERIVED.
                                       [F1 + F2 RULED]
  W2  Measurement, and what only a DAW can say          [3 tracks]
      T1 view-topology-cache ......... core/view memoization  [GATED on W1-T1's measurement;
                                       "not worth taking" is a legitimate outcome]
      T2 content-detect-throttle ..... the 30/sec enumerate, throttled not weakened
      T3 omega-verification-sweep .... the deliverable is a RECORD, not a fix

  Small tidy work sequenced BEFORE Lambda, and deliberately not another Lambda: no format,
  no wire version, no parameter id, no new artifact. All six D-rulings SETTLED 2026-08-03
  (F1 widen the mapping scope; F2 hidden in Trigger / grey-disabled in Gate; F3 single
  button extends to Filter, Loop, Mono|Stereo; F4 AccentPrimary not AccentHot; F5 both
  entry surfaces; F6 separate UI from processing). ZERO open forks.
  Two invariant amendments are track deliverables: instrument-control-surface.md 6.3(d)
  + 6.4 (T5) and shell/view/CLAUDE.md's FX-parking load hitch (T1).
  The solo surface index is EXONERATED (~2T calls, sub-ms) — do not re-litigate it.
  reasampler_editor.h has ~36 lines of ceiling margin and THREE W1 tracks add state to it;
  the drag-state seam TODO.md names is OUT OF SCOPE — a track over the ceiling stops and
  flags. Shared files, named: reasampler_editor.h (T3|T4), editor_paint_waveform.cpp
  (T3 envelope cache | T5 marks+mapping), editor_controls.cpp (T4 deckDescs | T5
  waveMarksFor), app/CMakeLists.txt + main.cpp (T1, cross-phase with Lambda).

Phase Lambda — ReaSampler on Linux      (none of the seventeen; a direct request 2026-08-02)
  W1  The audits                                                          [COMPLETE]
      T1 build-toolchain-audit ....... L-01..L-10, V1..V10, D1..D7
      T2 source-runtime-audit ........ L2-01..L2-12

  W2  Make it buildable, and make it honest             [4 tracks, file-disjoint]
      T1 linux-compile-blockers ...... L2-01, L2-02, L2-03      -> V1, V2
      T2 toolchain-floor ............. L-02..L-05, L-07, L-09, L-10  -> V5, V6, V8
      T3 panel-dialog-resource ....... L-01, L2-06, L2-07       -> V7   [GATED: L-F2]
      T4 locale-independent-numerics . L2-04     [the one track verifiable on Windows]
  W3  First light, and the verification sweep           [1 track, deliberately]
      T1 linux-verification-sweep .... V1..V10 + T2 section 5's seven; the deliverable
                                       is a RECORD, not a fix. V1 / V9 / the API-load
                                       count run FIRST — each reprices what follows.
  W4  Correctness and safety, repriced                  [3 tracks, disjoint by dir]
      T1 prune-deletion-safety ....... L2-05 both halves + the symlink reclaim lie
      T2 linux-font-faces ............ L2-09
      T3 source-partition-and-invariants . L-06, L2-10, L2-11 (Linux half)
  W5  Ship the extension                                [1 track; GATED: L-F4]
      T1 linux-packaging-and-install . L-08 + L-02's shipped half + both channels
  W6  The instrument module + the host-safety contract  [T1 before T2 — serial]
      T1 vst-linux-module ............ B5 (the D5 reversal, FIRST), B1, B2
      T2 vst-host-safety-contract .... D3's observable contract; passes with ZERO params
  W7  The SWELL bootstrap                               [1 track]
      T1 swell-dylib-bootstrap ....... B3 via route B3a, WITHOUT the stub's exit(2)
                                       [prereq: V9 affirmative, else L-D2 re-rules]
  W8  The X11 editor                                    [1 track]
      T1 x11-embed-view .............. B4, L2-08. The phase's one L-band source item.

  NOTHING here has been verified on a Linux machine. W2's edits are authored BLIND from the
  audits' citations and their acceptance criteria are discharged in W3 — W2 is not "done"
  until W3 runs, and this plan says so rather than pretending otherwise.
  W6 may run concurrently with W4 and W5 — file-disjoint; the only reason to serialise is
  attention. Six D-rulings are SETTLED (instrument in scope; SWELL via dlopen of REAPER's
  libSwell.so; REAPER-only editor but other hosts must degrade SAFELY; macOS out; shipped
  not developer-only; hard unlink acceptable with a platform-aware confirmation).
  OPEN, none ruled: L-F1 (CI — gates nothing), L-F2 (dialog-resource route — the ONLY fork
  gating a dispatch, W2-T3), L-F3 (copy-only invariant wording — gates nothing),
  L-F4 (declared support floor — gates W5, not a dispatch).
  Lambda registers NO VST3 parameter, ever — that table is Gamma-W4-T1's and is frozen.
```
