From 02319041e3248dad2dd8b36ed651faaf7dee90c6 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 2 Aug 2026 18:27:54 -0400 Subject: [PATCH] docs: retire Phases Theta, Xi, Psi and Epsilon from the plan All four are landed in full. Their narratives live in COMPLETED.md; the plan now carries only Gamma and Lambda. Cross-references and VERIFICATION.md citations repointed. --- docs/COMPLETED.md | 27 +- docs/PLAN.md | 856 +------------------------------------------ docs/VERIFICATION.md | 52 +-- 3 files changed, 63 insertions(+), 872 deletions(-) diff --git a/docs/COMPLETED.md b/docs/COMPLETED.md index 5bc5c87..80e52e3 100644 --- a/docs/COMPLETED.md +++ b/docs/COMPLETED.md @@ -818,9 +818,28 @@ own refs table. Pre-existing — `bakeWindowNeedsHold` is only a new *consumer* Seven tracks across three waves, code-complete, reviewed, remediated, and integrated on this branch: 89/89 tests passing, a clean build. Phase Ψ came from a direct list of -seven defects and refinements (Daniel, 2026-08-01) rather than a backing product doc — -see `docs/PLAN.md`'s Phase Ψ section for the Ψ.1–Ψ.7 provenance list this phase traces -back to. +seven defects and refinements (Daniel, 2026-08-01) rather than a backing product doc, so +the list is carried here verbatim rather than by reference, now that `docs/PLAN.md`'s +Phase Ψ section (its original home) is retired: + +> **Ψ.1** — item and track captures should be named (labeled) after their source track +> name, plus a discriminator (date, etc.); currently they aren't named anything useful. +> **Ψ.2** — Design vs. Arrange modes: any SOLO state in one mode is cached and removed +> when switching to another mode, disjoining the solo surfaces. +> **Ψ.3** — the Design/Arrange active-mode toggle is gated if playback is running; only +> allow the switch when the project is not playing. +> **Ψ.4** — the action that moves a Media Explorer item to a new ReaSampler on the +> selected track is not in the Media Explorer action category, so it cannot be added to +> the Media Explorer toolbar; fix this. +> **Ψ.5** — drag-and-drop targets are inexact: sometimes dropping into the arrange +> doesn't work, sometimes dropping into the FX area doesn't work; dragging between banks +> is fine. +> **Ψ.6** — capture into mono: if the left and right channels of a new capture are +> bit-identical, collapse to mono — one channel of data, mono arrange items, ReaSamplers +> load in mono mode. +> **Ψ.7** — capture item / capture track with a small time selection on a large item +> captures the entire item, not the selection/razor; make capture regions consistent and +> correct. **Ψ-W1-T1 — `capture-range-exactness`.** A ranged item capture now renders the requested window instead of the whole item, by re-sourcing through the selected-tracks @@ -884,7 +903,7 @@ has been confirmed in a running REAPER. Several rest on a **shared unverified inference** about how REAPER's selected-tracks render source interacts with custom time bounds — and Ψ-W3's refusal now rests on it too, meaning if the inference is wrong that refusal costs a working capture. Each track's DAW-verification obligation -is recorded in `docs/PLAN.md`'s Phase Ψ section; `docs/verify-track-scope-multitrack.md` +is recorded in `docs/VERIFICATION.md`; `docs/verify-track-scope-multitrack.md` is a new standalone verification script on this branch, for Ψ-W3-T1's multi-track refusal specifically. No human has observed any of these seven behaviors in a DAW. diff --git a/docs/PLAN.md b/docs/PLAN.md index 54b61c4..77eaacd 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -242,345 +242,6 @@ touches them. The instrument adds a sixth surface that binds every Θ track: --- -## Phase Θ — ReaSampler 9000: one parameter set, a filter, shapeable envelopes, a legible editor - -**Ships:** the instrument with the zone system retired, a resonant HP/LP filter stage in -the voice path, curve-shapeable and spline-drawable envelopes on all three EGs, -Gate-mode loop sustain, and a re-laid, high-DPI-clean editor — plus the two extension -drag/drop defects that block getting captures into it. - -**Consolidates items** 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16. - -**All seven waves have landed — Phase Θ is complete.** W1 through W7 each carry their own -landed note above; see `docs/COMPLETED.md` for every track's full narrative. Θ-W7 was -opened after Θ-W6-T1 shipped, to fix two rendering defects Daniel found by eye; it did not -exist in the plan when this phase was originally scoped. - -**The organizing constraint.** Three surfaces in the instrument are single-writer by -nature and dictate the wave shape: - -1. **The parameter model** (`zone_params.h` + `component_state_io`) — every parameter - addition touches both. Two tracks adding parameters concurrently is a merge fight - and two competing `ComponentState` version bumps. -2. **The voice render path** (`sampler_core.cpp`) — filter insertion, envelope - evaluation, loop read, and the Trigger tail all live there. -3. **The Sample face** (`editor_paint_sample.cpp` 516 lines / `editor_input_sample.cpp` - 583 lines) — every UI item repaints it, and both are already at the ceiling. - -Parallelism in this phase therefore comes from **splitting the Sample face into bands** -(done once, in Θ-W1-T1) and from **extension-side work being genuinely disjoint** — not -from running two parameter-model tracks at once. Where a wave has one track, the -collision is real and the serialization is the correct answer. - ---- - -### Θ-W1 — Collapse and re-seam - -**All three tracks have landed** — Θ-W1-T1 (`zone-retirement`), Θ-W1-T2 -(`capture-handoff-bugs`), and Θ-W1-T3 (`filter-dsp-port`) — see `docs/COMPLETED.md` for -the full narrative of each. Between them: the zone subsystem is retired, the wave's two -responsibility seams (the `sampler_core` split, the Sample-face band split) are in -place, the two extension-side capture-handoff defects are fixed, and the filter DSP has -landed as a standalone pure module — Θ-W2-T1 has since wired it into the voice path (see -below). - ---- - -### Θ-W2 — Filter in the voice path; waveform and chrome bands - -**Depends on W1 for:** the one-parameter-set model and the `sampler_core` seam that T1 -writes the filter into; the `filter` module T1 wires up; the Sample-face band split and -band-stack allocator that T2 and T3 fill; and W1-T1's key-range answer, which decides -what T3's piano strip displays. Authoring any of this against the per-zone model means -writing storage plumbing W1 deletes. - -**All three tracks have landed** — Θ-W2-T1 (`filter-voice-path`), Θ-W2-T2 -(`stereo-waveform-lanes`), and Θ-W2-T3 (`toolbar-and-piano-strip`) — see -`docs/COMPLETED.md` for the full narrative of each. Between them: the filter sits in the -per-voice signal path with its own deck, the waveform band shows both channels in -stereo mode behind a type-enforced full-height overlay contract, and the chrome band's -toolbar and piano strip are cleaned up per spec. The three tracks were disjoint by -band — T1 owned the parameter model and the deck band, T2 owned the waveform band, T3 -the chrome band — and none re-allocated the band stack. - ---- - -### Θ-W3 — Live parameters, then the staged envelope system - -**Depends on W2 for:** the filter envelope's existence — items 1, 8, and 14 govern -*three* envelopes, and the filter is the third; and for the Filter deck, which must -exist before it can receive a corner radio switch and inner curve dials. T1 additionally -depends on W2-T1 for the filter itself: the filter is the first control set where the -latched-at-note-on delivery model fails audibly, and it is what made the defect visible. - -**Both tracks have landed** — Θ-W3-T1 (`live-parameter-delivery`) and Θ-W3-T2 -(`staged-envelope-curves`) — see `docs/COMPLETED.md` for the full narrative of each. -Between them: every continuous playback control (filter cutoff/Q/morph/drive/mod -amount/key-track, every stage time and level on all three envelopes) now reaches a -sounding voice live instead of latching at note-on, and the envelope-overlay editor -grew from an amp-only fixture into the shared graphical surface for all three -envelopes — a corner radio switch per deck (none active by default), curve-shapeable -segments on every sloped stage (0.1–10 exponent, an inner dial paired with an overlay -knot), the release-right-anchored AHDSR layout against the pitch envelope's 1:1 AHD, -and the Trigger amp/filter fade pair folded into a Trigger AHD, consolidating what -were two staged-shape mechanisms into one. The Trigger × Preserve end-of-sample click -is fixed; the landed fix is wider than scoped, also ringing out Gate × Preserve × -source-exhaustion, previously a hard cut. The ordering was deliberately serial — T1's -live-delivery mechanism landed first so T2's new curve exponents and Trigger AHD -fields were authored directly into it rather than backfilled afterward. - ---- - -### Θ-W4 — Loop sustain and the velocity deck - -**Depends on W3 for:** the envelope system both tracks compose with — T1's loop-sustain -is the Gate-mode sustain the AHDSR releases out of, and T2's bipolar pitch/filter curves -modulate targets whose envelopes W3 just reshaped. T2 additionally depends on W2-T1 for -the filter's existence and on W2-T3 for the preview button's toolbar position. - -**Both tracks have landed** — Θ-W4-T1 (`gate-loop-sustain`) and Θ-W4-T2 -(`velocity-deck-and-bipolar-curves`) — see `docs/COMPLETED.md` for the full narrative of -each. Between them: loop points are now a usable feature, with a Gate-mode loop acting -as the sustain and a parameterized crossfade at the seam; and the three velocity-curve -popups (amp, pitch, filter) now live together in a new VELOCITY deck group, with the -pitch and filter curves bipolar and flat-by-default so their modulation is off until -drawn, while the amp curve stays unipolar and unchanged, and the preview button's text -is replaced by a drawn play-triangle glyph. Params payload reached v11 with T1's loop -block and v12 with T2's velocity→pitch curve appended after it. - ---- - -### Θ-W5 — Spline EGs - -**Depends on W4 for:** the bipolar velocity-curve domain (W4-T2) — the spline algorithm -is **singly implemented and multi-referenced**, so its enhancement must land against the -final consumer set, and the last consumer to change domain is the pitch/filter velocity -curve; and for the Gate-mode loop (W4-T1), since "Gate is unavailable in Spline mode" is -only a real, testable rule once Gate has something to be unavailable *for*. It also -depends on W3's radio switch, which is how a spline contour reaches the overlay at all. - -**Θ-W5-T1 has landed** — `spline-egs` — see `docs/COMPLETED.md` for the full narrative. -It shipped a free-drawn alternative to every staged envelope: pitch, filter, and amp -EGs can switch Staged → Spline and have their contour drawn directly on the waveform -overlay. The one shared monotone-spline implementation gained hard points — sharp -corners, no smoothing on either adjacent segment — flowing to every consumer including -the existing velocity→amp transfer curve, no fork. Both Staged and Spline state persist -simultaneously (saved-but-inactive, lossless round-trip); params payload reached v13, -and v12 projects still load. Gate is unavailable while a Spline EG is active; the -contour is a pure time function over the full sample length, normalized and drawn 1:1 -with the sample's time axis. Point grammar converged on left-click add / right-click -delete / control-click hard-smooth toggle, one grammar across both spline consumers. -Staged segment knobs and their inner curve dials render disabled and reject edits while -Spline is active. Point-count ceiling: 128, a musical bound rather than a performance -one. A follow-on change in the same track reworked deck cell width: `-1` in `cellIds` -now means one cell's width, reserved and redistributed, rather than a blank cell holding -geometry — a Trigger face that drops Sustain and Release gets wider cells instead of -dead slots; group widths, row packing, deck height, and Gate-mode cell widths are -unchanged. - ---- - -### Θ-W6 — Editor legibility pass - -**Depends on W5 for:** the last change to a drawn surface. Item 13 is an audit whose -output is a disposition list over "every class of drawn surface," and item 10's sizing -pass is judged by eye over the finished layout — running either while the spline contour, -the disabled-knob state, or the deck inventory was still moving would have meant -auditing and then re-auditing. Θ-W5-T1 has landed (see `docs/COMPLETED.md`), so that -surface has stopped moving and this dependency is satisfied. The source doc names this -sequencing as an observation (item 13 after the layout/knob work); this plan adopts it -as the boundary. - -**One track.** Both items repaint essentially every surface in the editor; concurrent -tracks would collide everywhere. - -**Θ-W6-T1 has landed** — `legibility-and-antialiasing` — see `docs/COMPLETED.md` for the -full narrative. It shipped both halves together: knobs grew 28→40 px (inner curve dial -14→20), the deck cell 48×58→60×74, and the label band 12→16 px, now drawn in -`Font::Label` rather than `Font::Micro` — group captions and toggle segments deliberately -stayed `Font::Micro`, since bumping them would outgrow row 1's headroom at the floor -width. The editor's default/minimum size grew 840×620→980×680 to fit the wider deck at -floor width; an existing saved instance's window grows on open, and the floor is -validated by a derived test rather than literals. All fourteen time-constant labels now -read in ms (display-only; internal representation untouched) — `holdFraction` knobs, -`Len %`, and the bank panel's duration readout stayed out of scope. Double-click resets -each ring independently — outer ring resets the value, inner dial resets the exponent to -1.0 — reaching the chrome's preview-velocity knob too via a shared `inKnobFace` rule. - -The antialiasing audit fixed knob arcs and needle, the inner dial arc and needle, staged -and spline envelope slopes, the spline contour, the velocity-popup trace, the waveform -outline, and the preview triangle; node handles, curve knots, knob discs, buttons, piano -keys, loop markers, borders, gradients, and text were already clean. The disposition -table is a standing artifact in `docs/product/visual-design-language.md` §8. The -piano-key open question is answered: not aliasing — every key is an axis-aligned -`LICE_FillRect`, so the earlier width defect was integer-division residue in the tiling, -not a sloped edge. High-DPI host scaling itself is unverified (deferred, see -`docs/TODO.md`). - -All visual outcomes remain pending Daniel's by-eye sign-off on `dev`. - ---- - -### Θ-W7 — Arc-and-spline antialiasing fix - -**Depends on W6 for:** a drawn surface to find a defect on — this wave did not exist in -the plan; it was opened after Daniel found two rendering defects by eye once Θ-W6-T1 -shipped, so the audit's own output is what surfaced them. - -**One track.** - -**Θ-W7-T1 has landed** — `arc-and-spline-aa` — see `docs/COMPLETED.md` for the full -narrative. The stacked-`LICE_Arc` knob and dial rings never reached an opaque core (peak -alpha measured 138/255), and the staged/spline envelope and velocity-curve traces were -fully aliased rather than gapped, from integer `cy` quantizing the slope. Both defects, -plus the two needles, now route through one pure analytic thick-stroke rasterizer — -coverage in a new `core/ui/stroke_aa`, the single LICE blend in a new -`shell/instrument/editor_stroke` — replacing `LICE_Arc` and `LICE_ThickFLine` outright. -Measured: arc peak alpha 138/255 → 255/255, arc perpendicular-weight ripple 67% → 5%, -spline weight ripple 29% → 3%, at a cost of +0.09 ms per full editor repaint. Daniel then -ruled every sub-2 px stroker width up to 2 px, since the stroker only guarantees an -opaque core at width ≥ 2 px; the knob track arc, the inner-dial needle, and the deck's -mini velocity trace each moved 1.0 → 2.0 px. `docs/product/visual-design-language.md` §8 -is corrected — a false "keeps every ring antialiased" claim is deleted, the rows are -re-dispositioned with measurements, and the audit's methodological lesson (verifying -which primitive was called is not verifying what it rasterized) is recorded as a standing -blockquote. All visual outcomes remain pending Daniel's by-eye sign-off on `dev`. - ---- - -## Phase Ξ — The resample loop - -**Ships:** one consolidated, fully robust provenance/usage tracking system, and on top of -it the one-click in-sampler resample — dial → bake → dial again, with the bank as the -medium each iteration passes through. - -**Consolidates items 15, 17.** - -**Why these two and not more.** Item 17 is a **prerequisite of meaning** for item 15's -bank-side half: the replace-vs-add rule is "does provenance-tied usage exist," which -denotes nothing until the consolidated lineage records exist. Item 17 is otherwise -independent of the editor chain — which is what makes Ξ-W1 concurrency-safe with Θ. - -**Ξ-W2 onward requires Phase Θ complete.** Item 15 presupposes the processing surface it -bakes — "filtering, pitching, amp all set up nice" is the instrument items 1, 2, 3, and 14 -build — and its settled reset scope enumerates the filter parameters, the spline contours, -and the loop points by name. Baking a processing chain that does not exist yet is not a -schedule preference; the feature is not expressible. Phase Θ landed as of Θ-W7-T1 (see -`docs/COMPLETED.md`); this gate is satisfied. - -**All three waves have landed — Phase Ξ is complete.** W1 through W3 each carry their own -landed note below; see `docs/COMPLETED.md` for every track's full narrative. - ---- - -### Ξ-W1 — Consolidated tracking, and the programmed-note model - -#### Ξ-W1-T1 — `tracking-consolidation` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. The provenance/usage -territory is now one system: a new `src/core/tracking/` directory holds `origin_ledger` -(the record family — `OriginRecord`/`OriginKind`, the insertion-ordered `OriginLedger`, -its JSON codec, and the `Fresh`/`Loaded`/`Unreadable`/`FutureVersion` load -classification) and `tracking_authority` (the one decision surface: `pruneProtection` -and `tiedUsageExists`), retiring `core/model/owned_manifest`. Both prune's protected set -and the resample's replace-vs-add decision are computed from one borrowed -`TrackingState`, so the two safety-critical consumers cannot drift apart. -`sample_usage` deliberately **stays in `core/wire`** — the consolidation is of the -decisions, not the codecs. The deferred persisted-instance-identity fix was **not** -folded in — the deferral is restated in `docs/TODO.md`, its one home. This track -landed `OriginRecord`'s birth-time `parentSampleId` chain as the lineage mechanism, but -**Ξ-W2-T1's "naming and lineage" open question (jointly held with this track) is -unaffected and still theirs to close** — display naming and user-readable iteration -lineage were not decided here. - -#### Ξ-W1-T2 — `note-program-model` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. The programmed-capture- -signal model is a new pure module directory, `src/core/instrument/note/` — a fourth -peer of `engine/`/`map/`/`ui/` under `core/instrument/` — holding `musical_division`, -`tempo`, and `note_program` (`Velocity`, the denominated `OffsetAmount`, the anchored -`StartOffset`/`EndOffset`, `NoteProgram`, `resolveNote`). **Both open questions below -are answered, for Ξ-W3-T1:** negative offsets are legal in both directions (sign -uniform, positive is later in time; only an inverted window is refused, reported via -`ResolvedNote::windowCollapsed`), and the denomination seam is confirmed as of this -landing — note length stays musical-division-only, and an offset stores the denomination -it was entered in. **Superseded by Ξ-W3-T1's landed work** (see `docs/COMPLETED.md`): -`note/CLAUDE.md`'s musical-division-only rule is amended — a note length now carries -EITHER an exact derived duration or a musical division, not division-only. The offset -denomination rule is unaffected. - ---- - -### Ξ-W2 — The bake chain - -**Depends on Ξ-W1 for:** the consolidated lineage records that make replace-vs-add -computable (T1) and the programmed-note record the offline pass renders (T2). **Also -depends on all of Phase Θ** — see the phase note above. - -**This wave ran AHEAD of Phase Γ, and that was not a choice.** The plan asserted Γ must land -first so the bake's reset list would be complete on the day it shipped; Ξ-W2-T1 was already -live. The consequence is owned, not absorbed: **Γ-W3-T2 `bake-reset-amendment`** completes -the list afterwards, and **Γ-W4-T1** adds the host-notification obligation once parameters -exist. Neither is this wave's work, and neither is a defect report against it. - -**One track.** The bake is one gesture and one chain; the architecture decision at its -head governs every step after it. - -#### Ξ-W2-T1 — `resample-bake-chain` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. The architecture decision -(this track's first deliverable) is ratified: **Decision 1 = (1b)**, the editor invokes -the extension's bake action directly over the VST-host bridge -(`NamedCommandLookup`/`Main_OnCommandEx`), no poller/nonce, dissolving the S13 DEGRADED -verdict; **Decision 2 = (2c)**, the instrument renders in-process and the extension -banks the file, taken over the plan's leaning toward (2a) on an engine-version-skew -argument. **Extension presence** resolved to cleanly-unavailable, not silently lossy — -the affordance refuses up front when the extension is not loaded. **Reset-scope edge -cases** classified against the ratified rule: play mode → RESET to Trigger, start point -→ RESET, channel mode and preview velocity → SURVIVE. **Phase Γ's additions remain -NOT this track's** — Γ-W3-T2 `bake-reset-amendment` still amends the reset list for -Γ's own new values, unaffected by this landing. **Naming and lineage** is proposed -(`Kick` → `Kick r2` → `Kick r3`) but not itself ratified — still open jointly with -Ξ-W1-T1's lineage-record question, which Ξ-W1-T1 answered on its own side -(`OriginRecord::parentSampleId`). Nothing was verified in a live REAPER session; -Daniel's manual verification is still owed. - ---- - -### Ξ-W3 — The capture-signal popup - -**Depends on Ξ-W2 for:** the bake chain this track was originally scoped to build a -preview against, under the acceptance criterion "preview and bake cannot diverge." **That -motivation is retired, not satisfied** — see below. - -**One track.** - -**Ξ-W3-T1 has landed** — `capture-signal-popup` — see `docs/COMPLETED.md` for the full -narrative, and it diverges substantially and deliberately from this section's spec. The -popup this track was scoped to build (a musical-division note-length picker, ms/beat- -editable offsets, velocity, and a preview trigger) was built (~1500 lines) and then -**abandoned unmerged** on Daniel's ruling: *"I didn't realize you had already derived a -usable window. The manual stuff for baking a specific midi length was just an idea, if we -have a smarter, fewer-clicks way of doing it, that is ideal. I just don't want to lose -anything when we bake. We can abandon the whole parameterized bake window if we can -safely derive the window in gate and trigger modes."* An audit, backed by executable -tests, established the window derives losslessly everywhere except Gate over an active -sustain loop, which has no intrinsic duration to derive. What shipped instead: the bake -window derives itself in Trigger and loop-less Gate; one control, a musical-division -**Hold** picker, covers the one irreducible case and is shown only for Gate-with-active- -loop (`bakeWindowNeedsHold`, reading the engine's own loop fold); bake velocity now reads -the instance's persisted preview velocity rather than a hard-coded value; and the -chrome-row play button stays a pure MIDI trigger — Daniel's ruling: *"play button is pure -MIDI trigger, Bake parameters are their own thing."* **This retires this section's -acceptance criterion 2 ("preview and bake cannot diverge") and its preview-trigger -behavior bullet by ruling, not by shortfall** — there is no popup and no preview trigger. -Three pre-existing truncation bugs were found and fixed along the way, and -`note/CLAUDE.md`'s musical-division-only invariant is amended (see Ξ-W1-T2 above): a note -length now carries either an exact derived duration or a musical division, not -division-only. - ---- - ## Phase Γ — The instrument's control surface **Ships:** the deck reflowed into two categorical rows with a double-height MASTER bus deck, @@ -1820,7 +1481,8 @@ this plan asserted. feature (see "Flagged for awareness" item 2). **Spec:** `docs/product/instrument-control-surface.md` §3.4, and Ξ-W2-T1's own "Reset scope" -block above — **which is the ratified rule this track applies, not a rule it may reinterpret.** +block, now in `docs/COMPLETED.md` (Phase Ξ has landed and its `PLAN.md` section is retired) — +**which is the ratified rule this track applies, not a rule it may reinterpret.** **Surface boundary — owns:** the bake's parameter-reset step, wherever Ξ-W2-T1 landed it, and its tests. **Does not own** the bake chain, the crossing architecture, the replace-vs-add @@ -2088,503 +1750,6 @@ value semantics, any deck geometry, or the bake's reset *membership* (W3-T2's). --- -## Phase Ψ — The extension trust pass: exact bounds, disjoint solo surfaces, reachable actions, honest drops, real names, true mono - -**Ships:** capture ranges that mean what was asked — a time selection or razor area over -a longer item captures the selection, both scopes; per-mode solo surfaces that cache, -clear, and restore across the Design/Arrange switch, with the switch itself refused while -the transport runs; the Media-Explorer import action registered into the Media Explorer -action section so it can live on that toolbar; a drag-out gesture law that resolves its -target from what is actually under the cursor, continuously and reversibly, with no -silent no-op release anywhere; captures labeled after their source track instead of the -literal `"item"`/`"track"`, surfaced on the panel card; and lossless mono collapse for -new captures whose channels are bit-identical. - -**Consolidates: none of the seventeen.** Phase Ψ came from a direct list of seven defects -and refinements (Daniel, 2026-08-01). There is no backing product doc — the design -content lives inline in the tracks below, and the seven are recorded here as the phase's -provenance, cited throughout as Ψ.1–Ψ.7: - -> **Ψ.1** — item and track captures should be named (labeled) after their source track -> name, plus a discriminator (date, etc.); currently they aren't named anything useful. -> **Ψ.2** — Design vs. Arrange modes: any SOLO state in one mode is cached and removed -> when switching to another mode, disjoining the solo surfaces. -> **Ψ.3** — the Design/Arrange active-mode toggle is gated if playback is running; only -> allow the switch when the project is not playing. -> **Ψ.4** — the action that moves a Media Explorer item to a new ReaSampler on the -> selected track is not in the Media Explorer action category, so it cannot be added to -> the Media Explorer toolbar; fix this. -> **Ψ.5** — drag-and-drop targets are inexact: sometimes dropping into the arrange -> doesn't work, sometimes dropping into the FX area doesn't work; dragging between banks -> is fine. -> **Ψ.6** — capture into mono: if the left and right channels of a new capture are -> bit-identical, collapse to mono — one channel of data, mono arrange items, ReaSamplers -> load in mono mode. -> **Ψ.7** — capture item / capture track with a small time selection on a large item -> captures the entire item, not the selection/razor; make capture regions consistent and -> correct. - -**Where the seven land:** - -| Ψ-item | Track | Worktree slug | -|---|---|---| -| Ψ.7 | Ψ-W1-T1 | `ppsi-w1-t1-capture-range-exactness` | -| Ψ.2 + Ψ.3 | Ψ-W1-T2 | `ppsi-w1-t2-mode-switch-discipline` | -| Ψ.4 | Ψ-W1-T3 | `ppsi-w1-t3-media-explorer-section` | -| Ψ.5 | Ψ-W1-T4 | `ppsi-w1-t4-drop-target-resolution` | -| Ψ.1 | Ψ-W2-T1 | `ppsi-w2-t1-capture-naming` | -| Ψ.6 | Ψ-W2-T2 | `ppsi-w2-t2-mono-collapse` | - -**Ψ-W3 is not one of the seven** — opened mid-phase, after Ψ-W2's review surfaced that -the track scope carried the same multi-track stem-collapse hole Ψ-W1-T1 had just closed -for item scope. It consolidates none of the original seven and carries no `Ψ-item` row -above; see Ψ-W3 below. - -**All three waves have landed — Phase Ψ is complete.** W1 and W2 each carry their own -landed notes below; W3 carries its own too. See `docs/COMPLETED.md` for every track's -full narrative. **None of the seven tracks is DAW-verified** — all are code-complete and -unit-tested, several resting on a shared unverified inference about how REAPER's -selected-tracks render source interacts with custom time bounds, which Ψ-W3-T1's -refusal now also rests on; each track's DAW-verification obligation is restated inline -below. - -Ψ.2 and Ψ.3 share one track deliberately: they share one chokepoint — `applyMode`, the -sole mode mutator (`shell/view/view.cpp:380-469`) — and one discriminator -(`targetModeId != model.activeModeId()`, the test that distinguishes a real switch from a -reapply). Splitting them is two tracks fighting over the same function. - -**Concurrency with Γ and Ξ.** Phase Ψ is extension-side. Γ and Ξ-W3 live in -`core/instrument/` + `shell/instrument/`; the surfaces are disjoint with ONE named -exception — Ψ-W2-T2 touches `shell/instrument/processor_reload.cpp` for a stale comment -and an index/file consistency check, and that touch is bounded to the minimum in its -surface boundary precisely because Γ is live in that directory. - -**Three invariant amendments were DELIVERABLES of this phase, not asides.** Each landed -in its owning track, as an acceptance criterion of that track: - -1. **The never-touch-solo rule** (Ψ-W1-T2): `src/shell/view/CLAUDE.md:14-17`, - `src/core/view/CLAUDE.md:10`, and `docs/product/design-view.md:160-165` + `:588-592`. - Ψ.2 requires writing `I_SOLO`; the amended form is stated in the track. A track that - lands solo writes without the amendment reads as an invariant breach in review. -2. **The action-registration contract** (Ψ-W1-T3): root `CLAUDE.md` - §"REAPER extension contract" documents only the main-section 4-step pattern; the - second, non-main mechanism (`custom_action` + `hookcommand2` + `-custom_action` - mirror) must be added beside it. -3. **The channel-count-preserved rule** (Ψ-W2-T2): root `CLAUDE.md:208` — "channel count - preserved (no silent stereo fold)" — is contradicted in text (not in spirit) by a - lossless bit-identical collapse, and was already untrue in the other direction (a mono - source renders at `RENDER_CHANNELS=2` today). The amended form is stated in the track. - -**Performance posture.** Every Ψ surface is cold — per-capture, per-click, -per-mode-switch, per-mouse-move. None of the named hot paths (peaks envelope compute, -audition, realtime-capture tick, instrument `process()`) is touched. Two disciplines -carry anyway: the realtime tick's single-pointer-test idle fast path is untouched by -Ψ-W1-T1's render work, and Ψ-W1-T4 keeps the inside-client drag path free of SDK -hit-tests, exactly as today (`panel_drag.cpp:273-274` — "costs nothing on the common -internal-drag path"). - ---- - -### Ψ-W1 — Exact bounds, disciplined switches, reachable actions, resolved drops - -**Depends on:** nothing in this phase. - -#### Ψ-W1-T1 — `capture-range-exactness` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. A ranged item capture now -renders exactly the requested window instead of the whole item, by re-sourcing through -the selected-tracks render when the item extent does not already print the window. -**Deviation:** the spec named two candidate architectures (re-source vs. render-then- -trim); the engineer shipped a conditional form of the re-source candidate — the -full-extent case runs literally unchanged code, keeping the byte-identity regression -floor structural and the fix cheap to revert if the override inference proves wrong. -Also landed: a transient isolation guard (cutting `B_MAINSEND` on direct folder -children, muting receives) so an item capture stays true to item scope, and a -post-render frame-count gate (±1 tolerance, tail-None only) that refuses a widened -render and retains it outside the bank for diagnosis rather than deleting it. New modules `core/capture/render_window`, `core/capture/track_topology`, -`shell/capture/render_selection`, `shell/capture/render_isolation`. The whole fix rests -on the unverified inference that REAPER's selected-tracks render source overrides -custom time bounds — Ψ-W3-T1 (below) now also depends on it. **DAW-verification -obligation, unmet:** the four-cell scope × selection-type matrix over a source item -substantially longer than the selection (tail None), plus one razor-union case — none of -it run in a live REAPER session yet. - -#### Ψ-W1-T2 — `mode-switch-discipline` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. Per-mode SOLO surfaces now -cache, clear, and restore across a Design/Arrange switch, with the switch itself -refused, visibly, while the transport is playing or recording. The never-touch-solo -invariant — stated three times (`src/shell/view/CLAUDE.md`, `src/core/view/CLAUDE.md`, -`docs/product/design-view.md`) — is amended, in this track, to the snapshot sense of -non-destructive: solo is cached per mode on a real switch and restored verbatim, not -left untouched absolutely the way `B_MUTE` and the master track are. Also closed: a -pre-existing bug where a footer mode-segment click never persisted view state. New -`core/view/solo_cache`, `shell/view/view_solo`. **DAW-verification obligation, unmet:** -the disjoint-surface solo matrix, the playback-gated refusal (playing and recording), -and the panel-persist case — none run in a live REAPER session yet. - -#### Ψ-W1-T3 — `media-explorer-section` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. The Media Explorer import -action now registers into REAPER's Media Explorer action section (32063) via -`custom_action` + `hookcommand2`, alongside its existing Main-section entry so existing -keybindings survive — a second FOREVER-STABLE id, `INGEST_IMPORT_MEDIA_EXPLORER_MX`, -minted per channel. Root `CLAUDE.md`'s "REAPER extension contract" is amended, in this -track, with the second, non-main registration mechanism beside the original four-step -main-section pattern. **DAW-verification obligation, unmet:** adding the action to the -Media Explorer toolbar and firing it from there; confirming the Main-section binding -still fires; and confirming unload/reload does not leak a duplicate Media Explorer entry -(the `-custom_action` unload mirror is unconfirmed against the SDK header) — none run in -a live REAPER session yet. - -#### Ψ-W1-T4 — `drop-target-resolution` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. The drag-out gesture is now -a per-move, stateless law: target class resolves from what is under the cursor on every -move, every class transition is reversible until release or until the pointer leaves -REAPER, and the OS hand-off is reserved for leaving REAPER entirely — the whole TCP/MCP -is now an instrument-drop hotspot, and a single-card arrange drop lands a timeline item -at the pointer's track and time. New `shell/actions/arrange_drop_win`. -**DAW-verification obligation, unmet:** the full target-class matrix (single- and -multi-card), reversibility across a drag that crosses the arrange en route to an FX -window, drag-speed independence, and the narrow-TCP case — none run in a live REAPER -session yet. - ---- - -### Ψ-W2 — Names and channels, over the settled render block - -**Depends on Ψ-W1 — specifically Ψ-W1-T1**, which rewrites the capture -render-configuration block in `shell/capture/capture.cpp` and -`core/capture/render_settings.cpp`; both W2 tracks edit adjacent regions of those same -TUs, so W2 dispatches only after W1-T1 is on `dev`. (T2–T4 of W1 gate nothing here; the -wave boundary is the file collision, not a semantic dependency.) - -#### Ψ-W2-T1 — `capture-naming` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. Captures are now named -after their source track's name plus a discriminator -(` [+N] [#ordinal] MM-DD HHMM`) at every interactive mint site, with the name -shown on the panel card over a scrim clearing the 4.5:1 contrast floor. New -`core/capture/capture_name`. Recapture, ingest, and the bake deliberately keep their own -naming — the bake's naming-and-lineage open question stays Ξ-W1-T1's/Ξ-W2-T1's to close, -untouched here. **DAW-verification obligation, unmet:** capturing from a named track, an -unnamed track, and a multi-item selection, and confirming the labels show on the card -and in the instrument — none run in a live REAPER session yet. - -#### Ψ-W2-T2 — `mono-collapse` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. A capture whose channels -are bit-identical now collapses losslessly to one mono channel, written via temp file -plus atomic rename, with `Sample::channelCount` measured off the landed file rather than -echoed from the request on every capture path — including realtime, which previously -parsed the layout and echoed the request anyway. Root `CLAUDE.md:208`'s -channel-count-preserved invariant is amended, in this track, to the landed wording: -channel count preserved, except that bit-identical channels may collapse losslessly to -mono; a lossy fold remains forbidden. The `shell/instrument/processor_reload.cpp` touch -stayed to the minimum named in its surface boundary (Phase Γ was live in that -directory). **Open, not closed here:** whether bake landings collapse too — `[propose]`, -leaning yes, still deferred to be confirmed against what Ξ-W2-T1 actually shipped. -**DAW-verification obligation, unmet:** capturing a dead-center mono source and a -true-stereo source, inserting both, loading both into the instrument, and running the -null test on the collapsed one (REAPER's mono-item-on-stereo-track summing at unity is -the specific thing to confirm) — none run in a live REAPER session yet. - ---- - -### Ψ-W3 — Closing the track-scope stem-collapse hole - -**Depends on Ψ-W2 for:** existing at all — this wave did not exist when the phase was -scoped. Daniel opened it after Ψ-W2's review surfaced that the track scope carried the -same multi-track stem-collapse hole Ψ-W1-T1 had just closed for item scope. - -**One track. Consolidates none of the seven** — it came from a review finding, not from -Ψ.1–Ψ.7. - -#### Ψ-W3-T1 — `track-scope-range` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. Any multi-track -selected-tracks render now refuses, in both scopes, keyed on the render *source* rather -than the capture scope — closing the hole Ψ-W1-T1 left open for track scope. Realtime -deliberately diverges and was left untouched, because it sums correctly. The refusal -rests on the same unverified inference Ψ-W1-T1 rests on — that REAPER's selected-tracks -render source overrides custom time bounds — so if that inference is wrong, this refusal -costs a working capture. `docs/verify-track-scope-multitrack.md` is a new standalone -verification script on this branch, for this track's multi-track refusal specifically. -**DAW-verification obligation, unmet:** confirmed nowhere in a live REAPER session yet. - ---- - -## Phase Ε — The bank package: one file that carries a bank between projects - -**Ships:** a bank exported to a single version-tagged `.rsbank` file — audio bytes -byte-exact, index metadata intact — and imported into another project's bank folder and -index, with a compatibility policy that names both directions concretely: an older package -in a newer build always imports, a newer package in an older build refuses whole with an -actionable message, and neither direction is ever a partial landing. - -**Consolidates: none of the seventeen.** Phase Ε came from a direct request (Daniel, -2026-08-02) and is scoped in `docs/product/bank-package.md`. Nothing in `docs/TODO.md` or -`docs/TODO-1.0.md` records export, import, or a package format — a sweep of `docs/` for -`export|package|portable` returns only unrelated matches (prune's portable move-to-trash, -MIDI-playback prose) — so this phase **supersedes nothing and absorbs nothing.** - -**Three forks were opened at framing and all three are RULED** (Daniel, 2026-08-02). The -rationale for each — including why two went against the framing recommendation — is -recorded at `docs/product/bank-package.md` §"Rulings"; the binding specification for each -is in the section of that doc named below. - -| Fork | Ruling | Specified in | Bound into | -|---|---|---|---| -| **Ε-F1** | **Proprietary container** — the hand-rolled `RSBK`. ZIP via the vendored MiniZip64 (`vendor/WDL/WDL/zlib/`) and a hand-written stored-only ZIP shape are both rejected and are not to be revisited in this phase | §"The container" | Ε-W1-T1 | -| **Ε-F2** | **Import always lands as a new bank**, with an automatic numeric suffix on a display-name collision — no prompt, no overwrite. Merge-into-existing is **out of scope for Phase Ε**, not deferred | §"Identity and collision on import", incl. the auto-suffix rule | Ε-W2-T2 | -| **Ε-F3** | **Refuse** the import when the tracking ledger is degraded. No confirm-and-proceed path, no opt-out | §"Import under a degraded tracking ledger" | Ε-W2-T2 | - -**No Ε track is gated on a decision.** Every track in this phase is dispatchable as -written. - -### Phase-Ε acceptance criteria - -These bind every track in this phase, in addition to the plan-wide set above. - -- **Byte-exact round-trip is the phase's trust anchor.** Export → import → export yields - byte-identical payloads, and the `hashBytes` digest of every landed file equals the digest - recorded at export. Frame count, sample rate, bit depth, and channel count are untouched on - both sides. **No re-encode anywhere:** `wav_codec` may be called to hash and to read - metadata already recorded, never to rebuild, trim, normalize, or collapse. The mono collapse - is a capture-path behaviour and must not reach the import path — the same exclusion ingest - already carries (root `CLAUDE.md`, exact-bounds invariant). -- **The format cannot express a path.** Manifest entries are bare file names — no directory - component, no `..`, no drive letter, no leading separator — validated on encode *and* - decode. Relative-paths-only becomes structural rather than remembered, and the - archive-traversal bug class closes by construction. -- **Import places no timeline item.** Capture and placement stay separate acts; import is a - capture-shaped act, not a placement one. -- **Import never overwrites and never deletes an existing bank-folder file.** The one - deletion path is the rollback of files *this call wrote* that no index ever referenced — - the documented carve-out at `src/shell/persist/prune_fs.cpp:5-11`, which every track - touching it must **cite, not restate**. -- **Export is read-only against the project.** No ext-state write, no `bumpBankGeneration()`, - no undo point. Import does the opposite: it bumps the generation - (`src/shell/persist/session.h:121`) so live ReaSampler 9000 instances reload, and batches - its index mutation into one Ctrl-Z through `persistBankOp`. -- **All-or-nothing on both sides.** No partial export, no partial import. A truncated - `.rsbank` must never exist on disk (temp file + atomic rename, the Ψ-W2-T2 precedent); a - half-imported bank must never exist in the index (rollback). -- **At most one entry's payload in memory at a time**, on both paths. The pure codec owns - framing and offset arithmetic; the shell owns the stream. A whole-package - `vector` on either side is a rejected shape, not an optimization opportunity. -- **The pure planners take value inputs, never a handle.** The decoded manifest, the - destination `BankBook`, and the set of names present in the bank folder cross the seam as - values. No `ReaSamplerSession&`, no service container, no "pass the thing that has - everything" reaches `core/package/`. If a circular dependency appears during the build, the - fix is a service split or a thin interface — **not** parameter propagation, and **not** a - base class gaining a dependency that grows its subclasses' constructors. -- **Every pure module gets a `_tests` target** that runs without REAPER or a DAW. - The whole collision/version rule set is expressible as pure functions over strings and - hashes; if a rule can only be tested through the shell, the seam is in the wrong place. - -**Performance posture.** Every surface in this phase is cold — per-gesture, once. None of -the named hot paths (peaks envelope compute, audition, realtime-capture tick, instrument -`process()`) is touched by any track here. The one performance fact that *is* load-bearing is -the memory criterion above, and it is stated as a structural constraint rather than a -guardrail because exceeding it does not slow the feature down, it makes it fail. - -**Concurrency with Γ and Λ.** Phase Ε is extension-side and lands almost entirely in **two -new directories** (`src/core/package/`, `src/shell/package/`) that no other phase touches. -Γ lives in `core/instrument/` + `shell/instrument/`; Λ is being specced concurrently and is -not read here. The only pre-existing files any Ε track edits are named per track below — -`core/tracking/origin_ledger` (W1-T3, exclusively), the root `CMakeLists.txt` -`add_subdirectory` list (W1-T1 and W1-T2, one line each), `src/app/main.cpp` and the panel's -bank menu (W2-T1 and W2-T2, one registration line and one menu row each), -`core/model/bank_book.{h,cpp}` (W2-T2 only — **one additive public `const` member**, required -by the Ε-F2 auto-suffix rule so the name fold keeps its single home), and -`shell/persist/session.h` (W2-T2 only — one additive public accessor, `ledgerStatus()`, so the -import gate can key on `LedgerStatus` alone without going through `pruneDryRun()`'s -enumeration+scan). **No Ε track touches `core/instrument/`, `shell/instrument/`, or any -capture backend.** - -**All three waves have landed — Phase Ε is complete.** W1 through W3 each carry their own -landed note below; see `docs/COMPLETED.md` for every track's full narrative. - ---- - -### Ε-W1 — The contract, the filesystem, and the ledger's new kind - -**All three tracks have landed** — Ε-W1-T1 (`package-format`), Ε-W1-T2 -(`package-fs-shell`), and Ε-W1-T3 (`import-origin-kind`) — see `docs/COMPLETED.md` for -the full narrative of each. - -**Depends on:** nothing in this phase. **Three tracks, disjoint by directory** — the split is -by *what each track's inputs are*, which is why they genuinely parallelize: T1 knows only -bytes and structs, T2 knows only paths and bytes, T3 knows only the ledger. - -| Track | Owns | -|---|---| -| **T1** `package-format` | the whole of the new `src/core/package/` **except** `export_plan` / `import_plan` (W2's), plus its `CMakeLists.txt` and `CLAUDE.md` | -| **T2** `package-fs-shell` | the whole of the new `src/shell/package/` **except** `export_bank` / `import_bank` (W2's), plus its `CMakeLists.txt` and `CLAUDE.md` | -| **T3** `import-origin-kind` | `src/core/tracking/origin_ledger.{h,cpp}` and its tests, exclusively | - -**One shared file in the wave, named rather than discovered at merge:** the root -`CMakeLists.txt` `add_subdirectory` list — T1 appends `src/core/package`, T2 appends -`src/shell/package`. Two append-only lines in one list: **textual merge adjacency, not -semantic contention.** Whichever lands second rebases. - -**All three tracks are dispatchable now.** T1 carries the Ε-F1 ruling — the container is the -hand-rolled `RSBK`, decided, not a candidate T1 chooses among. T2 and T3 never depended on -that ruling anyway: T2's API is bytes-in/bytes-out regardless of what those bytes mean, and -T3 touches no package code at all. - -#### Ε-W1-T1 — `package-format` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. The pure `src/core/package/` -codec for the hand-rolled `RSBK` container (Ε-F1, ruled — no ZIP, no compressor, no link -edge to `vendor/WDL/WDL/zlib/`): a fixed little-endian header carrying two version -integers — `formatVersion` (what the writer emitted) and `minReaderVersion` (the oldest -reader that can read it safely) — a length-prefixed JSON manifest, and payloads -concatenated in manifest order. `classifyPackageVersion` answers `Readable`/`TooNew`/ -`Malformed`; a `TooNew` header refuses whole, producing no manifest, so the refusal can -still name the writer's semver rather than half-succeeding. The ladder's one -counterintuitive rule rides with the contract: **growing a persisted enum's vocabulary is -structural, not additive** — `BankModel::deserialize` *rejects* an out-of-range `SourceMode` -or `Tier` rather than degrading it (`bank_model.cpp:232-239`, `:339-346`), and every enum a -package carries rides inside the nested `BankModel` blob, so a new value bumps both -integers, where a new `Sample` field with a defined absent-value bumps `formatVersion` -alone. Landed as three modules: `package_format` (the contract, the version ladder, and -three name-validation rules — -`isValidEntryName`, `sameEntryName`'s ASCII-case fold, `isValidNestedSamplePath`), -`package_manifest` (the manifest model + JSON codec, carrying the bank's `slot_map` and a -whole-file `hashBytes` digest per entry — deliberately not `hashWavContent`, which skips -chunks and so cannot answer "did these bytes survive"), and `bank_package` -(framing/layout arithmetic: `encodePackage`/`decodePackage`/`requiredPrefixSize`, never -holding or hashing a payload itself). Hostile input is refused, never UB, at every byte -offset. - -#### Ε-W1-T2 — `package-fs-shell` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. `src/shell/package/`: -streaming, atomic package filesystem I/O (`package_io`'s `PackageFileWriter`/ -`PackageFileReader`, at most one entry's payload materialized at a time, backed by a -`.rsbanktmp` sibling that reaches the destination only through a `commit()` rename — -process-crash atomic, not power-loss atomic, deliberately, since an `fsync` over a whole -sample bank is a real stall) and the rollback journal (`package_rollback`'s -`LandedFileJournal`, citing the `prune_fs.cpp` carve-out rather than restating it, -disarmed only after the caller's own write has returned success). `package_pickers` is the -one picker seam, REAPER's own in both directions as specified — `GetUserFileName` with -`mode=0` for export's destination and `mode=1` for import's source, so no platform `#ifdef`, -no SWELL `BrowseForSaveFile`, no Win32 `GetSaveFileNameW`, and no `GetUserFileNameForRead`, -which the SDK header marks superseded by `GetUserFileName`. No fallback path was needed -either: `main.cpp` aborts the extension load if any API pointer fails to resolve, so no -REAPER build that loads the extension can lack it. The picker is `[verify — DAW]` in both -directions, never exercised in a live REAPER session. - -#### Ε-W1-T3 — `import-origin-kind` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. `OriginKind::PackageImport` -appended to the tracking ledger as value 5 — package-sourced vs `Ingest`'s user-picked. -Append-only, per `core/tracking/CLAUDE.md`'s persisted-integer rule; an unrecognized kind -degrades to `Unknown` rather than failing the parse, and `kLedgerVersion` stays at 2 — a -vocabulary addition, not a document-version bump. No decision surface changed: -`pruneProtection`'s output is unaffected for every existing kind. - ---- - -### Ε-W2 — The two verbs - -**Both tracks have landed** — Ε-W2-T1 (`bank-export`) and Ε-W2-T2 (`bank-import`) — see -`docs/COMPLETED.md` for the full narrative of each. - -**Depends on Ε-W1 — all three tracks.** T1 for the format the verbs speak, T2 for every -filesystem act they perform, T3 for the kind their birth records carry. No part of either -verb is authorable against a format that has not settled. - -**Two tracks, disjoint by direction.** They share only the manifest type. The split is real -enough that the plan **pre-split the pure planner into two TUs** (`export_plan` / -`import_plan`) rather than one `package_plan` — that separation exists specifically so these -two tracks do not fight over a file. - -| Track | Owns | -|---|---| -| **T1** `bank-export` | `core/package/export_plan`, `shell/package/export_bank`, `shell/actions/package_export_action` | -| **T2** `bank-import` | `core/package/import_plan`, `shell/package/import_bank`, `shell/actions/package_import_action`, the panel's `.rsbank` drop route | - -**Two shared files, named — and the disjointness here is CONDITIONAL, unlike W1's.** -`src/app/main.cpp` (one action-family registration line each) and the panel's bank menu (one -row each). Both are textual adjacency by construction, but Phase Ψ set the precedent of -granting `main.cpp` to a single track rather than sharing it (Ψ-W1-T3). **If the dispatcher -wants zero contention, serialize T2 behind T1** — T2 is the larger track and loses nothing by -starting second. The plan's default is to run them in parallel and rebase whichever lands -second. - -#### Ε-W2-T1 — `bank-export` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. New `core/package/export_plan` -(pure: which entries, what names, what is missing, and therefore whether the export may -proceed — verdict `Ready`/`Incomplete`/`Refused`) and `shell/package/export_bank` (the -promptless verb, in three composable public steps — `surveyBankExport`, `digestSources`, -`writePackageFile` — arriving with a **const** `ReaSamplerSession&`, so "writes no ext -state, opens no undo point, never bumps the generation" holds by the type rather than by -memory), plus `shell/actions/package_export_action`, one `main.cpp` action-table row, and -one panel bank-menu row. Nothing is re-encoded; payloads are copied and hashed. The -exported unit is one bank — the pool included, since the pool is structurally one -`BankIndex` among many — and whole-book export stays out of scope for the phase. Both open -questions were answered at review: affordance ships as **both** the bindable action and -the panel row, and the default file name derives from the bank's display name through -`capture_paths::sanitizeStem`. - -#### Ε-W2-T2 — `bank-import` - -**Landed** — see `docs/COMPLETED.md` for the full narrative. New -`core/package/import_plan` (pure: the id remap table, the parent remap, the per-entry -land/skip-already-present/rename disposition, and the destination bank's display name -after `BankBook`'s own uniqueness fold — reached through a new additive -`BankBook::uniqueDisplayName` member, the only `core/model/` edit in the phase), and on -the shell side a REAPER-free `import_landing` (decode, verify every payload's -`hashBytes` digest against the manifest BEFORE the bank folder is created, then land -through the rollback journal) plus a REAPER-facing `import_bank` (the only piece -touching the extension's project state — the undo-batched persist and the generation -bump), `shell/actions/package_import_action`, the panel's `.rsbank` drop route, one -`main.cpp` row, one panel menu row, and a new `src/core/util/ascii_ws.h`. The -tracking-ledger guard runs before the file picker opens (Ε-F3, ruled: refuse outright on -`Unreadable`/`FutureVersion`, no confirm-and-proceed); the version gate runs before any -byte is written; all four collision classes — sample id, file name, content hash, bank -display name — are answered explicitly, with the display-name collision auto-suffixed -and never prompted (Ε-F2, ruled: always a new bank, never a merge); birth records land -via `recordCreated(sample, OriginKind::PackageImport)` in the same straight-line block -as the index add; the index mutation is one Ctrl-Z, and the landed files' survival as -orphans until the next prune is stated in the user-facing summary, not left implicit. -**Beyond spec:** `import_plan`'s `spelledLikeABankFile` mints a fresh name even absent a -collision, whenever the package's own entry name isn't spelled the way -`deriveBankPaths` would spell it — counted separately from a genuine folder-name -collision (`sanitizeRenameCount` vs `collisionRenameCount`) so a hostile or -foreign-spelled entry name (e.g. an unexpected extension) always lands sanitized rather -than verbatim. - ---- - -### Ε-W3 — The compatibility fixtures - -**Depends on Ε-W2 for:** both verbs existing. A round-trip claim cannot be tested against one -half of a round trip, and a "this build refuses a future package" claim cannot be tested -against a package this build is incapable of writing. - -**One track.** The whole deliverable is one corpus and the harness over it; splitting it -would mean two tracks writing two halves of one fixture set. - -**Ε-W3-T1 has landed** — `package-compat-fixtures` — see `docs/COMPLETED.md` for the full -narrative. 23 frozen `.rsbank` fixtures now live under `tests/fixtures/package_compat/` -(three version fixtures, nine truncations, eleven hostile-name packages, ~15 KB total), -decoded by a new `package_compat_tests` and driven through export → import → export by a -new `package_round_trip_tests`, plus the DAW script `docs/verify-package-transfer.md` for -the one claim no unit test can make. A repo-root `.gitattributes` (`*.rsbank binary`) -keeps the frozen bytes frozen under `core.autocrlf`. No production module was touched. -**Open question resolved:** the recommendation was followed — the corpus is one-sample -packages with a 300-byte payload each. **Deviation from spec:** RSBK stores no layout -section (it is derived from the manifest), so the planned "mid-layout" truncation lands -at the payload boundary instead, where the manifest parses but the exact-size proof fails. - ---- - ## Phase Λ — ReaSampler on Linux: both artifacts, shipped **Ships:** `reaper_reasampler.so` and `reasampler_9000.vst3` built, installed and documented @@ -3472,7 +2637,9 @@ All textual adjacency, not semantic contention, unless marked otherwise. ## Traceability — all seventeen items -The check that nothing was dropped. Every row points at a track that exists above. +The check that nothing was dropped. Every row points at a track. All seventeen items are +Θ or Ξ work, and both phases have since landed in full — their track sections are retired +from this file; see `docs/COMPLETED.md` for each track's full narrative. | # | Item (short) | Phase-Wave-Track | Worktree slug | |---|---|---|---| @@ -3521,10 +2688,11 @@ proof it exists to give. - **All of Phase Ψ** (`ppsi-*`). **Seven tracks across three waves**, from a direct list of seven defects and refinements (Daniel, 2026-08-01), not from `TODO-1.0.md`. Listed here as a block, like Γ; unlike Γ it has no backing product doc — the seven are - recorded verbatim in the phase header as its provenance (Ψ.1–Ψ.7), and the design - content lives inline in its tracks. The seventh track, Ψ-W3-T1, is not one of the - seven defects/refinements itself — it came from a review finding mid-phase; see the - Phase Ψ section for detail. + recorded verbatim in the phase header as its provenance (Ψ.1–Ψ.7). Phase Ψ has since + landed in full and its `PLAN.md` section is retired; the design content and the + Ψ.1–Ψ.7 list now live in `docs/COMPLETED.md`. The seventh track, Ψ-W3-T1, is not one of + the seven defects/refinements itself — it came from a review finding mid-phase; see + `docs/COMPLETED.md` for detail. - **Γ-W3-T2 `bake-reset-amendment` is a CORRECTION, not a feature**, and belongs on this list for a different reason from the others: it exists only because Ξ-W2-T1 shipped ahead of this plan's sequencing claim. If more corrections of this shape appear, they belong here rather @@ -3596,6 +2764,7 @@ than discovering it later: ``` Phase Θ — ReaSampler 9000: one parameter set, filter, shapeable envelopes, legible editor + [LANDED — all seven waves (W1-W7); see docs/COMPLETED.md; full detail section removed from this file] W1 Collapse and re-seam T1 zone-retirement ......................... 16 T2 capture-handoff-bugs .................... 5, 6 @@ -3618,6 +2787,7 @@ Phase Θ — ReaSampler 9000: one parameter set, filter, shapeable envelopes, le T1 arc-and-spline-aa ....................... (not one of the seventeen) Phase Ξ — The resample loop (W1 concurrency-safe with Θ from Θ-W2 onward) + [LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file] W1 Consolidated tracking, and the programmed-note model T1 tracking-consolidation .................. 17 T2 note-program-model ...................... 15 (model) @@ -3656,6 +2826,7 @@ Phase Γ — The instrument's control surface (none of the seventeen; ends what Xi-W2-T1 shipped, and serialize behind T1 if it does not hold. Phase Psi — The extension trust pass (none of the seventeen; a direct list of seven) + [LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file] W1 Exact bounds, disciplined switches, reachable actions, resolved drops [4 tracks] T1 capture-range-exactness ..... Psi.7 [opens with a DAW repro matrix] T2 mode-switch-discipline ...... Psi.2 + Psi.3 [one chokepoint: applyMode] @@ -3674,6 +2845,7 @@ Phase Psi — The extension trust pass (none of the seventeen; a direct exclusively. Phase Epsilon — The bank package (none of the seventeen; a direct request 2026-08-02) + [LANDED — all three waves (W1-W3); see docs/COMPLETED.md; full detail section removed from this file] W1 The contract, the filesystem, and the ledger's new kind [3 tracks, disjoint by dir] T1 package-format .............. core/package: framing + TWO version ints [E-F1 RULED: proprietary RSBK. No ZIP, no zlib] diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index 7b59278..acb7e66 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -16,16 +16,16 @@ Checks for Θ, Ξ, Ψ, and Ε work that no unit test can close. Build **Release* ## The decisive observation -- [ ] **Run first.** `docs/verify-track-scope-multitrack.md` §3 by hand, and count the files REAPER writes (`docs/TODO.md:552`, `PLAN.md:2134`, `PLAN.md:2238`) -- [ ] Two files confirms Ψ-W1-T1 and Ψ-W3-T1 at once; **one summed file invalidates both** — stop and report, the refusal is costing a capture 1.0.0 accepted (`PLAN.md:2239`, `docs/COMPLETED.md:884`) -- [ ] Then walk the rest of `docs/verify-track-scope-multitrack.md` (§1–§2, §4–§7) for the multi-track refusal itself (`PLAN.md:2240`) +- [ ] **Run first.** `docs/verify-track-scope-multitrack.md` §3 by hand, and count the files REAPER writes (`docs/TODO.md:552`, `docs/COMPLETED.md:894`, `docs/COMPLETED.md:906`) +- [ ] Two files confirms Ψ-W1-T1 and Ψ-W3-T1 at once; **one summed file invalidates both** — stop and report, the refusal is costing a capture 1.0.0 accepted (`docs/COMPLETED.md:905`) +- [ ] Then walk the rest of `docs/verify-track-scope-multitrack.md` (§1–§2, §4–§7) for the multi-track refusal itself (`docs/COMPLETED.md:898`) ## Capture range and bounds -- [ ] Over an item much longer than the selection: item scope × time selection, and item scope × razor — each lands exactly the window, not the whole item (`PLAN.md:2136`) -- [ ] Same source: track scope × time selection, and track scope × razor — same exact window (`PLAN.md:2136`) -- [ ] One razor-union case (two disjoint areas, one track) — lands the requested window, no `ReaSampler capture failed:` line (`PLAN.md:2137`) -- [ ] Capture an item whose extent already equals the window — still lands, unchanged (the byte-identity regression floor) (`docs/COMPLETED.md:829`) +- [ ] Over an item much longer than the selection: item scope × time selection, and item scope × razor — each lands exactly the window, not the whole item (`docs/COMPLETED.md:844`) +- [ ] Same source: track scope × time selection, and track scope × razor — same exact window (`docs/COMPLETED.md:844`) +- [ ] One razor-union case (two disjoint areas, one track) — lands the requested window, no `ReaSampler capture failed:` line (`docs/COMPLETED.md:852`) +- [ ] Capture an item whose extent already equals the window — still lands, unchanged (the byte-identity regression floor) (`docs/COMPLETED.md:849`) - [ ] **The millisecond floor — SETTLED, nothing to re-run for `TailMode::None`.** The floor lives in the custom-time-bounds field (`RENDER_BOUNDSFLAG=0`), not in the render engine. Two live 48 kHz `TailMode::None` renders on `RENDER_BOUNDSFLAG=2` (time selection, handed over via `GetSet_LoopTimeRange`) came back exact — 97627 frames against 97627 — the second over a window whose START carried a sub-millisecond remainder, with no floored model of that window able to reproduce the count. Time selection is now the only bounds mode a capture can reach; the console verdict line and the `RENDER_STARTPOS`/`ENDPOS` read-back probe that answered this are gone. Full observation: `src/core/capture/render_settings.h`'s `kRenderBoundsTimeSelection` - [ ] **Still open — Auto and Manual tail.** `checkRenderedBounds` judges `TailMode::None` only (Auto/Manual add frames by design), so the settled result covers those two by INFERENCE, not observation, and the inference rests on an unverified PREMISE too: that the (retired) floor applied to the bounds identically across all three tail modes, and that all three now hand the window over the same way. Neither is measured — both live short renders that settled the bounds mode were `TailMode::None`; no Auto or Manual capture has been observed at all. **On Auto/Manual, the ONLY automatic check left is the 0-byte gate (`checkRenderedFileNotEmpty`)** — there is no automatic bounds signal for those two modes at all until this bullet is closed by hand. What would establish it: repeat an off-grid-start capture at **Manual** over a source that is loud right to the window's end, and check the landed file's frames against window + `tailMs` — a floored edge shows up in that count. **Auto** cannot be checked by count (it trims trailing silence), so it needs the null test by ear/inversion against the source instead - [ ] `[verify — DAW]` A tail is assumed to render PAST the window end — the SDK header (`:3048`) confirms only that `RENDER_TAILMS` is a length in ms, not that it extends past the end. If that assumption is wrong, a tail capture is silently SHORTER than its window with no detector at all. Report whether either tail capture comes up short against the source @@ -34,28 +34,28 @@ Checks for Θ, Ξ, Ψ, and Ε work that no unit test can close. Build **Release* ## Names and channels -- [ ] Capture from a named track — the card reads ` MM-DD HHMM`; capture again the same minute and the second carries an ordinal (`PLAN.md:2199`) -- [ ] Capture from an unnamed track, and from a multi-item selection — both readable, `+N` present on the multi (`PLAN.md:2199`, `docs/COMPLETED.md:862`) -- [ ] Load a named capture into ReaSampler 9000 — the same name shows there (`PLAN.md:2200`) -- [ ] The card label stays legible over its scrim at every card size (`docs/COMPLETED.md:863`) -- [ ] Capture a dead-center mono source — the `.wav` is roughly half the size of the equivalent stereo capture (`PLAN.md:2216`) -- [ ] Insert that collapsed file on a stereo track and null it against the source — confirms REAPER sums a 1-channel item at unity (`PLAN.md:2218`) -- [ ] Capture a true-stereo source — stays 2-channel, and both it and the collapsed file load into the instrument correctly (`PLAN.md:2217`) +- [ ] Capture from a named track — the card reads ` MM-DD HHMM`; capture again the same minute and the second carries an ordinal (`docs/COMPLETED.md:882`) +- [ ] Capture from an unnamed track, and from a multi-item selection — both readable, `+N` present on the multi (`docs/COMPLETED.md:882`) +- [ ] Load a named capture into ReaSampler 9000 — the same name shows there (`docs/COMPLETED.md:881`) +- [ ] The card label stays legible over its scrim at every card size (`docs/COMPLETED.md:883`) +- [ ] Capture a dead-center mono source — the `.wav` is roughly half the size of the equivalent stereo capture (`docs/COMPLETED.md:887`) +- [ ] Insert that collapsed file on a stereo track and null it against the source — confirms REAPER sums a 1-channel item at unity (`docs/COMPLETED.md:891`) +- [ ] Capture a true-stereo source — stays 2-channel, and both it and the collapsed file load into the instrument correctly (`docs/COMPLETED.md:889`) ## Mode switching -- [ ] Solo tracks in Arrange, switch to Design, solo different tracks, switch back — each mode restores its own solo set verbatim (`PLAN.md:2151`) -- [ ] Attempt a mode switch while the transport is playing, then while recording — both refuse, visibly (`PLAN.md:2151`) -- [ ] Click the footer mode segment, save, reopen the project — the mode persisted (`PLAN.md:2152`, `docs/COMPLETED.md:841`) +- [ ] Solo tracks in Arrange, switch to Design, solo different tracks, switch back — each mode restores its own solo set verbatim (`docs/COMPLETED.md:864`) +- [ ] Attempt a mode switch while the transport is playing, then while recording — both refuse, visibly (`docs/COMPLETED.md:859`) +- [ ] Click the footer mode segment, save, reopen the project — the mode persisted (`docs/COMPLETED.md:861`) ## Actions and drops -- [ ] Add the import action to a Media Explorer toolbar and fire it from there — it imports (`PLAN.md:2162`) -- [ ] Fire the existing Main-section import binding — still works (`PLAN.md:2163`) -- [ ] Unload/reload (restart REAPER) — no duplicate Media Explorer entry in the action list (`PLAN.md:2164`) -- [ ] Drag one card across the arrange, over an FX window, over the TCP/MCP and back — cue changes per surface, every transition reverses (`PLAN.md:2176`, `PLAN.md:2177`) -- [ ] Drag fast, and drag onto a narrow TCP — target class still resolves; no release anywhere in REAPER is a silent no-op (`PLAN.md:2178`, `docs/COMPLETED.md:858`) -- [ ] Drop a single card into the arrange — an item lands at the pointer's track and time (`PLAN.md:2174`) +- [ ] Add the import action to a Media Explorer toolbar and fire it from there — it imports (`docs/COMPLETED.md:867`) +- [ ] Fire the existing Main-section import binding — still works (`docs/COMPLETED.md:869`) +- [ ] Unload/reload (restart REAPER) — no duplicate Media Explorer entry in the action list (`docs/COMPLETED.md:870`) +- [ ] Drag one card across the arrange, over an FX window, over the TCP/MCP and back — cue changes per surface, every transition reverses (`docs/COMPLETED.md:875`, `docs/COMPLETED.md:876`) +- [ ] Drag fast, and drag onto a narrow TCP — target class still resolves; no release anywhere in REAPER is a silent no-op (`docs/COMPLETED.md:875`, `docs/COMPLETED.md:878`) +- [ ] Drop a single card into the arrange — an item lands at the pointer's track and time (`docs/COMPLETED.md:877`) - [ ] Drag-out to an external app twenty-plus times in a row — audio arrives every time; this is a soak, a single pass is not a gate (`docs/COMPLETED.md:109`) - [ ] Drop a capture onto an FX container — the instrument loads with that capture (`docs/COMPLETED.md:110`) @@ -65,7 +65,7 @@ Checks for Θ, Ξ, Ψ, and Ε work that no unit test can close. Build **Release* export/import round trip: writes-one-file, the transfer itself, re-importing the same file never overwrites, the round trip back to the source, the too-new / truncated / mid-payload-corruption refusals (each an exact string), the - unsaved-project refusals, and drag-and-drop (`docs/COMPLETED.md:1068`, `PLAN.md:2578`) + unsaved-project refusals, and drag-and-drop (`docs/COMPLETED.md:1126`) - [ ] Force a degraded tracking ledger and confirm the import refuses **before the file picker opens**: save a project with a bank, close REAPER, edit the saved `.rpp`'s `owned_files` ext-state value inside its `` block — corrupt @@ -115,8 +115,8 @@ Checks for Θ, Ξ, Ψ, and Ε work that no unit test can close. Build **Release* ## Look and feel -- [ ] Sign off by eye in a live editor window: knob arcs, needles, envelope splines, waveform outline (`docs/COMPLETED.md:669`, `PLAN.md:375`) -- [ ] Same pass for legibility: text sizes, arc weight, and whether the waveform stroke thickens the docked panel (`docs/COMPLETED.md:613`, `PLAN.md:348`) +- [ ] Sign off by eye in a live editor window: knob arcs, needles, envelope splines, waveform outline (`docs/COMPLETED.md:669`, `docs/COMPLETED.md:583`) +- [ ] Same pass for legibility: text sizes, arc weight, and whether the waveform stroke thickens the docked panel (`docs/COMPLETED.md:613`, `docs/COMPLETED.md:562`) - [ ] Piano strip at the 840 px default — keys tile uniformly, the 37 px end gutters read as acceptable (`docs/COMPLETED.md:219`) - [ ] Resize the editor across several widths — gutters stay symmetric, no key width jumps (`docs/COMPLETED.md:217`) - [ ] Set host/OS scaling to 150% then 200% — record how the strip and the AA strokes actually look (`docs/TODO.md:418`, `docs/COMPLETED.md:226`)