app
AI Flow: Reverse Engineering
Public Made by Adomby adom
Photos of a PCB to a tagged KiCad copy: parts placed, copper and nets with their evidence, a schematic, a measured confidence score, bench tests and a video of the run.
main
Caleb
Version 0.3.0: new copper reader, dome via filter, every layer read from the photos, net evidence, measured confidence;…
e874d7d
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
<!doctype html><meta charset=utf-8><style>
@font-face{font-family:Sat;src:url(fonts/satoshi-400.woff2);font-weight:400}@font-face{font-family:Sat;src:url(fonts/satoshi-500.woff2);font-weight:500}
@font-face{font-family:Fam;src:url(fonts/familjen-grotesk-500.woff2);font-weight:500}@font-face{font-family:Fam;src:url(fonts/familjen-grotesk-700.woff2);font-weight:700}
@font-face{font-family:Jet;src:url(fonts/jetbrains-mono-400.woff2);font-weight:400}@font-face{font-family:Jet;src:url(fonts/jetbrains-mono-500.woff2);font-weight:500}
body{margin:0;background:#0d1117}canvas{display:block}</style><canvas id=c width=1920 height=1080></canvas><script>
const c=document.getElementById('c').getContext('2d');c.imageSmoothingQuality='high';
const BG='#0d1117',PANEL='#161b22',EDGE='#30363d',INK='#e6edf3',MUT='#8b949e',TEAL='#00b8b0',TEAL2='#00e6dc',AMB='#d29922',RED='#f85149',GRN='#3fb950',VIO='#a371f7';
let D,IM={};const LEAD=.25,TAIL=.45;
const load=n=>new Promise((r,j)=>{const i=new Image();i.onload=()=>{IM[n]=i;r()};i.onerror=j;i.src=n+'.jpg'});
const cl=(x,a=0,b=1)=>Math.min(b,Math.max(a,x)),ez=x=>{x=cl(x);return x*x*(3-2*x)},lerp=(a,b,t)=>a+(b-a)*t;
const K=(a,b,t)=>a.map((v,i)=>lerp(v,b[i],ez(t)));
// camera: [x,y,w] in image fractions, height follows the target's aspect
function img(n,cam,R,al=1){const I=IM[n];if(al<=0)return;const[x,y,w]=cam,rw=R[2],rh=R[3],sw=w*I.width,sh=sw*rh/rw;c.save();c.globalAlpha=al;rr(R,14);c.clip();c.drawImage(I,x*I.width,y*I.height,sw,sh,R[0],R[1],rw,rh);c.restore()}
function rr(R,r){c.beginPath();c.roundRect(R[0],R[1],R[2],R[3],r)}
function frame(R){c.save();rr(R,14);c.lineWidth=1.5;c.strokeStyle=EDGE;c.stroke();c.restore()}
const BW=1168,B=[36,50,BW,980]; // board panel, 1.182 aspect
const COL=[1244,46,640,988];
const W=2946,H=2471;// mm to panel px given camera
const mm=(x,y,cam,R)=>{const[cx,cy,cw]=cam,s=R[2]/(cw*W);return[R[0]+((x-100)*40-cx*W)*s,R[1]+((y-100)*40-cy*H)*s,s]};
function clipR(R,f){c.save();rr(R,14);c.clip();f();c.restore()}
function T(s,x,y,o={}){c.save();c.globalAlpha=o.a??1;c.font=`${o.w||400} ${o.s||24}px ${o.f||'Sat'}`;c.fillStyle=o.c||INK;c.textAlign=o.al||'left';c.textBaseline='alphabetic';c.fillText(s,x,y);c.restore()}
function wrap(s,w,font){c.save();c.font=font;const L=[];let l='';for(const t of s.split(' ')){const n=l?l+' '+t:t;if(c.measureText(n).width>w&&l){L.push(l);l=t}else l=n}L.push(l);c.restore();return L}
function P(s,x,y,w,o={}){const f=`${o.w||400} ${o.s||24}px ${o.f||'Sat'}`;let yy=y;for(const l of wrap(s,w,f)){T(l,x,yy,o);yy+=(o.lh||(o.s||24)*1.36)}return yy}
function head(i,title){T(title,COL[0],92,{f:'Fam',w:700,s:36})}
// stat card, slides in from cue
function stat(y,big,label,t,cue,col=TEAL,hgt=120){const a=ez((t-cue)/.45);if(a<=0)return y+hgt+14;const x=COL[0]+(1-a)*18;c.save();c.globalAlpha=a;c.fillStyle=PANEL;rr([x,y,COL[2],hgt],12);c.fill();c.strokeStyle=EDGE;c.lineWidth=1;c.stroke();c.fillStyle=col;rr([x,y+12,4,hgt-24],2);c.fill();c.restore();
T(big,x+26,y+54,{f:'Fam',w:700,s:42,c:col===MUT?INK:col,a});P(label,x+26,y+88,COL[2]-52,{s:21,c:'#c3ccd6',a,lh:27});return y+hgt+14}
function card(s,x,y,w,o={}){const a=o.a??1;if(a<=0)return;const f=`${o.w||500} ${o.s||26}px ${o.f||'Sat'}`,L=wrap(s,w-48,f),h=L.length*(o.s||26)*1.36+30;c.save();c.globalAlpha=a*.94;c.fillStyle='#10161e';rr([x,y-h,w,h],12);c.fill();c.strokeStyle=o.b||TEAL;c.lineWidth=1.5;c.stroke();c.restore();L.forEach((l,i)=>T(l,x+24,y-h+15+(o.s||26)*(1.02+i*1.36),{...o,a}))}
const on=(t,a,f=.4)=>ez((t-a)/f);const seg=(t,a,b,f=.45)=>ez((t-a)/f)*(1-ez((t-b)/f));
function ring(x,y,r,col,lw=2.5,a=1){c.save();c.globalAlpha=a;c.strokeStyle=col;c.lineWidth=lw;c.beginPath();c.arc(x,y,r,0,7);c.stroke();c.restore()}
function label(s,x,y,o={}){c.save();c.font=`500 ${o.s||20}px Sat`;const w=c.measureText(s).width+22;c.globalAlpha=o.a??1;c.fillStyle='rgba(13,17,23,.86)';rr([x,y-26,w,34],7);c.fill();c.strokeStyle=o.b||EDGE;c.lineWidth=1;c.stroke();c.fillStyle=o.c||INK;c.textBaseline='alphabetic';c.fillText(s,x+11,y-3);c.restore()}
const FULL=[0,0,1];const fit=(x=0,y=0,w=1)=>[x,y,w];
const PROMPT="Can you rewrite this for reverse engineering PCBs from pictures (see Driver 1 through 4). 1) Identify as many parts as possible. 2) Identify functional blocks. 3) Identify zones and routing. 4) Directly mimic all of this that is visible. 5) Save the project, then attempt to make the final connections and propose the layer count by how challenging it is to do so. 6) Do a final looping review stating your confidence in the results. 7) Provide a list of tests that can confirm anything that is unknown.";
// keyframed camera: [[time,[x,y,w]],...]
const path=(t,K0)=>{if(t<=K0[0][0])return K0[0][1];for(let i=1;i<K0.length;i++)if(t<K0[i][0]){const a=K0[i-1],b=K0[i];return K(a[1],b[1],(t-a[0])/(b[0]-a[0]))}return K0[K0.length-1][1]};
const FB=[0,0,1920,1080];
const chipRow=(rows,x,y,t,o={})=>{let xx=x;c.save();rows.forEach(([col,s,cue])=>{const a=cue==null?1:on(t,cue);c.font='500 21px Sat';const w=c.measureText(s).width;c.globalAlpha=.25+.75*a;c.fillStyle=col;rr([xx,y-18,18,18],4);c.fill();T(s,xx+26,y-1,{s:21,a:.25+.75*a});xx+=w+56});c.restore()};
function stills(list,t,d,R,zoom=.92){const q=t/d*list.length,i=Math.min(list.length-1,Math.floor(q)),z=lerp(1,zoom,q-i);img(list[i],[(1-z)/2,(1-z)/2,z],R);if(q-i<.2&&i>0)img(list[i-1],[(1-zoom)/2,(1-zoom)/2,zoom],R,1-(q-i)/.2)}
const N=x=>Number(x).toLocaleString('en-US');
const SC={
board(t,C,d){const k=[[0,[.03,.06,.40]],[C[0]+2.2,[.03,.06,.40]],[C[0]+4.8,[.30,.08,.38]],[C[1]+.2,[.35,.30,.38]],[C[2]-.1,[.38,.35,.32]],[C[2]+1.2,[.36,.48,.36]]];const m=path(t,k);const a=1-on(t,C[2]+1.7,.6);if(a>0)img('raw_Driver1',m,FB,a);
const b=on(t,C[2]+1.7,.6);if(b>0){const G=[[60,46,880,492],[980,46,880,492],[60,548,880,492],[980,548,880,492]];['raw_Driver1','raw_Driver2','raw_Driver3','raw_Driver4'].forEach((n,i)=>{const z=lerp(1,.94,(t-C[2]-1.7)/4),I=IM[n],asp=G[i][2]/G[i][3],hw=z*Math.min(1,asp*I.height/I.width);img(n,[.5-hw/2,.5-(hw*I.width/asp/I.height)/2,hw],G[i],b);label(['Top, photo 1','Top, photo 2','Bottom, photo 3','Bottom, photo 4'][i],G[i][0]+14,G[i][1]+46,{a:b})})}},
underside(t,C,d){const k=[[0,[.08,.50,.42]],[C[1]+.2,[.08,.55,.40]],[C[1]+3.6,[.42,.24,.36]],[C[2]+.2,[.48,.42,.36]],[d,[.58,.58,.38]]];img('raw_Driver3',path(t,k),FB)},
ask(t,C,d){const R=[120,70,1680,500];c.fillStyle=PANEL;rr(R,16);c.fill();c.strokeStyle=EDGE;c.lineWidth=1.5;c.stroke();T('The first prompt',R[0]+36,R[1]+50,{f:'Fam',w:700,s:28});T('typed again from the saved text',R[0]+250,R[1]+50,{s:19,c:MUT});
const n=Math.floor(PROMPT.length*cl(t/(C[1]-.4))),s=PROMPT.slice(0,n),f='400 27px Jet',L=wrap(s,R[2]-76,f);L.forEach((l,i)=>T(l,R[0]+38,R[1]+102+i*42,{f:'Jet',s:27,c:'#c9d4de'}));
const last=L[L.length-1]||'';c.font=f;const cx=R[0]+38+c.measureText(last).width+4;if(n<PROMPT.length||Math.floor(t*2.5)%2==0){c.fillStyle=TEAL;c.fillRect(cx,R[1]+102+(L.length-1)*42-24,13,30)}
const Q=[['"attribute every piece of copper to a net"',C[1]+.2],['"exactly match the photos on the pcb layers"',C[1]+2.4],['"kept looping until you achieved 90% confidence"',C[1]+4.8]];Q.forEach(([s,cu],i)=>{const a=on(t,cu);if(a<=0)return;const y=610+i*140;c.save();c.globalAlpha=a;c.fillStyle=PANEL;rr([120+(1-a)*16,y,1680,120],14);c.fill();c.strokeStyle=TEAL;c.lineWidth=1.5;c.stroke();c.restore();T(s,170,y+74,{f:'Fam',w:500,s:40,a})})},
flow(t,C,d){img('f_rec133',K([0,0,.62],[.0,.3,.62],t/d),FB);const m=[['A Rust program measures. An AI supplies the judgment.',C[0]+.3,C[1]],['11 steps, each with a check that has to pass',C[1]+.1,99]];m.forEach(([s,a,b])=>card(s,60,1040,1200,{a:seg(t,a,b),s:30}))},
photos(t,C,d){const L=[60,120,880,738],Rr=[980,120,880,738];const a1=seg(t,-1,C[1]-.1);if(a1>0){const z=lerp(1,.94,t/5);for(const[n,R,s]of[['outline-Driver1',L,'Board edge found, top'],['outline-Driver3',Rr,'Board edge found, bottom']]){const I=IM[n],asp=R[2]/R[3],hw=z*Math.min(1,asp*I.height/I.width);img(n,[.5-hw/2,.5-(hw*I.width/asp/I.height)/2,hw],R,a1);label(s,R[0]+14,R[1]+46,{a:a1})}}
const a2=on(t,C[1]-.1);if(a2>0){const p=(t-C[1]-.3)/1.5;img('top',[0,0,1],L,a2);label('Top, straightened',L[0]+14,L[1]+46,{a:a2});const f=Math.cos(Math.PI*cl(p));c.save();const cy=Rr[1]+Rr[3]/2;c.globalAlpha=a2;c.translate(0,cy);c.scale(1,Math.max(.002,Math.abs(f)));c.translate(0,-cy);if(f>0){const I=IM['prep-Driver3'];c.drawImage(I,0,0,I.width,I.width*Rr[3]/Rr[2],Rr[0],Rr[1],Rr[2],Rr[3])}else c.drawImage(IM.bottom,0,0,W,W*Rr[3]/Rr[2],Rr[0],Rr[1],Rr[2],Rr[3]);c.restore();label(f>0?'Bottom, as photographed':'Bottom, turned over',Rr[0]+14,Rr[1]+46,{a:a2});
const b=on(t,C[2]-.1);if(b>0){T('0.026 mm',960,950,{f:'Fam',w:700,s:60,c:TEAL,al:'center',a:b});T('RMS on 40 shared holes, each held out in turn',960,1000,{s:26,c:'#c3ccd6',al:'center',a:b})}}},
scale(t,C,d){head(3,'Scale');const a=K(FULL,[.62,.2,.36],(t-.4)/2.4);const b=K(a,[.31,.28,.22],(t-C[1]+.2)/1.8);const cam=t<C[1]-.2?a:b;img('top',cam,B);
clipR(B,()=>{D.holes.forEach(h=>{const[x,y,s]=mm(h.x,h.y,cam,B);ring(x,y,Math.max(5,h.outer_mm*20*s),'rgba(230,237,243,.45)',1.5)});
D.rows.forEach((r,i)=>{const a=on(t,1+i*.4);if(a<=0)return;const col=Math.abs(r.pitch_mm-2.54)<.1?AMB:TEAL;c.save();c.globalAlpha=a;c.strokeStyle=col;c.lineWidth=3;c.beginPath();r.holes.forEach((j,k)=>{const h=D.holes[j],[x,y]=mm(h.x,h.y,cam,B);k?c.lineTo(x,y):c.moveTo(x,y)});c.stroke();c.restore();r.holes.forEach(j=>{const h=D.holes[j],[x,y,s]=mm(h.x,h.y,cam,B);ring(x,y,Math.max(7,h.outer_mm*22*s),col,3,a)})});
const u=on(t,C[1]+.2);if(u>0){const[x,y,s]=mm(134.671,127.936,cam,B);c.save();c.translate(x,y);c.rotate(Math.PI/4);c.strokeStyle=TEAL2;c.lineWidth=3;c.globalAlpha=u;const q=6*40*s;c.strokeRect(-q/2,-q/2,q,q);c.restore()}});frame(B);
let y=120;y=stat(y,'2.00 mm','Five header rows on the top face',t,.8);y=stat(y,'2.54 mm','One row at a second standard pitch',t,2.2,AMB);y=stat(y,'6 x 6 mm','The DRV8323S body from TI, which settles which standard the rows are',t,C[1],MUT,146);y=stat(y,'46.6 px/mm','Solved from the hole rows. Photos are then redrawn at 40 px/mm.',t,C[1]+3,TEAL,146)},
parts(t,C,d){head(4,'Parts');const k=t<C[1]-.3?K(FULL,[.03,.03,.94],t/d):t<C[2]-.3?K(FULL,[.28,.27,.36],(t-C[1]+.3)/1.4):K([.28,.27,.36],FULL,(t-C[2]+.3)/1.2);img('top',k,B);
clipR(B,()=>{const n=D.parts.length;D.parts.forEach((p,i)=>{const a=on(t,.2+i/n*(C[1]-1),.12);if(a<=0)return;const[x,y,s]=mm(p.x,p.y,k,B);let col=TEAL2,al=a,r=Math.max(4,1.1*40*s);if(t>C[0]+2.8&&t<C[1]-.3){if(!p.pads){col=MUT;al=.4}else col=GRN}if(t>C[2]){if(p.unk)col=AMB;else{col=MUT;al=.35}}if(t>C[1]-.3&&t<C[2])al=a*.35;c.save();c.globalAlpha=al;c.strokeStyle=col;c.lineWidth=2.2;c.strokeRect(x-r,y-r,2*r,2*r);c.restore()})});frame(B);
const cn=Math.round(137*cl((t-.2)/(C[1]-1)));let y=120;y=stat(y,String(cn),'parts found',t,.2);y=stat(y,'103','placed by their own pads on the photo',t,C[0]+2.8,GRN);
const pa=seg(t,C[1],C[2]-.1);if(pa>0){const Z=[COL[0]+50,y,540,540];img('pin1-U2',[0,0,1],Z,pa);c.save();c.globalAlpha=pa;rr(Z,14);c.strokeStyle=EDGE;c.stroke();c.restore();label('U2: marking read, DRV8323S',Z[0]+12,Z[1]+500,{a:pa,c:TEAL2})}
stat(y,'47 of 137','have a value it can stand behind. The rest are mostly unmarked ceramics.',t,C[2],AMB,146)},
blocks(t,C,d){head(5,'Blocks');const k=K(FULL,[.02,.02,.96],t/d);img('top',k,B);const pal=[TEAL2,'#f0883e','#ffd33d','#3fb950','#a371f7','#58a6ff','#f778ba','#7ee787','#d2a8ff','#ff7b72','#e6edf3'];const b=D.blocks,st=C[1],sp=(d-st-.8)/b.length,tm=b.map((_,i)=>i==0?C[0]+.8:st+i*sp);
clipR(B,()=>b.forEach((q,i)=>{const a=on(t,tm[i],.35);if(a<=0)return;const cur=i==b.length-1||t<tm[i+1];if(q.id=='mechanical'){q.pts.forEach(p=>{const[x,y,s]=mm(p[0],p[1],k,B);ring(x,y,70*s,pal[i],3,a)});return}
c.save();c.beginPath();q.hull.forEach((p,j)=>{const[x,y]=mm(p[0],p[1],k,B);j?c.lineTo(x,y):c.moveTo(x,y)});c.closePath();c.lineJoin='round';c.strokeStyle=pal[i];c.lineWidth=cur?46:34;c.globalAlpha=a*(cur?.34:.16);c.stroke();c.fillStyle=pal[i];c.fill();c.restore();q.pts.forEach(p=>{const[x,y]=mm(p[0],p[1],k,B);c.save();c.globalAlpha=a*(cur?1:.55);c.fillStyle=pal[i];c.beginPath();c.arc(x,y,cur?6:4,0,7);c.fill();c.restore()})}));frame(B);
b.forEach((q,i)=>{const a=on(t,tm[i],.35),y=132+i*72;const cur=a>0&&(i==b.length-1||t<tm[i+1]);c.save();c.globalAlpha=.25+.75*a;c.fillStyle=cur?'#1c2128':PANEL;rr([COL[0],y,COL[2],62],10);c.fill();c.fillStyle=a>0?pal[i]:EDGE;rr([COL[0]+12,y+14,10,34],4);c.fill();c.restore();T(q.name,COL[0]+36,y+40,{f:'Fam',w:500,s:27,a:.3+.7*a});T(q.n+' parts',COL[0]+COL[2]-20,y+40,{f:'Jet',s:20,c:MUT,al:'right',a:.3+.7*a})})},
copper(t,C,d){const sideOn=on(t,C[2]-.1,.5);if(sideOn<1){head(6,'Reading the copper');const cam=K([.26,.28,.40],[.30,.32,.40],t/C[1]);const k2=K(cam,FULL,(t-C[1])/1.6);const u=t<C[1]?cam:k2;img('bottom',u,B);img('gaps-bottom',u,B,seg(t,C[0]+3.2,C[1]+.4,.6));img('copper-islands-bottom',u,B,on(t,C[1]+.3,.6));frame(B);label(t<C[0]+3.2?'Bottom photo':t<C[1]+.3?'Gap lines the reader found':'Islands, bottom face',B[0]+14,B[1]+46);
let y=120;y=stat(y,'A bit lighter','Copper against bare laminate, each compared with its own neighbourhood',t,.7,TEAL,146);y=stat(y,'Thin dark lines','The gaps between copper, followed from both ends. Silkscreen can be crossed, parts and pads can\'t.',t,C[0]+3.2,VIO,178);y=stat(y,'2,250','islands read across the two faces',t,C[1]+.3)}
if(sideOn>0){c.globalAlpha=sideOn;c.fillStyle=BG;c.fillRect(0,0,1920,1080);c.globalAlpha=1;const L=[30,92,920,772],Rr=[970,92,920,772],k=K(FULL,[.32,.26,.42],(t-C[2]-.6)/3.6);img('read_old_bot',k,L,sideOn);img('read_new_bot',k,Rr,sideOn);frame(L);frame(Rr);T('Bottom copper as read',60,66,{f:'Fam',w:700,s:36,a:sideOn});label('Published: 116 pours, 314 tracks in all',L[0]+14,L[1]+46,{a:sideOn});label('This run: 828 pours, 2,588 tracks in all',Rr[0]+14,Rr[1]+46,{a:sideOn});const b=on(t,C[3]);if(b>0){T('828 pours',60,960,{f:'Fam',w:700,s:60,c:GRN,a:b});T('2,588 tracks',440,960,{f:'Fam',w:700,s:60,c:GRN,a:b});T('The fine tracks now come through',60,1020,{s:30,c:'#c3ccd6',a:b})}}},
domes(t,C,d){const Rs=[[40,110,600,600],[660,110,600,600],[1280,110,600,600]],z=K([0,0,1],[.08,.08,.84],t/d);img('dome_photo',z,Rs[0]);img('dome_mid',z,Rs[1]);img('dome_new',z,Rs[2],on(t,C[1]+.2,.6));Rs.forEach(frame);T('A defect found by looking',40,76,{f:'Fam',w:700,s:36});label('Bottom photo',Rs[0][0]+14,Rs[0][1]+46);label('First merged test',Rs[1][0]+14,Rs[1][1]+46);label('This run',Rs[2][0]+14,Rs[2][1]+46,{a:on(t,C[1]+.2)});
const a=on(t,1.2);T('Solid pours under the mask, but the dark side of each dome was cut out of the copper.',40,772,{s:30,a});const b=on(t,C[1]+3.4);T('396',40,900,{f:'Fam',w:700,s:90,c:RED,a:b});T('to',240,890,{s:40,c:MUT,a:b});T('68',300,900,{f:'Fam',w:700,s:90,c:GRN,a:b});T('of 726 domes with a hole beside them',440,890,{s:32,a:b})},
vias(t,C,d){const L=[30,92,920,772],Rr=[970,92,920,772];const a=K(FULL,[.015,.015,.97],t/C[1]);const fa=[.0,.0,.22];const cam=t<C[1]?a:K(a,fa,(t-C[1])/1.6);const camO=cam;img('bottom_old',camO,L);img('bottom',cam,Rr);const dr=(R,V,col,n)=>clipR(R,()=>{V.forEach((v,i)=>{if(i>n)return;const[x,y,s]=mm(v[0],v[1],cam,R);if(x<R[0]-20||x>R[0]+R[2]+20||y<R[1]-20||y>R[1]+R[3]+20)return;const r=Math.max(2.4,.33*40*s);c.save();c.strokeStyle=col;c.lineWidth=r>6?2.4:1.6;c.beginPath();c.arc(x,y,r,0,7);c.stroke();c.restore()})});
const p=cl((t-.4)/(C[1]-.6));dr(L,D.vo,'#ff3cdc',D.vo.length*p);dr(Rr,D.vn,'#ff3cdc',D.vn.length*p);frame(L);frame(Rr);T('Vias on the bottom photo',60,66,{f:'Fam',w:700,s:36});label('Published: '+N(Math.round(1028*p)),L[0]+14,L[1]+46);label('This run: '+N(Math.round(1329*p)),Rr[0]+14,Rr[1]+46);
const b=on(t,C[0]+.8);T('Lit on one side, shadowed on the other, like all the rest',60,930,{f:'Fam',w:500,s:34,a:b*(1-on(t,C[1]-.1))});const e=on(t,C[1]+.2);T('Published: rings on the pale silkscreen line, most domes skipped',60,930,{f:'Fam',w:500,s:30,c:RED,a:e*(1-on(t,C[2]-.1))});T('This run: on the domes, with a row along the top edge still missed',60,980,{f:'Fam',w:500,s:30,c:GRN,a:e*(1-on(t,C[2]-.1))});const g=on(t,C[2]);T('1,329 kept',60,950,{f:'Fam',w:700,s:60,c:TEAL,a:g});T('All from the bottom: the top face shows none it can trust',60,1010,{s:30,c:'#c3ccd6',a:g})},
layers(t,C,d){const a=1-on(t,C[1]-.2,.5);if(a>0){const k=K([.02,.02,.96],FULL,t/C[1]);img('top',k,B,a);img('materials-top',k,B,a*on(t,.8,.8));frame(B);c.globalAlpha=a;T('Every layer',COL[0],92,{f:'Fam',w:700,s:36,a});chipRow([['#c000c8','Part',.9]],COL[0],170,t);chipRow([['#ff3020','Part metal',1.4]],COL[0],215,t);chipRow([['#60c8ff','Silkscreen',1.9]],COL[0],260,t);chipRow([['#ffb800','Exposed metal',2.4]],COL[0],305,t);chipRow([['#ffffff','Hole',2.9]],COL[0],350,t);chipRow([['#0a1a1a','Solder mask',3.4]],COL[0],395,t);c.globalAlpha=1}
const b=on(t,C[1]-.2,.5);if(b>0){const G=[['p_f-cu','Copper, top'],['p_f-silk','Silkscreen, top'],['p_f-mask','Mask, top'],['p_b-cu','Copper, bottom'],['p_b-silk','Silkscreen, bottom'],['p_b-mask','Mask, bottom']];G.forEach(([n,s],i)=>{const q=on(t,C[1]+i*.8,.4)*b;if(q<=0)return;const R=[20+(i%3)*632,20+Math.floor(i/3)*530,620,518],z=lerp(1,.9,cl((t-C[1])/(d-C[1])));img(n,[(1-z)/2,(1-z)/2,z],R,q);frame(R);label(s,R[0]+14,R[1]+46,{a:q})})}},
copy(t,C,d){const b=seg(t,-1,C[1]-.1);if(b>0){const k=K(FULL,[.04,.04,.92],t/C[1]);head(8,'The KiCad copy');img('overlay-top',k,B,b);img('overlay-bottom',k,B,b*on(t,C[0]+4.6));frame(B);label(t<C[0]+4.6?'Copy over the top photo':'Copy over the bottom photo',B[0]+14,B[1]+46,{a:b});let y=120;y=stat(y,'138','footprints, 546 pads',t,.8);y=stat(y,'828','pours, as photographed',t,1.3);y=stat(y,'2,588','tracks',t,1.8);y=stat(y,'1,329','vias',t,2.3)}
const n=on(t,C[1]-.1);if(n>0){const L=[['n_GND','GND'],['n_VIN','VIN'],['n_PHASE_X1','PHASE_X1']],st=C[1]+2.6,q=cl((t-st)/(d-st))*L.length,i=Math.min(L.length-1,Math.floor(q)),R=[318,0,1285,1080],cm=K([0,0,1],[.03,.03,.94],t/d);c.fillStyle=BG;c.globalAlpha=n;c.fillRect(0,0,1920,1080);c.globalAlpha=1;img('p_top',cm,R,n);if(t>st){img(L[i][0],cm,R,cl((q-i)/.12));if(i>0&&q-i<.12)img(L[i-1][0],cm,R,1-(q-i)/.12);label('Net highlighted',30,70,{s:24,c:MUT});T(L[i][1],30,130,{f:'Fam',w:700,s:42,c:TEAL2})}else{label('Interactive board view',30,70,{s:24})}}},
fidelity(t,C,d){head(9,'Against the photos');const bot=t>C[2]-.2&&t<C[2]+3.4;const k=K(FULL,[.03,.03,.94],t/d);img('fidelity-F_Cu',k,B);img('fidelity-B_Cu',k,B,bot?on(t,C[2]-.2):0);frame(B);label(bot?'Bottom copper':'Top copper',B[0]+14,B[1]+46);let y=120;
const a=on(t,C[1]);[[ '#1ebe3c','Green: drawn and seen'],['#ff2828','Red: drawn, not seen'],['#3cc8ff','Blue: seen, not drawn'],['#3c1e3c','Purple: hidden, not scored']].forEach(([cc,s],i)=>{const q=on(t,C[1]+i*1.0);c.save();c.globalAlpha=.25+.75*q;c.fillStyle=cc;rr([COL[0],y+i*48,26,26],5);c.fill();c.restore();T(s,COL[0]+40,y+21+i*48,{s:25,a:.25+.75*q})});y+=216;y=stat(y,'0.87','Bottom copper, held out tiles. 87% of the face is visible.',t,C[2],GRN,146);y=stat(y,'0.82','Top copper, held out tiles. Only 48% of the face is visible.',t,C[2]+2.4,AMB,146);y=stat(y,'Separate program','This check does not share the reader\'s thresholds',t,.6,MUT,120)},
nets(t,C,d){const sch=on(t,C[3]-.2,.5);if(sch<1){const L=[30,92,920,772],Rr=[970,92,920,772];const g=t<C[1];const zo=K(FULL,[.02,.02,.96],t/C[3]);img(g?'name_top':'rung_top',zo,L,1);img(g?'name_bot':'rung_bot',zo,Rr,1);if(!g&&t<C[1]+.5){img('name_top',zo,L,1-(t-C[1])/.5);img('name_bot',zo,Rr,1-(t-C[1])/.5)}frame(L);frame(Rr);T(g?'Copper by net name':'How each piece got its net',60,66,{f:'Fam',w:700,s:36});label('Top',L[0]+14,L[1]+46);label('Bottom',Rr[0]+14,Rr[1]+46);
if(g){const a=on(t,1.2);T(N(D.ev.pieces)+' pieces, stitched into '+N(D.ev.conductors)+' conductors',60,950,{f:'Fam',w:700,s:42,c:TEAL,a})}else{const l=D.ladder,A=C[1]+.2;chipRow([['#28c85a','pad touches it '+l.copper.areaPct.toFixed(0)+'%',A],['#28bed2','via '+l.via.areaPct.toFixed(0)+'%',A+1.5],['#f0d23c','inferred '+l.inferred.areaPct.toFixed(0)+'%',A+2.4],['#f08c28','group '+l.group.areaPct.toFixed(0)+'%',A+3.2],['#eb3232','no net '+l.none.areaPct.toFixed(0)+'%',A+4]],60,930,t);T('Share of copper area',60,968,{s:22,c:MUT});const q=on(t,C[2]+.2);T(l.none.lengthPct.toFixed(0)+'% of track length has no net',60,1040,{f:'Fam',w:700,s:42,c:RED,a:q})}}
if(sch>0){img('s_sch',K([.28,.42,.36],[.30,.60,.32],(t-C[3])/(d-C[3])),FB,sch);label('Schematic generated by the flow, 77 nets, all tagged inferred',30,1050,{a:sch})}},
router(t,C,d){head(11,'Routing what\'s hidden');const k=t<C[2]?K(FULL,[.03,.03,.94],t/C[2]):K([.03,.03,.94],[.30,.28,.5],(t-C[2])/4);img('route_6',k,B);frame(B);label('6 layers, on the dimmed top photo',B[0]+14,B[1]+46);let y=120;y=stat(y,'Every inner layer','Through vias on a 0.05 mm grid, all signal layers of the stackup',t,.5,TEAL,146);y=stat(y,'Expensive, not forbidden','Crossing copper from the photos costs 6 to 12 times more, and the length crossed is reported',t,C[1],AMB,146);y=stat(y,'45 of 253','connections stay open at 6 layers',t,C[2],RED,120);y=stat(y,'150','router vias, white circles. Crosses mark where it got stuck.',t,C[2]+1.6,MUT,120)},
count(t,C,d){head(12,'Layer count');const cam=K(FULL,[.03,.03,.94],t/d);const seq=[[0,'route_2'],[C[1]+2.4,'route_4'],[C[2],'route_6'],[C[2]+1.4,'route_8'],[C[2]+3,'route_6']];let cur=seq[0][1];seq.forEach(([a,n])=>{if(t>=a)cur=n});img('route_2',cam,B);seq.forEach(([a,n],i)=>{const e=seq[i+1]?seq[i+1][0]:99;img(n,cam,B,seg(t,a,e,.25))});frame(B);label(cur.split('_')[1]+' layers',B[0]+14,B[1]+46);
const X=COL[0],Y=130,cols=[20,130,300,460];['Layers','Open','New DRC','Crossing'].forEach((s,i)=>T(s,X+cols[i],Y+24,{s:21,c:MUT}));const cu=[C[1]+.1,C[1]+2.4,C[2],C[2]+1.4];D.cand.forEach((r,i)=>{const y=Y+44+i*120,a=on(t,cu[i]),act=i==2&&t>C[2]+3;c.save();c.globalAlpha=.3+.7*a;c.fillStyle=PANEL;rr([X,y,COL[2],106],12);c.fill();c.strokeStyle=act?TEAL:EDGE;c.lineWidth=act?2.5:1;c.stroke();c.restore();T(String(r.l),X+cols[0],y+68,{f:'Fam',w:700,s:46,a:.35+.65*a});if(a>0){T(r.open+' / '+r.conn,X+cols[1],y+66,{f:'Fam',w:500,s:30,a});T(String(r.drc),X+cols[2],y+66,{f:'Fam',w:500,s:32,a});T(Math.round(r.cross)+' mm',X+cols[3],y+66,{f:'Fam',w:700,s:34,c:r.cross>100?RED:GRN,a})}});
const e=on(t,C[2]+3.2);if(e>0){stat(Y+44+4*120+10,'6 proposed','Past 6, more layers stop helping. The real design has 6, but the flow doesn\'t know that.',t,C[2]+3.2,TEAL,146)}},
review(t,C,d){T('Measured confidence',60,86,{f:'Fam',w:700,s:42});const S=D.review;const asp=IM.audit_sheet.height/IM.audit_sheet.width,L=[60,120,700,Math.min(900,700*asp)];const a=seg(t,-1,C[2]-.1);if(a>0){img('audit_sheet',K([0,0,1],[.05,.03,.9],t/C[2]),L,a);frame(L);label('Random crops, judged by auditors who did none of the work',L[0]+14,L[1]+46,{a})}
const X=800,W2=1060;const v=on(t,C[1]);if(v>0&&t<C[2]-.1){c.save();c.globalAlpha=v;c.fillStyle=PANEL;rr([X,160,W2,190],14);c.fill();c.strokeStyle=RED;c.lineWidth=2.5;c.stroke();c.restore();T('Sample voided',X+36,236,{f:'Fam',w:700,s:48,c:RED,a:v});T('A planted false claim was called OK, so a new auditor redid blocks',X+36,296,{s:26,a:v})}
const b=on(t,C[2]-.1);if(b>0){const rows=S.cats,YY=130,H=90;rows.forEach((r,i)=>{const y=YY+i*H,q=on(t,C[2]+i*.28);c.save();c.globalAlpha=b;c.fillStyle=PANEL;rr([60,y,1800,H-12],10);c.fill();c.restore();T(r.name,86,y+H/2+4,{f:'Fam',w:500,s:30,a:b});const bx=360,bw=1000;c.save();c.globalAlpha=b;c.fillStyle='#21262d';rr([bx,y+H/2-16,bw,20],6);c.fill();c.fillStyle=r.score>=90?GRN:r.score>=50?AMB:RED;rr([bx,y+H/2-16,Math.max(6,bw*r.score/100*q),20],6);c.fill();c.fillStyle=INK;c.fillRect(bx+bw*.9,y+H/2-22,2,32);c.restore();T(q>0?r.score.toFixed(0):'',bx+bw+30,y+H/2+4,{f:'Fam',w:700,s:32,c:r.score>=90?GRN:r.score>=50?AMB:RED,a:b*q});})}const fin=on(t,C[4]);if(fin>0){c.save();c.globalAlpha=fin;c.fillStyle=PANEL;rr([60,866,1800,170],14);c.fill();c.strokeStyle=RED;c.lineWidth=2.5;c.stroke();c.restore();T('Overall 0 of 100',96,950,{f:'Fam',w:700,s:64,c:RED,a:fin});T('STALLED. Target 90 not reached.',96,1006,{f:'Fam',w:500,s:32,a:fin});T('Lowest category sets the overall score',1830,944,{s:26,c:'#c3ccd6',al:'right',a:fin});T('Photos cannot settle values, layers or nets',1830,992,{s:26,c:'#c3ccd6',al:'right',a:fin})}},
tests(t,C,d){const Rx=[60,60,1800,960];c.fillStyle=PANEL;rr(Rx,16);c.fill();c.strokeStyle=EDGE;c.stroke();T('119 tests',100,130,{f:'Fam',w:700,s:48,c:TEAL});T('what to measure, what to expect, what to change if it differs',340,128,{s:26,c:'#c3ccd6'});clipR([60,160,1800,850],()=>{const off=Math.max(0,t-.6)*62;D.tests.forEach((r,i)=>{const y=210+i*60-off;if(y<130||y>1060)return;T(r.id,100,y,{f:'Jet',s:24,c:TEAL2,w:500});T(r.m,360,y,{s:24,c:'#c3ccd6'})})})},
key(t,C,d){const o=D.led0,n=D.led1,oF=o['copper_F.Cu'],nF=n['copper_F.Cu'],oB=o['copper_B.Cu'],nB=n['copper_B.Cu'];T('Checked against the real design',60,86,{f:'Fam',w:700,s:44});const a0=on(t,.4);T('Sealed answer key. The flow never reads it, agents never see it, only totals come out.',60,134,{s:27,c:'#c3ccd6',a:a0});
const rows=[['Vias that are real',o.via_precision_pct,n.via_precision_pct,C[1],1],['Design vias found',o.via_recall_pct,n.via_recall_pct,C[1]+2.6,1],['Copper match, top',oF.iou,nF.iou,C[2],1],['Copper match, bottom',oB.iou,nB.iou,C[2]+2.6,1],['Layer count','6, correct','6, correct',C[2]+5,0],['Pins on a consistent net',o.pin_net_purity_pct,n.pin_net_purity_pct,C[3],-1]];T('Published',1090,200,{s:24,c:MUT});T('This run',1420,200,{s:24,c:MUT});rows.forEach(([l,a,b,cu,g],i)=>{const q=on(t,cu),y=224+i*126;c.save();c.globalAlpha=.25+.75*q;c.fillStyle=PANEL;rr([60,y,1800,110],12);c.fill();c.restore();T(l,96,y+70,{f:'Fam',w:500,s:40,a:.3+.7*q});if(q>0){const val=typeof a=='number'?(a+(b-a)*ez((t-cu)/1.2)):b;T(typeof a=='number'?a.toFixed(1)+'%':a,1090,y+72,{f:'Fam',w:500,s:42,c:MUT,a:q});T(typeof a=='number'?val.toFixed(1)+'%':b,1420,y+72,{f:'Fam',w:700,s:52,c:g>0?GRN:g<0?RED:INK,a:q})}})},
notdone(t,C,d){const a=seg(t,-1,1.6,.4);if(a>0){T('Not a finished board',960,520,{f:'Fam',w:700,s:96,al:'center',a});}
const b=on(t,1.5);if(b>0){head(14,'Not done');img('route_6',K(FULL,[.05,.05,.9],(t-1.5)/8),B,b);frame(B);let y=120;y=stat(y,'45','connections still open at 6 layers',t,1.8,RED);y=stat(y,'441','missing per KiCad, mostly same-net copper not joined in the copy',t,3.4,AMB,146);y=stat(y,'594','new design rule errors',t,C[1],AMB);y=stat(y,'One board','Thresholds came from this board. A second board is the real test.',t,C[1]+3.2,TEAL,146)}},
record(t,C,d){stills(['f_rec15','f_rec73','f_rec97','f_rec120','f_rec123','f_rec126','f_sch','f_findings'],t,d,FB,.96)},
cost(t,C,d){T('What it cost',60,110,{f:'Fam',w:700,s:52});const K2=D.cost;const G=[[K2.active,'AI time in the main thread, idle gaps over 15 min cut',TEAL,C[0]+.4],[K2.clock,'by the clock, first prompt to this video\'s build',MUT,C[0]+2.4],[K2.output,'output tokens',TEAL,C[1]+.2],[K2.cread,'cache read tokens',TEAL,C[1]+2],[K2.cwrite,'cache write tokens',MUT,C[1]+3.8],[K2.calls,'model calls, across '+K2.agents+' sub-agents and the main thread',TEAL,C[1]+4.8]];G.forEach(([b,l,col,cu],i)=>{const x=60+(i%3)*610,y=160+Math.floor(i/3)*230,a=on(t,cu);c.save();c.globalAlpha=a;c.fillStyle=PANEL;rr([x+(1-a)*14,y,590,210],14);c.fill();c.strokeStyle=EDGE;c.stroke();c.restore();T(b,x+30,y+92,{f:'Fam',w:700,s:64,c:col==MUT?INK:col,a});P(l,x+30,y+144,530,{s:25,c:'#c3ccd6',a})});const a=on(t,C[0]+1);P('Counted from the session logs, sub-agents included. Rendering this video is not included.',60,680,1800,{s:24,c:MUT,a});},
};
const NEED={board:['raw_Driver1','raw_Driver2','raw_Driver3','raw_Driver4'],underside:['raw_Driver3'],ask:[],flow:['f_rec133'],photos:['outline-Driver1','outline-Driver3','top','prep-Driver3','bottom'],scale:['top'],parts:['top','pin1-U2'],blocks:['top'],copper:['bottom','gaps-bottom','copper-islands-bottom','read_old_bot','read_new_bot'],domes:['dome_photo','dome_mid','dome_new'],vias:['bottom_old','bottom'],layers:['top','materials-top','p_f-cu','p_f-silk','p_f-mask','p_b-cu','p_b-silk','p_b-mask'],copy:['overlay-top','overlay-bottom','p_top','n_GND','n_VIN','n_PHASE_X1'],fidelity:['fidelity-F_Cu','fidelity-B_Cu'],nets:['name_top','name_bot','rung_top','rung_bot','s_sch'],router:['route_6'],count:['route_2','route_4','route_6','route_8'],review:['audit_sheet'],tests:[],key:[],notdone:['route_6'],record:['f_rec15','f_rec73','f_rec97','f_rec120','f_rec123','f_rec126','f_sch','f_findings'],cost:[]};
window.ready=(async()=>{D=await(await fetch('data.json')).json();const sid=new URLSearchParams(location.search).get('s');await Promise.all((NEED[sid]||[]).map(load));for(const f of ['700 30px Fam','500 20px Fam','400 20px Sat','500 20px Sat','400 20px Jet','500 20px Jet'])await document.fonts.load(f);return 1})();
window.draw=(id,t,dur,lead)=>{const s=D.timing.find(x=>x.id===id);const C=s.starts.map(v=>v+lead);c.globalAlpha=1;c.fillStyle=BG;c.fillRect(0,0,1920,1080);SC[id](t,C,dur);c.globalAlpha=1;const f=Math.min(ez(t/.25),ez((dur-t)/.25));if(f<1){c.save();c.globalAlpha=1-f;c.fillStyle=BG;c.fillRect(0,0,1920,1080);c.restore()}};
window.grab=()=>document.getElementById('c').toDataURL('image/jpeg',.94).slice(23);
</script>