Repository navigation
Render stored mute automation on the strip #1651
Description
Render stored mute automation on the strip
Proposed by Research: render stored session automation in the engine, identically on every
platform (#1058), answers A1.5, A2 and A10, under decision 15 D15-16
(docs/rulings/live-updates-seamless-swaps-and-one-control-plane-2026-10-05.md). A draft for root
to file. Code anchors verified on 6ee64f484. Batch R2. It lands in the same push as #1652
Follow an automated mute on following sends in render and #1653 Write a following send's route
cell from the shared commit: #1651 must not reach main without them, or a send that follows an
automated mute leaks.
Product outcome
A producer's stored mute automation (row 6) plays on every host. A strip lane mutes and unmutes at
the exact samples the session names, over the session mute ramp. Solo (the browser's host
overlay) and VCA mutes still compose with the automated mute. A mute automation edit is a carried
rebuild; a static mute edit on an automated lane, and a browser live mute command on it, commit as
model_only on both hosts through the one shared rule of #1648. Sends that follow the strip's
mute are #1652 and #1653.
Context
- The mute composition.
user_mute || vca_mute || (any_solo && !solo_safe && !soloed)is
written once,LiveControlSoloState::effective_mute(crates/host-core/src/solo.rs:255-263).
Admit browser live edits in the Worker through the committed model (Admit browser live edits in the Worker through the committed model #1382) D3 composes the solo
term in the shared commit from the browser's host overlay; the C ABI passes no overlay. Deliver
value-only VCA edits to the running C ABI plan (Deliver value-only VCA edits to the running C ABI plan #1247) D2 emits stripMuterecords from the
effective mute. - The mute stage.
FaderRampStage::set_muteretargets the lane's gain to 0 or to the
rememberedfader_gain(crates/builtins/src/lib.rs:2773-2794); the kernel clears a muted lane
from the frame its ramp settles on (crates/lane/src/kernels/builtins.rs:159-202). Apply timed operations inside a block on the strip fader stage #1638 D1
addsSetMute { muted, samples }at an offset andRememberGain. - The cell. Hold live values in latest-target cells on both hosts (Hold live values in latest-target cells on both hosts #1312) D3 gives each mute
lane a cell of two words (value bits, ramp). - Seam. The fader and mute stage is seam-side (
crates/builtins-compiler/src/lib.rs:758-771), so
mute automation never declines a mono collapse. - The classifier row list and the predicate are Classify fader automation edits as carried rebuilds #1648 D1 and D3.
Decisions frozen for this slice
- D1. Mute cells (id 6). Only
stepwith values 0 or 1 (Validate builtin automation targets against their rows at preparation #1645 D1). A step curve is constant
between its discontinuities, so a mute cell has no grid events, only jumps.- The stage mute of a strip lane is
curve || terms, wheretermsisvca_mute || solo_mute. - The terms word. For an automated mute lane, this slice gives Hold live values in latest-target cells on both hosts #1312's mute cell value word
the meaningterms(the mute terms the session does not automate); for every other lane the
word keeps Hold live values in latest-target cells on both hosts #1312's meaning. Preparation seeds it. Hold live values in latest-target cells on both hosts #1312 is not amended: this slice adds the
meaning for a lane kind that exists only from this slice on. - At each discontinuity at render sample
r, and at the first sample of the block after the mute
cell changes, render composes the stage mute. Only a change emits Apply timed operations inside a block on the strip fader stage #1638'sSetMuteat that
offset, over the session mute length (Hold the automation jump lengths in a plan cell #1639'smuteword) for a curve event and over the
cell's ramp word for a cell change. The caller reports the mute ramp to the cell's fader events
through Compile stored automation into per-cell events in node time #1637'shold_until. - At a session seek where the seek reaches the fader node, the stage mute is set exactly (
n = 0). - While a lane's mute ramp is in flight or the lane is muted, fader events only remember the gain
(Apply timed operations inside a block on the strip fader stage #1638 D2).
- The stage mute of a strip lane is
- D2. Edits, one rule on both hosts.
- The classifier's row list (Classify fader automation edits as carried rebuilds #1648 D1) gains row 6: a mute automation edit is a carried
rebuild (Classify fader automation edits as carried rebuilds #1648 D2 carries mute cells by the same address scheme). A static mute edit on an
automated lane gives no record (Classify fader automation edits as carried rebuilds #1648 D3); a browser live mute edit on it reaches that rule
through the Worker's apply (Admit browser live edits in the Worker through the committed model #1382) and repliesmodel_only. This turns red Classify fader automation edits as carried rebuilds #1648's gate 1
case "A mute (row 6) entry change givesOkwith no records"
(crates/host-core/tests/live_delta.rs); this slice rewrites that case in place to expect
Err(LiveRebuild::Automation)(AGENTS.md: a change that supersedes a test rewrites it in the
same PR), and gate 4 below holds the new rule. - For an automated mute lane, the shared commit's mute composition (Deliver value-only VCA edits to the running C ABI plan #1247's VCA mute, Admit browser live edits in the Worker through the committed model #1382 D3's
solo overlay) writes the strip mute cell'stermsinstead of emitting aMuterecord, and only
when the terms' bits change. On the C ABI the overlay is absent, sotermsis the VCA mute;
the code is the same on both hosts.
- The classifier's row list (Classify fader automation edits as carried rebuilds #1648 D1) gains row 6: a mute automation edit is a carried
- D3. The acked-batch question: can an ack ever precede a drop? No. Every check of a
transaction (and of Admit browser live edits in the Worker through the committed model #1382's overlay) precedes the first cell write; writes cannot fail; render
generates curve events from the plan.
Deliverables
- D1: mute events on the fader stage (both bank forms and the fused form), and the terms word's
seed. - D2: the classifier row and the shared commit's terms write.
Authorized paths
crates/builtins-compiler/src/lib.rs(the fader stage's mute events and the terms read),
crates/builtins/src/lib.rs(only if Apply timed operations inside a block on the strip fader stage #1638's mute operation needs a change)crates/host-core/src/{prepare.rs,live_delta.rs,solo.rs},crates/host-core/tests/live_delta.rscrates/control-plane/src/(the overlay composition's terms write)crates/capi/src/runtime/live_tests.rs,hosts/host-web/src/tests.rs,
crates/control-plane/tests/mute_automation_realtime.rs(new)crates/control-plane/Cargo.toml(abench-supportdev-dependency for that binary, only if an
earlier slice has not added it),Cargo.lock
Non-goals
- Sends that follow the mute, route lanes and the route op (Follow an automated mute on following sends in render #1652 and Write a following send's route cell from the shared commit #1653).
- Options B, C and D of OQ1.
Hazards
- One push with Follow an automated mute on following sends in render #1652 and Write a following send's route cell from the shared commit #1653. Alone, this slice renders an automated mute while a following
send keeps its prepared gate; the send leaks. Root merges the three together. - A redundant retarget moves bits (README "Change only";
crates/host-core/src/solo.rs:58-67).
D1 and D2 compare before they write or retarget.
Objective gates
- Exact samples (both hosts:
crates/capi/src/runtime/live_tests.rsand
hosts/host-web/src/tests.rs, new). Trackt, with no following send, has a mute entry on
bothwith steps 0, 1, 0, at samples that are not grid samples. Each mute ramp starts at its step
sample; from the end of each ramp the output is bit-identical to a plan prepared with the same
static mute, fed the same PCM. At quanta 128 and 100. - Mute and fader compose (
crates/builtins-compilertest, new). A fader ride and a mute step on
one lane: fader events inside the mute only remember the gain; after the unmute ramp the lane is
on the ride, bit-identical to the stage driven directly with Apply timed operations inside a block on the strip fader stage #1638's operations. - Composition (browser, new). Solo on another track mutes
tby solo while its curve is 0;
tmutes; unsolo restores the curve's state. A VCA mute oftcomposes the same way, on both
hosts. - Edits (
crates/host-core/tests/live_delta.rs, new). A mute entry change gives
Err(LiveRebuild::Automation); a static mute change on an automated lane gives no record; on the
other, non-automated lane it gives itsMuterecord; a VCA mute oftgives one terms write and
noMuterecord for the automated lane. Browser: a live mute edit through the Worker's apply on the automated lane replies
model_onlyand moves no bit. - Realtime.
crates/control-plane/tests/mute_automation_realtime.rs(new integration binary in control-plane, the crate that
holds the shared commit,control_plane::SessionState, Extract the C ABI control plane into a portable crate both hosts call #1309 D1-D9. A host-core binary cannot
reach the commit without a dev-dependency cycle, since control-plane depends on host-core.
scripts/check-bench-policy.sh:257-280allows abench-supportdev-dependency only in a
crates/manifest, so no host-web binary can link it. The binary linksbench_support::alloc
and callsassert_installed()first). It drives the script through control-plane's shared
commit and host-core's render session, the code the browser Worker (Admit browser live edits in the Worker through the committed model #1382) and the C ABI both
run. For gate 1's script:
allocations == 0 && frees == 0around every render call after warm-up.
cargo build --locked --release -p audit -p capi && ./target/release/audit capireports all
violation counts 0. - Commands:
cargo test --locked -p host-core --features host-core/test-support,
cargo test --locked -p builtins-compiler --features test-support,cargo test --locked -p capi,
cargo test --locked -p host-web --features host-web/test-support,
cargo test --locked -p control-plane --features test-support- the workspace debug leg (
test-debug-a) and the DSP leg (test-debug-b) in
.github/workflows/qualification.yml cargo build --locked --release -p audit && bash scripts/trace-builtins-audit.sh target/release/audit && bash scripts/trace-builtins-graph-audit.sh target/release/auditbash scripts/check-web-audioworklet.sh,bash scripts/test-web-audioworklet.shbash scripts/check-realtime-policy.sh,bash scripts/check-host-core-policy.sh,
bash scripts/check-workspace-policy.sh,bash scripts/check-cross-targets.sh(README F19, the iOS memset rule),
bash scripts/run-aarch64-tests.sh debugcargo clippy --locked --workspace --all-targets --all-features -- -D warnings,
cargo fmt --all -- --check
- No rendered bit moves for a session with no stored automation:
audit capigives the same
pcm_digestat base and head (PR evidence); the browser legs pass with unchanged digests.
Test value
- Gate 1: red if a mute step snaps to the grid, uses another row's length, or ramps where the static
plan does not. - Gate 2: red if a fader event restarts the mute ramp, which
set_fader_gaindoes today. - Gate 3: red if solo or a VCA mute overwrites the curve instead of composing with it.
- Gate 4: red if the mute row stays masked, a static edit or a live command fights the curve, or a
VCA mute emits a record the next curve event would contradict. - Gate 5: red if the mute events or the cell read allocate on render.
Dependencies
Batch R2. Direct dependencies:
- Classify fader automation edits as carried rebuilds #1648 Classify fader automation edits as carried rebuilds (the row list, the predicate and
the carry; it brings Prepare stored fader automation and render it flat #1646 and, through it, Admit browser live edits in the Worker through the committed model #1382 and Deliver value-only VCA edits to the running C ABI plan #1247). - Hold the automation jump lengths in a plan cell #1639 Hold the automation jump lengths in a plan cell (the mute ramp length is its
mute
word).
#1638's SetMute and the cells of Hold live values in latest-target cells on both hosts
(#1312) arrive through #1648. #1652 and #1653 depend on this draft and land in the same push.
Amendment (controls coordinator, 2026-10-14)
Checked against 12d4a4f9d.
- OQ1 = C: a hand mute ORs. The stage mute of an automated lane is
curve || terms, and
termsnow includes the lane's own static mute:static_mute || any reaching VCA mute || solo term, the effective mute ofcontrols::effective(C7) without the curve. So aSET_CONTROL mute
on an automated lane is a livetermswrite, pathlive, notmodel_only. D2's "A static mute
edit on an automated lane gives no record (Classify fader automation edits as carried rebuilds #1648 D3)" and the browser sentence are replaced by
this rule; gate 4 tests a hand mute that closes an open curve, and a hand unmute that leaves a
closed curve closed. When the entry is removed, the lane's mute is the static mute, as before. - Solo is on both hosts (C8, Solo a strip on the C ABI through the core's monitor overlay #1559): "On the C ABI the overlay is absent" is no longer true; the
code is the same on both hosts. - Moved files.
crates/host-core/src/solo.rsis deleted; the composition is
controls::effective(crates/controls/src/effective.rs). Authorized paths change to
crates/controls/src/effective.rs(read) andcrates/host-core/src/live_delta.rs. - The jump length is
LiveRamps::for_row(Mute)(Compile stored automation into per-cell events in node time #1637, amended).
Amendment (controls coordinator, batch 10, 2026-10-14)
- Automated VCA mutes (root's F20 ruling) compose with this slice's
curve || termsin
Render stored VCA offset and mute automation #1655: the stage mute of a member lane gains|| vca_curve(τ)for each reaching VCA whose mute is
automated. This slice'stermsword keeps the static VCA mutes.
Amendment (controls coordinator, batch 13b, root ruling)
- A
SetMutethat changes nothing does nothing (constraint). Today Apply timed operations inside a block on the strip fader stage #1638'sSetMute { muted, samples }reachesmute_channel(crates/builtins/src/lib.rs, throughapply_operation; the
liveset_mutecalls the same function), which always writesmuted, retargets the lane to0.0
(muted) or to the rememberedfader_gain(unmuted) oversamples, and setsmute_rampwhen
samplesis not 0, whether or not the lane's mute state changed. So aSetMutethat repeats the
lane's state restarts a fader ramp in flight over the mute length (its shape changes), and its
mute_rampmakesgain_heldtrue for that window, so the fader events inside it are only
remembered. The rule: aSetMutewhosemutedequals the lane's and channel's current mute state
leaves the stage as it was (no retarget,mute_rampand the ramp's remaining frames unchanged).
D1's "only a change emits" still holds; this rule makes the stage itself safe against a repeated
SetMute, whatever composed it. The change
is incrates/builtins/src/lib.rs, which this slice's authorized paths already allow; its test
goes incrates/builtins/tests/fader_ramp.rs(authorized). - Gate 2, new case. On the stage driven directly with Apply timed operations inside a block on the strip fader stage #1638's operations: (a) an unmuted lane
with a faderRetargetGainin flight takesSetMute { muted: false, samples: 240 }at an offset
inside the ramp, then aRetargetGain10 samples later; (b) a lane in a mute ramp toward0.0
takesSetMute { muted: true, samples: 240 }inside it. Each block is bit-identical to a twin
that never received the repeatedSetMute, and in (a)gain_heldstays false. Red if a repeated
SetMuteretargets the lane or starts a mute ramp. Gate 7 holds that the change moves no bit of a
session with no stored automation.
Amendment (controls coordinator, batch 18 spec check, 2026-10-17)
Checked against ad03780ee. #1637, #1638, #1639, #1312, #1648 and #1650 have landed (batch 17,
PR #1712).
- Anchors. The mute composition is
controls::effective_strip
(crates/controls/src/effective.rs:326-366);EffectiveStrip::mute(:198) is
own || vca_mute || solo_term. The stage:set_muteandmute_channel
(crates/builtins/src/lib.rs:3129-3155),apply_operation(:3167-3178),FaderOperation
(:4947-4958); kernelscrates/lane/src/kernels/builtins.rs:187and:273. The cells:
FaderLaneandFaderCells(crates/builtins-compiler/src/lib.rs:255-427); the drain
drain_fader_controls(:3940). Seam:FaderBankProcessor::seam_side(:3433). Classifier:
controls::automation_row_renders(crates/controls/src/automation.rs:383-387),
rendered_automation_changed(:472-489), step 3 (crates/host-core/src/live_delta.rs:415-418). - Not landed. The batch 13b rule has not landed:
mute_channelstill writes and retargets on
every call. It stays this slice's deliverable. - D1 on batch 17's code.
- Mute cells join the plan's one program (
prepare_fader_automation,FaderAutomationPlan),
after the fader cells, so a fader-only session builds the same program. A new
controls::MUTE_CELL_RULEsits besideFADER_CELL_RULE: completionEnd,
JumpLength::Block(the caller resolvesLiveRamps::for_row(Mute)from the block's jump
lengths, asblock_fader_jumpdoes), seekExact. - The lane's slot (
LaneFaderAutomation) gains the mute channels: cell index,CellState,
terms, ramp word, adoption. Samea(n)(R17-2), same span fromset_block_time(R17-3). - The prepared stage mute of an automated lane is
first start_value != 0 || terms
(prepared_fader,:6777). Preparation seedstermswithEffectiveStrip::mute. - The terms word. Under OQ1 = C,
termsequalsEffectiveStrip::mute. So theMuterecord
the shared commit already emits (only on change, with its ramp) is the terms write, and the
cell's value word carries it unchanged. No new record, no new cell, no edit under
crates/engine/src/realtime/. The drain (the bank's and the scalar oracle'sdrain_controls)
gives a mute value of an automated channel to the slot: the slot storestermsand the ramp
word, composestarget || termswith the cell's current target (the curve state the stage
has heard), and appliesSetMuteat the block boundary over the ramp word only when it
differs (theread_offsetspattern). The block's curve events then compose with the new
terms. - Curve events go through the one
FaderEventDriver(oneFaderOperationSourceper block; the
sink takes one gain and one mute operation per lane, channel and offset, gain first). A mute
cursor pushesSetMuteover the block's mute jump length only whencurve || termschanges. - Fix. "The caller reports the mute ramp ... through Compile stored automation into per-cell events in node time #1637's
hold_until" cannot hold:
CellEventsborrows the state (Render moving stored fader automation, seeks and latency #1647 constraint 2) and D1.2 is deleted (B17-R11). Instead, at
each mute event at offseto, the driver ends that channel's fader piece atoand starts the
next piece ato + 1with the hold theSetMutegives (muted: held to the block's end; an
unmute overM:E = o + M), through the shipped D1.6 obligation and cut atE. A gain
operation atokeeps the old hold (gain before mute). Bounded by the block's mute events; no
buffer. - Seeks. A seek
Setcomposes like any event. With the 13b rule, aSetthat does not
change the state leaves the stage as it is, so a ramp toward that state finishes (as Render moving stored fader automation, seeks and latency #1647
constraint 5 for a held fader). Root confirms this reading (report ruling 1). - Scalar oracle (R17-4). Same slot and driver. A strip with mute automation and no fader
automation bindsAutomatedFaderProcessor(:7497) too. - CanonicalFpEnv (R17-7). The mute path has no float arithmetic (values 0 and 1, then
booleans), and preparation already runs inside the guard. No new guard. - For Follow an automated mute on following sends in render #1652. This slice makes the program
Arcand each strip's mute cell indices readable
by host-core preparation, so Follow an automated mute on following sends in render #1652's route lanes read the same table.
- Mute cells join the plan's one program (
- D2 on batch 17's code.
- Row 6 joins
automation_row_renders;rendered_automation_changedthen makes a mute entry
changeErr(LiveRebuild::Automation). - Fix. The fader-only readers of that predicate test the fader row:
fader_automationand
automated_cell(automation.rs:393-448),model_fader_offsets(effective.rs:488),
mask_fader_offsets(mask.rs:137) and B17-R14'sautomates
(crates/control-plane/src/control.rs:2613-2616). Without it, a mute entry gives its lane a
hand offset (S,fader_offset), a fader edit becomes an offsets write, and a mute-only
rebuild is flaggedTransitionFallback. - The second bullet is replaced by the terms word above: a hand mute (OQ1 = C), a VCA mute
and solo give the lane itsMuterecord, as today; render ORs it with the curve. The commit
code does not change. - Carry (Classify fader automation edits as carried rebuilds #1648 D2).
continued_cellsbits 2 and 3 are the mute lanes (a new
controls::continued_mute_lanes, set in host-core'sStripJoin,prepare.rs:5338); graph
passes theu8unchanged (a doc edit only).LaneFaderAutomation::adoptcopies a mute
channel's state,termsand ramp by address; a changed table or a moveda(n)repositions;
the first block emitsSetMuteover the mute jump length only on a change. A lost mute cell:
the join emits aMuterecord ofnext's effective mute (asjoin_level_records,:4660,
releases a fader lane); the 13b rule makes it a no-op when the stage agrees. - TransitionFallback and watermark. Nothing new: a mute adoption never stretches a live
move, soautomatesstays fader-only. A terms write is a live record; a mute automation edit
is a rebuild whose jump lands at the adoption block. - Superseded tests, rewritten in place:
crates/host-core/tests/live_delta.rs:1411-1451
(Classify fader automation edits as carried rebuilds #1648 gate 1's three mute asserts now expectErr(LiveRebuild::Automation)) and the mute
branch ofa_fader_automation_edit_rebuilds_alike_on_both_hosts
(hosts/host-web/tests/control_parity.rs:2846-2857,ModelOnlybecomesRebuild). - Root rules whether this slice sets the control listing's
automatedflag on an automated
mute row (Store a hand move on an automated fader as a dB offset #1650 D4; report ruling 2). Until then the flag stays fader-only.
- Row 6 joins
- Gates.
- Gate 1: both hosts in one binary,
hosts/host-web/tests/control_parity.rs(batch 17's form),
at quanta 128, 100 and 37, plus a session seek into a muted span (muted exactly from the seek
sample). Widths: the same script incrates/host-core/tests/stored_mute_automation.rs(new)
atSimd8(withavx2) andSimd4, split and fused, live and between render calls, and the
scalar oracle: the same bits. - Gate 2 runs in
crates/host-core/tests/stored_mute_automation.rs, against the stage driven
directly with Apply timed operations inside a block on the strip fader stage #1638's operations, with a new case: fader events inside a curve mute, and the
move atEafter a curve unmute, at quanta 128, 100, 64 and 37, the same bits. Red if the
fader piece is not cut at the mute event. The 13b case stays in
crates/builtins/tests/fader_ramp.rs. - Gate 3 runs in
control_parity.rs, both hosts. - Gate 4: "one terms write and no
Muterecord" now reads "oneMuterecord (the terms
write)". New cases: a mute entry ontgivestno hand offset and keepsS; aSET_CONTROL fader_dbont's unautomated fader gives aFaderDbrecord, not an offsets write. Red if a
fader-only reader sees the mute entry. - Gate 8 (new, carry;
stored_mute_automation.rs): a changed curve, an added entry and a removed
entry each rebuild and carryt's stage; the lane jumps at the adoption block only when
curve || termschanges; after removal it holds the static mute; the swap block allocates and
frees nothing. A mute-only session's rebuild with a live fader move in flight completes
Exact. Red if a mute cell restarts, misses or repeats the adoption jump, or a lost cell keeps
the curve's state. - Gate 6:
scripts/check-realtime-policy.shis deleted; runcargo build --locked --release -p realtime-policy && ./target/release/realtime_policy. Addcargo test --locked -p controls
andcargo test --locked -p builtins.crates/control-plane/Cargo.tomlalready has
bench-support(Compose VCA offsets with stored fader automation #1649).
- Gate 1: both hosts in one binary,
- Authorized paths (this list replaces the earlier ones). Render paths are marked (R). None is
undercrates/engine/src/realtime/.- (R)
crates/builtins/src/lib.rs(the 13b rule inmute_channelonly),
crates/builtins/tests/fader_ramp.rs - (R)
crates/builtins-compiler/src/lib.rs(mute cells, slot, seed, drain branch, driver, scalar
oracle, adopt, export for Follow an automated mute on following sends in render #1652) crates/controls/src/{automation.rs,effective.rs,mask.rs,lib.rs},
crates/controls/tests/automation.rscrates/control-plane/src/control.rs(B17-R14'sautomatesonly)crates/host-core/src/prepare.rs(the strip join only),crates/host-core/src/live_delta.rs
(docs only),crates/graph/src/lib.rs(GraphStripMove::continued_cellsdoc only)crates/host-core/tests/{live_delta.rs,stored_mute_automation.rs},
crates/capi/src/runtime/live_tests.rs,hosts/host-web/tests/control_parity.rs,
hosts/host-web/src/tests.rs,crates/control-plane/tests/mute_automation_realtime.rs(new)docs/SESSION_SCHEMA_V1.md(:290-305, the mute row renders),docs/REALTIME_MEMORY.md
(:13)- Removed:
crates/host-core/src/solo.rs(deleted),crates/control-plane/Cargo.toml,
Cargo.lock(no new dependency).
- (R)
Decision record: T1 (batch 18)
- Shipped. The batch 13b rule:
FaderRampStage::mute_channel(crates/builtins/src/lib.rs)
returns at once whenmutedequals the channel's current mute state. No retarget, and
mute_rampand the ramp's remaining frames stay as they were. One compare on aboolarray
element; no allocation, no new panic path. Every route reaches it:apply_operation'sSetMute
and the liveset_mute, on the Simd8 and Simd4 banks and the scalar track. - Gate (13b Gate 2, new case).
crates/builtins/tests/fader_ramp.rs,
a_repeated_mute_leaves_the_stage_as_it_was, on every subject (Simd4, Simd8 withavx2,
scalar), at quanta 128 and 100: (a) an unmuted lane withRetargetGainin flight takes
SetMute { false, 240 }at offset 20 and aRetargetGainat offset 30; (b) a lane in a 300-sample
mute ramp takesSetMute { true, 240 }at offset 20; (c) the liveset_muterepeats the state
between blocks. Each block, both planes, is bit-identical to a twin without the repeat; the held
flag agrees, and in (a)gain_heldstays false. Red on the basemute_channel(case (a), Simd4,
q=128, block 0: the repeat restarts the fader ramp over 240 samples); green with the change. - Superseded tests, rewritten in place (each used a repeated zero-window unmute to end an
unmute ramp, the behaviour this rule removes):crates/builtins/tests/fader_ramp.rs::the_held_flag_follows_the_mute_ramp: mutes the lane
first, then unmutes over 40, so the unmute is a real change.crates/host-core/tests/stored_fader_automation.rs:
a_live_write_that_frees_a_held_lane_releases_its_hold,
a_live_write_that_frees_a_lane_with_nothing_owed_releases_its_holdand
an_offsets_jump_at_the_entry_that_frees_the_lane_keeps_its_ramp(ramping form) free the lane
with a liveFaderDbwrite (live_fader,Act::fader) instead. The R17-10 release depends on
the stage's held state at the block entry, not on the record kind, so the gates keep their
claims. This edit is outside the spec's authorized list (a test file of batch 17's slice); it is
mechanical to the new rule.
- Measured.
audit capipcm_digestbffc21e857b5e134(base: the same),allocations0. - Departures. None from the rule's text.
Decision record: T2 (batch 18)
- Shipped (no rendered bit moves). A new
controls::fader_row(target)(strip, builtins, row
5) is the fader-only predicate;automation_row_rendersis todayfader_row(target)and T3a
adds row 6 to it alone. The fader-only readers now testfader_row. - Reader classification (every reader of
automation_row_rendersat0e078aad8):- Fader-only, now
fader_row:fader_automation(automation.rs; also feeds
continued_fader_lanes,ModelView::fader_automation,OwnerEdits::take_fader_automation
and builtins-compiler's per-strip cells),model_fader_offsets(effective.rs; feeds
EffectiveStrip::fader_offset/offsets_sumandmask_values' automated lanes),
mask_fader_offsets(mask.rs), and B17-R14'sautomates(control-plane/src/control.rs). - Any rendered row, unchanged:
rendered_automation_changed(a mute entry change must rebuild,
D2), andautomated_cell(keyed by the address's own row: it already compares
target.control == address.control, soautomated_fader_lanes, its one caller, asks at
fader_dband a mute entry never matches; at a mute address it will say where T3a's mute cell
exists, which is the doc's claim). - Any rendered row, outside T2's files:
prepare_fader_automation's early return
(builtins-compiler/src/lib.rs:2095). It is right for T3a (the program then also builds mute
cells); until T3a it is the same predicate.
- Fader-only, now
- R18-4 (withdrawn by root at U1 fold 1). There is no general claim that a mute adoption never
stretches a move: the U1 review found a hand or VCA mute committed in the same transaction as a
mute curve edit landing over the mute length instead of its own ramp. B17-R14's window stays
fader-only because a mute cell's adoption lands only aSetMute, never a level move overJ,
and the U1 fold 1 fix makes that adoption land as committed: a terms record at the swap block
lands over its own ramp word, and the adoption landing adds only the curve's own change over the
mute length. So a successor that automates only mute rows completesExact. - Gate.
crates/controls/tests/automation.rs::a_mute_entry_reaches_no_fader_only_reader: a
bothmute entry before aleftfader entry onvocal; the fader cells are the fader entry's
left lane only,automated_fader_lanes == [true, false], the right lane has no hand offset and
the sameSbits as the session without the mute entry, andmask_valuesmasks the right
lane's static dB. Row 6 cannot render before T3a, so the test is green on the base too; its
predicate asserts (fader_rowrefuses a mute target, every fader target renders) hold the split
now. Probe (not committed): with row 6 added toautomation_row_renders, the test stays green
with the split and turns red withfader_automation(the mute entry takes the left cell) or
model_fader_offsets([Some(0.0), Some(0.0)]) reverted to the old predicate.
mask_fader_offsetsreverted is not observable: a validated mute entry holds+0.0offsets,
so copying one moves nothing. The control-planeautomateschange has no T2 test (no mute row
renders yet); T3a's gate 8 (a mute-only rebuild with a fader move in flight completesExact)
holds it. - Ran.
cargo test --locked -p controls --all-features,-p control-plane --features test-support,-p host-core --features host-core/test-support,-p capi: all pass. fmt,
clippy (controls,control-plane, all targets and features) and the no-feature check pass. - Departures.
automated_cellis not changed (above). None else.
Decision record: T3a (batch 18)
- Shipped (row 6 renders).
controls::automation_row_rendersisfader_row || mute_row(the
only reader that gains row 6; T2's fader-only readers are unchanged). New incontrols:
MUTE_CELL_RULE(grid 64,JumpLength::Block, completionEnd, restartable, seekExact; the
caller resolvesLiveRamps::for_row(Mute)),mute_rowandmute_automation(the
fader_automationrule on row 6). - Program.
prepare_fader_automationpushes every fader cell first (unchanged order), then
one mute cell per automated mute lane in strip and channel order. A fader-only session builds
the same program; a mute-only session builds mute cells only (the early return already reads
automation_row_renders). The prepared stage mute of an automated mute lane is
first start_value != 0 || terms,termsseeded withEffectiveStrip::mute
(FaderAutomationPlan::mute, read byprepared_fader). - Slot.
LaneFaderAutomationgainsmute: [Option<AutomatedMuteCell>; 2](cell index,
CellStateseeded with the prepared curve value,terms) andmute_jump, both from
set_block_time(block_jumpsreplacesblock_fader_jumpand resolves both rows through
LiveRamps). A strip with mute automation and no fader automation gets a bank slot and, on the
scalar oracle, bindsAutomatedFaderProcessor(R17-4); the bank resource charges its slots. - Drain branch.
drain_fader_controls(bank, block and carry drains) and the oracle's
LiveControlFaderProcessor::drain_controlsgive a mute value of an automated channel to
LaneFaderAutomation::mute_terms(shared helperapply_mute_value): it storesterms, composes
target != 0 || termswith the cell's current target and appliesSetMuteover the record's
ramp word only when that differs from the stage's mute. A non-automated channel of the same
record goes to the stage as before. The commit code does not change. - Driver.
FaderEventDriverholds oneMuteCellCursorper automated mute channel beside the
fader cursors (samea(n), same span, same first-blockSetrule). At each offset the fader
cursors push first; then each mute event composescurve || termsand pushesSetMuteonly on
a change (jump: over the block's mute length; seekSet:0), into the sink's mute slot, so
the stage applies gain before mute.finishdrains the mute cursors. A mute event in the block
makes the fused fader-matrix pair decline that block (itsnext_offsetis in the block). - R18-1 holds through the 13b rule and the change-only compare: a seek
Setthat leaves
curve || termsequal to the stage's mute pushes nothing, so a ramp toward that state finishes. - Export for Follow an automated mute on following sends in render #1652.
PreparedBuiltinsSession::strip_mute_cells(&self, track_id: &str) -> Option<(Arc<automation::Program>, [Option<u32>; 2])>: the plan's one program (Arc::ptr_eq
with the fader owners') and each lane's mute cell index,[left, right];Nonewhen the strip
automates no mute lane. Read it before lowering (into_graph_artifacttakes the plan). - Docs.
host-core/src/live_delta.rs(step 3 and theMuterecord paragraph),
host-core/src/prepare.rs(LiveRebuild::Automation),docs/SESSION_SCHEMA_V1.md(the mute
row renders),docs/REALTIME_MEMORY.md. - Superseded tests, rewritten in place.
crates/host-core/tests/live_delta.rs
(a_fader_automation_edit_is_a_rebuild: the three mute asserts expect
Err(LiveRebuild::Automation)) andhosts/host-web/tests/control_parity.rs
(a_fader_automation_edit_rebuilds_alike_on_both_hosts: the mute entry isEditPath::Rebuild).
live_delta.rsis gated on host-core'scontrol-providerfeature: run with
--features host-core/test-support,host-core/control-provider(70 pass). - Gates (all pass).
crates/host-core/tests/stored_mute_automation.rs(new; every form: Simd8 on AVX2, Simd4,
scalar oracle; each with no live control, live controls, and between render calls):
a_mute_step_mutes_the_lane_at_its_exact_sample_over_the_mute_length(steps 0, 1, 0 at 1,000
and 2,500, quanta 128, 100 and 37:SetMuteat exactly those samples overM = 480, output
bit for bit the stage driven directly; render allocates nothing);
a_curve_that_starts_muted_prepares_the_lane_muted;
a_seek_that_leaves_the_mute_unchanged_lets_the_unmute_ramp_finish(R18-1: bit-identical to
the no-seek twin; and a seek into the muted span sets the lane exactly,n = 0);
a_hand_mute_on_an_automated_lane_composes_with_the_curve(the drain branch, OQ1 = C);
a_mute_entry_leaves_a_fader_ride_as_it_was.crates/builtins-compiler/src/lib.rs
tests::the_mute_cells_follow_the_fader_cells_in_the_one_program(cell order, program bytes,
the export, a mute-only program).
- Measured. One-time PR evidence (not committed): a fader-only session (three tracks: a fade,
a step and fade, a step; live mutes, a seek; every form; quanta 128 and 100) renders the same
bits at basee7848d05aand head (FNV digesta58534af7e8b0509both).audit capi
pcm_digestbffc21e857b5e134, all violation counts 0 (audit capireports no
capi_retainedfield).fader_automation_sizes().lane_slot_bytes304 -> 392 (two mute
channels and the mute jump word);program_overhead_bytes64 andoffsets_cell_bytes120
unchanged.realtime_policy: ok, 161 marked regions (base 160:apply_mute_value). - Departures.
- The slot stores no ramp word and no adoption for a mute channel: the drain uses the ramp word
at once and curve events use the block's mute length, so nothing reads them before T4's
carry; T4 adds whatadoptneeds. - The defensive refusals of a mute entry use the code
host.automation.mute(unreachable for a
validated session, as the fader's). TestOnlyFaderEventgainsmute: bool(the probe records each pushedSetMute).
- The slot stores no ramp word and no adoption for a mute channel: the drain uses the ramp word
- What T3b must add. A mute event inside a block renders correctly on a lane with no fader
automation (all gates above). On a lane with both, the in-block fader cut is missing: after an
in-block mute atothe fader cursor still pushes its later events as free (the stage only
remembers them, and no obligation is set), and after an in-block unmute overMthe fader
events inside[o, o + M)are only remembered with no move owed atE = o + M, so the lane
can end the unmute ramp at a stale gain and the cell's committed target suppresses the
correction. T3b: at each pushedSetMuteatoon a channel with a fader cursor, end that
channel's fader piece ato, start the next ato + 1with the hold theSetMutegives
(muted: held to the block's end; unmute overM:E = o + M), mark owed events belowE
through the D1.6 obligation and cut atE; a gain operation atokeeps the old hold (gain
before mute). Gate 2 holds it. - For T4.
LaneFaderAutomation::adoptdoes not touch the mute channels yet (a carried lane
keeps its prepared mute seed andterms, is not fresh, and lands noSet); the carry drain
already routes an automated channel's mute value to the predecessor slot'sterms, which
composes onto the predecessor's stage before export.
Decision record: T1 gate power check (root, batch 18)
- Each batch 17 mutation was applied again at
0e078aad8. The nothing-owed gate (P1b at q37) is red
under release-only-when-owed and under no-release. The F1 gate is red when theheld_throughreset is
deleted, and its rewritten ramping form is red alone. a_live_write_that_frees_a_held_lane_releases_its_holdwas green under no-release in its T1 form and
in its pre-T1 form. It went void at the D1.2 removal (the drain gives the cell no hold), not at T1.
The fix puts an offsets write at 3,200 under an 8,000-sample unmute, so the cell holds and owes a move;
the lane is then freed at 6,400. The gate is now red under no-release and under
release-only-when-nothing-owed, and green on the code.- No test catches a free write that keeps the owed flag (deleting
self.owed = falsestays green).
Root moved that gap to Keep a live level move's own length across a Reposition swap #1711 (batch 19).
Decision record: T3b (batch 18)
- Shipped (the in-block fader cut at mute events). In the one
FaderEventDriver
(crates/builtins-compiler/src/lib.rs), so the Simd8 and Simd4 banks, the fused fader-matrix
pair's fallback and the scalar oracle share it (R17-4). A fader channel's block is rendered in
pieces: each piece ends at the D1.6 cutE, at the offset of the same lane and channel's next
mute event, or at the block's last sample (FaderCellCursor::piece_last), and the next piece
starts at+ 1(open,piece_span). A piece is consumed to its end before the next starts,
so the cell's state advances only through events the stage was given (Render moving stored fader automation, seeks and latency #1647 constraint 2). At
each offset: the fader cursors push first (a gain operation atokeeps the old hold, gain
before mute), then the mute cursors, then each fader channel whose piece ended at its mute
event takes the hold the pushedSetMutegives (rehold) and opens its next piece: muted ->
held to the block's end (held_through); unmuted overM->E = o + M: inside the block a
cut (owed events belowEmove there through D1.6), at the block's enddue_at_entry, past it
the next block finds the hold from the stage; unmuted exactly (M = 0) -> free, nothing owed
(the stage takes the remembered gain, the cell's committed target). A mute event that changes
nothing (not pushed) keeps the old hold. An older cut ahead is dropped by any pushedSetMute.
Mute cursors are now built before a lane's fader cursors, so each fader channel's first piece is
cut at its first mute event.HoldEndloses itsrestspan (the next piece is built at the cut). - Departure from the handoff's text. "Start the next piece at
o + 1" is applied at every
mute event of the channel, pushed or not: the piece must end there because the driver pulls one
event ahead, and the hold is known only after the mute cursor composescurve || terms. A
non-pushed event splits the piece with the hold unchanged, which moves no event (the same split
the D1.6 cut makes). - Gates (all pass).
crates/host-core/tests/stored_mute_automation.rs, every form (Simd8 on
AVX2, Simd4, scalar oracle; no live control, live controls, between render calls), quanta 128,
100, 64 and 37; the driver's pushed fader events and both planes are compared with a model of
the rule written apart from the driver (compose_model: the fader-only plan's events, each
remembered by the stage while held, the owed move atEoverJto the curve atE + J - 1),
driven into the stage directly with Apply timed operations inside a block on the strip fader stage #1638's operations.- Gate 2:
a_fader_ride_under_a_curve_mute_is_remembered_and_moves_at_the_unmute_end(grid
events inside a curve mute and inside its unmute ramp, the move atE = 2,480). Red if the
move atEis missing (mutationmoving = false: first difference at 2,480). Green on the base driver: itsElies in a later block, which batch 17's block-entry rule already handles. - Block boundaries,
M = 48(and 96):a_hold_that_starts_and_ends_inside_a_block_moves_at_its_end:
Einside the unmute's block,Eexactly at a block end,Ein the next block, an unmute and
a re-mute in the same block (afterE: two moves; inside the unmute ramp: the firstE
dropped, the owed move kept through the mute), a mute at offset 0 with an unmute atq - 1,
a mute atq - 1with an unmute at 0, and fader steps exactly at the mute and the unmute
samples. Red checks (each a temporary mutation, then reverted): no rehold at the mute event
(base behaviour, first difference at 1,348);Eoff by one either way (bits differ at 1,348;
events at 1,349 / 1,347); owed events belowEdropped (free_from = o: bits at 1,348);
due_at_entrynot set at anEon the block's end (bits at 1,408); a re-mute that keeps the
old cut (the pushed events: a move at 1,396 under the mute). The base driver (ce32c1b1f) is
red at 1,348. - Batch 17's gates stay green:
stored_fader_automation.rs(30),stored_mute_automation.rs's
T3a tests, capilive_tests, host-web parity.
- Gate 2:
- Measured. One-time PR evidence (not committed): a fader-only probe (three tracks: a fade, a
step and fade, two steps; live mutes with ramps and with0; a seek; every form; quanta 128, 100
and 37) renders the same bits at basece32c1b1fand head (FNV7caf69e2bae5afcdboth).
audit capipcm_digestbffc21e857b5e134, allocations 0.realtime_policy: ok, 161 marked regions (unchanged).lane_slot_bytes392 unchanged (the slot is not
touched; the new fields live in the driver on the render stack). - Phase-2 placeholder. A cell hold set by an offsets jump on a lane held by a mute ramp
(AutomatedFaderCell::offsets_jump: until the stage's ramp end at the block entry) is not
shortened when a curve unmute inside that block ends the hold earlier; grid events between the
newEand the old ramp end are pushed as held (RememberGainon a free stage) until the cell's
next free event. Needs a VCA write during a mute ramp and a curve unmute inside it.
Decision record: T5 (batch 18)
- Shipped (root ruling R18-2). The control list marks a strip's builtin
muterow
automated(ControlEntryfield 35) on each lane a mute entry drives, on both hosts; the row's
value stays the lane's static (hand) mute.controls::owner_rows_inreads the driving entries
from the committed table (controls::mute_automation, as the fader reads its own) and passes
them with the fader offsets tostrip::push_rowsas onestrip::Driven. No row class or table
changes, so the SDK is not regenerated. Docs:docs/SESSION_SCHEMA_V1.md(the mute row
paragraph) anddocs/CONTROL_PROTOCOL_REGISTRY.md(field 35, outside the listed paths: one
clause, mechanical to the ruling).docs/REALTIME_MEMORY.mdandhost-core/src/live_delta.rs
were not stale (T3a). One stale doc line incrates/host-core/tests/live_delta.rs
(fader_ride: "which nothing renders") is fixed. - Gates (all pass).
- Gates 1 and 3 and R18-2, both hosts: one script in two copies (kept equal; a diff of the two
script halves is empty),crates/capi/src/runtime/mute_automation_tests.rs(new, onemod
line inruntime/mod.rs) andhosts/host-web/tests/stored_mute_automation.rs(new). Each
host compares its own output with plans of the same host prepared withtstatically
unmuted and muted, fed the same PCM (both oft's lanes negative in every fourth stretch of
300 frames,uat-0.0in others).- Gate 1:
a_mute_curve_mutes_at_its_exact_samples(steps at 1,000 and 2,500; every sample
before a step equals the static plan it leaves, the step sample andstep + M - 2differ,
every sample from the first block boundary at or afterstep + Mto the next step equals
the new state's static plan; quanta 128, 100, 37) and
a_seek_into_a_muted_span_mutes_exactly_from_the_seek(a plain session seek to 1,600 at the
fifth block: muted exactly from that block, the opening ramps 900 samples later; quanta 128,
100, 37). - Gate 3 (and gate 4's browser case):
solo_vca_and_hand_mutes_compose_with_the_curve(solo
onu, a VCA mute and a hand mute oft, each set while the curve is open and released
while closed, released while open, or kept; solo repliesmonitor, the VCA and the hand
mutelive, so a hand mute through the Worker's apply isliveand the output follows
curve || terms). - R18-2:
the_list_marks_a_mute_row_an_entry_drives_automated(aleftand abothentry;
t's fader rows andu's mute rows unmarked; a hand mute moves the row's value only).
- Gate 1:
- Gate 4:
crates/host-core/tests/live_delta.rs::a_mute_curve_takes_its_terms_as_mute_records
(abothand aleftcurve: an entry change isErr(LiveRebuild::Automation); a hand mute
on either lane, a VCA mute of one lane and of both are each oneMuterecord; a VCA fader
move and a staticfader_dbchange giveFaderDbrecords and no offsets write). - Gate 5:
crates/control-plane/tests/mute_automation_realtime.rs::stored_mute_automation_renders_allocation_free
(eq0of the nine-track fixture with the curve, throughSessionState::commandand
PlanState::render: both steps, a hand mute and a VCA mute set and released live, a plain
session seek to 1,600 (a declared successor) and the opening after it; 44 blocks, each
allocations == 0 && frees == 0; a twin without the curve differs inside each mute and equals
at the end).
- Gates 1 and 3 and R18-2, both hosts: one script in two copies (kept equal; a diff of the two
- Red checks (temporary mutations, each reverted; red on both hosts). Terms overwrite the
curve inLaneFaderAutomation::mute_terms(gate 3, "released at 2560"); a curve event
overwrites the terms in the driver (gate 3, "kept", at 3,500); a seekSetramps over 480
(seek gate, at the seek block); the jump ramps over half the mute length (gate 1 and the seek
gate, atstep + M - 2); the listing flag fader-only (R18-2 gate). An allocation in
mute_termsaborts gate 5's binary (the render-locked allocator). Gate 4 was not mutated: its
mutation lives incrates/host-core/src/prepare.rs(fader_records), which this tranche must
not touch. - Departures.
- Gates 1 and 3 are not one binary that compares the two hosts bit for bit (the batch 18
amendment'scontrol_parity.rsform): a new host-web test file may not call the C ABI's
unsafeexports (realtime_policy's unsafe allowlist), and the shared files were kept free
of this tranche. Each host instead holds the same claims against its own static plans, as
the filed gate text has it. - Gate 1's widths are not repeated here: the host tests run the native backend; Simd8, Simd4
and the scalar oracle are T3a'scrates/host-core/tests/stored_mute_automation.rs.
- Gates 1 and 3 are not one binary that compares the two hosts bit for bit (the batch 18
- Ran.
cargo test --lockedon-p controls --all-features(72),-p host-core --features host-core/test-support,host-core/control-provider(569),-p capi(229),-p host-web --features host-web/test-support(211),-p control-plane --features test-support(26): all
pass.check-host-core-policy.sh,check-workspace-policy.sh,realtime_policy(161 marked
regions, unchanged), fmt, clippy (the five crates, all targets and features,-D warnings)
and the no-featurecargo check --all-targets: ok.
Decision record: T4 (batch 18)
- Shipped (the strip carry of mute automation).
LaneFaderAutomation::adopt(crates/builtins-compiler/src/lib.rs) adopts the mute
channels by address (strip, row 6, channel): a carried channel takes the predecessor cell's
CellStateandterms(the terms its stage heard; the successor's ownMuterecords, the
join's and later live writes, move them, as they move a fader channel'sS). It owes a
reposition (AutomatedMuteCell::reposition, new) whencontinued_cellsbit2 + channelis
clear,a(n)moved, or the carried cell still owed one; a gained channel keeps its prepared
seed andtermsand repositions.- At the lane's next block with node time,
FaderEventDriver::newrepositions the cell at the
block's node time and opens the block with a ramped landing (SeekMode::Ramp, a step at
offset 0) over the block's mute jump length. The mute cursor's one compose rule pushes
SetMute { curve || terms, M }only when that differs from the stage's mute, so a continued
or an agreeing cell lands nothing (and the 13b rule makes any repeat a no-op). A session
seek in that block lands instead, exactly. T3b's fader cut takes the landing like any mute
event. LaneFaderAutomation::mute_terms: on a cell that owes a reposition (and has node time this
block) the drain only storesterms; the driver composes them with the curve at the swap
block, so the swap lands the oneSetMutethe two changes owe, not one from the prepared
seed and a second that reverses it. (Superseded by U1 fold 1: the drain composes the record
with the curve at the block's node time and applies it over its own ramp word.)controls::continued_mute_lanes(new,crates/controls/src/automation.rs; the fader and
the mute rule sharecontinued_lanes). host-core's strip join sets bits 2 and 3 of
continued_cells; graph passes the byte unchanged (doc only).- A lost mute cell: the join emits a
Muterecord of the lane's effective mute innext
(EffectiveStrip::mute: static, VCA and solo terms) over the lane's mute length, unless the
lane already has aMuterecord. The 13b rule makes it a no-op where the stage agrees.
- host-core
prepare.rsfunctions touched (strip join only): theStripJoinstruct's
continued_cellsdoc,join_strips(bits 2 and 3), andEffectiveDiff::join_level_records
(the mute release beside the fader release).resolve_route_carryis not touched. - R18-4 for the strip (withdrawn by root at U1 fold 1; corrected text). The landing is a
SetMute, which retargets only the lane's gain ramp toward0(or the remembered gain) of the
one lane and channel it addresses (dual-mono lanes have separate stages), and the stage keeps
fader_gainas the target the live move aimed for (Apply timed operations inside a block on the strip fader stage #1638 D2, D1.6), so after the unmute the
lane is back at that gain. No level ramp is carried overJ, so B17-R14's window stays
fader-only. The adoption lands as committed only with the U1 fold 1 fix: before it, the drain of
a repositioned cell dropped a same-transaction terms record's ramp word and the landing always
ramped over the mute length (see "Decision record: U1 fold 1"). Gate: below. - Gates (all pass),
crates/host-core/tests/stored_mute_automation.rs, every bank width
(Simd8 on AVX2, Simd4) and the scalar oracle, each with and without live controls, quanta 128
and 100, a swap at block 8, a signed never-zero source; each case compares, plane by plane,
the swapped run with a fresh plan of the new model fed the same PCM from the first block
boundary at or afterswap + M, with the old model's plan before the swap, and holds the swap
block at 0 allocations, reallocations and frees:a_rebuild_that_adds_a_mute_entry_mutes_from_the_swap_over_the_mute_length(a gained cell
muted now: oneSetMute(true, 480)per channel at the swap, the bank lane ramps; then a
gained cell whose curve agrees with a static mute the same rebuild opens: no jump). Red:
adopt without the mute channels (the lane stays open, first difference at 1,536); the
landing exact instead of ramped ("ramps at the swap"); the drain'stermscomposed with the
prepared seed (two jumps at the swap).a_rebuild_that_removes_a_mute_entry_holds_the_static_mute. Red: the join without the mute
release (the lane stays muted, first difference at 1,536).a_rebuild_that_changes_a_mute_curve_jumps_only_when_the_composed_mute_changes(muted now:
one jump; changed only after the swap: none). Red: a changed table that continues its
carried cursor (the jump becomes a 64-sample grid event), adopt without the mute channels.a_mute_adoption_keeps_the_fader_move_in_flight_for_the_unmute(R18-4: a live -12 dB
FaderDbover 2,000 in flight across a rebuild that adds a curve mute on at the swap; muted
to+0.0after the ramp, and after the curve's unmute equal to the never-swapped plan; live
forms). Red: a stage that remembers the in-flight gain at the mute (temporary mutation in
builtins::mute_channel, first difference at 3,200).crates/host-core/tests/route_mute.rs::a_send_edit_and_a_mute_entry_in_one_rebuild_adopt_over_one_length
un-ignored and green: the strip and the send reach+0.0at 1,247 (swap + M - 1) on Simd8
and Simd4. Red at the base and with adopt without the mute channels ("the strip mutes").
- Departures.
- The scalar oracle binds the fader as a graph node, which no plan swap carries: its strip
restarts at the swap (prepared muted exactly, as a fresh plan). Gate 8's scalar leg holds
the post-ramp claim and the swap block's zero allocations only; the route_mute test runs the
bank widths only (its scalar leg failed on "one length for the strip and the send": the strip
is set at 768). Carrying a bound fader node is outside this slice. - The adoption landing ramps over the block's mute jump length even when a
termsrecord of
another length arrives at the same block (the two changes land as oneSetMute). (Defect,
U1 review MAJOR-2; fixed by U1 fold 1.) - A cell that owes a reposition on a lane with no node time keeps it owed; a
termsrecord
there applies at once, composed with the carried target (no curve renders on that lane).
- The scalar oracle binds the fader as a graph node, which no plan swap carries: its strip
- Measured.
audit capipcm_digestbffc21e857b5e134(unchanged), allocations 0,
total_violations0.realtime_policy: ok, 168 marked regions (unchanged: the new code sits
in existing regions).lane_slot_bytes392 (unchanged: the newboolfits the mute cell's
padding). - Ran (all pass).
cargo test --lockedon-p builtins-compiler --features test-support,
-p host-core --features host-core/test-support,host-core/control-provider(579),-p capi,
-p host-web --features host-web/test-support,-p control-plane --features test-support,
-p graph --features test-support,-p controls --all-features; fmt, clippy (builtins-compiler,
controls, host-core, graph; all targets and features,-D warnings) and the no-feature
cargo check --all-targets.
Decision record: U1 fold 1 (batch 18)
Fold round 1 of the U1 review (FAIL at 1d2e2bcc2). Root rulings with it: MAJOR-2 takes the exact
fix (no fallback flag); R18-4 is withdrawn for the strip too (T2 and T4 text corrected above);
MAJOR-1 may extend the authorized paths to crates/automation/src/events.rs.
- MAJOR-2 (a terms record in a curve rebuild lands as committed).
LaneFaderAutomation::mute_terms(crates/builtins-compiler/src/lib.rs): the drain stores
terms; when the record changes the stage mute the curve the stage heard gives (the old rule:
target || termsdiffers from the stage's mute), a cell that owes a reposition and has node time
this block composes the record with the curve at the block's first node time
(value_at(cell, node_time_at(span, 0)), the value the driver's ramped landing aims at, since a
step curve is constant between discontinuities) and appliesSetMuteat the block boundary over
the record's ramp word. The driver's adoption landing then composes the same value with the stage's
updated mute and pushes only a change the curve itself makes, over the mute length. At most one
SetMuteper channel at the swap block. A record that changes nothing against the heard curve is
left to the landing (the change, if any, is the curve's). No new field, branch-only. Also fixes
NIT-6 (a session seek in the swap block no longer delays the terms change: the record applies at
the block boundary). Not separately gated. - MINOR-3.
hosts/host-web/tests/control_parity.rs::a_mute_curve_rebuild_while_a_level_move_ramps_completes_exactly_on_both_hosts
(gate 8's sentence): a mute-only session (a mute curve ont1), a hand fader move over 4,000 in
flight, then a mute-curve rebuild:EXACTandtransition_fallback_count == 0on both hosts,
the same watermark and bits, at every launch rate. New helperCAbi::transition_fallback_count. - MINOR-4.
control_parity.rs::a_composed_stored_mute_renders_alike_on_both_hosts
(test-support): gate 3's script (solo ont2, a VCA mute oft1through a new VCAm, a hand
mute oft1; set while the curve is open, released while closed, while open, or kept) and a plain
session seek to 2,600 (inside the muted span) after the opening; the same verdicts and reply
bytes and the same bits per block on the browser and the C ABI, at quanta 128 and 37; the muted
span is heard (differs from a twin with no curve). The send does not followt1's mute here: see
the phase-2 line. Helpers:CAbi::with_limits,submit_planes,render_frames(the existing
new,submit_from,rendercall them) andRig::boot_atinhosts/host-web/tests/common/mod.rs
(outside the listed paths: a mechanical test-helper extension;Rig::bootcalls it). - MINOR-5.
crates/host-core/tests/stored_fader_automation.rs::a_live_write_that_frees_a_held_lane_releases_its_hold
now frees the lane with the production trigger:Mute { true, 0 }at the block entry before
6,400 andMute { false, 0 }at 6,400 (an exact hand unmute of a muted lane,held_throughset),
at quanta 128, 100 and 64 and on the oracle (per quantum: the muted block differs between
quanta).live_faderis now unused and deleted. The other two R17-10 gates keep their
FaderDbform. - MAJOR-1 (a curve unmute ends an older cell hold). Path extension approved by root:
automation::CellState::release_hold_by(t)(crates/automation/src/events.rs, shorten-only:
ramp_done = tonly when a ramp is in flight and completes aftert). In the driver,
FaderCellCursor::rehold's unmuted branches record a release node (node(at)forM = 0,
node(at + M) - 1otherwise) andFaderCellCursor::openapplies it to the cell's state before
it builds the next piece's events, also when no piece is left in the block (an unmute on its last
sample). Not an unconditionalhold_until. The T3b phase-2 placeholder (an offsets-jump hold not
shortened by an in-block curve unmute) is this defect and is closed by it. - Gates (all pass).
- MAJOR-2:
crates/control-plane/tests/mute_automation_realtime.rs::a_hand_mute_committed_with_a_mute_curve_edit_lands_as_committed
(the review's repro through the shared commit: one transaction =UpsertAutomationmoving
eq0's closing step 1,000 -> 1,100 +SetControlmute = 1 withramp_samplesnone /0/
2,000; pathRebuild; every block equals the live twin, no adoption offset) and, at the bank
widths,crates/host-core/tests/stored_mute_automation.rs::a_hand_mute_in_a_curve_rebuild_lands_over_its_own_ramp
(Simd8 on AVX2 and Simd4, live forms, quanta 128 and 100, rampsM/0/2,000: the
successor's first block takes the record; every sample equals the never-swapped twin; the swap
block allocates nothing;carry_runnow passes the successor's live handles and gate toedit
from the swap block on). The scalar oracle restarts its strip at a swap (T4 departure), so it
has no leg. - MAJOR-1:
stored_mute_automation.rs::a_curve_unmute_ends_an_older_hold_of_the_fader_cell(the
review's probe: a ride-30 -> 0dB, a hand mute at 512, a hand unmute over 4,000 at 1,024, an
offsets write under it at 1,536, a curve mute 2,001 to 2,570;M = 0andM = 48; every live
form incl. the scalar oracle; quanta 128, 100, 37: each completion sample afterE + Jequals
the composed curve) andcrates/automation/tests/events.rs::a_released_hold_ends_early_and_is_never_lengthened. - Every mute gate (13 in
stored_mute_automation.rs) and fader gate (30 in
stored_fader_automation.rs) green.
- MAJOR-2:
- Red checks (temporary, reverted).
- MAJOR-2 control-plane gate on the base
lib.rs: red atSome(0), block 4, and atSome(2000),
block 4 (Noneis green, as the review found). Bank-width gate on the base: red at ramps0
and2,000, Simd8 and Simd4, both planes, quanta 128 (first difference 1,024) and 100 (800). - MINOR-3:
automatesmutated toautomation_row_renders-> outcome flags 2 (transition
fallback) instead of 1 at 44.1 kHz. - MINOR-4: both hosts run one shared core, so a shared-code mutation moves both legs alike; a
browser-only mutation (the adapter's plain session seek anchored one block late) is red at the
seek ("block 36 bits", quantum 128). The gate is green on the pre-fold code. - MINOR-5: no release (
if false && releasein the driver) -> red at 6,463 (quantum 128, bank). - MAJOR-1 driver gate: the release removed (
self.release = None) and the baselib.rs: red at
M = 0on every form and quantum (first completion sample 2,879: the ride frozen at -23.7 dB).
Unit test: an emptyrelease_hold_by-> red (hold to 1,000 kept);hold_untilin its place
-> red (hold to 200 lengthened to 500).
- MAJOR-2 control-plane gate on the base
- Departures.
- MAJOR-1 at
M = 48is not red on the base, and cannot be made red: the review's probe itself
shows no freeze atM = 48on the base (the grid events of the held span leave a move owed at
E, whose landing re-commits the cell and replaces the old hold). A freeze atM > 0needs
nothing owed at an in-blockEwith the old hold set after the last remembered event, that is
no grid point in[X, E)whereXis the offsets write's entry at or before the curve mute;
automation discontinuities are at least 64 samples apart, soE - X > 64and a grid event
always lies there (the held branch clears the target, so it emits and owes). TheM = 48leg
stays in the gate as a claim (green on both). - MAJOR-2's gate in host-core runs the bank widths only (no scalar leg: T4 departure).
- MAJOR-1 at
- Measured.
audit capipcm_digestbffc21e857b5e134(unchanged), allocations 0,
total_violations0.realtime_policy: ok, 168 marked regions (unchanged). - Ran (all pass).
cargo test --lockedon-p builtins-compiler --features test-support
(87),-p automation --all-features(32),-p host-core --features host-core/test-support,host-core/control-provider(581),-p capi(231),-p host-web --features host-web/test-support(214),-p control-plane --features test-support(28). fmt, clippy
(automation, builtins-compiler, control-plane, host-core, host-web; all targets and features,
-D warnings) and the no-featurecargo check --all-targetsof the same crates andcapi. - Resolved by Write a following send's route cell from the shared commit #1653 (verified on the merge
a63588680). A solo ont2in a session wheret1
has a mute curve and a send that followst1's mute was refused withcontrol.internalon lane M
before Write a following send's route cell from the shared commit #1653 landed. On the merged tree it is accepted on both hosts and composes; the MINOR-4 gate
now keeps the following send (itsfollows_mute = falseoverride is removed). - U1 fold verification notes (batch 18). The M > 0 release branch of
reholdis defensive: a
muted span is at least 64 samples, so a grid event before E always owes a move that re-commits the
cell, and the M = 48 leg cannot turn red (the verifier confirmed the argument). MINOR-4's unique
catch is a divergence in a host adapter's seek or term delivery (both hosts run the one core). When
a hand record and a curve edit are both needed for a change (P3), the landing ramps over M and the
hand ramp is not used, by the rule "a record that changes nothing against the heard curve is left
to the landing".
Activity