diff --git a/algos/multi-layer-ijump/MultilayerIjump.ts b/algos/multi-layer-ijump/MultilayerIjump.ts
index 751c3e6..1e429ef 100644
--- a/algos/multi-layer-ijump/MultilayerIjump.ts
+++ b/algos/multi-layer-ijump/MultilayerIjump.ts
@@ -489,7 +489,11 @@ export class MultilayerIjump extends GeneralizedAstarAutorouter {
})
}
}
- if (travelDir.wallDistance === Infinity) {
+ if (
+ travelDir.wallDistance === Infinity &&
+ isGoalInTravelDir &&
+ goalDistAlongTravelDir > 0
+ ) {
travelDirs3.push({
...travelDir,
travelDistance: goalDistAlongTravelDir,
diff --git a/algos/multi-layer-ijump/tests/repros/issue92-no-mirrored-goal-jump.test.tsx b/algos/multi-layer-ijump/tests/repros/issue92-no-mirrored-goal-jump.test.tsx
new file mode 100644
index 0000000..ee01954
--- /dev/null
+++ b/algos/multi-layer-ijump/tests/repros/issue92-no-mirrored-goal-jump.test.tsx
@@ -0,0 +1,89 @@
+import { Circuit } from "@tscircuit/core"
+import { expect, test } from "bun:test"
+import { getSimpleRouteJson } from "solver-utils"
+import { MultilayerIjump } from "../../MultilayerIjump"
+
+const Pad = (props: {
+ name: string
+ pcbX: number
+ pcbY: number
+ width?: string
+ height?: string
+}) => (
+
+
+
+
+
+)
+
+class InspectingMultilayerIjump extends MultilayerIjump {
+ mirroredGoalJumps: Array<{
+ from: { x: number; y: number }
+ to: { x: number; y: number }
+ }> = []
+
+ override getNeighbors(node: any) {
+ const neighbors = super.getNeighbors(node)
+ const goal = this.goalPoint as any
+ const epsilon = 1e-6
+
+ for (const neighbor of neighbors) {
+ const dx = neighbor.x - node.x
+ const dy = neighbor.y - node.y
+ const goalDx = goal.x - node.x
+ const goalDy = goal.y - node.y
+
+ const mirroredHorizontalJump =
+ Math.abs(dy) < epsilon &&
+ Math.abs(dx) > epsilon &&
+ Math.sign(dx) === -Math.sign(goalDx) &&
+ Math.abs(Math.abs(dx) - Math.abs(goalDx)) < epsilon
+
+ const mirroredVerticalJump =
+ Math.abs(dx) < epsilon &&
+ Math.abs(dy) > epsilon &&
+ Math.sign(dy) === -Math.sign(goalDy) &&
+ Math.abs(Math.abs(dy) - Math.abs(goalDy)) < epsilon
+
+ if (mirroredHorizontalJump || mirroredVerticalJump) {
+ this.mirroredGoalJumps.push({
+ from: { x: node.x, y: node.y },
+ to: { x: neighbor.x, y: neighbor.y },
+ })
+ }
+ }
+
+ return neighbors
+ }
+}
+
+test("issue 92: does not jump to the mirrored goal coordinate", () => {
+ const circuit = new Circuit()
+
+ circuit.add(
+
+
+
+
+
+ ,
+ )
+
+ const input = getSimpleRouteJson(circuit.getCircuitJson(), { layerCount: 2 })
+ const autorouter = new InspectingMultilayerIjump({ input, debug: true })
+ autorouter.allowLayerChange = false
+
+ const solution = autorouter.solveAndMapToTraces()
+
+ expect(solution).toHaveLength(1)
+ expect(autorouter.mirroredGoalJumps).toEqual([])
+})
diff --git a/algos/multi-layer-ijump/tests/repros/issue92-wild-trace-jump.test.tsx b/algos/multi-layer-ijump/tests/repros/issue92-wild-trace-jump.test.tsx
new file mode 100644
index 0000000..0c36961
--- /dev/null
+++ b/algos/multi-layer-ijump/tests/repros/issue92-wild-trace-jump.test.tsx
@@ -0,0 +1,56 @@
+import { Circuit } from "@tscircuit/core"
+import { expect, test } from "bun:test"
+import { getSimpleRouteJson } from "solver-utils"
+import { getDebugSvg } from "../../../infinite-grid-ijump-astar/tests/fixtures/get-debug-svg"
+import { MultilayerIjump } from "../../MultilayerIjump"
+
+const Pad = (props: {
+ name: string
+ pcbX: number
+ pcbY: number
+ width?: string
+ height?: string
+}) => (
+
+
+
+
+
+)
+
+test("issue 92: off-axis goal does not need a wild mirrored jump", () => {
+ const circuit = new Circuit()
+
+ circuit.add(
+
+
+
+
+
+ ,
+ )
+
+ const inputCircuitJson = circuit.getCircuitJson()
+ const input = getSimpleRouteJson(inputCircuitJson, { layerCount: 2 })
+ const autorouter = new MultilayerIjump({ input, debug: true })
+
+ // Keep this reproduction planar so the intersection-jump behavior is
+ // visible without a via providing an alternate escape route.
+ autorouter.allowLayerChange = false
+
+ const solution = autorouter.solveAndMapToTraces()
+
+ expect(
+ getDebugSvg({ inputCircuitJson, autorouter, solution }),
+ ).toMatchSvgSnapshot(import.meta.path)
+
+ expect(solution).toHaveLength(1)
+})