@tscircuit/fanout-solver 0.0.22 → 0.0.24

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/README.md CHANGED
@@ -61,12 +61,12 @@ and treats each bus-layer decision atomically.
61
61
  - Uses a straight pad-pair escape when the via fits. Otherwise it uses a 45°
62
62
  four-pad interstitial escape and nested side bands that spread deeper
63
63
  two-layer buses around already-routed outer buses.
64
- - `compactBusTracks` bends each bus into a trace/clearance-pitch routing
65
- envelope, so a wide pad row does not consume a disproportionately wide
66
- breakout corridor.
67
- - `preferOriginalEndpointTracks` projects boundary tracks onto the original
68
- downstream pad coordinates and prefers their layer when legal. This lets
69
- ordered edge-pad buses make direct, visibly continuous pad connections.
64
+ - `compactBusTracks` provides a trace/clearance-pitch fallback envelope when
65
+ the original endpoint track cannot route, so a wide pad row does not consume
66
+ a disproportionately wide breakout corridor.
67
+ - Boundary tracks project onto the original downstream pad coordinates and
68
+ prefer their layer when legal. This lets ordered edge-pad buses make direct,
69
+ visibly continuous pad connections.
70
70
  - Chamfers orthogonal routing corners into 45° segments before validating and
71
71
  emitting the fanout.
72
72
  - Verifies oriented-pad, via, trace, and already-routed fanout clearance on
@@ -125,7 +125,6 @@ const fanoutSolver = new FanoutSolver(simpleRouteJson, {
125
125
  availableCornersAndSides: ["top_left", "top", "top_right"],
126
126
  borderDistribution: "even",
127
127
  compactBusTracks: true,
128
- preferOriginalEndpointTracks: true,
129
128
  buses: [
130
129
  {
131
130
  busId: "ground",
@@ -40,7 +40,6 @@ interface ResolvedFanoutConfig {
40
40
  viaHoleDiameter: number
41
41
  clearance: number
42
42
  compactBusTracks: boolean
43
- preferOriginalEndpointTracks: boolean
44
43
  allowSameNetMerges: boolean
45
44
  singleLayerPushAndShove: boolean
46
45
  singleLayerAdaptiveExits: boolean
@@ -132,7 +131,6 @@ function resolveConfig(
132
131
  viaHoleDiameter,
133
132
  clearance,
134
133
  compactBusTracks: options.compactBusTracks ?? false,
135
- preferOriginalEndpointTracks: options.preferOriginalEndpointTracks ?? false,
136
134
  allowSameNetMerges: options.allowSameNetMerges ?? false,
137
135
  singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
138
136
  singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
@@ -210,6 +208,19 @@ function getBusDistanceToBoundary(bus: PreparedBus): number {
210
208
  }
211
209
  }
212
210
 
211
+ function busUsesOriginalEndpointTracks(bus: PreparedBus): boolean {
212
+ const isHorizontal = bus.direction === "left" || bus.direction === "right"
213
+ return bus.connections.some((connection) => {
214
+ const sourceTrack = isHorizontal
215
+ ? connection.sourcePoint.y
216
+ : connection.sourcePoint.x
217
+ const targetTrack = isHorizontal
218
+ ? connection.targetPoint.y
219
+ : connection.targetPoint.x
220
+ return Math.abs(sourceTrack - targetTrack) > 1e-6
221
+ })
222
+ }
223
+
213
224
  function busIsOnOutwardComponentEdge(bus: PreparedBus): boolean {
214
225
  const isHorizontal = bus.direction === "left" || bus.direction === "right"
215
226
  const directionalCoordinates = isHorizontal
@@ -256,14 +267,8 @@ function createPreferredLayerAssignment(params: {
256
267
  buses: PreparedBus[]
257
268
  escapeLayers: string[]
258
269
  escapeLayersByBusId: Readonly<Record<string, readonly string[]>>
259
- preferOriginalEndpointTracks: boolean
260
270
  }): Readonly<Record<string, string>> {
261
- const {
262
- buses,
263
- escapeLayers,
264
- escapeLayersByBusId,
265
- preferOriginalEndpointTracks,
266
- } = params
271
+ const { buses, escapeLayers, escapeLayersByBusId } = params
267
272
  const assignment: Record<string, string> = {}
268
273
  const directionsByComponent = new Map<string, Set<PreparedBus["direction"]>>()
269
274
  let nextViaLayerIndex = 0
@@ -288,7 +293,7 @@ function createPreferredLayerAssignment(params: {
288
293
  )
289
294
  if (
290
295
  routableEscapeLayers.includes(sourceLayer) &&
291
- (preferOriginalEndpointTracks || busIsOnOutwardComponentEdge(bus))
296
+ (busUsesOriginalEndpointTracks(bus) || busIsOnOutwardComponentEdge(bus))
292
297
  ) {
293
298
  assignment[bus.busId] = sourceLayer
294
299
  } else if (viaLayers.length > 0) {
@@ -347,7 +352,6 @@ function getCandidateEscapeLayersForBus(params: {
347
352
  viaHoleDiameter: config.viaHoleDiameter,
348
353
  clearance: config.clearance,
349
354
  compactBusTracks: config.compactBusTracks,
350
- preferOriginalEndpointTracks: config.preferOriginalEndpointTracks,
351
355
  allowSameNetMerges: config.allowSameNetMerges,
352
356
  staticClearanceCache,
353
357
  }) !== null,
@@ -458,7 +462,6 @@ export class FanoutSolver extends BaseSolver {
458
462
  buses: this.preparedBuses,
459
463
  escapeLayers: this.config.escapeLayers,
460
464
  escapeLayersByBusId,
461
- preferOriginalEndpointTracks: this.config.preferOriginalEndpointTracks,
462
465
  }),
463
466
  generatedAssignments,
464
467
  maxAssignments: this.config.maxLayerCombinations,
@@ -605,7 +608,6 @@ export class FanoutSolver extends BaseSolver {
605
608
  viaHoleDiameter: this.config.viaHoleDiameter,
606
609
  clearance: this.config.clearance,
607
610
  compactBusTracks: this.config.compactBusTracks,
608
- preferOriginalEndpointTracks: this.config.preferOriginalEndpointTracks,
609
611
  allowSameNetMerges: this.config.allowSameNetMerges,
610
612
  staticClearanceCache: this.routeStaticClearanceCache,
611
613
  blockingBusCounts: currentBusBlockingCounts,
@@ -791,7 +793,12 @@ export class FanoutSolver extends BaseSolver {
791
793
  const viaCount = getPlanViaCount(state.plans)
792
794
  const offEndpointLayerConnectionCount = this.preparedBuses.reduce(
793
795
  (count, bus) => {
794
- if (bus.termination.type !== "boundary") return count
796
+ if (
797
+ bus.termination.type !== "boundary" ||
798
+ !busUsesOriginalEndpointTracks(bus)
799
+ ) {
800
+ return count
801
+ }
795
802
  const sourceLayer = bus.connections[0]?.sourceLayer
796
803
  return state.assignment[bus.busId] === sourceLayer
797
804
  ? count
@@ -802,9 +809,7 @@ export class FanoutSolver extends BaseSolver {
802
809
  return (
803
810
  routeLength +
804
811
  viaCount * 0.1 +
805
- (this.config.preferOriginalEndpointTracks
806
- ? offEndpointLayerConnectionCount * 10_000
807
- : 0) +
812
+ offEndpointLayerConnectionCount * 10_000 +
808
813
  assignmentLoadPenalty(
809
814
  state.assignment,
810
815
  this.preparedBuses,
@@ -835,10 +840,11 @@ export class FanoutSolver extends BaseSolver {
835
840
  )
836
841
  }
837
842
  const sourceLayer = bus.connections[0]?.sourceLayer
843
+ const preferSourceLayer = busUsesOriginalEndpointTracks(bus)
838
844
  const orderedLayers = candidateLayers.toSorted(
839
845
  (first, second) =>
840
846
  (layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) ||
841
- (this.config.preferOriginalEndpointTracks
847
+ (preferSourceLayer
842
848
  ? Number(second === sourceLayer) - Number(first === sourceLayer)
843
849
  : Number(first === sourceLayer) -
844
850
  Number(second === sourceLayer)) ||
@@ -858,8 +864,6 @@ export class FanoutSolver extends BaseSolver {
858
864
  viaHoleDiameter: this.config.viaHoleDiameter,
859
865
  clearance: this.config.clearance,
860
866
  compactBusTracks: this.config.compactBusTracks,
861
- preferOriginalEndpointTracks:
862
- this.config.preferOriginalEndpointTracks,
863
867
  allowSameNetMerges: this.config.allowSameNetMerges,
864
868
  staticClearanceCache: this.routeStaticClearanceCache,
865
869
  },
package/lib/route-bus.ts CHANGED
@@ -39,7 +39,6 @@ export interface RouteBusParams {
39
39
  viaHoleDiameter: number
40
40
  clearance: number
41
41
  compactBusTracks: boolean
42
- preferOriginalEndpointTracks?: boolean
43
42
  allowSameNetMerges?: boolean
44
43
  staticClearanceCache?: RouteBusStaticClearanceCache
45
44
  blockingBusCounts?: Map<string, number>
@@ -293,10 +292,19 @@ function getTrackCandidates(params: {
293
292
  function getPreferredTrack(params: {
294
293
  bus: PreparedBus
295
294
  connection: PreparedConnection
295
+ }): number {
296
+ return getPerpendicularAxis(
297
+ params.connection.targetPoint,
298
+ params.bus.direction,
299
+ )
300
+ }
301
+
302
+ function getLegacyPreferredTrack(params: {
303
+ bus: PreparedBus
304
+ connection: PreparedConnection
296
305
  targetUsesVia: boolean
297
306
  interstitialEscape: boolean
298
307
  compactBusTracks: boolean
299
- preferOriginalEndpointTracks: boolean
300
308
  traceWidth: number
301
309
  viaDiameter: number
302
310
  clearance: number
@@ -307,7 +315,6 @@ function getPreferredTrack(params: {
307
315
  targetUsesVia,
308
316
  interstitialEscape,
309
317
  compactBusTracks,
310
- preferOriginalEndpointTracks,
311
318
  traceWidth,
312
319
  viaDiameter,
313
320
  clearance,
@@ -319,9 +326,6 @@ function getPreferredTrack(params: {
319
326
  connection.sourcePoint,
320
327
  bus.direction,
321
328
  )
322
- if (preferOriginalEndpointTracks) {
323
- return getPerpendicularAxis(connection.targetPoint, bus.direction)
324
- }
325
329
  if (compactBusTracks) {
326
330
  const connectionRank = getConnectionRank(bus, connection)
327
331
  const componentCenter =
@@ -332,11 +336,9 @@ function getPreferredTrack(params: {
332
336
  (traceWidth + clearance)
333
337
  )
334
338
  }
335
- if (!targetUsesVia) return sourceTrack
336
- if (!interstitialEscape) return sourceTrack
339
+ if (!targetUsesVia || !interstitialEscape) return sourceTrack
337
340
 
338
341
  const depthInRows = getDepthInRows(bus)
339
-
340
342
  const trackPitch = traceWidth + clearance
341
343
  const halfConnectionCount = Math.ceil(bus.connections.length / 2)
342
344
  const sideBandWidth = (halfConnectionCount - 1) * trackPitch
@@ -1296,7 +1298,6 @@ export function routeBusAlternatives(
1296
1298
  viaHoleDiameter,
1297
1299
  clearance,
1298
1300
  compactBusTracks,
1299
- preferOriginalEndpointTracks = false,
1300
1301
  staticClearanceCache,
1301
1302
  blockingBusCounts,
1302
1303
  allowSameNetMerges = false,
@@ -1359,23 +1360,42 @@ export function routeBusAlternatives(
1359
1360
 
1360
1361
  const preparedConnection = connectionOrder[connectionIndex]!
1361
1362
  const connectionRank = getConnectionRank(bus, preparedConnection)
1362
- const trackCandidates = getTrackCandidates({
1363
- bus,
1364
- connection: preparedConnection,
1365
- preferredTrack: getPreferredTrack({
1363
+ const preferredTracks = [
1364
+ getPreferredTrack({
1365
+ bus,
1366
+ connection: preparedConnection,
1367
+ }),
1368
+ getLegacyPreferredTrack({
1366
1369
  bus,
1367
1370
  connection: preparedConnection,
1368
1371
  targetUsesVia,
1369
1372
  interstitialEscape,
1370
1373
  compactBusTracks,
1371
- preferOriginalEndpointTracks,
1372
1374
  traceWidth,
1373
1375
  viaDiameter,
1374
1376
  clearance,
1375
1377
  }),
1376
- traceWidth,
1377
- clearance,
1378
- })
1378
+ ].filter(
1379
+ (track, index, tracks) =>
1380
+ tracks.findIndex((candidate) => Math.abs(candidate - track) < 1e-9) ===
1381
+ index,
1382
+ )
1383
+ const trackCandidates = preferredTracks
1384
+ .flatMap((preferredTrack) =>
1385
+ getTrackCandidates({
1386
+ bus,
1387
+ connection: preparedConnection,
1388
+ preferredTrack,
1389
+ traceWidth,
1390
+ clearance,
1391
+ }),
1392
+ )
1393
+ .filter(
1394
+ (track, index, tracks) =>
1395
+ tracks.findIndex(
1396
+ (candidate) => Math.abs(candidate.value - track.value) < 1e-9,
1397
+ ) === index,
1398
+ )
1379
1399
  for (
1380
1400
  let trackIndex = 0;
1381
1401
  trackIndex < trackCandidates.length;
package/lib/types.ts CHANGED
@@ -102,13 +102,6 @@ export interface FanoutSolverOptions {
102
102
  viaHoleDiameter?: number
103
103
  clearance?: number
104
104
  compactBusTracks?: boolean
105
- /**
106
- * Prefer the perpendicular coordinate of each original downstream endpoint
107
- * when assigning boundary tracks. Ordered edge-pad buses can then enter
108
- * same-layer pads directly instead of requiring a second global router to
109
- * undo compacted fanout tracks.
110
- */
111
- preferOriginalEndpointTracks?: boolean
112
105
  /** Allow branches belonging to the same electrical net to share copper. */
113
106
  allowSameNetMerges?: boolean
114
107
  singleLayerPushAndShove?: boolean
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "@tscircuit/capacity-autorouter": "0.0.718",
38
38
  "@tscircuit/solver-utils": "0.0.19",
39
- "graphics-debug": "0.0.96"
39
+ "graphics-debug": "0.0.99"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@biomejs/biome": "2.5.5",
@@ -46,7 +46,7 @@
46
46
  "@types/bun": "1.3.14",
47
47
  "@types/react": "19.2.14",
48
48
  "@types/react-dom": "19.2.3",
49
- "bun-match-svg": "^0.0.15",
49
+ "bun-match-svg": "^0.0.16",
50
50
  "circuit-json": "0.0.455",
51
51
  "react": "19.2.8",
52
52
  "react-cosmos": "7.3.0",