Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5fc3b7b
Check the batch 18 specs against main and remove the closed batch 17 …
tamashi095 Oct 17, 2026
d49c0c1
Lay out the seven-word route cell for a send that follows an automate…
tamashi095 Oct 17, 2026
0e078aa
Leave the fader stage as it was on a SetMute that changes nothing (#1…
tamashi095 Oct 17, 2026
062be53
Follow an automated source mute in the route op at the tap's arrival …
tamashi095 Oct 17, 2026
e7848d0
Split the fader row from the rendered automation rows (#1651 T2)
tamashi095 Oct 17, 2026
3d063f0
Carry a following route's ramp by route ID across a plan swap (#1652d…
tamashi095 Oct 17, 2026
9a3eb4b
Add the owed-discharge gap to #1711 (root ruling, batch 18)
tamashi095 Oct 17, 2026
377fcd8
Render stored mute automation on the strip lane (#1651 T3a)
tamashi095 Oct 17, 2026
4084522
Restore the power of the hold-release-at-discharge gate (#1651, root …
tamashi095 Oct 17, 2026
ce32c1b
Merge branch 'codex/controls-batch-18' into codex/controls-batch-18m
tamashi095 Oct 17, 2026
5e8fa19
Merge branch 'codex/controls-batch-18m' into codex/controls-batch-18r
tamashi095 Oct 17, 2026
103e1e5
Cut the fader piece at each in-block mute event (#1651 T3b)
tamashi095 Oct 17, 2026
977d696
Fold the U2a review into the following route op (#1652, fold 1)
tamashi095 Oct 17, 2026
6f758cb
Attach a following route to every send of an automated mute (#1652c)
tamashi095 Oct 17, 2026
31f7245
Keep the graph manifest comment clear of the builtins policy scan (#1…
tamashi095 Oct 17, 2026
c14566d
Merge branch 'codex/controls-b18-u2afold' into codex/controls-batch-18r
tamashi095 Oct 17, 2026
266a272
Gate stored mute automation on both hosts and list a driven mute row …
tamashi095 Oct 17, 2026
c9823d6
Merge branch 'codex/controls-batch-18r' into codex/controls-batch-18m
tamashi095 Oct 17, 2026
1d2e2bc
Carry stored mute automation across a plan swap (#1651 T4)
tamashi095 Oct 17, 2026
1132a1a
Flag a following route carried into a six-word route and clear the ke…
tamashi095 Oct 17, 2026
f07ee9a
Merge branch 'codex/controls-batch-18m' into codex/controls-batch-18r
tamashi095 Oct 17, 2026
283c480
Write a following send's route cell from the shared commit (#1653)
tamashi095 Oct 17, 2026
259f6dd
Renumber placeholder TBD51 as #1713 (filed by root)
tamashi095 Oct 17, 2026
6bf8986
Fold the U1 review into stored mute automation (#1651, fold 1)
tamashi095 Oct 17, 2026
a635886
Merge branch 'codex/controls-batch-18m' into codex/controls-batch-18r
tamashi095 Oct 17, 2026
e7938ee
Close the batch 18 review test gaps (tests and docs only)
tamashi095 Oct 17, 2026
7762273
Add placeholder TBD52 and widen #1710 to mute entries (batch 18)
tamashi095 Oct 17, 2026
a6574be
Fix the batch 18 doc links and apply the U1 verification leftovers
tamashi095 Oct 17, 2026
782a0a3
Merge branch 'codex/controls-batch-18' into codex/controls-batch-18r
tamashi095 Oct 17, 2026
993bed6
Number the batch 18 placeholder: #1714
tamashi095 Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
631 changes: 0 additions & 631 deletions .github/ISSUE_SPECS/1646-prepare-stored-fader-automation.md

This file was deleted.

950 changes: 0 additions & 950 deletions .github/ISSUE_SPECS/1647-render-moving-stored-fader-automation.md

This file was deleted.

820 changes: 0 additions & 820 deletions .github/ISSUE_SPECS/1648-classify-and-carry-fader-automation-edits.md

This file was deleted.

This file was deleted.

This file was deleted.

621 changes: 621 additions & 0 deletions .github/ISSUE_SPECS/1651-render-stored-mute-automation.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,168 @@ Checked against `12d4a4f9d`.
- Authorized paths: `crates/host-core/src/{live_delta.rs,route_controls.rs}`,
`crates/host-core/tests/live_delta.rs`, `crates/control-plane/src/`,
`crates/capi/src/runtime/live_tests.rs`, `hosts/host-web/tests/control_parity.rs`.

## Amendment (controls coordinator, batch 18 spec check, 2026-10-17)

Checked against `ad03780ee`. The filed text above stays; where it disagrees, this section wins.

**Anchors now.**
- The one record function of a send is `route_record_between`,
`crates/host-core/src/prepare.rs:4154-4193`. The classifier (`route_records`,
`crates/host-core/src/live_delta.rs:1467`) and the swap join (`join_routes`, `prepare.rs:5473`)
both call it. So the branch is in `prepare.rs`, which this slice now needs.
- The classifier lowers a route only when its rows changed or its effective gate changed
(`live_delta.rs:1019`). The gate is `controls::effective_gate`
(`crates/controls/src/effective.rs:381`).
- `route_record` and `RouteControlProducer`: `crates/host-core/src/route_controls.rs:146`, `:201`.
- The commit resolves route producers at `crates/control-plane/src/control.rs:3226-3242` and
writes them at `:3419-3421`, in the same stamp as the strip and jump-length writes.
- The in-place classifier (`classify_live_staged`, `live_delta.rs:565`) must equal the dense one
(#1613; `crates/host-core/tests/in_place_commit.rs`, R17-1).

**How the slice maps onto the landed code.**
- **D1.** For a route that the next plan gives a seven-word cell (#1652 D1, from the `after`
model), `route_record_between` returns a seven-word record: the open transform from
`route_values` (#1652 makes it public), the flags word of #1652's amendment, and the ramp from
the existing length rule. `LiveRouteRecord` carries either a folded record or a seven-word one.
The join gets the same record with no second code.
- **Trigger (fix).** For a seven-word route, the classifier also lowers it when its source lane's
effective mute (`terms`) changes and the gate does not (for example while `follows_mute` is
off). So the cell always holds the committed words, and "writes only when a word's bits change"
is exact.
- **D2.** After OQ1 = C, `terms[lane]` is `controls::effective`'s source-lane mute, with the
overlay, for every lane (it holds no curve). Solo is on both hosts (C8).
- **D3.** The mirror and its setter are gone. `follows_mute` is an input of `controls::effective`
and the committed model is the shadow. A toggle writes the cell only.
- **D5 (the acked-batch question), made exact.** Before the first write, the commit checks that
each seven-word record goes to a seven-word producer, and that its `automated` bits equal the
producer's plan. Any mismatch is `CommandError::Internal`, before any write, so no ack precedes a
drop.
- **Watermark.** The path is `live` (`monitor` for solo), with the existing rule. The strip's
terms write (#1651) and the route's cell write of one transaction share one stamp and one block
snapshot, so they apply in the same block. No `TransitionFallback` is set.
- **FP (R17-7).** `route_values` runs inside the guards that R17-7 put at the commit entry and the
preparation entry. No new guard.

**Gates (fixes).**
- Gate 2 runs on both hosts (solo is on the C ABI too): `hosts/host-web/tests/control_parity.rs`.
- Gate 1 also runs in host-core at `Backend::Simd8` (`cfg(target_feature = "avx2")`) and
`Backend::Simd4`, with the `Scalar` oracle: the classifier's records written through the
producers (`crates/host-core/tests/live_routes.rs`).
- Gate 3, rewritten for OQ1 = C: a `follows_mute` toggle gives one seven-word record and no folded
record; a hand mute on the automated lane gives #1651's terms write and one seven-word record; a
hand mute on the other lane gives that lane's `Mute` record and one seven-word record; a route
gain edit gives one seven-word record with the new transform; a terms change while
`follows_mute` is off gives one seven-word record; an edit that leaves every word unchanged
gives nothing. The same cases pass through the in-place path: add a session with a mute entry
and a following send to `crates/host-core/tests/support/edit_sessions.rs`.

**Authorized paths after the check.**
- `crates/host-core/src/prepare.rs` (`route_record_between`'s branch only; after #1652),
`crates/host-core/src/live_delta.rs`, `crates/host-core/src/route_controls.rs`,
`crates/host-core/src/lib.rs` (re-exports only).
- `crates/host-core/tests/live_delta.rs`, `crates/host-core/tests/live_routes.rs`,
`crates/host-core/tests/support/edit_sessions.rs`.
- `crates/control-plane/src/control.rs` (the route write and its pre-write check).
- `crates/capi/src/runtime/live_tests.rs`, `hosts/host-web/tests/control_parity.rs`,
`hosts/host-web/src/tests.rs`.
- No render code and no path under `crates/engine/src/realtime/`.

## Decision record: #1653 (batch 18)

Replaces #1652c's stopgap (`RebuildOnly(RuledLiveNotBuilt { issue: 1653 })` in `route_records`);
no placeholder remains. No render code, nothing under `crates/engine/src/realtime/`.

- **D1 (classifier, `crates/host-core/src/live_delta.rs`).** `route_records` lowers a route the
next plan gives the seven-word cell through `follow_route_record_between` (terms from the diff,
`automated` from the structure's `controls::mute_automation`), any other route through
`route_record_between`. `LiveRouteRecord::record` is now `SendRecord` (`Folded` | `Following`),
public and re-exported (`host_core::SendRecord`, with `folded()` / `following()`); the swap
join's `SendRecord` is the same type. The path is `live`.
- **Trigger (fix).** `LevelDiff` gains `terms(before, after)`: `EffectiveDiff::strip_mutes` (dense)
and the held source strip's `EffectiveStrip::mute` (`LocalDiff`, fast path). `lower_routes`
lowers a following route also when its terms change with the gate unchanged
(`follows_mute` off); `LocalDiff::new` holds such a route when its source's mute moved, whatever
its `follows_mute`. A record is written only when a word's bits change
(`follow_route_record_between`'s rule, #1652c).
- **D3.** A `follows_mute` toggle is a route row change and writes the cell only (no mirror).
- **D5 (control plane, `crates/control-plane/src/{compile.rs,control.rs}`).** `StripLanes` keeps
`follow_routes` (the handles' `follow_route_controls`, no longer dropped by `..`). Step 2a
resolves a `Folded` record among the six-word producers and a `Following` one with
`host_core::follow_route_producer_position`, and refuses with `CommandError::Internal`, before
any write, a record with no producer of its kind or whose `automated` bits differ from the
producer's plan. The writes go under the transaction's one stamp, with the strip's terms
(`Mute`) records, so they apply in one block. No `TransitionFallback`.
- **Departure (mechanical, outside the listed paths).** `crates/graph/src/lib.rs`:
`GraphFollowRouteControlProducer` keeps the bound source's `flags.automated` (one
`[bool; 2]` field set in `follow_route_control_pair`) behind `automated()`, which D5's check
reads; the producer is charged by `size_of`, so its charge follows. `TestTransactionSnapshot`
(test observer) gains `follow_route_cells`. `crates/host-core/tests/in_place_commit.rs` gains the
differential case (the `Pair` harness lives there).
- **`crates/host-core/src/prepare.rs` functions touched.** `SendRecord` (public, accessors, doc),
`follows_automated_mute` (now `following_route_lanes(..).is_some()`), new
`following_route_lanes` (the predicate and the `automated` bits in one place). The join and
`route_record_between` are unchanged.
- **Gates (all pass).**
- Gate 1: `crates/capi/src/runtime/live_tests.rs`
`a_follows_mute_toggle_on_a_following_route_is_live`; `hosts/host-web/tests/control_parity.rs`
`a_follows_mute_toggle_on_a_following_route_is_live_on_both_hosts`;
`crates/host-core/tests/live_routes.rs`
`a_follows_mute_toggle_on_a_following_route_settles_on_a_fresh_plan` (Simd8 on AVX2, Simd4,
Scalar oracle; the classifier's records written through the seven-word producers; every width
equals the oracle bit for bit).
- Gate 2: `hosts/host-web/tests/control_parity.rs`
`solo_and_a_vca_mute_compose_with_the_curve_for_a_following_send_on_both_hosts` (solo path
`monitor`, VCA mute `live`; a pre-fader route into the output still plays under the mute).
- Gate 3: `crates/host-core/tests/live_delta.rs`
`a_following_routes_edits_write_its_seven_word_cell_only` (every case also through
`classify_live_staged`, equal to the dense delta); in-place path:
`crates/host-core/tests/in_place_commit.rs`
`a_following_routes_edits_commit_as_the_full_path_does` over
`edit_sessions::following_session` / `following_script` (48 and 96 kHz).
- Superseded rebuild gate `an_edit_of_a_following_route_rebuilds` replaced in place by
`an_edit_of_a_following_route_is_live` (send gain and hand mute: `live`, the committed
document's fresh bits after the ramps).
- **Red checks (temporary mutations, reverted).** Folded record still emitted for a following
route -> gate 3, host-core gate 1, both capi gates and both parity gates red. Terms trigger
missing in both classifiers -> gate 3 red ("one seven-word record", 0). Terms trigger missing in
the fast path only -> the in-place differential red. The commit skips the seven-word write ->
both capi gates and both parity gates red. The pre-write `automated` check removed -> no test
turns red: every record and every producer of a live commit derive `automated` from the same
committed structure (automation is structure, so a value commit cannot change it, and the
newest epoch is prepared from that structure), so the refusal is unreachable through the public
paths; it stays as D5's defence.

## Decision record: batch 18 cleanup

Test and doc only (U2b review PASS; U2a fold 2 verification PASS). No render, control or
preparation logic changed.

- **MINOR-1 (the seeded `mute` bit).** `crates/host-core/tests/route_mute.rs`
`a_following_send_leaks_nothing_of_a_stored_mute` gains the case `model(128, PreFader, false,
true)` with `routes[1].mute = true` and a `mute_entry(0.0, &[(3_000, 1.0)])`, `Live::None`, at
Simd8, Simd4 and the scalar oracle: the send plane is exact `+0.0` while the strip is heard.
Red with `mute: false` in the seeded `RouteFollowFlags` (`prepare.rs`): "muted route Simd8: a
muted following send leaks".
- **MINOR-2 (the join's `before` terms).** New `route_mute.rs`
`a_rebuild_that_hand_mutes_the_source_ramps_the_send_over_the_edits_length` (all three widths;
the strip's reach of `+0.0` asserted on the bank widths only, as the scalar oracle restarts its
strip). A rebuild from an open `t` with a held-open mute entry to a hand-muted `t`, with the
transaction's `(Strip t, Mute)` edit ramp at 200 (not `M` = 480): the send reaches exact `+0.0`
at the swap block plus 199 or 200. Red with `[m[1], m[1]]` for the join's `terms`
(`join_routes`): the send reaches `+0.0` at 479. The helper `follow::live_swap_ramped` takes the
`EditRamps`; `live_swap` forwards with none.
- **NIT-1 (capi_retained +96 B).** Read from the code, not re-measured: `StripLanes` gained
`follow_routes: Box<[GraphFollowRouteControlProducer]>`, a 16-byte pointer and length held
inline in every `ProviderEpoch`. The control row charges the layout of `PENDING_PROVIDERS` (2) +
`RETIRED_PROVIDERS` (3) parked epochs, and the running epoch is inline in the charged
`SessionState`: six epochs x 16 B = 96 B (168,650 on `main`, 168,746 at head). An empty box
allocates nothing. `crates/capi/tests/resource_lifecycle.rs` explains no row by number, so its
doc is unchanged.
- **NIT-2 (realtime gate).** `crates/control-plane/tests/follow_automation_realtime.rs`
`a_following_send_of_a_stored_mute_renders_allocation_free` now commits the send's
`follows_mute` off at block 9 and on at block 10 (`SetControl` on the route's row 7, each
asserted `EditPath::Live`), inside the muted span, with every render block still allocation-
and free-free; the silence check moved from block 13 to block 18 so the toggles' ramps end
first. Red with the "on" write dropped (the send stays open: "the send follows the curve's
mute").
Loading
Loading