diff --git a/.github/ISSUE_SPECS/1722-delete-digest-type-size-and-unrun-tests.md b/.github/ISSUE_SPECS/1722-delete-digest-type-size-and-unrun-tests.md new file mode 100644 index 000000000..4dd21be2d --- /dev/null +++ b/.github/ISSUE_SPECS/1722-delete-digest-type-size-and-unrun-tests.md @@ -0,0 +1,192 @@ +# Delete digest pins, type-size recomputations and unrun ignored tests (test policy cut 1) + +Owner approval: owner-approved policy cut, relayed by root on 2026-10-18. The deletions below need +no mutation proof: each one falls in a class that AGENTS.md ("Test value") already refuses, or it +never runs. Base: `main` at `81a3074fc`. Branch: `codex/batch-test-policy-1`. Phase B PR 1 of the +2026-10 test audit. + +## Mission + +Remove test code that defends no claim the repository accepts as a test claim: + +- (a) a digest or exact byte count of something that is not a wire, ABI or on-disk format and not + the single owner of a cross-target corpus (G5); +- (b) a test that re-derives `size_of`/`align_of` or a struct byte total of an internal type; +- (c) an ignored test that asserts nothing, or an ignored sweep that no workflow runs; +- (d) the builtins-compiler scalar-oracle pairing tests (#1059: the pairing is an optimisation + inside a test-only oracle). + +Helpers that become unused go in the same change. Docs and mutation records that name a deleted +gate say so. + +## Scope + +Delete or trim exactly the tests listed under "Evidence". In a test that mixes a refused class +with a real claim, only the refused assertions go; the real claim (an exact cap accepts, one byte +below refuses, ownership is returned, a fold rolls back on overflow, an allocator ceiling) stays. + +## Non-goals + +- No product-code change. The scalar oracle stays as the reference for vector banking. +- No MERGE work (per-host duplicates, parameter families) from the audit. +- No change to ceilings the product enforces, C ABI or browser layouts, wire goldens, the G5 + corpus and its owner, fuzz targets, randomized differentials, allocation counts, or defect + reproducers of open issues. +- The ASK items below stay until the owner rules. + +## Objective gates + +1. `cargo fmt --all --check`. +2. `cargo clippy --locked -p --all-targets --all-features -- -D warnings`. +3. `cargo test --locked --all-features -p `. +4. `bash scripts/check-workspace-policy.sh`. + +Touched packages: builtins-compiler, capi, host-core, host-web, math, parametric-eq, +console-workload, audit, graph-compiler, graph, effect-compiler. + +## Test value + +No new test. Two tests were rewritten as relations without `size_of`: +`effect_control_resources_charge_actual_tables_and_shared_owner_once` (red if two producers that +share one factory are charged it twice, or if the producer table is charged by length and not by +capacity) and `effect_control_resources_charge_no_registry_owned_factory` (red if the launch +registry's factory is charged to a plan). Both claims were in the deleted exact-byte forms. + +Review fold 1 kept three relations the first cut lost, each recorded red on a mutant and restored: +- `live_scalar_owner_bytes_are_published_and_capped_before_binding` keeps "runtime metadata is + admitted once" (`plain` estimate minus semantic estimate == `checked_for_with_response_bindings` + total, from node IDs and string lengths, no `size_of`). Mutant: pass `0` for + `response_binding_string_bytes` in `graph-compiler/src/compile.rs` -> RED, `left: 448`, + `right: 492`. +- `the_charge_counts_each_kind_and_the_activity_table_once` asserts the #1652 MAJOR-1 activation + offsets (`both.activity_bytes == alone.activity_bytes + ACTIVITY_ROUTES * 4`, one `u32` per + prepared route). Mutant: `activity_bytes.saturating_add(offsets)` -> `saturating_add(0)` in + `graph/src/lib.rs` -> RED, `left: 4096`, `right: 4124`. +- `runtime_metadata_charge_covers_mixed_ops_once_and_refuses_overflow` asserts that the two #936 + tables and the #1609 block-end table hold one entry per emitted op (doubling the op count + doubles each, and each is nonzero). + +## Evidence + +Line counts are lines removed from the file (`git diff --minimal --numstat`, net of the few added +lines), helpers and docs included. + +### (a) Digest and byte-count pins + +| file | test | change | lines | +|---|---|---|---| +| `crates/graph-compiler/tests/track_delay.rs` | `the_zero_delay_plan_digest_is_the_current_semantic_plan`, `a_delayed_session_is_a_different_plan`, `ZERO_DELAY_CANONICAL_SHA256`, the `sha256` field | deleted (plan SHA; `graph_fixture` owns canonical text) | 75 | +| `crates/graph-compiler/src/lib.rs` | `issue122_reverse_route_ids_emit_sorted_levels_and_bind` | the pinned plan SHA (`f5e310c7…`, four sites) removed; the schedule, levels, repeat and render stay | about 15 | +| `tools/audit/src/fixture_builtins.rs` | `issue064_checked_corpus_is_read_only_and_complete` | the `MANIFEST.tsv` SHA-256 assertion and its re-pin history removed; `--check` and the entry count stay | 104 | +| `tools/console-workload/tests/gain_pan_profile.rs` | `digests` (ignored, "no bit moved" against a base) | deleted; `probes_split_the_bank_units_and_move_no_bit` is the standing gate | 17 | +| `tools/console-workload/src/lib.rs` | `the_driver_fed_rows_metadata_charge_grows_by_exactly_the_executor_tables` | deleted (exact metadata byte count) | 121 | + +### (b) Type-size recomputations (trimmed in place) + +| file | test | what went | lines | +|---|---|---|---| +| `crates/graph-compiler/src/lib.rs` | `live_scalar_owner_bytes_are_published_and_capped_before_binding` | nothing after review fold 1: its runtime-metadata block uses no `size_of` and was restored | 0 | +| same | `effect_control_resource_uses_independent_queue_and_owner_arithmetic` | owner bytes from `size_of`; now: the deltas agree, are zero exactly when nothing is controlled, exact caps accept and one below refuses | about 100 | +| same | `runtime_bank_slot_reservation_is_published_and_capped_transactionally` | the `size_of` slot literal; now folds the product's `checked_for` reservation | about 45 | +| `crates/graph/src/lib.rs` | `runtime_metadata_charge_covers_mixed_ops_once_and_refuses_overflow`, `split_table_resource_is_zero_when_declined_and_one_entry_when_eligible` | `size_of` totals; layout witnesses, one-entry-per-op scaling, fold and overflow stay | about 28 | +| same | `the_charge_counts_each_kind_its_own_bytes_and_the_activity_table_once` (renamed `the_charge_counts_each_kind_and_the_activity_table_once`) | per-kind `size_of` bytes; counts, IDs, joint sum, activation offsets and activity-once stay | about 23 | +| `crates/graph-compiler/tests/live_routes.rs` | `route_control_resources_cover_the_allocation` | the `LatestCell` layout pin and the field-by-field formula; the audited-allocator ceiling stays | 19 | +| `crates/effect-compiler/src/prepare.rs` | `effect_control_resources_charge_actual_tables_and_shared_owner_once`, `effect_control_resources_charge_no_registry_owned_factory` | rewritten as relations (shared factory once, table by capacity, registry factory not charged) | 58 | + +### (c) Ignored tests that assert nothing, and unrun ignored sweeps + +`--ignored` runs were checked in `.github/workflows/*.yml` and `scripts/` first. + +| file | test | why | lines | +|---|---|---|---| +| `crates/host-core/tests/live_edit_classifier.rs` | `timing_line` | prints a time | 70 | +| `crates/capi/src/runtime/live_tests.rs` | `in_place_command_timing_line` | prints a time | 44 | +| `hosts/host-web/tests/control_command_timing.rs` | `control_command_timing_line` (and its `frame` helper) | prints a time | 99 | +| `crates/math/tests/m2_lane_identity.rs` | `m2_exp2_lane_identity_all_f32_bit_patterns`, `m2_log2_lane_identity_all_f32_bit_patterns` (and their two helpers) | no workflow runs them | 63 | +| `crates/math/tests/scalar_accuracy.rs` | `sqrtf_is_correctly_rounded_exhaustive`, `sqrt_is_correctly_rounded_large_oracle` | no workflow runs them | 42 | +| `crates/parametric-eq/src/lib.rs` | `every_word_gets_the_input_contracts_verdict` | no workflow runs it | 67 | + +Kept: every ignored test that `nightly.yml` or `qualification.yml` runs (`f1_fast_db_bounds`, +`m1_exhaustive`, `designer_total`, the 65,537-track scale gates, the release time budgets +including `a_vca_move_at_the_bound_fits_a_quantum_in_release`, the nightly PEQ and conformance +sets, `exhaustive_f32_round_trip`); the open-defect reproducers (#391, #1069, #1070, #1072, +#1073); `automation`'s `print_pins` (the G5 pin generator). + +### (d) Scalar-oracle pairing tests (#1059) + +17 of the 20 deleted from `crates/builtins-compiler/src/lib.rs`: +`actual_scalar_graph_queues_fuse_and_fall_back_against_separate_owners`, +`actual_scalar_nonadjacent_schedule_selects_the_split_owner`, +`actual_scalar_overlapping_nonadjacent_candidates_select_one_and_keep_the_other_separate`, +`actual_scalar_nonadjacent_output_track_takes_the_split_pair_now_the_output_is_dedicated`, +`actual_scalar_nonadjacent_invalid_fader_matrix_boundaries_preserve_retry_order`, +`actual_scalar_graph_preserves_scheduled_matrix_error_and_applies_the_next_write`, +`actual_scalar_extra_reader_declines_and_retains_separate_owner_pcm`, +`actual_scalar_effect_sidechain_declines_and_matches_separate_owner_data_and_state`, +`staggered_observed_scalar_track_stays_separate_while_eligible_peer_pairs`, +`actual_scalar_nonunity_send_reader_declines_with_reference_output`, +`scalar_factory_checks_both_exact_owners_and_policies_without_consuming_state`, +`scalar_split_owner_defers_settled_fader_and_completes_before_matrix_error`, +`scalar_split_invalid_begin_envelope_does_not_arm_pending_or_drain_matrix`, +`scalar_invalid_envelope_leaves_the_later_matrix_queue_untouched`, +`scalar_pair_reset_matches_reset_original_owners`, +`scalar_owner_resource_is_independent_two_boxes_plus_two_pointer_outer`, +`the_scalar_owner_row_charges_one_automated_fader_lane`. With them went the now-unused harness +helpers (`render_scalar_pair_and_compare`, `scalar_live_control_owners`, `write_fader_both`, +`write_matrix_both`, `ScalarLiveControlOwners`) and the harness's `Sidechain` variant with its +`SidechainSum` fixture effect. 2,328 lines. + +The oracle keeps its callers: no oracle type or function became dead. One oracle-side witness did: +`initial_matrix_state` (crate level, `cfg(all(test, feature = "test-support"))`, the only reader +of `SCALAR_INITIAL_MATRIX_TRACE`). Its only readers are three of the 20 tests, which stay until the +owner rules (ASK 1): +`actual_scalar_nonadjacent_intervening_observer_error_completes_and_retries`, +`actual_scalar_nonadjacent_failed_render_materializes_post_fader_before_error`, +`actual_scalar_nonadjacent_ramp_retarget_and_failed_retry_stay_at_original_boundaries` (713 lines). + +### ASK (not cut; owner ruling needed) + +1. The three pairing tests above. Options: (A) delete them and the witness + (`initial_matrix_state`, `record_initial_matrix_state`, `SCALAR_INITIAL_MATRIX_TRACE`, its trace + type and its one call in the oracle's matrix owner): test-only instrumentation, not the oracle's + render path; (B) keep them. `#[allow(dead_code)]` is not an option. +2. `crates/math/tests/m2_lane_identity.rs` `EXP2_DIGEST`/`LOG2_DIGEST`. A second pin of cases G5 + owns, so class (a) by form. But `scripts/lib/aarch64-known-defects.py` names + `m2_exp2_lane_identity` as the `aarch64-release` expected failure of LANE-3 (#1019), judged by + that digest's panic text. Without the pin the test passes on AArch64 release and the leg fails + ("a pass fails the leg"), and #1019 loses its native witness. Cut together with that row only + if the owner accepts losing the witness. +3. `crates/host-core/src/limiter_linked_session.rs` `PIN`: a rendered-output digest recorded on the + pre-#990 kernel (the "no bit moved against a base" shape), but self-declared "the single + cross-target owner of the 64-track linked-limiter session corpus" and cited by + `true-peak-limiter` MUTATIONS row 996-K2. +4. `tools/console-workload/tests/gain_pan_profile.rs` `phase_profile`: an ignored measurement + harness no workflow runs (R9 retired descriptive benchmarks). Deleting it leaves the + `graph::test_only_phase_profile` probes with no consumer but their own test. +5. Exact move-table byte counts in `crates/host-core/tests/successor_swap.rs` (14 `size_of` sites) and + single `size_of` lines in `host-core` `route_mute.rs`, `submix_strip.rs`, `prepare.rs` and `capi` + `resource_lifecycle.rs`: each is one line inside a larger test and encodes a move or entry count. + The launch-effect fixture tests in `graph-compiler/src/lib.rs` (a MERGE family) also use + `literal_bank_slot_reservation_bytes`. + +### Not cut, by class + +- Wire, ABI and layout: `protocol` `message_wire` goldens; `host-web` `src/tests.rs` `size_of` + layouts and its browser digest pins; `effect-contract` `live_control.rs` (`<= 64` is a ceiling). +- G5: every effect and `math` corpus digest table; `bench-support` SHA-256 known answers. +- Product code: `tools/audit` `builtins.rs` and `builtins_graph.rs` fixture identities are checks + in the tool itself, not test assertions. +- Ceilings: `graph` `runtime.rs` executor-table bound, `bypass_resources.rs`, allocation trackers. +- `track_delay.rs` `the_estimate_charges_each_lane_its_own_ring`: four bytes per `f32` sample is the + product's accounting rule, not a struct total. + +### Docs updated + +`.github/ISSUE_SPECS/1379-define-how-node-tails-compose-through-gain-in-the-graph-extent.md` (lines +169 and 263) names `ZERO_DELAY_CANONICAL_SHA256` as a re-pin step. That pin is deleted here, so the +step no longer applies; #1379's spec is left as written. + +`crates/graph/tests/MUTATIONS.md` and `crates/graph-compiler/tests/MUTATIONS.md` gain a TA1 note +naming the rows whose tests were deleted or trimmed; `docs/derivations/241-schema-repins.md` +notes the deleted companion assertion. Module docs of the touched test files were corrected. +`docs/handoffs/` and `docs/audits/` are dated records and were not changed. diff --git a/crates/builtins-compiler/src/lib.rs b/crates/builtins-compiler/src/lib.rs index 3a63b05d6..3e2039b5a 100644 --- a/crates/builtins-compiler/src/lib.rs +++ b/crates/builtins-compiler/src/lib.rs @@ -10333,20 +10333,14 @@ mod tests { use super::*; use core::num::{NonZeroU32, NonZeroU64, NonZeroUsize}; use core::sync::atomic::{AtomicUsize, Ordering}; - use effect_contract::{ - EffectDescriptor, EffectId, EffectProcessBlock, EffectQuality, LinkMode, LinkModeSet, - PortDescriptor, PortId, PortLayout, PortRole, PreparedEffectMetadata, PreparedNativeEffect, - PreparedPorts, PreparedSidechainPort, ProcessReport, ResetKind, StatePayloadError, - StatePayloadInput, StatePayloadOutput, StatePayloadSizes, - }; #[cfg(test)] use engine::realtime::LiveSnapshot; use engine::{QuantumFrames, SampleRateHz}; use graph::{ - EffectNodeId, GraphEdge, GraphEdgeId, GraphNode, GraphNodeBinding, GraphPortId, - GraphPortKind, GraphPreparedEffect, GraphPreparedSourceSetDriver, GraphResourceEstimate, - GraphSourceInputClaim, GraphSourceSetResourceReport, PreparedGraphPlanParts, PreparedRoute, - RackId, RouteGate, RouteTransform, + GraphEdge, GraphEdgeId, GraphNode, GraphNodeBinding, GraphPortId, GraphPortKind, + GraphPreparedSourceSetDriver, GraphResourceEstimate, GraphSourceInputClaim, + GraphSourceSetResourceReport, PreparedGraphPlanParts, PreparedRoute, RouteGate, + RouteTransform, }; /// The compiler always emits `spec.nodes` sorted by id; hand-built fixtures list them in @@ -11199,89 +11193,6 @@ mod tests { } } - const SIDECHAIN_SUM_ID: EffectId = match EffectId::new("scalar-sidechain-sum") { - Ok(value) => value, - Err(_) => panic!("effect ID"), - }; - const SIDECHAIN_MAIN_IN: PortId = match PortId::new("main-in") { - Ok(value) => value, - Err(_) => panic!("port ID"), - }; - const SIDECHAIN_MAIN_OUT: PortId = match PortId::new("main-out") { - Ok(value) => value, - Err(_) => panic!("port ID"), - }; - const SIDECHAIN_INPUT: PortId = match PortId::new("sidechain-in") { - Ok(value) => value, - Err(_) => panic!("port ID"), - }; - static SIDECHAIN_SUM_PORTS: [PortDescriptor; 3] = [ - PortDescriptor { - id: SIDECHAIN_MAIN_IN, - role: PortRole::MainInput, - required: true, - layout: PortLayout::DualMonoPlanar, - }, - PortDescriptor { - id: SIDECHAIN_MAIN_OUT, - role: PortRole::MainOutput, - required: true, - layout: PortLayout::DualMonoPlanar, - }, - PortDescriptor { - id: SIDECHAIN_INPUT, - role: PortRole::SidechainInput, - required: true, - layout: PortLayout::DualMonoPlanar, - }, - ]; - static SIDECHAIN_SUM_DESCRIPTOR: EffectDescriptor = EffectDescriptor { - id: SIDECHAIN_SUM_ID, - display_name: "Scalar sidechain sum fixture", - contract_major: 1, - contract_minor: 0, - state_layout_version: 1, - supported_link_modes: LinkModeSet::DUAL_MONO, - parameters: &[], - ports: &SIDECHAIN_SUM_PORTS, - qualities: &[], - tail_and_rest: |_, _| effect_contract::NodeTailBound { - tail: effect_contract::TailSamples::Finite(0), - tail_every_peak: effect_contract::TailSamples::Infinite, - rest: effect_contract::RestBound::Unstated, - composition: effect_contract::CompositionBound::Unstated, - }, - observations: &[], - }; - - /// Reads no prepared value, so holds none (issue #1461). - struct SidechainSum; - - impl PreparedNativeEffect for SidechainSum { - fn reset(&mut self, _kind: ResetKind) {} - fn process(&mut self, block: EffectProcessBlock<'_>) -> ProcessReport { - let (side_left, side_right) = block.sidechain.expect("connected sidechain"); - for frame in 0..block.left.len() { - block.left[frame] += side_left[frame]; - block.right[frame] += side_right[frame]; - } - ProcessReport::default() - } - fn snapshot_state_payload( - &self, - _output: StatePayloadOutput<'_>, - ) -> Result<(), StatePayloadError> { - Ok(()) - } - fn restore_state_payload( - &mut self, - _version: u32, - _input: StatePayloadInput<'_>, - ) -> Result<(), StatePayloadError> { - Ok(()) - } - } - /// Builds the five-level graph the harness renders: one level per track stage. /// /// `Output` is fed by one track's `PostMatrix` only (track 0's, or the last track's for @@ -11294,7 +11205,6 @@ mod tests { SelectedSend, Alias, AliasObserved, - Sidechain, #[cfg(all(test, feature = "test-support"))] Nonadjacent, NonadjacentTrackA, @@ -11351,12 +11261,6 @@ mod tests { // discriminates the observer barrier on that one track while track 0 pairs throughout. let alias_track = n - 1; let alias = stage(alias_track, TrackStage::PostDynamic); - let sidechain_effect_id = EffectNodeId { - track_id: StableGraphId::parse("sidechain-proof").expect("effect track"), - rack: RackId::Dynamic, - effect_id: StableGraphId::parse("sum").expect("effect ID"), - }; - let sidechain_effect = GraphNodeId::Effect(sidechain_effect_id.clone()); let stages = [ TrackStage::Input, TrackStage::PostInputBuiltins, @@ -11387,11 +11291,7 @@ mod tests { }); } } - let main_target = if matches!(variant, BoundaryVariant::Sidechain) { - sidechain_effect.clone() - } else { - output.clone() - }; + let main_target = output.clone(); // Issue #1420: only `NonadjacentOutputConflict` moves the output track, and the #916 test // reads that edge. `NonadjacentTrackA` used to give `n - 1` too, but since #916 the // `Output` is dedicated storage and no outcome read it, so it takes track 0. @@ -11419,41 +11319,6 @@ mod tests { }, path: "$.routes[0]".to_owned(), }); - if matches!(variant, BoundaryVariant::Sidechain) { - edges.push(GraphEdge { - id: GraphEdgeId::EffectSidechain { - effect: sidechain_effect_id.clone(), - port: SIDECHAIN_INPUT.as_str().to_owned(), - }, - source: GraphPortId { - node: stage(alias_track, TrackStage::PostFader), - kind: GraphPortKind::MainOutput, - effect_port: None, - }, - destination: GraphPortId { - node: sidechain_effect.clone(), - kind: GraphPortKind::SidechainInput, - effect_port: Some(SIDECHAIN_INPUT.as_str().to_owned()), - }, - path: "$.effects[sidechain-proof].sidechain".to_owned(), - }); - edges.push(GraphEdge { - id: GraphEdgeId::TrackMain { - target: output.clone(), - }, - source: GraphPortId { - node: sidechain_effect.clone(), - kind: GraphPortKind::MainOutput, - effect_port: None, - }, - destination: GraphPortId { - node: output.clone(), - kind: GraphPortKind::MainInput, - effect_port: None, - }, - path: "$.effects[sidechain-proof].output".to_owned(), - }); - } if matches!( variant, BoundaryVariant::Send | BoundaryVariant::SelectedSend @@ -11539,9 +11404,6 @@ mod tests { }); } nodes.push(output.clone()); - if matches!(variant, BoundaryVariant::Sidechain) { - nodes.push(sidechain_effect.clone()); - } if matches!( variant, BoundaryVariant::Send | BoundaryVariant::SelectedSend @@ -11554,12 +11416,6 @@ mod tests { ) { nodes.push(alias.clone()); } - if matches!(variant, BoundaryVariant::Sidechain) { - levels.push(DependencyLevel { - level: stages.len() as u64, - nodes: vec![sidechain_effect.clone()], - }); - } levels.push(DependencyLevel { level: stages.len() as u64, nodes: vec![output.clone()], @@ -11639,43 +11495,7 @@ mod tests { Vec::new() }, track_delays: Vec::new(), - effects: if matches!(variant, BoundaryVariant::Sidechain) { - let metadata = PreparedEffectMetadata { - descriptor: &SIDECHAIN_SUM_DESCRIPTOR, - sample_rate: 48_000, - quantum: HARNESS_QUANTUM, - quality: EffectQuality::Normal, - bypass: false, - link_mode: LinkMode::DualMono, - ports: PreparedPorts { - sidechain: PreparedSidechainPort::Connected { - id: SIDECHAIN_INPUT, - required: true, - }, - }, - latency: effect_contract::LatencySamples(0), - tail: effect_contract::TailSamples::Finite(0), - tail_every_peak: effect_contract::TailSamples::Infinite, - rest: effect_contract::RestBound::Unstated, - composition: effect_contract::CompositionBound::Unstated, - state_sizes: StatePayloadSizes { - common_bytes: 0, - left_bytes: 0, - right_bytes: 0, - }, - scratch_bytes: 0, - automation_capacity: 0, - }; - vec![GraphPreparedEffect { - id: sidechain_effect_id, - metadata, - processor: Box::new(SidechainSum), - response_snapshot_declared: false, - native_id: "miso.test.sidechain-sum", - }] - } else { - Vec::new() - }, + effects: Vec::new(), effect_controls: Vec::new(), banks: Vec::new(), builtin_banks: Vec::new(), @@ -12208,13 +12028,6 @@ mod tests { route_id: StableGraphId::parse("proof-send").expect("route"), }); } - if matches!(variant, BoundaryVariant::Sidechain) { - schedule.push(GraphNodeId::Effect(EffectNodeId { - track_id: StableGraphId::parse("sidechain-proof").expect("effect track"), - rack: RackId::Dynamic, - effect_id: StableGraphId::parse("sum").expect("effect ID"), - })); - } schedule.push(GraphNodeId::Output { output_id: StableGraphId::parse("main-out").expect("output"), }); @@ -12456,67 +12269,6 @@ mod tests { .expect("owner matrix state trace") } - /// Render one block of the paired plan and of its separate-owner twin, and require four - /// things: - /// - /// * the same post-matrix PCM on the captured track; - /// * the same host planes; - /// * exactly `pairs` scalar pair owners invoked once each, with the given `(fused, fallback)` - /// branch counts summed over those owners; - /// * every owner's last recorded fader and matrix state equal to its separate twin's. - /// - /// `pairs` is 2 wherever both tracks of the two-track harness pair, and 1 where the schedule - /// admits a single split pair. Issue #916 made track 0 eligible, now that its post-matrix - /// buffer is no longer the graph output. - /// - /// The host planes are track 0's post-matrix output, the Output's only input. Comparing them - /// is the check that a plan whose track 0 takes the scalar pair renders what the same plan - /// with the pair declined renders. The separate twin is prepared with concurrent control - /// delivery, which the pair factories refuse. Before #916 this helper compared only the - /// captured trailing track, because track 0 never paired. - /// - /// Owner state is compared per owner rather than as the one globally last-recorded word. Which - /// owner records last depends on where the pair's arithmetic runs: a split pair on track 0 - /// records at `M0`, after the separate `F1`. That order is not a property of either owner. - fn render_scalar_pair_and_compare( - paired: &mut PreparedBuiltinsGraphBound, - separate: &mut PreparedBuiltinsGraphBound, - paired_capture: &Arc>>, - separate_capture: &Arc>>, - sample: u64, - pairs: u64, - branches: (u64, u64), - ) -> TestOnlyFaderMatrixWitness { - test_only_reset_fader_matrix_witness(); - let paired_host = render_bound(paired, sample); - let witness = test_only_fader_matrix_witness(); - let paired_trace = test_only_scalar_state_trace(); - test_only_reset_fader_matrix_witness(); - let separate_host = render_bound(separate, sample); - let separate_trace = test_only_scalar_state_trace(); - assert_eq!( - std::mem::take(&mut *paired_capture.lock().unwrap()), - std::mem::take(&mut *separate_capture.lock().unwrap()), - "actual selected scalar PCM must equal old separate-owner PCM" - ); - assert_eq!( - paired_host, separate_host, - "the host planes of a paired track 0 must equal its separate-owner twin's" - ); - assert!( - paired_host.iter().any(|word| *word != 0), - "the host planes carry audio" - ); - assert_eq!( - (witness.process_calls, witness.process_members), - (pairs, pairs), - "this assertion fails under the real selection-to-separate mutation" - ); - assert_eq!((witness.fused_calls, witness.fallback_calls), branches); - assert_owner_states_match(paired_trace, separate_trace); - witness - } - /// Every owner that recorded a scalar state in either run recorded one in both, and its last /// recorded fader and matrix words are equal. fn assert_owner_states_match( @@ -12711,18 +12463,16 @@ mod tests { ); } - /// #443's compact product fixture reaches the genuine scalar graph selection seam. The - /// Concurrent twin is the independently prepared old two-owner execution and therefore also - /// acts as the selection-to-separate mutation: identical PCM alone cannot satisfy the witness. + #[cfg(all(test, feature = "test-support"))] #[test] - fn actual_scalar_graph_queues_fuse_and_fall_back_against_separate_owners() { + fn actual_scalar_nonadjacent_intervening_observer_error_completes_and_retries() { let _guard = PAIR_WITNESS_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); let separate_capture = Arc::new(std::sync::Mutex::new(Vec::new())); test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_fixture( + let mut paired = prepared_pair_graph_variant_observed( false, false, false, @@ -12730,8 +12480,23 @@ mod tests { Some(Arc::clone(&paired_capture)), Backend::Scalar, 2, + BoundaryVariant::Nonadjacent, + 1, + None, + false, + true, + ) + .0; + let paired_prepared = test_only_fader_matrix_witness(); + assert_eq!( + ( + paired_prepared.factory_calls, + paired_prepared.factory_members + ), + (1, 1) ); - let mut separate = prepared_pair_graph_fixture( + test_only_reset_fader_matrix_witness(); + let mut separate = prepared_pair_graph_variant_observed( false, false, false, @@ -12739,1340 +12504,362 @@ mod tests { Some(Arc::clone(&separate_capture)), Backend::Scalar, 2, - ); - let selected = test_only_fader_matrix_witness(); - - let settled = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 0, - 2, - (2, 0), - ); - // Issue #916: both tracks pair. Track 0's post-matrix buffer is no longer the graph - // output, so it is eligible beside track 1. Every block below therefore counts track 0's - // settled pair (always fused: nothing is queued for it) on top of track 1's branch. - assert_eq!((selected.factory_calls, selected.factory_members), (2, 2)); + BoundaryVariant::Nonadjacent, + 1, + None, + false, + true, + ) + .0; + let separate_prepared = test_only_fader_matrix_witness(); assert_eq!( - (settled.fader_values_applied, settled.matrix_values_applied), + ( + separate_prepared.factory_calls, + separate_prepared.factory_members + ), (0, 0) ); - - let fader_fifo = [ - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Left, - db: -6.0, - smoothing_samples: 0, - }, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Left, - db: -12.0, - smoothing_samples: 0, - }, - ]; - for record in fader_fifo { - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| controls[1].fader.write(stamp, record)); - } - } - let matrix_now = TrackControlRecord { + let fader = TrackFaderRecord::FaderDb { + lanes: BuiltinLaneSelector::Both, + db: -6.0, + smoothing_samples: 0, + }; + let matrix = TrackControlRecord { matrix: Matrix2x2 { ll: 0.5, lr: 0.25, - rl: -0.5, + rl: -0.25, rr: 0.75, }, smoothing_samples: 0, }; + // Issue #916: the selected split pair is t00's, so its controls are the ones the failing + // observer must interrupt between `F0` and `M0`. + assert!(matches!( + paired.test_only_post_fader_node.as_ref(), + Some(GraphNodeId::TrackStage { + track_id, + stage: TrackStage::PostFader, + }) if track_id.as_str() == "t00" + )); for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| controls[1].producer.write(stamp, matrix_now)); + bound.test_only_write(|controls, stamp| controls[0].fader.write(stamp, fader)); + bound.test_only_write(|controls, stamp| controls[0].producer.write(stamp, matrix)); } - let immediate = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - HARNESS_QUANTUM as u64, - 2, - (2, 0), - ); - // The two `Left` fader writes share one cell: render applies the second, and the first - // is the one write a later write replaced before any block read it. - assert_eq!( - ( - immediate.fader_values_applied, - immediate.matrix_values_applied - ), - (1, 1) - ); - let ramp = TrackFaderRecord::Mute { - lanes: BuiltinLaneSelector::Right, - muted: true, - smoothing_samples: 3, - }; - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| controls[1].fader.write(stamp, ramp)); + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); + test_only_reset_fader_matrix_witness(); + let mut paired_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; + let paired_error = render_bound_result(&mut paired, 0, &mut paired_pcm).unwrap_err(); + let paired_failed = test_only_fader_matrix_witness(); + let paired_failed_trace = test_only_scalar_state_trace(); + assert_eq!(paired_error, RenderError::InvalidEnvelope); + assert_eq!(INTERVENING_OBSERVER_ERRORS.load(Ordering::Relaxed), 1); + assert_eq!(paired_failed.fader_values_applied, 1); + assert_eq!(paired_failed.matrix_values_applied, 0); + + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); + test_only_reset_fader_matrix_witness(); + let mut separate_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; + let separate_error = render_bound_result(&mut separate, 0, &mut separate_pcm).unwrap_err(); + let separate_failed = test_only_fader_matrix_witness(); + let separate_failed_trace = test_only_scalar_state_trace(); + assert_eq!(separate_error, paired_error); + assert_eq!(separate_failed.fader_values_applied, 1); + assert_eq!(separate_failed.matrix_values_applied, 0); + // Per owner, because the pending fader is completed after t01's separate fader ran, so + // the globally last-recorded word names a different owner in the two runs. + assert!(!paired_failed_trace.fader_overflow && !paired_failed_trace.matrix_overflow); + assert!(!separate_failed_trace.fader_overflow && !separate_failed_trace.matrix_overflow); + for owner in [0, 1] { + assert_eq!( + last_fader_state(paired_failed_trace, owner), + last_fader_state(separate_failed_trace, owner), + "observer error preserves fader {owner}'s state before retry" + ); } - let ramping = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 2 * HARNESS_QUANTUM as u64, - 2, - (1, 1), + let initial_matrix = initial_matrix_state(0).expect("t00 initial matrix state"); + assert_eq!( + last_matrix_state(paired_failed_trace, 0), + initial_matrix, + "observer error leaves the later matrix command untouched" ); - assert_eq!(ramping.fader_values_applied, 1); - let _settled_again = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 3 * HARNESS_QUANTUM as u64, - 2, - (2, 0), + assert!( + !separate_failed_trace.matrix_owners[..separate_failed_trace.matrix_len].contains(&0), + "the separate twin never reached M0 either" ); - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - controls[1].fader.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -18.0, - smoothing_samples: 96, - }, - ) - }); - bound.test_only_write(|controls, stamp| { - controls[1].producer.write( - stamp, - TrackControlRecord { - matrix: Matrix2x2::IDENTITY, - smoothing_samples: 96, - }, - ) - }); - } - let _long_ramp = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 4 * HARNESS_QUANTUM as u64, - 2, - (1, 1), - ); - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - controls[1].fader.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Right, - db: -3.0, - smoothing_samples: 3, - }, - ) - }); - } - let _mid_ramp_retarget = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 5 * HARNESS_QUANTUM as u64, - 2, - (1, 1), - ); - let _retarget_settled = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 6 * HARNESS_QUANTUM as u64, - 2, - (2, 0), - ); - // One transaction: the mute and the fader move of the same lane. - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - let producer = &mut controls[1].fader; - producer.write( - stamp, - TrackFaderRecord::Mute { - lanes: BuiltinLaneSelector::Left, - muted: true, - smoothing_samples: 0, - }, - ); - producer.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Left, - db: -2.0, - smoothing_samples: 0, - }, - ); - }); - } - let muted = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 7 * HARNESS_QUANTUM as u64, - 2, - (2, 0), - ); - assert_eq!(muted.scalar_fader_words[12], 1); - assert_ne!( - muted.scalar_fader_words[5], 0, - "muted lane remembers its gain" - ); - assert_eq!(muted.scalar_fader_words[0], 0.0_f32.to_bits()); - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - controls[1].fader.write( - stamp, - TrackFaderRecord::Mute { - lanes: BuiltinLaneSelector::Left, - muted: false, - smoothing_samples: 0, - }, - ) - }); - } - let unmuted = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 8 * HARNESS_QUANTUM as u64, - 2, - (2, 0), - ); - assert_eq!(unmuted.scalar_fader_words[12], 0); - assert_eq!(unmuted.scalar_fader_words[0], unmuted.scalar_fader_words[5]); - - // Mutate the actual graph boundary with a post-fader observer. The same serialized - // concrete owners remain, but the observer guard must retain separate execution. Exact - // post-matrix PCM still matches an independently prepared Concurrent old-owner graph, so - // only the selected-mechanism assertion discriminates this mutation. - let declined_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let declined_reference_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut declined = prepared_pair_graph_fixture( - true, - false, - false, - true, - Some(Arc::clone(&declined_capture)), - Backend::Scalar, - 2, - ); - let mut declined_reference = prepared_pair_graph_fixture( - true, - false, - false, - false, - Some(Arc::clone(&declined_reference_capture)), - Backend::Scalar, - 2, - ); - let _ = render_bound(&mut declined, 0); - let mutation_witness = test_only_fader_matrix_witness(); - let _ = render_bound(&mut declined_reference, 0); - assert_eq!( - std::mem::take(&mut *declined_capture.lock().unwrap()), - std::mem::take(&mut *declined_reference_capture.lock().unwrap()) - ); - // Issue #916: the meter observes track 1's fader, so track 1 declines; track 0 is - // unobserved and still pairs. The mutation takes the pair count from 2 to 1. - assert_eq!( - ( - mutation_witness.process_calls, - mutation_witness.factory_calls - ), - (1, 1), - "the SAME selected-mechanism assertion above fails for this graph mutation" - ); - assert!( - declined - .meter_consumers - .first_mut() - .is_some_and(|meter| meter.consumer.try_pop().is_ok()), - "the declining post-fader observer publishes real data" - ); - } - - #[cfg(all(test, feature = "test-support"))] - #[test] - fn actual_scalar_nonadjacent_schedule_selects_the_split_owner() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let separate_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_variant( - false, - false, - false, - true, - Some(Arc::clone(&paired_capture)), - Backend::Scalar, - 2, - BoundaryVariant::Nonadjacent, - 1, - None, - ); - let selected = test_only_fader_matrix_witness(); - assert_eq!((selected.factory_calls, selected.factory_members), (1, 1)); - // Issue #916: the split owner is t00's. The stage-major schedule offers `F0` first, and - // t00 is eligible now that its post-matrix buffer is no longer the graph output. Before - // #916 it was ineligible, and t01's interval was the one selected. One split pair is - // admitted per plan, so t01 stays separate. - assert_eq!( - graph::test_only_selected_split_fader() - .expect("a split pair is selected") - .node, - GraphNodeId::TrackStage { - track_id: StableGraphId::parse("t00").expect("track"), - stage: TrackStage::PostFader, - } - ); - let mut separate = prepared_pair_graph_variant( - false, - false, - false, - false, - Some(Arc::clone(&separate_capture)), - Backend::Scalar, - 2, - BoundaryVariant::Nonadjacent, - 1, - None, - ); - let settled = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 0, - 1, - (1, 0), - ); - assert_eq!(settled.factory_calls, 0, "factory runs only at preparation"); - assert_eq!( - settled.fused_calls, 1, - "settled split pair uses fused arithmetic" - ); - assert_eq!(settled.fallback_calls, 0); - } - - #[test] - fn actual_scalar_overlapping_nonadjacent_candidates_select_one_and_keep_the_other_separate() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let paired_a = Arc::new(std::sync::Mutex::new(Vec::new())); - let paired_b = Arc::new(std::sync::Mutex::new(Vec::new())); - let separate_a = Arc::new(std::sync::Mutex::new(Vec::new())); - let separate_b = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_variant( - false, - false, - false, - true, - Some(Arc::clone(&paired_b)), - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentTrackA, - 1, - Some((0, Arc::clone(&paired_a))), - ); - let selected = graph::test_only_selected_split_fader() - .expect("the first overlapping candidate is selected"); - assert_eq!( - selected.node, - GraphNodeId::TrackStage { - track_id: StableGraphId::parse("t00").expect("track"), - stage: TrackStage::PostFader, - } - ); - let preparation = test_only_fader_matrix_witness(); - assert_eq!( - (preparation.factory_calls, preparation.factory_members), - (1, 1) - ); - - let mut separate = prepared_pair_graph_variant( - false, - false, - false, - false, - Some(Arc::clone(&separate_b)), - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentTrackA, - 1, - Some((0, Arc::clone(&separate_a))), - ); - test_only_reset_fader_matrix_witness(); - let paired_output = render_bound(&mut paired, 0); - let paired_witness = test_only_fader_matrix_witness(); - let paired_trace = test_only_scalar_state_trace(); - test_only_reset_fader_matrix_witness(); - let separate_output = render_bound(&mut separate, 0); - let separate_trace = test_only_scalar_state_trace(); - - assert_eq!( - paired_output, separate_output, - "overlap keeps PCM unchanged" - ); - assert_eq!( - std::mem::take(&mut *paired_a.lock().unwrap()), - std::mem::take(&mut *separate_a.lock().unwrap()), - "the selected first candidate has exact PCM" - ); - assert_eq!( - std::mem::take(&mut *paired_b.lock().unwrap()), - std::mem::take(&mut *separate_b.lock().unwrap()), - "the later overlapping candidate stays a separate owner" - ); - assert_eq!( - (paired_witness.process_calls, paired_witness.process_members), - (1, 1) - ); - assert_eq!( - (paired_witness.fused_calls, paired_witness.fallback_calls), - (1, 0) - ); - assert!(paired_trace.fader_owners[..paired_trace.fader_len].contains(&1)); - assert!(paired_trace.matrix_owners[..paired_trace.matrix_len].contains(&1)); - assert_eq!( - first_fader_state(paired_trace, 1), - first_fader_state(separate_trace, 1), - "the later overlapping fader keeps its separate state: paired={paired_trace:?}, separate={separate_trace:?}" - ); - assert_eq!( - first_matrix_state(paired_trace, 1), - first_matrix_state(separate_trace, 1), - "the later overlapping matrix keeps its separate state" - ); - assert_eq!( - last_fader_state(paired_trace, 1), - last_fader_state(separate_trace, 1), - "the later overlapping fader reaches the same final state" - ); - assert_eq!( - last_matrix_state(paired_trace, 1), - last_matrix_state(separate_trace, 1), - "the later overlapping matrix reaches the same final state" - ); - } - - /// The output track takes the split pair: there is no physical output conflict any more. - /// - /// Before issue #916 this test was - /// `actual_scalar_nonadjacent_physical_output_conflict_declines_before_owner_transfer`. The - /// Output folded in place onto the output track's post-matrix buffer, so that track's fader - /// buffer *was* the graph output. `scalar_split_interval_is_clear` declined it before either - /// owner moved. Now the Output is dedicated storage whose block is the host's planes, so no - /// fader shares a live buffer with it. The slot comparison that declined the pair went with - /// the premise (`crates/graph/tests/MUTATIONS.md` row 916-16). - /// - /// The first fixture is unchanged. With t00's fader metered and t00 the output track, t01's - /// interval is the only candidate, and it is selected. The second fixture keeps the t00 meter - /// and makes t01 the output track. t01 is now selected there too, where it used to be - /// declined. It must render exactly what the separate-owner twin renders: the host planes, - /// which are t01's post-matrix output, the captured post-matrix PCM, and both owners' states. - #[cfg(all(test, feature = "test-support"))] - #[test] - fn actual_scalar_nonadjacent_output_track_takes_the_split_pair_now_the_output_is_dedicated() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let reference_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - - // Issue #1420: the premise. The first fixture's output track is t00 and the second's is - // t01, named by literal ID so this does not restate the harness. - let output_source = |variant| { - let (graph, _) = track_graph_variant(2, variant); - let sources = graph - .spec - .edges - .iter() - .filter(|edge| matches!(edge.destination.node, GraphNodeId::Output { .. })) - .map(|edge| edge.source.node.clone()) - .collect::>(); - assert_eq!(sources.len(), 1, "one edge feeds the Output"); - sources[0].clone() - }; - for (variant, track) in [ - (BoundaryVariant::Nonadjacent, "t00"), - (BoundaryVariant::NonadjacentOutputConflict, "t01"), - ] { - assert_eq!( - output_source(variant), - GraphNodeId::TrackStage { - track_id: StableGraphId::parse(track).expect("track"), - stage: TrackStage::PostMatrix, - }, - "{variant:?} feeds the Output from {track}'s post-matrix" - ); - } - - // With t00 observed, the t01 interval is the only remaining candidate when the output is - // t00. It is selected. - test_only_reset_fader_matrix_witness(); - let eligible = prepared_pair_graph_variant( - true, - false, - false, - true, - None, - Backend::Scalar, - 2, - BoundaryVariant::Nonadjacent, - 0, - None, - ); - let eligible_witness = test_only_fader_matrix_witness(); - assert_eq!( - ( - eligible_witness.factory_calls, - eligible_witness.factory_members - ), - (1, 1), - "the t01 candidate is eligible when t00 is the output track" - ); - assert_eq!( - graph::test_only_selected_split_fader() - .expect("eligible t01 candidate") - .node, - GraphNodeId::TrackStage { - track_id: StableGraphId::parse("t01").expect("track"), - stage: TrackStage::PostFader, - } - ); - drop(eligible); - - // The same candidate when t01 is the output track: selected too (issue #916). - test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_variant( - true, - false, - false, - true, - Some(Arc::clone(&paired_capture)), - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentOutputConflict, - 0, - None, - ); - let prepared = test_only_fader_matrix_witness(); - assert_eq!( - (prepared.factory_calls, prepared.factory_members), - (1, 1), - "the output track's interval is selected: its buffer is no longer the graph output" - ); - assert_eq!( - graph::test_only_selected_split_fader() - .expect("the output track's candidate") - .node, - GraphNodeId::TrackStage { - track_id: StableGraphId::parse("t01").expect("track"), - stage: TrackStage::PostFader, - } - ); - let mut reference = prepared_pair_graph_variant( - false, - false, - false, - false, - Some(Arc::clone(&reference_capture)), - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentOutputConflict, - 0, - None, - ); - test_only_reset_fader_matrix_witness(); - let paired_output = render_bound(&mut paired, 0); - let paired_state = test_only_fader_matrix_witness(); - let paired_trace = test_only_scalar_state_trace(); - test_only_reset_fader_matrix_witness(); - let reference_output = render_bound(&mut reference, 0); - let reference_state = test_only_fader_matrix_witness(); - let reference_trace = test_only_scalar_state_trace(); - assert_eq!( - paired_output, reference_output, - "the host planes, the paired output track's post-matrix output, are the separate \ - owners'" - ); - assert!(paired_output.iter().any(|word| *word != 0)); - assert_eq!( - std::mem::take(&mut *paired_capture.lock().unwrap()), - std::mem::take(&mut *reference_capture.lock().unwrap()), - "the paired output track preserves separate-owner PCM" - ); - assert_eq!( - paired_state.scalar_fader_words, - reference_state.scalar_fader_words - ); - assert_eq!( - paired_state.scalar_matrix_words, - reference_state.scalar_matrix_words - ); - assert_owner_states_match(paired_trace, reference_trace); - assert_eq!( - (paired_state.process_calls, paired_state.factory_calls), - (1, 0), - "the selected split pair runs once per block and is built only at preparation" - ); - assert_eq!( - (reference_state.process_calls, reference_state.factory_calls), - (0, 0), - "the concurrent twin selects no pair" - ); - } - - #[cfg(all(test, feature = "test-support"))] - #[test] - fn actual_scalar_nonadjacent_intervening_observer_error_completes_and_retries() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let separate_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_variant_observed( - false, - false, - false, - true, - Some(Arc::clone(&paired_capture)), - Backend::Scalar, - 2, - BoundaryVariant::Nonadjacent, - 1, - None, - false, - true, - ) - .0; - let paired_prepared = test_only_fader_matrix_witness(); - assert_eq!( - ( - paired_prepared.factory_calls, - paired_prepared.factory_members - ), - (1, 1) - ); - test_only_reset_fader_matrix_witness(); - let mut separate = prepared_pair_graph_variant_observed( - false, - false, - false, - false, - Some(Arc::clone(&separate_capture)), - Backend::Scalar, - 2, - BoundaryVariant::Nonadjacent, - 1, - None, - false, - true, - ) - .0; - let separate_prepared = test_only_fader_matrix_witness(); - assert_eq!( - ( - separate_prepared.factory_calls, - separate_prepared.factory_members - ), - (0, 0) - ); - let fader = TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -6.0, - smoothing_samples: 0, - }; - let matrix = TrackControlRecord { - matrix: Matrix2x2 { - ll: 0.5, - lr: 0.25, - rl: -0.25, - rr: 0.75, - }, - smoothing_samples: 0, - }; - // Issue #916: the selected split pair is t00's, so its controls are the ones the failing - // observer must interrupt between `F0` and `M0`. - assert!(matches!( - paired.test_only_post_fader_node.as_ref(), - Some(GraphNodeId::TrackStage { - track_id, - stage: TrackStage::PostFader, - }) if track_id.as_str() == "t00" - )); - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| controls[0].fader.write(stamp, fader)); - bound.test_only_write(|controls, stamp| controls[0].producer.write(stamp, matrix)); - } - - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - test_only_reset_fader_matrix_witness(); - let mut paired_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; - let paired_error = render_bound_result(&mut paired, 0, &mut paired_pcm).unwrap_err(); - let paired_failed = test_only_fader_matrix_witness(); - let paired_failed_trace = test_only_scalar_state_trace(); - assert_eq!(paired_error, RenderError::InvalidEnvelope); - assert_eq!(INTERVENING_OBSERVER_ERRORS.load(Ordering::Relaxed), 1); - assert_eq!(paired_failed.fader_values_applied, 1); - assert_eq!(paired_failed.matrix_values_applied, 0); - - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - test_only_reset_fader_matrix_witness(); - let mut separate_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; - let separate_error = render_bound_result(&mut separate, 0, &mut separate_pcm).unwrap_err(); - let separate_failed = test_only_fader_matrix_witness(); - let separate_failed_trace = test_only_scalar_state_trace(); - assert_eq!(separate_error, paired_error); - assert_eq!(separate_failed.fader_values_applied, 1); - assert_eq!(separate_failed.matrix_values_applied, 0); - // Per owner, because the pending fader is completed after t01's separate fader ran, so - // the globally last-recorded word names a different owner in the two runs. - assert!(!paired_failed_trace.fader_overflow && !paired_failed_trace.matrix_overflow); - assert!(!separate_failed_trace.fader_overflow && !separate_failed_trace.matrix_overflow); - for owner in [0, 1] { - assert_eq!( - last_fader_state(paired_failed_trace, owner), - last_fader_state(separate_failed_trace, owner), - "observer error preserves fader {owner}'s state before retry" - ); - } - let initial_matrix = initial_matrix_state(0).expect("t00 initial matrix state"); - assert_eq!( - last_matrix_state(paired_failed_trace, 0), - initial_matrix, - "observer error leaves the later matrix command untouched" - ); - assert!( - !separate_failed_trace.matrix_owners[..separate_failed_trace.matrix_len].contains(&0), - "the separate twin never reached M0 either" - ); - - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - test_only_reset_fader_matrix_witness(); - paired_pcm.fill(0.0); - render_bound_result(&mut paired, HARNESS_QUANTUM as u64, &mut paired_pcm) - .expect("paired retry after observer error"); - let paired_retry = test_only_fader_matrix_witness(); - let paired_retry_trace = test_only_scalar_state_trace(); - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - test_only_reset_fader_matrix_witness(); - separate_pcm.fill(0.0); - render_bound_result(&mut separate, HARNESS_QUANTUM as u64, &mut separate_pcm) - .expect("separate retry after observer error"); - let separate_retry = test_only_fader_matrix_witness(); - let separate_retry_trace = test_only_scalar_state_trace(); - assert_eq!( - paired_pcm - .iter() - .map(|sample| sample.to_bits()) - .collect::>(), - separate_pcm - .iter() - .map(|sample| sample.to_bits()) - .collect::>(), - "retry renders the same PCM after the observer error" - ); - assert_eq!(paired_retry.matrix_values_applied, 1); - assert_eq!( - separate_retry.matrix_values_applied, 1, - "the separate twin drains the same queued matrix command on retry" - ); - assert_owner_states_match(paired_retry_trace, separate_retry_trace); - assert_eq!( - (paired_retry.fused_calls, paired_retry.fallback_calls), - (1, 0) - ); - assert_eq!( - std::mem::take(&mut *paired_capture.lock().unwrap()), - std::mem::take(&mut *separate_capture.lock().unwrap()) - ); - } - - #[cfg(all(test, feature = "test-support"))] - #[test] - fn actual_scalar_nonadjacent_failed_render_materializes_post_fader_before_error() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let fader = TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -6.0, - smoothing_samples: 0, - }; - let matrix = Matrix2x2 { - ll: 0.5, - lr: 0.25, - rl: -0.25, - rr: 0.75, - }; - let make_observer_case = |between_render_calls| { - let mut bound = prepared_pair_graph_variant_observed( - false, - false, - false, - between_render_calls, - None, - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentTrackA, - 1, - None, - false, - true, - ) - .0; - bound.test_only_write(|controls, stamp| controls[0].fader.write(stamp, fader)); - bound.test_only_write(|controls, stamp| { - controls[0].producer.write( - stamp, - TrackControlRecord { - matrix, - smoothing_samples: 0, - }, - ) - }); - bound - }; - - let mut paired = make_observer_case(true); - let mut separate = make_observer_case(false); - assert!(matches!( - paired.test_only_post_fader_node.as_ref(), - Some(GraphNodeId::TrackStage { - track_id, - stage: TrackStage::PostFader, - }) if track_id.as_str() == "t00" - )); - assert!(separate.test_only_post_fader_node.is_none()); - let selected_post_fader_buffer = paired - .test_only_post_fader_buffer - .expect("paired fixture has a selected post-fader buffer"); - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - test_only_reset_fader_matrix_witness(); - let (paired_error, paired_capture) = - render_failed_buffer_result(&mut paired, 0, false, selected_post_fader_buffer); - let paired_failed = test_only_fader_matrix_witness(); - let paired_trace = test_only_scalar_state_trace(); - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - test_only_reset_fader_matrix_witness(); - let (separate_error, separate_capture) = - render_failed_buffer_result(&mut separate, 0, false, selected_post_fader_buffer); - let separate_failed = test_only_fader_matrix_witness(); - let separate_trace = test_only_scalar_state_trace(); - assert!(!paired_trace.fader_overflow && !paired_trace.matrix_overflow); - assert!(!separate_trace.fader_overflow && !separate_trace.matrix_overflow); - assert_eq!(paired_error, separate_error); - assert!(paired_capture.captured && separate_capture.captured); - assert!(!paired_capture.overflow && !separate_capture.overflow); - assert_eq!(paired_capture, separate_capture); - assert_eq!( - first_fader_state(paired_trace, 0), - first_fader_state(separate_trace, 0), - "observer failure preserves t00 fader state at F_A" - ); - assert_eq!( - last_fader_state(paired_trace, 0), - first_fader_state(separate_trace, 0), - "observer failure completes t00 fader state before returning" - ); - let initial_matrix = initial_matrix_state(0).expect("t00 initial matrix state"); - assert_eq!( - first_matrix_state(paired_trace, 0), - initial_matrix, - "observer failure preserves t00 matrix state at M_A" - ); - assert_eq!( - last_matrix_state(paired_trace, 0), - initial_matrix, - "observer failure preserves t00 matrix state at completion" - ); - assert_eq!(paired_failed.matrix_values_applied, 0); - assert_eq!(separate_failed.matrix_values_applied, 0); - - // The same post-fader assertion must discriminate the executor's completion step. With - // completion disabled, the split owner leaves its deferred fader output behind while the - // separate owner has already materialized it. - let mut disabled = make_observer_case(true); - INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); - let (_, disabled_capture) = - render_failed_buffer_result(&mut disabled, 0, true, selected_post_fader_buffer); - assert_ne!( - disabled_capture, separate_capture, - "omitting pending completion must fail the post-fader buffer equality" - ); - - let make_matrix_case = |between_render_calls| { - let mut bound = prepared_pair_graph_variant( - false, - false, - false, - between_render_calls, - None, - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentTrackA, - 1, - None, - ); - // One transaction: a valid fader move and a matrix target render refuses. The cell - // holds the refused target alone, since a later write replaces an earlier one. - bound.test_only_write(|controls, stamp| { - controls[0].fader.write(stamp, fader); - controls[0].producer.write( - stamp, - TrackControlRecord { - matrix: Matrix2x2 { - ll: f32::NAN, - ..matrix - }, - smoothing_samples: 0, - }, - ) - }); - bound - }; - let mut paired = make_matrix_case(true); - let mut separate = make_matrix_case(false); - assert!(matches!( - paired.test_only_post_fader_node.as_ref(), - Some(GraphNodeId::TrackStage { - track_id, - stage: TrackStage::PostFader, - }) if track_id.as_str() == "t00" - )); - assert!(separate.test_only_post_fader_node.is_none()); - let selected_post_fader_buffer = paired - .test_only_post_fader_buffer - .expect("paired fixture has a selected post-fader buffer"); - test_only_reset_fader_matrix_witness(); - let (paired_error, paired_capture) = - render_failed_buffer_result(&mut paired, 0, false, selected_post_fader_buffer); - let paired_failed = test_only_fader_matrix_witness(); - let paired_trace = test_only_scalar_state_trace(); + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); test_only_reset_fader_matrix_witness(); - let (separate_error, separate_capture) = - render_failed_buffer_result(&mut separate, 0, false, selected_post_fader_buffer); - let separate_failed = test_only_fader_matrix_witness(); - let separate_trace = test_only_scalar_state_trace(); - assert!(!paired_trace.fader_overflow && !paired_trace.matrix_overflow); - assert!(!separate_trace.fader_overflow && !separate_trace.matrix_overflow); - assert_eq!(paired_error, separate_error); - assert!(paired_capture.captured && separate_capture.captured); - assert_eq!(paired_capture, separate_capture); - assert_eq!( - first_fader_state(paired_trace, 0), - first_fader_state(separate_trace, 0), - "matrix failure preserves t00 fader state at F_A" - ); - assert_eq!( - last_fader_state(paired_trace, 0), - first_fader_state(separate_trace, 0), - "matrix failure completes t00 fader state before returning" - ); - let initial_matrix = initial_matrix_state(0).expect("t00 initial matrix state"); - assert_eq!( - first_matrix_state(paired_trace, 0), - initial_matrix, - "matrix failure preserves t00 matrix state at F_A" - ); - assert_eq!( - last_matrix_state(paired_trace, 0), - first_matrix_state(separate_trace, 0), - "matrix failure preserves t00 matrix state at M_A/error completion" - ); - assert_eq!(paired_failed.fader_values_applied, 1); - assert_eq!(paired_failed.matrix_values_applied, 1); - assert_eq!( - paired_failed.fader_values_applied, - separate_failed.fader_values_applied - ); - assert_eq!( - paired_failed.matrix_values_applied, - separate_failed.matrix_values_applied - ); - } - - #[cfg(all(test, feature = "test-support"))] - #[test] - fn actual_scalar_nonadjacent_ramp_retarget_and_failed_retry_stay_at_original_boundaries() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let matrix_a = Matrix2x2 { - ll: 0.5, - lr: 0.25, - rl: -0.25, - rr: 0.75, - }; - let matrix_b = Matrix2x2 { - ll: 0.75, - lr: -0.5, - rl: 0.125, - rr: 0.625, - }; - - // The selected production fixture is t00's genuinely nonadjacent F_A -> F_B -> M_A -> - // M_B interval. t00's post-matrix capture observes the paired owner itself, beside the - // graph's final PCM. - let make_pair = || { - let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let separate_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let paired = prepared_pair_graph_variant( - false, - false, - false, - true, - Some(Arc::clone(&paired_capture)), - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentTrackA, - 1, - Some((0, Arc::clone(&paired_capture))), - ); - let separate = prepared_pair_graph_variant( - false, - false, - false, - false, - Some(Arc::clone(&separate_capture)), - Backend::Scalar, - 2, - BoundaryVariant::NonadjacentTrackA, - 1, - Some((0, Arc::clone(&separate_capture))), - ); - assert_eq!( - ( - test_only_fader_matrix_witness().factory_calls, - test_only_fader_matrix_witness().factory_members - ), - (1, 1), - "paired fixture selects the t00 split owner" - ); - test_only_reset_fader_matrix_witness(); - (paired, separate, paired_capture, separate_capture) - }; - - let render_and_compare = |paired: &mut PreparedBuiltinsGraphBound, - separate: &mut PreparedBuiltinsGraphBound, - paired_capture: &Arc>>, - separate_capture: &Arc>>, - sample: u64| { - let mut paired_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; - let mut separate_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; - test_only_reset_fader_matrix_witness(); - render_bound_result(paired, sample, &mut paired_pcm).expect("paired render"); - let paired_witness = test_only_fader_matrix_witness(); - let paired_trace = test_only_scalar_state_trace(); - test_only_reset_fader_matrix_witness(); - render_bound_result(separate, sample, &mut separate_pcm).expect("separate render"); - let separate_witness = test_only_fader_matrix_witness(); - let separate_trace = test_only_scalar_state_trace(); - assert!(!paired_trace.fader_overflow && !paired_trace.matrix_overflow); - assert!(!separate_trace.fader_overflow && !separate_trace.matrix_overflow); - assert_eq!( - std::mem::take(&mut *paired_capture.lock().unwrap()), - std::mem::take(&mut *separate_capture.lock().unwrap()), - "paired and separate t00/t01 post-matrix PCM" - ); - assert_eq!( - paired_pcm - .iter() - .map(|sample| sample.to_bits()) - .collect::>(), - separate_pcm - .iter() - .map(|sample| sample.to_bits()) - .collect::>(), - "paired and separate output PCM" - ); - assert_eq!( - last_fader_state(paired_trace, 0), - last_fader_state(separate_trace, 0), - "t00 fader state at its original boundary" - ); - assert_eq!( - last_matrix_state(paired_trace, 0), - last_matrix_state(separate_trace, 0), - "t00 matrix state at its original boundary" - ); - ( - paired_witness, - separate_witness, - paired_trace, - separate_trace, - ) - }; - - // A ramping fader must execute at F_A. The split owner cannot defer this block, so the - // matrix boundary uses the original two-stage fallback and leaves no pending fader. - let (mut paired, mut separate, paired_capture, separate_capture) = make_pair(); - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - controls[0].fader.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -6.0, - smoothing_samples: 3, - }, - ) - }); - } - let (paired_ramp, separate_ramp, paired_trace, separate_trace) = render_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 0, - ); - assert_eq!( - (paired_ramp.process_calls, paired_ramp.process_members), - (1, 1) - ); - assert_eq!( - (paired_ramp.fused_calls, paired_ramp.fallback_calls), - (0, 1) - ); - assert_eq!( - ( - paired_ramp.fader_values_applied, - paired_ramp.matrix_values_applied - ), - (1, 0) - ); - assert_eq!( - first_fader_state(paired_trace, 0), - last_fader_state(paired_trace, 0), - "ramping fader state is materialized at F_A before M_A" - ); - assert_eq!( - first_fader_state(paired_trace, 0), - first_fader_state(separate_trace, 0) - ); - assert_eq!( - paired_ramp.fader_values_applied, - separate_ramp.fader_values_applied - ); - - // A settled fader plus a ramping matrix must defer only the fader and fall back at M_A. - let (mut paired, mut separate, paired_capture, separate_capture) = make_pair(); - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - controls[0].fader.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -6.0, - smoothing_samples: 0, - }, - ) - }); - bound.test_only_write(|controls, stamp| { - controls[0].producer.write( - stamp, - TrackControlRecord { - matrix: matrix_a, - smoothing_samples: 96, - }, - ) - }); - } - let (paired_matrix_ramp, separate_matrix_ramp, _, _) = render_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - 0, + paired_pcm.fill(0.0); + render_bound_result(&mut paired, HARNESS_QUANTUM as u64, &mut paired_pcm) + .expect("paired retry after observer error"); + let paired_retry = test_only_fader_matrix_witness(); + let paired_retry_trace = test_only_scalar_state_trace(); + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); + test_only_reset_fader_matrix_witness(); + separate_pcm.fill(0.0); + render_bound_result(&mut separate, HARNESS_QUANTUM as u64, &mut separate_pcm) + .expect("separate retry after observer error"); + let separate_retry = test_only_fader_matrix_witness(); + let separate_retry_trace = test_only_scalar_state_trace(); + assert_eq!( + paired_pcm + .iter() + .map(|sample| sample.to_bits()) + .collect::>(), + separate_pcm + .iter() + .map(|sample| sample.to_bits()) + .collect::>(), + "retry renders the same PCM after the observer error" ); + assert_eq!(paired_retry.matrix_values_applied, 1); assert_eq!( - ( - paired_matrix_ramp.fused_calls, - paired_matrix_ramp.fallback_calls - ), - (0, 1) + separate_retry.matrix_values_applied, 1, + "the separate twin drains the same queued matrix command on retry" ); + assert_owner_states_match(paired_retry_trace, separate_retry_trace); assert_eq!( - ( - paired_matrix_ramp.fader_values_applied, - paired_matrix_ramp.matrix_values_applied - ), - (1, 1) + (paired_retry.fused_calls, paired_retry.fallback_calls), + (1, 0) ); assert_eq!( - paired_matrix_ramp.matrix_values_applied, - separate_matrix_ramp.matrix_values_applied + std::mem::take(&mut *paired_capture.lock().unwrap()), + std::mem::take(&mut *separate_capture.lock().unwrap()) ); + } - // Retargeting the matrix while its prior ramp is in flight must still run at M_A. The - // second block also proves that the split owner clears its pending flag on success. - for bound in [&mut paired, &mut separate] { + #[cfg(all(test, feature = "test-support"))] + #[test] + fn actual_scalar_nonadjacent_failed_render_materializes_post_fader_before_error() { + let _guard = PAIR_WITNESS_LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let fader = TrackFaderRecord::FaderDb { + lanes: BuiltinLaneSelector::Both, + db: -6.0, + smoothing_samples: 0, + }; + let matrix = Matrix2x2 { + ll: 0.5, + lr: 0.25, + rl: -0.25, + rr: 0.75, + }; + let make_observer_case = |between_render_calls| { + let mut bound = prepared_pair_graph_variant_observed( + false, + false, + false, + between_render_calls, + None, + Backend::Scalar, + 2, + BoundaryVariant::NonadjacentTrackA, + 1, + None, + false, + true, + ) + .0; + bound.test_only_write(|controls, stamp| controls[0].fader.write(stamp, fader)); bound.test_only_write(|controls, stamp| { controls[0].producer.write( stamp, TrackControlRecord { - matrix: matrix_b, - smoothing_samples: 3, + matrix, + smoothing_samples: 0, }, ) }); - } - let (paired_retarget, separate_retarget, _, _) = render_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - HARNESS_QUANTUM as u64, + bound + }; + + let mut paired = make_observer_case(true); + let mut separate = make_observer_case(false); + assert!(matches!( + paired.test_only_post_fader_node.as_ref(), + Some(GraphNodeId::TrackStage { + track_id, + stage: TrackStage::PostFader, + }) if track_id.as_str() == "t00" + )); + assert!(separate.test_only_post_fader_node.is_none()); + let selected_post_fader_buffer = paired + .test_only_post_fader_buffer + .expect("paired fixture has a selected post-fader buffer"); + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); + test_only_reset_fader_matrix_witness(); + let (paired_error, paired_capture) = + render_failed_buffer_result(&mut paired, 0, false, selected_post_fader_buffer); + let paired_failed = test_only_fader_matrix_witness(); + let paired_trace = test_only_scalar_state_trace(); + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); + test_only_reset_fader_matrix_witness(); + let (separate_error, separate_capture) = + render_failed_buffer_result(&mut separate, 0, false, selected_post_fader_buffer); + let separate_failed = test_only_fader_matrix_witness(); + let separate_trace = test_only_scalar_state_trace(); + assert!(!paired_trace.fader_overflow && !paired_trace.matrix_overflow); + assert!(!separate_trace.fader_overflow && !separate_trace.matrix_overflow); + assert_eq!(paired_error, separate_error); + assert!(paired_capture.captured && separate_capture.captured); + assert!(!paired_capture.overflow && !separate_capture.overflow); + assert_eq!(paired_capture, separate_capture); + assert_eq!( + first_fader_state(paired_trace, 0), + first_fader_state(separate_trace, 0), + "observer failure preserves t00 fader state at F_A" ); assert_eq!( - (paired_retarget.fused_calls, paired_retarget.fallback_calls), - (0, 1) + last_fader_state(paired_trace, 0), + first_fader_state(separate_trace, 0), + "observer failure completes t00 fader state before returning" ); + let initial_matrix = initial_matrix_state(0).expect("t00 initial matrix state"); assert_eq!( - paired_retarget.matrix_values_applied, - separate_retarget.matrix_values_applied + first_matrix_state(paired_trace, 0), + initial_matrix, + "observer failure preserves t00 matrix state at M_A" + ); + assert_eq!( + last_matrix_state(paired_trace, 0), + initial_matrix, + "observer failure preserves t00 matrix state at completion" ); + assert_eq!(paired_failed.matrix_values_applied, 0); + assert_eq!(separate_failed.matrix_values_applied, 0); - // A failed M_A render must complete the pending fader before returning and retry exactly - // once with the next write. A cell holds the newest value only, so the malformed target - // is the one the failed block reads, and the tail is written after it. - let (mut paired, mut separate, paired_capture, separate_capture) = make_pair(); - let invalid_matrix = TrackControlRecord { - matrix: Matrix2x2 { - ll: f32::NAN, - ..matrix_a - }, - smoothing_samples: 0, - }; - let matrix_tail = TrackControlRecord { - matrix: Matrix2x2::IDENTITY, - smoothing_samples: 0, - }; - for bound in [&mut paired, &mut separate] { + // The same post-fader assertion must discriminate the executor's completion step. With + // completion disabled, the split owner leaves its deferred fader output behind while the + // separate owner has already materialized it. + let mut disabled = make_observer_case(true); + INTERVENING_OBSERVER_ERRORS.store(0, Ordering::Relaxed); + let (_, disabled_capture) = + render_failed_buffer_result(&mut disabled, 0, true, selected_post_fader_buffer); + assert_ne!( + disabled_capture, separate_capture, + "omitting pending completion must fail the post-fader buffer equality" + ); + + let make_matrix_case = |between_render_calls| { + let mut bound = prepared_pair_graph_variant( + false, + false, + false, + between_render_calls, + None, + Backend::Scalar, + 2, + BoundaryVariant::NonadjacentTrackA, + 1, + None, + ); + // One transaction: a valid fader move and a matrix target render refuses. The cell + // holds the refused target alone, since a later write replaces an earlier one. bound.test_only_write(|controls, stamp| { - controls[0].fader.write( + controls[0].fader.write(stamp, fader); + controls[0].producer.write( stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -6.0, + TrackControlRecord { + matrix: Matrix2x2 { + ll: f32::NAN, + ..matrix + }, smoothing_samples: 0, }, ) }); - bound.test_only_write(|controls, stamp| { - controls[0].producer.write(stamp, invalid_matrix) - }); - } - let selected_buffer = paired + bound + }; + let mut paired = make_matrix_case(true); + let mut separate = make_matrix_case(false); + assert!(matches!( + paired.test_only_post_fader_node.as_ref(), + Some(GraphNodeId::TrackStage { + track_id, + stage: TrackStage::PostFader, + }) if track_id.as_str() == "t00" + )); + assert!(separate.test_only_post_fader_node.is_none()); + let selected_post_fader_buffer = paired .test_only_post_fader_buffer - .expect("paired fixture has t00 post-fader buffer"); + .expect("paired fixture has a selected post-fader buffer"); test_only_reset_fader_matrix_witness(); - let (paired_error, paired_failed_capture) = - render_failed_buffer_result(&mut paired, 0, false, selected_buffer); + let (paired_error, paired_capture) = + render_failed_buffer_result(&mut paired, 0, false, selected_post_fader_buffer); let paired_failed = test_only_fader_matrix_witness(); - let paired_failed_trace = test_only_scalar_state_trace(); + let paired_trace = test_only_scalar_state_trace(); test_only_reset_fader_matrix_witness(); - let (separate_error, separate_failed_capture) = - render_failed_buffer_result(&mut separate, 0, false, selected_buffer); + let (separate_error, separate_capture) = + render_failed_buffer_result(&mut separate, 0, false, selected_post_fader_buffer); let separate_failed = test_only_fader_matrix_witness(); - let separate_failed_trace = test_only_scalar_state_trace(); + let separate_trace = test_only_scalar_state_trace(); + assert!(!paired_trace.fader_overflow && !paired_trace.matrix_overflow); + assert!(!separate_trace.fader_overflow && !separate_trace.matrix_overflow); assert_eq!(paired_error, separate_error); - assert_eq!(paired_failed_capture, separate_failed_capture); - assert_eq!( - ( - paired_failed.fader_values_applied, - paired_failed.matrix_values_applied - ), - (1, 1) - ); + assert!(paired_capture.captured && separate_capture.captured); + assert_eq!(paired_capture, separate_capture); assert_eq!( - ( - paired_failed.fader_values_applied, - paired_failed.matrix_values_applied - ), - ( - separate_failed.fader_values_applied, - separate_failed.matrix_values_applied - ) + first_fader_state(paired_trace, 0), + first_fader_state(separate_trace, 0), + "matrix failure preserves t00 fader state at F_A" ); assert_eq!( - last_fader_state(paired_failed_trace, 0), - first_fader_state(separate_failed_trace, 0), - "failed M_A render completes t00 fader before returning" + last_fader_state(paired_trace, 0), + first_fader_state(separate_trace, 0), + "matrix failure completes t00 fader state before returning" ); + let initial_matrix = initial_matrix_state(0).expect("t00 initial matrix state"); assert_eq!( - last_matrix_state(paired_failed_trace, 0), - last_matrix_state(separate_failed_trace, 0), - "failed M_A render leaves the matrix state alone" + first_matrix_state(paired_trace, 0), + initial_matrix, + "matrix failure preserves t00 matrix state at F_A" ); - - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| controls[0].producer.write(stamp, matrix_tail)); - } - let (paired_retry, separate_retry, _, _) = render_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - HARNESS_QUANTUM as u64, + assert_eq!( + last_matrix_state(paired_trace, 0), + first_matrix_state(separate_trace, 0), + "matrix failure preserves t00 matrix state at M_A/error completion" ); + assert_eq!(paired_failed.fader_values_applied, 1); + assert_eq!(paired_failed.matrix_values_applied, 1); assert_eq!( - (paired_retry.fused_calls, paired_retry.fallback_calls), - (1, 0) + paired_failed.fader_values_applied, + separate_failed.fader_values_applied ); - assert_eq!(paired_retry.matrix_values_applied, 1); assert_eq!( - ( - paired_retry.fader_values_applied, - paired_retry.matrix_values_applied - ), - ( - separate_retry.fader_values_applied, - separate_retry.matrix_values_applied - ), - "retry applies only the write after the error, once" + paired_failed.matrix_values_applied, + separate_failed.matrix_values_applied ); } + #[cfg(all(test, feature = "test-support"))] #[test] - fn actual_scalar_nonadjacent_invalid_fader_matrix_boundaries_preserve_retry_order() { + fn actual_scalar_nonadjacent_ramp_retarget_and_failed_retry_stay_at_original_boundaries() { let _guard = PAIR_WITNESS_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); - let cases = [ - ("F_A", 0_usize, true, false), - ("F_B", 1_usize, true, false), - ("M_A", 0_usize, false, true), - ("M_B", 1_usize, false, true), - ]; - for (label, track, invalid_fader, _invalid_matrix) in cases { + let matrix_a = Matrix2x2 { + ll: 0.5, + lr: 0.25, + rl: -0.25, + rr: 0.75, + }; + let matrix_b = Matrix2x2 { + ll: 0.75, + lr: -0.5, + rl: 0.125, + rr: 0.625, + }; + + // The selected production fixture is t00's genuinely nonadjacent F_A -> F_B -> M_A -> + // M_B interval. t00's post-matrix capture observes the paired owner itself, beside the + // graph's final PCM. + let make_pair = || { let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); let separate_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_variant( + let paired = prepared_pair_graph_variant( false, false, false, @@ -14082,19 +12869,9 @@ mod tests { 2, BoundaryVariant::NonadjacentTrackA, 1, - None, - ); - let paired_prepared = test_only_fader_matrix_witness(); - assert_eq!( - ( - paired_prepared.factory_calls, - paired_prepared.factory_members - ), - (1, 1), - "{label} paired preparation selects one split owner" + Some((0, Arc::clone(&paired_capture))), ); - test_only_reset_fader_matrix_witness(); - let mut separate = prepared_pair_graph_variant( + let separate = prepared_pair_graph_variant( false, false, false, @@ -14104,586 +12881,298 @@ mod tests { 2, BoundaryVariant::NonadjacentTrackA, 1, - None, + Some((0, Arc::clone(&separate_capture))), ); - let separate_prepared = test_only_fader_matrix_witness(); assert_eq!( ( - separate_prepared.factory_calls, - separate_prepared.factory_members + test_only_fader_matrix_witness().factory_calls, + test_only_fader_matrix_witness().factory_members ), - (0, 0), - "{label} reference preparation keeps separate owners" + (1, 1), + "paired fixture selects the t00 split owner" ); - // The cell holds the refused value alone: a later write replaces an earlier one. - for bound in [&mut paired, &mut separate] { - if invalid_fader { - bound.test_only_write(|controls, stamp| { - controls[track].fader.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: f32::NAN, - smoothing_samples: 0, - }, - ) - }); - } else { - bound.test_only_write(|controls, stamp| { - controls[track].producer.write( - stamp, - TrackControlRecord { - matrix: Matrix2x2 { - ll: f32::NAN, - ..Matrix2x2::IDENTITY - }, - smoothing_samples: 0, - }, - ) - }); - } - } + test_only_reset_fader_matrix_witness(); + (paired, separate, paired_capture, separate_capture) + }; + let render_and_compare = |paired: &mut PreparedBuiltinsGraphBound, + separate: &mut PreparedBuiltinsGraphBound, + paired_capture: &Arc>>, + separate_capture: &Arc>>, + sample: u64| { let mut paired_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; let mut separate_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; test_only_reset_fader_matrix_witness(); - let paired_error = render_bound_result(&mut paired, 0, &mut paired_pcm) - .expect_err(&format!("{label} paired boundary must fail")); - let paired_failed = test_only_fader_matrix_witness(); - let paired_failed_trace = test_only_scalar_state_trace(); - test_only_reset_fader_matrix_witness(); - let separate_error = render_bound_result(&mut separate, 0, &mut separate_pcm) - .expect_err(&format!("{label} separate boundary must fail")); - let separate_failed = test_only_fader_matrix_witness(); - let separate_failed_trace = test_only_scalar_state_trace(); - assert!( - !paired_failed_trace.fader_overflow && !paired_failed_trace.matrix_overflow, - "{label} paired state trace capacity" - ); - assert!( - !separate_failed_trace.fader_overflow && !separate_failed_trace.matrix_overflow, - "{label} separate state trace capacity" - ); - assert_eq!(paired_error, separate_error, "{label} error identity"); - if invalid_fader { - assert_eq!( - paired_failed.fader_values_applied, 1, - "{label} refused fader value" - ); - assert_eq!( - paired_failed.matrix_values_applied, 0, - "{label} matrix untouched" - ); - } else { - assert_eq!( - paired_failed.fader_values_applied, 0, - "{label} fader untouched" - ); - assert_eq!( - paired_failed.matrix_values_applied, 1, - "{label} refused matrix value" - ); - } - assert_eq!( - paired_failed.fader_values_applied, separate_failed.fader_values_applied, - "{label} fader drain order" - ); - assert_eq!( - paired_failed.matrix_values_applied, separate_failed.matrix_values_applied, - "{label} matrix drain order" - ); - assert_eq!( - first_fader_state(paired_failed_trace, 0), - first_fader_state(separate_failed_trace, 0), - "{label} t00 fader state at F_A" - ); - assert_eq!( - last_fader_state(paired_failed_trace, 0), - first_fader_state(separate_failed_trace, 0), - "{label} t00 fader state at error/completion" - ); - if !invalid_fader { - assert_eq!( - last_matrix_state(paired_failed_trace, 0), - first_matrix_state(separate_failed_trace, 0), - "{label} t00 matrix state at M_A" - ); - } - - // The write after the failed block is the value the retry applies. - for bound in [&mut paired, &mut separate] { - if invalid_fader { - bound.test_only_write(|controls, stamp| { - controls[track].fader.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -3.0, - smoothing_samples: 0, - }, - ) - }); - } else { - bound.test_only_write(|controls, stamp| { - controls[track].producer.write( - stamp, - TrackControlRecord { - matrix: Matrix2x2::IDENTITY, - smoothing_samples: 0, - }, - ) - }); - } - } - let mut paired_retry_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; - let mut separate_retry_pcm = vec![0.0_f32; HARNESS_QUANTUM as usize * 2]; - test_only_reset_fader_matrix_witness(); - render_bound_result(&mut paired, HARNESS_QUANTUM as u64, &mut paired_retry_pcm) - .expect("paired retry after boundary error"); - let retry = test_only_fader_matrix_witness(); - let paired_retry_trace = test_only_scalar_state_trace(); + render_bound_result(paired, sample, &mut paired_pcm).expect("paired render"); + let paired_witness = test_only_fader_matrix_witness(); + let paired_trace = test_only_scalar_state_trace(); test_only_reset_fader_matrix_witness(); - render_bound_result( - &mut separate, - HARNESS_QUANTUM as u64, - &mut separate_retry_pcm, - ) - .expect("separate retry after boundary error"); - let separate_retry = test_only_fader_matrix_witness(); - let separate_retry_trace = test_only_scalar_state_trace(); - assert!( - !paired_retry_trace.fader_overflow && !paired_retry_trace.matrix_overflow, - "{label} paired retry state trace capacity" - ); - assert!( - !separate_retry_trace.fader_overflow && !separate_retry_trace.matrix_overflow, - "{label} separate retry state trace capacity" - ); + render_bound_result(separate, sample, &mut separate_pcm).expect("separate render"); + let separate_witness = test_only_fader_matrix_witness(); + let separate_trace = test_only_scalar_state_trace(); + assert!(!paired_trace.fader_overflow && !paired_trace.matrix_overflow); + assert!(!separate_trace.fader_overflow && !separate_trace.matrix_overflow); assert_eq!( std::mem::take(&mut *paired_capture.lock().unwrap()), std::mem::take(&mut *separate_capture.lock().unwrap()), - "{label} retry post-matrix PCM" + "paired and separate t00/t01 post-matrix PCM" ); assert_eq!( - paired_retry_pcm + paired_pcm .iter() .map(|sample| sample.to_bits()) .collect::>(), - separate_retry_pcm + separate_pcm .iter() .map(|sample| sample.to_bits()) .collect::>(), - "{label} retry output PCM" - ); - assert_eq!((retry.process_calls, retry.process_members), (1, 1)); - assert_eq!((retry.fused_calls, retry.fallback_calls), (1, 0)); - assert_eq!( - retry.fader_values_applied, separate_retry.fader_values_applied, - "{label} retry fader drain order" - ); - assert_eq!( - retry.matrix_values_applied, separate_retry.matrix_values_applied, - "{label} retry matrix drain order" - ); - assert_eq!( - first_fader_state(paired_retry_trace, 0), - first_fader_state(separate_retry_trace, 0), - "{label} retry t00 fader state at F_A" + "paired and separate output PCM" ); assert_eq!( - last_fader_state(paired_retry_trace, 0), - first_fader_state(separate_retry_trace, 0), - "{label} retry t00 fader state at M_A" + last_fader_state(paired_trace, 0), + last_fader_state(separate_trace, 0), + "t00 fader state at its original boundary" ); assert_eq!( - last_matrix_state(paired_retry_trace, 0), - first_matrix_state(separate_retry_trace, 0), - "{label} retry t00 matrix state at M_A" + last_matrix_state(paired_trace, 0), + last_matrix_state(separate_trace, 0), + "t00 matrix state at its original boundary" ); - if invalid_fader { - assert_eq!(retry.fader_values_applied, 1, "{label} fader retry"); - assert_eq!( - retry.matrix_values_applied, 0, - "{label} matrix remains empty" - ); - } else { - assert_eq!(retry.fader_values_applied, 0, "{label} fader remains empty"); - assert_eq!(retry.matrix_values_applied, 1, "{label} matrix retry"); - } - } - } - - #[test] - fn actual_scalar_graph_preserves_scheduled_matrix_error_and_applies_the_next_write() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let paired_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let separate_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let mut paired = prepared_pair_graph_fixture( - false, - false, - false, - true, - Some(Arc::clone(&paired_capture)), - Backend::Scalar, - 2, - ); - let mut separate = prepared_pair_graph_fixture( - false, - false, - false, - false, - Some(Arc::clone(&separate_capture)), - Backend::Scalar, - 2, - ); - let fader = TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -9.0, - smoothing_samples: 0, - }; - let invalid = TrackControlRecord { - matrix: Matrix2x2 { - ll: f32::NAN, - ..Matrix2x2::IDENTITY - }, - smoothing_samples: 0, - }; - let tail = TrackControlRecord { - matrix: Matrix2x2::IDENTITY, - smoothing_samples: 0, + ( + paired_witness, + separate_witness, + paired_trace, + separate_trace, + ) }; - // One transaction: a valid fader move and a matrix target render refuses. A cell holds - // the newest value only, so the next write is the one the retry reads. - for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| { - controls[1].fader.write(stamp, fader); - controls[1].producer.write(stamp, invalid); - }); - } - let mut pcm = vec![0.0; HARNESS_QUANTUM as usize * 2]; - test_only_reset_fader_matrix_witness(); - let paired_error = render_bound_result(&mut paired, 0, &mut pcm).unwrap_err(); - let paired_state = test_only_fader_matrix_witness(); - pcm.fill(0.0); - test_only_reset_fader_matrix_witness(); - let separate_error = render_bound_result(&mut separate, 0, &mut pcm).unwrap_err(); - let separate_state = test_only_fader_matrix_witness(); - assert_eq!(paired_error, separate_error); - assert_eq!(paired_state.fader_values_applied, 1); - assert_eq!(paired_state.matrix_values_applied, 1); - assert_eq!( - paired_state.scalar_fader_words, - separate_state.scalar_fader_words - ); - assert_eq!( - paired_state.scalar_matrix_words, - separate_state.scalar_matrix_words - ); + // A ramping fader must execute at F_A. The split owner cannot defer this block, so the + // matrix boundary uses the original two-stage fallback and leaves no pending fader. + let (mut paired, mut separate, paired_capture, separate_capture) = make_pair(); for bound in [&mut paired, &mut separate] { - bound.test_only_write(|controls, stamp| controls[1].producer.write(stamp, tail)); - } - // Issue #916: track 0 pairs beside track 1, and its settled pair fuses on the retry too. - let retry = render_scalar_pair_and_compare( - &mut paired, - &mut separate, - &paired_capture, - &separate_capture, - HARNESS_QUANTUM as u64, - 2, - (2, 0), - ); - assert_eq!( - retry.matrix_values_applied, 1, - "the write after the error applies in the next block" - ); - } - - #[test] - fn actual_scalar_extra_reader_declines_and_retains_separate_owner_pcm() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let declined_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let reference_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut declined = prepared_pair_graph_variant( - false, - false, - false, - true, - Some(Arc::clone(&declined_capture)), - Backend::Scalar, - 2, - BoundaryVariant::AliasObserved, - 1, - None, + bound.test_only_write(|controls, stamp| { + controls[0].fader.write( + stamp, + TrackFaderRecord::FaderDb { + lanes: BuiltinLaneSelector::Both, + db: -6.0, + smoothing_samples: 3, + }, + ) + }); + } + let (paired_ramp, separate_ramp, paired_trace, separate_trace) = render_and_compare( + &mut paired, + &mut separate, + &paired_capture, + &separate_capture, + 0, ); - let prepared = test_only_fader_matrix_witness(); - let mut reference = prepared_pair_graph_variant( - false, - false, - false, - false, - Some(Arc::clone(&reference_capture)), - Backend::Scalar, - 2, - BoundaryVariant::AliasObserved, - 1, - None, + assert_eq!( + (paired_ramp.process_calls, paired_ramp.process_members), + (1, 1) ); - // Issue #916: t00 pairs, since its post-matrix buffer is no longer the graph output. The - // extra reader is on t01's fader, the selected trailing track, and it still declines - // t01 before ownership transfer. So preparation builds one pair, t00's, where it used to - // build none; the plain two-track fixture builds two. assert_eq!( - (prepared.factory_calls, prepared.factory_members), - (1, 1), - "the selected scalar fader's real extra reader declines before ownership transfer" + (paired_ramp.fused_calls, paired_ramp.fallback_calls), + (0, 1) ); - test_only_reset_fader_matrix_witness(); - let declined_host = render_bound(&mut declined, 0); - let rendered = test_only_fader_matrix_witness(); - let reference_host = render_bound(&mut reference, 0); assert_eq!( ( - rendered.process_calls, - rendered.process_members, - rendered.factory_calls + paired_ramp.fader_values_applied, + paired_ramp.matrix_values_applied ), - (1, 1, 0), - "only t00's pair runs" + (1, 0) + ); + assert_eq!( + first_fader_state(paired_trace, 0), + last_fader_state(paired_trace, 0), + "ramping fader state is materialized at F_A before M_A" ); assert_eq!( - declined_host, reference_host, - "the host planes, t00's paired post-matrix output, are the separate owners'" + first_fader_state(paired_trace, 0), + first_fader_state(separate_trace, 0) ); - assert!(declined_host.iter().any(|word| *word != 0)); assert_eq!( - std::mem::take(&mut *declined_capture.lock().unwrap()), - std::mem::take(&mut *reference_capture.lock().unwrap()), - "decline keeps the original separate-owner post-matrix PCM" + paired_ramp.fader_values_applied, + separate_ramp.fader_values_applied ); - } - #[test] - fn actual_scalar_effect_sidechain_declines_and_matches_separate_owner_data_and_state() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let paired_side = Arc::new(std::sync::Mutex::new(Vec::new())); - let reference_side = Arc::new(std::sync::Mutex::new(Vec::new())); - let paired_main = Arc::new(std::sync::Mutex::new(Vec::new())); - let reference_main = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut declined = prepared_pair_graph_variant( - true, - false, - false, - true, - Some(Arc::clone(&paired_side)), - Backend::Scalar, - 2, - BoundaryVariant::Sidechain, + // A settled fader plus a ramping matrix must defer only the fader and fall back at M_A. + let (mut paired, mut separate, paired_capture, separate_capture) = make_pair(); + for bound in [&mut paired, &mut separate] { + bound.test_only_write(|controls, stamp| { + controls[0].fader.write( + stamp, + TrackFaderRecord::FaderDb { + lanes: BuiltinLaneSelector::Both, + db: -6.0, + smoothing_samples: 0, + }, + ) + }); + bound.test_only_write(|controls, stamp| { + controls[0].producer.write( + stamp, + TrackControlRecord { + matrix: matrix_a, + smoothing_samples: 96, + }, + ) + }); + } + let (paired_matrix_ramp, separate_matrix_ramp, _, _) = render_and_compare( + &mut paired, + &mut separate, + &paired_capture, + &separate_capture, 0, - Some((0, Arc::clone(&paired_main))), ); - let prepared = test_only_fader_matrix_witness(); - let mut reference = prepared_pair_graph_variant( - true, - false, - false, - false, - Some(Arc::clone(&reference_side)), - Backend::Scalar, - 2, - BoundaryVariant::Sidechain, - 0, - Some((0, Arc::clone(&reference_main))), + assert_eq!( + ( + paired_matrix_ramp.fused_calls, + paired_matrix_ramp.fallback_calls + ), + (0, 1) ); assert_eq!( - (prepared.factory_calls, prepared.factory_members), - (0, 0), - "the real EffectSidechain reader declines before owner transfer" + ( + paired_matrix_ramp.fader_values_applied, + paired_matrix_ramp.matrix_values_applied + ), + (1, 1) ); - - test_only_reset_fader_matrix_witness(); - let declined_output = render_bound(&mut declined, 0); - let declined_state = test_only_fader_matrix_witness(); - test_only_reset_fader_matrix_witness(); - let reference_output = render_bound(&mut reference, 0); - let reference_state = test_only_fader_matrix_witness(); - - assert_eq!(declined_output, reference_output, "effect output PCM"); - let paired_side = std::mem::take(&mut *paired_side.lock().unwrap()); - let reference_side = std::mem::take(&mut *reference_side.lock().unwrap()); - let paired_main = std::mem::take(&mut *paired_main.lock().unwrap()); - let reference_main = std::mem::take(&mut *reference_main.lock().unwrap()); assert_eq!( - paired_side, reference_side, - "sidechain-source post-matrix PCM" + paired_matrix_ramp.matrix_values_applied, + separate_matrix_ramp.matrix_values_applied ); - assert_eq!(paired_main, reference_main, "effect-main post-matrix PCM"); - assert_ne!( - declined_output, paired_main, - "the effect consumed its sidechain input" + + // Retargeting the matrix while its prior ramp is in flight must still run at M_A. The + // second block also proves that the split owner clears its pending flag on success. + for bound in [&mut paired, &mut separate] { + bound.test_only_write(|controls, stamp| { + controls[0].producer.write( + stamp, + TrackControlRecord { + matrix: matrix_b, + smoothing_samples: 3, + }, + ) + }); + } + let (paired_retarget, separate_retarget, _, _) = render_and_compare( + &mut paired, + &mut separate, + &paired_capture, + &separate_capture, + HARNESS_QUANTUM as u64, ); assert_eq!( - declined_state.scalar_fader_words, - reference_state.scalar_fader_words + (paired_retarget.fused_calls, paired_retarget.fallback_calls), + (0, 1) ); assert_eq!( - declined_state.scalar_matrix_words, - reference_state.scalar_matrix_words + paired_retarget.matrix_values_applied, + separate_retarget.matrix_values_applied ); - } - #[test] - fn staggered_observed_scalar_track_stays_separate_while_eligible_peer_pairs() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let eligible = Arc::new(std::sync::Mutex::new(Vec::new())); - let eligible_reference = Arc::new(std::sync::Mutex::new(Vec::new())); - let observed = Arc::new(std::sync::Mutex::new(Vec::new())); - let observed_reference = Arc::new(std::sync::Mutex::new(Vec::new())); + // A failed M_A render must complete the pending fader before returning and retry exactly + // once with the next write. A cell holds the newest value only, so the malformed target + // is the one the failed block reads, and the tail is written after it. + let (mut paired, mut separate, paired_capture, separate_capture) = make_pair(); + let invalid_matrix = TrackControlRecord { + matrix: Matrix2x2 { + ll: f32::NAN, + ..matrix_a + }, + smoothing_samples: 0, + }; + let matrix_tail = TrackControlRecord { + matrix: Matrix2x2::IDENTITY, + smoothing_samples: 0, + }; + for bound in [&mut paired, &mut separate] { + bound.test_only_write(|controls, stamp| { + controls[0].fader.write( + stamp, + TrackFaderRecord::FaderDb { + lanes: BuiltinLaneSelector::Both, + db: -6.0, + smoothing_samples: 0, + }, + ) + }); + bound.test_only_write(|controls, stamp| { + controls[0].producer.write(stamp, invalid_matrix) + }); + } + let selected_buffer = paired + .test_only_post_fader_buffer + .expect("paired fixture has t00 post-fader buffer"); test_only_reset_fader_matrix_witness(); - let mut paired = prepared_pair_graph_variant( - true, - false, - false, - true, - Some(Arc::clone(&eligible)), - Backend::Scalar, - 3, - BoundaryVariant::Plain, - 1, - Some((1, Arc::clone(&observed))), - ); - let prepared = test_only_fader_matrix_witness(); - let mut reference = prepared_pair_graph_variant( - true, - false, - false, - false, - Some(Arc::clone(&eligible_reference)), - Backend::Scalar, - 3, - BoundaryVariant::Plain, - 1, - Some((1, Arc::clone(&observed_reference))), - ); - // Issue #916: both unobserved peers pair, t02 and t00. t00 is eligible now that its - // post-matrix buffer is no longer the graph output. The metered t01 stays separate. - assert_eq!((prepared.factory_calls, prepared.factory_members), (2, 2)); + let (paired_error, paired_failed_capture) = + render_failed_buffer_result(&mut paired, 0, false, selected_buffer); + let paired_failed = test_only_fader_matrix_witness(); + let paired_failed_trace = test_only_scalar_state_trace(); test_only_reset_fader_matrix_witness(); - let paired_host = render_bound(&mut paired, 0); - let rendered = test_only_fader_matrix_witness(); - let reference_host = render_bound(&mut reference, 0); - assert_eq!((rendered.process_calls, rendered.process_members), (2, 2)); - assert_eq!( - paired_host, reference_host, - "the host planes, t00's paired post-matrix output, are the separate owners'" - ); + let (separate_error, separate_failed_capture) = + render_failed_buffer_result(&mut separate, 0, false, selected_buffer); + let separate_failed = test_only_fader_matrix_witness(); + let separate_failed_trace = test_only_scalar_state_trace(); + assert_eq!(paired_error, separate_error); + assert_eq!(paired_failed_capture, separate_failed_capture); assert_eq!( - std::mem::take(&mut *eligible.lock().unwrap()), - std::mem::take(&mut *eligible_reference.lock().unwrap()), - "the unobserved staggered peer executes the selected scalar pair" + ( + paired_failed.fader_values_applied, + paired_failed.matrix_values_applied + ), + (1, 1) ); - let observed = std::mem::take(&mut *observed.lock().unwrap()); assert_eq!( - observed, - std::mem::take(&mut *observed_reference.lock().unwrap()), - "the observed crossfeeding matrix track retains separate-owner PCM" - ); - assert!( - observed - .iter() - .any(|word| *word != 0 && *word != 0x8000_0000) + ( + paired_failed.fader_values_applied, + paired_failed.matrix_values_applied + ), + ( + separate_failed.fader_values_applied, + separate_failed.matrix_values_applied + ) ); - let frames = HARNESS_QUANTUM as usize; - assert_ne!(&observed[..frames], &observed[frames..]); - let observed_meter = paired.meter_consumers[0] - .consumer - .try_pop() - .expect("the observed separate track publishes a meter window"); - let reference_meter = reference.meter_consumers[0] - .consumer - .try_pop() - .expect("the reference track publishes a meter window"); - assert_eq!(observed_meter.handle, reference_meter.handle); assert_eq!( - observed_meter.reset_generation, - reference_meter.reset_generation + last_fader_state(paired_failed_trace, 0), + first_fader_state(separate_failed_trace, 0), + "failed M_A render completes t00 fader before returning" ); assert_eq!( - observed_meter.window_sequence, - reference_meter.window_sequence + last_matrix_state(paired_failed_trace, 0), + last_matrix_state(separate_failed_trace, 0), + "failed M_A render leaves the matrix state alone" ); - assert_eq!(observed_meter.start_sample, reference_meter.start_sample); - assert_eq!(observed_meter.end_sample, reference_meter.end_sample); - assert_eq!(observed_meter.frames, reference_meter.frames); - assert_eq!(observed_meter.left, reference_meter.left); - assert_eq!(observed_meter.right, reference_meter.right); - assert!(observed_meter.frames > 0); - assert!(observed_meter.left.energy > 0.0 || observed_meter.right.energy > 0.0); - assert!(observed_meter.left.sample_peak > 0.0 || observed_meter.right.sample_peak > 0.0); - } - #[test] - fn actual_scalar_nonunity_send_reader_declines_with_reference_output() { - let _guard = PAIR_WITNESS_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let declined_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - let reference_capture = Arc::new(std::sync::Mutex::new(Vec::new())); - test_only_reset_fader_matrix_witness(); - let mut declined = prepared_pair_graph_variant( - false, - false, - false, - true, - Some(Arc::clone(&declined_capture)), - Backend::Scalar, - 2, - BoundaryVariant::SelectedSend, - 1, - None, - ); - let prepared = test_only_fader_matrix_witness(); - let mut reference = prepared_pair_graph_variant( - false, - false, - false, - false, - Some(Arc::clone(&reference_capture)), - Backend::Scalar, - 2, - BoundaryVariant::SelectedSend, - 1, - None, + for bound in [&mut paired, &mut separate] { + bound.test_only_write(|controls, stamp| controls[0].producer.write(stamp, matrix_tail)); + } + let (paired_retry, separate_retry, _, _) = render_and_compare( + &mut paired, + &mut separate, + &paired_capture, + &separate_capture, + HARNESS_QUANTUM as u64, ); assert_eq!( - (prepared.factory_calls, prepared.factory_members), - (1, 1), - "the non-send peer pairs while the send-reading scalar track stays separate" - ); - test_only_reset_fader_matrix_witness(); - let declined_output = render_bound(&mut declined, 0); - let rendered = test_only_fader_matrix_witness(); - let reference_output = render_bound(&mut reference, 0); - assert_eq!((rendered.process_calls, rendered.process_members), (1, 1)); - assert_eq!(declined_output, reference_output); - assert!( - declined_output - .iter() - .any(|word| *word != 0 && *word != 0x8000_0000) + (paired_retry.fused_calls, paired_retry.fallback_calls), + (1, 0) ); + assert_eq!(paired_retry.matrix_values_applied, 1); assert_eq!( - std::mem::take(&mut *declined_capture.lock().unwrap()), - std::mem::take(&mut *reference_capture.lock().unwrap()) + ( + paired_retry.fader_values_applied, + paired_retry.matrix_values_applied + ), + ( + separate_retry.fader_values_applied, + separate_retry.matrix_values_applied + ), + "retry applies only the write after the error, once" ); } @@ -15735,718 +14224,90 @@ mod tests { gate.write_published(|stamp| { cells.write( stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -30.0, - smoothing_samples: 0, - }, - ); - }); - processor.begin_block(0, covers_forty).unwrap(); - assert_eq!(counter.get(), 78, "39 replaced writes in each channel cell"); - assert_eq!( - test_only_fader_matrix_witness().fader_values_applied, - 1, - "two channel cells with equal words are one `Both` apply" - ); - let after_forty = builtins::test_support::fader_bank_lane_words(&processor.bank, 0); - assert_ne!(after_forty, initial); - // The next block's snapshot reaches the 41st write, which replaced nothing. - processor.begin_block(0, gate.snapshot()).unwrap(); - assert_eq!(counter.get(), 78); - assert_ne!( - builtins::test_support::fader_bank_lane_words(&processor.bank, 0), - after_forty, - "the later write applied in the block whose snapshot covers it" - ); - // Nothing is pending now. - let settled = builtins::test_support::fader_bank_lane_words(&processor.bank, 0); - processor.begin_block(0, gate.snapshot()).unwrap(); - assert_eq!( - builtins::test_support::fader_bank_lane_words(&processor.bank, 0), - settled - ); - } - - /// The matrix counterpart of the fader drain test above (#1312 D2): the matrix/pan cell counts - /// the writes a later write replaced, in the same session counter, and applies only what its - /// block's snapshot covers. Thirty writes of the one matrix cell count `29` replaced writes. - #[test] - fn a_matrix_drain_counts_the_replaced_writes_and_applies_only_what_its_snapshot_covers() { - let counter = LiveValuesSuperseded::default(); - let (mut cells, reader) = matrix_lane(&counter); - let width = BankWidth::Four; - let mut processor = MatrixBankProcessor { - bank: BuiltinMatrixBank::new( - Backend::Simd4, - width, - vec![(Matrix2x2::IDENTITY, 0); width.lanes() as usize], - ) - .expect("bank"), - controls: core::iter::once(Some(reader)) - .chain((1..width.lanes()).map(|_| None)) - .collect(), - process_calls: 0, - frames_processed: 0, - control_delivery: BuiltinControlDelivery::BetweenRenderCalls, - }; - let record = |index: u32| TrackControlRecord { - matrix: Matrix2x2 { - ll: 1.0 - index as f32 * 0.01, - lr: 0.0, - rl: 0.0, - rr: 1.0, - }, - smoothing_samples: 0, - }; - let mut gate = TestOnlyGate::new(); - let initial = builtins::test_support::matrix_bank_lane_words(&processor.bank, 0); - for index in 0..30_u32 { - gate.write_published(|stamp| cells.write(stamp, record(index))); - } - // The block's snapshot covers the 30th write; the 29 before it were replaced. - let covers_thirty = gate.snapshot(); - // A 31st write is published after the snapshot: this block must not apply it. - gate.write_published(|stamp| cells.write(stamp, record(30))); - processor.begin_block(0, covers_thirty).unwrap(); - assert_eq!(counter.get(), 29, "29 replaced writes in the matrix cell"); - let after_thirty = builtins::test_support::matrix_bank_lane_words(&processor.bank, 0); - assert_ne!(after_thirty, initial); - // The next block's snapshot reaches the 31st write, which replaced nothing. - processor.begin_block(0, gate.snapshot()).unwrap(); - assert_eq!(counter.get(), 29); - assert_ne!( - builtins::test_support::matrix_bank_lane_words(&processor.bank, 0), - after_thirty, - "the later write applied in the block whose snapshot covers it" - ); - } - - type ScalarLiveControlOwners = ( - Box, - Box, - FaderCells, - MatrixCells, - ); - - /// One transaction into two owners' cells: the same fader record, published as one revision. - fn write_fader_both( - gate: &mut TestOnlyGate, - left: &mut FaderCells, - right: &mut FaderCells, - record: TrackFaderRecord, - ) { - gate.write_published(|stamp| { - left.write(stamp, record); - right.write(stamp, record); - }); - } - - /// The matrix counterpart of [`write_fader_both`]. - fn write_matrix_both( - gate: &mut TestOnlyGate, - left: &mut MatrixCells, - right: &mut MatrixCells, - record: TrackControlRecord, - ) { - gate.write_published(|stamp| { - left.write(stamp, record); - right.write(stamp, record); - }); - } - - fn scalar_live_control_owners( - fader_delivery: BuiltinControlDelivery, - matrix_delivery: BuiltinControlDelivery, - ) -> ScalarLiveControlOwners { - let parameters = BuiltinParameters { - left: builtins::ChannelParameters { - fader_db: -3.0, - ..Default::default() - }, - right: builtins::ChannelParameters { - fader_db: -7.0, - ..Default::default() - }, - matrix: Matrix2x2 { - ll: 0.75, - lr: 0.125, - rl: -0.25, - rr: 0.5, - }, - smoothing_samples: 0, - }; - let (_, _, matrix) = BuiltinChain::new(48_000, parameters) - .expect("scalar sections") - .into_sections(); - let counter = LiveValuesSuperseded::default(); - let (fader_tx, fader_rx) = fader_lane(&counter); - let (matrix_tx, matrix_rx) = matrix_lane(&counter); - ( - Box::new(LiveControlFaderProcessor { - fader: FaderMuteRampBuiltins::new(parameters).expect("fader"), - control: fader_rx, - control_delivery: fader_delivery, - automation: None, - #[cfg(any(test, feature = "test-support"))] - test_only_owner: u16::MAX, - }), - Box::new(LiveControlMatrixProcessor { - matrix, - control: matrix_rx, - control_delivery: matrix_delivery, - #[cfg(any(test, feature = "test-support"))] - test_only_owner: u16::MAX, - }), - fader_tx, - matrix_tx, - ) - } - - #[test] - fn scalar_factory_checks_both_exact_owners_and_policies_without_consuming_state() { - let mut gate = TestOnlyGate::new(); - struct AdvertisedWrongOwner { - calls: Arc, - } - impl GraphRuntimeProcessor for AdvertisedWrongOwner { - fn process(&mut self, _: GraphBindingBlock<'_>) -> Result<(), RenderError> { - self.calls.fetch_add(1, Ordering::Relaxed); - Ok(()) - } - fn scalar_pair_factory(&self) -> Option { - Some(make_scalar_pair) - } - } - - let calls = Arc::new(AtomicUsize::new(0)); - let (fader, matrix, _, _) = scalar_live_control_owners( - BuiltinControlDelivery::BetweenRenderCalls, - BuiltinControlDelivery::BetweenRenderCalls, - ); - let wrong_first = make_scalar_pair( - Box::new(AdvertisedWrongOwner { - calls: Arc::clone(&calls), - }), - matrix, - ) - .err() - .expect("wrong first owner declines"); - assert_eq!( - wrong_first.0.as_ref().type_id(), - core::any::TypeId::of::() - ); - assert_eq!( - wrong_first.1.as_ref().type_id(), - core::any::TypeId::of::() - ); - let wrong_second = make_scalar_pair( - fader, - Box::new(AdvertisedWrongOwner { - calls: Arc::clone(&calls), - }), - ) - .err() - .expect("wrong second owner declines"); - assert_eq!( - wrong_second.0.as_ref().type_id(), - core::any::TypeId::of::() - ); - assert_eq!( - wrong_second.1.as_ref().type_id(), - core::any::TypeId::of::() - ); - assert_eq!( - calls.load(Ordering::Relaxed), - 0, - "factory never invokes owners" - ); - - for (fader_policy, matrix_policy) in [ - ( - BuiltinControlDelivery::Concurrent, - BuiltinControlDelivery::BetweenRenderCalls, - ), - ( - BuiltinControlDelivery::BetweenRenderCalls, - BuiltinControlDelivery::Concurrent, - ), - ] { - let (fader, matrix, mut fader_tx, mut matrix_tx) = - scalar_live_control_owners(fader_policy, matrix_policy); - gate.write_published(|stamp| { - fader_tx.write( - stamp, - TrackFaderRecord::Mute { - lanes: BuiltinLaneSelector::Left, - muted: true, - smoothing_samples: 0, - }, - ) - }); - gate.write_published(|stamp| { - matrix_tx.write( - stamp, - TrackControlRecord { - matrix: Matrix2x2::IDENTITY, - smoothing_samples: 0, - }, - ) - }); - let returned = make_scalar_pair(fader, matrix) - .err() - .expect("either concurrent owner declines"); - let mut fader = (returned.0 as Box) - .downcast::() - .unwrap(); - let mut matrix = (returned.1 as Box) - .downcast::() - .unwrap(); - let mut left = [1.0_f32, 0.5]; - let mut right = [-0.25_f32, -0.75]; - fader - .process(GraphBindingBlock { - left: &mut left, - right: &mut right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - matrix - .process(GraphBindingBlock { - left: &mut left, - right: &mut right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - assert_eq!(left, [0.0, 0.0], "queued fader state stayed with owner"); - } - } - - #[test] - fn scalar_split_owner_defers_settled_fader_and_completes_before_matrix_error() { - let mut gate = TestOnlyGate::new(); - let make = || { - scalar_live_control_owners( - BuiltinControlDelivery::BetweenRenderCalls, - BuiltinControlDelivery::BetweenRenderCalls, - ) - }; - let (fader, matrix, _paired_fader, mut paired_matrix) = make(); - let factory = fader - .scalar_split_pair_factory() - .expect("exact live fader advertises split owner"); - let mut split = - factory(fader, matrix).unwrap_or_else(|_| panic!("split owner accepts exact owners")); - let (separate_fader, separate_matrix, _separate_fader_tx, mut separate_matrix_tx) = make(); - - let invalid = TrackControlRecord { - matrix: Matrix2x2 { - ll: f32::NAN, - ..Matrix2x2::IDENTITY - }, - smoothing_samples: 0, - }; - let tail = TrackControlRecord { - matrix: Matrix2x2::IDENTITY, - smoothing_samples: 0, - }; - // A cell holds only the newest value: the invalid target is the one render reads, and the - // value written after the failed block is the one the retry reads. - gate.write_published(|stamp| paired_matrix.write(stamp, invalid)); - gate.write_published(|stamp| separate_matrix_tx.write(stamp, invalid)); - let mut paired_left = [0.5_f32, 0.25]; - let mut paired_right = [-0.25_f32, -0.125]; - let mut separate_left = paired_left; - let mut separate_right = paired_right; - split - .begin_fader(GraphBindingBlock { - left: &mut paired_left, - right: &mut paired_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - assert_eq!(paired_left, [0.5, 0.25], "settled fader remains deferred"); - assert_eq!(paired_right, [-0.25, -0.125]); - let mut separate_fader = (separate_fader as Box) - .downcast::() - .unwrap(); - let mut separate_matrix = (separate_matrix as Box) - .downcast::() - .unwrap(); - separate_fader - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - let error = split - .finish_matrix(GraphBindingBlock { - left: &mut paired_left, - right: &mut paired_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap_err(); - assert_eq!(error, RenderError::InvalidEnvelope); - assert_eq!( - paired_left.map(f32::to_bits), - separate_left.map(f32::to_bits) - ); - assert_eq!( - paired_right.map(f32::to_bits), - separate_right.map(f32::to_bits) - ); - separate_matrix - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap_err(); - - gate.write_published(|stamp| paired_matrix.write(stamp, tail)); - gate.write_published(|stamp| separate_matrix_tx.write(stamp, tail)); - let mut paired_left = [0.75_f32, 0.5]; - let mut paired_right = [-0.5_f32, -0.25]; - let mut separate_left = paired_left; - let mut separate_right = paired_right; - split - .begin_fader(GraphBindingBlock { - left: &mut paired_left, - right: &mut paired_right, - first_sample: 64, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - split - .finish_matrix(GraphBindingBlock { - left: &mut paired_left, - right: &mut paired_right, - first_sample: 64, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - separate_fader - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 64, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - separate_matrix - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 64, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - assert_eq!( - paired_left.map(f32::to_bits), - separate_left.map(f32::to_bits) - ); - assert_eq!( - paired_right.map(f32::to_bits), - separate_right.map(f32::to_bits) - ); - } - - #[test] - fn scalar_split_invalid_begin_envelope_does_not_arm_pending_or_drain_matrix() { - let mut gate = TestOnlyGate::new(); - let (fader, matrix, mut fader_tx, mut matrix_tx) = scalar_live_control_owners( - BuiltinControlDelivery::BetweenRenderCalls, - BuiltinControlDelivery::BetweenRenderCalls, - ); - let factory = fader - .scalar_split_pair_factory() - .expect("exact live fader advertises split owner"); - let mut split = - factory(fader, matrix).unwrap_or_else(|_| panic!("split owner accepts exact owners")); - gate.write_published(|stamp| { - fader_tx.write( - stamp, - TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -9.0, - smoothing_samples: 0, - }, - ) - }); - gate.write_published(|stamp| { - matrix_tx.write( - stamp, - TrackControlRecord { - matrix: Matrix2x2 { - ll: 0.5, - lr: 0.25, - rl: -0.75, - rr: 0.125, - }, + TrackFaderRecord::FaderDb { + lanes: BuiltinLaneSelector::Both, + db: -30.0, smoothing_samples: 0, }, - ) + ); }); - - let mut short_left = [0.5_f32]; - let mut long_right = [-0.25_f32, -0.5]; - test_only_reset_fader_matrix_witness(); + processor.begin_block(0, covers_forty).unwrap(); + assert_eq!(counter.get(), 78, "39 replaced writes in each channel cell"); assert_eq!( - split.begin_fader(GraphBindingBlock { - left: &mut short_left, - right: &mut long_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }), - Err(RenderError::InvalidEnvelope) + test_only_fader_matrix_witness().fader_values_applied, + 1, + "two channel cells with equal words are one `Both` apply" ); - assert_eq!(short_left, [0.5]); - assert_eq!(long_right, [-0.25, -0.5]); - let failed = test_only_fader_matrix_witness(); - assert_eq!(failed.fader_values_applied, 1); - assert_eq!( - failed.matrix_values_applied, 0, - "the direct invalid envelope must not drain the later matrix owner" + let after_forty = builtins::test_support::fader_bank_lane_words(&processor.bank, 0); + assert_ne!(after_forty, initial); + // The next block's snapshot reaches the 41st write, which replaced nothing. + processor.begin_block(0, gate.snapshot()).unwrap(); + assert_eq!(counter.get(), 78); + assert_ne!( + builtins::test_support::fader_bank_lane_words(&processor.bank, 0), + after_forty, + "the later write applied in the block whose snapshot covers it" ); - - // A completion call after the failed begin is the mutation-sensitive part of this seam: - // if pending were armed before validation, it would alter this valid buffer. - let mut left = [0.5_f32, 0.25]; - let mut right = [-0.25_f32, -0.5]; - let before = (left, right); - split.complete_pending(GraphBindingBlock { - left: &mut left, - right: &mut right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }); + // Nothing is pending now. + let settled = builtins::test_support::fader_bank_lane_words(&processor.bank, 0); + processor.begin_block(0, gate.snapshot()).unwrap(); assert_eq!( - (left, right), - before, - "invalid begin left no pending completion" + builtins::test_support::fader_bank_lane_words(&processor.bank, 0), + settled ); } + /// The matrix counterpart of the fader drain test above (#1312 D2): the matrix/pan cell counts + /// the writes a later write replaced, in the same session counter, and applies only what its + /// block's snapshot covers. Thirty writes of the one matrix cell count `29` replaced writes. #[test] - fn scalar_invalid_envelope_leaves_the_later_matrix_queue_untouched() { - let mut gate = TestOnlyGate::new(); - let make = || { - let (fader, matrix, fader_tx, matrix_tx) = scalar_live_control_owners( - BuiltinControlDelivery::BetweenRenderCalls, - BuiltinControlDelivery::BetweenRenderCalls, - ); - let pair = match make_scalar_pair(fader, matrix) { - Ok(pair) => pair, - Err(_) => panic!("serialized scalar pair"), - }; - (pair, fader_tx, matrix_tx) - }; - let (mut interrupted, mut interrupted_fader, mut interrupted_matrix) = make(); - let (mut reference, mut reference_fader, mut reference_matrix) = make(); - let fader_record = TrackFaderRecord::FaderDb { - lanes: BuiltinLaneSelector::Both, - db: -12.0, - smoothing_samples: 0, + fn a_matrix_drain_counts_the_replaced_writes_and_applies_only_what_its_snapshot_covers() { + let counter = LiveValuesSuperseded::default(); + let (mut cells, reader) = matrix_lane(&counter); + let width = BankWidth::Four; + let mut processor = MatrixBankProcessor { + bank: BuiltinMatrixBank::new( + Backend::Simd4, + width, + vec![(Matrix2x2::IDENTITY, 0); width.lanes() as usize], + ) + .expect("bank"), + controls: core::iter::once(Some(reader)) + .chain((1..width.lanes()).map(|_| None)) + .collect(), + process_calls: 0, + frames_processed: 0, + control_delivery: BuiltinControlDelivery::BetweenRenderCalls, }; - let matrix_record = TrackControlRecord { + let record = |index: u32| TrackControlRecord { matrix: Matrix2x2 { - ll: 0.25, - lr: 0.5, - rl: -0.75, + ll: 1.0 - index as f32 * 0.01, + lr: 0.0, + rl: 0.0, rr: 1.0, }, smoothing_samples: 0, }; - gate.write_published(|stamp| interrupted_fader.write(stamp, fader_record)); - gate.write_published(|stamp| interrupted_matrix.write(stamp, matrix_record)); - gate.write_published(|stamp| reference_fader.write(stamp, fader_record)); - gate.write_published(|stamp| reference_matrix.write(stamp, matrix_record)); - - test_only_reset_fader_matrix_witness(); - let mut short_left = [0.5_f32]; - let mut long_right = [-0.25_f32, -0.5]; - assert_eq!( - interrupted.process(GraphBindingBlock { - left: &mut short_left, - right: &mut long_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }), - Err(RenderError::InvalidEnvelope) - ); - assert_eq!((short_left, long_right), ([0.5], [-0.25, -0.5])); - let failed = test_only_fader_matrix_witness(); - assert_eq!(failed.fader_values_applied, 1); - assert_eq!( - failed.matrix_values_applied, 0, - "invalid envelope returns before the later matrix queue is touched" - ); - - let mut interrupted_left = [0.5_f32, 0.25]; - let mut interrupted_right = [-0.25_f32, -0.5]; - let mut reference_left = interrupted_left; - let mut reference_right = interrupted_right; - interrupted - .process(GraphBindingBlock { - left: &mut interrupted_left, - right: &mut interrupted_right, - first_sample: 2, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - reference - .process(GraphBindingBlock { - left: &mut reference_left, - right: &mut reference_right, - first_sample: 2, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - assert_eq!( - interrupted_left.map(f32::to_bits), - reference_left.map(f32::to_bits) - ); - assert_eq!( - interrupted_right.map(f32::to_bits), - reference_right.map(f32::to_bits) - ); - assert_ne!( - interrupted_left, - [0.5, 0.25], - "queued matrix command applied on retry" - ); - } - - #[test] - fn scalar_pair_reset_matches_reset_original_owners() { - let make = || { - scalar_live_control_owners( - BuiltinControlDelivery::BetweenRenderCalls, - BuiltinControlDelivery::BetweenRenderCalls, - ) - }; - let (fader, matrix, mut paired_fader_tx, mut paired_matrix_tx) = make(); - let paired = - make_scalar_pair(fader, matrix).unwrap_or_else(|_| panic!("eligible scalar pair")); - let paired: Box = paired; - let mut paired = paired.downcast::().unwrap(); - let (fader, matrix, mut separate_fader_tx, mut separate_matrix_tx) = make(); - let fader: Box = fader; - let matrix: Box = matrix; - let mut separate_fader = fader.downcast::().unwrap(); - let mut separate_matrix = matrix.downcast::().unwrap(); - let fader_record = TrackFaderRecord::Mute { - lanes: BuiltinLaneSelector::Both, - muted: true, - smoothing_samples: 7, - }; - let matrix_record = TrackControlRecord { - matrix: Matrix2x2 { - ll: 0.5, - lr: 0.25, - rl: -0.25, - rr: 0.75, - }, - smoothing_samples: 7, - }; let mut gate = TestOnlyGate::new(); - write_fader_both( - &mut gate, - &mut paired_fader_tx, - &mut separate_fader_tx, - fader_record, - ); - write_matrix_both( - &mut gate, - &mut paired_matrix_tx, - &mut separate_matrix_tx, - matrix_record, - ); - let mut paired_left = [0.5_f32; 3]; - let mut paired_right = [-0.25_f32; 3]; - paired - .process(GraphBindingBlock { - left: &mut paired_left, - right: &mut paired_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - let mut separate_left = [0.5_f32; 3]; - let mut separate_right = [-0.25_f32; 3]; - separate_fader - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - separate_matrix - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 0, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - - paired.fader.fader.reset(); - paired.matrix.matrix.reset(); - separate_fader.fader.reset(); - separate_matrix.matrix.reset(); - let expected_fader = builtins::test_support::scalar_fader_words(&separate_fader.fader); - let expected_matrix = builtins::test_support::scalar_matrix_words(&separate_matrix.matrix); - assert_eq!( - builtins::test_support::scalar_fader_words(&paired.fader.fader), - expected_fader - ); - assert_eq!( - builtins::test_support::scalar_matrix_words(&paired.matrix.matrix), - expected_matrix - ); - let mut paired_left = [0.75_f32; 4]; - let mut paired_right = [-0.5_f32; 4]; - let mut separate_left = paired_left; - let mut separate_right = paired_right; - paired - .process(GraphBindingBlock { - left: &mut paired_left, - right: &mut paired_right, - first_sample: 3, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - separate_fader - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 3, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - separate_matrix - .process(GraphBindingBlock { - left: &mut separate_left, - right: &mut separate_right, - first_sample: 3, - live_snapshot: LiveSnapshot::ALL, - }) - .unwrap(); - assert_eq!( - paired_left.map(f32::to_bits), - separate_left.map(f32::to_bits) - ); - assert_eq!( - paired_right.map(f32::to_bits), - separate_right.map(f32::to_bits) + let initial = builtins::test_support::matrix_bank_lane_words(&processor.bank, 0); + for index in 0..30_u32 { + gate.write_published(|stamp| cells.write(stamp, record(index))); + } + // The block's snapshot covers the 30th write; the 29 before it were replaced. + let covers_thirty = gate.snapshot(); + // A 31st write is published after the snapshot: this block must not apply it. + gate.write_published(|stamp| cells.write(stamp, record(30))); + processor.begin_block(0, covers_thirty).unwrap(); + assert_eq!(counter.get(), 29, "29 replaced writes in the matrix cell"); + let after_thirty = builtins::test_support::matrix_bank_lane_words(&processor.bank, 0); + assert_ne!(after_thirty, initial); + // The next block's snapshot reaches the 31st write, which replaced nothing. + processor.begin_block(0, gate.snapshot()).unwrap(); + assert_eq!(counter.get(), 29); + assert_ne!( + builtins::test_support::matrix_bank_lane_words(&processor.bank, 0), + after_thirty, + "the later write applied in the block whose snapshot covers it" ); } @@ -16968,195 +14829,6 @@ mod tests { ); } - #[test] - fn scalar_owner_resource_is_independent_two_boxes_plus_two_pointer_outer() { - let compiled = n_track_session(3); - let controls = (0..3) - .map(|index| TrackControlRequest { - track_id: track_name(index), - superseded: LiveValuesSuperseded::default(), - input_lane: true, - }) - .collect::>(); - let (_, levels) = track_graph(3); - let classes = SessionPoolClasses::from_session(&compiled); - let fader = core::mem::size_of::() as u64; - let matrix = core::mem::size_of::() as u64; - let pair_outer = core::mem::size_of::() as u64; - let split_outer = core::mem::size_of::() as u64; - let outer = pair_outer.max(split_outer); - assert!( - split_outer > pair_outer, - "the split owner's pending flag is included in the bounded outer reservation" - ); - assert_eq!( - outer, split_outer, - "the charged outer covers the larger split owner" - ); - let fader_fields = core::mem::size_of::() - + core::mem::size_of::() - + core::mem::size_of::() - + core::mem::size_of::>>(); - let matrix_fields = core::mem::size_of::() - + core::mem::size_of::() - + core::mem::size_of::(); - assert!(core::mem::size_of::() >= fader_fields); - assert!(core::mem::size_of::() >= matrix_fields); - assert!( - core::mem::size_of::() - fader_fields - < core::mem::align_of::() - ); - assert!( - core::mem::size_of::() - matrix_fields - < core::mem::align_of::() - ); - - let serialized = prepare_session_builtins_between_render_calls( - &compiled, - &effective_of(&compiled), - &[], - &controls, - caps(), - ) - .expect("serialized owners") - .graph_scalar_owner_resource(Backend::Scalar, &levels, &classes) - .expect("checked scalar estimate"); - let split_table_entry = - core::mem::size_of::>() as u64; - assert_eq!(serialized.split_pair_table_bytes, split_table_entry); - assert_eq!( - serialized.total_bytes, - 3 * (fader + matrix + outer) + split_table_entry - ); - assert_eq!( - serialized.largest_allocation_bytes, - fader.max(matrix).max(outer).max(split_table_entry) - ); - - let concurrent = prepare_session_builtins_with_live_controls( - &compiled, - &effective_of(&compiled), - &[], - &controls, - caps(), - ) - .expect("concurrent owners") - .graph_scalar_owner_resource(Backend::Scalar, &levels, &classes) - .expect("checked scalar estimate"); - assert_eq!(concurrent.total_bytes, 3 * (fader + matrix)); - assert_eq!(concurrent.largest_allocation_bytes, fader.max(matrix)); - - let no_live_controls = prepare_session_builtins(&compiled, &[], caps()) - .expect("no-live-control owners") - .graph_scalar_owner_resource(Backend::Scalar, &levels, &classes) - .expect("checked scalar estimate"); - assert_eq!( - no_live_controls, - graph::GraphScalarOwnerResourceEstimate::default() - ); - - let bank_selected = prepare_session_builtins_between_render_calls( - &compiled, - &effective_of(&compiled), - &[], - &controls, - caps(), - ) - .expect("bank candidates") - .graph_scalar_owner_resource(Backend::current(), &levels, &classes) - .expect("checked vector estimate"); - assert_eq!( - bank_selected, - graph::GraphScalarOwnerResourceEstimate::default(), - "planned banks consume both controlled post-fader and post-matrix owners" - ); - } - - /// #1646 D4 (R17-4): the scalar oracle's owner row charges one automated fader lane exactly: - /// the boxed `LaneFaderAutomation` its strip's fader owner holds, one offsets cell, and the - /// program once (its shared allocation and the builder's byte report). - /// - /// Test value: red if the scalar owner row drops or double-counts any term of the stored - /// fader automation (the boxed slot, the offsets cell or the program), which no vector-bank - /// test sees because a vector dispatch leaves no scalar owner. - #[test] - fn the_scalar_owner_row_charges_one_automated_fader_lane() { - let plain = n_track_session(3); - let mut model = plain.normalized_model().clone(); - let entry = session::Automation { - id: session::StableId::parse("fader-t01").expect("id"), - target: session::ControlAddress { - owner: session::Owner::Strip( - session::StableId::parse(&track_name(1)).expect("track"), - ), - section: session::Section::Builtins, - control: 5, - lane: session::ParameterChannel::Left, - }, - segments: vec![session::AutomationSegment { - shape: session::AutomationShape::Linear, - start_sample: 0, - end_sample: 4_096, - start_value: -6.5, - end_value: -2.0, - unit: session::ParameterUnit::Db, - }], - offset: session::AutomationOffset::default(), - }; - let segments = controls::stored_segments(&entry); - model.automation = vec![entry]; - let automated = compile_session( - &model, - CompileCaps { - max_compiled_model_bytes: u64::MAX, - max_requested_runtime_bytes: u64::MAX, - max_single_allocation_bytes: u64::MAX, - max_queue_items: u64::MAX, - max_source_ring_frames: u64::MAX, - max_source_ring_bytes: u64::MAX, - }, - ) - .expect("the automated session compiles"); - let controls = (0..3) - .map(|index| TrackControlRequest { - track_id: track_name(index), - superseded: LiveValuesSuperseded::default(), - input_lane: false, - }) - .collect::>(); - let (_, levels) = track_graph(3); - let scalar = |compiled: &CompiledSession| { - let classes = SessionPoolClasses::from_session(compiled); - prepare_session_builtins_between_render_calls( - compiled, - &effective_of(compiled), - &[], - &controls, - caps(), - ) - .expect("scalar owners") - .graph_scalar_owner_resource(Backend::Scalar, &levels, &classes) - .expect("checked scalar estimate") - }; - let without = scalar(&plain); - let with = scalar(&automated); - let sizes = fader_automation_sizes(); - let program = - automation::ProgramBuilder::bytes(&[controls::FADER_CELL_RULE.describe(&segments)]); - assert!(program > 0, "the program holds the segment"); - let slot = core::mem::size_of::() as u64; - assert_eq!( - with.total_bytes - without.total_bytes, - slot + sizes.offsets_cell_bytes + sizes.program_overhead_bytes + program, - "one boxed slot, one offsets cell and the program, once" - ); - assert_eq!( - with.split_pair_table_bytes, without.split_pair_table_bytes, - "the automation adds no split table entry" - ); - assert!(with.largest_allocation_bytes >= slot.max(sizes.offsets_cell_bytes).max(program)); - } - /// #1651 D1: the mute cells follow every fader cell in the plan's one program, so the fader /// cells keep their indices and tables, and [`PreparedBuiltinsSession::strip_mute_cells`] /// (the export #1652's route lanes read) names each mute lane's cell in that same program; a diff --git a/crates/capi/src/runtime/live_tests.rs b/crates/capi/src/runtime/live_tests.rs index 58384083c..72419c66c 100644 --- a/crates/capi/src/runtime/live_tests.rs +++ b/crates/capi/src/runtime/live_tests.rs @@ -10624,50 +10624,6 @@ fn the_cost_of_a_live_only_command_does_not_grow_with_the_session() { ); } -/// The timing line of the whole command, before (the full preparation forced) and after (in place), -/// for a fader edit at 64 and 256 tracks: `cargo test --release -p capi -- --ignored --nocapture -/// timing_line`. -#[test] -#[ignore = "descriptive timing"] -fn in_place_command_timing_line() { - for tracks in [64_usize, 256] { - let mut rig = Rig::with_limits(&grown_session(tracks), grown_limits()); - rig.step(); - let fader = |db: f32| { - vec![SessionEdit::set_control( - strip_row( - "g0007", - protocol::control_core::Section::Builtins, - 5, - protocol::control_core::Lane::Both, - ), - db, - None, - )] - }; - for (label, force) in [("full", true), ("in place", false)] { - control_plane::test_force_full_preparation(force); - let calls = 200_u32; - let mut spent = std::time::Duration::ZERO; - for call in 0..calls + 1 { - let bytes = rig.transaction(&fader(if call % 2 == 0 { -6.0 } else { -3.0 })); - let start = std::time::Instant::now(); - let (result, _) = command_c(rig.session, &bytes); - if call > 0 { - spent += start.elapsed(); - } - assert_eq!(result, crate::RESULT_OK); - rig.drain_reliable(); - } - println!( - "fader edit, {tracks} tracks, {label}: {:?} per command", - spent / calls - ); - } - control_plane::test_force_full_preparation(false); - } -} - /// `document` with one stored fader ride on its first track (builtin 5, both lanes). fn with_fader_automation(document: &str) -> String { with_model(document, |model| { diff --git a/crates/effect-compiler/src/prepare.rs b/crates/effect-compiler/src/prepare.rs index 1a9c9e75e..e0befd3c0 100644 --- a/crates/effect-compiler/src/prepare.rs +++ b/crates/effect-compiler/src/prepare.rs @@ -1143,13 +1143,10 @@ mod live_address_tests { mod owner_tests { use super::*; use crate::EffectControlOwnerPhase; - use core::alloc::Layout; - use core::mem::size_of; - use core::sync::atomic::AtomicUsize; use effect_contract::{ NativeEffectFactory, ParameterChannel, PreparedEffectTarget, default_initial_values, }; - use engine::realtime::{CellWriter, GatePin, RevisionGate}; + use engine::realtime::{GatePin, RevisionGate}; use parametric_eq::{PARAMETRIC_EQ_DESCRIPTOR, ParametricEqFactory}; use std::sync::Arc; @@ -1330,122 +1327,67 @@ mod owner_tests { assert_eq!(rig.owner.committed(), &edited[..]); } + /// Red if two producers that share one factory are charged that factory twice (the charge is + /// then no smaller than for two producers with a factory each), or if the producer table is + /// charged by its length and not by the capacity its vector holds. #[test] fn effect_control_resources_charge_actual_tables_and_shared_owner_once() { - let factory: Arc = Arc::new(ParametricEqFactory); - let mut opted = Vec::with_capacity(5); - opted.push(resource_producer( - "track-a", - "effect-a", - Some(Arc::clone(&factory)), - )); - opted.push(resource_producer( - "track-b", - "effect-b", - Some(Arc::clone(&factory)), - )); - let resources = effect_control_resources(&opted).expect("resource facts"); - let values = preparation().initial_values.len(); - let owner_box = size_of::() as u64; - let row_backing = (values * size_of::()) as u64; - let owner_payload = owner_box + row_backing; - // The EQ lane's writer holds one cell writer per `(slot, channel)` of its target cells and - // none for parameters: its parameters ride its targets. - let writer_backing = (2 - * EQ_SLOTS - * size_of::>()) - as u64; - let factory_layout = Layout::new::() - .extend(Layout::new::()) - .expect("Arc header layout") - .0 - .extend(Layout::for_value(factory.as_ref())) - .expect("factory layout") - .0 - .pad_to_align() - .size() as u64; - let expected_payload = "track-a".len() as u64 - + "effect-a".len() as u64 - + "track-b".len() as u64 - + "effect-b".len() as u64 - + (2 * (owner_payload + writer_backing)) - + factory_layout; - let expected_table = 5 * size_of::() as u64; - let expected_largest = [ - "track-a".len() as u64, - "effect-a".len() as u64, - "track-b".len() as u64, - "effect-b".len() as u64, - owner_box, - row_backing, - writer_backing, - factory_layout, - ] - .into_iter() - .max() - .expect("nonempty resource rows"); - assert_eq!(resources.producer_table_bytes, expected_table); - assert_eq!(resources.owned_payload_bytes, expected_payload); - assert_eq!(resources.largest_owned_allocation_bytes, expected_largest); - assert_eq!( - resources.total_bytes(), - Some(expected_table + expected_payload) - ); - assert_eq!( - resources.largest_allocation_bytes(), - expected_table.max(expected_largest) - ); - - // A producer with no owner holds a writer for each parameter cell: the EQ's 22 `Block` - // parameters are all `PerLane`, so 44. - let mut production = Vec::with_capacity(3); - production.push(resource_producer("track", "effect", None)); - let production_resources = - effect_control_resources(&production).expect("production resource facts"); - let parameter_writers = (44 * size_of::>()) as u64; - assert_eq!( - production_resources.producer_table_bytes, - 3 * size_of::() as u64 + let shared: Arc = Arc::new(ParametricEqFactory); + let charge = |factories: [Arc; 2], capacity: usize| { + let mut producers = Vec::with_capacity(capacity); + for ((track, effect), factory) in [("track-a", "effect-a"), ("track-b", "effect-b")] + .into_iter() + .zip(factories) + { + producers.push(resource_producer(track, effect, Some(factory))); + } + effect_control_resources(&producers).expect("resource facts") + }; + let once = charge([Arc::clone(&shared), Arc::clone(&shared)], 2); + let twice = charge( + [Arc::new(ParametricEqFactory), Arc::new(ParametricEqFactory)], + 2, ); - assert_eq!( - production_resources.owned_payload_bytes, - "track".len() as u64 + "effect".len() as u64 + parameter_writers + assert!( + once.owned_payload_bytes < twice.owned_payload_bytes, + "a shared factory is charged once: {} against {}", + once.owned_payload_bytes, + twice.owned_payload_bytes ); + let spare = charge([Arc::clone(&shared), Arc::clone(&shared)], 5); + assert!(once.producer_table_bytes > 0); assert_eq!( - production_resources.largest_owned_allocation_bytes, - parameter_writers.max("effect".len() as u64) + 2 * spare.producer_table_bytes, + 5 * once.producer_table_bytes, + "the table is charged by its capacity" ); + assert_eq!(spare.owned_payload_bytes, once.owned_payload_bytes); } /// #1469 Amendment 1. Red if a plan is charged for a factory the process-lifetime launch - /// registry owns: the two owners share the registry's EQ factory, so the charge is their - /// strings, owner payloads and writers alone. + /// registry owns: two owners that share the registry's EQ factory are charged less than two + /// owners that share a factory the plan owns. #[test] fn effect_control_resources_charge_no_registry_owned_factory() { - let factory = launch_native_effect_registry() + let registry_factory = launch_native_effect_registry() .expect("launch registry") .get_shared_ascii("miso.parametric-eq") .expect("launch EQ"); - assert!(launch_registry_owns_factory(&factory)); - assert!(!launch_registry_owns_factory( - &(Arc::new(ParametricEqFactory) as Arc) - )); - let mut producers = Vec::with_capacity(2); - for (track, effect) in [("track-a", "effect-a"), ("track-b", "effect-b")] { - producers.push(resource_producer(track, effect, Some(Arc::clone(&factory)))); - } - let values = preparation().initial_values.len(); - let owner_payload = size_of::() as u64 - + (values * size_of::()) as u64 - + (2 * EQ_SLOTS - * size_of::>()) - as u64; - let strings = ["track-a", "effect-a", "track-b", "effect-b"] - .map(|text| text.len() as u64) - .into_iter() - .sum::(); - let resources = effect_control_resources(&producers).expect("resource facts"); - assert_eq!(resources.owned_payload_bytes, strings + 2 * owner_payload); + let plan_factory: Arc = Arc::new(ParametricEqFactory); + assert!(launch_registry_owns_factory(®istry_factory)); + assert!(!launch_registry_owns_factory(&plan_factory)); + let charge = |factory: &Arc| { + let mut producers = Vec::with_capacity(2); + for (track, effect) in [("track-a", "effect-a"), ("track-b", "effect-b")] { + producers.push(resource_producer(track, effect, Some(Arc::clone(factory)))); + } + effect_control_resources(&producers).expect("resource facts") + }; + assert!( + charge(®istry_factory).owned_payload_bytes + < charge(&plan_factory).owned_payload_bytes, + "the registry's factory is not charged to the plan" + ); } #[test] diff --git a/crates/graph-compiler/src/lib.rs b/crates/graph-compiler/src/lib.rs index 705929f01..b0fa600cf 100644 --- a/crates/graph-compiler/src/lib.rs +++ b/crates/graph-compiler/src/lib.rs @@ -423,8 +423,8 @@ mod tests { prepare_native_session_effects_with_console_eligibility, }; use effect_contract::{ - EffectControlLane, EffectControlRecord, EffectPrepareError, EffectProcessBlock, - EffectTargetRequest, InitialParameterValue, NativeEffectFactory, NativeEffectRegistry, + EffectControlRecord, EffectPrepareError, EffectProcessBlock, EffectTargetRequest, + InitialParameterValue, NativeEffectFactory, NativeEffectRegistry, NativeEffectTargetPreparation, ParameterChannel as ContractParameterChannel, PrepareEffectBankRequest, PrepareEffectRequest, PreparedEffectTarget, PreparedNativeEffect, PreparedNativeEffectBank, ProcessReport, StatePayloadOutput, @@ -2409,17 +2409,13 @@ mod tests { levels: &[DependencyLevel], canonical: &[u8], expected_schedule: &[&str], - expected_sha256: &str, ) -> Result<(), &'static str> { dependency_level_contract(graph, schedule, levels)?; if schedule.iter().map(node_text).collect::>() != expected_schedule { return Err("schedule identity"); } let rebuilt = canonical_with_levels(graph, report, schedule, levels); - if rebuilt != canonical - || hex_sha256(&rebuilt) != hex_sha256(canonical) - || hex_sha256(canonical) != expected_sha256 - { + if rebuilt != canonical || hex_sha256(&rebuilt) != hex_sha256(canonical) { return Err("canonical identity"); } Ok(()) @@ -2534,11 +2530,6 @@ mod tests { "route:z-downstream", "output:main-out", ]; - // #241 re-pin: this graph identity commits the canonical session source shape; its - // schedule, dependency levels, and rendered PCM remain independently fixed below. - // #1200 re-pin: a submix lowers to a strip, so the `submix:` nodes became - // `track::` chains; the rendered PCM below did not move (both submixes are - // transparent and the impulse is finite and never `-0.0`). reverse_fixture_identity_contract( baseline.graph(), baseline.report(), @@ -2546,7 +2537,6 @@ mod tests { &baseline.graph().dependency_levels, &GraphCompiler::evidence(baseline.graph(), baseline.report()).canonical_bytes, &expected_schedule, - "f5e310c7c527680261b4dfc37aa003e3ea0b6abd6d8e399ae58b6c3cdbc6ac5e", ) .expect("sorted production identity"); let level_transcript: Vec<_> = baseline @@ -2688,10 +2678,6 @@ mod tests { &baseline.graph().dependency_levels, ); assert_ne!(legacy_canonical, baseline_canonical); - assert_eq!( - GraphCompiler::sha256(baseline.graph(), baseline.report()), - "f5e310c7c527680261b4dfc37aa003e3ea0b6abd6d8e399ae58b6c3cdbc6ac5e" - ); let mut reversed = baseline.graph().dependency_levels.clone(); reversed[15].nodes.reverse(); assert_eq!( @@ -2731,7 +2717,6 @@ mod tests { &baseline.graph().dependency_levels, &baseline_canonical, &expected_schedule, - "f5e310c7c527680261b4dfc37aa003e3ea0b6abd6d8e399ae58b6c3cdbc6ac5e", ), Err("schedule level order") ); @@ -2745,7 +2730,6 @@ mod tests { &baseline.graph().dependency_levels, &canonical_corruption, &expected_schedule, - "f5e310c7c527680261b4dfc37aa003e3ea0b6abd6d8e399ae58b6c3cdbc6ac5e", ), Err("canonical identity") ); @@ -3114,11 +3098,9 @@ mod tests { #[test] fn effect_control_resource_uses_independent_queue_and_owner_arithmetic() { - // Keep the expected bytes in this test independent of `effect_control_resource`: a lane's - // heap is its own accounting, which `tests/bypass_resources.rs` holds to the audited - // allocator (`a_lanes_own_accounting_is_what_the_allocator_sees`), and the owners around - // it are sized here from their types, while the report only supplies the final graph - // deltas and cap boundary. + // The bytes the owners retain are held to the audited allocator in + // `tests/bypass_resources.rs`; here the published deltas must agree with each other, be + // zero exactly when no instance is controlled, and bound the exact caps. let model = queued_eq_model(); let registry = launch_native_effect_registry().expect("launch registry"); let scalar_registry = NativeEffectRegistry::new([Box::new(ScalarOnlyDelegateFactory { @@ -3135,8 +3117,6 @@ mod tests { }; struct OwnerExpectation { - total: u64, - largest: u64, controlled: usize, target_staging: bool, banked: bool, @@ -3158,8 +3138,6 @@ mod tests { ) .expect("checked effect live-control attachment"); assert_eq!(producers.len(), effects.entries.len()); - let mut expected_total = 0_u64; - let mut expected_largest = 0_u64; for (index, entry) in effects.entries.iter_mut().enumerate() { if index >= controlled { entry.control = None; @@ -3167,9 +3145,7 @@ mod tests { } // The lane the plan retains, in the layout under test: a prepared-target owner's // (one cell per `(slot, channel)` and no parameter cell) or an ordinary effect's - // (one cell per live parameter). The lane's own accounting is the witness for its - // heap, each allocation once; `bypass_resources` measures the same bytes with the - // workspace's audited allocator. + // (one cell per live parameter). let slots = if target_staging { parametric_eq::ParametricEqFactory.target_slots() } else { @@ -3198,100 +3174,13 @@ mod tests { }, "the EQ's parameters ride its targets; an ordinary lane has a cell for each" ); - let resources = lane.retained_resources(); - expected_total = expected_total - .checked_add(resources.total_bytes) - .expect("lane resource arithmetic"); - expected_largest = expected_largest.max(resources.largest_allocation_bytes); entry.control = Some(Box::new(lane)); } - let bank_width = BankWidth::for_backend(dispatch) - .map(|width| width.lanes() as usize) - .unwrap_or(0); - let banked = bank_width != 0; - // Issue #1100: every controlled instance also gets a live-control owner around its - // effect. Each lane here has a live channel, so each owner holds a staging window of - // the lane's live parameter cells (#1621 D1; none for a prepared-target owner), and a - // shunt of two dry blocks and two latency lines. - let metadata = &effects.entries[0].metadata; - let span = size_of::(); - let quantum = metadata.quantum as usize; - let latency = usize::try_from(metadata.latency.0).expect("EQ latency"); - if banked && controlled != 0 { - assert_eq!(8 % bank_width, 0, "EQ fixture fills complete banks"); - let banks = controlled.div_ceil(bank_width) as u64; - // The bank's lane array. Beside it the bank keeps a `(lane index, queue)` entry for - // each lane that has an observation queue; these lanes are built with none - // (`observation_depth: 0`), so the table is empty and allocates nothing. The - // tapped table is held to the allocator in `tests/bypass_resources.rs`. - let bank_bytes = (bank_width * size_of::>()) as u64; - expected_total = expected_total - .checked_add(bank_bytes * banks) - .expect("bank owner arithmetic"); - expected_largest = expected_largest.max(bank_bytes); - // `LiveControlEffectBankStage` in place of `EffectBankStage`: its growth, one - // lane's staging window, the packed window of every lane, and the AoSoA shunt. - let stage = size_of::(); - let growth = stage - size_of::(); - // #1611 D3: the window is the widest lane's live parameter cells, none for a - // prepared-target owner (its parameters ride its targets). - let window_spans = if target_staging { - 0 - } else { - effect_contract::EffectLaneShape::live_parameter_cells( - effects.entries[0].factory.descriptor(), - ) - }; - let staging = window_spans * span; - let packed = window_spans * bank_width * span; - let shunt = 2 * (quantum + latency) * bank_width * size_of::(); - expected_total = expected_total - .checked_add((growth + staging + packed + shunt) as u64 * banks) - .expect("bank live-control owner arithmetic"); - expected_largest = expected_largest - .max(stage as u64) - .max(staging as u64) - .max(packed as u64) - .max((quantum.max(latency) * bank_width * size_of::()) as u64); - } else if !banked && controlled != 0 { - let lane_bytes = size_of::() as u64; - let scalar_bytes = (controlled as u64) - .checked_mul(lane_bytes) - .expect("scalar owner arithmetic"); - expected_total = expected_total - .checked_add(scalar_bytes) - .expect("scalar owner arithmetic"); - expected_largest = expected_largest.max(lane_bytes); - // `graph`'s boxed `LiveControlEffect`, its staging window and its shunt. - // The allocator-observed retention gate covers the owner. - let owner = graph::EFFECT_RENDER_NODE_BYTES - + size_of::>() - + size_of::>() - + size_of::() - + size_of::>>() - + size_of::(); - let window = if target_staging { - 0 - } else { - effect_contract::EffectLaneShape::live_parameter_cells( - effects.entries[0].factory.descriptor(), - ) * span - }; - let shunt = 2 * (quantum + latency) * size_of::(); - expected_total = expected_total - .checked_add((owner + window + shunt) as u64 * controlled as u64) - .expect("scalar live-control owner arithmetic"); - expected_largest = expected_largest - .max(owner as u64) - .max(window as u64) - .max((quantum.max(latency) * size_of::()) as u64); - } + let banked = BankWidth::for_backend(dispatch).is_some(); ( effects, OwnerExpectation { - total: expected_total, - largest: expected_largest, controlled, target_staging, banked, @@ -3369,10 +3258,10 @@ mod tests { GraphCompiler::evidence(live.graph(), live.report()).canonical_bytes, "{label}: controls do not alter semantic canonical bytes" ); - // Both published estimates carry exactly the control owners: the report's, and the - // whole-plan estimate with the builtin banks attached -- the one the caps below are - // checked against (#964). The builtin terms are the same in `plain` and `live`, so - // every delta is the owners' alone. + // Both published estimates carry the control owners: the report's, and the whole-plan + // estimate with the builtin banks attached -- the one the caps below are checked + // against (#964). The builtin terms are the same in `plain` and `live`, so every delta + // is the owners' alone. for (estimate, plain_resource, live_resource) in [ ("report", &plain.report().estimate, &live.report().estimate), ( @@ -3381,31 +3270,28 @@ mod tests { live.graph_resource_estimate(), ), ] { - assert_eq!( - live_resource.graph_metadata_bytes - plain_resource.graph_metadata_bytes, - expected.total, - "{label}: {estimate} graph owner delta" - ); + let owners = + live_resource.graph_metadata_bytes - plain_resource.graph_metadata_bytes; assert_eq!( live_resource.incremental_plan_bytes - plain_resource.incremental_plan_bytes, - expected.total, + owners, "{label}: {estimate} plan owner delta" ); assert_eq!( live_resource.session_plus_plan_bytes - plain_resource.session_plus_plan_bytes, - expected.total, + owners, "{label}: {estimate} session owner delta" ); assert_eq!( - live_resource.largest_allocation_bytes, - plain_resource - .largest_allocation_bytes - .max(expected.largest), - "{label}: {estimate} largest owner is a maximum, never a sum" + owners == 0, + controlled == 0, + "{label}: {estimate} owner bytes exactly when an instance is controlled" + ); + assert!( + live_resource.largest_allocation_bytes + >= plain_resource.largest_allocation_bytes, + "{label}: {estimate} largest owner is a maximum, never smaller" ); - if controlled == 0 { - assert_eq!(expected.total, 0, "no controls retain no owner bytes"); - } } let live_resource = live.graph_resource_estimate(); @@ -3524,13 +3410,6 @@ mod tests { #[test] #[allow(clippy::result_large_err)] fn runtime_bank_slot_reservation_is_published_and_capped_transactionally() { - assert_eq!(core::mem::size_of::(), 1); - let literal = |n: u64, width: effect_contract::BankWidth| { - let f = core::mem::size_of::>() as u64; - let b = core::mem::size_of::() as u64; - let w = width.lanes() as u64 * core::mem::size_of::() as u64; - (n * (f + 3 * b + 3 * w), (n * f).max(n * b).max(w)) - }; assert_eq!( graph::GraphBankSlotResourceEstimate::checked_for( 0, @@ -3548,21 +3427,6 @@ mod tests { None, "a populated scalar selection cannot describe bank slots" ); - let three_per_width = effect_contract::BankWidth::ALL - .iter() - .map(|&width| (3, width)); - for (n, width) in [(1, effect_contract::BankWidth::Four)] - .into_iter() - .chain(three_per_width) - { - let (total, largest) = literal(n, width); - let resource = graph::GraphBankSlotResourceEstimate::checked_for(n, Some(width)) - .expect("checked reservation"); - assert_eq!( - (resource.total_bytes, resource.largest_allocation_bytes), - (total, largest) - ); - } // The widest bank this build has: eight lanes where `avx2` is enabled, else four (#1112). let widest = effect_contract::BankWidth::for_backend(host_dispatch()).expect("vector host"); assert_eq!( @@ -3663,7 +3527,7 @@ mod tests { // builtins compiled at `Backend::Scalar`. Until #964 this was a builtins-less compile at // the host dispatch, which bound no bank either. With the builtins-less compile gone, a // scalar dispatch is the one production compile that has no slot owner to charge, so the - // host compile below must publish exactly its estimate plus the literal reservation. + // host compile below must publish exactly its estimate plus the reservation. let semantic_baseline = GraphCompiler::compile_with_builtins(GraphBuiltinsCompileRequest { input_floors: Default::default(), dispatch: Backend::Scalar, @@ -3703,23 +3567,10 @@ mod tests { effect_contract::BankWidth::for_backend(host_dispatch()), ) .expect("slot resource"); - let width = effect_contract::BankWidth::for_backend(host_dispatch()) - .expect("delivery host bank width"); - let slot_total = literal_bank_slot_reservation_bytes( - builtin_resource.bank_count, - u64::from(width.lanes()), - ); - let slot_largest = (builtin_resource.bank_count - * core::mem::size_of::>() as u64) - .max(builtin_resource.bank_count * core::mem::size_of::() as u64) - .max(u64::from(width.lanes()) * core::mem::size_of::() as u64); - assert_eq!(slots.total_bytes, slot_total); - assert_eq!(slots.largest_allocation_bytes, slot_largest); let mut expected = semantic_baseline.report().estimate.clone(); - expected.graph_metadata_bytes += slot_total; - expected.incremental_plan_bytes += slot_total; - expected.session_plus_plan_bytes += slot_total; - expected.largest_allocation_bytes = expected.largest_allocation_bytes.max(slot_largest); + expected + .checked_add_bank_slot_owners(slots) + .expect("slot reservation fold"); let artifact = GraphCompiler::compile_with_builtins(GraphBuiltinsCompileRequest { input_floors: Default::default(), dispatch: host_dispatch(), @@ -3746,8 +3597,8 @@ mod tests { // A separate real compiler fixture combines prepared native effects and builtins. Its // prior owners are observed the same way -- the fixture at `Backend::Scalar`, where neither // its effects nor its builtins bank -- and the effect banks the host dispatch binds are - // charged by their own resource term; direct arithmetic below derives the combined slot - // reservation and the attached builtin payload. Until #964 an effect-only, builtins-less + // charged by their own resource term; the combined slot reservation and the attached + // builtin payload are folded onto them below. Until #964 an effect-only, builtins-less // compile at the host dispatch supplied the prior owners and the effect-bank term together. let scalar_mixed = GraphCompiler::compile_with_builtins(GraphBuiltinsCompileRequest { input_floors: Default::default(), @@ -3797,29 +3648,19 @@ mod tests { assert!(mixed_builtin_resource.bank_count > 0); let width = effect_contract::BankWidth::for_backend(host_dispatch()) .expect("delivery host bank width"); - let lanes = u64::from(width.lanes()); let combined_count = effect_count .checked_add(mixed_builtin_resource.bank_count) .expect("combined count"); - // One reservation over the combined count. Its total is linear in the count, so what tells - // it from one reservation per kind is the largest allocation: one array of - // `combined_count` entries, not two shorter ones. - let combined_slot_bytes = literal_bank_slot_reservation_bytes(combined_count, lanes); - let stage = core::mem::size_of::>() as u64; - let slot = core::mem::size_of::() as u64; - let mask = lanes * core::mem::size_of::() as u64; - let combined_largest = (combined_count * stage) - .max(combined_count * slot) - .max(mask); + // One reservation over the combined count, not one reservation per kind. + let combined = + graph::GraphBankSlotResourceEstimate::checked_for(combined_count, Some(width)) + .expect("combined slot reservation"); let mut expected_report = scalar_mixed.report().estimate.clone(); crate::banks::checked_add_effect_banks(&mut expected_report, effect_resource) .expect("effect bank fold"); - expected_report.graph_metadata_bytes += combined_slot_bytes; - expected_report.incremental_plan_bytes += combined_slot_bytes; - expected_report.session_plus_plan_bytes += combined_slot_bytes; - expected_report.largest_allocation_bytes = expected_report - .largest_allocation_bytes - .max(combined_largest); + expected_report + .checked_add_bank_slot_owners(combined) + .expect("combined slot fold"); let mixed = GraphCompiler::compile_with_builtins(GraphBuiltinsCompileRequest { input_floors: Default::default(), diff --git a/crates/graph-compiler/tests/MUTATIONS.md b/crates/graph-compiler/tests/MUTATIONS.md index 28503fd50..0d2409dfa 100644 --- a/crates/graph-compiler/tests/MUTATIONS.md +++ b/crates/graph-compiler/tests/MUTATIONS.md @@ -347,3 +347,22 @@ prevalidation (1045-4, Sol's P5b), the real builtin bank kernels rendering at th quadratic compile and bind (1045-5). Bank attachment left this list in attempt 3 (1045-12), the compiler's builtin bank plan in attempt 4 (1045-14, 1045-15), builtin bank lowering in attempt 5 (1045-17, 1045-18). The spec's attempt-5 evidence carries the complete layer map. + +## TA1 policy cut (2026-10-18): tests these rows name that no longer exist or no longer assert it + +The owner-approved test policy cut (`.github/ISSUE_SPECS/1722-delete-digest-type-size-and-unrun-tests.md`) +deleted or trimmed some tests that rows above name. The rows stay as history; they were not re-run. + +- 964-2: `issue122_reverse_route_ids_emit_sorted_levels_and_bind` no longer pins the plan's SHA-256. + Its schedule, level and rendered-PCM assertions remain. +- 964-3 and its two rechecks: `track_delay::the_zero_delay_plan_digest_is_the_current_semantic_plan` + and its companion `a_delayed_session_is_a_different_plan` are deleted. The structural off gate + `a_zero_delay_session_lowers_no_delay_node` remains. +- 964-5: `runtime_bank_slot_reservation_is_published_and_capped_transactionally` now folds the + product's own `GraphBankSlotResourceEstimate::checked_for` reservation onto the observed prior + in place of a `size_of` literal. By reading, the mutation in that row still moves the published + estimate away from that fold; it was not re-run. +- 964-6: `effect_control_resource_uses_independent_queue_and_owner_arithmetic` no longer derives + the owner bytes from `size_of`. It asserts that the three deltas agree, are zero exactly when no + instance is controlled, and that the exact caps accept and one byte below refuses. A partial + under-charge such as this row's is left to the allocator ceiling in `tests/bypass_resources.rs`. diff --git a/crates/graph-compiler/tests/live_routes.rs b/crates/graph-compiler/tests/live_routes.rs index 833b34494..5a2008360 100644 --- a/crates/graph-compiler/tests/live_routes.rs +++ b/crates/graph-compiler/tests/live_routes.rs @@ -28,9 +28,8 @@ use engine::realtime::{ }; use graph::{ GraphBindingBlock, GraphCompileCaps, GraphEdgeId, GraphNodeBinding, GraphNodeId, - GraphRouteControlBinding, GraphRouteControlError, GraphRouteControlProducer, - GraphRuntimeBindings, GraphRuntimeProcessor, LIVE_ROUTE_OWNER_BYTES, ROUTE_RAMP_LENGTH_MAXIMUM, - RouteControlLane, RouteControlRecord, TrackStage, route_control_resources, + GraphRouteControlError, GraphRouteControlProducer, GraphRuntimeBindings, GraphRuntimeProcessor, + ROUTE_RAMP_LENGTH_MAXIMUM, RouteControlRecord, TrackStage, route_control_resources, }; use graph_compiler::{ Backend, GraphBuiltinsCompileRequest, GraphCompiler, PreparedGraphBuiltinsArtifact, @@ -1408,9 +1407,9 @@ fn retained( (live_bytes, producers) } -/// Gate 8 (VERIFY-2 MINOR 5). `route_control_resources` equals the D8 formula computed here from -/// the route count and the route IDs, and covers what the attach and the bind -/// retain beyond the same plan without route controls. Once for a plan with no silencing gate, +/// Gate 8 (VERIFY-2 MINOR 5). `route_control_resources` counts the routes, both copies of each +/// route ID and the route-activity table, and covers what the attach and the bind retain beyond the +/// same plan without route controls. Once for a plan with no silencing gate, /// whose route-activity table the attach charges, and once with a muted route, whose table the /// compile-time estimate already charged. /// @@ -1427,18 +1426,8 @@ fn route_control_resources_cover_the_allocation() { let routes = producers.len() as u64; assert_eq!(routes, 2); - // One `Arc` allocation per route: the owner counts, the cell's three slots (six words, a - // revision and a sequence each) and the dirty word. - let cell = (2 * core::mem::size_of::() - + core::mem::size_of::>() - + core::mem::size_of::()) as u64; - assert_eq!(cell, 2 * 8 + 3 * (6 * 4 + 8 + 8) + 8, "the cell's layout"); - let owner = (core::mem::size_of::() - + core::mem::size_of::() - + LIVE_ROUTE_OWNER_BYTES) as u64; // The producer's copy and the binding's node-ID copy (#1220 verdict MINOR-1). let ids: u64 = producers.iter().map(|p| 2 * p.route_id.len() as u64).sum(); - let table = routes * core::mem::size_of::() as u64; let artifact = compile(&model); let nodes = artifact.graph().spec.nodes.len() as u64; let prepared_routes = artifact.graph().routes().len() as u64; @@ -1448,16 +1437,8 @@ fn route_control_resources_cover_the_allocation() { graph::route_activity_bound_bytes(prepared_routes, nodes).expect("bound") }; assert_eq!(resources.routes, routes); - assert_eq!(resources.cell_bytes, routes * cell); - assert_eq!(resources.owner_bytes, routes * owner); - assert_eq!(resources.producer_table_bytes, table); assert_eq!(resources.route_id_bytes, ids); assert_eq!(resources.activity_bytes, activity); - assert_eq!( - resources.total_bytes, - routes * cell + routes * owner + table + ids + activity - ); - assert!(resources.largest_allocation_bytes >= cell); let added = with .checked_sub(without) diff --git a/crates/graph-compiler/tests/track_delay.rs b/crates/graph-compiler/tests/track_delay.rs index 63251f16d..9ca53b074 100644 --- a/crates/graph-compiler/tests/track_delay.rs +++ b/crates/graph-compiler/tests/track_delay.rs @@ -5,8 +5,8 @@ //! //! * **P2-2, the off gate.** A session that declares `delay_samples = 0` -- which is every session //! in the tree, and will be almost every session a host ever writes -- must compile to the -//! current zero-delay semantic plan. The digest pin records that plan; the structural row below -//! proves that track delay itself adds no node or ring. +//! current zero-delay semantic plan: the structural row below proves that track delay itself +//! adds no node or ring. //! * **P2-3, PDC non-interaction.** A delayed track's *output* shifts. Its *PDC report* does not. //! * **P2-4, accounting.** The rings are charged to `delay_bytes`, exactly `sum * 4` per lane, and //! a hostile session that asks for more than the caps allow is rejected rather than allocated. @@ -128,7 +128,6 @@ fn session_with_delay(left: u32, right: u32) -> CompiledSession { struct Compiled { artifact: PreparedGraphBuiltinsArtifact, - sha256: String, output_latency: u64, delay_bytes: u64, largest_allocation_bytes: u64, @@ -142,7 +141,6 @@ impl Compiled { /// The production compile: the session's own builtins, prepared with no meters and no live /// controls, attached through `compile_with_builtins` at the width this build renders at (#964). -/// The canonical digest is dispatch-independent, so the pin below holds on every host. fn compile(session: CompiledSession, caps: GraphCompileCaps) -> Result { let registry = launch_native_effect_registry().expect("launch registry"); let builtins = prepare_session_builtins( @@ -188,13 +186,11 @@ fn compile(session: CompiledSession, caps: GraphCompileCaps) -> Result>() .join(",") })?; - let sha256 = GraphCompiler::sha256(artifact.graph(), artifact.report()); // The whole-plan estimate, with the builtin banks attached: the one the caps are checked // against, and the one a host publishes. let estimate = artifact.graph_resource_estimate().clone(); Ok(Compiled { output_latency: artifact.report().output_latency.0, - sha256, delay_bytes: estimate.delay_bytes, largest_allocation_bytes: estimate.largest_allocation_bytes, artifact, @@ -216,7 +212,7 @@ fn compiled(left: u32, right: u32) -> Compiled { /// lowered program rather than a claim about it. /// /// Red mutation: emit a `PreparedTrackDelay` unconditionally in `compile.rs` (drop the -/// `filter`) -> this fails, and so does every digest row below. +/// `filter`) -> this fails. #[test] fn a_zero_delay_session_lowers_no_delay_node() { let zero = compiled(0, 0); @@ -227,85 +223,14 @@ fn a_zero_delay_session_lowers_no_delay_node() { assert_eq!(zero.delay_bytes, 0, "no ring, no bytes"); } -/// The zero-delay plan has the current semantic digest for this feature set. -/// -/// The pre-track-delay comparison was measured at 17682b4 and re-pinned by #241. Issue #805 -/// independently accounted for its prepared EQ state growth: nine instances added 2,736 bytes, -/// producing the baseline `e9e6b012...209433e4` recorded in that issue's canonical derivation. -/// -/// Issue #807 retains two dedicated-cut enable words per channel: each EQ's serialized state -/// grows from 920 to 936 bytes, so nine instances add exactly 144 bytes. Applying only that -/// change to the preserved #805 canonical text gives the compiler's actual new hash below: -/// `declared_effect_bytes` 8,280 -> 8,424 and both plan-byte totals 150,415 -> 150,559. -/// Every other canonical byte is retained in that independently derived expected text. The -/// numbered #807 spec records the matching computed/compiled hashes and exact estimate row. -/// -/// Issue #964 compiles through `compile_with_builtins`, as every host does. The fixture's input -/// sections declare a 20 Hz high-pass and a 20 kHz low-pass on every lane, so each of the nine -/// `post-input-builtins` nodes carries an `infinite` tail rather than `finite:0`. Applying only -/// that change to the #807 canonical text -- the nine `node` rows and the nine `tail` rows of those -/// nodes, eighteen tokens, and no other byte -- gives the hash below; the builtins-less compile -/// hashed the unchanged text to `eb3ca776...cb18e0ea10`. The #964 spec records the diff. -/// -/// Issue #1328 amendment A9 carries each channel input's silence counter in the EQ's state -/// payload: one word per channel, so each EQ's serialized state grows from 936 to 944 bytes and -/// nine instances add exactly 72. Applying only that change to the #964 canonical text -- -/// `declared_effect_bytes` 8,424 -> 8,496 and both plan-byte totals 150,559 -> 150,631, three -/// tokens of the `estimate` row and no other byte -- reproduces the compiler's new hash below; -/// reversing the three tokens in the compiled text hashes back to the #964 pin `957e97ca...7640e42`. -/// -/// Issue #1328's follow-up declares the EQ's two rest planes as scratch (8 bytes per frame per -/// prepared lane, `parametric_eq::REST_PLANE_BYTES_PER_FRAME`), which the estimate charges in -/// `declared_effect_bytes`: nine instances at a 128-frame quantum add exactly 9,216 bytes. -/// `declared_effect_bytes` 8,496 -> 17,712 and both plan-byte totals 150,631 -> 159,847, the same -/// three tokens of the `estimate` row and no other byte; reversing them in the compiled text hashes -/// back to the A9 pin `bb250287...ffbfc40bf` (verified on the dumped canonical text). -/// -/// Issue #1329 states a certified finite tail for every builtin input section (D1, D4, D7): the -/// fixture's 20 Hz high-pass into 20 kHz low-pass at 48 kHz now reports `finite:10048` where it -/// reported `infinite`. Exactly the eighteen tokens #964 introduced move -- the nine `node` rows -/// and the nine `tail` rows of the `post-input-builtins` nodes -- and no other byte (diffed on the -/// dumped canonical text against the follow-up pin `bf2dfd6c...ad7b10d8b3`, which the unchanged -/// tree reproduces). The value is the preparation's computed bound, not a pinned figure: a change -/// to the bound's derivation moves this digest, which is the point of pinning the plan. -/// -/// The structural off-delay gate above still proves that no zero-length delay node or ring was -/// introduced. Emitting a zero-length entry contributes no `delay_bytes` and leaves this digest -/// unchanged; `a_zero_delay_session_lowers_no_delay_node` catches that program mutation. -#[test] -fn the_zero_delay_plan_digest_is_the_current_semantic_plan() { - assert_eq!( - compiled(0, 0).sha256, - ZERO_DELAY_CANONICAL_SHA256, - "a zero-delay session must compile to the ruled canonical plan" - ); -} - -/// Historical #241 schema arithmetic remains in `docs/derivations/241-schema-repins.md`; -/// the numbered #805 and #807 specs preserve their subsequent state-size derivations, and the -/// #964 spec the builtin-tail re-pin. -const ZERO_DELAY_CANONICAL_SHA256: &str = - "60cae21e7692ccbdf17440c7c84f15bd1bb29c3bb2369a0d1b52a5a56fc6c362"; - -/// ...and a delayed one is a genuinely different plan, so the digest above is not inert. -#[test] -fn a_delayed_session_is_a_different_plan() { - assert_ne!( - compiled(480, 480).sha256, - ZERO_DELAY_CANONICAL_SHA256, - "a delay that changes the estimate must change the plan digest" - ); -} - // --------------------------------------------------------------------------------------------- // P2-3: PDC non-interaction // --------------------------------------------------------------------------------------------- /// Track delay is not latency, so nothing PDC owns moves when a track declares one. /// -/// The four things PDC owns are asserted individually rather than through the digest, because the -/// digest is a conjunction: it would go red for the right reason and give no evidence about *which* -/// of them moved. `inserted_delays` and `route_timings` are the compensation sets the design names; +/// The four things PDC owns are asserted individually, so a failure says *which* of them moved. +/// `inserted_delays` and `route_timings` are the compensation sets the design names; /// `output_latency` is what a host reports to its own clock; and the per-node latencies are the /// input PDC computes all three from. /// diff --git a/crates/graph/src/lib.rs b/crates/graph/src/lib.rs index 570257bff..b320b40ba 100644 --- a/crates/graph/src/lib.rs +++ b/crates/graph/src/lib.rs @@ -6414,12 +6414,8 @@ mod tests { let resource = GraphRuntimeMetadataResourceEstimate::checked_for(emitted) .expect("checked runtime metadata"); let (op_delta, unit_delta) = runtime::scalar_split_op_layout(); - let (op_size, unit_size) = ( - u64::try_from(core::mem::size_of::()).expect("op size"), - u64::try_from(core::mem::size_of::()).expect("unit size"), - ); let (_, runtime_field) = runtime::scalar_split_runtime_layout(); - let (executor_field, executor_size) = scalar_split_runtime_owner_layout(); + let (executor_field, _) = scalar_split_runtime_owner_layout(); assert_eq!(resource.runtime_field_bytes, runtime_field); assert_eq!(resource.runtime_op_layout_delta_bytes, op_delta); assert_eq!(resource.runtime_unit_layout_delta_bytes, unit_delta); @@ -6428,34 +6424,27 @@ mod tests { resource.emitted_op_layout_delta_bytes, op_delta.max(unit_delta) ); - assert_eq!(resource.runtime_op_containing_bytes, op_size * emitted); - assert_eq!(resource.runtime_unit_containing_bytes, unit_size * emitted); - // Issue #936: the executor's two bind-sized tables, each at one entry per emitted op. - let active_table = u64::try_from(core::mem::size_of::()).expect("entry") * emitted; - let source_table = - u64::try_from(core::mem::size_of::<(usize, u32)>()).expect("entry") * emitted; - assert_eq!(resource.active_unit_table_bytes, active_table); - assert_eq!(resource.source_input_table_bytes, source_table); - // Issue #1609: the runtime's block-end unit table, one `u32` per emitted op. - let block_end_table = u64::try_from(core::mem::size_of::()).expect("entry") * emitted; - assert_eq!(resource.block_end_unit_table_bytes, block_end_table); - assert_eq!( - resource.total_bytes, - runtime_field - + op_delta.max(unit_delta) * emitted - + active_table - + source_table - + block_end_table - ); - assert_eq!( - resource.largest_allocation_bytes, - executor_size - .max(op_size * emitted) - .max(unit_size * emitted) - .max(active_table) - .max(source_table) - .max(block_end_table) - ); + // Issue #936 and #1609: the executor's two bind-sized tables and the block-end unit table + // each hold one entry per emitted op, so they scale with the op count. + let doubled = GraphRuntimeMetadataResourceEstimate::checked_for(2 * emitted) + .expect("checked runtime metadata"); + for (table, twice) in [ + ( + resource.active_unit_table_bytes, + doubled.active_unit_table_bytes, + ), + ( + resource.source_input_table_bytes, + doubled.source_input_table_bytes, + ), + ( + resource.block_end_unit_table_bytes, + doubled.block_end_unit_table_bytes, + ), + ] { + assert!(table > 0, "a table is charged for emitted ops"); + assert_eq!(twice, 2 * table, "one entry per emitted op"); + } // The executor's layout witness mirrors every field but the one it removes, the two #936 // tables included: the owner-level delta is then exactly the runtime-level one, and a // table added to `GraphExecutor` alone would inflate it by its box. @@ -6496,12 +6485,10 @@ mod tests { assert_eq!(zero, GraphScalarSplitRuntimeResourceEstimate::default()); let one = GraphScalarSplitRuntimeResourceEstimate::checked_for(1).expect("one split table entry"); - let entry = u64::try_from(core::mem::size_of::>()) - .expect("split table entry"); assert_eq!(one.possible_pair_count, 1); - assert_eq!(one.split_pair_table_bytes, entry); - assert_eq!(one.total_bytes, entry); - assert_eq!(one.largest_allocation_bytes, entry); + assert!(one.split_pair_table_bytes > 0, "one entry is charged"); + assert_eq!(one.total_bytes, one.split_pair_table_bytes); + assert_eq!(one.largest_allocation_bytes, one.split_pair_table_bytes); assert_eq!( GraphScalarSplitRuntimeResourceEstimate::checked_for(u64::MAX), None @@ -10885,20 +10872,18 @@ mod follow_route_tests { } } - /// The charge of six-word and following routes together is each kind's own cell, owner and - /// producer bytes, the route IDs twice, and the plan's one route-activity table once. A plan - /// with no following route is charged exactly as before (the six-word terms alone). + /// The charge of six-word and following routes together counts each route once, the route IDs + /// twice, and the plan's one route-activity table once. A plan with no following route is + /// charged its six-word terms alone. /// - /// Test value: red if a following route is charged a six-word cell or owner, if a kind is left - /// out, or if the activity table is charged per kind. + /// Test value: red if a kind is left out of the joint charge, or if the activity table is + /// charged per kind. #[test] - fn the_charge_counts_each_kind_its_own_bytes_and_the_activity_table_once() { - let size = |bytes: usize| bytes as u64; + fn the_charge_counts_each_kind_and_the_activity_table_once() { let superseded = LiveValuesSuperseded::new(); /// The plan's prepared routes: their activation offsets join the activity charge /// once, for a plan with a following route (#1652 MAJOR-1). const ACTIVITY_ROUTES: u64 = 7; - let offsets = ACTIVITY_ROUTES * size(core::mem::size_of::()); let activity = (4_096, 1_024); let six = [ six_word_producer("a", activity), @@ -10919,61 +10904,33 @@ mod follow_route_tests { .1 }) .collect(); - let six_cell = size(core::mem::size_of::< - engine::realtime::SharedAllocation, - >()); - let seven_cell = size(core::mem::size_of::< - engine::realtime::SharedAllocation, - >()); - assert!(seven_cell > six_cell, "the seventh word has its own cell"); - let six_owner = size( - core::mem::size_of::() - + core::mem::size_of::() - + LIVE_ROUTE_OWNER_BYTES, - ); - // #1654 T3: the one-lane source's left column hears one curve: its walk's table and its - // probe twin's, one cursor each. - let cursors = 2 * size(automation::Composed::<([f32; 2], bool)>::bytes(1)); - let seven_owner = size( - core::mem::size_of::() - + core::mem::size_of::() - + LIVE_FOLLOW_ROUTE_OWNER_BYTES, - ) + cursors; - let six_table = 2 * size(core::mem::size_of::()); - let seven_table = 3 * size(core::mem::size_of::()); - let ids = 2 * (1 + 2 + 3 + 4 + 5); let both = route_control_resources(&six, &seven); assert_eq!(both.routes, 5); - assert_eq!(both.cell_bytes, 2 * six_cell + 3 * seven_cell); - assert_eq!(both.owner_bytes, 2 * six_owner + 3 * seven_owner); - assert_eq!(both.producer_table_bytes, six_table + seven_table); - assert_eq!(both.route_id_bytes, ids); - assert_eq!(both.activity_bytes, activity.0 + offsets); - assert_eq!( - both.total_bytes, - 2 * six_cell - + 3 * seven_cell - + 2 * six_owner - + 3 * seven_owner - + six_table - + seven_table - + ids - + activity.0 - + offsets - ); - assert!(both.largest_allocation_bytes >= seven_cell.max(activity.1)); + assert_eq!(both.route_id_bytes, 2 * (1 + 2 + 3 + 4 + 5)); let alone = route_control_resources(&six, &[]); assert_eq!(alone.routes, 2); - assert_eq!(alone.cell_bytes, 2 * six_cell); - assert_eq!(alone.owner_bytes, 2 * six_owner); - assert_eq!(alone.producer_table_bytes, six_table); assert_eq!(alone.route_id_bytes, 2 * (1 + 2)); assert_eq!(alone.activity_bytes, activity.0); + // #1652 MAJOR-1: a plan with a following route also charges the activation offsets bind + // adds to the table, one `u32` per prepared route, once. + assert_eq!( + both.activity_bytes, + alone.activity_bytes + ACTIVITY_ROUTES * 4, + "the activation offsets are charged with the table" + ); let following = route_control_resources(&[], &seven); - assert_eq!(following.cell_bytes, 3 * seven_cell); - assert_eq!(following.owner_bytes, 3 * seven_owner); + assert_eq!( + alone.cell_bytes + following.cell_bytes, + both.cell_bytes, + "each kind's cells are charged in the joint plan" + ); + assert_eq!( + alone.owner_bytes + following.owner_bytes, + both.owner_bytes, + "each kind's owners are charged in the joint plan" + ); assert_eq!( alone.total_bytes + following.total_bytes - activity.0, both.total_bytes, diff --git a/crates/graph/tests/MUTATIONS.md b/crates/graph/tests/MUTATIONS.md index 7e43367d9..be1b11569 100644 --- a/crates/graph/tests/MUTATIONS.md +++ b/crates/graph/tests/MUTATIONS.md @@ -734,3 +734,19 @@ each source file restored after its run. x86-64 AVX2 host, debug profile. The ga | 1220-10 | the arena-form sum skips a destination whose every input is inactive (#1217 verdict 2, V3c) | `graph/src/runtime.rs` `reduce_gated` | `a_bus_whose_every_send_goes_inactive_is_refilled_with_positive_zero` | RED, 1 of 10: `plane 0 frame 0: -1.2888975, expected +0.0`. | | 1220-11 | `route_segments` forgets that an opening run stored (#1217 verdict 2, MC) | `graph/src/runtime.rs` `route_segments` | `host-core` `a_middle_inactive_route_keeps_every_active_contribution` | RED, 1 of 13: `Bus, 3 inputs, muted [1], plane 0: sample 0: 0.29300022 != 0.36220396`. | | 1220-12 | a second attach is accepted (the `AlreadyAttached` refusal removed) | `graph/src/lib.rs` `attach_route_controls` | `route_controls_attach_once` | RED: the second call returns `Ok(2)`, replacing the first call's lanes and orphaning its producers. | + +## TA1 policy cut (2026-10-18): tests these rows name that no longer exist or no longer assert it + +The owner-approved test policy cut (`.github/ISSUE_SPECS/1722-delete-digest-type-size-and-unrun-tests.md`) +deleted or trimmed some tests that rows above name. The rows stay as history; they were not re-run. + +- 916-16 and 916-17: the builtins-compiler scalar-oracle pairing tests + `actual_scalar_graph_queues_fuse_and_fall_back_against_separate_owners`, + `staggered_observed_scalar_track_stays_separate_while_eligible_peer_pairs`, + `actual_scalar_nonadjacent_output_track_takes_the_split_pair_now_the_output_is_dedicated` and + `actual_scalar_overlapping_nonadjacent_candidates_select_one_and_keep_the_other_separate` are + deleted. The other tests those rows name remain. +- #936 "the metadata test" (`runtime_metadata_charge_covers_mixed_ops_once_and_refuses_overflow`) + no longer re-derives the charge from `size_of`; it keeps the layout-witness, fold and overflow + assertions. console-workload "gate 5" + (`the_driver_fed_rows_metadata_charge_grows_by_exactly_the_executor_tables`) is deleted. diff --git a/crates/host-core/tests/live_edit_classifier.rs b/crates/host-core/tests/live_edit_classifier.rs index 1d5041c2a..7f39365ce 100644 --- a/crates/host-core/tests/live_edit_classifier.rs +++ b/crates/host-core/tests/live_edit_classifier.rs @@ -2,17 +2,14 @@ //! //! `classify_live_edit` reads a transaction of `SetControl`s from the rows it names, with no model //! clone and no canonical JSON, and must give exactly what `classify_live_delta_with_monitor` gives -//! for the same models. This file holds the three gates: +//! for the same models. This file holds the two gates: //! //! 1. the differential: the #1560 parity script and a seeded random set of edits, the full //! classifier as the oracle; -//! 2. the cost: the allocations of a live-only classification do not depend on the track count; -//! 3. one descriptive timing line (ignored: `cargo test --release -p host-core --all-features -//! --test live_edit_classifier -- --ignored --nocapture`). +//! 2. the cost: the allocations of a live-only classification do not depend on the track count. #![cfg(all(feature = "control-provider", feature = "test-support"))] use std::collections::BTreeSet; -use std::time::Instant; #[path = "support/edit_sessions.rs"] mod edit_sessions; @@ -401,70 +398,3 @@ fn the_cost_of_a_live_edit_does_not_grow_with_the_session() { "solo: the allocations depend on the track count: {counts:?}" ); } - -// --------------------------------------------------------------------------------------------- -// Gate 3: one descriptive timing line -// --------------------------------------------------------------------------------------------- - -/// The timing line: a fader edit on the session #1562a timed, 200 calls, by the full classifier -/// (before) and by `classify_live_edit` (after). -#[test] -#[ignore = "descriptive timing: cargo test --release -p host-core --all-features --test live_edit_classifier -- --ignored --nocapture"] -fn timing_line() { - for tracks in [64, 256] { - let store = store(plain_session(tracks)); - let edits = [set(strip_row("t0003", FADER_DB, Lane::Both), -9.0)]; - let none = MonitorOverlay::default(); - let (prepared, overlay, ramp) = prepare(&store, &edits, &none).expect("applies"); - let current = store.compiled().normalized_model(); - let next = prepared - .compiled() - .expect("a compiled transaction") - .normalized_model(); - let monitor = MonitorBase { - committed: &none, - next: &overlay, - ramp, - }; - let ramps = LiveRamps::for_session(next); - let calls = 200; - let start = Instant::now(); - for _ in 0..calls { - let delta = classify_live_delta_with_monitor( - current, - next, - ramps, - prepared.edit_ramps(), - monitor, - ) - .expect("live"); - assert_eq!(delta.strips.len(), 1); - } - let before = start.elapsed() / calls; - let start = Instant::now(); - for _ in 0..calls { - let delta = classify_live_edit( - current, - next, - ramps, - prepared.edit_ramps(), - monitor, - prepared.set_control_rows(), - ) - .expect("live"); - assert_eq!(delta.strips.len(), 1); - } - let after = start.elapsed() / calls; - // The protocol's preparation of the same transaction (a model clone and a compile), which - // this slice does not change: the share of a whole command that stays linear in the session. - let start = Instant::now(); - for _ in 0..calls { - let prepared = prepare(&store, &edits, &none).expect("applies"); - drop(prepared); - } - let preparation = start.elapsed() / calls; - println!( - "classifier fader edit, {tracks} tracks: full {before:?} per call, fast {after:?} per call; protocol preparation of the transaction {preparation:?}" - ); - } -} diff --git a/crates/math/tests/m2_lane_identity.rs b/crates/math/tests/m2_lane_identity.rs index aa831fc6f..eaa71df56 100644 --- a/crates/math/tests/m2_lane_identity.rs +++ b/crates/math/tests/m2_lane_identity.rs @@ -15,15 +15,13 @@ //! `select`, `floor`, `min`, `max`, `exp2_int`, `frexp`, and (for L3 `log2_lane`) `div` — so 83a's //! gate G1 already proves each operation individually. M2 proves the composition over a corpus //! that includes the inputs G1's per-operation pools do not reach in this combination: the fold -//! boundaries, the clamp rails and the `sqrt(2)` mantissa split. Ignored exhaustive tests also -//! compare each function's scalar and vector results for every `u32` input pattern. +//! boundaries, the clamp rails and the `sqrt(2)` mantissa split. //! //! The pinned digest is generated by the scalar instantiation, so 83d can replay it under //! wasmtime as part of G5/M3 without needing a native run to compare against. use lane::Lane; use sha2::{Digest, Sha256}; -use std::thread; /// Number of inputs in the identity corpus. const POINTS: usize = 1 << 20; @@ -269,67 +267,6 @@ fn m2_log2_lane_identity() { check::("log2_lane", &input, &LOG2_DIGEST); } -/// Compare a bit-pattern range at all lane widths in constant space per worker. -fn compare_patterns(lo: u64, hi: u64) { - let mut base = lo; - let mut results = [(0_usize, [0_u32; 8]); 2]; - while base < hi { - let input = - std::array::from_fn::<_, 8, _>(|lane| f32::from_bits((base + lane as u64) as u32)); - let mut widths = 0; - lane::each_vector_lane!(|L, N| { - results[widths].0 = N; - for offset in (0..8).step_by(N) { - F::at::(L::load(&input[offset..])).store_bits(&mut results[widths].1[offset..]); - } - widths += 1; - }); - for lane in 0..8 { - let want = F::at::(input[lane]).to_bits(); - for (width, result) in &results[..widths] { - assert_eq!( - result[lane], - want, - "Simd{width} differs at {:#010x}", - input[lane].to_bits() - ); - } - } - base += 8; - } -} - -/// Compare every possible f32 input bit pattern at all lane widths. -/// -/// This includes signed zero, all subnormals, infinities and NaN payloads. The log2 clamp maps -/// every non-positive and NaN input to its finite lower rail, so NaN canonicalisation cannot -/// obscure a lane-width discrepancy. The four worker ranges are disjoint and aligned to width 8. -fn compare_all_patterns() { - const WORKERS: usize = 4; - let total = 1_u64 << 32; - let chunk = total / WORKERS as u64; - - thread::scope(|scope| { - for worker in 0..WORKERS { - let lo = worker as u64 * chunk; - let hi = lo + chunk; - scope.spawn(move || compare_patterns::(lo, hi)); - } - }); -} - -#[test] -#[ignore = "2^32 input patterns: run with --release -- --ignored"] -fn m2_exp2_lane_identity_all_f32_bit_patterns() { - compare_all_patterns::(); -} - -#[test] -#[ignore = "2^32 input patterns: run with --release -- --ignored"] -fn m2_log2_lane_identity_all_f32_bit_patterns() { - compare_all_patterns::(); -} - /// The corpus must reach the branches that make M2 worth running: both sides of the `exp2` fold, /// both clamp rails, and both sides of the `log2` mantissa split. #[test] diff --git a/crates/math/tests/scalar_accuracy.rs b/crates/math/tests/scalar_accuracy.rs index 9fec68940..f049c3181 100644 --- a/crates/math/tests/scalar_accuracy.rs +++ b/crates/math/tests/scalar_accuracy.rs @@ -285,8 +285,7 @@ fn f32_functions_match_platform_libm_in_f64() { /// `sqrt` is the one function here that IEEE 754 specifies exactly, and it is re-derived rather /// than vendored (VENDORED.md), so it gets the strongest check available. The default run strides /// the bit pattern by 1021 (odd, and coprime with the 2^23 exponent stride, so every exponent and -/// a dense spread of significands are covered); `sqrtf_is_correctly_rounded_exhaustive` walks all -/// 2^31 non-negative patterns and is `#[ignore]`d because it costs about 40 s. +/// a dense spread of significands are covered). #[test] fn sqrtf_is_correctly_rounded() { check_sqrtf(1021); @@ -295,33 +294,6 @@ fn sqrtf_is_correctly_rounded() { } } -/// All 2^32 raw patterns are checked against the independent software oracle. Non-NaN results -/// must match bit-for-bit; NaNs are compared by class because their sign and payload are -/// target-dependent. -#[test] -#[ignore = "2^32 sweep: run with --release -- --ignored"] -fn sqrtf_is_correctly_rounded_exhaustive() { - let mut non_nan = 0_u64; - let mut nan = 0_u64; - for raw in 0..=u64::from(u32::MAX) { - let x = f32::from_bits(raw as u32); - let got = m::sqrtf(x); - let want = software_sqrtf(x); - if got.is_nan() || want.is_nan() { - assert!( - got.is_nan() && want.is_nan(), - "sqrtf({x:?}) NaN disagreement" - ); - nan += 1; - } else { - assert_eq!(got.to_bits(), want.to_bits(), "sqrtf({x:?})"); - non_nan += 1; - } - } - assert_eq!(non_nan, 2_139_095_042); - assert_eq!(nan, 2_155_872_254); -} - fn check_sqrtf(stride: u32) { let mut bits = 0u32; while bits < 0x7f80_0000 { @@ -431,20 +403,6 @@ fn sqrt_is_correctly_rounded() { assert_sqrt_matches_software(f64::NAN); } -/// Large deterministic raw-pattern sweep against the independent exact-integer oracle. -#[test] -#[ignore = "large deterministic sqrt oracle sweep"] -fn sqrt_is_correctly_rounded_large_oracle() { - let mut state = 0x1319_8a2e_0370_7344_u64; - for _ in 0..100_000_000 { - state ^= state >> 12; - state ^= state << 25; - state ^= state >> 27; - let bits = state.wrapping_mul(0x2545_f491_4f6c_dd1d); - assert_sqrt_matches_software(f64::from_bits(bits)); - } -} - /// `floor`/`floorf` agree with the platform on every exponent class, including the signed-zero and /// infinity cases the bit-twiddling form has to special-case. #[test] diff --git a/crates/parametric-eq/src/lib.rs b/crates/parametric-eq/src/lib.rs index 84db23985..e315d09e7 100644 --- a/crates/parametric-eq/src/lib.rs +++ b/crates/parametric-eq/src/lib.rs @@ -5758,73 +5758,6 @@ mod elision { ); } - /// Issue #980: the min/max gate and the rejection accumulator agree on every one of the - /// `2^32` words, alone and in every run of 64 consecutive words. - /// - /// Both predicates are a conjunction of the same per-word test, so the lone-word verdict is the - /// whole claim; the 64-word runs put every word through the vectorised loop body as well. - /// Ignored by default (about a minute in release, several in a dev build): run it with - /// `cargo test -p parametric-eq --release --lib every_word -- --ignored`. - #[test] - #[ignore = "exhaustive over 2^32 words; run explicitly"] - fn every_word_gets_the_input_contracts_verdict() { - use std::hint::black_box; - const RUN: u64 = 64; - let threads = std::thread::available_parallelism() - .map_or(1, core::num::NonZeroUsize::get) - .clamp(1, 8) as u64; - let per_thread = (1_u64 << 32) / threads / RUN * RUN; - let admitted: u64 = std::thread::scope(|scope| { - let workers: Vec<_> = (0..threads) - .map(|thread| { - scope.spawn(move || { - let start = thread * per_thread; - let end = if thread + 1 == threads { - 1_u64 << 32 - } else { - start + per_thread - }; - let mut admitted = 0_u64; - let mut run = [0.0_f32; RUN as usize]; - let mut first = start; - while first < end { - for (offset, word) in run.iter_mut().enumerate() { - let bits = first + offset as u64; - *word = f32::from_bits(bits as u32); - // `black_box` keeps the optimiser from proving the two - // bodies equal on a known word and deleting the comparison: - // both run, on a word they cannot see through. - let lone = [*word]; - let verdict = block_admits_elision(black_box(&lone)); - assert_eq!( - verdict, - block_admits_elision_oracle(black_box(&lone)), - "word {bits:#010x}" - ); - admitted += u64::from(verdict); - } - assert_eq!( - block_admits_elision(black_box(&run)), - block_admits_elision_oracle(black_box(&run)), - "the run of {RUN} words from {first:#010x}" - ); - first += RUN; - } - admitted - }) - }) - .collect(); - workers - .into_iter() - .map(|worker| worker.join().expect("worker")) - .sum() - }); - // Every magnitude pattern from `0` to the ceiling is admitted at either sign, except the - // one `-0.0` pattern. - let expected = 2 * (u64::from(ELISION_MAGNITUDE_CEILING) + 1) - 1; - assert_eq!(admitted, expected, "admitted words"); - } - /// A channel with the two general bands at sections 1 and 3 live on every lane, and each /// dedicated cut live only on the lanes whose bit is set in its mask: dry on the others. fn dry_cut_channel( diff --git a/docs/derivations/241-schema-repins.md b/docs/derivations/241-schema-repins.md index a9a118e42..b7bef12c5 100644 --- a/docs/derivations/241-schema-repins.md +++ b/docs/derivations/241-schema-repins.md @@ -267,3 +267,6 @@ prose reason recorded in the commit was not, and this document supersedes it. The companion assertion `a_delayed_session_is_a_different_plan` keeps its meaning: a 480-sample delay changes `total_delay_samples` and `delay_bytes` away from 0, so the digest is not inert. + +(2026-10-18: the TA1 test policy cut deleted the zero-delay digest pin and this companion +assertion. This document is history.) diff --git a/hosts/host-web/tests/control_command_timing.rs b/hosts/host-web/tests/control_command_timing.rs index c112e5ff7..b0cea43bf 100644 --- a/hosts/host-web/tests/control_command_timing.rs +++ b/hosts/host-web/tests/control_command_timing.rs @@ -1,15 +1,8 @@ -//! #1612 and #1613, descriptive: the time of one whole `control_command` (the browser's one entry -//! for a `SET_CONTROL` transaction, which is the control plane's `SessionState::command` and so the -//! C ABI's too) for a fader edit on a session of many tracks, native. +//! Batch 7 review M1 and M2: the time of one whole `control_command` (the browser's one entry for a +//! `SET_CONTROL` transaction, which is the control plane's `SessionState::command` and so the C +//! ABI's too) for a VCA offset move on a session at the VCA bound, native, in release. //! -//! Ignored: it prints one line per size and asserts nothing about time. -//! `cargo test --release -p host-web --features test-support --test control_command_timing -- -//! --ignored --nocapture` -//! -//! Recorded on this box (release, x86-64): after #1612 (the classifier from the rows, the model still -//! cloned and compiled) 267 us at 64 tracks and 1.11 ms at 256; after #1613 (the edit committed in -//! place) about 19 us at both. The C ABI's pair, with the full preparation forced on the same -//! session, is `in_place_command_timing_line` in `crates/capi/src/runtime/live_tests.rs`. +//! Ignored, because it asserts time; the nightly runs it in release. #![cfg(feature = "test-support")] @@ -24,100 +17,8 @@ use protocol::{ use session::{ParameterChannel, StableId, canonical_session_json, parse_session_json}; const FIXTURE: &str = include_str!("../../../fixtures/session/v1/parametric-eq-nine-track.json"); -const FADER_DB: u32 = 5; const CALLS: u64 = 200; -fn frame(request: u64, revision: u64, strip: &str, db: f32) -> Vec { - let edits = [SessionEdit::SetControl { - expected_effect: None, - address: ControlAddress { - owner: Owner::Strip(StableId::parse(strip).expect("strip ID")), - section: Section::Builtins, - control: FADER_DB, - lane: ParameterChannel::Both, - }, - value: db, - ramp_samples: None, - }]; - let mut bytes = vec![0_u8; 4_096]; - let length = ProtocolCodec::default() - .encode_command_frame_into( - &TypedCommandFrame { - request_id: RequestId::new(request).expect("nonzero request"), - expected_revision: ExpectedRevision::Exact(SessionRevision(revision)), - payload: CommandPayload::SessionTransactionApply(&edits), - }, - &mut bytes, - ) - .expect("a transaction frame"); - bytes.truncate(length); - bytes -} - -#[test] -#[ignore = "descriptive timing"] -fn control_command_timing_line() { - std::thread::spawn(|| { - for tracks in [64_usize, 256] { - let mut model = parse_session_json(FIXTURE).expect("the fixture parses"); - model.console.pre_insert.clear(); - model.console.post_insert.clear(); - let template = model.tracks[0].clone(); - let route = model.routes[0].clone(); - model.tracks.clear(); - model.routes.clear(); - for index in 0..tracks { - let mut track = template.clone(); - track.id = StableId::parse(&format!("t{index:04}")).expect("track ID"); - track.console.clear(); - track.inserts.effects.clear(); - let mut route = route.clone(); - route.id = StableId::parse(&format!("t{index:04}-main")).expect("route ID"); - route.source = session::RouteSource::Track { - track_id: track.id.clone(), - tap: session::SendTap::PostPan, - }; - model.tracks.push(track); - model.routes.push(route); - } - let document = canonical_session_json(&model).expect("canonical"); - let options = WebBootOptions::explicit_defaults(); - let length = - native_staging::boot(options, document.as_bytes()).expect("the session stages"); - let handle = host_web::miso_engine_web_v1_boot(length); - assert_ne!(handle, 0, "the session boots"); - let mut spent = std::time::Duration::ZERO; - for (revision, call) in (model.revision..).zip(0..CALLS) { - let db = if call % 2 == 0 { -6.0 } else { -3.0 }; - let bytes = frame(call + 1, revision, "t0003", db); - let start = Instant::now(); - let (result, _) = - native_staging::control_command(handle, &bytes).expect("a live handle"); - spent += start.elapsed(); - assert_eq!(result, RESULT_OK, "call {call}"); - // The reliable lane holds two commit events; an undrained third commit would be - // refused with backpressure, so drain the way the worklet does. - for lane in [host_web::EVENT_LANE_RELIABLE, host_web::EVENT_LANE_LOSSY] { - loop { - let (result, event) = native_staging::control_event(handle, lane); - assert_eq!(result, RESULT_OK, "event lane {lane}"); - if event.is_empty() { - break; - } - } - } - } - println!( - "control_command fader edit, {tracks} tracks: {:?} per call", - spent / u32::try_from(CALLS).expect("calls") - ); - assert_eq!(host_web::miso_engine_web_v1_dispose(handle), RESULT_OK); - } - }) - .join() - .expect("the run completes"); -} - const VCA_OFFSET_DB: u32 = 1; /// The bound of ruling V-Q3 (`host_core::MAXIMUM_VCAS_BOUNDED`, `MAXIMUM_VCA_REACH_PAIRS`): 256 /// VCAs and 16,384 (strip, VCA) pairs, here 64 tracks under a chain of 256 VCAs. diff --git a/tools/audit/src/fixture_builtins.rs b/tools/audit/src/fixture_builtins.rs index 58811a755..b1cbe0759 100644 --- a/tools/audit/src/fixture_builtins.rs +++ b/tools/audit/src/fixture_builtins.rs @@ -5283,110 +5283,6 @@ mod tests { ); let manifest = parse_manifest(&root).expect("checked manifest"); assert_eq!(manifest.entries.len(), 50, "frozen checked payload count"); - assert_eq!( - sha256(&fs::read(root.join("MANIFEST.tsv")).expect("checked manifest bytes")), - // Re-pinned by issue #210 phase 2: the two `prepare_256_tracks` workload fixtures name - // `fixtures/session/v1/canonical.json` by digest, and that session grew a required - // `builtins.*.delay_samples` key. The payload count is unchanged. - // - // Re-pinned again by issue #210 phase 3: `resources.jsonl` alone moved -- the strip's - // input section is deferred to lowering with the fader and the matrix, so preparation - // retains no binding vector and boxes no processor, and `InputStage` carries the live - // trim ramp. No PCM, meter, response or benchmark fixture moved, which is what a - // control-plane-and-transient-path phase should look like from here: the render bits - // of every checked case are the bits they were. - // - // Re-pinned by issue #241: the same two preparation workloads now name the canonical - // session by its source-content identity after the source-schema migration. Their - // payload lengths and every render-bearing fixture remain unchanged. - // Re-pinned by issue #519: the metric-selection field grows `MeterAccumulator` by 8 - // bytes after alignment while `MeterSnapshot` remains 160 by using existing padding. - // Re-pinned by issue #816: `GraphNodeObserverBinding` grows from 80 to 88 bytes after - // the controlled-activation flag is added; only the resource rows and this joined - // manifest identity move, while PCM, meter, response and benchmark payloads remain. - // Re-pinned by issue #818 A1: `MeterSnapshot` and `MeterAccumulator` grow to their - // actual Rust layouts (168 and 240 bytes); A2's request seal remains 56 bytes. Only - // the resource rows and this joined manifest identity move. - // Re-pinned by issue #1080: the controlled-activation flag goes again, so - // `GraphNodeObserverBinding` is 80 bytes and each meter row loses eight bytes per - // meter; only the resource rows and this joined manifest identity move. - // Re-pinned again by issue #1080: A1's observation generation goes too, so - // `MeterSnapshot` is 160 and `MeterAccumulator` 232 bytes; only the resource rows and - // this joined manifest identity move. - // Re-pinned by issue #1093: `canonical.json` moved to decision 12's console shape, so - // the two `prepare_256_tracks` workloads name its new digest (and the `post_pan` tap); - // no PCM, meter, response or resource payload moved. - // Re-pinned by issue #1216: route field `mute` added, value false, so `canonical.json` - // and the two `prepare_256_tracks` workloads naming it move; nothing else moved. - // Re-pinned by issue #1218: route field `follows_mute` added, value false, so - // `canonical.json` and the two `prepare_256_tracks` workloads naming it move; nothing - // else moved. - // Re-pinned by issue #1240: root key `vcas` added, empty, so `canonical.json` and the - // two `prepare_256_tracks` workloads naming it move; nothing else moved. - // Re-pinned by issue #1328 amendment A9: `resources.jsonl` moves by the input - // section's two `f32` silence counters and its four carried `f32` constants (+24 - // bytes per input stage, counted in the preparation strip and in the bound - // processor), and `reference/filter-response.csv` - // moves in `impulse_dft_magnitude_db` alone (565 of 1,630 rows, at most 3.2e-5 dB, at - // probes 187 dB down) because the twin's one-second impulse now arms the joint flush - // after `N_SILENCE` zero samples. No PCM, meter or benchmark fixture moved. - // Re-pinned by issue #1451: `resources.jsonl` alone moves, by the four carried `f32` - // chain constants leaving the input section's coefficients (-16 bytes per input - // stage, counted in the preparation strip and in the bound processor). No PCM, meter, - // response or benchmark fixture moved. - // Re-pinned by issue #1329: `resources.jsonl` alone moves, by the strip bounds kept - // beside each tail (`(Box, InputSectionBound)`, 72 bytes, against 24 for the - // `BuiltinTail` entry before: +48 bytes in each of the payload's and the seal's tail - // vectors, +96 per track in both payload counts). The input section and the strip - // preparation keep their size, so the largest allocation does not move. No PCM, - // meter, response or benchmark fixture moved. - // Re-pinned by issue #1464: `resources.jsonl` alone moves, by the `composition` field - // each strip's bound gains (`(Box, NodeTailBound)`, 104 bytes, against 72: +32 - // bytes in each of the payload's and the seal's tail vectors, +64 per track in both - // processor payload counts). The largest allocation and the allocation counts do not - // move. No PCM, meter, response or benchmark fixture moved. - // Re-pinned by issue #1484: `resources.jsonl` alone moves, by the second stall the - // composition gains (`peak_stall` and `tail_stall` replace `stall`; `(Box, - // NodeTailBound)` 112 bytes, against 104: +8 bytes in each of the payload's and the - // seal's tail vectors, +16 per track in both processor payload counts). The largest - // allocation and the allocation counts do not move. No PCM, meter, response or - // benchmark fixture moved. - // Re-pinned by issue #1312: `resources.jsonl` alone moves, by +8 bytes per track in - // every processor payload count (`StripPreparation` 1080 -> 1088: its control - // consumers hold the fader and matrix cell readers in place of two ring consumers). - // The allocation counts do not move; `maximum_single_allocation_bytes` does, by the - // same +8 bytes per track, because the strip vector is the largest allocation. No PCM, - // meter, response or benchmark fixture moved. - // - // Re-pinned by issue #1346: `resources.jsonl` alone moves, by +48 bytes per track in - // every processor payload count and in the largest allocation (`StripPreparation` - // 1088 -> 1136: its control consumers hold the input cell reader, 88 bytes, in place - // of the 40-byte input ring consumer). The allocation counts do not move. No PCM, - // meter, response or benchmark fixture moved. - // - // Re-pinned by issue #1054 (D1a): root key `control_smoothing` added, `default`, so - // `canonical.json` and the two `prepare_256_tracks` workloads naming it move, by the - // `session_template_sha256` of `canonical.json`. No PCM, meter, response or resource - // fixture moved. - // - // Re-pinned by issue #1558: `resources.jsonl` alone moves, by +2 bytes in every - // processor payload count and +1 retained allocation per row (the routes' effective - // follow gates, `Box<[[bool; 2]]>`, one `[bool; 2]` per route, kept for graph - // lowering; the fixture session declares one route at every track count). The largest - // allocation does not move. No PCM, meter, response or benchmark fixture moved. - // - // Re-pinned by issue #1609: `GraphNodeObserverBinding` grows from 80 to 88 bytes after - // the bind-time `block_end` acceptance flag is added; only the meter-bearing resource - // rows (eight bytes per meter) and this joined manifest identity move. - // - // Re-pinned by issue #1630 carry marks (batch 14b review fold): each input, fader and - // matrix cell reader keeps two u16 masks, so `resources.jsonl` alone moves, by +24 - // bytes per track in every processor payload count and in the largest allocation - // (`StripPreparation` 1136 -> 1160). The allocation counts do not move. No PCM, - // meter, response or benchmark fixture moved. - "58ba5b37f3ae85692472b2c36731c3a5ab56f9aef323e376c9bc820699fc75f7", - "accepted joined-corpus manifest identity" - ); remove_temporary_root(root); } diff --git a/tools/console-workload/src/lib.rs b/tools/console-workload/src/lib.rs index 6a99db19d..507c5f576 100644 --- a/tools/console-workload/src/lib.rs +++ b/tools/console-workload/src/lib.rs @@ -2576,126 +2576,6 @@ mod tests { ); } - /// Issue #956 gate 3 (#936 gate 5, re-homed): on the driver-fed gain/pan row, the runtime - /// metadata the compile with builtins charges grows by exactly the byte lengths it charges for - /// the executor's two bind-sized tables, and the tables the row binds fit in them. - /// - /// The row's graph is the one `build_full` compiles: `prepare_session_builtins` with no meter, - /// then `compile_with_builtins` at the native width. Past the semantic estimate its graph - /// metadata carries two charges. The runtime metadata is recomputed here from the compile's own - /// inputs: one emitted op per scheduled node and one response owner per track (its id, - /// `input-filters` and `miso.builtin.input-filters`) -- the row prepares no effect. The bank - /// slot reservation is recomputed from the builtin banks the artifact retains: one slot per - /// bank, masked at its width. Nothing else is charged there: the strip is banked whole at a - /// vector width, so no scalar owner is retained, and with no effect there is no effect control - /// or effect bank. The runtime metadata is the terms it carried before issue #936 -- the - /// split-table field, the per-op layout delta, the observation state and the response owners, - /// none of which the issue moved (`graph`'s - /// `runtime_metadata_charge_covers_mixed_ops_once_and_refuses_overflow` pins that the executor's - /// layout witnesses carry the new table) -- plus the two tables, each at one entry per emitted - /// op, and the block-end unit table of issue #1609 (one `u32` per emitted op). The row binds every - /// unit but its sixty-four inputs as dispatched units (4 bytes each) and copies no claim. - #[test] - fn the_driver_fed_rows_metadata_charge_grows_by_exactly_the_executor_tables() { - let workload = Workload::SixtyFourTrackGainPanRing; - let model = console_model(workload); - let session = compile_session(&model, compile_caps()).expect("compiled console session"); - let registry = launch_native_effect_registry().expect("launch effect registry"); - let effects = prepare_native_session_effects(&session, registry, effect_caps()) - .expect("prepared console effects"); - let builtins = builtins_compiler::prepare_session_builtins(&session, &[], builtin_caps()) - .expect("prepared console builtins"); - let Ok(artifact) = GraphCompiler::compile_with_builtins(GraphBuiltinsCompileRequest { - input_floors: Default::default(), - dispatch: Backend::current(), - plan_id: PLAN_ID, - effects, - builtins, - caps: graph_caps(), - }) else { - panic!("gain/pan console graph"); - }; - let emitted = artifact - .graph() - .dependency_levels - .iter() - .map(|level| level.nodes.len() as u64) - .sum::(); - let fixed = ["input-filters".len(), "miso.builtin.input-filters".len()]; - let (strings, largest) = - model - .tracks - .iter() - .fold((0_u64, 0_u64), |(total, largest), track| { - let id = track.id.as_str().len() as u64; - ( - total + id + (fixed[0] + fixed[1]) as u64, - largest.max(id).max(fixed[0] as u64).max(fixed[1] as u64), - ) - }); - let resource = - graph::GraphRuntimeMetadataResourceEstimate::checked_for_with_response_bindings( - emitted, - model.tracks.len() as u64, - strings, - largest, - ) - .expect("runtime metadata"); - let banks: Vec<_> = artifact.prepared_builtin_banks().collect(); - assert!(!banks.is_empty(), "the gain/pan strip is banked"); - let mask_bytes = banks - .iter() - .map(|bank| u64::from(bank.width.lanes())) - .max() - .expect("one bank at least") - * core::mem::size_of::() as u64; - let slots = - graph::GraphBankSlotResourceEstimate::checked_for_mask(banks.len() as u64, mask_bytes) - .expect("bank slot reservation"); - let report = artifact.report(); - assert_eq!( - report.estimate.graph_metadata_bytes - report.semantic_estimate.graph_metadata_bytes, - resource.total_bytes + slots.total_bytes, - "the runtime metadata and the bank slot reservation are the row's only charges past \ - the semantic estimate" - ); - let tables = [ - emitted * core::mem::size_of::() as u64, - emitted * core::mem::size_of::<(usize, u32)>() as u64, - ]; - assert_eq!( - [ - resource.active_unit_table_bytes, - resource.source_input_table_bytes - ], - tables, - "each table at one entry per emitted op" - ); - assert_eq!( - resource.block_end_unit_table_bytes, - emitted * core::mem::size_of::() as u64, - "the block-end unit table at one entry per emitted op" - ); - let before = resource.runtime_field_bytes - + resource.emitted_op_layout_delta_bytes * emitted - + resource.response_binding_table_bytes - + resource.response_binding_string_bytes; - assert_eq!( - resource.total_bytes, - before + tables[0] + tables[1] + resource.block_end_unit_table_bytes, - "the charge grows by exactly the two executor tables and the block-end unit table" - ); - let ring = SessionRuntime::new(workload); - let bound = graph::test_only_executor_table_bytes(); - let dispatched = ring.unit_eligibility().len() as u64 - u64::from(workload.tracks()); - assert_eq!( - bound, - [dispatched * core::mem::size_of::() as u64, 0], - "the row dispatches every unit but its inputs and copies no claim" - ); - assert!(bound[0] <= tables[0] && bound[1] <= tables[1]); - } - /// The driver's five methods, called directly under the realtime audit: `played_planes` lends /// exactly the words `copy_track_input` copies, claim by claim, and both are the claimed /// track's frozen block through that track's declared channel mapping. @@ -3208,8 +3088,7 @@ mod tests { /// derived from D1 rather than pinned, and every send in it is active. /// /// Compiled through the calls `build_full` makes at [`PlanConfig::BASELINE`] (no meters, no - /// live controls, `Concurrent` builtins), on the precedent of - /// `the_driver_fed_rows_metadata_charge_grows_by_exactly_the_executor_tables`. The fold count + /// live controls, `Concurrent` builtins). The fold count /// is printed for the baseline's report, not asserted (#1227 D6 says why). /// /// Red mutations: drop the row's `console_model` arm (it renders the intended fixture: 64 diff --git a/tools/console-workload/tests/gain_pan_profile.rs b/tools/console-workload/tests/gain_pan_profile.rs index 810a47771..b72a222f4 100644 --- a/tools/console-workload/tests/gain_pan_profile.rs +++ b/tools/console-workload/tests/gain_pan_profile.rs @@ -29,10 +29,8 @@ //! is one cohort's builtin strip in its fixed order (AGENTS.md: input section, fader, pan //! matrix), so slot 0 is the input section, slot 1 the fader and slot 2 the pan matrix. //! -//! `digests` (also ignored) prints the 64-block digest of every native session row with the probes -//! off and on, so a change to the probes can be checked against a base commit's bits in one -//! command. `probes_split_the_bank_units_and_move_no_bit` is the standing, untimed gate: the -//! probes fire where they claim to and render the row's bits. +//! `probes_split_the_bank_units_and_move_no_bit` is the standing, untimed gate: the probes fire +//! where they claim to and render the row's bits. //! //! Cycles are derived, not counted: the core clock is calibrated from a dependent chain of scalar //! `f32` adds at [`FADD_LATENCY_CYCLES`] cycles each (Zen 3), timed by the same monotonic clock the @@ -42,7 +40,7 @@ use std::hint::black_box; use std::time::Instant; use bench_support::digest::Sha256Sink; -use console_workload::{PlanConfig, QUANTUM, SessionRuntime, Workload, native_session_rows}; +use console_workload::{PlanConfig, QUANTUM, SessionRuntime, Workload}; use graph::test_only_phase_profile as profile; use profile::bank; @@ -422,21 +420,6 @@ fn digest(workload: Workload, blocks: u64) -> String { hash.finish_hex() } -#[test] -#[ignore = "measurement harness; run with --ignored --nocapture"] -fn digests() { - for workload in native_session_rows() { - profile::enable(false); - let off = digest(workload, 64); - profile::reset(); - profile::enable(true); - let on = digest(workload, 64); - profile::enable(false); - println!("{:<44} {off}", workload.kind()); - assert_eq!(on, off, "{}: the probes moved a bit", workload.kind()); - } -} - /// The probes fire where they claim to, and a profiled render is the row's own bits. /// /// Untimed. Every chain run opens and closes one sub-phase profile, gathers once and scatters