@tscircuit/schematic-trace-solver 0.0.87 → 0.0.89

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 (118) hide show
  1. package/.agents/skills/schematic-trace-visualization/SKILL.md +115 -0
  2. package/.github/scripts/import-json-bug-report.ts +15 -1
  3. package/.github/workflows/json-bug-report.yml +3 -2
  4. package/AGENTS.md +22 -0
  5. package/dist/index.js +30 -22
  6. package/lib/solvers/Example28Solver/reroute.ts +10 -0
  7. package/lib/testing/PipelineStageDebugRunner.ts +344 -0
  8. package/package.json +3 -2
  9. package/scripts/debug-pipeline-stages.ts +146 -0
  10. package/site/bug-reports/bug-report-20260706T220324Z.page.tsx +4 -0
  11. package/site/bug-reports/bug-report-20260707T020342Z.page.tsx +4 -0
  12. package/site/bug-reports/bug-report-20260707T092615Z.page.tsx +4 -0
  13. package/site/bug-reports/bug-report-20260707T134549Z.page.tsx +4 -0
  14. package/site/bug-reports/bug-report-20260707T134722Z.page.tsx +4 -0
  15. package/site/bug-reports/bug-report-20260707T140410Z.page.tsx +4 -0
  16. package/site/bug-reports/bug-report-20260707T141025Z.page.tsx +4 -0
  17. package/site/bug-reports/bug-report-20260707T141421Z.page.tsx +4 -0
  18. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +386 -0
  19. package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.json +1471 -0
  20. package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +12 -0
  21. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +357 -3448
  22. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +64 -0
  23. package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.json +233 -0
  24. package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.test.ts +12 -0
  25. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +252 -0
  26. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.json +1392 -0
  27. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +12 -0
  28. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +124 -0
  29. package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.json +652 -0
  30. package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.test.ts +12 -0
  31. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +73 -0
  32. package/tests/bug-reports/bug-report-20260707T134722Z/bug-report-20260707T134722Z.json +265 -0
  33. package/tests/bug-reports/bug-report-20260707T134722Z/bug-report-20260707T134722Z.test.ts +12 -0
  34. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +154 -0
  35. package/tests/bug-reports/bug-report-20260707T140410Z/bug-report-20260707T140410Z.json +610 -0
  36. package/tests/bug-reports/bug-report-20260707T140410Z/bug-report-20260707T140410Z.test.ts +12 -0
  37. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +67 -0
  38. package/tests/bug-reports/bug-report-20260707T141025Z/bug-report-20260707T141025Z.json +185 -0
  39. package/tests/bug-reports/bug-report-20260707T141025Z/bug-report-20260707T141025Z.test.ts +12 -0
  40. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +114 -0
  41. package/tests/bug-reports/bug-report-20260707T141421Z/bug-report-20260707T141421Z.json +499 -0
  42. package/tests/bug-reports/bug-report-20260707T141421Z/bug-report-20260707T141421Z.test.ts +23 -0
  43. package/tests/examples/__snapshots__/example01.snap.svg +59 -162
  44. package/tests/examples/__snapshots__/example02.snap.svg +64 -273
  45. package/tests/examples/__snapshots__/example03.snap.svg +85 -290
  46. package/tests/examples/__snapshots__/example04.snap.svg +50 -94
  47. package/tests/examples/__snapshots__/example05.snap.svg +73 -191
  48. package/tests/examples/__snapshots__/example06.snap.svg +49 -95
  49. package/tests/examples/__snapshots__/example07.snap.svg +65 -218
  50. package/tests/examples/__snapshots__/example08.snap.svg +61 -143
  51. package/tests/examples/__snapshots__/example09.snap.svg +123 -493
  52. package/tests/examples/__snapshots__/example10.snap.svg +57 -136
  53. package/tests/examples/__snapshots__/example11.snap.svg +64 -173
  54. package/tests/examples/__snapshots__/example12.snap.svg +58 -159
  55. package/tests/examples/__snapshots__/example13.snap.svg +78 -348
  56. package/tests/examples/__snapshots__/example14.snap.svg +77 -293
  57. package/tests/examples/__snapshots__/example15.snap.svg +140 -984
  58. package/tests/examples/__snapshots__/example16.snap.svg +62 -162
  59. package/tests/examples/__snapshots__/example17.snap.svg +67 -198
  60. package/tests/examples/__snapshots__/example18.snap.svg +69 -249
  61. package/tests/examples/__snapshots__/example19.snap.svg +63 -202
  62. package/tests/examples/__snapshots__/example20.snap.svg +60 -148
  63. package/tests/examples/__snapshots__/example21.snap.svg +73 -298
  64. package/tests/examples/__snapshots__/example22.snap.svg +62 -156
  65. package/tests/examples/__snapshots__/example23.snap.svg +66 -172
  66. package/tests/examples/__snapshots__/example24.snap.svg +64 -164
  67. package/tests/examples/__snapshots__/example25.snap.svg +76 -299
  68. package/tests/examples/__snapshots__/example26.snap.svg +64 -164
  69. package/tests/examples/__snapshots__/example27.snap.svg +83 -300
  70. package/tests/examples/__snapshots__/example28.snap.svg +54 -115
  71. package/tests/examples/__snapshots__/example29.snap.svg +257 -1299
  72. package/tests/examples/__snapshots__/example30.snap.svg +91 -293
  73. package/tests/examples/__snapshots__/example31.snap.svg +47 -88
  74. package/tests/examples/__snapshots__/example32.snap.svg +107 -405
  75. package/tests/examples/__snapshots__/example33.snap.svg +71 -180
  76. package/tests/examples/__snapshots__/example34.snap.svg +55 -116
  77. package/tests/examples/__snapshots__/example35.snap.svg +69 -176
  78. package/tests/examples/__snapshots__/example36.snap.svg +65 -159
  79. package/tests/examples/__snapshots__/example37.snap.svg +75 -223
  80. package/tests/examples/__snapshots__/example38.snap.svg +46 -76
  81. package/tests/examples/__snapshots__/example39.snap.svg +51 -106
  82. package/tests/examples/__snapshots__/example40.snap.svg +83 -270
  83. package/tests/examples/__snapshots__/example41.snap.svg +54 -118
  84. package/tests/examples/__snapshots__/example42.snap.svg +67 -194
  85. package/tests/examples/__snapshots__/example43.snap.svg +62 -168
  86. package/tests/examples/__snapshots__/example44.snap.svg +86 -369
  87. package/tests/examples/__snapshots__/example45.snap.svg +60 -169
  88. package/tests/examples/__snapshots__/example46.snap.svg +80 -342
  89. package/tests/examples/__snapshots__/example47.snap.svg +82 -323
  90. package/tests/examples/__snapshots__/example48.snap.svg +64 -167
  91. package/tests/examples/__snapshots__/example49.snap.svg +120 -565
  92. package/tests/examples/__snapshots__/example50.snap.svg +71 -189
  93. package/tests/fixtures/getLastStepGraphicsObject.ts +38 -0
  94. package/tests/fixtures/getLastStepSvg.ts +9 -0
  95. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +67 -218
  96. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +47 -87
  97. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +73 -272
  98. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +88 -0
  99. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +118 -0
  100. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +94 -395
  101. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +88 -467
  102. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +109 -569
  103. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +95 -459
  104. package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +64 -176
  105. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +51 -112
  106. package/tests/repros/assets/repro-atmega328p-fault-pullup.input.json +239 -0
  107. package/tests/repros/assets/repro-atmega328p-missing-gnd-netlabel.input.json +432 -0
  108. package/tests/repros/repro-atmega328p-fault-pullup.test.ts +11 -0
  109. package/tests/repros/repro-atmega328p-missing-gnd-netlabel.test.ts +16 -0
  110. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +59 -159
  111. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +54 -118
  112. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +58 -245
  113. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +73 -222
  114. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +65 -204
  115. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +65 -183
  116. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +54 -139
  117. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +61 -215
  118. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +54 -124
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: schematic-trace-visualization
3
+ description: Work with schematic trace solver visualization in this repo. Use when changing solver `visualize()` or `preview()` output, adding debug graphics, creating SVG snapshots, inspecting pipeline stages, or generating graphics-debug artifacts for AI/headless review.
4
+ ---
5
+
6
+ # Schematic Trace Visualization
7
+
8
+ Use `GraphicsObject` from `graphics-debug` for solver debug views, SVG snapshots, and headless artifacts.
9
+
10
+ ## Graphics Object Types
11
+
12
+ Import visualization types from `graphics-debug`:
13
+
14
+ ```ts
15
+ import type {
16
+ GraphicsObject,
17
+ Point,
18
+ Line,
19
+ Rect,
20
+ Circle,
21
+ Text,
22
+ } from "graphics-debug"
23
+ ```
24
+
25
+ Do not redefine these shapes in repo docs, tests, or utilities.
26
+
27
+ ## Image Functions
28
+
29
+ This repo uses `graphics-debug` for SVG and PNG rendering:
30
+
31
+ ```ts
32
+ import {
33
+ getPngBufferFromGraphicsObject,
34
+ getSvgFromGraphicsObject,
35
+ } from "graphics-debug"
36
+ ```
37
+
38
+ Call them with a white background for stable artifacts:
39
+
40
+ ```ts
41
+ const svg = getSvgFromGraphicsObject(graphics, { backgroundColor: "white" })
42
+ const png = await getPngBufferFromGraphicsObject(graphics, {
43
+ backgroundColor: "white",
44
+ pngWidth: 1536,
45
+ pngHeight: 1536,
46
+ })
47
+ ```
48
+
49
+ ## Existing Repo Helpers
50
+
51
+ - `tests/fixtures/matcher.ts`: `expect(solver).toMatchSolverSnapshot(import.meta.path)` converts `solver.visualize()` to SVG and snapshots it.
52
+ - `tests/fixtures/getLastStepGraphicsObject.ts`: filters a `GraphicsObject` to its highest numbered `step`.
53
+ - `tests/fixtures/getLastStepSvg.ts`: converts only the last step to SVG.
54
+ - `lib/testing/PipelineStageDebugRunner.ts`: runs `SchematicTracePipelineSolver` and writes per-stage PNG/SVG/graphics JSON artifacts.
55
+ - `scripts/debug-pipeline-stages.ts`: CLI wrapper around the stage debug runner.
56
+
57
+ ## Minimal SVG Tests
58
+
59
+ Prefer focused solver SVG snapshots over broad dumps:
60
+
61
+ ```ts
62
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
63
+ solver.solve()
64
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
65
+ ```
66
+
67
+ For multi-step graphics where only the final stage matters:
68
+
69
+ ```ts
70
+ expect(getLastStepSvg(solver.visualize())).toMatchSvgSnapshot(import.meta.path)
71
+ ```
72
+
73
+ Update snapshots with:
74
+
75
+ ```bash
76
+ BUN_UPDATE_SNAPSHOTS=1 bun test
77
+ ```
78
+
79
+ ## Headless Stage Debugging
80
+
81
+ To inspect a pipeline run without Cosmos:
82
+
83
+ ```bash
84
+ bun run debug:pipeline tests/assets/example01.json
85
+ ```
86
+
87
+ Useful options:
88
+
89
+ ```bash
90
+ bun run debug:pipeline tests/assets/example17.json --out tmp/schematic-trace-debug/example17
91
+ bun run debug:pipeline tests/assets/example17.json --stop-after schematicTraceLinesSolver
92
+ bun run debug:pipeline tests/assets/example17.json --no-json
93
+ bun run debug:pipeline tests/assets/example17.json --svg --step-pngs
94
+ ```
95
+
96
+ The runner writes:
97
+
98
+ - `logs.txt`
99
+ - `stageNN-stageName.png`
100
+ - `stageNN-stageName.graphics.json` unless `--no-json` is passed
101
+
102
+ Pass `--svg` when SVG companion artifacts are useful.
103
+ Pass `--step-pngs` to split graphics with `step` values into per-step PNGs.
104
+
105
+ Stage numbers are important because this pipeline can reuse the same solver name more than once, for example `netLabelPlacementSolver`.
106
+
107
+ ## Choosing The View
108
+
109
+ Use solver-native `visualize()` for internal state, candidates, failed geometry, staged progress, net labels, and trace cleanup state.
110
+
111
+ Use `SchematicTracePipelineSolver.visualize()` for final combined output after `solve()`.
112
+
113
+ Use `visualizeInputProblem(inputProblem)` when the question is about the raw input geometry, schematic components, pins, direct connections, or net connection labels before routing.
114
+
115
+ Keep SVG backgrounds white. Treat colors, labels, line widths, opacity, and `step` values as snapshot-sensitive.
@@ -48,15 +48,19 @@ async function main() {
48
48
  const reportId = `bug-report-${timestamp}`
49
49
  const branchName = `json-bug-report/${reportId}`
50
50
  const reportDir = path.join("tests", "bug-reports", reportId)
51
+ const siteReportDir = path.join("site", "bug-reports")
51
52
  const reportDirAbs = path.join(repoRoot, reportDir)
52
53
  const jsonPath = path.join(reportDir, `${reportId}.json`)
53
54
  const testPath = path.join(reportDir, `${reportId}.test.ts`)
55
+ const pagePath = path.join(siteReportDir, `${reportId}.page.tsx`)
54
56
 
55
57
  writeOutput("report_id", reportId)
56
58
  writeOutput("branch_name", branchName)
57
59
  writeOutput("report_dir", reportDir)
60
+ writeOutput("site_report_dir", siteReportDir)
58
61
  writeOutput("json_path", jsonPath)
59
62
  writeOutput("test_path", testPath)
63
+ writeOutput("page_path", pagePath)
60
64
 
61
65
  if (fs.existsSync(reportDirAbs)) {
62
66
  writeOutput("created", "false")
@@ -70,15 +74,17 @@ async function main() {
70
74
  assertInputProblem(jsonAttachment.value)
71
75
 
72
76
  fs.mkdirSync(reportDirAbs, { recursive: true })
77
+ fs.mkdirSync(path.join(repoRoot, siteReportDir), { recursive: true })
73
78
  fs.writeFileSync(
74
79
  path.join(repoRoot, jsonPath),
75
80
  `${JSON.stringify(jsonAttachment.value, null, 2)}\n`,
76
81
  )
77
82
  fs.writeFileSync(path.join(repoRoot, testPath), getTestFile(reportId))
83
+ fs.writeFileSync(path.join(repoRoot, pagePath), getPageFile(reportId))
78
84
 
79
85
  writeOutput("created", "true")
80
86
  writeOutput("attachment_url", jsonAttachment.url)
81
- console.log(`Generated ${jsonPath} and ${testPath}`)
87
+ console.log(`Generated ${jsonPath}, ${testPath}, and ${pagePath}`)
82
88
  }
83
89
 
84
90
  function isJsonBugReportIssue(issue: IssueEvent["issue"], body: string) {
@@ -458,6 +464,14 @@ test("${reportId}", () => {
458
464
  `
459
465
  }
460
466
 
467
+ function getPageFile(reportId: string) {
468
+ return `import { PipelineDebugger } from "site/components/PipelineDebugger"
469
+ import inputProblem from "../../tests/bug-reports/${reportId}/${reportId}.json"
470
+
471
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
472
+ `
473
+ }
474
+
461
475
  function writeOutput(key: string, value: string) {
462
476
  if (!githubOutputPath) return
463
477
  fs.appendFileSync(githubOutputPath, `${key}=${value}\n`)
@@ -64,7 +64,7 @@ jobs:
64
64
 
65
65
  - name: Format generated test
66
66
  if: steps.import.outputs.created == 'true'
67
- run: ./node_modules/.bin/biome format --write "${{ steps.import.outputs.test_path }}"
67
+ run: ./node_modules/.bin/biome format --write "${{ steps.import.outputs.test_path }}" "${{ steps.import.outputs.page_path }}"
68
68
 
69
69
  - name: Open pull request
70
70
  id: pr
@@ -82,6 +82,7 @@ jobs:
82
82
  git config user.email "tscircuit-bot@users.noreply.github.com"
83
83
  git checkout -B "$BRANCH_NAME"
84
84
  git add "${{ steps.import.outputs.report_dir }}"
85
+ git add "${{ steps.import.outputs.page_path }}"
85
86
 
86
87
  if git diff --cached --quiet; then
87
88
  echo "No changes to commit."
@@ -96,7 +97,7 @@ jobs:
96
97
  {
97
98
  echo "Generated from #${ISSUE_NUMBER}."
98
99
  echo
99
- echo "Adds a snapshot-only regression test for the attached JSON solver input."
100
+ echo "Adds a snapshot-only regression test and debugger page for the attached JSON solver input."
100
101
  echo
101
102
  echo "Workflow run: ${RUN_URL}"
102
103
  } > "$pr_body_file"
package/AGENTS.md ADDED
@@ -0,0 +1,22 @@
1
+ # Agent Notes
2
+
3
+ - Use `bunx tsc --noEmit` to type check.
4
+ - Use `bun test path/to/test.ts` to run a test.
5
+ - Use `bun add <package>` to add a package.
6
+
7
+ ## Solver Visualization
8
+
9
+ For schematic trace solver visualization work, use the repo-local skill:
10
+
11
+ - `.agents/skills/schematic-trace-visualization/SKILL.md`
12
+
13
+ This repo supports headless stage artifact generation without starting Cosmos:
14
+
15
+ ```bash
16
+ bun run debug:pipeline tests/assets/example01.json
17
+ ```
18
+
19
+ The command writes per-stage PNG and graphics JSON artifacts under `tmp/schematic-trace-debug/...`.
20
+ Pass `--svg` when SVG companion files are needed.
21
+
22
+ Interactive debugging still exists through `site/**/*.page.tsx`, but agents should prefer the headless debug runner when they need to inspect solver state programmatically.
package/dist/index.js CHANGED
@@ -5419,6 +5419,28 @@ var moveAttachedLabelsToReroutedTrace = ({
5419
5419
  };
5420
5420
  });
5421
5421
 
5422
+ // lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
5423
+ var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
5424
+ for (let i = 0; i < path.length - 1; i++) {
5425
+ const start = path[i];
5426
+ const end = path[i + 1];
5427
+ for (const obstacle of chipObstacles) {
5428
+ if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5429
+ if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
5430
+ return true;
5431
+ }
5432
+ }
5433
+ }
5434
+ return false;
5435
+ };
5436
+ var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5437
+ const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5438
+ if (isVertical4) {
5439
+ return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
5440
+ }
5441
+ return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
5442
+ };
5443
+
5422
5444
  // lib/solvers/Example28Solver/reroute.ts
5423
5445
  var LABEL_CLEARANCE = 0.1;
5424
5446
  var findBestReroutePath = ({
@@ -5529,6 +5551,14 @@ var createCandidateResult = ({
5529
5551
  selected: false
5530
5552
  };
5531
5553
  }
5554
+ if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
5555
+ return {
5556
+ path,
5557
+ status: "chip-collision",
5558
+ usesHorizontalSegmentPush,
5559
+ selected: false
5560
+ };
5561
+ }
5532
5562
  return {
5533
5563
  path,
5534
5564
  score: scoreTracePath({
@@ -5792,28 +5822,6 @@ var countCrossingsWithOtherTraces = (params) => {
5792
5822
  return crossingCount;
5793
5823
  };
5794
5824
 
5795
- // lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
5796
- var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
5797
- for (let i = 0; i < path.length - 1; i++) {
5798
- const start = path[i];
5799
- const end = path[i + 1];
5800
- for (const obstacle of chipObstacles) {
5801
- if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5802
- if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
5803
- return true;
5804
- }
5805
- }
5806
- }
5807
- return false;
5808
- };
5809
- var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5810
- const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5811
- if (isVertical4) {
5812
- return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
5813
- }
5814
- return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
5815
- };
5816
-
5817
5825
  // lib/solvers/Example28Solver/scoreSolutionTracePath.ts
5818
5826
  var scoreSolutionTracePath = (params) => {
5819
5827
  const { traces, outputTraces, chipObstacles } = params;
@@ -14,6 +14,7 @@ import {
14
14
  getPathLength,
15
15
  isPathCollidingWithChipInterior,
16
16
  } from "./geometry"
17
+ import { doesPathRunAlongChipBoundary } from "./doesPathRunAlongChipBoundary"
17
18
  import type {
18
19
  ChipObstacle,
19
20
  RerouteCandidateResult,
@@ -165,6 +166,15 @@ const createCandidateResult = ({
165
166
  }
166
167
  }
167
168
 
169
+ if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
170
+ return {
171
+ path,
172
+ status: "chip-collision",
173
+ usesHorizontalSegmentPush,
174
+ selected: false,
175
+ }
176
+ }
177
+
168
178
  return {
169
179
  path,
170
180
  score: scoreTracePath({
@@ -0,0 +1,344 @@
1
+ import { mkdir, writeFile } from "node:fs/promises"
2
+ import path from "node:path"
3
+ import {
4
+ getPngBufferFromGraphicsObject,
5
+ getSvgFromGraphicsObject,
6
+ } from "graphics-debug"
7
+ import type { GraphicsObject } from "graphics-debug"
8
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
9
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
10
+
11
+ type PipelineStepLike = {
12
+ solverName: string
13
+ }
14
+
15
+ type VisualizingSolver = {
16
+ visualize: () => GraphicsObject
17
+ iterations?: number
18
+ error?: string | null
19
+ solved?: boolean
20
+ failed?: boolean
21
+ }
22
+
23
+ export type PipelineStageArtifact = {
24
+ stageName: string
25
+ stageNumber: number
26
+ pngPath?: string
27
+ svgPath?: string
28
+ graphicsJsonPath?: string
29
+ stepPngPaths: string[]
30
+ }
31
+
32
+ export type PipelineStageDebugRunnerResult = {
33
+ outputDir: string
34
+ logsPath: string
35
+ stageArtifacts: PipelineStageArtifact[]
36
+ solved: boolean
37
+ failed: boolean
38
+ error: string | null
39
+ stoppedAfterStage: string | null
40
+ }
41
+
42
+ export class PipelineStageDebugRunner {
43
+ readonly pipelineSolver: SchematicTracePipelineSolver
44
+ readonly outputDir: string
45
+ readonly logsPath: string
46
+ readonly pngWidth: number
47
+ readonly pngHeight: number
48
+ readonly writePng: boolean
49
+ readonly writeSvg: boolean
50
+ readonly writeGraphicsJson: boolean
51
+ readonly writeStepPngs: boolean
52
+ readonly stopAfterStage?: string
53
+ readonly context: Record<string, string | number | boolean | null | undefined>
54
+ readonly onLog?: (line: string) => void
55
+
56
+ private readonly stageArtifacts: PipelineStageArtifact[] = []
57
+ private readonly capturedStageNumbers = new Set<number>()
58
+
59
+ constructor(opts: {
60
+ pipelineSolver: SchematicTracePipelineSolver
61
+ outputDir: string
62
+ pngWidth?: number
63
+ pngHeight?: number
64
+ writePng?: boolean
65
+ writeSvg?: boolean
66
+ writeGraphicsJson?: boolean
67
+ writeStepPngs?: boolean
68
+ stopAfterStage?: string
69
+ context?: Record<string, string | number | boolean | null | undefined>
70
+ onLog?: (line: string) => void
71
+ }) {
72
+ this.pipelineSolver = opts.pipelineSolver
73
+ this.outputDir = opts.outputDir
74
+ this.logsPath = path.join(this.outputDir, "logs.txt")
75
+ this.pngWidth = opts.pngWidth ?? 1536
76
+ this.pngHeight = opts.pngHeight ?? 1536
77
+ this.writePng = opts.writePng ?? true
78
+ this.writeSvg = opts.writeSvg ?? false
79
+ this.writeGraphicsJson = opts.writeGraphicsJson ?? true
80
+ this.writeStepPngs = opts.writeStepPngs ?? false
81
+ this.stopAfterStage = opts.stopAfterStage
82
+ this.context = opts.context ?? {}
83
+ this.onLog = opts.onLog
84
+ }
85
+
86
+ async run(): Promise<PipelineStageDebugRunnerResult> {
87
+ await mkdir(this.outputDir, { recursive: true })
88
+ await writeFile(this.logsPath, "")
89
+
90
+ await this.log(`startedAt=${new Date().toISOString()}`)
91
+ await this.log(`outputDir=${this.toDisplayPath(this.outputDir)}`)
92
+ await this.log(
93
+ `artifacts png=${this.writePng} svg=${this.writeSvg} graphicsJson=${this.writeGraphicsJson} stepPngs=${this.writeStepPngs} pngSize=${this.pngWidth}x${this.pngHeight}`,
94
+ )
95
+ for (const [key, value] of Object.entries(this.context)) {
96
+ await this.log(`${key}=${value ?? ""}`)
97
+ }
98
+
99
+ let stoppedAfterStage: string | null = null
100
+ let thrownError: unknown = null
101
+
102
+ await this.log(
103
+ this.getStageEnterLogLine(this.pipelineSolver.getCurrentPhase(), 0),
104
+ )
105
+
106
+ while (!this.pipelineSolver.solved && !this.pipelineSolver.failed) {
107
+ const previousStageNumber = this.pipelineSolver.currentPipelineStepIndex
108
+ const previousStageName = this.pipelineSolver.getCurrentPhase()
109
+
110
+ try {
111
+ this.pipelineSolver.step()
112
+ } catch (error) {
113
+ thrownError = error
114
+ }
115
+
116
+ const currentStageNumber = this.pipelineSolver.currentPipelineStepIndex
117
+ const currentStageName = this.pipelineSolver.getCurrentPhase()
118
+
119
+ if (
120
+ currentStageNumber !== previousStageNumber &&
121
+ previousStageName !== "none"
122
+ ) {
123
+ await this.captureStage(previousStageNumber)
124
+ if (
125
+ previousStageName === this.stopAfterStage ||
126
+ String(previousStageNumber + 1) === this.stopAfterStage
127
+ ) {
128
+ stoppedAfterStage = previousStageName
129
+ break
130
+ }
131
+ }
132
+
133
+ if (
134
+ currentStageNumber !== previousStageNumber &&
135
+ currentStageName !== "none"
136
+ ) {
137
+ await this.log(
138
+ this.getStageEnterLogLine(currentStageName, currentStageNumber),
139
+ )
140
+ }
141
+
142
+ if (thrownError) break
143
+ }
144
+
145
+ if (this.pipelineSolver.failed) {
146
+ await this.captureActiveStage()
147
+ }
148
+
149
+ if (thrownError) {
150
+ await this.log(`thrownError=${this.formatError(thrownError)}`)
151
+ }
152
+
153
+ const status = stoppedAfterStage
154
+ ? "stopped"
155
+ : this.pipelineSolver.solved
156
+ ? "solved"
157
+ : this.pipelineSolver.failed
158
+ ? "failed"
159
+ : "incomplete"
160
+
161
+ await this.log(
162
+ `completed status=${status} iterations=${this.pipelineSolver.iterations} error=${this.pipelineSolver.error ?? ""}`,
163
+ )
164
+
165
+ return {
166
+ outputDir: this.outputDir,
167
+ logsPath: this.logsPath,
168
+ stageArtifacts: [...this.stageArtifacts],
169
+ solved: this.pipelineSolver.solved,
170
+ failed: this.pipelineSolver.failed,
171
+ error:
172
+ this.pipelineSolver.error ??
173
+ (thrownError ? this.formatError(thrownError) : null),
174
+ stoppedAfterStage,
175
+ }
176
+ }
177
+
178
+ private async captureActiveStage() {
179
+ const stageNumber = this.pipelineSolver.currentPipelineStepIndex
180
+ if (this.pipelineSolver.getCurrentPhase() === "none") return
181
+ if (this.capturedStageNumbers.has(stageNumber)) return
182
+
183
+ await this.captureStage(stageNumber, this.pipelineSolver.activeSubSolver)
184
+ }
185
+
186
+ private async captureStage(stageNumber: number, solver?: BaseSolver | null) {
187
+ if (this.capturedStageNumbers.has(stageNumber)) return
188
+
189
+ const stageDef = this.getStageDef(stageNumber)
190
+ if (!stageDef) return
191
+
192
+ const stageSolver = solver ?? this.getStageSolver(stageDef.solverName)
193
+ if (!stageSolver) {
194
+ throw new Error(
195
+ `Unable to resolve solver for stage ${stageNumber + 1} "${stageDef.solverName}"`,
196
+ )
197
+ }
198
+
199
+ const basePath = path.join(
200
+ this.outputDir,
201
+ `stage${String(stageNumber + 1).padStart(2, "0")}-${this.getSafeStageName(stageDef.solverName)}`,
202
+ )
203
+ const pngPath = this.writePng ? `${basePath}.png` : undefined
204
+ const svgPath = this.writeSvg ? `${basePath}.svg` : undefined
205
+ const graphicsJsonPath = this.writeGraphicsJson
206
+ ? `${basePath}.graphics.json`
207
+ : undefined
208
+ const graphics = stageSolver.visualize()
209
+ const svg = getSvgFromGraphicsObject(graphics, { backgroundColor: "white" })
210
+
211
+ if (pngPath) {
212
+ await writeFile(pngPath, await this.renderGraphicsToPng(graphics))
213
+ }
214
+
215
+ const stepPngPaths =
216
+ this.writeStepPngs && this.writePng
217
+ ? await this.writeStepPngsForGraphics(basePath, graphics)
218
+ : []
219
+
220
+ if (svgPath) {
221
+ await writeFile(svgPath, svg)
222
+ }
223
+
224
+ if (graphicsJsonPath) {
225
+ await writeFile(graphicsJsonPath, JSON.stringify(graphics, null, 2))
226
+ }
227
+
228
+ this.capturedStageNumbers.add(stageNumber)
229
+ const artifact = {
230
+ stageName: stageDef.solverName,
231
+ stageNumber: stageNumber + 1,
232
+ pngPath,
233
+ svgPath,
234
+ graphicsJsonPath,
235
+ stepPngPaths,
236
+ } satisfies PipelineStageArtifact
237
+ this.stageArtifacts.push(artifact)
238
+
239
+ await this.log(
240
+ `captured stage=${artifact.stageNumber} name=${artifact.stageName} iterations=${stageSolver.iterations ?? 0} png=${pngPath ? this.toDisplayPath(pngPath) : ""} svg=${svgPath ? this.toDisplayPath(svgPath) : ""} steps=${stepPngPaths.length}`,
241
+ )
242
+ }
243
+
244
+ private async writeStepPngsForGraphics(
245
+ basePath: string,
246
+ graphics: GraphicsObject,
247
+ ): Promise<string[]> {
248
+ const stepPngPaths: string[] = []
249
+
250
+ for (const step of this.getObjectSteps(graphics)) {
251
+ const stepPngPath = `${basePath}.step-${step}.png`
252
+ await writeFile(
253
+ stepPngPath,
254
+ await this.renderGraphicsToPng(
255
+ this.filterGraphicsByStep(graphics, step),
256
+ ),
257
+ )
258
+ stepPngPaths.push(stepPngPath)
259
+ }
260
+
261
+ return stepPngPaths
262
+ }
263
+
264
+ private renderGraphicsToPng(graphics: GraphicsObject): Promise<Uint8Array> {
265
+ return getPngBufferFromGraphicsObject(graphics, {
266
+ backgroundColor: "white",
267
+ pngWidth: this.pngWidth,
268
+ pngHeight: this.pngHeight,
269
+ })
270
+ }
271
+
272
+ private getObjectSteps(graphics: GraphicsObject): number[] {
273
+ const steps = new Set<number>()
274
+ for (const objects of this.getGraphicsObjectArrays(graphics)) {
275
+ for (const object of objects) {
276
+ if (typeof object.step === "number") steps.add(object.step)
277
+ }
278
+ }
279
+
280
+ return [...steps].sort((a, b) => a - b)
281
+ }
282
+
283
+ private filterGraphicsByStep(
284
+ graphics: GraphicsObject,
285
+ step: number,
286
+ ): GraphicsObject {
287
+ const filtered: Record<string, unknown[]> = {}
288
+
289
+ for (const key of Object.keys(graphics)) {
290
+ const value = (graphics as Record<string, unknown>)[key]
291
+ filtered[key] = Array.isArray(value)
292
+ ? value.filter((object) => {
293
+ return (
294
+ typeof object === "object" &&
295
+ object !== null &&
296
+ (object as { step?: number }).step === step
297
+ )
298
+ })
299
+ : []
300
+ }
301
+
302
+ return filtered as GraphicsObject
303
+ }
304
+
305
+ private getGraphicsObjectArrays(
306
+ graphics: GraphicsObject,
307
+ ): Array<Array<{ step?: number }>> {
308
+ return Object.values(graphics).filter(Array.isArray) as Array<
309
+ Array<{ step?: number }>
310
+ >
311
+ }
312
+
313
+ private getStageDef(stageNumber: number): PipelineStepLike | undefined {
314
+ return this.pipelineSolver.pipelineDef[stageNumber]
315
+ }
316
+
317
+ private getStageSolver(stageName: string): VisualizingSolver | undefined {
318
+ return (
319
+ this.pipelineSolver as unknown as Record<string, VisualizingSolver>
320
+ )[stageName]
321
+ }
322
+
323
+ private getSafeStageName(stageName: string): string {
324
+ return stageName.replace(/[^a-zA-Z0-9._-]/g, "_")
325
+ }
326
+
327
+ private getStageEnterLogLine(stageName: string, stageNumber: number) {
328
+ return `enter stage=${stageNumber + 1} name=${stageName}`
329
+ }
330
+
331
+ private async log(line: string) {
332
+ await writeFile(this.logsPath, `${line}\n`, { flag: "a" })
333
+ this.onLog?.(line)
334
+ }
335
+
336
+ private toDisplayPath(filePath: string): string {
337
+ return path.relative(process.cwd(), filePath) || "."
338
+ }
339
+
340
+ private formatError(error: unknown) {
341
+ if (error instanceof Error) return `${error.name}: ${error.message}`
342
+ return String(error)
343
+ }
344
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.87",
4
+ "version": "0.0.89",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
8
+ "debug:pipeline": "bun scripts/debug-pipeline-stages.ts",
8
9
  "build": "tsup-node lib/index.ts --format esm --dts",
9
10
  "format": "biome format --write .",
10
11
  "format:check": "biome format ."
@@ -18,7 +19,7 @@
18
19
  "calculate-elbow": "^0.0.12",
19
20
  "connectivity-map": "^1.0.0",
20
21
  "flatbush": "^4.5.0",
21
- "graphics-debug": "^0.0.62",
22
+ "graphics-debug": "^0.0.96",
22
23
  "react": "^19.1.1",
23
24
  "react-cosmos": "^7.0.0",
24
25
  "react-cosmos-plugin-vite": "^7.0.0",