35 lines
8.4 KiB
JavaScript
35 lines
8.4 KiB
JavaScript
import fs from 'node:fs';
|
|
import vm from 'node:vm';
|
|
const prodHtml=fs.readFileSync(new URL('../../index.html',import.meta.url),'utf8');
|
|
const scripts=[...prodHtml.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]);
|
|
const kc={};vm.runInNewContext(scripts[0],kc);const kernels=kc.MANDEL_WEBGPU_KERNELS;
|
|
function appContext(){
|
|
const element={width:1365,height:768,clientWidth:1365,clientHeight:768,style:{},hidden:false,value:'',textContent:'',disabled:false,addEventListener(){},classList:{toggle(){}},setAttribute(){},removeAttribute(){},showModal(){},close(){},toBlob(){},getBoundingClientRect(){return{left:0,top:0,width:1365,height:768}}};
|
|
const context={MANDEL_WEBGPU_KERNELS:kernels,document:{querySelector:()=>element,getElementById:()=>element,documentElement:{classList:{toggle(){}}},body:{classList:{toggle(){}},appendChild(){}}},navigator:{hardwareConcurrency:4,deviceMemory:8},location:{search:'',hash:''},history:{replaceState(){}},performance,URLSearchParams,URL:{createObjectURL:()=>`blob:test`,revokeObjectURL(){}},TextEncoder,Blob:class{},console,addEventListener(){},requestAnimationFrame:()=>1,cancelAnimationFrame(){},setTimeout,clearTimeout,setInterval,clearInterval,innerWidth:1365,innerHeight:768,devicePixelRatio:1,localStorage:{getItem(){return null},setItem(){},removeItem(){}}};
|
|
const cut=scripts[1].indexOf('// ── boot / teardown');
|
|
const code=scripts[1].slice(0,cut)+`\nglobalThis.internal={state,fastReferenceWorkerSource,referenceWorkerSource,precisionFallbackWorkerSource,fastReferenceBits,pixelPrecisionBits,referencePixelForSource,spanMantExp,adaptiveProbeSources,adaptiveProbeChoice,adaptiveIterationBonus,fixedSourceForPixel,log2FixedAt};})();`;
|
|
vm.runInNewContext(code,context);return context.internal;
|
|
}
|
|
const app=appContext();
|
|
function runWorker(source,data){let result;const c={self:{},postMessage:d=>{result=d},performance,console,BigInt,Math,ArrayBuffer,DataView,Float32Array,Uint32Array,Uint8Array};vm.runInNewContext(source,c);c.self.onmessage({data});if(!result)throw new Error('no result');if(result.type==='error')throw new Error(result.error);return result;}
|
|
function maxAdaptive(snap,w){const spanLog2=app.log2FixedAt(snap.span,snap.bits),z=Math.max(0,Math.log10(3.4)-spanLog2/Math.log2(10)),screenBits=Math.max(0,Math.log2(w)+z*Math.LOG2E*Math.LN10),bonus=app.adaptiveIterationBonus(screenBits,1);return Math.min(16384,Math.max(350,350+bonus));}
|
|
function initialIter(snap,w,h){const adaptive=maxAdaptive(snap,w),sources=app.adaptiveProbeSources(snap,w,h),targetBits=Math.max(app.pixelPrecisionBits(snap,w,40)+32),scored=runWorker(app.referenceWorkerSource(),{type:'score',id:1,bits:snap.bits,targetBits,re:snap.re.toString(),im:snap.im.toString(),span:snap.span.toString(),width:w,height:h,iter:adaptive,candidates:sources.map(c=>({re:c.re.toString(),im:c.im.toString()}))});return {adaptive,...app.adaptiveProbeChoice(scored.escapes||[],adaptive,350),escapes:scored.escapes};}
|
|
function buildFast(snap,iter,w,h){const targetBits=app.fastReferenceBits(snap,w),d=runWorker(app.fastReferenceWorkerSource(),{type:'build',id:2,key:'analysis',sourceBits:snap.bits,targetBits,re:snap.re.toString(),im:snap.im.toString(),span:snap.span.toString(),width:w,height:h,iter});d.source={bits:snap.bits,re:BigInt(d.referenceRe),im:BigInt(d.referenceIm),viewRe:snap.re,viewIm:snap.im,span:snap.span,iter};return d;}
|
|
function oracle(snap,iter,w,h,indices){const targetBits=app.pixelPrecisionBits(snap,w,40);return runWorker(app.precisionFallbackWorkerSource(),{type:'solve',id:3,bits:snap.bits,targetBits,re:snap.re.toString(),im:snap.im.toString(),span:snap.span.toString(),width:w,height:h,iter,terminalClass:-1,indices:Uint32Array.from(indices).buffer});}
|
|
const f=Math.fround;const add=(a,b)=>f(f(a)+f(b)),mul=(a,b)=>f(f(a)*f(b));const maxabs=v=>Math.max(Math.abs(v[0]),Math.abs(v[1]));
|
|
function cmul(a,b){return [add(mul(a[0],b[0]),-mul(a[1],b[1])),add(mul(a[0],b[1]),mul(a[1],b[0]))]}
|
|
function ld(v,e){return f(v*Math.pow(2,e))}
|
|
function refsFrom(buf,n){const dv=new DataView(buf),a=[];for(let i=0;i<=n;i++){const o=i*16;a.push([[dv.getFloat32(o,true),dv.getFloat32(o+4,true)],[dv.getFloat32(o+8,true),dv.getFloat32(o+12,true)]])}return a}
|
|
function simulate(snap,w,h,iter,ctx,index,{rebase=false,glitchG=0}={}){const px=index%w,py=Math.floor(index/w),rpix=app.referencePixelForSource(ctx.source,snap,w,h),se=app.spanMantExp(snap),mh=f(se.mant),ml=f(se.mant-mh),iw=f(1/w),iwlo=f(1/w-iw),gx=f(px+.5),gy=f(py+.5),ox=f(gx-f(rpix.x)),oy=f(f(rpix.y)-gy),dxh=mul(ox,iw),dxl=mul(ox,iwlo),dyh=mul(oy,iw),dyl=mul(oy,iwlo);let d=[add(mul(mh,dxh),add(mul(mh,dxl),mul(ml,dxh))),add(mul(mh,dyh),add(mul(mh,dyl),mul(ml,dyh)))];let ww=[0,0],scaleExp=se.exp,n=0,m=0,rebases=0,glitches=0,minRatio=Infinity;const refs=refsFrom(ctx.refs,ctx.refLen);
|
|
while(true){const rp=refs[Math.min(m,ctx.refLen)],delta=[ld(ww[0],scaleExp),ld(ww[1],scaleExp)],rv=[add(rp[0][0],rp[1][0]),add(rp[0][1],rp[1][1])],z=[add(rp[0][0],add(rp[1][0],delta[0])),add(rp[0][1],add(rp[1][1],delta[1]))],mag=add(mul(z[0],z[0]),mul(z[1],z[1])),rmag=add(mul(rv[0],rv[0]),mul(rv[1],rv[1])),dmag=add(mul(delta[0],delta[0]),mul(delta[1],delta[1]));if(rmag>0)minRatio=Math.min(minRatio,mag/rmag);
|
|
if(m>0&&glitchG>0&&rmag>0&&mag<glitchG*rmag){glitches++;return{kind:'glitch',n,m,rebases,glitches,minRatio};}
|
|
if(mag>4)return{kind:'escape',n,rebases,glitches,minRatio};if(n>=iter)return{kind:'op',n,rebases,glitches,minRatio};if(m>=ctx.refLen)return{kind:'refend',n,rebases,glitches,minRatio};
|
|
if(rebase&&m>0&&mag<dmag&&Number.isFinite(mag)&&Number.isFinite(dmag)){ww=[ld(z[0],-scaleExp),ld(z[1],-scaleExp)];m=0;rebases++;if(!Number.isFinite(ww[0])||!Number.isFinite(ww[1]))return{kind:'rebase-range',n,m,rebases,glitches,minRatio};continue;}
|
|
const l1=cmul(rp[0],ww),l2=cmul(rp[1],ww),linear=[f(2*f(l1[0]+l2[0])),f(2*f(l1[1]+l2[1]))],sq0=cmul(ww,ww),sq=[ld(sq0[0],scaleExp),ld(sq0[1],scaleExp)];ww=[add(add(linear[0],sq[0]),d[0]),add(add(linear[1],sq[1]),d[1])];m++;n++;const mm=Math.max(maxabs(ww),maxabs(d));if(!Number.isFinite(mm)||mm>=1e30)return{kind:'range',n,rebases,glitches,minRatio};if(mm>65536){ww=[mul(ww[0],1/65536),mul(ww[1],1/65536)];d=[mul(d[0],1/65536),mul(d[1],1/65536)];scaleExp+=16}else if(mm>0&&mm<1/65536&&scaleExp>se.exp){ww=[mul(ww[0],65536),mul(ww[1],65536)];d=[mul(d[0],65536),mul(d[1],65536)];scaleExp-=16}if(scaleExp>126)return{kind:'range',n,rebases,glitches,minRatio};}}
|
|
function grid(w,h,nx=16,ny=10){const out=[];for(let j=0;j<ny;j++)for(let i=0;i<nx;i++){const x=Math.min(w-1,Math.floor((i+.5)*w/nx)),y=Math.min(h-1,Math.floor((j+.5)*h/ny));out.push(y*w+x)}return out}
|
|
const cases=[
|
|
{name:'mini-instability-1',bits:256,re:'-148292766829529091721540000955948596563327036751005092894699321061162899553437',im:'7497924611962244671337883039784517355486082422571896698766224071500989488498',sp:'1051264689747601722664903477680432560343035141150552570739220680259730166'},
|
|
{name:'mini-instability-2',bits:256,re:'44279008708751415573784266681073018338222298986985889938567243759258661091705',im:'11537730301689015207261638283847108540669173974016900715268231497454915998519',sp:'136466963900827996566957710260403731102960944336062216372133428878224980'},
|
|
{name:'mini-black-3',bits:256,re:'15717041212470055224256502413765101609014610520343947152977545154690386564664',im:'74092703473662244164463718953593537954306995881211045997604051198538528963626',sp:'7384741851176872716004457996517031059911173801417894624776297655376888'}];
|
|
const rows=[];for(const c of cases){const snap={bits:c.bits,re:BigInt(c.re),im:BigInt(c.im),span:BigInt(c.sp)},w=1365,h=768,sel=initialIter(snap,1365,768),indices=grid(1365,768),ctx=buildFast(snap,sel.iter,1365,768),o=oracle(snap,sel.iter,1365,768,indices),meta=new Uint32Array(o.meta);for(const mode of [{name:'base'},{name:'rebase',rebase:true},{name:'g1e-7',glitchG:1e-7},{name:'g1e-6',glitchG:1e-6},{name:'g1e-5',glitchG:1e-5}]){let counts={},mismatch=0,falseEscape=0,falseNon=0,rebases=0,minRatio=Infinity;for(let k=0;k<indices.length;k++){const exact=((meta[k]>>>28)&3)===1,q=simulate(snap,1365,768,sel.iter,ctx,indices[k],mode),fast=q.kind==='escape';counts[q.kind]=(counts[q.kind]||0)+1;rebases+=q.rebases||0;minRatio=Math.min(minRatio,q.minRatio??Infinity);if(fast!==exact){mismatch++;if(fast)falseEscape++;else falseNon++;}}rows.push({case:c.name,initial:sel.iter,adaptive:sel.adaptive,refLen:ctx.refLen,selectionEscape:ctx.selectionEscape,refPixel:app.referencePixelForSource(ctx.source,snap,1365,768),mode:mode.name,counts,mismatch,falseEscape,falseNon,rebases,minRatio});}}
|
|
console.log(JSON.stringify(rows,null,2));
|