77eb7c6680
The per-directory CLAUDE.md count is re-derived at twenty-seven rather than carried from either side. The "Decouple the instrument reload from VST3 activation" TODO entry does not survive: Γ-W3-T1 landed it, and COMPLETED.md carries the discharge.
2021 lines
139 KiB
Markdown
2021 lines
139 KiB
Markdown
# 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<phase>-w<wave>-t<track>-<slug>`. 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 `<module>_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 `<module>_tests` target** that runs without REAPER or a DAW.
|
||
Λ-W2-T4 is the phase's proof that this is not ceremonial: it is a `core/`-only fix, fully
|
||
testable on the current Windows box, and it lands before any Linux session.
|
||
- **The ~600-line ceiling and the structural heuristics bind unchanged.** Λ-W8-T1 is the
|
||
track most likely to strain them; its own criteria name the seam vocabulary to reuse.
|
||
|
||
**Performance posture.** No named hot path is touched by any track in this phase — not the
|
||
`peaks` envelope compute, not audition, not the realtime-capture tick's single-pointer-test
|
||
idle fast path, not the instrument's `process()`. Two consequences are stated as criteria
|
||
rather than left implicit: **nothing is added to `process()`** (no `dlopen`, no probe, no
|
||
platform branch on the per-voice-per-sample path — the SWELL availability probe is computed
|
||
once, lazily, off the audio thread), and **Λ-W2-T4's locale fix stays on the JSON/persist
|
||
path**, which root `CLAUDE.md` already declares off all hot paths. Λ-02 is the phase's one
|
||
genuine performance item and it runs the other way: today a Linux build carries **no `-O`
|
||
flag at all**, on a tree whose `peaks` path is documented as presuming an optimizing build.
|
||
|
||
### The non-REAPER-host safety contract
|
||
|
||
Λ-D3 stated as something a person can check. **This is an observable contract, and it is the
|
||
specification the two instrument waves are graded against** — the full argument is
|
||
`docs/product/linux-readiness.md` §"The non-REAPER-host safety criterion", which a brief for
|
||
Λ-W6-T2, Λ-W7-T1 or Λ-W8-T1 must be written against.
|
||
|
||
**The finding that makes it urgent, and it is a fact rather than a risk.** The vendored
|
||
`vendor/WDL/WDL/swell/swell-modstub-generic.cpp` declares a **file-scope static** at `:125`,
|
||
so its constructor runs when our `.so` is `dlopen`ed — i.e. **during the host's plugin
|
||
scan** — and `:102` calls **`exit(2)`** when `dlopen` of `libSwell.so` fails, `:117`
|
||
**`exit(1)`** on an incomplete API table. Compiling the vendored `SWELL_LOAD_SWELL_DYLIB`
|
||
path unmodified therefore means **a Bitwig or Ardour plugin scan on a machine without
|
||
`libSwell.so` terminates the host process** — a killed DAW mid-scan, with the user's
|
||
session. The mitigating detail that shapes the fix: `doinit` substitutes a zero-returning
|
||
`dummyFunc` for each unresolved name, so a *partial* table degrades rather than crashes. A
|
||
partial load is survivable; `exit()` is not.
|
||
|
||
**In any Linux host, with or without `libSwell.so`:** the module scans and enumerates its one
|
||
class with no crash, hang, process exit or blacklist entry; it instantiates, produces audio,
|
||
plays MIDI, and round-trips component state byte-identically with the Windows build; when no
|
||
usable platform surface exists `isPlatformTypeSupported` returns `kResultFalse` for **every**
|
||
type — including `kPlatformTypeX11EmbedWindowID` — and `createView(kEditor)` returns
|
||
**`nullptr`**, never a view that then fails to attach and never one that draws nothing; the
|
||
host falls back to its generic parameter UI; exactly **one** diagnostic line per process
|
||
names why the editor is unavailable, through the SDK's logging or stderr, never a modal;
|
||
teardown crashes nothing and leaves no partially-initialised SWELL table reachable.
|
||
|
||
**Verified with a harness that is already on disk and that neither audit named**, because
|
||
neither swept the SDK's `samples/` tree:
|
||
`vendor/vst3sdk/public.sdk/samples/vst-hosting/validator/` is a scriptable, headless,
|
||
REAPER-free host, under `public.sdk/` and therefore pulled by the documented narrow submodule
|
||
init. The four checks, all `[verify — Linux]`: `validator` completes with **no `libSwell.so`
|
||
on the filesystem** (the direct negation of the `exit(2)` finding, and the load-bearing one);
|
||
a real Ardour or Bitwig scan reaching browser, instantiation and generic UI; a REAPER-on-Linux
|
||
load where the editor opens; and a **negative control** — rename `libSwell.so` beside a Linux
|
||
REAPER and confirm the generic-UI fallback plus the single diagnostic line.
|
||
|
||
### Cross-phase boundary — Γ-W4-T1, and the frozen parameter table
|
||
|
||
**Λ must never register a VST3 parameter.** Γ-W4-T1's `ParamID` table is FOREVER-FROZEN from
|
||
the moment it ships, on the same footing as the command-id strings and the class UIDs. A
|
||
parameter minted in Λ to make a Linux fallback look better would collide with a table Λ does
|
||
not own. **This is a hard boundary and it is the only Γ↔Λ interaction that could actually go
|
||
wrong.**
|
||
|
||
Neither phase blocks the other. The host's fallback for a plug-in reporting no usable editor
|
||
is its generic parameter UI, and today the plugin registers zero parameters — so "no editor"
|
||
currently degrades to *nothing* rather than to *controls*. **Λ-W6-T2's acceptance criteria are
|
||
written to pass with zero parameters registered**: an empty generic UI is a pass. Once
|
||
Γ-W4-T1 lands its 44 derived parameters the identical Λ code path degrades to a usable
|
||
generic UI instead — a better result, not a different criterion. **Λ assumes no schedule for
|
||
Γ**; a Λ track needing a Γ fact reads `dev` at dispatch time.
|
||
|
||
**Concurrency.** Λ is the widest phase in this plan by file surface, and it is disjoint from
|
||
the others by *kind* rather than by directory: its edits are platform guards, CMake, docs and
|
||
one new `shell/instrument/` TU. Γ owns `core/instrument/` + `shell/instrument/` **sources**
|
||
(Λ-W6…Λ-W8 own that directory's CMake, its `CLAUDE.md` files, and `editor_platform`'s
|
||
non-Windows branch); Ε lands in the new `core/package/` + `shell/package/`; Ρ touches
|
||
`core/capture`, one new `shell/capture` TU, `panel_input.cpp` and `main.cpp`. **The genuine
|
||
adjacency to watch is CMake**: Ε appends two `add_subdirectory` lines to the root
|
||
`CMakeLists.txt` while Λ-W2-T2 owns that file's language and target settings — append-only
|
||
lines against property blocks, textual adjacency rather than semantic contention. Λ's own
|
||
shared files are named in the wave sections below.
|
||
|
||
---
|
||
|
||
### Λ-W1 — The audits *(complete)*
|
||
|
||
Two tracks, both landed: **T1 `build-toolchain-audit`** (Λ-01…Λ-10, V1…V10, D1…D7) and
|
||
**T2 `source-runtime-audit`** (L2-01…L2-12). Recorded so the wave numbering matches the audit
|
||
filenames and branch names. Nothing to re-spec; nothing to dispatch.
|
||
|
||
---
|
||
|
||
### Λ-W2 — Make it buildable, and make it honest
|
||
|
||
**Depends on:** nothing. **Four tracks, disjoint at the file level.** Three are authored
|
||
blind against the audits and verified in Λ-W3; **T4 is the one track fully verifiable on the
|
||
current Windows box.**
|
||
|
||
| Track | Owns |
|
||
|---|---|
|
||
| **T1** `linux-compile-blockers` | two source blockers + `main.cpp`'s API-load failure branch |
|
||
| **T2** `toolchain-floor` | the CMake files' language, property and platform blocks |
|
||
| **T3** `panel-dialog-resource` | `panel_window.cpp`'s dialog creation, `resource.rc` / `resource.h` — **gated on Λ-F2** |
|
||
| **T4** `locale-independent-numerics` | the number codec in four `core/` TUs |
|
||
|
||
**Shared file in the wave, named rather than discovered at merge:**
|
||
`src/app/CMakeLists.txt` — T2 takes the property and platform blocks, T3 takes **one
|
||
`target_sources` line under the resgen route only**. Disjoint regions; whichever lands second
|
||
rebases. The wave's severity question is dissolved rather than adjudicated: T2 grades L2-03 a
|
||
Blocker on failure-mode quality with no direct evidence and L2-04 a Major with a confirmed
|
||
mechanism, and flags its own inconsistency — both land here, in different tracks, so the
|
||
relative grade never has to be settled.
|
||
|
||
#### Λ-W2-T1 — `linux-compile-blockers`
|
||
|
||
**Goal.** The extension compiles and links under GCC/Clang, and when it refuses to load it
|
||
says why instead of vanishing.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T1; findings L2-01, L2-02, L2-03.
|
||
|
||
**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp` (`loadFont`, `:70–77`),
|
||
`src/shell/actions/instrument_drop_win.cpp` (`writeTempPreset`, `:50–61`), `src/app/main.cpp`
|
||
(**the `REAPERAPI_LoadAPI` failure branch only**). **Does not own:** any `CMakeLists.txt`,
|
||
`panel_window.cpp`, or any `core/` file.
|
||
|
||
**Behavior.**
|
||
- **`FF_DONTCARE` (L2-01).** `draw_kit.cpp:73` passes `DEFAULT_PITCH | FF_DONTCARE` to
|
||
`CreateFont`; the symbol has **zero occurrences anywhere in `vendor/WDL/`**, and the file is
|
||
not platform-guarded, only its include is. `draw_kit` links into **both** modules, so
|
||
nothing builds until this is fixed. Drop the term or define it locally in the non-Windows
|
||
include branch. **Do not add `windows.h`** — L2-01's stated direction. The family bits are
|
||
advisory to Windows' font mapper and meaningless to fontconfig.
|
||
- **`GetCurrentProcessId()` (L2-02).** `instrument_drop_win.cpp:59` calls it with no platform
|
||
branch anywhere in the TU; SWELL exports `GetCurrentThreadId` and not this. The PID exists
|
||
only to keep two concurrent REAPER instances from colliding in the shared temp dir, and the
|
||
atomic counter at `:52` already carries the intra-process half. Replace with a
|
||
platform-neutral uniqueness source behind a guard.
|
||
- **The silent load failure (L2-03).** Either switch `main.cpp` to `REAPERAPI_MINIMAL` plus an
|
||
explicit `WANT` list — the pattern `panel_window.cpp` and `panel_audition.cpp` already use,
|
||
and the honest inventory of what this extension actually needs — or keep the full load and
|
||
print the failure count via `rec->GetFunc("ShowConsoleMsg")` before returning 0.
|
||
|
||
**Acceptance criteria.**
|
||
- `cmake -B build -S . -G Ninja && cmake --build build` produces `build/reaper_reasampler.so`
|
||
with no errors. `[verify — Linux]` = **V1**.
|
||
- `ctest --test-dir build --output-on-failure` passes all 91 test targets, **no `-C` flag
|
||
needed** on a single-config generator. `[verify — Linux]` = **V2**.
|
||
- A deliberately misspelled `WANT` entry (or a forced non-zero `failcnt`) produces a visible
|
||
REAPER console line naming the count, not a silent refusal.
|
||
- Windows build and `ctest` unchanged.
|
||
|
||
**Prerequisites.** None. **Discharges:** L2-01, L2-02, L2-03; enables V1, V2, V3.
|
||
|
||
#### Λ-W2-T2 — `toolchain-floor`
|
||
|
||
**Goal.** The Linux build is optimized when asked, links what it uses, hides what it does not
|
||
export, and reports diagnostics no one has seen yet.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T2; findings Λ-02, Λ-03, Λ-04, Λ-05, Λ-07,
|
||
Λ-09, Λ-10.
|
||
|
||
**Surface boundary — owns:** root `CMakeLists.txt`, `src/app/CMakeLists.txt` (**the target
|
||
property and platform blocks; NOT the source list — that is Λ-W4-T3's**),
|
||
`cmake/reasampler_targets.cmake`, `src/shell/instrument/CMakeLists.txt` (**thread linkage
|
||
only; the `WIN32` gate is Λ-W6-T1's**), `README.md`, and root `CLAUDE.md` §"Build and test" /
|
||
§"Install / reload" / §"One-time submodule setup". **Does not own:** any `.cpp` or `.h`.
|
||
|
||
**Behavior.**
|
||
- **Λ-02** — root `CMakeLists.txt:28–30` is the *complete* list of language settings: no
|
||
`CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no IPO/LTO, no `target_compile_options` anywhere
|
||
in the tree. Default `CMAKE_BUILD_TYPE` when neither it nor `CMAKE_CONFIGURATION_TYPES` is
|
||
set, and correct the docs' ship instruction: `--config Release` is *accepted and ignored*
|
||
by Ninja and Make, and the README currently sends a Linux user to `build/Release/`, which
|
||
does not exist there — the module lands at `build/reaper_reasampler.so`.
|
||
- **Λ-03** — `CXX_VISIBILITY_PRESET hidden` + `VISIBILITY_INLINES_HIDDEN` on both module
|
||
targets. SWELL's own build already uses `-fvisibility=hidden`, and the symbols that must
|
||
stay exported carry their own `visibility("default")` attributes
|
||
(`reaper_plugin.h`'s `REAPER_PLUGIN_DLL_EXPORT`, `fplatform.h`'s `SMTG_EXPORT_SYMBOL`,
|
||
`swell-modstub-generic.cpp:135`'s `SWELL_dllMain`).
|
||
- **Λ-04** — `find_package(Threads REQUIRED)` + `Threads::Threads`. Correct on all three
|
||
platforms, costs nothing on Windows.
|
||
- **Λ-09** — `-Wall -Wextra` and `CMAKE_CXX_EXTENSIONS OFF`. **No `-Werror` in this change**
|
||
(the audit is explicit): the diagnostic-set size over this tree is not estimable from
|
||
Windows. Separately `-Wl,--no-undefined` on the module targets, restoring the
|
||
fail-at-link-time behaviour MSVC gives and GNU `ld` does not — **or** a recorded reason why
|
||
`SWELL_PROVIDED_BY_APP`'s function-pointer design makes the gap moot.
|
||
- **Λ-05** — pin the `reaper_plugin.h` → `../WDL/swell/swell.h` include coincidence with a
|
||
comment or an `INTERFACE` target carrying both include dirs as one unit. Invisible on
|
||
Windows, load-bearing off it.
|
||
- **Λ-07** — record the compiler floor (**Λ-F4's input**), or add `-lstdc++fs` and document
|
||
why.
|
||
- **Λ-10** — one sentence in the platform-support docs: on Linux `vendor/vst3sdk` is optional
|
||
until Λ-W6, so `git submodule update --init vendor/reaper-sdk vendor/WDL` is the complete
|
||
extension-only prerequisite.
|
||
|
||
**Acceptance criteria.**
|
||
- `compile_commands.json` or a verbose build log shows an explicit `-O` flag on a bare
|
||
`cmake --build build`. `[verify — Linux]`.
|
||
- `nm -D --defined-only reaper_reasampler.so | grep -E 'ReaperPluginEntry|SWELL_dllMain'`
|
||
finds **both** after the visibility preset. `[verify — Linux]` = **V6**.
|
||
- The extension links with `Threads::Threads` **removed** — proving the include-only pthread
|
||
dependency needs no flag — or the symbol forcing it is named. `[verify — Linux]` = **V5**.
|
||
- `capture_paths_tests` links without an explicit `-lstdc++fs` on the declared floor, or the
|
||
flag is added and the floor documented. `[verify — Linux]` = **V8**.
|
||
- **The warning count from the first `-Wall -Wextra` build is recorded, not fixed**, and
|
||
handed to Λ-W4 as an input.
|
||
- Windows build unchanged; `--config Release` still behaves as documented there.
|
||
|
||
**Prerequisites.** None; concurrent with T1, T3, T4. **Discharges:** Λ-02, Λ-03, Λ-04, Λ-05,
|
||
Λ-07, Λ-09, Λ-10; enables V5, V6, V8.
|
||
|
||
#### Λ-W2-T3 — `panel-dialog-resource` *(GATED on Λ-F2 — do not dispatch until it is ruled)*
|
||
|
||
**Goal.** The docked bank panel opens on Linux, and if it ever fails to, it says so.
|
||
|
||
**Spec:** `docs/product/linux-readiness.md` §Λ-W2-T3 and §Λ-F2; findings Λ-01, L2-06, L2-07.
|
||
|
||
**The defect.** `panel_window.cpp: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 `<windows.h>` with no `SMTG_OS_*` guard; `linuxmain.cpp` exports `ModuleEntry` and
|
||
`ModuleExit`, **both mandatory** — the SDK's own loader refuses the module without either.
|
||
Both files are already vendored; this is a source swap plus a platform `if()`.
|
||
- **Split the `WIN32 AND EXISTS` conjunction** at `src/shell/instrument/CMakeLists.txt:9`.
|
||
Today it is a conjunction, so a Linux configure silently omits `reasampler_vst` **even with
|
||
the submodule slice fully initialised.** The `EXISTS` half stays — a fresh clone with no
|
||
VST3 slice must still configure — and the `WIN32` half becomes a Windows-or-Linux predicate.
|
||
- **B2** — the artifact becomes a **directory bundle**:
|
||
`reasampler_9000.vst3/Contents/<uname -m>-linux/reasampler_9000.so`, per V10's answer and
|
||
Λ-F4's floor. `Contents/Resources/moduleinfo.json` is **optional** — the SDK's
|
||
`getModuleInfoPath` returns empty when absent rather than failing — **so do not author
|
||
one.** Install roots: `$HOME/.vst3/`, `/usr/lib/vst3/`, `/usr/local/lib/vst3/`,
|
||
`$APPFOLDER/vst3/`.
|
||
- **`SWELL_PROVIDED_BY_APP` + the modstub TU** are added to this target in the **default
|
||
(non-`SWELL_LOAD_SWELL_DYLIB`) branch** — the same branch the extension already uses
|
||
(`src/app/CMakeLists.txt: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.
|
||
```
|