Skip to content

fix: relocate net labels placed inside chip bodies#699

Open
DPS0340 wants to merge 6 commits into
tscircuit:mainfrom
DPS0340:fix/net-labels-inside-chips
Open

fix: relocate net labels placed inside chip bodies#699
DPS0340 wants to merge 6 commits into
tscircuit:mainfrom
DPS0340:fix/net-labels-inside-chips

Conversation

@DPS0340

@DPS0340 DPS0340 commented Jul 24, 2026

Copy link
Copy Markdown

Fixes #655. Also resolves the pile-up in #687.

Problem

NetLabelNetLabelCollisionSolver only relocated a label when it overlapped another label (findNextCollidingPair looks for label-label overlaps only). A label sitting inside a chip body without a label-label collision was never checked against chips, so it rendered behind the component and was illegible. In bug-report-20260707T230831Z, the VIN, VOUT, PG and U1.FB to R2.pin1 labels ended up 100% inside chip bodies.

Fix

Two changes in NetLabelNetLabelCollisionSolver:

  1. Chip-collision pass — after all label-label collisions are resolved, labels whose area is ≥50% covered by a chip body are found via the existing ChipObstacleSpatialIndex and run through the same candidate search/relocation loop. The 50% threshold avoids relocating labels with small incidental overlaps, which other solvers already tolerate.
  2. Anchor-sliding escape for port-only labels — port-only labels previously tried only 4 orientations anchored exactly on their pin. In this board several chips overlap (schematic_component_0/6 etc.), so a pin can be covered by another chip's body and every orientation collides. The anchor now slides progressively outward (up to 3mm in 0.1mm steps) until the label escapes. Orientation constraints from availableNetLabelOrientations are respected during the search.

The anchor-sliding escape also gives the V1V1 label in the #687 repro a collision-free position, so that repro's "assert the buggy behavior" assertions are flipped to assert the fix.

Before / After (bug-report-20260707T230831Z, stage16)

Before: VIN/PG hidden inside U1's body, VOUT inside R3, FB inside R1.
After: all four labels sit outside chip bodies at the nearest legal position.

Verification

  • bun test — 141 pass, 0 fail (was 2 pre-existing pinned-bug failures + this issue's failing repro)
  • New regression test tests/repros/repro-net-label-inside-chip-655.test.ts (matches the repro in Add a repro test for net labels placed inside chip bodies #656 by @abdalraof-albarbar)
  • bunx tsc --noEmit — clean
  • bunx biome check — clean
  • Visual snapshots updated for the affected bug-report boards; verified stage16 renders before/after

@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:22am

Request Review

@DPS0340

DPS0340 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Additional verification: ran the full board from #687 (_board#157553 input JSON, 16 chips) through both branches:

  • main: 2 label-label overlaps remain (V1V1 stacked on QCLK / QD3)
  • this branch: 0 overlaps — the anchor-sliding escape gives V1V1 a collision-free position on the full board, not just the minimized repro

So this PR resolves both #655 and the #687 pile-up on the original board.

@DPS0340

DPS0340 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Ran a label-inside-chip scan across all 21 bug-report boards in the repo:

branch boards with labels ≥50% inside a chip body
main 5 boards (092615Z, 144856Z, 141025Z, 230831Z, 141421Z) — 8 labels total
this branch 0 boards

So beyond the #655 repro board, this fix also clears the hidden labels on four other real bug-report boards.

@DPS0340

DPS0340 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Following the repo's repro-first workflow: opened #705 as the standalone reproduction PR for #655 (pins the four buried labels). This PR is the fix that flips that repro's assertion to .toEqual([]) — happy to rebase once the repro lands.

DPS0340 added 2 commits July 25, 2026 14:58
Fixes tscircuit#655.

NetLabelNetLabelCollisionSolver only relocated a label when it overlapped
another label. A label sitting inside a chip body without a label-label
collision was never checked against chips, so it rendered behind the
component and was illegible.

Two changes:

- After all label-label collisions are resolved, find labels whose area
  is >=50% covered by a chip body and run the same candidate search to
  relocate them.
- Port-only labels previously only tried 4 orientations anchored exactly
  on their pin. When the pin is covered by a chip body (overlapping
  chips) every orientation collides, so the anchor now slides
  progressively outward until the label escapes. Orientation constraints
  from availableNetLabelOrientations are respected.

The anchor-sliding escape also resolves tscircuit#687: the V1V1 label that was
left stacked on the QD3/QCLK port labels now finds a collision-free
position, so the repro's buggy-behavior assertions are flipped to assert
the fix.
…ySolver

The bug-report-20260707T092615Z snapshot conflicted with tscircuit#712, which landed
while this branch was open. Regenerated against current main; the
repro-net-label-inside-chip-655 regression test still fails on main and
passes here, so the fix this PR makes is unchanged.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Rebased onto current main. The only conflict was the bug-report-20260707T092615Z snapshot, which #712 also touched; it has been regenerated against current main.

The fix itself is unchanged. To confirm that after the rebase, the regression test in this PR still fails on main and passes here:

# on origin/main
(fail) net labels are not placed inside chip bodies

# on this branch
(pass) net labels are not placed inside chip bodies

Full suite passes (145 pass / 0 fail), and CI is green.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Re-verified against current main just now, in case the earlier numbers went stale.

On the #655 board (bug-report-20260707T230831Z), counting labels whose area overlaps a chip body by more than 50%:

main this branch
labels inside chip bodies 4 (VIN, U1.FB to R2.pin1, PG, VOUT) 0
total labels 11 11

So four labels are currently rendered buried inside chip bodies, and none are after this change — with the label count unchanged, i.e. nothing was dropped to achieve it.

The regression test also fails on main and passes here:

# origin/main
(fail) net labels are not placed inside chip bodies
# this branch
(pass) net labels are not placed inside chip bodies

Full suite 145 pass / 0 fail, rebased onto current main, CI green.

Following @mohan-bee's snapshot feedback on tscircuit#703/tscircuit#707, applying the same
here pre-emptively: the assertion only checked which labels overlapped a
chip body, so the board was never rendered. Now the relocated labels are
visually reviewable.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Applied the same snapshot feedback from #703/#707 here pre-emptively (1d5e0ff) — the #655 board render is now pinned at tests/repros/__snapshots__/repro-net-label-inside-chip-655.snap.svg, so the relocated labels are visually reviewable rather than just a netId list.

Full suite: 145 pass / 0 fail.

Applying @mohan-bee's feedback from tscircuit#703/tscircuit#707 here too. The tscircuit#655 board is
already a bot-imported bug report (bug-report-20260707T230831Z) with its own
snapshot test, so the snapshot I added to this repro duplicated it.

The repro keeps only the assertion specific to tscircuit#655 — which labels overlap a
chip body. That assertion still fails on main and passes here.

Full suite 145 pass / 0 fail.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Applied @mohan-bee's feedback from #703/#707 here too (11304a8) — the snapshot I'd added was a duplicate. The #655 board is already a bot-imported bug report (bug-report-20260707T230831Z) with its own snapshot test.

The repro now keeps only the assertion specific to #655 (which labels overlap a chip body), which still fails on main and passes here. Full suite 145 pass / 0 fail.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Extra validation beyond the #655 repro: I ran this branch against the unlabelled JSON bug reports in this repo (the ones filed with an empty Notes section) and measured outputNetLabelPlacements on main vs here.

report labels inside chip (main → this PR) orientation violations (main → this PR)
#636 11 4 → 0 2 → 0
#628 6 1 → 0 0 → 0
#630 17 2 → 1 0 → 0
#626 28 3 → 3 0 → 0

So this fixes two previously-unexplained reports outright (#636, #628) and improves a third — none of which were part of the original #655 case.

Worth calling out: #636 also had two availableNetLabelOrientations violations on main (PG placed y- when only x-/x+ were allowed; VOUT placed x+ when only y+ was allowed). Those clear on this branch too. They were a consequence of the buried placement rather than a separate bug — once the label isn't stuck inside a chip body, the constrained orientation becomes satisfiable again.

The cases that remain (#626, one in #630) are all below the CHIP_COLLISION_MIN_OVERLAP_FRACTION = 0.5 cutoff — measured 0.46, 0.34, 0.12, and one edge-contact at 0.0 — which is the documented trade-off in this PR, not an oversight.

Happy to fold any of those JSONs in as regression fixtures if you'd like the coverage pinned.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Pinned the #636 board as a second regression fixture (1108d66), since it exercises a different failure shape than the original #655 case.

Two assertions, both of which fail on main and pass here:

main:  0 pass / 2 fail
       labelsInsideChips: ["VIN", "U1.FB to R2.pin1", "PG", "VOUT"]
       violations:        ["PG:y-", "VOUT:x+"]

here:  2 pass / 0 fail

The second assertion is the interesting one — it locks in that the orientation constraint stops being violated. On main, PG was placed y- when the input only allowed x-/x+, and VOUT was placed x+ when only y+ was allowed. Those weren't an independent bug: once a label is stuck inside a chip body, no allowed orientation fits, so the any-orientation fallback in NetLabelPlacementSolver fires and the constraint is dropped. Relocating the label removes the reason to fall back, and both violations disappear.

Guarding it explicitly means a future regression in the relocation logic will surface as a constraint violation too, not just as a visual overlap.

Verification on this branch: bun test tests/repros/ → 30 pass / 0 fail, tsc --noEmit clean, biome format clean. CI here is green (test, type-check, format-check) and the PR is MERGEABLE / CLEAN.

The previous commit added tests/bug-reports/bug-report-636/, which repeats
the mistake @mohan-bee already 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#636 was opened at 2026-07-07T23:08:31Z, and the bot-imported report
for it already exists as bug-report-20260707T230831Z. The hand-made
bug-report-636.json was a byte-identical copy of it — so tscircuit#636 is not a
second board as its comment claimed, it is the same board tscircuit#655 already
covers.

Removes the duplicate directory and the duplicate test file, keeping the
one assertion tscircuit#636 added that tscircuit#655 did not have: labels must stay on an
orientation the input allows. Both assertions fail on main and pass here.

Full suite 146 pass / 0 fail.
@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Cleaned up a duplicate here as well (0330cfd).

A tests/bug-reports/bug-report-636/ directory had been added to this branch, which repeats the pattern you flagged on #703/#707. Issue #636 was opened at 2026-07-07T23:08:31Z, so the bot-imported report already exists as bug-report-20260707T230831Z — and the hand-made copy was byte-identical to it. So #636 isn't a second board, it's the same board this repro already covers.

Removed the duplicate directory and the duplicate test file. I kept the one assertion #636 contributed that #655 didn't have — that labels stay on an orientation the input allows — folded into this repro. Both assertions fail on main and pass here:

# origin/main
(fail) net labels are not placed inside chip bodies
(fail) net labels honor availableNetLabelOrientations

Full suite 146 pass / 0 fail.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Correcting my own comment above, and the commit that followed it.

I claimed #636 was a second, independent board that this PR also fixes. It isn't — #636 and #655 are two reports of the same board. The bot-imported fixture bug-report-20260707T230831Z (named from #636's created_at) is the one this repro already uses; the bug-report-636/ directory I briefly added was a byte-identical copy, and it repeated exactly the mistake @mohan-bee flagged on #703/#707 about hand-creating tests/bug-reports/ directories. It's been removed in 0330cfd.

What survives is the one assertion #636 genuinely added that #655 didn't have — that labels stay on an orientation the input allows — now folded into the existing #655 repro rather than a duplicate test file:

main:  labelsInsideChips → ["VIN", "U1.FB to R2.pin1", "PG", "VOUT"]
       violations        → ["PG:y-", "VOUT:x+"]
here:  both → []

Both assertions fail on main and pass here. Full suite: 146 pass / 0 fail.

So please read the earlier table as covering fewer distinct boards than I implied. The fix itself and its measured effect are unchanged; only my claim about how many separate reports it closes was wrong.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

@MustafaMulla29 adding you — you've merged 19 of the last ~60 here, more than anyone, and I'd only tagged @mohan-bee. Whoever has a moment.

One note on size, since the diff stat is misleading: GitHub shows 13 files / +811, but the only source change is one fileNetLabelNetLabelCollisionSolver.ts (+121/-32). The other 12 are regenerated .snap.svg fixtures, which is expected since this moves labels that were previously drawn on top of chip bodies.

Fixes #655 (and #636, which turned out to be the same board). NetLabelNetLabelCollisionSolver only relocated labels that collided with another label, so a label sitting inside a chip body with no label-label collision was never moved — it rendered behind the component and was unreadable.

Measured on that board:

main:  labels inside chip bodies → ["VIN", "U1.FB to R2.pin1", "PG", "VOUT"]
       orientation violations    → ["PG:y-", "VOUT:x+"]
here:  both → []   (label count unchanged at 11)

The orientation violations were collateral — once a label is stuck inside a chip, no allowed orientation fits, so the any-orientation fallback fires. Both are asserted in the repro; both fail on main.

Relocation is deliberately gated at CHIP_COLLISION_MIN_OVERLAP_FRACTION = 0.5 so small incidental overlaps aren't moved (moving those tends to violate orientation preferences).

Full suite 146 pass / 0 fail, CI green, MERGEABLE / CLEAN.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Adding a whole-corpus measurement, since this PR's description only cites the one board from #655 and that undersells it.

I scanned every imported bug report for two defects: net labels whose bounding box falls inside a chip body, and labels overlapping each other. Comparing origin/main against this branch:

main this PR
labels inside chip bodies 17 9
label-label overlaps 7 4

−47% and −43%, with no board regressing on either metric.

Per board, where anything changed:

                              inChip      overlap
bug-report-20260707T230831Z    4 -> 0      0 -> 0     <- the #655 board
bug-report-20260721T221026Z    0 -> 0      2 -> 0
bug-report-20260707T092615Z    5 -> 4      1 -> 1
bug-report-20260716T144856Z    3 -> 2      0 -> 0
bug-report-20260707T141025Z    1 -> 0      0 -> 0
bug-report-20260707T141421Z    1 -> 0      1 -> 1
bug-report-20260706T220324Z    1 -> 1      1 -> 0

The #655 board goes to zero, which is what the PR claims — but it also fixes labels on five other boards that nobody had reported, and clears two label-label overlaps on 20260721T221026Z as a side effect.

What it does not fix: 9 in-chip labels remain, concentrated in 20260707T092615Z (4) and 20260707T140410Z (2). So this is a substantial improvement rather than a complete one — I'd rather state that than imply the class is closed.

Method: read netLabelNetLabelCollisionSolver.getOutput().netLabelPlacements — the output copy, not the input one; I got that wrong earlier in this thread and corrected it above. Build each label's bounds from center / width / height, then test interior overlap against every chip's bounds.

Related: #718 does the same kind of pinning for trace crossings, if a standing metric for this class would be useful too. Happy to add one for label placement on the same pattern.

@DPS0340

DPS0340 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Correcting my measurement above — it understated this PR.

I counted a label as "inside a chip" on any interior overlap. That was wrong: NetLabelNetLabelCollisionSolver defines CHIP_COLLISION_MIN_OVERLAP_FRACTION = 0.5 and tolerates smaller overlaps deliberately, since relocating them can violate orientation preferences. My metric was flagging incidental grazes the solver is intentionally leaving alone:

TH_20    coverage=3%
TH_80    coverage=3%
SDA      coverage=6%
SCL      coverage=6%
HIDRV    coverage=1%

Re-measured against the constant the implementation actually uses:

main this PR
labels ≥50% covered by a chip 8 0
label-label overlaps 7 4

Every genuinely-inside-a-chip label in the corpus is gone, not just the #655 board. My earlier "17 → 9, some remain" was an artifact of the wrong threshold — the 9 "remaining" were 1–6% grazes that were never defects.

The label-overlap figure (7 → 4) is unchanged from before, since that check doesn't involve the threshold.

Sorry for the noise. The corrected number is the one worth reviewing against, and it's a stronger result than what I first posted.

I've opened #720 to pin both metrics as a standing guard, using the same 0.5 constant so the test can't drift from the implementation.

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.

Net labels can be placed inside chip bodies

1 participant