ShiboSoftwareDev/linux-gameboy-advance
A four-layer Linux handheld game-console carrier PCB built around an F1C100S SoC, USB-C and 2×AA power, LCD/microSD interfaces, flash, audio amplifier/speaker, controls, service headers, and RGB status LED.
- Version
- 1.0.124
- License
- unset
- Stars
- 0
carrier-router.ts
import verifiedCarrierRoutes from "./carrier-routes-v2.json"
import {
AutoroutingPipelineSolver8,
AutoroutingPipelineSolver9_PreloadedTraceGraph,
} from "@tscircuit/capacity-autorouter"
import { MultilayerIjump } from "@tscircuit/infgrid-ijump-astar"
type EventName = "complete" | "error" | "progress"
type Handler = (event: any) => void
const wire = (x: number, y: number, width: number, layer: string, extra = {}) => ({
route_type: "wire",
x,
y,
width,
layer,
...extra,
})
const via = (x: number, y: number, fromLayer: string, toLayer: string) => ({
route_type: "via",
x,
y,
from_layer: fromLayer,
to_layer: toLayer,
via_diameter: 0.45,
via_hole_diameter: 0.3,
})
/** Apply the small, reviewed clearance corrections to the freshly solved
* carrier. Keeping them declarative makes the website build deterministic and
* leaves the 247-route solution easy to reproduce from carrier-routes-v2.json. */
function applyClearanceRepairs(traces: any[]) {
const byId = new Map(traces.map((trace) => [trace.pcb_trace_id, trace]))
const endingWith = (suffix: string) => {
const matches = traces.filter((trace) => trace.pcb_trace_id.endsWith(suffix))
if (matches.length !== 1)
throw new Error(`Expected one saved carrier route ending in ${suffix}, found ${matches.length}`)
return matches[0]
}
// One USB-C now carries both power and data. The 0.60 mm receptacle locating
// holes prevent a safe outward escape, so bring both fine-pitch VBUS pads
// inward instead. This route passes through the input-capacitor pad: its two
// buried-layer legs are the actual 0.50 mm connector-to-capacitor trunk. The
// ESD VBUS pin only needs the separate short 0.12 mm reference stubs below;
// it does not carry the board input current.
endingWith("source_net_1_mst1_0").route = [
wire(2.4001603, -34.3258552, 0.2, "top", { start_pcb_port_id: "pcb_port_424" }),
wire(2.263740263467107, -34.910461184525055, 0.2, "top"),
wire(1.6487900108640128, -35.52541143712815, 0.2, "top"),
via(1.6487900108640128, -35.52541143712815, "top", "bottom"),
wire(1.6487900108640128, -35.52541143712815, 0.5, "bottom"),
wire(1.6487900108640128, -37, 0.5, "bottom"),
wire(-10.8, -37, 0.5, "bottom"),
via(-10.8, -37, "bottom", "top"),
wire(-10.8, -37, 0.5, "top"),
wire(-10, -31.825, 0.5, "top"),
wire(-9.2, -37.8, 0.5, "top"),
via(-9.2, -37.8, "top", "inner1"),
wire(-9.2, -37.8, 0.5, "inner1"),
wire(-3.4, -37.8, 0.5, "inner1"),
wire(-1.7307744585397424, -35.52541143712815, 0.5, "inner1"),
via(-1.7307744585397424, -35.52541143712815, "inner1", "top"),
wire(-1.7307744585397424, -35.52541143712815, 0.2, "top"),
wire(-2.3999571, -34.85622879566789, 0.2, "top"),
wire(-2.3999571, -34.3260203, 0.2, "top", { end_pcb_port_id: "pcb_port_425" }),
]
endingWith("source_net_1_mst0_0").route = [
wire(-2.3999571, -34.3260203, 0.12, "top", { start_pcb_port_id: "pcb_port_425" }),
wire(-2.3999571, -33.826821333110885, 0.12, "top"),
wire(-3.1058044987744378, -33.12097393433645, 0.12, "top"),
wire(-3.96, -31.5, 0.12, "top"),
wire(-5.04, -31.32, 0.12, "top"),
wire(-5.282030715524831, -31.405295445197837, 0.12, "top"),
wire(-4.876735270326995, -31, 0.12, "top"),
wire(-4.7, -31, 0.12, "top", { end_pcb_port_id: "pcb_port_434" }),
]
endingWith("source_net_1_mst2_0").route = [
wire(-10, -31.825, 0.12, "top", { start_pcb_port_id: "pcb_port_436" }),
wire(-9.522753754969138, -31.825, 0.12, "top"),
wire(-8.976392759812146, -32.37136099515699, 0.12, "top"),
wire(-8.812, -32.539, 0.12, "top"),
via(-8.812, -32.539, "top", "inner1"),
wire(-8.812, -32.539, 0.12, "inner1"),
wire(-7.687852076626941, -33.66314792337306, 0.12, "inner1"),
wire(-5.150112, -33.66525443158813, 0.12, "inner1"),
via(-5.150112, -33.66525443158813, "inner1", "top"),
wire(-5.150112, -33.66525443158813, 0.12, "top"),
wire(-5.150112, -33.4859594774709, 0.12, "top"),
wire(-4.094177867408728, -32.586849774754306, 0.12, "top"),
wire(-4.0065270632285745, -32.26235149310325, 0.12, "top"),
wire(-4.0065270632285745, -31.540351493103255, 0.12, "top"),
wire(-4.011527063228574, -31.336351493103255, 0.12, "top"),
wire(-4.136753220085069, -31.289378828569852, 0.12, "top"),
wire(-4.410621171430148, -31.289378828569852, 0.12, "top"),
wire(-4.7, -31, 0.12, "top", { end_pcb_port_id: "pcb_port_434" }),
]
endingWith("source_net_1_mst3_0").route = [
wire(-10, -31.825, 0.5, "top", { start_pcb_port_id: "pcb_port_436" }),
wire(-15.800106, -32, 0.5, "top", { end_pcb_port_id: "pcb_port_450" }),
]
// J_SD now sits on the uninterrupted outer right edge. Preserve the proven
// fanout and extend only the terminal ends by the component's 4.24868915 mm
// X translation; this keeps every layer change and dense SoC escape intact.
const microSdPortIds = new Set(Array.from({ length: 13 }, (_, index) => `pcb_port_${303 + index}`))
for (const trace of traces) {
for (const point of trace.route ?? []) {
const terminalId = point.start_pcb_port_id ?? point.end_pcb_port_id
if (microSdPortIds.has(terminalId)) point.x += 4.24868915
}
}
// Keep C_SD on the component side and drop only the middle of its short VDD
// connection to the bottom layer. The 9.81 mm planar path avoids all six SD
// signal lines; tscircuit additionally counts the two 1.6 mm via barrels.
endingWith("source_net_6_mst17_0").route = [
wire(49.5, 20.579884, 0.12, "top", { start_pcb_port_id: "pcb_port_316" }),
wire(48.5, 20.579884, 0.12, "top"),
via(48.5, 20.579884, "top", "bottom"),
wire(48.5, 20.579884, 0.12, "bottom"),
wire(56, 20.9398675, 0.12, "bottom"),
via(56, 20.9398675, "bottom", "top"),
wire(56, 20.9398675, 0.12, "top"),
wire(57.2976015, 20.9398675, 0.12, "top", { end_pcb_port_id: "pcb_port_306" }),
]
// Move the extended CMD run off the poured top layer, returning beside the
// pad for only the final short stub. This prevents the former pour island
// from touching the newly lengthened surface segment.
const microSdCmd = byId.get("source_trace_93_0")
microSdCmd.route.splice(
-1,
1,
via(53.049, 22.04, "top", "inner1"),
wire(53.049, 22.04, 0.12, "inner1"),
wire(56.2, 22.04, 0.12, "inner1"),
via(56.2, 22.04, "inner1", "top"),
wire(56.2, 22.04, 0.12, "top"),
wire(57.2976015, 22.0399161, 0.12, "top", { end_pcb_port_id: "pcb_port_305" }),
)
// The low-profile TS6695ZJ shoulder switches use the intermediate 9.5 mm
// actuator. Their bodies finish at the board edge and their tips project
// 3.03 mm past it. The footprint is mirrored relative to TS6665ZJ, so the
// non-polarized contact uses the opposite pin names while retaining the same
// physical signal/ground escape sides.
const rightShoulderGround = byId.get("source_net_0_mst45_0")
rightShoulderGround.connectsTo = ["pcb_port_290", "pcb_port_407"]
rightShoulderGround.route = [
wire(39.99783625, 28.999972, 0.5, "top", { start_pcb_port_id: "pcb_port_290" }),
wire(40.5, 28.5, 0.5, "top"),
via(40.5, 28.5, "top", "bottom"),
wire(40.5, 28.5, 0.5, "bottom"),
wire(55, 28.5, 0.5, "bottom"),
wire(55, 41.4, 0.5, "bottom"),
wire(50, 41.4, 0.5, "bottom"),
wire(47.250059, 40.8200176, 0.5, "bottom", { end_pcb_port_id: "pcb_port_407" }),
]
const leftShoulderGround = byId.get("source_net_0_mst85_0")
leftShoulderGround.connectsTo = ["pcb_port_403", "pcb_port_493"]
leftShoulderGround.route = [
wire(-42.749941, 40.8200176, 0.5, "bottom", { start_pcb_port_id: "pcb_port_403" }),
wire(-40, 41.1, 0.5, "bottom"),
wire(-32, 38.5, 0.5, "bottom"),
wire(-32, 12.05, 0.5, "bottom"),
wire(-39.5, 12.05, 0.5, "bottom"),
via(-39.5, 12.05, "bottom", "top"),
wire(-39.5, 12.05, 0.5, "top"),
wire(-38.1125, 12.05, 0.5, "top", { end_pcb_port_id: "pcb_port_493" }),
]
const leftShoulderSignal = byId.get("source_trace_242_0")
leftShoulderSignal.connectsTo = ["pcb_port_402", "pcb_port_350"]
Object.assign(leftShoulderSignal.route[0], { x: -47.250059, y: 40.8200176 })
leftShoulderSignal.route[0].start_pcb_port_id = "pcb_port_402"
Object.assign(leftShoulderSignal.route[1], { x: -50.5, y: 40.8200176 })
Object.assign(leftShoulderSignal.route[2], { x: -50.5, y: 19.274230884615385 })
const rightShoulderSignal = byId.get("source_trace_244_0")
rightShoulderSignal.connectsTo = ["pcb_port_406", "pcb_port_351"]
Object.assign(rightShoulderSignal.route[0], { x: 42.749941, y: 40.8200176 })
rightShoulderSignal.route[0].start_pcb_port_id = "pcb_port_406"
Object.assign(rightShoulderSignal.route[1], { x: 44.6001933, y: 40.8200176 })
// Move the removable boot shunt away from the new upper-left volume wheel.
// Preserve the long reviewed routes and replace only their service-area tails.
const flashCsBoot = byId.get("source_net_89_mst1_0")
const flashCsTail = flashCsBoot.route.findIndex((point: any) =>
point.route_type === "wire" && point.layer === "inner1" &&
Math.abs(point.x + 12.266) < 1e-6 && Math.abs(point.y - 26.10002009631631) < 1e-6)
flashCsBoot.route.splice(flashCsTail + 1, Infinity,
wire(-14, 28.5, 0.12, "inner1"),
wire(-43, 28.5, 0.12, "inner1"),
wire(-43, 18, 0.12, "inner1"),
via(-43, 18, "inner1", "top"),
wire(-43, 18, 0.12, "top"),
wire(-46.73, 18, 0.12, "top", { end_pcb_port_id: "pcb_port_335" }),
)
const socCsBoot = byId.get("source_net_79_0")
const socCsTail = socCsBoot.route.findIndex((point: any) =>
point.route_type === "wire" && point.layer === "inner1" &&
Math.abs(point.x + 56.27) < 1e-6 && Math.abs(point.y + 7.810194999999999) < 1e-6)
socCsBoot.route.splice(socCsTail + 1, Infinity,
wire(-51, 18, 0.12, "inner1"),
via(-51, 18, "inner1", "top"),
wire(-51, 18, 0.12, "top"),
wire(-49.27, 18, 0.12, "top", { end_pcb_port_id: "pcb_port_334" }),
)
// The volume wheel follows the tangent of the mirrored upper-left corner.
// Shift the terminal escapes with the part; approach pins 1/3 from the open
// lower side and pin 2 from the open upper side, clear of the unused holes.
const volumeGround = byId.get("source_net_0_mst24_0")
volumeGround.connectsTo = ["pcb_port_403", "pcb_port_503"]
volumeGround.route = [
wire(-42.749941, 40.8200176, 0.5, "bottom", { start_pcb_port_id: "pcb_port_403" }),
wire(-40, 40.8200176, 0.5, "bottom"),
wire(-40, 42, 0.5, "bottom"),
wire(-51.5, 42, 0.5, "bottom"),
wire(-51.5, 35.5, 0.5, "bottom"),
wire(-51, 33.5, 0.5, "bottom"),
wire(-51, 24.000008, 0.5, "bottom"),
via(-51, 24.000008, "bottom", "top"),
wire(-51, 24.000008, 0.5, "top"),
wire(-53, 24.000008, 0.5, "top"),
wire(-53, 17.0, 0.5, "top"),
wire(-62.49816609, 17.0, 0.5, "top"),
wire(-62.49816609, 19.99085249, 0.5, "top", { end_pcb_port_id: "pcb_port_503" }),
]
const volumeInput = byId.get("source_trace_328_0")
volumeInput.connectsTo = ["pcb_port_502", "pcb_port_505"]
volumeInput.route = [
wire(20, -18.4201, 0.12, "top", { start_pcb_port_id: "pcb_port_502" }),
wire(20.331, -18.7511, 0.12, "top"),
wire(20.331, -19.034, 0.12, "top"),
via(20.331, -19.034, "top", "inner2"),
wire(20.331, -19.034, 0.12, "inner2"),
wire(20.47274845913, -19.17574845913, 0.12, "inner2"),
wire(20.51827957883286, -19.17574845913, 0.12, "inner2"),
wire(31.34252711970286, -29.999996, 0.12, "inner2"),
wire(31.34252711970286, -29.5, 0.12, "inner2"),
wire(27.5, -29.5, 0.12, "inner2"),
via(27.5, -29.5, "inner2", "inner1"),
wire(27.5, -29.5, 0.12, "inner1"),
wire(27.5, -30, 0.12, "inner1"),
wire(-57.449805, -30, 0.12, "inner1"),
wire(-57.449805, -29.5, 0.12, "inner1"),
via(-57.449805, -29.5, "inner1", "bottom"),
wire(-57.449805, -29.5, 0.12, "bottom"),
wire(-57.449805, -25, 0.12, "bottom"),
via(-57.449805, -25, "bottom", "inner1"),
wire(-57.449805, -25, 0.12, "inner1"),
wire(-57.449805, 18, 0.12, "inner1"),
via(-57.449805, 18, "inner1", "top"),
wire(-57.449805, 18, 0.12, "top"),
wire(-55, 18, 0.12, "top"),
wire(-55, 28.5, 0.12, "top"),
wire(-58.38921946, 28.5, 0.12, "top"),
wire(-58.38921946, 24.36308460, 0.12, "top", { end_pcb_port_id: "pcb_port_505" }),
]
const volumeWiper = byId.get("source_trace_330_0")
volumeWiper.connectsTo = ["pcb_port_504", "pcb_port_510"]
volumeWiper.route = [
wire(-59.75886834, 22.90567390, 0.12, "bottom", { start_pcb_port_id: "pcb_port_504" }),
wire(-55.5, 23.6, 0.12, "bottom"),
wire(-53.05, 23.6, 0.12, "bottom"),
wire(-53.05, 4, 0.12, "bottom"),
via(-53.05, 4, "bottom", "inner1"),
wire(-53.05, 4, 0.12, "inner1"),
wire(-53.05, 1.2, 0.12, "inner1"),
via(-53.05, 1.2, "inner1", "bottom"),
wire(-53.05, 1.2, 0.12, "bottom"),
wire(-55.5, 1.2, 0.12, "bottom"),
wire(-55.5, -32, 0.12, "bottom"),
wire(25, -32, 0.12, "bottom"),
via(25, -32, "bottom", "inner2"),
wire(25, -32, 0.12, "inner2"),
wire(26.5, -32, 0.12, "inner2"),
wire(26.5, -26, 0.12, "inner2"),
via(26.5, -26, "inner2", "bottom"),
wire(26.5, -26, 0.12, "bottom"),
wire(26.5, -24, 0.12, "bottom"),
via(26.5, -24, "bottom", "inner2"),
wire(26.5, -24, 0.12, "inner2"),
wire(26.5, -18.4201, 0.12, "inner2"),
via(26.5, -18.4201, "inner2", "top"),
wire(26.5, -18.4201, 0.12, "top"),
wire(27, -18.4201, 0.12, "top", { end_pcb_port_id: "pcb_port_510" }),
]
// Move J_BAT left while preserving its right-facing orientation. The JST-PH
// housing now stops 5.95 mm before the cable cutout, leaving a real wire-bend
// corridor instead of placing the plug directly against the routed slot.
byId.get("source_net_2_0").route = [
wire(-40, -38.37499805, 0.5, "top", { start_pcb_port_id: "pcb_port_438" }),
wire(-41.2, -38.37499805, 0.5, "top"),
wire(-41.2, -33, 0.5, "top"),
wire(-39.853438, -31, 0.5, "top", { end_pcb_port_id: "pcb_port_440" }),
]
byId.get("source_net_0_mst68_0").route = [
wire(-40, -36.37500205, 0.5, "top", { start_pcb_port_id: "pcb_port_439" }),
wire(-39.5, -35.87500205, 0.5, "top"),
wire(-31.5625, -34.53750128125, 0.5, "top"),
wire(-31.5625, -28.5625, 0.5, "top"),
wire(-30, -27, 0.5, "top", { end_pcb_port_id: "pcb_port_466" }),
]
// Relocate the power control to the straight left edge. The switch's real
// housing is inset 0.50 mm while only its actuator projects through the
// enclosure. Keep the battery path at 0.5 mm and avoid the rounded corner.
byId.get("source_net_3_0").route = [
wire(-36.146562, -31, 0.5, "top", { start_pcb_port_id: "pcb_port_441" }),
wire(-34.5, -28.5, 0.5, "top"),
via(-34.5, -28.5, "top", "inner1"),
wire(-34.5, -28.5, 0.5, "inner1"),
wire(-50, -28, 0.5, "inner1"),
wire(-65, -27.175, 0.5, "inner1"),
wire(-68.983, -27.175, 0.5, "inner1", { end_pcb_port_id: "pcb_port_442" }),
]
byId.get("source_net_4_0").route = [
wire(-68.983, -24.127, 0.5, "inner2", { start_pcb_port_id: "pcb_port_443" }),
wire(-64.5, -24.127, 0.5, "inner2"),
wire(-64.5, -38, 0.5, "inner2"),
wire(-55, -39, 0.5, "inner2"),
wire(-44, -39, 0.5, "inner2"),
wire(-44, -34.5, 0.5, "inner2"),
wire(-35, -34.5, 0.5, "inner2"),
via(-35, -34.5, "inner2", "top"),
wire(-35, -34.5, 0.5, "top"),
wire(-33.199894, -31, 0.5, "top", { end_pcb_port_id: "pcb_port_448" }),
]
byId.get("source_net_0_mst78_0").route = [
wire(-56.200017, -21.249805, 0.5, "top", { start_pcb_port_id: "pcb_port_395" }),
wire(-64, -20, 0.5, "top"),
wire(-68.983, -17.523, 0.5, "top", { end_pcb_port_id: "pcb_port_445" }),
]
byId.get("source_net_0_mst82_0").route = [
wire(-68.983, -17.523, 0.5, "top", { start_pcb_port_id: "pcb_port_445" }),
wire(-66.25, -17.523, 0.5, "top"),
wire(-66.25, -30.477, 0.5, "top"),
wire(-68.983, -30.477, 0.5, "top", { end_pcb_port_id: "pcb_port_446" }),
]
// Keep this short ground link at the board minimum width. The autorouter's
// global ground expansion otherwise encroaches on adjacent VCI pin 42.
byId.get("source_net_0_mst8_0").route = byId
.get("source_net_0_mst8_0")
.route.map((point: any) => ({ ...point, width: 0.12 }))
// The SimpleRouteJson converter omits the connector's two non-plated shell
// holes. Take the shield-to-shield ground link below both 0.6 mm drills.
byId.get("source_net_0_mst39_0").route = [
wire(-3.1999555, -34.3259568, 0.5, "top", { start_pcb_port_id: "pcb_port_422" }),
wire(-4, -35.1259568, 0.5, "top"),
wire(-4, -36.6, 0.5, "top"),
wire(4, -36.6, 0.5, "top"),
wire(4, -35.1258552, 0.5, "top"),
wire(3.2000063, -34.3258552, 0.5, "top", { end_pcb_port_id: "pcb_port_423" }),
]
// A long LCD signal passes a fixed VCC_DRAM module via. Move only its
// inner-layer elbow; all terminal and layer transitions remain.
const lcdPe5 = byId.get("source_trace_125_0")
lcdPe5.route.splice(
2,
1,
wire(-9.9, -10.8, 0.12, "inner1"),
wire(-7.564621678403883, -12.742778726988721, 0.12, "inner1"),
)
// JLCPCB measures via-hole-to-copper spacing from the finished drill edge.
// This V3V3 via used to leave only 0.177 mm to the module's inner2 HPL
// trace. Drop the transition below that elbow while retaining both layers
// and the original endpoints; the resulting finished-hole clearance is over
// the published 0.20 mm minimum.
const v3v3ClearanceRoute = endingWith("source_net_6_mst40_0")
for (const point of v3v3ClearanceRoute.route.slice(2, 5)) {
point.x = 8.399
point.y = 10.65
}
// The solved carrier reduced the regulator's 0.5 mm output-capacitor links
// to the 0.12 mm signal minimum. Restore both local switching-current paths
// to their requested width. The feedback branch remains a short 0.12 mm
// Kelvin-sense connection, as intended.
for (const suffix of ["source_net_6_mst14_0", "source_net_6_mst23_0"])
endingWith(suffix).route = endingWith(suffix).route.map((point: any) =>
point.route_type === "wire" ? { ...point, width: 0.5 } : point)
// Approach the buck input capacitor's negative pad from its open right-hand
// side. The previous final segment passed the positive pad too closely and
// also put its layer-change via inside that pad's copper clearance.
byId.get("source_net_0_mst34_0").route = [
wire(-26.199896, -19.05004, 0.5, "top", { start_pcb_port_id: "pcb_port_454" }),
wire(-27.0605, -19.389, 0.5, "top"),
via(-27.0605, -19.389, "top", "inner2"),
wire(-27.0605, -19.389, 0.5, "inner2"),
wire(-19.634, -19.097, 0.5, "inner2"),
via(-19.634, -19.097, "inner2", "top"),
wire(-19.634, -19.097, 0.5, "top"),
wire(-20.5, -18.9125, 0.5, "top", { end_pcb_port_id: "pcb_port_460" }),
]
// Rotate the input capacitor so its ground pad faces the proven inner-layer
// return. Feed its positive pad around the open upper side of the buck,
// preserving a 0.5 mm current path without crossing the capacitor body.
byId.get("source_trace_287__source_net_5_mst1_0").route = [
wire(-20.5, -17.0875, 0.5, "top", { start_pcb_port_id: "pcb_port_459" }),
wire(-20.5, -15.5, 0.5, "top"),
wire(-23.8, -15.5, 0.5, "top"),
wire(-23.800104, -19.05004, 0.5, "top", { end_pcb_port_id: "pcb_port_453" }),
]
return traces
}
/** Route the rear service connectors explicitly. The saved carrier solution
* predates these headers, so keeping this small route set beside it makes the
* published build deterministic while still letting tscircuit run its normal
* connectivity, clearance, and short checks on every build. */
function createServiceConnectorRoutes() {
const trace = (
id: string,
connectsTo: string[],
route: any[],
) => ({
type: "pcb_trace",
pcb_trace_id: id,
connection_name: id.replace(/_0$/, ""),
connectsTo,
route,
})
return [
// UART follows the standard programmer order RX-GND-TX. Rather than
// crossing the processor escape, both signals join the already-routed
// UART test-point copper around the quiet left perimeter.
trace("uart_rx_jst_0", ["pcb_port_564", "pcb_port_581"], [
wire(-19.000125, 35.6749963, 0.12, "top", { start_pcb_port_id: "pcb_port_564" }),
wire(-19.3, 35.3, 0.12, "top"),
wire(-19.3, 33.2, 0.12, "top"),
via(-19.3, 33.2, "top", "inner1"),
wire(-19.3, 33.2, 0.12, "inner1"),
wire(-47, 33.2, 0.12, "inner1"),
wire(-47, 20.8, 0.12, "inner1"),
wire(-50, 20.8, 0.12, "inner1"),
via(-50, 20.8, "inner1", "inner2"),
wire(-50, 20.8, 0.12, "inner2"),
wire(-60, 20.8, 0.12, "inner2"),
via(-60, 20.8, "inner2", "inner1"),
wire(-60, 20.8, 0.12, "inner1"),
wire(-60, 18, 0.12, "inner1"),
wire(-64, 18, 0.12, "inner1"),
wire(-64, -26.2, 0.12, "inner1"),
wire(-60, -26.2, 0.12, "inner1"),
via(-60, -26.2, "inner1", "inner2"),
wire(-60, -26.2, 0.12, "inner2"),
wire(-55, -26.2, 0.12, "inner2"),
via(-55, -26.2, "inner2", "inner1"),
wire(-55, -26.2, 0.12, "inner1"),
wire(-36.5, -26.2, 0.12, "inner1"),
wire(-39.46, -23.51, 0.12, "inner1"),
via(-39.46, -23.51, "inner1", "top"),
wire(-39.46, -23.51, 0.12, "top", { end_pcb_port_id: "pcb_port_581" }),
]),
trace("uart_rx_mcu_0", ["pcb_port_582", "pcb_port_499"], [
wire(-39.46, -22.49, 0.12, "top", { start_pcb_port_id: "pcb_port_582" }),
wire(-39.46, -20, 0.12, "top", { end_pcb_port_id: "pcb_port_499" }),
]),
trace("uart_tx_jst_0", ["pcb_port_566", "pcb_port_583"], [
wire(-17.000129, 35.6749963, 0.12, "top", { start_pcb_port_id: "pcb_port_566" }),
wire(-16.7, 35.3, 0.12, "top"),
wire(-16.7, 30.8, 0.12, "top"),
via(-16.7, 30.8, "top", "inner2"),
wire(-16.7, 30.8, 0.12, "inner2"),
wire(-49, 30.8, 0.12, "inner2"),
wire(-49, 19.5, 0.12, "inner2"),
wire(-60, 19.5, 0.12, "inner2"),
wire(-60, 17.2, 0.12, "inner2"),
wire(-63, 17.2, 0.12, "inner2"),
wire(-63, -25.5, 0.12, "inner2"),
wire(-42, -25.5, 0.12, "inner2"),
wire(-42, -23.51, 0.12, "inner2"),
via(-42, -23.51, "inner2", "top"),
wire(-42, -23.51, 0.12, "top", { end_pcb_port_id: "pcb_port_583" }),
]),
trace("uart_tx_mcu_0", ["pcb_port_584", "pcb_port_498"], [
wire(-42, -22.49, 0.12, "top", { start_pcb_port_id: "pcb_port_584" }),
wire(-42, -20, 0.12, "top", { end_pcb_port_id: "pcb_port_498" }),
]),
// Both expansion headers share one 3V3 branch. The SPI-side leg then joins
// the existing LCD/SD 3V3 copper, avoiding a second long supply route.
trace("source_trace_381_0", ["pcb_port_568", "pcb_port_574"], [
wire(-27.500128, 34.9994833, 0.5, "top", { start_pcb_port_id: "pcb_port_568" }),
wire(-27.5, 36, 0.5, "top"),
via(-27.5, 36, "top", "inner2"),
wire(-27.5, 36, 0.5, "inner2"),
wire(23.5, 36, 0.5, "inner2"),
via(23.5, 36, "inner2", "top"),
wire(23.5, 36, 0.5, "top"),
wire(23.499743, 34.9499914, 0.5, "top", { end_pcb_port_id: "pcb_port_574" }),
]),
// Route I2C across the open top strip, then approach the two LCD control
// pads from the connector's clear right-hand side.
trace("source_trace_382_0", ["pcb_port_569", "pcb_port_284"], [
wire(-28.50038, 34.9999913, 0.12, "top", { start_pcb_port_id: "pcb_port_569" }),
wire(-28.5, 34, 0.12, "top"),
via(-28.5, 34, "top", "inner2"),
wire(-28.5, 34, 0.12, "inner2"),
// The remapped top-side SPI exits no longer occupy this inner-layer
// corridor, so keep SDA entirely on inner2 until the LCD approach.
wire(17, 34, 0.12, "inner2"),
wire(47.4, 34, 0.12, "inner2"),
wire(47.4, 24.606427015, 0.12, "inner2"),
wire(40.657547168, 24.606427015, 0.12, "inner2"),
]),
trace("source_trace_383_0", ["pcb_port_570", "pcb_port_283"], [
wire(-29.500378, 34.9999913, 0.12, "top", { start_pcb_port_id: "pcb_port_570" }),
wire(-29.5, 31.7, 0.12, "top"),
via(-29.5, 31.7, "top", "inner2"),
wire(-29.5, 31.7, 0.12, "inner2"),
wire(46, 31.7, 0.12, "inner2"),
wire(46, 25.3, 0.12, "inner2"),
via(46, 25.3, "inner2", "inner1"),
wire(46, 25.3, 0.12, "inner1"),
wire(46, 24, 0.12, "inner1"),
via(46, 24, "inner1", "inner2"),
wire(46, 24, 0.12, "inner2"),
wire(46, 23.430917565, 0.12, "inner2"),
wire(43.439988568, 23.430917565, 0.12, "inner2"),
]),
// Give each service-header supply pin its own terminal-to-rail branch;
// tscircuit's port-connectivity check intentionally does not infer an
// intermediate terminal merely from copper passing through its pad.
trace("source_trace_387_0", ["pcb_port_574", "pcb_port_292"], [
wire(23.499743, 34.9499914, 0.5, "top", { start_pcb_port_id: "pcb_port_574" }),
wire(23.5, 33, 0.5, "top"),
wire(33.675, 33, 0.5, "top"),
wire(33.675, 22, 0.5, "top", { end_pcb_port_id: "pcb_port_292" }),
]),
// The four SPI stubs land directly on open sections of the existing LCD
// serial-control routes. This is electrically identical to routing to the
// FPC pads, but avoids disturbing the dense 0.5 mm-pitch fanout.
trace("source_trace_388_0", ["pcb_port_575", "pcb_port_275"], [
wire(22.499745, 34.9499914, 0.12, "top", { start_pcb_port_id: "pcb_port_575" }),
wire(22.499745, 31.007408282, 0.12, "top"),
wire(26.140204113690785, 31.007408282415952, 0.12, "top"),
wire(26.140204113690785, 28.911967176070917, 0.12, "top"),
wire(32.96441209992152, 22.087759189840185, 0.12, "top"),
wire(33.0956574204766, 21.9565138692851, 0.12, "top"),
wire(33.035369773175994, 21.59281815660092, 0.12, "top"),
wire(33.16442657334175, 21.365219273651356, 0.12, "top"),
wire(33.335, 21.326, 0.12, "top"),
wire(33.438, 21.226, 0.12, "top"),
wire(33.41719017363501, 21.24620371491747, 0.12, "top"),
wire(34.34965491041322, 20.313738978139256, 0.12, "top"),
wire(42.28726102186074, 20.313738978139256, 0.12, "top"),
wire(42.35140025, 20.2500022, 0.12, "top", { end_pcb_port_id: "pcb_port_275" }),
]),
trace("source_trace_389_0", ["pcb_port_576", "pcb_port_273"], [
wire(21.499747, 34.9499914, 0.12, "top", { start_pcb_port_id: "pcb_port_576" }),
wire(21.499747, 32.3, 0.12, "top"),
wire(18, 32.3, 0.12, "top"),
wire(18, 32.3, 0.12, "top"),
via(18, 32.3, "top", "inner1"),
wire(18, 32.3, 0.12, "inner1"),
wire(18, 28.442000206, 0.12, "inner1"),
via(18, 28.442000206, "inner1", "inner2"),
wire(18, 28.442000206, 0.12, "inner2"),
wire(27.48906884571497, 28.442000206130313, 0.12, "inner2"),
wire(36.62496828175495, 19.30610077009033, 0.12, "inner2"),
wire(39.05768638020163, 19.305686380201635, 0.12, "inner2"),
wire(39.06672504896254, 19.315539487365136, 0.12, "inner2"),
via(39.06672504896254, 19.315539487365136, "inner2", "top"),
wire(39.06672504896254, 19.315539487365136, 0.12, "top"),
wire(42.288, 19.313, 0.12, "top"),
wire(42.35140025, 19.2500042, 0.12, "top", { end_pcb_port_id: "pcb_port_273" }),
]),
trace("source_trace_390_0", ["pcb_port_577", "pcb_port_272"], [
wire(20.499749, 34.9499914, 0.12, "top", { start_pcb_port_id: "pcb_port_577" }),
wire(20.499749, 33, 0.12, "top"),
via(20.499749, 33, "top", "inner1"),
wire(20.499749, 33, 0.12, "inner1"),
wire(19, 33, 0.12, "inner1"),
wire(19, 25.703043614, 0.12, "inner1"),
wire(25.253639359, 25.703043614, 0.12, "inner1"),
wire(25.706676384, 25.248323616, 0.12, "inner1"),
wire(25.802, 25.153, 0.12, "inner1"),
via(25.802, 25.153, "inner1", "inner2"),
wire(25.802, 25.153, 0.12, "inner2"),
wire(26.039936057, 25.153, 0.12, "inner2"),
wire(29.053, 22.141, 0.12, "inner2"),
via(29.053, 22.141, "inner2", "inner1"),
wire(29.053, 22.141, 0.12, "inner1"),
wire(32.461429269, 18.732570731, 0.12, "inner1"),
wire(38.504971558, 18.714513468, 0.12, "inner1"),
via(38.504971558, 18.714513468, "inner1", "top"),
wire(38.504971558, 18.714513468, 0.12, "top"),
wire(42.35140025, 18.7500052, 0.12, "top", { end_pcb_port_id: "pcb_port_272" }),
]),
trace("source_trace_391_0", ["pcb_port_578", "pcb_port_277"], [
wire(19.499751, 34.9499914, 0.12, "top", { start_pcb_port_id: "pcb_port_578" }),
wire(19.499751, 36.682, 0.12, "top"),
wire(20.44983163, 36.682, 0.12, "top"),
via(20.44983163, 36.682, "top", "inner1"),
wire(20.44983163, 36.682, 0.12, "inner1"),
wire(28.45334812702196, 28.678483502616913, 0.12, "inner1"),
wire(28.653695150034004, 28.678483502616913, 0.12, "inner1"),
wire(36.110178652650916, 21.222, 0.12, "inner1"),
wire(38.637, 21.222, 0.12, "inner1"),
via(38.637, 21.222, "inner1", "top"),
wire(38.637, 21.222, 0.12, "top"),
wire(42.323, 21.222, 0.12, "top"),
wire(42.35140025, 21.2500002, 0.12, "top", { end_pcb_port_id: "pcb_port_277" }),
]),
// Explicit ground branches keep every SMT ground and shield terminal
// connected after the headers move from the bottom to the top assembly
// side. The endpoints tie into the two plated shoulder-switch ground
// terminals, which already join the board-wide ground network.
trace("service_ground_uart_i2c_0", ["pcb_port_565", "pcb_port_567"], [
wire(-18.000127, 35.6749963, 0.5, "top", { start_pcb_port_id: "pcb_port_565" }),
wire(-18, 30, 0.5, "top"),
via(-18, 30, "top", "bottom"),
wire(-18, 30, 0.5, "bottom"),
wire(-26.5, 30, 0.5, "bottom"),
via(-26.5, 30, "bottom", "top"),
wire(-26.5, 30, 0.5, "top"),
wire(-26.50013, 34.9994833, 0.5, "top", { end_pcb_port_id: "pcb_port_567" }),
]),
trace("service_ground_i2c_shields_0", ["pcb_port_571", "pcb_port_572"], [
wire(-30.800096, 38.8755233, 0.5, "top", { start_pcb_port_id: "pcb_port_571" }),
wire(-30.8, 40.5, 0.5, "top"),
wire(-25.2, 40.5, 0.5, "top"),
wire(-25.199904, 38.8750153, 0.5, "top", { end_pcb_port_id: "pcb_port_572" }),
]),
trace("service_ground_i2c_pin_0", ["pcb_port_572", "pcb_port_567"], [
wire(-25.199904, 38.8750153, 0.5, "top", { start_pcb_port_id: "pcb_port_572" }),
wire(-24.8, 37, 0.5, "top"),
wire(-26.50013, 34.9994833, 0.5, "top", { end_pcb_port_id: "pcb_port_567" }),
]),
trace("service_ground_i2c_board_0", ["pcb_port_571", "pcb_port_402"], [
wire(-30.800096, 38.8755233, 0.5, "top", { start_pcb_port_id: "pcb_port_571" }),
wire(-30.8, 41.4, 0.5, "top"),
wire(-42.749941, 41.4, 0.5, "top"),
wire(-42.749941, 39.7199436, 0.5, "top", { end_pcb_port_id: "pcb_port_402" }),
]),
trace("service_ground_spi_shields_0", ["pcb_port_579", "pcb_port_580"], [
wire(18.200033, 38.8250154, 0.5, "top", { start_pcb_port_id: "pcb_port_579" }),
wire(18.2, 41.4, 0.5, "top"),
wire(25.8, 41.4, 0.5, "top"),
wire(25.799967, 38.8250154, 0.5, "top", { end_pcb_port_id: "pcb_port_580" }),
]),
trace("service_ground_spi_pin_0", ["pcb_port_580", "pcb_port_573"], [
wire(25.799967, 38.8250154, 0.5, "top", { start_pcb_port_id: "pcb_port_580" }),
wire(26.2, 37, 0.5, "top"),
wire(24.499995, 34.9499914, 0.5, "top", { end_pcb_port_id: "pcb_port_573" }),
]),
trace("service_ground_spi_board_0", ["pcb_port_573", "pcb_port_406"], [
wire(24.499995, 34.9499914, 0.5, "top", { start_pcb_port_id: "pcb_port_573" }),
wire(24.5, 41.5, 0.5, "top"),
wire(47.250059, 41.5, 0.5, "top"),
wire(47.250059, 39.7199436, 0.5, "top", { end_pcb_port_id: "pcb_port_406" }),
]),
]
}
/** Reviewed local copper for the two-AA input selector and TPS63020 island.
* The rest of the carrier continues to come from carrier-routes-v2.json. */
function createTwoAaPowerRoutes(input: any, fixedTraces: any[]) {
const pointById = new Map<string, any>()
for (const connection of input.connections ?? [])
for (const point of connection.pointsToConnect ?? [])
if (point.pcb_port_id) pointById.set(point.pcb_port_id, point)
const point = (id: string) => {
const value = pointById.get(id)
if (!value) throw new Error(`Missing two-AA power terminal ${id}`)
return value
}
const trace = (id: string, connectionName: string, connectsTo: string[], route: any[]) => ({
type: "pcb_trace",
pcb_trace_id: `${id}_0`,
connection_name: connectionName,
connectsTo,
route,
})
const top = (
id: string,
connectionName: string,
startId: string,
endId: string,
width: number,
middle: { x: number, y: number }[] = [],
) => {
const start = point(startId)
const end = point(endId)
return trace(id, connectionName, [startId, endId], [
wire(start.x, start.y, width, "top", { start_pcb_port_id: startId }),
...middle.map(({ x, y }) => wire(x, y, width, "top")),
wire(end.x, end.y, width, "top", { end_pcb_port_id: endId }),
])
}
const routes: any[] = []
const mixedTop = (
id: string,
connectionName: string,
startId: string,
endId: string,
vertices: Array<{ x: number, y: number, width: number }>,
) => trace(id, connectionName, [startId, endId], vertices.map((vertex, index) => wire(
vertex.x,
vertex.y,
vertex.width,
"top",
index === 0 ? { start_pcb_port_id: startId }
: index === vertices.length - 1 ? { end_pcb_port_id: endId }
: {},
)))
// The DSJ package uses 0.5 mm pitch and 0.25 mm-wide pads. Every paired
// pin and first escape segment is therefore 0.12 mm; copper widens to
// 0.50 mm only once it has cleared the neighboring pads and package body.
routes.push(top("twoaa_vin_pair", "source_trace_289", "pcb_port_460", "pcb_port_461", 0.12))
routes.push(mixedTop("twoaa_vina_input", "source_trace_290", "pcb_port_451", "pcb_port_470", [
{ ...point("pcb_port_451"), width: 0.12 },
{ x: -27.5, y: -21.45, width: 0.12 },
{ x: -27.5, y: -17.35, width: 0.12 },
{ ...point("pcb_port_470"), width: 0.12 },
]))
routes.push(top("twoaa_enable", "source_trace_291", "pcb_port_462", "pcb_port_461", 0.12))
routes.push(mixedTop("twoaa_input_cap_a", "source_trace_295", "pcb_port_468", "pcb_port_460", [
{ ...point("pcb_port_468"), width: 0.5 },
{ x: -24.5, y: -16.9, width: 0.5 },
{ x: -24.5, y: -17.95, width: 0.12 },
{ ...point("pcb_port_460"), width: 0.12 },
]))
routes.push(mixedTop("twoaa_input_cap_b", "source_trace_297", "pcb_port_470", "pcb_port_461", [
{ ...point("pcb_port_470"), width: 0.5 },
{ x: -27.2, y: -16.4, width: 0.5 },
{ x: -26.5, y: -17.1, width: 0.5 },
{ x: -25, y: -17.1, width: 0.5 },
{ x: -25, y: -17.95, width: 0.12 },
{ ...point("pcb_port_461"), width: 0.12 },
]))
routes.push(top("twoaa_l1_pair", "source_trace_299", "pcb_port_458", "pcb_port_459", 0.12))
routes.push(mixedTop("twoaa_l1", "source_trace_300", "pcb_port_458", "pcb_port_466", [
{ ...point("pcb_port_458"), width: 0.12 },
{ x: -22.75, y: -18.55, width: 0.12 },
{ x: -22.35, y: -18.55, width: 0.5 },
{ x: -20.2, y: -18.55, width: 0.5 },
{ ...point("pcb_port_466"), width: 0.5 },
]))
routes.push(top("twoaa_l2_pair", "source_trace_301", "pcb_port_457", "pcb_port_456", 0.12))
routes.push(mixedTop("twoaa_l2", "source_trace_302", "pcb_port_467", "pcb_port_457", [
{ ...point("pcb_port_467"), width: 0.5 },
{ x: -17.8, y: -22.7, width: 0.5 },
{ x: -22.7, y: -22.7, width: 0.5 },
{ x: -22.95, y: -22.45, width: 0.12 },
{ ...point("pcb_port_457"), width: 0.12 },
]))
routes.push(top("twoaa_vout_pair", "source_trace_303", "pcb_port_454", "pcb_port_455", 0.12))
routes.push(mixedTop("twoaa_output_cap_a", "source_trace_305", "pcb_port_472", "pcb_port_454", [
{ ...point("pcb_port_472"), width: 0.5 },
{ x: -24, y: -23.2, width: 0.5 },
{ x: -25, y: -23.2, width: 0.5 },
{ x: -25, y: -22.15, width: 0.12 },
{ ...point("pcb_port_454"), width: 0.12 },
]))
routes.push(mixedTop("twoaa_output_cap_b", "source_trace_306", "pcb_port_474", "pcb_port_455", [
{ ...point("pcb_port_474"), width: 0.5 },
{ x: -27, y: -23.65, width: 0.5 },
{ x: -25.4, y: -23.65, width: 0.5 },
{ x: -24.5, y: -22.75, width: 0.5 },
{ x: -24.5, y: -22.15, width: 0.12 },
{ ...point("pcb_port_455"), width: 0.12 },
]))
routes.push(mixedTop("twoaa_vina_cap", "source_trace_408", "pcb_port_599", "pcb_port_451", [
{ ...point("pcb_port_599"), width: 0.12 },
{ x: -27.85, y: -20.4, width: 0.12 },
{ x: -27.1, y: -21.15, width: 0.12 },
{ ...point("pcb_port_451"), width: 0.12 },
]))
routes.push(mixedTop("twoaa_output_cap_c", "source_trace_410", "pcb_port_601", "pcb_port_455", [
{ ...point("pcb_port_601"), width: 0.5 },
{ x: -21, y: -23.6, width: 0.5 },
{ x: -23.4, y: -23.6, width: 0.5 },
{ x: -24.5, y: -22.5, width: 0.5 },
{ x: -24.5, y: -22.15, width: 0.12 },
{ ...point("pcb_port_455"), width: 0.12 },
]))
routes.push(top("twoaa_feedback_top_in", "source_trace_412", "pcb_port_603", "pcb_port_474", 0.12, [
{ x: -30, y: point("pcb_port_603").y }, { x: -30, y: -25 }, { x: -27, y: -25 },
]))
routes.push(top("twoaa_feedback_top_out", "source_trace_413", "pcb_port_604", "pcb_port_453", 0.12, [
{ x: -27.75, y: point("pcb_port_604").y }, { x: -26.2, y: -22.2 }, { x: -25.5, y: -22.2 },
]))
routes.push(top("twoaa_feedback_low", "source_trace_414", "pcb_port_605", "pcb_port_453", 0.12, [
{ x: -28.49, y: -22.9 }, { x: -27.75, y: -22.9 },
{ x: -26.2, y: -22.2 }, { x: -25.5, y: -22.2 },
]))
// USB/battery source selection. The 0.50 mm battery and SYS_IN routes use
// buried layers only after a short component-side escape; the gate network
// remains a quiet 0.12 mm top-layer circuit away from the switching node.
routes.push(top("twoaa_usb_gate", "source_net_1", "pcb_port_447", "pcb_port_450", 0.12, [
{ x: point("pcb_port_447").x, y: -29.4 }, { x: -14.8, y: -29.4 },
{ x: -14.8, y: -31 },
]))
{
const a = point("pcb_port_443")
const b = point("pcb_port_596")
routes.push(trace("twoaa_battery_path", "source_net_4", ["pcb_port_443", "pcb_port_596"], [
wire(a.x, a.y, 0.5, "inner2", { start_pcb_port_id: "pcb_port_443" }),
wire(-66, -30.127, 0.5, "inner2"),
wire(-42.114, -30.127, 0.5, "inner2"),
wire(-37.968, -34.274, 0.5, "inner2"),
wire(-37.062, -34.135, 0.5, "inner2"),
via(-37.062, -34.135, "inner2", "top"),
wire(-37.062, -34.135, 0.5, "top"),
wire(-35.5, -35.7, 0.5, "top"),
wire(-35.5, -39, 0.5, "top"),
wire(-28.5, -39, 0.5, "top"),
wire(-28.5, b.y, 0.5, "top"),
wire(b.x, b.y, 0.5, "top", { end_pcb_port_id: "pcb_port_596" }),
]))
}
routes.push(top("twoaa_gate_pull", "source_net_5", "pcb_port_448", "pcb_port_597", 0.12, [
{ x: -28, y: point("pcb_port_448").y }, { x: -28, y: point("pcb_port_597").y },
]))
routes.push(top("twoaa_gate_fet", "source_net_5", "pcb_port_597", "pcb_port_594", 0.12, [
{ x: -30, y: point("pcb_port_597").y }, { x: -31, y: -34.5 },
]))
{
const usb = point("pcb_port_449")
const battery = point("pcb_port_595")
routes.push(trace("twoaa_sys_sources", "source_net_6", ["pcb_port_449", "pcb_port_595"], [
wire(usb.x, usb.y, 0.5, "top", { start_pcb_port_id: "pcb_port_449" }),
wire(-21.5, -33.3, 0.5, "top"), via(-21.5, -33.3, "top", "inner1"),
wire(-21.5, -33.3, 0.5, "inner1"), wire(-27.5, -33.3, 0.5, "inner1"),
wire(-30.5, -38.2, 0.5, "inner1"), wire(battery.x, -38.2, 0.5, "inner1"),
via(battery.x, -38.2, "inner1", "top"),
wire(battery.x, -38.2, 0.5, "top"),
wire(battery.x, battery.y, 0.5, "top", { end_pcb_port_id: "pcb_port_595" }),
]))
}
// Reviewed SYS_IN route. It escapes below the SS34, changes layers after
// clearing both diode pads, crosses the control buses on inner2, and comes
// back to the input capacitor through a clear bottom-side corridor.
{
const usb = point("pcb_port_449")
const inputCap = point("pcb_port_468")
routes.push(trace("twoaa_sys_input", "source_net_6", ["pcb_port_449", "pcb_port_468"], [
wire(usb.x, usb.y, 0.5, "top", { start_pcb_port_id: "pcb_port_449" }),
wire(-17.2, -34, 0.5, "top"),
via(-17.2, -34, "top", "bottom"),
wire(-17.2, -34, 0.5, "bottom"),
wire(-15.94, -34, 0.5, "bottom"),
via(-15.94, -34, "bottom", "inner2"),
wire(-15.94, -34, 0.5, "inner2"),
wire(-15.94, -18.50269346877618, 0.5, "inner2"),
wire(-14.509878649533757, -18.50269346877618, 0.5, "inner2"),
via(-14.509878649533757, -18.50269346877618, "inner2", "bottom"),
wire(-14.509878649533757, -18.50269346877618, 0.5, "bottom"),
wire(-14.509878649533757, -14, 0.5, "bottom"),
wire(-18, -14, 0.5, "bottom"),
via(-18, -14, "bottom", "top"),
wire(-18, -14, 0.5, "top"),
wire(-18, inputCap.y, 0.5, "top"),
wire(inputCap.x, inputCap.y, 0.5, "top", { end_pcb_port_id: "pcb_port_468" }),
]))
}
// A short trace record at each new ground pad lets netlist verification
// see the terminal-to-pour connection without laying unnecessary copper
// across the switching island. Both vertices stay inside the same pad.
for (const groundPort of [
"pcb_port_439", "pcb_port_452", "pcb_port_469", "pcb_port_471",
"pcb_port_473", "pcb_port_475", "pcb_port_556", "pcb_port_581",
"pcb_port_588", "pcb_port_589", "pcb_port_598", "pcb_port_600",
"pcb_port_602", "pcb_port_606",
]) {
const terminal = point(groundPort)
routes.push(trace(`twoaa_ground_stub_${groundPort}`, "source_net_0", [groundPort], [
wire(terminal.x, terminal.y, 0.12, "top", { start_pcb_port_id: groundPort }),
wire(terminal.x + 0.02, terminal.y, 0.12, "top"),
]))
}
const dynamicObstacles = [
...(input.obstacles ?? []),
...[...fixedTraces, ...routes].flatMap((fixedTrace: any) => fixedTraceToObstacles(fixedTrace, [])),
]
const routeBranch = (
id: string,
connectionName: string,
startId: string,
endId: string,
width: number,
) => {
const normalizePoint = (id: string) => {
const terminal = point(id)
const { layers, ...withoutLayers } = terminal
return terminal.layer
? withoutLayers
: { ...withoutLayers, layer: layers?.[0] ?? "top" }
}
const branch = {
name: connectionName,
nominalTraceWidth: width,
pointsToConnect: [normalizePoint(startId), normalizePoint(endId)],
}
const boardBounds = input.bounds
const endpointXs = branch.pointsToConnect.map((terminal: any) => terminal.x)
const endpointYs = branch.pointsToConnect.map((terminal: any) => terminal.y)
const localMargin = id === "twoaa_sys_input" ? 10 : id === "twoaa_v3v3_trunk" ? 12 : 6
const localBounds = {
minX: Math.max(boardBounds.minX, Math.min(...endpointXs) - localMargin),
maxX: Math.min(boardBounds.maxX, Math.max(...endpointXs) + localMargin),
minY: Math.max(boardBounds.minY, Math.min(...endpointYs) - localMargin),
maxY: Math.min(boardBounds.maxY, Math.max(...endpointYs) + localMargin),
}
let result: any = null
console.log(`[two-aa] routing ${id}`)
for (const attempt of [
{ bounds: localBounds, gridStep: 0.15, maxIterations: 80_000, viaCost: 5 },
{ bounds: localBounds, gridStep: 0.1, maxIterations: 160_000, viaCost: 3 },
]) {
const solver = new MultilayerIjump({
input: {
...input,
bounds: attempt.bounds,
connections: [branch],
traces: [],
obstacles: dynamicObstacles.filter((obstacle: any) => {
const halfWidth = (obstacle.width ?? 0) / 2
const halfHeight = (obstacle.height ?? 0) / 2
return obstacle.center.x + halfWidth >= attempt.bounds.minX
&& obstacle.center.x - halfWidth <= attempt.bounds.maxX
&& obstacle.center.y + halfHeight >= attempt.bounds.minY
&& obstacle.center.y - halfHeight <= attempt.bounds.maxY
}),
minTraceWidth: width,
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
min_via_hole_diameter: 0.3,
min_via_pad_diameter: 0.45,
allowViaInPad: true,
},
GRID_STEP: attempt.gridStep,
OBSTACLE_MARGIN: 0.12,
MAX_ITERATIONS: attempt.maxIterations,
VIA_COST: attempt.viaCost,
optimizeWithGoalBoxes: false,
})
result = solver.solve()[0]
if (result?.solved) break
}
if (!result?.solved) throw new Error(`Two-AA power branch solve failed: ${id}`)
const route: any[] = []
for (let index = 0; index < result.route.length; index += 1) {
const routePoint = result.route[index]
const previous = result.route[index - 1]
if (previous && previous.layer !== routePoint.layer) {
route.push(via(routePoint.x, routePoint.y, previous.layer, routePoint.layer))
}
route.push(wire(routePoint.x, routePoint.y, width, routePoint.layer))
}
route[0].start_pcb_port_id = startId
route.at(-1).end_pcb_port_id = endId
const solvedTrace = trace(id, connectionName, [startId, endId], route)
dynamicObstacles.push(...fixedTraceToObstacles(solvedTrace, []))
routes.push(solvedTrace)
}
const routeCoordinateBranch = (
id: string,
connectionName: string,
start: { x: number, y: number, layer: string },
end: { x: number, y: number, layer: string },
width: number,
) => {
const branch = {
name: connectionName,
nominalTraceWidth: width,
pointsToConnect: [
{ ...start, pcb_port_id: "pcb_port_449" },
{ ...end, pcb_port_id: "pcb_port_468" },
],
}
let result: any = null
console.log(`[two-aa] routing ${id}`)
for (const attempt of [
{ gridStep: 0.15, maxIterations: 180_000, viaCost: 20 },
{ gridStep: 0.1, maxIterations: 300_000, viaCost: 12 },
]) {
const solver = new MultilayerIjump({
input: {
...input,
bounds: { minX: -35, maxX: -5, minY: -40, maxY: -10 },
connections: [branch],
traces: [],
obstacles: dynamicObstacles,
minTraceWidth: width,
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
allowViaInPad: true,
},
GRID_STEP: attempt.gridStep,
OBSTACLE_MARGIN: 0.12,
MAX_ITERATIONS: attempt.maxIterations,
VIA_COST: attempt.viaCost,
optimizeWithGoalBoxes: false,
})
result = solver.solve()[0]
if (result?.solved) break
}
if (!result?.solved) throw new Error(`Two-AA coordinate branch solve failed: ${id}`)
const route: any[] = []
for (let index = 0; index < result.route.length; index += 1) {
const routePoint = result.route[index]
const previous = result.route[index - 1]
if (previous && previous.layer !== routePoint.layer)
route.push(via(routePoint.x, routePoint.y, previous.layer, routePoint.layer))
route.push(wire(routePoint.x, routePoint.y, width, routePoint.layer))
}
routes.push(trace(id, connectionName, ["pcb_port_449", "pcb_port_468"], route))
}
// Freeze the solved V3V3 branch so local and cloud builds are deterministic.
routes.push(trace("twoaa_v3v3_trunk", "source_net_7", ["pcb_port_472", "pcb_port_490"], [
wire(-24, -24.5, 0.5, "top", { start_pcb_port_id: "pcb_port_472" }),
wire(-25.75, -24.5, 0.5, "top"),
wire(-25.75, -28.34, 0.5, "top"),
wire(-29.7125, -28.34, 0.5, "top"),
wire(-29.7125, -26.06, 0.5, "top"),
wire(-31.06, -26.06, 0.5, "top"),
wire(-31.06, -16.80995, 0.5, "top"),
wire(-29.5, -16.80995, 0.5, "top"),
wire(-29.5, -5, 0.5, "top"),
wire(-29.8, -4.7, 0.5, "top"),
wire(-29.8, 2.889999999999997, 0.5, "top"),
wire(-29.7125, 2.889999999999997, 0.5, "top", { end_pcb_port_id: "pcb_port_490" }),
]))
// Every ground terminal lands directly into the four-layer GND pours using
// the core's thermal reliefs. Extra point-to-point ground traces here are
// actively harmful: they cut across the feedback and switch-node escapes.
return routes
}
/** Route the deliberately local RGB-status island against the complete,
* already-reviewed carrier. These branches are kept out of the legacy route
* snapshot so adding the indicator cannot renumber or perturb established
* control, USB, LCD or power copper. */
function createStatusIndicatorRoutes(input: any, fixedTraces: any[]) {
const pointById = new Map<string, any>()
for (const connection of input.connections ?? [])
for (const point of connection.pointsToConnect ?? [])
if (point.pcb_port_id) pointById.set(point.pcb_port_id, point)
const point = (id: string) => {
const value = pointById.get(id)
if (!value) throw new Error(`Missing RGB status terminal ${id}`)
const { layers, ...withoutLayers } = value
return value.layer ? withoutLayers : { ...withoutLayers, layer: layers?.[0] ?? "top" }
}
const trace = (id: string, connectionName: string, startId: string, endId: string, route: any[]) => ({
type: "pcb_trace",
pcb_trace_id: `${id}_0`,
connection_name: connectionName,
connectsTo: [startId, endId],
route,
})
const routes: any[] = []
// Channel current-limit branches: expander output -> resistor cathode.
// These are deliberately frozen after checking every segment against all
// four copper layers. Keeping them deterministic prevents a cosmetic LED
// from perturbing the reviewed LCD, USB, control, audio or power routing.
{
const start = point("pcb_port_353")
const end = point("pcb_port_614")
routes.push(trace("status_green_io", "source_net_19", "pcb_port_353", "pcb_port_614", [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: "pcb_port_353" }),
wire(12.32512, -19.49947, 0.12, "top"),
wire(13.611501, -19.49947, 0.12, "top"),
wire(13.611501, -18.5, 0.12, "top"),
wire(13.829151, -18.5, 0.12, "top"),
wire(13.829151, -18.28, 0.12, "top"),
wire(15.021701, -18.28, 0.12, "top"),
wire(15.021701, -17.87162, 0.12, "top"),
via(15.021701, -17.87162, "top", "inner1"),
wire(15.021701, -17.87162, 0.12, "inner1"),
wire(15.021701, -16.901973, 0.12, "inner1"),
wire(3.325, -16.901973, 0.12, "inner1"),
via(3.325, -16.901973, "inner1", "top"),
wire(3.325, -16.901973, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: "pcb_port_614" }),
]))
}
{
const start = point("pcb_port_354")
const end = point("pcb_port_616")
routes.push(trace("status_blue_io", "source_net_20", "pcb_port_354", "pcb_port_616", [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: "pcb_port_354" }),
wire(11.675134, -21, 0.12, "top"),
wire(11.675134, -21.42, 0.12, "top"),
wire(9.944922, -21.42, 0.12, "top"),
wire(9.944922, -22.911976, 0.12, "top"),
wire(8.166445, -22.911976, 0.12, "top"),
wire(8.166445, -22.393429, 0.12, "top"),
wire(8.331445, -22.393429, 0.12, "top"),
wire(8.331445, -21.347903, 0.12, "top"),
wire(3.325, -21.347903, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: "pcb_port_616" }),
]))
}
// Freeze the four short package connections into measured clear corridors.
// The generic solver otherwise takes unnecessary long detours around the
// exact 1.6 mm LED courtyard. All layer changes are ordinary 0.30/0.45 mm
// through-vias, placed at least 0.10 mm from existing copper on every layer.
{
const start = point("pcb_port_607")
const end = point("pcb_port_360")
routes.push(trace("status_anode", "source_net_7", "pcb_port_607", "pcb_port_360", [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: "pcb_port_607" }),
wire(-7.8, -19, 0.12, "top"),
via(-7.8, -19, "top", "inner2"),
wire(-7.8, -19, 0.12, "inner2"),
wire(-7.8, -18, 0.12, "inner2"),
wire(4.5, -18, 0.12, "inner2"),
via(4.5, -18, "inner2", "top"),
wire(4.5, -18, 0.12, "top"),
wire(4.5, -19, 0.12, "top"),
wire(7.7, -19, 0.12, "top"),
wire(7.7, -17.079884, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: "pcb_port_360" }),
]))
}
{
const start = point("pcb_port_608")
const end = point("pcb_port_611")
routes.push(trace("status_red_led", "source_trace_420", "pcb_port_608", "pcb_port_611", [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: "pcb_port_608" }),
wire(-7.8, -17.2, 0.12, "top"),
wire(-11.6, -17.2, 0.12, "top"),
wire(-11.6, -16.5, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: "pcb_port_611" }),
]))
}
{
const start = point("pcb_port_610")
const end = point("pcb_port_615")
routes.push(trace("status_blue_led", "source_trace_424", "pcb_port_610", "pcb_port_615", [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: "pcb_port_610" }),
wire(-5, -19, 0.12, "top"),
via(-5, -19, "top", "inner2"),
wire(-5, -19, 0.12, "inner2"),
wire(-5, -21.2, 0.12, "inner2"),
wire(0.6, -21.2, 0.12, "inner2"),
via(0.6, -21.2, "inner2", "top"),
wire(0.6, -21.2, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: "pcb_port_615" }),
]))
}
{
const start = point("pcb_port_609")
const end = point("pcb_port_613")
routes.push(trace("status_green_led", "source_trace_422", "pcb_port_609", "pcb_port_613", [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: "pcb_port_609" }),
wire(-5.2, -17.2, 0.12, "top"),
wire(-3.64, -17.2, 0.12, "top"),
wire(-3.64, -11.9, 0.12, "top"),
wire(-0.5, -11.9, 0.12, "top"),
wire(-0.5, -12.6, 0.12, "top"),
wire(0.8, -12.6, 0.12, "top"),
wire(1.675, -12.6, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: "pcb_port_613" }),
]))
}
// Let the multilayer solver choose the two non-local branches after the
// short package escapes above have been frozen. Solving these last means
// it sees every reviewed legacy trace plus the indicator's fixed copper,
// instead of silently cutting across an existing control or SPI route.
const dynamicObstacles = [
...(input.obstacles ?? []),
...fixedTraces.flatMap((fixedTrace: any) => fixedTraceToObstacles(fixedTrace, [])),
...routes.flatMap((fixedTrace: any) => fixedTraceToObstacles(fixedTrace, [])),
]
const routeDynamic = (id: string, connectionName: string, startId: string, endId: string) => {
const start = point(startId)
const end = point(endId)
const bounds = {
minX: Math.max(input.bounds.minX, Math.min(start.x, end.x) - 8),
maxX: Math.min(input.bounds.maxX, Math.max(start.x, end.x) + 8),
minY: Math.max(input.bounds.minY, Math.min(start.y, end.y) - 8),
maxY: Math.min(input.bounds.maxY, Math.max(start.y, end.y) + 8),
}
let result: any = null
console.log(`[status] routing ${id}`)
for (const attempt of [
{ gridStep: 0.1, maxIterations: 260_000, viaCost: 7 },
{ gridStep: 0.075, maxIterations: 420_000, viaCost: 5 },
]) {
const solver = new MultilayerIjump({
input: {
...input,
bounds,
connections: [{
name: connectionName,
nominalTraceWidth: 0.12,
pointsToConnect: [start, end],
}],
traces: [],
obstacles: dynamicObstacles.filter((obstacle: any) => {
const halfWidth = (obstacle.width ?? 0) / 2
const halfHeight = (obstacle.height ?? 0) / 2
return obstacle.center.x + halfWidth >= bounds.minX
&& obstacle.center.x - halfWidth <= bounds.maxX
&& obstacle.center.y + halfHeight >= bounds.minY
&& obstacle.center.y - halfHeight <= bounds.maxY
}),
minTraceWidth: 0.12,
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
min_via_hole_diameter: 0.3,
min_via_pad_diameter: 0.45,
allowViaInPad: true,
},
GRID_STEP: attempt.gridStep,
OBSTACLE_MARGIN: 0.16,
MAX_ITERATIONS: attempt.maxIterations,
VIA_COST: attempt.viaCost,
optimizeWithGoalBoxes: false,
})
result = solver.solve()[0]
if (result?.solved) break
}
if (!result?.solved) throw new Error(`RGB status branch solve failed: ${id}`)
const route: any[] = []
for (let index = 0; index < result.route.length; index += 1) {
const routePoint = result.route[index]
const previous = result.route[index - 1]
if (previous && previous.layer !== routePoint.layer)
route.push(via(routePoint.x, routePoint.y, previous.layer, routePoint.layer))
route.push(wire(routePoint.x, routePoint.y, 0.12, routePoint.layer))
}
route[0].start_pcb_port_id = startId
route.at(-1).end_pcb_port_id = endId
const solved = trace(id, connectionName, startId, endId, route)
routes.push(solved)
dynamicObstacles.push(...fixedTraceToObstacles(solved, []))
}
routeDynamic("status_red_io", "source_net_18", "pcb_port_352", "pcb_port_612")
return routes
}
/** Reviewed top-side backlight copper. The electrical transitions use the
* available routing layers, while every `via()` is emitted as the board's
* standard 0.30 mm drill / 0.45 mm pad plated through-via. */
function createTopBacklightRoutes() {
const trace = (
id: string,
connectsTo: string[],
route: any[],
) => ({
type: "pcb_trace",
pcb_trace_id: `${id}_0`,
connection_name: id,
connectsTo,
route,
})
return [
trace("source_trace_361", ["pcb_port_551", "pcb_port_472"], [
wire(-23.7, 23.95, 0.5, "top", { start_pcb_port_id: "pcb_port_551" }),
wire(-21, 25.5, 0.5, "top"),
via(-21, 25.5, "top", "bottom"),
wire(-21, 25.5, 0.5, "bottom"),
wire(-20, 10.05, 0.5, "bottom"),
via(-20, 10.05, "bottom", "top"),
wire(-20, 10.05, 0.5, "top"),
wire(-18.7125, 10.05, 0.5, "top", { end_pcb_port_id: "pcb_port_472" }),
]),
trace("source_trace_362", ["pcb_port_549", "pcb_port_551"], [
wire(-23.7, 22.05, 0.12, "top", { start_pcb_port_id: "pcb_port_549" }),
via(-23.7, 22.05, "top", "inner2"),
wire(-23.7, 22.05, 0.12, "inner2"),
wire(-22.5, 25.5, 0.12, "inner2"),
via(-22.5, 25.5, "inner2", "top"),
wire(-22.5, 25.5, 0.12, "top"),
wire(-23.7, 23.95, 0.12, "top", { end_pcb_port_id: "pcb_port_551" }),
]),
trace("source_trace_363", ["pcb_port_547", "pcb_port_557"], [
wire(-26.3, 23, 0.12, "top", { start_pcb_port_id: "pcb_port_547" }),
via(-26.3, 23, "top", "inner1"),
wire(-26.3, 23, 0.12, "inner1"),
wire(-28, 23, 0.12, "inner1"),
wire(-28, 19.4125, 0.12, "inner1"),
wire(-30, 19.4125, 0.12, "inner1"),
via(-30, 19.4125, "inner1", "top"),
wire(-30, 19.4125, 0.12, "top", { end_pcb_port_id: "pcb_port_557" }),
]),
trace("source_trace_364", ["pcb_port_551", "pcb_port_552"], [
wire(-23.7, 23.95, 0.5, "top", { start_pcb_port_id: "pcb_port_551" }),
via(-23.7, 23.95, "top", "inner1"),
wire(-23.7, 23.95, 0.5, "inner1"),
wire(-35, 23, 0.5, "inner1"),
via(-35, 23, "inner1", "top"),
wire(-35, 23, 0.5, "top"),
wire(-32.5, 23, 0.5, "top", { end_pcb_port_id: "pcb_port_552" }),
]),
trace("source_trace_365", ["pcb_port_551", "pcb_port_556"], [
wire(-23.7, 23.95, 0.5, "top", { start_pcb_port_id: "pcb_port_551" }),
wire(-20, 25.5, 0.5, "top"),
via(-20, 25.5, "top", "bottom"),
wire(-20, 25.5, 0.5, "bottom"),
wire(-30, 16.5, 0.5, "bottom"),
via(-30, 16.5, "bottom", "top"),
wire(-30, 16.5, 0.5, "top"),
wire(-30, 17.5875, 0.5, "top", { end_pcb_port_id: "pcb_port_556" }),
]),
trace("source_trace_366", ["pcb_port_557", "pcb_port_486"], [
wire(-30, 19.4125, 0.12, "top", { start_pcb_port_id: "pcb_port_557" }),
wire(-34, 18, 0.12, "top"),
via(-34, 18, "top", "inner1"),
wire(-34, 18, 0.12, "inner1"),
wire(-34, 13, 0.12, "inner1"),
wire(-31.7, 13, 0.12, "inner1"),
via(-31.7, 13, "inner1", "top"),
wire(-31.7, 13, 0.12, "top", { end_pcb_port_id: "pcb_port_486" }),
]),
trace("source_trace_367", ["pcb_port_546", "pcb_port_553"], [
wire(-26.3, 23.95, 0.5, "top", { start_pcb_port_id: "pcb_port_546" }),
wire(-26.3, 25.5, 0.5, "top"),
wire(-29.5, 25.5, 0.5, "top"),
wire(-29.5, 23, 0.5, "top", { end_pcb_port_id: "pcb_port_553" }),
]),
trace("source_trace_368", ["pcb_port_546", "pcb_port_555"], [
wire(-26.3, 23.95, 0.5, "top", { start_pcb_port_id: "pcb_port_546" }),
wire(-26.3, 26.5, 0.5, "top"),
wire(-19, 26.5, 0.5, "top"),
wire(-19, 24.7, 0.5, "top", { end_pcb_port_id: "pcb_port_555" }),
]),
trace("source_trace_369", ["pcb_port_297", "pcb_port_554"], [
wire(34.9625, 7, 0.12, "top", { start_pcb_port_id: "pcb_port_297" }),
wire(34.9625, 6.5, 0.12, "top"),
via(34.9625, 6.5, "top", "bottom"),
wire(34.9625, 6.5, 0.12, "bottom"),
wire(23.5, 7, 0.12, "bottom"),
wire(11, 15.25, 0.12, "bottom"),
wire(7.5, 18.75, 0.12, "bottom"),
wire(5.75, 18.5, 0.12, "bottom"),
wire(-3.25, 18.75, 0.12, "bottom"),
via(-3.25, 18.75, "bottom", "inner1"),
wire(-3.25, 18.75, 0.12, "inner1"),
wire(-6.25, 19.25, 0.12, "inner1"),
wire(-16.5, 20, 0.12, "inner1"),
wire(-20, 20, 0.12, "inner1"),
via(-20, 20, "inner1", "top"),
wire(-20, 20, 0.12, "top"),
wire(-19, 21.3, 0.12, "top", { end_pcb_port_id: "pcb_port_554" }),
]),
trace("source_trace_370", ["pcb_port_550", "pcb_port_554"], [
wire(-23.7, 23, 0.12, "top", { start_pcb_port_id: "pcb_port_550" }),
wire(-21.5, 23, 0.12, "top"),
wire(-21.5, 21.3, 0.12, "top"),
wire(-19, 21.3, 0.12, "top", { end_pcb_port_id: "pcb_port_554" }),
]),
trace("source_trace_371", ["pcb_port_558", "pcb_port_554"], [
wire(-18, 17.0875, 0.5, "top", { start_pcb_port_id: "pcb_port_558" }),
wire(-20, 17.0875, 0.5, "top"),
wire(-20, 21.3, 0.5, "top"),
wire(-19, 21.3, 0.5, "top", { end_pcb_port_id: "pcb_port_554" }),
]),
trace("source_trace_372", ["pcb_port_559", "pcb_port_557"], [
wire(-18, 18.9125, 0.12, "top", { start_pcb_port_id: "pcb_port_559" }),
via(-18, 18.9125, "top", "inner2"),
wire(-18, 18.9125, 0.12, "inner2"),
wire(-30, 19.4125, 0.12, "inner2"),
via(-30, 19.4125, "inner2", "top"),
wire(-30, 19.4125, 0.12, "top", { end_pcb_port_id: "pcb_port_557" }),
]),
trace("source_trace_373", ["pcb_port_548", "pcb_port_298"], [
wire(-26.3, 22.05, 0.12, "top", { start_pcb_port_id: "pcb_port_548" }),
via(-26.3, 22.05, "top", "inner2"),
wire(-26.3, 22.05, 0.12, "inner2"),
wire(-28, 21, 0.12, "inner2"),
wire(-18, 21, 0.12, "inner2"),
wire(-12.5, 26.5, 0.12, "inner2"),
wire(-12, 26.5, 0.12, "inner2"),
wire(-10.75, 23.75, 0.12, "inner2"),
wire(-8, 21.5, 0.12, "inner2"),
wire(-6.5, 20.5, 0.12, "inner2"),
wire(-2.25, 20.5, 0.12, "inner2"),
wire(0.5, 17.75, 0.12, "inner2"),
via(0.5, 17.75, "inner2", "bottom"),
wire(0.5, 17.75, 0.12, "bottom"),
wire(5.5, 16.75, 0.12, "bottom"),
wire(6.75, 16.25, 0.12, "bottom"),
wire(7.5, 15, 0.12, "bottom"),
wire(10, 13, 0.12, "bottom"),
wire(33, 2, 0.12, "bottom"),
wire(36, 0, 0.12, "bottom"),
via(36, 0, "bottom", "top"),
wire(36, 0, 0.12, "top"),
wire(33.5, 0, 0.12, "top"),
wire(33.5, 1.500002, 0.12, "top", { end_pcb_port_id: "pcb_port_298" }),
]),
trace("source_trace_374", ["pcb_port_548", "pcb_port_560"], [
wire(-26.3, 22.05, 0.12, "top", { start_pcb_port_id: "pcb_port_548" }),
wire(-26.3, 20.5, 0.12, "top"),
wire(-24.4625, 20.5, 0.12, "top"),
wire(-24.4625, 19, 0.12, "top", { end_pcb_port_id: "pcb_port_560" }),
]),
trace("source_trace_375", ["pcb_port_561", "pcb_port_562"], [
wire(-21.5375, 19, 0.12, "top", { start_pcb_port_id: "pcb_port_561" }),
wire(-21.5375, 16.5, 0.12, "top", { end_pcb_port_id: "pcb_port_562" }),
]),
trace("source_trace_376", ["pcb_port_563", "pcb_port_557"], [
wire(-24.4625, 16.5, 0.12, "top", { start_pcb_port_id: "pcb_port_563" }),
wire(-29, 16.5, 0.12, "top"),
wire(-29, 19.4125, 0.12, "top"),
wire(-30, 19.4125, 0.12, "top", { end_pcb_port_id: "pcb_port_557" }),
]),
]
}
/**
* Schematic-sheet net labels can change generated source_net numbers without
* changing a single PCB terminal. The checked copper is keyed by terminal
* IDs, so bind every saved branch back to the current SimpleRoute connection
* before returning it. This keeps the physical solution stable while making
* schematic-only organization safe.
*/
function bindSavedRoutesToCurrentConnections(routes: any[], input: any) {
const connections = input?.connections ?? []
const portIdsFor = (connection: any) => new Set(
(connection.pointsToConnect ?? [])
.map((point: any) => point.pcb_port_id)
.filter(Boolean),
)
const indexed = connections.map((connection: any) => ({
connection,
portIds: portIdsFor(connection),
}))
return routes.map((route) => {
const endpointIds = (route.connectsTo ?? []).filter((id: string) => id?.startsWith("pcb_port_"))
if (endpointIds.length < 2) return route
const match = indexed.find(({ portIds }: any) => endpointIds.every((id: string) => portIds.has(id)))
if (!match) return route
const oldName = route.connection_name
const currentName = match.connection.name
return {
...route,
pcb_trace_id: route.pcb_trace_id.startsWith(`${oldName}_`)
? `${currentName}${route.pcb_trace_id.slice(oldName.length)}`
: route.pcb_trace_id,
connection_name: currentName,
}
})
}
/** The TPS63020 has nine more physical terminals than the former AP63203.
* Every established component after the regulator therefore moves forward by
* nine generated PCB-port IDs. Translate the legacy carrier at the replay
* boundary instead of rewriting hundreds of reviewed route endpoints. */
function translateLegacyCarrierPortIds(routes: any[]) {
const translate = (id: string | undefined) => {
if (!id?.startsWith("pcb_port_")) return id
const number = Number(id.slice("pcb_port_".length))
return number >= 457 ? `pcb_port_${number + 9}` : id
}
return routes.map((route) => ({
...route,
connectsTo: (route.connectsTo ?? []).map(translate),
route: (route.route ?? []).map((point: any) => ({
...point,
start_pcb_port_id: translate(point.start_pcb_port_id),
end_pcb_port_id: translate(point.end_pcb_port_id),
})),
}))
}
/** These terminals belonged to the removed battery Schottky and AP63203
* switching island. All other saved copper remains valid after the ID
* translation above; only this compact island is replaced. */
function isRemovedLegacyPowerRoute(route: any) {
const legacyPowerPort = (id: string) => {
const number = Number(id?.replace("pcb_port_", ""))
return number === 447 || number === 448 || (number >= 451 && number <= 466)
}
return (route.connectsTo ?? []).some(legacyPowerPort)
}
/** Final hook for reviewed route-wide transforms. */
function finalizeReviewedRouteWidths(routes: any[]) {
return routes
}
function fixedTraceToObstacles(trace: any, extraConnectedTo: string[]) {
const connectedTo = [...new Set([trace.connection_name, ...extraConnectedTo])]
const obstacles: any[] = []
for (const point of trace.route ?? []) {
if (point.route_type !== "via") continue
obstacles.push({
type: "rect",
layers: ["top", "inner1", "inner2", "bottom"],
center: { x: point.x, y: point.y },
width: point.via_diameter ?? 0.45,
height: point.via_diameter ?? 0.45,
connectedTo,
})
}
for (let index = 0; index < (trace.route?.length ?? 0) - 1; index += 1) {
const start = trace.route[index]
const end = trace.route[index + 1]
if (start.route_type !== "wire" || end.route_type !== "wire" || start.layer !== end.layer) continue
const dx = end.x - start.x
const dy = end.y - start.y
const length = Math.hypot(dx, dy)
const segmentCount = Math.max(1, Math.ceil(length / 0.5))
const width = Math.max(start.width ?? 0.12, end.width ?? 0.12)
for (let segment = 0; segment < segmentCount; segment += 1) {
const t0 = segment / segmentCount
const t1 = (segment + 1) / segmentCount
const x0 = start.x + dx * t0
const y0 = start.y + dy * t0
const x1 = start.x + dx * t1
const y1 = start.y + dy * t1
obstacles.push({
type: "rect",
layers: [start.layer],
center: { x: (x0 + x1) / 2, y: (y0 + y1) / 2 },
width: Math.abs(x1 - x0) + width,
height: Math.abs(y1 - y0) + width,
connectedTo,
})
}
}
return obstacles
}
/** Rebuild only the branches whose physical terminals moved with the centred
* Start/Select controls or tangent volume wheel. The remainder of the reviewed
* carrier stays byte-for-byte stable, while these branches are solved against
* the current component courtyards and reviewed copper as fixed obstacles. */
function rebuildMovedControlRoutes(input: any, reviewedTraces: any[]) {
const movedPortIds = new Set([
...Array.from({ length: 8 }, (_, index) => `pcb_port_${394 + index}`),
])
const buttonPortIds = new Set(Array.from({ length: 8 }, (_, index) => `pcb_port_${394 + index}`))
const pointByPortId = new Map<string, any>()
for (const connection of input.connections ?? [])
for (const point of connection.pointsToConnect ?? [])
if (point.pcb_port_id) pointByPortId.set(point.pcb_port_id, point)
// C_IO moved only far enough to clear the centred controls. Keep this
// decoupler connection short and deterministic instead of asking the maze
// router to escape through the narrow U_IO corner. Its ground pad and the
// U_IO strap pads connect directly to the top-layer ground pour, so the two
// obsolete point-to-point ground branches are intentionally removed.
const ioCapPositive = reviewedTraces.find((trace: any) =>
(trace.connectsTo ?? []).includes("pcb_port_360")
&& (trace.connectsTo ?? []).includes("pcb_port_359"))
if (!ioCapPositive) throw new Error("Reviewed C_IO positive branch was not found")
const capPositive = pointByPortId.get("pcb_port_360")
const ioVcc = pointByPortId.get("pcb_port_359")
const rebuiltIoCapPositive = {
...ioCapPositive,
route: [
wire(capPositive.x, capPositive.y, 0.12, "top", { start_pcb_port_id: "pcb_port_360" }),
wire(7.7, capPositive.y, 0.12, "top"),
wire(7.7, ioVcc.y, 0.12, "top"),
wire(ioVcc.x, ioVcc.y, 0.12, "top", { end_pcb_port_id: "pcb_port_359" }),
],
}
const ioCapGround = reviewedTraces.find((trace: any) =>
(trace.connectsTo ?? []).includes("pcb_port_361")
&& (trace.connectsTo ?? []).includes("pcb_port_356"))
if (!ioCapGround) throw new Error("Reviewed C_IO ground branch was not found")
const capGround = pointByPortId.get("pcb_port_361")
const rebuiltIoCapGround = {
...ioCapGround,
connectsTo: ["pcb_port_361"],
route: [
wire(capGround.x, capGround.y, 0.12, "top", { start_pcb_port_id: "pcb_port_361" }),
wire(6.75, -18.25, 0.12, "top"),
via(6.75, -18.25, "top", "inner1"),
wire(6.75, -18.25, 0.12, "inner1"),
],
}
const reviewedWithIoCap = [
...reviewedTraces.filter((trace: any) =>
trace !== ioCapPositive && !(trace.connectsTo ?? []).includes("pcb_port_361")),
rebuiltIoCapPositive,
rebuiltIoCapGround,
]
const affected = reviewedWithIoCap.filter((trace: any) =>
(trace.connectsTo ?? []).some((id: string) => movedPortIds.has(id)))
if (affected.length === 0) throw new Error("No moved-control carrier branches were found")
// Ground is already a thermally relieved pour on every copper layer. The old
// long switch-return branches merely duplicated that plane and become
// counterproductive after centring the controls, so the pours provide the
// external return. The four local duplicate-pad links cross a dense USB and
// power corridor; give each an intentional inner1 dogleg with vias outside
// the switch body instead of accepting an opaque maze-router result.
const internalButtonBranches = affected
.filter((trace: any) => (trace.connectsTo ?? []).every((id: string) => buttonPortIds.has(id)))
.map((trace: any) => {
const [startId, endId] = trace.connectsTo
const start = pointByPortId.get(startId)
const end = pointByPortId.get(endId)
const centerX = (start.x + end.x) / 2
const isSignalSide = start.y > -26
const doglegY = centerX < -5
? (isSignalSide ? -25.8 : -27.0)
: (isSignalSide ? -22.0 : -29.2)
return {
...trace,
route: [
wire(start.x, start.y, 0.12, "top", { start_pcb_port_id: startId }),
wire(start.x, doglegY, 0.12, "top"),
via(start.x, doglegY, "top", "inner1"),
wire(start.x, doglegY, 0.12, "inner1"),
wire(end.x, doglegY, 0.12, "inner1"),
via(end.x, doglegY, "inner1", "top"),
wire(end.x, doglegY, 0.12, "top"),
wire(end.x, end.y, 0.12, "top", { end_pcb_port_id: endId }),
],
}
})
const buttonSignalBranches = affected
.filter((trace: any) => !trace.pcb_trace_id.startsWith("source_net_0_")
&& (trace.connectsTo ?? []).filter((id: string) => buttonPortIds.has(id)).length === 1)
.map((trace: any) => {
const buttonId = trace.connectsTo.find((id: string) => buttonPortIds.has(id))
const ioId = trace.connectsTo.find((id: string) => !buttonPortIds.has(id))
const button = pointByPortId.get(buttonId)
const io = pointByPortId.get(ioId)
if (buttonId === "pcb_port_401") {
return {
...trace,
route: [
wire(button.x, button.y, 0.12, "top", { start_pcb_port_id: buttonId }),
wire(button.x, -20.437904, 0.12, "top"),
wire(-4.068, -20.437904, 0.12, "top"),
wire(-4.068, -22.0084291684, 0.12, "top"),
wire(-2.5, -22.0084291684, 0.12, "top"),
wire(-2.5, -22.5, 0.12, "top"),
via(-2.5, -22.5, "top", "inner1"),
wire(-2.5, -22.5, 0.12, "inner1"),
wire(7.9464453981, -22.0084291684, 0.12, "inner1"),
via(7.9464453981, -22.0084291684, "inner1", "top"),
wire(7.9464453981, -22.0084291684, 0.12, "top"),
wire(7.9464453981, -24.109, 0.12, "top"),
wire(11.34, -24.109, 0.12, "top"),
wire(11.34, -24.975195, 0.12, "top"),
wire(io.x, -24.975195, 0.12, "top"),
wire(io.x, -23.76, 0.12, "top"),
wire(17.2236421818, -23.76, 0.12, "top"),
wire(17.2236421818, io.y, 0.12, "top"),
wire(18.1710502238, io.y, 0.12, "top"),
wire(18.1710502238, -18.5, 0.12, "top"),
wire(io.x, -18.5, 0.12, "top"),
wire(io.x, io.y, 0.12, "top", { end_pcb_port_id: ioId }),
],
}
}
if (buttonId === "pcb_port_396") {
return {
...trace,
route: [
wire(button.x, button.y, 0.12, "top", { start_pcb_port_id: buttonId }),
wire(-7.1, button.y, 0.12, "top"),
via(-7.1, button.y, "top", "inner1"),
wire(-7.1, button.y, 0.12, "inner1"),
wire(12.565, button.y, 0.12, "inner1"),
via(12.565, button.y, "inner1", "top"),
wire(12.565, button.y, 0.12, "top"),
wire(12.565, -22.7, 0.12, "top"),
wire(16.8, -22.7, 0.12, "top"),
wire(16.8, io.y, 0.12, "top"),
wire(io.x, io.y, 0.12, "top", { end_pcb_port_id: ioId }),
],
}
}
throw new Error(`Unexpected centred-button signal endpoint ${buttonId}`)
})
const redundantGroundBranches = affected.filter((trace: any) =>
trace.pcb_trace_id.startsWith("source_net_0_")
&& (trace.connectsTo ?? []).some((id: string) => buttonPortIds.has(id))
&& !internalButtonBranches.some((internal: any) => internal.pcb_trace_id === trace.pcb_trace_id))
const branchesToRoute = affected.filter((trace: any) =>
!internalButtonBranches.some((internal: any) => internal.pcb_trace_id === trace.pcb_trace_id)
&& !buttonSignalBranches.some((manual: any) => manual.pcb_trace_id === trace.pcb_trace_id)
&& !redundantGroundBranches.includes(trace))
const fixed = [
...reviewedWithIoCap.filter((trace: any) => !affected.includes(trace)),
...internalButtonBranches,
...buttonSignalBranches,
]
const branches = branchesToRoute.map((trace: any, index: number) => {
const endpointIds = (trace.connectsTo ?? []).filter((id: string) => pointByPortId.has(id))
if (endpointIds.length !== 2)
throw new Error(`Moved-control branch ${trace.pcb_trace_id} has ${endpointIds.length} current terminals`)
const savedTraceWidth = Math.max(...trace.route
.filter((point: any) => point.route_type === "wire")
.map((point: any) => point.width ?? 0.12))
const nominalTraceWidth = trace.pcb_trace_id.startsWith("source_net_0_")
? Math.min(savedTraceWidth, 0.2)
: savedTraceWidth
return {
name: `moved_control_branch_${index}`,
originalConnectionName: trace.connection_name,
originalTraceId: trace.pcb_trace_id,
nominalTraceWidth,
pointsToConnect: endpointIds.map((id: string) => {
const point = pointByPortId.get(id)
const { layers, ...withoutLayers } = point
return point.layer ? withoutLayers : { ...withoutLayers, layer: layers?.[0] ?? "top" }
}),
}
})
const branchNamesByOriginal = new Map<string, string[]>()
for (const branch of branches) {
const names = branchNamesByOriginal.get(branch.originalConnectionName) ?? []
names.push(branch.name)
branchNamesByOriginal.set(branch.originalConnectionName, names)
}
const fixedObstacles = fixed.flatMap((trace: any) => fixedTraceToObstacles(
trace,
branchNamesByOriginal.get(trace.connection_name) ?? [],
))
const componentObstacles = (input.obstacles ?? []).map((obstacle: any) => ({
...obstacle,
connectedTo: [
...(obstacle.connectedTo ?? []),
...branches
.filter((branch: any) => (obstacle.connectedTo ?? []).includes(branch.originalConnectionName))
.map((branch: any) => branch.name),
],
}))
const dynamicObstacles = [...componentObstacles, ...fixedObstacles]
const rebuilt: any[] = []
const orderedBranches = [...branches].sort((left: any, right: any) => {
const widthDifference = left.nominalTraceWidth - right.nominalTraceWidth
if (widthDifference !== 0) return widthDifference
const length = (branch: any) => Math.hypot(
branch.pointsToConnect[1].x - branch.pointsToConnect[0].x,
branch.pointsToConnect[1].y - branch.pointsToConnect[0].y,
)
return length(right) - length(left)
})
for (const [branchIndex, branch] of orderedBranches.entries()) {
console.log(`[control-carrier] routing ${branchIndex + 1}/${orderedBranches.length} ${branch.originalTraceId}`)
let result: any = null
const xs = branch.pointsToConnect.map((point: any) => point.x)
const ys = branch.pointsToConnect.map((point: any) => point.y)
const boardBounds = input.bounds
const localBounds = (padding: number) => ({
minX: Math.max(boardBounds.minX, Math.min(...xs) - padding),
maxX: Math.min(boardBounds.maxX, Math.max(...xs) + padding),
minY: Math.max(boardBounds.minY, Math.min(...ys) - padding),
maxY: Math.min(boardBounds.maxY, Math.max(...ys) + padding),
})
const attempts = [
{ bounds: localBounds(6), gridStep: 0.1, maxIterations: 60_000, viaCost: 5 },
{ bounds: localBounds(12), gridStep: 0.1, maxIterations: 120_000, viaCost: 3 },
{ bounds: boardBounds, gridStep: 0.1, maxIterations: 180_000, viaCost: 3 },
]
for (const attempt of attempts) {
const solver = new MultilayerIjump({
input: {
...input,
bounds: attempt.bounds,
connections: [branch],
traces: [],
obstacles: dynamicObstacles.filter((obstacle: any) => {
const halfWidth = (obstacle.width ?? 0) / 2
const halfHeight = (obstacle.height ?? 0) / 2
return obstacle.center.x + halfWidth >= attempt.bounds.minX
&& obstacle.center.x - halfWidth <= attempt.bounds.maxX
&& obstacle.center.y + halfHeight >= attempt.bounds.minY
&& obstacle.center.y - halfHeight <= attempt.bounds.maxY
}),
minTraceWidth: branch.nominalTraceWidth,
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
min_via_hole_diameter: 0.3,
min_via_pad_diameter: 0.45,
allowViaInPad: true,
},
GRID_STEP: attempt.gridStep,
// The router's geometric via model is slightly less conservative than
// tscircuit's final copper DRC. 0.30 mm keeps regenerated vias and
// traces outside the final 0.10 mm copper-clearance envelope.
OBSTACLE_MARGIN: 0.3,
MAX_ITERATIONS: attempt.maxIterations,
VIA_COST: attempt.viaCost,
optimizeWithGoalBoxes: false,
})
result = solver.solve()[0]
if (result?.solved) break
}
if (!result?.solved) throw new Error(`Moved-control branch solve failed: ${branch.originalTraceId}`)
const route: any[] = []
for (let index = 0; index < result.route.length; index += 1) {
const point = result.route[index]
const previous = result.route[index - 1]
if (previous && previous.layer !== point.layer)
route.push(via(point.x, point.y, previous.layer, point.layer))
route.push(wire(point.x, point.y, branch.nominalTraceWidth, point.layer))
}
route[0].start_pcb_port_id = branch.pointsToConnect[0].pcb_port_id
route.at(-1).end_pcb_port_id = branch.pointsToConnect[1].pcb_port_id
const trace = {
type: "pcb_trace",
pcb_trace_id: branch.originalTraceId,
connection_name: branch.originalConnectionName,
connectsTo: branch.pointsToConnect.map((point: any) => point.pcb_port_id),
route,
}
rebuilt.push(trace)
dynamicObstacles.push(...fixedTraceToObstacles(
trace,
branchNamesByOriginal.get(branch.originalConnectionName) ?? [],
))
}
if (rebuilt.length !== branches.length)
throw new Error(`Moved-control carrier rebuild solved ${rebuilt.length}/${branches.length} branches`)
return [...fixed, ...rebuilt]
}
/** Replays the checked carrier solution. Every build still runs tscircuit's
* copper, connectivity, clearance, DRC, and short-circuit validation. */
class CarrierAutorouter {
input: any
isRouting = false
private handlers: Record<EventName, Handler[]> = {
complete: [],
error: [],
progress: [],
}
constructor(input: any) { this.input = input }
on(event: EventName, callback: Handler) {
this.handlers[event].push(callback)
}
private getTraces() {
if (process.env.LINUX_GBA_DUMP_POWER_CONNECTIONS === "1") {
const powerIds = new Set([
...Array.from({ length: 25 }, (_, index) => `pcb_port_${442 + index}`),
...Array.from({ length: 13 }, (_, index) => `pcb_port_${594 + index}`),
])
console.log(`POWER_CONNECTIONS=${JSON.stringify((this.input.connections ?? []).filter((connection: any) =>
(connection.pointsToConnect ?? []).some((point: any) => powerIds.has(point.pcb_port_id))))}`)
}
if (process.env.LINUX_GBA_FULL_REBUILD === "1") {
const sanitized = structuredClone(this.input)
for (const connection of sanitized.connections ?? []) {
connection.pointsToConnect = (connection.pointsToConnect ?? []).map((point: any) => {
if (!point.layer) return point
const { layers, ...withoutLayers } = point
return withoutLayers
})
}
const solver = new AutoroutingPipelineSolver9_PreloadedTraceGraph({
...sanitized,
traces: [],
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
min_via_hole_diameter: 0.3,
min_via_pad_diameter: 0.45,
allowViaInPad: true,
}, {
effort: 100,
cacheProvider: null,
maxNodeDimension: 2,
minNodeArea: 0.08,
})
solver.solve()
if (solver.failed)
throw new Error(`Full carrier rebuild failed: ${solver.error ?? "unknown error"}`)
return solver.getOutputSimplifiedPcbTraces().map((trace: any) => ({
...trace,
route: trace.route.map((point: any) => point.route_type === "via"
? { ...point, via_diameter: 0.45, via_hole_diameter: 0.3 }
: point),
}))
}
if (process.env.LINUX_GBA_REBUILD_CARRIER === "1") {
const lcdIds = new Set(Array.from({ length: 52 }, (_, index) => `pcb_port_${240 + index}`))
const reviewed = applyClearanceRepairs(structuredClone(verifiedCarrierRoutes as any[]))
const allReviewed = bindSavedRoutesToCurrentConnections([
...reviewed,
...createTopBacklightRoutes(),
...createServiceConnectorRoutes(),
], this.input)
const socDisplayIds = new Set(Array.from({ length: 57 }, (_, index) => `pcb_port_${183 + index}`))
const allLcdRoutes = allReviewed.filter((trace: any) =>
(trace.connectsTo ?? []).some((id: string) => lcdIds.has(id)))
const affected = allLcdRoutes.filter((trace: any) =>
(trace.connectsTo ?? []).some((id: string) => lcdIds.has(id))
&& (trace.connectsTo ?? []).some((id: string) => socDisplayIds.has(id)))
const localLcdRoutes = allLcdRoutes.filter((trace: any) => !affected.includes(trace))
const fixed = allReviewed.filter((trace: any) => !allLcdRoutes.includes(trace))
const pointByPortId = new Map<string, any>()
for (const connection of this.input.connections ?? [])
for (const point of connection.pointsToConnect ?? [])
if (point.pcb_port_id) pointByPortId.set(point.pcb_port_id, point)
const branches = affected.map((trace: any, index: number) => {
const endpointIds = (trace.connectsTo ?? []).filter((id: string) => pointByPortId.has(id))
if (endpointIds.length !== 2)
throw new Error(`LCD branch ${trace.pcb_trace_id} has ${endpointIds.length} current terminals`)
return {
name: `lcd_branch_${index}`,
originalConnectionName: trace.connection_name,
originalTraceId: trace.pcb_trace_id,
nominalTraceWidth: Math.max(...trace.route
.filter((point: any) => point.route_type === "wire")
.map((point: any) => point.width ?? 0.12)),
pointsToConnect: endpointIds.map((id: string) => {
const point = pointByPortId.get(id)
const { layers, ...withoutLayers } = point
return point.layer
? withoutLayers
: { ...withoutLayers, layer: layers?.[0] ?? "top" }
}),
}
})
const branchNamesByOriginal = new Map<string, string[]>()
for (const branch of branches) {
const names = branchNamesByOriginal.get(branch.originalConnectionName) ?? []
names.push(branch.name)
branchNamesByOriginal.set(branch.originalConnectionName, names)
}
const fixedObstacles = fixed.flatMap((trace: any) => fixedTraceToObstacles(
trace,
branchNamesByOriginal.get(trace.connection_name) ?? [],
))
const componentObstacles = (this.input.obstacles ?? []).map((obstacle: any) => ({
...obstacle,
connectedTo: [
...(obstacle.connectedTo ?? []),
...branches
.filter((branch: any) => (obstacle.connectedTo ?? []).includes(branch.originalConnectionName))
.map((branch: any) => branch.name),
],
}))
if (process.env.LINUX_GBA_CAPACITY_SUBSET === "1") {
const solver = new AutoroutingPipelineSolver8({
...this.input,
connections: branches,
traces: [],
obstacles: [...componentObstacles, ...fixedObstacles],
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
min_via_hole_diameter: 0.3,
min_via_pad_diameter: 0.45,
allowViaInPad: true,
}, {
effort: 100,
cacheProvider: null,
maxNodeDimension: 2,
minNodeArea: 0.08,
})
solver.solve()
if (solver.failed) throw new Error(`LCD capacity rebuild failed: ${solver.error ?? "unknown error"}`)
const rebuilt = solver.getOutputSimplifiedPcbTraces().map((trace: any) => {
const branch = branches.find((candidate: any) => candidate.name === trace.connection_name)
if (!branch) throw new Error(`Unknown rebuilt LCD branch ${trace.connection_name}`)
return {
...trace,
pcb_trace_id: branch.originalTraceId,
connection_name: branch.originalConnectionName,
connectsTo: branch.pointsToConnect.map((point: any) => point.pcb_port_id),
route: trace.route.map((point: any) => point.route_type === "via"
? { ...point, via_diameter: 0.45, via_hole_diameter: 0.3 }
: { ...point, width: branch.nominalTraceWidth }),
}
})
if (rebuilt.length !== branches.length)
throw new Error(`LCD capacity rebuild solved ${rebuilt.length}/${branches.length} branches`)
return finalizeReviewedRouteWidths([...fixed, ...localLcdRoutes, ...rebuilt])
}
const dynamicObstacles = [...componentObstacles, ...fixedObstacles]
const rebuilt: any[] = []
const orderedBranches = [...branches].sort((left: any, right: any) => {
const sharedServicePriority = (branch: any) =>
/^source_net_1[1-6]$/.test(branch.originalConnectionName) ? 0
: branch.originalConnectionName === "source_net_0" || branch.originalConnectionName === "source_net_6" ? 2
: 1
const priorityDifference = sharedServicePriority(left) - sharedServicePriority(right)
if (priorityDifference !== 0) return priorityDifference
const length = (branch: any) => Math.hypot(
branch.pointsToConnect[1].x - branch.pointsToConnect[0].x,
branch.pointsToConnect[1].y - branch.pointsToConnect[0].y,
)
return length(right) - length(left)
})
for (const [branchIndex, branch] of orderedBranches.entries()) {
console.log(`[lcd-carrier] routing ${branchIndex + 1}/${orderedBranches.length} ${branch.originalTraceId}`)
const xs = branch.pointsToConnect.map((point: any) => point.x)
const ys = branch.pointsToConnect.map((point: any) => point.y)
const boardBounds = this.input.bounds
const bounds = {
minX: Math.max(boardBounds.minX, Math.min(...xs) - 12),
maxX: Math.min(boardBounds.maxX, Math.max(...xs) + 12),
minY: Math.max(boardBounds.minY, Math.min(...ys) - 12),
maxY: Math.min(boardBounds.maxY, Math.max(...ys) + 12),
}
let result: any = null
const attempts = [
{ bounds, gridStep: 0.1, maxIterations: 200_000, viaCost: 8 },
{ bounds: boardBounds, gridStep: 0.1, maxIterations: 500_000, viaCost: 3 },
{ bounds: boardBounds, gridStep: 0.05, maxIterations: 500_000, viaCost: 3 },
]
for (const attempt of attempts) {
const solver = new MultilayerIjump({
input: {
...this.input,
bounds: attempt.bounds,
connections: [branch],
traces: [],
obstacles: dynamicObstacles.filter((obstacle: any) => {
const halfWidth = (obstacle.width ?? 0) / 2
const halfHeight = (obstacle.height ?? 0) / 2
return obstacle.center.x + halfWidth >= attempt.bounds.minX
&& obstacle.center.x - halfWidth <= attempt.bounds.maxX
&& obstacle.center.y + halfHeight >= attempt.bounds.minY
&& obstacle.center.y - halfHeight <= attempt.bounds.maxY
}),
minTraceWidth: branch.nominalTraceWidth,
minViaHoleDiameter: 0.3,
minViaPadDiameter: 0.45,
min_via_hole_diameter: 0.3,
min_via_pad_diameter: 0.45,
allowViaInPad: true,
},
GRID_STEP: attempt.gridStep,
OBSTACLE_MARGIN: 0.1,
MAX_ITERATIONS: attempt.maxIterations,
VIA_COST: attempt.viaCost,
optimizeWithGoalBoxes: false,
})
result = solver.solve()[0]
if (result?.solved) break
}
if (!result?.solved) throw new Error(`LCD branch solve failed: ${branch.originalTraceId}`)
const route: any[] = []
for (let index = 0; index < result.route.length; index += 1) {
const point = result.route[index]
const previous = result.route[index - 1]
if (previous && previous.layer !== point.layer)
route.push(via(point.x, point.y, previous.layer, point.layer))
route.push(wire(point.x, point.y, branch.nominalTraceWidth, point.layer))
}
route[0].start_pcb_port_id = branch.pointsToConnect[0].pcb_port_id
route.at(-1).end_pcb_port_id = branch.pointsToConnect[1].pcb_port_id
const trace = {
type: "pcb_trace",
pcb_trace_id: branch.originalTraceId,
connection_name: branch.originalConnectionName,
connectsTo: branch.pointsToConnect.map((point: any) => point.pcb_port_id),
route,
}
rebuilt.push(trace)
dynamicObstacles.push(...fixedTraceToObstacles(
trace,
branchNamesByOriginal.get(branch.originalConnectionName) ?? [],
))
}
if (rebuilt.length !== branches.length)
throw new Error(`LCD carrier rebuild solved ${rebuilt.length}/${branches.length} branches`)
return finalizeReviewedRouteWidths([...fixed, ...localLcdRoutes, ...rebuilt])
}
const traces = applyClearanceRepairs(structuredClone(verifiedCarrierRoutes as any[]))
const legacyTraces = [
...traces,
...createTopBacklightRoutes(),
...createServiceConnectorRoutes(),
].filter((trace) => !isRemovedLegacyPowerRoute(trace))
const boundTraces = bindSavedRoutesToCurrentConnections(
translateLegacyCarrierPortIds(legacyTraces),
this.input,
)
const fixedTraces = rebuildMovedControlRoutes(this.input, boundTraces)
const twoAaPowerRoutes = createTwoAaPowerRoutes(this.input, fixedTraces)
return finalizeReviewedRouteWidths([
...fixedTraces,
...twoAaPowerRoutes,
...createStatusIndicatorRoutes(this.input, [...fixedTraces, ...twoAaPowerRoutes]),
])
}
start() {
if (this.isRouting) return
this.isRouting = true
queueMicrotask(() => {
this.isRouting = false
const event = { type: "complete", traces: this.getTraces() }
for (const callback of this.handlers.complete) callback(event)
})
}
stop() {
this.isRouting = false
}
solveSync() {
return this.getTraces()
}
}
export async function createCarrierAutorouter(simpleRouteJson: any) {
return new CarrierAutorouter(simpleRouteJson)
}