0hmX/am3352-ddr-track-a-local-dfm-wip-20261007

Experimental RJ45-corrected board with isolated PG indication. 1005/1005 source joins pass; 15 expanded-guard physical errors, sourcing and qualification gates remain. Not fabrication-ready.

Version
0.0.14
License
unset
Stars
0

scripts/check-meander-gap.ts

/** Screen opposing return legs on a sampled path. Distances are in mm. */
export function checkMeanderGap(points: [number, number][], width: number, edgeGap: number) {
  const required = width + edgeGap
  const cumulative = [0]
  for (let i = 1; i < points.length; i++) {
    cumulative.push(cumulative[i - 1] + Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]))
  }
  const pointDistance = (p: number[], a: number[], b: number[]) => {
    const dx = b[0] - a[0], dy = b[1] - a[1]
    const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / (dx * dx + dy * dy || 1)))
    return Math.hypot(p[0] - a[0] - t * dx, p[1] - a[1] - t * dy)
  }
  const orientation = (a: number[], b: number[], c: number[]) => (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
  for (let i = 0; i < points.length - 1; i++) {
    const a = points[i], b = points[i + 1], dx = b[0] - a[0], dy = b[1] - a[1], len = Math.hypot(dx, dy)
    if (len < 1e-5) continue
    for (let j = i + 2; j < points.length - 1; j++) {
      // Nearby chords on the same rounded corner are not separate returning arms.
      if (cumulative[j] - cumulative[i + 1] < 1.5 * required) continue
      const c = points[j], d = points[j + 1], ex = d[0] - c[0], ey = d[1] - c[1], otherLen = Math.hypot(ex, ey)
      if (otherLen < 1e-5 || (dx * ex + dy * ey) / (len * otherLen) > -0.9) continue
      const s1 = ((c[0] - a[0]) * dx + (c[1] - a[1]) * dy) / len
      const s2 = ((d[0] - a[0]) * dx + (d[1] - a[1]) * dy) / len
      if (Math.min(len, Math.max(s1, s2)) - Math.max(0, Math.min(s1, s2)) < Math.min(0.005, len / 4, otherLen / 4)) continue
      if (Math.max(a[0], b[0]) + required < Math.min(c[0], d[0]) || Math.max(c[0], d[0]) + required < Math.min(a[0], b[0]) ||
          Math.max(a[1], b[1]) + required < Math.min(c[1], d[1]) || Math.max(c[1], d[1]) + required < Math.min(a[1], b[1])) continue
      const intersects = orientation(a, b, c) * orientation(a, b, d) < 0 && orientation(c, d, a) * orientation(c, d, b) < 0
      const centerDistance = intersects ? 0 : Math.min(pointDistance(a, c, d), pointDistance(b, c, d), pointDistance(c, a, b), pointDistance(d, a, b))
      if (centerDistance < required - 1e-6) return { ok: false, centerDistance, edgeGap: centerDistance - width, indices: [i, j] }
    }
  }
  return { ok: true }
}

if (import.meta.main) {
  const { ddrRoutes } = await import('../data/ddr-routes')
  const names = process.argv.slice(2)
  const selected = names.length ? ddrRoutes.filter(route => names.includes(route.name)) : ddrRoutes
  if (names.some(name => !selected.some(route => route.name === name))) throw new Error('Unknown DDR signal name')
  let failures = 0
  for (const route of selected) {
    const result = checkMeanderGap(route.points, 0.1, 0.2)
    if (!result.ok) { failures++; console.error(`${route.name}: ${result.edgeGap?.toFixed(4)} mm return-leg edge gap`) }
  }
  console.log(`${selected.length} routes screened, ${failures} below the 0.20 mm edge-gap target`)
  if (failures) process.exitCode = 1
}