project
Adom Custom Electronic Load
Public Made by Adomby adom
Two-layer digitally controlled electronic-load prototype. Compact R11 is under active electrical, cooling and probe-access review; not a manufacturing release.
← Commit history
Record unclipped current rework candidate and pending mesh/thermal checks
7 files changed
+283−1
design/current-hotspot-candidates.json+91−1@@ -110,7 +110,97 @@ "warnings": 15, "opens": 0, "landed": false,- "status": "Native copper-net invariant, refill/save/reload and DRC passed; current solve pending"+ "status": "Diagnostic only; current not accepted",+ "unclippedCurrentPeaks": {+ "DUT_POS": 48.634830474853516,+ "LOAD_POS": 64.95350646972656,+ "GND": 66.49111938476562,+ "SHUNT_P": 60.97330856323242,+ "DUT_FUSED": 45.40793228149414,+ "5V_AUX": 18.182907104492188+ },+ "meshMm": 0.2+ },+ {+ "candidate": "v12",+ "sha256": "392750669089e8214d520f37f267346b294312925b7dd3d5526b1f29e7a0b6ce",+ "errors": 2,+ "warnings": 15,+ "opens": 0,+ "landed": false,+ "status": "Rejected by DRC"+ },+ {+ "candidate": "v13",+ "sha256": "9de9d07b62bac91a8e45ff5e45cdc4952abb2e1923f3d0af433bfa19acf08d85",+ "errors": 0,+ "warnings": 15,+ "opens": 0,+ "landed": false,+ "status": "Diagnostic only; current not accepted",+ "unclippedCurrentPeaks": {+ "DUT_POS": 48.634830474853516,+ "LOAD_POS": 64.74903106689453,+ "GND": 60.01053237915039,+ "SHUNT_P": 59.331459045410156,+ "DUT_FUSED": 45.40793228149414,+ "5V_AUX": 18.182907104492188+ },+ "meshMm": 0.2+ },+ {+ "candidate": "v14",+ "sha256": "4eb9ce41726056614a81849ef385bafcd524bd8f95f25f43e1a07bffdb2abb51",+ "errors": 1,+ "warnings": 16,+ "opens": 0,+ "landed": false,+ "status": "Rejected by DRC"+ },+ {+ "candidate": "v15",+ "sha256": "e324f7457e31683c3fdfa5b590b0b1c27f4a916fbecc3d8b4a8a5ffd36fbde31",+ "errors": 0,+ "warnings": 16,+ "opens": 0,+ "landed": false,+ "status": "Current still fails; additional dangling-track warning requires correction",+ "unclippedCurrentPeaks": {+ "DUT_POS": 48.634830474853516,+ "LOAD_POS": 62.81111145019531,+ "SHUNT_P": 59.331459045410156,+ "GND": 58.70158004760742,+ "DUT_FUSED": 45.40793228149414,+ "5V_AUX": 18.182907104492188+ },+ "meshMm": 0.2+ },+ {+ "candidate": "v16",+ "sha256": "1f790a271eb57dbd5706728e055d99b0348acbe6c77768200fd3f572f3158af7",+ "errors": 0,+ "warnings": 15,+ "opens": 0,+ "landed": false,+ "status": "Diagnostic only; current not accepted",+ "unclippedCurrentPeaks": {+ "DUT_POS": 48.634830474853516,+ "LOAD_POS": 63.52288818359375,+ "SHUNT_P": 59.331459045410156,+ "GND": 58.70158004760742,+ "DUT_FUSED": 45.40793228149414,+ "5V_AUX": 18.182907104492188+ },+ "meshMm": 0.2+ },+ {+ "candidate": "v17",+ "sha256": "7d1170bf1c4a5b15d4cc90928034133ef442d2f87fcb177b8b43c0fc262fe547",+ "errors": 1,+ "warnings": 15,+ "opens": 0,+ "landed": false,+ "status": "Rejected by DRC" } ] }
design/hotspot-v18-review.jsonadded+41@@ -0,0 +1,41 @@+{+ "candidate": "v18",+ "boardSha256": "4c9931fc668f0732827d11240590c06fb1ad11b957bcff0233bf0f33072e221e",+ "landed": false,+ "formalAcceptance": false,+ "native": {+ "errors": 0,+ "opens": 0,+ "warnings": 15,+ "copperNetInvariant": "passed before/after native connectivity, refill, save and reload"+ },+ "coarseMeshMm": 0.2,+ "unclippedCurrentPeaks": {+ "DUT_POS": 48.634830474853516,+ "LOAD_POS": 57.297489166259766,+ "SHUNT_P": 59.331459045410156,+ "GND": 58.70158004760742,+ "DUT_FUSED": 45.40793228149414,+ "5V_AUX": 18.182907104492188+ },+ "fineMesh": "0.1 mm solve in progress",+ "thermalAttributionOnly": {+ "note": "Q1 loss omitted to isolate diode/shunt board heating; not an operating case or thermal acceptance",+ "parts": [+ {+ "reference": "D1",+ "watts": 1.65,+ "riseC": 107.2,+ "riseMovingC": 82.4+ },+ {+ "reference": "RSH1",+ "watts": 0.18,+ "riseC": 43.7,+ "riseMovingC": 25.1+ }+ ]+ },+ "sharedFieldsFix": "https://wiki.adom.inc/adom/adom-fields/prs/8",+ "sharedSolverDeployed": false+}
design/step12-hotspot-progress.pngadded⋯ 1 unchanged line ⋯
docs/completion-plan.md+10@@ -1,3 +1,13 @@+## Active checkpoint — unclipped current rework v18++The isolated candidate passes native net-intent invariants, DRC (zero errors and opens; 15 inherited library warnings), and the six-net 0.2 mm current screen. Ground 58.70, drain 57.30 and shunt 59.33 A/mm² remain below the unchanged 60 threshold at this mesh only. A 0.1 mm three-net refinement is running. The live PCB is unchanged. No current or thermal gate is marked passed.++The saved diff inventories 37 copper changes and moves Q2, R7 and U3. A landing plan is prepared but must not execute before the refinement review. A heat-source attribution run, deliberately omitting Q1 loss, still predicts D1 copper rise of 107.2 C in still air or 82.4 C at the model's moving-air coefficient. This is not an operating case or a cooling qualification; it identifies the diode spread as remaining work.++Twelve individually described action clips are open in the video-post storyboard. User feedback is pending before film concatenation; board work continues independently. The package's missing executable is already tracked as adom/adom-video-post#1; a local source build provides the review app without claiming a shared release fix.++Latest checkpoint: AIFlow 0.1.80 is published and installed (128 tests). Fields PR8 supersedes PR7 and contains unclipped current reporting plus consistent CLI/viewer summaries. Native v11 candidate passes DRC and copper-net preservation, but still fails density: GND 66.49, LOAD_POS 64.95, SHUNT_P 60.97 at 0.2 mm. v12 is under native checks; see tools/hotspot_via_candidate_v12.py. No candidate has landed. Raw video count is 69. Continue this current rework and full thermal/firmware/molecule/video scope; do not stop at this checkpoint.+ Critical current finding: Fields peakJ was display-clipped. Unclipped 0.2 mm screening reports LOAD_POS 68.6 and SHUNT_P 61.0 A/mm² above 60. The earlier isolated pass is invalid for maximum-density acceptance; live current stayed failed. Review the actual hotspot geometry and fix the source reporting before acceptance. See design/step12-unclipped-peaks.json. Latest 10 October work: AIFlow 0.1.79 is source-published, registry-published and installed; 127 workspace tests passed. Explicit branch-current analysis passed an isolated copy of this run, not the live acceptance gate. Fields PR 6 remains open on PR 5. The current ground review is quantifying analog-supply return offset on SHUNT_N_SENSE. A centerline resistor graph refused disconnected overlaps, so a separately instrumented copper-field diagnostic is being used instead. The dashboard is on step 12 and the reviewed raw video inventory is 68. Continue current/thermal engineering, not another source-release handoff.
tools/hotspot_via_candidate_v18.pyadded+85@@ -0,0 +1,85 @@+"""Run using native desktop KiCad Python; modifies only an isolated board copy."""+import pcbnew,sys,json+from pathlib import Path+source=Path(sys.argv[1]);out=Path(sys.argv[2]);board=pcbnew.LoadBoard(str(source));nets=board.GetNetsByName()+changes=[]+for net,x,y in [('GND',133.0,100.8),('GND',138.0,100.8),('GND',139.0,93.0),('GND',136.5,93.0),('GND',139.4,90.4),('GND',139.4,91.4),('GND',139.4,92.4)]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(y)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets[net]);board.Add(via);changes.append({'kind':'via','net':net,'at':[x,y],'sizeMm':.8,'drillMm':.4,'uuid':str(via.m_Uuid.AsString())})+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'net':'LOAD_POS','sizeMm':1.0,'drillMm':.6})+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['91eed079-74e4-4863-8dc1-f211a19b6ee0','ad353074-a567-44a9-a20a-744b8057c5a9']:+ board.Remove(item);changes.append({'kind':'remove-track','uuid':uid})+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'sizeMm':1.0,'drillMm':.6})+def track(net,layer,a,b,width):+ t=pcbnew.PCB_TRACK(board);t.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(a[0]),pcbnew.FromMM(a[1])));t.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(b[0]),pcbnew.FromMM(b[1])));t.SetWidth(pcbnew.FromMM(width));t.SetLayer(layer);t.SetNet(nets[net]);board.Add(t);changes.append({'kind':'track','net':net,'start':a,'end':b,'widthMm':width,'layer':board.GetLayerName(layer)})+for a,b in [((127.46,82),(127.46,83.3)),((127.46,83.3),(126.4,84.36)),((126.4,84.36),(126.4,86.5))]:track('LOAD_POS',pcbnew.F_Cu,a,b,1.5)+for a,b in [((139.4,90.4),(139.4,92)),((139.4,92),(138.4,93)),((138.4,93),(136.5,93))]:track('GND',pcbnew.B_Cu,a,b,1.5)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();xy=(round(pcbnew.ToMM(p.x),3),round(pcbnew.ToMM(p.y),3))+ if xy in [(139.4,86.8),(136.5,93.0),(139.4,90.4),(139.4,91.4),(139.4,92.4)]:item.SetWidth(pcbnew.FromMM(1));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','at':xy,'sizeMm':1,'drillMm':.6})+for fp in board.GetFootprints():+ delta={'Q2':-.3,'U3':.3,'R7':.1}.get(fp.GetReference())+ if delta:+ pos=fp.GetPosition();pos.x+=pcbnew.FromMM(delta);fp.SetPosition(pos);changes.append({'kind':'move-footprint','ref':fp.GetReference(),'dxMm':delta})+for item in list(board.GetTracks()):+ if item.GetNetname()=='SHUNT_P_SENSE' and isinstance(item,pcbnew.PCB_VIA):+ pos=item.GetPosition()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:+ pos.x=pcbnew.FromMM(123.3);item.SetPosition(pos);changes.append({'kind':'move-sense-via','from':[123.6,85.4],'to':[123.3,85.4]})+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='SHUNT_P_SENSE':+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:pos.x=pcbnew.FromMM(123.3);setter(pos)+ if item.GetNetname()=='SHUNT_N_SENSE' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter();x,y=pcbnew.ToMM(pos.x),pcbnew.ToMM(pos.y)+ if 128.79<x<129.31 and 87.29<y<88.81:pos.x+=pcbnew.FromMM(.2);setter(pos)+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)))+ if uid=='13d2f936-e4d3-40cb-9bf7-1b6b26ca4cf2':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)));item.SetWidth(pcbnew.FromMM(1.5))+ if uid=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.2)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.2)));item.SetWidth(pcbnew.FromMM(1.9))+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-126.4)<.001 and abs(pcbnew.ToMM(p.y)-86.5)<.001:p.y=pcbnew.FromMM(86.7);setter(p)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.B_Cu or item.GetNetname()!='SHUNT_P_SENSE':continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(y-85.4)<.001 and (abs(x-123.3)<.001 or abs(x-132)<.001):+ p.y=pcbnew.FromMM(85.6)+ if abs(x-132)<.001:p.x=pcbnew.FromMM(132.2)+ setter(p)+# Ease the drain right-angle turn; relocate its adjacent setpoint via with endpoints.+track('LOAD_POS',pcbnew.B_Cu,(126.35,89.0),(125.65,89.7),1.5)+# Preserve explicit branch junctions instead of overlapped dangling endpoints.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4));item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.05),pcbnew.FromMM(86.7)))+ if uid in ['b486fb59-b64e-41ee-a1a7-5a72fcf0a780','8e5d5e2b-78f3-4ed2-af96-409d2ea9d57b']:+ a=item.GetStart();b=item.GetEnd();start=(pcbnew.ToMM(a.x),pcbnew.ToMM(a.y));end=(pcbnew.ToMM(b.x),pcbnew.ToMM(b.y))+ mid=(126.35,89.0) if uid=='b486fb59-b64e-41ee-a1a7-5a72fcf0a780' else (125.65,89.7)+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,start,mid,1.5);track('LOAD_POS',pcbnew.B_Cu,mid,end,1.5)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(86.7)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['LOAD_POS']);board.Add(via)+expected_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+board.BuildConnectivity()+actual_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+assert actual_nets==expected_nets, {'unexpectedNetChanges':[(k,v,actual_nets.get(k)) for k,v in expected_nets.items() if actual_nets.get(k)!=v]}+for zone in board.Zones():zone.SetNeedRefill(True)+pcbnew.ZONE_FILLER(board).Fill(board.Zones());assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}==expected_nets, 'Zone fill changed copper nets'+pcbnew.SaveBoard(str(out),board)+readback=pcbnew.LoadBoard(str(out))+assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in readback.GetTracks()}==expected_nets, 'Save/readback changed copper nets' +out.with_suffix('.changes.json').write_text(json.dumps({'source':str(source),'candidate':str(out),'changes':changes},indent=2));print(json.dumps({'saved':str(out),'changes':changes}))
tools/plot_hotspot_progress.pyadded+22@@ -0,0 +1,22 @@+"""Scientific diagnostic figure from saved Fields reports, never a gate override."""+import json+from pathlib import Path+import matplotlib+matplotlib.use('Agg')+import matplotlib.pyplot as plt+root=Path(__file__).resolve().parents[1]+base=json.loads((root/'design/fields-unclipped-diagnostic/fields.json').read_text());new=json.loads((root/'checkpoints/unclipped-hotspots-v1/fields-v18/fields.json').read_text())+keys=['GND','LOAD_POS','SHUNT_P'];a={n['net']:n['peakJ'] for n in base['nets']};b={n['net']:n['peakJ'] for n in new['nets']}+plt.rcParams.update({'font.size':15,'text.color':'#e6edf3','axes.labelcolor':'#e6edf3','xtick.color':'#c1ccd8','ytick.color':'#c1ccd8'})+fig,ax=plt.subplots(figsize=(16,9),facecolor='#101820');ax.set_facecolor('#101820')+x=list(range(3));ax.bar([i-.19 for i in x],[a[k] for k in keys],.36,color='#d58b50',label='Saved board 19');ax.bar([i+.19 for i in x],[b[k] for k in keys],.36,color='#56a6cf',label='Isolated candidate v18')+for i,k in enumerate(keys):+ for off,value in [(-.19,a[k]),(.19,b[k])]:ax.text(i+off,value+1,f'{value:.1f}',ha='center',fontsize=18)+ax.axhline(60,color='#fa7180',linewidth=2,linestyle='--',label='Unchanged screen: 60 A/mm²');ax.set_xticks(x,keys);ax.set_ylim(0,90);ax.set_ylabel('Unclipped modeled maximum (A/mm²)');ax.grid(axis='y',alpha=.15);ax.set_axisbelow(True)+for spine in ax.spines.values():spine.set_color('#465360')+legend=ax.legend(loc='upper right',frameon=False,fontsize=13)+for t in legend.get_texts():t.set_color('#e6edf3')+fig.suptitle('Current rework: improvement, not acceptance',fontsize=25,y=.94)+fig.text(.1,.065,'0.2 mm mesh · nominal 35 µm copper · 3 A main-path screen · coarse-mesh peaks below 60; finer mesh pending\nLive board unchanged. Finer mesh, full boundary conditions and thermal qualification remain open.',fontsize=13,color='#bec9d3')+fig.subplots_adjust(top=.84,bottom=.19,left=.1,right=.95)+fig.savefig(root/'design/step12-hotspot-progress.png',dpi=120,facecolor=fig.get_facecolor())
tools/review_hotspot_diff.pyadded+34@@ -0,0 +1,34 @@+"""Inventory every saved geometry change; does not replace native DRC or electrical review."""+import json,sys,hashlib+from pathlib import Path+from sexp import parse,find,first+root=Path(__file__).resolve().parents[1]+base=root/'checkpoints/unclipped-hotspots-v1/baseline.kicad_pcb'+candidate=Path(sys.argv[1]);candidate=candidate if candidate.is_absolute() else root/candidate++def identity(x):return first(x,'uuid')[1]+def copper(b):+ result={}+ for kind in ['segment','via']:+ for x in find(b,kind):+ result[identity(x)]={'kind':kind,**{k:first(x,k)[1:] for k in ['start','end','at','width','size','drill','layer','layers','net'] if first(x,k)}}+ return result++def footprints(b):+ result={}+ for fp in find(b,'footprint'):+ ref=next(p[2] for p in find(fp,'property') if p[1]=='Reference')+ result[ref]={'at':first(fp,'at')[1:],'pads':{str(p[1]):{'at':first(p,'at')[1:],'size':first(p,'size')[1:],'net':(first(p,'net') or ['net',''])[1:]} for p in find(fp,'pad')}}+ return result++a=parse(base.read_text());b=parse(candidate.read_text());ca,cb=copper(a),copper(b);fa,fb=footprints(a),footprints(b)+changes=[]+for uid in sorted(set(ca)|set(cb)):+ if ca.get(uid)!=cb.get(uid):changes.append({'uuid':uid,'before':ca.get(uid),'after':cb.get(uid)})+netchanges=[x for x in changes if x['before'] and x['after'] and x['before'].get('net')!=x['after'].get('net')]+fpchanges=[{'ref':k,'before':fa.get(k),'after':fb.get(k)} for k in sorted(set(fa)|set(fb)) if fa.get(k)!=fb.get(k)]+assert not netchanges,netchanges+assert fa.keys()==fb.keys(),'Footprint inventory changed'+for k in fa:assert fa[k]['pads']==fb[k]['pads'],f'{k} local pad geometry or connectivity changed'+report={'baselineSha256':hashlib.sha256(base.read_bytes()).hexdigest(),'candidateSha256':hashlib.sha256(candidate.read_bytes()).hexdigest(),'formalAcceptance':False,'existingCopperNetChanges':netchanges,'footprintChanges':fpchanges,'copperChanges':changes,'note':'All track/via changes inventoried by saved UUID. Native net invariant additionally checks newly created copper before and after connectivity/fill/save. Zone fill geometry and placement still require visual/native review.'}+out=candidate.with_suffix('.complete-diff.json');out.write_text(json.dumps(report,indent=2)+'\n');print(json.dumps({'report':str(out),'copperChanges':len(changes),'footprintChanges':[x['ref'] for x in fpchanges],'existingCopperNetChanges':0}))