@tscircuit/core 0.0.1849 → 0.0.1850

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.
Files changed (2) hide show
  1. package/dist/index.js +467 -19
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -21600,6 +21600,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21600
21600
  allowSameNetMerges = false,
21601
21601
  maximumRouteOrderAttempts,
21602
21602
  reservedVias = [],
21603
+ softReservedVias = [],
21603
21604
  gridStepDivisor = 1,
21604
21605
  preferTargetDirectedLaneBias = false,
21605
21606
  allowSourceLayerRouting = false,
@@ -21647,6 +21648,36 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21647
21648
  point: { x: gridMinX + column * gridStep, y: gridMinY + row * gridStep }
21648
21649
  };
21649
21650
  });
21651
+ const softViaCosts = softReservedVias.length ? new Float32Array(nodeCount) : void 0;
21652
+ if (softViaCosts) {
21653
+ for (const { via } of softReservedVias) {
21654
+ if (!via.spanLayers.includes(targetLayer)) continue;
21655
+ const radius = via.diameter / 2 + traceWidth / 2 + clearance;
21656
+ const minimumColumn = Math.max(
21657
+ 0,
21658
+ Math.ceil((via.center.x - radius - gridMinX) / gridStep)
21659
+ );
21660
+ const maximumColumn = Math.min(
21661
+ columnCount - 1,
21662
+ Math.floor((via.center.x + radius - gridMinX) / gridStep)
21663
+ );
21664
+ const minimumRow = Math.max(
21665
+ 0,
21666
+ Math.ceil((via.center.y - radius - gridMinY) / gridStep)
21667
+ );
21668
+ const maximumRow = Math.min(
21669
+ rowCount - 1,
21670
+ Math.floor((via.center.y + radius - gridMinY) / gridStep)
21671
+ );
21672
+ for (let row = minimumRow; row <= maximumRow; row++) {
21673
+ for (let column = minimumColumn; column <= maximumColumn; column++) {
21674
+ const index = row * columnCount + column;
21675
+ if (distance9(nodes[index].point, via.center) < radius)
21676
+ softViaCosts[index] = softViaCosts[index] + 25 * gridStep;
21677
+ }
21678
+ }
21679
+ }
21680
+ }
21650
21681
  const sampledGridPoints = includeVisualization ? nodes.filter(
21651
21682
  (_, index) => index % Math.max(1, Math.ceil(nodes.length / 1e3)) === 0
21652
21683
  ).map((node) => node.point) : [];
@@ -22052,7 +22083,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22052
22083
  boundaryDirection
22053
22084
  );
22054
22085
  const lanePenalty = laneBias === 0 ? 0 : laneBias > 0 ? Math.max(0, targetTrack - nextTrack) * 0.2 : Math.max(0, nextTrack - targetTrack) * 0.2;
22055
- const nextDistance = currentDistance + (deltaColumn !== 0 && deltaRow !== 0 ? gridStep * Math.SQRT2 : gridStep) + (addsTurn ? gridStep * 0.2 : 0) + lanePenalty;
22086
+ const nextDistance = currentDistance + (deltaColumn !== 0 && deltaRow !== 0 ? gridStep * Math.SQRT2 : gridStep) + (addsTurn ? gridStep * 0.2 : 0) + lanePenalty + (softViaCosts?.[nextNode] ?? 0);
22056
22087
  const nextState = nextNode * 9 + directionIndex;
22057
22088
  if (nextDistance >= distances[nextState] - EPSILON7) continue;
22058
22089
  const edgeIndex = current.node * 8 + directionIndex;
@@ -22440,6 +22471,59 @@ function getWindingCrossoverLayer(params) {
22440
22471
  (layer) => layer !== escapeLayer
22441
22472
  );
22442
22473
  }
22474
+ function getDistributedBoundaryTargetTracks(params) {
22475
+ const { bus, boundaryDirection, traceWidth, clearance } = params;
22476
+ if (getCornerSide(bus) || !busUsesCoordinatedWindingChannel(bus))
22477
+ return void 0;
22478
+ const layers = new Set(
22479
+ bus.connections.map(
22480
+ (connection) => connection.exitTargetPoint?.layer ?? getPointLayer2(connection.targetPoint)
22481
+ )
22482
+ );
22483
+ if (layers.size < 2) return void 0;
22484
+ const minimum = isHorizontal(boundaryDirection) ? bus.sharedBoundary.minY : bus.sharedBoundary.minX;
22485
+ const maximum = isHorizontal(boundaryDirection) ? bus.sharedBoundary.maxY : bus.sharedBoundary.maxX;
22486
+ const tracks = bus.connections.map(
22487
+ (connection) => Math.max(
22488
+ minimum,
22489
+ Math.min(
22490
+ maximum,
22491
+ getPerpendicularAxis2(
22492
+ connection.exitTargetPoint ?? connection.targetPoint,
22493
+ boundaryDirection
22494
+ )
22495
+ )
22496
+ )
22497
+ ).toSorted((a, b) => a - b);
22498
+ const pitch = traceWidth + clearance;
22499
+ if (tracks.every(
22500
+ (track, index) => index === 0 || track - tracks[index - 1] >= pitch - 1e-9
22501
+ ))
22502
+ return void 0;
22503
+ if (maximum - minimum < (tracks.length - 1) * pitch - 1e-9) return void 0;
22504
+ const blocks = [];
22505
+ for (const [index, track] of tracks.entries()) {
22506
+ blocks.push({ start: index, count: 1, mean: track - index * pitch });
22507
+ while (blocks.length > 1 && blocks.at(-2).mean > blocks.at(-1).mean) {
22508
+ const second = blocks.pop();
22509
+ const first = blocks.pop();
22510
+ blocks.push({
22511
+ start: first.start,
22512
+ count: first.count + second.count,
22513
+ mean: (first.mean * first.count + second.mean * second.count) / (first.count + second.count)
22514
+ });
22515
+ }
22516
+ }
22517
+ for (const block of blocks) {
22518
+ const mean = Math.max(
22519
+ minimum,
22520
+ Math.min(maximum - (tracks.length - 1) * pitch, block.mean)
22521
+ );
22522
+ for (let index = block.start; index < block.start + block.count; index++)
22523
+ tracks[index] = mean + index * pitch;
22524
+ }
22525
+ return tracks;
22526
+ }
22443
22527
  function getBoundaryTargetTrack(params) {
22444
22528
  const requestedTrack = getPerpendicularAxis2(
22445
22529
  params.connection.exitTargetPoint ?? params.connection.targetPoint,
@@ -22447,6 +22531,19 @@ function getBoundaryTargetTrack(params) {
22447
22531
  );
22448
22532
  const boundaryMinimum = isHorizontal(params.boundaryDirection) ? params.bus.sharedBoundary.minY : params.bus.sharedBoundary.minX;
22449
22533
  const boundaryMaximum = isHorizontal(params.boundaryDirection) ? params.bus.sharedBoundary.maxY : params.bus.sharedBoundary.maxX;
22534
+ const distributedTracks = params.traceWidth !== void 0 && params.clearance !== void 0 ? getDistributedBoundaryTargetTracks({
22535
+ ...params,
22536
+ traceWidth: params.traceWidth,
22537
+ clearance: params.clearance
22538
+ }) : void 0;
22539
+ if (distributedTracks && params.layerNames && params.targetLayer) {
22540
+ const { rank } = getWindingTargetRank({
22541
+ ...params,
22542
+ layerNames: params.layerNames,
22543
+ targetLayer: params.targetLayer
22544
+ });
22545
+ return distributedTracks[rank];
22546
+ }
22450
22547
  return Math.max(boundaryMinimum, Math.min(boundaryMaximum, requestedTrack));
22451
22548
  }
22452
22549
  function getCornerTargetTrack(params) {
@@ -22936,7 +23033,11 @@ function buildPlan2(params) {
22936
23033
  }) : usesLayeredWindingChannel ? getBoundaryTargetTrack({
22937
23034
  bus,
22938
23035
  connection: preparedConnection,
22939
- boundaryDirection
23036
+ boundaryDirection,
23037
+ traceWidth,
23038
+ clearance,
23039
+ layerNames,
23040
+ targetLayer
22940
23041
  }) : track;
22941
23042
  const connectionRank = getConnectionRank(bus, preparedConnection);
22942
23043
  const localCornerSide = getLocalCornerSide(bus);
@@ -24019,6 +24120,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24019
24120
  stopAfterFirstRejectedViaMinimalCandidate = false,
24020
24121
  fixedViaPointsByConnectionIndex,
24021
24122
  reservedVias = [],
24123
+ softReservedVias = [],
24022
24124
  viaMinimalOnly = false,
24023
24125
  allowBoundarySideViaFallback = false,
24024
24126
  preferCornerBoundaryVia = false,
@@ -24113,7 +24215,12 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24113
24215
  })
24114
24216
  );
24115
24217
  const maximumThroughAllRouteOrderAttempts = 24;
24116
- const windingTargetOrderCount = cornerSide ? getWindingTargetOrders({
24218
+ const windingTargetOrderCount = cornerSide || getDistributedBoundaryTargetTracks({
24219
+ bus,
24220
+ boundaryDirection,
24221
+ traceWidth,
24222
+ clearance
24223
+ }) ? getWindingTargetOrders({
24117
24224
  bus,
24118
24225
  boundaryDirection,
24119
24226
  layerNames,
@@ -24217,7 +24324,12 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24217
24324
  useViaInPad: true,
24218
24325
  getViaHandedness: () => 0
24219
24326
  };
24220
- const coordinatedViaPoints = fixedViaPointsByConnectionIndex ?? (!allowBlindAndBuriedVias && bus.connections.length >= 8 && reservedVias.length === 0 ? matchComponentDogboneViaSites([bus], {
24327
+ const coordinatedViaPoints = fixedViaPointsByConnectionIndex ?? (!allowBlindAndBuriedVias && (bus.connections.length >= 8 || getDistributedBoundaryTargetTracks({
24328
+ bus,
24329
+ boundaryDirection,
24330
+ traceWidth,
24331
+ clearance
24332
+ })) && reservedVias.length === 0 ? matchComponentDogboneViaSites([bus], {
24221
24333
  viaDiameter,
24222
24334
  viaHoleDiameter,
24223
24335
  traceWidth,
@@ -24321,7 +24433,12 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24321
24433
  }) : getBoundaryTargetTrack({
24322
24434
  bus,
24323
24435
  connection: preparedConnection,
24324
- boundaryDirection
24436
+ boundaryDirection,
24437
+ traceWidth,
24438
+ clearance,
24439
+ layerNames,
24440
+ targetLayer,
24441
+ windingOrderIndex: terminalPattern.windingOrderIndex
24325
24442
  });
24326
24443
  return {
24327
24444
  connection: preparedConnection,
@@ -24405,6 +24522,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24405
24522
  alignGridToPads,
24406
24523
  includeReverseTargetRotation: terminalPattern.localDogboneRepair,
24407
24524
  reservedVias,
24525
+ softReservedVias,
24408
24526
  gridStepDivisor,
24409
24527
  preferTargetDirectedLaneBias: terminalPattern.preferTargetDirectedLaneBias
24410
24528
  },
@@ -24473,7 +24591,11 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24473
24591
  }) : getBoundaryTargetTrack({
24474
24592
  bus,
24475
24593
  connection: preparedConnection,
24476
- boundaryDirection
24594
+ boundaryDirection,
24595
+ traceWidth,
24596
+ clearance,
24597
+ layerNames,
24598
+ targetLayer
24477
24599
  })
24478
24600
  );
24479
24601
  const finalExitPoints = finalTracks.map(
@@ -24549,7 +24671,50 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24549
24671
  })
24550
24672
  )
24551
24673
  ) : [];
24674
+ const matchedVias = bus.connections.map(
24675
+ (connection) => fixedViaPointsByConnectionIndex.get(connection.connectionIndex)
24676
+ );
24677
+ const packageEdgeViaCandidates = [
24678
+ {
24679
+ distance: sourceCenter.x - bus.componentBounds.minX,
24680
+ axis: "x",
24681
+ value: bus.componentBounds.minX - 2 * insetStep
24682
+ },
24683
+ {
24684
+ distance: bus.componentBounds.maxX - sourceCenter.x,
24685
+ axis: "x",
24686
+ value: bus.componentBounds.maxX + 2 * insetStep
24687
+ },
24688
+ {
24689
+ distance: sourceCenter.y - bus.componentBounds.minY,
24690
+ axis: "y",
24691
+ value: bus.componentBounds.minY - 2 * insetStep
24692
+ },
24693
+ {
24694
+ distance: bus.componentBounds.maxY - sourceCenter.y,
24695
+ axis: "y",
24696
+ value: bus.componentBounds.maxY + 2 * insetStep
24697
+ }
24698
+ ].toSorted((a, b) => a.distance - b.distance).flatMap(({ axis, value }) => {
24699
+ const otherAxis = axis === "x" ? "y" : "x";
24700
+ const mean = matchedVias.reduce((sum, via) => sum + via[otherAxis], 0) / matchedVias.length;
24701
+ const order = matchedVias.map((via, index) => ({ index, track: via[otherAxis] })).toSorted((a, b) => a.track - b.track || a.index - b.index);
24702
+ const pitch = viaDiameter + clearance;
24703
+ const points = matchedVias.map((via, index) => ({
24704
+ ...via,
24705
+ [axis]: value,
24706
+ [otherAxis]: order.length === 2 && Math.abs(order[1].track - order[0].track) < pitch ? mean + (order.findIndex((v) => v.index === index) - 0.5) * pitch : via[otherAxis]
24707
+ }));
24708
+ return points.length === 2 ? [points, [points[1], points[0]]] : [points];
24709
+ });
24710
+ const preferPackageEdgeVias = bus.connections.length === 2 && Boolean(getCornerSide(bus));
24552
24711
  const viaCandidates = [
24712
+ ...(preferPackageEdgeVias ? packageEdgeViaCandidates : []).map(
24713
+ (points) => ({
24714
+ points,
24715
+ boundarySide: false
24716
+ })
24717
+ ),
24553
24718
  ...displacedViaCandidates.map((points) => ({
24554
24719
  points,
24555
24720
  boundarySide: false
@@ -24557,7 +24722,16 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24557
24722
  ...boundaryViaCandidates.map((points) => ({
24558
24723
  points,
24559
24724
  boundarySide: true
24560
- }))
24725
+ })),
24726
+ // Preserve existing centered and singleton escapes before trying a short
24727
+ // source-layer route beyond the package. The target layer can then wind to the exit
24728
+ // without a local via being fenced in by an already-routed wide bus.
24729
+ ...(!preferPackageEdgeVias ? packageEdgeViaCandidates : []).map(
24730
+ (points) => ({
24731
+ points,
24732
+ boundarySide: false
24733
+ })
24734
+ )
24561
24735
  ];
24562
24736
  for (const { points: boundaryViaPoints, boundarySide } of viaCandidates) {
24563
24737
  const boundaryVias = bus.connections.map((connection, index) => ({
@@ -24608,6 +24782,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24608
24782
  allowBlindAndBuriedVias,
24609
24783
  allowSameNetMerges,
24610
24784
  maximumRouteOrderAttempts: bus.connections.length === 1 ? 3 : 6,
24785
+ softReservedVias,
24611
24786
  reservedVias: bus.connections.length > 1 ? [...reservedVias, ...boundaryVias] : reservedVias,
24612
24787
  gridStepDivisor: 2,
24613
24788
  allowSourceLayerRouting: true,
@@ -24651,6 +24826,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24651
24826
  allowSameNetMerges,
24652
24827
  maximumRouteOrderAttempts: 6,
24653
24828
  reservedVias,
24829
+ softReservedVias,
24654
24830
  gridStepDivisor: 2,
24655
24831
  alignGridToPads: true
24656
24832
  },
@@ -24701,7 +24877,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24701
24877
  );
24702
24878
  return {
24703
24879
  ...sourceLayerPlan,
24704
- ...preferCornerBoundaryVia || bus.connections.length > 1 ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length } : {},
24880
+ ...preferCornerBoundaryVia || bus.connections.length > 1 || !boundarySide ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length } : {},
24705
24881
  targetLayer,
24706
24882
  exitPoint: finalExitPoint,
24707
24883
  via,
@@ -25511,6 +25687,99 @@ function matchBusPlanLengths(params) {
25511
25687
  if (++attempts >= 4) break;
25512
25688
  }
25513
25689
  }
25690
+ if (!acceptedPlans && params.allowUnconstrainedLaneRerouting) {
25691
+ let rerouteAttempts = 0;
25692
+ candidateSearch: for (const targetAddedLength of targetAddedLengths) {
25693
+ const candidates = createTunedPlanCandidates({
25694
+ plan: shortest,
25695
+ bus,
25696
+ targetAddedLength,
25697
+ clearance,
25698
+ sharedBoundary: bus.sharedBoundary,
25699
+ allowInsideDenseBounds: allowMatchingInsideDenseBounds
25700
+ });
25701
+ for (const candidate of candidates) {
25702
+ if (!fanoutPlansAreClear({
25703
+ plans: [candidate],
25704
+ srj: inputSrj,
25705
+ sharedBoundary,
25706
+ clearance,
25707
+ allowBlindAndBuriedVias,
25708
+ allowSameNetMerges
25709
+ }))
25710
+ continue;
25711
+ const blockers = matchedPlans.filter(
25712
+ (plan) => plan !== shortest && !fanoutPlansAreMutuallyClear({
25713
+ plans: [candidate, plan],
25714
+ srj: inputSrj,
25715
+ clearance,
25716
+ allowSameNetMerges
25717
+ })
25718
+ );
25719
+ if (blockers.length !== 1) continue;
25720
+ const blocker = blockers[0];
25721
+ const blockerBus = preparedBuses.find(
25722
+ (prepared) => prepared.busId === blocker.busId
25723
+ );
25724
+ if (!blockerBus || blockerBus.termination.type !== "boundary" || blockerBus.maxLengthSkew !== void 0 || blockerBus.connections.length !== 1 || !blocker.via || blocker.additionalVias?.length || blocker.planeEndpointVia || blocker.segments.filter(
25725
+ (segment2) => segment2.layer === blocker.sourceLayer
25726
+ ).length !== 1)
25727
+ continue;
25728
+ const nextPlans = matchedPlans.map(
25729
+ (plan) => plan === shortest ? candidate : plan
25730
+ );
25731
+ for (const alignGridToPads of [true, false]) {
25732
+ if (rerouteAttempts++ >= 4) break candidateSearch;
25733
+ const rerouted = routeViaMinimalWinding({
25734
+ srj: inputSrj,
25735
+ bus: blockerBus,
25736
+ terminals: [
25737
+ {
25738
+ connection: blockerBus.connections[0],
25739
+ viaPoint: blocker.via.center,
25740
+ exitPoint: blocker.exitPoint
25741
+ }
25742
+ ],
25743
+ targetLayer: blocker.targetLayer,
25744
+ acceptedPlans: nextPlans.filter((plan) => plan !== blocker),
25745
+ layerNames: getCopperLayerNames(inputSrj.layerCount),
25746
+ traceWidth: blocker.segments[0].width,
25747
+ viaDiameter: blocker.via.diameter,
25748
+ viaHoleDiameter: blocker.via.holeDiameter,
25749
+ clearance,
25750
+ allowBlindAndBuriedVias,
25751
+ allowSameNetMerges,
25752
+ gridStepDivisor: 2,
25753
+ alignGridToPads,
25754
+ maximumRouteOrderAttempts: 1,
25755
+ preferTargetDirectedLaneBias: true
25756
+ })?.[0];
25757
+ if (!rerouted) continue;
25758
+ const repaired = createPlanWithSegments(blocker, [
25759
+ blocker.segments[0],
25760
+ ...rerouted.segments.slice(1)
25761
+ ]);
25762
+ if (!repaired) continue;
25763
+ const repairedPlans = nextPlans.map(
25764
+ (plan) => plan === blocker ? repaired : plan
25765
+ );
25766
+ if (getBusSkew(
25767
+ repairedPlans.filter((plan) => plan.busId === bus.busId)
25768
+ ) > maxLengthSkew + EPSILON8 || !fanoutPlansAreClear({
25769
+ plans: repairedPlans,
25770
+ srj: inputSrj,
25771
+ sharedBoundary,
25772
+ clearance,
25773
+ allowBlindAndBuriedVias,
25774
+ allowSameNetMerges
25775
+ }) || candidatePlansAreFeasible && !candidatePlansAreFeasible(repairedPlans))
25776
+ continue;
25777
+ acceptedPlans = repairedPlans;
25778
+ break candidateSearch;
25779
+ }
25780
+ }
25781
+ }
25782
+ }
25514
25783
  if (!acceptedPlans) return { plans: null, failedBus: bus };
25515
25784
  matchedPlans = acceptedPlans;
25516
25785
  }
@@ -30598,6 +30867,7 @@ var FanoutSolver = class extends BaseSolver {
30598
30867
  */
30599
30868
  *routeDenseThroughAllMixedTerminationSteps(params) {
30600
30869
  if (this.config.allowBlindAndBuriedVias) return null;
30870
+ let boundaryViaPointsForRetry = params.preferredBoundaryViaPoints;
30601
30871
  const usePadAlignedDenseRouting = params.denseRoutingStrategy !== "boundary-aligned";
30602
30872
  const debugDense = (...values) => {
30603
30873
  if (process.env.FANOUT_DEBUG_DENSE === "1") {
@@ -30632,7 +30902,7 @@ var FanoutSolver = class extends BaseSolver {
30632
30902
  this.config.allowBlindAndBuriedVias
30633
30903
  );
30634
30904
  const useConfiguredDensePlaneRouting = configuredDensePlaneRouting || useAdaptiveDensePlaneRouting;
30635
- const useAdaptiveJointPlaneSelection = useAdaptiveDensePlaneRouting && (params.planeReservationRetryCount ?? 0) === 0;
30905
+ const useAdaptiveJointPlaneSelection = useAdaptiveDensePlaneRouting && ((params.planeReservationRetryCount ?? 0) === 0 || Boolean(params.preferredBoundaryViaPoints));
30636
30906
  const matchLengthsAfterPlanes = useConfiguredDensePlaneRouting && params.lengthMatchingStage !== "before-planes";
30637
30907
  const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
30638
30908
  unsortedBoundaryBuses.map((bus) => bus.connections.length)
@@ -30895,9 +31165,23 @@ var FanoutSolver = class extends BaseSolver {
30895
31165
  const throughAllLeadingSingletonBuses = hasThreeWideBoundaryBuses ? singletonBoundaryBuses.filter((singletonBus) => {
30896
31166
  const containingWideBus = getContainingWideSourceField(singletonBus);
30897
31167
  const singletonTargetLayer = params.busLayerAssignments[singletonBus.busId];
30898
- const containingWideLayers = containingWideBus?.routableEscapeLayers ?? containingWideBus?.allowedLayers ?? [];
31168
+ if (!containingWideBus || !singletonTargetLayer) return false;
31169
+ const containingWideLayers = containingWideBus.routableEscapeLayers ?? containingWideBus.allowedLayers ?? [];
31170
+ const sourceAxis = singletonBus.exitEdge === "left" || singletonBus.exitEdge === "right" ? "x" : "y";
31171
+ const wideCoordinates = containingWideBus.connections.map(
31172
+ (connection) => connection.sourcePoint[sourceAxis]
31173
+ );
31174
+ const outwardSourceCoordinate = singletonBus.exitEdge === "left" || singletonBus.exitEdge === "bottom" ? Math.min(...wideCoordinates) : Math.max(...wideCoordinates);
31175
+ const isOnOutwardSourceEdge = singletonBus.connections.every(
31176
+ (connection) => Math.abs(
31177
+ connection.sourcePoint[sourceAxis] - outwardSourceCoordinate
31178
+ ) < 1e-9
31179
+ );
30899
31180
  return Boolean(
30900
- containingWideBus && singletonTargetLayer && !containingWideLayers.includes(singletonTargetLayer)
31181
+ !containingWideLayers.includes(singletonTargetLayer) || useAdaptiveDensePlaneRouting && isOnOutwardSourceEdge && getCornerBandSide(
31182
+ singletonBus.exitEdge,
31183
+ singletonBus.preferredExit
31184
+ )
30901
31185
  );
30902
31186
  }) : [];
30903
31187
  const throughAllLeadingCompanionBuses = throughAllLeadingSingletonBuses.flatMap((singletonBus) => {
@@ -30940,8 +31224,15 @@ var FanoutSolver = class extends BaseSolver {
30940
31224
  ...process.env.FANOUT_DEBUG_PROVISIONAL_NARROW === "1" ? boundaryBuses.filter((bus) => bus.connections.length < 8) : [],
30941
31225
  ...singletonDeferralCandidates.filter((bus) => {
30942
31226
  const containingBus = getContainingWideSourceField(bus);
30943
- const sharesContainingBusLayer = usePadAlignedDenseRouting && !useConfiguredDensePlaneRouting && containingBus && params.busLayerAssignments[containingBus.busId] === params.busLayerAssignments[bus.busId];
30944
- return !leadingWideSingletonBuses.includes(bus) && !sharesContainingBusLayer;
31227
+ const sourcePoint = bus.connections[0].sourcePoint;
31228
+ const componentCenter = {
31229
+ x: (bus.componentBounds.minX + bus.componentBounds.maxX) / 2,
31230
+ y: (bus.componentBounds.minY + bus.componentBounds.maxY) / 2
31231
+ };
31232
+ const boundaryDirection = bus.exitEdge ? getDirectionForExitEdge(bus.exitEdge) : bus.direction;
31233
+ const inwardProjection = boundaryDirection === "right" ? componentCenter.x - sourcePoint.x : boundaryDirection === "left" ? sourcePoint.x - componentCenter.x : boundaryDirection === "up" ? componentCenter.y - sourcePoint.y : sourcePoint.y - componentCenter.y;
31234
+ const reserveEmbeddedSourceEscape = usePadAlignedDenseRouting && !useConfiguredDensePlaneRouting && containingBus && (params.busLayerAssignments[containingBus.busId] === params.busLayerAssignments[bus.busId] || inwardProjection > 1e-9);
31235
+ return !leadingWideSingletonBuses.includes(bus) && !reserveEmbeddedSourceEscape;
30945
31236
  }),
30946
31237
  ...hasThreeWideBoundaryBuses ? boundaryBuses.filter(
30947
31238
  (bus) => bus.connections.length === 2 && !narrowBusOverlapsWideTargetSpan(bus) && !leadingWideSingletonBuses.includes(bus)
@@ -30972,6 +31263,7 @@ var FanoutSolver = class extends BaseSolver {
30972
31263
  traceWidth: this.config.traceWidth,
30973
31264
  clearance: this.config.clearance,
30974
31265
  maximumSearchStates: 1e5,
31266
+ preferredViaPointsByConnectionIndex: params.preferredBoundaryViaPoints,
30975
31267
  preferredBoundaryPerpendicularSideByBusId,
30976
31268
  preferBoundaryOutwardByBusId,
30977
31269
  additionalObstacles: denseAdditionalObstacles,
@@ -30987,6 +31279,7 @@ var FanoutSolver = class extends BaseSolver {
30987
31279
  traceWidth: this.config.traceWidth,
30988
31280
  clearance: this.config.clearance,
30989
31281
  maximumSearchStates: 2e4,
31282
+ preferredViaPointsByConnectionIndex: params.preferredBoundaryViaPoints,
30990
31283
  preferredBoundaryPerpendicularSideByBusId,
30991
31284
  preferBoundaryOutwardByBusId,
30992
31285
  additionalObstacles: denseAdditionalObstacles,
@@ -31034,6 +31327,7 @@ var FanoutSolver = class extends BaseSolver {
31034
31327
  traceWidth: this.config.traceWidth,
31035
31328
  clearance: this.config.clearance,
31036
31329
  maximumSearchStates: 1e5,
31330
+ preferredViaPointsByConnectionIndex: params.preferredBoundaryViaPoints,
31037
31331
  preferredBoundaryPerpendicularSideByBusId,
31038
31332
  preferBoundaryOutwardByBusId,
31039
31333
  fixedViaPointsByConnectionIndex: seedViaPoints,
@@ -31084,6 +31378,74 @@ var FanoutSolver = class extends BaseSolver {
31084
31378
  bus
31085
31379
  ])
31086
31380
  ];
31381
+ const areAdjacentInvertedNarrowBuses = (first, second) => {
31382
+ if (!usePadAlignedDenseRouting || useConfiguredDensePlaneRouting || first.componentId !== second.componentId || first.exitEdge !== second.exitEdge || first.direction !== second.direction || getCornerBandSide(first.exitEdge, first.preferredExit) !== getCornerBandSide(second.exitEdge, second.preferredExit) || params.busLayerAssignments[first.busId] !== params.busLayerAssignments[second.busId] || getContainingWideSourceField(first) !== getContainingWideSourceField(second) || !getContainingWideSourceField(first))
31383
+ return false;
31384
+ const firstCenter = {
31385
+ x: first.connections.reduce(
31386
+ (sum, connection) => sum + connection.sourcePoint.x,
31387
+ 0
31388
+ ) / first.connections.length,
31389
+ y: first.connections.reduce(
31390
+ (sum, connection) => sum + connection.sourcePoint.y,
31391
+ 0
31392
+ ) / first.connections.length
31393
+ };
31394
+ const secondCenter = {
31395
+ x: second.connections.reduce(
31396
+ (sum, connection) => sum + connection.sourcePoint.x,
31397
+ 0
31398
+ ) / second.connections.length,
31399
+ y: second.connections.reduce(
31400
+ (sum, connection) => sum + connection.sourcePoint.y,
31401
+ 0
31402
+ ) / second.connections.length
31403
+ };
31404
+ if (Math.hypot(
31405
+ firstCenter.x - secondCenter.x,
31406
+ firstCenter.y - secondCenter.y
31407
+ ) > 1.5 * Math.min(first.pitchX, first.pitchY))
31408
+ return false;
31409
+ const axis = first.exitEdge === "left" || first.exitEdge === "right" ? "y" : "x";
31410
+ const targetTrack = (bus) => bus.connections.reduce(
31411
+ (sum, connection) => sum + (connection.exitTargetPoint ?? connection.targetPoint)[axis],
31412
+ 0
31413
+ ) / bus.connections.length;
31414
+ return (firstCenter[axis] - secondCenter[axis]) * (targetTrack(first) - targetTrack(second)) < -1e-9;
31415
+ };
31416
+ const centeredPairPromotionGroups = /* @__PURE__ */ new Set();
31417
+ for (const singleton of multiLayerLeadingSingletonBuses) {
31418
+ if (getCornerBandSide(singleton.exitEdge, singleton.preferredExit))
31419
+ continue;
31420
+ for (const pair of boundaryBuses) {
31421
+ if (pair.connections.length !== 2 || !areAdjacentInvertedNarrowBuses(pair, singleton))
31422
+ continue;
31423
+ const pairIndex = denseBoundaryBusesInRoutingOrder.indexOf(pair);
31424
+ const singletonIndex = denseBoundaryBusesInRoutingOrder.indexOf(singleton);
31425
+ if (pairIndex > singletonIndex) {
31426
+ denseBoundaryBusesInRoutingOrder.splice(pairIndex, 1);
31427
+ denseBoundaryBusesInRoutingOrder.splice(singletonIndex, 0, pair);
31428
+ centeredPairPromotionGroups.add(
31429
+ `${pair.componentId}:${pair.exitEdge}`
31430
+ );
31431
+ }
31432
+ }
31433
+ }
31434
+ for (const pair of boundaryBuses) {
31435
+ if (!centeredPairPromotionGroups.has(
31436
+ `${pair.componentId}:${pair.exitEdge}`
31437
+ ) || pair.connections.length !== 2 || !getCornerBandSide(pair.exitEdge, pair.preferredExit))
31438
+ continue;
31439
+ for (const singleton of singletonBoundaryBuses) {
31440
+ if (!areAdjacentInvertedNarrowBuses(singleton, pair)) continue;
31441
+ const singletonIndex = denseBoundaryBusesInRoutingOrder.indexOf(singleton);
31442
+ const pairIndex = denseBoundaryBusesInRoutingOrder.indexOf(pair);
31443
+ if (singletonIndex > pairIndex) {
31444
+ denseBoundaryBusesInRoutingOrder.splice(singletonIndex, 1);
31445
+ denseBoundaryBusesInRoutingOrder.splice(pairIndex, 0, singleton);
31446
+ }
31447
+ }
31448
+ }
31087
31449
  let fixedViaPointsByConnectionIndex = seedViaPoints;
31088
31450
  let matchedPlans = [];
31089
31451
  let matchedRoutingSucceeded = true;
@@ -31169,7 +31531,9 @@ var FanoutSolver = class extends BaseSolver {
31169
31531
  allowBoundarySideViaFallback: bus.connections.length === 1,
31170
31532
  preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
31171
31533
  adaptiveWindingRouteOrder,
31172
- alignWindingGridToPads: usePadAlignedDenseRouting && !useConfiguredDensePlaneRouting,
31534
+ // Retain pad-aligned channels even when plane sites are reserved
31535
+ // adaptively; the boundary grid can fence off a turning wide bus.
31536
+ alignWindingGridToPads: usePadAlignedDenseRouting && !configuredDensePlaneRouting,
31173
31537
  fixedViaFallbackRouteOrderAttempts: adaptiveWindingRouteOrder ? 60 : useConfiguredDensePlaneRouting ? 6 : 24,
31174
31538
  cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus)
31175
31539
  };
@@ -31194,6 +31558,7 @@ var FanoutSolver = class extends BaseSolver {
31194
31558
  (candidate) => candidate.connections.length < 8 && getContainingWideSourceField(candidate) === bus && matchedPlans.some((plan) => plan.busId === candidate.busId)
31195
31559
  );
31196
31560
  const preferSingleLayerWinding = useConfiguredDensePlaneRouting && singleLayerBus !== bus && !embeddedNarrowBusAlreadyRouted;
31561
+ let usedSoftPlaneRepair = false;
31197
31562
  let busPlans = (yield* routeAlternatives(
31198
31563
  preferSingleLayerWinding ? { ...routeParams, bus: singleLayerBus } : routeParams,
31199
31564
  1
@@ -31293,6 +31658,71 @@ var FanoutSolver = class extends BaseSolver {
31293
31658
  }
31294
31659
  }
31295
31660
  }
31661
+ if (!busPlans && bus.connections.length >= 8 && boundaryBuses.some(
31662
+ (candidate) => candidate.connections.length >= 8 && matchedPlans.some((plan) => plan.busId === candidate.busId)
31663
+ )) {
31664
+ const committedNames = new Set(
31665
+ matchedPlans.map((plan) => plan.connectionName)
31666
+ );
31667
+ const futurePlaneNames = new Set(
31668
+ this.preparedBuses.filter((candidate) => candidate.termination.type === "plane").flatMap(
31669
+ (candidate) => candidate.connections.map(
31670
+ (connection) => connection.connection.name
31671
+ )
31672
+ ).filter((name) => !committedNames.has(name))
31673
+ );
31674
+ const freePlans = (yield* routeAlternatives(
31675
+ {
31676
+ ...routeParams,
31677
+ fixedViaPointsByConnectionIndex: void 0,
31678
+ reservedVias: routeParams.reservedVias.filter(
31679
+ (reserved) => !futurePlaneNames.has(reserved.connectionName)
31680
+ ),
31681
+ softReservedVias: routeParams.reservedVias.filter(
31682
+ (reserved) => futurePlaneNames.has(reserved.connectionName)
31683
+ )
31684
+ },
31685
+ 1
31686
+ ))[0];
31687
+ if (freePlans) {
31688
+ const allPlans = [...matchedPlans, ...freePlans];
31689
+ const rematchedPoints = matchComponentDogboneViaSites(
31690
+ this.preparedBuses,
31691
+ {
31692
+ viaDiameter: this.config.viaDiameter,
31693
+ viaHoleDiameter: this.config.viaHoleDiameter,
31694
+ traceWidth: this.config.traceWidth,
31695
+ clearance: this.config.clearance,
31696
+ maximumSearchStates: 3e6,
31697
+ preferredBoundaryPerpendicularSideByBusId,
31698
+ preferBoundaryOutwardByBusId,
31699
+ fixedViaPointsByConnectionIndex: new Map(
31700
+ allPlans.filter((plan) => plan.via).map((plan) => [plan.connectionIndex, plan.via.center])
31701
+ ),
31702
+ preferredViaPointsByConnectionIndex: fixedViaPointsByConnectionIndex,
31703
+ blockingSegments: allPlans.flatMap(
31704
+ (plan) => plan.segments.map((segment2) => ({
31705
+ connectionIndex: plan.connectionIndex,
31706
+ segment: segment2
31707
+ }))
31708
+ ),
31709
+ additionalObstacles: this.routingSrj.obstacles,
31710
+ preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
31711
+ canShareCopper
31712
+ }
31713
+ );
31714
+ debugDense(
31715
+ "free-sites:rematched",
31716
+ bus.busId,
31717
+ rematchedPoints?.size ?? "failed"
31718
+ );
31719
+ if (rematchedPoints) {
31720
+ busPlans = freePlans;
31721
+ fixedViaPointsByConnectionIndex = rematchedPoints;
31722
+ usedSoftPlaneRepair = true;
31723
+ }
31724
+ }
31725
+ }
31296
31726
  if (busPlans && bus.maxLengthSkew !== void 0) {
31297
31727
  const lengths = busPlans.map((plan) => plan.length);
31298
31728
  const rawSkew = Math.max(...lengths) - Math.min(...lengths);
@@ -31302,7 +31732,7 @@ var FanoutSolver = class extends BaseSolver {
31302
31732
  rawSkew,
31303
31733
  maximumSkew: bus.maxLengthSkew
31304
31734
  });
31305
- if (needsRouteDiversity && !matchLengthsAfterPlanes) {
31735
+ if (needsRouteDiversity && !matchLengthsAfterPlanes && !usedSoftPlaneRepair) {
31306
31736
  busPlans = (yield* routeAlternatives(routeParams, 3)).toSorted(
31307
31737
  (first, second) => {
31308
31738
  const firstLengths = first.map((plan) => plan.length);
@@ -31317,6 +31747,20 @@ var FanoutSolver = class extends BaseSolver {
31317
31747
  return false;
31318
31748
  }
31319
31749
  matchedPlans.push(...busPlans);
31750
+ if (matchedPlans.length === boundaryBuses.reduce(
31751
+ (sum, candidate) => sum + candidate.connections.length,
31752
+ 0
31753
+ ) && matchedPlans.some((plan) => {
31754
+ const bus2 = boundaryBuses.find(
31755
+ (candidate) => candidate.busId === plan.busId
31756
+ );
31757
+ if (bus2?.connections.length !== 1 || !plan.via) return false;
31758
+ const { center } = plan.via;
31759
+ return center.x < bus2.componentBounds.minX || center.x > bus2.componentBounds.maxX || center.y < bus2.componentBounds.minY || center.y > bus2.componentBounds.maxY;
31760
+ }))
31761
+ boundaryViaPointsForRetry = new Map(
31762
+ matchedPlans.filter((plan) => plan.via).map((plan) => [plan.connectionIndex, plan.via.center])
31763
+ );
31320
31764
  debugDense("route:complete", bus.busId, busPlans.length);
31321
31765
  return true;
31322
31766
  }.bind(this);
@@ -31753,7 +32197,9 @@ var FanoutSolver = class extends BaseSolver {
31753
32197
  }
31754
32198
  let alternatePlaneSearchStates = 0;
31755
32199
  const maximumAlternatePlaneSearchStates = Number(
31756
- process.env.FANOUT_DEBUG_ALTERNATE_SEARCH_STATES ?? (useConfiguredDensePlaneRouting ? 3e6 : 1e3)
32200
+ process.env.FANOUT_DEBUG_ALTERNATE_SEARCH_STATES ?? // Keep the initial search short before reserving blocked
32201
+ // plane sites. Later joint repairs retain the full budget.
32202
+ (useAdaptiveDensePlaneRouting && (params.planeReservationRetryCount ?? 0) === 0 ? 1e4 : useConfiguredDensePlaneRouting ? 3e6 : 1e3)
31757
32203
  );
31758
32204
  const maximumAlternatePlaneRoutes = Number(
31759
32205
  process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ?? (useConfiguredDensePlaneRouting ? 128 : 8)
@@ -32494,7 +32940,8 @@ var FanoutSolver = class extends BaseSolver {
32494
32940
  allowBlindAndBuriedVias: false,
32495
32941
  allowSameNetMerges: this.config.allowSameNetMerges,
32496
32942
  allowMatchingInsideDenseBounds: true,
32497
- allowPairLaneSpreading: true
32943
+ allowPairLaneSpreading: true,
32944
+ allowUnconstrainedLaneRerouting: true
32498
32945
  };
32499
32946
  let matchedLengthResult = matchBusPlanLengths(lengthMatchingParams);
32500
32947
  const shortenedBusIds = /* @__PURE__ */ new Set();
@@ -32569,6 +33016,7 @@ var FanoutSolver = class extends BaseSolver {
32569
33016
  debugDense("promote-reservations", refinedPromotions);
32570
33017
  return yield* this.routeDenseThroughAllMixedTerminationSteps({
32571
33018
  ...params,
33019
+ preferredBoundaryViaPoints: boundaryViaPointsForRetry,
32572
33020
  promotedPlaneReservationBusIds: [
32573
33021
  ...params.promotedPlaneReservationBusIds ?? [],
32574
33022
  ...refinedPromotions
@@ -46511,7 +46959,7 @@ function Group_getRoutingPhasePlans(group) {
46511
46959
  var package_default = {
46512
46960
  name: "@tscircuit/core",
46513
46961
  type: "module",
46514
- version: "0.0.1848",
46962
+ version: "0.0.1849",
46515
46963
  types: "dist/index.d.ts",
46516
46964
  main: "dist/index.js",
46517
46965
  module: "dist/index.js",
@@ -46551,7 +46999,7 @@ var package_default = {
46551
46999
  "@tscircuit/common": "^0.0.20",
46552
47000
  "@tscircuit/copper-pour-solver": "^0.0.52",
46553
47001
  "@tscircuit/create-fdm-enclosure": "0.0.4",
46554
- "@tscircuit/fanout-solver": "0.0.58",
47002
+ "@tscircuit/fanout-solver": "0.0.60",
46555
47003
  "@tscircuit/footprinter": "^0.0.426",
46556
47004
  "@tscircuit/image-utils": "^0.0.8",
46557
47005
  "@tscircuit/implicit-copper-pour-solver": "github:tscircuit/implicit-copper-pour-solver#a5cbbda0cd65fd4707c5c1af25779837c25aba92",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.1849",
4
+ "version": "0.0.1850",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -41,7 +41,7 @@
41
41
  "@tscircuit/common": "^0.0.20",
42
42
  "@tscircuit/copper-pour-solver": "^0.0.52",
43
43
  "@tscircuit/create-fdm-enclosure": "0.0.4",
44
- "@tscircuit/fanout-solver": "0.0.58",
44
+ "@tscircuit/fanout-solver": "0.0.60",
45
45
  "@tscircuit/footprinter": "^0.0.426",
46
46
  "@tscircuit/image-utils": "^0.0.8",
47
47
  "@tscircuit/implicit-copper-pour-solver": "github:tscircuit/implicit-copper-pour-solver#a5cbbda0cd65fd4707c5c1af25779837c25aba92",