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
work/compare-style-candidate.tsx
import { Circuit } from "@tscircuit/core"
import * as checks from "@tscircuit/checks"
import { Children, cloneElement, isValidElement } from "react"
import { resolve } from "node:path"
import { pathToFileURL } from "node:url"
import Board from "../index"
import { SavedRoutes, type SavedRouteDefinition } from "../layout/SavedRoutes"
import { savedRoutes as baselineRoutes } from "../data/saved-route-geometry"
import { pcbGeometryFingerprint } from "../layout/saved-geometry-guard"
// Compare independently rendered TSX sources. Compiled circuit data is never
// serialized; only normalized diagnostic findings are printed.
const checkNames = [
"runAllNetlistChecks", "checkSourceTracesHavePcbTraces",
"checkTracesAreContiguous", "checkEachPcbPortConnectedToPcbTraces",
"checkEachPcbTraceNonOverlapping", "checkPadTraceClearance",
"checkViaTraceClearance", "checkViaPadClearance",
"checkDifferentNetViaSpacing", "checkPcbTracesOutOfBoard",
] as const
function referenceNames(records: any[], routes: SavedRouteDefinition[]) {
const names = new Map<string, string>()
const identify = (record: any, name: string) => {
const id = record[`${record.type}_id`]
if (typeof id === "string") names.set(id, `${record.type}:${name}`)
}
const ref = (id: string) => names.get(id) ?? id
for (const r of records) {
if (r.type === "source_component") identify(r, r.name)
if (r.type === "source_net") identify(r, r.name)
}
for (const r of records) {
if (r.type === "source_port") identify(r, `${ref(r.source_component_id)}.${r.pin_number || r.name}`)
if (r.type === "pcb_component") identify(r, ref(r.source_component_id))
}
for (const r of records) {
if (r.type === "pcb_port") identify(r, ref(r.source_port_id))
if (r.type === "source_trace") identify(r, JSON.stringify([
(r.connected_source_port_ids ?? []).map(ref).sort(),
(r.connected_source_net_ids ?? []).map(ref).sort(),
]))
}
let traceIndex = 0
for (const r of records) {
if (r.type === "pcb_trace") {
const route = routes[traceIndex++]
if (!route) throw Error("Rendered trace count exceeds the authored route count")
identify(r, route[0])
}
if (["pcb_via", "pcb_smtpad", "pcb_plated_hole", "pcb_hole"].includes(r.type)) {
identify(r, JSON.stringify([
ref(r.source_net_id), ref(r.pcb_port_id), ref(r.pcb_component_id),
r.x, r.y, r.layer, r.layers, r.shape, r.hole_diameter,
r.outer_diameter, r.width, r.height, r.radius,
]))
}
}
if (traceIndex !== routes.length) throw Error(`Expected ${routes.length} authored traces, rendered ${traceIndex}`)
return names
}
export function findingFingerprints(findings: any[], records: any[], routes: SavedRouteDefinition[]) {
const names = referenceNames(records, routes)
const fingerprints = new Map<string, number>()
for (const finding of findings) {
const normalize = (value: any, key = ""): any => {
if (typeof value === "string") return names.get(value) ?? value
if (Array.isArray(value)) {
const entries = value.map(entry => normalize(entry))
return key.endsWith("_ids") ? entries.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))) : entries
}
if (value && typeof value === "object") return Object.fromEntries(
Object.keys(value).sort().filter(k => k !== `${finding.type}_id`).map(k => [k, normalize(value[k], k)]),
)
return value
}
const fingerprint = JSON.stringify(normalize(finding))
fingerprints.set(fingerprint, (fingerprints.get(fingerprint) ?? 0) + 1)
}
return fingerprints
}
async function evaluate(label: string, routes: SavedRouteDefinition[]) {
const start = Date.now()
const circuit = new Circuit()
circuit.schematicDisabled = true
const board = Board()
const children = Children.toArray(board.props.children).map(child =>
isValidElement(child) && child.type === SavedRoutes ? <SavedRoutes routes={routes} /> : child)
circuit.add(cloneElement(board, {}, ...children))
console.log(`${label}: rendering TSX`)
await circuit.renderUntilSettled()
const records = circuit.getCircuitJson()
console.log(`${label}: rendered ${routes.length} traces in ${((Date.now() - start) / 1000).toFixed(1)} s`)
let styleCount: number | undefined
if (process.argv.includes("--style")) {
const { analyzePcbStyle } = await import("@tscircuit/circuit-json-pcb-style-analysis")
const { issues } = analyzePcbStyle(records)
styleCount = issues.length
const traces = records.filter(r => r.type === "pcb_trace")
console.log(`${label}: full PCB-style analyzer = ${styleCount} findings`)
for (const issue of issues) {
const index = traces.findIndex(trace => trace.pcb_trace_id === issue.pcbTraceId)
console.log(`STYLE ${label}: ${routes[index]?.[0]} ${issue.lineItemType}: ${issue.message}`)
}
}
const output = new Map<string, Map<string, number>>()
const geometry = pcbGeometryFingerprint(circuit.db)
const allChecks: Array<[string, () => any]> = [
["renderErrors", () => records.filter(r => r.type.endsWith("_error"))],
...checkNames.map(name => [name, () => (checks as Record<string, any>)[name](records)] as [string, () => any]),
]
for (const [name, check] of allChecks) {
console.log(`${label}: checking ${name}`)
const findings = await check()
output.set(name, findingFingerprints(findings, records, routes))
console.log(`${label}: ${name} = ${findings.length}`)
}
return { geometry, output, styleCount }
}
if (import.meta.main) {
const candidateArgument = process.argv.find(arg => arg.startsWith("--candidate="))?.slice("--candidate=".length)
const candidateModule = candidateArgument
? pathToFileURL(resolve(candidateArgument)).href
: new URL("./style-targeted-routes.tsx", import.meta.url).href
const { savedRoutes: candidateRoutes } = await import(candidateModule)
if (!Array.isArray(candidateRoutes)) throw Error("Candidate TSX module must export savedRoutes")
if (baselineRoutes.length !== candidateRoutes.length || baselineRoutes.some((route, i) => route[0] !== candidateRoutes[i][0])) {
throw Error("Baseline and candidate must preserve authored route identities and ordering")
}
const baseline = await evaluate("BASELINE", baselineRoutes)
const candidate = await evaluate("CANDIDATE", candidateRoutes)
let introduced = 0, removed = 0
if (baseline.geometry !== candidate.geometry) throw Error("Candidate changed component, pad, via or net-binding geometry")
for (const [check, before] of baseline.output) {
const after = candidate.output.get(check)!
let addedForCheck = 0, removedForCheck = 0
for (const fingerprint of new Set([...before.keys(), ...after.keys()])) {
const difference = (after.get(fingerprint) ?? 0) - (before.get(fingerprint) ?? 0)
if (!difference) continue
console.log(`${difference > 0 ? "INTRODUCED" : "REMOVED"} ${check} x${Math.abs(difference)} ${fingerprint}`)
if (difference > 0) addedForCheck += difference
else removedForCheck -= difference
}
introduced += addedForCheck
removed += removedForCheck
console.log(`COMPARE ${check}: ${addedForCheck} introduced, ${removedForCheck} removed`)
}
console.log(`Exact finding comparison: ${introduced} introduced, ${removed} removed; physical geometry unchanged`)
if (baseline.styleCount !== undefined) console.log(`PCB-style findings: ${baseline.styleCount} → ${candidate.styleCount}`)
process.exitCode = introduced ? 1 : 0
}