Skip to content

Close the CM5 autorouting verdict's last unknowns (0 unknown, per-connection certificates)#677

Merged
ecto merged 1 commit into
mainfrom
claude/close-cm5-unknowns
Jul 25, 2026
Merged

Close the CM5 autorouting verdict's last unknowns (0 unknown, per-connection certificates)#677
ecto merged 1 commit into
mainfrom
claude/close-cm5-unknowns

Conversation

@ecto

@ecto ecto commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Every connection on the best CM5 board now ends ROUTED or PROVED-INFEASIBLE — no unknowns — and every certificate names the copper forming its cut.

Result

run verdict
before (same input, same driver contract) routed 43 / proved 21 / unknown 31
after routed 44 / proved 51 / unknown 0
second pass over the output routed 0 / proved 45 / unknown 0 (fixed point)

The 31 unknowns were 28 cluster-level budget trips, 1 singleton budget trip, and 2 paths the fail-closed oracle rejected — budget and discretization artifacts, as expected.

What closes them

  • k = 1 is decided by reachability, not the exhaustive DFS. A lone connection cannot trip a budget: BFS returns a shortest path, or an exhausted reachable component that is the proof. This is what makes splitting a cluster terminate.
  • A sequential-BFS witness pass runs before the DFS for joint instances. The DFS's first descent is a greedy walk, not a shortest path, so on a wide window it could wander for millions of expansions before its first completion — the dominant source of unknowns. Success is a witness (node-disjoint by construction); failure proves nothing and falls through, so completeness is untouched.
  • WindowBudget couples the expansion budget to a cells-per-axis cap, so a wider window keeps its pitch at the width + separation floor instead of coarsening until unrelated terminals collide in one cell. (Lever 2, done the right way: cap raised with the cell budget.)
  • The driver escalates instead of accepting an unknown: joint at 1x/5x/25x budget, then per connection in its own 2 / 8 / 20 mm window. A joint infeasibility is no longer charged to the individual connections — it says only that the k cannot all be routed at once, so each connection earns its own verdict.
  • Pair-aware pitch: twins in one window are spaced by their declared gap, restoring "distinct cells ⇒ mutually legal" for pairs (lever 3).

Certificates got sharper, not weaker. The severed case reports whichever terminal's pocket is smaller, with the enclosing cut's node count, extent, and the nets holding it; the walled-in case names the pad's own layers and its blockers. All 51 name real copper:

net /LPDDR4 RAM/CK_C_B is severed inside the window: breadth-first search from its from terminal at (128.65, 77.15) exhausted every reachable node — 5 of the 50108 clearance-free (cell, layer) nodes on the 10-layer stack — without touching the other terminal at (133.55, 66.42) (layer FCu); the enclosing cut is 14 blocked nodes spanning x=128.22..129.41, y=76.08..77.57, held by /LPDDR4 RAM/CA5_B (10), /LPDDR4 RAM/CK_T_B (10), /LPDDR4 RAM/CA2_B (8). With k = 1 reachability is exact, so no path exists on the canonical grid at pitch 0.297 mm

Three legality gaps closed in the session oracle

Routing more connections surfaced three ways "routed" copper could still fail the board. Fixing them is what keeps the new copper DRC-clean:

  • probe_drill / commit_drill — hole-to-hole across layer spans and nets. Two vias on disjoint spans share no copper layer, so no layer-scoped probe ever compared them; they landed as drill collisions (the durable fix for a known open item). A via barrel is also now probed on every layer it spans, and consecutive transitions at one point merge into one barrel instead of stacking drills at zero spacing.
  • TerminalLayers — terminals can be pinned to the layers their net actually has copper on, with a probed dog-bone escape (stub on the pad's layer, barrel checked across its span) when the search wants a different layer. Previously a stub took the first free layer, which on a 10-layer board is usually not the pad's: 142 of 162 new pad-touching stub ends were electrically dangling.
  • set_strict_pair_coupling — scores the intra-pair gap the way the DRC does, pads and via annuli included, with the DRC's 1.5 mm pad-breakout exemption mirrored. Opt-in, because it is stricter than the geometry the pair router realizes today (its dog-bone offsets and jogs are sized to base clearance); the verdict driver turns it on and pays for it in routability. Flipping the default needs the pair router's geometry fixed first — noted below.

DRC (same input board, drc_json)

rule base after
Clearance 74 74 (baseline floor, exactly)
Short 1315 1315
MinTraceWidth 69 69
HoleToHole 0 0
UnconnectedNet 252 223
NetIslands 18 30
total 1813 1796

No new route-attributable violations. NetIslands +12 is an accounting shift: all 19 newly-listed nets had zero copper on the base board (they were UnconnectedNet entries) and now carry partial copper — some MST edges routed, the rest proved infeasible. An independent connectivity check over all 41 touched nets finds zero floating groups: every committed path is galvanically attached to its net's pads or pre-existing copper, with no zero-length traces and no coincident vias.

Verification

  • cargo test -p vcad-ecad-pcb — 252 passed, 0 failed (4 new tests: lone connection never unknown, severed connection proved with a named cut, pinned terminals attach on their own layer, drill probe sees holes no copper probe could).
  • cargo clippy -p vcad-ecad-pcb --features gpu -- -D warnings — clean. (--all-targets reports two items after a test module lints in router/auto.rs and router/si_claims.rs; both are pre-existing on main, confirmed by stashing.)
  • cargo fmt --all --check — clean.

Follow-up (not in this PR)

The pair router emits dog-bone offsets and jogs that come a gap-width short of the DRC's pair rule (a leg trace 0.175 mm from its twin's via, where the DRC requires 0.245 mm mid-board). Repro: router::pair::tests::pair_layer_transition_keeps_twin_clearance with set_strict_pair_coupling(true). Fixing that geometry is what would let strict coupling become the default.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

4 Skipped Deployments
Project Deployment Actions Updated (UTC)
mecheval Ignored Ignored Jul 25, 2026 12:58pm
vcad Ignored Ignored Jul 25, 2026 12:58pm
vcad-docs Ignored Ignored Jul 25, 2026 12:58pm
vcad-mcp Ignored Ignored Jul 25, 2026 12:58pm

Request Review

@chojiai

chojiai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Choji has used up today's reviews

Your 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.

…on certificates

The verdict ladder ended at "16 proved-infeasible + 27 unknown" for reasons
that were budget and discretization artifacts, not physics. It now reaches
zero unknowns on the best CM5 board — 44 routed / 51 proved-infeasible / 0
unknown, converging to a fixed point on a second pass (45 proved, 0 newly
routable) — and every certificate names the copper forming its cut.

What closes them:

- k = 1 is decided by reachability, not by the exhaustive DFS. A lone
  connection cannot trip an expansion budget: BFS returns a shortest path or
  an exhausted reachable component, which *is* the proof. This is what makes
  splitting a cluster terminate.
- A cheap sequential-BFS witness pass runs before the DFS for joint
  instances. The DFS's first descent is a greedy walk, not a shortest path, so
  on a wide window it could wander for millions of expansions before its first
  completion — the dominant source of unknowns.
- WindowBudget couples the expansion budget to a cells-per-axis cap, so a
  wider window keeps its pitch at the width+separation floor instead of
  coarsening until unrelated terminals collide in one cell.
- The driver escalates instead of accepting an unknown: joint at 1x/5x/25x
  budget, then per connection in its own 2/8/20 mm window. A joint
  infeasibility is no longer charged to the individual connections — it says
  only that the k cannot all be routed at once, so each earns its own verdict.
- The pitch is pair-aware: twins in one window are spaced by their declared
  gap, restoring "distinct cells ⇒ mutually legal" for pairs.

Certificates got sharper, not weaker: the severed case reports whichever
terminal's pocket is smaller, with the enclosing cut's size, extent, and the
nets holding it; the walled-in case names the pad's layers and its blockers.

Three legality gaps that let "routed" copper fail the board are now closed in
the session oracle, which is what keeps the new copper DRC-clean:

- probe_drill / commit_drill: hole-to-hole across layer spans and nets. Two
  vias on disjoint spans share no layer, so no layer-scoped copper probe ever
  compared them — they landed as drill collisions.
- Terminals can be pinned to the layers their net actually has copper on, with
  a probed dog-bone escape (stub on the pad's layer, barrel checked on every
  layer it spans) when the search wants another layer. Stubs no longer float
  on an inner layer with nothing joining them to the pad.
- set_strict_pair_coupling scores the intra-pair gap the way the DRC does,
  pads and via annuli included, with the DRC's 1.5 mm pad-breakout exemption
  mirrored. Opt-in: it is stricter than the geometry the pair router realizes
  today, so the verdict driver turns it on and pays in routability.

Board DRC vs the same input: Clearance 74 (baseline floor, unchanged), Short
1315 (unchanged), MinTraceWidth 69 (unchanged), HoleToHole 0 (unchanged),
UnconnectedNet 252 -> 223. NetIslands 18 -> 30 is the accounting shift for 19
nets that had no copper at all and now carry partial copper; an independent
connectivity check finds zero floating groups — every committed path is
galvanically attached to its net's pads or existing copper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ecto
ecto force-pushed the claude/close-cm5-unknowns branch from cf3372f to 2da37c4 Compare July 25, 2026 12:58
@ecto

ecto commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main (a5a57e4a) — the four red Rust/WASM jobs were not this branch. They died at dependency resolution (failed to select a version for the requirement phyz-md = "^0.1", candidate 0.3.1) because CI clones the sibling phyz repo at its moving main tip, which had bumped to 0.3.1. #675 fixed that on main 20 minutes after this PR opened; the rebase picks it up. WASM build, KiCad export verification, and both audits are green again on the rebased head.

Verified locally against phyz at its actual origin/main (99778e6, the state CI sees) using a detached phyz worktree with the path deps temporarily repointed, since this machine's phyz checkout lags main:

  • cargo clippy --workspace --exclude vcad-desktop --features vcad-kernel-text/no-builtin-font -- -D warnings — clean (the exact command the failing job runs)
  • cargo check --workspace --exclude vcad-desktop --all-targets — clean
  • cargo test -p vcad-ecad-pcb — 252 passed
  • board result unchanged after the rebase: routed 44 / proved-infeasible 51 / unknown 0, Clearance 74 / Short 1315 / HoleToHole 0 all at the baseline floor

Overlap worth resolving before merge

#672 (fix(router): enforce via hole-to-hole spacing at probe time) independently adds the same capability to RouteSession, with a broader API (probe_hole / probe_via_hole / commit_hole, per-net via drill from net classes, blocker reporting, wired into auto.rs / maze.rs / pair.rs / complete.rs). This PR's narrower probe_drill / commit_drill will conflict with it in session.rs. #672's is the better base; the two things worth porting from here are the grid-pitch floor at drill + hole_to_hole in router/complete.rs and the ViaClass-driven barrel probe on every layer a via spans.

One substantive difference to settle either way: #672 skips same-net holes (h.net == net), while drc::check_hole_to_hole is net-agnostic — it compares every via barrel and through-hole pad drill with no net filter, so two same-net vias closer than rules.hole_to_hole but not coincident are a real violation a same-net-exempt probe won't catch. probe_drill here is net-agnostic to match the DRC.

#681 (universal pair coupling and the SI receipt) looks like it supersedes the opt-in set_strict_pair_coupling added here, and may well fix the pair-router dog-bone/jog geometry that forced it to be opt-in (see the follow-up note in the PR description). If #681 lands first, this flag should probably become the default or disappear.

@ecto
ecto merged commit 08ba94a into main Jul 25, 2026
14 checks passed
ecto added a commit that referenced this pull request Jul 25, 2026
main gained its own drill index in #677 (`DrillElement` / `probe_drill` /
`commit_drill`), so this branch's parallel `SessionHole` / `probe_hole` /
`commit_hole` is dropped and its wiring re-expressed on main's API. What
survives is the part #677 left unfinished:

- `commit_drill` now returns a `SpanId` and the barrel is tombstoned like
  any other span, so rip-up-and-reroute takes the hole out with the
  copper. Without this the router walls itself in — every via it tried
  and abandoned would keep its ground forever. `probe_drill` skips dead
  barrels; the index compacts on the same half-tombstone rule as copper.
- The check is wired into every via-placement path, not just the complete
  window router: the maze's in-search `via_ok` (so A* avoids bad sites
  instead of failing at commit), the escape/stitch `via_legal`, and
  `validate_and_commit`'s gate. `commit_via` indexes the barrel, which is
  what makes router-vs-router collisions visible at all — on main
  `commit_drill` had no production caller, so only pre-existing board
  holes were ever seen, and the CM5 failure mode (two *router-placed*
  vias) was still open.
- `via_drill_for` / `probe_via_drill` size a candidate from its net class
  the way `legalize::via_geom_for` does on write-back, so no call site
  has to plumb drill diameters.

Adopts main's net-agnostic semantics: the hole rule ignores nets, which
matches `drc::check_hole_to_hole` exactly and is stricter than this
branch's earlier same-net exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant