muse/book-reading-clip-lamp
A 60‑mm ESP32‑S3 lamp-head PCB combining USB‑C battery charging/power regulation, OV5640 autofocus camera interfacing, I²S microphone and differential speaker audio, six‑LED boost-driven reading light, buttons, status indicators, and battery monitoring.
- Version
- 0.2.5
- License
- unset
- Stars
- 0
verify-routing-guard.ts
Schematic
import capturedInput from "./saved-routing-input.json"
import capturedHoles from "./saved-routing-holes.json"
import { replaySavedRoutes } from "./saved-routing"
type Input = Parameters<typeof replaySavedRoutes>[0]
const copy = () => JSON.parse(JSON.stringify(capturedInput)) as Input
async function expectRoutes(input: Input) {
const router = await replaySavedRoutes(input)
let count = 0
router.on("complete", event => { count = event.traces.length })
router.start()
if (count < 100) throw new Error(`Missing saved copper: only ${count} traces`)
}
await expectRoutes(copy())
await expectRoutes({ ...copy(), minTraceToHoleEdgeClearance: 0.2 } as Input)
const explicitHoles = copy()
const holeIndices = explicitHoles.obstacles.flatMap((o,i) =>
capturedHoles.some(h => Math.abs(h.x-o.center.x)<1e-8 && Math.abs(h.y-o.center.y)<1e-8 &&
Math.abs(h.width-o.width)<1e-8 && Math.abs(h.height-o.height)<1e-8 && o.connectedTo.length===0) ? [i] : [])
if (holeIndices.length !== capturedHoles.length) throw new Error("Not every physical drill has a routing obstacle")
for (const i of holeIndices) {
Object.assign(explicitHoles.obstacles[i], { isNonPlatedHole:true })
}
await expectRoutes(explicitHoles)
const incorrectlyPlatedHole = copy()
Object.assign(incorrectlyPlatedHole.obstacles[holeIndices[0]], { isNonPlatedHole:false })
let platingChangeRejected = false
try { await replaySavedRoutes(incorrectlyPlatedHole) } catch { platingChangeRejected = true }
if (!platingChangeRejected) throw new Error("A changed hole plating flag must invalidate saved routing")
const movedHole = copy()
movedHole.obstacles[holeIndices[0]].width += 0.01
let holeChangeRejected = false
try { await replaySavedRoutes(movedHole) } catch { holeChangeRejected = true }
if (!holeChangeRejected) throw new Error("A changed mounting drill must invalidate saved routing")
const reordered = copy()
const obstacle = reordered.obstacles.find(o => o.connectedTo.length > 2)!
obstacle.connectedTo.reverse()
obstacle.connectedTo.push(obstacle.connectedTo[0])
await expectRoutes(reordered)
const lostConnection = copy()
lostConnection.connections[0].name = "changed_net_binding"
let netChangeRejected = false
try { await replaySavedRoutes(lostConnection) } catch { netChangeRejected = true }
if (!netChangeRejected) throw new Error("A changed named net binding must invalidate saved routing")
const browserNoise = copy()
browserNoise.connections.find(c=>c.pointsToConnect.length>1)!.pointsToConnect[1].x += 1e-14
await expectRoutes(browserNoise)
const movedPad = copy()
movedPad.connections.find(c=>c.pointsToConnect.length>1)!.pointsToConnect[1].x += 0.01
let rejected = false
try { await replaySavedRoutes(movedPad) } catch { rejected = true }
if (!rejected) throw new Error("A real 0.01 mm pad move must invalidate saved routing")
const changedClearance = { ...copy(), minTraceToHoleEdgeClearance: 0.25 } as Input
rejected = false
try { await replaySavedRoutes(changedClearance) } catch { rejected = true }
if (!rejected) throw new Error("A changed drill-clearance rule must invalidate saved routing")
console.log("PASS: captured and equivalent browser inputs replay copper; net, pad, drill, plating and clearance changes are rejected")