@tscircuit/core 0.0.1851 → 0.0.1853

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 +376 -30
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -21192,6 +21192,24 @@ function getComponentDogboneViaSiteCandidates(preparedBuses, rules) {
21192
21192
  )
21193
21193
  );
21194
21194
  }
21195
+ function* getSingleDogboneViaSiteRepairs(bus, rules, fixedViaPointsByConnectionIndex) {
21196
+ const sites = getComponentDogboneViaSiteCandidates([bus], rules);
21197
+ let remainingAttempts = 24;
21198
+ for (const connection of bus.connections.toReversed()) {
21199
+ const original = fixedViaPointsByConnectionIndex.get(
21200
+ connection.connectionIndex
21201
+ );
21202
+ if (!original) continue;
21203
+ for (const candidate of sites) {
21204
+ if (candidate.connectionIndex !== connection.connectionIndex || distance9(candidate.point, original) <= EPSILON6)
21205
+ continue;
21206
+ if (remainingAttempts-- <= 0) return;
21207
+ const replacement = new Map(fixedViaPointsByConnectionIndex);
21208
+ replacement.set(connection.connectionIndex, candidate.point);
21209
+ yield replacement;
21210
+ }
21211
+ }
21212
+ }
21195
21213
 
21196
21214
  // node_modules/@tscircuit/fanout-solver/lib/route-via-minimal-winding.ts
21197
21215
  var EPSILON7 = 1e-7;
@@ -21606,7 +21624,8 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21606
21624
  allowSourceLayerRouting = false,
21607
21625
  adaptiveRouteOrder = false,
21608
21626
  alignGridToPads = false,
21609
- includeReverseTargetRotation = false
21627
+ includeReverseTargetRotation = false,
21628
+ reserveTerminalExitPoints = false
21610
21629
  } = params;
21611
21630
  if (maximumRouteOrderAttempts !== void 0 && (!Number.isInteger(maximumRouteOrderAttempts) || maximumRouteOrderAttempts < 1)) {
21612
21631
  throw new Error(
@@ -21794,6 +21813,11 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21794
21813
  return false;
21795
21814
  }
21796
21815
  }
21816
+ for (const other of reserveTerminalExitPoints ? terminals : []) {
21817
+ if (sharesNet(connectionName, other.connection.connection.name)) continue;
21818
+ if (distancePointToSegment(other.exitPoint, segment2.start, segment2.end) < traceWidth + clearance - EPSILON7)
21819
+ return false;
21820
+ }
21797
21821
  const segmentMinX = Math.min(segment2.start.x, segment2.end.x);
21798
21822
  const segmentMaxX = Math.max(segment2.start.x, segment2.end.x);
21799
21823
  const segmentMinY = Math.min(segment2.start.y, segment2.end.y);
@@ -22442,6 +22466,35 @@ function getWindingTargetOrders(params) {
22442
22466
  candidateOrders.push(adjacentExtension);
22443
22467
  }
22444
22468
  candidateOrders.push(legacyOrderedConnections);
22469
+ const ordinaryOrderCount = new Set(
22470
+ candidateOrders.map(
22471
+ (order) => order.map((candidate) => candidate.connectionIndex).join(",")
22472
+ )
22473
+ ).size;
22474
+ if (!getCornerSide(bus) && bus.connections.length <= 8 && orderedLayers.length > 1) {
22475
+ const layerSequences = orderedLayers.map(
22476
+ (layer) => connectionsByLayer.get(layer).toSorted(compareWithinLayer)
22477
+ );
22478
+ const offsets = layerSequences.map(() => 0);
22479
+ const current = [];
22480
+ const append = () => {
22481
+ if (candidateOrders.length >= 128) return;
22482
+ if (current.length === bus.connections.length) {
22483
+ candidateOrders.push([...current]);
22484
+ return;
22485
+ }
22486
+ for (let layer = 0; layer < layerSequences.length; layer++) {
22487
+ const next = layerSequences[layer][offsets[layer]];
22488
+ if (!next) continue;
22489
+ offsets[layer]++;
22490
+ current.push(next);
22491
+ append();
22492
+ current.pop();
22493
+ offsets[layer]--;
22494
+ }
22495
+ };
22496
+ append();
22497
+ }
22445
22498
  const seenOrders = /* @__PURE__ */ new Set();
22446
22499
  const orders = candidateOrders.filter((order) => {
22447
22500
  const key = order.map((candidate) => candidate.connectionIndex).join(",");
@@ -22449,7 +22502,7 @@ function getWindingTargetOrders(params) {
22449
22502
  seenOrders.add(key);
22450
22503
  return true;
22451
22504
  });
22452
- return { orders, legacyOrder: legacyOrderedConnections };
22505
+ return { orders, legacyOrder: legacyOrderedConnections, ordinaryOrderCount };
22453
22506
  }
22454
22507
  function getWindingTargetRank(params) {
22455
22508
  const { connection, windingOrderIndex = 0 } = params;
@@ -22496,7 +22549,7 @@ function getDistributedBoundaryTargetTracks(params) {
22496
22549
  )
22497
22550
  ).toSorted((a, b) => a - b);
22498
22551
  const pitch = traceWidth + clearance;
22499
- if (tracks.every(
22552
+ if (!params.allowLayerInterleaving && tracks.every(
22500
22553
  (track, index) => index === 0 || track - tracks[index - 1] >= pitch - 1e-9
22501
22554
  ))
22502
22555
  return void 0;
@@ -24215,17 +24268,22 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24215
24268
  })
24216
24269
  );
24217
24270
  const maximumThroughAllRouteOrderAttempts = 24;
24218
- const windingTargetOrderCount = cornerSide || getDistributedBoundaryTargetTracks({
24219
- bus,
24220
- boundaryDirection,
24221
- traceWidth,
24222
- clearance
24223
- }) ? getWindingTargetOrders({
24271
+ const usesDistributedWindingTargets = Boolean(
24272
+ cornerSide || getDistributedBoundaryTargetTracks({
24273
+ bus,
24274
+ boundaryDirection,
24275
+ traceWidth,
24276
+ clearance
24277
+ })
24278
+ );
24279
+ const windingTargetOrders = getWindingTargetOrders({
24224
24280
  bus,
24225
24281
  boundaryDirection,
24226
24282
  layerNames,
24227
24283
  targetLayer
24228
- }).orders.length : 1;
24284
+ });
24285
+ const windingTargetOrderCount = windingTargetOrders.orders.length;
24286
+ const ordinaryWindingTargetOrderCount = usesDistributedWindingTargets ? windingTargetOrders.ordinaryOrderCount : 1;
24229
24287
  const uniformDogboneTerminalPatterns = viaHandednesses.map((viaHandedness) => ({
24230
24288
  label: `uniform-${viaHandedness}`,
24231
24289
  useViaInPad: false,
@@ -24353,7 +24411,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24353
24411
  }) : null);
24354
24412
  const fixedViaTerminalPatterns = coordinatedViaPoints ? [
24355
24413
  ...Array.from(
24356
- { length: windingTargetOrderCount },
24414
+ { length: ordinaryWindingTargetOrderCount },
24357
24415
  (_, windingOrderIndex) => ({
24358
24416
  label: `component-matched-vias-winding-${windingOrderIndex}`,
24359
24417
  useViaInPad: false,
@@ -24374,7 +24432,23 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24374
24432
  maximumRouteOrderAttempts: fixedViaFallbackRouteOrderAttempts,
24375
24433
  windingOrderIndex: 0,
24376
24434
  preferTargetDirectedLaneBias: true
24377
- }
24435
+ },
24436
+ // Keep ordinary fixed-site attempts first. Forward retries reserve
24437
+ // future exits so an earlier lane cannot close their final gap.
24438
+ ...!getCornerSide(bus) && windingTargetOrderCount > 1 ? Array.from(
24439
+ { length: windingTargetOrderCount },
24440
+ (_, windingOrderIndex) => [true, false].map((preferTargetDirectedLaneBias) => ({
24441
+ label: `expanded-winding-${windingOrderIndex}-${preferTargetDirectedLaneBias}`,
24442
+ useViaInPad: false,
24443
+ getViaHandedness: () => 0,
24444
+ getViaPoint: (connection) => coordinatedViaPoints.get(connection.connectionIndex),
24445
+ maximumRouteOrderAttempts: 1,
24446
+ windingOrderIndex,
24447
+ preferTargetDirectedLaneBias,
24448
+ reserveTerminalExitPoints: !preferTargetDirectedLaneBias,
24449
+ allowLayerInterleaving: true
24450
+ }))
24451
+ ).flat() : []
24378
24452
  ] : [];
24379
24453
  const planeTerminationsAlreadyOccupyTheFanout = acceptedPlans.some(
24380
24454
  (plan) => plan.termination.type === "plane"
@@ -24415,6 +24489,21 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24415
24489
  }
24416
24490
  }
24417
24491
  }
24492
+ if (alignWindingGridToPads && cornerSide) {
24493
+ const layerLocalExitOffset = getCornerLaneOffsets(
24494
+ bus,
24495
+ acceptedPlans.filter((plan) => plan.targetLayer === targetLayer)
24496
+ ).exit;
24497
+ if (layerLocalExitOffset !== cornerLaneOffsets.exit) {
24498
+ terminalPatterns.push(
24499
+ ...terminalPatterns.map((pattern) => ({
24500
+ ...pattern,
24501
+ label: `${pattern.label}-layer-local-corner`,
24502
+ cornerExitLaneOffset: layerLocalExitOffset
24503
+ }))
24504
+ );
24505
+ }
24506
+ }
24418
24507
  const seenTerminalSignatures = /* @__PURE__ */ new Set();
24419
24508
  for (const terminalPattern of terminalPatterns) {
24420
24509
  const terminals = bus.connections.map((preparedConnection) => {
@@ -24422,7 +24511,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24422
24511
  const boundaryTrack = cornerSide ? getCornerTargetTrack({
24423
24512
  bus,
24424
24513
  connection: preparedConnection,
24425
- cornerExitLaneOffset: cornerLaneOffsets.exit,
24514
+ cornerExitLaneOffset: terminalPattern.cornerExitLaneOffset ?? cornerLaneOffsets.exit,
24426
24515
  traceWidth,
24427
24516
  viaDiameter,
24428
24517
  clearance,
@@ -24431,6 +24520,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24431
24520
  windingOrderIndex: terminalPattern.windingOrderIndex,
24432
24521
  cornerBandTargetTrackOffset
24433
24522
  }) : getBoundaryTargetTrack({
24523
+ allowLayerInterleaving: terminalPattern.allowLayerInterleaving,
24434
24524
  bus,
24435
24525
  connection: preparedConnection,
24436
24526
  boundaryDirection,
@@ -24500,7 +24590,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24500
24590
  (terminal) => `${terminal.connection.connectionIndex}:${terminal.viaPoint.x}:${terminal.viaPoint.y}:${terminal.exitPoint.x}:${terminal.exitPoint.y}`
24501
24591
  ).join(
24502
24592
  "|"
24503
- )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}:${Boolean(terminalPattern.localDogboneRepair)}`;
24593
+ )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}:${Boolean(terminalPattern.localDogboneRepair)}:${Boolean(terminalPattern.preferTargetDirectedLaneBias)}:${Boolean(terminalPattern.reserveTerminalExitPoints)}`;
24504
24594
  if (seenTerminalSignatures.has(terminalSignature)) continue;
24505
24595
  seenTerminalSignatures.add(terminalSignature);
24506
24596
  const windingSteps = routeViaMinimalWindingAlternativesSteps(
@@ -24523,6 +24613,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24523
24613
  includeReverseTargetRotation: terminalPattern.localDogboneRepair,
24524
24614
  reservedVias,
24525
24615
  softReservedVias,
24616
+ reserveTerminalExitPoints: terminalPattern.reserveTerminalExitPoints,
24526
24617
  gridStepDivisor,
24527
24618
  preferTargetDirectedLaneBias: terminalPattern.preferTargetDirectedLaneBias
24528
24619
  },
@@ -25286,7 +25377,23 @@ function replacementCopperIsSelfClear(params) {
25286
25377
  if (pointsMatch2(via.center, replacement.start) || pointsMatch2(via.center, replacement.end)) {
25287
25378
  continue;
25288
25379
  }
25289
- if (distancePointToSegment(via.center, replacement.start, replacement.end) < via.diameter / 2 + replacement.width / 2 + clearance - EPSILON8) {
25380
+ const viaClearance = via.diameter / 2 + replacement.width / 2 + clearance;
25381
+ const connectsThroughShortStub = (direction) => {
25382
+ let point6 = direction === -1 ? replacement.start : replacement.end;
25383
+ let pathDistance = 0;
25384
+ for (let index = replacementIndex + direction; index >= 0 && index < segments.length; index += direction) {
25385
+ const segment2 = segments[index];
25386
+ if (segment2.layer !== replacement.layer) return false;
25387
+ const connectedEnd = direction === -1 ? segment2.end : segment2.start;
25388
+ if (!pointsMatch2(point6, connectedEnd)) return false;
25389
+ pathDistance += distance9(segment2.start, segment2.end);
25390
+ if (pathDistance > viaClearance + EPSILON8) return false;
25391
+ point6 = direction === -1 ? segment2.start : segment2.end;
25392
+ if (pointsMatch2(point6, via.center)) return true;
25393
+ }
25394
+ return false;
25395
+ };
25396
+ if (distancePointToSegment(via.center, replacement.start, replacement.end) < viaClearance - EPSILON8 && !connectsThroughShortStub(-1) && !connectsThroughShortStub(1)) {
25290
25397
  return false;
25291
25398
  }
25292
25399
  }
@@ -30289,7 +30396,92 @@ function createInitialLayerAssignment(params) {
30289
30396
  assignment[bus.busId] = sourceLayer;
30290
30397
  } else if (viaLayers.length > 0) {
30291
30398
  if (preferOrderedCoordinatedWindingLayers && busUsesCoordinatedWinding(bus)) {
30292
- assignment[bus.busId] = viaLayers[0];
30399
+ const cornerSide = getCornerBandSide(bus.exitEdge, bus.preferredExit);
30400
+ const getBoundaryCongestion = (layer) => {
30401
+ if (!bus.exitEdge || bus.connections.length < 8) return 0;
30402
+ const horizontalEdge = bus.exitEdge === "left" || bus.exitEdge === "right";
30403
+ const axis = horizontalEdge ? "y" : "x";
30404
+ const minimum = horizontalEdge ? bus.sharedBoundary.minY : bus.sharedBoundary.minX;
30405
+ const maximum = horizontalEdge ? bus.sharedBoundary.maxY : bus.sharedBoundary.maxX;
30406
+ if (!cornerSide) {
30407
+ const tracks = bus.connections.map((connection) => {
30408
+ const target = connection.exitTargetPoint ?? connection.targetPoint;
30409
+ return Math.max(minimum, Math.min(maximum, target[axis]));
30410
+ });
30411
+ const bandMinimum = Math.min(...tracks);
30412
+ const bandMaximum = Math.max(...tracks);
30413
+ const forwardAxis = horizontalEdge ? "x" : "y";
30414
+ const sign = bus.exitEdge === "right" || bus.exitEdge === "top" ? 1 : -1;
30415
+ const sourceNearEnd = Math.min(
30416
+ ...bus.connections.map(
30417
+ (connection) => sign * connection.sourcePoint[forwardAxis]
30418
+ )
30419
+ );
30420
+ const sourceMinimum = Math.min(
30421
+ ...bus.connections.map(
30422
+ (connection) => connection.sourcePoint[axis]
30423
+ )
30424
+ );
30425
+ const sourceMaximum = Math.max(
30426
+ ...bus.connections.map(
30427
+ (connection) => connection.sourcePoint[axis]
30428
+ )
30429
+ );
30430
+ return buses.reduce((count, other) => {
30431
+ const otherCorner = getCornerBandSide(
30432
+ other.exitEdge,
30433
+ other.preferredExit
30434
+ );
30435
+ if (other === bus || other.termination.type !== "boundary" || other.componentId !== bus.componentId || other.exitEdge !== bus.exitEdge || !otherCorner || getCommonExplicitExitTargetLayer(other) !== layer)
30436
+ return count;
30437
+ const center = minimum + (maximum - minimum) * (otherCorner === "minimum" ? 0.25 : 0.75);
30438
+ const halfWidth = (Math.max(
30439
+ other.connections.length,
30440
+ other.cornerBandConnectionCount ?? 0
30441
+ ) - 1) * Math.max(
30442
+ params.traceWidth + params.clearance,
30443
+ params.viaDiameter + params.clearance
30444
+ ) / 2;
30445
+ const margin = params.traceWidth + params.clearance;
30446
+ const overlapsBoundaryBand = bandMaximum + margin >= center - halfWidth && bandMinimum - margin <= center + halfWidth;
30447
+ const crossesSourceField = Math.max(
30448
+ ...other.connections.map(
30449
+ (connection) => sign * connection.sourcePoint[forwardAxis]
30450
+ )
30451
+ ) < sourceNearEnd - 1e-9 && Math.min(
30452
+ ...other.connections.map(
30453
+ (connection) => connection.sourcePoint[axis]
30454
+ )
30455
+ ) <= sourceMaximum + margin && Math.max(
30456
+ ...other.connections.map(
30457
+ (connection) => connection.sourcePoint[axis]
30458
+ )
30459
+ ) >= sourceMinimum - margin;
30460
+ return count + (overlapsBoundaryBand || crossesSourceField ? other.connections.length : 0);
30461
+ }, 0);
30462
+ }
30463
+ const bandCenter = minimum + (maximum - minimum) * (cornerSide === "minimum" ? 0.25 : 0.75);
30464
+ const pitch = Math.max(
30465
+ params.traceWidth + params.clearance,
30466
+ params.viaDiameter + params.clearance
30467
+ );
30468
+ const bandHalfWidth = (Math.max(
30469
+ bus.connections.length,
30470
+ bus.cornerBandConnectionCount ?? 0
30471
+ ) - 1) * pitch / 2;
30472
+ return buses.reduce((count, other) => {
30473
+ if (other === bus || other.termination.type !== "boundary" || other.componentId !== bus.componentId || other.exitEdge !== bus.exitEdge || getCornerBandSide(other.exitEdge, other.preferredExit) || getCommonExplicitExitTargetLayer(other) !== layer)
30474
+ return count;
30475
+ return count + other.connections.filter((connection) => {
30476
+ const target = connection.exitTargetPoint ?? connection.targetPoint;
30477
+ const track = Math.max(minimum, Math.min(maximum, target[axis]));
30478
+ return Math.abs(track - bandCenter) <= bandHalfWidth + params.traceWidth + params.clearance;
30479
+ }).length;
30480
+ }, 0);
30481
+ };
30482
+ assignment[bus.busId] = viaLayers.toSorted(
30483
+ (first, second) => getBoundaryCongestion(first) - getBoundaryCongestion(second)
30484
+ )[0];
30293
30485
  continue;
30294
30486
  }
30295
30487
  const componentDirections = directionsByComponent.get(bus.componentId);
@@ -30585,6 +30777,9 @@ var FanoutSolver = class extends BaseSolver {
30585
30777
  buses: this.preparedBuses,
30586
30778
  escapeLayers: this.config.escapeLayers,
30587
30779
  escapeLayersByBusId: this.escapeLayersByBusId,
30780
+ traceWidth: this.config.traceWidth,
30781
+ viaDiameter: this.config.viaDiameter,
30782
+ clearance: this.config.clearance,
30588
30783
  preferOrderedCoordinatedWindingLayers: this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0 || shouldUseAdaptiveDensePlaneRouting(
30589
30784
  this.preparedBuses,
30590
30785
  this.config.allowBlindAndBuriedVias
@@ -30926,6 +31121,40 @@ var FanoutSolver = class extends BaseSolver {
30926
31121
  const wideBoundaryBuses = unsortedBoundaryBuses.filter(
30927
31122
  (bus) => bus.connections.length >= 8
30928
31123
  );
31124
+ const adjacentCenteredFieldByTurningBus = /* @__PURE__ */ new Map();
31125
+ if (usePadAlignedDenseRouting && !configuredDensePlaneRouting) {
31126
+ for (const bus of wideBoundaryBuses) {
31127
+ if (!getCornerBandSide(bus.exitEdge, bus.preferredExit) || new Set(bus.routableEscapeLayers ?? bus.allowedLayers ?? []).size !== 1)
31128
+ continue;
31129
+ const axis = bus.direction === "up" || bus.direction === "down" ? "y" : "x";
31130
+ const track = axis === "x" ? "y" : "x";
31131
+ const sign = bus.direction === "up" || bus.direction === "right" ? 1 : -1;
31132
+ const forwardEnd = Math.max(
31133
+ ...bus.connections.map((c) => sign * c.sourcePoint[axis])
31134
+ );
31135
+ const minTrack = Math.min(
31136
+ ...bus.connections.map((c) => c.sourcePoint[track])
31137
+ );
31138
+ const maxTrack = Math.max(
31139
+ ...bus.connections.map((c) => c.sourcePoint[track])
31140
+ );
31141
+ const pitch = axis === "x" ? bus.pitchX : bus.pitchY;
31142
+ const field = wideBoundaryBuses.find((candidate) => {
31143
+ if (candidate === bus || candidate.componentId !== bus.componentId || candidate.exitEdge !== bus.exitEdge || getCornerBandSide(candidate.exitEdge, candidate.preferredExit))
31144
+ return false;
31145
+ const nearEnd = Math.min(
31146
+ ...candidate.connections.map((c) => sign * c.sourcePoint[axis])
31147
+ );
31148
+ const gap = nearEnd - forwardEnd;
31149
+ return gap >= -1e-9 && gap <= pitch + 1e-9 && Math.min(
31150
+ ...candidate.connections.map((c) => c.sourcePoint[track])
31151
+ ) <= maxTrack + 1e-9 && Math.max(
31152
+ ...candidate.connections.map((c) => c.sourcePoint[track])
31153
+ ) >= minTrack - 1e-9;
31154
+ });
31155
+ if (field) adjacentCenteredFieldByTurningBus.set(bus, field);
31156
+ }
31157
+ }
30929
31158
  const hasThreeWideBoundaryBuses = useConfiguredDensePlaneRouting && wideBoundaryBuses.length === 3;
30930
31159
  const getBoundaryTargetSpan = (bus) => {
30931
31160
  const coordinates = bus.connections.map((connection) => {
@@ -31378,8 +31607,29 @@ var FanoutSolver = class extends BaseSolver {
31378
31607
  bus
31379
31608
  ])
31380
31609
  ];
31610
+ for (const [bus] of adjacentCenteredFieldByTurningBus) {
31611
+ const axis = bus.direction === "up" || bus.direction === "down" ? "y" : "x";
31612
+ const sign = bus.direction === "up" || bus.direction === "right" ? 1 : -1;
31613
+ const backwardEnd = Math.min(
31614
+ ...bus.connections.map(
31615
+ (connection) => sign * connection.sourcePoint[axis]
31616
+ )
31617
+ );
31618
+ const index = denseBoundaryBusesInRoutingOrder.indexOf(bus);
31619
+ const earlierCornerIndex = denseBoundaryBusesInRoutingOrder.findIndex(
31620
+ (candidate) => candidate !== bus && candidate.connections.length >= 8 && candidate.componentId === bus.componentId && candidate.direction === bus.direction && Math.max(
31621
+ ...candidate.connections.map(
31622
+ (connection) => sign * connection.sourcePoint[axis]
31623
+ )
31624
+ ) <= backwardEnd + 1e-9 && candidate.exitEdge === bus.exitEdge && getCornerBandSide(candidate.exitEdge, candidate.preferredExit) === getCornerBandSide(bus.exitEdge, bus.preferredExit)
31625
+ );
31626
+ if (earlierCornerIndex >= 0 && earlierCornerIndex < index) {
31627
+ denseBoundaryBusesInRoutingOrder.splice(index, 1);
31628
+ denseBoundaryBusesInRoutingOrder.splice(earlierCornerIndex, 0, bus);
31629
+ }
31630
+ }
31381
31631
  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))
31632
+ if (!usePadAlignedDenseRouting || 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
31633
  return false;
31384
31634
  const firstCenter = {
31385
31635
  x: first.connections.reduce(
@@ -31414,7 +31664,7 @@ var FanoutSolver = class extends BaseSolver {
31414
31664
  return (firstCenter[axis] - secondCenter[axis]) * (targetTrack(first) - targetTrack(second)) < -1e-9;
31415
31665
  };
31416
31666
  const centeredPairPromotionGroups = /* @__PURE__ */ new Set();
31417
- for (const singleton of multiLayerLeadingSingletonBuses) {
31667
+ for (const singleton of leadingWideSingletonBuses) {
31418
31668
  if (getCornerBandSide(singleton.exitEdge, singleton.preferredExit))
31419
31669
  continue;
31420
31670
  for (const pair of boundaryBuses) {
@@ -31446,6 +31696,21 @@ var FanoutSolver = class extends BaseSolver {
31446
31696
  }
31447
31697
  }
31448
31698
  }
31699
+ for (const field of adjacentCenteredFieldByTurningBus.values()) {
31700
+ for (const pair of boundaryBuses) {
31701
+ if (pair.connections.length !== 2 || getContainingWideSourceField(pair) !== field)
31702
+ continue;
31703
+ for (const singleton of singletonBoundaryBuses) {
31704
+ if (!areAdjacentInvertedNarrowBuses(singleton, pair)) continue;
31705
+ const pairIndex = denseBoundaryBusesInRoutingOrder.indexOf(pair);
31706
+ const singletonIndex = denseBoundaryBusesInRoutingOrder.indexOf(singleton);
31707
+ if (singletonIndex > pairIndex) {
31708
+ denseBoundaryBusesInRoutingOrder.splice(singletonIndex, 1);
31709
+ denseBoundaryBusesInRoutingOrder.splice(pairIndex, 0, singleton);
31710
+ }
31711
+ }
31712
+ }
31713
+ }
31449
31714
  let fixedViaPointsByConnectionIndex = seedViaPoints;
31450
31715
  let matchedPlans = [];
31451
31716
  let matchedRoutingSucceeded = true;
@@ -31558,7 +31823,7 @@ var FanoutSolver = class extends BaseSolver {
31558
31823
  (candidate) => candidate.connections.length < 8 && getContainingWideSourceField(candidate) === bus && matchedPlans.some((plan) => plan.busId === candidate.busId)
31559
31824
  );
31560
31825
  const preferSingleLayerWinding = useConfiguredDensePlaneRouting && singleLayerBus !== bus && !embeddedNarrowBusAlreadyRouted;
31561
- let usedSoftPlaneRepair = false;
31826
+ let usedRepairedViaSites = false;
31562
31827
  let busPlans = (yield* routeAlternatives(
31563
31828
  preferSingleLayerWinding ? { ...routeParams, bus: singleLayerBus } : routeParams,
31564
31829
  1
@@ -31658,14 +31923,95 @@ var FanoutSolver = class extends BaseSolver {
31658
31923
  }
31659
31924
  }
31660
31925
  }
31661
- if (!busPlans && bus.connections.length >= 8 && boundaryBuses.some(
31662
- (candidate) => candidate.connections.length >= 8 && matchedPlans.some((plan) => plan.busId === candidate.busId)
31926
+ if (!busPlans && useAdaptiveDensePlaneRouting && bus.connections.length >= 8 && getCornerBandSide(bus.exitEdge, bus.preferredExit) && new Set(bus.routableEscapeLayers ?? bus.allowedLayers ?? []).size === 1 && !matchedPlans.some(
31927
+ (plan) => wideBoundaryBuses.some(
31928
+ (candidate) => candidate.busId === plan.busId
31929
+ )
31663
31930
  )) {
31931
+ const reservedVias = getReservedVias(bus);
31932
+ const siteRepairs = getSingleDogboneViaSiteRepairs(
31933
+ bus,
31934
+ {
31935
+ viaDiameter: this.config.viaDiameter,
31936
+ viaHoleDiameter: this.config.viaHoleDiameter,
31937
+ traceWidth: this.config.traceWidth,
31938
+ clearance: this.config.clearance,
31939
+ additionalObstacles: this.routingSrj.obstacles,
31940
+ blockingSegments: [
31941
+ ...matchedPlans.flatMap(
31942
+ (plan) => plan.segments.map((segment2) => ({
31943
+ connectionIndex: plan.connectionIndex,
31944
+ segment: segment2
31945
+ }))
31946
+ ),
31947
+ ...reservedVias.flatMap(
31948
+ (reserved) => reserved.sourceEscapeSegment ? [
31949
+ {
31950
+ connectionIndex: -1,
31951
+ segment: reserved.sourceEscapeSegment
31952
+ }
31953
+ ] : []
31954
+ )
31955
+ ],
31956
+ blockingVias: [
31957
+ ...matchedPlans.flatMap(
31958
+ (plan) => plan.via ? [{ connectionIndex: plan.connectionIndex, ...plan.via }] : []
31959
+ ),
31960
+ ...reservedVias.map((reserved) => ({
31961
+ connectionIndex: -1,
31962
+ ...reserved.via
31963
+ }))
31964
+ ]
31965
+ },
31966
+ fixedViaPointsByConnectionIndex
31967
+ );
31968
+ for (const replacementPoints of siteRepairs) {
31969
+ const replacementPlans = (yield* routeAlternatives(
31970
+ {
31971
+ ...routeParams,
31972
+ fixedViaPointsByConnectionIndex: replacementPoints,
31973
+ reservedVias,
31974
+ fixedViaFallbackRouteOrderAttempts: 1
31975
+ },
31976
+ 1
31977
+ ))[0];
31978
+ if (!replacementPlans) continue;
31979
+ busPlans = replacementPlans;
31980
+ fixedViaPointsByConnectionIndex = replacementPoints;
31981
+ usedRepairedViaSites = true;
31982
+ break;
31983
+ }
31984
+ }
31985
+ if (!busPlans && useAdaptiveDensePlaneRouting && bus.connections.length >= 8 && getCornerBandSide(bus.exitEdge, bus.preferredExit)) {
31986
+ busPlans = (yield* routeAlternatives(
31987
+ {
31988
+ ...routeParams,
31989
+ fixedViaPointsByConnectionIndex: void 0,
31990
+ reservedVias: getReservedVias(bus)
31991
+ },
31992
+ 1
31993
+ ))[0];
31994
+ if (busPlans) {
31995
+ usedRepairedViaSites = true;
31996
+ fixedViaPointsByConnectionIndex = new Map([
31997
+ ...fixedViaPointsByConnectionIndex,
31998
+ ...busPlans.filter((plan) => plan.via).map(
31999
+ (plan) => [plan.connectionIndex, plan.via.center]
32000
+ )
32001
+ ]);
32002
+ }
32003
+ debugDense("local-sites", bus.busId, busPlans?.length ?? "failed");
32004
+ }
32005
+ if (!busPlans && bus.connections.length >= 8 && (adjacentCenteredFieldByTurningBus.has(bus) || boundaryBuses.some(
32006
+ (candidate) => candidate.connections.length >= 8 && matchedPlans.some((plan) => plan.busId === candidate.busId)
32007
+ ))) {
31664
32008
  const committedNames = new Set(
31665
32009
  matchedPlans.map((plan) => plan.connectionName)
31666
32010
  );
31667
- const futurePlaneNames = new Set(
31668
- this.preparedBuses.filter((candidate) => candidate.termination.type === "plane").flatMap(
32011
+ const provisionalReservationNames = new Set(
32012
+ this.preparedBuses.filter(
32013
+ (candidate) => candidate.termination.type === "plane" || candidate === adjacentCenteredFieldByTurningBus.get(bus)
32014
+ ).flatMap(
31669
32015
  (candidate) => candidate.connections.map(
31670
32016
  (connection) => connection.connection.name
31671
32017
  )
@@ -31676,10 +32022,10 @@ var FanoutSolver = class extends BaseSolver {
31676
32022
  ...routeParams,
31677
32023
  fixedViaPointsByConnectionIndex: void 0,
31678
32024
  reservedVias: routeParams.reservedVias.filter(
31679
- (reserved) => !futurePlaneNames.has(reserved.connectionName)
32025
+ (reserved) => !provisionalReservationNames.has(reserved.connectionName)
31680
32026
  ),
31681
32027
  softReservedVias: routeParams.reservedVias.filter(
31682
- (reserved) => futurePlaneNames.has(reserved.connectionName)
32028
+ (reserved) => provisionalReservationNames.has(reserved.connectionName)
31683
32029
  )
31684
32030
  },
31685
32031
  1
@@ -31719,7 +32065,7 @@ var FanoutSolver = class extends BaseSolver {
31719
32065
  if (rematchedPoints) {
31720
32066
  busPlans = freePlans;
31721
32067
  fixedViaPointsByConnectionIndex = rematchedPoints;
31722
- usedSoftPlaneRepair = true;
32068
+ usedRepairedViaSites = true;
31723
32069
  }
31724
32070
  }
31725
32071
  }
@@ -31732,7 +32078,7 @@ var FanoutSolver = class extends BaseSolver {
31732
32078
  rawSkew,
31733
32079
  maximumSkew: bus.maxLengthSkew
31734
32080
  });
31735
- if (needsRouteDiversity && !matchLengthsAfterPlanes && !usedSoftPlaneRepair) {
32081
+ if (needsRouteDiversity && !matchLengthsAfterPlanes && !usedRepairedViaSites) {
31736
32082
  busPlans = (yield* routeAlternatives(routeParams, 3)).toSorted(
31737
32083
  (first, second) => {
31738
32084
  const firstLengths = first.map((plan) => plan.length);
@@ -46959,7 +47305,7 @@ function Group_getRoutingPhasePlans(group) {
46959
47305
  var package_default = {
46960
47306
  name: "@tscircuit/core",
46961
47307
  type: "module",
46962
- version: "0.0.1850",
47308
+ version: "0.0.1852",
46963
47309
  types: "dist/index.d.ts",
46964
47310
  main: "dist/index.js",
46965
47311
  module: "dist/index.js",
@@ -46999,7 +47345,7 @@ var package_default = {
46999
47345
  "@tscircuit/common": "^0.0.20",
47000
47346
  "@tscircuit/copper-pour-solver": "^0.0.52",
47001
47347
  "@tscircuit/create-fdm-enclosure": "0.0.4",
47002
- "@tscircuit/fanout-solver": "0.0.60",
47348
+ "@tscircuit/fanout-solver": "0.0.62",
47003
47349
  "@tscircuit/footprinter": "^0.0.426",
47004
47350
  "@tscircuit/image-utils": "^0.0.8",
47005
47351
  "@tscircuit/implicit-copper-pour-solver": "github:tscircuit/implicit-copper-pour-solver#a5cbbda0cd65fd4707c5c1af25779837c25aba92",
@@ -47008,7 +47354,7 @@ var package_default = {
47008
47354
  "@tscircuit/jlcpcb-manufacturing-specs": "git+https://github.com/tscircuit/jlcpcb-manufacturing-specs#e08af159db01a37db007e33f0a7268d0e4a279a5",
47009
47355
  "@tscircuit/krt-wasm": "^0.1.1",
47010
47356
  "@tscircuit/log-soup": "^1.0.2",
47011
- "@tscircuit/matchpack": "^0.0.88",
47357
+ "@tscircuit/matchpack": "^0.0.93",
47012
47358
  "@tscircuit/math-utils": "^0.0.36",
47013
47359
  "@tscircuit/miniflex": "^0.0.4",
47014
47360
  "@tscircuit/ngspice-spice-engine": "^0.0.20",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.1851",
4
+ "version": "0.0.1853",
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.60",
44
+ "@tscircuit/fanout-solver": "0.0.62",
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",
@@ -50,7 +50,7 @@
50
50
  "@tscircuit/jlcpcb-manufacturing-specs": "git+https://github.com/tscircuit/jlcpcb-manufacturing-specs#e08af159db01a37db007e33f0a7268d0e4a279a5",
51
51
  "@tscircuit/krt-wasm": "^0.1.1",
52
52
  "@tscircuit/log-soup": "^1.0.2",
53
- "@tscircuit/matchpack": "^0.0.88",
53
+ "@tscircuit/matchpack": "^0.0.93",
54
54
  "@tscircuit/math-utils": "^0.0.36",
55
55
  "@tscircuit/miniflex": "^0.0.4",
56
56
  "@tscircuit/ngspice-spice-engine": "^0.0.20",