From ff660290a65ab0287a5fcf5fa4f9f07121a659fc Mon Sep 17 00:00:00 2001 From: Simon Keimer Date: Mon, 7 Sep 2026 20:27:42 +0200 Subject: [PATCH 1/3] chore: silence needless_late_init so the local gate can run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four identical "resolve effective wire endpoint list" blocks — three in `nec_solver/src/linear.rs`, one in `nec-cli/src/solve_session.rs` — trip `clippy::needless_late_init` on clippy 1.98. CI pins 1.97.1 and does not see them, so they are not a CI failure today; but `.githooks/pre-commit` runs the workspace clippy with `-D warnings` against whatever toolchain is installed, so on a 1.98 host every commit in this repo needs `--no-verify`. A quality gate that has to be bypassed in order to commit is not a gate. Mechanical: clippy's own suggested form, no behaviour change. `nec_solver`'s 232 lib tests pass unchanged and the workspace clippy is clean afterwards. Unrelated to the plane-wave fix on this branch; separated into its own commit for that reason. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018p7FxX7QMWNJVNp9LbaLkB --- apps/nec-cli/src/solve_session.rs | 9 ++++----- crates/nec_solver/src/linear.rs | 27 ++++++++++++--------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/nec-cli/src/solve_session.rs b/apps/nec-cli/src/solve_session.rs index 8dd5b3d..0e04680 100644 --- a/apps/nec-cli/src/solve_session.rs +++ b/apps/nec-cli/src/solve_session.rs @@ -192,14 +192,13 @@ pub(super) fn residual_hallen( let n = z.n; let mut r = vec![Complex64::new(0.0, 0.0); n]; - let endpoints: &[(usize, usize)]; let fallback_endpoints; - if wire_endpoints.is_empty() || n == 0 { + let endpoints: &[(usize, usize)] = if wire_endpoints.is_empty() || n == 0 { fallback_endpoints = if n > 0 { vec![(0usize, n - 1)] } else { vec![] }; - endpoints = &fallback_endpoints; + &fallback_endpoints } else { - endpoints = wire_endpoints; - } + wire_endpoints + }; let mut row_wire = vec![0usize; n]; for (wi, &(first, last)) in endpoints.iter().enumerate() { diff --git a/crates/nec_solver/src/linear.rs b/crates/nec_solver/src/linear.rs index b6a6c6f..369ae5c 100644 --- a/crates/nec_solver/src/linear.rs +++ b/crates/nec_solver/src/linear.rs @@ -421,14 +421,13 @@ pub fn solve_hallen_sinusoidal_basis( } // Resolve effective wire endpoint list. - let endpoints: &[(usize, usize)]; let fallback_endpoints; - if wire_endpoints.is_empty() || n == 0 { + let endpoints: &[(usize, usize)] = if wire_endpoints.is_empty() || n == 0 { fallback_endpoints = if n > 0 { vec![(0usize, n - 1)] } else { vec![] }; - endpoints = &fallback_endpoints; + &fallback_endpoints } else { - endpoints = wire_endpoints; - } + wire_endpoints + }; // If any wire has fewer than 2 segments, fall back to standard Hallén. if endpoints.iter().any(|&(first, last)| last <= first) { @@ -686,14 +685,13 @@ pub fn solve_hallen( } // Build the endpoint constraint list: per-wire if supplied, else global endpoints. - let endpoints: &[(usize, usize)]; let fallback_endpoints; - if wire_endpoints.is_empty() || n == 0 { + let endpoints: &[(usize, usize)] = if wire_endpoints.is_empty() || n == 0 { fallback_endpoints = if n > 0 { vec![(0usize, n - 1)] } else { vec![] }; - endpoints = &fallback_endpoints; + &fallback_endpoints } else { - endpoints = wire_endpoints; - } + wire_endpoints + }; // Build the set of endpoint segment indices that participate in at least one // junction constraint. These will receive a continuity constraint rather than @@ -921,14 +919,13 @@ pub fn solve_hallen_planewave( }); } - let endpoints: &[(usize, usize)]; let fallback_endpoints; - if wire_endpoints.is_empty() || n == 0 { + let endpoints: &[(usize, usize)] = if wire_endpoints.is_empty() || n == 0 { fallback_endpoints = if n > 0 { vec![(0usize, n - 1)] } else { vec![] }; - endpoints = &fallback_endpoints; + &fallback_endpoints } else { - endpoints = wire_endpoints; - } + wire_endpoints + }; let w = endpoints.len(); // Two endpoint constraints (I=0 at first and last) per wire. From f80e406f5f384d0916ca17fea6215ef7abe19745 Mon Sep 17 00:00:00 2001 From: Simon Keimer Date: Mon, 7 Sep 2026 20:28:01 +0200 Subject: [PATCH 2/3] fix(solver): let a collinear split receive a plane wave, as it already transmits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build_planewave_hallen` grouped segments by raw `GW` card while its delta-gap sibling `build_hallen_rhs` grouped by merged conductor — and while `solve_hallen_planewave`, which consumes what it builds, was already being handed the merged list by its caller. So a straight wire written as two collinear `GW` cards was seen as a junction and refused outright, though the same geometry driven by `EX 0` or `EX 4` solves (FND-142). The merged list now drives all three uses at once: the junction test, the segment grouping, and the along-wire coordinate. Fixing only the junction test would have been the worse bug — see the sabotage below. Gated by an equality, not a similarity: the pair is segmented identically (25 + 25 against 50 over the same span), so every segment midpoint coincides and nothing differs but the card boundary. Measured relative agreement 8.1e-12. Sabotage-verified in two halves, because this one-line change does two things: - revert the merge outright -> the deck is refused again, test fails; - keep the merged junction test but restore the per-`GW` grouping -> the deck SOLVES, at relative error 1.0004. That is what the blanket refusal had been protecting against, and it is why the coordinate half matters as much as the gate half. A genuine bent junction is still refused, pinned by its own test: a bend is not a collinear continuation, so the merge is a no-op there. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018p7FxX7QMWNJVNp9LbaLkB --- crates/nec_solver/src/planewave.rs | 29 ++++- crates/nec_solver/tests/collinear_merge.rs | 135 +++++++++++++++++++++ docs/project/findings-ledger.md | 2 +- 3 files changed, 159 insertions(+), 7 deletions(-) diff --git a/crates/nec_solver/src/planewave.rs b/crates/nec_solver/src/planewave.rs index bd505a8..507324f 100644 --- a/crates/nec_solver/src/planewave.rs +++ b/crates/nec_solver/src/planewave.rs @@ -153,12 +153,16 @@ fn dot(a: [f64; 3], b: [f64; 3]) -> f64 { /// Build the Hallén forcing + homogeneous columns for the first incident /// plane-wave EX card in `deck`. /// -/// Supports one or more **straight, non-junctioned** wires (e.g. a parallel -/// dipole array). Each wire carries its own Hallén particular solution: the -/// tangential field uses that wire's axis, the along-wire coordinate is measured -/// from that wire's midpoint, and the `sin(k|sₘ−s_p|)` kernel sums only over -/// segments on the same wire. Junctioned geometry is rejected (its continuity +/// Supports one or more **straight, non-junctioned** conductors (e.g. a parallel +/// dipole array). Each conductor carries its own Hallén particular solution: the +/// tangential field uses that conductor's axis, the along-wire coordinate is +/// measured from its midpoint, and the `sin(k|sₘ−s_p|)` kernel sums only over +/// segments on the same conductor. Junctioned geometry is rejected (its continuity /// constraints are not modelled by [`crate::solve_hallen_planewave`]). +/// +/// "Conductor" rather than "`GW` card": a straight wire split across several +/// collinear `GW` cards is one conductor here, via +/// [`crate::geometry::merge_collinear_wire_endpoints`]. pub fn build_planewave_hallen( deck: &NecDeck, segs: &[Segment], @@ -174,7 +178,20 @@ pub fn build_planewave_hallen( .ok_or(PlaneWaveError::NoPlaneWaveCard)?; let n = segs.len(); - let wire_endpoints = crate::geometry::wire_endpoints_from_segs(segs); + // Collinear `GW` splits are merged into one logical conductor, exactly as the + // delta-gap sibling `crate::build_hallen_rhs` does — and as + // `crate::solve_hallen_planewave`, which consumes what this builds, already + // assumed: its caller hands it `merge_collinear_wire_endpoints`, so a raw + // per-`GW` list here meant the builder and the solver disagreed about what a + // wire is. All three uses below need the merged list, not just the junction + // test: the `sin(k|s_m - s_p|)` sum must run over the whole conductor, and `s` + // must be measured from the conductor's midpoint rather than reset at a split. + // + // On geometry with no collinear split this is a strict no-op — the merge + // returns exactly `wire_endpoints_from_segs` there — so no deck that solved + // before changes, and a genuine T/Y junction is still detected and refused + // (FND-142). + let wire_endpoints = crate::geometry::merge_collinear_wire_endpoints(segs); if !crate::geometry::detect_wire_junctions( segs, &wire_endpoints, diff --git a/crates/nec_solver/tests/collinear_merge.rs b/crates/nec_solver/tests/collinear_merge.rs index 7abe427..803577d 100644 --- a/crates/nec_solver/tests/collinear_merge.rs +++ b/crates/nec_solver/tests/collinear_merge.rs @@ -187,3 +187,138 @@ fn merge_joins_collinear_same_radius_chain() { // Two 10-seg wires → one merged block spanning all 20 segments. assert_eq!(merge_collinear_wire_endpoints(&segs), vec![(0, 19)]); } + +/// The **receive** twin of `collinear_chain_recovers_single_wire_impedance`. +/// +/// A straight conductor split across two collinear `GW` cards, lit by an incident +/// plane wave, must carry the same induced current as the same conductor written +/// as one card. It did not: it was **refused outright** (FND-142). The delta-gap +/// builder merged collinear splits and the plane-wave builder did not, so +/// `detect_wire_junctions` saw the join as a junction and +/// `build_planewave_hallen` rejected the deck — while +/// `solve_hallen_planewave`, which consumes what that builder produces, was +/// already being handed the *merged* endpoint list by its caller. +/// +/// The two decks are segmented **identically** (25 + 25 against 50 over the same +/// span, so every segment midpoint coincides). That is what makes this an +/// equality rather than a similarity: nothing differs but the card boundary and +/// the bookkeeping it drives, so any residual is the basis disagreeing with +/// itself. +#[test] +fn collinear_chain_recovers_single_wire_plane_wave_currents() { + // Broadside incidence (θ = 90°, φ = 0, η = 0) puts ê along ẑ, parallel to the + // dipole: maximum coupling. A near-axial wave would drive the conductor to + // almost nothing and let this pass on two vectors of noise. + let ex1 = ExCard { + excitation_type: 1, + tag: 0, + segment: 0, + i4: 0, + voltage_real: 90.0, // θ_inc, degrees + voltage_imag: 0.0, // φ_inc, degrees + polarization_deg: 0.0, + polarization_ratio: 0.0, + theta_inc: 0.0, + phi_inc: 0.0, + }; + + let gw = |tag: u32, segments: u32, z0: f64, z1: f64| { + Card::Gw(GwCard { + tag, + segments, + start: [0.0, 0.0, z0], + end: [0.0, 0.0, z1], + radius: 0.001, + }) + }; + + let mut whole = NecDeck::new(); + whole.cards.push(gw(1, 50, -5.282, 5.282)); + whole.cards.push(Card::Ex(ex1.clone())); + + let mut split = NecDeck::new(); + split.cards.push(gw(1, 25, -5.282, 0.0)); + split.cards.push(gw(2, 25, 0.0, 5.282)); + split.cards.push(Card::Ex(ex1)); + + let induced = |deck: &NecDeck| -> Vec { + let segs = build_geometry(deck).unwrap(); + let z = assemble_z_matrix_with_ground(&segs, FREQ, &GroundModel::FreeSpace); + solve_hallen_planewave_routed(deck, &segs, &z, FREQ) + .expect("a collinear split is one conductor, not a junction") + }; + + let i_whole = induced(&whole); + let i_split = induced(&split); + assert_eq!(i_whole.len(), 50); + assert_eq!(i_split.len(), 50); + + // The split really is a split: two `GW` cards that merge to one conductor. + let split_segs = build_geometry(&split).unwrap(); + assert_eq!(wire_endpoints_from_segs(&split_segs).len(), 2); + assert_eq!(merge_collinear_wire_endpoints(&split_segs), vec![(0, 49)]); + + // Floor: an all-zero pair would satisfy every ratio below. The measured peak + // is ~1.23 mA; this only has to exclude nothing. + let peak = i_whole.iter().map(|c| c.norm()).fold(0.0, f64::max); + assert!( + peak > 1e-4, + "fixture induces no current, so the comparison is vacuous: peak = {peak:e}" + ); + + let worst = i_whole + .iter() + .zip(&i_split) + .map(|(a, b)| (a - b).norm()) + .fold(0.0, f64::max); + assert!( + worst / peak < 1e-9, + "split conductor disagrees with the same conductor written whole: \ + worst |Δ| = {worst:e} against peak |I| = {peak:e} (relative {:e})", + worst / peak + ); +} + +/// A genuine junction is still refused — the merge must not have widened the +/// gate it was narrowing. A bent (non-collinear) pair shares an endpoint, so the +/// merge is a no-op and the junction test still fires. +#[test] +fn a_bent_junction_is_still_refused_on_the_plane_wave_path() { + let ex1 = ExCard { + excitation_type: 1, + tag: 0, + segment: 0, + i4: 0, + voltage_real: 90.0, + voltage_imag: 0.0, + polarization_deg: 0.0, + polarization_ratio: 0.0, + theta_inc: 0.0, + phi_inc: 0.0, + }; + let mut bent = NecDeck::new(); + bent.cards.push(Card::Gw(GwCard { + tag: 1, + segments: 21, + start: [-5.0, 0.0, 0.0], + end: [0.0, 0.0, 3.0], + radius: 0.001, + })); + bent.cards.push(Card::Gw(GwCard { + tag: 2, + segments: 21, + start: [0.0, 0.0, 3.0], + end: [5.0, 0.0, 0.0], + radius: 0.001, + })); + bent.cards.push(Card::Ex(ex1)); + let segs = build_geometry(&bent).unwrap(); + assert_eq!( + merge_collinear_wire_endpoints(&segs), + wire_endpoints_from_segs(&segs), + "a bend is not a collinear continuation, so the merge must be a no-op here" + ); + let err = build_planewave_hallen(&bent, &segs, FREQ) + .expect_err("a bent junction is not modelled by the per-conductor basis"); + assert_eq!(err, PlaneWaveError::JunctionedGeometryNotSupported); +} diff --git a/docs/project/findings-ledger.md b/docs/project/findings-ledger.md index ad0fa6b..c824508 100644 --- a/docs/project/findings-ledger.md +++ b/docs/project/findings-ledger.md @@ -44,7 +44,7 @@ An `open` row is not a failure — it is the point. What the process forbids is |:---|:------|:------|:--------|:-----------------| | FND-144 | 2026-08-31 | open | **[low] `cargo test -p nec_accel` does not compile: its four `tests/*.rs` binaries need the `wgpu` feature and nothing declares it.** `crates/nec_accel/src/lib.rs:62` gates `wgpu_device` and its re-exports on `#[cfg(feature = "wgpu")]`, and the four GPU test files import those symbols unconditionally, so a per-crate run fails with four `E0432 unresolved import` errors. It only works under `cargo test --workspace`, where feature unification turns `wgpu` on because another member asks for it — so the whole-workspace gate is green and the natural per-crate command is broken. | Found 2026-08-31 while measuring test counts for FND-065. Reproduced: `cargo test -p nec_accel --tests -- --list` exits 1; `cargo test --workspace` builds the same targets fine. The fix is `required-features = ["wgpu"]` on those four `[[test]]` targets, which makes cargo SKIP them rather than fail. Also the reason the same crate reports 22 lib tests alone and 26 in the workspace. | | FND-143 | 2026-08-31 | open | **[medium] `docs/project/test-catalog.md`'s counts had drifted by more than a factor of two, and every figure looked precise.** It claimed "~532 `#[test]` functions" and "539 passing across 53 test binaries"; the measured tree has **1086 tests (565 unit + 514 integration + 7 doctests), 1084 passing / 0 failed / 2 ignored across 93 reporting binaries**. (The first figures recorded here were 519/1084: measured before this cycle's own new tests, and wrong by 7 besides — the checker was attributing doctests to whichever `tests/*.rs` binary was listed last, because `Doc-tests` headers carry no `Running` line. It passed while doing so, being self-consistent with its own bug.) Per-crate rows were wrong by up to 132 (`nec_solver` claimed 100, has 232) and `nec-gui` had no row at all despite 91 unit tests. | Found 2026-08-31 while fixing FND-065, whose scope was only "the lib count is off by one". Fixed in the same change by DERIVING the numbers: `scripts/check-test-catalog-counts.py` enumerates what the harness will actually run (`cargo test --workspace -- --list`, not a `#[test]` grep) and fails the build on drift; wired into the `test` CI job, where the build is already warm. Sabotage-verified three ways: a crate row off by one, a wrong total marker, and a test ADDED to the tree with the doc untouched — the direction that actually causes the rot. **NOT fully closed, and the row says so:** the checker covers the per-crate unit rows and the three totals. The per-file integration table is still hand-maintained, and measuring it found **12 listed rows wrong** (`gui_smoke.rs` says 47, has 120; `geometry_diagnostics.rs` says 3, has 15) and **~30 test binaries with no row at all**. Checking it correctly needs `cargo test --message-format=json` to map each executable to its `src_path`, because two packages both ship `tests/current_source_junction.rs` and a file-stem key silently merges them — a first attempt at the check did exactly that and reported both rows against one measured count. Fixing those rows also means writing the 'Validates' and 'Gates' cells for 30 files, which is content, not counting. STAYS OPEN. #447 derived and CI-gated the per-crate unit rows and the three totals; the per-file integration table is still hand-maintained and still wrong, so the row is not resolved. Marking it fixed on the strength of the half that is done is exactly the state this ledger exists to prevent. | -| FND-142 | 2026-08-31 | open | **[low] A collinear-split deck driven by an incident plane wave is refused, where the same geometry driven by `EX 0` or `EX 4` solves.** `classify_paths` calls it `Reducible`, so the plane-wave arm takes the per-wire builder, and `build_planewave_hallen` refuses any geometry with a junction (`crates/nec_solver/src/planewave.rs:178`). The receive twin of FND-140's gap: there the same reducible-but-junctioned class was being wrongly refused by the current-source arm; here the plane-wave builder refuses it by construction. | Found by fable's diff review of #444, 2026-08-31, and verified by running `corpus/dipole-ex4-collinear-split-51seg.nec`'s geometry with an `EX 1` card: refused with "incident plane wave is supported on straight, non-junctioned wires", while the `EX 4` original solves 74.31 + j13.89. Behaviour is unchanged by #444 — recorded because nothing recorded it. | +| FND-142 | 2026-08-31 | fixed | **[low] A collinear-split deck driven by an incident plane wave is refused, where the same geometry driven by `EX 0` or `EX 4` solves.** `classify_paths` calls it `Reducible`, so the plane-wave arm takes the per-wire builder, and `build_planewave_hallen` refuses any geometry with a junction (`crates/nec_solver/src/planewave.rs:178`). The receive twin of FND-140's gap: there the same reducible-but-junctioned class was being wrongly refused by the current-source arm; here the plane-wave builder refuses it by construction. | Found by fable's diff review of #444, 2026-08-31, and verified by running `corpus/dipole-ex4-collinear-split-51seg.nec`'s geometry with an `EX 1` card: refused with "incident plane wave is supported on straight, non-junctioned wires", while the `EX 4` original solves 74.31 + j13.89. Behaviour is unchanged by #444 — recorded because nothing recorded it. Fixed in #448: `build_planewave_hallen` now takes `merge_collinear_wire_endpoints` rather than `wire_endpoints_from_segs`, the same list its delta-gap sibling `build_hallen_rhs` has always used and the same list `solve_hallen_planewave` was already being handed by its caller — so the builder and the solver had disagreed about what a wire is. The merged list drives all three uses at once: the junction test, the segment grouping, and the along-wire coordinate. Gated by `collinear_chain_recovers_single_wire_plane_wave_currents`, an equality on an identically-segmented pair (25+25 against 50 over the same span). Sabotage-verified in two halves: reverting the merge outright refuses the deck, and keeping the merged junction test with the old per-`GW` grouping *solves* it at **relative error 1.0004** — which is what the blanket refusal had been protecting against. | | FND-141 | 2026-08-31 | open | **[low] `refresh_editor_preview` clears the three viewport pending run ids only when the edit renders AND its geometry loads, so an edit that fails validation leaves them armed.** A half-typed coordinate fails `to_deck_string`, takes the `Err` arm (`apps/nec-gui/src/app_state.rs`), and a completion still in flight is then accepted over an edited document. Typing passes through invalid intermediate states routinely, so the window is not exotic. | Found by fable while reviewing the FND-133 design, 2026-08-31. Pre-dates that work: the discard site was written for the solver-switch case, where the document is always valid. FND-133's own two ids are cleared unconditionally at the top of the same function for exactly this reason; the three viewport legs were left as they are rather than changing behaviour that no finding asked about. | | FND-140 | 2026-08-31 | fixed | **[medium] The conductor-path routing decision has a FIFTH copy, and the `path_of`/`free_ends` assembly it guards has three.** Beyond the CLI receive copy recorded as FND-128, `crates/nec_solver/src/current_source.rs:121-134` re-derives both the predicate and the grouping. It is dead through the routed path today — `hallen_session.rs:359` calls it only when `grouped` is `None`, i.e. when `nontrivial_paths` already returned `None` — but `solve_current_source_hallen` is `pub` and re-exported (`crates/nec_solver/src/lib.rs:26`), so any external caller reaches the diverged copy. `hallen_session.rs:380` already records the branch as "a SECOND copy of the current-source decision". The grouping now exists at `hallen_session.rs:241-251`, `apps/nec-cli/src/solve_session.rs:294-302` and `current_source.rs:126-134`. | Found by fable's design review of the FND-128/FND-133 fix set, 2026-08-31; the review is the artifact. Fix them together with [[FND-128]]: the seam is the whole PlaneWave arm of `solve_hallen_routed_inner` (`hallen_session.rs:255-277`) extracted as a `pub fn` over `&ZMatrix`, not the predicate alone. The sweep cannot call `solve_hallen_routed` directly because that takes `&mut ZMatrix` and stamps load columns as one-shot deltas (`hallen_session.rs:224-237`), which per-direction calls would double-stamp. Fixed in #444 together with FND-128: `classify_paths` returns a three-way `PathRoute`, `group_paths` is the one grouping loop, and `current_source.rs` matches on all three arms. Sabotage-verified: merging `Reducible` with `Unsupported` fails `reducible_collinear_split_current_source_keeps_the_per_wire_basis`. | | FND-139 | 2026-08-31 | open | **[low] Two decks in `corpus/` carry no numerical gate at all: `dipole-ex4-collinear-split-51seg.nec` and `dipole-gm-nrpt2-freesp.nec`.** Neither appears in `reference-results.json`, and no test reads either file; both are covered only by `corpus_deck_sanity.rs`, which asserts a `GE` card is present. `dipole-gm-nrpt2-freesp.nec` records nec2c-captured segment counts, tags and z-centres in its `CE` comments, and `corpus/README.md:333` names it as covering the FND-119 discrimination — but the test that actually discriminates (`crates/nec_solver/tests/gm_nec2c.rs:52`) builds the same cards as an inline string, so editing the deck file breaks nothing. | Found 2026-08-31 while re-deriving FND-110's evidence tier. `grep -rl ` over `*.rs`/`*.py` returns no test for either deck; 56 `.nec` files are on disk against 46 distinct `deck_file` values in `reference-results.json`, and the other 8 unlisted decks are each read by a named test. Related: [[FND-110]] overclaims that every deck in the directory is validated against a reference engine. Half closed in #444: `dipole-ex4-collinear-split-51seg.nec` is now the fixture for `reducible_collinear_split_current_source_keeps_the_per_wire_basis`. `dipole-gm-nrpt2-freesp.nec` is still gated by nothing. | From d4c6fd42e33b88dcc212b52c235528487df2ad5d Mon Sep 17 00:00:00 2001 From: Simon Keimer Date: Mon, 7 Sep 2026 20:59:12 +0200 Subject: [PATCH 3/3] docs(tests): record the two tests this branch adds `scripts/check-test-catalog-counts.py` failed CI on #448 exactly as designed: the receive-twin invariance test and the bent-junction refusal test took the integration subtotal from 514 to 516. Numbers re-derived with the checker, not typed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018p7FxX7QMWNJVNp9LbaLkB --- docs/project/test-catalog.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/project/test-catalog.md b/docs/project/test-catalog.md index a8c5b43..daca918 100644 --- a/docs/project/test-catalog.md +++ b/docs/project/test-catalog.md @@ -2,7 +2,7 @@ project: fnec-rust doc: docs/project/test-catalog.md status: living -last_updated: 2026-08-31 +last_updated: 2026-09-07 --- # Test catalog @@ -57,7 +57,7 @@ counts (measured, not estimated). Aggregate pass/fail is recorded separately in | `apps/nec-cli/tests/current_source_junction.rs` | 1 | CLI junctioned current source: split-dipole EX-4 feedpoint Z=V/i0 matches voltage-source Z (~2e-4) | PH9-CHK-002 | | `crates/nec_worker/tests/gpu_exec.rs` | 2 | Worker-level GPU execution vs CPU parity | PH7-CHK-004 | -Integration subtotal: **514** test +Integration subtotal: **516** test functions across the `tests/` binaries listed above. ## Unit tests (in `src/`) @@ -82,9 +82,9 @@ Unit subtotal: **565** `#[test]` functions. ## Totals -- **Test functions**: **1086** = 565 unit + 514 integration + **7 doctests**. -- **`cargo test --workspace` aggregate**: **1084 passing, 0 failed, 2 ignored**, - measured 2026-08-31 — the authoritative pass count in [test-results.md](test-results.md). +- **Test functions**: **1088** = 565 unit + 516 integration + **7 doctests**. +- **`cargo test --workspace` aggregate**: **1086 passing, 0 failed, 2 ignored**, + measured 2026-09-07 — the authoritative pass count in [test-results.md](test-results.md). Doctests are counted separately on purpose. `cargo test --workspace -- --list` prints them under `Doc-tests ` headers that carry no `Running` line, so a