Skip to content

Latest commit

 

History

History
599 lines (475 loc) · 36.3 KB

VALIDATION.md

File metadata and controls

599 lines (475 loc) · 36.3 KB

Validation Report

Independent correctness and usability audit of pcb-toolkit v0.1.5.

Date: 2026-09-14 · Toolchain: cargo/rustc 1.98.0 (x86_64-pc-windows-msvc) Method: every calculator was driven through the CLI and compared against an independent reimplementation of the published formula (written separately, in Python), against the reference vectors in docs/notes/17-test-vectors.md, and against physical invariants. Nothing in this report was taken from the library's own test suite.


Resolution status (updated 2026-09-14)

The correctness defects below were fixed in a follow-up pass. All Critical, High and Medium findings are resolved; the deferred items were explicitly scoped out and are listed as such.

ID Finding Status
C1 microstrip Er_eff/Zo wrong Resolved — rewritten as true Hammerstad-Jensen 1980
C2 pi-pad used the T-pad series formula Resolved — Z·(K²−1)/(2K); tests + source table corrected
H1 --freq silently ignored Resolved — Kirschning-Jansen dispersion implemented
H2 stripline negative Zo Resolved — rejected by a shared post-condition
H3 coplanar Er_eff > Er Resolved — bounded partial-capacitance form; Zo moved to the matching CBCPW form
M1 formulas misattributed Resolved — the implementation is now genuinely H-J
M2 two disagreeing microstrip models Resolved (narrowed) — divergence cut from 5.4% to 0.19%; differential deliberately keeps IPC-2141 (see below)
M3 NaN / impossible Er_eff at high t/h Resolved — H-J is well-behaved there, and the post-condition backstops it
— embedded microstrip Zo discontinuity Resolved (found while fixing; Zo now scales as 1/√Er_eff)
— elliptic_ratio had no domain guard Resolved — clamped, and degenerate W/h rejected
— thickness unvalidated in stripline/coplanar Resolved
M4 STATUS.md overstates completeness Deferred (doc accuracy, out of scope)
L5 crosstalk diverges from Saturn Resolved (2026-10-06) — Saturn decompiled; it is wrong by 48×. Docs re-framed, absolute + decomposition tests added
L1 dead COPPER_MELTING_POINT_C holds gold's melting point Resolved (2026-10-08) — constant deleted; constants.rs rewritten, 13 constants down to 7
L2 §7 of the test-vector doc repeats the melting-point error Resolved (2026-10-08) — corrected, with a note that the PDF prints the wrong figure
L3 fusing reference vector is internally inconsistent Won't fix — the reference is wrong (10 × 0.70 ≠ 18.79 sq.mils), so no assertion can be built from it. Recorded in the module docs instead
L4 wire_gauge::area_saturn is unexplained public API Resolved (2026-10-08) — identified as ampacity at 700 circular mils/A, renamed ampacity_a, documented, and tested against the published current table
L6 hygiene (dead toml dep, dead print_result branch, rustfmt, stale docs) Resolved (2026-10-08) — all five items

Verification: 214 tests pass (up from 192), cargo clippy clean, and every microstrip result was diffed against an independently written Python implementation of H-J and K-J across 9 geometries including dispersion — agreement is exact to 0.00000000%.

differential::edge_coupled_external intentionally still uses IPC-2141. It is the one module matching its reference vector exactly on all 8 outputs, so re-baselining it would have discarded the repo's only end-to-end validation. The two models now agree to 0.19%.


Current state (2026-10-08)

A second remediation pass closed the findings the audit deferred — L1, L2, L4 and L6 — and re-examined the "Unverified" ratings, several of which were stale at the time they were written. L3 is deliberately left open: the fusing reference vector is self-inconsistent (it states a 10 mil × 0.70 mil trace with an 18.79 sq.mil cross-section, when that product is 7.0 and etching only removes copper), so there is nothing correct to assert against. The Onderdonk core is right; the vector is not. The baseline-health and per-module tables further down this document describe the repo as it was audited in September and are kept as the historical record; the two tables in this section are the current ones.

The goal also changed, which changes what "verified" can mean here. Saturn PCB Toolkit is now treated as inspiration, not a specification — see CLAUDE.md — so a module whose only former grading criterion was "matches Saturn" is graded against published literature and physical invariants instead. crosstalk is the clearest case: it deliberately does not match Saturn, because Saturn is wrong by 48×.

Baseline health

Check Result
cargo test -p pcb-toolkit -p pcb-toolkit-cli 330 passed, 0 failed, 1 ignored (306 library + 24 CLI)
Non-finite inputs Rejected everywhere. A CLI sweep over every numeric argument of every command found 313 accepted spellings of nan/inf; CalcError::NotFinite closed all of them, and non_finite_numbers_are_rejected_everywhere keeps them closed
cargo clippy --workspace --all-targets -- -D warnings Clean, including the Tauri crate
cargo fmt --all -- --check Clean (was 103 diffs across 35 files)
CLI tests 24, in crates/pcb-toolkit-cli/tests/cli.rs (was zero)
CI None. The GitHub Actions workflows were removed on 2026-10-09; STATUS.md lists the commands to run by hand. tsc --noEmit and the Tauri crate's tests consequently have no automated coverage — see the note there
RUSTDOCFLAGS=-D warnings cargo doc --no-deps --workspace --lib Clean. The --lib scope avoids a bin/lib output filename collision, since the CLI's bin target is pcb-toolkit and the library pcb_toolkit
clippy.toml Exists, so disallowed_types is no longer inert — bans f32 and HashMap

The one #[ignore] is differential::edge_coupled_internal_sym's second reference vector, whose published Zeven is below its Zodd; the vector is internally inconsistent and the ignore carries that reason inline.

The Tauri entry point carries a scoped #[allow(clippy::disallowed_types)] because tauri::generate_context! expands to a HashMap; the ban still applies to the rest of that crate.

Per-module confidence

Three gradings:

  • Verified — at least one assertion against a value computed outside this codebase (a published reference vector, a standard table, or a closed form worked by hand).
  • Verified (invariant) — no external vector exists, but the module is pinned by properties that cannot all hold for a wrong implementation (duality, matching conditions, monotonicity, continuity, dimensional reduction to a known case).
  • Plausible — implements the cited published formula and is guarded, but nothing independent confirms the numbers.
Module Rating Evidence
impedance::microstrip Verified True Hammerstad-Jensen 1980; diffed against an independent Python H-J/K-J across 9 geometries, exact
impedance::stripline Verified Downgraded to Plausible and then rewritten on 2026-10-08. It is now genuinely Cohn's two-branch model and asserts the SLINE worksheet's stored result (51.437079595 Ω) to 1e-9 — the module's first absolute assertion. The single-branch IPC-2141 form it replaced was within 0.04% near w/b ≈ 0.3 and −3.8% at w/b = 0.52
impedance::offset_stripline Verified Asserts the SLINE2 worksheet's stored result (51.726315928 Ω) to 1e-9. That vector straddles both Cohn branches — Za at w/b = 0.52, Zb at 0.12 — so it pins the branch switch as well. Also asserted to reduce exactly to impedance::stripline when the two heights are equal
wires::{round_wire, coax, twisted_pair} Verified Twisted pair asserts the TWIST worksheet (101.3 Ω); coax is checked against the 50 Ω PTFE diameter ratio; all three have their derived Lo/Co cross-checked against the worksheets' independently published per-inch coefficients
signal Verified (invariant) Knee frequency and critical length are checked at the figures the source quotes, and critical length additionally by the property that defines it (flight time = Tr/rule) rather than by its own arithmetic. Γ, VSWR, return loss and mismatch loss are asserted to be recoverable from one another. er_from_delay is asserted to invert propagation_delay exactly
reactance::reactance_to_edge Verified Asserts the CAPAC worksheet's two figures (15.915 Ω, 62.832 Ω), and that both forms equal the sine-domain reactance at the knee frequency — which is what they are
inductor::{circular_loop, rectangular_loop} Verified Circular asserts both CIRCULAR worksheet vectors (100.325 nH at 10 mil wire, 53.628 nH at 100 mil); rectangular asserts the RECTANGL expression term by term, plus symmetry in its sides
resistance Verified Plane spreading resistance asserts the RESIST worksheet (0.91 mΩ), and the Ω·mil resistivity is asserted to be the SI constant converted rather than retyped
impedance::coplanar Plausible Bounded partial-capacitance form; no reference vector, and no dispersion model exists for CPW
impedance::embedded Plausible No reference vector; does now apply frequency (H1 resolved)
differential::edge_coupled_external Verified All 8 outputs match ref p11 to 5+ s.f.
differential::edge_coupled_internal_{sym,asym} Plausible Coupling coefficient corrected to the published 0.347 (Saturn transposes it to 0.374); denominator is the plane-to-plane span
differential::edge_coupled_embedded Plausible No reference vector
differential::broadside_coupled Plausible No reference vector; self-declared low confidence
via Verified saturn_page36_via_vector — C/L/Z/f_res to 5+ s.f.
padstack::thru_hole Verified saturn_page23_thru_hole_vector — 56/56/80 exact
padstack::corner_to_corner Verified 3-4-5 triangle, exact
inductor Verified saturn_page30_square — Mohan/Wheeler to 0.009%
ppm Verified Three reference vectors (Hz↔PPM, XTAL load)
spacing Verified saturn_page20_b1_vector, plus monotonicity in voltage, continuity across the 500 V extrapolation boundary, and imperial/metric agreement
pdn Verified Reference vector, a hand-computed case, Xc against the closed form, and the magic 0.225 pF/in tied back to ε₀ derived from MU_0
thermal Verified (invariant) Hand-computed 25 + 2.5·40 = 125 °C = 257 °F, °F/°C tracking, and zero power ⇒ junction = ambient
ohms_law (E-I-R, LED, R/C/L) Verified Reference vectors, all exact
ohms_law::{pi_pad, t_pad} Verified (invariant) C2's bad source table is no longer trusted: both networks are analysed by hand for input match and voltage ratio, and asserted to be duals (T_series · Pi_shunt = Z²)
reactance Verified Matches the closed form; more precise than the reference's own f_res
wavelength Verified 59.015 vs ref 59.01426 (the residual is Saturn's c = 11.8 in/ns)
wire_gauge Verified AWG 4/0, 10, 22, 40 against standard tables
materials Verified 45 entries, Er correct on spot checks. roughness_factor deleted — it was a repo-local invention, not even a faithful copy of Saturn's fudge
current Verified IPC-2221A formula known; DC resistance and skin depth correct, the latter over two decades of frequency. IPC-2152 is desktop-only
fusing Verified Onderdonk; the trace-area path remains unvalidatable against any published vector
crosstalk Verified (microstrip) Absolute values plus a factor-by-factor decomposition, and a test asserting the exact 45.9317× ratio to Saturn's output so the divergence cannot drift silently (Saturn's two defects alone account for 48.014× of it; the remainder is an Er_eff modelling difference). Stripline mode not implemented
constants Verified (invariant) η₀ cross-checked via √(µ₀/ε₀); c in/ns asserted not to be the rounded 11.803. the file went from 13 constants to 7, the deletions all being genuinely unreferenced — among them a copper melting point of 1064.62 °C, which is gold's

Two changes of rating are worth calling out explicitly, because the September table was already behind the code when it was written:

  • via, inductor, ppm and padstack were graded Unverified or absent, but each already asserted a reference vector from the Saturn help PDF.
  • pdn, thermal, spacing and the attenuators were the ones that genuinely lacked independent checks. All four now have them.

What is still Plausible is where to be careful: coplanar, embedded, and four of the five differential topologies have no external vector, and none exists to find — the published sources give formulas, not worked examples.

Stripline is the exception, and it was graded wrong here first. This section originally called it Verified "exact within validity", which no test in the module supports: its assertions are all ranges and monotonicity. An external check does exist after all — the Mathcad worksheets shipped with Johnson & Graham's High-Speed Digital Design carry stored results for Cohn's centred stripline, and a transcription of them is on this machine at gerbers2ems/hsddpy. Against that:

geometry w/b pcb-toolkit Cohn diff
b=20 mil, w=6, t=1.37, Er=4.5 0.30 51.418 51.437 −0.04%
b=64.5 mil, w=8, t=1.5, Er=4.5 0.12 77.981 77.629 +0.45%
b=15.5 mil, w=8, t=1.5, Er=4.5 0.52 37.217 38.677 −3.8%

The module's own stated bound (W/(2H − T) < 0.35) was therefore honest, and the formula was genuinely good inside it. The gap was that 50 Ω stripline on a thin core often lands at w/b ≈ 0.3–0.5, right where the error starts to matter.

Resolved the same day. Cohn's model has two branches — a skinny-trace log form and a wide-trace form selected at w > 0.35b — and only the skinny one had been implemented. impedance::stripline now implements both, with the Canright-corrected K1 shape factor, and asserts the worksheet's stored 51.437079595 Ω. Two side effects worth recording:

  • The wide-trace branch has no logarithm of the geometry to drive negative, so finding H2's negative impedance is no longer possible — the test that used to assert a wide trace is rejected now asserts that it computes, and is physical.
  • impedance::offset_stripline became nearly free, since the offset case is the parallel combination 2·Za·Zb/(Za + Zb) of two centred striplines taken about each plane in turn. It could not have been added correctly on the single-branch model: its reference geometry puts one half-line in each regime.

Verdict

Everything from here down describes the state of the repo as audited, before the fixes. It is kept as the historical record and as the rationale for each change — see the Resolution status table above for what has since been fixed. Present-tense claims below refer to the pre-fix code, and the per-module confidence table likewise predates the repairs.

Usable: yes. Correct: mostly, with two calculators that return materially wrong numbers and three that return unphysical output outside their validity range.

The repo builds clean, has no unsafe, no stubs, no panics on the paths tested, and 192 tests that genuinely pass. Most calculators are right — several match their reference to 5+ significant figures. The CLI works, its help is accurate, and the README examples reproduce exactly.

But the test suite does not establish correctness, and that gap is not cosmetic. The flagship calculator — impedance::microstrip — is wrong by ~5% and no test detects it, because its only impedance assertion is 20.0 < Zo < 80.0. A second defect (ohms_law::pi_pad) is actively pinned in place by tests that assert the wrong value.

The headline "192 tests passing" in STATUS.md is accurate as a count and misleading as a claim of validation.

Severity summary

Count Items
Critical — silently returns wrong numbers for valid, ordinary input 2 C1 microstrip, C2 pi-pad
High — unphysical output or silently ignored input 3 H1 frequency, H2 stripline, H3 coplanar
Medium — correctness-adjacent, or misleading documentation 4 M1–M4
Low / informational 5 L1–L5

Baseline health

Check Result
cargo build --workspace Clean, zero warnings
cargo test --workspace --all-targets 192 passed, 0 failed (matches STATUS.md)
cargo clippy --workspace --all-targets Clean, zero warnings
cargo fmt --check Fails — 103 diffs across 35 files
cargo doc --no-deps Builds; warns of bin/lib output filename collision
CLI tests Zero. No tests/, no benches, no doc tests

CI (.github/workflows/ci.yml) runs only build + test. It runs neither clippy nor fmt --check, which is why 103 formatting diffs accumulated unnoticed. The workspace declares [workspace.lints.clippy] disallowed_types = "deny" (Cargo.toml:25), but that lint is inert without a clippy.toml listing the disallowed types, and none exists — the lint configuration currently enforces nothing.


Per-module confidence

Ratings earned by independent recomputation, not by the presence of tests.

Module Rating Evidence
differential::edge_coupled_external Verified All 8 outputs match ref p11 to 5+ s.f.
via Verified C/L/Z/f_res match ref p36 to 5+ s.f.
pdn Verified All 3 outputs match ref p28 exactly
padstack::thru_hole Verified 56/56/80 exact
padstack::corner_to_corner Verified √2·100 = 141.421 exact
reactance Verified Matches closed form; more precise than the doc's f_res
wavelength Verified 59.015 vs ref 59.01426
inductor Verified Matches Mohan/Wheeler to 0.009%; the doc's value is the outlier
spacing Verified Matches IPC-2221 at 4 spot voltages exactly
wire_gauge Verified AWG 4/0, 10, 22, 40 match standard tables
materials Verified 45 entries; Er correct on spot checks
ohms_law (E-I-R, LED, R/C/L, t-pad) Verified All exact
ohms_law::pi_pad WRONG C2 — series arm uses the T-pad formula
impedance::microstrip WRONG C1 — Er_eff +5.8%, Zo −5.4%
impedance::stripline Plausible / unguarded Exact within validity; H2 negative Zo outside it
impedance::coplanar Plausible / unguarded H3 Er_eff exceeds Er at large gap
impedance::embedded Unverified No reference vector; H1 ignores frequency
differential::* (other 4) Unverified No reference vectors exist
differential::broadside_coupled Unverified Self-declared "confidence: LOW"
current Partial DC resistance & skin depth correct; IPC-2152 path unreachable from CLI
fusing Partial Onderdonk core correct; trace-area path unvalidatable (L3)
crosstalk Verified (microstrip) Divergence from Saturn fully accounted for; Saturn is wrong by 48× (L5, resolved). Stripline mode not implemented

Critical findings

C1 — impedance::microstrip returns wrong Zo and Er_eff ✅ RESOLVED

Files: impedance/common.rs (er_eff_static, effective_width), applied at microstrip.rs:42-46

The conductor-thickness correction is applied by widening the trace (we = w + Δw) and feeding u = we/h into er_eff_static. But er_eff_static is monotonically increasing in u, so finite thickness makes Er_eff go up. Physically it must go down: a thicker conductor pushes more field into the air above the substrate.

Reference geometry (docs/notes/17-test-vectors.md §1 — W=17, H=10, T=2.10, Er=4.6):

Quantity pcb-toolkit Independent Hammerstad-Jensen Reference Error
Er_eff 3.4681 3.2772 3.2802 +5.8%
Zo (Ω) 49.1445 49.6744 50.7426 −3.1%
Tpd (ps/in) 157.7812 153.3784 153.4869 +2.8%
Co (pF/in) 3.2106 3.0877 3.0248 +6.1%

The direction of the error is confirmed by the zero-thickness case: independent HJ gives Er_eff 3.4323 at t=0 falling to 3.2772 at t=2.10, while pcb-toolkit rises from 3.4341 to 3.4681.

The library contradicts itself. For identical single-ended geometry (W=10, H=15, T=2.10, Er=4.6), two modules disagree:

impedance microstrip          -> Zo = 73.4951   <-- outlier
differential (its internal Zo) -> Zo = 77.5036
reference (Saturn p11)         -> Zo = 77.5040
independent Hammerstad-Jensen  -> Zo = 77.6499

differential::edge_coupled_external computes single-ended Zo with the IPC-2141 formula 87/√(er+1.41)·ln(5.98h/(0.8w+t)) (edge_coupled_external.rs:47) and agrees with all three references. impedance::microstrip uses a different model and is the sole disagreeing value.

The README's own headline example is affected. impedance microstrip -w 10 --height 5 --er 4.6 is printed in all three READMEs as Zo = 44.3599, Er_eff = 3.5172. Independent Hammerstad-Jensen for that geometry gives Zo = 44.8322, Er_eff = 3.3075 — the published example is +6.3% on Er_eff. The example reproduces exactly, so the documentation is faithful; the number it faithfully documents is wrong.

Failure scenario: a user sizing a 50 Ω trace on 10 mil FR-4 gets a width ~5% off. On a controlled-impedance board that is a real fabrication error, and it is the single most likely thing anyone uses this library for.

Why no test catches it: microstrip.rs has three tests — 20.0 < Zo < 80.0, "narrow

wide", and "negative width errors". Every value in the table above passes all three.

C2 — ohms_law::pi_pad computes the series resistor with the T-pad formula ✅ RESOLVED

File: ohms_law.rs:325

let r_shunt_ohm  = z_ohm * (k + 1.0) / (k - 1.0);   // correct for Pi
let r_series_ohm = z_ohm * (k - 1.0) / (k + 1.0);   // this is the T-pad series formula

The Pi-pad series arm should be Z·(K²−1)/(2K). As written, pi_pad and t_pad return identical series resistance at every attenuation:

dB pi_series (ours) t_series (ours) correct pi_series error
1 2.8751 2.8751 5.7692 2.0×
6 16.6139 16.6139 37.3519 2.2×
20 40.9091 40.9091 247.5000 6.0×

Shunt values are correct for both topologies; only the Pi series arm is wrong.

Failure scenario: anyone building a Pi attenuator from this output gets the wrong series resistor — at 20 dB, 41 Ω instead of 248 Ω. The pad will not attenuate correctly and will not present the design impedance.

This one is worse than C1 in one respect: tests at ohms_law.rs:413 and :421 assert the wrong values (2.88, 25.97), so the bug is locked in. Those expectations trace to the Pi-pad table in docs/notes/17-test-vectors.md, whose series column is itself a duplicate of the T-pad column — a bad source faithfully reproduced.


High findings

H1 — --freq is silently ignored by microstrip and embedded ✅ RESOLVED

MicrostripInput::frequency is documented "Used for Kirschning-Jansen dispersion correction" (microstrip.rs:19) but is destructured away with .. at microstrip.rs:25 and never read. Output is bit-identical from 0 Hz to 50 GHz:

f=0       zo=49.144468 er_eff=3.468128
f=50GHz   zo=49.144468 er_eff=3.468128

No dispersion code exists anywhere. KJ_DISPERSION_A/B (constants.rs:35,38) are declared and never used. Yet common.rs:4 advertises "Kirschning-Jansen frequency dispersion — used by all topology modules", and CLAUDE.md and STATUS.md both claim "frequency-dependent Er_eff".

Failure scenario: a user computing impedance at 10 GHz silently gets the DC answer. Dispersion is precisely what matters at that frequency. The CLI accepts the flag, so there is no signal that it did nothing.

stripline correctly omits -f (homogeneous medium — genuinely non-dispersive).

H2 — stripline returns negative impedance outside its validity range ✅ RESOLVED

The IPC-2141 formula is valid only for w/(b−t) < 0.35. There is no guard. Sweeping width at h=20 mil, t=1.4 mil, Er=4.6 (validity limit ≈ 14 mil):

w=10  -> Zo =  59.4311   (valid, matches IPC-2141 to 0.008%)
w=14  -> Zo =  51.2347   (validity limit)
w=50  -> Zo =  17.9560   (well outside validity, no warning)
w=100 -> Zo =  -0.9579   <-- negative

Failure scenario: a wide stripline (power distribution, low-impedance trace) silently returns a negative characteristic impedance, and negative Lo. Nothing errors.

H3 — coplanar reports Er_eff greater than Er ✅ RESOLVED

Er_eff is a weighted average of the substrate Er and air, so 1 ≤ Er_eff ≤ Er always. At w=10, h=10, Er=4.6:

gap=10  -> Er_eff = 3.6777   ok
gap=50  -> Er_eff = 5.0643   <-- exceeds Er = 4.6
gap=200 -> Er_eff = 6.4802   <-- exceeds Er = 4.6

Failure scenario: wide-gap CPW returns an impossible Er_eff, and every derived value (Tpd, Lo, Co) inherits the error.


Medium findings

M1 — Formulas are misattributed. impedance::microstrip is documented "Hammerstad-Jensen 1980" in CLAUDE.md, STATUS.md, and its module header. It actually implements the Wheeler/IPC-2141 closed form for Zo and the Schneider 1975 (1+12/u)^−0.5 approximation for Er_eff (common.rs:12-18). Hammerstad-Jensen 1980 is the a(u)/b(εr) formulation, which appears nowhere. A reader choosing this library for HJ accuracy gets a cruder model than advertised.

M2 — Two inconsistent microstrip models coexist. See C1. Whichever is correct, the library should not return two different answers for one physical structure.

M3 — microstrip produces NaN and impossible Er_eff at high t/h. At h=1 mil:

t=100 -> er_eff =   509.0669, zo = null   (NaN serialized as JSON null)
t=500 -> er_eff = 36362.5008, zo = null

Er_eff must be ≤ 4.6. zo reaches consumers as JSON null, which will break deserialization into f64. Validation bounds each dimension independently but never their ratio. There is also a discontinuity at u = π/2 where effective_width switches branches (Zo jumps 14.29 → 52.61 between t=5 and t=20).

M4 — STATUS.md overstates completeness. It marks every CLI command "Complete". In fact: padstack exposes 2 of the 7 sub-calculators documented in docs/notes/06-padstack.md; the entire IPC-2152 path (current::calculate_ipc2152, ~230 lines) has no CLI exposure; ohms_law::pi_pad/t_pad support only matched impedances, not the unmatched case described in docs/notes/ghidra-ohmslaw.md; and the 45-material database is never consumed by any calculator or command.


Low / informational

L1 — Dead pub constant holds gold's melting point. ✅ RESOLVED (2026-10-08) constants.rs:23 defines COPPER_MELTING_POINT_C = 1064.62. Copper melts at 1084.62 °C; 1064.18 °C is gold. The value is unused — fusing.rs correctly uses its own COPPER_MELTING_TEMP_C = 1084.62 — but it is public API, so a downstream consumer can import the wrong constant and get a ~2% fusing-current error.

L2 — docs/notes/17-test-vectors.md §7 repeats the same error. ✅ RESOLVED (2026-10-08) It quoted the melting temperature as 1064.62 where every other document in the repo says 1084.62. §7 now carries 1084.62 with a parenthetical noting that the source PDF prints the wrong figure, so the discrepancy is recorded rather than silently flipped.

L3 — The fusing reference vector is internally inconsistent and cannot validate the trace path. ⛔ WON'T FIX — the reference is the thing that is wrong. §7 states W=10 mil, thickness 0.70 mil, cross-section 18.79 sq.mils — but 10 × 0.70 = 7.0 sq.mils, and etching only reduces area. The stated area implies a ~26.8 mil trace. The library's own test sidesteps this by feeding area directly (saturn_fusing_current_from_area), so it passes while fusing_current_trace disagrees with the vector by 2.7× (1.304 A vs 3.515 A). The Onderdonk core itself is correct.

L4 — wire_gauge::area_saturn is an undocumented magic quantity. ✅ RESOLVED (2026-10-08) It exported diameter_mils² / 700.0 with no units and no physical interpretation, as public API.

The quantity was identified rather than deleted: 700 circular mils per ampere is the conservative power-transmission current rule, and dividing the circular-mil area by it reproduces the standard table at every gauge it is published for — AWG 10 → 14.83 A (published 15), 12 → 9.33 (9.3), 14 → 5.87 (5.9), 16 → 3.69 (3.7), 18 → 2.32 (2.3), 22 → 0.92 (0.92). So it is not an area at all; it is ampacity, and a useful output.

Renamed ampacity_a, the divisor named CIRCULAR_MILS_PER_AMPERE, documented including the caveat that chassis wiring is rated several times higher, relabelled in the CLI and the desktop schema, and pinned by ampacity_matches_the_published_power_transmission_table, which asserts each row to within half of its own last published digit.

L5 — crosstalk diverges from its reference, and that is correct. ✅ RESOLVED (2026-10-06) Ours: −35.48 dB / 0.084 V. Saturn: −2.23327 dB / 3.86640 V.

This finding originally argued only from implausibility ("77% coupling from a 250 mil run"). Saturn's V8.47 handler has since been decompiled — it had never been analysed before (PROGRESS.md:83 and NOTES.md:50 both record 0x004bde04 as unanalysed, so our formula was never a reverse-engineering of Saturn in the first place). TForm12.Button1Click at 0x501780 thunks to 0x501888; the microstrip branch 0x501f05–0x501fb9 is:

er_eff = 0.475*Er + 0.67
t      = 1.017 * sqrt(er_eff)       // 1.017 is ns per FOOT
ratio  = 2*t*L_inches / Trise_ns    // clamped at 1.0
coeff  = [1/(1 + (S/H)^2)] * ratio  // no 1/4 factor

which reproduces Saturn's published vector to all six of its digits. The gap decomposes exactly: 4.000000 (Saturn omits the 1/4 in the backward-coupling coefficient — the solver contains no 0.25 and no 4) × 12.003651 (1.017 × 11.803: a per-foot constant applied to a length converted to inches) × 0.956631 (√(2.855/3.119729), Saturn's IPC-2141 Er_eff approximation vs our Hammerstad-Jensen) = 45.932276, the observed ratio. The first two factors are Saturn's bugs and alone come to 48.015× (+33.63 dB).

Actions taken: crosstalk.rs's module docs rewritten (they previously blamed this crate and carried a // - Match Saturn formula exactly TODO that would have introduced the bug); absolute test reference_geometry_absolute added, plus saturn_divergence_is_fully_accounted_for pinning the decomposition above; the §17 vector had been transcribed in the docs and asserted nowhere. Also fixed in passing: the invented trace_width_mils input (required by the CLI with no default, silently defaulted to 10 by the desktop — two users got different dB for the same documented geometry) now has a single crosstalk::DEFAULT_TRACE_WIDTH_MILS; the unguarded log10; and the kb / kb_db naming collision with differential::*, which measure different quantities under the same names.

Still open: Saturn offers a stripline mode (branch 0x502794, two plane heights) that this crate does not implement, and the output is now labelled microstrip to say so. See docs/notes/18-v847-vs-rust-comparison.md §1.

L6 — Hygiene. ✅ RESOLVED (2026-10-08) All five items:

Item Fix
Unused toml dependency in the CLI Removed from the CLI and the workspace manifest
print_result's non-JSON branch dead (all 34 sites pass true) Function is now print_json with no flag; the commands' own titled-block output was always the real text path, and the dead branch would have printed a {:#?} struct dump
103 rustfmt diffs cargo fmt --all applied. fmt --check is in the manual check list in STATUS.md
PROGRESS.md listed "Begin Rust implementation" as the next priority Replaced with a status note saying the implementation is done and the scope is closed
NOTES.md's material table self-contradicting Er column marked superseded in place, pointing at materials-er-mapping.md, which is what materials.rs implements

On the test suite

192 tests pass, but they are weighted toward assertions that cannot fail for a plausible implementation: 159 assert! (largely range and monotonicity) against 122 assert_relative_eq!, and only ~14 tests named for a reference vector.

Three distinct problems, in descending order of importance:

  1. Absolute assertions sourced from a bad reference actively entrench bugs — C2 is the example. Worse than no test.
  2. Range assertions on the flagship path (20.0 < Zo < 80.0) admit a 60 Ω window and cannot detect a 5% error.
  3. Tests that avoid the disputed step — saturn_fusing_current_from_area validates Onderdonk but skips the area derivation that actually disagrees (L3).

docs/notes/17-test-vectors.md contains 18 transcribed reference vectors; roughly half are never asserted anywhere, including the microstrip vector that would have caught C1 on day one. The tests/vectors/*.json store planned in docs/notes/16-rust-design-research.md was never created.


Remediation backlog

Not executed — this audit made no code changes.

Priority 1 — wrong numbers

  1. Fix pi_pad series arm to Z·(K²−1)/(2K); correct the tests at ohms_law.rs:413,421 and the Pi-pad table in 17-test-vectors.md (C2). Smallest fix, unambiguous, highest confidence.
  2. Fix the microstrip thickness correction so Er_eff decreases with thickness (C1). Decide deliberately whether the target is true Hammerstad-Jensen or the IPC-2141 model already used by edge_coupled_external — then use one model library-wide (M1, M2).
  3. Wire the microstrip vector (§1) into a test as an absolute assertion. It is already transcribed and would have caught C1.

Priority 2 — unphysical output 4. Implement Kirschning-Jansen dispersion, or remove --freq and the claims in CLAUDE.md / STATUS.md / common.rs:4 (H1). Silently ignoring an input is the worst of the three options. 5. Add validity guards: stripline w/(b−t) < 0.35 (H2), coplanar Er_eff ≤ Er (H3), microstrip t/h bound (M3). Return CalcError::OutOfRange rather than a wrong number. 6. Add a debug assertion on the universal invariant 1 ≤ Er_eff ≤ Er in every impedance path — it alone would have caught H3 and M3.

Priority 3 — trust 7. Convert the remaining vectors in 17-test-vectors.md into absolute assertions; mark unvalidatable ones #[ignore] with a reason rather than substituting a range check. 8. Correct STATUS.md's "Complete" markers (M4); delete or fix COPPER_MELTING_POINT_C (L1) done; document or remove area_saturn (L4) done; re-frame the crosstalk note (L5) done. 9. Add clippy + fmt --check to CI — superseded: both run, but by hand; the CI workflows were removed on 2026-10-09 (see the Current state table). Either populate clippy.toml or drop the inert disallowed_types lint done. Add CLI integration tests done — 24 of them.


Reproducing this report

cargo build --workspace && cargo test --workspace --all-targets

Every numeric claim above comes from driving target/debug/pcb-toolkit with --json at the stated inputs, compared against an independent Python implementation of the published formula. Key reproductions:

pcb-toolkit --json impedance microstrip -w 17 --height 10 -t 2.10 --er 4.6 -f 500MHz
pcb-toolkit --json ohms-law pi-pad --attenuation 20 --impedance 50
pcb-toolkit --json ohms-law t-pad  --attenuation 20 --impedance 50
pcb-toolkit --json impedance stripline -w 100 --height 20 -t 1.4 --er 4.6
pcb-toolkit --json impedance coplanar -w 10 -g 200 --height 10 --er 4.6
pcb-toolkit --json impedance microstrip -w 10 --height 1 -t 500 --er 4.6