Skip to content

fix: keep labeled rail nets on net labels instead of long traces (#670)#702

Open
DPS0340 wants to merge 3 commits into
tscircuit:mainfrom
DPS0340:fix/long-trace-instead-of-netlabel
Open

fix: keep labeled rail nets on net labels instead of long traces (#670)#702
DPS0340 wants to merge 3 commits into
tscircuit:mainfrom
DPS0340:fix/long-trace-instead-of-netlabel

Conversation

@DPS0340

@DPS0340 DPS0340 commented Jul 24, 2026

Copy link
Copy Markdown

Fixes #670.

Problem

On the attached board, LongDistancePairSolver draws 9–13 unit orthogonal bus traces across the schematic for pins on the labeled V3_3/GND rails:

JP1.2-JP2.2  net=V3_3  len=12.7   (8.0,2.3)->(8.0,2.5)->(5.5,2.5)->(5.5,-7.6)
D2.2-JP6.1   net=GND   len=11.3
U1.2-JP6.2   net=V3_3  len=9.8

Every one of those pins already receives a net label (V3_3 is declared y+, GND y- in availableNetLabelOrientations), so the long traces duplicate the label connection — the "long trace instead of netlabel" in the issue title.

Root cause

LongDistancePairSolver connects unconnected pins to up to 3 nearest neighbors on the same net with no distance cap and no awareness of net labels. MspConnectionPairSolver deliberately skips pairs beyond maxMspPairDistance (2.4 here) so they become label-connected — but the long-distance pass then bridges those same pins anyway whenever a collision-free route exists.

Fix

For nets with declared availableNetLabelOrientations and more than two pins, neighbor candidates are now limited to maxMspPairDistance. This keeps:

  • short traces between nearby pins on labeled rails (unchanged behavior),
  • long traces on unlabeled nets (the feature: Allow long traces that don't cross any other traces #74 feature this solver exists for),
  • long traces on two-pin labeled nets, where a single trace is cleaner than a floating label pair (pinned by rotated-components-rail-label).

Verification

  • New regression test with the JSON Bug Report: long trace instead of netlabel #670 board: no >5-unit traces remain on labeled nets (fails on main with the three traces above)
  • bun test — 141 pass, 0 fail
  • 20 board snapshots updated — reviewed several before/after: long V3_3/GND/VCC bus traces disappear while the already-placed labels take over; no component or label positions change
  • bunx tsc --noEmit / biome check — clean

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
schematic-trace-solver Ready Ready Preview, Comment Jul 25, 2026 7:23am

Request Review

@DPS0340

DPS0340 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Repro-first workflow: opened #704 as the standalone reproduction PR for #670 (pins the three long bus traces with a visual snapshot). This PR is the fix that flips that repro's assertion to .toEqual([]). Happy to rebase this on top of #704 once the repro lands.

DPS0340 added 2 commits July 25, 2026 14:52
Fixes tscircuit#670.

LongDistancePairSolver connected any unconnected pins on a net with up to
3 nearest-neighbor traces, regardless of whether the net is rendered with
net labels. On the tscircuit#670 board, pins on the labeled V3_3/GND rails (e.g.
JP1.2-JP2.2, 10 units apart) got 9-13 unit orthogonal bus traces snaking
across the schematic even though every one of those pins already gets a
net label.

For nets with declared availableNetLabelOrientations and more than two
pins, neighbor candidates are now limited to maxMspPairDistance — the
same threshold MspConnectionPairSolver uses to decide a pair is too far
apart to wire directly. Nearby pins on labeled rails keep their short
traces; distant ones stay label-connected. Two-pin labeled nets are
unaffected (a single trace is cleaner than a floating label pair).

Adds a regression test with the tscircuit#670 board asserting no >5-unit traces
remain on labeled nets. 20 board snapshots updated: long V3_3/GND/VCC bus
traces disappear in favor of the labels that were already being placed.
Follow-up to the tscircuit#670 fix after rebasing onto UnroutedTraceRecoverySolver
(tscircuit#712), which landed while this branch was open.

LongDistancePairSolver declines to wire distant pairs on labeled rails, so
those pairs now arrive at UnroutedTraceRecoverySolver as failed connections
and were being recovered as long detour traces — reintroducing exactly the
bus trace tscircuit#670 asks us to avoid. On the ina237 board, BUS_HIGH pins U1.10
and R_SHUNT.1 sit 2.31 units apart (within maxMspPairDistance of 2.4) but
are only reachable by a 9.07 unit path around the chips, so the pin-distance
guard did not catch it.

The recovery solver now skips candidate paths on labeled rail nets whose
routed length exceeds maxMspPairDistance. Because the pins can be close
while the route is long, the routed length is what decides this, not the
straight-line pin distance.

The labeled-rail test — declared net-label orientations plus more than two
pins — is shared by both solvers in lib/utils/labeledRailNets.ts so they
cannot drift apart.

Measured against origin/main (total/max orthogonal trace length per board):

  bug-report-20260716-long-trace-670  63.10/12.73 -> 22.48/2.88
  bug-report-20260707T092615Z        109.66/10.20 -> 81.58/4.17
  repro-ina237-current-monitor        31.68/7.47  -> 20.07/3.38
  example13                           39.84/6.88  -> 34.53/6.88 (1 -> 0 long rail traces)
  example32                           21.99/4.40  -> 18.29/4.40

Full suite passes; three snapshots updated to reflect the shorter routes.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Rebased onto current main — this now includes a real fix, not just a conflict resolution.

UnroutedTraceRecoverySolver (#712) landed while this PR was open, and it interacts with this change. Since LongDistancePairSolver now declines to wire distant pairs on labeled rails, those pairs arrive at the recovery solver as failed connections, and it was routing them as long detour traces — reintroducing the exact bus trace #670 is about.

The ina237 board shows why the existing pin-distance guard did not catch it: BUS_HIGH pins U1.10 and R_SHUNT.1 are 2.31 units apart (inside maxMspPairDistance of 2.4), but the only route between them is a 9.07 unit detour around the chips. So the recovery solver now measures the routed path length, not the straight-line pin distance, before recovering a pair on a labeled rail.

The labeled-rail test (declared net-label orientations + more than two pins) is shared by both solvers in lib/utils/labeledRailNets.ts so they cannot drift apart.

Measured against origin/main, total/max orthogonal trace length per board:

board before after
bug-report-20260716-long-trace-670 63.10 / 12.73 22.48 / 2.88
bug-report-20260707T092615Z 109.66 / 10.20 81.58 / 4.17
repro-ina237-current-monitor 31.68 / 7.47 20.07 / 3.38
example13 39.84 / 6.88 34.53 / 6.88 (long rail traces 1 → 0)
example32 21.99 / 4.40 18.29 / 4.40

Every board improves and none regress. Full suite passes (145 pass / 0 fail); three snapshots updated to reflect the shorter routes.

Same issue @mohan-bee flagged on tscircuit#703/tscircuit#707: bug report directories are
generated by .github/scripts/import-json-bug-report.ts and named from the
issue's created_at, so they must not be hand-created.

Issue tscircuit#670 was opened at 2026-07-16T14:48:56Z and its bot-imported report
already exists as bug-report-20260716T144856Z. The hand-made
bug-report-20260716-long-trace-670/input.json was a byte-identical copy.

Removes the duplicate and points the repro at the existing bug report.

Full suite 145 pass / 0 fail.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Applied your tests/bug-reports/ feedback from #703/#707 here too (88fed78), before it needed flagging.

This branch had the same problem: issue #670 was opened at 2026-07-16T14:48:56Z, so the bot-imported report already exists as bug-report-20260716T144856Z, and my hand-made bug-report-20260716-long-trace-670/input.json was a byte-identical copy of it. Removed, and the repro now imports the existing bug report.

I've swept my other open branches on this repo for the same mistake — this and #699 were the only two, both fixed now.

Full suite 145 pass / 0 fail.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

@mohan-bee @seveibar — flagging this one because GitHub's diff stat makes it look far larger than it is, and I suspect that's why it's been skipped.

It shows 29 files, +2073/-1658. But 25 of those files are regenerated .snap.svg snapshots. The actual change is 4 files, +135/-9:

lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts        +29/-9
lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts  +21
lib/utils/labeledRailNets.ts                                        +60  (new)
tests/repros/repro-long-trace-instead-of-netlabel-670.test.ts       +34  (new)

Fixes #670: a net that already has labels was being wired with a long bus trace across the board instead of using those labels. labeledRailNets.ts identifies nets whose pins are already served by net labels, and LongDistancePairSolver declines to wire distant pairs on them.

One thing worth knowing for review: UnroutedTraceRecoverySolver (#712) landed while this was open and interacts with it — since long-distance pairs on labeled rails are now declined, those pairs reach the recovery solver, which would re-add the very traces this removes. The +21 there teaches it to skip labeled rails. That interaction is the reason for the snapshot churn.

Regression test fails on main, passes here. Full suite green, MERGEABLE / CLEAN. Happy to split the snapshot refresh into its own commit if that makes review easier.

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.

JSON Bug Report: long trace instead of netlabel

1 participant