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:
+14
-842
@@ -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
|
||||
(`<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]
|
||||
|
||||
Reference in New Issue
Block a user