docs: rule Phase Rho's three forks
Refuse multi-track; result track always Arrange; follow panel tail. Rho-F2 overrides the mode-following framing: explicit Arrange tags on track and item, plus an explicit-tag-wins filter in detectNewContent.
This commit is contained in:
+223
-98
@@ -9,12 +9,20 @@ the capture/placement separation, the prior art it borrows from, the reuse inven
|
||||
that makes it small, and the handful of decisions the shape actually turns on.
|
||||
|
||||
Status: framed by product-designer (2026-08-02) from Daniel's direct request the same
|
||||
day. **Three [Daniel]-class forks are open** — Ρ-F1 (multi-track), Ρ-F2 (the new
|
||||
track's mode when Design is active), Ρ-F3 (tail) — each stated with a recommendation
|
||||
in §"Open forks". None of them blocks a dispatch; all three are answerable at
|
||||
implementation review if Daniel prefers, but all three are user-visible policy rather
|
||||
than implementation detail. Everything else below is a product-designer call with its
|
||||
reasoning stated; contradict it in review with an argument, not a preference.
|
||||
day; **all three [Daniel]-class forks RULED the same day** — Ρ-F1 **refuse a
|
||||
multi-track selection**, Ρ-F2 **the result track always goes to Arrange**, Ρ-F3
|
||||
**follow the panel tail setting**. See §"Rulings" for the index and the recorded
|
||||
rationale; each is specified in place in the section that owns it. Nothing in this
|
||||
doc is open.
|
||||
|
||||
**Ρ-F2 overrode the framing, including Daniel's own original phrasing.** The request
|
||||
as first stated had the new track take whatever mode was active when the action fired;
|
||||
the ruling replaced that with an absolute rule. §"Mode transitions" is written against
|
||||
the ruling, and the A/B-on-the-bench behaviour that mode-following would have produced
|
||||
**no longer exists anywhere in this design** — do not reintroduce it as a benefit.
|
||||
|
||||
Everything else below is a product-designer call with its reasoning stated; contradict
|
||||
it in review with an argument, not a preference.
|
||||
|
||||
---
|
||||
|
||||
@@ -23,8 +31,9 @@ reasoning stated; contradict it in review with an argument, not a preference.
|
||||
**Render in place takes one selected track, renders its output over the current
|
||||
range to a file, and drops that file as an item on a brand-new sibling track at the
|
||||
exact position it was rendered from — then moves the source track into Design mode.**
|
||||
The new track inherits the source's colour and its name with a `Capture ` prefix. The
|
||||
bank is never opened, never read, never written.
|
||||
The new track inherits the source's colour and its name with a `Capture ` prefix, and
|
||||
belongs to Arrange unconditionally (Ρ-F2). The bank is never opened, never read, never
|
||||
written.
|
||||
|
||||
The model Daniel named is REAPER's own *Render selected track time selection to new
|
||||
track (stereo) and mute original*. Phase Ρ differs in exactly one respect, and that
|
||||
@@ -235,19 +244,46 @@ different and much less predictable verb.
|
||||
render *source* (`SelectedTracks`, which track scope always uses), so any selection of
|
||||
more than one track refuses with `multiTrackRefusalMessage(CaptureScope::Track)`
|
||||
before a single project setting is written. Ρ inherits this for free and adds no
|
||||
check of its own. See fork Ρ-F1 for the alternative.
|
||||
check of its own.
|
||||
|
||||
**Tail follows the panel setting** — None / Auto / Manual, read from
|
||||
`bankPanelTailSetting()` like every other capture path. Under Auto or Manual the
|
||||
rendered file is longer than the requested window by design (the chain's decay rings
|
||||
past the range end), so the placed item is correspondingly longer than the source
|
||||
window. That is the musically correct answer for a design chain with reverb on it,
|
||||
and forcing None would be Ρ inventing a policy the rest of the tool does not have.
|
||||
Two consequences to state rather than discover: the exact-bounds gate in
|
||||
`OfflineRenderBackend::capture` runs **only** under `TailMode::None`, so Auto/Manual
|
||||
renders are unguarded against widening (inherited, not introduced); and the placed
|
||||
item's *start* is exact in every mode, because a tail is added at the end only. See
|
||||
fork Ρ-F3.
|
||||
**Ρ-F1, RULED by Daniel, 2026-08-02: *refuse*.** One selected track per fire is a
|
||||
**settled non-goal**, in the same sense as the other entries in §"What Phase Ρ
|
||||
explicitly is NOT" — not a deferral. There is no per-track loop planned, no second
|
||||
wave holding one, and no seam to be left half-open on the way past. If multi-track is
|
||||
ever wanted it is a new phase with its own framing, and the counter-argument that made
|
||||
this a fork (the stem-collapse hazard does not apply to a per-track loop, so the
|
||||
refusal is inherited rather than required) is recorded in §"Rulings" so it does not
|
||||
have to be rediscovered.
|
||||
|
||||
**Tail follows the panel setting — Ρ-F3, RULED by Daniel, 2026-08-02: *"follow panel
|
||||
tail settings."*** None / Auto / Manual, read from `bankPanelTailSetting()` like every
|
||||
other capture path. Forcing `None` would be Ρ inventing a policy the rest of the tool
|
||||
does not have, and a decaying design chain wants its tail when its source is about to
|
||||
be silenced.
|
||||
|
||||
**Two consequences of that ruling, both accepted, both stated here because an
|
||||
implementer meets them directly:**
|
||||
|
||||
1. **Under Auto or Manual the placed item is longer than the window it replaces.** The
|
||||
chain's decay rings past the range end and the render carries it, so the item on
|
||||
the result track extends past the source range. This is correct for reverb and
|
||||
wrong for a section meant to butt against the next one — and the ruling accepts
|
||||
that trade rather than splitting the behaviour. The user's lever is the panel's own
|
||||
tail setting: set it to None before firing if a hard edge is wanted.
|
||||
2. **The exact-bounds gate is inactive under Auto and Manual.** The gate in
|
||||
`OfflineRenderBackend::capture` runs **only** under `TailMode::None`, so Ρ renders
|
||||
in the other two modes are unguarded against a widened render. This is inherited
|
||||
from every other capture path, not introduced by Ρ, and it is not a caveat to bury:
|
||||
the phase's exactness guarantee under Auto/Manual is the *start* only.
|
||||
|
||||
The placed item's **start is exact in every tail mode**, because a tail is only ever
|
||||
added at the end. The null test therefore holds in all three modes — the shared region
|
||||
nulls; the tail simply has nothing to null against.
|
||||
|
||||
A third option was floated at framing and is **not ruled in**: keep the panel setting
|
||||
but run the bounds gate's start-alignment check regardless of tail mode, since a tail
|
||||
only ever extends the end. It is recorded as an unexercised alternative in §"Rulings"
|
||||
and is not to be built into this phase.
|
||||
|
||||
**Mono collapse applies**, unchanged. A render whose channels are bit-identical
|
||||
collapses losslessly to one channel and REAPER derives a mono item from the file
|
||||
@@ -313,6 +349,14 @@ sight in the same gesture, so restoring the selection would leave the user selec
|
||||
an invisible track. The new track is the workflow's next subject; select it. The edit
|
||||
cursor *is* restored, since nothing about Ρ argues for moving it.
|
||||
|
||||
**The Ρ-F2 ruling inverts that reasoning in the Design-fired case, and the rule stays
|
||||
absolute anyway.** Fired from Design, the result track is the parked one and the source
|
||||
is the visible one, so leaving the result selected selects an invisible track — the
|
||||
exact thing the rule exists to avoid in the other direction. Making the selection
|
||||
conditional on the active mode would reintroduce mode-relative behaviour, which is
|
||||
precisely what the ruling removed from this action. One rule, both cases: the result
|
||||
track ends up selected, alone.
|
||||
|
||||
---
|
||||
|
||||
## The new track — index, folder, colour, name
|
||||
@@ -406,11 +450,14 @@ unlike `kManagedLanePrefix` or an action-id suffix, changing it later strands no
|
||||
|
||||
---
|
||||
|
||||
## Mode transitions — resolving "stays/goes"
|
||||
## Mode transitions — the source parks, the result goes to Arrange
|
||||
|
||||
Daniel's phrasing was *"the source track stays/goes to design mode, and the resulting
|
||||
new sibling track […] stays in whatever mode was active when the action was run."*
|
||||
Both halves resolve into the shipped membership model without inventing anything.
|
||||
Daniel's original phrasing was *"the source track stays/goes to design mode, and the
|
||||
resulting new sibling track […] stays in whatever mode was active when the action was
|
||||
run."* **The second half was overridden by his own later ruling — Ρ-F2, 2026-08-02:
|
||||
*"for this action which is not a capture, the result track should always go to
|
||||
arrange."*** The source half stands exactly as first stated; the result half is now
|
||||
absolute and mode-independent.
|
||||
|
||||
**Source track: unconditionally a Design member afterwards.**
|
||||
`membership().tag(sourceGuid, kDesignModeId)` covers both readings in one call —
|
||||
@@ -433,29 +480,91 @@ Two inherited behaviours to state rather than fight:
|
||||
inherits it. Do **not** invent a cascade that tags the children — that changes the
|
||||
membership model to make one feature convenient.
|
||||
|
||||
**New track: tagged to the mode that was active when the action fired**,
|
||||
synchronously and explicitly — `membership().tag(newTrackGuid, view.activeModeId())`.
|
||||
**Result track: unconditionally an Arrange member**, whatever mode was active —
|
||||
`membership().tag(newTrackGuid, kArrangeModeId)`, synchronously and explicitly, **not**
|
||||
an `untag()` to the Arrange default. The distinction is load-bearing: an explicit
|
||||
membership record is what protects the ruling from the auto-tag detector (below). An
|
||||
untagged track is an Arrange member by observable behaviour but carries no record, and
|
||||
a record is what the detector must be made to respect.
|
||||
|
||||
**This must happen before `applyMode` reapplies, and that ordering is load-bearing.**
|
||||
The auto-tag poller (`panel_input::detectNewContent`, over `guid_diff::GuidBaseline`)
|
||||
would tag the new track on its next tick, and would reach the same answer — but the
|
||||
reapply inside Ρ's own gesture runs first. An untagged track is an Arrange member by
|
||||
default, so if the active mode is Design and Ρ reapplies before tagging, the reapply
|
||||
**parks the brand-new capture track** — hidden, out of the mix — and it stays parked
|
||||
until the next mode switch. Tagging explicitly, first, closes that window; the
|
||||
poller's later observation is then idempotent (it re-derives the same mode).
|
||||
**Note what is novel here, because it is worth a reviewer's attention.** Ρ is the
|
||||
**first** path in the tree to write an explicit `kArrangeModeId` record. The shipped
|
||||
*tag selected tracks → Arrange* action does not — it dispatches to `doUntag()`, i.e.
|
||||
Arrange-by-absence, which is why the constant appears in `view_mode_model` and
|
||||
`mode_enable` but never in a `tag()` call. The record is nonetheless well-formed and
|
||||
behaviourally identical to the untagged state everywhere that matters:
|
||||
`ViewModeModel::isMember` answers `true` for `arrange` and `false` for `design` in
|
||||
both cases, the derived-parent and lane rules read the same, and `untag()` still
|
||||
returns it to absence. The only differences are one more entry in the persisted
|
||||
membership index and — the point — its visibility to the detector's filter. **[verify
|
||||
— DAW]** that a project saved with an explicit Arrange record round-trips and shows no
|
||||
behavioural difference from an untagged track; the JSON round-trip is unit-testable and
|
||||
should be tested, but the live view behaviour is not.
|
||||
|
||||
**The placed item is tagged Arrange too**, explicitly, for the same reason. Item
|
||||
membership is a separate index from track membership and it is what drives lane
|
||||
minting; leaving the item untagged hands it to the detector, which tags a new item to
|
||||
the active mode whenever its track has no pre-existing single-mode content — and a
|
||||
brand-new track never does. After `InsertMedia`, enumerate the new track's items (it
|
||||
is brand new, so they are exactly the ones just placed) and tag each;
|
||||
`shell/capture/item_read::itemGuid` is the existing GUID seam.
|
||||
|
||||
### The one hazard the ruling creates — the auto-tag detector
|
||||
|
||||
The ruling **dissolves** the ordering hazard the mode-following rule carried and
|
||||
**creates a different one** in its place. Both are stated, because the first was
|
||||
written into the plan's acceptance criteria and is now wrong.
|
||||
|
||||
**Dissolved.** Under mode-following, the tag had to precede the `applyMode` reapply:
|
||||
an untagged track is an Arrange member by default, so a Design reapply running first
|
||||
would have parked a track destined for Design, and it would have stayed parked until
|
||||
the next switch. Under the ruling the result track *is* an Arrange member, so a Design
|
||||
reapply parking it is the correct outcome and the ordering no longer changes what the
|
||||
user sees. Tag first anyway, for state hygiene — it is simply no longer load-bearing.
|
||||
|
||||
**Created, and it is a defect rather than a nuance.** The panel's new-content detector
|
||||
(`panel_input::detectNewContent` over `guid_diff::GuidBaseline`, feeding
|
||||
`view_mode_model::autoTagNewContent`) tags **every new track to the active mode**,
|
||||
unconditionally: it diffs live GUIDs against the previous tick and never consults the
|
||||
membership index. Fire Ρ while Design is active and, on the next timer tick, the
|
||||
detector re-tags the brand-new result track — and its item — from Arrange to Design,
|
||||
silently reversing the ruling inside a second. Nothing in Ρ's own gesture can outrun
|
||||
it, because it runs after.
|
||||
|
||||
**The fix is one rule, and it is worth stating generally: an explicit tag wins over
|
||||
the detector.** `detectNewContent` drops any added GUID that already carries a
|
||||
membership record (`MembershipIndex::query(guid) != nullptr`) before building its
|
||||
auto-tag input. Two lines, at the point in that function where the model is already in
|
||||
hand. The rule is right beyond Ρ — the detector exists to classify content the *user*
|
||||
made, not content the tool made and has already classified — and it is a strict
|
||||
improvement on an existing rough edge: a track brought back by undo keeps its original
|
||||
mode instead of being re-tagged to whatever mode happens to be active at the time.
|
||||
|
||||
Two alternatives were considered and rejected. Re-arming the baseline after Ρ's
|
||||
gesture (the `bankPanelNotifyProjectLoaded` mechanism) works, but it absorbs an entire
|
||||
tick of genuinely new content silently — a blunt instrument aimed at two GUIDs.
|
||||
Changing `autoTagNewContent`'s own track rule changes Design View's behaviour for
|
||||
every caller in order to serve one.
|
||||
|
||||
The resulting behaviour, stated completely:
|
||||
|
||||
| Active mode when fired | Source afterwards | New track afterwards | What the user sees |
|
||||
| Active mode when fired | Source afterwards | Result track afterwards | What the user sees |
|
||||
|---|---|---|---|
|
||||
| **Arrange** | Design — parked, hidden, FX offline | Arrange — visible, in the mix | The headline case. The design chain vanishes from the arrangement and its audio takes its place, at the same position, same colour, named after it. |
|
||||
| **Design** | Design — visible | Design — visible | Both on the bench, side by side, for A/B. Neither reaches Arrange. |
|
||||
| **Design** | Design — visible on the bench | Arrange — parked while Design is active | The bench keeps only the source. The render is not on screen; it is waiting in the arrangement, and switching to Arrange shows it in the source's place. |
|
||||
|
||||
The Design-active case is coherent — you are iterating on the bench and want the
|
||||
render beside its source — but it means firing Ρ from Design never puts anything into
|
||||
the arrangement. That follows directly from Daniel's stated rule and is a genuinely
|
||||
useful second behaviour, not a defect. It is also the subject of fork Ρ-F2.
|
||||
**The Design-fired case produces no visible change, and that is the ruling's accepted
|
||||
cost.** Fire Ρ from the bench and the immediate feedback is that nothing appears. The
|
||||
price buys the action meaning exactly one thing wherever it is fired — *commit this
|
||||
design work into the arrangement* — which is the reasoning behind the ruling: Ρ is not
|
||||
a capture, and a capture's mode-relative habits do not apply to it.
|
||||
|
||||
**Feedback deserves reconsideration for this case, and only this case.** Ρ is
|
||||
otherwise silent on success because the new track is the feedback; fired from Design
|
||||
there is no visible new track, so a silent success is indistinguishable from a no-op.
|
||||
**[propose at review]** whether the Design-fired path should emit a one-line
|
||||
`ShowConsoleMsg` naming the track it created. Recommendation: yes — it costs one line
|
||||
and one string, and it is the only place in the phase where success is invisible.
|
||||
|
||||
**Lane minting runs**, via `mintManagedLanes(view, nullptr)` before the reapply, on
|
||||
the same path `doMoveItems` already uses — so a track that ends up carrying content
|
||||
@@ -535,8 +644,12 @@ are named:
|
||||
|
||||
- **Not a bank capture, in any form.** No index entry, no ledger record, no
|
||||
generation bump, no instance reload.
|
||||
- **Not multi-track** (this phase — see Ρ-F1). One selected track per fire; more than
|
||||
one refuses.
|
||||
- **Not multi-track.** Ρ-F1, RULED: one selected track per fire; more than one refuses
|
||||
with the message that already exists. Settled, not deferred — no per-track loop is
|
||||
planned, half-built toward, or left a seam for.
|
||||
- **Not mode-following.** Ρ-F2, RULED: the result track is an Arrange member
|
||||
unconditionally. There is no variant that follows the active mode, no per-fire
|
||||
choice, and no bench-visible result — firing from Design puts nothing on the bench.
|
||||
- **Not item-scoped.** No `RENDER_ITEMS_IN_PLACE`, no item-extent range fallback. The
|
||||
seam is left open by the id family; the feature is not built.
|
||||
- **Not a tempo-conforming insert.** No conform variant, ever — a conform would
|
||||
@@ -549,13 +662,14 @@ are named:
|
||||
- **Not a new persisted state.** Membership writes go into the existing `"reasampler"`
|
||||
view section. Ρ adds no key, no version rung, no wire format.
|
||||
- **Not a new directory.** Three small pure additions to existing `core/capture`
|
||||
modules, one new shell TU in `shell/capture`, one row in `main.cpp`.
|
||||
modules, one new shell TU in `shell/capture`, two lines in `panel_input.cpp`, one
|
||||
row in `main.cpp`.
|
||||
|
||||
---
|
||||
|
||||
## Invariant amendments this phase owns
|
||||
|
||||
Two statements in the tree become false the moment Ρ lands, and amending them is a
|
||||
Three statements in the tree become false the moment Ρ lands, and amending them is a
|
||||
**deliverable of the track**, not a follow-up — the precedent is Phase Ψ, where three
|
||||
such amendments were carried as acceptance criteria of the tracks that broke them. A
|
||||
track that lands Ρ without these reads as an invariant breach in review.
|
||||
@@ -572,6 +686,15 @@ track that lands Ρ without these reads as an invariant breach in review.
|
||||
lives in `shell/capture/`, but the sentence reads as a claim about the system.
|
||||
Amend it to be explicit that it scopes to *this directory*, and cross-reference the
|
||||
third verb.
|
||||
3. **`src/core/view/CLAUDE.md` §Invariants** — *"New tracks are tagged to the active
|
||||
mode at creation."* Ρ-F2 makes this conditional for the first time: the detector's
|
||||
active-mode rule now applies only to a GUID that carries **no** membership record,
|
||||
because an explicit tag wins over it (§"Mode transitions"). Amend the sentence to
|
||||
say so, and state the reason in one clause — the detector classifies content the
|
||||
user made, not content the tool made and already classified. `src/shell/panel/`'s
|
||||
own CLAUDE.md describes `panel_input` as "the new-content auto-tag timer" without
|
||||
restating the rule, so it needs no amendment; if that changes, the rule has one
|
||||
home and this is it.
|
||||
|
||||
Root `CLAUDE.md` §"The load-bearing principle" should gain **one sentence**, not a
|
||||
rewrite: that a render which never enters the bank and never leaves it is a third
|
||||
@@ -605,6 +728,11 @@ directory:
|
||||
derivation) and `CaptureResult::absolutePath`. **No behavioural change on the bank
|
||||
path**: the enum defaults to `Bank`, and the bank branch must be byte-identical to
|
||||
today.
|
||||
- `shell/panel/panel_input.cpp` — two lines inside `detectNewContent`: drop added
|
||||
GUIDs that already carry a membership record, so an explicit tag wins over the
|
||||
auto-tag detector (§"Mode transitions"). This edit exists **only because of the Ρ-F2
|
||||
ruling**; without it the ruling reverses itself on the next timer tick. No other
|
||||
function in the file is touched.
|
||||
- `src/app/main.cpp` — one `ActionTableRow`.
|
||||
|
||||
**Performance posture:** every surface is cold — one gesture, once. None of the named
|
||||
@@ -633,9 +761,15 @@ than a discovery. Nothing in Ρ is unit-testable past the pure functions.
|
||||
mono, and confirm it sums at the same level as the stereo source did. This is root
|
||||
`CLAUDE.md`'s existing `[verify — DAW]` on mono-item-on-stereo-track summing,
|
||||
promoted to load-bearing by Ρ.
|
||||
- **Both mode transitions** — fired from Arrange (source parks, new track visible) and
|
||||
fired from Design (both visible, neither in Arrange) — plus the ordering check that
|
||||
the new track is never momentarily parked.
|
||||
- **Both mode transitions** — fired from Arrange (source parks; result track visible
|
||||
and in the mix) and fired from Design (source stays on the bench; result track
|
||||
parked, then present in the source's place after switching to Arrange). **In each
|
||||
case wait out at least one panel timer tick and re-check the membership.** That is
|
||||
the auto-tag-detector regression, and it is the check that catches a missing
|
||||
explicit-tag-wins filter or an untagged item — either of which silently reverses the
|
||||
Ρ-F2 ruling. The old ordering check (that the new track is never momentarily parked)
|
||||
no longer applies: under the ruling the result track is an Arrange member and a
|
||||
Design reapply parking it is correct.
|
||||
- **Undo** — one Ctrl-Z removes the track and item and restores the folder depth; the
|
||||
file survives; the source stays tagged Design.
|
||||
- **The name and colour clone**, including a second run over an already-prefixed track
|
||||
@@ -645,60 +779,51 @@ than a discovery. Nothing in Ρ is unit-testable past the pure functions.
|
||||
|
||||
---
|
||||
|
||||
## Open forks — Daniel's
|
||||
## Rulings — Daniel's, 2026-08-02
|
||||
|
||||
### Ρ-F1 — Multi-track: inherit the refusal, or render each selected track?
|
||||
All three [Daniel]-class forks this doc opened were ruled the same day it was framed.
|
||||
Nothing here is open. This section is an index; each ruling is **specified** in the
|
||||
section that owns it, and that section is the implementation-binding text.
|
||||
|
||||
**Recommendation: inherit the refusal.** One selected track per fire; more than one
|
||||
refuses with the message that already exists. Daniel's phrasing was singular ("the
|
||||
source track"), and it costs zero code.
|
||||
| Fork | Ruling | Specified in |
|
||||
|---|---|---|
|
||||
| **Ρ-F1** | **Refuse** a multi-track selection. One track per fire, inherited from `isMultiTrackStemRender`. A per-track loop is a settled non-goal for this phase, not a deferral | §"The render" — the multi-track paragraph; §"What Phase Ρ explicitly is NOT" |
|
||||
| **Ρ-F2** | **The result track always goes to Arrange**, whatever mode was active. *"For this action which is not a capture, the result track should always go to arrange."* | §"Mode transitions" — the absolute rule, the item tag, and the auto-tag-detector fix it requires |
|
||||
| **Ρ-F3** | **Follow the panel tail setting** (None / Auto / Manual), with both consequences accepted | §"The render" — the tail paragraph and its two numbered consequences |
|
||||
|
||||
**The counter-argument is real**, which is why this is a fork rather than a call.
|
||||
REAPER's model action operates on the selection, and the multi-track *stem-collapse*
|
||||
hazard does not actually apply here: Ρ could loop, running `renderOffline` once per
|
||||
selected track with a one-track source each time, and every individual render would
|
||||
be single-track and safe. What it would cost is not the render but the bookkeeping —
|
||||
each insertion shifts the indices of every later track, so the folder arithmetic must
|
||||
be re-derived per iteration; and the undo label, the partial-failure story ("three of
|
||||
five rendered"), and the selection-afterwards rule all have to be answered.
|
||||
**Ρ-F2 went against the framing, and against the request's own original wording.**
|
||||
The framing recommended mode-following on the strength of Daniel's first phrasing
|
||||
("stays in whatever mode was active") and of one use it enabled — an A/B of a chain
|
||||
against its own render, both on the bench, neither touching the arrangement. The
|
||||
ruling took the alternative the framing had itself named: an absolute rule, because Ρ
|
||||
is *not a capture* and the mode-relative habit belongs to the capture pillar, not
|
||||
here. **That A/B use no longer exists, and no text in this doc or in `PLAN.md` may
|
||||
still claim it as a benefit.** The cost the ruling accepts is that firing Ρ from
|
||||
Design produces no visible change; the compensating console message is a
|
||||
[propose at review] item in §"Mode transitions".
|
||||
|
||||
If Daniel wants multi-track, the honest shape is a **second wave**, not a bigger
|
||||
first one — the single-track verb is a strict prerequisite either way, and the design
|
||||
already leaves room (the placement function takes an index and returns a plan, so a
|
||||
loop just re-reads the depth list each pass).
|
||||
**The ruling also has a cost the framing did not anticipate**, recorded here so it is
|
||||
not read as scope creep at review: the panel's auto-tag detector tags every new track
|
||||
to the active mode, so without an explicit-tag-wins filter the Design-fired case
|
||||
reverses the ruling on the next timer tick. The two-line fix in
|
||||
`panel_input::detectNewContent` is a **deliverable of this phase**, caused by this
|
||||
ruling. Under mode-following it would not have been needed — the detector would have
|
||||
agreed with Ρ.
|
||||
|
||||
### Ρ-F2 — When Design is the active mode, where does the new track go?
|
||||
**Ρ-F1 and Ρ-F3 both matched the recommendation**, so nothing in the spec moved. The
|
||||
counter-arguments are kept because they are the reason each was a fork rather than a
|
||||
call, and they will resurface: for Ρ-F1, that the stem-collapse hazard does **not**
|
||||
apply to a per-track loop (each iteration renders one track), so the refusal is
|
||||
inherited rather than forced — what a loop would actually cost is bookkeeping (indices
|
||||
shift per insertion, so the folder arithmetic re-derives each pass; plus an undo label,
|
||||
a partial-failure story, and a selection-afterwards rule). For Ρ-F3, that a drop-in
|
||||
replacement arguably wants exact length, and that forcing `None` would have kept the
|
||||
exact-bounds gate live on every Ρ render.
|
||||
|
||||
**Recommendation: to Design, as Daniel stated** — the new track takes whatever mode
|
||||
was active. Implemented as written.
|
||||
|
||||
**The alternative is defensible**: make Ρ *absolute* rather than mode-relative — the
|
||||
source always goes to Design, the capture always goes to Arrange, regardless of which
|
||||
stance you fired from. That reading makes Ρ mean "promote this design work into the
|
||||
arrangement" in every context, which is arguably the verb's actual purpose, and it
|
||||
means the action does the same thing wherever you are.
|
||||
|
||||
The relative rule wins on Daniel's explicit words and on one real use: firing Ρ from
|
||||
Design to get an A/B of a chain against its own render, on the bench, without either
|
||||
touching the arrangement. That is a genuinely useful thing the absolute rule cannot
|
||||
express. But it does mean a user in Design mode who expects to have "committed
|
||||
something to the arrangement" has not. **One confirm.**
|
||||
|
||||
### Ρ-F3 — Tail: follow the panel setting, or force None?
|
||||
|
||||
**Recommendation: follow the panel setting** (None / Auto / Manual), for consistency
|
||||
with every other render path and because a decaying design chain wants its tail when
|
||||
its source is about to be silenced.
|
||||
|
||||
**The counter:** Ρ's pitch is a drop-in replacement for the source, and under Auto or
|
||||
Manual the placed item is longer than the window it replaces — which is correct for
|
||||
reverb and wrong for a section you intend to butt against the next one. Forcing None
|
||||
would also keep the exact-bounds gate active on every Ρ render, since that gate runs
|
||||
only under `TailMode::None`.
|
||||
|
||||
A third option exists and is worth naming: follow the panel setting but **run the
|
||||
bounds gate's start-alignment check regardless of tail mode**, since a tail only ever
|
||||
extends the end. That gets both properties at the cost of a small change to the gate's
|
||||
condition, and it is the option I would take if Daniel wants the guard without losing
|
||||
the tail. Not recommended by default only because it edits a shared gate for one
|
||||
caller's benefit.
|
||||
**One unexercised alternative, recorded and not built.** For Ρ-F3, a third option was
|
||||
floated at framing: follow the panel setting *and* run the bounds gate's
|
||||
start-alignment check regardless of tail mode, since a tail only ever extends the end.
|
||||
It would recover the widening guard on the start edge without losing the tail, at the
|
||||
cost of editing a gate shared by every capture path for one caller's benefit. It is
|
||||
**not ruled in** and is not part of this phase. If the unguarded Auto/Manual render
|
||||
ever produces a real bug, this is the shape of the fix.
|
||||
|
||||
Reference in New Issue
Block a user