π§Ή v0.19.1 β copper we were shipping that we shouldn't have been (corpus DRC 461 β 15) #512
drandyhaas
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Hey everyone! π
v0.19.1 is out. If 0.19.0 was about fixing a default we got wrong, 0.19.1 is about copper we were shipping that we shouldn't have been β routes the engine had already withdrawn, plane copper restored on top of other nets, and grading floors that judged good copper against a spec the board never routed to. Across the 165-board corpus, router-introduced DRC fell 461 β 15 and KiCad's own count fell 389 β 18. This is a correctness release, and the honesty section at the bottom says plainly what it did not improve.
π¦ How to get it: download KiCadRoutingTools-0.19.1.zip from the Releases page, then in KiCad open Plugin and Content Manager β Install from Fileβ¦ and pick that zip. Prebuilt router binaries for Linux/macOS/Windows are attached β
python build_router.pyfetches them automatically, no Rust toolchain needed.π The corpus, before and after
Same 165 real open-source boards, same scripted chains, replayed end-to-end with no LLM in the loop, and β importantly β the baseline was re-graded with today's graders, not read off an old table:
connection_widthitemsIndividual boards moved a long way:
crkbd119 β 0 DRC,lily5845 β 0,caravel_nucleo35 β 0,orangecrab35 β 0,ulx5m_gatemate16 β 0.𧨠The headline: we were shipping copper the engine had already withdrawn
The single largest cause was a bug class, not a bug. The router keeps two representations of a board β the live
pcb_datait reasons about, and the write list of results it eventually emits β and they were free to drift apart. When they did, a route that had been ripped up, narrowed, or rejected could still reach the output file, because the write list remembered a version of it that no longer existed.#508 closed that class: 19 distinct divergences, each with a ledger entry recording which side was authoritative and why. The associated diagnostic technique turned out to be the useful part β probe
pcb_dataat the start and end of a pass and compare, which tells you immediately whether you're looking at a stale emission or a live desync, before you start guessing.#509 is the same disease in the plane engine, and it was the nastier one: when a plane operation failed, the restore-on-failure path put the old copper back without checking whether another net had since taken that space β so a failed plane repair could silently delete a different net's routing. Restore now refuses rather than displaces, shares geometry rather than re-deriving it, catches per-net, and defaults to a full rip.
#463 was a third instance: plane repair could write copper that the reconnect step had explicitly withdrawn.
π Grading floors that judged copper against the wrong spec
connection_widthfell from 6946 to 341, and almost none of that was routing β it was #505, where the checker graded the minimum copper web against the board's stockmin_connectionvalue while the chain had deliberately routed below it.ulx5m_gatematealone went 4749 β 2 once the floor was clamped to what was actually routed. The board was always fine; the grader was measuring against a spec nobody had agreed to.The same issue's siblings were real bugs, though: a terminal connector projecting its target onto a copper-free layer and coming out obstacle-exempt with a 0.000 mm clearance stamp (
cparti_fpga's short), the #156 A* track margin not landing on a lattice distance, an Edge.Cuts contour containing pads being dropped instead of reclassified, and an NPTH pad's clearance override not being enforced against via copper.π©Ή The #489 audit wave
A structured audit of the writer and analysis paths turned up a run of independent defects, all now fixed:
lib_symbols, corrupting sibling designs.π Impedance: CPW-over-ground, and a coplanar declaration (#486)
Coplanar waveguide is now modelled properly rather than approximated as microstrip, with a post-route audit that checks what was actually built against what was asked for. The practical consequence is that computed widths came out 20β30% wider than before on coplanar nets β if you routed impedance-controlled coplanar traces on 0.19.0, they were too narrow. Note that coplanar is a declaration: passing
--coplanar-gapwithout--impedancenow warns, because it means the geometry was described but never targeted.ποΈ Plane repair that joins what it claims to join (#479)
The plane-region joiner became incremental (Prim-style) with strap reuse, learned to treat plated through-hole barrels as free layer transitions, and gained an authoritative gate so a "join" is verified against the fill model rather than asserted. Dead-end pruning is now fill-anchored β the previous version converted grading gaps into real board damage. Multi-board files (several outlines in one
.kicad_pcb) are now graded and routed per outline instead of as one tangled net list.π¬ New: watch your board route itself (#482, #506)
KICAD_ROUTE_TRACE=1records every route, rip, restore and plane operation, andmake_movie.pyturns it into an mp4 β with a geometry-first renderer that needs neither KiCad nor SVG. There's now a "Make routing movie" debug checkbox in the GUI too. It is genuinely useful for diagnosis, not just a toy: watching large nets get routed and immediately ripped is what kicked off the rip-churn investigation (#510).Also new:
make_plan.py/run_plan.py(#507) build and execute routing plans straight from the command line, the same plan format the GUI's Claude integration uses.π Determinism and GUI/CLI parity
A long tail of "why did the same board route differently twice?" got closed: pad coordinates snap to KiCad's integer-nm grid, copper order is canonicalised once at engine entry, endpoint/group/edge selection no longer depends on board segment order, overlapping plane zones get distinct fill priorities,
math.fsummakes geometry interpreter-independent, and the GUI's net list is sorted (it was randomizing routing order via aset). One headless segfault turned out to be re-parsing the board inside the wx dispatch.Both GUI-parity gates now drive the real dialog through a plan, so a new engine parameter no longer needs a hand-maintained mirror to be covered.
π« AQFN packages: refuse, don't grind (#500)
bga_fanout.pynow refuses a staggered multi-row no-lead package and tells you theqfn_fanout.pycommand to use instead. It used to accept them and grind for ~50 minutes to reach an answerqfn_fanout.py --escape-method underpad --allow-via-in-padreaches in 2.4 seconds β because a staggered package's two offset rows project onto each axis at half the real pad spacing, so the detected pitch is half the truth and the escape budget evaluates to a negative via size.π¬ The honesty section
Connectivity did not improve. Nets routed is 98.43% vs 98.44% β flat. On the 164 boards comparable across both runs, 4 more nets are reported incomplete, even though 4 more boards are now 100% connected.
That is mostly not a regression in routing; it's the divergence fixes doing their job. Some of the copper that used to make a net look connected was copper the engine had withdrawn or that a plane restore had stamped over another net. Removing it means a handful of nets are now honestly reported unrouted instead of dishonestly reported routed.
cparti_fpga(4β11) andbutterstick(8β15) are the clearest cases β both also went DRC 8β0 and 0β0 respectively. We'd rather have the true number, but it is a real cost and worth naming.Two known open items: diff-pair coupling is essentially flat at 471/639 (73.7%), with two specific regressions we're tracking (
dc_poe_injector7/8β6/8,cm5_minima19/10β8/10), andminiscope_v4_flexβ a flex board β accounts for a disproportionate share of remaining DRC in newer corpus sets.Also worth knowing if you use the Differential Pairs tab: #511 β 25 Basic-tab controls (including
length_match_toleranceandtime_match_tolerance) are currently ignored when routing diff pairs in the GUI, because that tab maintains its own config builder that fell behind. The equivalent CLI flags inroute_diff.pyall work. Fixing it changes GUI routing results, so it's staged deliberately rather than slipped into a patch release.Full commit-level detail in the v0.19.0β¦v0.19.1 compare (158 commits) and the Rust changelog in
rust_router/README.md.Bug reports and feedback hugely welcome β open an issue or reply here. Happy routing! π
All reactions