# 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`. ## 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-w-t-`. Greek phase letters transliterate: **Θ → `th`**, **Ξ → `xi`**, **Γ → `g`**, **Ψ → `psi`**, **Ε → `e`**, **Ρ → `r`**, **Λ → `l`**. 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`, and Λ-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`). ## 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.** **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 both phases and are stated once here rather than repeated per track. **Phase Γ adds a set of its own**, stated in its phase header. **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 `_tests` target** that runs without REAPER or a DAW. New pure modules in this plan are not optional-test. --- ## Phase Γ — The instrument's control surface **Ships:** the deck reflowed into two categorical rows with a double-height MASTER bus deck, a PITCH/RATE deck with playback-rate and baseline-pitch controls, a master limiter with dynamic reported latency and a real output meter, one consistent knob interaction/taper law across every variable control **over a stage-time range raised 2 s → 10 s**, a fix for staged contour traces drawing straight, a re-approached loop/crossfade marker UX under an explicit chrome-row loop enable, **the Phase Ξ bake's reset list corrected**, and — as the phase's last track — **the instrument's first VST3 automatable parameters, reported to the host under a frozen id contract.** **Consolidates:** none of the seventeen. Phase Γ came from a direct interview with Daniel (2026-08-01); the product reasoning, the measured layout table, the invariant collisions and the fork rulings are in **`docs/product/instrument-control-surface.md`**, and the parameter system's is in **`docs/product/parameter-automation.md` §§6–10**. Read §1.2 (the layout table) and §7 (collisions) before dispatching any track here — every number in this phase is derived there, and `docs/TODO.md`'s old deck-rework geometry is superseded. **All four waves have landed — Phase Γ is complete.** W1 through W4 each carry their own landed note below; see `docs/COMPLETED.md` for every track's full narrative. **Fork state — SEVEN ruled, ONE OF THEM LATER REVERSED, NONE OPEN.** Indexed at spec §8, folded into the tracks below: - **Γ-F1** — `kEditorMinHeight` stays **680**. - **Γ-F2** — the limiter has **lookahead with DYNAMIC reported latency** (zero when off, the lookahead when on, reported to host PDC). *This inverted the product recommendation;* W1-T2's scope grows accordingly — spec §3.1.1. - **Γ-F3 — RULED, THEN REVERSED THE SAME DAY.** First ruled *"the ceiling stays 2.0 s in this phase"*; then Daniel: ***"extend the stage lengths to 10s."*** `kEnvTimeMaxSeconds` / `kGateStageMaxSeconds` move **2.0 → 10.0 in Γ-W1-T1**, and the `docs/TODO.md` entry that carried the ambition is discharged rather than deferred. **The reversal's cause is Ruling 1** — parameters now ship in-phase, so the ceiling is a one-way door that must be walked through before them, not after. Spec §4.3.1. - **Γ-F4** — there **is** an explicit loop enable, and it lives on the **chrome row**, not in a deck. W2-T2's scope grows accordingly — spec §6.4. - **Γ-F5** — MASTER's reserved slot is **one** cell. The 90 px headroom argument behind that is spec §1.6 and governs every future control addition. (**Moved afterwards:** Γ-W3-T1 widened the row block, so §1.6's ledger is now 82 px — the ruling and its purchasing power are unchanged, only the number.) - **Γ-F6** — **ship dynamic latency as ruled.** The `restartComponent(kLatencyChanged)` deactivate/reactivate the SDK mandates is accepted: *"the limiter will either be on or off on its instance, toggling during playback is not a use case."* No constant-latency fallback, no measurement gate. *This ruling also corrected the analysis* — spec §3.1.1. - **Γ-F7 — RULED: SIGNAL FLOW.** Daniel, 2026-08-01: *"signal flow order."* The VST3 parameter order — both the frozen id numbering and the `getParameterInfo` presentation index — is **PITCH/RATE → PITCH ENV → FILTER → FILTER ENV → AMP → VELOCITY → VOICE → MASTER**, the deck's own `sampleDeckGroups` rule, with each group's cells in the semantic order the id table freezes. The editor's visual rows after the reflow were the rejected alternative. **The reason, because a future reader will ask why the id order does not match the screen:** the editor's layout has already moved twice (Θ-W6-T1 grew the floor 840 → 980; Γ-W3-T1 takes it to 1198 and re-rows every group) and within-row order is settled by width fitting, not by meaning — so **binding a permanently-frozen id order to a demonstrably mobile layout guarantees the two drift apart**, after which the order is neither logical nor matching. Signal flow is the axis that does not move. Full argument and the accepted residual cost: `docs/product/parameter-automation.md` §6.4; **the resulting 44-id table is stated at §6.2.** **Ruling 1 (Daniel, 2026-08-01) — VST3 parameter reporting ships in this phase.** Verbatim intent: *"correct the phase gamma plan to account for complying with the VST3 standard for parameter reporting… by the end of gamma we have the automatable params reported. Make the parameter order logical."* `docs/product/parameter-automation.md` was written as scoping and has been **promoted in place**: §§1–5 are the original analysis, **§§6–10 are the specification** Γ-W4-T1 is built from. Four things it decides that the scoping pass left open: the blob stays authoritative and parameters are a third surface onto the one model (§6.1); the id space is an independent, hand-assigned, FOREVER-FROZEN table, **now stated in full as 44 numbered rows in signal-flow order** (§6.2, §6.3); the exposed list is **derived from the three-state commit predicate**, never hand-maintained (§7); and, under Ruling 3, every parameter's **plain unit, range and display precision** (§6.7). Today the plugin has **zero** parameters — `ReaSamplerProcessor::initialize` (`reasampler_processor.cpp:56-73`) never populates `SingleComponentEffect::parameters`, so `getParameterCount()` returns the SDK default 0. **What the Γ-F6 ruling changed in the analysis, not just in the plan.** Dynamic latency reporting is **routine** for VST3 instruments and REAPER handles it as a matter of course; the SDK's deactivate/reactivate requirement (`pluginterfaces/vst/ivsteditcontroller.h:105-108`) is the normal contract, not an exotic one. What made the cycle expensive **here** was entirely our own doing: `ReaSamplerProcessor::setActive(true)` called `reloadInstrument()` — a bridge read plus a full WAV re-decode — where a typical plugin's `setActive` only allocates and frees buffers. **The cost was therefore ours to reduce, and the reduction was decoupling reload from activation — not abandoning dynamic latency.** **Landed in Γ-W3**: the activate branch (`reasampler_processor.cpp:86-132`) now resumes the voice state around a parked `SampleData` and reloads only when there is nothing to resume from or a bank refresh moved what the park was decoded from; the deactivate branch parks the PCM and frees everything else. Narrative and consequences are in `docs/COMPLETED.md`. **Sequencing against Phase Ξ — the ordering claim is RETIRED and replaced by an owned correction.** This plan previously asserted that Γ must run before Ξ-W2 and called it *"a correctness point, not a preference."* **Ξ-W2-T1 ran first.** That was not a decision anyone took — the track was live before this phase existed (Daniel: *"xi was started before I spun you up, we'll have to correct phase xi inside gamma. wasn't a choice."*). So: 1. **The bake's reset list is incomplete as shipped, and Γ-W3-T2 amends it.** Rate, pitch offset and the limiter flag are all **reset** under Ξ-W2's own ratified rule (spec §3.4), so no Daniel decision is owed — only the edit, and it must be made **against what Ξ-W2-T1 actually shipped rather than against what this plan predicted it would ship.** 2. **Ruling 1 adds a second correction of the same shape, and it lands one wave later.** Exposing the reset-class values as VST3 parameters means the bake's reset must notify the host, and a host automation lane on a reset-class parameter re-imposes its curve onto already-baked audio. Both are Γ-W4-T1's acceptance criteria — that track creates the condition, so it carries it (`docs/product/parameter-automation.md` §9). 3. **The payload-ladder half of the old claim needs re-checking, not restating** — see the ladder block below, which now states rungs **relatively** rather than by number. **The organizing constraint.** Six surfaces are single-writer and dictate the wave shape: `ui/deck_values.cpp` **and the taper module extracted from it** (the taper law and the new ceiling, then the two new controls, then the host normalization — three tracks, three waves), `editor_paint_waveform.cpp` (the contour trace, then the loop marks), `ui/deck_groups.cpp` (the row predicate, then the PITCH/RATE descriptor, then the reflow's row consumption — three tracks, three waves), `engine/voice.cpp` (the Preserve read path, then the rate compounding into it), `shell/instrument/reasampler_processor` (the limiter chain and latency, then the parameter surface), and the params-payload ladder. **Every wave boundary below is one of those collisions**, not a preference. Where a wave has more than one track, the tracks are disjoint by surface. **Resequenced 2026-08-01 (Daniel), three changes.** The prior four-wave shape put the reflow at W3 and the Preserve stretcher at W4; both moved. **Ruling 1 then added a fourth wave** — see "The wave shape after Ruling 1" below. 1. **The reflow is split, canvas from arrangement.** The window floor and the width budget it is derived from land **early** (Γ-W1-T4), so every other UI track in the phase is drawn, tested and judged at the final 1198 × 680 window instead of at a size a later wave changes under it. The two-row *arrangement* stays late (Γ-W3-T1), because it can only be measured once the final PITCH/RATE and MASTER descriptors exist. The seam is stated at Γ-W1-T4. 2. **`preserve-time-stretch` moved W4 → W1-T5.** It is the longest pole in the phase and has **zero dependency on any UI work** — a pure `core/instrument/engine/` module. Scheduling it last was a scheduling error. Consequence: it is no longer Rate's *successor* but its **prerequisite**, which retires the interim resample-and-cancel stand-in entirely — see Γ-W2-T1. Net: four waves become three, and both of the phase's DSP unknowns (the limiter, the stretcher) are exposed in wave 1 rather than one of them landing last. **The wave shape after Ruling 1 — three waves become four.** The parameter system cannot be a track inside any existing wave, and the reason is a chain of hard prerequisites, not caution: - **after W1-T1**, because the taper and the 10 s ceiling *are* the host-facing normalization, and Γ-W1-T1 is also what extracts them into the one module the host will read through; - **after W1-T2 and W2-T1**, because every control that could be a parameter must exist before the list is declared — the list is derived from the control inventory, and an inventory that is still growing produces a list that has to be re-frozen; - **after W2-T1 specifically**, because `deckParamCommit` becoming three-valued is the *prerequisite* of the classification, not an incidental of it: the exposed set is exactly `Live ∪ NoteOnLatched`; - **after W3-T1**, because MASTER's inventory (the limiter toggle, the GR bubble, the reserved cell) is the last change to what controls exist at all; - **after W3-T2**, so the bake's reset list is already complete when Γ-W4-T1 adds the host-notification obligation over it — one amendment instead of an amendment to an amendment. The result is a single-track **Γ-W4**, which is the right shape for it anyway: the storage decision governs every part of the work, exactly as Ξ-W2-T1's crossing decision governs its chain. **And it satisfies Daniel's own framing literally** — *"by the end of gamma we have the automatable params reported."* **The params-payload ladder — re-checked, and now stated RELATIVELY.** The old block named v14 and v15 as absolutes. **That is no longer safe to assume**, because Ξ ran ahead of its sequencing and this plan is not the record of what Ξ-W2-T1 actually took. On `dev` today `kParamsPayloadVersion` is **14** (`map/component_state_io.h:163`) and Ξ-W2-T1 was specced to take no rung — but the plan's prediction is not evidence. So: > **Γ owns the next three rungs above whatever `dev` carries when Γ-W1-T2 dispatches, and > that number is READ, not assumed.** In order: **the first rung to W1-T2** (the limiter > enable flag), **the second to W2-T1** (rate + pitch offset), **the third RESERVED for > W4-T1** — spent only if the storage-architecture verification forces a persisted field, > which the specification says it will not (`docs/product/parameter-automation.md` §6.1, > §10). If unspent, that rung falls through to the next phase unclaimed. > > **On `dev` as of 2026-08-01 that resolves to v15 / v16 / v17-reserved.** Ξ-W3-T1 landed and > consumed a rung (v14, the bake Hold division) — not Ξ-W2-T1, which took none as specced — so > every number shifted by one and **nothing else about the ownership changes** — which is the > whole point of stating it relatively. **Every other track in the phase owns no rung**: W1-T1 changes no persisted field (the payload stores raw engine doubles, so both the taper and the new ceiling are persistence-neutral), W1-T3, W1-T4 and W1-T5 add no field, W2-T2's loop enable maps onto the already-persisted `SampleLoop::hasLoop`, W3-T1 is layout only, and W3-T2 changes a reset list, not a format. **The editor's deck is knowingly mis-composed between Γ-W1-T4 and Γ-W3-T1, and that is not a defect report.** Raising the floor without the reflow leaves the greedy whole-group wrap packing two ragged left-aligned rows with categorically wrong membership. Γ-W1-T4 states the exact interim layout; do not "fix" it in a track that does not own it. **Phase-wide acceptance criteria** (in addition to the ones stated at the top of this file): - **Bypassed means byte-identical.** With the limiter off, the per-sample output path is byte-identical to today's bare ramped multiply — the same discipline that makes `live == nullptr` byte-identical to the pre-live core and the filter's exact skip at `modAmount == 0` hold the at-rest path unchanged. - **No `ComponentState` sound change.** A project saved before this phase reopens sounding identical: absent rate lifts to 100 %, absent pitch offset to 0 st, absent limiter flag to bypassed. Re-tapering a knob **and raising the stage-time ceiling** (both Γ-W1-T1) change needle angles only — the payload stores raw engine doubles, so saved values reload bit-identical, and a 3 s stage saved at the old ceiling is simply unreachable-by-hand rather than altered. - **`kVelocityPitchRangeSemitones` / `kPitchDepthMaxSemis` (24.0) does not move.** It is load-bearing in the v12 wire format. The new Pitch knob **reads** it; it does not mint a second ±24 constant. **From Γ-W4-T1 it is also a frozen host normalization** — one more reason, not a new rule. - **The taper has exactly ONE home and three consumers, and from Γ-W4-T1 the taper IS the host's `toPlain`/`toNormalized`.** Γ-W1-T1 extracts it into a pure module; the knob's needle (`deck_values`), the AHDSR overlay's schematic axis (`envelope_overlay` + `envelope_edit`), and the host's `normalizedParamToPlain` / `plainParamToNormalized` (Γ-W4-T1) all call the same function. **Three functions that agree today is a defect, not an implementation choice** — the failure it prevents is a host automation lane that means one value and a needle that draws another. Under Ruling 3 this stops being an analogy: `toNormalized` is not *like* the taper, it *is* the taper (`docs/product/parameter-automation.md` §6.7.3). - **ONE formatter per unit category, and the editor and the host are both its callers.** The formatter is pure and returns the **digits** of a plain value in that category's single `units` string — no embedded unit, no magnitude-switched unit, no width-conditional abbreviation, no caller-side branch. The editor's knob label and `getParamStringByValue` read the same function. **The editor and the host printing different text for the same stored value is a defect class, forbidden structurally rather than caught at review** — same discipline, same reason, as the taper criterion above. Consequences (existing formatters stop embedding their unit; cutoff's `k` abbreviation is retired; the curve dial's `^` is static cell chrome, not value): spec §6.7.2. - **Every default value has an EXACT normalized preimage under its own taper.** Binds Γ-W1-T1 (which designs the taper) and Γ-W4-T1 (which declares `ParameterInfo::defaultNormalizedValue`). `resetDeckParam` bypasses the taper; **a host's reset-to-default cannot**, so exactness in the map itself is the only thing that makes the editor's reset and the host's reset land on the same value. **Ruling 3 tightens this twice and adds one non-requirement** (§6.7.7): `defaultNormalizedValue` is **computed** as `toNormalized(default)`, never written as a normalized literal; the assertion is made on `toPlain(defaultNormalizedValue)`, the pair the host actually calls; and `toNormalized(toPlain(n)) == n` at **arbitrary** n is explicitly NOT required — no log map satisfies it in double, and demanding it would over-constrain the taper for nothing. - **Nothing in this phase may re-map the filter's four normalized controls.** Cutoff, Q, morph and drive persist as `*Norm` doubles in payload v9 — their laws are already wire-frozen, and re-tapering them would re-tune every saved project independently of automation. The snap-unit table names them; that is display, not law. **This does NOT conflict with Ruling 3's real-unit requirement**, and the two must not be read as a collision: `toPlain` is a pure read-side mapping that never touches the stored value, so reporting Hz / Q / drive depth means **calling** `filterCutoffHzFromNorm` and its peers, not replacing them — which the editor's own labels already do today. The prohibition forbids *editing* those laws; the requirement is satisfied by *calling* them. One additive gap: drive has no published inverse and `filterNormFromDriveDepth` must be added beside the two that exist — the analytic inverse of a frozen law is not a change to it. Spec §6.7.5. - **Shift-snap is a drag rule; `stepCount` is a parameter property; they are independent.** The editor's snap grid must never be exposed as `ParameterInfo::stepCount` — that would quantize the parameter itself, permanently and for the host's automation too, freezing the grid into the forever contract and putting continuous cents out of reach from a lane. **All 44 exposed parameters ship `stepCount = 0`**, swept and confirmed, and the coincidence is structural: every discrete control is reload or rebuild tier and therefore omitted by the predicate. Spec §6.7.6. - **From Γ-W4-T1, the parameter-id table is FOREVER-FROZEN**, on the same footing as the extension's `"STABLE_FOREVER_STRING"` command ids, the two VST3 class UIDs, and the params-payload field order. No id is reassigned, reused or re-pointed; no exposed parameter's normalization ever changes; a retired control's id is retired with it. Full wording: `docs/product/parameter-automation.md` §6.3. - **The exposed parameter set is DERIVED, never hand-maintained.** A control is a parameter if and only if its commit class is `Live` or `NoteOnLatched`. There is no second table beside `deckParamCommit` / `liveCommitFor`, and no list that can drift from it. - **The window floor is 1198 × 680 and must not exceed 1280 × 720.** **Γ-W1-T4 set it at 1190, in wave 1; no other track in the phase may move it** — with ONE ruled exception, Daniel 2026-08-02: Γ-W3-T1 widened the row block 1020 → 1028 and the floor 1190 → 1198, so the justification law puts both rows' filter edges on one pixel (spec §1.3). That is the only reopening, and only these two constants moved. From that point every track is authored and judged at it. A track that pushes the floor past 1280 has failed, not overrun. **`kEditorMinHeight` stays 680** (Γ-F1). The remaining **82 px of width headroom is the budget for the life of this layout** — one deck cell is 60 px, so there is room for exactly one more, once. Spec §1.6 states the ledger; read it before adding any control. Chrome-row additions are a **separate purse** (they are paid for out of the title slot, not the floor) and must not be charged against this one. - **Reported latency is zero unless the limiter is on.** `getLatencySamples()` returns 0 with the limiter bypassed, in every track and at every point in the phase. Only W1-T2 may introduce a non-zero value, and only under the limiter-on condition. - **Geometry stays pure.** Every new layout, cap, label and hit-test rule lands in a pure CTest-covered module (`knob_deck`, `sample_bands`, `waveform_view`), never in a painter. --- ### Γ-W1 — Foundations **Depends on:** nothing in this phase. **All seven tracks have landed** — Γ-W1-T1 (`knob-interaction-law`), Γ-W1-T2 (`master-bus-audio`), Γ-W1-T3 (`contour-trace-curves`), Γ-W1-T4 (`editor-floor-and-row-law`), Γ-W1-T5 (`preserve-time-stretch`), Γ-W1-T6 (`exhaustive-switch gate on pure libraries`), and Γ-W1-T7 (`psola-preserve`) — see `docs/COMPLETED.md` for the full narrative of each. **T6 has no subsection below**, matching this plan's original choice not to give it one; its record in `docs/COMPLETED.md` is reconstructed from the CMake change and its enforcement call site rather than from a spec section here. #### Γ-W1-T1 — `knob-interaction-law` **Landed** — see `docs/COMPLETED.md` for the full narrative. One consistent interaction and taper law across every variable control, landed before Rate/Pitch or any VST3 parameter existed so both are authored into it rather than retrofitted. The taper is extracted into its own pure module, `core/instrument/ui/param_taper` — the one home three consumers read (the knob's needle, the AHDSR overlay's schematic axis and its drag inverse, and — from a later wave — the VST3 host's `toPlain`/`toNormalized`). Shift snaps to whole units in the control's displayed category; Ctrl scales the drag by 0.05; Shift+Ctrl resolves to Shift; a mid-drag modifier transition re-anchors value and cursor position. **The stage-time ceiling moves 2.0 s → 10.0 s** (`kGateStageMaxSeconds`/`kEnvTimeMaxSeconds`, moved together so they cannot drift), reversing Γ-F3 on Daniel's later ruling, and every default now has an exact normalized preimage under its own taper — the requirement Γ-W4-T1's `defaultNormalizedValue` depends on, since a host's reset-to-default has no `resetDeckParam` bypass to fall back on. The filter's four `*Norm` controls stay untouched (wire-frozen in payload v9); the change is persistence-neutral throughout. #### Γ-W1-T2 — `master-bus-audio` **Landed** — see `docs/COMPLETED.md` for the full narrative. The master bus: a bypassable true-peak limiter (baked −0.3 dBTP ceiling, default off, no makeup gain, stereo-linked, sidechain-only oversampled detection), the meter's audio and publication half, and the plugin's first latency report — `getLatencySamples()` returns 0 with the limiter off and the lookahead in samples with it on, driving `restartComponent(kLatencyChanged)` on toggle. New pure modules `core/instrument/engine/limiter` and `engine/meter_ballistics`. **Spent the phase's first payload rung: `kParamsPayloadVersion` reaches 15**, appending the limiter enable flag as a strict suffix. #### Γ-W1-T3 — `contour-trace-curves` **Landed** — see `docs/COMPLETED.md` for the full narrative. Staged envelope segments now draw as the curve their exponent defines, closing the defect where the mid-segment knot floated off its own trace. A new pure module, `curve_tessellate`, draws every sloped stage through the same curve the audio's evaluators use, so the drawn stage and the sound it makes cannot diverge. All three envelopes, both play modes, every sloped stage, share the one fix; audio is unchanged. #### Γ-W1-T4 — `editor-floor-and-row-law` **Landed** — see `docs/COMPLETED.md` for the full narrative. Commits the editor's canvas ahead of the rest of the phase's UI work: `kEditorMinWidth` moves 980 → 1190 (`kEditorMinHeight` stays 680, Γ-F1), derived from three budget constants — the row block (1020), MASTER's reserved width (142), and the 1280 ceiling (`kEditorCeilingWidth`, relocated into `sample_bands.h`) — leaving 90 px of headroom. Row membership becomes a property of the group id via an exhaustive `deckRowFor(DeckGroupId)` switch (Sound / Contour / Spanning), consumed by no one yet — **that consumption, and the fit inside the 1020 block, is Γ-W3-T1's** to assert. No drawing code, descriptor, parameter, or audio changed in this track. **Superseded in part:** Γ-W3-T1 asserted the fit and found 1020 could not deliver the tie-line it was chosen for, so the block is now 1028 and the floor 1198 — see that track's block below. #### Γ-W1-T5 — `preserve-time-stretch` **Landed** — see `docs/COMPLETED.md` for the full narrative. A real pitch-preserving time-stretcher for Preserve mode, moved up from a later wave (Daniel, 2026-08-01) so Rate ships onto a finished engine instead of a disposable stand-in — the interim resample-and-cancel path that had been planned for Γ-W2-T1 was not built at all. New header-only pure module `time_stretch` alongside `pitch_shift`'s existing shift-ratio control; rate 1.0 is exactly one source frame per output frame with no residue, keeping the unity-ratio Preserve read bit-identical to the pre-stretch engine. No new third-party dependency, no allocation/lock in `process()`, no per-sample dispatch. --- #### Γ-W1-T7 — `psola-preserve` **Landed** — see `docs/COMPLETED.md` for the full narrative, including the corrected closure status below. Preserve's splices become pitch-synchronous: a new pure module, `core/instrument/engine/period_detect` (two-pass YIN), estimates the source's fundamental period once at load; `pitch_shift`'s splice jump becomes the multiple of that period nearest the fixed window, so an aligned landing point exists by construction. Detection runs off the audio thread by link graph — `sampler_core` does not link `period_detect` — and an unknown period restores the fixed-window geometry byte for byte. A period is derived from the audio at load, so it is cache, not state: no `ComponentState` field, no payload rung. It gates the Rate control (Γ-W2-T1) on the plan's own stated principle that Rate must not ship before its Preserve engine. **Both failure modes this track set out to close are now closed.** The **geometry** failure mode (no phase-aligned landing existing inside the search window for low material) closed at the original merge. The **cadence** failure mode — splices recurring faster than the output period — was left open at that point, with unresolved review findings from a later review of a follow-up fold; three remediation commits have since landed and a re-review confirmed the earlier findings closed. The closing measurement (one-machine, Debug-build) is recorded in `docs/COMPLETED.md`, not restated here. --- ### Γ-W2 — New controls, and the overlay's marks **Depended on Γ-W1** for the taper law and the Preserve stretcher (T1), and the contour trace and the floor/row law (T2) — see `docs/COMPLETED.md` for the full narrative. **Both tracks have landed** — Γ-W2-T1 (`pitch-rate-deck`) and Γ-W2-T2 (`loop-crossfade-ux`) — see `docs/COMPLETED.md` for the full narrative of each. #### Γ-W2-T1 — `pitch-rate-deck` **Landed** — see `docs/COMPLETED.md` for the full narrative. PITCH became PITCH/RATE: three knobs (`Key Trk | Rate | Pitch`) under the existing Varisp|Presrv toggle, both new controls wired through the engine. Rate is 50–200 % on a taper linear in semitones over ±12 (the stated exception to the centre-expansion law), note-on latched; Pitch is a ±24 st baseline offset, live. Keytrack × rate × pitch-offset compound into a single read-increment multiply — the per-sample path gained nothing. Merged as `9dbb8b8`; spent the phase's second payload rung, v16, as a strict suffix, a v15 blob lifting to rate 100 % / pitch 0 st. **`isLiveDeckParam` was renamed `deckParamCommit`** and now returns a three-state `LiveCommit` (`Live` / `NoteOnLatched` / `Reload`) rather than a bool — one predicate widened, not a second mechanism. **Rate classifies `NoteOnLatched`, Pitch classifies `Live`; Γ-W4-T1 derives the VST3 exposed parameter set from this predicate**, so the classification is load-bearing two waves out. Code review found one Critical (the resample bake read a stale frame window at non-unity Rate; fixed by deriving the window from the rate the voice actually reads at, proved non-vacuous by reverting the fix) and folded in a ruling that Pitch, unlike key-track and velocity, is now compensated against Varispeed's stage-time coupling. A Varispeed golden hash was added, honestly witnessed from the remediation commit rather than pre-track, so it stands as a witness for the next track rather than proof of this one. #### Γ-W2-T2 — `loop-crossfade-ux` **Landed** — see `docs/COMPLETED.md` for the full narrative. An explicit loop enable, a legible mark grammar, and the crossfade painted where it is heard. **No format change** — no `ComponentState` version moved, no new persisted field, `resolveLoop` untouched, audio unchanged. The enable **is** `SampleLoop::hasLoop`, whose provenance changes from marker-gesture-derived to user-owned, with the gestures as shortcuts onto it. Merged as `a8e30a9`. A new pure module, `core/instrument/ui/loop_marks`, holds the state machine (`resolveLoopMarks`/`applyLoopMarks`); the four marks (START/LOOP/END/XFADE) get one grammar — line + shaped cap + label, the cap being the grip — with cap/label/suppression geometry pure and unit-tested. The crossfade moved to `[loopEnd − crossfade, loopEnd)`, drawing as a top-and-bottom edge wedge, never a second fill; the loop fill's peak alpha stays exactly 0.20. **START draws in `overlay/trace`, a deliberate deviation from `docs/product/instrument-control-surface.md` §6.3's table**, because the spec's `accent/primary` choice would measure 1:1 against the waveform fill it marks. Code review found three Majors, all fixed before merge. `docs/TODO.md`'s "Pre-existing staged-envelope-node shadow at zero-attack" entry was resolved incidentally — giving START a cap is what closed it — and rewritten in place with the recorded outcome by the track itself. **One thing is deliberately left open, not accepted:** the audible crossfade wedge draws at 1.60:1 against the envelope trace, under the 3:1 floor, with no alpha fix available; Daniel is judging it visually in the DAW and has not yet ruled. **Neither track has been verified in a running DAW; both are asserted in CTest only** — 99/99, Debug config, on one machine. --- ### Γ-W3 — The reflow, and the bake correction **Depended on Γ-W2** for the PITCH/RATE descriptor (W2-T1) — the reflow measured the real three-cell group rather than a forecast of it, which is the whole reason the arrangement was scheduled late. **Depended on Γ-W1** for W1-T2's published meter/GR/clip state, which MASTER's deck draws, and W1-T4's floor, budget constants and row predicate, which T1 consumed rather than re-derived. **T2 depended on Phase Ξ** — `Ξ-W2-T1 (resample-bake-chain)` landing on `dev` first, the phase's only external gate — see `docs/COMPLETED.md` for the full narrative of each landed track. **All three tracks have landed** — Γ-W3-T1 (`deck-reflow`), Γ-W3-T2 (`bake-reset-amendment`), and Γ-W3-T3 (`bake-prints-limiter`) — see `docs/COMPLETED.md` for the full narrative of each. **None of the three tracks takes a payload rung.** T1 was layout only; T2 changed a reset list, not a format; T3 changes what the render's audio contains, not what is stored. #### Γ-W3-T1 — `deck-reflow` **Landed** — see `docs/COMPLETED.md` for the full narrative. The knob deck's row law stops being a wrap outcome and becomes a property of the group descriptor: two categorical rows (Sound — PITCH/RATE, FILTER, VELOCITY, VOICE; Contour — PITCH ENV, FILTER ENV, AMP ENVELOPE) plus a double-height, right-anchored MASTER bus deck outside both, carrying the limiter enable toggle, one reserved cell, the output meter column and a passive gain-reduction lamp. `DeckRow`/`deckRowFor` (`ui/deck_groups`) is an exhaustive switch over every `DeckGroupId`, so a group added later without a row assignment is a compile error; the greedy whole-group wrap this replaces is gone entirely. FILTER's `Band|Notch` moves into its caption's previously-unused second toggle slot, taking the group from 524 to 432 px; VOICE deliberately keeps its `Retrig|Legato` row toggle, since moving it to the caption would make VOICE wider, not narrower. **The row block widened 1020 → 1028 px and the editor floor moved 1190 → 1198 px (Daniel's ruling, 2026-08-02).** The originally specified 1020 could not simultaneously deliver the filter tie-line (both rows' FILTER/FILTER ENV right edges at one x) and equal, no-narrower-than-12px gutters on both rows; at 1028 all three hold, with 82 px of headroom left against the 1280 px ceiling. The MASTER meter's per-block state moved from a plain overwriting store to an accumulated one — at 48 kHz/512-frame blocks roughly 47 blocks elapse between two 500 ms UI ticks, and the overwriting store had been displaying one block in ~47 and dropping the rest. The processor now folds a per-channel peak max and a limiter min-gain across the whole interval, drained by `masterBusMeter()`. **The instrument reload was decoupled from VST3 activation as part of this track** — `setActive(false)` now parks the decoded `SampleData` and destroys only the voice state, `setActive(true)` rebuilds the voices around the parked sample — discharging the `docs/TODO.md` follow-up already recorded in full there. The limiter toggle's commit is split so the audible state commits inline on the click and only the host's `restartComponent(kLatencyChanged)` notification is deferred to the editor's existing 500 ms sync tick. **Not verified in a running DAW — CTest-asserted only:** the meter at its 500 ms UI cadence, the GR lamp under real limiter action, the limiter toggle's latency renegotiation, the clip cap's click-to-clear, and the recapture-while-editor-closed path. #### Γ-W3-T2 — `bake-reset-amendment` **Landed** — see `docs/COMPLETED.md` for the full narrative. The correction Phase Γ owed Phase Ξ: Ξ-W2-T1's bake shipped ahead of the sequencing this plan asserted, so its reset list predated rate, pitch offset, the limiter enable, and the loop enable. **`resetAfterBake` needed no code change** — all four already reset by construction, since the function defaults everything and copies back only survivors, and none of the four was ever named a survivor. The track shipped field-by-field assertions over two independently-dialled fixtures, never struct equality, plus a mutation-verified spot-check sweep confirming both fixtures actually moved every asserted field off its default. **One invariant correction, at the time this track landed:** `bake/CLAUDE.md` had claimed the whole signal chain prints, master gain included. It didn't yet — the render's gain multiply was the only master-stage value it printed; the limiter ran in the processor's block, off the bake path entirely, so a capture baked with the limiter engaged came back unlimited. **Γ-W3-T3 (below) has since closed that gap** — the limiter is printed too now, and `bake/CLAUDE.md`'s invariant text is corrected again to match. **Spec:** `docs/product/instrument-control-surface.md` §3.4, and Ξ-W2-T1's own "Reset scope" block, now in `docs/COMPLETED.md` (Phase Ξ has landed and its `PLAN.md` section is retired) — **which is the ratified rule this track applies, not a rule it may reinterpret.** **Neither track has been verified in a running DAW; both are asserted in CTest only.** #### Γ-W3-T3 — `bake-prints-limiter` **Landed** — see `docs/COMPLETED.md` for the full narrative. The bake's master stage now prints the limiter as well as the gain multiply: `renderBake` instantiates its own `Limiter` (the bake-only-engine precedent, never linked into `reaper_reasampler`), the lookahead is compensated inside the render (an extra `flushFrames` of silence past the window, the capture read out at `leadInFrames + flushFrames`), and `bake/CLAUDE.md`'s invariant text is corrected alongside the code. All three of the track's `[propose at review]` open questions are answered in that entry. --- ### Γ-W4 — VST3 parameters **Depended on every earlier wave in this phase** — W1-T1's taper module (the host-facing normalization itself), W1-T2 and W2-T1's control inventory and three-state commit predicate (the exposed set is exactly `Live ∪ NoteOnLatched`), W3-T1's final MASTER inventory, and W3-T2's completed bake reset list — each a hard prerequisite rather than a courtesy. See `docs/COMPLETED.md` for the full narrative of each. **The track has landed** — Γ-W4-T1 (`vst3-parameter-set`) — see `docs/COMPLETED.md` for the full narrative. One track: the storage decision governed every part of the work, so a pure/host-wiring split would have been serial and bought no concurrency. #### Γ-W4-T1 — `vst3-parameter-set` **Landed** — see `docs/COMPLETED.md` for the full narrative. The instrument now reports its automatable parameters to the host under a FOREVER-FROZEN `ParamID` table (blocks of 100 per deck group in signal-flow order, steps of 10 within a block, a curve dial at its outer knob's id + 1) — 44 of 44 issue, derived from `deckParamCommit` / `liveCommitFor` rather than hand-maintained, each with real units, a plain range and display precision at the host boundary. A new pure module, `core/instrument/param`, holds the id table, the plain-value layer, the one formatter per unit category, and the audio thread's block-boundary merge decision; `shell/instrument/instrument_params` adapts it onto `Steinberg::Vst::Parameter` and decides nothing. **Both VST3 delivery channels are serviced** — the controller's `setParamNormalized` and the audio thread's `IParameterChanges` drain, mirroring the SDK's own `SingleComponentEffect` sample rather than the controller-only delivery an earlier pass shipped. **A host automation point's authority is bounded**, not permanent: it outranks the model only until the UI thread folds it in and republishes — at most one UI tick — never a later restore, bake reset or knob move. `shell/instrument/CLAUDE.md`'s "THE AUTHORITY MODEL" section states it, `param_merge` enforces it. **Two rulings, both Daniel, 2026-08-02.** Pitch key-track and Trigger length promote from `Reload` to `NoteOnLatched` — the promotion that takes the count to 44 of 44 and issues ids 1000 and 1450, and which needed `LiveValues`/`foldLive` widened and `Voice::start` handed the two latched values, not just a predicate flip; the new `param::valueHomeFor` guard closes the class of bug the promotion exposed. And the curve-shape dials' ±0.01 snap-to-centre band now applies on the mouse-drag path only, never on a host-facing map. **The bake's reset now notifies the host; the double-processing limitation (a host automation lane outranking the reset) is a named, documented boundary, not discovered later.** `IMidiMapping`, `IParameterFunctionName` and `IAutomationState` are all assessed and NOT implemented. **Not verified in a running DAW — CTest-asserted only.** `docs/TODO.md` carries the residual DAW-verification items. --- ## 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 `_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:135` 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:97` has the `target_sources` line commented out (`:86` for macOS). The registry head stays null, `SWELL_CreateDialog` returns null, `:137` 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 `:96` (`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 `:135–137`, the dialog proc's platform contract, the drop-accept opt-in at `:145–150`), `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–51` (**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 `` 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/-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:91–92`). 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. ### 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) 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 [3 tracks; T1/T2 landed, T3 open] T1 deck-reflow ................ two rows + double-height MASTER, by construction T2 bake-reset-amendment ....... the Xi correction Gamma owns T3 bake-prints-limiter ........ NOT STARTED — bake to print the limiter through the master stage [Daniel, 2026-08-02] W4 VST3 parameters [1 track] T1 vst3-parameter-set ......... 44 derived params, frozen id table [Ruling 1] [rung 3 RESERVED, spent only if verify says so] [OPEN: Gamma-F7, the parameter order — Daniel] 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. On this branch at 2026-08-01, the first is already spent — kParamsPayloadVersion == 15 (Γ-W1-T2, landed, above) — the second is the next free rung, v16 (Γ-W2-T1, stated there), and the third remains RESERVED as v17, spent only if Γ-W4-T1's storage verification forces 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:208). 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 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. ```