@tscircuit/schematic-trace-solver 0.0.21 → 0.0.22

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.
@@ -4,6 +4,9 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - main
7
+ paths:
8
+ - 'lib/**'
9
+ workflow_dispatch:
7
10
  jobs:
8
11
  publish:
9
12
  runs-on: ubuntu-latest
package/dist/index.d.ts CHANGED
@@ -161,6 +161,7 @@ declare class SchematicTraceSingleLineSolver extends BaseSolver {
161
161
  chipMap: Record<string, InputChip>;
162
162
  movableSegments: Array<MovableSegment>;
163
163
  baseElbow: Point[];
164
+ allCandidatePaths: Array<Point[]>;
164
165
  queuedCandidatePaths: Array<Point[]>;
165
166
  chipObstacleSpatialIndex: ChipObstacleSpatialIndex;
166
167
  solvedTracePath: {
package/dist/index.js CHANGED
@@ -6520,81 +6520,167 @@ var dir = (facingDirection) => {
6520
6520
  };
6521
6521
 
6522
6522
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts
6523
- var generateElbowVariants = ({
6524
- baseElbow,
6525
- guidelines
6526
- }) => {
6527
- const movableSegments = [];
6528
- for (let i = 1; i < baseElbow.length - 2; i++) {
6529
- const prev = baseElbow[i - 1];
6530
- const start = baseElbow[i];
6531
- const end = baseElbow[i + 1];
6532
- const isHorz = Math.abs(start.y - end.y) < 1e-6;
6533
- let freedom;
6534
- if (isHorz) {
6535
- freedom = prev.y <= start.y ? "y+" : "y-";
6536
- } else {
6537
- freedom = prev.x <= start.x ? "x+" : "x-";
6523
+ var EPS = 1e-6;
6524
+ var MIN_LEN = 1e-6;
6525
+ var isAxisAligned = (a, b) => Math.abs(a.x - b.x) < EPS || Math.abs(a.y - b.y) < EPS;
6526
+ var orientationOf = (a, b) => {
6527
+ const dx = Math.abs(a.x - b.x);
6528
+ const dy = Math.abs(a.y - b.y);
6529
+ if (dx < EPS && dy >= EPS) return "vertical";
6530
+ if (dy < EPS && dx >= EPS) return "horizontal";
6531
+ throw new Error("Non-orthogonal or degenerate segment detected.");
6532
+ };
6533
+ var assertOrthogonalPolyline = (pts) => {
6534
+ if (pts.length < 2) {
6535
+ throw new Error("Polyline must have at least two points.");
6536
+ }
6537
+ for (let i = 0; i < pts.length - 1; i++) {
6538
+ const a = pts[i];
6539
+ const b = pts[i + 1];
6540
+ if (!isAxisAligned(a, b)) {
6541
+ throw new Error("Polyline contains a non-orthogonal segment.");
6542
+ }
6543
+ const manhattan = Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
6544
+ if (manhattan < MIN_LEN) {
6545
+ throw new Error("Polyline contains a near-zero length segment.");
6538
6546
  }
6539
- movableSegments.push({ start, end, freedom, dir: dir(freedom) });
6540
6547
  }
6541
- const segmentGuidelineOptions = [];
6542
- for (const segment of movableSegments) {
6543
- const relevantPositions = [];
6544
- if (segment.freedom === "x+" || segment.freedom === "x-") {
6545
- for (const guideline of guidelines) {
6546
- if (guideline.orientation === "vertical" && guideline.x !== void 0) {
6547
- relevantPositions.push(guideline.x);
6548
- }
6548
+ };
6549
+ var uniqSortedWithTol = (vals, tol = EPS) => {
6550
+ const sorted = vals.slice().sort((a, b) => a - b);
6551
+ const out = [];
6552
+ for (const v of sorted) {
6553
+ if (out.length === 0 || Math.abs(v - out[out.length - 1]) > tol) out.push(v);
6554
+ }
6555
+ return out;
6556
+ };
6557
+ var keyForPolyline = (pts, decimals = 9) => pts.map((p) => `${p.x.toFixed(decimals)},${p.y.toFixed(decimals)}`).join("|");
6558
+ var preprocessElbow = (pts, tol = MIN_LEN) => {
6559
+ if (pts.length === 0) return [];
6560
+ const out = [{ ...pts[0] }];
6561
+ for (let i = 1; i < pts.length; i++) {
6562
+ const p = pts[i];
6563
+ const last = out[out.length - 1];
6564
+ const manhattan = Math.abs(p.x - last.x) + Math.abs(p.y - last.y);
6565
+ if (manhattan > tol) out.push({ ...p });
6566
+ }
6567
+ return out;
6568
+ };
6569
+ var collectAxisCandidates = (axis, guidelines, currentCoord) => {
6570
+ const positions = [];
6571
+ for (const g of guidelines) {
6572
+ if (axis === "x") {
6573
+ if (g.orientation === "vertical" && typeof g.x === "number") {
6574
+ positions.push(g.x);
6549
6575
  }
6550
- relevantPositions.push(segment.start.x);
6551
6576
  } else {
6552
- for (const guideline of guidelines) {
6553
- if (guideline.orientation === "horizontal" && guideline.y !== void 0) {
6554
- relevantPositions.push(guideline.y);
6555
- }
6577
+ if (g.orientation === "horizontal" && typeof g.y === "number") {
6578
+ positions.push(g.y);
6556
6579
  }
6557
- relevantPositions.push(segment.start.y);
6558
6580
  }
6559
- segmentGuidelineOptions.push(
6560
- [...new Set(relevantPositions)].sort((a, b) => a - b)
6561
- );
6562
6581
  }
6563
- const generateCombinations = (options) => {
6564
- if (options.length === 0) return [[]];
6565
- if (options.length === 1) return options[0].map((pos) => [pos]);
6566
- const combinations = [];
6567
- const firstOptions = options[0];
6568
- const restCombinations = generateCombinations(options.slice(1));
6569
- for (const firstOption of firstOptions) {
6570
- for (const restCombination of restCombinations) {
6571
- combinations.push([firstOption, ...restCombination]);
6582
+ positions.push(currentCoord);
6583
+ return uniqSortedWithTol(positions);
6584
+ };
6585
+ var computeFreedom = (prev, start, end) => {
6586
+ const orient = orientationOf(start, end);
6587
+ if (orient === "horizontal") {
6588
+ const facing = prev.y <= start.y ? "y+" : "y-";
6589
+ return { axis: "y", facing };
6590
+ } else {
6591
+ const facing = prev.x <= start.x ? "x+" : "x-";
6592
+ return { axis: "x", facing };
6593
+ }
6594
+ };
6595
+ var cartesian = (arrays) => arrays.length === 0 ? [[]] : arrays.reduce(
6596
+ (acc, curr) => acc.flatMap((a) => curr.map((c) => [...a, c])),
6597
+ [[]]
6598
+ );
6599
+ var generateElbowVariants = ({
6600
+ baseElbow,
6601
+ guidelines
6602
+ }) => {
6603
+ const elbow = preprocessElbow(baseElbow);
6604
+ assertOrthogonalPolyline(elbow);
6605
+ const nPts = elbow.length;
6606
+ const nSegs = nPts - 1;
6607
+ if (nSegs < 5) {
6608
+ return {
6609
+ elbowVariants: [elbow.map((p) => ({ ...p }))],
6610
+ movableSegments: []
6611
+ };
6612
+ }
6613
+ const firstMovableIndex = 2;
6614
+ const lastMovableIndex = nPts - 4;
6615
+ const movableSegments = [];
6616
+ const movableIdx = [];
6617
+ const axes = [];
6618
+ const optionsPerSegment = [];
6619
+ for (let i = firstMovableIndex; i <= lastMovableIndex; i++) {
6620
+ const prev = elbow[i - 1];
6621
+ const start = elbow[i];
6622
+ const end = elbow[i + 1];
6623
+ const next2 = elbow[i + 2];
6624
+ const { axis, facing } = computeFreedom(prev, start, end);
6625
+ movableSegments.push({
6626
+ start: { ...start },
6627
+ end: { ...end },
6628
+ freedom: facing,
6629
+ dir: dir(facing)
6630
+ });
6631
+ movableIdx.push(i);
6632
+ axes.push(axis);
6633
+ const currentCoord = axis === "x" ? start.x : start.y;
6634
+ const rawCandidates = collectAxisCandidates(axis, guidelines, currentCoord);
6635
+ const filtered = rawCandidates.filter((pos) => {
6636
+ if (axis === "y") {
6637
+ const lenPrev = Math.abs(prev.y - pos);
6638
+ const lenNext = Math.abs(next2.y - pos);
6639
+ return lenPrev > MIN_LEN && lenNext > MIN_LEN;
6640
+ } else {
6641
+ const lenPrev = Math.abs(prev.x - pos);
6642
+ const lenNext = Math.abs(next2.x - pos);
6643
+ return lenPrev > MIN_LEN && lenNext > MIN_LEN;
6572
6644
  }
6573
- }
6574
- return combinations;
6575
- };
6576
- const positionCombinations = generateCombinations(segmentGuidelineOptions);
6645
+ });
6646
+ optionsPerSegment.push(filtered);
6647
+ }
6648
+ const combos = cartesian(optionsPerSegment);
6649
+ const seen = /* @__PURE__ */ new Set();
6577
6650
  const elbowVariants = [];
6578
- for (const combination of positionCombinations) {
6579
- const variant = [...baseElbow];
6580
- for (let segmentIndex = 0; segmentIndex < movableSegments.length; segmentIndex++) {
6581
- const segment = movableSegments[segmentIndex];
6582
- const newPosition = combination[segmentIndex];
6583
- const elbowIndex = segmentIndex + 1;
6584
- if (segment.freedom === "x+" || segment.freedom === "x-") {
6585
- variant[elbowIndex] = { ...variant[elbowIndex], x: newPosition };
6586
- variant[elbowIndex + 1] = { ...variant[elbowIndex + 1], x: newPosition };
6651
+ {
6652
+ const key = keyForPolyline(elbow);
6653
+ seen.add(key);
6654
+ elbowVariants.push(elbow.map((p) => ({ ...p })));
6655
+ }
6656
+ for (const combo of combos) {
6657
+ if (combo.length === 0) continue;
6658
+ const variant = elbow.map((p) => ({ ...p }));
6659
+ for (let k = 0; k < movableIdx.length; k++) {
6660
+ const segIndex = movableIdx[k];
6661
+ const axis = axes[k];
6662
+ const newPos = combo[k];
6663
+ if (typeof newPos !== "number" || Number.isNaN(newPos)) continue;
6664
+ if (axis === "y") {
6665
+ variant[segIndex] = { ...variant[segIndex], y: newPos };
6666
+ variant[segIndex + 1] = { ...variant[segIndex + 1], y: newPos };
6587
6667
  } else {
6588
- variant[elbowIndex] = { ...variant[elbowIndex], y: newPosition };
6589
- variant[elbowIndex + 1] = { ...variant[elbowIndex + 1], y: newPosition };
6668
+ variant[segIndex] = { ...variant[segIndex], x: newPos };
6669
+ variant[segIndex + 1] = { ...variant[segIndex + 1], x: newPos };
6590
6670
  }
6591
6671
  }
6592
- elbowVariants.push(variant);
6672
+ try {
6673
+ assertOrthogonalPolyline(variant);
6674
+ } catch {
6675
+ continue;
6676
+ }
6677
+ const key = keyForPolyline(variant);
6678
+ if (!seen.has(key)) {
6679
+ seen.add(key);
6680
+ elbowVariants.push(variant);
6681
+ }
6593
6682
  }
6594
- return {
6595
- elbowVariants,
6596
- movableSegments
6597
- };
6683
+ return { elbowVariants, movableSegments };
6598
6684
  };
6599
6685
 
6600
6686
  // lib/solvers/GuidelinesSolver/visualizeGuidelines.ts
@@ -6642,6 +6728,7 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6642
6728
  chipMap;
6643
6729
  movableSegments;
6644
6730
  baseElbow;
6731
+ allCandidatePaths;
6645
6732
  queuedCandidatePaths;
6646
6733
  chipObstacleSpatialIndex;
6647
6734
  solvedTracePath = null;
@@ -6691,7 +6778,8 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6691
6778
  }
6692
6779
  return len;
6693
6780
  };
6694
- this.queuedCandidatePaths = [this.baseElbow, ...elbowVariants].sort(
6781
+ this.allCandidatePaths = [this.baseElbow, ...elbowVariants];
6782
+ this.queuedCandidatePaths = [...this.allCandidatePaths].sort(
6695
6783
  (a, b) => getPathLength(a) - getPathLength(b)
6696
6784
  );
6697
6785
  }
@@ -6728,12 +6816,12 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6728
6816
  const bounds = getInputChipBounds(this.chipMap[startPin.chipId]);
6729
6817
  const dx = end.x - start.x;
6730
6818
  const dy = end.y - start.y;
6731
- const EPS = 1e-9;
6819
+ const EPS2 = 1e-9;
6732
6820
  const onLeft = Math.abs(start.x - bounds.minX) < 1e-9;
6733
6821
  const onRight = Math.abs(start.x - bounds.maxX) < 1e-9;
6734
6822
  const onBottom = Math.abs(start.y - bounds.minY) < 1e-9;
6735
6823
  const onTop = Math.abs(start.y - bounds.maxY) < 1e-9;
6736
- const entersInterior = onLeft && dx > EPS || onRight && dx < -EPS || onBottom && dy > EPS || onTop && dy < -EPS;
6824
+ const entersInterior = onLeft && dx > EPS2 || onRight && dx < -EPS2 || onBottom && dy > EPS2 || onTop && dy < -EPS2;
6737
6825
  if (entersInterior) return;
6738
6826
  if (!excludeChipIds.includes(startPin.chipId)) {
6739
6827
  excludeChipIds.push(startPin.chipId);
@@ -6748,12 +6836,12 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6748
6836
  const bounds = getInputChipBounds(this.chipMap[endPin.chipId]);
6749
6837
  const dx = start.x - end.x;
6750
6838
  const dy = start.y - end.y;
6751
- const EPS = 1e-9;
6839
+ const EPS2 = 1e-9;
6752
6840
  const onLeft = Math.abs(end.x - bounds.minX) < 1e-9;
6753
6841
  const onRight = Math.abs(end.x - bounds.maxX) < 1e-9;
6754
6842
  const onBottom = Math.abs(end.y - bounds.minY) < 1e-9;
6755
6843
  const onTop = Math.abs(end.y - bounds.maxY) < 1e-9;
6756
- const entersInterior = onLeft && dx > EPS || onRight && dx < -EPS || onBottom && dy > EPS || onTop && dy < -EPS;
6844
+ const entersInterior = onLeft && dx > EPS2 || onRight && dx < -EPS2 || onBottom && dy > EPS2 || onTop && dy < -EPS2;
6757
6845
  if (entersInterior) return;
6758
6846
  if (!excludeChipIds.includes(endPin.chipId)) {
6759
6847
  excludeChipIds.push(endPin.chipId);
@@ -6956,13 +7044,13 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
6956
7044
  }
6957
7045
  }
6958
7046
  _step() {
6959
- const EPS = 1e-6;
7047
+ const EPS2 = 1e-6;
6960
7048
  const offsets = this.overlappingTraceSegments.map((_, idx) => {
6961
7049
  const n = Math.floor(idx / 2) + 1;
6962
7050
  const signed = idx % 2 === 0 ? -n : n;
6963
7051
  return signed * this.SHIFT_DISTANCE;
6964
7052
  });
6965
- const eq = (a, b) => Math.abs(a - b) < EPS;
7053
+ const eq = (a, b) => Math.abs(a - b) < EPS2;
6966
7054
  const samePoint = (p, q) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y);
6967
7055
  this.overlappingTraceSegments.forEach((group, gidx) => {
6968
7056
  const offset = offsets[gidx];
@@ -6985,8 +7073,8 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
6985
7073
  if (si === 0 || si === pts.length - 2) continue;
6986
7074
  const start = pts[si];
6987
7075
  const end = pts[si + 1];
6988
- const isVertical = Math.abs(start.x - end.x) < EPS;
6989
- const isHorizontal = Math.abs(start.y - end.y) < EPS;
7076
+ const isVertical = Math.abs(start.x - end.x) < EPS2;
7077
+ const isHorizontal = Math.abs(start.y - end.y) < EPS2;
6990
7078
  if (!isVertical && !isHorizontal) continue;
6991
7079
  if (isVertical) {
6992
7080
  if (!appliedX.has(si)) {
@@ -7095,7 +7183,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7095
7183
  return islands;
7096
7184
  }
7097
7185
  findNextOverlapIssue() {
7098
- const EPS = 1e-6;
7186
+ const EPS2 = 1e-6;
7099
7187
  const netIds = Object.keys(this.traceNetIslands);
7100
7188
  for (let i = 0; i < netIds.length; i++) {
7101
7189
  for (let j = i + 1; j < netIds.length; j++) {
@@ -7113,7 +7201,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7113
7201
  const minB = Math.min(b1, b2);
7114
7202
  const maxB = Math.max(b1, b2);
7115
7203
  const overlap = Math.min(maxA, maxB) - Math.max(minA, minB);
7116
- return overlap > EPS;
7204
+ return overlap > EPS2;
7117
7205
  };
7118
7206
  for (let pa = 0; pa < pathsA.length; pa++) {
7119
7207
  const pathA = pathsA[pa];
@@ -7121,8 +7209,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7121
7209
  for (let sa = 0; sa < ptsA.length - 1; sa++) {
7122
7210
  const a1 = ptsA[sa];
7123
7211
  const a2 = ptsA[sa + 1];
7124
- const aVert = Math.abs(a1.x - a2.x) < EPS;
7125
- const aHorz = Math.abs(a1.y - a2.y) < EPS;
7212
+ const aVert = Math.abs(a1.x - a2.x) < EPS2;
7213
+ const aHorz = Math.abs(a1.y - a2.y) < EPS2;
7126
7214
  if (!aVert && !aHorz) continue;
7127
7215
  for (let pb = 0; pb < pathsB.length; pb++) {
7128
7216
  const pathB = pathsB[pb];
@@ -7130,11 +7218,11 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7130
7218
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
7131
7219
  const b1 = ptsB[sb];
7132
7220
  const b2 = ptsB[sb + 1];
7133
- const bVert = Math.abs(b1.x - b2.x) < EPS;
7134
- const bHorz = Math.abs(b1.y - b2.y) < EPS;
7221
+ const bVert = Math.abs(b1.x - b2.x) < EPS2;
7222
+ const bHorz = Math.abs(b1.y - b2.y) < EPS2;
7135
7223
  if (!bVert && !bHorz) continue;
7136
7224
  if (aVert && bVert) {
7137
- if (Math.abs(a1.x - b1.x) < EPS) {
7225
+ if (Math.abs(a1.x - b1.x) < EPS2) {
7138
7226
  if (overlaps1D(a1.y, a2.y, b1.y, b2.y)) {
7139
7227
  const keyA = `${pa}:${sa}`;
7140
7228
  const keyB = `${pb}:${sb}`;
@@ -7155,7 +7243,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7155
7243
  }
7156
7244
  }
7157
7245
  } else if (aHorz && bHorz) {
7158
- if (Math.abs(a1.y - b1.y) < EPS) {
7246
+ if (Math.abs(a1.y - b1.y) < EPS2) {
7159
7247
  if (overlaps1D(a1.x, a2.x, b1.x, b2.x)) {
7160
7248
  const keyA = `${pa}:${sa}`;
7161
7249
  const keyB = `${pb}:${sb}`;
@@ -7270,24 +7358,24 @@ function getRectBounds(center, w, h) {
7270
7358
  }
7271
7359
 
7272
7360
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
7273
- function segmentIntersectsRect(p1, p2, rect, EPS = 1e-9) {
7274
- const isVert = Math.abs(p1.x - p2.x) < EPS;
7275
- const isHorz = Math.abs(p1.y - p2.y) < EPS;
7361
+ function segmentIntersectsRect(p1, p2, rect, EPS2 = 1e-9) {
7362
+ const isVert = Math.abs(p1.x - p2.x) < EPS2;
7363
+ const isHorz = Math.abs(p1.y - p2.y) < EPS2;
7276
7364
  if (!isVert && !isHorz) return false;
7277
7365
  if (isVert) {
7278
7366
  const x = p1.x;
7279
- if (x < rect.minX - EPS || x > rect.maxX + EPS) return false;
7367
+ if (x < rect.minX - EPS2 || x > rect.maxX + EPS2) return false;
7280
7368
  const segMinY = Math.min(p1.y, p2.y);
7281
7369
  const segMaxY = Math.max(p1.y, p2.y);
7282
7370
  const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
7283
- return overlap > EPS;
7371
+ return overlap > EPS2;
7284
7372
  } else {
7285
7373
  const y = p1.y;
7286
- if (y < rect.minY - EPS || y > rect.maxY + EPS) return false;
7374
+ if (y < rect.minY - EPS2 || y > rect.maxY + EPS2) return false;
7287
7375
  const segMinX = Math.min(p1.x, p2.x);
7288
7376
  const segMaxX = Math.max(p1.x, p2.x);
7289
7377
  const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
7290
- return overlap > EPS;
7378
+ return overlap > EPS2;
7291
7379
  }
7292
7380
  }
7293
7381
  function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
@@ -7612,14 +7700,14 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7612
7700
  };
7613
7701
  let bestCandidate = null;
7614
7702
  let bestScore = -Infinity;
7615
- const EPS = 1e-6;
7703
+ const EPS2 = 1e-6;
7616
7704
  for (const curr of tracesToScan) {
7617
7705
  const pts = curr.tracePath.slice();
7618
7706
  for (let si = 0; si < pts.length - 1; si++) {
7619
7707
  const a = pts[si];
7620
7708
  const b = pts[si + 1];
7621
- const isH = Math.abs(a.y - b.y) < EPS;
7622
- const isV = Math.abs(a.x - b.x) < EPS;
7709
+ const isH = Math.abs(a.y - b.y) < EPS2;
7710
+ const isV = Math.abs(a.x - b.x) < EPS2;
7623
7711
  if (!isH && !isV) continue;
7624
7712
  const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
7625
7713
  const candidateOrients = orientations.filter(
@@ -24,6 +24,7 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
24
24
  movableSegments: Array<MovableSegment>
25
25
  baseElbow: Point[]
26
26
 
27
+ allCandidatePaths: Array<Point[]>
27
28
  queuedCandidatePaths: Array<Point[]>
28
29
 
29
30
  chipObstacleSpatialIndex: ChipObstacleSpatialIndex
@@ -91,7 +92,8 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
91
92
  return len
92
93
  }
93
94
 
94
- this.queuedCandidatePaths = [this.baseElbow, ...elbowVariants].sort(
95
+ this.allCandidatePaths = [this.baseElbow, ...elbowVariants]
96
+ this.queuedCandidatePaths = [...this.allCandidatePaths].sort(
95
97
  (a, b) => getPathLength(a) - getPathLength(b),
96
98
  )
97
99
  }