docs: close out Ξ-W1-T1, Ξ-W1-T2 and Θ-W3-T1 into COMPLETED; map the note directory; document the Release build

This commit is contained in:
2026-07-31 07:02:54 -04:00
parent 98594df878
commit 87d7ceb066
4 changed files with 168 additions and 456 deletions
+85
View File
@@ -228,3 +228,88 @@ uniform key widths, note-name tooltips, root displayed and settable.
carries a "confirm the fix survives DPI scaling" item and now genuinely inherits it.
- A stale-hover latch was fixed across **all** drag kinds and both drag-termination
paths, wider than the strip work that surfaced it.
### Θ-W3-T1 — live-parameter-delivery
Continuous playback controls are now delivered live to sounding voices instead of being
latched at note-on. New `src/core/instrument/engine/live_params.{h,cpp}` holds a
seqlock-published `LiveValues` block owned at **processor-instance scope**, above
`LoadedInstrument`, so `live_` and `draining_` observe the same one (a drain-slot voice
tracks the knob, which is the desired behavior). `foldLive(const PlayParams&)` is the
single derivation from the value type; `PlayParams` stays a plain copyable value type.
**Daniel's two decisions, both implemented:**
- **Reload tier = Grouping B.** Continuous knobs live (filter cutoff/Q/morph/drive/mod
amount/key-track; every stage time and level on all three envelopes). Root note, loop
span, and start frame still trigger a full reload.
- **Mid-stage rule = candidate (iv), hold normalized stage position.** φ =
elapsed/duration held fixed across a duration change, then advancing at
1/newDuration — expressed over normalized position specifically so Θ-W3-T2's
per-segment curve exponent composes with it.
**Deviations from spec:**
- **Trigger's %-length and fades are NOT live** — they are baked into `SampleData` at
build, so reload is the only tier that can deliver them. Consequence: a Trigger-mode
instance gets zero live amp delivery until Θ-W3-T2 folds the fade pair into the AHD.
The five non-live exclusions (`kKeyTrack`, `kFilterVel`, `kTrigLength`,
`kTrigFadeIn`, `kTrigFadeOut`) are documented in `src/core/instrument/ui/deck_groups.h`,
now their single home.
- Open question 3 resolved as **F2 + seqlock**; open question 4 (sub-block resolution)
was not built but not foreclosed — the writer interface assumes no UI thread; open
question 5 verified — a live edit still persists, `commitLive` keeps the
`setInstrumentParams` write.
- A filter envelope only advances while its depth is non-zero (the exact-skip at
`modAmount == 0`), which is what keeps the at-rest path byte-identical.
### Ξ-W1-T1 — tracking-consolidation
Consolidates the provenance/usage territory into one system: the retired
`owned_manifest` gives way to a new `src/core/tracking/` directory holding
`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`). 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. `isAbsolutePath` was hoisted out to a new
`src/core/util/relative_path.h`, shared with `bank_model`'s `Sample.relativePath`.
**Deviations from spec:**
- The deferred persisted-instance-identity fix was **not** folded in — open question 5
resolved as "restate the deferral." `docs/TODO.md` already carries the sharpened
rationale (the session-epoch candidate and its sibling-drop flaw); not duplicated here.
- `sample_usage` deliberately **stays in `core/wire`** — the consolidation is of the
*decisions*, not the codecs.
- A realtime record interrupted by a project switch strands an untracked WAV in the old
project's bank folder. Resolved as document-don't-delete (prune is the exclusive
deletion authority); `docs/TODO.md` carries the entry.
- `PruneReport` fields were renamed; a malformed ledger is now reported as a distinct
blocker with its own recovery instructions.
### Ξ-W1-T2 — note-program-model
Lands the programmed-capture-signal model as a new pure module directory,
`src/core/instrument/note/` — a fourth peer of `engine/`/`map/`/`ui/` under
`core/instrument/` — holding `musical_division` (the 1/6464/1 ladder with
dotted/triplet multipliers, the 39-entry picker order), `tempo` (validated BPM plus
every beats↔seconds↔ms conversion), and `note_program` (`Velocity`, the denominated
`OffsetAmount`, the anchored `StartOffset`/`EndOffset`, the `NoteProgram` record, and
`resolveNote`).
**Deviations / resolutions from spec:**
- Open question "negative offsets" resolved: both directions are legal and the sign is
uniform (positive is later in time); only an *inverted* window is refused, reported
via `ResolvedNote::windowCollapsed`.
- Open question "denomination seam" confirmed: note length is musical-division-only;
the ms/beats duality belongs to the offsets alone. An offset stores the denomination
it was **entered in**, deriving the other view on demand, so a beats offset follows a
tempo change and a ms offset holds still.
- Module name/location resolved as `src/core/instrument/note/` — three modules, not
one, with the layering enforced by the CMake link line.
- **Beyond spec:** every value type closes its domain at construction behind a single
normalizing door (`makeDivision`, `offsetOf`, `Tempo::fromBpm`, `Velocity::of`), with
private value constructors. Consequence: `resolveNote` needs no failure path and
`ResolvedNote` no validity flag, because every returned field is finite for every
constructible program and tempo. Junk detection is relocated to the future codec,
which sees both the bytes it read and the value construction produced. `NoteProgram`
deliberately carries no MIDI note number — render pitch is deferred to Ξ-W2 as an
additive field.