47 lines
4.9 KiB
JavaScript
47 lines
4.9 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import vm from 'node:vm';
|
|
import {createHash} from 'node:crypto';
|
|
|
|
const beforeUrl=new URL('../../docs/reported-view-bugfix/index.before.html',import.meta.url);
|
|
const afterUrl=new URL('../../index.html',import.meta.url);
|
|
const view={
|
|
bits:273,
|
|
re:'-15958919693715511752069626580195447581643016686947107473855747766272418724092005269',
|
|
im:'3884297345146583343999140717464554120497735725078805729405733239132420430333375719',
|
|
span:'35679807704506851074208413824041445358585910697267232113437882789120793',
|
|
width:1108,height:1582,iter:1536,
|
|
};
|
|
|
|
function extract(html){return [...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1])}
|
|
function appInternal(html){
|
|
const scripts=extract(html);const kc={};vm.runInNewContext(scripts[0],kc);const kernels=kc.MANDEL_WEBGPU_KERNELS;
|
|
const element={width:view.width,height:view.height,clientWidth:view.width,clientHeight:view.height,style:{},hidden:false,value:'',textContent:'',disabled:false,addEventListener(){},classList:{toggle(){}},setAttribute(){},removeAttribute(){},showModal(){},close(){},toBlob(){},getBoundingClientRect(){return{left:0,top:0,width:view.width,height:view.height}}};
|
|
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:view.width,innerHeight:view.height,devicePixelRatio:1,localStorage:{getItem(){return null},setItem(){},removeItem(){}}};
|
|
const cut=scripts[1].indexOf('// ── boot / teardown');
|
|
const code=scripts[1].slice(0,cut)+`\nglobalThis.internal={fastReferenceWorkerSource,pixelPrecisionBits};})();`;
|
|
vm.runInNewContext(code,context);return context.internal;
|
|
}
|
|
function runWorker(source,message){let result;const wc={self:{},postMessage:r=>{result=r},performance};vm.runInNewContext(source,wc);wc.self.onmessage({data:message});if(!result)throw new Error('worker returned nothing');if(result.type==='error')throw new Error(result.error);return result}
|
|
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
|
|
function fixedLog2Abs(v,bits){v=v<0n?-v:v;if(v===0n)return-Infinity;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh));return Math.log2(top)+sh-bits}
|
|
function fixedRatio(a,b){if(!b||!a)return 0;let neg=a<0n;if(neg)a=-a;const q=(a<<52n)/b,v=Number(q)/4503599627370496;return neg?-v:v}
|
|
function spanMantExp(span,bits){const l=fixedLog2Abs(span,bits),exp=Math.floor(l);return{mant:Math.pow(2,l-exp),exp}}
|
|
function complexMul(ar,ai,br,bi){return [Math.fround(Math.fround(ar*br)-Math.fround(ai*bi)),Math.fround(Math.fround(ar*bi)+Math.fround(ai*br))]}
|
|
function nonFiniteSeriesStarts(series,refX,refY,spanMant,width,height,cols=32,rows=32){
|
|
if(!series?.jump)return 0;let bad=0;const c=series.coeffs.map(Math.fround);
|
|
for(let jy=0;jy<rows;jy++)for(let ix=0;ix<cols;ix++){
|
|
const gx=(ix+.5)*width/cols,gy=(jy+.5)*height/rows,dx=Math.fround((gx-refX)/width),dy=Math.fround((refY-gy)/width),d=[Math.fround(spanMant*dx),Math.fround(spanMant*dy)];
|
|
const d2=complexMul(d[0],d[1],d[0],d[1]),d4=complexMul(d2[0],d2[1],d2[0],d2[1]);
|
|
const t4=complexMul(c[6],c[7],d4[0],d4[1]);
|
|
if(!Number.isFinite(t4[0])||!Number.isFinite(t4[1]))bad++;
|
|
}
|
|
return bad;
|
|
}
|
|
async function analyze(path){
|
|
const html=await fs.readFile(path,'utf8'),a=appInternal(html);const snap={bits:view.bits,re:BigInt(view.re),im:BigInt(view.im),span:BigInt(view.span)};const targetBits=a.pixelPrecisionBits(snap,view.width,40);
|
|
const result=runWorker(a.fastReferenceWorkerSource(),{type:'build',id:1,key:'reported-view',sourceBits:view.bits,targetBits,re:view.re,im:view.im,span:view.span,width:view.width,height:view.height,iter:view.iter});
|
|
const refRe=BigInt(result.referenceRe),refIm=BigInt(result.referenceIm),span=BigInt(view.span),refX=view.width*.5+fixedRatio(refRe-BigInt(view.re),span)*view.width,refY=view.height*.5-fixedRatio(refIm-BigInt(view.im),span)*view.width,se=spanMantExp(span,view.bits);
|
|
return {sha256:createHash('sha256').update(html).digest('hex'),targetBits,refLen:result.refLen,selectionEscape:result.selectionEscape,referencePixel:{x:refX,y:refY},series:result.series,nonFiniteSeriesStartSamples:nonFiniteSeriesStarts(result.series,refX,refY,se.mant,view.width,view.height),seriesSampleCount:32*32};
|
|
}
|
|
const before=await analyze(beforeUrl),after=await analyze(afterUrl);const output={view,before,after,checks:{baselineSeriesActive:before.series.jump>0,baselineHasNonFiniteSeriesStarts:before.nonFiniteSeriesStartSamples>0,patchedSeriesDisabled:after.series.jump===0&&after.nonFiniteSeriesStartSamples===0}};
|
|
console.log(JSON.stringify(output,null,2));
|