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) +})