@tscircuit/schematic-trace-solver 0.0.140 → 0.0.141

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.
package/dist/index.d.ts CHANGED
@@ -279,6 +279,7 @@ declare class TraceOverlapIssueSolver extends BaseSolver {
279
279
  }
280
280
 
281
281
  type ConnNetId = string;
282
+ type TraceState = Record<MspConnectionPairId, SolvedTracePath>;
282
283
  /**
283
284
  * This solver finds traces that overlap or meet collinearly and aren't
284
285
  * connected via the globalConnMap, then shifts them apart in the direction
@@ -308,6 +309,7 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
308
309
  */
309
310
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>;
310
311
  correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath>;
312
+ recentTraceStates: TraceState[];
311
313
  cleanupPhase: "diagonals" | "done" | null;
312
314
  constructor(params: {
313
315
  inputProblem: InputProblem;
@@ -324,6 +326,8 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
324
326
  private findNextDiagonalSegment;
325
327
  private findAndFixNextDiagonalSegment;
326
328
  _step(): void;
329
+ private rememberTraceState;
330
+ private returnsToPreviousTraceState;
327
331
  visualize(): graphics_debug.GraphicsObject;
328
332
  }
329
333
 
package/dist/index.js CHANGED
@@ -2102,6 +2102,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
2102
2102
  };
2103
2103
 
2104
2104
  // lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts
2105
+ var TRACE_STATE_POSITION_EPSILON = 1e-6;
2105
2106
  var TraceOverlapShiftSolver = class extends BaseSolver {
2106
2107
  inputProblem;
2107
2108
  inputTracePaths;
@@ -2112,6 +2113,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2112
2113
  */
2113
2114
  traceNetIslands = {};
2114
2115
  correctedTraceMap = {};
2116
+ // Keep only the current and previous layouts to detect a two-state cycle
2117
+ // without accumulating routing history for the whole solve.
2118
+ recentTraceStates = [];
2115
2119
  cleanupPhase = null;
2116
2120
  constructor(params) {
2117
2121
  super();
@@ -2123,6 +2127,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2123
2127
  const { mspPairId } = tracePath;
2124
2128
  this.correctedTraceMap[mspPairId] = tracePath;
2125
2129
  }
2130
+ this.rememberTraceState(this.correctedTraceMap);
2126
2131
  this.traceNetIslands = this.computeTraceNetIslands();
2127
2132
  }
2128
2133
  getConstructorParams() {
@@ -2337,11 +2342,17 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2337
2342
  }
2338
2343
  _step() {
2339
2344
  if (this.activeSubSolver?.solved) {
2340
- for (const [mspPairId, newTrace] of Object.entries(
2341
- this.activeSubSolver.correctedTraceMap
2342
- )) {
2343
- this.correctedTraceMap[mspPairId] = newTrace;
2345
+ const nextTraceState = {
2346
+ ...this.correctedTraceMap,
2347
+ ...this.activeSubSolver.correctedTraceMap
2348
+ };
2349
+ if (this.returnsToPreviousTraceState(nextTraceState)) {
2350
+ this.activeSubSolver = null;
2351
+ this.solved = true;
2352
+ return;
2344
2353
  }
2354
+ this.correctedTraceMap = nextTraceState;
2355
+ this.rememberTraceState(nextTraceState);
2345
2356
  this.activeSubSolver = null;
2346
2357
  this.traceNetIslands = this.computeTraceNetIslands();
2347
2358
  }
@@ -2374,6 +2385,34 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2374
2385
  traceIdsToShift: this.traceIdsToShift
2375
2386
  });
2376
2387
  }
2388
+ rememberTraceState(traceState) {
2389
+ this.recentTraceStates.push({ ...traceState });
2390
+ if (this.recentTraceStates.length > 2) {
2391
+ this.recentTraceStates.shift();
2392
+ }
2393
+ }
2394
+ returnsToPreviousTraceState(candidateTraceState) {
2395
+ if (this.recentTraceStates.length < 2) return false;
2396
+ const previousTraceState = this.recentTraceStates[0];
2397
+ const traceIds = Object.keys(candidateTraceState);
2398
+ if (traceIds.length !== Object.keys(previousTraceState).length) return false;
2399
+ for (const traceId of traceIds) {
2400
+ const candidatePath = candidateTraceState[traceId];
2401
+ const previousPath = previousTraceState[traceId];
2402
+ if (!candidatePath || !previousPath) return false;
2403
+ if (candidatePath.tracePath.length !== previousPath.tracePath.length) {
2404
+ return false;
2405
+ }
2406
+ for (let i = 0; i < candidatePath.tracePath.length; i++) {
2407
+ const candidatePoint = candidatePath.tracePath[i];
2408
+ const previousPoint = previousPath.tracePath[i];
2409
+ if (Math.abs(candidatePoint.x - previousPoint.x) > TRACE_STATE_POSITION_EPSILON || Math.abs(candidatePoint.y - previousPoint.y) > TRACE_STATE_POSITION_EPSILON) {
2410
+ return false;
2411
+ }
2412
+ }
2413
+ }
2414
+ return true;
2415
+ }
2377
2416
  visualize() {
2378
2417
  if (this.activeSubSolver) {
2379
2418
  return this.activeSubSolver.visualize();
@@ -11,6 +11,9 @@ import {
11
11
  import type { MspConnectionPairId } from "../MspConnectionPairSolver/MspConnectionPairSolver"
12
12
 
13
13
  type ConnNetId = string
14
+ type TraceState = Record<MspConnectionPairId, SolvedTracePath>
15
+
16
+ const TRACE_STATE_POSITION_EPSILON = 1e-6
14
17
 
15
18
  /**
16
19
  * This solver finds traces that overlap or meet collinearly and aren't
@@ -44,6 +47,9 @@ export class TraceOverlapShiftSolver extends BaseSolver {
44
47
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>> = {}
45
48
 
46
49
  correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
50
+ // Keep only the current and previous layouts to detect a two-state cycle
51
+ // without accumulating routing history for the whole solve.
52
+ recentTraceStates: TraceState[] = []
47
53
 
48
54
  cleanupPhase: "diagonals" | "done" | null = null
49
55
 
@@ -64,6 +70,8 @@ export class TraceOverlapShiftSolver extends BaseSolver {
64
70
  this.correctedTraceMap[mspPairId] = tracePath
65
71
  }
66
72
 
73
+ this.rememberTraceState(this.correctedTraceMap)
74
+
67
75
  this.traceNetIslands = this.computeTraceNetIslands()
68
76
  }
69
77
 
@@ -361,11 +369,19 @@ export class TraceOverlapShiftSolver extends BaseSolver {
361
369
 
362
370
  override _step() {
363
371
  if (this.activeSubSolver?.solved) {
364
- for (const [mspPairId, newTrace] of Object.entries(
365
- this.activeSubSolver.correctedTraceMap,
366
- )) {
367
- this.correctedTraceMap[mspPairId] = newTrace
372
+ const nextTraceState = {
373
+ ...this.correctedTraceMap,
374
+ ...this.activeSubSolver.correctedTraceMap,
368
375
  }
376
+ // Returning to the older retained layout means corrections are bouncing
377
+ // A -> B -> A. Keep B and finish this pass instead of retrying forever.
378
+ if (this.returnsToPreviousTraceState(nextTraceState)) {
379
+ this.activeSubSolver = null
380
+ this.solved = true
381
+ return
382
+ }
383
+ this.correctedTraceMap = nextTraceState
384
+ this.rememberTraceState(nextTraceState)
369
385
  this.activeSubSolver = null
370
386
  this.traceNetIslands = this.computeTraceNetIslands()
371
387
  }
@@ -407,6 +423,46 @@ export class TraceOverlapShiftSolver extends BaseSolver {
407
423
  })
408
424
  }
409
425
 
426
+ private rememberTraceState(traceState: TraceState) {
427
+ this.recentTraceStates.push({ ...traceState })
428
+ if (this.recentTraceStates.length > 2) {
429
+ this.recentTraceStates.shift()
430
+ }
431
+ }
432
+
433
+ private returnsToPreviousTraceState(candidateTraceState: TraceState) {
434
+ if (this.recentTraceStates.length < 2) return false
435
+
436
+ const previousTraceState = this.recentTraceStates[0]!
437
+ const traceIds = Object.keys(candidateTraceState)
438
+ if (traceIds.length !== Object.keys(previousTraceState).length) return false
439
+
440
+ // Corrections create new objects, so compare the trace geometry itself.
441
+ for (const traceId of traceIds) {
442
+ const candidatePath = candidateTraceState[traceId]
443
+ const previousPath = previousTraceState[traceId]
444
+ if (!candidatePath || !previousPath) return false
445
+ if (candidatePath.tracePath.length !== previousPath.tracePath.length) {
446
+ return false
447
+ }
448
+
449
+ for (let i = 0; i < candidatePath.tracePath.length; i++) {
450
+ const candidatePoint = candidatePath.tracePath[i]!
451
+ const previousPoint = previousPath.tracePath[i]!
452
+ if (
453
+ Math.abs(candidatePoint.x - previousPoint.x) >
454
+ TRACE_STATE_POSITION_EPSILON ||
455
+ Math.abs(candidatePoint.y - previousPoint.y) >
456
+ TRACE_STATE_POSITION_EPSILON
457
+ ) {
458
+ return false
459
+ }
460
+ }
461
+ }
462
+
463
+ return true
464
+ }
465
+
410
466
  override visualize() {
411
467
  if (this.activeSubSolver) {
412
468
  return this.activeSubSolver.visualize()
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.140",
8
+ "version": "0.0.141",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",