ShiboSoftwareDev/mspm0g3507-usb-c-dev-board

The circuit assembles a USB-C port with integrated ESD protection, power regulation, UART interface, and microcontroller (MSPM0G3507) with supporting components for USB data lines, CC termination, I2C, SPI, reset circuitry, and indicator LEDs.

Version
1.4.1
License
unset
Stars
0

scripts/audit-fabrication.mjs

import { readFileSync } from "node:fs"
import {
  runAllNetlistChecks,
  runAllPinSpecificationChecks,
  runAllPlacementChecks,
  runAllRoutingChecks,
} from "@tscircuit/checks"

const circuitPath = process.argv[2] ?? "dist/index/circuit.json"
const circuit = JSON.parse(readFileSync(circuitPath, "utf8"))
const source = readFileSync("index.circuit.tsx", "utf8")
const failures = []

const elements = (type) => circuit.filter((element) => element.type === type)
const assert = (condition, message) => {
  if (!condition) failures.push(message)
}
const rounded = (value) => Number(value.toFixed(3))

const sourceComponents = elements("source_component")
const sourcePorts = elements("source_port")
const sourceNets = elements("source_net")
const sourceTraces = elements("source_trace")
const pcbComponents = elements("pcb_component")
const pcbBoards = elements("pcb_board")
const pcbPorts = elements("pcb_port")
const pcbTraces = elements("pcb_trace")
const vias = elements("pcb_via")
const schematicSheets = elements("schematic_sheet")
const schematicComponents = elements("schematic_component")
const schematicTraces = elements("schematic_trace")
const courtyards = [
  ...elements("pcb_courtyard_outline"),
  ...elements("pcb_courtyard_rect"),
]

const sourceComponentById = new Map(
  sourceComponents.map((component) => [component.source_component_id, component]),
)
const componentByName = new Map(
  sourceComponents.map((component) => [component.name, component]),
)
const sourceNetByName = new Map(sourceNets.map((net) => [net.name, net]))
const portsByComponentId = new Map()
for (const port of sourcePorts) {
  const ports = portsByComponentId.get(port.source_component_id) ?? []
  ports.push(port)
  portsByComponentId.set(port.source_component_id, ports)
}
const pcbComponentBySourceId = new Map(
  pcbComponents.map((component) => [component.source_component_id, component]),
)
const approximately = (actual, expected, tolerance = 0.001) =>
  Math.abs(actual - expected) <= tolerance
const pcbPortBySourceId = new Map(
  pcbPorts.map((port) => [port.source_port_id, port]),
)
const pcbPortById = new Map(pcbPorts.map((port) => [port.pcb_port_id, port]))
const pcbTracesBySourceId = new Map()
for (const trace of pcbTraces) {
  const traces = pcbTracesBySourceId.get(trace.source_trace_id) ?? []
  traces.push(trace)
  pcbTracesBySourceId.set(trace.source_trace_id, traces)
}
const viaCountByPcbTraceId = new Map()
for (const via of vias) {
  viaCountByPcbTraceId.set(
    via.pcb_trace_id,
    (viaCountByPcbTraceId.get(via.pcb_trace_id) ?? 0) + 1,
  )
}

const sourcePort = (componentName, portName) => {
  const component = componentByName.get(componentName)
  return (portsByComponentId.get(component?.source_component_id) ?? []).find(
    (port) => port.name === portName || port.port_hints?.includes(portName),
  )
}
const assertPortNet = (componentName, portName, netName) => {
  const port = sourcePort(componentName, portName)
  const net = sourceNetByName.get(netName)
  assert(Boolean(port), `${componentName}.${portName} is missing`)
  assert(Boolean(net), `net.${netName} is missing`)
  assert(
    port?.subcircuit_connectivity_map_key === net?.subcircuit_connectivity_map_key,
    `${componentName}.${portName} is not on net.${netName}`,
  )
}

const traceLength = (trace) => {
  let length = 0
  for (let index = 1; index < trace.route.length; index += 1) {
    const previous = trace.route[index - 1]
    const current = trace.route[index]
    length += Math.hypot(current.x - previous.x, current.y - previous.y)
  }
  return length
}
const traceStats = (name) => {
  const sourceTrace = sourceTraces.find((trace) => trace.name === name)
  assert(sourceTrace, `Missing named source trace ${name}`)
  if (!sourceTrace) return { length: Number.NaN, vias: Number.NaN, layers: [] }
  const traces = pcbTracesBySourceId.get(sourceTrace.source_trace_id) ?? []
  assert(traces.length > 0, `${name} has no routed PCB trace`)
  return {
    sourceTrace,
    traces,
    length: traces.reduce((sum, trace) => sum + traceLength(trace), 0),
    vias: traces.reduce(
      (sum, trace) => sum + (viaCountByPcbTraceId.get(trace.pcb_trace_id) ?? 0),
      0,
    ),
    layers: [
      ...new Set(
        traces.flatMap((trace) =>
          trace.route.map((point) => point.layer).filter(Boolean),
        ),
      ),
    ],
  }
}

// Three-terminal copper trees can be represented as autorouter MST segments.
// Prefer a routed endpoint-to-endpoint segment; otherwise use the same
// straight-line fallback as `tsci check trace-length`.
const endpointTraceLength = (name) => {
  const stats = traceStats(name)
  const sourceIds = new Set(stats.sourceTrace.connected_source_port_ids)
  const exact = stats.traces.find((trace) => {
    const pcbSourceIds = (trace.connectsTo ?? [])
      .map((pcbPortId) => pcbPortById.get(pcbPortId)?.source_port_id)
      .filter(Boolean)
    return pcbSourceIds.length === sourceIds.size && pcbSourceIds.every((id) => sourceIds.has(id))
  })
  if (exact) return traceLength(exact)
  const endpoints = stats.sourceTrace.connected_source_port_ids
    .map((id) => pcbPortBySourceId.get(id))
    .filter(Boolean)
  assert(endpoints.length === 2, `${name} does not have two positioned endpoints`)
  return Math.hypot(endpoints[1].x - endpoints[0].x, endpoints[1].y - endpoints[0].y)
}

const checkResults = {
  netlist: await runAllNetlistChecks(circuit),
  pinSpecification: await runAllPinSpecificationChecks(circuit),
  placement: await runAllPlacementChecks(circuit),
  routing: await runAllRoutingChecks(circuit),
}
for (const [name, results] of Object.entries(checkResults)) {
  assert(
    results.length === 0,
    `${name} checks returned ${results.length}: ${results.map((result) => result.message).join(" | ")}`,
  )
}

assert(sourceComponents.length === 38, `Expected 38 source components, found ${sourceComponents.length}`)
assert(pcbComponents.length === 38, `Expected 38 PCB components, found ${pcbComponents.length}`)

assert(pcbBoards.length === 1, `Expected one PCB outline, found ${pcbBoards.length}`)
const pcbBoard = pcbBoards[0]
assert(
  approximately(pcbBoard?.width, 90) && approximately(pcbBoard?.height, 21),
  `PCB bounds are ${pcbBoard?.width ?? "missing"} mm by ${pcbBoard?.height ?? "missing"} mm instead of 90 mm by 21 mm`,
)
const expectedBoardOutline = [
  [-45, 10.5], [45, 10.5], [45, -10.5], [-45, -10.5],
]
assert(
  pcbBoard?.outline?.length === expectedBoardOutline.length &&
    expectedBoardOutline.every(([x, y], index) =>
      approximately(pcbBoard.outline[index]?.x, x) &&
      approximately(pcbBoard.outline[index]?.y, y),
    ),
  "PCB outline is not the expected uniform 90 mm by 21 mm Pico-width rectangle",
)
const topHeader = pcbComponentBySourceId.get(componentByName.get("J_TOP")?.source_component_id)
const bottomHeader = pcbComponentBySourceId.get(componentByName.get("J_BOTTOM")?.source_component_id)
const headerRowSpacing = Math.abs(topHeader?.center.y - bottomHeader?.center.y)
const boardHalfHeight = 10.5
const boardMinX = -45
const boardMaxX = 45
const edgeOverhang = boardHalfHeight - Math.abs(topHeader?.center.y)
const breadboardPitch = 2.54
const nominalJumperHousingHalfWidth = breadboardPitch / 2
const adjacentHoleCenterClearance =
  Math.abs(topHeader?.center.y) + breadboardPitch - boardHalfHeight
const adjacentJumperBodyClearance =
  adjacentHoleCenterClearance - nominalJumperHousingHalfWidth
const headerPadEdgeClearance =
  boardHalfHeight - (Math.abs(topHeader?.center.y) + topHeader?.height / 2)
assert(
  approximately(topHeader?.center.x, 0) && approximately(topHeader?.center.y, 8.89),
  "J_TOP is not centered on the +8.89 mm breadboard row",
)
assert(
  approximately(bottomHeader?.center.x, 0) && approximately(bottomHeader?.center.y, -8.89),
  "J_BOTTOM is not centered on the -8.89 mm breadboard row",
)
assert(approximately(headerRowSpacing, 17.78), `Header row spacing is ${headerRowSpacing} mm instead of 17.78 mm`)
assert(approximately(edgeOverhang, 1.61), `Header-to-board-edge overhang is ${edgeOverhang} mm instead of 1.61 mm`)
assert(
  adjacentHoleCenterClearance >= 0.8,
  `Immediately adjacent breadboard holes are obstructed: only ${adjacentHoleCenterClearance.toFixed(3)} mm from center to PCB edge`,
)
assert(
  headerPadEdgeClearance >= 0.3,
  `Header copper-to-board-edge clearance is only ${headerPadEdgeClearance.toFixed(3)} mm`,
)
for (const header of [topHeader, bottomHeader]) {
  assert(
    approximately(header?.width, 49.86) && approximately(header?.height, 1.6),
    "Header copper footprint is not 49.86 mm by 1.6 mm",
  )
}
let lastHeaderHousingClearance = Number.POSITIVE_INFINITY
for (const headerName of ["J_TOP", "J_BOTTOM"]) {
  const header = componentByName.get(headerName)
  const headerPcbComponent = pcbComponentBySourceId.get(header?.source_component_id)
  const xs = pcbPorts
    .filter((port) => port.pcb_component_id === headerPcbComponent?.pcb_component_id)
    .map((port) => port.x)
    .sort((a, b) => a - b)
  assert(xs.length === 20, `${headerName} has ${xs.length} PCB pins instead of 20`)
  assert(
    xs.slice(1).every((x, index) => approximately(x - xs[index], 2.54)),
    `${headerName} is not on a 2.54 mm breadboard grid`,
  )
  const headerHoles = elements("pcb_plated_hole").filter(
    (hole) => hole.pcb_component_id === headerPcbComponent?.pcb_component_id,
  )
  assert(headerHoles.length === 20, `${headerName} has ${headerHoles.length} plated holes instead of 20`)
  assert(
    headerHoles.every((hole) =>
      hole.shape === "circular_hole_with_rect_pad" &&
      approximately(hole.hole_diameter, 1.05) &&
      approximately(hole.rect_pad_width, 1.6) &&
      approximately(hole.rect_pad_height, 1.6) &&
      approximately(hole.rect_border_radius, 0.8) &&
      (Math.min(hole.rect_pad_width, hole.rect_pad_height) - hole.hole_diameter) / 2 >= 0.275 - 0.001,
    ),
    `${headerName} does not retain the supplier's 1.05 mm drill and 0.275 mm annular ring`,
  )
  const housingEnvelope = Math.max(...xs.map(Math.abs)) + nominalJumperHousingHalfWidth
  lastHeaderHousingClearance = Math.min(
    lastHeaderHousingClearance,
    Math.min(boardMaxX - housingEnvelope, -boardMinX - housingEnvelope),
  )
}
assert(
  lastHeaderHousingClearance >= 0.5,
  `Board ends too early: only ${lastHeaderHousingClearance.toFixed(3)} mm beyond the end-pin jumper housing`,
)

assert(schematicSheets.length === 1, `Expected one schematic sheet, found ${schematicSheets.length}`)
const mainSchematicSheet = schematicSheets[0]
assert(mainSchematicSheet?.name === "MAIN", "The main schematic sheet is missing or misnamed")
assert(mainSchematicSheet?.sheet_size === "ansi_b", "The main schematic sheet is not ANSI B")
assert(
  mainSchematicSheet?.sheet_width === 431.8 && mainSchematicSheet?.sheet_height === 279.4,
  "The main schematic sheet dimensions are not 431.8 mm by 279.4 mm",
)
assert(
  schematicComponents.length === sourceComponents.length &&
    schematicComponents.every(
      (component) => component.schematic_sheet_id === mainSchematicSheet?.schematic_sheet_id,
    ),
  "Not every source component is represented on the main schematic sheet",
)
assert(
  schematicTraces.every(
    (trace) => trace.schematic_sheet_id === mainSchematicSheet?.schematic_sheet_id,
  ),
  "Not every schematic trace is assigned to the main schematic sheet",
)
const schematicText = new Set(elements("schematic_text").map((text) => text.text))
for (const sectionTitle of [
  "USB-C receptacle & CC",
  "USB protection & UART bridge",
  "5 V to 3.3 V power",
  "MCU, reset & boot",
  "RGB debug LED",
  "I2C connectors",
  "SPI display connector",
  "GPIO & debug headers",
]) {
  assert(schematicText.has(sectionTitle), `Schematic section title is missing: ${sectionTitle}`)
}
for (const component of sourceComponents) {
  assert(Boolean(component.manufacturer_part_number), `${component.name} has no manufacturer part number`)
  assert(
    Array.isArray(component.supplier_part_numbers?.jlcpcb) &&
      component.supplier_part_numbers.jlcpcb.length > 0,
    `${component.name} has no JLCPCB part number`,
  )
}

const courtyardComponentIds = new Set(courtyards.map((courtyard) => courtyard.pcb_component_id))
for (const component of pcbComponents) {
  const sourceComponent = sourceComponentById.get(component.source_component_id)
  assert(
    courtyardComponentIds.has(component.pcb_component_id),
    `${sourceComponent?.name ?? component.pcb_component_id} has no courtyard`,
  )
  assert(component.layer === "top", `${sourceComponent?.name ?? component.pcb_component_id} is not on the top layer`)
}

for (const via of vias) {
  assert(
    via.layers.length === 2 && via.layers.includes("top") && via.layers.includes("bottom"),
    `${via.pcb_via_id} is not a top-to-bottom through-via`,
  )
  assert(
    approximately(via.hole_diameter, 0.3),
    `${via.pcb_via_id} drill is ${via.hole_diameter} mm instead of 0.30 mm`,
  )
  assert(
    approximately(via.outer_diameter, 0.45),
    `${via.pcb_via_id} pad is ${via.outer_diameter} mm instead of 0.45 mm`,
  )
}

const usbTraceNames = [
  "USB_DP_FLIP_A",
  "USB_DP_FLIP_B",
  "USB_DM_FLIP_A",
  "USB_DM_FLIP_B",
  "USB_DM_TO_ESD",
  "USB_DP_PROTECTED",
  "USB_DM_PROTECTED",
]
const criticalStats = Object.fromEntries(
  usbTraceNames.map((name) => [name, traceStats(name)]),
)
for (const [name, stats] of Object.entries(criticalStats)) {
  assert(stats.vias === 0, `${name} contains ${stats.vias} via(s)`)
  assert(
    stats.layers.length === 1 && stats.layers[0] === "top",
    `${name} is not entirely top-layer`,
  )
}

const protectedLengths = {
  DP: endpointTraceLength("USB_DP_PROTECTED"),
  DM: endpointTraceLength("USB_DM_PROTECTED"),
}
const protectedSkew = Math.abs(protectedLengths.DP - protectedLengths.DM)
assert(protectedSkew <= 0.2, `Protected USB pair skew is ${protectedSkew.toFixed(3)} mm`)

const joinPin1 = pcbPortBySourceId.get(sourcePort("R_USB_DM_JOIN", "pin1")?.source_port_id)
const joinPin2 = pcbPortBySourceId.get(sourcePort("R_USB_DM_JOIN", "pin2")?.source_port_id)
const joinSpan = Math.hypot(joinPin2.x - joinPin1.x, joinPin2.y - joinPin1.y)
const usbPaths = {
  A_DP: endpointTraceLength("USB_DP_FLIP_A") + protectedLengths.DP,
  A_DM: endpointTraceLength("USB_DM_FLIP_A") + endpointTraceLength("USB_DM_TO_ESD") + protectedLengths.DM,
  B_DP: endpointTraceLength("USB_DP_FLIP_B") + protectedLengths.DP,
  B_DM: endpointTraceLength("USB_DM_FLIP_B") + joinSpan + endpointTraceLength("USB_DM_TO_ESD") + protectedLengths.DM,
}
const usbSkew = {
  A: Math.abs(usbPaths.A_DP - usbPaths.A_DM),
  B: Math.abs(usbPaths.B_DP - usbPaths.B_DM),
}
assert(
  Math.max(usbSkew.A, usbSkew.B) <= 1,
  `USB end-to-end skew exceeds 1 mm: ${JSON.stringify(usbSkew)}`,
)

const vcoreStats = traceStats("VCORE_DECOUPLE")
assert(vcoreStats.vias === 0, `VCORE_DECOUPLE contains ${vcoreStats.vias} via(s)`)
assert(
  vcoreStats.layers.length === 1 && vcoreStats.layers[0] === "top",
  "VCORE_DECOUPLE is not entirely top-layer",
)
assert(vcoreStats.length <= 4, `VCORE_DECOUPLE is ${vcoreStats.length.toFixed(3)} mm, over 4 mm`)

for (const switchName of ["SW_BOOT", "SW_RESET"]) {
  const component = componentByName.get(switchName)
  const ports = portsByComponentId.get(component?.source_component_id) ?? []
  const pcbComponent = pcbComponentBySourceId.get(component?.source_component_id)
  const physicalPorts = pcbPorts.filter((port) => port.pcb_component_id === pcbComponent?.pcb_component_id)
  assert(component?.ftype === "simple_push_button", `${switchName} is not a semantic pushbutton`)
  assert(ports.length === 2 && physicalPorts.length === 2, `${switchName} is not a true two-terminal switch`)
  assert(
    ports[0]?.subcircuit_connectivity_map_key !== ports[1]?.subcircuit_connectivity_map_key,
    `${switchName} terminals are shorted together`,
  )
}
assertPortNet("SW_BOOT", "pin1", "PA18_BSL_INVOKE")
assertPortNet("SW_BOOT", "pin2", "VDD_3V3")
assertPortNet("SW_RESET", "pin1", "MCU_RESET_N")
assertPortNet("SW_RESET", "pin2", "GND")

const rgbLed = componentByName.get("D_USER_RGB")
const rgbPorts = portsByComponentId.get(rgbLed?.source_component_id) ?? []
assert(rgbLed?.ftype === "simple_chip", "D_USER_RGB is not a four-channel semantic component")
assert(rgbLed?.manufacturer_part_number === "SZYY1615RGB-A 4pin", "D_USER_RGB has the wrong MPN")
assert(rgbLed?.supplier_part_numbers?.jlcpcb?.[0] === "C3029072", "D_USER_RGB has the wrong JLCPCB part number")
assert(rgbPorts.length === 4, `D_USER_RGB has ${rgbPorts.length} ports instead of four`)
assertPortNet("D_USER_RGB", "pin4", "GND")
assert(sourcePort("D_USER_RGB", "pin4")?.requires_ground === true, "D_USER_RGB common cathode is not marked requiresGround")
const rgbAnodeNetKeys = new Set()
for (const [color, resistor, gpioNet, ledPin] of [
  ["red", "R_RGB_RED", "PB2_RGB_RED", "pin1"],
  ["green", "R_RGB_GREEN", "PB3_RGB_GREEN", "pin2"],
  ["blue", "R_RGB_BLUE", "PB14_RGB_BLUE", "pin3"],
]) {
  const resistorComponent = componentByName.get(resistor)
  assert(resistorComponent?.resistance === 330, `${resistor} is not 330 ohms`)
  assert(resistorComponent?.manufacturer_part_number === "0402WGF3300TCE", `${resistor} has the wrong MPN`)
  assert(resistorComponent?.supplier_part_numbers?.jlcpcb?.[0] === "C25104", `${resistor} has the wrong JLCPCB part number`)
  assertPortNet(resistor, "pin1", gpioNet)
  const ledPort = sourcePort("D_USER_RGB", ledPin)
  assert(ledPort?.requires_power === true, `D_USER_RGB ${color} anode is not marked requiresPower`)
  rgbAnodeNetKeys.add(ledPort?.subcircuit_connectivity_map_key)
  assert(
    ledPort?.subcircuit_connectivity_map_key ===
      sourcePort(resistor, "pin2")?.subcircuit_connectivity_map_key,
    `D_USER_RGB ${color} anode is not connected through ${resistor}`,
  )
}
assert(rgbAnodeNetKeys.size === 3, "D_USER_RGB color channels are shorted together")
assertPortNet("U_MCU", "PA7", "PA7")
assertPortNet("J_BOTTOM", "PA7", "PA7")

const expectedConnectorNets = {
  J_I2C_JST_SH: { pin1: "GND", pin2: "VDD_3V3", pin3: "PA0_I2C_SDA", pin4: "PA1_I2C_SCL" },
  J_GROVE_I2C: { pin1: "PA1_I2C_SCL", pin2: "PA0_I2C_SDA", pin3: "VDD_3V3", pin4: "GND" },
  J_SPI_DISPLAY: {
    pin1: "GND", pin2: "VDD_3V3", pin3: "PB9_SPI_SCK", pin4: "PB8_SPI_MOSI",
    pin5: "PB7_SPI_MISO", pin6: "PB6_SPI_CS", pin7: "PA8_SPI_DC", pin8: "PA9_SPI_RST",
  },
}
for (const [componentName, pins] of Object.entries(expectedConnectorNets)) {
  assert(componentByName.get(componentName)?.ftype === "simple_connector", `${componentName} is not a connector`)
  for (const [pin, net] of Object.entries(pins)) assertPortNet(componentName, pin, net)
}

for (const contact of ["A6", "B6", "A7", "B7"]) {
  assert(Boolean(sourcePort("J_USB_C", contact)), `J_USB_C is missing ${contact}`)
}
assertPortNet("U_USB_UART", "VCC", "VDD_3V3")
assertPortNet("U_USB_UART", "V3", "VDD_3V3")
assertPortNet("R_UART_TX", "pin2", "PA11_UART_RX")
assertPortNet("R_UART_RX", "pin1", "PA10_UART_TX")

const mountingHoles = elements("pcb_hole").filter(
  (hole) => hole.pcb_component_id === null && Math.abs(hole.hole_diameter - 2.7) < 0.001,
)
const fiducials = elements("pcb_smtpad").filter(
  (pad) => pad.pcb_component_id === null && pad.shape === "circle" && pad.radius === 0.5,
)
assert(mountingHoles.length === 4, `Expected four M2.5 mounting holes, found ${mountingHoles.length}`)
assert(fiducials.length === 3, `Expected three SMT fiducials, found ${fiducials.length}`)
for (const [x, y] of [[-42, 8.3], [-42, -8.3], [35.5, 8.3], [26.75, -8.3]]) {
  assert(
    mountingHoles.some((hole) => approximately(hole.x, x) && approximately(hole.y, y)),
    `Missing M2.5 mounting hole at (${x}, ${y}) mm`,
  )
}
for (const hole of mountingHoles) {
  const edgeClearance = Math.min(
    pcbBoard.width / 2 - Math.abs(hole.x),
    pcbBoard.height / 2 - Math.abs(hole.y),
  ) - hole.hole_diameter / 2
  assert(edgeClearance >= 0.8, `${hole.pcb_hole_id} has only ${edgeClearance.toFixed(3)} mm edge clearance`)
}

const silk = elements("pcb_silkscreen_text").map((text) => text.text)
for (const requiredText of ["19 3V3", "3 3V3", "JST P1:G 2:3V3 3:SDA 4:SCL", "GROVE P1:SCL 2:SDA 3:3V3 4:G", "RGB R:PB2 G:PB3 B:PB14"]) {
  assert(silk.includes(requiredText), `Required silkscreen label is missing: ${requiredText}`)
}
assert(silk.some((text) => text.startsWith("SPI P1:G 2:3V3")), "SPI pinout silkscreen is missing")
assert(!source.includes("PA7_USER_LED"), "Stale PA7 white-LED net name remains")
assert(!source.includes("D_USER_WHITE"), "Stale white-LED component remains")

assert(!sourceComponents.some((component) => component.ftype === "simple_crystal"), "A crystal is present; CH340C should use its internal oscillator")
assert(!/<pcbtrace\b/i.test(source), "Manual <pcbtrace> routing is present")
assert(!/<via\b/i.test(source), "Manual <via> placement is present")
assert(!/<tracehint\b/i.test(source), "Manual <tracehint> routing is present")
assert(!/\bpcbPath\s*=/.test(source), "Manual pcbPath routing is present")
assert((source.match(/<differentialpair\b/g) ?? []).length === 1, "Expected one explicit protected USB differential pair")

const pasteLayers = [...new Set(elements("pcb_solder_paste").map((paste) => paste.layer))]
assert(pasteLayers.length === 1 && pasteLayers[0] === "top", `Unexpected solder-paste layers: ${pasteLayers.join(", ")}`)
assert(!circuit.some((element) => ["inner1", "inner2", "inner3", "inner4"].includes(element.layer)), "Inner-layer PCB elements are present")

const embeddedErrors = circuit.filter((element) => element.type.endsWith("_error"))
assert(embeddedErrors.length === 0, `Circuit JSON contains ${embeddedErrors.length} embedded errors`)
assert(
  elements("schematic_element_outside_sheet_warning").length === 0,
  "One or more schematic elements extend outside the sheet",
)
assert(
  !elements("schematic_component_styling_warning").some(
    (warning) => warning.styling_issue_type === "missing_schematic_sheet",
  ),
  "The circuit reports a missing schematic sheet",
)
const allowedWarnings = new Set([
  "schematic_component_styling_warning",
  "supplier_footprint_mismatch_warning",
  "source_part_not_found_warning",
])
const unexpectedWarnings = circuit.filter(
  (element) => element.type.endsWith("_warning") && !allowedWarnings.has(element.type),
)
assert(unexpectedWarnings.length === 0, `Circuit JSON contains unexpected warnings: ${unexpectedWarnings.map((warning) => warning.type).join(", ")}`)
const footprintWarnings = elements("supplier_footprint_mismatch_warning")
const ch340FootprintWarnings = footprintWarnings.filter((warning) => warning.message.includes("jlcpcb:C84681"))
assert(
  footprintWarnings.length === 1 && ch340FootprintWarnings.length === 1,
  "Unexpected supplier-footprint warning set",
)
const supplierFetchWarnings = elements("source_part_not_found_warning")

console.log(JSON.stringify({
  checks: Object.fromEntries(Object.entries(checkResults).map(([name, results]) => [name, results.length])),
  components: sourceComponents.length,
  courtyards: courtyardComponentIds.size,
  pcbTraces: pcbTraces.length,
  throughVias: vias.length,
  mountingHoles: mountingHoles.length,
  fiducials: fiducials.length,
  mechanical: {
    overallBoardBoundsMm: [pcbBoard?.width, pcbBoard?.height],
    uniformPicoWidthMm: boardHalfHeight * 2,
    headerRowSpacingMm: rounded(headerRowSpacing),
    headerPitchMm: 2.54,
    edgeOverhangMm: rounded(edgeOverhang),
    adjacentHoleCenterToBoardEdgeMm: rounded(adjacentHoleCenterClearance),
    nominalJumperBodyPlanOverlapMm: rounded(Math.max(0, -adjacentJumperBodyClearance)),
    boardBeyondEndPinJumperHousingMm: rounded(lastHeaderHousingClearance),
    headerPadToBoardEdgeMm: rounded(headerPadEdgeClearance),
    immediatelyAdjacentBreadboardHoleCentersOutsideBoard: true,
  },
  pasteLayers,
  schematic: {
    sheet: mainSchematicSheet?.display_name,
    size: mainSchematicSheet?.sheet_size,
    components: schematicComponents.length,
    traces: schematicTraces.length,
    sections: 8,
  },
  usb: {
    pathsMm: Object.fromEntries(Object.entries(usbPaths).map(([name, value]) => [name, rounded(value)])),
    skewMm: { A: rounded(usbSkew.A), B: rounded(usbSkew.B), protected: rounded(protectedSkew) },
    allTopLayerAndViaFree: usbTraceNames.every((name) => criticalStats[name].vias === 0),
  },
  vcore: { lengthMm: rounded(vcoreStats.length), vias: vcoreStats.vias },
  switches: "two terminal, distinct nets, active-high BOOT and active-low RESET",
  rgbDebugLed: "common cathode; PB2 red, PB3 green, PB14 blue; active-high through 330Ω",
  warningsReviewed: {
    exactFootprintComparison: ch340FootprintWarnings.map((warning) => warning.message),
    transientSupplierFetchWarnings: supplierFetchWarnings.length,
  },
  failures,
}, null, 2))

if (failures.length > 0) process.exitCode = 1