diff --git a/docs/product/linux-readiness.md b/docs/product/linux-readiness.md new file mode 100644 index 0000000..c9e983f --- /dev/null +++ b/docs/product/linux-readiness.md @@ -0,0 +1,1303 @@ +# Linux readiness — product notes + +Framing, rationale, and design-direction calls behind **Phase Λ — ReaSampler on Linux: +the `reaper_reasampler` extension and the ReaSampler 9000 VST3 instrument, both +shipped.** The tickable spec belongs in `docs/PLAN.md` (§Phase Λ); the architecture +detail belongs in the affected `src/**/CLAUDE.md` files once tracks land. This doc holds +the *why* — what "Linux-ready" is defined to mean here, what the tree's actual posture +is, the six decisions Daniel settled on 2026-08-02, the sequencing argument, and the four +forks still open. + +**This doc is downstream of two audits and does not repeat them.** +`docs/product/audit-notes/lambda-w1-t1-build-toolchain.md` (findings Λ-01…Λ-10, verify +items V1…V10) owns the build system, toolchain, vendored dependencies, resources, test +harness and packaging. `docs/product/audit-notes/lambda-w1-t2-source-runtime.md` (findings +L2-01…L2-12) owns source portability and runtime behaviour. Both were code-reviewed against +the tree and remediated in place. **Everything below cites them by finding number rather +than re-deriving.** Grep the audits for a cited number; do not read either whole. + +Status: framed by product-designer (2026-08-02) after Daniel's rulings the same day. +**Four forks are open** — Λ-F1 (CI), Λ-F2 (the dialog-resource route), Λ-F3 (the +copy-only drag-out invariant), Λ-F4 (the declared support floor) — each stated with the +evidence for both sides in §"Open forks". Λ-F2 gates one track and nothing else; +the other three gate nothing. Everything not marked as a fork is a product-designer call +with its reasoning stated; contradict it in review with an argument, not a preference. + +**Slug convention.** `Λ → l`, so tracks dispatch into `pl-w-t-` — +Λ-W2-T1 into `pl-w2-t1-linux-compile-blockers`. The two audits already ran under +`pl-w1-t1-build-toolchain-audit` and `pl-w1-t2-source-runtime-audit`. **`docs/PLAN.md:33–36` +lists the transliterations for Θ / Ξ / Γ / Ψ / Ε / Ρ and does not yet carry Λ — adding it +is a plan-doc edit this phase's PLAN.md entry must make.** + +--- + +## Why now, and what "Linux-ready" means here + +The reason to do this now is not demand — it is that **the cost is already sunk and the +window for it to stay sunk is closing.** Two facts from the audits: + +- `docs/product/versioning-and-release.md` already commits, in writing, to + "three platform artifacts per channel per release" (T1 §1e). Linux is not a new promise; + it is an unredeemed one. +- The panel layer — the product's primary surface — was written against SWELL's + vocabulary throughout and is portable **call-for-call**, verified name-by-name against + the vendored headers (T2 §1.5, §"Surfaces checked"). That is an asset the tree acquired + by discipline, not by luck, and it decays every time someone writes a Win32 call without + a `#else`. Phase Ψ, Phase Ε and Phase Ρ are all adding extension-side surface right now. + Compiling on Linux once converts that discipline from a convention nobody can check into + a build that fails when it is broken. + +**What "Linux-ready" is defined to mean in this phase.** A Linux REAPER user installs +`reaper_reasampler.so` and `reasampler_9000.vst3` and gets the same product a Windows user +gets, with four named exceptions: + +1. **Prune deletes permanently.** No Recycle Bin, no trash. The confirmation says so. + (Λ-D5.) +2. **The OS drag-out's copy-only guarantee is conventional, not structural.** 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-10). Λ-F3 decides how the invariant is worded. +3. **Fonts are not Segoe UI and Consolas.** The kit gets an explicit Linux face list + (see §"Calls made here"); the faces are different, the layout is not. +4. **The ReaSampler 9000 editor comes up under REAPER only.** In any other Linux host the + plugin loads, scans, instantiates, and processes audio — and shows the host's generic + parameter UI instead of its own editor. This is a *contract*, not a shortfall; §"The + non-REAPER-host safety criterion" specifies it as an observable. + +**What a Linux user explicitly does not get:** an XDG-trash prune, a macOS build, the +TCP/MCP embed strip, or an editor in Bitwig/Ardour. §"What Phase Λ does not do" states +each with its reason. + +--- + +## The posture as found + +Compressed from the two audits. Each claim below is theirs; the citations are the ones +this doc re-read against the tree while writing. + +**The extension is close — two one-line compile blockers stand between the tree and a +GCC/Clang build.** + +- `src/shell/panel/draw_kit.cpp:73` passes `DEFAULT_PITCH | FF_DONTCARE` to `CreateFont`. + `FF_DONTCARE` has zero occurrences anywhere in `vendor/WDL/` (L2-01); the file is not + platform-guarded, only its include is (`:70–77`). `draw_kit` links into both modules, so + nothing builds. +- `src/shell/actions/instrument_drop_win.cpp:59` calls `GetCurrentProcessId()` inside + `writeTempPreset` with no platform branch anywhere in the TU. SWELL exports + `GetCurrentThreadId` and not this (L2-02). The PID exists only to keep two concurrent + REAPER instances from colliding in the shared temp dir; the atomic counter at `:52` + already carries the intra-process half. + +**`core/` is genuinely pure, and it was verified rather than assumed.** Every `#include` +under `src/core/**` is a `core/` sibling, one of 26 standard headers, or the generated +`version_generated.h` — zero REAPER, SWELL, WDL, LICE, VST3 or `windows.h` (T2 §1.1). The +whole directory contains nine preprocessor conditional lines, exactly one of which is a +platform fork, and that one (`capture_paths.cpp:18–20`, the Windows case-fold) is *correct* +for Linux with both branches already asserted by `tests/test_capture_paths.cpp`. All 91 +test TUs under `tests/` are platform-neutral. + +**The panel layer is already SWELL-portable, call for call.** Dialog lifecycle, docking, +double-buffered LICE `WM_PAINT`, mouse/wheel/capture, `WM_CAPTURECHANGED` rollback, all +seven cursors, menus, the keyboard accelerator path, modifier reads, tooltips, drag-out and +`DragQueryFile`/`DragFinish` were each checked by name against `swell-functions.h` / +`swell-types.h` and are present (T2 §1.5). The Windows-only escapes are three: +`DragAcceptFiles` (`panel_window.cpp:148–150`, `#ifdef _WIN32`), `SHFileOperationW` +(prune), and OLE `DoDragDrop` (drag-out) — each already carrying a non-Windows branch or a +documented reason it does not. **This is the audits' single most load-bearing finding.** + +**The dock panel will not appear until the dialog-resource question is answered.** +`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 (and `:86` for macOS). The registry head stays null, `SWELL_CreateDialog` +returns null, `panel_window.cpp:137` returns, and the toggle action is a silent no-op with +no console line and no Actions-list checkmark (Λ-01, L2-06). Three defects stack in 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 (Λ-01). Λ-F2 decides the route. + +**The instrument's Linux editor is a from-scratch X11 job, and today the target does not +configure at all.** `src/shell/instrument/CMakeLists.txt:9` is +`if(WIN32 AND EXISTS "${VST3_SDK}/…/pluginfactory.cpp")` — a conjunction, so a Linux +configure silently omits `reasampler_vst` even with the submodule slice fully initialised. +Beyond the gate: the wrong module entry point is compiled, the artifact is a file where +Linux wants a directory bundle, nothing hands a VST3 plugin the SWELL function table, and +the view reports `kPlatformTypeHWND` only (B1–B4, L2-08). The paint side is the cheapest +part — every painter already draws into a `LICE_IBitmap` through the shared kit, so the +drawing survives a window-system change intact. It is the window and event plumbing that +is entirely absent. + +**Nothing about the build is optimized, and the documented ship command is a no-op on +Linux.** Root `CMakeLists.txt:28–30` is the complete list of language settings — there is +no `CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no IPO/LTO, and no `target_compile_options` +anywhere in the tree. `--config Release` is accepted and ignored by Ninja and Make, so the +README's ship incantation produces a binary with no `-O` flag at all, on a tree whose +`peaks` hot path is documented as presuming an optimizing build (Λ-02). There is also no +symbol-visibility control (Λ-03), no thread library linked (Λ-04), no warning flags +(Λ-09), no `install()` rule anywhere, and no CI configuration in the repo on any platform +(T1 §1e). + +--- + +## Settled decisions (Daniel, 2026-08-02) + +Six rulings. They are settled; do not re-litigate them. Each is recorded with the reasoning +given, because the reasoning is what a later reader will need. + +### Λ-D1 — The VST3 instrument is in scope. (Audit fork D1 = **Fork B**.) + +Phase Λ ships both artifacts on Linux, not just the extension. + +**Why.** The instrument is not a standalone product on any platform. The capture browser +reads the extension's bank over the bridge, and the resample bake invokes an extension +action synchronously over the VST3 host bridge — root `CLAUDE.md` §"The resample bake" +makes the crossing structural. **A non-REAPER Linux host was never getting the full +instrument regardless**, because the two features that make it ReaSampler rather than a +generic sampler both tie into the extension directly. So the marginal thing Λ-D1 buys is +precisely a REAPER-hosted Linux instrument — which is the only complete one that exists on +any platform. + +**What it costs.** T1 §4 states the asymmetry plainly: Fork A's build work is small and its +unknown is source portability; Fork B's build work is genuinely large *and* carries an +unbounded source tail *and* requires reversing a recorded decision. They are not "the same +phase, one bigger" — they are different-shaped commitments, and this doc's wave structure +keeps them separable (Λ-W6…Λ-W8 can be halted without unshipping Λ-W2…Λ-W5). + +**Consequence — D5 is reversed, in writing.** "No cross-platform… Windows-only, VST3-only, +REAPER-only (D5)" is a recorded invariant in three files: `src/core/instrument/CLAUDE.md` +(twice — Invariants and Non-goals), `src/shell/instrument/CLAUDE.md` (Non-goals), and +`src/shell/panel/CLAUDE.md` ("Windows-only (D5) — font/GDI/HFONT choices assume Windows; no +cross-platform font-fallback concern"). D5's *other* two clauses survive untouched: still +VST3-only, still REAPER-only. **Only the platform clause moves**, and it moves to +"Windows and Linux; the editor is REAPER-hosted." Rewriting those three passages is +audit finding B5 and is a named deliverable of Λ-W6-T1. + +### Λ-D2 — The instrument reaches SWELL by `dlopen`ing REAPER's own `libSwell.so`. (D2 = **route B3a**.) + +`vendor/WDL/WDL/swell/swell-modstub-generic.cpp:69–131` is the vendored implementation: +resolve the host executable's directory from `/proc/self/exe` (falling back to `dladdr`), +`dlopen` `libSwell.so` beside it, `dlsym` `SWELLAPI_GetFunc`, call `SWELL_set_app_main`, +populate the API table. + +**Route B3b is rejected.** Building SWELL into the module means the 14 SWELL TUs plus 8 +LICE TUs from SWELL's own Makefile and a wholly new third-party dependency +surface — GDK/GTK3, FreeType, Fontconfig, OpenGL — that the extension deliberately avoids +via `SWELL_PROVIDED_BY_APP`. That is a scope expansion in a phase whose entire cost +advantage comes from not having one. + +**The consequence Daniel accepted:** B3a makes the instrument's UI load-bearing on being +hosted by REAPER specifically. That is consistent with D5's surviving REAPER-only clause +and with Λ-D1's reasoning above. **It is also why Λ-D3 exists** — the coupling has to be +safe, not merely intended. + +**Important: the vendored stub cannot be used as-is.** See §"The non-REAPER-host safety +criterion" — `swell-modstub-generic.cpp:102` calls `exit(2)` on a failed `dlopen`, from a +static constructor. Route B3a is the ruled route; *how* it is implemented is specified +there and is not a re-litigation of this ruling. + +### Λ-D3 — Non-REAPER hosts must be protected. First-class acceptance criterion. + +Daniel, verbatim: *"REAPER-only is good for now, but we need to protext other hosts."* + +A working editor on Linux is required under REAPER only. But the plugin must **degrade +safely** in Bitwig, Ardour, or any other Linux host: no crash, no host takedown, defined +behaviour when `libSwell.so` is absent or unusable. The audits flagged the absent-`libSwell` +failure mode as unverified; this phase must both determine it and hard-guard it. It is +specified as an observable contract in its own section below, and it is an acceptance +criterion of two tracks, not a footnote on one. + +### Λ-D4 — macOS is out. (D3 = **out**.) + +Daniel, verbatim: *"I don't have a mac to compile on."* + +Λ-01, Λ-03, Λ-04, Λ-07, Λ-09 and L2-09 are all marked *shared macOS/Linux* in the audits, +meaning the same edit covers both. **Those edits still get made in their shared form and +noted as shared** — a `#ifdef _WIN32` / `#else` that is right for both costs nothing extra +and does not require a mac. What is out is: macOS as a phase deliverable, any macOS +verification, the `swell-modstub.mm`-under-a-CXX-only-`project()` question (root +`CMakeLists.txt:26` is `LANGUAGES CXX`), signing and notarization, and the macOS-only +half of L2-11 (`normalizeSlashes` under-folds on case-insensitive APFS — a real pre-existing +defect this phase surfaces and does not own). + +**Do not "fix macOS while you're in there."** An unverifiable edit to the APPLE branch is +worse than no edit, because it looks tested. + +### Λ-D5 — The Linux artifact is shipped, not developer-only. (D6 = **shipped**.) Hard `unlink` is acceptable. (T2 [Daniel] 2 = **yes**.) + +Two rulings that travel together, because both are about what "shipped" obliges. + +**Shipped** means Λ-02 (an actual Release/optimized build) and Λ-08 (a documented install +path and artifact name) are **must-fix, not nice-to-have**, and that a Linux `install()` +rule is worth authoring. A developer-only artifact could have skipped all of it. + +**Hard `unlink` is acceptable**, with a **platform-aware confirmation string**. XDG trash +is not a ship blocker. The user must be told, at the confirm, that the deletion is +permanent on this platform. Note that L2-05 has a **second half the ruling does not +cover**: on Windows a bank file held open by REAPER's audio engine cannot be deleted and is +counted as "skipped"; on Linux `unlink` on an open file succeeds, the audio keeps playing +from the open fd, and the bytes are gone when it closes. That is not a trash question — it +is the disappearance of the recovery floor root `CLAUDE.md` §"The resample bake" relies on +("the superseded file survives on disk until a prune reclaims it"). Λ-W4-T1 owns it. + +### Λ-D6 — Fork B's editor work is real work, and it is sequenced last. + +Not a separate Daniel ruling but the direct consequence of Λ-D1 + Λ-D2 + Λ-D3: the X11 +editor is the phase's only L-band source item (L2-08; T1 B4 costs it "build S, source L") +and it is the last thing sequenced. Everything before it ships something. If Λ-W8 is +halted, Linux users still have a shipped extension (Λ-W5) and a loading, processing, +generic-UI instrument (Λ-W6) — which is a real product, not a stub. + +--- + +## The non-REAPER-host safety criterion + +Λ-D3 stated as something a person can check. This section is the specification the two +instrument waves are graded against. + +### The finding that makes this urgent + +The audits flagged the absent-`libSwell.so` failure mode as unverified. **It is verifiable +from the vendored source, and the answer is bad.** Reading +`vendor/WDL/WDL/swell/swell-modstub-generic.cpp`: + +- `:125` declares `SwellAPPInitializer m_swell_appAPIinit;` — a **file-scope static + object**, so its constructor runs when our `.so` is `dlopen`ed, i.e. **during the host's + plugin scan**, before any VST3 code of ours executes. +- `:98–103` — `dlopen(fn, RTLD_LAZY)`; if it returns null, the stub prints to stdout and + calls **`exit(2)`**. +- `:117` — if `doinit` reports any unresolved SWELL name, **`exit(1)`**. + +So compiling the vendored `SWELL_LOAD_SWELL_DYLIB` path unmodified means: **a Bitwig or +Ardour plugin scan on a machine with no `libSwell.so` beside the host binary terminates the +host process.** Not a failed scan — a killed DAW, mid-scan, with the user's session. That is +precisely the host takedown Λ-D3 forbids, and it is a certainty rather than a risk. + +The mitigating detail, worth knowing because it shapes the fix: `doinit` (`:53–67`) does +**not** leave unresolved entries null. It substitutes `&dummyFunc` (`:63`), and `dummyFunc` +(`:51`) is `static int dummyFunc() { return 0; }`. A *partial* SWELL therefore degrades to +"every missing call returns 0/NULL" rather than jumping through a null pointer. A partial +load is survivable; `exit()` is not. + +### The implementation shape this obliges + +Route B3a (Λ-D2) is the ruled route. It must be reached without the stub's own loader: + +1. **Never define `SWELL_LOAD_SWELL_DYLIB`.** Compile + `swell-modstub-generic.cpp` in its default branch — the same branch the extension already + uses (`src/app/CMakeLists.txt:91–92`) — which exports `SWELL_dllMain(hInst, callMode, + GetFunc)` (`:135`) and calls `doinit` on the pointer it is handed. The whole file is + inside `#ifdef SWELL_PROVIDED_BY_APP` (`:21`), so the VST3 target must define that + symbol too; today it does not. +2. **Own the load.** A new `shell/instrument/swell_bootstrap` TU does the work the stub + would have done — 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. No vendored + file is patched. 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 is sufficient 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 the bootstrap on the host name via + `IHostApplication::getName()` **and** on the `dlopen` succeeding; either failing means no + editor. Cheap, and it makes the REAPER-only coupling explicit rather than emergent. +4. **Probe once, lazily, off the audio thread.** The result is a tri-state — untried / + available / unavailable — computed on first `createView` and never recomputed. No + `dlopen` from `process()`, no per-`createView` retry, no static-constructor work. + +### The observable contract + +In **any** Linux host, with or without `libSwell.so`: + +- **Scan.** The host opens `reasampler_9000.vst3`, finds `ModuleEntry` / `ModuleExit` / + `GetPluginFactory`, and enumerates the one class. No crash, no hang, no process exit, no + blacklist entry. +- **Instantiate and run.** `setActive(true)` and `process()` produce audio (or silence with + nothing loaded). MIDI notes play. The component state round-trips through + `setState`/`getState` byte-identically with the Windows build. +- **Editor request.** When no usable platform surface exists, `isPlatformTypeSupported` + returns `kResultFalse` for **every** type — including `kPlatformTypeX11EmbedWindowID` — + and `createView(kEditor)` returns **`nullptr`**. It never returns a view whose + `attached()` then fails, and never returns a view that draws nothing. The host falls back + to its generic parameter UI. +- **Diagnosis.** Exactly one line, once per process, naming why the editor is unavailable + (host not REAPER / `libSwell.so` not found at `` / SWELL API incomplete). Written + through the SDK's own logging or stderr — never a modal, never a message box, never + repeated per instantiation. +- **Teardown.** Unloading the module does not crash. Nothing calls `exit()`, `abort()`, or + leaves a partially-initialised SWELL table reachable by a later call. + +Under REAPER on Linux the same contract holds with the editor branch taken instead: the +view reports `kPlatformTypeX11EmbedWindowID`, attaches to the host-supplied window id, and +drives its timer through `Linux::IRunLoop`. + +### How it is verified + +**The VST3 SDK ships a non-REAPER host harness and it is already on disk.** +`vendor/vst3sdk/public.sdk/samples/vst-hosting/validator/source/validator.cpp` is present +in the vendored slice (it lives under `public.sdk/`, which the documented narrow submodule +init already pulls). It is a scriptable, headless, REAPER-free host that instantiates a +plugin and exercises the standard suite. **It is the right instrument for this criterion** +and neither audit named it, because neither swept the SDK's `samples/` tree. + +The verification set, all `[verify — Linux]`: + +- `validator reasampler_9000.vst3` completes and reports no failures, on a machine with + **no** `libSwell.so` on the filesystem. This is the load-bearing check: the validator + process surviving is the direct negation of the `exit(2)` finding. +- The same, in **Ardour or Bitwig**, through a real plugin scan: the DAW finishes scanning, + the plugin appears in the browser, an instance instantiates, and the generic parameter UI + opens. A hand check, but the one that answers Daniel's actual sentence. +- The same, under **REAPER on Linux**: the editor opens. +- A **negative-control** run: rename `libSwell.so` beside a Linux REAPER, load the plugin, + and confirm the generic-UI fallback and the single diagnostic line — not a crash, not a + blank window. + +**The acceptance criteria above must pass with zero VST3 parameters registered.** See +§"Cross-phase dependency: Γ-W4-T1" — today the generic UI is empty, and "the host shows an +empty generic UI" is a pass. It is a poor experience, and Γ fixes it; it is not Λ's to fix. + +--- + +## Wave and track structure + +### Why this sequence + +Three constraints shape it, and they conflict. + +**One: the `[verify — Linux]` items are worthless as a late checklist and near-free as an +early wave.** T1's own §6 Stage 0 says it outright — running V1 and V2 on a Linux box is +"the one action that reprices everything", and every effort band in both audits is +provisional until it happens. A plan that puts verification at the end has, in effect, no +estimates at all. + +**Two: nothing can be verified from this Windows box.** Every acceptance criterion for the +first Linux edits is itself a `[verify — Linux]` item. This is not a fixable property of +the plan; it is the phase. The honest structure is to say so: **Λ-W2's edits are authored +blind, from the audits' citations, and Λ-W3 is the session that discharges Λ-W2's +acceptance criteria and answers the sweep in the same sitting.** Λ-W2 is not "done" in the +usual sense until Λ-W3 runs. Pretending otherwise would launder unverified work into +landed work, which is exactly what the audits' `[verify]` discipline exists to prevent. + +**Three: the sweep cannot answer the panel-dependent items until the panel renders.** +T2 §6 step 4 flags this and notes the critical path runs through the other track. The +resolution is to pull the dialog-resource work forward into Λ-W2 — which is why Λ-F2 is +worth ruling early, and why the plan states what happens if it is not (Λ-W2-T3 slips and +Λ-W3 splits). + +Two further sequencing decisions, both deliberate: + +- **The locale codec (L2-04) is pulled forward into Λ-W2**, 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, and there + is no reason to spend a Linux session discovering that. +- **V9 (does REAPER's Linux install ship `libSwell.so` beside its executable?) runs in the + sweep, not in the instrument waves.** It is a one-line `ls`, and the entire Λ-D2 route + rests on it. If it is false, B3a is void and the instrument's editor needs a re-ruling — + a thing to learn in Λ-W3, not four waves later. + +**On the audits' severity disagreement.** 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 the +inconsistency as a scope call for whoever prioritises the two. This plan **dissolves it +rather than resolving it**: both land in Λ-W2, in different tracks, so the relative grade +never has to be adjudicated. + +### Λ-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 here so the wave numbering matches the +audit filenames and branch names; nothing to re-spec. + +--- + +### Λ-W2 — Make it buildable, and make it honest + +**Four tracks, disjoint at the file level.** Three of them are authored blind against the +audits and verified in Λ-W3; T4 is verifiable on the current box. + +#### Λ-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. + +**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.** +- Drop the `| FF_DONTCARE` 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. +- Replace `GetCurrentProcessId()` with a platform-neutral uniqueness source behind a guard; + the atomic counter at `:52` already carries the intra-process half (L2-02). +- On the load-failure branch, either switch `main.cpp` to `REAPERAPI_MINIMAL` plus an + explicit `WANT` list — the pattern `panel_window.cpp` and `panel_audition.cpp` already + use — or keep the full load and print the failure count via + `rec->GetFunc("ShowConsoleMsg")` before returning 0 (L2-03). The minimal list is also the + honest inventory of what this extension actually needs. + +**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 targets, no `-C` flag needed. + `[verify — Linux]` = **V2**. +- A deliberately misspelled entry in the WANT list (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. + +**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** — default `CMAKE_BUILD_TYPE` when neither it nor `CMAKE_CONFIGURATION_TYPES` is + set, and correct the docs' ship instruction to name the single-config-generator path. The + README currently sends a Linux user to `build/Release/`, which on Ninja/Make does not + exist; 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`; the two 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`, + and `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 the tree is not estimable from + Windows. Separately, `-Wl,--no-undefined` on the module targets, to restore 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. It is invisible + on Windows and load-bearing off it. +- **Λ-07** — record the compiler floor, 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. Runs concurrently with T1, T3, T4. +**Discharges:** Λ-02, Λ-03, Λ-04, Λ-05, Λ-07, Λ-09, Λ-10; enables V5, V6, V8. + +#### Λ-W2-T3 — `panel-dialog-resource` *(gated on Λ-F2)* + +**Goal.** The docked bank panel opens on Linux, and if it ever fails to, it says so. + +**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. + +**Behavior.** Whichever route Λ-F2 picks, plus, unconditionally: 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** — land it on both platforms, not behind a guard. 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** — see the fork. +- 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 the process locale, so a +bank index written on a comma-decimal machine is not written unparseable. + +**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. Add pure tests that pin the emitted text for a fractional value, and that a +fractional value survives a write/read round trip. + +Why this 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 (L2-04). 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` in + the test itself. 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; 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 this change 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 + +**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. + +**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 false answer voids Λ-D2's +route and needs a re-ruling before Λ-W6 is dispatched), 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, and any font check that needs a rendered +panel) are deferred 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 + +**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. + +**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), 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` on + an open file succeeds and the branch never fires. Either the branch 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` relies on + ("the superseded file survives on disk until a prune reclaims it") has nothing under it + on Linux otherwise. +- **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, and it is not covered by Λ-D5's ruling. + +**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. + +**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. + +**Surface boundary — owns:** `src/shell/panel/draw_kit.cpp`'s five `loadFont` call sites 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 worth carrying into the work: `draw_kit.cpp:74`'s `if (!hf) return` guard does +**not** catch the failure mode here. SWELL's `CreateFont` always returns a non-null handle +even when the face never resolved — the failure is recorded internally as a null +`typedata`, not as a null return. So 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. + +**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** — 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. **The wording is Λ-F3's + ruling**; the edit is this track's regardless of which way it goes. +- **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 + +#### Λ-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. + +**Surface boundary — owns:** an `install()` rule in `src/app/CMakeLists.txt`, `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 first `install()` rule in the tree, 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.** It is 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. + +**Acceptance criteria.** +- A clean-machine walkthrough: clone, narrow submodule init, configure, build, install, + restart REAPER, actions present, panel docks. Following only the README. + `[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 — the Λ-02 criterion, + re-checked on the artifact that actually ships. +- `versioning-and-release.md` no longer promises an artifact this phase does not produce. + +**Prerequisites.** Λ-W3-T1 (V4), Λ-W4 (all three tracks — shipping means the correctness +fixes are in). **Discharges:** Λ-08, and Λ-02's shipped half. + +--- + +### Λ-W6 — The instrument module, and the host-safety contract + +**Two tracks, disjoint: one is build + docs, the other is source.** At the end of this wave +the Linux instrument loads in any host, processes audio, and has **no editor at all** — +which is 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. + +**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: platform clause + becomes "Windows and Linux; the editor is REAPER-hosted", VST3-only and REAPER-only + survive verbatim. Nothing else in this wave may land before this does — the invariant + files are what a future implementer reads. +- **B1** — swap `public.sdk/source/main/dllmain.cpp` for `linuxmain.cpp` on Linux. `dllmain` + includes `` with no `SMTG_OS_*` guard; `linuxmain.cpp` exports `ModuleEntry` + and `ModuleExit`, **both mandatory** — the SDK's own loader refuses the module without + either. Both files are already vendored; this is a source swap plus a platform `if()`. +- **Split the `WIN32 AND EXISTS` conjunction** at `src/shell/instrument/CMakeLists.txt:9`. + The `EXISTS` half stays (a fresh clone with no VST3 slice must still configure); the + `WIN32` half becomes a Windows-or-Linux predicate. +- **B2** — the artifact becomes a directory: + `reasampler_9000.vst3/Contents/-linux/reasampler_9000.so`, per V10's answer. + `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 `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; 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. + +**Prerequisites.** Λ-W2-T2 (thread linkage, visibility, warnings), Λ-W3-T1 (V10, and 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. + +**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), 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 until someone decides otherwise; see §"What Phase Λ does + not do". +- **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 in §"The non-REAPER-host safety +criterion", 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. 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** (see the Γ dependency below). + +**Prerequisites.** Λ-W6-T1 (there must be a module to load). Concurrent with nothing else in +this wave. **Discharges:** Λ-D3's Λ-W6 half; L2-08's fallback half. + +--- + +### Λ-W7 — The SWELL bootstrap + +#### Λ-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. + +**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 implementation shape in §"The non-REAPER-host safety +criterion": never define `SWELL_LOAD_SWELL_DYLIB`; own the load through exported surface +only (`SWELLAPI_GetFunc`, `SWELL_set_app_main`, our own `SWELL_dllMain`); gate on the host +name **and** the `dlopen`; probe once, lazily, off the audio thread. + +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 one is not fatal** +— `doinit` substitutes a zero-returning `dummyFunc` for each miss, so a partial table +degrades rather than crashes; whether to accept a partial table or refuse is a judgment for +implementation 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, and **V9 answered affirmatively**. If V9 is negative, +this track does not exist and Λ-D2 needs a re-ruling. +**Discharges:** B3 (route B3a); Λ-D3's runtime half. + +--- + +### Λ-W8 — The X11 editor + +#### Λ-W8-T1 — `x11-embed-view` + +**Goal.** The ReaSampler 9000 editor opens, draws and responds under REAPER on Linux. + +**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 cost this at **L**, and call it a new competence rather than a port +(L2-08; T1 B4 = "build S, source L"). 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` / their unregisters +against `Linux::IRunLoop`). 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. It sits +under `public.sdk/`, so the documented narrow submodule init already pulls it. **This does +not shrink the L band** — it is a reading input, not a library — but 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, 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 (the phase-wide + structural heuristic in root `CLAUDE.md`). +- 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." + +**Prerequisites.** Λ-W7-T1. **Discharges:** B4, L2-08. + +--- + +### Concurrency, and the shared files + +**Wave order is serial; tracks within a wave are concurrent.** Two deliberate exceptions to +the strict reading: + +- **Λ-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. +- **Λ-W6-T2 is prerequisite-gated on Λ-W6-T1** (there must be a module before it can refuse + to show a view). They are file-disjoint but not order-free — the same shape PLAN.md + already records for Γ-W3 ("T1 before T2 — serial"). + +**Shared files, named** — all textual adjacency, not semantic contention, unless marked: + +| 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 resource-id-0 route they are 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. | + +--- + +## Cross-phase dependency: Γ-W4-T1 + +**Verified against `docs/PLAN.md` while writing this: `Γ-W4-T1 vst3-parameter-set` is +listed as pending** (PLAN.md §Γ-W4, and in the plan's summary map). Its own spec states the +current position plainly: *"Today the plugin has zero parameters"* — +`ReaSamplerProcessor::initialize` never populates `SingleComponentEffect::parameters`, so +`getParameterCount()` returns the SDK default 0. The T2 audit confirms the same by grep. + +**Why it matters to Λ.** The host's fallback when a plug-in reports no usable editor is its +**generic parameter UI**. With zero parameters registered, that fallback is empty — so +today, "no editor" degrades to *nothing*, not to *controls*. Once Γ-W4-T1 lands its 44 +derived parameters, the identical Λ code path degrades to a usable generic UI instead. + +**The dependency and its direction:** + +- **Λ does not block Γ**, and **Γ does not block Λ.** Λ-W6-T2's acceptance criteria are + written to pass with zero parameters — "the host shows its generic UI, which may be + empty" is a pass. Nothing in Λ is scheduled against Γ. +- **Γ landing strictly improves Λ's user-visible outcome**, at no cost to Λ. If Γ-W4-T1 is + already on `dev` when Λ-W6-T2 is verified, the tester will see a populated generic UI; + that is a better result, not a different criterion. +- **Λ must not register any VST3 parameter itself.** Γ-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.** +- **Another team is running Γ concurrently. Λ assumes no schedule for it** — not "Γ will + land first", not "Γ will land later". If a Λ track's brief needs a Γ fact, it reads + `dev` at dispatch time. + +--- + +## Calls made here (product-designer calls, not forks) + +Two questions the audits raised as `[Daniel]` that this doc answers rather than escalates. +Contradict either in review with an argument. + +**Silent font substitution is not acceptable; the kit gets an explicit Linux face list** +(T2 [Daniel] 3, landed as Λ-W4-T2). The reasoning is `docs/product/visual-design-language.md`: +the kit's type is part of a deliberate visual identity, and the palette-role discipline +exists precisely so a visual direction is a single-file change rather than an emergent +property of the host. Letting fontconfig pick is the opposite of that. The cost is five +string literals. The one thing that would change the call is if the sweep shows the stock +substitution is *already* the intended face on every target distro — unlikely, and it would +make the face list a no-op rather than wrong. + +**The `[verify — Linux]` items are not forks and do not wait for Daniel.** T2's [Daniel] +list included items that a Linux box settles (which SWELL build REAPER ships, whether +fontconfig lands Consolas on a monospace face, whether drag-out is accepted as a copy). +Those are Λ-W3 sweep items. A question is only a fork here if reading code and running a +Linux box both leave it open. + +--- + +## What Phase Λ does not do + +- **macOS.** Λ-D4. Shared-form edits are made and noted as shared; no macOS build, no macOS + verification, no signing or notarization, no `swell-modstub.mm`-under-CXX-only + investigation, no fix for L2-11's case-insensitive-filesystem defect. +- **XDG trash.** Λ-D5. Hard `unlink` with a platform-aware confirmation. The XDG spec + (`~/.local/share/Trash` plus a `.trashinfo` sidecar) is a self-contained move-plus-metadata + write and remains a clean follow-up if a Linux user ever asks for it — file it to + `docs/TODO.md`, do not smuggle it into Λ-W4-T1. +- **Route B3b's dependency surface.** Λ-D2. No vendored SWELL build, no GDK/GTK3, no + FreeType, no Fontconfig, no OpenGL, no `pkg-config` in this tree. If a track finds itself + reaching for one of those, the design has drifted and the track stops. +- **An editor in a non-REAPER host.** Λ-D3 requires safe degradation, not capability. Even + with `libSwell.so` reachable, a non-REAPER host has no SWELL message loop, so the + host-name gate refuses on purpose. +- **The TCP/MCP embed strip on Linux.** `reasampler_embed`'s + `REAPER_FXEMBED_WM_IS_SUPPORTED` keeps returning 0 off Windows. It is a second 0→1 on a + read-only secondary surface, its paint body is LICE and would port, and it becomes + genuinely cheap once Λ-W8 lands. Deliberately out, and worth revisiting after. +- **VST2 / AU / CLAP / standalone.** D5's format and host clauses survive Λ-D1 untouched. +- **CI.** Λ-F1 is open; until it is ruled, Λ ships nothing that presumes a runner. +- **Any parameter registration.** Γ-W4-T1's, exclusively. See above. +- **Windows behaviour changes.** Every track carries a "Windows unchanged" criterion. This + is a port, and a port that improves Windows by accident has also changed Windows by + accident. + +--- + +## Open forks — Daniel's + +### Λ-F1 — Does CI get built in this phase, and on what runner? *(audit D4, unruled)* + +**No recommendation.** This one is genuinely a resourcing call, and both sides are +defensible enough that a product-designer preference would be noise. + +**For building it now:** `versioning-and-release.md` already commits to multiple platform +artifacts per channel per release and hands "the pipeline" to dev-ops — and there is no CI +configuration anywhere in the repo, on any platform (`.github` and `.gitlab-ci.yml` are both +absent). The moment Linux ships, the tree has two toolchains and one of them is not on the +developer's machine. Every Windows-only commit after that is a Linux regression waiting to +be discovered by a user. A GitHub-Actions job running `cmake` + `ctest` on Ubuntu would +catch L2-01-class breakage — an undefined Win32 symbol — at push time, which is exactly the +class of defect this whole phase exists to clear. + +**For deferring it:** CI is infrastructure, not product, and this phase is already the +largest thing in the plan. A runner also forces Λ-F4 (the support floor) to be answered +immediately, because the runner image *is* the declared floor. And CI's value is highest +*after* the first Linux build works, not before — a red pipeline on day one teaches nothing +the Λ-W3 sweep is not already teaching. + +**What closing it unblocks:** whether Λ-W5-T1 writes a pipeline paragraph or a +"deferred to dev-ops" paragraph, and whether Λ-F4 must be closed in this phase or can be +carried. Nothing is gated on it before Λ-W5. + +### Λ-F2 — The dialog-resource route: resource-id-0, or resgen? *(audit D7, unruled)* + +**This is the only fork that gates a track.** Both audits now carry the evidence. + +**Route A — the resource-id-0 escape hatch.** SWELL documents it in +`swell-functions.h:606–608`: a `CreateDialog` creating a child window may pass resource id +0, which creates an opaque child window, provided a `WNDPROC` returning `LRESULT` (cast to +`DLGPROC`) is passed instead of a real `DLGPROC`. The implementation confirms both halves — +`swell-dlg-generic.cpp` skips the resource lookup entirely when `resid` is 0. And +`IDD_BANK_PANEL` is precisely the case it was written for: `src/resource.rc:18–22` is a +`WS_CHILD` dialog with an empty `BEGIN`/`END` body and zero controls, whose own header +comment says "the bank_panel shell owns every pixel and draws the sample grid with LICE in +`WM_PAINT`". **Taking this route deletes the entire resgen pipeline from the non-Windows +builds** — the PHP dependency, the generated-file-in-the-source-tree problem, the +per-platform regeneration story, and the three stacked defects in the current commented-out +instructions. Cost: a source change in `panel_window.cpp` and the DLGPROC→WNDPROC return +contract. + +**Route B — wire up resgen properly.** `find_program(PHP …)`, an `add_custom_command` +generating into `${CMAKE_CURRENT_BINARY_DIR}` (never the source tree — `.gitignore` has no +pattern for the generated name), a one-line shim TU that includes `swell-dlggen.h` then the +generated file, and that shim added to `target_sources`. This is the M end of Λ-01's band. + +**The asymmetry neither audit stated, and it is the reason this is worth ruling rather than +delegating.** L2-07 establishes that SWELL's only opt-in surface for `WS_EX_ACCEPTFILES` is +the **dialog resource** — `swell_resgen` translates a dialog's `WS_EX_ACCEPTFILES` style +into `SWELL_DLG_WS_DROPTARGET`, and `swell-dlg-generic.cpp` sets the ex-style from that flag +at creation time. **Under Route A there is no resource, so that bit can never be set from +one.** L2-07's own analysis says the bit only *suppresses* the unhandled-drop +forward-to-parent and that the drop reaches `dlgProc` anyway via the GDK backend's +`ChildWindowFromPoint` descent — and notes, correctly, that with zero child controls the +descent has nowhere to land but the panel HWND itself. That reasoning is good but it is +**reasoning, not a run**. So: + +- Route A is cheaper and structurally simpler, and it stakes the panel's file-drop ingest — + one of three ingest surfaces — entirely on that unverified descent. +- Route B is more machinery and one more build-time dependency, and it can set the bit + explicitly if the descent turns out not to be enough. + +**What closing it unblocks:** Λ-W2-T3's dispatch, and therefore whether Λ-W3 is one Linux +session or two. **Leaving it open has a real cost and it is the only fork here that does.** + +**A third option, if Daniel wants the cheap route without the exposure:** take Route A and +treat the file-drop check as a hard gate in Λ-W3's sweep — if the drop does not arrive, +Route B becomes a follow-up track rather than a rewrite, because the panel is already +rendering by then and the resgen work is additive. That is the option I would take, and it +is stated as an option rather than a recommendation because it spends a second Linux session +in the bad case. + +### Λ-F3 — Does "copy-only is structural" survive as a shipped invariant? *(T2 [Daniel] 4, unruled)* + +**Recommendation: keep the invariant, scope the *enforcement* claim to Windows.** The +product promise ("dragging a bank file out of ReaSampler copies it; the bank is never +raided by a drop target") stays; the *mechanism* sentence in `drag_out_win.h` becomes +"structurally enforced on Windows via `DoDragDrop`'s `DROPEFFECT_COPY`-only mask; +advertised, not enforced, on SWELL, which takes no effect mask." + +**The counter-argument is real.** An invariant that one platform cannot enforce is arguably +not an invariant, and the tree's house style treats invariants as load-bearing rather than +aspirational. Weakening the *global* wording to "copy-only, best effort" would be the honest +alternative — and it would also correctly warn a Windows reader not to rely on the guarantee +in shared code. What that loses is a genuinely stronger Windows property, stated as weaker +than it is. + +**Non-gating.** Λ-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, not the decision)* + +The audits surface three separate manifestations of one unmade decision: Λ-07 (no stated +minimum compiler; GCC 8 needs an explicit `-lstdc++fs` for `std::filesystem`, GCC ≥ 9 does +not), V8 (the same, as a check), and V10 (which `uname -m` values the VST3 bundle must +carry — `module_linux.cpp` keys the bundle directory off exactly that string, and which +architectures REAPER itself ships for Linux is an external fact with no source in this tree). + +**Recommendation: declare a narrow floor — current-stable-distro glibc, GCC ≥ 9, +`x86_64-linux` only — and widen it on request.** ReaSampler is a per-project capture tool +for people running a commercial DAW; the audience running a five-year-old toolchain on +armv7 is small, and every architecture in the bundle is a build target someone has to +maintain and verify. + +**The counter:** REAPER's own Linux reputation is partly built on running well on modest and +non-x86 hardware, and `aarch64` in particular is no longer exotic. Adding it later means a +second bundle directory and a second verification pass on every future release, which is +cheaper to establish once than to retrofit. + +**What closing it unblocks:** Λ-W2-T2's compiler-floor line, Λ-W5-T1's install and README +text, Λ-W6-T1's bundle directory set, and Λ-F1's runner image. **Non-gating for Λ-W2** (a +recorded floor can be widened), **gating for Λ-W5** — a shipped artifact has to say what it +runs on. + +--- + +## Where the audits disagreed, or left a hole + +Recorded so a reader knows which parts of this doc are synthesis rather than citation. + +1. **The `WS_EX_ACCEPTFILES` / resource-id-0 interaction is a genuine gap between the two + audits.** T1's Λ-01 recommends the id-0 route and says it makes resgen disappear; T2's + L2-07 independently establishes that the drop-accept ex-style bit can only come from the + dialog resource. Neither doc composed the two. §Λ-F2 does, and it is the substantive + reason that fork is worth ruling rather than delegating to review. + +2. **T2 flags its own severity inconsistency and hands it on.** L2-03 is graded Blocker on + failure-mode quality with no direct evidence; L2-04 is graded Major with a confirmed + mechanism and an equally severe outcome. T2 says explicitly that resolving the axis is a + scope call for whoever prioritises the two. This doc **dissolves rather than resolves** + it — both land in Λ-W2, in different tracks — so the grade never has to be adjudicated. + +3. **The absent-`libSwell.so` failure mode was flagged unverified and is, in fact, + readable.** Both audits treat it as a `[verify — Linux]` question. It is not: the + vendored `swell-modstub-generic.cpp` calls `exit(2)` on a failed `dlopen` from a static + constructor, and `exit(1)` on an incomplete API table. That is a fact about a file in + this tree, and it converts Daniel's Λ-D3 from a defensive precaution into a specific + design constraint on the ruled route. Filled here, in §"The non-REAPER-host safety + criterion". + +4. **Neither audit swept the VST3 SDK's `samples/` tree.** Both enumerate the vendored + *slice* the build compiles, which is correct for their questions, but + `public.sdk/samples/vst-hosting/` is on disk under the documented narrow submodule init + and contains two directly relevant things: `validator/` (a headless non-REAPER host + harness — the right instrument for Λ-D3's acceptance criteria) and + `editorhost/source/platform/linux/` (a worked `Linux::IRunLoop` implementation and an + X11-embed window). Named in Λ-W6-T2, Λ-W7-T1 and Λ-W8-T1. **This does not move the L + band on L2-08** — the audits' estimate stands; it removes a "we are guessing at the + contract" risk from it. + +5. **The beta channel on Linux is uncosted in both audits.** T1 confirms the channel fork is + platform-independent by construction and stops there; neither audit builds, installs, or + coexists two channels on Linux, and neither considers the VST3 bundle's per-channel name + and UID on that platform. Almost certainly free; assigned to Λ-W5-T1 and Λ-W6-T1 as an + explicit criterion rather than an assumption. + +6. **Two loose ends left as-is, deliberately.** T1's `lice_colorspace.cpp` question (SWELL's + own Makefile lists a fifth LICE TU our four-TU slice omits; the audit calls it + "unreconciled but harmless") and the `-Wl,--no-undefined` question are both answered by a + single successful Linux link. Both are Λ-W3 sweep items, not findings. + +--- + +## Wave map + +``` +Phase Λ — ReaSampler on Linux (extension + ReaSampler 9000; macOS out) + W1 The audits [COMPLETE] + T1 build-toolchain-audit ....... Λ-01..Λ-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 ............. Λ-02..Λ-05, Λ-07, Λ-09, Λ-10 -> V5, V6, V8 + T3 panel-dialog-resource ....... Λ-01, L2-06, L2-07 -> V7 [GATED: Λ-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 §5's seven; 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 . Λ-06, L2-10, L2-11 (Linux half) + + W5 Ship the extension [1 track] + T1 linux-packaging-and-install . Λ-08 + Λ-02's shipped half + both channels + + W6 The instrument module, and 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 Λ-D2 re-rules] + + W8 The X11 editor [1 track] + T1 x11-embed-view .............. B4, L2-08. The phase's one L-band source item. + + W6 may run concurrently with W4 and W5 — file-disjoint; the only reason to serialise + is attention. W2's edits are authored BLIND and their acceptance criteria are + discharged in W3; W2 is not "done" until W3 runs, and the plan says so rather than + pretending otherwise. + Open: Λ-F1 (CI), Λ-F2 (resource route — the only one that gates a track), + Λ-F3 (copy-only invariant wording), Λ-F4 (declared support floor — gates W5). +```