docs: record D2-W3-A landed in COMPLETED.md

planLaneMinting pure decision, applyMintPlan shell, reconcileManagedLanes
on load, persist via ViewModeModel view_state. CTest 14/14; DAW verify
pending. (PLAN.md D2-W3-B narrowing already landed via a concurrent commit.)
This commit is contained in:
2026-07-23 20:24:31 -04:00
parent 8d2a4ac872
commit 960c5166ee
+67
View File
@@ -547,3 +547,70 @@ projects with no stored key fall back to Off / 2 s.
(Off mode, 2 s stored length) — graceful for projects saved before this feature shipped. (Off mode, 2 s stored length) — graceful for projects saved before this feature shipped.
- `kProjExtTailKey = "tail_setting"` is forever-stable (changing it would orphan saved choices, - `kProjExtTailKey = "tail_setting"` is forever-stable (changing it would orphan saved choices,
falling back to the default — graceful but lossy). falling back to the default — graceful but lossy).
---
## D2-W3-A — lane minting + item→lane assignment + persist round-trip
**Goal:** The functional core that makes item-lanes appear: a pure `planLaneMinting`
decision (which tracks hold >1 mode's content, which managed lane each item lands on)
plus the shell apply path in `view.cpp` — enables fixed-lane mode, mints one managed
`reasampler:<mode>`-named lane per involved mode, assigns each item (including
pre-existing) to its mode's lane, and drives per-lane play state, all under one undo
block, triggered off the auto-tag detection tick. Reconciles the lane-ownership index
from durable lane names on project load before active-mode visibility is reapplied.
The lane-ownership index persists inside the `"reasampler"` `view_state` blob (rides
in `ViewModeModel::serialize()` / `deserialize()`).
**Verify:** CTest green (14/14). Pure decision unit-tested in `view_mode_model_tests`.
**DAW verification pending** (Daniel testing on dev): two behaviors are
REAPER-runtime-only — whether lane names stick when written on the same tick the track
flips to fixed-lane mode, and whether the leftover empty default lane 0 is silent.
**Depends on:** D2-W2.
- [x] Pure `planLaneMinting` decision (`view_mode_model.{h,cpp}`): for each reported
track, collect the distinct modes of managed-eligible items; if < 2 modes, no split
(D1 whole-track parking still separates stances); if ≥ 2 modes, emit one
`TrackSplit`, one `LaneMint` per involved mode (durable key = `laneNameForMode(mode)`,
owned by that mode), and one `LaneAssign` per managed-eligible item — including
pre-existing items, so a track that just gained a second mode retroactively lanes all
its content. Manual-lane items (`onManualLane = true`) are exempt at the source:
never counted, never reassigned, never minted-over.
- [x] Shell apply path `applyMintPlan` in `view.cpp`: enables `I_FREEMODE` = fixed
lanes, grows `I_NUMFIXEDLANES` (never shrinks — user's manual lanes are never
deleted), stamps each managed lane's durable name via `P_LANENAME`, records
ownership in the model (`lanes().setManaged`), assigns each item to its mode's lane
via `I_FIXEDLANE` resolved from the durable key. Returns `changed` so the Undo block
is only kept when state actually changed (idempotent re-runs produce no undo point).
- [x] Per-lane play state driven immediately after minting: `planToggle` lane ops
applied via `applyLaneOps` so the freshly-minted lanes take the correct
`C_LANEPLAYS` state for the active mode without a full `applyMode` re-run (which
would re-park/restore whole tracks — not correct for a minting tick).
- [x] `mintManagedLanes` entry point in `view.cpp`: reads live track/item picture via
`readLaneTracks`, calls `planLaneMinting`, wraps the apply in one Undo block labelled
`"ReaSampler: separate cross-mode content into lanes"`, calls `UpdateTimeline()` +
`UpdateArrange()` after a fixed-lane mode change.
- [x] `reconcileManagedLanes` in `view.cpp`: on project load, reads every fixed-lane
track's `P_LANENAME` values; for each name carrying the managed prefix, records the
lane as managed-for-its-mode in the ownership index — pure read of REAPER state, no
lane created or renamed. Called from `main.cpp`'s load path before `applyMode`.
- [x] Lane-ownership index persists via `ViewModeModel::serialize()` /
`deserialize()` — the `LaneOwnershipIndex` is a member of `ViewModeModel` and
round-trips inside the `"reasampler"` `view_state` key alongside modes, membership,
snapshots, and active mode. No new persistence key required.
- [x] Detection tick integration: `mintManagedLanes` is called from the `bank_panel`
timer after the auto-tag pass, so a newly-tagged multi-mode track is split into lanes
on the same tick the content is detected.
**Notes/decisions:**
- **Single-mode-track rule:** a track carrying content of only ONE mode is not split —
D1's whole-track parking continues to separate its stance from the other mode without
lane overhead. The lane-split only engages when a track genuinely holds ≥ 2 modes'
content.
- **Manual-lane invariant upheld at the source:** `planLaneMinting` never receives
manual-lane items as split candidates. The shell's `readLaneTracks` marks items on
manual lanes `onManualLane = true`; the pure decision skips them entirely. Managed
lanes are always appended (tail ordinals), never overwriting a user's existing lanes.
- **Idempotency:** re-reporting an already-split track produces the same plan; the
shell's ensure/assign writes are no-ops when state already matches. The Undo block is
closed with no label (discarded by REAPER) when the plan is non-empty but every write
was already satisfied, so no phantom undo points accumulate.
- **Review passed** with no Critical or Major findings.