# Λ-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 `.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 `` 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:56`), 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:378`, not in root `CLAUDE.md`) — those belong to `sampler_core`, which `src/app/CMakeLists.txt:57–59` 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 `` 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 `` on non-Win32 — and it is not the only entry point: `vendor/reaper-sdk/sdk/reaper_plugin.h:50` includes `` 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 `` 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 `` 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 `` — 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 `) 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 `` (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 `` 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 `). 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 ` 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 `-linux/` where `` is `uname().machine` (`:158–163`, helper at `:88–97`), containing `.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 `` 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 ``, 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` — `` 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.