tscircuit / tscircuit/core

MultilayerIjump (infgrid-ijump-astar 0.0.35) emits jumps mirrored away from the goal; fix + repro on a fork since tscircuit/autorouting is archived

Open
#3,927 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
58
Forks
203
Avg merge
7h 39m
Merged PRs (30d)
286

Description

Where this bites in core

Trace_doInitialSchematicTraceRender.ts and Trace_doInitialPcbTraceRender.ts import MultilayerIjump from @tscircuit/infgrid-ijump-astar (^0.0.35). That package was last published from tscircuit/autorouting, which was archived on 2025-08-15 with tscircuit/autorouting#92 ("Multilayer Ijump: Remove wild trace jumps") still open. The repo is read-only, so the fix can't be PR'd or /attempted there; I'm filing here because core is the live consumer. Fix branch (1 commit, 3 files): https://github.com/tscircuit/autorouting/compare/main...twzrd-sol:autorouting:fix/multilayer-ijump-wild-jumps

Mechanism

In MultilayerIjump.getNeighbors, when a node has just hit a wall (node.obstacleHit set) and a perpendicular direction is open, this branch runs:

if (travelDir.wallDistance === Infinity) {
  travelDirs3.push({ ...travelDir, travelDistance: goalDistAlongTravelDir, ... })
}

distAlongDir is an absolute value and, unlike the goal-axis branch above it, this push has no isGoalInTravelDir guard. The earlier branch already handles the goal being ahead along that direction, so by the time this runs the goal is behind (or level): the neighbor lands at node + dir * |goal - node|, mirrored AWAY from the goal by the full goal distance. Whenever the nearby overcome neighbors dead-end, A* expands that far node and the final trace contains the leap. The fix removes the push and keeps the !== Infinity guard on the wall-approach tiers that followed it (10 lines).

Repro (hand-built SimpleRouteJson, in the branch as a snapshot test)

Start pad at (0,0), goal at (8,-8), 2 layers, OBSTACLE_MARGIN default:

  • W: wall, x in [-2, 38], y in [-1.1, -0.9], both layers
  • F: block, x in [-6, -1], y in [-6, -1.3], both layers
  • V: seals the right side, x = 11, y in [-10.5, 2.5], both layers

Before: (0,0) -> (0,-0.75) -> (-8,-0.75) -> (-8,-8) -> (8,-8) — a leap of exactly |8 - 0| = 8 to the left, overshooting F's face by 2mm and W's end by 6mm, then 16mm back. Move the goal to x=20 and the router instead hugs F, so route quality depended on where the goal happened to sit on the other axis.
After: (0,0) -> (0,-0.75) -> (-3,-0.75) -> (-3,-1.15) -> (-7,-1.15) -> (-7,-8) -> (8,-8) (hugs F at the wide-margin berth).

A second test records every neighbor getNeighbors emits and asserts none lands more than largestMargin + OBSTACLE_MARGIN outside the problem bounds; before the fix it caught e.g. (-3.00,-1.15) -> (-14.00,-1.15) and (8.00,0.00) -> (8.00,8.00).

Verification on the repo's dataset generators

single-trace-multilayer x60, single-trace x60, traces x40, distant-single-trace x30, keyboards x12 (2 layers). A neighbor/route point counts as "wild" when it is more than 1.15 outside the problem bounds:

before after
cases with wild neighbors 118 11
wild neighbors / points 7121 2193
final routes leaving bounds 55 15
total route length 12057.2 11934.6
A* iterations 7395 7099
unsolved connections 29 32

The +3 unsolved are all in traces#5 (43 connections in ~5.6x15mm); the three the old code "solved" did so via routes that left the board. keyboards #8/#9/#10 all got shorter. All existing multilayer snapshot tests are byte-identical, and the full suite passes (29/29).

Two related changes I measured and deliberately left out
  • Adding the 2D routers' final isObstacleAt neighbor filter to MultilayerIjump: ObstacleList3d.isObstacleAt defaults its margin to GRID_STEP (0.1), so with OBSTACLE_MARGIN <= 0.1 it discards legitimate wall-hugging neighbors — it fails the existing shorten-path-with-shortcuts-5 test (OBSTACLE_MARGIN: 0.1) and makes keyboards #8/#9 ~52mm longer.
  • getDistanceToOvercomeObstacle calls obstacles.getObstacleAt(x, y) with no layer; ObstacleList3d.getObstacleAt takes (x, y, l), so it always returns null and conjoined-obstacle detection is silently dead in the multilayer router. Passing the layer through re-enables it but produced more far neighbors (2193 -> 5856), one more unsolved connection and a +5.5mm route on traces#34, with no measurable upside, so I'm just noting it.
Ask

How would you like this landed? (a) republish infgrid-ijump-astar from a new home (move algos/ into tscircuit-autorouter or core), (b) vendor MultilayerIjump into core, or (c) something else — happy to open the PR wherever the code should live. tscircuit/autorouting#92 carries an Algora bounty that the archive makes unclaimable through the normal flow; if this fix is useful I'd appreciate it being considered.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Trace_doInitialSchematicTraceRender.ts and Trace_doInitialPcbTraceRender.ts, then inspect MultilayerIjump.getNeighbors in the cited autorouting fix branch and its package source. Confirm the snapshot and dataset-generator results, then get a maintainer decision on republishing, vendoring, or another landing location; done means the wild-jump regression is covered and the relevant suite passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.