@tscircuit/schematic-trace-solver 0.0.123 → 0.0.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -875,6 +875,7 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
875
875
  private intersectsAnyOtherNetLabel;
876
876
  private getCornerCandidatesForLabel;
877
877
  private isConfiguredRailLabel;
878
+ private isLabelCrossedByOtherNetTrace;
878
879
  private pointsEqual;
879
880
  private getVerticalSegmentPointIndices;
880
881
  private isPinAlignedCorner;
package/dist/index.js CHANGED
@@ -5108,6 +5108,51 @@ var getVisibleTraceMetrics = (traces) => {
5108
5108
  var getVisibleTraceLength = (traces) => getVisibleTraceMetrics(traces).length;
5109
5109
  var getVisibleTraceSegmentCount = (traces) => getVisibleTraceMetrics(traces).segmentCount;
5110
5110
 
5111
+ // lib/solvers/TraceCleanupSolver/shortenExcessiveLabelPaddingDetour.ts
5112
+ var MIN_EXCESSIVE_DETOUR_LENGTH_RATIO = 2.5;
5113
+ var getTracePathLength = (tracePath) => {
5114
+ let pathLength = 0;
5115
+ for (let pointIndex = 1; pointIndex < tracePath.length; pointIndex++) {
5116
+ const previousPoint = tracePath[pointIndex - 1];
5117
+ const point = tracePath[pointIndex];
5118
+ pathLength += Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
5119
+ }
5120
+ return pathLength;
5121
+ };
5122
+ var shortenExcessiveLabelPaddingDetour = ({
5123
+ inputProblem,
5124
+ trace
5125
+ }) => {
5126
+ const exactConnection = inputProblem.directConnections.find(
5127
+ (connection) => connection.pinIds.every((pinId) => trace.pinIds.includes(pinId))
5128
+ );
5129
+ if (!exactConnection) return trace;
5130
+ const chipMap = {};
5131
+ for (const chip of inputProblem.chips) chipMap[chip.chipId] = chip;
5132
+ const candidateSolver = new SchematicTraceSingleLineSolver2({
5133
+ pins: trace.pins,
5134
+ connectionPair: trace,
5135
+ inputProblem: {
5136
+ ...inputProblem,
5137
+ directConnections: [
5138
+ exactConnection,
5139
+ ...inputProblem.directConnections.filter(
5140
+ (connection) => connection !== exactConnection
5141
+ )
5142
+ ]
5143
+ },
5144
+ chipMap
5145
+ });
5146
+ candidateSolver.solve();
5147
+ if (!candidateSolver.solvedTracePath) return trace;
5148
+ const currentLength = getTracePathLength(trace.tracePath);
5149
+ const candidateLength = getTracePathLength(candidateSolver.solvedTracePath);
5150
+ if (currentLength <= candidateLength * MIN_EXCESSIVE_DETOUR_LENGTH_RATIO) {
5151
+ return trace;
5152
+ }
5153
+ return { ...trace, tracePath: candidateSolver.solvedTracePath };
5154
+ };
5155
+
5111
5156
  // lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
5112
5157
  var minimizeTurnsWithFilteredLabels = ({
5113
5158
  targetMspConnectionPairId,
@@ -5117,12 +5162,16 @@ var minimizeTurnsWithFilteredLabels = ({
5117
5162
  mergedLabelNetIdMap,
5118
5163
  paddingBuffer
5119
5164
  }) => {
5120
- const targetTrace = traces.find(
5165
+ const originalTargetTrace = traces.find(
5121
5166
  (t) => t.mspPairId === targetMspConnectionPairId
5122
5167
  );
5123
- if (!targetTrace) {
5168
+ if (!originalTargetTrace) {
5124
5169
  throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
5125
5170
  }
5171
+ const targetTrace = shortenExcessiveLabelPaddingDetour({
5172
+ inputProblem,
5173
+ trace: originalTargetTrace
5174
+ });
5126
5175
  const targetPinIds = new Set(targetTrace.pinIds);
5127
5176
  const otherTraces = traces.filter(
5128
5177
  (trace) => trace.mspPairId !== targetMspConnectionPairId
@@ -8996,6 +9045,7 @@ var ensureGraphicsArrays2 = (graphics) => {
8996
9045
  };
8997
9046
 
8998
9047
  // lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts
9048
+ var LABEL_TRACE_CLEARANCE3 = 0.1;
8999
9049
  var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9000
9050
  inputProblem;
9001
9051
  traces;
@@ -9177,11 +9227,11 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9177
9227
  const anchorAlignedCandidates = [];
9178
9228
  const railAlignedCandidates = [];
9179
9229
  const labelTraces = this.getTraceLinesForLabel(label);
9180
- const allowRailAlignedFallback = this.isConfiguredRailLabel(label) && !labelTraces.some(
9230
+ const allowRailAlignedFallback = this.isConfiguredRailLabel(label) && (this.isLabelCrossedByOtherNetTrace(label) || !labelTraces.some(
9181
9231
  (trace) => getTraceCorners(trace.tracePath).some(
9182
9232
  (corner) => this.pointsEqual(corner, label.anchorPoint)
9183
9233
  )
9184
- );
9234
+ ));
9185
9235
  for (const trace of labelTraces) {
9186
9236
  const path = trace.tracePath;
9187
9237
  const pins = [path[0], path[path.length - 1]];
@@ -9220,6 +9270,13 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9220
9270
  (orientation) => orientation === "y+" || orientation === "y-"
9221
9271
  );
9222
9272
  }
9273
+ isLabelCrossedByOtherNetTrace(label) {
9274
+ const bounds = getRectBounds(label.center, label.width, label.height);
9275
+ const otherNetTraceMap = Object.fromEntries(
9276
+ this.traces.filter((trace) => trace.globalConnNetId !== label.globalConnNetId).map((trace) => [trace.mspPairId, trace])
9277
+ );
9278
+ return traceCrossesBoundsInterior(bounds, otherNetTraceMap);
9279
+ }
9223
9280
  pointsEqual(a, b) {
9224
9281
  return Math.abs(a.x - b.x) <= EPS6 && Math.abs(a.y - b.y) <= EPS6;
9225
9282
  }
@@ -9269,6 +9326,30 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9269
9326
  shiftedCoordinates.add(chipBounds.minX - label.width / 2);
9270
9327
  shiftedCoordinates.add(chipBounds.maxX + label.width / 2);
9271
9328
  }
9329
+ for (const otherTrace of this.traces) {
9330
+ if (otherTrace.globalConnNetId === label.globalConnNetId) continue;
9331
+ for (let i = 0; i < otherTrace.tracePath.length - 1; i++) {
9332
+ const start = otherTrace.tracePath[i];
9333
+ const end = otherTrace.tracePath[i + 1];
9334
+ if (!segmentCrossesBoundsInterior(start, end, labelBounds)) continue;
9335
+ const halfLabelWidth = label.width / 2;
9336
+ if (Math.abs(start.x - end.x) <= EPS6) {
9337
+ shiftedCoordinates.add(
9338
+ start.x - halfLabelWidth - LABEL_TRACE_CLEARANCE3
9339
+ );
9340
+ shiftedCoordinates.add(
9341
+ start.x + halfLabelWidth + LABEL_TRACE_CLEARANCE3
9342
+ );
9343
+ } else {
9344
+ shiftedCoordinates.add(
9345
+ Math.min(start.x, end.x) - halfLabelWidth - LABEL_TRACE_CLEARANCE3
9346
+ );
9347
+ shiftedCoordinates.add(
9348
+ Math.max(start.x, end.x) + halfLabelWidth + LABEL_TRACE_CLEARANCE3
9349
+ );
9350
+ }
9351
+ }
9352
+ }
9272
9353
  return [...shiftedCoordinates].flatMap((x) => {
9273
9354
  if (Math.abs(x - corner.x) <= EPS6) return [];
9274
9355
  const reroutedTracePath = simplifyPath(
@@ -41,7 +41,11 @@ export const traceCrossesBoundsInterior = (
41
41
  return false
42
42
  }
43
43
 
44
- const segmentCrossesBoundsInterior = (p1: Point, p2: Point, bounds: Bounds) => {
44
+ export const segmentCrossesBoundsInterior = (
45
+ p1: Point,
46
+ p2: Point,
47
+ bounds: Bounds,
48
+ ) => {
45
49
  const interiorBounds = {
46
50
  minX: bounds.minX + TRACE_BOUNDARY_TOLERANCE,
47
51
  minY: bounds.minY + TRACE_BOUNDARY_TOLERANCE,
@@ -1,6 +1,9 @@
1
1
  import type { GraphicsObject } from "graphics-debug"
2
2
  import type { Point } from "@tscircuit/math-utils"
3
- import { traceCrossesBoundsInterior } from "lib/solvers/AvailableNetOrientationSolver/geometry"
3
+ import {
4
+ segmentCrossesBoundsInterior,
5
+ traceCrossesBoundsInterior,
6
+ } from "lib/solvers/AvailableNetOrientationSolver/geometry"
4
7
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
5
8
  import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
6
9
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
@@ -32,6 +35,8 @@ import type {
32
35
  import { visualizeRailNetLabelCornerPlacementSolver } from "./visualize"
33
36
  import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
34
37
 
38
+ const LABEL_TRACE_CLEARANCE = 0.1
39
+
35
40
  export class RailNetLabelCornerPlacementSolver extends BaseSolver {
36
41
  inputProblem: InputProblem
37
42
  traces: SolvedTracePath[]
@@ -271,11 +276,12 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
271
276
  const labelTraces = this.getTraceLinesForLabel(label)
272
277
  const allowRailAlignedFallback =
273
278
  this.isConfiguredRailLabel(label) &&
274
- !labelTraces.some((trace) =>
275
- getTraceCorners(trace.tracePath).some((corner) =>
276
- this.pointsEqual(corner, label.anchorPoint),
277
- ),
278
- )
279
+ (this.isLabelCrossedByOtherNetTrace(label) ||
280
+ !labelTraces.some((trace) =>
281
+ getTraceCorners(trace.tracePath).some((corner) =>
282
+ this.pointsEqual(corner, label.anchorPoint),
283
+ ),
284
+ ))
279
285
 
280
286
  for (const trace of labelTraces) {
281
287
  const path = trace.tracePath
@@ -324,6 +330,17 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
324
330
  )
325
331
  }
326
332
 
333
+ private isLabelCrossedByOtherNetTrace(label: NetLabelPlacement) {
334
+ const bounds = getRectBounds(label.center, label.width, label.height)
335
+ const otherNetTraceMap = Object.fromEntries(
336
+ this.traces
337
+ .filter((trace) => trace.globalConnNetId !== label.globalConnNetId)
338
+ .map((trace) => [trace.mspPairId, trace]),
339
+ )
340
+
341
+ return traceCrossesBoundsInterior(bounds, otherNetTraceMap)
342
+ }
343
+
327
344
  private pointsEqual(a: Point, b: Point) {
328
345
  return Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS
329
346
  }
@@ -394,6 +411,32 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
394
411
  shiftedCoordinates.add(chipBounds.minX - label.width / 2)
395
412
  shiftedCoordinates.add(chipBounds.maxX + label.width / 2)
396
413
  }
414
+ for (const otherTrace of this.traces) {
415
+ if (otherTrace.globalConnNetId === label.globalConnNetId) continue
416
+
417
+ for (let i = 0; i < otherTrace.tracePath.length - 1; i++) {
418
+ const start = otherTrace.tracePath[i]!
419
+ const end = otherTrace.tracePath[i + 1]!
420
+ if (!segmentCrossesBoundsInterior(start, end, labelBounds)) continue
421
+
422
+ const halfLabelWidth = label.width / 2
423
+ if (Math.abs(start.x - end.x) <= EPS) {
424
+ shiftedCoordinates.add(
425
+ start.x - halfLabelWidth - LABEL_TRACE_CLEARANCE,
426
+ )
427
+ shiftedCoordinates.add(
428
+ start.x + halfLabelWidth + LABEL_TRACE_CLEARANCE,
429
+ )
430
+ } else {
431
+ shiftedCoordinates.add(
432
+ Math.min(start.x, end.x) - halfLabelWidth - LABEL_TRACE_CLEARANCE,
433
+ )
434
+ shiftedCoordinates.add(
435
+ Math.max(start.x, end.x) + halfLabelWidth + LABEL_TRACE_CLEARANCE,
436
+ )
437
+ }
438
+ }
439
+ }
397
440
 
398
441
  return [...shiftedCoordinates].flatMap((x) => {
399
442
  if (Math.abs(x - corner.x) <= EPS) return []
@@ -9,6 +9,7 @@ import {
9
9
  getVisibleTraceSegmentCount,
10
10
  RAIL_ALIGNMENT_EPSILON,
11
11
  } from "./sameNetRailAlignment/geometry"
12
+ import { shortenExcessiveLabelPaddingDetour } from "./shortenExcessiveLabelPaddingDetour"
12
13
 
13
14
  /**
14
15
  * Minimizes turns with a strict pass that treats every other trace as an
@@ -31,12 +32,16 @@ export const minimizeTurnsWithFilteredLabels = ({
31
32
  mergedLabelNetIdMap: Record<string, Set<string>>
32
33
  paddingBuffer: number
33
34
  }): SolvedTracePath => {
34
- const targetTrace = traces.find(
35
+ const originalTargetTrace = traces.find(
35
36
  (t) => t.mspPairId === targetMspConnectionPairId,
36
37
  )
37
- if (!targetTrace) {
38
+ if (!originalTargetTrace) {
38
39
  throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
39
40
  }
41
+ const targetTrace = shortenExcessiveLabelPaddingDetour({
42
+ inputProblem,
43
+ trace: originalTargetTrace,
44
+ })
40
45
 
41
46
  const targetPinIds = new Set(targetTrace.pinIds)
42
47
  const otherTraces = traces.filter(
@@ -0,0 +1,64 @@
1
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2
+ import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
3
+ import type { InputChip, InputProblem } from "lib/types/InputProblem"
4
+
5
+ const MIN_EXCESSIVE_DETOUR_LENGTH_RATIO = 2.5
6
+
7
+ const getTracePathLength = (
8
+ tracePath: SolvedTracePath["tracePath"],
9
+ ): number => {
10
+ let pathLength = 0
11
+ for (let pointIndex = 1; pointIndex < tracePath.length; pointIndex++) {
12
+ const previousPoint = tracePath[pointIndex - 1]!
13
+ const point = tracePath[pointIndex]!
14
+ pathLength +=
15
+ Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y)
16
+ }
17
+ return pathLength
18
+ }
19
+
20
+ /**
21
+ * Retries an already-solved severe detour with its exact direct connection
22
+ * first, preventing a sibling branch's label width from inflating text padding.
23
+ * The shorter route is used only when it is less than 40% of the original.
24
+ */
25
+ export const shortenExcessiveLabelPaddingDetour = ({
26
+ inputProblem,
27
+ trace,
28
+ }: {
29
+ inputProblem: InputProblem
30
+ trace: SolvedTracePath
31
+ }): SolvedTracePath => {
32
+ const exactConnection = inputProblem.directConnections.find((connection) =>
33
+ connection.pinIds.every((pinId) => trace.pinIds.includes(pinId)),
34
+ )
35
+ if (!exactConnection) return trace
36
+
37
+ const chipMap: Record<string, InputChip> = {}
38
+ for (const chip of inputProblem.chips) chipMap[chip.chipId] = chip
39
+
40
+ const candidateSolver = new SchematicTraceSingleLineSolver2({
41
+ pins: trace.pins,
42
+ connectionPair: trace,
43
+ inputProblem: {
44
+ ...inputProblem,
45
+ directConnections: [
46
+ exactConnection,
47
+ ...inputProblem.directConnections.filter(
48
+ (connection) => connection !== exactConnection,
49
+ ),
50
+ ],
51
+ },
52
+ chipMap,
53
+ })
54
+ candidateSolver.solve()
55
+ if (!candidateSolver.solvedTracePath) return trace
56
+
57
+ const currentLength = getTracePathLength(trace.tracePath)
58
+ const candidateLength = getTracePathLength(candidateSolver.solvedTracePath)
59
+ if (currentLength <= candidateLength * MIN_EXCESSIVE_DETOUR_LENGTH_RATIO) {
60
+ return trace
61
+ }
62
+
63
+ return { ...trace, tracePath: candidateSolver.solvedTracePath }
64
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.123",
4
+ "version": "0.0.125",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260805T061316Z/bug-report-20260805T061316Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260806T061548Z/bug-report-20260806T061548Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -1,20 +1,20 @@
1
- <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -3.79,0.39" data-type="line" data-label="" points="357.4007220216606,371.0469314079422 95.5956678700361,361.9494584837545" fill="none" stroke="hsl(136, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -2.22,0.38000000000000006" data-type="line" data-label="" points="357.4007220216606,371.0469314079422 254.29602888086637,362.96028880866425" fill="none" stroke="hsl(136, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="-3.79,-0.3899999999999999 -2.22,-0.37999999999999995" data-type="line" data-label="" points="95.5956678700361,440.79422382671476 254.29602888086637,439.783393501805" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-2.22,0.3800000000000002 -2.22,0.5900000000000002 -3.79,0.5900000000000002 -3.79,0.39" data-type="line" data-label="" points="254.29602888086637,362.9602888086642 254.29602888086637,341.7328519855595 95.5956678700361,341.7328519855595 95.5956678700361,361.9494584837545" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-1.2000000000000002,0.3 -1.4125,0.3 -1.4125,1.78 -2.22,1.78 -2.22,0.38000000000000006" data-type="line" data-label="" points="357.4007220216606,371.0469314079422 335.9205776173285,371.0469314079422 335.9205776173285,221.4440433212996 254.29602888086637,221.4440433212996 254.29602888086637,362.96028880866425" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-3.79,-0.3899999999999999 -3.79,-0.5900000000000001 -2.22,-0.5900000000000001 -2.22,-0.3800000000000001" data-type="line" data-label="" points="95.5956678700361,440.79422382671476 95.5956678700361,461.0108303249097 254.29602888086637,461.0108303249097 254.29602888086637,439.78339350180505" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="-3.675" data-y="5.551115123125783e-17" x="40" y="361.9494584837545" width="134.44043321299637" height="78.84476534296027" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="-2.085" data-y="5.551115123125783e-17" x="221.44404332129963" y="362.96028880866425" width="92.99638989169677" height="76.82310469314075" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_2" data-x="0" data-y="0" x="357.4007220216606" y="350.8303249097473" width="242.5992779783394" height="101.08303249097469" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="U1" data-x="-0.68" data-y="0.615" x="391.76895306859205" y="326.5703971119133" width="36.38989169675091" height="25.270758122743644" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="netId: GND
2
- globalConnNetId: connectivity_net1" data-x="-3.79" data-y="-0.8" x="71.33574007220213" y="461.01083032490976" width="48.519855595667934" height="42.454873646209364" fill="#00000066" stroke="#000000" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="netId: .U1 &gt; .pin1 to .C2 &gt; .pin1
3
- globalConnNetId: connectivity_net0" data-x="-2.22" data-y="2.2" x="244.18772563176887" y="136.53429602888082" width="20.216606498195006" height="84.90974729241876" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009892857142857142"/></g><g><circle data-type="point" data-label="schematic_port_0
4
- y-" data-x="-3.79" data-y="-0.3899999999999999" cx="95.5956678700361" cy="440.79422382671476" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_1
5
- y+" data-x="-3.79" data-y="0.39" cx="95.5956678700361" cy="361.9494584837545" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_2
6
- y+" data-x="-2.22" data-y="0.38000000000000006" cx="254.29602888086637" cy="362.96028880866425" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_3
7
- y-" data-x="-2.22" data-y="-0.37999999999999995" cx="254.29602888086637" cy="439.783393501805" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_4
8
- x-" data-x="-1.2000000000000002" data-y="0.30000000000000004" cx="357.4007220216606" cy="371.0469314079422" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_5
9
- x-" data-x="-1.2000000000000002" data-y="0.10000000000000003" cx="357.4007220216606" cy="391.2635379061371" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_6
10
- x-" data-x="-1.2000000000000002" data-y="-0.09999999999999998" cx="357.4007220216606" cy="411.48014440433207" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_7
11
- x-" data-x="-1.2000000000000002" data-y="-0.30000000000000004" cx="357.4007220216606" cy="431.696750902527" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_8
12
- x+" data-x="1.2000000000000002" data-y="-0.30000000000000004" cx="600" cy="431.696750902527" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_9
13
- x+" data-x="1.2000000000000002" data-y="-0.10000000000000003" cx="600" cy="411.4801444043321" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_10
14
- x+" data-x="1.2000000000000002" data-y="0.09999999999999998" cx="600" cy="391.2635379061372" r="3" fill="hsl(184, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_11
15
- x+" data-x="1.2000000000000002" data-y="0.30000000000000004" cx="600" cy="371.0469314079422" r="3" fill="hsl(184, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
16
- orientation: y-" data-x="-3.79" data-y="-0.5900000000000001" cx="95.5956678700361" cy="461.0108303249097" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
17
- orientation: y+" data-x="-2.22" data-y="1.78" cx="254.29602888086637" cy="221.4440433212996" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -3.79,0.39" data-type="line" data-label="" points="357.4007220216606,296.2454873646209 95.5956678700361,287.1480144404332" fill="none" stroke="hsl(136, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -2.22,0.38000000000000006" data-type="line" data-label="" points="357.4007220216606,296.2454873646209 254.29602888086637,288.1588447653429" fill="none" stroke="hsl(136, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="-3.79,-0.3899999999999999 -2.22,-0.37999999999999995" data-type="line" data-label="" points="95.5956678700361,365.9927797833935 254.29602888086637,364.9819494584837" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-2.22,0.3800000000000002 -2.22,0.5800000000000003 -3.79,0.5800000000000003 -3.79,0.39" data-type="line" data-label="" points="254.29602888086637,288.1588447653429 254.29602888086637,267.94223826714796 95.5956678700361,267.94223826714796 95.5956678700361,287.1480144404332" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-1.2000000000000002,0.3 -1.4125,0.3 -1.4125,0.5800000000000003 -2.22,0.5800000000000003 -2.22,0.38000000000000006" data-type="line" data-label="" points="357.4007220216606,296.24548736462094 335.9205776173285,296.24548736462094 335.9205776173285,267.94223826714796 254.29602888086637,267.94223826714796 254.29602888086637,288.1588447653429" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-3.79,-0.3899999999999999 -3.79,-0.5900000000000001 -2.22,-0.5900000000000001 -2.22,-0.3800000000000001" data-type="line" data-label="" points="95.5956678700361,365.9927797833935 95.5956678700361,386.2093862815884 254.29602888086637,386.2093862815884 254.29602888086637,364.98194945848377" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="-3.675" data-y="5.551115123125783e-17" x="40" y="287.1480144404332" width="134.44043321299637" height="78.84476534296027" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="-2.085" data-y="5.551115123125783e-17" x="221.44404332129963" y="288.1588447653429" width="92.99638989169677" height="76.8231046931408" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_2" data-x="0" data-y="0" x="357.4007220216606" y="276.028880866426" width="242.5992779783394" height="101.08303249097469" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="U1" data-x="-0.68" data-y="0.615" x="391.76895306859205" y="251.76895306859208" width="36.38989169675091" height="25.270758122743644" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="netId: GND
2
+ globalConnNetId: connectivity_net1" data-x="-3.79" data-y="-0.8" x="71.33574007220213" y="386.2093862815884" width="48.519855595667934" height="42.45487364620942" fill="#00000066" stroke="#000000" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="netId: .U1 &gt; .pin1 to .C2 &gt; .pin1
3
+ globalConnNetId: connectivity_net0" data-x="-1.3062500000000001" data-y="0.72" x="336.55234657039705" y="211.33574007220213" width="20.216606498195006" height="84.90974729241879" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009892857142857142"/></g><g><circle data-type="point" data-label="schematic_port_0
4
+ y-" data-x="-3.79" data-y="-0.3899999999999999" cx="95.5956678700361" cy="365.9927797833935" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_1
5
+ y+" data-x="-3.79" data-y="0.39" cx="95.5956678700361" cy="287.1480144404332" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_2
6
+ y+" data-x="-2.22" data-y="0.38000000000000006" cx="254.29602888086637" cy="288.1588447653429" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_3
7
+ y-" data-x="-2.22" data-y="-0.37999999999999995" cx="254.29602888086637" cy="364.9819494584837" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_4
8
+ x-" data-x="-1.2000000000000002" data-y="0.30000000000000004" cx="357.4007220216606" cy="296.2454873646209" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_5
9
+ x-" data-x="-1.2000000000000002" data-y="0.10000000000000003" cx="357.4007220216606" cy="316.46209386281583" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_6
10
+ x-" data-x="-1.2000000000000002" data-y="-0.09999999999999998" cx="357.4007220216606" cy="336.6787003610108" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_7
11
+ x-" data-x="-1.2000000000000002" data-y="-0.30000000000000004" cx="357.4007220216606" cy="356.8953068592058" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_8
12
+ x+" data-x="1.2000000000000002" data-y="-0.30000000000000004" cx="600" cy="356.8953068592058" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_9
13
+ x+" data-x="1.2000000000000002" data-y="-0.10000000000000003" cx="600" cy="336.67870036101084" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_10
14
+ x+" data-x="1.2000000000000002" data-y="0.09999999999999998" cx="600" cy="316.4620938628159" r="3" fill="hsl(184, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_11
15
+ x+" data-x="1.2000000000000002" data-y="0.30000000000000004" cx="600" cy="296.2454873646209" r="3" fill="hsl(184, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
16
+ orientation: y-" data-x="-3.79" data-y="-0.5900000000000001" cx="95.5956678700361" cy="386.2093862815884" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
17
+ orientation: y+" data-x="-1.3062500000000001" data-y="0.3" cx="346.66064981949455" cy="296.24548736462094" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
18
18
  document.currentScript.parentElement.addEventListener('mousemove', (e) => {
19
19
  const svg = e.currentTarget;
20
20
  const rect = svg.getBoundingClientRect();
@@ -36,7 +36,7 @@ orientation: y+" data-x="-2.22" data-y="1.78" cx="254.29602888086637" cy="221.44
36
36
  v.setAttribute('y2', '640');
37
37
 
38
38
  // Calculate real coordinates using inverse transformation
39
- const matrix = {"a":101.08303249097473,"c":0,"e":478.7003610108303,"b":0,"d":-101.08303249097473,"f":401.3718411552346};
39
+ const matrix = {"a":101.08303249097473,"c":0,"e":478.7003610108303,"b":0,"d":-101.08303249097473,"f":326.57039711191334};
40
40
  // Manually invert and apply the affine transform
41
41
  // Since we only use translate and scale, we can directly compute:
42
42
  // x' = (x - tx) / sx
@@ -3,10 +3,28 @@ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipeline
3
3
  import inputProblem from "./bug-report-20260804T225912Z.json"
4
4
  import "tests/fixtures/matcher"
5
5
 
6
+ const EXPECTED_CHIP_TO_CAPACITOR_MAX_Y = 0.58
7
+
6
8
  test("bug-report-20260804T225912Z", () => {
7
9
  const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
10
 
9
11
  solver.solve()
10
12
 
13
+ const chipToCapacitorTrace = solver.traceCleanupSolver
14
+ ?.getOutput()
15
+ .traces.find(
16
+ (trace) =>
17
+ trace.pinIds.includes("schematic_port_4") &&
18
+ trace.pinIds.includes("schematic_port_2"),
19
+ )
20
+ expect(chipToCapacitorTrace).toBeDefined()
21
+ if (!chipToCapacitorTrace)
22
+ throw new Error("Chip-to-capacitor trace not found")
23
+
24
+ const maxTraceY = Math.max(
25
+ ...chipToCapacitorTrace.tracePath.map((point) => point.y),
26
+ )
27
+
28
+ expect(maxTraceY).toBeCloseTo(EXPECTED_CHIP_TO_CAPACITOR_MAX_Y)
11
29
  expect(solver).toMatchSolverSnapshot(import.meta.path)
12
30
  })