@tscircuit/core 0.0.1853 → 0.0.1854

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 +141 -42
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -18308,6 +18308,10 @@ function distanceSegmentToObstacle(segment2, obstacle) {
18308
18308
  function segmentsAreClear(first, second, clearance) {
18309
18309
  if (first.layer !== second.layer) return true;
18310
18310
  const requiredDistance = (first.width + second.width) / 2 + clearance;
18311
+ const broadPhaseDistance = requiredDistance + EPSILON;
18312
+ if (Math.min(first.start.x, first.end.x) - Math.max(second.start.x, second.end.x) > broadPhaseDistance || Math.min(second.start.x, second.end.x) - Math.max(first.start.x, first.end.x) > broadPhaseDistance || Math.min(first.start.y, first.end.y) - Math.max(second.start.y, second.end.y) > broadPhaseDistance || Math.min(second.start.y, second.end.y) - Math.max(first.start.y, first.end.y) > broadPhaseDistance) {
18313
+ return true;
18314
+ }
18311
18315
  return distanceSegmentToSegment(
18312
18316
  first.start,
18313
18317
  first.end,
@@ -21755,6 +21759,13 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21755
21759
  }));
21756
21760
  const boundaryDirection = getDirectionForExitEdge(bus.exitEdge);
21757
21761
  const sharesNet = (first, second) => first === second || allowSameNetMerges && connectionsShareElectricalNet(srj, first, second);
21762
+ const boundedBlockingSegments = blockingSegments.map((blocker) => ({
21763
+ ...blocker,
21764
+ minX: Math.min(blocker.segment.start.x, blocker.segment.end.x),
21765
+ maxX: Math.max(blocker.segment.start.x, blocker.segment.end.x),
21766
+ minY: Math.min(blocker.segment.start.y, blocker.segment.end.y),
21767
+ maxY: Math.max(blocker.segment.start.y, blocker.segment.end.y)
21768
+ }));
21758
21769
  const allBlockingVias = [...blockingVias, ...terminalVias];
21759
21770
  const maximumViaToTraceDistance = allBlockingVias.reduce(
21760
21771
  (maximum, { via }) => Math.max(maximum, via.diameter / 2 + traceWidth / 2 + clearance),
@@ -21776,6 +21787,10 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21776
21787
  const segmentIsClear = (params2) => {
21777
21788
  const { segment: segment2, terminal, acceptedAttemptSegments } = params2;
21778
21789
  const connectionName = terminal.connection.connection.name;
21790
+ const segmentMinX = Math.min(segment2.start.x, segment2.end.x);
21791
+ const segmentMaxX = Math.max(segment2.start.x, segment2.end.x);
21792
+ const segmentMinY = Math.min(segment2.start.y, segment2.end.y);
21793
+ const segmentMaxY = Math.max(segment2.start.y, segment2.end.y);
21779
21794
  const requiredObstacleClearance = segment2.width / 2 + clearance;
21780
21795
  for (const obstacle of targetLayerObstacleIndex.querySegment(
21781
21796
  segment2,
@@ -21788,21 +21803,25 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21788
21803
  return false;
21789
21804
  }
21790
21805
  }
21791
- for (const blocker of blockingSegments) {
21806
+ for (const blocker of boundedBlockingSegments) {
21792
21807
  if (sharesNet(connectionName, blocker.connectionName)) continue;
21808
+ const margin = (segment2.width + blocker.segment.width) / 2 + clearance;
21809
+ if (segmentMaxX + margin < blocker.minX || segmentMinX - margin > blocker.maxX || segmentMaxY + margin < blocker.minY || segmentMinY - margin > blocker.maxY) {
21810
+ continue;
21811
+ }
21793
21812
  if (distanceSegmentToSegment(
21794
21813
  segment2.start,
21795
21814
  segment2.end,
21796
21815
  blocker.segment.start,
21797
21816
  blocker.segment.end
21798
- ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON7) {
21817
+ ) < margin - EPSILON7) {
21799
21818
  return false;
21800
21819
  }
21801
21820
  }
21802
21821
  for (const blocker of acceptedAttemptSegments) {
21803
21822
  if (sharesNet(connectionName, blocker.connectionName)) continue;
21804
21823
  const margin = (segment2.width + blocker.segment.width) / 2 + clearance;
21805
- if (Math.max(segment2.start.x, segment2.end.x) + margin < Math.min(blocker.segment.start.x, blocker.segment.end.x) || Math.min(segment2.start.x, segment2.end.x) - margin > Math.max(blocker.segment.start.x, blocker.segment.end.x) || Math.max(segment2.start.y, segment2.end.y) + margin < Math.min(blocker.segment.start.y, blocker.segment.end.y) || Math.min(segment2.start.y, segment2.end.y) - margin > Math.max(blocker.segment.start.y, blocker.segment.end.y))
21824
+ if (segmentMaxX + margin < Math.min(blocker.segment.start.x, blocker.segment.end.x) || segmentMinX - margin > Math.max(blocker.segment.start.x, blocker.segment.end.x) || segmentMaxY + margin < Math.min(blocker.segment.start.y, blocker.segment.end.y) || segmentMinY - margin > Math.max(blocker.segment.start.y, blocker.segment.end.y))
21806
21825
  continue;
21807
21826
  if (distanceSegmentToSegment(
21808
21827
  segment2.start,
@@ -21818,10 +21837,6 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21818
21837
  if (distancePointToSegment(other.exitPoint, segment2.start, segment2.end) < traceWidth + clearance - EPSILON7)
21819
21838
  return false;
21820
21839
  }
21821
- const segmentMinX = Math.min(segment2.start.x, segment2.end.x);
21822
- const segmentMaxX = Math.max(segment2.start.x, segment2.end.x);
21823
- const segmentMinY = Math.min(segment2.start.y, segment2.end.y);
21824
- const segmentMaxY = Math.max(segment2.start.y, segment2.end.y);
21825
21840
  for (let viaIndex = getFirstViaAtOrAfterX(
21826
21841
  segmentMinX - maximumViaToTraceDistance
21827
21842
  ); viaIndex < viasByX.length; viaIndex++) {
@@ -21992,16 +22007,25 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21992
22007
  );
21993
22008
  const previous = new Int32Array(stateCount).fill(-1);
21994
22009
  const heap = new MinHeap();
21995
- const heuristic = (point6) => {
22010
+ const remainingDistances = new Float64Array(nodeCount);
22011
+ const lanePenalties = new Float64Array(nodeCount);
22012
+ const targetTrack = getPerpendicularAxis(
22013
+ terminal.exitPoint,
22014
+ boundaryDirection
22015
+ );
22016
+ for (let nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
22017
+ const point6 = nodes[nodeIndex].point;
21996
22018
  const deltaX = Math.abs(point6.x - terminal.exitPoint.x);
21997
22019
  const deltaY = Math.abs(point6.y - terminal.exitPoint.y);
21998
- return Math.max(deltaX, deltaY) + (Math.SQRT2 - 1) * Math.min(deltaX, deltaY);
21999
- };
22020
+ remainingDistances[nodeIndex] = Math.max(deltaX, deltaY) + (Math.SQRT2 - 1) * Math.min(deltaX, deltaY);
22021
+ const nextTrack = getPerpendicularAxis(point6, boundaryDirection);
22022
+ lanePenalties[nodeIndex] = laneBias === 0 ? 0 : laneBias > 0 ? Math.max(0, targetTrack - nextTrack) * 0.2 : Math.max(0, nextTrack - targetTrack) * 0.2;
22023
+ }
22000
22024
  for (const start of starts) {
22001
22025
  const state = start.nodeIndex * 9 + 8;
22002
22026
  if (start.length >= distances[state]) continue;
22003
22027
  distances[state] = start.length;
22004
- const remaining = heuristic(nodes[start.nodeIndex].point);
22028
+ const remaining = remainingDistances[start.nodeIndex];
22005
22029
  heap.push({
22006
22030
  node: start.nodeIndex,
22007
22031
  direction: 8,
@@ -22018,6 +22042,13 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22018
22042
  [0, -1],
22019
22043
  [1, -1]
22020
22044
  ];
22045
+ const nextDirectionsByIncoming = Array.from(
22046
+ { length: 9 },
22047
+ (_, incoming) => directions.flatMap((_2, directionIndex) => {
22048
+ const delta = Math.abs(incoming - directionIndex);
22049
+ return incoming === 8 || Math.min(delta, 8 - delta) <= 1 ? [directionIndex] : [];
22050
+ })
22051
+ );
22021
22052
  const startsByNode = /* @__PURE__ */ new Map();
22022
22053
  for (const start of starts) {
22023
22054
  const values = startsByNode.get(start.nodeIndex) ?? [];
@@ -22035,7 +22066,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22035
22066
  if (current.score >= bestGoalCost - EPSILON7) break;
22036
22067
  const state = current.node * 9 + current.direction;
22037
22068
  const currentDistance = distances[state];
22038
- if (current.score > currentDistance + heuristic(nodes[current.node].point) + EPSILON7)
22069
+ if (current.score > currentDistance + remainingDistances[current.node] + EPSILON7)
22039
22070
  continue;
22040
22071
  expandedStateCount++;
22041
22072
  expandedStatesSinceYield++;
@@ -22085,13 +22116,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22085
22116
  }
22086
22117
  }
22087
22118
  const node = nodes[current.node];
22088
- for (let directionIndex = 0; directionIndex < directions.length; directionIndex++) {
22089
- if (current.direction !== 8) {
22090
- const rawDirectionDelta = Math.abs(current.direction - directionIndex);
22091
- if (Math.min(rawDirectionDelta, 8 - rawDirectionDelta) > 1) {
22092
- continue;
22093
- }
22094
- }
22119
+ for (const directionIndex of nextDirectionsByIncoming[current.direction]) {
22095
22120
  const [deltaColumn, deltaRow] = directions[directionIndex];
22096
22121
  const column = node.column + deltaColumn;
22097
22122
  const row = node.row + deltaRow;
@@ -22101,12 +22126,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22101
22126
  const nextNode = row * columnCount + column;
22102
22127
  const nextPoint = nodes[nextNode].point;
22103
22128
  const addsTurn = current.direction !== 8 && current.direction !== directionIndex;
22104
- const nextTrack = getPerpendicularAxis(nextPoint, boundaryDirection);
22105
- const targetTrack = getPerpendicularAxis(
22106
- terminal.exitPoint,
22107
- boundaryDirection
22108
- );
22109
- const lanePenalty = laneBias === 0 ? 0 : laneBias > 0 ? Math.max(0, targetTrack - nextTrack) * 0.2 : Math.max(0, nextTrack - targetTrack) * 0.2;
22129
+ const lanePenalty = lanePenalties[nextNode];
22110
22130
  const nextDistance = currentDistance + (deltaColumn !== 0 && deltaRow !== 0 ? gridStep * Math.SQRT2 : gridStep) + (addsTurn ? gridStep * 0.2 : 0) + lanePenalty + (softViaCosts?.[nextNode] ?? 0);
22111
22131
  const nextState = nextNode * 9 + directionIndex;
22112
22132
  if (nextDistance >= distances[nextState] - EPSILON7) continue;
@@ -22128,7 +22148,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22128
22148
  if (edgeClearance[edgeIndex] === 2) continue;
22129
22149
  distances[nextState] = nextDistance;
22130
22150
  previous[nextState] = state;
22131
- const remaining = heuristic(nextPoint);
22151
+ const remaining = remainingDistances[nextNode];
22132
22152
  heap.push({
22133
22153
  node: nextNode,
22134
22154
  direction: directionIndex,
@@ -22236,6 +22256,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22236
22256
  };
22237
22257
  const alternatives = [];
22238
22258
  const seenAlternativeKeys = /* @__PURE__ */ new Set();
22259
+ const failedSearches = /* @__PURE__ */ new Map();
22239
22260
  let routeOrderAttemptCount = 0;
22240
22261
  for (const routeOrder of adaptiveRouteOrders()) {
22241
22262
  for (const laneBias of laneBiases) {
@@ -22248,13 +22269,33 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22248
22269
  let failed = false;
22249
22270
  for (let terminalIndex = 0; terminalIndex < routeOrder.length; terminalIndex++) {
22250
22271
  const terminal = routeOrder[terminalIndex];
22272
+ const failedSearchKey = JSON.stringify([
22273
+ terminals.indexOf(terminal),
22274
+ laneBias,
22275
+ acceptedAttemptSegments.map(({ connectionName: connectionName2, segment: segment2 }) => [
22276
+ connectionName2,
22277
+ segment2.start.x,
22278
+ segment2.start.y,
22279
+ segment2.end.x,
22280
+ segment2.end.y,
22281
+ segment2.width,
22282
+ segment2.layer
22283
+ ])
22284
+ ]);
22285
+ const cachedFailure = failedSearches.get(failedSearchKey);
22251
22286
  const connectionSteps = routeOneSteps({
22252
22287
  terminal,
22253
22288
  acceptedAttemptSegments,
22254
22289
  laneBias
22255
22290
  });
22256
- let connectionResult = connectionSteps.next();
22257
- let searchBatch = 0;
22291
+ let connectionResult = cachedFailure === void 0 ? connectionSteps.next() : {
22292
+ done: true,
22293
+ value: {
22294
+ points: null,
22295
+ expandedStateCount: cachedFailure.expandedStateCount
22296
+ }
22297
+ };
22298
+ let searchBatch = cachedFailure?.searchBatch ?? 0;
22258
22299
  let expandedStateCount = 0;
22259
22300
  while (!connectionResult.done) {
22260
22301
  expandedStateCount = connectionResult.value.expandedStateCount;
@@ -22296,6 +22337,10 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
22296
22337
  } : {}
22297
22338
  };
22298
22339
  if (!points) {
22340
+ failedSearches.set(failedSearchKey, {
22341
+ expandedStateCount: finalExpandedStateCount,
22342
+ searchBatch
22343
+ });
22299
22344
  if (adaptiveRouteOrder && maximumRouteOrderAttempts !== void 0 && terminalIndex > 0) {
22300
22345
  const others = routeOrder.filter(
22301
22346
  (candidate) => candidate !== terminal
@@ -23630,6 +23675,8 @@ function planIsClearOfPlans(params) {
23630
23675
  clearance,
23631
23676
  blockingBusCounts
23632
23677
  } = params;
23678
+ const planSegments = getPlanSegments(plan);
23679
+ const planVias = getPlanVias2(plan);
23633
23680
  for (const otherPlan of otherPlans) {
23634
23681
  if (allowSameNetMerges && connectionsShareElectricalNet(
23635
23682
  srj,
@@ -23647,9 +23694,7 @@ function planIsClearOfPlans(params) {
23647
23694
  (blockingBusCounts.get(otherPlan.busId) ?? 0) + 1
23648
23695
  );
23649
23696
  };
23650
- const planSegments = getPlanSegments(plan);
23651
23697
  const otherSegments = getPlanSegments(otherPlan);
23652
- const planVias = getPlanVias2(plan);
23653
23698
  const otherVias = getPlanVias2(otherPlan);
23654
23699
  for (const segment2 of planSegments) {
23655
23700
  for (const otherSegment of otherSegments) {
@@ -24180,6 +24225,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24180
24225
  adaptiveWindingRouteOrder = false,
24181
24226
  alignWindingGridToPads = false,
24182
24227
  fixedViaFallbackRouteOrderAttempts = 24,
24228
+ allowFixedViaReservedExitFallback = false,
24183
24229
  cornerBandTargetTrackOffset
24184
24230
  } = params;
24185
24231
  if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
@@ -24448,7 +24494,23 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24448
24494
  reserveTerminalExitPoints: !preferTargetDirectedLaneBias,
24449
24495
  allowLayerInterleaving: true
24450
24496
  }))
24451
- ).flat() : []
24497
+ ).flat() : [],
24498
+ // A single-layer target permutation can also let an early lane
24499
+ // close a future terminal's exit gap. Keep the original attempts
24500
+ // first, then retry the same fixed sites with every exit reserved.
24501
+ ...allowFixedViaReservedExitFallback && fixedViaPointsByConnectionIndex && !getCornerSide(bus) && windingTargetOrderCount === 1 && bus.connections.length > 2 ? [true, false].map((preferTargetDirectedLaneBias) => ({
24502
+ label: `fixed-vias-reserved-exits-${preferTargetDirectedLaneBias}`,
24503
+ useViaInPad: false,
24504
+ getViaHandedness: () => 0,
24505
+ getViaPoint: (connection) => coordinatedViaPoints.get(connection.connectionIndex),
24506
+ maximumRouteOrderAttempts: Math.min(
24507
+ maximumThroughAllRouteOrderAttempts,
24508
+ fixedViaFallbackRouteOrderAttempts
24509
+ ),
24510
+ windingOrderIndex: 0,
24511
+ preferTargetDirectedLaneBias,
24512
+ reserveTerminalExitPoints: true
24513
+ })) : []
24452
24514
  ] : [];
24453
24515
  const planeTerminationsAlreadyOccupyTheFanout = acceptedPlans.some(
24454
24516
  (plan) => plan.termination.type === "plane"
@@ -31097,7 +31159,9 @@ var FanoutSolver = class extends BaseSolver {
31097
31159
  this.config.allowBlindAndBuriedVias
31098
31160
  );
31099
31161
  const useConfiguredDensePlaneRouting = configuredDensePlaneRouting || useAdaptiveDensePlaneRouting;
31100
- const useAdaptiveJointPlaneSelection = useAdaptiveDensePlaneRouting && ((params.planeReservationRetryCount ?? 0) === 0 || Boolean(params.preferredBoundaryViaPoints));
31162
+ const useBoundaryRecovery = params.boundaryRecovery !== void 0;
31163
+ const useJointPlaneRepair = useConfiguredDensePlaneRouting || useBoundaryRecovery;
31164
+ const useJointPlaneSelection = useBoundaryRecovery || useAdaptiveDensePlaneRouting && ((params.planeReservationRetryCount ?? 0) === 0 || Boolean(params.preferredBoundaryViaPoints));
31101
31165
  const matchLengthsAfterPlanes = useConfiguredDensePlaneRouting && params.lengthMatchingStage !== "before-planes";
31102
31166
  const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
31103
31167
  unsortedBoundaryBuses.map((bus) => bus.connections.length)
@@ -31252,7 +31316,7 @@ var FanoutSolver = class extends BaseSolver {
31252
31316
  return first.busId.localeCompare(second.busId);
31253
31317
  }
31254
31318
  );
31255
- const debugBoundaryOrder = process.env.FANOUT_DEBUG_BOUNDARY_ORDER?.split(",") ?? (process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS ? [process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS] : []);
31319
+ const debugBoundaryOrder = process.env.FANOUT_DEBUG_BOUNDARY_ORDER?.split(",") ?? (process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS ? [process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS] : params.boundaryRecovery ? [params.boundaryRecovery.busId] : []);
31256
31320
  const boundaryBuses = debugBoundaryOrder.length > 0 ? initiallySortedBoundaryBuses.toSorted((first, second) => {
31257
31321
  const firstIndex = debugBoundaryOrder.indexOf(first.busId);
31258
31322
  const secondIndex = debugBoundaryOrder.indexOf(second.busId);
@@ -31289,6 +31353,7 @@ var FanoutSolver = class extends BaseSolver {
31289
31353
  ];
31290
31354
  }
31291
31355
  const unroutablePlaneBusIds = /* @__PURE__ */ new Set();
31356
+ let failedWideBoundaryBus;
31292
31357
  debugDense(
31293
31358
  "start",
31294
31359
  boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
@@ -31348,6 +31413,16 @@ var FanoutSolver = class extends BaseSolver {
31348
31413
  if (entersNeighboringSourceField)
31349
31414
  preferBoundaryOutwardByBusId.set(bus.busId, false);
31350
31415
  }
31416
+ if (params.boundaryRecovery) {
31417
+ preferredBoundaryPerpendicularSideByBusId.set(
31418
+ params.boundaryRecovery.busId,
31419
+ params.boundaryRecovery.perpendicularSide
31420
+ );
31421
+ preferBoundaryOutwardByBusId.set(
31422
+ params.boundaryRecovery.busId,
31423
+ params.boundaryRecovery.preferOutward
31424
+ );
31425
+ }
31351
31426
  const debugFlippedBoundaryBus = process.env.FANOUT_DEBUG_FLIP_BOUNDARY_BUS;
31352
31427
  if (debugFlippedBoundaryBus) {
31353
31428
  preferredBoundaryPerpendicularSideByBusId.set(debugFlippedBoundaryBus, -1);
@@ -31796,6 +31871,7 @@ var FanoutSolver = class extends BaseSolver {
31796
31871
  allowBoundarySideViaFallback: bus.connections.length === 1,
31797
31872
  preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
31798
31873
  adaptiveWindingRouteOrder,
31874
+ allowFixedViaReservedExitFallback: useBoundaryRecovery,
31799
31875
  // Retain pad-aligned channels even when plane sites are reserved
31800
31876
  // adaptively; the boundary grid can fence off a turning wide bus.
31801
31877
  alignWindingGridToPads: usePadAlignedDenseRouting && !configuredDensePlaneRouting,
@@ -32089,6 +32165,7 @@ var FanoutSolver = class extends BaseSolver {
32089
32165
  }
32090
32166
  }
32091
32167
  if (!busPlans) {
32168
+ if (bus.connections.length >= 8) failedWideBoundaryBus ??= bus;
32092
32169
  debugDense("route:failed", bus.busId);
32093
32170
  return false;
32094
32171
  }
@@ -32371,6 +32448,7 @@ var FanoutSolver = class extends BaseSolver {
32371
32448
  let feasibleAlternatePlanePlans = [];
32372
32449
  const matchViaPointsAroundPlans = (candidatePlans, promotedAlternatePlaneBusIds = /* @__PURE__ */ new Set()) => {
32373
32450
  feasibleAlternatePlanePlans = [];
32451
+ matchedPlaneBusesInRoutingOrder = null;
32374
32452
  const fixedBoundaryViaPoints = new Map(
32375
32453
  candidatePlans.flatMap(
32376
32454
  (plan) => plan.via ? [[plan.connectionIndex, plan.via.center]] : []
@@ -32420,9 +32498,9 @@ var FanoutSolver = class extends BaseSolver {
32420
32498
  });
32421
32499
  if (retainedViaPoints)
32422
32500
  return new Map([...fixedBoundaryViaPoints, ...retainedViaPoints]);
32423
- if (useConfiguredDensePlaneRouting || process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1") {
32501
+ if (useJointPlaneRepair || process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1") {
32424
32502
  let incrementalViaPoints = new Map(fixedBoundaryViaPoints);
32425
- const matchedPlaneBuses = [...activeBoundaryReservationPlaneBuses];
32503
+ const matchedPlaneBuses = useBoundaryRecovery ? [] : [...activeBoundaryReservationPlaneBuses];
32426
32504
  for (const planeBus of matchedPlaneBuses) {
32427
32505
  for (const connection of planeBus.connections) {
32428
32506
  const reservedPoint = fixedViaPointsByConnectionIndex.get(
@@ -32538,14 +32616,14 @@ var FanoutSolver = class extends BaseSolver {
32538
32616
  "plane-match:preflight-failed",
32539
32617
  independentlyUnmatchablePlaneBuses.map((bus) => bus.busId)
32540
32618
  );
32541
- if (!useConfiguredDensePlaneRouting && process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1") {
32619
+ if (!useJointPlaneRepair && process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1") {
32542
32620
  return null;
32543
32621
  }
32544
32622
  let alternatePlaneSearchStates = 0;
32545
32623
  const maximumAlternatePlaneSearchStates = Number(
32546
32624
  process.env.FANOUT_DEBUG_ALTERNATE_SEARCH_STATES ?? // Keep the initial search short before reserving blocked
32547
32625
  // plane sites. Later joint repairs retain the full budget.
32548
- (useAdaptiveDensePlaneRouting && (params.planeReservationRetryCount ?? 0) === 0 ? 1e4 : useConfiguredDensePlaneRouting ? 3e6 : 1e3)
32626
+ (useAdaptiveDensePlaneRouting && (params.planeReservationRetryCount ?? 0) === 0 ? 1e4 : useConfiguredDensePlaneRouting ? 3e6 : useBoundaryRecovery ? 1e4 : 1e3)
32549
32627
  );
32550
32628
  const maximumAlternatePlaneRoutes = Number(
32551
32629
  process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ?? (useConfiguredDensePlaneRouting ? 128 : 8)
@@ -32674,7 +32752,7 @@ var FanoutSolver = class extends BaseSolver {
32674
32752
  return null;
32675
32753
  };
32676
32754
  let alternatePlanePlans;
32677
- if (useConfiguredDensePlaneRouting || process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1") {
32755
+ if (useJointPlaneRepair || process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1") {
32678
32756
  const candidateSets = orderedAlternatePlaneBuses.map(
32679
32757
  (planeBus) => ({
32680
32758
  planeBus,
@@ -32688,7 +32766,7 @@ var FanoutSolver = class extends BaseSolver {
32688
32766
  }))
32689
32767
  })
32690
32768
  );
32691
- if (useAdaptiveJointPlaneSelection) {
32769
+ if (useJointPlaneSelection) {
32692
32770
  const acceptedPlans = [
32693
32771
  ...candidatePlans,
32694
32772
  ...feasibleAlternatePlanePlans
@@ -32945,7 +33023,7 @@ var FanoutSolver = class extends BaseSolver {
32945
33023
  );
32946
33024
  if (!alternatePlanePlans) return null;
32947
33025
  feasibleAlternatePlanePlans = alternatePlanePlans;
32948
- if (useAdaptiveJointPlaneSelection) {
33026
+ if (useJointPlaneSelection) {
32949
33027
  matchedPlaneBusesInRoutingOrder = [];
32950
33028
  return new Map([
32951
33029
  ...fixedBoundaryViaPoints,
@@ -33383,6 +33461,27 @@ var FanoutSolver = class extends BaseSolver {
33383
33461
  denseRoutingStrategy: "boundary-aligned"
33384
33462
  });
33385
33463
  if (boundaryAlignedState) return boundaryAlignedState;
33464
+ if (!useBoundaryRecovery && failedWideBoundaryBus) {
33465
+ const busId = failedWideBoundaryBus.busId;
33466
+ const outward = preferBoundaryOutwardByBusId.get(busId) ?? true;
33467
+ const side = preferredBoundaryPerpendicularSideByBusId.get(busId) ?? 1;
33468
+ for (const [preferOutward, perpendicularSide] of [
33469
+ [!outward, -side],
33470
+ [!outward, side],
33471
+ [outward, -side],
33472
+ [outward, side]
33473
+ ]) {
33474
+ const recoveredState = yield* this.routeDenseThroughAllMixedTerminationSteps({
33475
+ ...params,
33476
+ boundaryRecovery: {
33477
+ busId,
33478
+ preferOutward,
33479
+ perpendicularSide
33480
+ }
33481
+ });
33482
+ if (recoveredState) return recoveredState;
33483
+ }
33484
+ }
33386
33485
  }
33387
33486
  if (!usePadAlignedDenseRouting || process.env.FANOUT_DEBUG_DENSE_ONLY === "1")
33388
33487
  return null;
@@ -47305,7 +47404,7 @@ function Group_getRoutingPhasePlans(group) {
47305
47404
  var package_default = {
47306
47405
  name: "@tscircuit/core",
47307
47406
  type: "module",
47308
- version: "0.0.1852",
47407
+ version: "0.0.1853",
47309
47408
  types: "dist/index.d.ts",
47310
47409
  main: "dist/index.js",
47311
47410
  module: "dist/index.js",
@@ -47345,7 +47444,7 @@ var package_default = {
47345
47444
  "@tscircuit/common": "^0.0.20",
47346
47445
  "@tscircuit/copper-pour-solver": "^0.0.52",
47347
47446
  "@tscircuit/create-fdm-enclosure": "0.0.4",
47348
- "@tscircuit/fanout-solver": "0.0.62",
47447
+ "@tscircuit/fanout-solver": "0.0.64",
47349
47448
  "@tscircuit/footprinter": "^0.0.426",
47350
47449
  "@tscircuit/image-utils": "^0.0.8",
47351
47450
  "@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.1853",
4
+ "version": "0.0.1854",
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.62",
44
+ "@tscircuit/fanout-solver": "0.0.64",
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",