@tscircuit/fanout-solver 0.0.46 → 0.0.48

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.
@@ -448,6 +448,7 @@ export function shouldUseJointBoundaryViaReservation(
448
448
  boundaryBusConnectionCounts.length === 6 ||
449
449
  boundaryBusConnectionCounts.length === 7 ||
450
450
  boundaryBusConnectionCounts.length === 8 ||
451
+ boundaryBusConnectionCounts.length === 9 ||
451
452
  (boundaryBusConnectionCounts.length === 4 &&
452
453
  new Set(boundaryBusConnectionCounts).size > 1)
453
454
  )
@@ -466,25 +467,96 @@ export function shouldDeferSingletonBoundaryViaReservation(
466
467
  boundaryBusCount === 7) &&
467
468
  singletonBusCount === 1) ||
468
469
  (boundaryBusCount === 8 &&
469
- (singletonBusCount === 1 || singletonBusCount === 2))
470
+ (singletonBusCount === 1 || singletonBusCount === 2)) ||
471
+ (boundaryBusCount === 9 && singletonBusCount >= 1 && singletonBusCount <= 3)
470
472
  )
471
473
  }
472
474
 
475
+ export function getDenseSingletonDeferralCandidateCount(
476
+ boundaryBusConnectionCounts: readonly number[],
477
+ ): number {
478
+ if (
479
+ !shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
480
+ ) {
481
+ return 0
482
+ }
483
+ const singletonBusCount = boundaryBusConnectionCounts.filter(
484
+ (count) => count === 1,
485
+ ).length
486
+ return Math.max(1, singletonBusCount - 1)
487
+ }
488
+
489
+ interface DenseSourceFieldBus {
490
+ componentId: string
491
+ exitEdge?: PreparedBus["exitEdge"]
492
+ allowedLayers?: readonly string[]
493
+ routableEscapeLayers?: readonly string[]
494
+ connections: readonly { sourcePoint: { x: number; y: number } }[]
495
+ }
496
+
497
+ export function isDenseSingletonEmbeddedInMultiLayerWideBus(params: {
498
+ singletonBus: DenseSourceFieldBus
499
+ singletonTargetLayer: string
500
+ wideBuses: readonly DenseSourceFieldBus[]
501
+ }): boolean {
502
+ const sourcePoint = params.singletonBus.connections[0]?.sourcePoint
503
+ if (params.singletonBus.connections.length !== 1 || !sourcePoint) return false
504
+ return params.wideBuses.some((wideBus) => {
505
+ const wideLayers =
506
+ wideBus.routableEscapeLayers ?? wideBus.allowedLayers ?? []
507
+ if (
508
+ wideBus.connections.length < 8 ||
509
+ wideBus.componentId !== params.singletonBus.componentId ||
510
+ wideBus.exitEdge !== params.singletonBus.exitEdge ||
511
+ wideLayers.length < 2 ||
512
+ !wideLayers.includes(params.singletonTargetLayer)
513
+ ) {
514
+ return false
515
+ }
516
+ const sourceXs = wideBus.connections.map(
517
+ (connection) => connection.sourcePoint.x,
518
+ )
519
+ const sourceYs = wideBus.connections.map(
520
+ (connection) => connection.sourcePoint.y,
521
+ )
522
+ return (
523
+ sourcePoint.x >= Math.min(...sourceXs) - 1e-9 &&
524
+ sourcePoint.x <= Math.max(...sourceXs) + 1e-9 &&
525
+ sourcePoint.y >= Math.min(...sourceYs) - 1e-9 &&
526
+ sourcePoint.y <= Math.max(...sourceYs) + 1e-9
527
+ )
528
+ })
529
+ }
530
+
531
+ export function getDenseLeadingCornerBandTargetTrackOffset(params: {
532
+ leadingLaneCount: number
533
+ traceWidth: number
534
+ viaDiameter: number
535
+ clearance: number
536
+ }): number {
537
+ const cornerPitch = Math.max(
538
+ params.traceWidth + params.clearance,
539
+ params.viaDiameter + params.clearance,
540
+ )
541
+ return (-params.leadingLaneCount * cornerPitch) / 2
542
+ }
543
+
473
544
  export function shouldSearchAdditionalBoundaryRouteTopologies(params: {
474
545
  boundaryBusCount: number
475
546
  connectionCount: number
476
547
  rawSkew: number
477
548
  maximumSkew: number
478
549
  }): boolean {
479
- if (params.boundaryBusCount === 5 || params.boundaryBusCount > 8) {
550
+ if (params.boundaryBusCount === 5 || params.boundaryBusCount > 9) {
480
551
  return false
481
552
  }
482
553
  if (
483
554
  params.boundaryBusCount === 6 ||
484
555
  params.boundaryBusCount === 7 ||
485
- params.boundaryBusCount === 8
556
+ params.boundaryBusCount === 8 ||
557
+ params.boundaryBusCount === 9
486
558
  ) {
487
- // Six through eight buses remove enough meander space that a severely skewed wide
559
+ // Six through nine buses remove enough meander space that a severely skewed wide
488
560
  // topology can be impossible to tune. Keep the retry away from narrow
489
561
  // differential/control groups and from modest deficits that the atomic
490
562
  // length matcher can absorb directly.
@@ -537,6 +609,21 @@ export function getDenseSingletonBoundaryGeometry(
537
609
  export function compareDenseSingletonBoundaryDeferralPriority(
538
610
  first: DenseSingletonBoundaryGeometryBus,
539
611
  second: DenseSingletonBoundaryGeometryBus,
612
+ ): number {
613
+ const firstGeometry = getDenseSingletonBoundaryGeometry(first)
614
+ const secondGeometry = getDenseSingletonBoundaryGeometry(second)
615
+ return (
616
+ Number(firstGeometry.isCorner) - Number(secondGeometry.isCorner) ||
617
+ (firstGeometry.isCorner && secondGeometry.isCorner
618
+ ? firstGeometry.targetProjection - secondGeometry.targetProjection
619
+ : 0) ||
620
+ first.busId.localeCompare(second.busId)
621
+ )
622
+ }
623
+
624
+ function compareReleasedDenseSingletonBoundaryDeferralPriority(
625
+ first: DenseSingletonBoundaryGeometryBus,
626
+ second: DenseSingletonBoundaryGeometryBus,
540
627
  ): number {
541
628
  const firstGeometry = getDenseSingletonBoundaryGeometry(first)
542
629
  const secondGeometry = getDenseSingletonBoundaryGeometry(second)
@@ -569,7 +656,9 @@ export function getDenseBoundaryPairRoutingPriorityKeys(params: {
569
656
  const firstBus = pairBuses[0]
570
657
  const firstSourceLayer = firstBus?.connections[0]?.sourceLayer
571
658
  if (
572
- (params.boundaryBusCount !== 7 && params.boundaryBusCount !== 8) ||
659
+ (params.boundaryBusCount !== 7 &&
660
+ params.boundaryBusCount !== 8 &&
661
+ params.boundaryBusCount !== 9) ||
573
662
  pairBuses.length !== 3 ||
574
663
  firstBus === undefined ||
575
664
  firstBus.exitEdge === undefined ||
@@ -989,13 +1078,14 @@ export class FanoutSolver extends BaseSolver {
989
1078
  if (
990
1079
  unsortedBoundaryBuses.length !== 6 &&
991
1080
  unsortedBoundaryBuses.length !== 7 &&
992
- unsortedBoundaryBuses.length !== 8
1081
+ unsortedBoundaryBuses.length !== 8 &&
1082
+ unsortedBoundaryBuses.length !== 9
993
1083
  ) {
994
1084
  return 0
995
1085
  }
996
1086
  // The general routing order can differ across the two components as a
997
1087
  // function of local pad geometry. Keep otherwise-equivalent corner buses
998
- // in one deterministic order for the six- through eight-bus paths so their
1088
+ // in one deterministic order for the six- through nine-bus paths so their
999
1089
  // boundary lanes do not swap between the two ends of a direct
1000
1090
  // interconnect. Leave the released four- and five-bus tie behavior
1001
1091
  // unchanged.
@@ -1011,7 +1101,7 @@ export class FanoutSolver extends BaseSolver {
1011
1101
  )
1012
1102
  if (
1013
1103
  boundaryBuses.length === 0 ||
1014
- boundaryBuses.length > 8 ||
1104
+ boundaryBuses.length > 9 ||
1015
1105
  planeBuses.length < 8 ||
1016
1106
  boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
1017
1107
  planeBuses.some((bus) => bus.connections.length !== 1) ||
@@ -1029,11 +1119,15 @@ export class FanoutSolver extends BaseSolver {
1029
1119
  ),
1030
1120
  ),
1031
1121
  )
1032
- const singletonBoundaryBusCount = boundaryBuses.filter(
1033
- (bus) => bus.connections.length === 1,
1122
+ const boundaryBusConnectionCounts = boundaryBuses.map(
1123
+ (bus) => bus.connections.length,
1124
+ )
1125
+ const singletonBoundaryBusCount = boundaryBusConnectionCounts.filter(
1126
+ (connectionCount) => connectionCount === 1,
1034
1127
  ).length
1035
1128
  const useGeometryAwareSingletonOutwardPreference =
1036
- boundaryBuses.length === 8 && singletonBoundaryBusCount === 2
1129
+ singletonBoundaryBusCount > 1 &&
1130
+ shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
1037
1131
  const preferredBoundaryPerpendicularSideByBusId = new Map(
1038
1132
  boundaryBuses.map((bus) => [bus.busId, 1 as const]),
1039
1133
  )
@@ -1067,29 +1161,93 @@ export class FanoutSolver extends BaseSolver {
1067
1161
  ),
1068
1162
  )
1069
1163
  }
1070
- // Five through eight boundary buses, and heterogeneous four-bus groups, leave too little
1164
+ // Five through nine boundary buses, and heterogeneous four-bus groups, leave too little
1071
1165
  // slack for incremental site allocation: a valid early trace can consume
1072
1166
  // the last dogbone site of a later narrow bus. Reserve the multi-line bus
1073
- // barrels before routing copper. The least-constrained eligible one-line
1074
- // bus stays provisional because its flexible site can be rematched around
1075
- // the wide-bus copper. Prefer a centered singleton, then a corner singleton
1076
- // whose explicit target lies inward along its local escape direction.
1167
+ // barrels before routing copper. Classify the least-constrained eligible
1168
+ // one-line buses as deferral candidates, preferring a centered singleton,
1169
+ // then a corner singleton whose explicit target lies inward along its local
1170
+ // escape direction. A candidate embedded in a multi-layer wide-bus source
1171
+ // field must instead reserve an outward dogbone and route before that wide
1172
+ // bus; the remaining candidates stay provisional and can be rematched
1173
+ // around completed copper.
1077
1174
  // Plane sites are likewise rematched around completed boundary plans.
1078
- const boundaryBusConnectionCounts = boundaryBuses.map(
1079
- (bus) => bus.connections.length,
1080
- )
1081
1175
  const singletonBoundaryBuses = boundaryBuses.filter(
1082
1176
  (bus) => bus.connections.length === 1,
1083
1177
  )
1084
- const provisionalSingletonBuses =
1178
+ const singletonDeferralCandidates =
1085
1179
  shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
1086
1180
  ? singletonBoundaryBuses
1087
- .toSorted(compareDenseSingletonBoundaryDeferralPriority)
1088
- .slice(0, 1)
1181
+ .toSorted(
1182
+ boundaryBuses.length === 9
1183
+ ? compareDenseSingletonBoundaryDeferralPriority
1184
+ : compareReleasedDenseSingletonBoundaryDeferralPriority,
1185
+ )
1186
+ .slice(
1187
+ 0,
1188
+ getDenseSingletonDeferralCandidateCount(
1189
+ boundaryBusConnectionCounts,
1190
+ ),
1191
+ )
1089
1192
  : []
1090
- const provisionalSingletonBusSet = new Set(provisionalSingletonBuses)
1193
+ const leadingWideSingletonBuses =
1194
+ boundaryBuses.length === 9
1195
+ ? singletonDeferralCandidates.filter((singletonBus) => {
1196
+ const singletonTargetLayer =
1197
+ params.busLayerAssignments[singletonBus.busId]
1198
+ return Boolean(
1199
+ singletonTargetLayer &&
1200
+ isDenseSingletonEmbeddedInMultiLayerWideBus({
1201
+ singletonBus,
1202
+ singletonTargetLayer,
1203
+ wideBuses: boundaryBuses,
1204
+ }),
1205
+ )
1206
+ })
1207
+ : []
1208
+ const leadingLaneCountByWideCornerBand = new Map<string, number>()
1209
+ if (boundaryBuses.length === 9 && leadingWideSingletonBuses.length > 0) {
1210
+ for (const bus of leadingWideSingletonBuses) {
1211
+ preferBoundaryOutwardByBusId.set(bus.busId, true)
1212
+ const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
1213
+ if (!bus.exitEdge || !side) continue
1214
+ const bandKey = `${bus.exitEdge}:${side}`
1215
+ const sharesBandWithWideBus = boundaryBuses.some((candidate) => {
1216
+ if (candidate === bus || candidate.connections.length < 8)
1217
+ return false
1218
+ return (
1219
+ candidate.exitEdge === bus.exitEdge &&
1220
+ getCornerBandSide(candidate.exitEdge, candidate.preferredExit) ===
1221
+ side
1222
+ )
1223
+ })
1224
+ if (!sharesBandWithWideBus) continue
1225
+ leadingLaneCountByWideCornerBand.set(
1226
+ bandKey,
1227
+ (leadingLaneCountByWideCornerBand.get(bandKey) ?? 0) +
1228
+ bus.connections.length,
1229
+ )
1230
+ }
1231
+ }
1232
+ const viaProvisionalSingletonBusSet = new Set(
1233
+ singletonDeferralCandidates.filter(
1234
+ (bus) => !leadingWideSingletonBuses.includes(bus),
1235
+ ),
1236
+ )
1237
+ const getCornerBandTargetTrackOffset = (bus: PreparedBus): number => {
1238
+ const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
1239
+ if (!bus.exitEdge || !side) return 0
1240
+ const leadingLaneCount =
1241
+ leadingLaneCountByWideCornerBand.get(`${bus.exitEdge}:${side}`) ?? 0
1242
+ return getDenseLeadingCornerBandTargetTrackOffset({
1243
+ leadingLaneCount,
1244
+ traceWidth: this.config.traceWidth,
1245
+ viaDiameter: this.config.viaDiameter,
1246
+ clearance: this.config.clearance,
1247
+ })
1248
+ }
1091
1249
  const initiallyMatchedBoundaryBuses = boundaryBuses.filter(
1092
- (bus) => !provisionalSingletonBusSet.has(bus),
1250
+ (bus) => !viaProvisionalSingletonBusSet.has(bus),
1093
1251
  )
1094
1252
  const jointViaPoints = useJointBoundaryViaReservation
1095
1253
  ? matchComponentDogboneViaSites(
@@ -1119,6 +1277,12 @@ export class FanoutSolver extends BaseSolver {
1119
1277
  canShareCopper,
1120
1278
  })
1121
1279
  if (seedViaPoints) {
1280
+ const denseBoundaryBusesInRoutingOrder = [
1281
+ ...leadingWideSingletonBuses,
1282
+ ...boundaryBuses.filter(
1283
+ (bus) => !leadingWideSingletonBuses.includes(bus),
1284
+ ),
1285
+ ]
1122
1286
  let fixedViaPointsByConnectionIndex: ReadonlyMap<
1123
1287
  number,
1124
1288
  { x: number; y: number }
@@ -1179,6 +1343,7 @@ export class FanoutSolver extends BaseSolver {
1179
1343
  fixedViaPointsByConnectionIndex,
1180
1344
  reservedVias: getReservedVias(bus),
1181
1345
  viaMinimalOnly: true,
1346
+ cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus),
1182
1347
  } as const
1183
1348
  let busPlans = routeBusAlternatives(routeParams, 1)[0]
1184
1349
  if (busPlans && bus.maxLengthSkew !== undefined) {
@@ -1215,14 +1380,14 @@ export class FanoutSolver extends BaseSolver {
1215
1380
  return true
1216
1381
  }
1217
1382
 
1218
- const firstBoundaryBus = boundaryBuses[0]!
1383
+ const firstBoundaryBus = denseBoundaryBusesInRoutingOrder[0]!
1219
1384
  const routedBoundaryBuses: PreparedBus[] = []
1220
1385
  if (routeMatchedBoundaryBus(firstBoundaryBus)) {
1221
1386
  routedBoundaryBuses.push(firstBoundaryBus)
1222
1387
  } else {
1223
1388
  matchedRoutingSucceeded = false
1224
1389
  }
1225
- const remainingBoundaryBuses = boundaryBuses.slice(1)
1390
+ const remainingBoundaryBuses = denseBoundaryBusesInRoutingOrder.slice(1)
1226
1391
  while (matchedRoutingSucceeded && remainingBoundaryBuses.length > 0) {
1227
1392
  const blockingSegments = matchedPlans.flatMap((plan) =>
1228
1393
  plan.segments.map((segment) => ({
package/lib/route-bus.ts CHANGED
@@ -60,6 +60,8 @@ export interface RouteBusParams {
60
60
  fixedViaPointsByConnectionIndex?: ReadonlyMap<number, Point2D>
61
61
  reservedVias?: readonly ViaMinimalWindingReservedVia[]
62
62
  viaMinimalOnly?: boolean
63
+ /** Dense corner-band phase that preserves existing lane centers when leading lanes are prepended. */
64
+ cornerBandTargetTrackOffset?: number
63
65
  }
64
66
 
65
67
  interface TrackCandidate {
@@ -287,6 +289,24 @@ function getWindingCrossoverLayer(params: {
287
289
  )
288
290
  }
289
291
 
292
+ export function getBoundaryTargetTrack(params: {
293
+ bus: PreparedBus
294
+ connection: PreparedConnection
295
+ boundaryDirection: FanoutDirection
296
+ }): number {
297
+ const requestedTrack = getPerpendicularAxis(
298
+ params.connection.exitTargetPoint ?? params.connection.targetPoint,
299
+ params.boundaryDirection,
300
+ )
301
+ const boundaryMinimum = isHorizontal(params.boundaryDirection)
302
+ ? params.bus.sharedBoundary.minY
303
+ : params.bus.sharedBoundary.minX
304
+ const boundaryMaximum = isHorizontal(params.boundaryDirection)
305
+ ? params.bus.sharedBoundary.maxY
306
+ : params.bus.sharedBoundary.maxX
307
+ return Math.max(boundaryMinimum, Math.min(boundaryMaximum, requestedTrack))
308
+ }
309
+
290
310
  function getCornerTargetTrack(params: {
291
311
  bus: PreparedBus
292
312
  connection: PreparedConnection
@@ -297,6 +317,7 @@ function getCornerTargetTrack(params: {
297
317
  layerNames: readonly string[]
298
318
  targetLayer: string
299
319
  windingOrderIndex?: number
320
+ cornerBandTargetTrackOffset?: number
300
321
  }): number {
301
322
  const {
302
323
  bus,
@@ -308,6 +329,7 @@ function getCornerTargetTrack(params: {
308
329
  layerNames,
309
330
  targetLayer,
310
331
  windingOrderIndex,
332
+ cornerBandTargetTrackOffset = 0,
311
333
  } = params
312
334
  const side = getCornerSide(bus)
313
335
  if (!side || !bus.exitEdge) {
@@ -326,7 +348,8 @@ function getCornerTargetTrack(params: {
326
348
  const pitch = Math.max(traceWidth + clearance, viaDiameter + clearance)
327
349
  const baseBandCenter =
328
350
  boundaryMinimum +
329
- (boundaryMaximum - boundaryMinimum) * (side === "minimum" ? 0.25 : 0.75)
351
+ (boundaryMaximum - boundaryMinimum) * (side === "minimum" ? 0.25 : 0.75) +
352
+ cornerBandTargetTrackOffset
330
353
  const windingTarget = busUsesCoordinatedWindingChannel(bus)
331
354
  ? getWindingTargetRank({
332
355
  bus,
@@ -945,6 +968,7 @@ function buildPlan(params: {
945
968
  allowBlindAndBuriedVias: boolean
946
969
  initialViaPoint?: Point2D
947
970
  sourceEscapePath?: readonly Point2D[]
971
+ cornerBandTargetTrackOffset?: number
948
972
  }): FanoutRoutePlan {
949
973
  const {
950
974
  preparedConnection,
@@ -967,6 +991,7 @@ function buildPlan(params: {
967
991
  allowBlindAndBuriedVias,
968
992
  initialViaPoint,
969
993
  sourceEscapePath,
994
+ cornerBandTargetTrackOffset,
970
995
  } = params
971
996
  const sourcePoint = {
972
997
  x: preparedConnection.sourcePoint.x,
@@ -1048,13 +1073,14 @@ function buildPlan(params: {
1048
1073
  clearance,
1049
1074
  layerNames,
1050
1075
  targetLayer,
1076
+ cornerBandTargetTrackOffset,
1051
1077
  })
1052
1078
  : usesLayeredWindingChannel
1053
- ? getPerpendicularAxis(
1054
- preparedConnection.exitTargetPoint ??
1055
- preparedConnection.targetPoint,
1079
+ ? getBoundaryTargetTrack({
1080
+ bus,
1081
+ connection: preparedConnection,
1056
1082
  boundaryDirection,
1057
- )
1083
+ })
1058
1084
  : track
1059
1085
  const connectionRank = getConnectionRank(bus, preparedConnection)
1060
1086
  const localCornerSide = getLocalCornerSide(bus)
@@ -2365,6 +2391,7 @@ export function routeBusAlternatives(
2365
2391
  fixedViaPointsByConnectionIndex,
2366
2392
  reservedVias = [],
2367
2393
  viaMinimalOnly = false,
2394
+ cornerBandTargetTrackOffset,
2368
2395
  } = params
2369
2396
  if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
2370
2397
  throw new Error(
@@ -2698,12 +2725,13 @@ export function routeBusAlternatives(
2698
2725
  layerNames,
2699
2726
  targetLayer,
2700
2727
  windingOrderIndex: terminalPattern.windingOrderIndex,
2728
+ cornerBandTargetTrackOffset,
2701
2729
  })
2702
- : getPerpendicularAxis(
2703
- preparedConnection.exitTargetPoint ??
2704
- preparedConnection.targetPoint,
2730
+ : getBoundaryTargetTrack({
2731
+ bus,
2732
+ connection: preparedConnection,
2705
2733
  boundaryDirection,
2706
- )
2734
+ })
2707
2735
  return {
2708
2736
  connection: preparedConnection,
2709
2737
  viaPoint: terminalPattern.getViaPoint
@@ -2882,6 +2910,7 @@ export function routeBusAlternatives(
2882
2910
  clearance,
2883
2911
  terminateAtVia: false,
2884
2912
  allowBlindAndBuriedVias,
2913
+ cornerBandTargetTrackOffset,
2885
2914
  })
2886
2915
  if (
2887
2916
  !planIsClear({
@@ -2889,7 +2918,7 @@ export function routeBusAlternatives(
2889
2918
  otherPlans: [...acceptedPlans, ...candidatePlans],
2890
2919
  staticClearanceCache,
2891
2920
  blockingBusCounts,
2892
- cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}:${bus.exitEdge ?? "legacy"}:${cornerLaneOffsets.exit}:${cornerLaneOffsets.localChannel}:${cornerLaneOffsets.boundaryChannel}`,
2921
+ cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}:${bus.exitEdge ?? "legacy"}:${cornerLaneOffsets.exit}:${cornerLaneOffsets.localChannel}:${cornerLaneOffsets.boundaryChannel}:${cornerBandTargetTrackOffset ?? 0}`,
2893
2922
  srj,
2894
2923
  sharedBoundary: bus.sharedBoundary,
2895
2924
  clearance,
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "lib"
9
9
  ],
10
+ "workspaces": [
11
+ "scripts/generate-repro"
12
+ ],
10
13
  "license": "MIT",
11
14
  "repository": {
12
15
  "type": "git",
@@ -28,6 +31,7 @@
28
31
  "typecheck": "bunx tsc --noEmit",
29
32
  "test": "bun test",
30
33
  "benchmark": "bun benchmarks/run-benchmark.ts",
34
+ "generate:repro04": "bun scripts/generate-repro/generate-repro04.tsx",
31
35
  "benchmark:srj29": "./benchmark.sh",
32
36
  "render:dataset": "bun scripts/render-dataset-pngs.ts",
33
37
  "format": "biome format --write .",
@@ -44,7 +48,12 @@
44
48
  "@tsci/tscircuit.dataset-srj29-bga-decoupling": "github:tscircuit/dataset-srj29-bga-decoupling#bfdf7bc",
45
49
  "@tscircuit/alphabet": "0.0.25",
46
50
  "@tscircuit/circuit-json-util": "0.0.97",
51
+ "@tscircuit/copper-pour-solver": "0.0.51",
47
52
  "@tscircuit/footprinter": "0.0.394",
53
+ "@tscircuit/infer-cable-insertion-point": "0.0.4",
54
+ "@tscircuit/miniflex": "0.0.4",
55
+ "@tscircuit/schematic-trace-solver": "0.0.175",
56
+ "@tscircuit/soup-util": "0.0.41",
48
57
  "@types/bun": "1.3.14",
49
58
  "@types/react": "19.2.14",
50
59
  "@types/react-dom": "19.2.3",
@@ -52,12 +61,16 @@
52
61
  "circuit-json": "0.0.455",
53
62
  "circuit-json-to-connectivity-map": "0.0.23",
54
63
  "circuit-to-svg": "0.0.364",
55
- "format-si-unit": "0.0.7",
64
+ "connectivity-map": "1.0.0",
65
+ "flatbush": "4.5.0",
66
+ "format-si-unit": "0.0.12",
67
+ "minicssgrid": "0.0.9",
56
68
  "react": "19.2.8",
57
69
  "react-cosmos": "7.3.0",
58
70
  "react-cosmos-plugin-vite": "7.3.0",
59
71
  "react-dom": "19.2.8",
60
72
  "schematic-symbols": "0.0.231",
73
+ "spicey": "0.0.14",
61
74
  "vite": "8.1.5"
62
75
  },
63
76
  "peerDependencies": {