@tscircuit/rectdiff 0.0.39 → 0.0.41

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 (67) hide show
  1. package/.github/workflows/bun-test.yml +1 -1
  2. package/benchmark.sh +61 -0
  3. package/global.d.ts +7 -0
  4. package/lib/RectDiffPipeline.ts +0 -27
  5. package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +4 -108
  6. package/package.json +3 -1
  7. package/scripts/benchmark/benchmark-child/constants.ts +6 -0
  8. package/scripts/benchmark/benchmark-child/createSolver.ts +34 -0
  9. package/scripts/benchmark/benchmark-child/getAutorouterPipeline4ModulePath.ts +7 -0
  10. package/scripts/benchmark/benchmark-child/handleWorkerTaskLine.ts +64 -0
  11. package/scripts/benchmark/benchmark-child/importRuntimeModule.ts +8 -0
  12. package/scripts/benchmark/benchmark-child/types.ts +20 -0
  13. package/scripts/benchmark/benchmark-child/writeWorkerResultMessage.ts +10 -0
  14. package/scripts/benchmark/benchmark-types.ts +34 -0
  15. package/scripts/benchmark/benchmark.child.ts +11 -0
  16. package/scripts/benchmark/index.ts +413 -0
  17. package/tests/benchmark-rectdiff-override.test.ts +41 -0
  18. package/tests/benchmark-results.test.ts +30 -0
  19. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +3 -3
  20. package/tests/solver/arduino-uno-inner2-ground-bottom-power/__snapshots__/arduino-uno-inner2-ground-bottom-power.snap.svg +3 -3
  21. package/tests/solver/arduino-uno-inner2-ground-inner1-power/__snapshots__/arduino-uno-inner2-ground-inner1-power.snap.svg +1 -1
  22. package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
  23. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +3 -3
  24. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  25. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  26. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  27. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +3 -3
  28. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +3 -3
  29. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
  30. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
  31. package/tests/solver/bugreport18-1b2d06/__snapshots__/bugreport18-1b2d06.snap.svg +3 -3
  32. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  33. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  34. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +2 -2
  35. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  36. package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +3 -3
  37. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +2 -2
  38. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +3 -3
  39. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  40. package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
  41. package/tests/solver/offboardconnects01/__snapshots__/offboardconnects01.snap.svg +1 -1
  42. package/tests/solver/pcb_trace_id-should-return-root-connection-name/__snapshots__/pcb_trace_id-should-return-root-connection-name.snap.svg +3 -3
  43. package/tests/solver/repros/merge-single-layer-node/__snapshots__/merge-single-layer-node.snap.svg +3 -3
  44. package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
  45. package/lib/math/layers/getUnionZ.ts +0 -6
  46. package/lib/math/layers/getZLayerName.ts +0 -6
  47. package/lib/math/layers/getZSpanMask.ts +0 -6
  48. package/lib/math/layers/hasContiguousZSpan.ts +0 -11
  49. package/lib/math/rects/intersectRects.ts +0 -28
  50. package/lib/math/rects/mergeRects.ts +0 -12
  51. package/lib/math/rects/rectArea.ts +0 -7
  52. package/lib/math/rects/rectContainsRect.ts +0 -18
  53. package/lib/math/rects/rectsTouchOrOverlap.ts +0 -12
  54. package/lib/math/rects/subtractRects.ts +0 -23
  55. package/lib/solvers/SparseMultilayerPromotionSolver/SparseMultilayerPromotionSolver.ts +0 -134
  56. package/lib/solvers/SparseMultilayerPromotionSolver/cloneNode.ts +0 -15
  57. package/lib/solvers/SparseMultilayerPromotionSolver/cloneNodeWithRect.ts +0 -34
  58. package/lib/solvers/SparseMultilayerPromotionSolver/createResidualNodes.ts +0 -42
  59. package/lib/solvers/SparseMultilayerPromotionSolver/findBestCoalesceCandidate.ts +0 -98
  60. package/lib/solvers/SparseMultilayerPromotionSolver/findBestPromotionCandidate.ts +0 -72
  61. package/lib/solvers/SparseMultilayerPromotionSolver/getUsableMultilayerVolumeShare.ts +0 -34
  62. package/lib/solvers/SparseMultilayerPromotionSolver/isFreeNode.ts +0 -8
  63. package/lib/solvers/SparseMultilayerPromotionSolver/nodeToRect.ts +0 -13
  64. package/lib/solvers/SparseMultilayerPromotionSolver/solvers/CoalesceMultilayerTilesSolver.ts +0 -104
  65. package/lib/solvers/SparseMultilayerPromotionSolver/solvers/PromoteSparseMultilayerCoverageSolver.ts +0 -148
  66. package/lib/solvers/SparseMultilayerPromotionSolver/solvers/TrimContainedSingleLayerCoverageSolver.ts +0 -137
  67. package/lib/solvers/SparseMultilayerPromotionSolver/types.ts +0 -23
@@ -28,4 +28,4 @@ jobs:
28
28
  run: bun install
29
29
 
30
30
  - name: Run tests
31
- run: bun test --timeout 999999
31
+ run: bun test --timeout 9999
package/benchmark.sh ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ LIMIT=""
5
+
6
+ default_concurrency() {
7
+ getconf _NPROCESSORS_ONLN 2>/dev/null || nproc 2>/dev/null || echo 4
8
+ }
9
+
10
+ CONCURRENT="${BENCHMARK_CONCURRENCY:-$(default_concurrency)}"
11
+
12
+ print_help() {
13
+ cat <<'EOF'
14
+ Usage:
15
+ ./benchmark.sh [--concurrent N] [--limit N]
16
+
17
+ Options:
18
+ --concurrent N Number of Bun worker processes to run, or "auto"
19
+ --limit N Run only the first N dataset scenarios
20
+ -h, --help Show this help
21
+
22
+ Examples:
23
+ ./benchmark.sh
24
+ ./benchmark.sh --concurrent 8
25
+ ./benchmark.sh --limit 25
26
+ ./benchmark.sh --concurrent auto --limit 50
27
+ EOF
28
+ }
29
+
30
+ while [ "$#" -gt 0 ]; do
31
+ case "$1" in
32
+ -h|--help)
33
+ print_help
34
+ exit 0
35
+ ;;
36
+ --concurrent)
37
+ CONCURRENT="${2:-}"
38
+ if [ "$CONCURRENT" = "auto" ]; then
39
+ CONCURRENT="$(default_concurrency)"
40
+ fi
41
+ shift 2
42
+ ;;
43
+ --limit)
44
+ LIMIT="${2:-}"
45
+ shift 2
46
+ ;;
47
+ *)
48
+ echo "Unknown argument: $1"
49
+ echo "Run ./benchmark.sh --help for usage"
50
+ exit 1
51
+ ;;
52
+ esac
53
+ done
54
+
55
+ CMD=(bun "scripts/benchmark/index.ts" "--concurrent" "$CONCURRENT")
56
+
57
+ if [ -n "$LIMIT" ]; then
58
+ CMD+=("--limit" "$LIMIT")
59
+ fi
60
+
61
+ "${CMD[@]}"
package/global.d.ts CHANGED
@@ -2,3 +2,10 @@ declare module "*.json" {
2
2
  const value: any
3
3
  export default value
4
4
  }
5
+
6
+ declare module "@tscircuit/autorouting-dataset-01" {
7
+ import type { SimpleRouteJson } from "./lib/types/srj-types"
8
+
9
+ const dataset: Record<string, SimpleRouteJson>
10
+ export = dataset
11
+ }
@@ -16,20 +16,17 @@ import { computeInverseRects } from "./solvers/RectDiffSeedingSolver/computeInve
16
16
  import { buildZIndexMap } from "./solvers/RectDiffSeedingSolver/layers"
17
17
  import { buildObstacleClearanceGraphics } from "./utils/renderObstacleClearance"
18
18
  import { mergeGraphics } from "graphics-debug"
19
- import { SparseMultilayerPromotionSolver } from "./solvers/SparseMultilayerPromotionSolver/SparseMultilayerPromotionSolver"
20
19
 
21
20
  export interface RectDiffPipelineInput {
22
21
  simpleRouteJson: SimpleRouteJson
23
22
  gridOptions?: Partial<GridFill3DOptions>
24
23
  obstacleClearance?: number
25
- sparseMultilayerPromotionTargetShare?: number
26
24
  }
27
25
 
28
26
  export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
29
27
  rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline
30
28
  gapFillSolver?: GapFillSolverPipeline
31
29
  outerLayerContainmentMergeSolver?: OuterLayerContainmentMergeSolver
32
- sparseMultilayerPromotionSolver?: SparseMultilayerPromotionSolver
33
30
  boardVoidRects: XYRect[] | undefined
34
31
  zIndexByName?: Map<string, number>
35
32
  layerNames?: string[]
@@ -90,25 +87,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
90
87
  },
91
88
  ],
92
89
  ),
93
- definePipelineStep(
94
- "sparseMultilayerPromotionSolver",
95
- SparseMultilayerPromotionSolver,
96
- (rectDiffPipeline: RectDiffPipeline) => [
97
- {
98
- meshNodes:
99
- rectDiffPipeline.outerLayerContainmentMergeSolver?.getOutput()
100
- .outputNodes ??
101
- rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ??
102
- rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput()
103
- .meshNodes ??
104
- [],
105
- promotionTargetShare:
106
- rectDiffPipeline.inputProblem
107
- .sparseMultilayerPromotionTargetShare ?? 0.86,
108
- simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
109
- },
110
- ],
111
- ),
112
90
  ]
113
91
 
114
92
  override _setup(): void {
@@ -142,11 +120,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
142
120
  override getOutput(): { meshNodes: CapacityMeshNode[] } {
143
121
  const outerLayerMergeOutput =
144
122
  this.outerLayerContainmentMergeSolver?.getOutput()
145
- const sparseMultilayerOutput =
146
- this.sparseMultilayerPromotionSolver?.getOutput()
147
- if (sparseMultilayerOutput) {
148
- return { meshNodes: sparseMultilayerOutput.outputNodes }
149
- }
150
123
  if (outerLayerMergeOutput) {
151
124
  return { meshNodes: outerLayerMergeOutput.outputNodes }
152
125
  }
@@ -29,22 +29,6 @@ const nodeToRect = (node: CapacityMeshNode): XYRect => ({
29
29
 
30
30
  const rectArea = (rect: XYRect) => rect.width * rect.height
31
31
 
32
- const intersectRects = (a: XYRect, b: XYRect): XYRect | null => {
33
- const x0 = Math.max(a.x, b.x)
34
- const y0 = Math.max(a.y, b.y)
35
- const x1 = Math.min(a.x + a.width, b.x + b.width)
36
- const y1 = Math.min(a.y + a.height, b.y + b.height)
37
-
38
- if (x1 <= x0 + EPS || y1 <= y0 + EPS) return null
39
-
40
- return {
41
- x: x0,
42
- y: y0,
43
- width: x1 - x0,
44
- height: y1 - y0,
45
- }
46
- }
47
-
48
32
  const cloneNode = (node: CapacityMeshNode): CapacityMeshNode => ({
49
33
  ...node,
50
34
  center: { ...node.center },
@@ -103,7 +87,6 @@ const isFullyCoveredByRects = (target: XYRect, coveringRects: XYRect[]) => {
103
87
  export class OuterLayerContainmentMergeSolver extends BaseSolver {
104
88
  private outputNodes: CapacityMeshNode[] = []
105
89
  private promotedNodeIds = new Set<string>()
106
- private fullyPromotedNodeIds = new Set<string>()
107
90
  private residualNodeIds = new Set<string>()
108
91
 
109
92
  constructor(private input: OuterLayerContainmentMergeSolverInput) {
@@ -113,7 +96,6 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
113
96
  override _setup() {
114
97
  this.outputNodes = this.input.meshNodes.map(cloneNode)
115
98
  this.promotedNodeIds.clear()
116
- this.fullyPromotedNodeIds.clear()
117
99
  this.residualNodeIds.clear()
118
100
  }
119
101
 
@@ -134,8 +116,6 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
134
116
  const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0)
135
117
  const originalNodes = this.input.meshNodes.map(cloneNode)
136
118
  const obstaclesByLayer = this.buildObstaclesByLayer(layerCount)
137
- const shouldAllowPartialPromotion =
138
- this.getUsableMultilayerVolumeShare(originalNodes) < 0.5
139
119
  const mutableOuterNodes = originalNodes.filter(
140
120
  (node) =>
141
121
  isFreeNode(node) &&
@@ -186,51 +166,23 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
186
166
  continue
187
167
  }
188
168
  if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
189
- if (!shouldAllowPartialPromotion) {
190
- continue
191
- }
192
-
193
- const partialPromotionRects = this.getSupportedPromotionRects({
194
- candidateRect,
195
- supportRects: oppositeSupportRects,
196
- minRectSize: viaMinSize,
197
- })
198
-
199
- if (partialPromotionRects.length === 0) {
200
- continue
201
- }
202
-
203
- for (const partialRect of partialPromotionRects) {
204
- const promotedNode = cloneNodeWithRect(
205
- candidate,
206
- partialRect,
207
- `${candidate.capacityMeshNodeId}-outer-partial-${promotedNodes.length}`,
208
- )
209
- promotedNode.availableZ = [topZ, bottomZ]
210
- promotedNode.layer = `z${topZ},${bottomZ}`
211
- promotedNodes.push(promotedNode)
212
- promotedRects.push(partialRect)
213
- this.promotedNodeIds.add(promotedNode.capacityMeshNodeId)
214
- }
215
169
  continue
216
170
  }
217
171
 
218
- const promotedNode = {
172
+ promotedNodes.push({
219
173
  ...candidate,
220
174
  availableZ: [topZ, bottomZ],
221
175
  layer: `z${topZ},${bottomZ}`,
222
- }
223
- promotedNodes.push(promotedNode)
176
+ })
224
177
  promotedRects.push(candidateRect)
225
- this.promotedNodeIds.add(promotedNode.capacityMeshNodeId)
226
- this.fullyPromotedNodeIds.add(candidate.capacityMeshNodeId)
178
+ this.promotedNodeIds.add(candidate.capacityMeshNodeId)
227
179
  }
228
180
 
229
181
  let nextResidualId = 0
230
182
  const residualNodes: CapacityMeshNode[] = []
231
183
 
232
184
  for (const node of mutableOuterNodes) {
233
- if (this.fullyPromotedNodeIds.has(node.capacityMeshNodeId)) {
185
+ if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
234
186
  continue
235
187
  }
236
188
 
@@ -259,62 +211,6 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
259
211
  return [...immutableNodes, ...promotedNodes, ...residualNodes]
260
212
  }
261
213
 
262
- private getUsableMultilayerVolumeShare(nodes: CapacityMeshNode[]) {
263
- let totalVolume = 0
264
- let obstacleVolume = 0
265
- let multilayerVolume = 0
266
-
267
- for (const node of nodes) {
268
- const volume = node.width * node.height * node.availableZ.length
269
- totalVolume += volume
270
- if (node._containsObstacle) {
271
- obstacleVolume += volume
272
- continue
273
- }
274
- if (node.availableZ.length > 1) {
275
- multilayerVolume += volume
276
- }
277
- }
278
-
279
- const usableVolume = totalVolume - obstacleVolume
280
- if (usableVolume <= EPS) return 0
281
- return multilayerVolume / usableVolume
282
- }
283
-
284
- private getSupportedPromotionRects(params: {
285
- candidateRect: XYRect
286
- supportRects: XYRect[]
287
- minRectSize: number
288
- }) {
289
- const { candidateRect, supportRects, minRectSize } = params
290
- const promotedPieces: XYRect[] = []
291
-
292
- for (const supportRect of supportRects) {
293
- const overlapRect = intersectRects(candidateRect, supportRect)
294
- if (!overlapRect) continue
295
-
296
- let remainingPieces = [overlapRect]
297
- for (const existingPiece of promotedPieces) {
298
- remainingPieces = remainingPieces.flatMap((piece) =>
299
- subtractRect2D(piece, existingPiece),
300
- )
301
- if (remainingPieces.length === 0) break
302
- }
303
-
304
- for (const piece of remainingPieces) {
305
- if (
306
- piece.width + EPS < minRectSize ||
307
- piece.height + EPS < minRectSize
308
- ) {
309
- continue
310
- }
311
- promotedPieces.push(piece)
312
- }
313
- }
314
-
315
- return promotedPieces
316
- }
317
-
318
214
  private buildObstaclesByLayer(layerCount: number): ObstacleWithRect[][] {
319
215
  const out = Array.from(
320
216
  { length: layerCount },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "type": "module",
5
5
  "main": "lib/index.ts",
6
6
  "types": "lib/index.ts",
@@ -18,6 +18,8 @@
18
18
  "devDependencies": {
19
19
  "@biomejs/biome": "^2.3.5",
20
20
  "@react-hook/resize-observer": "^2.0.2",
21
+ "@tscircuit/autorouting-dataset-01": "^1.0.32",
22
+ "@tscircuit/capacity-autorouter": "https://npm-releases.tscircuit.com/npm-tarballs/25314012755-1/tscircuit-capacity-autorouter-0.0.489.tgz",
21
23
  "@tscircuit/math-utils": "^0.0.29",
22
24
  "@tscircuit/solver-utils": "^0.0.16",
23
25
  "@types/bun": "latest",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Marks the first phase after port-point pathing completes.
3
+ * The benchmark uses this as the stop condition for partial solver runs.
4
+ */
5
+ export const NEXT_PHASE_AFTER_PORT_POINT_PATHING =
6
+ "uniformPortDistributionSolver"
@@ -0,0 +1,34 @@
1
+ import { RectDiffPipeline } from "../../../lib/RectDiffPipeline"
2
+ import type { WorkerTaskMessage } from "../benchmark-types"
3
+ import { getAutorouterPipeline4ModulePath } from "./getAutorouterPipeline4ModulePath"
4
+ import { importRuntimeModule } from "./importRuntimeModule"
5
+ import type { Pipeline4Constructor } from "./types"
6
+
7
+ /**
8
+ * Creates the benchmark solver with the repo-local RectDiff pipeline override.
9
+ * The benchmark imports Pipeline 4 from the published autorouter bundle while
10
+ * forcing it to instantiate this repo's RectDiff implementation by default.
11
+ */
12
+ export const createSolver = async (
13
+ scenario: WorkerTaskMessage["task"]["scenario"],
14
+ RectDiffPipelineClass: typeof RectDiffPipeline = RectDiffPipeline,
15
+ ) => {
16
+ // Load Pipeline 4 from the installed capacity-autorouter dist bundle, then
17
+ // inject the RectDiff class explicitly through its runtime override hook.
18
+ const solverModule = await importRuntimeModule(
19
+ getAutorouterPipeline4ModulePath(),
20
+ )
21
+
22
+ // TODO: Replace this cast once capacity-autorouter exposes a typed
23
+ // Pipeline 4 override interface. As of this repo state, the installed
24
+ // Pipeline 4 constructor only exposes CapacityMeshSolverOptions and does
25
+ // not type the benchmark's overrides.RectDiffPipelineClass injection.
26
+ const AutoroutingPipelineSolver4 = (solverModule as any)
27
+ .AutoroutingPipelineSolver4 as Pipeline4Constructor
28
+
29
+ return new AutoroutingPipelineSolver4(scenario, {
30
+ overrides: {
31
+ RectDiffPipelineClass,
32
+ },
33
+ })
34
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolves the published capacity-autorouter dist entry for benchmarking.
3
+ * The benchmark relies on the bundled runtime export instead of package-
4
+ * internal source paths.
5
+ */
6
+ export const getAutorouterPipeline4ModulePath = () =>
7
+ import.meta.resolve("@tscircuit/capacity-autorouter")
@@ -0,0 +1,64 @@
1
+ import type { WorkerResult, WorkerTaskMessage } from "../benchmark-types"
2
+ import { NEXT_PHASE_AFTER_PORT_POINT_PATHING } from "./constants"
3
+ import { createSolver } from "./createSolver"
4
+ import { writeWorkerResultMessage } from "./writeWorkerResultMessage"
5
+
6
+ /**
7
+ * Handles one benchmark request line from the parent process.
8
+ * Each line must contain a single JSON-encoded worker task message.
9
+ */
10
+ export const handleWorkerTaskLine = async (line: string) => {
11
+ if (!line.trim()) {
12
+ return
13
+ }
14
+
15
+ let message: WorkerTaskMessage
16
+ try {
17
+ message = JSON.parse(line) as WorkerTaskMessage
18
+ } catch (error) {
19
+ const result: WorkerResult = {
20
+ scenarioName: "unknown",
21
+ elapsedTimeMs: 0,
22
+ didSolve: false,
23
+ didTimeout: false,
24
+ error: `Invalid worker message: ${String(error)}`,
25
+ }
26
+ writeWorkerResultMessage(result)
27
+ return
28
+ }
29
+
30
+ const startedAt = performance.now()
31
+
32
+ try {
33
+ const solver = await createSolver(message.task.scenario)
34
+
35
+ while (
36
+ !solver.failed &&
37
+ solver.getCurrentPhase() !== NEXT_PHASE_AFTER_PORT_POINT_PATHING
38
+ ) {
39
+ solver.step()
40
+ }
41
+
42
+ const result: WorkerResult = {
43
+ scenarioName: message.task.scenarioName,
44
+ elapsedTimeMs: Math.max(0, Math.round(performance.now() - startedAt)),
45
+ didSolve:
46
+ !solver.failed &&
47
+ solver.getCurrentPhase() === NEXT_PHASE_AFTER_PORT_POINT_PATHING &&
48
+ solver.portPointPathingSolver !== undefined,
49
+ didTimeout: false,
50
+ error: solver.failed ? solver.error : null,
51
+ }
52
+
53
+ writeWorkerResultMessage(result)
54
+ } catch (error) {
55
+ const result: WorkerResult = {
56
+ scenarioName: message.task.scenarioName,
57
+ elapsedTimeMs: Math.max(0, Math.round(performance.now() - startedAt)),
58
+ didSolve: false,
59
+ didTimeout: false,
60
+ error: String(error),
61
+ }
62
+ writeWorkerResultMessage(result)
63
+ }
64
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Imports a runtime-resolved module path.
3
+ * This keeps the benchmark loader on a runtime-only import path.
4
+ */
5
+ export const importRuntimeModule = (modulePath: string) =>
6
+ new Function("modulePath", "return import(modulePath)")(
7
+ modulePath,
8
+ ) as Promise<unknown>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Describes the subset of solver state the benchmark worker needs.
3
+ * This keeps the child process decoupled from internal package typings.
4
+ */
5
+ export type BenchmarkWorkerSolver = {
6
+ failed: boolean
7
+ error: string | null
8
+ portPointPathingSolver?: unknown
9
+ getCurrentPhase(): string
10
+ step(): void
11
+ }
12
+
13
+ /**
14
+ * Represents the Pipeline 4 constructor shape used by the benchmark.
15
+ * It allows the worker to inject the local RectDiff override.
16
+ */
17
+ export type Pipeline4Constructor = new (
18
+ srj: unknown,
19
+ opts?: Record<string, unknown>,
20
+ ) => BenchmarkWorkerSolver
@@ -0,0 +1,10 @@
1
+ import type { WorkerResult, WorkerResultMessage } from "../benchmark-types"
2
+
3
+ /**
4
+ * Writes a worker result message as a single JSON line.
5
+ * The parent benchmark process reads these messages over stdout.
6
+ */
7
+ export const writeWorkerResultMessage = (result: WorkerResult) => {
8
+ const response: WorkerResultMessage = { result }
9
+ process.stdout.write(`${JSON.stringify(response)}\n`)
10
+ }
@@ -0,0 +1,34 @@
1
+ import type { SimpleRouteJson } from "../../lib/types/srj-types"
2
+
3
+ /**
4
+ * Defines one benchmark scenario loaded from the dataset package.
5
+ */
6
+ export type BenchmarkTask = {
7
+ scenarioName: string
8
+ scenario: SimpleRouteJson
9
+ }
10
+
11
+ /**
12
+ * Wraps a benchmark task for line-delimited worker communication.
13
+ */
14
+ export type WorkerTaskMessage = {
15
+ task: BenchmarkTask
16
+ }
17
+
18
+ /**
19
+ * Captures the outcome of a single benchmark worker run.
20
+ */
21
+ export type WorkerResult = {
22
+ scenarioName: string
23
+ elapsedTimeMs: number
24
+ didSolve: boolean
25
+ didTimeout: boolean
26
+ error: string | null
27
+ }
28
+
29
+ /**
30
+ * Wraps a worker result for line-delimited stdout transport.
31
+ */
32
+ export type WorkerResultMessage = {
33
+ result: WorkerResult
34
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import * as readline from "node:readline"
4
+ import { handleWorkerTaskLine } from "./benchmark-child/handleWorkerTaskLine"
5
+
6
+ const reader = readline.createInterface({
7
+ input: process.stdin,
8
+ crlfDelay: Infinity,
9
+ })
10
+
11
+ reader.on("line", handleWorkerTaskLine)