Files
reasampler/PLAN.md
T
daniel a434ef47f8 docs(plan): ground L3 readiness against landed Phase S surfaces
Both VST draw shells still draw flat off a local pre-L1 palette — L3
restyle confirmed live, not a born-in-kit no-op. Name the concrete gate
files, add the palette-reconcile point, enumerate the landed editor set.
2026-07-27 03:42:24 -04:00

689 lines
49 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAN.md — ReaSampler milestone roadmap
Living milestone roadmap for ReaSampler. Derived from CONTEXT.md's 11-step build
order; CONTEXT.md remains the authoritative spec — this file is the tickable
checklist, not a re-statement of the spec. When a point lands, doc-keeper removes
it here and appends it to `COMPLETED.md`.
**Conventions**
- One checkbox `- [ ]` = one discrete, independently-landable point.
- Each milestone opens with a **Goal** (one line) and a **Verify** criterion
(the acceptance gate; precision invariants pulled in where one applies).
- Verify-in-DAW points require a manual REAPER run; pure points are gated by CTest.
- "See CONTEXT.md §…" points at the authoritative detail — do not duplicate it here.
---
## Milestone 9 — slots (MPC-style)
> **Explicitly deferred (Daniel, 2026-07-26) — not silently dropped. Can be picked
> up later if wanted.** An extra, not scheduled work.
**Goal:** "Capture to slot N" / "insert slot N", MIDI-bindable. CONTEXT.md
Build order 9.
**Verify (in DAW):** Slot capture and slot insert fire from MIDI bindings; slot
state persists via the index.
- [ ] Slot model + slot↔sample assignment.
- [ ] "Capture to slot N" / "insert slot N" actions, MIDI-bindable.
## Open questions to resolve during build
Carried from CONTEXT.md §Open questions — keep visible until each is closed by a
landed milestone.
- **`parseInt` narrowing hardening:** `src/bank_model.cpp` `parseInt` casts
`int64_t → int` via `static_cast` without a range check; integers that fit
in int64 but exceed `INT_MAX` are implementation-defined. Hardening candidate
— add bounds check before the cast when integer-field validation is in scope.
- **Capture send/routing isolation (TODO):** The FX-scope capture neutralizes out-of-scope FX, gain, and pan — but NOT aux **sends**. So a downstream coloring send (e.g. a folder → reverb-track send) still routes and blends the reverb into an item/track capture, past the intended isolation point. A true item-level capture should be taken at the isolated graph point — the target scope's output before out-of-scope track FX/gain/pan **and** before out-of-scope aux/parallel sends. The hard part: distinguish **source routing that must be preserved** (e.g. a MIDI send T1→T2 where T2's synth is where a MIDI item's audio is actually produced — the "item level" for that MIDI item is T2's synth output) from **coloring sends that must be excluded** (folder→reverb). Repro: folder F1; T1 (MIDI) sends MIDI to T2 (synth); T1+T2 → F1; F1 sends to reverb T3; capturing the MIDI item on T1 currently includes the reverb, should be isolated to T2's synth output pre-F1 with the MIDI send preserved and the reverb send excluded. Likely approach: snapshot + mute out-of-scope tracks' aux sends during the render while preserving the main/source signal path — needs a rule for which sends are load-bearing.
---
# Phase D2 — Two-canvas (item-level mode projection; additive to D1)
> **Design View sub-phase.** Extends D1's track-level mode projection to **item
> level** via REAPER 7 fixed lanes: on a track present in both stances, each mode
> owns a fixed lane — the active mode's lane shows and plays, the inactive mode's is
> hidden and silenced — so a Design take and an Arrange take can share the same
> track and time position without colliding on the view. Nothing in D1 changes.
> Runtime floor rises to **REAPER 7** for this sub-phase (no version-gate branch;
> below v7 it is simply unavailable). Authoritative spec: **CONTEXT.md §Two-canvas
> sub-phase (Phase D2 / Phase E)** and the surrounding §Design View — additive phase
> spec. Product framing: `docs/product/design-view.md` §Two-canvas direction. When a
> point lands, doc-keeper moves it to `COMPLETED.md`.
>
> **D2-W1 (pure lane extension), D2-W2 (shell: lane application + new-content
> detection), D2-W3-A (lane minting + item→lane assignment + persist round-trip),
> and D2-W3-B (item-level mode actions + W3-A polish) have all landed** — see
> `COMPLETED.md`. **Phase D2 is functionally complete.**
>
> **Deferred:** panel UI indicator for per-track lane/mode state (a per-track
> lane-split marker). The mode switch already shows the active mode; no natural
> cheap home for a per-track indicator was found in the bank panel. Explicitly
> deferred — not silently dropped. Can be picked up later if wanted.
---
# Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
> **New pillar, own lettered namespace, and — uniquely — a second build artifact.**
> Every prior phase ships inside the one `reaper_reasampler` extension binary; Phase
> S does not. A REAPER extension *cannot* be a MIDI-triggered instrument (it is not a
> node in any track's signal chain), so the instrument is a **separate native VST3
> plugin** the user instantiates on an instrument track, reading ReaSampler's banks
> and playing them MIDI-triggered. Namespaced **`S` (Sampler)** rather than "D"
> (Daniel's call — "D" collides with Design View). Authoritative spec: **CONTEXT.md
> §MIDI-playback instrument — additive phase spec (Phase S)**. Product framing +
> the settled decision record (D1/D5/D6 locked, D-A..D-D settled 2026-07-26):
> `docs/product/midi-playback.md`. When a point lands, doc-keeper moves it to
> `COMPLETED.md`.
>
> **Locked (see `docs/product/midi-playback.md` §4):** D1 native VST3 (not JSFX);
> D5 Windows-only / VST3-only / REAPER-only; D6 two products, tightly integrated via
> the VST-host bridge (live `"reasampler"` ext-state, project-aware). **Settled forks
> (2026-07-26):** D-A bare Steinberg VST3 SDK + LICE editor (no JUCE); D-B split seam
> with root-note + loop-points added to `Sample` *now*; D-C Tier 01 committed (Tier 2
> held, Tier 3 optional-forever); D-D embedded TCP/MCP UI **scheduled** as a later
> in-phase point (after the main editor exists).
>
> **Second build artifact (load-bearing, flagged up front):** Phase S produces a
> *separate* VST3 binary alongside `reaper_reasampler`. The Steinberg VST3 SDK is a
> **new vendored dependency** (vendor at the spike — an implementation-time
> prerequisite, not done here), and CMake grows a second target with Windows VST3
> module-export/bundle wiring. Both are established by S1 so nothing downstream leans
> on an unbuilt target.
## S1 — opening spike: VST3 skeleton + `IPlugView`↔LICE bridge (proof + second target)
**Goal:** Stand up the second build artifact and prove the two least-trodden
unknowns before the engine build leans on them: (1) a silent-but-loading VST3
`SingleComponentEffect` skeleton that REAPER hosts, and (2) an `IPlugView` that hosts
a LICE-drawn surface. Converts §1a's experienced-estimates (Windows module-export
names, factory-macro spellings, exact bridge marshalling) into verified fact.
CONTEXT.md §Phase S (build shape, module architecture, API surface).
**Prerequisite (implementation-time):** vendor the Steinberg VST3 SDK (a new
submodule/dependency alongside `reaper-sdk` / `WDL`); confirm whether VSTGUI is
bundled (moot for D-A but resolves the noted fallback rung).
**Verify (in DAW):** the VST3 skeleton loads in REAPER on an instrument track,
enumerates via `GetPluginFactory`, sets up an event-in + audio-out bus, and runs an
empty `process` without error; an `IPlugView` opens and draws a LICE surface with a
working hit-test; the VST-host bridge resolves `GetProjExtState` by name and reads a
known `"reasampler"` value. Nothing plays yet — this is the loading/drawing/bridge
proof.
- [ ] CMake second target: a separate VST3 module artifact built alongside
`reaper_reasampler` (Windows VST3 export/bundle wiring; `GetPluginFactory` +
`InitDll`/`ExitDll`**verify exact export names against the vendored SDK**).
- [ ] `SingleComponentEffect` skeleton: factory + class registration, `initialize`
declaring an event-input bus + an audio-output bus (no audio input),
`setupProcessing`, `setActive`, empty `process`. Loads silently in REAPER.
- [ ] `IPlugView`↔LICE bridge spike: open a plugin editor window hosting a LICE-drawn
surface (window creation/sizing, host→draw/hit-test event routing), reusing the
`bank_panel` LICE/SWELL competence. **The decision's one real unknown — prove it
here.** (VSTGUI is the noted fallback only if this proves gnarlier than the panel
work suggests.)
- [ ] Bridge read spike: resolve `GetProjExtState`/`EnumProjExtState` by name over the
host callback (`hostcb` opcode `0xdeadf00d`), fetch host project context
(`0xdeadf00e`), and read a known `"reasampler"` ext-state value. **Verify opcodes +
marshalling against `reaper_plugin.h` / `video_processor.h` /
`reaper_plugin_functions.h`.**
## S2 — `Sample` intrinsic fields (root note + loop points; in the *extension*)
**Goal:** Add the two bank-intrinsic seam fields to `Sample`**root note** (MIDI
note the sample was recorded at; distinct from the existing optional *musical key*)
and **loop points** (sustain-loop start/end, sample-accurate, zero-crossing-aware) —
as an additive field extension with JSON round-trip, populated at/after capture. This
touches the **extension** codebase, is independently shippable, and lands early to
close the backfill cliff before the instrument consumes the fields. CONTEXT.md
§Phase S (seam fields, D-B). **Same additive shape as `provenance` (M1).**
**Verify:** CTest green. Round-trip lossless across the new fields; pre-existing
samples (no root note / loop points) deserialize with clean defaults (no loss, no
migration break); capture populates root note where derivable and loop points where
set; relative-paths-only unaffected; `BankIndex` behavior unchanged (purely
additive).
**Depends on:** nothing in Phase S (extension-only; can land before or in parallel
with S1).
- [ ] Add `rootNote` (optional MIDI note) + `loopStart`/`loopEnd` (optional
sample-accurate loop points) to `Sample`; JSON serialize/deserialize with clean
defaults for samples lacking them (additive, backward-compatible — mirror of how
`provenance` was added).
- [ ] Populate the fields on capture where derivable (root note) / settable (loop
points); leave them cleanly empty otherwise. No existing `Sample` field changes.
- [ ] Tests: full round-trip lossless including the new fields; a legacy `Sample`
JSON (no new fields) parses with defaults and re-serializes without loss; additive
invariant (no change to existing fields, dedup, tier, or `BankIndex` behavior).
## S3 — pure sampler core (voice engine / envelope / keymap / repitch)
**Goal:** The REAPER-free **and** VST3-free sampler core — voice allocation/polyphony,
amplitude envelope (ADSR), key→sample and velocity→sample mapping (the keymap),
repitch/interpolation from root note, keymap resolution — unit-tested in CTest against
known signals. **The heart of the phase (D3); the mirror of
`bank_model`/`peaks`/`view_mode_model`/`bank_book`; test it hard.** The core is
invariant under the build-shape choice — no VST3 or REAPER type at its boundary.
CONTEXT.md §Phase S (pure core, module architecture).
**Verify:** CTest green. Voice allocation is correct under polyphony (note-on/off,
voice stealing where bounded); ADSR shape asserted against a known signal (mirror of
`peaks`); repitch from root note produces the expected pitch ratio; keymap resolution
maps a (note, velocity) to the correct sample/zone; the core takes and returns only
plain data (no VST3/REAPER types) — enforced by the test target linking neither SDK.
**Depends on:** S2 (consumes `rootNote` / loop points as core inputs).
- [ ] Voice engine: polyphonic voice allocation (note-on/off, bounded voice stealing),
per-voice state, mono-and-basic-polyphony sufficient for Tier 0.
- [ ] Amplitude envelope (ADSR) math — asserted against a known signal.
- [ ] Repitch/interpolation from root note (chromatic pitch ratio across the
keyboard); loop-point-aware sustain for held notes.
- [ ] Keymap model + resolution: key ranges/zones (Tier-1 shape) and the
(note, velocity) → sample/zone query; Tier-0 chromatic-from-single-root as the
degenerate case.
- [ ] Tests: voice allocation under polyphony + stealing; ADSR envelope shape;
repitch pitch-ratio correctness; keymap resolution (single-root chromatic + zoned);
core boundary is plain-data-only (no VST3/REAPER types).
## S4 — Tier 0: "the bank plays" (single sample, chromatic)
**Goal:** The honest MVP — one bank sample mapped chromatically across the keyboard
from its root note, basic polyphony, a simple amp envelope, velocity→volume. Wire the
S3 core into the S1 VST3 shell over the live-state seam (bridge-read bank + audio via
the M4 project-relative path machinery). Editor deferrable behind a parameters-only
default view. CONTEXT.md §Phase S (Tier 0, seams). **Delivers the core promise.**
**Verify (in DAW):** on an instrument track, the VST3 plays a chosen bank sample
MIDI-triggered, repitched chromatically from its root note, with basic polyphony,
an amp envelope, and velocity→volume; it reads the live `"reasampler"` bank via the
bridge and resolves the WAV audio the same project-relative way `persist` does;
following the active project works; it never captures and never inserts into the
arrange (read-only over the bank).
**Depends on:** S1, S2, S3.
- [ ] VST3 `process` marshalling: read MIDI note-on/off/velocity off the event bus,
drive the S3 core, write per-voice audio to the output bus.
- [ ] Live-state seam: read the bank index + selected sample's root note from
`"reasampler"` ext-state via the bridge; resolve the WAV audio path the M4
project-relative way (shared convention with `persist`, not re-implemented).
- [ ] Sample selection UI (minimal, in the `IPlugView` LICE editor or a
parameters-only default view): choose which bank sample this instance plays.
- [ ] Tier-0 playback: chromatic-from-root, basic polyphony, amp envelope,
velocity→volume — plays in REAPER's routing/record/render path like any VSTi.
## S5 — Tier 1: "a keymap" (zoned multisamples, per-sample root notes)
**Goal:** Multiple bank samples zoned across the keyboard (key ranges), each with its
own root note — a captured *kit* (one-shots) or a *multisampled instrument* (same
instrument sampled at several pitches) plays correctly. One sample per key-region.
CONTEXT.md §Phase S (Tier 1). **Where the root-note + key-range seam fields earn
their place.**
**Verify (in DAW):** a keymap of several bank samples plays correctly zoned across
the keyboard, each repitched from its own root note within its range; a captured kit
and a multisampled instrument both play as expected; the keymap is authored in the
instrument (performance map) while root notes come from the bank intrinsics (S2);
editing the keymap does not touch the bank.
**Depends on:** S4.
- [ ] Keymap editor in the `IPlugView` LICE editor: assign bank samples to key ranges
(low/high note per sample), each with its own root note (from S2 intrinsics,
overridable in the performance map).
- [ ] Tier-1 playback: zoned resolution — a note picks its zone's sample and repitches
from that sample's root note; one sample per key-region.
- [ ] Performance-map persistence: the keymap (zones, per-sample assignment) is the
instrument's own state — held in the instrument (read/written over the live
`"reasampler"` seam per D-B's data-ownership split), never written back as a bank
intrinsic.
## S6 — embedded TCP/MCP UI (D-D — scheduled in-phase, after the editor)
**Goal:** Render a compact keymap/level strip **inline in the track/mixer control
panel** via `reaper_plugin_fx_embed.h` (`IReaperUIEmbedInterface`) — the same
Cockos surface REAPER's own embedded FX use — so the instrument draws inline, not only
in its own window. Composes with the S1/S5 LICE editor path (same LICE-class drawing).
**Scheduled, not deferred (D-D settled 2026-07-26):** a real later point, sequenced
last because it is polish over a Tier-0 need — but on the roadmap. CONTEXT.md §Phase S
(embedded UI, D-D).
**Verify (in DAW):** the instrument draws a compact inline strip in the TCP/MCP (not
only its own editor window); the inline surface reflects and (where offered) edits the
keymap/levels; the embed lifecycle is clean (open/close/resize); the same LICE drawing
as the main editor is reused.
**Depends on:** S5 (composes over the existing LICE editor). **Must-verify before
build:** the `IReaperUIEmbedInterface` contract + embed message/lifecycle against
`vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h`.
- [ ] Implement `IReaperUIEmbedInterface` on the VST3; draw a compact keymap/level
strip inline in the TCP/MCP using the same LICE surface as the editor.
- [ ] Embed lifecycle (open/close/resize/hit-test inline) handled cleanly; reflects
the live keymap/levels.
## Phase S — held and optional-forever (noted, not specified)
- **Tier 2 — "expressive" (HELD).** Velocity layers, round-robin (anti-machine-gun),
full ADSR, per-sample tuning/gain trim, sustain loops. The next depth increment once
Tier 01 proves the instrument belongs — **its points are not drawn up here.**
- **Tier 3 — "instrument polish" (optional-forever).** Filters, filter/pitch
envelopes, LFOs, per-voice pan, choke groups, a modest FX slot. A direction to leave
room for, never a commitment.
## Phase S — must-verify-before-build (carried from CONTEXT.md §Phase S)
- **Steinberg VST3 SDK surface** — interface members, base-class overrides,
factory-macro spellings, Windows module-export symbol names
(`InitDll`/`ExitDll`/`GetPluginFactory`), and whether VSTGUI is bundled. Several are
§1a experienced-estimates until S1 confirms them against the vendored SDK.
- **VST-host bridge** — opcodes `0xdeadf00d` (resolve-by-name) / `0xdeadf00e` (host
context) and the exact call marshalling, against `reaper_plugin.h` /
`video_processor.h` / `reaper_plugin_functions.h`.
- **`IReaperUIEmbedInterface`** — embed contract + message/lifecycle, against
`reaper_plugin_fx_embed.h` (needed only at S6).
---
# Phase L — Look-and-feel (system-wide visual design language)
> **New pillar, own lettered namespace, taken up by a parallel team.** Phase L is the
> whole-system look-and-feel effort: a shared LICE drawing kit and the surfaces that
> adopt it, so ReaSampler and ReaSampler 9000 shed the flat "temple os" drawing for a
> modern, sleek 2026 dark synth look. It answers Daniel's post-DAW-test verdict on the
> instrument ("this looks like temple os… the VST is dogshit… scope it for the whole
> system… does Cockos have a toolkit?"). Namespaced **`L` (Look-and-feel)** so it is
> orthogonal to and ungated by the M/D/B/R/V/S pillars — a parallel team owns it while
> Phase S feature work proceeds independently. Authoritative spec: **CONTEXT.md §Phase L
> — visual design language (design-system spec)**. Product framing, the settled decision
> record (DS-1/DS-2/DS-3 all SETTLED 2026-07-26), palette, and the three visual
> directions: `docs/product/visual-design-language.md`. When a point lands, doc-keeper
> moves it to `COMPLETED.md`.
>
> **L1 (shared LICE drawing kit — the foundation), L2 (dock-panel layout redesign), L4
> (dock-panel button layout enhancement), L5 (dock-panel button refinements), L6 (toolbar
> polish), and L7 (capture ordering, card metadata, and selection styling) have landed** —
> `theme`/palette module, `component_geometry` geometry/hit-test helpers, `draw_kit` shell, GDI
> `DrawText` retirement in `bank_panel` (L1); `action_bar` pure task-grouped layout module, full
> M11-aware button inventory placed by task cluster, `bank_panel` redesigned through the L1 kit
> (L2); three-zone layout (top capture/placement/maintenance toolbar, bottom Design-View toolbar,
> footer toggle + Tail button + Prune), `footer_bar` pure module, `ActionCluster::Tagging`/
> `Switching` in `action_bar` (L4); top-bar overflow menu (`overflow_menu` pure module),
> custom LICE-kit hover-delay tooltips (`tooltip` pure module), opposite-mode Item/Track tag
> buttons + Show Both, Toggle + Activate-Arrange/Design buttons removed, grouping spacing
> widened (`mode_enable` pure module) (L5); single-row button faces, keybinding in tooltip,
> Cancel RT moved to overflow, top-bar cluster order tidied (L6); per-bank `SlotMap`
> (id→slot) in `bank_book`, sparse-grid rendering, `card_drag` + `card_meta` pure modules,
> `captureTimeSigNum`/`captureTimeSigDenom` on `Sample`, tertiary-border selection (L7). See
> `COMPLETED.md`. **L3 remains** — L3 gated on Phase S landing on dev.
>
> **This section is self-contained for a team without Phase S context.** Where a point
> touches a Phase S surface (the VST editor, the embed strip, the keyboard strip), the
> gate is stated explicitly so the team does not chase files that are not on dev yet.
>
> **Settled decisions (Daniel, 2026-07-26 — see `docs/product/visual-design-language.md`
> §6):**
> - **DS-1 — toolkit: LICE + WDL free game, no external frameworks.** Draw the modern
> look with LICE directly; reuse any useful WDL/vwnd piece (skin/image helpers, draw
> idioms, a control like the scroll listbox) where it beats re-deriving — "don't
> reinvent the wheel." Reject iPlug2 / JUCE / VSTGUI (external frameworks re-opening the
> settled bare-SDK+LICE build shape). Keep hit-test geometry in pure CTest-covered
> modules — do not import vwnd's retained-mode object model wholesale.
> - **DS-2 — visual direction: Direction B ("Neon Console") + Direction C's spectral
> keyboard strip. SETTLED 2026-07-26, REVISED 2026-07-26 (Daniel) — palette-only.**
> *Neutral surfaces (revised):* the neutral ladder moved **from near-black up into REAPER's
> mid-grey theme family** so the dock reads as part of REAPER, not a black slab — `bg/base`
> ≈ `#2b2b2b`, `bg/panel` ≈ `#333333`, `bg/cell` ≈ `#3a3a3a`, `line/hairline` ≈ `#4a4a4a`,
> `text/primary` ≈ `#dcdcdc`, `text/dim` ≈ `~#a0a0a0`+ (elevation-ladder discipline
> unchanged). *Accent layer (revised):* now a **three-accent pastel system** —
> `accent/primary` pastel lime green (live/active/selected), `accent/secondary` pastel teal
> + `accent/tertiary` pastel purple (categorical distinctions) — replacing the original
> single electric cyan. The spectral keyboard strip is a **pastel** sweep anchored on the
> three accents. *Tight WCAG pairs to re-verify against the grey ladder:* `text/dim`-on-grey
> (mid-grey-on-mid-grey, AA 4.5:1) and the three pastels-as-indicators on `bg/cell` (shrunk
> from ~15:1 to ~6:17:1). **A stylish/bundled-font upgrade was considered and DECLINED
> (Daniel):** no font bundling/redistribution — the kit keeps its current cached-font face,
> no new typeface. The kit palette stays abstract (roles, one constants block — three accent
> roles + grey neutrals), so the direction is a single-file change; final hex is locked
> against the theme WCAG tests within the pastel intent. Detail:
> `docs/product/visual-design-language.md` §2.1/§4/§6 + CONTEXT.md §Phase L.
> - **DS-3 — dock-panel scope: a thorough layout redesign, not a light re-skin.** L2 lays
> out the full button inventory (including M11's action-button additions) intuitively,
> uncluttered, and useful — then applies the kit. Sequenced after M11 merges.
## L3 — VST editor + embed-strip restyle (GATED on Phase S landing on dev)
**Goal:** Bring the ReaSampler 9000 VST editor (`IPlugView` LICE surface) and the S6 embed
strip up to the settled-and-revised **B + three-accent pastel** look via the L1 kit: kit
cached-font text (§3.1 — the kit's current face, no font change), kit component draws, the
Neon-Console palette with **REAPER-grey neutrals** (`#2b2b2b`/`#333333`/`#3a3a3a`) and the
**three pastel accents** (primary lime / secondary teal / tertiary purple), the **pastel
spectral keyboard strip** as the signature surface (DS-2
revised), and hover/pressed/drag states throughout. CONTEXT.md §Phase L (VST restyle + the
Phase S coordination contract). Product framing: `docs/product/visual-design-language.md` §5.3.
**Verify (in DAW):** the VST editor + embed strip render in the settled B + three-accent
pastel language through the L1 kit — kit AA cached-font text, gradient/rounded kit
components, the pastel spectral keyboard strip, working hover/pressed/drag; the VST3 class
UID is unchanged (a visual refresh is not a compat event).
**Depends on:** L1 (the kit) **AND — GATE — Phase S landing on dev.** The VST editor, embed
strip, and keyboard strip live in Phase S, which is **not on dev yet** (it exists on the
phase-s worktree). **L3 cannot be built on dev until Phase S's drawing shells
(`src/vst/reasampler_editor.cpp` + `reasampler_embed.cpp`) merge to dev. The Phase L team
must NOT chase these files on dev — they are not there.** Until then L3 is a planned,
blocked point; L1 and L2 are the live Phase L work.
> **Coordination contract with Phase S (load-bearing).** Phase S's editor/embed UX
> (S10S13, S15S18) build their interaction UX with the **current** drawing and **adopt the
> L1 kit when it is available — they are NOT gated on Phase L.** Whichever lands first (the
> L1 kit or the Phase S UX), the kit is the **one source of drawing**: if the Phase S
> surfaces reach dev before L1, they draw in the current language and L3 restyles them; if
> L1 lands first, they are born in the kit. **Reality check (product-designer, 2026-07-27,
> read-only against the phase-s worktree): the Phase S surfaces did NOT adopt the kit —
> `reasampler_editor.cpp` and `reasampler_embed.cpp` still draw flat `LICE_FillRect` blocks
> + raw GDI `DrawTextA`, off a local pre-L1 forest-green palette (`kColBackground` etc.),
> not `theme`/`draw_kit`.** So L3 lands as a **full restyle**, not a born-in-kit no-op — all
> three plan points below are live and confirmed still-needed. Either way there is one kit
> and one look, and L3 is the point that completes the VST/embed adoption and applies the
> settled B+spectral treatment.
>
> **Landed-scope note (product-designer, 2026-07-27).** All Phase S drawing is centralized
> in **exactly two shells** — `reasampler_editor.cpp` (the `IPlugView` editor) and
> `reasampler_embed.cpp` (the S6 embed strip). Every other `src/vst/` UI file
> (`editor_geometry`, `keyboard_strip`, `waveform_view`, `capture_browser`, `param_slider`,
> `browser_scroll`, `embed_strip`) is a **pure geometry/hit-test module — zero LICE, zero
> draw** (the house discipline held on the branch). So L3 touches two draw shells, but their
> rendered component set is broader than the original plan named: the editor draws a
> **capture-first browser** (search + tabs + thumbnails, S10), **channel toggles** (S7),
> **waveform + start/loop markers** (S11), the **keyboard strip + zone bars** (S10/S10-Z),
> and **ADSR / pitch sliders + mode toggles** (S12/S15/S16). Every one of these is on the
> restyle surface — enumerated in the points below.
- [ ] Route the VST editor's + embed strip's text through the kit's cached-font `text()`
(the kit's current face — §3.1, no font change); retire their raw GDI `DrawTextA` path
(present in both shells today). (Gated — Phase S surfaces must be on dev.)
- [ ] Retire the shells' **local pre-L1 palette** — the `kColBackground`/`kColCardBg`/
`kColThumb`/… forest-green-on-charcoal constants block in `reasampler_editor.cpp` (and the
mirrored constants in `reasampler_embed.cpp`) — and draw every surface through the L1
`theme` roles instead. **This is the coordination-contract reconcile: today there are two
palettes (the landed `theme` kit + this local constants block); L3 collapses them to the
one kit.** (Gated.)
- [ ] Restyle the editor + embed components through the kit (browser search/tabs/thumbnails,
channel toggles, ADSR + pitch sliders, mode toggles, zone bars, list rows, waveform,
segmented controls) in the B (Neon Console) palette — **REAPER-grey neutrals** (`bg/base`
`#2b2b2b` / `bg/panel` `#333333` / `bg/cell` `#3a3a3a`) with the **three pastel accents**
(primary lime = live layer; secondary teal / tertiary purple = categorical); add
hover/pressed/drag states. (Gated.)
- [ ] Apply **Direction C's pastel spectral treatment to the keyboard strip + zone bars**
(hue-mapped zones as a pastel sweep anchored on the three accents, active zone lifting to
`accent/primary` + a static glow — never a pulse, per the speed constraint) as the
signature surface; also carry the waveform + loop/start markers onto the kit's
`drawWaveform` + `warn`/accent marker roles; VST3 class UID unchanged. (Gated.)
## Phase L — sequencing
```
L1 (shared kit) ──► L2 (dock-panel layout redesign) [LANDED]
├─────────► L3 (VST editor + embed-strip restyle; GATED on Phase S landing on dev)
└─────────► L4 (dock-panel button layout enhancement) [LANDED]
└────► L5 (dock-panel button refinements; ungated, after L4) [LANDED]
└────► L6 (toolbar polish; ungated, after L5) [LANDED]
└────► L7 (capture ordering + card metadata + selection styling; ungated, after L6) [LANDED]
```
L1, L2, L4, L5, L6, and L7 have all landed. L3 waits for Phase S to reach dev. Phase S
feature work runs in parallel, ungated by Phase L.
## Phase L — must-verify-before-build
- **LICE design-kit surfaces (L1)** — `LICE_GradRect`, `LICE_RoundRect`, AA
`LICE_Line`/`LICE_FLine`/`LICE_ThickFLine`/`LICE_Circle`/`LICE_FillCircle`/
`LICE_DrawCBezier`, `LICE_FillTriangle`/`FillTrapezoid`/`FillConvexPolygon`, and the
`LICE_CachedFont`/`LICE_IFont` font engine (`SetFromHFont`, AA `DrawText`, shadow/outline/
glow FX flags). Verified *present* in `vendor/WDL/WDL/lice/lice.h` + `lice_text.h`
(design-language doc §1.1); **confirm exact signatures + the `LICE_CachedFont``HFONT`
lifecycle at build.**
- **WDL/vwnd reuse assessment (DS-1)** — at build time, evaluate whether a vwnd piece beats
re-deriving it: `virtwnd-slider.cpp` / `vwnd_slider_drawknobstack` as the slider/knob
drawing reference, `virtwnd-listbox.cpp` as a candidate scroll listbox, `virtwnd-controls.h`
for `WDL_STYLE_*` gradient hooks, `virtwnd-skin.h` for image-skin helpers. Reuse where
useful; keep hit-test geometry pure regardless.
- **M11 button inventory (L2)** — resolved at L2 build: inventory taken against dev after
M11 merged; all buttons placed by task cluster in the landed `action_bar` module.
- **L4 re-home surface (L4)** — resolved at L4 build: three-zone layout confirmed against the
post-palette-revision `bank_panel`; `action_bar` extended with `ActionCluster::Tagging` +
`Switching`; new pure `footer_bar` module covers footer layout/hit-test.
- **L5 refinement surface (L5)** — resolved at L5 build: `TrackPopupMenu` overflow menu
confirmed; tooltip mechanism resolved as **custom LICE-kit hover-delay tooltip** (`tooltip`
pure module; sourced from the registered action phrase, prefix stripped at draw time); item-move
and track-tag action ids confirmed; active-mode read confirmed via the same
`view().activeModeId()` the footer toggle uses.
- **L7 model + tempo surface (L7)** — **RESOLVED at build (L7 landed).** Forks F1/F2/F3 all
confirmed: `TimeMap_GetTimeSigAtTime` confirmed at build for the meter stamp; SWELL stock
cursors chosen for drop-result cues (Reorder→IDC_SIZEALL, Move→IDC_HAND, Copy→IDC_UPARROW,
Replace→IDC_SIZEWE); existing pool-privilege guard reused as-is for Alt-replace. Gap navigation
in the grid = skip gaps (arrow keys skip empty slots). See `COMPLETED.md` §L7.
- **L3 restyle surface (L3)** — **PRE-CONFIRMED read-only against the phase-s worktree
(product-designer, 2026-07-27); re-confirm against dev once Phase S merges.** (1) Both draw
shells (`reasampler_editor.cpp`, `reasampler_embed.cpp`) still draw flat — `LICE_FillRect` +
raw GDI `DrawTextA`, no `theme`/`draw_kit`/`LICE_CachedFont` include — so all three L3 points
are live (no born-in-kit no-op). (2) There is a **second palette** to reconcile: a local
`kCol*` forest-green-on-charcoal constants block in the editor shell, divergent from the
landed `theme` roles. (3) Draw is centralized in those two shells only; every other
`src/vst/` UI file is pure geometry (kit adoption is a shell-side change, no geometry
rework). (4) The phase-s branch carries an **older L1 kit** (its `action_bar` predates dev's
L4L7 `theme` palette revisions) — after Phase S merges, L3 draws against dev's current
`theme`/`draw_kit`, so re-confirm the palette roles L3 consumes are dev's, not the branch's
stale copy. **One genuine open question for Daniel** (see the assessment) — otherwise the
settled DS-1/2/3 spec forces the rest.
---
# Phase Q — Quality (structural reorganization; zero-runtime-cost)
> **New pillar, own lettered namespace, and — uniquely — the LAST structural pillar.** Phase
> Q is a **pure structural refactor**: it reorganizes `src/` into a healthier shape (more
> encapsulation, granular namespaces, `core/`/`shell/`/`app/` subdirectories) against a stated
> quality bar — *"mtytel Vital is my code reference for quality"* — to bring the codebase
> "into the realm of something I can stand to look at." It ships **no feature and changes no
> behavior**: the test suite passing unchanged is the proof of correctness. Namespaced **`Q`
> (Quality)** — M/D/B/R/V/S/L are all taken; `Q` names the *end* (the quality bar), the reorg
> being the *means*. Authoritative spec: **CONTEXT.md §Phase Q — structural reorganization
> (reorg spec)**. Product framing, the Vital-grounded target shape, the grep-verified SOLID
> audit that is the evidence base, and the settled/recommended fork record (Q-1..Q-6):
> `docs/product/code-organization.md`. When a point lands, doc-keeper moves it to
> `COMPLETED.md`.
>
> **THE GATE (load-bearing — state first).** Phase Q is **gated on the tree being otherwise
> quiescent.** It does not begin until **Phase S has merged to dev**, **Phase L (L2 + L3) has
> merged to dev**, any **D2 residuals** are closed, and **M9** is landed-or-abandoned. *Why:*
> Phase Q touches **nearly every file in `src/`** (relocate into subdirectories, re-namespace
> every header, split the four largest TUs). Every large in-flight branch (Phase S on its
> worktree, Phase L's `bank_panel`-touching L2/L3) is diffed against the *current flat layout*;
> landing a rename-and-relocate-everything reorg mid-flight forces every open branch through the
> worst conflict class (every hunk moved, every qualified reference changed) — a combinatorial
> re-resolution, not a linear one. Phase Q is *last* precisely because it reshapes the ground
> every other pillar stands on. Landing it early taxes every subsequent phase; landing it last
> taxes nothing. **Do not begin any Q point until the gate is satisfied.**
>
> **Settled (Q-1, this-doc):** the phase is **`Q` (Quality)**; point-id family `Q1..Qn`, wave
> prefixes `Q-W1..Q-W6`. **Recommended, Daniel's to call (Q-2..Q-6, see
> `docs/product/code-organization.md` §6):** Q-2 JSON extraction in scope + first (rec: yes);
> Q-3 directory shape `core/`/`shell/`/`app/` top-split with subsystem dirs beneath (rec: this
> over pure-Vital subsystem-first — it makes the pure/shell invariant *structural*); Q-4
> sub-namespace to match sub-directory (rec: both); Q-5 split god-modules to the audit's named
> seams, no finer (rec: yes); Q-6 OCP registration-table as the final wave (rec: in, last).
>
> **HARD CONSTRAINT — performance (see CONTEXT.md §Phase Q, `docs/product/code-organization.md`
> §3).** The reorg must cost **zero runtime.** On the three hot paths — `peaks` envelope
> compute, audition/preview, the realtime-capture tick — **no added virtual dispatch, no
> header→TU indirection, no changed call/inline or branch shape.** `computeEnvelope` stays a
> free function on `const std::vector<float>&`; audition split stays a direct call-through;
> the realtime idle tick stays a single pointer test; `FxBypassGuard` stays stack RAII. This is
> an acceptance criterion on every point: *a split that would add a hot-path indirection is out
> of scope — rework it or drop it.*
>
> **Every point is independently landable and CTest-green at EVERY step.** The CMake
> per-module static-lib + per-module test-executable seams already draw the module boundaries;
> a file move + namespace change is mechanically verifiable — `ctest --test-dir build` is green
> or it isn't. **Green-CTest-at-every-point is an acceptance criterion.** Big-bang is rejected;
> the reorg is risk-ordered waves (W1 safe opener → W2W5 god-module splits → W6 OCP finish).
## Q-W1 — safe opener: extract `core/json` + impose the directory/namespace layout on clean modules
**Goal:** The zero-god-module-risk opener. Two moves: (1) extract a pure **`core/json`** module
(parser + serializer) and **delete the four hand-rolled `Parser`s** in `bank_model` /
`bank_book` / `view_mode_model` / `owned_manifest` (the single largest DRY+SRP violation, and
entirely off the hot paths); (2) impose the settled `core/`/`shell/`/`app/` directory layout +
sub-namespaces (`reasampler::model`/`view`/`capture`/`audio`/`ui`/`reclaim`/`version`/`json`) on
the **30 clean pure libs + the clean shells that need no splitting** — pure relocation, no logic
change. Proves the wave discipline (relocate + encapsulate, CTest-green) before any god-module
surgery. CONTEXT.md §Phase Q (json extraction; directory + namespace map).
**Verify:** CTest green at every commit. The four duplicate `Parser`s are gone, replaced by one
`core/json` consumed by all four models; round-trip serialization is byte-identical to before
(no format change — a *structural* dedupe, not a behavior change). Every relocated clean module
compiles and its test executable passes unmoved. `Sample` (model) vs `AudioSample` (audio) vs
unified `Parser` (json) do not collide once sub-namespaced. No REAPER type crosses into any
`core/` file; the CMake pure/shell enforcement still holds.
**Depends on:** the GATE (tree quiescent). Nothing else in Phase Q.
- [ ] Extract `core/json` (pure parser + serializer: parseString/parseInt/parseKey/skipValue +
escape, plus emit helpers); unify under `reasampler::json`; guard the `Parser` name against
cross-lib collision. Off all hot paths — safe to abstract freely.
- [ ] Rewire `bank_model`, `bank_book`, `view_mode_model`, `owned_manifest` onto `core/json`;
**delete the four duplicate `Parser`s.** Round-trip output byte-identical (dedupe, not
reformat).
- [ ] Relocate the 30 clean pure libs into `core/{model,view,capture,audio,ui,reclaim,version,
json}/` and the clean shells into `shell/{capture,panel,view,persist,actions}/`; move
`main.cpp` to `app/`. Update `CMakeLists.txt` `src/` paths only (no target-graph change).
- [ ] Apply sub-namespaces matching the directories on every relocated *clean* module (the
god-modules re-namespace their own new TUs as they split, W2W5). Resolve `Sample`/
`AudioSample`/`Parser` homes.
- [ ] Confirm CTest green + no hot-path change: `peaks`/audition/realtime-tick untouched by this
wave (pure relocation of clean modules; `peaks` stays a free function).
## Q-W2 — split `bank_panel.cpp` (the biggest god-module, 2424 LOC)
**Goal:** Split the largest god-module (8+ responsibilities) along the audit's named seams:
`panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` / `panel_bank_ops` /
`panel_window`. Split the fat `bank_panel.h` alongside (Interface Segregation). **Preserve the
audition hot path as a direct call-through, never virtual.** `panel_bank_ops` becomes the single
home for the bank-CRUD verbs that W4 will dedupe `actions.cpp` against. CONTEXT.md §Phase Q
(bank_panel split seams; hot-path audition guardrail). See `docs/product/code-organization.md`
§2.1, §5.
**Verify:** CTest green at every commit. Each seam is its own TU under `shell/panel/`; the panel
draws, thumbnails, auditions, handles input, does bank ops, and manages its window exactly as
before (no behavior change — verify in DAW that the panel is visually and interactively
unchanged). Audition/preview call path stays a **direct call-through** (no virtual dispatch, no
added header→TU indirection on the preview path). The ~20-function public API is now segmented
across the split headers.
**Depends on:** Q-W1 (directory/namespace layout established). Independently landable.
- [ ] Split rendering (`draw*`/`paint*`) → `panel_render`; thumbnail compute+cache →
`panel_thumbnails`.
- [ ] Split the audio audition/preview engine → `panel_audition` — **direct call-through, not
virtual; preview idle path unchanged.**
- [ ] Split input handling (mouse/key/wheel) + new-content detection → `panel_input`; window
lifecycle + OS drag-out/drop-target → `panel_window`.
- [ ] Extract bank-CRUD verbs → `panel_bank_ops` (the future single owner; W4 dedupes
`actions.cpp` against it). Split `bank_panel.h` into per-seam headers (I).
- [ ] Verify in DAW: panel unchanged; CTest green; no hot-path indirection added.
## Q-W3 — split `main.cpp` (hoist orchestration; leave main = pointers + entry + dispatch)
**Goal:** Reduce `main.cpp` (1762 LOC) to its actual job — API pointers + `ReaperPluginEntry` +
dispatch (~the owns-pointers ~120 lines) — by hoisting: `capture_orchestrator` (`RunCapture` /
`captureAndIndexOne` / `renderOffline` / batch/recapture/realtime `Run*`), `scope_resolve`
(`resolveRange`/`resolveRazorRange`/`collectSelectedTracks` + provenance assembly inputs), and
`realtime_lifecycle` (the realtime-capture state machine + globals + selection guards).
**`FxBypassGuard` moves out but stays a stack RAII object (precision-critical); the realtime idle
tick stays a single pointer test.** CONTEXT.md §Phase Q (main split seams; FxBypassGuard +
realtime-tick guardrails). See `docs/product/code-organization.md` §2.1, §3.
**Verify:** CTest green at every commit. Capture (offline + realtime + batch + recapture) behaves
identically in DAW; the null test still nulls, bit-identical repeats still match (the precision
invariants `FxBypassGuard` protects are unchanged); capture ≠ placement holds (no hoisted `Run*`
path gains an `InsertMedia` call). The realtime idle fast-path is still a single pointer test.
`main.cpp` is now pointers + entry + dispatch only.
**Depends on:** Q-W1. Independent of Q-W2.
- [ ] Hoist capture orchestration → `capture_orchestrator` (`shell/capture/`); keep
`FxBypassGuard` a **stack RAII** object as it moves (precision-invariant-critical).
- [ ] Hoist scope/source resolution + provenance assembly inputs → `scope_resolve`.
- [ ] Hoist the realtime-capture lifecycle state machine + globals + the two RAII selection
guards → `realtime_lifecycle`; **idle tick stays a single pointer test.**
- [ ] Leave `main.cpp` = API-pointer ownership + `ReaperPluginEntry` + dispatch; move to `app/`.
- [ ] Verify in DAW: null test nulls, bit-identical repeats match, capture≠placement holds;
CTest green; no realtime-tick branch-shape change.
## Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops`
**Goal:** Split the two unrelated command-id families in one TU (981 LOC) into
`design_view_actions` / `bank_actions` / `prune_action`, and **dedupe** `actions.cpp`'s own
`promptText`/`mintBankId` and bank verbs against the `panel_bank_ops` single-owner established in
Q-W2. `prune_action` keeps the `doBankPruneFolder` deletion authority contract intact (routes to
`persist`'s `prune_fs` after W5). CONTEXT.md §Phase Q (actions split seams; bank-verb dedupe).
See `docs/product/code-organization.md` §2.1, §2.4.
**Verify:** CTest green at every commit. Every action fires identically in DAW (Design View
family; multi-bank create/rename/reorder/delete/evacuate/activate/move/copy/remove; prune). The
bank-CRUD verbs have **one** implementation home (no `bank_panel`/`actions` duplication). Each
bank verb still wraps its mutation in one batched undo point; the prune action still writes no
ext state and opens no undo point. Command-id strings are **unchanged** (FOREVER-STABLE
contract — a reorg must not touch a shipped command id).
**Depends on:** Q-W2 (`panel_bank_ops` is the dedupe target). Independent of Q-W3.
- [ ] Split → `design_view_actions` (toggle/activate/tag/untag/showBoth/moveItems),
`bank_actions` (bank CRUD family), `prune_action` (`doBankPruneFolder` — the single
file-deletion action).
- [ ] Dedupe `actions.cpp`'s `promptText`/`mintBankId` + bank verbs against `panel_bank_ops`
(one owner); do **not** change any command-id string.
- [ ] Verify in DAW: all action families fire unchanged; one bank op = one Ctrl-Z; prune still
no-undo/no-ext-state; CTest green.
## Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`)
**Goal:** Split `persist.cpp` (766 LOC, 5 responsibilities) into `session` (lifecycle+poll,
`BeginLoadProjectState` reload hook), `ext_state_io` (the ext-state ↔ JSON serialization bridge +
GUID minting + folder relocation), and **`prune_fs`** (prune scanning + `deleteOrphanFile` via
`SHFileOperationW`). The split **concentrates** the byte-deleting authority into one obvious
module — it must never spread it. CONTEXT.md §Phase Q (persist split seams; deletion-authority
isolation). See `docs/product/code-organization.md` §2.1, §7.
**Verify:** CTest green at every commit. Session save/load/undo-reload, ext-state round-trip,
folder relocation, and prune deletion all behave identically in DAW. **File deletion lives in
exactly one module (`prune_fs`)** — the single-file-deletion-authority invariant is *improved*
(concentrated), never diluted. Relative-paths-only persistence is unchanged.
**Depends on:** Q-W1. Best after Q-W4 (so `prune_action` routes cleanly to `prune_fs`), but
independently landable.
- [ ] Split → `session` (lifecycle/poll + `projectconfig` reload hook), `ext_state_io`
(serialization bridge + GUID minting + folder relocation).
- [ ] Isolate prune scanning + `deleteOrphanFile` (`SHFileOperationW`) → **`prune_fs`** — the
one file-deletion module; nothing else may delete bytes.
- [ ] Verify in DAW: save/load/undo-reload/relocation/prune unchanged; deletion authority is one
module; relative-paths-only holds; CTest green.
## Q-W6 — OCP registration-table + residual fat-header (I) splits
**Goal:** Close the last SOLID wart: replace the ~350-line hand-written **non-table** action
registration blocks (now isolated in `app/main.cpp` after Q-W3) with a **registration table**, so
adding an action edits one place, not four parallel ones (OCP). Split any remaining fat headers
(`capture.h`/`persist.h`) not already resolved by their TU splits (I). CONTEXT.md §Phase Q (OCP
registration-table). See `docs/product/code-organization.md` §2.3, §6 (Q-6).
**Verify:** CTest green at every commit. Every action still registers, appears in the Actions
list, and fires via `hookcommand` exactly as before; command-id + display strings unchanged
(FOREVER-STABLE, per-channel); unload still mirror-unregisters everything. Adding a hypothetical
new action now touches the table only (demonstrated in review, not shipped). Remaining fat
headers are segmented.
**Depends on:** Q-W3 (registration code must be isolated first). Sequenced last; the most
droppable point if the phase needs narrowing (Q-6).
- [ ] Convert the hand-written `Register("command_id"/"gaccel"/"hookcommand")` blocks to a
data-driven registration table; unload mirror-unregisters from the same table.
- [ ] Split residual fat headers (`capture.h`/`persist.h` and any other) alongside their TUs (I).
- [ ] Verify: all actions register/fire/unregister unchanged; command-id strings untouched; CTest
green.
## Phase Q — sequencing
```
GATE: Phase S + Phase L (L2+L3) + D2 residuals + M9 all merged/closed to dev (tree quiescent)
Q-W1 (safe opener: core/json extract + directory/namespace layout on clean modules)
├─► Q-W2 (split bank_panel) ──► Q-W4 (split actions + dedupe bank verbs vs panel_bank_ops)
├─► Q-W3 (split main.cpp; hoist orchestration) ──► Q-W6 (OCP registration-table + I splits)
└─► Q-W5 (split persist; isolate prune_fs) [best after Q-W4]
```
W1 is the safe, high-leverage opener (all later waves assume the layout it establishes). The four
god-module splits (W2W5) are risk-ordered and mostly parallel-safe; W4 depends on W2's
`panel_bank_ops`, W6 depends on W3's isolated registration code. Big-bang is rejected — every wave
is independently landable and CTest-green.
## Phase Q — must-verify-before-build
- **Hot-path call/inline shape** — before landing each split, confirm no virtual dispatch and no
header→TU indirection was added on `peaks` envelope compute, audition/preview, or the realtime
tick. `computeEnvelope` stays a free function on `const std::vector<float>&`;
audition stays a direct call-through; the idle tick stays a single pointer test. (CONTEXT.md
§Phase Q, `docs/product/code-organization.md` §3.)
- **Command-id + display strings are FOREVER-STABLE** — a reorg must not change a shipped
`command_id` string, action display name, ext-state namespace, or VST3 class UID. Re-namespacing
C++ symbols is orthogonal to these on-the-wire/on-disk contract strings; keep them byte-identical
(per-channel, per the Phase V V4 contract). (CONTEXT.md §Phase Q.)
- **Name-collision sweep on sub-namespacing** — `Sample` (model) vs `AudioSample` (audio) vs the
unified `Parser` (json), and any other cross-lib name that collides once flattened into
granular namespaces. Resolve by each symbol's new subsystem home before landing W1. (audit §2.4;
`docs/product/code-organization.md` §6 Q-4.)
- **The GATE** — do not begin any Q point until Phase S + Phase L (L2/L3) + D2 residuals + M9 are
merged/closed and the tree is quiescent. Re-confirm quiescence against dev before W1.