Skip to content

Controls batch 18: stored mute automation, and following sends that track it in render - #1715

Merged
tamashi095 merged 30 commits into
mainfrom
codex/controls-batch-18
Oct 17, 2026
Merged

tamashi095 merged 30 commits into
mainfrom
codex/controls-batch-18

Conversation

@tamashi095

@tamashi095 tamashi095 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Controls batch 18: stored mute automation and the sends that follow it (the R2 must-land group). A session's mute automation now renders on both hosts: a strip lane mutes and unmutes at the exact samples the session names, over the session mute length, and composes with solo, VCA mutes and a hand mute (OQ1 = C: the stage mute is curve || terms). Every send into a submix that follows an automated mute follows the curve at its tap's arrival, so it mutes exactly the audio the strip mutes, latency included. Live edits of such a send write its route cell from the shared commit.

Closes #1651
Closes #1652
Closes #1653

What changed

  • Render stored mute automation on the strip #1651 render stored mute automation on the strip (T1 0e078aa, T2 e7848d0, T3a 377fcd8, gate power 4084522, T3b 103e1e5, T5 266a272, T4 1d2e2bc; U1 fold 6bf8986).
    • A SetMute that repeats the lane's mute state leaves the fader stage as it was (the batch 13b rule): it no longer restarts a fader ramp in flight or holds fader events.
    • controls::fader_row splits the fader row from the rendered rows, so a mute entry gives no lane a hand offset, turns no fader edit into an offsets write and flags no mute-only rebuild.
    • Mute cells go into the plan's one program after the fader cells (a fader-only session builds the same program and bits). The one fader event driver walks mute cursors and pushes a SetMute only when curve || terms changes, at the exact sample over the session mute length. A seek sets it exactly; a seek that leaves the mute unchanged lets the ramp finish (R18-1). The bank forms, the fused fallback and the scalar oracle share the driver.
    • Inside a block, the fader piece is cut at each mute event: a mute holds the fader to the block end; an unmute over M frees it at E = o + M, where owed events move. An in-block curve unmute also ends an older fader-cell hold (CellState::release_hold_by, shorten-only, in crates/automation; path approved by root).
    • Terms are the effective strip mute (static or hand mute, VCA mute, solo). The Mute record the shared commit already sends is the terms write; no new record type.
    • A mute entry change is a rebuild on both hosts. The carry adopts the mute channels (continued_cells bits 2 and 3); the swap block composes once at node time; a hand or VCA mute committed in the same transaction as a mute-curve rebuild lands over its own ramp (U1 fold, MAJOR-2); a lost mute cell gets a Mute record of the effective mute.
    • R18-2: the control listing marks a mute row that an entry drives automated, on both hosts.
  • Follow an automated mute on following sends in render #1652 follow an automated mute on following sends in render (#1652a d49c0c1, #1652b 062be53, #1652d graph carry 3d063f0, #1652c 6f758cb, policy comment 31f7245; U2a fold 1 977d696; U2a fold 2 1132a1a).
    • A route into a submix whose source strip has a mute entry gets a seven-word route cell on every plan (open transform, one flags word, ramp) and its own boxed render state; six-word routes keep their code, bytes and bits.
    • The route op reads its cell at block entry, then walks the source's mute cells in the tap's node time timeline(r + ΣP - a(tap)) and retargets only when the coefficients' bits change, over the session mute length (exact on a seek).
    • R18-5: per-column ramps. Each source column has its own indexed ramp, so a curve step restarts only its own column; an open column's live move keeps its length across a carry, and per-lane steps each end with the strip's mute. One new lane kernel entry, route_mix_column_ramps_block, has the six-word kernel's vector shape (simd128, NEON, AVX2, outlined scalar tail; 0 panic sites in the wasm instantiation); with equal ramps it is that kernel bit for bit.
    • A following route that opens inside a block records its activation offset, so its destination reduces [0, o) without it (no signed zero reaches the bus or the session output).
    • The carry keeps a route's ramp by route ID whatever its kind in either plan; follow cursors are placed fresh; a carried first block applies the join's record with the heard curve first, then the curve jumps over J.
    • graph depends on crates/automation (R18-3; a leaf crate). The graph policy pin and its fixture gain automation in the same commit.
    • Current difference (Carry a following route's column moves exactly into a six-word successor #1713): a rebuild that carries a following route into a six-word route (its source strip automates a mute lane in the committed plan and none in the successor) can re-time a carried column's move: the six-word successor holds one ramp. The control plane marks such a rebuild CandidateOutcome::TransitionFallback, so the watermark reports OUTCOME_TRANSITION_FALLBACK, never EXACT, for it. It is conservative: it is set whether or not a column moves at the swap. The doc text (abi.rs, the C header comment, docs/C_ABI_V1_QUALIFICATION.md, both d.ts) says so. Carry a following route's column moves exactly into a six-word successor #1713 carries the columns exactly and deletes the flag.
  • Write a following send's route cell from the shared commit #1653 write a following send's route cell from the shared commit (283c480).
    • A route gain, matrix or mute edit, a follows_mute toggle, and a change of the source's terms (hand mute, VCA mute, solo) write the seven-word cell through the live path on both hosts, only when a word's bits change; no folded follow record. The strip's terms and the route's cell share one stamp.
    • Before the first write the commit refuses (Internal) a record with no producer of its kind or with automated bits that disagree with the plan (never an ack before a drop). The in-place commit equals the full path (R17-1).
  • Review test gaps (e7938ee, tests and docs only) and Carry a following route's column moves exactly into a six-word successor #1713 renumber (259f6dd).
  • Docs: the closed batch 17 specs (Prepare stored fader automation and render it flat #1646-Store a hand move on an automated fader as a dB offset #1650, Compose live level values inside the canonical floating-point environment #1709) are removed (5fc3b7b); Keep a live level move's own length across a Reposition swap #1711 gains the owed-discharge item (9a3eb4b, root ruling).

Evidence

  • audit capi pcm_digest bffc21e857b5e134, unchanged across the batch (the audit session has no stored automation). 0 violations, 0 allocations.
  • Re-pins and sizes, each measured, with reasons:
    • Realtime policy 160 -> 168 marked regions (follow flags and record, the route op, ColumnRamps, the mute-drain apply_mute_value, follow_mute_jump, cursors). No file under crates/engine/src/realtime/ was edited.
    • graph policy: automation added to graph's dependency pin and fixture (R18-3). The builtins policy scan stays clean.
    • C header: a doc-comment-only addition (the Carry a following route's column moves exactly into a six-word successor #1713 flag text); no symbol, constant or layout changed; check-capi-abi.sh passes.
    • Bytes (x86-64): lane slot 304 -> 392 B for an automated lane (mute channels and the mute jump word); LIVE_FOLLOW_ROUTE_OWNER_BYTES 352 B (a following route only); LIVE_ROUTE_OWNER_BYTES 72 B unchanged; RouteCarryStep 8 B unchanged (following-route checks live in a side table); RouteActivity 64 -> 80 B once per plan with live or gated routes (the activation offsets box, charged by size_of; root accepted). The charge test covers the attach (the_following_route_charge_covers_its_attach_and_bind).
    • capi_retained: 168,746 of 175,296 (168,650 on main; +96 B: a 16 B follow-routes box in each of six provider epochs).
    • Boot growth: 19,857,408 bytes, unchanged; simd128 artifact 4,384,816 B (4,281,649 B at batch 17: the route op, the column-ramp kernel and the mute driver) (pin 20,971,520).
  • Gates: batch-gates.sh all green on codex/controls-batch-18r at e7938ee except the Documentation job (two private intra-doc links in graph, one redundant link in host-core), fixed in a6574be. That commit and the Skip the grid walk for step-only automation cells #1714/Index the automation entries per strip once per build #1710 docs commit change only doc comments, one parity test and specs; on the final tree 782a0a3 these were re-run green: fmt; realtime policy 168; workspace clippy -D warnings; no-feature release and debug checks; the whole doc job; workspace tests (all features); check-sdk-generated; check-capi-abi; check-cross-targets; check-host-core-policy; CI path routing; script reachability; test-support CI. The e7938ee run also passed: bench policy; host-core clippy default / no-default / all features and the loom spectrum handoff; builtins, console and effect-runtime fixtures; graph determinism; effect contract; protocol wasm parity; SDK types; protocol allocation audit; builtins and builtins-graph trace audits; test-web-audioworklet; web build, check-web-audioworklet, SDK headless; browser expected resources, SDK deletions, command reason and kind vocabularies; fuzz check; audit capi pcm_digest bffc21e857b5e134, 0 allocations.
  • Browsers: chromium, firefox and webkit qualification (--check-matrix --self-test-mutations) all pass at e7938ee (no render change after it).

Review

Fresh opus-high adversarial reviewers, one per review unit, then a focused verification of every fold that touched render.

Placeholders to file

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

tamashi095 and others added 30 commits October 17, 2026 06:49
…specs

Amend #1651, #1652 and #1653 with a batch 18 spec check on ad03780:
anchors, batch 17 code (fader cells, the event driver, the carry, the
conservative fallback flag), decision 17 rules, gates at Simd8 and Simd4,
and the authorized paths. Remove the specs of #1646-#1650 and #1709,
closed in batch 17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d mute (#1652a)

A route whose source strip has an automated mute lane gets its own
lane type: the open transform, one flags word (route mute,
follows_mute, terms L/R, automated L/R) and the ramp, in a
LatestCell<7>, with a boxed render state beside LiveRoute so a
six-word route keeps its bytes and bits. The record refuses unknown
flag bits, a non-finite transform word and an over-long ramp. The
route charge counts each kind on its own and the activity table once.
graph gains a normal automation dependency (policy pin and fixture
updated); route_values is public. No render behaviour yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 T1)

mute_channel returns at once when the requested mute equals the
channel's current mute state, so a repeated SetMute (an operation or
the live set_mute) no longer restarts a fader ramp in flight or starts
a mute ramp that holds the fader events. Gate
a_repeated_mute_leaves_the_stage_as_it_was (Simd4, Simd8, scalar;
quanta 128 and 100) is red on the old code. Four tests that ended an
unmute ramp with a repeated unmute are rewritten to the new rule (the
three batch 17 hold-release gates now free the lane with a live fader
write). audit capi pcm_digest unchanged (bffc21e857b5e134).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(#1652b)

A following route reads its seven-word cell at block entry, then walks
its source lanes' mute cells in the tap's node time (node_span at
a(tap)), composes the gate with RouteFollowFlags::gate and
gated_route_coefficients, and retargets only when the coefficients'
bits change: a new indexed ramp from coefficients_at(position) at the
event's offset over the session mute length (exact on a seek). A route
inactive at block entry that opens inside the block mixes the rest.
Graph takes the mute length rule from host-core and never spells the
row. Six-word routes keep their code, bytes and bits. Gate 3 (split at
o in {1,63,64,127} at q=128 and in a block of 100) and five focused
tests, each red under a named mutation. Realtime policy 164 -> 165.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
controls::fader_row names the strip fader row. The readers that mean
the fader (fader_automation, model_fader_offsets, mask_fader_offsets
and the conservative B17-R14 fallback predicate) now test it, so the
mute row that T3a renders gives no lane a hand offset, turns no fader
edit into an offsets write and flags no mute-only rebuild (R18-4: a
mute adoption never stretches a live move). automation_row_renders is
still the fader row alone; no rendered bit moves. Gate
a_mute_entry_reaches_no_fader_only_reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, graph side)

A live route carries its ramp state by route ID whatever its kind in
either plan (six->seven, seven->seven, seven->six). Follow cursors are
not carried: the successor seeds them at its own node time, applies
the join's record first, then compares its composed target with the
adopted ramp's target and retargets over the mute length only when the
bits differ. A seven->six successor makes the same comparison against
its prepared target on its first block, so removing the curve never
leaves a send muted. Six->six keeps LiveRoute::adopt and its bits. The
swap-block allocation half of gate 4 stays with the host-core gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A free live fader write discharges the owed move (self.owed = false),
and no test turns red when that line is deleted. #1711 reworks the
same owed and hold machinery, so it now owns proving the discharge
observable and gating it, or proving it unobservable and deleting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Row 6 renders. Mute cells go into the plan's one program after every
fader cell (a fader-only session builds the same program and bits).
The stage mute of an automated lane is curve || terms, terms being the
effective strip mute (OQ1 = C); the drain routes the live Mute value
to the lane slot's terms and composes at the block boundary. The one
FaderEventDriver walks mute cursors and pushes a SetMute only when the
composed mute changes, at its exact sample over the session mute
length; a seek sets it exactly and a seek that leaves the mute
unchanged lets the ramp finish (R18-1). The scalar oracle runs the
same slot and driver. A mute entry change is a rebuild on both hosts;
the superseded #1648 gate 1 mute asserts and the parity mute branch
are rewritten. PreparedBuiltinsSession::strip_mute_cells exports the
program and the mute cell indices for #1652. Lane slot 304 -> 392 B;
realtime policy 161; pcm_digest unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…check)

a_live_write_that_frees_a_held_lane_releases_its_hold had been green
under its own mutation (no release) since the D1.2 removal: the drain
gives the cell no hold. It now puts an offsets write under a long
unmute, so the cell holds and owes a move, and frees the lane later.
It is red under no-release and under release-only-when-nothing-owed.
The gap of a free write that keeps the owed flag moves to #1711.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lane with fader and mute automation renders each fader channel's
block in pieces that end at the D1.6 cut at E, at the channel's next
mute event, or at the block end. At a pushed SetMute the next piece
takes the stage's hold: muted holds to the block end; an unmute over
M sets E = o + M (a cut inside the block, due at entry at the block
end, read from the stage past it). Fader events at an offset go first,
under the old hold. One driver serves both bank forms, the fused
fallback and the scalar oracle. Gate 2 and the in-block hold cases
(E inside, at, past the block end; unmute and re-mute in one block;
offsets 0 and q-1) are red under the named mutations. Fader-only bits
unchanged; pcm_digest unchanged; realtime policy 161.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- MAJOR-1: a following route that opens inside a block records its
  activation offset; its destination reduces [0, o) without it, so no
  signed zero reaches the bus.
- R18-5 (MAJOR-2, MINOR-1): per-column ramps. One indexed ramp per
  source column; only a column whose target bits change retargets,
  so an open column's live move keeps its length across a carry and
  per-lane curve steps each end with the strip's mute length. One
  new lane kernel entry, route_mix_column_ramps_block, with the
  six-word kernel's vector shape; with equal ramps it is that kernel
  bit for bit.
- MAJOR-3: a carried first block applies the join's record with the
  heard curve first, then the successor's curve jumps over J; a
  six-word successor does the same and then reopens.
- MINOR-2: adopt_predecessor finishes a pending route carry first.
- MINOR-3: RouteCarryStep is back to 8 B; following-route checks live
  in a side table sized for those moves only.
Realtime policy 166 -> 167.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every plan gives a route into a submix whose source strip has a mute
entry a seven-word lane, sourced from the strip's mute cells, the tap's
floored arrival and the session mute length rule, with flags seeded
from the effective strip mute. The swap join writes a seven-word record
through the follow producer only when a word changes (never on a change
of kind alone). Until #1653 lands in this batch, a live edit of such a
send rebuilds. Gates: no leak on both hosts and host-core at every
width; the send gates on the strip's timeline sample with the same
length; a rebuild adding or removing the entry keeps or reopens the
send allocation-free; the charge covers the attach; the realtime
binary. pcm_digest unchanged; realtime policy 167.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…652a)

The builtins policy forbids the word builtins in graph's manifest;
the #1652a comment named the strip compiler crate. Reworded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: the follow pair's doc (the fold's text, without the dead_code
expect that #1652c removed) and the #1652 decision records (both kept).
The attach passes the plan's route count as activity_routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…automated (#1651 T5, R18-2)

- R18-2: the control listing marks a mute row automated on each lane a
  mute entry drives, on both hosts; the row's value stays the hand mute.
- Gate 1: a curve mutes at its exact samples over the session mute
  length and is bit-identical to the static plan from the next block
  boundary; a seek into a muted span mutes exactly. Quanta 128, 100, 37.
- Gate 3: solo, a VCA mute and a hand mute compose with the curve
  (curve || terms) on both hosts.
- Gate 4: a mute entry change rebuilds; hand and VCA mutes give one
  Mute record each (the terms write); fader edits stay fader records.
- Gate 5: control-plane mute_automation_realtime renders the script
  allocation-free through the shared commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LaneFaderAutomation::adopt adopts the mute channels by address; bits 2
and 3 of continued_cells carry the mute lanes. A carried channel keeps
its cell state and terms and repositions when its table changed, its
arrival moved or a reposition is still owed; a gained channel
repositions. The swap block composes curve || terms once at node time
and lands a SetMute over the mute length only when it differs from the
stage. A lost mute cell gets a Mute record of the lane's effective
mute. R18-4 holds for the strip: a mute adoption keeps the fader move
in flight for the unmute. Gate 8 (add, remove, change, each mid-play;
Simd8, Simd4, scalar oracle post-ramp) and the combined send-and-strip
rebuild now adopt over one length. pcm_digest unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rnel's panic path (#1652, fold 2)

- MAJOR-A: a rebuild that carries a following route into a six-word
  route reports TransitionFallback on both hosts (the route join sets
  it; the shared commit reads it). Conservative: it can over-report on
  a rebuild that removes a strip's last mute automation while a
  following send exists; TBD51 (placeholder) carries the columns
  exactly and removes it.
- MINOR-B: route_mix_column_ramps_block clamps its split, so the wasm
  f32x4 instantiation has no panic site; no bits move.
- MINOR-C: the activation-offset gate also runs into the session
  output; NIT-D: a heard curve on a lane the successor no longer
  automates is forgotten; NIT-E: a successor of a plan that never
  rendered takes its pending comparison (host-core gate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An edit that changes a following route's gate writes its seven-word
cell through the live path on both hosts: a route gain, matrix or mute
edit, a follows_mute toggle, and a change of the source lane's terms
(hand mute, VCA mute, solo) re-send the cell, only when a word's bits
change, and no folded follow record is emitted. Before the first write
the commit refuses (Internal) a record with no producer of its kind or
with automated bits that disagree with the plan; the strip's terms and
the route's cell share the transaction's stamp. control-plane keeps
the follow route producers. The #1652c rebuild stopgap is replaced;
its gate is rewritten in place. In-place commit equals the full path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec becomes 1713-carry-a-following-routes-column-moves-exactly-
into-a-six-word-successor.md, filed by root from batch 18; the public
doc text (C header, abi.rs, C ABI qualification, both .d.ts copies,
still identical) and the code comments name #1713.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- MAJOR-2: a hand or VCA mute committed with a mute curve rebuild
  lands as committed. A cell that owes a reposition composes the
  record with the curve at the block's first node time and applies
  SetMute over the record's own ramp; the adoption landing then pushes
  only a change the curve itself makes, over M. R18-4 is withdrawn for
  the strip; the records say why the adoption now lands as committed.
- MAJOR-1: an in-block curve unmute ends an older fader-cell hold
  (release node; shorten-only CellState::release_hold_by in automation,
  path approved by root), so a ride under an offsets hold no longer
  freezes when the mute length is 0.
- MINOR-3: a mute-curve rebuild with a live fader move in flight
  completes Exact on both hosts. MINOR-4: a composed stored mute and a
  seek render the same bits on both hosts. MINOR-5: the hold-release
  gate frees the lane with the production trigger (an exact unmute).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	crates/control-plane/src/control.rs
- U2b MINOR-1: a muted following route stays silent from its seed.
- U2b MINOR-2: a rebuild that hand-mutes the source ramps the send over
  the edit's own length (the join's before terms).
- U2b NIT-2: the realtime binary toggles follows_mute live.
- Fold-2 NIT-1: a fader ride rebuild inside J after a flagged
  seven->six adoption reports EXACT (the narrowed landing window).
- Fold-2 NIT-2: the refused adopting render allocates nothing.
- Fold-2 NIT-3 and U2b NIT-1: doc wording of the seven->six rule, and
  the +96 B of capi_retained (a 16 B follow-routes box in six provider
  epochs) recorded in #1653.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TBD52: skip the grid walk for step-only automation cells (mute cells
and the following route's walk of them; weekly performance pass).
#1710: the per-owner index also serves the mute row and the
following-route predicate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Documentation job: two public docs in graph linked private items and
  one host-core doc had a redundant link target.
- The MINOR-4 cross-host gate keeps the following send (its
  follows_mute override, needed only before #1653, is removed) and its
  test-value sentence names the adapter as the unique catch.
- The M > 0 release arm of rehold is marked defensive; the #1651
  record notes the resolved solo refusal and the P3 landing rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tamashi095
tamashi095 merged commit 323433f into main Oct 17, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant