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.
This commit is contained in:
2026-08-02 18:27:54 -04:00
parent 4231b2321c
commit 02319041e3
3 changed files with 63 additions and 872 deletions
+14 -842
View File
@@ -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.110 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`. (T2T4 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
(`<Track> [+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<uint8_t>` 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 `<module>_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]