# Λ-W1 Track 1 — Linux build/toolchain audit (build-system lens)

Static analysis of the build surface only — the 23 `CMakeLists.txt` files, `cmake/`,
`src/resource.rc`/`src/resource.h` + the SWELL resgen pipeline, the three vendored submodules
as *build inputs*, the `ctest` harness, toolchain-level flags, and the packaging story in
`docs/product/versioning-and-release.md`. 2026-08-02, branch `pl-w1-t1-build-toolchain-audit`
(cut from `dev` at 6e937b9).

**Source portability is explicitly NOT audited here** — no Win32/GDI/LICE API usage, path
handling, threading, drag/drop, dialog code, fonts, or instrument runtime code. A parallel
track (T2) owns that; what I noticed in passing is listed under "Referred to T2" at the end
and nowhere else.

**Verification posture.** I am on Windows and cannot build or run anything on Linux. Every
claim below is grounded in a file I opened in this worktree — the tree's own CMake, or a
vendored header/source under `vendor/` — not asserted from memory of similar SDKs. Where a
claim depends on runtime behaviour I cannot observe (does REAPER's Linux build call
`SWELL_dllMain`? does GCC accept this TU?), it is marked `[verify — Linux]` with the exact
check, and it is **not** stated as fact. A subsequent citation pass caught several
line-number and attribution errors in this draft (corrected in place, disclosed at the point
each occurs) — grounded-in-a-file is a floor, not a guarantee against a mis-copied line range.
To get the vendored citations I ran `git submodule update --init` for all three submodules
inside this worktree; nothing in the tracked tree was modified.

Severity vocabulary, per the dispatch: **Blocker** = the Linux build cannot produce a loadable
artifact; **Major** = it builds but is degraded or unshippable; **Minor** = ergonomics,
warnings, CI polish. Effort bands are S (< half a day), M (1–3 days), L (a week+), and each
names the reason for the band rather than asserting a number.

---

## 1. Baseline — what the build does today, per platform

**Two loadable modules, 84 pure libraries declared under `src/core/`, 91 CTest targets,
three vendored submodules.** (84 = the `reasampler_pure_library` + bare `add_library` count,
grep-verified library-by-library across the 18 `src/core/**` CMake files that declare any —
`src/core/CMakeLists.txt` and `src/core/instrument/CMakeLists.txt` declare none, only
`add_subdirectory` calls (plus one test in the latter); there are 20 CMake files under
`src/core/**` total, matching the 19 figure in §1d below, which counts *test* declarations
across the whole tree, not `src/core/**` alone. 46 of the 84 are on the extension's link
line.)

- Root `CMakeLists.txt` is 95 lines and declares **no targets at all**: it owns the version
  string (`:24`), the channel fork (`:40–54`), the `configure_file` for
  `version_generated.h` (`:58–61`), the four vendor path variables (`:65–68`), the
  four-TU `LICE_SRC` list (`:78–83`), `enable_testing()` (`:90`), and three
  `add_subdirectory` calls (`:92–94`).
- Language/toolchain settings are exactly three lines: `CMAKE_CXX_STANDARD 17`,
  `CMAKE_CXX_STANDARD_REQUIRED ON`, `CMAKE_POSITION_INDEPENDENT_CODE ON`
  (`CMakeLists.txt:28–30`). There is **no** `CMAKE_BUILD_TYPE`, no `CMAKE_CXX_FLAGS`, no
  `CMAKE_CXX_EXTENSIONS`, no IPO/LTO setting, and no `target_compile_options` anywhere in
  the tree — I grepped all 23 CMake files for `compile_options`, `find_package`,
  `pkg_check`, `link_directories`, `target_link_options`, and `install(` and got zero hits
  outside the two files named below.
- **Platform conditionals exist in exactly two files.** Grepping all 23 `CMakeLists.txt`
  for `WIN32|APPLE|UNIX|LINUX|MSVC|GNU|Clang|CMAKE_SYSTEM` returns hits only in
  `src/app/CMakeLists.txt` (`:71` `if(WIN32)`, `:76` `elseif(APPLE)`, `:88` `else()`) and
  `src/shell/instrument/CMakeLists.txt` (`:9` `if(WIN32 AND EXISTS ...)`). Every other
  CMake file is platform-neutral by construction: they call the two helpers in
  `cmake/reasampler_targets.cmake` (`reasampler_pure_library` at `:8–15`,
  `reasampler_test` at `:19–24`) and nothing else.

### 1a. The REAPER extension (`reaper_reasampler`)

Declared as a `MODULE` library over 39 listed TUs (38 shell TUs + `app/main.cpp` itself)
plus `${LICE_SRC}` (`src/app/CMakeLists.txt:8–51`), linking 46 pure static libraries (`:52`) with
`${SDK_INC} ${WDL_INC}` on the include path (`:56`). Output naming is
`PREFIX ""` + channel-derived `OUTPUT_NAME`, pinned to the top of the build tree by
`LIBRARY_OUTPUT_DIRECTORY`/`ARCHIVE_OUTPUT_DIRECTORY` (`:65–69`).

The three-way platform block (`:71–98`) is:

| Branch | State | What it does |
|---|---|---|
| `if(WIN32)` `:71–74` | **Live and shipped** | adds `src/resource.rc` for the RC compiler |
| `elseif(APPLE)` `:76–86` | **Partly aspirational** | adds `swell-modstub.mm`, defines `SWELL_PROVIDED_BY_APP`, links `-framework AppKit`, forces `.dylib`. The resgen `target_sources` line at `:86` is **commented out**. |
| `else()` (Linux) `:88–97` | **Partly aspirational** | adds `swell-modstub-generic.cpp`, defines `SWELL_PROVIDED_BY_APP`, forces `.so`. The resgen `target_sources` line at `:97` is **commented out**. |

So on Linux the build system today produces `reaper_reasampler.so` with the SWELL modstub
compiled in and **no dialog resource of any kind**. `README.md:67–68` states this is
"expected, not a bug" — the tree is honest about it.

**Open question, macOS-only, unresolved by this doc.** The APPLE branch adds
`${SWELL}/swell-modstub.mm` (`:79`), an Objective-C++ TU, but `CMakeLists.txt:26` is
`project(reaper_reasampler VERSION 1.4.0 LANGUAGES CXX)` — OBJCXX is never enabled anywhere
in the tree. Whether CMake resolves a `.mm` source against a plain CXX-only project (some
Apple-toolchain configurations infer Objective-C++ from the extension regardless; others do
not) is unverified from this tree and untestable without a macOS box. `[verify — macOS]`.

Three build-input facts I verified in `vendor/` and that hold in the extension's favour:

- `vendor/WDL/WDL/swell/swell-modstub-generic.cpp` is entirely inside
  `#ifdef SWELL_PROVIDED_BY_APP` (`:21`, closed at `:150`). Since
  `SWELL_LOAD_SWELL_DYLIB` is not defined anywhere in this build, the `#else` branch at
  `:133` applies: the file's whole contribution is the three
  `SWELL_curmodule_*resource_head` globals (`:31–33`), the API function-pointer table
  built from `swell.h` (`:37–49`), and the `SWELL_dllMain` export (`:135`) that resolves
  every SWELL symbol from a host-supplied `GetFunc`. That is the correct stub for a
  REAPER extension, and the comment at `src/app/CMakeLists.txt:90` ("With
  SWELL_PROVIDED_BY_APP you can drop pkg-config / -lX11 entirely") is **correct**: no SWELL
  implementation TU, no X11, no GDK is compiled or linked into the extension.
- The four-TU LICE slice is self-contained on Linux. `lice.cpp` reaches SWELL only through
  `SWELL_DeleteGfxContext` (`:153`, `:218`), `SWELL_GetCtxFrameBuffer` (`:182`, `:226`) and
  `SWELL_CreateMemContext` (`:224`); `lice_textnew.cpp` reaches it through
  `SWELL_PushClipRegion`/`SWELL_SetClipRegion` (`:1027–1028`) and `SWELL_PopClipRegion`
  (`:1048`). All six are entries in SWELL's API table
  (`vendor/WDL/WDL/swell/swell-functions.h:857`, `:863`, `:877`, `:885–887`), so the
  modstub resolves them from the host. No fifth LICE TU is implied: the `_LICE_RGB2HSV_invtab`
  table lives inside `lice.cpp` itself (`:3040`), and the one non-Win32-only helper
  `utf8makechar` is a file-static defined at `lice_textnew.cpp:26` and used at `:241–242`.
  This is a link-success inference, not a from-first-principles proof — the actual proof is
  V1, a successful build. One loose end: SWELL's own `Makefile:106–107` lists
  `lice_colorspace.o` among the LICE objects *its* build needs — a fifth TU we don't compile.
  It stays unreconciled but harmless: `lice_colorspace.cpp` defines the real `LICE_RGB2HSV`
  function (declared `lice.h:577`, distinct from the `_invtab` table above), and grepping our
  four LICE_SRC TUs and all of `src/` for a call to it returns nothing — SWELL needs the TU
  for its own generic-GDK backend, which our four-TU slice never invokes into.
- `CMAKE_POSITION_INDEPENDENT_CODE ON` (`CMakeLists.txt:30`) is already set. This is the
  single most commonly missed setting for a `MODULE` target fed by `STATIC` libraries on
  Linux, and it is correct today.

### 1b. The VST3 instrument (`reasampler_vst`)

Hard-gated to Windows: `if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")`
(`src/shell/instrument/CMakeLists.txt:9`). The gate is a conjunction — even with the
`vendor/vst3sdk` slice fully initialised, a Linux configure silently omits the target.
`README.md:33–35` states this explicitly. Two module-level `CLAUDE.md` files record the
underlying product decision (D5) as *settled*, with "a mac/Linux build" named as a
non-goal: `src/core/instrument/CLAUDE.md` (Non-goals/guardrails) and
`src/shell/instrument/CLAUDE.md` (same). `src/shell/panel/CLAUDE.md` carries a third
instance ("Windows-only (D5) — font/GDI/HFONT choices assume Windows").

Inside the gate: a 20-source `vst3_sdk` static library (`:16–38`, grep-counted; lines 28/29/37
are comments) pinned to SDK tag
`v3.7.9_build_61` (confirmed by `git describe --tags` in `vendor/vst3sdk`), and the
`reasampler_vst` MODULE (`:44–81`) which additionally compiles `dllmain.cpp` and
`moduleinit.cpp` directly (`:78–79`, with the linker-stripping rationale at `:75–77`) plus
`${LICE_SRC}` (`:80`). Output is a single **file** named `<name>.vst3` (`:106–111`).

### 1c. Resources

`src/resource.rc` declares exactly one dialog — `IDD_BANK_PANEL`, a bare `WS_CHILD`
`DIALOG` with an empty `BEGIN`/`END` body and no controls (`:18–22`). `src/resource.h`
defines the single id `IDD_BANK_PANEL 1000` (`:8`). It is consumed by
`CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL), …)` at
`src/shell/panel/panel_window.cpp:135`.

### 1d. Test harness

91 test declarations across 19 `CMakeLists.txt` files (grep count), matching 91 files in
`tests/`. 90 go through `reasampler_test` (`cmake/reasampler_targets.cmake:19–24`, which is
`add_executable` + `target_link_libraries` + `add_test`); one is longhand —
`app_version_padding_tests` (`src/core/version/CMakeLists.txt:37–42`), which recompiles
`app_version.cpp` against a second `configure_file` output so the include-dir substitution
works. Neither shape carries anything platform-specific.

Only two test files mention a platform at all: `tests/test_capture_paths.cpp` (five
`#ifdef _WIN32` blocks at `:19`, `:41`, `:87`, `:152`, `:205`, each with a POSIX `#else`
branch already written — e.g. `:158–168` asserts `resolveBankFile("/home/d/proj", …)`), and
`tests/test_pitch_shift.cpp:3`, where the string is inside a comment explaining why the
target links nothing that would drag `<windows.h>` in. No test includes `reaper_plugin.h`,
`windows.h`, or any SWELL header. **The test harness is the healthiest part of this
surface**; see §3 for the one generator-shape caveat.

### 1e. Packaging / release, as already committed

`docs/product/versioning-and-release.md` already commits to Linux as a first-class target:

- `:361–363` — "**Artifact per platform.** The binary is `reaper_*.dll|.dylib|.so`; the
  macOS/Linux builds need the SWELL resgen step (CLAUDE.md §SWELL dialog resources) baked
  into the pipeline. Three platform artifacts per channel per release."
- `:307–310` — two named artifacts (stable + beta) per platform, "three platform artifacts
  each", explicitly handed off to dev-ops.
- `:364–366` — "Install is copy-in + restart… there is no server-side rollout."

What that document does **not** contain: any mention of code signing, macOS notarization, a
CI runner, or a Linux install path. I grepped it for `sign|notariz|CI|platform|linux` — one
hit is not cited above (`:393`, "artifacts (stable + beta) per platform", the same dev-ops
handoff as `:307–310`); the rest are the lines above. The substantive conclusion — no
signing/notarization/CI/Linux-install-path mention anywhere — holds regardless. There is no
CI configuration anywhere in the repo (`ls -a` on
the root shows `.git`, `.gitignore`, `.gitmodules`, `CLAUDE.md`, `CMakeLists.txt`, `LICENSE`,
`README.md`, `cmake`, `docs`, `src`, `tests`, `vendor` — no `.github`, no `.gitlab-ci.yml`),
and no `install()` rule in any of the 23 CMake files. So "the pipeline" is entirely notional
today, on every platform.

---

## 2. Findings

### Λ-01 — the Linux/macOS dialog-resource path is broken in three independent ways, and the fix may not need resgen at all
**Severity: Major. Effort: S (mechanical) to M (if the resgen route is taken and automated).**

**Citations.** `src/app/CMakeLists.txt:94–97` (the Linux comment block) and `:83–86` (the
identical macOS one); `vendor/WDL/WDL/swell/swell_resgen.php:313–314`;
`vendor/WDL/WDL/swell/swell-dlggen.h:22`; `vendor/WDL/WDL/swell/sample_project/myapp.cpp:192–193`;
`vendor/WDL/WDL/swell/swell-dlggen.h:214–218`; `vendor/WDL/WDL/swell/swell-modstub-generic.cpp:31–33`;
`vendor/WDL/WDL/swell/swell-functions.h:612–619`; `src/resource.rc:18–22`;
`src/shell/panel/panel_window.cpp:135`.

**What breaks.** Three defects stack, and each alone would sink the step:

1. **The named script does not exist.** Both comment blocks say
   `php ${WDL_INC}/swell/mac_resgen.php src/resource.rc` (`:85` and `:96`). Listing
   `vendor/WDL/WDL/swell/` shows `swell_resgen.php`, `swell_resgen.pl`, and
   `swell_resgen.sh` — there is no `mac_resgen.php`. `CLAUDE.md` and `README.md:120` both
   name the correct script; only the CMake comments are wrong, which is exactly the file an
   implementer will be reading when they do the work.
2. **The named output file is not what the generator writes.** Both commented
   `target_sources` lines (`:86`, `:97`) reference
   `${REASAMPLER_SRC_DIR}/resource.rc_mac_dlg.h`. `swell_resgen.php:313–314` composes its
   output names as `$srcfn . "_mac_menu"` and `$srcfn . "_mac_dlg"` — so the actual outputs
   are `src/resource.rc_mac_dlg` and `src/resource.rc_mac_menu`, with **no** `.h` suffix.
3. **Even the correct filename cannot be a `target_sources` entry.** `swell-dlggen.h:22`
   states the contract outright: "Use swell_resgen.pl or swell_resgen.php on the .rc file,
   which will produce .rc_mac_dlg, **include that**." The generated body is a run of
   `SWELL_DEFINE_DIALOG_RESOURCE_BEGIN(...)` macro invocations
   (`swell_resgen.php:117–129`) that only expand after `swell-dlggen.h` has been included —
   `swell-dlggen.h:214–218` shows the macro emitting a static `SWELL_DialogRegHelper` that
   registers into `SWELL_curmodule_dialogresource_head`. The canonical consumption pattern
   is the SDK's own sample: `sample_project/myapp.cpp:192–193` does
   `#include "../WDL/swell/swell-dlggen.h"` then `#include "res.rc_mac_dlg"`. A CMake
   `target_sources` entry for an extensionless file would not even get a language assigned.

**The runtime consequence, and why this is Major rather than Blocker.** The `.so` still
builds and still loads: the extension's whole action family, capture pipeline, and persist
layer are untouched. What fails is the dock panel. `swell-functions.h:615` defines
`CreateDialog(hinst,resid,par,dlgproc)` as
`SWELL_CreateDialog(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,0)`.
`swell-modstub-generic.cpp:32` *defines* that head but nothing ever populates it, because
the resgen'd registration TU does not exist — so it stays null and
`panel_window.cpp:135`'s `CreateDialogParam` gets a null resource list. The panel is the
product's primary surface, so a Linux build without it is not shippable; it is not a
Blocker only because the binary loads and the actions work.

**The route worth considering first.** `swell-functions.h:606–608` documents an escape
hatch: *"If you are using CreateDialog() and creating a child window, you can use a resource
ID of 0, which creates an opaque child window. Instead of passing a DLGPROC, you should pass
a (WNDPROC) routine that returns LRESULT (and cast it to DLGPROC)."* The header comment is
not just documentation — the implementation confirms both halves of it:
`swell-dlg-generic.cpp:291–292` is `SWELL_DialogResourceIndex *p=resById(reshead,resid); if
(!p&&resid) return 0;`, and `resById` (`:34–43`) walks from a (here, null) `reshead` and
returns 0 on a miss. That confirms the graceful-failure claim behind "Major rather than
Blocker" above (a null `reshead` plus a non-zero `resid` returns 0, not a crash — matching
`panel_window.cpp:137`'s `if (!g_panel.hwnd) return;`), **and** that `resid == 0` skips the
lookup entirely (`&&resid` short-circuits), which is the escape hatch this paragraph is about.
`src/resource.rc:18–22`
shows `IDD_BANK_PANEL` is precisely that: a `WS_CHILD` dialog with an empty body and zero
controls, whose comment at `:8–10` says "the bank_panel shell owns every pixel and draws the
sample grid with LICE in `WM_PAINT`". If the panel is created with resource id 0 on
non-Windows, **the entire resgen pipeline — the PHP dependency (`README.md:54`), the
generated-file-in-source-tree problem, and the per-platform regeneration story — disappears
from the Linux and macOS builds.** The cost is a source change in `panel_window.cpp` and the
DLGPROC→WNDPROC return-type contract; that half is T2's call, not mine, and I am naming the
option rather than choosing it. This is a shared macOS/Linux fix either way.

**If the resgen route is taken instead**, the build work is: a `find_program(PHP …)`, an
`add_custom_command` generating into `${CMAKE_CURRENT_BINARY_DIR}` (not the source tree —
`.gitignore` has no pattern matching `resource.rc_mac_dlg`, so today's manual step would
leave an untracked generated file sitting in `src/`), a new one-line shim TU that includes
`swell-dlggen.h` then the generated file, and that shim added to `target_sources`. That is
the M-effort half of the band.

---

### Λ-02 — `--config Release` is a silent no-op on Linux, so the documented ship command produces an unoptimized binary
**Severity: Major. Effort: S — one `if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)` block plus a doc correction.**

**Citations.** `CMakeLists.txt:28–30` (the complete list of language settings — I read the
whole 95-line file and there is no `CMAKE_BUILD_TYPE` anywhere); `README.md:106–109`;
`CLAUDE.md` §"Build and test" and §"Install / reload".

**What breaks.** Both docs prescribe `cmake --build build --config Release` as the
optimize-and-ship incantation, and `README.md:130–131` tells the user to copy from
`build/Release/`. `--config` is a multi-config-generator flag; on Ninja or Unix Makefiles it
is accepted and ignored. Because no `CMAKE_BUILD_TYPE` default is set, the resulting build
passes **no `-O` flag at all** to GCC/Clang — not `-O0` explicitly, just the compiler's
unoptimized default. The failure is silent in both directions: the command succeeds, and the
artifact lands at `build/reaper_reasampler.so` — a path the docs never state for a
single-config generator (a gap, not an error; see Λ-08).

This matters more here than the usual "forgot to set the build type" — but only for `peaks`.
`CLAUDE.md`'s §"Build and test" (not §"Performance guardrails") states, at `CLAUDE.md:54`,
that the header-inline hot paths "presume an **optimizing** build", with no LTO configured to
recover it if they don't. `peaks`' `computeEnvelope`/`lastFrameAboveThreshold` is one of
those paths and IS linked into the extension (`src/app/CMakeLists.txt:52`), so the finding
holds for Fork A as stated. It does **not** extend to `Voice::advanceFrame` or the three
`envelopes.h` evaluators (named at `src/core/instrument/CLAUDE.md:342`, not in root
`CLAUDE.md`) — those belong to `sampler_core`, which `src/app/CMakeLists.txt:53–55` states is
deliberately NOT linked into the extension and which only enters a build at all inside the
Windows-gated `reasampler_vst` target (§1b). On Fork A that code never compiles into
`reaper_reasampler` in the first place; the unoptimized-build risk to it is a Fork B concern,
not this extension-only one. A Linux user following the README's `--config Release` would
ship an extension binary whose `peaks` hot path did not inline.

**Done when:** `cmake --build build` on Linux (Ninja/Make, no `--config`) produces a build
whose compile commands (`compile_commands.json` or verbose build log) show an explicit `-O`
flag, and the README/CLAUDE.md ship instruction names the single-config-generator path.

**Sequencing note.** This is not strictly Linux-specific — Ninja on Windows has the same
shape — but it becomes load-bearing the moment Linux is a shipped platform, because Makefiles
and Ninja are the default there.

---

### Λ-03 — no symbol-visibility control, and two modules in one process both export a full copy of LICE
**Severity: Major. Effort: S — one `CXX_VISIBILITY_PRESET hidden` + `VISIBILITY_INLINES_HIDDEN` pair, plus a `[verify — Linux]` load test.**

**Citations.** `CMakeLists.txt:78–83` (`LICE_SRC`); `src/app/CMakeLists.txt:35` and
`src/shell/instrument/CMakeLists.txt:80` (the same four LICE TUs compiled into *both*
modules — the rationale for the duplication is at `CMakeLists.txt:75–77`);
`vendor/reaper-sdk/sdk/reaper_plugin.h:52`; `src/app/main.cpp:251`;
`vendor/vst3sdk/pluginterfaces/base/fplatform.h:164`;
`vendor/WDL/WDL/swell/Makefile:20`.

**What breaks.** Nothing in the build sets a visibility preset, so GCC/Clang default to
`-fvisibility=default` and every non-static symbol in both modules — all of LICE, all the
WDL helpers, every pure library's exported functions, and the whole C++ standard-library
instantiation set — lands in the module's dynamic symbol table. On Windows this is a
non-issue: each DLL has its own symbol table and per-module resolution. `[verify — Linux]`
on ELF, symbols from `dlopen`'d objects CAN participate in global interposition — but whether
they actually do here depends on REAPER's own `dlopen` flags (`RTLD_GLOBAL` vs `RTLD_LOCAL`)
and whether REAPER's executable is linked `--export-dynamic`, neither of which this tree can
answer. The scenario this doc originally named — a hypothetical `reasampler_9000.so` — does
not exist on Fork A: per Λ-10 and `src/shell/instrument/CMakeLists.txt:9`, the VST3 target is
gated `WIN32 AND EXISTS ...` and is never built on Linux unless D1 selects Fork B. The
Fork-A-real risk is different: any **second REAPER extension** loaded in the same REAPER
process (a third-party `.so` in `UserPlugins/`) that links its own copy of LICE/WDL and also
defaults to `-fvisibility=default` is a candidate to interpose with `reaper_reasampler.so`'s
`LICE_FillRect` and the rest of the exported LICE/WDL symbol set. That is a class of bug that
presents as "one extension's LICE draws with another extension's LICE build" and is invisible
on Windows, where each DLL keeps its own symbol table.

**Why hiding is safe here.** The two symbols that genuinely must be exported already say so
explicitly: `reaper_plugin.h:52` defines
`REAPER_PLUGIN_DLL_EXPORT __attribute__((visibility("default")))` on non-Windows, and
`main.cpp:251` uses it (`extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(...)`).
On the VST3 side, `fplatform.h:164` defines `SMTG_EXPORT_SYMBOL` the same way. SWELL's own
build already uses `-fvisibility=hidden` (`Makefile:20`), which is the precedent. The one
thing to confirm on a Linux box is that `swell-modstub-generic.cpp:135`'s `SWELL_dllMain`
survives — it carries its own `__attribute__ ((visibility ("default")))` at that line, so it
should, but the modstub is compiled by *our* build, not SWELL's.

*Shared macOS/Linux — the visibility default and the LICE-TU duplication (`CMakeLists.txt:75–77`)
apply the same way under the APPLE branch's Mach-O two-level namespace, though the concrete
Fork-A-real interposition mechanism above is ELF-specific; macOS's own dynamic-linker symbol
resolution is a separate `[verify]` this doc does not attempt.*

---

### Λ-04 — nothing links a thread library, and `<pthread.h>` is in the extension's include graph
**Severity: Major (VST3 target) / Minor (extension). Effort: S — `find_package(Threads)` + `Threads::Threads`.**

**Citations.** `src/shell/panel/panel_state.h:20–24`;
`vendor/vst3sdk/base/thread/source/flock.cpp:71–133`;
`src/shell/instrument/CMakeLists.txt:15–39`; `vendor/WDL/WDL/swell/Makefile:90`.

**What breaks.** No CMake file in the tree calls `find_package(Threads)` or links
`Threads::Threads`/`-lpthread`/`-ldl` (grep-verified across all 23). Two separate exposures:

- **Extension.** `panel_state.h:23` includes `<pthread.h>` on non-Win32 — and it is not the
  only entry point: `vendor/reaper-sdk/sdk/reaper_plugin.h:50` includes `<pthread.h>`
  unconditionally in its non-Windows branch, so it is in **every** shell TU's include graph
  (every `.cpp` that includes `reaper_plugin.h`/`reaper_plugin_functions.h`), not just the
  one panel file. This strengthens rather than changes the finding: I still did not find a
  pthread *call* in `src/shell/`'s own code, so this may be an include-only dependency that
  needs no link flag on a modern glibc (where the pthread symbols are in libc proper). Stays
  Minor, and `[verify — Linux]`.
- **VST3 target.** `flock.cpp` is unambiguous: on non-Windows it calls
  `pthread_mutexattr_init`/`_settype`/`_destroy` (`:71–76`), `pthread_mutex_init` (`:74`),
  `pthread_mutex_destroy` (`:89`), `_lock` (`:105`), `_unlock` (`:121`) and `_trylock`
  (`:133`). It is unconditionally in the `vst3_sdk` source list
  (`src/shell/instrument/CMakeLists.txt:27`). Any Linux VST3 target must link a thread
  library. SWELL's own Makefile uses `-lpthread -ldl` (`:90`) for the same reason.

Adding `find_package(Threads REQUIRED)` + `target_link_libraries(... Threads::Threads)` is
correct on all three platforms and costs nothing on Windows.

**Done when:** the extension links with `Threads::Threads` removed (proving the include-only
half needs no flag, or naming the symbol that forces it — see V5), and, if Fork B is taken,
`reasampler_vst` links clean with `Threads::Threads` present.

*Shared macOS/Linux — `reaper_plugin.h:50`'s non-Windows `<pthread.h>` include and
`flock.cpp`'s non-Windows pthread calls apply identically to the APPLE branch.*

---

### Λ-05 — `reaper_plugin.h`'s SWELL include resolves only by coincidence of the `-I` line
**Severity: Minor. Effort: S — a comment, or an `INTERFACE` target that carries both include dirs as one unit.**

**Citations.** `vendor/reaper-sdk/sdk/reaper_plugin.h:42–54`; `CMakeLists.txt:65–66`;
`src/app/CMakeLists.txt:56`; `src/shell/instrument/CMakeLists.txt:97`.

**What breaks — and, today, does not.** On non-Windows, `reaper_plugin.h:49` does
`#include "../WDL/swell/swell.h"`. Relative to the including file's own directory
(`vendor/reaper-sdk/sdk/`) that resolves to `vendor/reaper-sdk/WDL/swell/swell.h`, which does
not exist — listing `vendor/reaper-sdk/` shows only `README`, `reaper-plugins`, and `sdk`.
The include is nevertheless satisfiable, because the quoted-include fallback searches the
`-I` path, and `WDL_INC` is `${CMAKE_CURRENT_SOURCE_DIR}/vendor/WDL/WDL` (`CMakeLists.txt:66`) —
so `WDL_INC` + `../WDL/swell/swell.h` normalizes to `vendor/WDL/WDL/swell/swell.h`, which
exists. Both module targets pass `${WDL_INC}` (`src/app:56`, `src/shell/instrument:97`), so it
holds today.

The hazard is that this is a load-bearing coincidence with no comment anywhere, and it is
**invisible on Windows** — `reaper_plugin.h:43` takes `<windows.h>` instead, so dropping
`${WDL_INC}` from any target that includes `reaper_plugin.h` would pass CI on Windows and
fail only on Linux/macOS. Worth pinning before more targets are added.

---

### Λ-06 — the build has no platform partition of its source list, so every T2 finding lands here as a CMake change
**Severity: Minor (structural). Effort: S per TU once T2 says which ones need it.**

**Citations.** `src/app/CMakeLists.txt:8–51` and `:71–98`;
`src/shell/actions/drag_out_win.cpp:8`, `:249`, `:282`.

**Mechanism.** All 38 shell TUs (plus `app/main.cpp`, 39 listed TUs total) are listed unconditionally (`:8–51`); the platform block at
`:71–98` only *adds* sources, never removes or substitutes any. Every TU therefore enters a
Linux compile, and the only thing standing between the build and a compile error is each
file's own `#ifdef` discipline. Today that discipline is uneven in a way worth recording:

- `drag_out_win.cpp` is the model — `#ifdef _WIN32` at `:8` with a real
  `// ---- macOS / Linux (SWELL)` branch at `:249` and `#endif` at `:282`.
- `arrange_drop_win.cpp` and `instrument_drop_win.cpp` contain **zero** occurrences of
  `_WIN32` (grep count 0 each) and include no `<windows.h>` — they are `_win`-suffixed for
  the surface they serve, not for a platform dependency, and appear portable by inspection.

This is not itself a defect; it is the reason the ordering sketch in §5 puts "read T2's
output, then partition the source list" as a distinct step rather than folding it into the
resgen work.

---

### Λ-07 — `std::filesystem` in a pure library, with no stated minimum compiler
**Severity: Minor. Effort: S — a `cmake_minimum_required`-adjacent compiler check or a documented floor.**

**Citations.** `src/core/capture/capture_paths.cpp:5` (`#include <filesystem>`) and `:95`
(`std::filesystem::path(rppPath).parent_path().string()`); `README.md:50–54`.

**What breaks.** `capture_paths` is a *pure* library with its own CTest target
(`src/core/capture/CMakeLists.txt:1–2`), so this is on the test-harness path as well as the
extension's. GCC 8 requires an explicit `-lstdc++fs` for `std::filesystem`; GCC ≥ 9 and
libstdc++ ≥ 9 do not. `README.md:51` states only "A C++17 compiler" as the prerequisite, and
`cmake_minimum_required(VERSION 3.19)` (`CMakeLists.txt:1`) says nothing about the compiler.
On any current distro toolchain this is a non-issue; it becomes one on an older CI image.
Twelve other files in `src/shell/` also use `<filesystem>` (grep-verified), but one —
`src/shell/instrument/instrument_bake.cpp` — is in the `reasampler_vst` target, not the
extension: eleven in `reaper_reasampler`, sharing the fix below; the twelfth shares it only
if/when Fork B builds that target on Linux.

**Done when:** `capture_paths_tests` links on the oldest toolchain the phase targets without
`-lstdc++fs` added explicitly, or that flag is added and documented as the floor.

*Shared macOS/Linux — `capture_paths` and the eleven extension-side `<filesystem>` users
compile identically under the APPLE branch; whether Apple's libc++ needs the same kind of
minimum-toolchain floor as libstdc++ does is a separate `[verify]` this doc has not checked,
but the coverage-hole shape (undocumented compiler floor) is identical either way.*

---

### Λ-08 — the install instruction is silent about single-config generators, and no path is documented for Linux at all
**Severity: Minor. Effort: S — a doc fix, or an `install()` rule if packaging is ever automated.**

**Citations.** `src/app/CMakeLists.txt:68` (`LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}"`);
`README.md:130–131`; `CLAUDE.md` §"Install / reload";
`docs/product/versioning-and-release.md:364–366`.

**What breaks — a gap, not an error.** `README.md:130–131` and `CLAUDE.md` §"Install /
reload" both already carry the multi-config qualifier verbatim ("`build/Release/` on a
multi-config generator — not the default `Debug/` output") — so the instruction is not
wrong, it is simply silent about the other case. On a multi-config generator CMake appends
the config directory, giving the documented `build/Release/`. On Ninja/Make the module lands
directly at `build/reaper_reasampler.so`, and neither doc names that path. Compounding it,
there is no `install()` rule anywhere in the 23 CMake files, and no target directory is
documented for Linux at all — `CLAUDE.md` and `README.md:132` say "REAPER's `UserPlugins/`
folder (Options → Show REAPER resource path)", which is a correct user-facing instruction but
not a path a pipeline can use. I could not confirm REAPER's Linux resource-path root from the
SDK: grepping `vendor/reaper-sdk/sdk/` for `UserPlugins` and `reaper_*.so` returns nothing —
the `reaper_*.dll|dylib|so` filename convention stated in `CLAUDE.md` is not documented in the
vendored headers. `[verify — Linux]`.

---

### Λ-09 — no warning flags anywhere, so the first GCC/Clang build surfaces a diagnostic set nobody has ever seen
**Severity: Minor. Effort: S to add flags; **unknowable** to fix what they reveal — that is the honest band.**

**Citations.** grep for `target_compile_options` across all 23 CMake files: zero hits (the
only `compile_definitions` hits are `SWELL_PROVIDED_BY_APP` at `src/app/CMakeLists.txt:80`,
`:92` and the SDK's `DEVELOPMENT`/`RELEASE` selector at
`src/shell/instrument/CMakeLists.txt:42`). `CMakeLists.txt:28–30` for the absence of
`CMAKE_CXX_EXTENSIONS`.

**Mechanism.** The codebase has been compiled by exactly one compiler family. MSVC's default
is `/W1`; GCC/Clang's default is essentially silent. Adding `-Wall -Wextra` on the first
Linux build is the right move, but the size of the resulting diagnostic set over ~39k LOC is
not estimable from Windows, which is why the effort band is split. Do **not** add `-Werror`
in the same change.

Secondary, in the same family: `CMAKE_CXX_EXTENSIONS` is unset, so it defaults to `ON` and
GCC/Clang compile as `-std=gnu++17` rather than `-std=c++17`. Not a bug; a hygiene knob
worth flipping while the flags are being set, since it is the difference between "portable
C++17" and "whatever GNU extensions happened to be used".

**Coverage hole: the link-succeeds-but-load-fails asymmetry is never stated.** GNU `ld` does
not error on undefined symbols in a shared object by default — MSVC's linker does. For a
`MODULE` REAPER `dlopen()`s, that means a Linux build can link `reaper_reasampler.so` clean
and only discover a missing symbol when REAPER tries to load it (`dlopen` failing, or a
partially-resolved symbol table if a satisfying-by-coincidence symbol exists elsewhere in the
process — a real risk given Λ-03's global-interposition finding above). Neither `Threads`
(Λ-04) nor visibility (Λ-03) named this asymmetry, and it undercuts §5's framing of V1
(`cmake --build build`) as "the single highest-information action available" — a clean build
on Linux does not carry the same guarantee a clean Windows build does. `-Wl,--no-undefined`
(GNU ld) would restore the MSVC-equivalent fail-at-link-time behavior; whether it is needed
here specifically depends on whether `SWELL_PROVIDED_BY_APP`'s function-pointer design (§1a)
already accounts for every symbol the modstub doesn't itself define — that is `[verify —
Linux]`, since it requires an actual GCC/Clang link to observe.

**Done when:** the Linux build either adds `-Wl,--no-undefined` (or the equivalent) so an
undefined-symbol build failure surfaces at link time, or a stated reason is recorded for why
`SWELL_PROVIDED_BY_APP` makes the gap moot.

*Shared macOS/Linux — `-Wall -Wextra`, `CMAKE_CXX_EXTENSIONS OFF`, and the GNU-ld-vs-MSVC-ld
asymmetry (macOS's linker also does not default to erroring on undefined symbols) apply
identically to the APPLE branch.*

---

### Λ-10 — the submodule story costs a Linux developer a large download for a target that is gated off
**Severity: Minor. Effort: S — a doc paragraph.**

**Citations.** `.gitmodules` (three entries, no `shallow`, no `branch`);
`README.md:70–86`; `src/shell/instrument/CMakeLists.txt:9`; `CLAUDE.md` §"One-time submodule setup".

**Mechanism.** `README.md:72–75` already warns that `git clone --recursive` pulls all of
`vendor/vst3sdk`'s nested submodules (`vstgui4`, `tutorials`, `doc`, `cmake`) that this
project never links, and prescribes the narrow init instead. What neither the README nor
`CLAUDE.md` says is that on Linux the `vendor/vst3sdk` submodule is **pure cost**: the
`WIN32 AND EXISTS` gate at `src/shell/instrument/CMakeLists.txt:9` omits the target
regardless, so `git submodule update --init vendor/reaper-sdk vendor/WDL` is the complete
Linux prerequisite. One sentence in the platform-support table would save every Linux
contributor the download.

---

## 3. Surfaces checked and found clean

Recorded so the plan author does not re-spend effort here. Each was read or grepped in this
pass.

- **All 23 `CMakeLists.txt` files were read in full**, not sampled: root; `src/app`;
  `src/core`; `src/core/{audio,capture,instrument,json,model,reclaim,tracking,ui,util,version,view,wire}`;
  `src/core/instrument/{bake,engine,map,note,ui}`; `src/core/instrument/engine/{filter,loop}`;
  `src/shell/instrument`. Plus `cmake/reasampler_targets.cmake`. Of these, 21 are entirely
  platform-neutral — they declare libraries and tests through the two helpers and nothing
  else.
- **`CMAKE_POSITION_INDEPENDENT_CODE ON`** (`CMakeLists.txt:30`) — the usual Linux
  `MODULE`-fed-by-`STATIC` trap, already handled.
- **`.so` naming.** `PREFIX ""` (`src/app/CMakeLists.txt:66`) + `SUFFIX ".so"` (`:93`) +
  channel-derived `OUTPUT_NAME` (`:67`) yields `reaper_reasampler.so` /
  `reaper_reasampler_beta.so`, matching the `reaper_*` convention. `ARCHIVE_OUTPUT_DIRECTORY`
  (`:69`) is a Windows import-lib concern and harmlessly inert on Linux.
- **The channel fork is platform-independent.** `REASAMPLER_CHANNEL` (`CMakeLists.txt:40–54`)
  threads through `configure_file` (`:58–61`) into names only; nothing in it is
  Windows-specific, so beta builds fork identically on Linux.
- **The version padding canary** (`src/core/version/CMakeLists.txt:21–42`) uses a
  function-scoped `set()` + second `configure_file` + longhand `add_executable`. Portable;
  no platform assumption.
- **The whole test corpus is platform-neutral.** 91 files in `tests/`, 91 declarations in
  CMake. Only `test_capture_paths.cpp` branches on platform and it already has POSIX
  expectations written; only `test_pitch_shift.cpp` mentions `windows.h`, in a comment.
- **Generator-shape handling in the docs is already correct.** `README.md:93–98` explains
  that `-C Debug` is required on multi-config and unnecessary on Ninja/Make. `ctest --test-dir
  build` works bare on Linux; the `-C Debug` in the README's example is harmless there.
- **SWELL API coverage for the LICE slice** — all six SWELL GDI entry points LICE needs are
  in the modstub-resolved table (`swell-functions.h:857`, `:863`, `:877`, `:885–887`).
- **The LICE slice needs no fifth TU on Linux** (`lice.cpp:3040` `_LICE_RGB2HSV_invtab`
  table, `lice_textnew.cpp:26` file-static `utf8makechar`) — an inference from V1's link
  success plus these two spot checks, not exhaustive; see §1a's fuller caveat, including the
  unreconciled `lice_colorspace.o` in SWELL's own `Makefile:106–107`.
- **`swell-modstub-generic.cpp` compiles against its own includes** — it uses `printf`
  (`:61`), which arrives via `swell.h` → `swell-types.h:29` (`#include <stdio.h>`). The
  `dlopen`/`readlink` block (`:69–131`) is behind `SWELL_LOAD_SWELL_DYLIB`, which this build
  never defines.
- **The Linux run-loop IIDs are already in the vendored VST3 slice.**
  `public.sdk/source/common/commoniids.cpp:50–54` defines `Linux::IEventHandler`,
  `Linux::ITimerHandler` and `Linux::IRunLoop` under `#if SMTG_OS_LINUX`, and that file is
  already in the `vst3_sdk` source list (`src/shell/instrument/CMakeLists.txt:36`). A Linux
  VST3 port needs no additional SDK translation unit for the run loop.
- **`SMTG_OS_LINUX` / `SMTG_EXPORT_SYMBOL` are correct in the pinned SDK**
  (`pluginterfaces/base/fplatform.h:132`, `:164`).
- **The MSVC-ABI caveat is Windows-scoped.** `reaper_plugin.h:22` warns that the C++
  interfaces "require MSVC on win32, or at least the MSVC-compatible C++ ABI" — that is a
  constraint on Windows toolchains (mingw), not on Linux, where REAPER and the extension both
  use the Itanium ABI.

---

## 4. The VST3-on-Linux question, stated as a fork

Daniel rules on this. Below is the cost of each side with the evidence, and nothing else.

### Fork A — extension only on Linux

Scope: everything in §2 (Λ-01 through Λ-10). The VST3 gate at
`src/shell/instrument/CMakeLists.txt:9` stays exactly as it is; a Linux configure omits the
target and the `vendor/vst3sdk` submodule becomes optional there (Λ-10).

**Cost profile:** one Major that must be solved (Λ-01, the panel), two Majors that are
one-line-ish but load-bearing (Λ-02 optimization, Λ-03 visibility), one Major/Minor
(Λ-04 threads), and six Minors. No new third-party dependency. No new artifact format. No
change to any product decision on record.

**The unknown that dominates the band:** none of the extension's source portability is
audited here. The build-system work is small; whether the 38 shell TUs *compile and behave*
against SWELL's Win32 subset is T2's answer, and it, not this list, sets the phase's size.

### Fork B — extension + ReaSampler 9000 on Linux

Everything in Fork A, plus the following. Three of five are Blockers under the severity
vocabulary defined above ("the Linux build cannot produce a loadable artifact"): B1, B2, B3.
B4 downgrades to Major — the module still loads; only the editor fails to attach without it.
B5 is not a technical blocker at all — it is a documentation act, though one that must
precede the others in sequence (see §6 Stage 5).

- **B1 — the entry point is the wrong one, and the right one is already vendored.**
  `src/shell/instrument/CMakeLists.txt:78` compiles
  `public.sdk/source/main/dllmain.cpp` into the module. That file does
  `#include <windows.h>` at `:41` with no `SMTG_OS_*` guard — grepping the file for
  `SMTG_OS` returns nothing, and its only conditionals are
  `#if defined(_MSC_VER) && defined(DEVELOPMENT)` (`:43`, `:96`) and `#ifdef __cplusplus`
  (`:58`, `:87`). Linux needs
  `public.sdk/source/main/linuxmain.cpp`, which is present in the slice and exports
  `ModuleEntry`/`ModuleExit` (`extern "C"` declarations `linuxmain.cpp:47–52`; definitions
  `:59–67` and `:72–82` respectively). Both are **mandatory** — the
  SDK's own loader refuses the module without either
  (`public.sdk/source/vst/hosting/module_linux.cpp:189–204`, error strings "The shared
  library does not export the required 'ModuleEntry' function" and the same for `ModuleExit`).
  `GetPluginFactory` is checked at `:205`. **Effort S** — a `CMakeLists.txt` source swap
  (`dllmain.cpp` → `linuxmain.cpp`) plus the platform `if()`; both files are already vendored,
  nothing to write from scratch.

- **B2 — the artifact is a directory bundle, not a file.** Today the target sets
  `SUFFIX ".vst3"` on a MODULE (`src/shell/instrument/CMakeLists.txt:106–111`), producing a
  single file. `module_linux.cpp:146–170` (`getSOPath`) shows what a Linux host actually
  opens: the path must be a **directory**, containing `Contents/`, containing
  `<machine>-linux/` where `<machine>` is `uname().machine` (`:158–163`, helper at `:88–97`),
  containing `<stem>.so` (`:167–168`). So `reasampler_9000.vst3/Contents/x86_64-linux/reasampler_9000.so`.
  That is `add_custom_command(POST_BUILD)` work plus an install rule. Install roots are
  `$HOME/.vst3/`, `/usr/lib/vst3/`, `/usr/local/lib/vst3/` and `$APPFOLDER/vst3/`
  (`module_linux.cpp:282–302`). `Contents/Resources/moduleinfo.json` is **optional** —
  `getModuleInfoPath` (`:356–365`) returns empty when the file is absent rather than failing.
  **Effort M** — mechanical, but it is a new artifact shape with an install story and a
  per-architecture directory name.

- **B3 — the drawing stack is the real cost, and it is where Fork B stops being a build task.**
  The VST3 module compiles the same four LICE TUs (`src/shell/instrument/CMakeLists.txt:80`),
  and on non-Win32 LICE resolves its GDI through SWELL (§1a). But **a VST3 plugin is not a
  REAPER extension**: `swell-modstub-generic.cpp`'s default branch exports `SWELL_dllMain`
  (`:135`), which only REAPER's *extension* loader calls. Nothing hands a VST3 plugin the
  SWELL function table. That leaves two routes:
  - **Route B3a — `SWELL_LOAD_SWELL_DYLIB`** (`swell-modstub-generic.cpp:69–131`): the stub
    reads `/proc/self/exe`, strips to the directory, and `dlopen`s `libSwell.so` beside the
    host binary (`:81–98`), then calls `SWELL_set_app_main(SWELLAppMain)` (`:112–114`). That
    reference makes `SWELLAppMain` a **required definition in the plugin** — it is declared at
    `swell-types.h:137` as "to be implemented by app (if using swellappmain.mm)". Whether
    REAPER's Linux install ships a
    `libSwell.so` beside its executable is `[verify — Linux]`, and this route makes the VST3
    load-bearing on being hosted by REAPER specifically, which is consistent with D5's
    REAPER-only stance but is a new runtime coupling to state explicitly.
  - **Route B3b — build SWELL into the module.** `vendor/WDL/WDL/swell/Makefile:100–104`
    enumerates the 14 SWELL TUs (`swell.o swell-ini.o swell-miscdlg-generic.o
    swell-wnd-generic.o swell-menu-generic.o swell-kb-generic.o swell-dlg-generic.o
    swell-gdi-generic.o swell-misc-generic.o swell-gdi-lice.o swell-generic-headless.o
    swell-generic-gdk.o swell-appstub-generic.o swell-modstub-generic.o`) and `:106–107` the 8
    LICE TUs it needs alongside. Link requirements at `:90` (`-lpthread -ldl`), `:128–130`
    (pkg-config `gtk+-3.0` or `gdk-3.0`), `:145–151` (`freetype2` + `-lfontconfig`) and
    `:154` (`-lGL`).
    **This is a wholly new third-party dependency surface** — GDK/GTK3, FreeType, Fontconfig,
    OpenGL — that the extension deliberately avoids via `SWELL_PROVIDED_BY_APP`. Flagging it
    as a scope expansion, per the dispatch's constraint; it is not something to slip in.
  **Effort L either way**, and B3b is the larger of the two by a wide margin.

- **B4 — the editor's platform contract changes.** The view must report
  `kPlatformTypeX11EmbedWindowID` (`pluginterfaces/gui/iplugview.h:79`) instead of
  `kPlatformTypeHWND` (`:62`), and timers/file-descriptors must go through
  `Steinberg::Linux::IRunLoop` (`iplugview.h:267–279`) with `IEventHandler` (`:223–229`) and
  `ITimerHandler` (`:239–245`) — the header states the reason at `:255–256`: "On Linux the
  host has to provide this interface to the plug-in as there's no global event run loop
  defined as on other platforms." The whole `namespace Linux` block is `#if SMTG_OS_LINUX`
  (`:209`, closed `:284`), so it compiles in automatically. The **build-side** cost is nil
  (the IIDs are already vendored — see §3); the **source-side** cost is `editor_platform.cpp`
  and every timer in the editor, which is T2's surface. **Effort: build S, source L** — build
  is S because no new CMake source is needed (the SDK block compiles in automatically); source
  is L because it is a genuine window/run-loop rewrite (an X11 embed contract with no Win32
  analogue, not a mechanical port) and outside this doc's own portability scope to size
  further.

- **B5 — a settled product decision has to be reversed, in writing.** D5 ("Windows-only,
  VST3-only, REAPER-only") is recorded as a locked decision with "a mac/Linux build" named
  as a non-goal in `src/core/instrument/CLAUDE.md` (Invariants → the three locked decisions;
  and again under Non-goals/guardrails) and `src/shell/instrument/CLAUDE.md`
  (Non-goals/guardrails). `src/shell/panel/CLAUDE.md` carries a third dependent statement.
  Fork B is not just more work — it invalidates three recorded invariants and needs Daniel's
  explicit reversal before any code moves. **Effort S** — no code or CMake change of its own,
  a rewrite of the three `CLAUDE.md` passages naming D5 plus Daniel's sign-off; the L-sized
  cost sits in B3/B4, not here.

**The asymmetry worth naming.** Fork A's build work is small and its unknown is source
portability. Fork B's build work is genuinely large (B2, B3) *and* carries an unbounded
source-portability tail (B3b's dependency surface, B4's window/run-loop rewrite) *and*
requires reversing a settled decision. The two are not "the same phase, one bigger" — they
are different-shaped commitments.

---

## 5. Open questions

### `[verify — Linux]` — answerable by trying it on a Linux box

| # | Question | Exact check |
|---|---|---|
| V1 | Does the extension compile at all under GCC/Clang? | `cmake -B build -S . -G Ninja && cmake --build build 2>&1 \| tee build.log` — the single highest-information action available and gates most of T2's list too, with one caveat (Λ-09): a clean link is not the same guarantee here as on MSVC, since GNU `ld` does not error on undefined symbols in a `MODULE` by default — V1 answers "does it compile and link", not "does it load". |
| V2 | Does `ctest` pass all 91 targets? | `cmake -B build -S . -G Ninja && cmake --build build && ctest --test-dir build --output-on-failure` (no `-C` needed on Ninja). |
| V3 | Does REAPER's Linux build call `SWELL_dllMain` in a `reaper_*.so`, populating the API table? | Drop the built `.so` in `~/.config/REAPER/UserPlugins/`, restart, and check the extension's actions appear. A null table shows as immediate crash or dead actions. |
| V4 | Where exactly is `UserPlugins/` on Linux, and is `reaper_*.so` the right filename glob? | Options → Show REAPER resource path in a Linux REAPER. The SDK headers do not document either — grep of `vendor/reaper-sdk/sdk/` for `UserPlugins` and `reaper_*.so` returns nothing. |
| V5 | Does `panel_state.h:23`'s `<pthread.h>` actually require a link flag on the target glibc? | Build without `Threads::Threads`; if it links, it does not. |
| V6 | Does `-fvisibility=hidden` leave `ReaperPluginEntry` and `SWELL_dllMain` exported? | `nm -D --defined-only reaper_reasampler.so \| grep -E 'ReaperPluginEntry\|SWELL_dllMain'` after adding the preset. |
| V7 | With Λ-01 fixed by the resource-id-0 route, does `SWELL_CreateDialog` return a usable child HWND for the docker? | Panel toggle action in a Linux REAPER. |
| V8 | Does `std::filesystem` need `-lstdc++fs` on the chosen toolchain? | Build `capture_paths_tests` alone; the link error names it. |
| V9 | Does REAPER's Linux install ship a `libSwell.so` beside its executable (Fork B, route B3a)? | `ls $(dirname $(readlink -f $(which reaper)))/libSwell.so`. |
| V10 | Which `uname -m` values must the VST3 bundle carry (Fork B, B2)? | `uname -m` on each target. `module_linux.cpp:158–163` keys the bundle directory off exactly this string (tree-grounded and verified); which architectures REAPER itself ships for Linux (x86_64, aarch64, armv7l is the claim) is an external fact with no source in this tree — `[verify]` against REAPER's own download page before relying on it. |

### `[Daniel]` — genuine product/scope calls

| # | Question | Why it is not answerable by reading code |
|---|---|---|
| D1 | **Fork A or Fork B?** Extension only, or extension + ReaSampler 9000 on Linux? | §4 states both costs. The choice is scope, and D5 makes it a reversal of a recorded decision, not a technical lookup. |
| D2 | If Fork B: **route B3a (dlopen the host's `libSwell.so`, requires defining `SWELLAppMain`) or B3b (vendor and build full SWELL, adds GDK/GTK3 + FreeType + Fontconfig + OpenGL)?** | B3b is a dependency-surface expansion; the dispatch requires that be flagged rather than chosen. B3a is cheaper but couples the plugin's UI to being hosted by REAPER specifically. |
| D3 | Is **macOS in or out** of the same phase? | Λ-01, Λ-03, Λ-04, Λ-07 and Λ-09 are all marked *shared macOS/Linux* at their own sections above (Λ-01: identical broken comment block, `src/app/CMakeLists.txt:83–86` vs `:94–97`, and shared `swell_resgen.php` output per `README.md:120`; Λ-03/04/07/09: same mechanism under the APPLE branch, detailed at each). Doing both at once is cheaper than doing them serially — but that is a scope call, and macOS additionally implies signing/notarization (not mentioned anywhere in `versioning-and-release.md`) plus its own open question this doc does not resolve: whether `swell-modstub.mm` (`src/app/CMakeLists.txt:79`) even compiles under a CXX-only `project()` (`CMakeLists.txt:26`, no `OBJCXX`) — see §1a. |
| D4 | Does **CI get built in this phase**, and on what runner? | `versioning-and-release.md:307–311` and `:361–363` hand "the pipeline" to dev-ops and commit to three platform artifacts per channel, but no CI exists in the repo on any platform. Adding it is infrastructure and needs an explicit ask. |
| D6 | Is the Linux artifact **shipped or developer-only** for 1.x? | Determines whether Λ-02 (Release build) and Λ-08 (install path) are must-fix or nice-to-have, and whether a Linux `install()` rule is worth authoring. (Numbered D6, not D5, to avoid colliding with the settled product decision D5 — "Windows-only, VST3-only, REAPER-only" — cited above at §1b and B5.) |
| D7 | Λ-01's fork: **resource-id-0 escape hatch, or the resgen route?** §6 Stage 2 gates on T2's read of `panel_window.cpp`, and V7 presupposes the route is already picked. | It is a source-code + build-pipeline tradeoff (a `panel_window.cpp` DLGPROC→WNDPROC change vs. a PHP build step + generated-file handling) with no code-only right answer — needs Daniel's call once T2 reports, same as D1/D2. |

Deliberately **not** listed as `[Daniel]`, because reading more would settle them: whether
`arrange_drop_win.cpp`/`instrument_drop_win.cpp` need platform gating (read the files — T2);
whether the SDK's `moduleinfo.json` is required (it is not — `module_linux.cpp:362–364`);
whether the Linux run-loop IIDs need a new SDK TU (they do not — `commoniids.cpp:50–54`).

---

## 6. Ordering sketch

Dependency order only — this is sequencing input for a plan author, not the plan.

**Stage 0 — the one action that reprices everything.** Run V1 and V2 on a Linux box against
the tree as-is. Configure will succeed (nothing in the CMake requires Windows); the compile
will produce a diagnostic set that is the actual size of this phase. Every band below is
provisional until this is done. Λ-10's doc fix rides along free, since the person doing this
will hit it first.

**Stage 1 — toolchain floor, no source dependency.** Λ-02 (`CMAKE_BUILD_TYPE` default),
Λ-03 (visibility presets), Λ-04 (`Threads::Threads`), Λ-09 (warning flags + `CXX_EXTENSIONS
OFF`), Λ-05 (pin the `WDL_INC` coincidence), Λ-07 (compiler floor). All are root- or
target-property edits with no ordering constraint among them and none touching source. Doing
them *before* Stage 2 means the resource work is done under warnings rather than retrofitted
into them. Ship as one change.

**Stage 2 — the panel resource decision.** Λ-01. This forks on the resource-id-0 question
(§2, Λ-01) and therefore needs T2's read on `panel_window.cpp` first — so it is gated on T2
output, not on Stage 1. Whichever route wins, it is a shared macOS/Linux fix (see D3).

**Stage 3 — source partition.** Λ-06. Once T2 names which TUs need a platform branch or a
non-Windows sibling, the source list in `src/app/CMakeLists.txt:8–51` gains its
partition. Strictly after T2 and after Stage 2 (the resgen/shim TU, if that route is taken,
is one of the entries).

**Stage 4 — packaging.** Λ-08, plus whatever D6 decides. Depends on Stage 1 (there is no
point documenting an install path for an unoptimized artifact) and on V4.

**Stage 5 — Fork B, only if D1 says so.** Internally ordered: B1 (entry point swap, S) →
B3 (SWELL strategy per D2 — this gates everything downstream, because a module that cannot
draw cannot be tested) → B2 (bundle layout + install, needs V10) → B4 (X11 embed + run loop,
mostly T2's source work). B5 (the D5 reversal) precedes all of them as a documentation act.

---

## 7. Referred to T2

Source-level items I noticed while reading build files. **Recorded, not analyzed** — the
parallel track owns each of these and I have deliberately not investigated further.

1. `src/shell/panel/panel_window.cpp:135` — `CreateDialogParam` + `MAKEINTRESOURCE(IDD_BANK_PANEL)`.
   Under SWELL this is `SWELL_CreateDialog(SWELL_curmodule_dialogresource_head, …)`. Relevant
   to Λ-01's resource-id-0 option, which requires the proc to be a `WNDPROC` returning
   `LRESULT`, cast to `DLGPROC` (`swell-functions.h:606–608`).
2. `src/shell/actions/arrange_drop_win.cpp` and `src/shell/actions/instrument_drop_win.cpp` —
   zero `_WIN32` occurrences, no `<windows.h>`, yet `_win`-suffixed. Confirm they are
   genuinely portable, or add guards.
3. `src/shell/actions/drag_out_win.cpp:249–282` — the `#else` "macOS / Linux (SWELL)" branch
   exists but has never been compiled. Whether the SWELL drag path is functionally equivalent
   to the Win32 `IDataObject`/`IDropSource` path is unaudited.
4. `src/shell/persist/prune_fs.cpp:36` — `<windows.h>` behind two `_WIN32` guards. Confirm the
   non-Windows deletion path exists and is equivalent (this file is on the only
   file-deletion path in the product, per `src/core/reclaim/CLAUDE.md`).
5. `src/shell/panel/draw_kit.cpp:11–15`, `draw_kit.h:27–29`, `src/shell/panel/panel_state.h:18–26` —
   the `windows.h`-vs-`swell/swell.h` include switches. These look correct; whether the
   SWELL subset actually covers every GDI call the kit makes is the question.
6. `src/shell/panel/CLAUDE.md` records "Windows-only (D5) — font/GDI/HFONT choices assume
   Windows; no cross-platform font-fallback concern" as an invariant. Font selection on
   Linux SWELL (FreeType/Fontconfig-backed) is the concrete instance.
7. `src/core/capture/capture_paths.cpp` — `normalizeSlashes` lowercases on `_WIN32` only, and
   `tests/test_capture_paths.cpp:19–46` already encodes the case-sensitive POSIX contract.
   The test is written; the behaviour under a case-sensitive filesystem is unverified.
