docs(router): correct every CM5 DRC/SI claim measured against the inflated baseline#690
Conversation
A pair whose pads sit a few mm apart has no room for the lead-inset phantom centerline: the lead eats both ends and a ~1.8mm span keeps ~0.9mm to search, so both ladders fail and the pair falls back to two independent singles. Those singles land on DIFFERENT layers, and coupled_fraction only counts twin copper on the same layer — so the pair scores exactly 0.0 and pins the SI receipt's min_pair_coupled_fraction at zero however well the rest of the board routes (M7 named this: the /HS.* group). Such a pair does not need a search. Route it directly: one straight, lead-free centerline on the best layer all four pads share, offset into the two legs by the same realize_legs every other centerline uses. Four point probes choose the layer; the authoritative check is still validate_and_commit, so this adds no way to commit copper the oracle has not seen. CM5 pair census: 44 of 49 coupled, up from 39, and the centerline-search bail class — M7's dominant mode at 5-6 pairs — is now empty. The 5 that remain are long pairs failing at their connectors, a different mode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rint rotation KiCad stores a pad's angle ABSOLUTELY: it already includes the footprint's orientation. Measured on the CM5 fixture, the pad angle tracks the footprint exactly — fp_rot -90 -> pad_rot 270 (798 pads), 45 -> 45, 90 -> 90 (285), 135 -> 135, 180 -> 180. vcad's IR stores it RELATIVE, because all eleven geometry consumers (DRC, the router's spatial index, DFM, pour synthesis, render, the 3D mesh, eval) compose fp.rotation + pad.rotation. The importer stored the absolute angle, so every rotated footprint had its footprint rotation counted twice. A TQFN's 0.25 x 0.875mm pads at 0.5mm pitch came out turned 90 degrees — long axis ALONG the pitch — so neighbouring pads overlapped. That is not a subtle error: it invented copper. Fixed at the import boundary (reader subtracts, writer re-composes) so all eleven consumers become correct without touching them, pinned by a round-trip test. Measured on the CM5 fixture, copper stripped: short/clearance 980 -> 311 Clearance 74 -> 23 So 648 of the violations this project has been attributing to the reverse-engineered source were ours. Every DRC-delta claim measured against that baseline needs re-measuring. Pair coupling census: 39 -> 47 of 49. The four /ETH.* pairs that pinned both broken SI claims couple now that their pads no longer overlap. Also here, both found while chasing those bails and each worth a pair on its own (46 -> 47 together): - short pairs get a direct, lead-free centerline (a ~1.8mm pad-to-pad span has no room for the lead inset, so it fell back to singles on different layers and scored exactly 0.0 coupling); - a pad connector retreats along the centerline and escalates its search window, because a pad inside a fine-pitch field must escape laterally before it can drop a via, and a window sized from a 0.8mm hop cannot contain that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…aude/serene-taussig-bc9520
…lated baseline vcad's KiCad importer stored pad angles as read, but KiCad's pad angle is absolute — it already includes the footprint's orientation — while eleven consumers compose `fp.rotation + pad.rotation`. Rotated fine-pitch packages therefore had overlapping pads, manufacturing phantom shorts and clearance violations (PR #684). Those phantoms landed in the stripped-fixture *baseline*, which is the denominator of every route-attributable claim we publish. A bigger floor makes our delta look smaller, so the error flattered us in every comparison — the direction that most needs correcting. Re-measured from scratch on merged main with #684 in. DRC and SI are deterministic given a fixed board file; every figure was run twice with identical results, and the stochastic route stage is reported with wall-clock. Numbers that moved: - stripped-fixture floor: 980 -> 311 short/clearance, 74 -> 23 Clearance (648 violations blamed on the reverse-engineered source were ours) - human production board: 16,485 -> 14,104 violations - our full board: "route-attributable ZERO" was FALSE (74 - 74 = 0 against the inflated floor); corrected, 173 vs floor 23 = 150 attributable - full-board receipt: 2/4 -> 1/4 (vias_per_si_net 2.766 -> 3.128, crossing the 3.0 bound as routability rose 0.983 -> 0.994) - 40-net "receipt Pass (ALL HOLD)" WITHDRAWN -> 3/4 (intra-pair skew 1.353 vs 1.100); it already failed to reproduce pre-fix at 1.347, so the Pass was a one-branch artifact rather than a result - pad-level shorts in the import: ~906 -> 258; 40-net route adds 27, not ~409 (the conclusion stands: Short is not route-attributable, use Clearance) - route wall-clock 113 min -> 958.9 s (16.0 min); full chain 27.2 min, so the M6 "<30 min chain" target is met - pair-first couples 47 pairs in round 0, up from 43 No receipt bound was adjusted. The human board's anchor re-measures bit-identical (9.756 / 1.074 / 0.857 / 2.265, ALL HOLD) because pad angles feed pad geometry, not trace lengths — so the envelope argument survives intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Choji has used up today's reviewsYour team has reached the free plan's limit of 5 reviews per day, so Choji stepped aside on this pull request. That's a plan limit, not a reflection of the change itself. Your free reviews reset tomorrow. To have Choji review every push without waiting, upgrade to Pro for unlimited reviews. You're on the free plan · 5 reviews/day. |
Second full-board route: routability 0.994 again, byte-identical board (3,791 segments / 895 vias) — the route is deterministic given the tree. Wall-clock 895.5 s vs run 1's 958.9 s. The chain figure stays at one complete run: run 2's si_finish stage was truncated when the machine's disk filled, so its 1,288 s is not a valid chain sample and is not averaged in. Said so in the doc rather than presenting it as a second measurement — a prior version of this document published a single lucky run as typical, which is the habit this correction exists to break. Both runs shared the machine with another session's full-board route, so the timings are pessimistic; the <30 min conclusion holds regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Route spread — second run completeSecond full-board route on the corrected importer:
The route is deterministic given the tree — both runs produced a byte-identical board. Only wall-clock varies (~7%, from machine contention). One caveat, stated rather than buried: the full-chain figure (27.2 min) rests on one complete run. Run 2's route stage finished normally, but its Both runs also shared the machine with another session's full-board route (load average 23.4), so the timings are pessimistic. The All DRC and SI figures in this PR were already confirmed by two identical runs each and are unchanged. 🤖 Generated with Claude Code |
…-bc9520 # Conflicts: # crates/vcad-ecad-pcb/src/router/pair.rs
Main moved during this work: #685 (Eagle pad rotation), #687 (Gerber apertures turn with the pad), and #691 (per-layer controlled-impedance geometry). #691 rewrites router/pair.rs and adds a fifth SI claim, so the corrected numbers were re-run against it rather than assumed still valid. Nothing moved. The stripped floor (311 / 23), our board's DRC (Clearance 173), and the 40-net route (byte-identical board, receipt 1.353 / 0.582 / 2.474) all reproduce exactly — #691's per-layer widths are opt-in on target_impedance, which the CM5 fixture's classes do not declare. The new pair_impedance_correct_fraction claim is vacuous on this fixture: it reports 0.000 against a bound of 0.000 with the reason "no differential net class declares a target impedance — nothing to verify". The receipt is now five claims wide, so the raw fraction reads 2 of 5 on the full board and 4 of 5 on the subset. Those are not improvements, and the docs say so — the honest counts against claims that actually verify something remain 1 of 4 and 3 of 4. Also resolves the merge conflict with main in router/pair.rs by taking main's version; this branch carries no crate changes, only docs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Re-measures and corrects every published DRC and signal-integrity claim about the CM5 autorouting benchmark. All of them were computed against a stripped-fixture baseline that one of our own bugs had inflated.
Why
KiCad stores a pad's angle as absolute — it already includes the footprint's orientation. vcad's importer stored that raw value while eleven consumers compose
fp.rotation + pad.rotation, double-counting the footprint rotation on every rotated part. On fine-pitch rotated packages the neighbouring pads overlapped, manufacturing phantom shorts and clearance violations out of nothing (#684).Those phantoms landed in the baseline, which is the denominator of every route-attributable claim we publish. A bigger floor makes our delta look smaller, so the error flattered us in every published comparison — the direction that most needs correcting.
Method
Re-measured from scratch on merged main with #684 rebased in. DRC and SI are deterministic given a fixed board file, so each was run twice; every pair agreed exactly (no spread to report). The route stage is the only stochastic step and is reported with its wall-clock.
Note: the full-board route shared the machine with another session's full-board route (load average 23.4). Contention only inflates wall-clock, so the
<30 minconclusion holds a fortiori.Every number that moved
The baseline itself (stripped fixture, all copper removed)
ClearanceShort648 violations this project attributed to the reverse-engineered source were ours.
The human production board
Our board — the claim that broke
ClearanceClearance"Route-attributable violations: ZERO" was false. It read
74 − 74 = 0because the inflated floor happened to equal what our board scored.The shipped fab package re-measures at
Clearance81 vs floor 23 = 58 route-attributable.SI receipts
The "40-net subset — receipt Pass" headline is withdrawn. It also failed to reproduce on merged main before this fix (1.347mm), so the original Pass was a property of one branch state, not a reproducible result.
vias_per_si_netbroke as coverage improved — the board routes more connections (0.994 vs 0.983) and pays in vias. Worth stating plainly rather than reporting only the routability gain.Short accounting
"The import carries ~906 pad-level shorts, and routing only 40 nets adds ~409" → the import carries 258, and the 40-net route adds 27. The conclusion is unchanged and still correct:
Shortis not route-attributable on this fixture; useClearance.Wall-clock — the
<30 mintarget is metRoutability 0.994 is also a new record (prior best 0.985).
Incidental gain
Pair-first now couples 47 pairs in round 0, up from 43 — overlapping pads had been sealing the BGA fields that pairs escape through.
What did NOT move
No receipt bound was adjusted. The human board's anchor re-measures bit-identical:
9.756 / 1.074 / 0.857 / 2.265, ALL HOLD. Pad angles feed pad geometry, not trace lengths, so the calibration anchor and the envelope argument it supports survive intact.The claim that survives with a corrected number: the finishing pass still introduces no clearance violations. Published as "identical with the pass on and off (74)"; corrected, the 40-net board scores
Clearance23 — exactly the floor.Reviewer notes
gpu-router-m7-pair-si.mdis left as originally published, with a correction section appended, so the record shows what moved rather than silently overwriting it.~/Downloads/cm5-vcad-fab/FAB_NOTES.mdis corrected in place (outside the repo, so not in this diff). The gerbers do not need regenerating:board.kicad_pcbround-trips identically to the fixture under the corrected reader (1,403 pads across 227 rotated footprints, zero mismatch) — the old writer emitted the raw absolute angle verbatim, so the copper was always correct. Only the numbers computed from it were wrong. Itsdrc_report.txtstill carries the oldClearance 74and is flagged as predating the correction.cm5-fab-ready-campaign.mdandcm5-si-campaign.mdupdated with the corrected figures and the lesson: when an importer changes, re-derive the baseline before trusting any delta.🤖 Generated with Claude Code