This commit is contained in:
33333-33333 2026-08-23 23:27:38 +09:00
commit 8a2b1f6d4b
64 changed files with 10760 additions and 757 deletions

View file

@ -1,10 +1,12 @@
import fs from 'node:fs/promises';
const s=await fs.readFile(new URL('./webgpu-acceptance.js',import.meta.url),'utf8');
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(/row\.known<12/.test(s),'swirly minimum-known gate missing');
must(/row\.unknown!==0/.test(s),'stable-scene UNKNOWN=0 gate missing');
must(/corrected production frame produced UNKNOWN/.test(s),'corrected screen UNKNOWN=0 gate missing');
must(/forceDeep:true,correctUnknown:true/.test(s),'forced corrected deep dense probe missing');
must(/dense DS correction left known regression pixel UNKNOWN/.test(s),'dense DS recovery gate missing');
must(/falseEscaped/.test(s)&&/falseBounded/.test(s)&&/guardMismatch/.test(s),'membership/reference gates missing');
must(/dense false escape/.test(s)&&/dense false bounded/.test(s),'dense regression gates missing');
must(/exportSmoke\.unresolved!==0/.test(s)&&/exportSmokeAA\.unresolved!==0/.test(s),'export unresolved gate missing');
must(/backend router ignored measured 15% speed margin/.test(s)&&/viewProbe/.test(s),'view-specific router acceptance gate missing');
must(/exportSmoke\.unresolved!==0/.test(s)&&/exportSmokeAA\.unresolved!==0/.test(s),'corrected export unresolved gate missing');
must(/uncapturedErrors/.test(s),'uncaptured WebGPU error gate missing');
console.log(JSON.stringify({status:'pass',checks:6,stableUnknownMax:0,swirlyKnownMin:12},null,2));
must(/runFixed96Experiment/.test(s)&&/fixed96 experiment smoke failed/.test(s)&&/queueIntegrity/.test(s)&&/dispatchRate/.test(s),'fixed96 sparse-queue real-GPU smoke gate missing');
console.log(JSON.stringify({status:'pass',checks:8,correctedUnknownMax:0,routerSpeedMargin:.15,fixed96Smoke:true},null,2));

View file

@ -0,0 +1,10 @@
const REQUIRED=8,DEFAULT=128,MARGIN=.85,UNKNOWN_MAX=.005;
function choose(ratio,viewProbe=null){const eligible=Number.isFinite(ratio)&&ratio>=REQUIRED;if(!eligible)return'deep';if(ratio>=DEFAULT)return'direct';if(!viewProbe)return'probe';if(!viewProbe.qualityPass||viewProbe.classDisagreement!==0||viewProbe.deepUnknownRate>UNKNOWN_MAX)return'deep';if(viewProbe.predictedDeepMs<viewProbe.predictedDirectMs*MARGIN)return'deep';return'direct'}
const good=(d,u=0)=>({qualityPass:true,classDisagreement:0,deepUnknownRate:u,predictedDirectMs:100,predictedDeepMs:d});
const cases=[
[0.5,null,'deep'],[7.999,null,'deep'],[8,null,'probe'],[64,null,'probe'],[128,null,'direct'],
[32,good(80),'deep'],[32,good(86),'direct'],[32,good(60,.01),'deep'],
[32,{...good(60),qualityPass:false},'deep'],[32,{...good(60),classDisagreement:1},'deep']
];
for(const [ratio,p,want] of cases){const got=choose(ratio,p);if(got!==want)throw new Error(JSON.stringify({ratio,p,want,got}))}
console.log(JSON.stringify({status:'pass',kind:'view-specific-adaptive-backend-router',directRequiredRatio:REQUIRED,directDefaultRatio:DEFAULT,deepSpeedMargin:MARGIN,deepProbeUnknownMax:UNKNOWN_MAX,cases:cases.length},null,2));

View file

@ -6,9 +6,9 @@ function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}function log2FixedAt(v,b){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-b}function spanME(s){const l=log2FixedAt(s.span,s.bits),exp=Math.floor(l);return{mant:F(Math.pow(2,l-exp)),exp}}
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
function direct(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
function buildRef(s,limit){const b=s.bits+64,cr=align(s.re,s.bits,b),ci=align(s.im,s.bits,b);let zr=0n,zi=0n;const hi=new Float32Array((limit+1)*2),lo=new Float32Array((limit+1)*2);for(let n=0;n<=limit;n++){const rr=fixedNum(zr,b),ri=fixedNum(zi,b),hr=F(rr),hii=F(ri);hi[n*2]=hr;hi[n*2+1]=hii;lo[n*2]=F(rr-hr);lo[n*2+1]=F(ri-hii);if(n===limit)break;const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr}return{hi,lo,refLen:limit}}
function buildRef(s,limit){const b=s.bits+64,cr=align(s.re,s.bits,b),ci=align(s.im,s.bits,b),bail=16n<<BigInt(b);let zr=0n,zi=0n,refLen=limit;const hi=new Float32Array((limit+1)*2),lo=new Float32Array((limit+1)*2);for(let n=0;n<=limit;n++){const rr=fixedNum(zr,b),ri=fixedNum(zi,b),hr=F(rr),hii=F(ri);hi[n*2]=hr;hi[n*2+1]=hii;lo[n*2]=F(rr-hr);lo[n*2+1]=F(ri-hii);if(n===limit)break;const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;const mag=roundShift(zr*zr,b)+roundShift(zi*zi,b);if(mag>bail){refLen=n+1;const rr1=fixedNum(zr,b),ri1=fixedNum(zi,b),hr1=F(rr1),hi1=F(ri1);hi[refLen*2]=hr1;hi[refLen*2+1]=hi1;lo[refLen*2]=F(rr1-hr1);lo[refLen*2+1]=F(ri1-hi1);break}}return{hi,lo,refLen}}
function cmul(ar,ai,br,bi){return[F(F(ar*br)-F(ai*bi)),F(F(ar*bi)+F(ai*br))]}function scale(v,e){if(e<-126)return 0;if(e>126)return F(8.507059e37);return F(v*Math.pow(2,e))}
function perturb(ref,s,w,h,x,y,limit,strict=false){const se=spanME(s),dx=F((x+.5-.5*w)/w),dy=F((.5*h-y-.5)/w),U=2**-24;let dr=F(se.mant*dx),di=F(se.mant*dy),wr=0,wi=0,e=se.exp,n=0,m=0,ops=0,err=64*U*Math.max(Math.abs(dr),Math.abs(di));while(true){if(n>=limit){const rp=Math.min(m,ref.refLen)*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),ea=Math.abs(err*2**e)+64*U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30),threshold=strict?1e-4:1e-3;return ea<=threshold?{kind:'bounded',n,err:ea}:{kind:'unknown',n,reason:'error-bound',err:ea}}if(m>ref.refLen)return{kind:'unknown',n,reason:'ref'};const rp=m*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),mag=F(F(zr*zr)+F(zi*zi)),ea=Math.abs(err*2**e)+64*U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);if(mag>4){if(Math.hypot(zr,zi)-ea>2)return{kind:'escaped',n,err:ea};return{kind:'unknown',n,reason:'escape-uncertain',err:ea}}const dmag=F(F(delr*delr)+F(deli*deli));if(m>0&&dmag>0&&mag<dmag){if(se.exp-e<-96)return{kind:'unknown',n,reason:'rebase-gap'};err=ea;wr=zr;wi=zi;dr=scale(F(se.mant*dx),se.exp);di=scale(F(se.mant*dy),se.exp);e=0;m=0;err+=64*U*Math.max(Math.abs(dr),Math.abs(di));continue}if(m>=ref.refLen)return{kind:'unknown',n,reason:'ref-end'};const rr=ref.hi[rp],ri=ref.hi[rp+1],lr=ref.lo[rp],li=ref.lo[rp+1],refAbs=Math.max(Math.abs(rr),Math.abs(ri))+Math.max(Math.abs(lr),Math.abs(li)),wAbs=Math.max(Math.abs(wr),Math.abs(wi)),dAbs=Math.max(Math.abs(dr),Math.abs(di)),p2=Math.abs(2**e),gain=2*refAbs+2*wAbs*p2,roundErr=64*U*(2*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1e-30);err=gain*err+roundErr;const[a,b]=cmul(rr,ri,wr,wi),[c,d]=cmul(lr,li,wr,wi),[sqR,sqI]=cmul(wr,wi,wr,wi);wr=F(F(F(2*a)+F(2*c))+F(scale(sqR,e)+dr));wi=F(F(F(2*b)+F(2*d))+F(scale(sqI,e)+di));m++;n++;ops++;if(!Number.isFinite(wr)||!Number.isFinite(wi)||!Number.isFinite(err)||Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di))>=1e30||err>1e35)return{kind:'unknown',n,reason:'range'};const mm=Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di));if(mm>65536){wr=F(wr/65536);wi=F(wi/65536);dr=F(dr/65536);di=F(di/65536);err/=65536;e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wr=F(wr*65536);wi=F(wi*65536);dr=F(dr*65536);di=F(di*65536);err*=65536;e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n,reason:'guard'}}}
function perturb(ref,s,w,h,x,y,limit,strict=false){const se=spanME(s),dx=F((x+.5-.5*w)/w),dy=F((.5*h-y-.5)/w),U=2**-24;let dr=F(se.mant*dx),di=F(se.mant*dy),wr=0,wi=0,e=se.exp,n=0,m=0,ops=0,err=1*U*Math.max(Math.abs(dr),Math.abs(di));while(true){if(n>=limit){const rp=Math.min(m,ref.refLen)*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),ea=Math.abs(err*2**e)+1*U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30),threshold=strict?1e-4:1e-3;return ea<=threshold?{kind:'bounded',n,err:ea}:{kind:'unknown',n,reason:'error-bound',err:ea}}if(m>ref.refLen)return{kind:'unknown',n,reason:'ref'};const rp=m*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),mag=F(F(zr*zr)+F(zi*zi)),ea=Math.abs(err*2**e)+1*U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);if(mag>4){if(Math.hypot(zr,zi)-ea>2)return{kind:'escaped',n,err:ea};return{kind:'unknown',n,reason:'escape-uncertain',err:ea}}const dmag=F(F(delr*delr)+F(deli*deli));if(m>0&&dmag>0&&mag<dmag){if(se.exp-e<-96)return{kind:'unknown',n,reason:'rebase-gap'};err=ea;wr=zr;wi=zi;dr=scale(F(se.mant*dx),se.exp);di=scale(F(se.mant*dy),se.exp);e=0;m=0;err+=1*U*Math.max(Math.abs(dr),Math.abs(di));continue}if(m>=ref.refLen)return{kind:'unknown',n,reason:'ref-end'};const rr=ref.hi[rp],ri=ref.hi[rp+1],lr=ref.lo[rp],li=ref.lo[rp+1],refAbs=Math.max(Math.abs(rr),Math.abs(ri))+Math.max(Math.abs(lr),Math.abs(li)),wAbs=Math.max(Math.abs(wr),Math.abs(wi)),dAbs=Math.max(Math.abs(dr),Math.abs(di)),p2=Math.abs(2**e),gain=2*refAbs+2*wAbs*p2,roundErr=1*U*(2*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1e-30);err=gain*err+roundErr;const[a,b]=cmul(rr,ri,wr,wi),[c,d]=cmul(lr,li,wr,wi),[sqR,sqI]=cmul(wr,wi,wr,wi);wr=F(F(F(2*a)+F(2*c))+F(scale(sqR,e)+dr));wi=F(F(F(2*b)+F(2*d))+F(scale(sqI,e)+di));m++;n++;ops++;if(!Number.isFinite(wr)||!Number.isFinite(wi)||!Number.isFinite(err)||Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di))>=1e30||err>1e35)return{kind:'unknown',n,reason:'range'};const mm=Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di));if(mm>65536){wr=F(wr/65536);wi=F(wi/65536);dr=F(dr/65536);di=F(di/65536);err/=65536;e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wr=F(wr*65536);wi=F(wi*65536);dr=F(dr*65536);di=F(di*65536);err*=65536;e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n,reason:'guard'}}}
const cfg=[['period2-cusp-z14',1200],['swirly-seahorses-z12',2000],['period2-cusp-z20',1800],['period2-cusp-z100',2400],['period3-interior',1200],['period2-cusp-e280',2400],['period2-cusp-e400',1600]],w=17,h=11,pts=[];
for(let y=0;y<h;y++)for(let x=0;x<w;x++)pts.push([x,y]);
const report=[];

View file

@ -0,0 +1,16 @@
import fs from 'node:fs/promises';
const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('script.js',root),'utf8'),kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8'),html=await fs.readFile(new URL('index.html',root),'utf8');
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(/DIRECT_DS_WGSL/.test(kernels)&&/struct DS/.test(kernels)&&/centerReLo/.test(kernels),'DS-direct benchmark shader missing');
must(/direct-ds-benchmark/.test(script)&&/this\.dsDirect=/.test(script),'DS-direct pipeline missing');
must(/benchmarkDsDirect/.test(script)&&/renderTileMetaDsDirect/.test(script),'DS-direct benchmark methods missing');
must(/CROSSOVER_DEPTHS=\[0,2,4,6,8,10,12,14,16,20,30,40\]/.test(script),'round-depth crossover suite missing');
must(/runCrossoverBenchmark/.test(script)&&/referenceBuildMs/.test(script)&&/coldMedianMs/.test(script),'crossover benchmark timing/report missing');
must(/CROSSOVER_SPEED_W=256/.test(script)&&/CROSSOVER_SPEED_H=144/.test(script)&&/crossoverSpeedTiles/.test(script),'bounded representative speed tiles missing');
must(/fullMedianMs/.test(script)&&/msPerPixel/.test(script)&&/speedPixels/.test(script),'tile timing extrapolation missing');
must(/CROSSOVER_GPU_TIMEOUT_MS=15000/.test(script)&&/benchmarkTimeout/.test(script)&&/timed out after/.test(script),'benchmark watchdog missing');
must(/classMismatchRate/.test(script)&&/escapeIterationMismatchRate/.test(script),'GPU quality comparison missing');
must(/id="backendBench"/.test(html)&&/id="benchmarkDialog"/.test(html)&&/id="benchmarkSave"/.test(html),'benchmark UI missing');
const choose=(script.match(/function chooseBackend[\s\S]*?function deepNeeded/)||[''])[0];
must(!/dsDirect|backend='ds'/.test(choose),'experimental DS-direct leaked into production router');
console.log(JSON.stringify({status:'pass',check:'crossover-benchmark-contract',checks:11},null,2));

View file

@ -0,0 +1,106 @@
import fs from 'node:fs/promises';
const F=Math.fround;
const U=2**-24;
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const [m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits),d=10n**BigInt(places);if(neg)n=-n;return[n,d]}
function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),d)}
function bitsForDepth(z){return Math.max(256,Math.ceil(z*Math.log2(10))+300)}
function snap(re,im,z){const span=`3.4e-${z}`,bits=bitsForDepth(z);return{bits,re:decFixed(re,bits),im:decFixed(im,bits),span:decFixed(span,bits),spanText:span}}
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}
function log2FixedAt(v,b){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-b}
function spanME(s){const l=log2FixedAt(s.span,s.bits),exp=Math.floor(l);return{mant:Math.pow(2,l-exp),exp}}
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
function oracle(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
function f32Direct(cr,ci,limit){cr=F(cr);ci=F(ci);let zr=0,zi=0;for(let n=0;n<limit;n++){const a=F(zr*zr),b=F(zi*zi);zi=F(F(F(2*zr)*zi)+ci);zr=F(F(a-b)+cr);if(F(F(zr*zr)+F(zi*zi))>4)return n+1}return limit}
// DS helpers
function q(a,b){const x=F(a+b),e=F(b-F(x-a));return[x,e]}
function sum(a,b){const x=F(a+b),bb=F(x-a),e=F(F(a-F(x-bb))+F(b-bb));return[x,e]}
function prod(a,b){const x=F(a*b),ca=F(4097*a),ah=F(ca-F(ca-a)),al=F(a-ah),cb=F(4097*b),bh=F(cb-F(cb-b)),bl=F(b-bh);let e=F(F(ah*bh)-x);e=F(e+F(ah*bl));e=F(e+F(al*bh));e=F(e+F(al*bl));return[x,e]}
function add(a,b){const t=sum(a[0],b[0]);return q(t[0],F(t[1]+F(a[1]+b[1])))}
function neg(a){return[F(-a[0]),F(-a[1])]} function sub(a,b){return add(a,neg(b))}
function mul(a,b){const t=prod(a[0],b[0]);let e=F(t[1]+F(a[0]*b[1]));e=F(e+F(a[1]*b[0]));e=F(e+F(a[1]*b[1]));return q(t[0],e)}
function scaleDS(a,b){const t=prod(a[0],b);return q(t[0],F(t[1]+F(a[1]*b)))}
function cmp(a,b){if(a[0]<b[0])return-1;if(a[0]>b[0])return 1;if(a[1]<b[1])return-1;if(a[1]>b[1])return 1;return 0}
function cadd(a,b){return[add(a[0],b[0]),add(a[1],b[1])]} function cmulDS(a,b){return[sub(mul(a[0],b[0]),mul(a[1],b[1])),add(mul(a[0],b[1]),mul(a[1],b[0]))]}
function mag2DS(a){return add(mul(a[0],a[0]),mul(a[1],a[1]))}
function split(x){const h=F(x);return[h,F(x-h)]}
function dsDirect(s,w,h,x,y,limit){const cre=split(fixedNum(s.re,s.bits)),cim=split(fixedNum(s.im,s.bits)),span=split(fixedNum(s.span,s.bits));const offx=(x+.5-.5*w)/w,offy=(.5*h-y-.5)/w;const cr=add(cre,scaleDS(span,F(offx))),ci=add(cim,scaleDS(span,F(offy)));let z=[[0,0],[0,0]];for(let n=0;n<limit;n++){z=cadd(cmulDS(z,z),[cr,ci]);if(cmp(mag2DS(z),[4,0])>0)return n+1}return limit}
function adjacentUniq(s,w,h){const y=Math.floor(h/2),cx=Math.floor(w/2),f32s=new Set(),dss=new Set(),cre=split(fixedNum(s.re,s.bits)),cim=split(fixedNum(s.im,s.bits)),sp=split(fixedNum(s.span,s.bits));for(let x=cx-10;x<=cx+10;x++){const p=pixel(s,w,h,x,y),rr=fixedNum(p[0],s.bits),ii=fixedNum(p[1],s.bits);f32s.add(F(rr)+','+F(ii));const ox=(x+.5-.5*w)/w,oy=(.5*h-y-.5)/w,cr=add(cre,scaleDS(sp,F(ox))),ci=add(cim,scaleDS(sp,F(oy)));dss.add(cr[0]+':'+cr[1]+','+ci[0]+':'+ci[1])}return{f32:f32s.size,ds:dss.size}}
function buildRef(s,limit){const b=s.bits+64,cr=align(s.re,s.bits,b),ci=align(s.im,s.bits,b),bail=16n<<BigInt(b);let zr=0n,zi=0n,refLen=limit;const hi=new Float32Array((limit+1)*2),lo=new Float32Array((limit+1)*2);for(let n=0;n<=limit;n++){const rr=fixedNum(zr,b),ri=fixedNum(zi,b),hr=F(rr),hii=F(ri);hi[n*2]=hr;hi[n*2+1]=hii;lo[n*2]=F(rr-hr);lo[n*2+1]=F(ri-hii);if(n===limit)break;const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;const mag=roundShift(zr*zr,b)+roundShift(zi*zi,b);if(mag>bail){refLen=n+1;const rr1=fixedNum(zr,b),ri1=fixedNum(zi,b),hr1=F(rr1),hi1=F(ri1);hi[2*refLen]=hr1;hi[2*refLen+1]=hi1;lo[2*refLen]=F(rr1-hr1);lo[2*refLen+1]=F(ri1-hi1);break}}return{hi,lo,refLen}}
function cmul(ar,ai,br,bi){return[F(F(ar*br)-F(ai*bi)),F(F(ar*bi)+F(ai*br))]}
function scale(v,e){if(e<-126)return 0;if(e>126)return F(8.507059e37);return F(v*Math.pow(2,e))}
function primary(ref,s,w,h,x,y,limit){const se0=spanME(s),se={mant:F(se0.mant),exp:se0.exp},dx=F((x+.5-.5*w)/w),dy=F((.5*h-y-.5)/w);let dr=F(se.mant*dx),di=F(se.mant*dy),wr=0,wi=0,e=se.exp,n=0,m=0,ops=0,err=U*Math.max(Math.abs(dr),Math.abs(di));while(true){if(n>=limit){const rp=Math.min(m,ref.refLen)*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),ea=Math.abs(err*2**e)+U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);return ea<=1e-3?{kind:'bounded',n}:{kind:'unknown',n}}if(m>ref.refLen)return{kind:'unknown',n};const rp=m*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),mag=F(F(zr*zr)+F(zi*zi)),ea=Math.abs(err*2**e)+U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);if(mag>4){if(Math.hypot(zr,zi)-ea>2)return{kind:'escaped',n};return{kind:'unknown',n}}const dmag=F(F(delr*delr)+F(deli*deli));if(m>0&&dmag>0&&mag<dmag){if(se.exp-e<-96)return{kind:'unknown',n};err=ea;wr=zr;wi=zi;dr=scale(F(se.mant*dx),se.exp);di=scale(F(se.mant*dy),se.exp);e=0;m=0;err+=U*Math.max(Math.abs(dr),Math.abs(di));continue}if(m>=ref.refLen)return{kind:'unknown',n};const rr=ref.hi[rp],ri=ref.hi[rp+1],lr=ref.lo[rp],li=ref.lo[rp+1],refAbs=Math.max(Math.abs(rr),Math.abs(ri))+Math.max(Math.abs(lr),Math.abs(li)),wAbs=Math.max(Math.abs(wr),Math.abs(wi)),dAbs=Math.max(Math.abs(dr),Math.abs(di)),p2=Math.abs(2**e),gain=2*refAbs+2*wAbs*p2,roundErr=U*(2*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1e-30);err=gain*err+roundErr;const[a,b]=cmul(rr,ri,wr,wi),[c,d]=cmul(lr,li,wr,wi),[sqR,sqI]=cmul(wr,wi,wr,wi);wr=F(F(F(2*a)+F(2*c))+F(scale(sqR,e)+dr));wi=F(F(F(2*b)+F(2*d))+F(scale(sqI,e)+di));m++;n++;ops++;if(!Number.isFinite(wr)||!Number.isFinite(wi)||!Number.isFinite(err)||Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di))>=1e30||err>1e35)return{kind:'unknown',n};const mm=Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di));if(mm>65536){wr=F(wr/65536);wi=F(wi/65536);dr=F(dr/65536);di=F(di/65536);err/=65536;e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wr=F(wr*65536);wi=F(wi*65536);dr=F(dr*65536);di=F(di*65536);err*=65536;e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n}}}
function pow2DS(a,e){if(e<-126)return[0,0];if(e>126)return[F(8.507059e37),0];return[F(a[0]*2**e),F(a[1]*2**e)]}
function cscale(a,b){return[scaleDS(a[0],b),scaleDS(a[1],b)]} function cpow2(a,e){return[pow2DS(a[0],e),pow2DS(a[1],e)]} function maxabsDS(a){return Math.max(Math.abs(F(a[0][0]+a[0][1])),Math.abs(F(a[1][0]+a[1][1])))}
function correction(ref,s,w,h,x,y,limit){const se=spanME(s),mh=F(se.mant),sm=[mh,F(se.mant-mh)],iw=1/w,iwh=F(iw),inv=[iwh,F(iw-iwh)],ox=F(x+.5-.5*w),oy=F(.5*h-y-.5),dx=scaleDS(inv,ox),dy=scaleDS(inv,oy),d0=[mul(sm,dx),mul(sm,dy)];let d=d0,wv=[[0,0],[0,0]],e=se.exp,n=0,m=0,ops=0;while(true){if(n>=limit)return{kind:'bounded',n};if(m>ref.refLen)return{kind:'unknown',n};const rp=m*2,r=[[ref.hi[rp],ref.lo[rp]],[ref.hi[rp+1],ref.lo[rp+1]]],delta=cpow2(wv,e),z=cadd(r,delta),mg=mag2DS(z);if(cmp(mg,[4,0])>0)return{kind:'escaped',n};const dm=mag2DS(delta);if(m>0&&cmp(dm,[0,0])>0&&cmp(mg,dm)<0){if(se.exp-e<-96)return{kind:'unknown',n};wv=z;d=cpow2(d0,se.exp);e=0;m=0;continue}if(m>=ref.refLen)return{kind:'unknown',n};const linear=cscale(cmulDS(r,wv),2),sq=cpow2(cmulDS(wv,wv),e);wv=cadd(cadd(linear,sq),d);m++;n++;ops++;const mm=Math.max(maxabsDS(wv),maxabsDS(d));if(mm>=1e30||!Number.isFinite(mm))return{kind:'unknown',n};if(mm>65536){wv=cscale(wv,1/65536);d=cscale(d,1/65536);e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wv=cscale(wv,65536);d=cscale(d,65536);e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n}}}
function adaptiveIter(z){return Math.max(350,350+Math.floor(70*Math.sqrt(z)+15*z))}
const tracks=[
{id:'cusp',re:'-0.75',im:'0'},
{id:'seahorse',re:'-0.7453983606667815',im:'0.1125046349959942'}
];
const depths=[0,2,4,6,8,10,12,14,16,20,30,40];
const SW=21,SH=15,FULL_W=1600,FULL_H=900;
const rows=[];
for(const tr of tracks){
for(const z of depths){
const s=snap(tr.re,tr.im,z);
const limit=adaptiveIter(z);
const ref=buildRef(s,limit);
let guardMismatch=0,f32FE=0,f32FB=0,dsFE=0,dsFB=0,deepFE=0,deepFB=0,deepU=0,tested=0,f32IterMismatch=0,dsIterMismatch=0,deepIterMismatch=0;
const f32Coords=new Set(), dsCoords=new Set();
const t0=performance.now();
for(let sy=0;sy<SH;sy++){
const y=Math.round(sy*(FULL_H-1)/Math.max(1,SH-1));
for(let sx=0;sx<SW;sx++){
const x=Math.round(sx*(FULL_W-1)/Math.max(1,SW-1));
const p=pixel(s,FULL_W,FULL_H,x,y);
const g=pixel(s,FULL_W,FULL_H,x,y,s.bits+64);
const o=oracle(p[0],p[1],s.bits,limit);
const gg=oracle(g[0],g[1],s.bits+64,limit);
if(o!==gg){guardMismatch++;continue;}
tested++;
const rr=fixedNum(p[0],s.bits),ii=fixedNum(p[1],s.bits);
const fd=f32Direct(rr,ii,limit);
const dd=dsDirect(s,FULL_W,FULL_H,x,y,limit);
f32Coords.add(F(rr)+','+F(ii));
{const cre=split(fixedNum(s.re,s.bits)),cim=split(fixedNum(s.im,s.bits)),sp=split(fixedNum(s.span,s.bits)),ox=(x+.5-.5*FULL_W)/FULL_W,oy=(.5*FULL_H-y-.5)/FULL_W,crd=add(cre,scaleDS(sp,F(ox))),cid=add(cim,scaleDS(sp,F(oy)));dsCoords.add(crd[0]+':'+crd[1]+','+cid[0]+':'+cid[1]);}
const pr=primary(ref,s,FULL_W,FULL_H,x,y,limit);
const dp=pr.kind==='unknown'?correction(ref,s,FULL_W,FULL_H,x,y,limit):pr;
if(fd<limit&&o===limit)f32FE++;
if(fd===limit&&o<limit)f32FB++;
if(dd<limit&&o===limit)dsFE++;
if(dd===limit&&o<limit)dsFB++;
if(fd<limit&&o<limit&&fd!==o)f32IterMismatch++;
if(dd<limit&&o<limit&&dd!==o)dsIterMismatch++;
if(dp.kind==='unknown')deepU++;
else {
if(dp.kind==='escaped'&&o===limit)deepFE++;
if(dp.kind==='bounded'&&o<limit)deepFB++;
if(dp.kind==='escaped'&&o<limit&&dp.n!==o)deepIterMismatch++;
}
}
}
const adj=adjacentUniq(s,FULL_W,FULL_H);
const row={
track:tr.id,z,limit,tested,guardMismatch,adjacentUniqueX:adj,
f32:{falseEscaped:f32FE,falseBounded:f32FB,escapeIterationMismatch:f32IterMismatch,uniqueCoords:f32Coords.size},
ds:{falseEscaped:dsFE,falseBounded:dsFB,escapeIterationMismatch:dsIterMismatch,uniqueCoords:dsCoords.size},
deep:{falseEscaped:deepFE,falseBounded:deepFB,unknown:deepU,escapeIterationMismatch:deepIterMismatch},
modelMs:Math.round(performance.now()-t0)
};
rows.push(row);
}
}
const seahorse=rows.filter(r=>r.track==='seahorse');
const material=r=>((r.falseEscaped+r.falseBounded)/Math.max(1,r.tested))>0.01;
const firstF32=seahorse.find(r=>material({...r.f32,tested:r.tested})||r.adjacentUniqueX.f32<11)?.z??null;
const firstDs=seahorse.find(r=>material({...r.ds,tested:r.tested})||r.adjacentUniqueX.ds<11)?.z??null;
const deepIssues=seahorse.filter(r=>r.deep.falseEscaped||r.deep.falseBounded||r.deep.unknown);
const report={format:'mandelbrot-crossover-quality-v1',kind:'cpu-f32-model + BigInt P/P+64 oracle; not real GPU',viewport:[FULL_W,FULL_H],sampleGrid:[SW,SH],depths,tracks:tracks.map(t=>t.id),rows,summary:{firstMaterialF32DegradationDepth:firstF32,firstMaterialDsDegradationDepth:firstDs,deepIssueRows:deepIssues.map(r=>({z:r.z,...r.deep})),interpretation:'On the Seahorse track at a 1600x900 viewport, f32 direct becomes materially unsuitable around z6; DS direct remains a plausible bridge through roughly z8 and degrades by z10. Corrected deep is the best of the modeled backends, but one z8 false escape means it is not a membership certificate.'}};
if(rows.some(r=>r.guardMismatch))throw new Error('P/P+64 oracle guard mismatch in crossover corpus');
await fs.mkdir(new URL('../audit/',import.meta.url),{recursive:true});
await fs.writeFile(new URL('../audit/v24-crossover-quality.json',import.meta.url),JSON.stringify(report,null,2));
console.log(JSON.stringify({status:'pass',check:'crossover-quality-advisory',summary:report.summary},null,2));

View file

@ -0,0 +1,24 @@
import fs from 'node:fs/promises';
const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('script.js',root),'utf8'),kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8'),html=await fs.readFile(new URL('index.html',root),'utf8');
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(/DIRECT_DS_GUARDED_WGSL/.test(kernels)&&/riskLimit:f32/.test(kernels)&&/riskQueue:array<u32>/.test(kernels)&&/atomicAdd\(&hybridStats\.enqueued,1u\)/.test(kernels),'guarded DS sensitivity queue shader missing');
must(/DIRECT_FIXED96_QUEUE_ARGS_WGSL/.test(kernels)&&/dispatchCount/.test(kernels)&&/indirectArgs\.x=\(n\+63u\)\/64u/.test(kernels),'fixed96 queue indirect-args shader missing');
must(/DIRECT_FIXED96_CORRECT_WGSL/.test(kernels)&&/struct FX96/.test(kernels)&&/fn mul32/.test(kernels)&&/Q8\.88/.test(kernels),'fixed96 correction shader missing');
const a=kernels.indexOf('const DIRECT_FIXED96_CORRECT_WGSL'),b=kernels.indexOf('// Deep path',a),fx=kernels.slice(a,b);
must(!/fn fx_bad_range/.test(fx),'invalid Q8.88 sign-extension range guard regressed');
must(/let TWO=FX96\(0u,0u,0x02000000u\)/.test(fx)&&/fx_cmp_unsigned\(fx_abs\(zr\),TWO\)>0/.test(fx)&&/write_escape\(out,n,zr,zi\)/.test(fx),'fixed96 certain-escape guard missing');
must(a>=0&&b>a&&!fx.includes('var<storage,read> refs'),'fixed96 correction unexpectedly depends on deep reference');
must(/@workgroup_size\(64\)/.test(fx)&&/let out=riskQueue\[qi\]/.test(fx)&&/atomicLoad\(&hybridStats\.dispatchCount\)/.test(fx),'fixed96 correction is not queue-indexed');
must(!/@workgroup_size\(8,8\)/.test(fx),'fixed96 correction regressed to full-screen 2D workgroups');
must(/direct-fixed96-queue-args-experiment/.test(script)&&/this\.fixed96QueueArgs=/.test(script),'fixed96 queue-args pipeline missing');
must(/direct-fixed96-correction-experiment/.test(script)&&/this\.fixed96Correct=/.test(script),'fixed96 correction pipeline missing');
must(/q88FromRational/.test(script)&&/q88Words/.test(script)&&/fixed96TileData/.test(script),'exact BigInt-to-Q8.88 packing missing');
must(/benchmarkFixed96Direct/.test(script)&&/renderTileMetaFixed96Direct/.test(script),'fixed96 benchmark methods missing');
must(/GPUBufferUsage[\s\S]*?B\.INDIRECT/.test(script)&&/dispatchWorkgroupsIndirect\(indirect,0\)/.test(script),'true sparse indirect dispatch missing');
must(/fixed96QueueIntegrity/.test(script)&&/queue integrity failure/.test(script),'queue integrity fail-closed gate missing');
must(/FIXED96_DEPTHS=\[6,8,10,12\]/.test(script)&&/FIXED96_RISK_LIMITS=\[1e11,1e12,1e13,1e14\]/.test(script),'fixed96 depth/risk sweep missing');
must(/runFixed96Experiment/.test(script)&&/failureReason/.test(script)&&/Every risk candidate/.test(script),'fixed96 experiment/full failure reporting missing');
must(/speedQueueRate/.test(script)&&/timingRepresentative/.test(script)&&/sampling-mismatch/.test(script),'speed/quality sampling consistency gate missing');
must(/id="sparseBench"/.test(html)&&/Fixed96 Direct実験/.test(html)&&/id="sparseDialog"/.test(html),'fixed96 UI missing');
const choose=(script.match(/function chooseBackend[\s\S]*?function deepNeeded/)||[''])[0];must(!/fixed96|FIXED96/.test(choose),'diagnostic fixed96 leaked into production router');
console.log(JSON.stringify({status:'pass',check:'fixed96-experiment-contract',checks:20},null,2));

View file

@ -0,0 +1,15 @@
const MASK=0xffffffffn,MOD=1n<<96n,SIGN=1n<<95n,Q=88n;
const u32=x=>Number(BigInt(x)&MASK)>>>0;
function words(x){x%=MOD;if(x<0)x+=MOD;return[u32(x),u32(x>>32n),u32(x>>64n)]}
function signed(w){let x=BigInt(w[0])+(BigInt(w[1])<<32n)+(BigInt(w[2])<<64n);return x>=SIGN?x-MOD:x}
function neg(a){let lo=(~a[0]+1)>>>0,c0=lo===0?1:0,mid=(~a[1]+c0)>>>0,c1=c0&&mid===0?1:0,hi=(~a[2]+c1)>>>0;return[lo,mid,hi]}
function isNeg(a){return (a[2]&0x80000000)!==0} function absw(a){return isNeg(a)?neg(a):a}
function add(a,b){let lo=(a[0]+b[0])>>>0,c0=lo<a[0]?1:0,m0=(a[1]+b[1])>>>0,c1=m0<a[1]?1:0,mid=(m0+c0)>>>0,c2=mid<m0?1:0,hi=(a[2]+b[2]+c1+c2)>>>0;return[lo,mid,hi]}
function mul32(a,b){const a0=a&65535,a1=a>>>16,b0=b&65535,b1=b>>>16,p0=a0*b0,p1=a0*b1,p2=a1*b0,p3=a1*b1,middle=(p0>>>16)+(p1&65535)+(p2&65535),lo=((p0&65535)|((middle&65535)<<16))>>>0,hi=(p3+(p1>>>16)+(p2>>>16)+(middle>>>16))>>>0;return[lo,hi]}
function addAt(p,idx,v){v>>>=0;while(v&&idx<6){const old=p[idx]>>>0,sum=(old+v)>>>0;p[idx]=sum;v=sum<old?1:0;idx++}}
function addProd(p,idx,a,b){const q=mul32(a,b);addAt(p,idx,q[0]);addAt(p,idx+1,q[1])}
function mulfx(a0,b0){const sign=isNeg(a0)!==isNeg(b0),a=absw(a0),b=absw(b0),p=[0,0,0,0,0,0];addProd(p,0,a[0],b[0]);addProd(p,1,a[0],b[1]);addProd(p,2,a[0],b[2]);addProd(p,1,a[1],b[0]);addProd(p,2,a[1],b[1]);addProd(p,3,a[1],b[2]);addProd(p,2,a[2],b[0]);addProd(p,3,a[2],b[1]);addProd(p,4,a[2],b[2]);let r=[((p[2]>>>24)|(p[3]<<8))>>>0,((p[3]>>>24)|(p[4]<<8))>>>0,((p[4]>>>24)|(p[5]<<8))>>>0];if(((p[2]>>>23)&1)!==0)r=add(r,[1,0,0]);return sign?neg(r):r}
function roundShift(v,b){const negv=v<0n,a=negv?-v:v,q=(a+(1n<<(b-1n)))>>b;return negv?-q:q}
let seed=0x12345678;const rand=()=>{seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed};
for(let i=0;i<20000;i++){const ai=(BigInt(rand()%16000000)-8000000n)*(1n<<68n),bi=(BigInt(rand()%16000000)-8000000n)*(1n<<68n),a=words(ai),b=words(bi),got=signed(mulfx(a,b)),want=roundShift(ai*bi,Q);if(got!==want)throw new Error(`mul mismatch ${i}: ${got} != ${want}`)}
console.log(JSON.stringify({status:'pass',check:'fixed96-limb-multiply',cases:20000,format:'Q8.88'},null,2));

View file

@ -0,0 +1,24 @@
const MASK=0xffffffffn,MOD=1n<<96n,SIGN=1n<<95n,Q=88n,MIN=-(1n<<95n),MAX=(1n<<95n)-1n;
const u32=x=>Number(BigInt(x)&MASK)>>>0;
function words(x){if(x<MIN||x>MAX)throw new Error('Q8.88 overflow');if(x<0)x+=MOD;return[u32(x),u32(x>>32n),u32(x>>64n)]}
function signed(w){let x=BigInt(w[0])+(BigInt(w[1])<<32n)+(BigInt(w[2])<<64n);return x>=SIGN?x-MOD:x}
function neg(a){let lo=(~a[0]+1)>>>0,c0=lo===0?1:0,mid=(~a[1]+c0)>>>0,c1=c0&&mid===0?1:0,hi=(~a[2]+c1)>>>0;return[lo,mid,hi]}
function isNeg(a){return (a[2]&0x80000000)!==0} function absw(a){return isNeg(a)?neg(a):a}
function add(a,b){let lo=(a[0]+b[0])>>>0,c0=lo<a[0]?1:0,m0=(a[1]+b[1])>>>0,c1=m0<a[1]?1:0,mid=(m0+c0)>>>0,c2=mid<m0?1:0,hi=(a[2]+b[2]+c1+c2)>>>0;return[lo,mid,hi]}
function sub(a,b){return add(a,neg(b))}
function cmpu(a,b){for(let i=2;i>=0;i--){if(a[i]<b[i])return-1;if(a[i]>b[i])return 1}return 0}
function mul32(a,b){const a0=a&65535,a1=a>>>16,b0=b&65535,b1=b>>>16,p0=a0*b0,p1=a0*b1,p2=a1*b0,p3=a1*b1,middle=(p0>>>16)+(p1&65535)+(p2&65535),lo=((p0&65535)|((middle&65535)<<16))>>>0,hi=(p3+(p1>>>16)+(p2>>>16)+(middle>>>16))>>>0;return[lo,hi]}
function addAt(p,idx,v){v>>>=0;while(v&&idx<6){const old=p[idx]>>>0,sum=(old+v)>>>0;p[idx]=sum;v=sum<old?1:0;idx++}}
function addProd(p,idx,a,b){const q=mul32(a,b);addAt(p,idx,q[0]);addAt(p,idx+1,q[1])}
function mulfx(a0,b0){const sign=isNeg(a0)!==isNeg(b0),a=absw(a0),b=absw(b0),p=[0,0,0,0,0,0];addProd(p,0,a[0],b[0]);addProd(p,1,a[0],b[1]);addProd(p,2,a[0],b[2]);addProd(p,1,a[1],b[0]);addProd(p,2,a[1],b[1]);addProd(p,3,a[1],b[2]);addProd(p,2,a[2],b[0]);addProd(p,3,a[2],b[1]);addProd(p,4,a[2],b[2]);let r=[((p[2]>>>24)|(p[3]<<8))>>>0,((p[3]>>>24)|(p[4]<<8))>>>0,((p[4]>>>24)|(p[5]<<8))>>>0];if(((p[2]>>>23)&1)!==0)r=add(r,[1,0,0]);return sign?neg(r):r}
function roundShift(v,b=Q){const n=v<0n,a=n?-v:v,q=(a+(1n<<(b-1n)))>>b;return n?-q:q}
function exactMul(a,b){const r=roundShift(a*b);if(r<MIN||r>MAX)throw new Error('exact overflow');return r}
const TWO=words(2n<<Q),FOUR=words(4n<<Q);
function limbOrbit(cr0,ci0,limit){const cr=words(cr0),ci=words(ci0);let zr=[0,0,0],zi=[0,0,0];for(let n=1;n<=limit;n++){const zr2=mulfx(zr,zr),zi2=mulfx(zi,zi),zri=mulfx(zr,zi);zr=add(sub(zr2,zi2),cr);zi=add(add(zri,zri),ci);if(cmpu(absw(zr),TWO)>0||cmpu(absw(zi),TWO)>0)return n;const mag=add(mulfx(zr,zr),mulfx(zi,zi));if(cmpu(mag,FOUR)>0)return n}return limit}
function exactOrbit(cr,ci,limit){let zr=0n,zi=0n;const four=4n<<Q;for(let n=1;n<=limit;n++){const zr2=exactMul(zr,zr),zi2=exactMul(zi,zi),zri=exactMul(zr,zi);zr=zr2-zi2+cr;zi=2n*zri+ci;if((zr<0n?-zr:zr)>2n<<Q||(zi<0n?-zi:zi)>2n<<Q)return n;const mag=exactMul(zr,zr)+exactMul(zi,zi);if(mag>four)return n}return limit}
function qdec(x){return BigInt(Math.round(x*2**24))<<64n}
const cases=[];
for(const [re,im] of [[-0.7453983606667815,0.1125046349959942],[-0.75,0.1],[-0.743643887037151,0.13182590420533],[-1.8,0.2],[0.3,0.55],[1.2,0]]){
const cr=qdec(re),ci=qdec(im),a=limbOrbit(cr,ci,1200),b=exactOrbit(cr,ci,1200);if(a!==b)throw new Error(`orbit mismatch ${re},${im}: ${a} != ${b}`);cases.push({re,im,iter:a});
}
console.log(JSON.stringify({status:'pass',check:'fixed96-limb-orbit',cases},null,2));

View file

@ -0,0 +1,39 @@
import fs from 'node:fs/promises';
const F=Math.fround,Q=88,MOD=1n<<96n,MIN=-(1n<<95n),MAX=(1n<<95n)-1n;
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const[m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits),d=10n**BigInt(places);if(neg)n=-n;return[n,d]}
function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),d)}
function bitsForDepth(z){return Math.max(256,Math.ceil(z*Math.log2(10))+300)}
function snap(re,im,z){const bits=bitsForDepth(z);return{bits,re:decFixed(re,bits),im:decFixed(im,bits),span:decFixed(`3.4e-${z}`,bits)}}
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
function oracle(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
function q88FromRational(numer,denom,bits){let n=numer,d=denom,sh=Q-bits;if(sh>=0)n<<=BigInt(sh);else d<<=BigInt(-sh);return roundDiv(n,d)}
function qmul(a,b){const r=roundShift(a*b,Q);if(r<MIN||r>MAX)throw new Error('Q8.88 overflow');return r}
function qadd(a,b){const r=a+b;if(r<MIN||r>MAX)throw new Error('Q8.88 overflow');return r}
function fixed96Direct(s,w,h,x,y,limit){const den=4n*BigInt(w),ox4=4n*BigInt(x)+2n-2n*BigInt(w),oy4=2n*BigInt(h)-4n*BigInt(y)-2n,cr=q88FromRational(s.re*den+s.span*ox4,den,s.bits),ci=q88FromRational(s.im*den+s.span*oy4,den,s.bits);let zr=0n,zi=0n;const four=4n<<88n;for(let n=0;n<limit;n++){const zr2=qmul(zr,zr),zi2=qmul(zi,zi),ri=qmul(zr,zi);zr=qadd(qadd(zr2,-zi2),cr);zi=qadd(qadd(ri,ri),ci);if(qadd(qmul(zr,zr),qmul(zi,zi))>four)return n+1}return limit}
// DS sensitivity model matching the experimental guard.
function q(a,b){const x=F(F(a)+F(b)),e=F(F(b)-F(x-F(a)));return[x,e]}
function sum(a,b){a=F(a);b=F(b);const x=F(a+b),bb=F(x-a),e=F(F(a-F(x-bb))+F(b-bb));return[x,e]}
function prod(a,b){a=F(a);b=F(b);const x=F(a*b),ca=F(F(4097)*a),ah=F(ca-F(ca-a)),al=F(a-ah),cb=F(F(4097)*b),bh=F(cb-F(cb-b)),bl=F(b-bh);let e=F(F(ah*bh)-x);e=F(e+F(ah*bl));e=F(e+F(al*bh));e=F(e+F(al*bl));return[x,e]}
function add(a,b){const t=sum(a[0],b[0]);return q(t[0],F(t[1]+F(F(a[1])+F(b[1]))))}
function neg(a){return[F(-a[0]),F(-a[1])]} function sub(a,b){return add(a,neg(b))}
function mul(a,b){const t=prod(a[0],b[0]);let e=F(t[1]+F(F(a[0])*F(b[1])));e=F(e+F(F(a[1])*F(b[0])));e=F(e+F(F(a[1])*F(b[1])));return q(t[0],e)}
function scale(a,b){const t=prod(a[0],F(b));return q(t[0],F(t[1]+F(F(a[1])*F(b))))}
function cmp(a,b){if(a[0]<b[0])return-1;if(a[0]>b[0])return 1;if(a[1]<b[1])return-1;if(a[1]>b[1])return 1;return 0}
function cadd(a,b){return[add(a[0],b[0]),add(a[1],b[1])]} function cmul(a,b){return[sub(mul(a[0],b[0]),mul(a[1],b[1])),add(mul(a[0],b[1]),mul(a[1],b[0]))]}
function split(x){const h=F(x);return[h,F(x-h)]} function val(a){return F(F(a[0])+F(a[1]))}
function dsRisk(s,w,h,x,y,limit){const cre=split(fixedNum(s.re,s.bits)),cim=split(fixedNum(s.im,s.bits)),sp=split(fixedNum(s.span,s.bits)),ox=F(F(F(x+.5)-F(.5*w))/F(w)),oy=F(F(F(.5*h)-F(y+.5))/F(w)),c=[add(cre,scale(sp,ox)),add(cim,scale(sp,oy))];let z=[[0,0],[0,0]],dr=0,di=0,maxDer=0;for(let n=0;n<limit;n++){const zr=val(z[0]),zi=val(z[1]),ndr=F(F(2)*F(F(zr*dr)-F(zi*di))+F(1)),ndi=F(F(2)*F(F(zr*di)+F(zi*dr)));dr=ndr;di=ndi;maxDer=Math.max(maxDer,Math.hypot(dr,di));z=cadd(cmul(z,z),c);const mag=add(mul(z[0],z[0]),mul(z[1],z[1]));if(cmp(mag,[4,0])>0)return{n:n+1,maxDer}}return{n:limit,maxDer}}
function adaptiveIter(z){return Math.max(350,350+Math.floor(70*Math.sqrt(z)+15*z))}
const re='-0.7453983606667815',im='0.1125046349959942',depths=[6,8,10,12],risks=[1e11,1e12,1e13,1e14],W=1600,H=900,SW=31,SH=21,rows=[];
for(const z of depths){const s=snap(re,im,z),limit=adaptiveIter(z),samples=[];for(let sy=0;sy<SH;sy++){const y=Math.round(sy*(H-1)/(SH-1));for(let sx=0;sx<SW;sx++){const x=Math.round(sx*(W-1)/(SW-1)),p=pixel(s,W,H,x,y),pg=pixel(s,W,H,x,y,s.bits+64),o=oracle(p[0],p[1],s.bits,limit),g=oracle(pg[0],pg[1],s.bits+64,limit);if(o!==g){samples.push({guardMismatch:true});continue}const d=dsRisk(s,W,H,x,y,limit),f=fixed96Direct(s,W,H,x,y,limit);samples.push({guardMismatch:false,o,d,f})}}
const variants=[];for(const risk of risks){let tested=0,guardMismatch=0,uncertain=0,wrongBefore=0,wrongAfter=0;for(const a of samples){if(a.guardMismatch){guardMismatch++;continue}tested++;if((a.d.n<a.d.limit)!=(a.o<a.d.limit)){}const before=(a.d.n<limit)!=(a.o<limit);if(before)wrongBefore++;let r=a.d.n;if(a.d.maxDer>risk){uncertain++;r=a.f}if((r<limit)!=(a.o<limit))wrongAfter++}variants.push({risk,tested,guardMismatch,uncertain,uncertainRate:uncertain/Math.max(1,tested),wrongBefore,wrongAfter})}
rows.push({z,limit,variants})}
if(rows.some(r=>r.variants.some(v=>v.guardMismatch)))throw new Error('oracle guard mismatch');
for(const r of rows)if(!r.variants.some(v=>v.wrongAfter===0))throw new Error(`no safe fixed96 candidate at z${r.z}`);
const report={format:'mandelbrot-fixed96-q88-model-v1',kind:'Exact BigInt model of signed Q8.88 direct + DS risk gate + BigInt P/P+64 oracle; integer GPU arithmetic should be deterministic modulo implementation bugs',viewport:[W,H],sampleGrid:[SW,SH],rows};
await fs.mkdir(new URL('../audit/',import.meta.url),{recursive:true});await fs.writeFile(new URL('../audit/v24-fixed96-model.json',import.meta.url),JSON.stringify(report,null,2));
console.log(JSON.stringify({status:'pass',check:'fixed96-q88-model',rows:rows.map(r=>({z:r.z,variants:r.variants}))},null,2));

View file

@ -0,0 +1,22 @@
import fs from 'node:fs/promises';
import vm from 'node:vm';
const root=new URL('../',import.meta.url);
const script=await fs.readFile(new URL('script.js',root),'utf8');
const start=script.indexOf('function fixed96QueueIntegrity('),end=script.indexOf('\nfunction mulRatio',start);
if(start<0||end<=start)throw new Error('fixed96QueueIntegrity source missing');
const source=script.slice(start,end);
const ctx={};ctx.globalThis=ctx;
vm.runInNewContext(`const FIXED96_WORKGROUP_SIZE=64;${source};globalThis.check=fixed96QueueIntegrity`,ctx);
const check=ctx.check;
const stats=(selected,{overflow=0,enqueued=selected,dispatchCount=enqueued}={})=>new Uint32Array([selected,overflow,enqueued,dispatchCount,0,0,0,0]);
const fixed=(processed,{remaining=0,invalidIndex=0,staleEntry=0,corrected=processed-remaining}={})=>new Uint32Array([remaining,invalidIndex,processed,staleEntry,0,0,0,corrected]);
const args=n=>new Uint32Array([Math.ceil(n/64),1,1,0]);
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(check(stats(0),fixed(0),args(0),64).ok,'zero queue should be valid');
must(check(stats(20),fixed(20),args(20),64).ok,'20-entry queue should be valid');
must(check(stats(65),fixed(65),args(65),128).ok,'65-entry queue should use two workgroups');
must(!check(stats(65),fixed(65),new Uint32Array([1,1,1,0]),128).ok,'wrong indirect workgroup count was not rejected');
must(!check(stats(64,{overflow:1,enqueued:63,dispatchCount:63}),fixed(63),args(63),64).ok,'queue overflow was not rejected');
must(!check(stats(20),fixed(19,{corrected:19}),args(20),64).ok,'processed/enqueued mismatch was not rejected');
must(!check(stats(20),fixed(20,{staleEntry:1,remaining:1,corrected:19}),args(20),64).ok,'stale queue entry was not rejected');
console.log(JSON.stringify({status:'pass',check:'fixed96-queue-integrity-model',cases:7,workgroupSize:64},null,2));

View file

@ -0,0 +1,24 @@
// Regression for v24.2.6: Q8.88 values such as +1.2 were incorrectly treated
// as invalid sign extension. Model the corrected shader policy: all Q8.88
// values are valid; after each iteration, |Re|>2 or |Im|>2 is an immediate
// mathematically-certain escape, otherwise |z|^2 can be squared safely.
const Q=88n, ONE=1n<<Q, TWO=2n<<Q, FOUR=4n<<Q, MIN=-(1n<<95n), MAX=(1n<<95n)-1n;
function roundShift(v,b=Q){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(b-1n)))>>b;return neg?-q:q}
function q(x){return BigInt(Math.round(x*2**24)) << 64n} // exact enough for test constants
function mul(a,b){const r=roundShift(a*b);if(r<MIN||r>MAX)throw new Error('unexpected Q8.88 overflow');return r}
function add(a,b){const r=a+b;if(r<MIN||r>MAX)throw new Error('unexpected Q8.88 overflow');return r}
function abs(a){return a<0n?-a:a}
function iterate(cr,ci,maxIter=2000){let zr=0n,zi=0n;for(let n=1;n<=maxIter;n++){
const zr2=mul(zr,zr),zi2=mul(zi,zi),zri=mul(zr,zi);
zr=add(add(zr2,-zi2),cr);zi=add(add(zri,zri),ci);
if(abs(zr)>TWO||abs(zi)>TWO)return n;
const mag=add(mul(zr,zr),mul(zi,zi));if(mag>FOUR)return n;
}return maxIter}
// +1.2 is a valid Q8.88 value and must not be rejected merely because the
// integer byte is non-zero.
const plus12=q(1.2);if(plus12<=ONE||plus12>=TWO)throw new Error('test constant invalid');
// Escaping parameters whose orbit passes through component magnitudes >1.
for(const [re,im] of [[1.2,0],[-1.8,0.2],[0.5,0.8],[0.4,0.6]]){
const n=iterate(q(re),q(im),500);if(n>=500)throw new Error(`expected escape for ${re},${im}`);
}
console.log(JSON.stringify({status:'pass',check:'fixed96-shader-range-regression',cases:4,regression:'v24.2.6 rejected valid Q8.88 integer-byte values'},null,2));

View file

@ -1,6 +1,6 @@
const must=(x,m)=>{if(!x)throw new Error(m)};
for(const [W,H] of [[17,11],[1024,768],[16384,9216]])for(const tile of [64,512])for(const sx of [.25,.5,.75])for(const sy of [.25,.5,.75])for(const [tx,ty,lx,ly] of [[0,0,0,0],[tile,0,3,5],[tile*2,tile,7,11]]){if(tx+lx>=W||ty+ly>=H)continue;const gx=tx+lx+sx,gy=ty+ly+sy,fullX=(gx-.5*W)/W,fullY=(.5*H-gy)/W,tiledX=((tx+lx+sx)-.5*W)/W,tiledY=(.5*H-(ty+ly+sy))/W;must(Object.is(fullX,tiledX)&&Object.is(fullY,tiledY),'tile mapping seam')}
const maxFramePixels=8*1048576,fieldBytes=maxFramePixels*4;must(fieldBytes<=128*1024*1024,'default storage binding budget exceeded');
const oldQueueBytes=maxFramePixels*4,newCounterBytes=16;must(newCounterBytes<oldQueueBytes/100000,'unresolved queue was not reduced to a counter');
const oldQueueBytes=maxFramePixels*4,newCounterBytes=32;must(newCounterBytes<oldQueueBytes/100000,'unresolved queue was not reduced to a counter');
for(const [cw,ch] of [[390,844],[1920,1080],[1080,1920]]){const aspect=ch/cw,w0=16384,h0=Math.round(w0*aspect),h=Math.min(16384,h0),w=h0>16384?Math.max(64,Math.round(h/aspect)):w0;must(w<=16384&&h<=16384,'export dimension clamp failed')}
console.log(JSON.stringify({status:'pass',pixelContract:'centered',exportTile:512,strictRetryPass:false,unresolvedCounterBytes:newCounterBytes,maxExportSide:16384},null,2));

View file

@ -0,0 +1,15 @@
import fs from 'node:fs/promises';
const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('script.js',root),'utf8'),kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8'),html=await fs.readFile(new URL('index.html',root),'utf8');
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(/DIRECT_DS_GUARDED_WGSL/.test(kernels)&&/riskLimit:f32/.test(kernels)&&/deriv=2\.0\*cmul/.test(kernels),'guarded DS shader / derivative risk gate missing');
must(/direct-ds-guarded-experiment/.test(script)&&/this\.dsGuarded=/.test(script),'guarded DS pipeline missing');
must(/benchmarkHybrid/.test(script)&&/renderTileMetaHybrid/.test(script),'hybrid benchmark methods missing');
must(/HYBRID_DEPTHS=\[6,8,10,12\]/.test(script)&&/HYBRID_RISK_LIMITS=\[1e12,1e13,1e14\]/.test(script),'hybrid depth/risk sweep missing');
must(/runHybridExperiment/.test(script)&&/uncertainRate/.test(script)&&/speedupVsDeepCold/.test(script),'hybrid experiment/report missing');
must(/id="hybridBench"/.test(html)&&/id="hybridDialog"/.test(html)&&/id="hybridSave"/.test(html),'hybrid experiment UI missing');
must(/function openHybridDialog\(/.test(script)&&/function startHybridExperimentFromUi\(/.test(script),'hybrid UI start function missing');
must(/\$\('#hybridBench'\)\.onclick=\(\)=>\{toast\('Hybrid実験を開始'\);void startHybridExperimentFromUi\(\)\}/.test(script),'toolbar Hybrid button does not directly start experiment');
must(/\$\('#hybridStart'\)\.onclick=startHybridExperimentFromUi/.test(script),'dialog rerun button is not wired to experiment');
const choose=(script.match(/function chooseBackend[\s\S]*?function deepNeeded/)||[''])[0];
must(!/dsGuarded|hybrid-candidate|backend='hybrid'/.test(choose),'diagnostic hybrid leaked into production router');
console.log(JSON.stringify({status:'pass',check:'hybrid-experiment-contract',checks:10},null,2));

View file

@ -0,0 +1,39 @@
import fs from 'node:fs/promises';
const F=Math.fround;
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const[m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits),d=10n**BigInt(places);if(neg)n=-n;return[n,d]}
function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),d)}
function bitsForDepth(z){return Math.max(256,Math.ceil(z*Math.log2(10))+300)}
function snap(re,im,z){const span=`3.4e-${z}`,bits=bitsForDepth(z);return{bits,re:decFixed(re,bits),im:decFixed(im,bits),span:decFixed(span,bits)}}
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
function oracle(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
function q(a,b){const x=F(a+b),e=F(b-F(x-a));return[x,e]}
function sum(a,b){const x=F(a+b),bb=F(x-a),e=F(F(a-F(x-bb))+F(b-bb));return[x,e]}
function prod(a,b){const x=F(a*b),ca=F(4097*a),ah=F(ca-F(ca-a)),al=F(a-ah),cb=F(4097*b),bh=F(cb-F(cb-b)),bl=F(b-bh);let e=F(F(ah*bh)-x);e=F(e+F(ah*bl));e=F(e+F(al*bh));e=F(e+F(al*bl));return[x,e]}
function add(a,b){const t=sum(a[0],b[0]);return q(t[0],F(t[1]+F(a[1]+b[1])))}
function neg(a){return[F(-a[0]),F(-a[1])]}
function sub(a,b){return add(a,neg(b))}
function mul(a,b){const t=prod(a[0],b[0]);let e=F(t[1]+F(a[0]*b[1]));e=F(e+F(a[1]*b[0]));e=F(e+F(a[1]*b[1]));return q(t[0],e)}
function scaleDS(a,b){const t=prod(a[0],b);return q(t[0],F(t[1]+F(a[1]*b)))}
function cmp(a,b){if(a[0]<b[0])return-1;if(a[0]>b[0])return 1;if(a[1]<b[1])return-1;if(a[1]>b[1])return 1;return 0}
function cadd(a,b){return[add(a[0],b[0]),add(a[1],b[1])]}
function cmulDS(a,b){return[sub(mul(a[0],b[0]),mul(a[1],b[1])),add(mul(a[0],b[1]),mul(a[1],b[0]))]}
function mag2DS(a){return add(mul(a[0],a[0]),mul(a[1],a[1]))}
function split(x){const h=F(x);return[h,F(x-h)]}
function value(a){return F(a[0]+a[1])}
function dsRisk(s,w,h,x,y,limit){const cre=split(fixedNum(s.re,s.bits)),cim=split(fixedNum(s.im,s.bits)),span=split(fixedNum(s.span,s.bits)),offx=(x+.5-.5*w)/w,offy=(.5*h-y-.5)/w,cr=add(cre,scaleDS(span,F(offx))),ci=add(cim,scaleDS(span,F(offy))),c=[cr,ci];let z=[[0,0],[0,0]],dr=0,di=0,maxDer=0;for(let n=0;n<limit;n++){const zr=value(z[0]),zi=value(z[1]),ndr=2*(zr*dr-zi*di)+1,ndi=2*(zr*di+zi*dr);dr=ndr;di=ndi;maxDer=Math.max(maxDer,Math.hypot(dr,di));z=cadd(cmulDS(z,z),c);if(cmp(mag2DS(z),[4,0])>0)return{n:n+1,maxDer}}return{n:limit,maxDer}}
function adaptiveIter(z){return Math.max(350,350+Math.floor(70*Math.sqrt(z)+15*z))}
const re='-0.7453983606667815',im='0.1125046349959942',depths=[6,8,10,12],W=1600,H=900,SW=31,SH=21,riskLimit=1e13,rows=[];
for(const z of depths){const s=snap(re,im,z),limit=adaptiveIter(z);let tested=0,wrong=0,uncertain=0,caught=0,guardMismatch=0;for(let sy=0;sy<SH;sy++){const y=Math.round(sy*(H-1)/(SH-1));for(let sx=0;sx<SW;sx++){const x=Math.round(sx*(W-1)/(SW-1)),p=pixel(s,W,H,x,y),g=pixel(s,W,H,x,y,s.bits+64),o=oracle(p[0],p[1],s.bits,limit),gg=oracle(g[0],g[1],s.bits+64,limit);if(o!==gg){guardMismatch++;continue}tested++;const d=dsRisk(s,W,H,x,y,limit),bad=(d.n<limit)!=(o<limit),u=d.maxDer>riskLimit;if(bad)wrong++;if(u)uncertain++;if(bad&&u)caught++}}
rows.push({z,tested,guardMismatch,wrong,uncertain,caught,uncertainRate:uncertain/tested,catchRate:wrong?caught/wrong:1});}
if(rows.some(r=>r.guardMismatch))throw new Error('oracle guard mismatch');
for(const r of rows.slice(0,3))if(r.catchRate!==1)throw new Error(`risk gate missed DS class error at z${r.z}`);
if(!(rows[0].uncertainRate<.08&&rows[1].uncertainRate<.2&&rows[2].uncertainRate<.5))throw new Error('risk gate is too broad before z12');
if(!(rows[3].uncertainRate>.75))throw new Error('z12 should signal crossover toward Full Deep');
const report={format:'mandelbrot-hybrid-risk-model-v1',kind:'CPU Math.fround DS + BigInt P/P+64 oracle; not real GPU',viewport:[W,H],sampleGrid:[SW,SH],riskLimit,rows,interpretation:'At riskLimit=1e13 the derivative guard catches every observed DS classification error at z6/z8/z10 while sending about 3%/11%/35% of samples to Deep correction. At z12 it sends over 75%, which is a deliberate signal that Full Deep should replace the hybrid.'};
await fs.mkdir(new URL('../audit/',import.meta.url),{recursive:true});await fs.writeFile(new URL('../audit/v24-hybrid-risk-model.json',import.meta.url),JSON.stringify(report,null,2));
console.log(JSON.stringify({status:'pass',check:'hybrid-risk-model',rows},null,2));

View file

@ -0,0 +1,28 @@
import fs from 'node:fs/promises';
import vm from 'node:vm';
const source=await fs.readFile(new URL('../script.js',import.meta.url),'utf8');
const start=source.indexOf('function openHybridDialog()');
const end=source.indexOf('// ── diagnostics',start);
if(start<0||end<0)throw new Error('hybrid UI block not found');
const block=source.slice(start,end);
const el=(extra={})=>Object.assign({textContent:'',value:'',disabled:false,hidden:true,max:0,open:false,attrs:new Map(),setAttribute(k,v){this.attrs.set(k,String(v));if(k==='open')this.open=true},removeAttribute(k){this.attrs.delete(k)},showModal(){this.open=true},close(){this.open=false}},extra);
const elements={
'#hybridDialog':el(), '#hybridStart':el({textContent:'再実行'}), '#hybridBench':el(), '#hybridProgress':el(), '#hybridStatus':el(), '#hybridSave':el(), '#hybridOutput':el(), '#hybridTrack':el({value:'seahorse'}), '#hybridSamples':el({value:'2'}), '#hybridCancel':el()
};
let runCount=0,toastText='';
const ctx={
$:s=>elements[s], hybridJob:{active:false,cancelled:false,last:null}, benchmarkJob:{active:false}, HYBRID_DEPTHS:[6,8,10,12],
cancelRender(){}, state:{dirty:true}, refs:{cancelPending(){}}, markDirty(){}, toast(s){toastText=s}, hybridSummary(){return 'summary'},
async runHybridExperiment({onProgress}){runCount++;onProgress?.({index:1,total:4,z:6,phase:'probe'});return{rows:[1,2,3,4]}},
downloadBlob(){}, Blob:globalThis.Blob, Date, Math, JSON, String, Error
};
vm.createContext(ctx);vm.runInContext(block,ctx,{filename:'hybrid-ui-block.js'});
if(typeof elements['#hybridBench'].onclick!=='function')throw new Error('toolbar click handler not installed');
elements['#hybridBench'].onclick();
if(!elements['#hybridDialog'].open)throw new Error('toolbar click did not open dialog synchronously');
if(runCount!==1)throw new Error('toolbar click did not start experiment synchronously');
if(!/Hybrid実験/.test(toastText))throw new Error('toolbar click gives no visible acknowledgement');
await new Promise(r=>setTimeout(r,0));
if(!/実験完了/.test(elements['#hybridStatus'].textContent))throw new Error('experiment completion not reflected in UI: '+elements['#hybridStatus'].textContent);
if(elements['#hybridBench'].disabled)throw new Error('toolbar stayed disabled');
console.log(JSON.stringify({status:'pass',check:'hybrid-ui-runtime',runCount,dialogOpen:elements['#hybridDialog'].open,statusText:elements['#hybridStatus'].textContent,toastText},null,2));

View file

@ -1 +1,7 @@
import fs from 'node:fs/promises';const h=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');const req=['id="view"','id="processMode"','保守的 (Strict)','id="exportPrecision"','gpu-kernels.js','script.js'];for(const x of req)if(!h.includes(x))throw new Error('missing '+x);if(h.includes('<script src="kernels.js"></script>'))throw new Error('legacy kernels.js loaded');console.log(JSON.stringify({status:'pass',checks:req.length+1},null,2));
import fs from 'node:fs/promises';
const h=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');
const req=['id="view"','id="processMode"','保守的 (Strict)','id="exportPrecision"','id="backendBench"','id="benchmarkDialog"','id="sparseBench"','id="sparseDialog"','mandelbrot-bundle','data-bundle="gpu-kernels"','data-bundle="app"'];
for(const x of req)if(!h.includes(x))throw new Error('missing '+x);
if(/<script\s+src=/.test(h))throw new Error('standalone index still depends on external scripts');
if(h.includes('<script src="kernels.js"></script>'))throw new Error('legacy kernels.js loaded');
console.log(JSON.stringify({status:'pass',checks:req.length+2,singleFile:true},null,2));

View file

@ -3,14 +3,16 @@ const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('scri
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(/const VERSION=24/.test(script),'renderer version is not 24');
must(/navigator\.gpu\.requestAdapter/.test(script),'WebGPU adapter path missing');
must(/guarded rescaled perturbation/.test(script),'guarded deep WebGPU path missing');
must(/guarded perturbation/.test(script)&&/DEEP_CORRECT_WGSL/.test(kernels)&&/correctUnknownFrame/.test(script),'guarded deep + sparse DS correction path missing');
must(/referenceWorkerSource/.test(script)&&/BigInt\(d\.re\)/.test(script),'BigInt reference worker missing');
must(!/function buildBla\(/.test(script)&&!/BlaNode/.test(kernels)&&!/useBla/.test(kernels),'unsafe BLA path is not fully quarantined');
must(!/function buildBla\(/.test(script)&&!/BlaNode/.test(kernels)&&!/useBla/.test(kernels),'unsafe BLA path is not fully removed');
must(/F32_U/.test(kernels)&&/safe_abs_error/.test(kernels)&&/errAbs/.test(kernels),'deep error-bound guard missing');
must(/struct UnresolvedHead/.test(kernels)&&/atomicAdd\(&unresolved\.remaining/.test(kernels),'unresolved counter missing');
must(!/strict_queue|QUEUE_FINALIZE_WGSL/.test(kernels)&&!/dispatchWorkgroupsIndirect|qitems|strict-indirect/.test(script),'redundant strict retry queue remains');
must(/struct UnresolvedHead/.test(kernels)&&/atomicAdd\(&unresolved\.remaining/.test(kernels)&&/REASON_ERROR_BOUND/.test(kernels),'unresolved reason counters missing');
must(/CORRECTION_MARK/.test(kernels)&&/ds_prod/.test(kernels)&&/correct_pixel/.test(kernels),'double-single correction kernel missing');
must(!/strict_queue|QUEUE_FINALIZE_WGSL/.test(kernels)&&!/qitems|strict-indirect/.test(script),'obsolete strict retry queue remains');
must(/texture_storage_2d<rgba8unorm,write>/.test(kernels),'GPU color field path missing');
must(/AA_RESOLVE_WGSL/.test(kernels)&&/renderTileRGBA2x/.test(script),'GPU 2x2 export resolve missing');
must(/correctUnknown=true/.test(script)&&/encodeCorrectionNumeric/.test(script),'export/probe DS correction integration missing');
must(/class StreamingPng/.test(script)&&/CompressionStream\('deflate'\)/.test(script)&&/async abort\(reason\)/.test(script),'streaming PNG export/abort missing');
must(/PRESENT_WGSL/.test(kernels),'GPU reprojection/presentation missing');
must(/recolorPending/.test(script)&&!/const token=\+\+state\.token,stateSnap/.test(script),'recolor race fix missing');
@ -18,15 +20,14 @@ must(/this\.context=null/.test(script)&&/webgpuCanvasClaimed=true;this\.configur
must(/このズーム深度はWebGPUが必要です/.test(script),'deep fallback rejection missing');
must(!/Math\.min\(340/.test(script),'deep exact-coordinate formatter still capped at 340 digits');
must(/uncapturederror/.test(script)&&/pushErrorScope/.test(script),'WebGPU error capture missing');
must(/gpu-kernels\.js/.test(html)&&!/src="kernels\.js"/.test(html),'index is not WebGPU-only');
must(/mandelbrot-bundle/.test(html)&&/data-bundle="gpu-kernels"/.test(html)&&/data-bundle="app"/.test(html)&&!/<script\s+src=/.test(html),'standalone index is not self-contained WebGPU bundle');
must(/numericParams/.test(script)&&/colorParams/.test(script),'persistent frame parameter buffers missing');
must(/ensureExportWorkspace/.test(script)&&/exportWorkspaceDestroy/.test(script)&&/export tile exceeds reusable workspace/.test(script),'reusable export GPU workspace missing');
must(/if\(!adapter\)adapter=await navigator\.gpu\.requestAdapter\(\)/.test(script),'adapter fallback request missing');
must(/gpuInitFailed/.test(script)&&/WebGPU初期化失敗/.test(script),'shader/pipeline failure guard missing');
must(/if\(state\.gpuInitFailed\)return null/.test(script),'failed shader initialization can be retried on every render');
must(/if\(!r\)\{if\(state\.gpuInitFailed\)/.test(script),'shader failure still enters CPU fallback');
must(/return 524288/.test(script)&&/1572864/.test(script),'responsive screen pixel budgets missing');
must(/ensureExportWorkspace/.test(script)&&/unresolveds:Array\.from\(\{length:4\}/.test(script),'reusable corrected export workspace missing');
must(/DIRECT_REQUIRED_RATIO=8/.test(script)&&/DEEP_SPEED_MARGIN=\.85/.test(script)&&/ensureBackendProbe/.test(script),'adaptive direct/deep router missing');
must(/DIRECT_DS_WGSL/.test(kernels)&&/runCrossoverBenchmark/.test(script),'diagnostic DS/crossover benchmark missing');
must(/overlap-view-probe-required/.test(script)&&/qualityPass/.test(script)&&/backendQualityTiles/.test(script)&&/overlap-probe-failed-safe-deep/.test(script),'view-specific quality probe/fail-safe gate missing');
must(!/referenceSnapAtPixel|chooseUnknownCandidate|multi-reference-refine/.test(script),'obsolete multi-reference production path remains');
must(/gpuUnavailable/.test(script)&&/return 262144/.test(script),'lightweight no-adapter CPU fallback budget missing');
for(const banned of ['DEEP_SIMD_B64','DEEP_SCALAR_B64','BLA_SIMD_B64','BLA_SCALAR_B64','deepPool','deepWasm','highPrecisionDirectPixelAsync','renderDeepAdaptive','render_perturb_rebase_rect','WebAssembly.Instance'])must(!script.includes(banned),'legacy production deep dependency remains: '+banned);
must(!/Validated direct/.test(html),'obsolete Validated UI remains');
console.log(JSON.stringify({status:'pass',rendererVersion:24,shaderVersion:'24.1.3',checks:28},null,2));
console.log(JSON.stringify({status:'pass',rendererVersion:24,shaderVersion:'24.2.8-fixed96-sparse-queue',checks:28},null,2));

View file

@ -0,0 +1,45 @@
import fs from 'node:fs/promises';
const corpus=JSON.parse(await fs.readFile(new URL('./scenes.json',import.meta.url),'utf8'));
const sc=corpus.scenes.find(s=>s.id==='swirly-seahorses-z12');
if(!sc)throw new Error('swirly scene missing');
const F=Math.fround,U=2**-24;
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const [m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits),d=10n**BigInt(places);if(neg)n=-n;return[n,d]}
function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),d)}
function bitsForSpan(t){const s=String(t).toLowerCase(),a=s.split('e'),m=Math.abs(Number(a[0])),e=a[1]?Number(a[1]):0,l2=Math.log2(m)+e*Math.log2(10);return Math.max(256,Math.ceil(-l2)+240)}
function snap(scene){const bits=bitsForSpan(scene.span);return{bits,re:decFixed(scene.re,bits),im:decFixed(scene.im,bits),span:decFixed(scene.span,bits)}}
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}
function log2FixedAt(v,b){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-b}
function spanME(s){const l=log2FixedAt(s.span,s.bits),exp=Math.floor(l);return{mant:Math.pow(2,l-exp),exp}}
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
function direct(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
function buildRef(s,limit){const b=s.bits+64,cr=align(s.re,s.bits,b),ci=align(s.im,s.bits,b),bail=16n<<BigInt(b);let zr=0n,zi=0n,refLen=limit;const hi=new Float32Array((limit+1)*2),lo=new Float32Array((limit+1)*2);for(let n=0;n<=limit;n++){const rr=fixedNum(zr,b),ri=fixedNum(zi,b),hr=F(rr),hii=F(ri);hi[n*2]=hr;hi[n*2+1]=hii;lo[n*2]=F(rr-hr);lo[n*2+1]=F(ri-hii);if(n===limit)break;const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;const mag=roundShift(zr*zr,b)+roundShift(zi*zi,b);if(mag>bail){refLen=n+1;const rr1=fixedNum(zr,b),ri1=fixedNum(zi,b),hr1=F(rr1),hi1=F(ri1);hi[2*refLen]=hr1;hi[2*refLen+1]=hi1;lo[2*refLen]=F(rr1-hr1);lo[2*refLen+1]=F(ri1-hi1);break}}return{hi,lo,refLen}}
function cmul(ar,ai,br,bi){return[F(F(ar*br)-F(ai*bi)),F(F(ar*bi)+F(ai*br))]}
function scale(v,e){if(e<-126)return 0;if(e>126)return F(8.507059e37);return F(v*Math.pow(2,e))}
function primary(ref,s,w,h,x,y,limit){const se0=spanME(s),se={mant:F(se0.mant),exp:se0.exp},dx=F((x+.5-.5*w)/w),dy=F((.5*h-y-.5)/w);let dr=F(se.mant*dx),di=F(se.mant*dy),wr=0,wi=0,e=se.exp,n=0,m=0,ops=0,err=U*Math.max(Math.abs(dr),Math.abs(di));while(true){if(n>=limit){const rp=Math.min(m,ref.refLen)*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),ea=Math.abs(err*2**e)+U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);return ea<=1e-3?{kind:'bounded',n}:{kind:'unknown',n}}if(m>ref.refLen)return{kind:'unknown',n};const rp=m*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),mag=F(F(zr*zr)+F(zi*zi)),ea=Math.abs(err*2**e)+U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);if(mag>4){if(Math.hypot(zr,zi)-ea>2)return{kind:'escaped',n};return{kind:'unknown',n}}const dmag=F(F(delr*delr)+F(deli*deli));if(m>0&&dmag>0&&mag<dmag){if(se.exp-e<-96)return{kind:'unknown',n};err=ea;wr=zr;wi=zi;dr=scale(F(se.mant*dx),se.exp);di=scale(F(se.mant*dy),se.exp);e=0;m=0;err+=U*Math.max(Math.abs(dr),Math.abs(di));continue}if(m>=ref.refLen)return{kind:'unknown',n};const rr=ref.hi[rp],ri=ref.hi[rp+1],lr=ref.lo[rp],li=ref.lo[rp+1],refAbs=Math.max(Math.abs(rr),Math.abs(ri))+Math.max(Math.abs(lr),Math.abs(li)),wAbs=Math.max(Math.abs(wr),Math.abs(wi)),dAbs=Math.max(Math.abs(dr),Math.abs(di)),p2=Math.abs(2**e),gain=2*refAbs+2*wAbs*p2,roundErr=U*(2*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1e-30);err=gain*err+roundErr;const[a,b]=cmul(rr,ri,wr,wi),[c,d]=cmul(lr,li,wr,wi),[sqR,sqI]=cmul(wr,wi,wr,wi);wr=F(F(F(2*a)+F(2*c))+F(scale(sqR,e)+dr));wi=F(F(F(2*b)+F(2*d))+F(scale(sqI,e)+di));m++;n++;ops++;if(!Number.isFinite(wr)||!Number.isFinite(wi)||!Number.isFinite(err)||Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di))>=1e30||err>1e35)return{kind:'unknown',n};const mm=Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di));if(mm>65536){wr=F(wr/65536);wi=F(wi/65536);dr=F(dr/65536);di=F(di/65536);err/=65536;e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wr=F(wr*65536);wi=F(wi*65536);dr=F(dr*65536);di=F(di*65536);err*=65536;e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n}}}
function q(a,b){const x=F(a+b),e=F(b-F(x-a));return[x,e]}
function sum(a,b){const x=F(a+b),bb=F(x-a),e=F(F(a-F(x-bb))+F(b-bb));return[x,e]}
function prod(a,b){const x=F(a*b),ca=F(4097*a),ah=F(ca-F(ca-a)),al=F(a-ah),cb=F(4097*b),bh=F(cb-F(cb-b)),bl=F(b-bh);let e=F(F(ah*bh)-x);e=F(e+F(ah*bl));e=F(e+F(al*bh));e=F(e+F(al*bl));return[x,e]}
function add(a,b){const t=sum(a[0],b[0]);return q(t[0],F(t[1]+F(a[1]+b[1])))}
function neg(a){return[F(-a[0]),F(-a[1])]}
function sub(a,b){return add(a,neg(b))}
function mul(a,b){const t=prod(a[0],b[0]);let e=F(t[1]+F(a[0]*b[1]));e=F(e+F(a[1]*b[0]));e=F(e+F(a[1]*b[1]));return q(t[0],e)}
function scaleDS(a,b){const t=prod(a[0],b);return q(t[0],F(t[1]+F(a[1]*b)))}
function pow2DS(a,e){if(e<-126)return[0,0];if(e>126)return[F(8.507059e37),0];return[F(a[0]*2**e),F(a[1]*2**e)]}
function cmp(a,b){if(a[0]<b[0])return-1;if(a[0]>b[0])return 1;if(a[1]<b[1])return-1;if(a[1]>b[1])return 1;return 0}
function cadd(a,b){return[add(a[0],b[0]),add(a[1],b[1])]}
function cmulDS(a,b){return[sub(mul(a[0],b[0]),mul(a[1],b[1])),add(mul(a[0],b[1]),mul(a[1],b[0]))]}
function cscale(a,b){return[scaleDS(a[0],b),scaleDS(a[1],b)]}
function cpow2(a,e){return[pow2DS(a[0],e),pow2DS(a[1],e)]}
function mag2(a){return add(mul(a[0],a[0]),mul(a[1],a[1]))}
function val(a){return F(a[0]+a[1])}
function maxabsDS(a){return Math.max(Math.abs(val(a[0])),Math.abs(val(a[1])))}
function correction(ref,s,w,h,x,y,limit){const se=spanME(s),mh=F(se.mant),sm=[mh,F(se.mant-mh)],iw=1/w,iwh=F(iw),inv=[iwh,F(iw-iwh)],ox=F(x+.5-.5*w),oy=F(.5*h-y-.5),dx=scaleDS(inv,ox),dy=scaleDS(inv,oy),d0=[mul(sm,dx),mul(sm,dy)];let d=d0,wv=[[0,0],[0,0]],e=se.exp,n=0,m=0,ops=0;while(true){if(n>=limit)return{kind:'bounded',n};if(m>ref.refLen)return{kind:'unknown',n};const rp=m*2,r=[[ref.hi[rp],ref.lo[rp]],[ref.hi[rp+1],ref.lo[rp+1]]],delta=cpow2(wv,e),z=cadd(r,delta),mg=mag2(z);if(cmp(mg,[4,0])>0)return{kind:'escaped',n};const dm=mag2(delta);if(m>0&&cmp(dm,[0,0])>0&&cmp(mg,dm)<0){if(se.exp-e<-96)return{kind:'unknown',n};wv=z;d=cpow2(d0,se.exp);e=0;m=0;continue}if(m>=ref.refLen)return{kind:'unknown',n};const linear=cscale(cmulDS(r,wv),2),sq=cpow2(cmulDS(wv,wv),e);wv=cadd(cadd(linear,sq),d);m++;n++;ops++;const mm=Math.max(maxabsDS(wv),maxabsDS(d));if(mm>=1e30||!Number.isFinite(mm))return{kind:'unknown',n};if(mm>65536){wv=cscale(wv,1/65536);d=cscale(d,1/65536);e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wv=cscale(wv,65536);d=cscale(d,65536);e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n}}}
const s=snap(sc),w=61,h=39,limit=2000,ref=buildRef(s,limit);let guardMismatch=0,primaryUnknown=0,primaryFalseEscaped=0,primaryFalseBounded=0,corrected=0,remaining=0,correctionFalseEscaped=0,correctionFalseBounded=0,countMismatch=0,maxCountDiff=0;
for(let y=0;y<h;y++)for(let x=0;x<w;x++){const p=pixel(s,w,h,x,y),g=pixel(s,w,h,x,y,s.bits+64),oracle=direct(p[0],p[1],s.bits,limit),guard=direct(g[0],g[1],s.bits+64,limit);if(oracle!==guard){guardMismatch++;continue}const a=primary(ref,s,w,h,x,y,limit);if(a.kind==='escaped'&&oracle===limit)primaryFalseEscaped++;if(a.kind==='bounded'&&oracle<limit)primaryFalseBounded++;if(a.kind!=='unknown')continue;primaryUnknown++;const c=correction(ref,s,w,h,x,y,limit);if(c.kind==='unknown'){remaining++;continue}corrected++;if(c.kind==='escaped'&&oracle===limit)correctionFalseEscaped++;if(c.kind==='bounded'&&oracle<limit)correctionFalseBounded++;if(c.kind==='escaped'&&oracle<limit&&c.n!==oracle){countMismatch++;maxCountDiff=Math.max(maxCountDiff,Math.abs(c.n-oracle))}}
const report={status:'pass',kind:'sparse-double-single-correction-cpu-model-not-real-gpu',scene:sc.id,grid:[w,h],limit,referenceLength:ref.refLen,guardMismatch,primary:{unknown:primaryUnknown,falseEscaped:primaryFalseEscaped,falseBounded:primaryFalseBounded},correction:{corrected,remaining,falseEscaped:correctionFalseEscaped,falseBounded:correctionFalseBounded,escapeIterationMismatch:countMismatch,maxEscapeIterationDifference:maxCountDiff},note:'DS correction is a visual-quality recovery pass; escape iteration equality is not certified.'};
if(guardMismatch||primaryFalseEscaped||primaryFalseBounded||correctionFalseEscaped||correctionFalseBounded||remaining)throw new Error(JSON.stringify(report));
console.log(JSON.stringify(report,null,2));

View file

@ -0,0 +1,10 @@
import fs from 'node:fs/promises';
const root=new URL('../',import.meta.url),h=await fs.readFile(new URL('index.html',root),'utf8'),dev=await fs.readFile(new URL('index.external.html',root),'utf8'),script=await fs.readFile(new URL('script.js',root),'utf8');
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
must(!/<script\s+src=/.test(h),'root standalone HTML has external script dependency');
must(/data-bundle="gpu-kernels"/.test(h)&&/data-bundle="app"/.test(h),'inline bundle markers missing');
must(/id="sparseBench"/.test(h)&&/id="sparseDialog"/.test(h),'Fixed96 UI missing in standalone');
must(/\$\('#sparseBench'\)\.onclick/.test(h)&&/runFixed96Experiment/.test(h),'Fixed96 button handler not embedded in standalone');
must(/<script src="gpu-kernels\.js"><\/script>/.test(dev)&&/<script src="script\.js"><\/script>/.test(dev),'hosted/dev template no longer has explicit source scripts');
must(/\$\('#sparseBench'\)\.onclick/.test(script),'source button handler missing');
console.log(JSON.stringify({status:'pass',check:'single-file-ui-contract',checks:6,reason:'Opening HTML from an archive temp extraction does not require sibling JS files.'},null,2));

View file

@ -0,0 +1,13 @@
import fs from 'node:fs/promises';
import crypto from 'node:crypto';
const root=new URL('../',import.meta.url);
const html=await fs.readFile(new URL('index.html',root),'utf8');
const script=await fs.readFile(new URL('script.js',root));
const kernels=await fs.readFile(new URL('gpu-kernels.js',root));
const vs=await fs.readFile(new URL('script-v24.2.6.js',root));
const vk=await fs.readFile(new URL('gpu-kernels-v24.2.6.js',root));
const h=b=>crypto.createHash('sha256').update(b).digest('hex');
if(!html.includes('script-v24.2.6.js')||!html.includes('gpu-kernels-v24.2.6.js'))throw new Error('versioned browser assets not referenced');
if(html.includes('<script src="script.js"')||html.includes('<script src="gpu-kernels.js"'))throw new Error('bare cacheable browser assets still referenced');
if(h(script)!==h(vs)||h(kernels)!==h(vk))throw new Error('versioned browser assets differ from canonical source');
console.log(JSON.stringify({status:'pass',check:'ui-cacheproof-assets',scriptSha256:h(script),kernelSha256:h(kernels)},null,2));

View file

@ -21,15 +21,15 @@ async function checkScene(api,id,re,im,span,iter,mode){
const r=await api.setView({re,im,span,baseIter:iter,adaptive:false,processMode:mode}),diag=r.diag;
if(diag.backend!=='webgpu'||diag.webgpuError)throw new Error(id+'/'+mode+': WebGPU backend unavailable/error: '+(diag.webgpuError||diag.backend));
const st=api.state(),ps=points(st.width,st.height),meta=await api.sampleMeta(ps);let guardMismatch=0,falseEscaped=0,falseBounded=0,escapeIterationMismatch=0,unknown=0;
for(let i=0;i<ps.length;i++){const [x,y]=ps[i],p=pixel(st,x,y),pg=pixel(st,x,y,st.bits+64),a=orbit(p[0],p[1],st.bits,st.iter),g=orbit(pg[0],pg[1],st.bits+64,st.iter);if(a!==g){guardMismatch++;continue}const m=meta[i],cls=(m>>>28)&3,n=m&0x0fffffff;if(cls===0){unknown++;continue}if(cls===1&&a===st.iter)falseEscaped++;if(cls!==1&&a<st.iter)falseBounded++;if(cls===1&&a<st.iter&&n!==a)escapeIterationMismatch++}
for(let i=0;i<ps.length;i++){const [x,y]=ps[i],p=pixel(st,x,y),pg=pixel(st,x,y,st.bits+64),a=orbit(p[0],p[1],st.bits,st.iter),g=orbit(pg[0],pg[1],st.bits+64,st.iter);if(a!==g){guardMismatch++;continue}const m=meta[i],cls=(m>>>28)&3,n=m&0x000fffff;if(cls===0){unknown++;continue}if(cls===1&&a===st.iter)falseEscaped++;if(cls!==1&&a<st.iter)falseBounded++;if(cls===1&&a<st.iter&&n!==a)escapeIterationMismatch++}
const row={id,mode,width:st.width,height:st.height,iter:st.iter,deep:diag.deep,guardMismatch,falseEscaped,falseBounded,escapeIterationMismatch,unknown,known:ps.length-unknown,reference:diag.reference};
if(guardMismatch||falseEscaped||falseBounded)throw new Error(id+'/'+mode+' numeric gate failed: '+JSON.stringify(row));
if(id==='swirly-seahorses-z12'){if(row.known<12)throw new Error(id+'/'+mode+': excessive UNKNOWN rate: '+JSON.stringify(row));}
else if(row.unknown!==0)throw new Error(id+'/'+mode+': stable corpus scene produced UNKNOWN samples: '+JSON.stringify(row));
if(row.unknown!==0)throw new Error(id+'/'+mode+': corrected production frame produced UNKNOWN samples: '+JSON.stringify(row));
return row;
}
async function run(){await waitApp();const api=frame.contentWindow.__MANDEL_TEST__,report=[];for(const [id,re,im,span,iter] of scenes){for(const mode of (id==='z0'?['standard']:['standard','validate'])){out.textContent='running '+id+' / '+mode+'…\n'+JSON.stringify(report,null,2);report.push(await checkScene(api,id,re,im,span,iter,mode));}}
await api.setView({re:'-0.7453983606667815',im:'0.1125046349959942',span:'3.52e-12',baseIter:2000,adaptive:false,processMode:'validate'});const denseState={...api.state(),width:61,height:39},denseMeta=await api.probeMeta({w:61,h:39,strict:true}),densePoints=[[25,12],[26,15],[27,25]],denseRegression=[];for(const[x,y]of densePoints){const p=pixel(denseState,x,y),pg=pixel(denseState,x,y,denseState.bits+64),a=orbit(p[0],p[1],denseState.bits,denseState.iter),g=orbit(pg[0],pg[1],denseState.bits+64,denseState.iter);if(a!==g)throw new Error('dense guard mismatch '+x+','+y);const m=denseMeta[y*61+x],cls=(m>>>28)&3,n=m&0x0fffffff;if(cls===1&&a===denseState.iter)throw new Error('dense false escape '+JSON.stringify({x,y,a,cls,n}));if(cls!==0&&cls!==1&&a<denseState.iter)throw new Error('dense false bounded '+JSON.stringify({x,y,a,cls,n}));denseRegression.push({x,y,oracle:a,cls,n})}
await api.setView({re:'-0.75',im:'0',span:'3.4e-20',baseIter:1000,adaptive:false,processMode:'validate'});const exportSmoke=await api.smokeExportTile({w:48,h:32,strict:true,ss:1}),exportSmokeAA=await api.smokeExportTile({w:48,h:32,strict:true,ss:2});if(exportSmoke.length!==exportSmoke.expected||exportSmokeAA.length!==exportSmokeAA.expected)throw new Error('GPU export tile readback length mismatch');if(exportSmoke.unresolved!==0||exportSmokeAA.unresolved!==0)throw new Error('GPU export smoke left unresolved samples: '+JSON.stringify({exportSmoke,exportSmokeAA}));const diag=frame.contentWindow.__MANDEL_DIAG__.snapshot();if(diag.uncapturedErrors&&diag.uncapturedErrors.length)throw new Error('uncaptured WebGPU errors: '+JSON.stringify(diag.uncapturedErrors));const result={status:'pass',kind:'real-webgpu-acceptance',date:new Date().toISOString(),shaderVersion:diag.shaderVersion,adapter:diag.adapter,limits:diag.limits,exportSmoke,exportSmokeAA,denseRegression,report};out.textContent=JSON.stringify(result,null,2);document.title='PASS v24.1 WebGPU acceptance';globalThis.__WEBGPU_ACCEPTANCE__=result}
run().catch(e=>{const result={status:'fail',error:String(e&&e.stack||e)};out.textContent=JSON.stringify(result,null,2);document.title='FAIL v24.1 WebGPU acceptance';globalThis.__WEBGPU_ACCEPTANCE__=result});
await api.setView({re:'-0.7453983606667815',im:'0.1125046349959942',span:'3.52e-12',baseIter:2000,adaptive:false,processMode:'validate'});const denseState={...api.state(),width:61,height:39},denseMeta=await api.probeMeta({w:61,h:39,strict:true,forceDeep:true,correctUnknown:true}),densePoints=[[25,12],[26,15],[27,25]],denseRegression=[];for(const[x,y]of densePoints){const p=pixel(denseState,x,y),pg=pixel(denseState,x,y,denseState.bits+64),a=orbit(p[0],p[1],denseState.bits,denseState.iter),g=orbit(pg[0],pg[1],denseState.bits+64,denseState.iter);if(a!==g)throw new Error('dense guard mismatch '+x+','+y);const m=denseMeta[y*61+x],cls=(m>>>28)&3,n=m&0x000fffff;if(cls===0)throw new Error('dense DS correction left known regression pixel UNKNOWN '+JSON.stringify({x,y,a,cls,n}));if(cls===1&&a===denseState.iter)throw new Error('dense false escape '+JSON.stringify({x,y,a,cls,n}));if(cls!==1&&a<denseState.iter)throw new Error('dense false bounded '+JSON.stringify({x,y,a,cls,n}));denseRegression.push({x,y,oracle:a,cls,n})}
await api.setView({re:'0.4',im:'0',span:'0.0015',baseIter:500,adaptive:false,processMode:'standard'});const routerDiag=frame.contentWindow.__MANDEL_DIAG__.snapshot(),routerDecision=routerDiag.backendDecision;if(!routerDecision||routerDecision.probe||!routerDecision.viewProbe)throw new Error('overlap view-specific backend probe did not settle: '+JSON.stringify(routerDecision));const vp=routerDecision.viewProbe;if(!vp.qualityPass||vp.classDisagreement!==0||vp.deepUnknownRate>.005)throw new Error('overlap backend quality probe failed: '+JSON.stringify(vp));const deepShouldWin=vp.predictedDeepMs<vp.predictedDirectMs*.85;if((routerDecision.backend==='deep')!==deepShouldWin)throw new Error('backend router ignored measured 15% speed margin: '+JSON.stringify(routerDecision));
await api.setView({re:'-0.75',im:'0',span:'3.4e-20',baseIter:1000,adaptive:false,processMode:'validate'});const exportSmoke=await api.smokeExportTile({w:48,h:32,strict:true,ss:1}),exportSmokeAA=await api.smokeExportTile({w:48,h:32,strict:true,ss:2});if(exportSmoke.length!==exportSmoke.expected||exportSmokeAA.length!==exportSmokeAA.expected)throw new Error('GPU export tile readback length mismatch');if(exportSmoke.unresolved!==0||exportSmokeAA.unresolved!==0)throw new Error('GPU export smoke left unresolved samples: '+JSON.stringify({exportSmoke,exportSmokeAA}));const fixed96Smoke=await api.runFixed96Experiment({track:'seahorse',depths:[8],riskLimits:[1e13],warmups:0,samples:1,qualityW:32,qualityH:24}),sr=fixed96Smoke.rows[0],sv=sr&&sr.variants&&sr.variants[0];if(!sv||!Number.isFinite(sv.fullMedianMs)||sv.remaining!==0||sv.queueIntegrity!==true||Math.abs((sv.dispatchRate||0)-(sv.uncertainRate||0))>1e-9)throw new Error('fixed96 experiment smoke failed: '+JSON.stringify(fixed96Smoke));const diag=frame.contentWindow.__MANDEL_DIAG__.snapshot();if(diag.uncapturedErrors&&diag.uncapturedErrors.length)throw new Error('uncaptured WebGPU errors: '+JSON.stringify(diag.uncapturedErrors));const result={status:'pass',kind:'real-webgpu-acceptance',date:new Date().toISOString(),shaderVersion:diag.shaderVersion,adapter:diag.adapter,limits:diag.limits,routerDecision,fixed96Smoke,exportSmoke,exportSmokeAA,denseRegression,report};out.textContent=JSON.stringify(result,null,2);document.title='PASS v24.2 WebGPU acceptance';globalThis.__WEBGPU_ACCEPTANCE__=result}
run().catch(e=>{const result={status:'fail',error:String(e&&e.stack||e)};out.textContent=JSON.stringify(result,null,2);document.title='FAIL v24.2.8 WebGPU acceptance';globalThis.__WEBGPU_ACCEPTANCE__=result});
})();