shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
scripts/check-round9-regression.mjs
import fs from "node:fs"
import crypto from "node:crypto"
import assert from "node:assert/strict"
const base="../../checkpoints/nema17-round8-validated-local-20261020-ii8Wrw/restore-verified"
const read=p=>JSON.parse(fs.readFileSync(p,"utf8"))
const hash=p=>crypto.createHash("sha256").update(fs.readFileSync(p)).digest("hex")
const before=read(`${base}/dist/index/circuit.json`),after=read("dist/index/circuit.json")
const of=(c,t)=>c.filter(e=>e.type===t)
for(const t of ["source_component","source_port","source_trace","source_net","source_component_internal_connection",
"pcb_board","pcb_hole","pcb_plated_hole","schematic_component","schematic_port","schematic_trace","schematic_sheet",
"pcb_silkscreen_circle"])
assert.deepEqual(of(after,t),of(before,t),`unchanged ${t}; no net, value, constraint, outline, mount or schematic edits`)
assert.deepEqual(after.filter(e=>e.type.endsWith("_error")),[],"native errors must be zero")
const source=c=>new Map(of(c,"source_component").map(e=>[e.source_component_id,e.name]))
const oldNames=source(before),names=source(after)
const oldParts=new Map(of(before,"pcb_component").map(p=>[oldNames.get(p.source_component_id),p]))
const moves=read("round9-placement.json")
assert.deepEqual(Object.keys(moves).sort(),["C_BUCK_OUT1","C_ETH_RXP_AC","R_ETH_RAIL_AB","R_ISEN_B","C_REF","R_ISET_210","C_ETH_7"].sort())
for(const p of of(after,"pcb_component")){
const name=names.get(p.source_component_id),q=oldParts.get(name),move=moves[name]
if(move){
assert.ok(Math.hypot(p.center.x-move.x,p.center.y-move.y)<1e-6,`${name} checked position`)
assert.equal(p.rotation,move.rotation);assert.equal(p.layer,move.layer)
assert.ok(Math.abs(p.width*p.height-q.width*q.height)<1e-5,`${name} unchanged footprint envelope area`)
} else for(const key of ["center","rotation","width","height","layer","do_not_place"])
assert.deepEqual(p[key],q[key],`unaffected placement ${name}.${key}`)
assert.equal(p.do_not_place,q.do_not_place,`${name} assembly option unchanged`)
}
// One electrical port can have multiple physical lands (including lands with
// no port). Match the stable physical ID, not a lossy source-port map.
const oldPads=new Map(of(before,"pcb_smtpad").map(e=>[e.pcb_smtpad_id,e]))
for(const e of of(after,"pcb_smtpad")){
const p=oldPads.get(e.pcb_smtpad_id);assert.ok(p)
assert.equal(e.pcb_port_id,p.pcb_port_id,"unchanged physical-land electrical port")
if(e.shape==="polygon"){
assert.deepEqual(e,p,"USB polygon lands remain byte-for-byte unchanged")
continue
}
const dims=e.shape==="circle"?[e.radius??e.diameter/2]:[e.width,e.height].sort((a,b)=>a-b)
const prior=p.shape==="circle"?[p.radius??p.diameter/2]:[p.width,p.height].sort((a,b)=>a-b)
assert.equal(e.shape,p.shape)
assert.ok(dims.every((v,i)=>Math.abs(v-prior[i])<1e-6),"same land size; no shrinking or pad removal")
}
assert.equal(of(after,"pcb_smtpad").length,of(before,"pcb_smtpad").length)
const oldLabels=new Map(of(before,"pcb_silkscreen_text").map(e=>[e.pcb_silkscreen_text_id,e]))
for(const e of of(after,"pcb_silkscreen_text")){
const p=oldLabels.get(e.pcb_silkscreen_text_id);assert.ok(p)
if(["1","2","3","4"].includes(e.text)){
assert.equal(e.anchor_position.x,p.anchor_position.x)
assert.equal(e.anchor_position.y,-17.25)
assert.deepEqual({...e,anchor_position:p.anchor_position},p,"only motor pin-number Y moves for new body clearance")
}else assert.deepEqual(e,p,"other legends unchanged")
}
assert.equal(of(after,"pcb_silkscreen_text").length,oldLabels.size)
const vias=of(after,"pcb_via")
assert.ok(vias.every(v=>v.hole_diameter===.3&&v.outer_diameter===.45&&v.layers.length===4&&["top","inner1","inner2","bottom"].every(l=>v.layers.includes(l))),"all real manufactured vias 0.30/0.45 full stack")
const solder=read("reports/solder-process-audit.json"),prior=read(`${base}/reports/solder-process-audit.json`)
assert.equal(solder.circuitSha256,hash("dist/index/circuit.json"))
assert.equal(prior.ordinaryPadDrillIntersections.length,6)
assert.deepEqual(solder.ordinaryPadDrillIntersections,[],"all six ordinary solder-land/drill conflicts cleared, with none added")
const key=e=>[e.part,e.pin,e.layer,e.x,e.y].join(":")
assert.deepEqual(solder.thermalPadDrillIntersections.map(key).sort(),prior.thermalPadDrillIntersections.map(key).sort(),"intentional thermal-pad arrays are not removed to game the audit")
const modules=read("reports/module-test-regression.json")
assert.equal(modules.currentPassed,32);assert.equal(modules.currentFailed,0);assert.equal(modules.addedFailures,0)
let firmwareFiles=0
function visit(dir){for(const e of fs.readdirSync(`${base}/${dir}`,{withFileTypes:true})){const p=`${dir}/${e.name}`;if(e.isDirectory())visit(p);else if(/\.(c|h|py|md)$/.test(p)||e.name==="Makefile"){assert.equal(hash(p),hash(`${base}/${p}`),`unchanged firmware ${p}`);firmwareFiles++}}}
visit("firmware")
const report={checkedAt:new Date().toISOString(),circuitSha256:hash("dist/index/circuit.json"),baselineSha256:hash(`${base}/dist/index/circuit.json`),
allElectricalConnectionsAndValuesUnchanged:true,allOriginalTraceConstraintsUnchanged:true,footprintAndLandSizesUnchanged:true,
outlineAndAllHolesUnchanged:true,movedComponents:moves,ordinaryConflictsBefore:6,ordinaryConflictsAfter:0,
thermalIntersectionRecords:solder.thermalPadDrillIntersections.length,thermalStitchesRemoved:0,
throughVias:vias.length,viaHoleMm:.3,viaPadMm:.45,layers:4,thicknessMm:1.6,
firmwareFilesUnchanged:firmwareFiles,moduleTests:{passed:32,failed:0,originalTestsUnchanged:true},
nativeErrors:0,nativeWarnings:after.filter(e=>e.type.endsWith("_warning")).length,
fabricationReady:false,localChangesNotPushed:true}
fs.writeFileSync("reports/round9-regression.json",JSON.stringify(report,null,2)+"\n")
console.log(JSON.stringify(report))