This commit is contained in:
33333-33333 2026-08-23 17:12:29 +09:00
commit de177e8896
58 changed files with 3904 additions and 2847 deletions

View file

@ -1,27 +1,323 @@
<!doctype html>
<meta charset="utf-8">
<title>Mandelbrot v23 browser benchmark</title>
<style>body{font:14px system-ui;margin:20px;background:#111827;color:#eef2ff}button{padding:8px 14px}iframe{position:fixed;left:-20000px;top:0;border:0}pre{white-space:pre-wrap}</style>
<h1>v23 browser benchmark</h1>
<p>HTTP(S)でこのworkspaceを配信して実行します。iframeの実寸と実DPRを結果へ記録します。</p>
<button id="run">Run corpus</button>
<title>Mandelbrot v23 browser acceptance runner</title>
<style>
body{font:14px system-ui;margin:20px;background:#111827;color:#eef2ff}
button{padding:8px 14px}
iframe{position:fixed;left:-20000px;top:0;border:0}
pre{white-space:pre-wrap}
</style>
<h1>v23 browser acceptance runner</h1>
<p>
HTTP(S)で実行します。1回のrunは1 profile・1 buildだけを検査します。
例: <code>?profile=desktop&amp;target=hosted</code>
iframe寸法ではDPRを模擬できないため、要求DPRを持つbrowser contextごとに実行してください。
</p>
<button id="run">Run selected profile</button>
<pre id="output">Ready.</pre>
<iframe id="app" width="1440" height="900"></iframe>
<script type="module">
const out=document.querySelector('#output'),frame=document.querySelector('#app');
const wait=ms=>new Promise(r=>setTimeout(r,ms));
function fixed(decimal,bits){let s=String(decimal).toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);let[m,e='0']=s.split('e'),[i,f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-Number(e);if(places<0){digits+='0'.repeat(-places);places=0}let v=BigInt(digits)*(1n<<BigInt(bits))/(10n**BigInt(places));return neg?-v:v}
function bitsFor(span){const n=Math.abs(Number(span));return Number.isFinite(n)&&n>0?Math.max(256,Math.ceil(-Math.log2(n))+256):1280}
function hash(scene){const bits=bitsFor(scene.span),p=new URLSearchParams({v:'23',b:String(bits),re:String(fixed(scene.re,bits)),im:String(fixed(scene.im,bits)),sp:String(fixed(scene.span,bits)),pal:'0',cy:'.008',sh:'.18',it:'350',ad:'1'});return'#'+p}
async function poll(fn,timeout,label){const started=performance.now();while(performance.now()-started<timeout){let value;try{value=fn()}catch{}if(value)return{value,ms:performance.now()-started};await wait(25)}throw new Error('timeout: '+label)}
const p95=values=>values.slice().sort((a,b)=>a-b)[Math.min(values.length-1,Math.floor(values.length*.95))]||0;
async function canvasHash(win){const blob=await new Promise(resolve=>win.document.querySelector('#view').toBlob(resolve,'image/png'));const digest=await crypto.subtle.digest('SHA-256',await blob.arrayBuffer());return[...new Uint8Array(digest)].map(x=>x.toString(16).padStart(2,'0')).join('')}
function accessibilityAudit(win){const doc=win.document,failures=[],interactive=[...doc.querySelectorAll('button,select,input,summary')];for(const el of interactive){const target=el.type==='checkbox'?el.closest('label')||el:el,r=target.getBoundingClientRect();if(r.width<44||r.height<44)failures.push('target:'+el.id);if(el.matches('input:not([type=checkbox]),select')&&el.id&&!doc.querySelector(`label[for="${el.id}"]`))failures.push('label:'+el.id)}if(!doc.querySelector('#view[tabindex][aria-label]'))failures.push('canvas-keyboard-name');if(!doc.querySelector('[aria-live]'))failures.push('live-status');if(/user-scalable\s*=\s*no/i.test(doc.querySelector('meta[name=viewport]')?.content||''))failures.push('page-zoom-disabled');const css=[...doc.querySelectorAll('style')].map(x=>x.textContent).join('\n');if(!css.includes('prefers-reduced-motion'))failures.push('reduced-motion');if(!css.includes('prefers-reduced-transparency'))failures.push('reduced-transparency');return{pass:failures.length===0,failures,interactiveCount:interactive.length}}
async function interactionAudit(win){const canvas=win.document.querySelector('#view'),before=win.__MANDEL_DIAG__.snapshot(),durations=[];for(let i=0;i<16;i++){const t=performance.now();canvas.dispatchEvent(new win.WheelEvent('wheel',{deltaY:0,clientX:canvas.clientWidth/2,clientY:canvas.clientHeight/2,cancelable:true}));durations.push(performance.now()-t)}const during=win.__MANDEL_DIAG__.snapshot();await poll(()=>{const d=win.__MANDEL_DIAG__.snapshot();return d&&!d.rendering&&!d.scheduler.wheelActive&&d.lastPass==='preview'?d:null},120000,'wheel settle preview');await poll(()=>{const d=win.__MANDEL_DIAG__.snapshot();return d&&!d.rendering&&d.coverage>=.999&&['COVERED','RESOLVING','REFINING','REFINED'].includes(d.drawState)?d:null},180000,'wheel settle covered');return{dispatchP95Ms:p95(durations),renderStartsDuringGesture:during.runtimeMetrics.renderStartsDuringGesture-before.runtimeMetrics.renderStartsDuringGesture,renderStartsBeforeSettle:during.runtimeMetrics.renderStarts-before.runtimeMetrics.renderStarts}}
async function modeAudit(win){const select=win.document.querySelector('#processMode'),hq=win.document.querySelector('#hq');select.value='power';select.dispatchEvent(new win.Event('change',{bubbles:true}));await poll(()=>{const d=win.__MANDEL_DIAG__.snapshot();return d.automaticTarget==='PREVIEW'&&!d.rendering&&d.lastPass==='preview'?d:null},120000,'power preview');const powerBefore=win.__MANDEL_DIAG__.snapshot();await wait(900);const power=win.__MANDEL_DIAG__.snapshot(),powerPass=power.automaticTarget==='PREVIEW'&&power.lastPass==='preview'&&!power.rendering&&!power.scheduler.timer&&!power.detailActive&&!hq.checked&&power.screen.pixelBudget<=1048576&&power.runtimeMetrics.renderStarts===powerBefore.runtimeMetrics.renderStarts;select.value='standard';select.dispatchEvent(new win.Event('change',{bubbles:true}));await poll(()=>{const d=win.__MANDEL_DIAG__.snapshot();return d.automaticTarget==='COVERED'&&!d.rendering&&d.lastPass==='covered'&&d.coverage>=.999?d:null},180000,'standard covered');await wait(900);const standard=win.__MANDEL_DIAG__.snapshot(),standardPass=standard.automaticTarget==='COVERED'&&standard.lastPass==='covered'&&!standard.detailActive&&!standard.scheduler.unknownTimer&&!hq.checked&&standard.screen.pixelBudget<=4194304;return{pass:powerPass&&standardPass,power:{pass:powerPass,target:power.automaticTarget,pixelBudget:power.screen.pixelBudget,renderStartsAfterStable:power.runtimeMetrics.renderStarts-powerBefore.runtimeMetrics.renderStarts},standard:{pass:standardPass,target:standard.automaticTarget,pixelBudget:standard.screen.pixelBudget,detailActive:standard.detailActive,unknownTimer:standard.scheduler.unknownTimer}}}
async function recolorAudit(win){const select=win.document.querySelector('#palette'),before=win.__MANDEL_DIAG__.snapshot();select.value=before.palette===1?'2':'1';select.dispatchEvent(new win.Event('change',{bubbles:true}));await new Promise(resolve=>win.requestAnimationFrame(()=>win.requestAnimationFrame(resolve)));const after=win.__MANDEL_DIAG__.snapshot();return{pass:after.palette!==before.palette&&after.runtimeMetrics.renderStarts===before.runtimeMetrics.renderStarts,renderStarts:after.runtimeMetrics.renderStarts-before.runtimeMetrics.renderStarts}}
async function exportAudit(win){const doc=win.document,captured=[],nativeCreate=win.URL.createObjectURL.bind(win.URL),nativeClick=win.HTMLAnchorElement.prototype.click;win.URL.createObjectURL=blob=>{const url=nativeCreate(blob);captured.push({url,blob});return url};win.HTMLAnchorElement.prototype.click=function(){};try{doc.querySelector('#exportScale').value='0';doc.querySelector('#exportWidth').value='64';doc.querySelector('#exportAA').value='1';doc.querySelector('#exportPrecision').value='balanced';doc.querySelector('#exportStart').click();await poll(()=>!win.__MANDEL_DIAG__.snapshot().exporting&&captured.length>=2,120000,'small export');const png=captured.find(x=>x.blob.type==='image/png')?.blob,json=captured.find(x=>x.blob.type==='application/json')?.blob;if(!png||!json)throw new Error('export files missing');const bytes=new Uint8Array(await png.arrayBuffer()),width=(bytes[16]<<24)|(bytes[17]<<16)|(bytes[18]<<8)|bytes[19],height=(bytes[20]<<24)|(bytes[21]<<16)|(bytes[22]<<8)|bytes[23],meta=JSON.parse(await json.text()),completed={width,height,metadataComplete:meta.determinism?.allTilesCompleted===true,sampleCount:meta.sampleCount,kernelHashes:!!meta.kernelSha256};const prior=captured.length;doc.querySelector('#exportWidth').value='256';doc.querySelector('#exportPrecision').value='validated';doc.querySelector('#exportStart').click();doc.querySelector('#exportCancel').click();await poll(()=>!win.__MANDEL_DIAG__.snapshot().exporting,120000,'export cancel');return{pass:width===64&&height===Math.round(64*win.document.querySelector('#view').height/win.document.querySelector('#view').width)&&captured.length===prior,completed,cancelledWithoutDownload:captured.length===prior}}finally{win.URL.createObjectURL=nativeCreate;win.HTMLAnchorElement.prototype.click=nativeClick}}
async function runScene(scene,viewport){frame.width=viewport.cssWidth;frame.height=viewport.cssHeight;const loaded=new Promise((resolve,reject)=>{frame.onload=resolve;frame.onerror=reject});const started=performance.now();frame.src='../index.html'+hash(scene);await loaded;const win=frame.contentWindow;const label=viewport.id+'/'+scene.id;const preview=await poll(()=>{const d=win.__MANDEL_DIAG__?.snapshot();return d&&d.drawState==='PREVIEW'&&!d.rendering&&d.lastPass==='preview'?d:null},120000,'preview '+label);const previewSnapshot=preview.value,covered=await poll(()=>{const d=win.__MANDEL_DIAG__?.snapshot();return d&&['COVERED','RESOLVING','REFINING','REFINED','VALIDATING','VALIDATED','VALIDATION_INCOMPLETE'].includes(d.drawState)&&d.coverage>=.999?d:null},180000,'covered '+label);const refined=await poll(()=>{const d=win.__MANDEL_DIAG__?.snapshot();return d&&!d.detailActive&&!d.rendering&&!d.validating&&['COVERED','REFINED','VALIDATED','VALIDATION_INCOMPLETE'].includes(d.drawState)?d:null},240000,'refined '+label);const exercise=viewport.id==='desktop'&&scene.id==='z0',interaction=exercise?await interactionAudit(win):null,modes=exercise?await modeAudit(win):null,recolor=exercise?await recolorAudit(win):null,exportResult=exercise?await exportAudit(win):null,accessibility=exercise?accessibilityAudit(win):null,visualSha256=await canvasHash(win),resources=win.performance.getEntriesByType('resource').map(e=>String(e.name)),assetRequests={deep:resources.filter(x=>/\/(deep|bla|color)-/.test(x)),allWasm:resources.filter(x=>/\.wasm(?:$|\?)/.test(x))};await wait(2000);const before=win.__MANDEL_DIAG__.snapshot();await wait(1000);const after=win.__MANDEL_DIAG__.snapshot(),idle={raf:after.scheduler.raf,timer:after.scheduler.timer,pointerSettle:after.scheduler.pointerSettle,unknownTimer:after.scheduler.unknownTimer,backgroundJobs:after.scheduler.backgroundJobs,canvasWrites:after.runtimeMetrics.canvasWrites-before.runtimeMetrics.canvasWrites,domWrites:after.runtimeMetrics.domWrites-before.runtimeMetrics.domWrites,renderStarts:after.runtimeMetrics.renderStarts-before.runtimeMetrics.renderStarts};return{id:scene.id,profile:viewport.id,requestedViewport:viewport,actualViewport:{cssWidth:win.innerWidth,cssHeight:win.innerHeight},actualDevicePixelRatio:win.devicePixelRatio,totalMs:performance.now()-started,previewMs:preview.ms,coveredMs:covered.ms,refinedMs:refined.ms,preview:previewSnapshot,final:after,idle,visualSha256,assetRequests,interaction,modes,recolor,export:exportResult,accessibility}}
function evaluate(results){const exercise=results.find(x=>x.interaction),checks={idle:results.every(x=>!x.idle.raf&&!x.idle.timer&&!x.idle.pointerSettle&&!x.idle.unknownTimer&&x.idle.backgroundJobs===0&&x.idle.canvasWrites===0&&x.idle.domWrites===0&&x.idle.renderStarts===0),covered:results.every(x=>x.final.coverage>=.999&&x.final.hqTarget===x.final.frame),memory:results.every(x=>x.final.memory.managedBytes<=x.final.memory.budget),visual:results.every(x=>/^[0-9a-f]{64}$/.test(x.visualSha256)),startup:results.filter(x=>x.id==='z0').every(x=>x.assetRequests.deep.length===0),interaction:!!exercise&&exercise.interaction.dispatchP95Ms<16&&exercise.interaction.renderStartsDuringGesture===0&&exercise.interaction.renderStartsBeforeSettle===0,modes:exercise?.modes?.pass===true,recolor:exercise?.recolor?.pass===true,export:exercise?.export?.pass===true,accessibility:exercise?.accessibility?.pass===true,profileFidelity:results.every(x=>x.actualViewport.cssWidth===x.requestedViewport.cssWidth&&x.actualViewport.cssHeight===x.requestedViewport.cssHeight&&Math.abs(x.actualDevicePixelRatio-x.requestedViewport.dpr)<.01),previewBudget:results.every(x=>x.preview.lastRender<120)};const failures=Object.entries(checks).filter(([,pass])=>!pass).map(([name])=>name);return{pass:failures.length===0,checks,failures}}
document.querySelector('#run').onclick=async()=>{document.querySelector('#run').disabled=true;try{const corpus=await(await fetch('./scenes.json',{cache:'no-store'})).json(),results=[];for(const viewport of corpus.viewports)for(const scene of corpus.scenes){out.textContent='Running '+viewport.id+'/'+scene.id+'…\n'+JSON.stringify(results,null,2);results.push(await runScene(scene,viewport))}const report={format:'mandelbrot-browser-baseline-v23',generatedUtc:new Date().toISOString(),userAgent:navigator.userAgent,hostDevicePixelRatio:devicePixelRatio,profiles:corpus.viewports,results,acceptance:evaluate(results)};out.textContent=JSON.stringify(report,null,2);globalThis.__BENCHMARK_RESULT__=report}catch(error){out.textContent=String(error?.stack||error)}finally{document.querySelector('#run').disabled=false}};
const out=document.querySelector('#output');
const frame=document.querySelector('#app');
const query=new URLSearchParams(location.search);
const selectedProfile=query.get('profile')||'desktop';
const target=query.get('target')||'hosted';
const previewTrials=Math.max(30,Number(query.get('trials'))||30);
if(!['hosted','standalone'].includes(target))throw new Error('target must be hosted or standalone');
const appPath=target==='hosted'?'../dist/hosted/index.html':'../index.html';
const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms));
const nextFrame=win=>new Promise(resolve=>win.requestAnimationFrame(resolve));
function fixed(decimal,bits){
let s=String(decimal).toLowerCase(),neg=s.startsWith('-');
if(neg)s=s.slice(1);
let pair=s.split('e'),mantissa=pair[0],exponent=Number(pair[1]||0),parts=mantissa.split('.');
let digits=((parts[0]||'0')+(parts[1]||'')).replace(/^0+(?=\d)/,'')||'0';
let places=(parts[1]||'').length-exponent;
if(places<0){digits+='0'.repeat(-places);places=0}
let value=BigInt(digits)*(1n<<BigInt(bits))/(10n**BigInt(places));
return neg?-value:value
}
function bitsFor(span){
const n=Math.abs(Number(span));
return Number.isFinite(n)&&n>0?Math.max(256,Math.ceil(-Math.log2(n))+256):1280
}
function hash(scene){
const bits=bitsFor(scene.span);
const p=new URLSearchParams({
v:'23',b:String(bits),re:String(fixed(scene.re,bits)),im:String(fixed(scene.im,bits)),
sp:String(fixed(scene.span,bits)),pal:'0',cy:'.008',sh:'.18',it:'350',ad:'1'
});
return '#'+p
}
async function poll(fn,timeout,label){
const started=performance.now();
while(performance.now()-started<timeout){
let value;
try{value=fn()}catch{}
if(value)return{value,ms:performance.now()-started};
await wait(25)
}
throw new Error('timeout: '+label)
}
const p95=values=>values.slice().sort((a,b)=>a-b)[Math.min(values.length-1,Math.ceil(values.length*.95)-1)]||0;
async function sha256(bytes){
const digest=await crypto.subtle.digest('SHA-256',bytes);
return[...new Uint8Array(digest)].map(x=>x.toString(16).padStart(2,'0')).join('')
}
async function canvasAudit(win,scene,profile){
const canvas=win.document.querySelector('#view');
const context=canvas.getContext('2d',{willReadFrequently:true});
const pixels=context.getImageData(0,0,canvas.width,canvas.height).data;
const hashValue=await sha256(pixels.buffer);
let min=255,max=0;
for(let i=0;i<pixels.length;i+=4){min=Math.min(min,pixels[i],pixels[i+1],pixels[i+2]);max=Math.max(max,pixels[i],pixels[i+1],pixels[i+2])}
const expected=scene.visualGoldens?.[target]?.[profile.id]||null;
return{hash:hashValue,expected,nonBlank:max>min,pass:!!expected&&hashValue===expected&&max>min}
}
async function accessibilityAudit(win){
const doc=win.document,failures=[],interactive=[...doc.querySelectorAll('button,select,input,summary')];
for(const el of interactive){
const targetEl=el.type==='checkbox'?el.closest('label')||el:el,r=targetEl.getBoundingClientRect();
if(r.width<44||r.height<44)failures.push('target:'+el.id);
if(el.matches('input:not([type=checkbox]),select')&&el.id&&!doc.querySelector('label[for="'+el.id+'"]'))failures.push('label:'+el.id)
}
const canvas=doc.querySelector('#view');
if(!canvas?.matches('[tabindex][aria-label]'))failures.push('canvas-keyboard-name');
if(!doc.querySelector('[aria-live]'))failures.push('live-status');
if(/user-scalable\s*=\s*no/i.test(doc.querySelector('meta[name=viewport]')?.content||''))failures.push('page-zoom-disabled');
const css=[...doc.querySelectorAll('style')].map(x=>x.textContent).join('\n');
if(!css.includes('prefers-reduced-motion'))failures.push('reduced-motion');
if(!css.includes('prefers-reduced-transparency'))failures.push('reduced-transparency');
canvas.focus();
const before=win.location.hash;
canvas.dispatchEvent(new win.KeyboardEvent('keydown',{key:'ArrowRight',bubbles:true,cancelable:true}));
const keyboard=await poll(()=>win.location.hash!==before,2000,'keyboard navigation').then(()=>true).catch(()=>false);
if(!keyboard)failures.push('keyboard-navigation');
if(doc.activeElement!==canvas)failures.push('canvas-focus');
return{
pass:failures.length===0,failures,interactiveCount:interactive.length,keyboard,
pendingExternal:['visible-focus visual review','browser page-zoom and canvas-pinch coexistence','screen-reader announcement order']
}
}
async function interactionAudit(win){
const canvas=win.document.querySelector('#view'),before=win.__MANDEL_DIAG__.snapshot(),paintDurations=[];
for(let i=0;i<16;i++){
const writes=win.__MANDEL_DIAG__.snapshot().runtimeMetrics.canvasWrites,t=performance.now();
canvas.dispatchEvent(new win.WheelEvent('wheel',{
deltaY:i%2?6:-4,clientX:canvas.clientWidth/2,clientY:canvas.clientHeight/2,cancelable:true
}));
await poll(()=>win.__MANDEL_DIAG__.snapshot().runtimeMetrics.canvasWrites>writes,1000,'wheel transform paint');
paintDurations.push(performance.now()-t);
await nextFrame(win)
}
const during=win.__MANDEL_DIAG__.snapshot();
const preview=await poll(()=>{
const d=win.__MANDEL_DIAG__.snapshot();
return d&&!d.rendering&&!d.scheduler.wheelActive&&d.lastPass==='preview'?d:null
},120000,'wheel settle preview');
await poll(()=>{
const d=win.__MANDEL_DIAG__.snapshot();
return d&&!d.rendering&&d.coverage>=1&&d.drawState==='COVERED'?d:null
},180000,'wheel settle covered');
const after=win.__MANDEL_DIAG__.snapshot();
return{
paintP95Ms:p95(paintDurations),
previewAfterSettleMs:Math.max(0,preview.ms-110),
renderStartsDuringGesture:during.runtimeMetrics.renderStartsDuringGesture-before.runtimeMetrics.renderStartsDuringGesture,
renderStartsBeforeSettle:during.runtimeMetrics.renderStarts-before.runtimeMetrics.renderStarts,
longTasks:after.runtimeMetrics.longTasks-before.runtimeMetrics.longTasks
}
}
async function modeAudit(win){
const select=win.document.querySelector('#processMode'),hq=win.document.querySelector('#hq'),previewAfterSettle=[];
select.value='power';
select.dispatchEvent(new win.Event('change',{bubbles:true}));
for(let i=0;i<previewTrials;i++){
const canvas=win.document.querySelector('#view'),started=performance.now();
canvas.dispatchEvent(new win.WheelEvent('wheel',{
deltaY:1,clientX:canvas.clientWidth/2,clientY:canvas.clientHeight/2,cancelable:true
}));
await poll(()=>{
const d=win.__MANDEL_DIAG__.snapshot();
return d.automaticTarget==='PREVIEW'&&!d.rendering&&!d.scheduler.wheelActive&&d.lastPass==='preview'?d:null
},120000,'power preview trial');
previewAfterSettle.push(Math.max(0,performance.now()-started-110))
}
const powerBefore=win.__MANDEL_DIAG__.snapshot();
await wait(900);
const power=win.__MANDEL_DIAG__.snapshot();
const powerPass=power.automaticTarget==='PREVIEW'&&power.lastPass==='preview'&&!power.rendering&&!power.scheduler.timer&&!power.detailActive&&!hq.checked&&power.screen.pixelBudget<=1048576&&power.runtimeMetrics.renderStarts===powerBefore.runtimeMetrics.renderStarts;
select.value='standard';
select.dispatchEvent(new win.Event('change',{bubbles:true}));
await poll(()=>{
const d=win.__MANDEL_DIAG__.snapshot();
return d.automaticTarget==='COVERED'&&!d.rendering&&d.lastPass==='covered'&&d.coverage>=1&&d.drawState==='COVERED'?d:null
},180000,'standard covered');
await wait(900);
const standard=win.__MANDEL_DIAG__.snapshot();
const standardPass=standard.automaticTarget==='COVERED'&&standard.lastPass==='covered'&&!standard.detailActive&&!standard.scheduler.unknownTimer&&!hq.checked&&standard.screen.pixelBudget<=4194304;
select.value='fine';
select.dispatchEvent(new win.Event('change',{bubbles:true}));
const fineResult=await poll(()=>{
const d=win.__MANDEL_DIAG__.snapshot();
return d.automaticTarget==='REFINED'&&!d.rendering&&!d.detailActive&&d.drawState==='REFINED'?d:null
},240000,'fine refined');
const fine=fineResult.value;
const finePass=fine.coverage>=1&&fine.drawState==='REFINED';
return{
pass:powerPass&&standardPass&&finePass&&previewAfterSettle.length>=30&&p95(previewAfterSettle)<120,
previewTrials:previewAfterSettle.length,previewP95AfterSettleMs:p95(previewAfterSettle),
power:{pass:powerPass,target:power.automaticTarget,pixelBudget:power.screen.pixelBudget},
standard:{pass:standardPass,target:standard.automaticTarget,pixelBudget:standard.screen.pixelBudget},
fine:{pass:finePass,target:fine.automaticTarget,refinedMs:fineResult.ms}
}
}
async function recolorAudit(win){
const select=win.document.querySelector('#palette'),before=win.__MANDEL_DIAG__.snapshot();
select.value=before.palette===1?'2':'1';
select.dispatchEvent(new win.Event('change',{bubbles:true}));
await nextFrame(win);await nextFrame(win);
const after=win.__MANDEL_DIAG__.snapshot();
return{pass:after.palette!==before.palette&&after.runtimeMetrics.renderStarts===before.runtimeMetrics.renderStarts,renderStarts:after.runtimeMetrics.renderStarts-before.runtimeMetrics.renderStarts}
}
async function exportAudit(win){
const doc=win.document,captured=[],nativeCreate=win.URL.createObjectURL.bind(win.URL),nativeClick=win.HTMLAnchorElement.prototype.click;
win.URL.createObjectURL=blob=>{
const url=nativeCreate(blob);
captured.push({url,blob,progress:Number(doc.querySelector('#exportProgress').value),active:win.__MANDEL_DIAG__.snapshot().exporting});
return url
};
win.HTMLAnchorElement.prototype.click=function(){};
try{
async function balancedExport(){
const start=captured.length;
doc.querySelector('#exportScale').value='0';
doc.querySelector('#exportWidth').value='64';
doc.querySelector('#exportAA').value='2';
doc.querySelector('#exportPrecision').value='balanced';
doc.querySelector('#exportStart').click();
await poll(()=>!win.__MANDEL_DIAG__.snapshot().exporting&&captured.length>=start+2,120000,'small export');
return captured.slice(start)
}
const first=await balancedExport(),second=await balancedExport();
const png=first.find(x=>x.blob.type==='image/png'),json=first.find(x=>x.blob.type==='application/json');
const png2=second.find(x=>x.blob.type==='image/png');
if(!png||!json||!png2)throw new Error('export files missing');
const bytes=new Uint8Array(await png.blob.arrayBuffer()),width=(bytes[16]<<24)|(bytes[17]<<16)|(bytes[18]<<8)|bytes[19],height=(bytes[20]<<24)|(bytes[21]<<16)|(bytes[22]<<8)|bytes[23];
const meta=JSON.parse(await json.blob.text()),expectedHeight=Math.round(64*doc.querySelector('#view').height/doc.querySelector('#view').width);
const deterministic=await sha256(await png.blob.arrayBuffer())===await sha256(await png2.blob.arrayBuffer());
const completed=meta.determinism?.allTilesCompleted===true&&meta.sampleCount===width*height*4&&!!meta.kernelSha256&&typeof meta.unresolvedSamples==='number';
const noEarlyDownload=[...first,...second].every(x=>x.progress>=1);
const prior=captured.length;
doc.querySelector('#exportWidth').value='256';
doc.querySelector('#exportPrecision').value='validated';
doc.querySelector('#exportStart').click();
doc.querySelector('#exportCancel').click();
await poll(()=>!win.__MANDEL_DIAG__.snapshot().exporting,120000,'export cancel');
const cancelledWithoutDownload=captured.length===prior;
return{pass:width===64&&height===expectedHeight&&completed&&noEarlyDownload&&deterministic&&cancelledWithoutDownload,width,height,completed,noEarlyDownload,deterministic,cancelledWithoutDownload}
}finally{
win.URL.createObjectURL=nativeCreate;
win.HTMLAnchorElement.prototype.click=nativeClick
}
}
async function observedMemory(win){
if(typeof win.performance.measureUserAgentSpecificMemory!=='function')return{status:'unsupported'};
try{
const result=await win.performance.measureUserAgentSpecificMemory();
return{status:'measured',bytes:result.bytes}
}catch(error){return{status:'error',error:String(error?.message||error)}}
}
async function runScene(scene,profile){
frame.width=profile.cssWidth;frame.height=profile.cssHeight;
const loaded=new Promise((resolve,reject)=>{frame.onload=resolve;frame.onerror=reject});
const started=performance.now();
frame.src=appPath+hash(scene);
await loaded;
const win=frame.contentWindow,label=profile.id+'/'+scene.id;
const preview=await poll(()=>{
const d=win.__MANDEL_DIAG__?.snapshot();
return d&&d.drawState==='PREVIEW'&&!d.rendering&&d.lastPass==='preview'?d:null
},120000,'preview '+label);
const covered=await poll(()=>{
const d=win.__MANDEL_DIAG__?.snapshot();
return d&&d.drawState==='COVERED'&&d.coverage>=1&&!d.rendering?d:null
},180000,'covered '+label);
const initial=covered.value,visual=await canvasAudit(win,scene,profile);
const exercise=scene.id==='z0';
const interaction=exercise?await interactionAudit(win):null;
const modes=exercise?await modeAudit(win):null;
const recolor=exercise?await recolorAudit(win):null;
const exportResult=exercise?await exportAudit(win):null;
const accessibility=exercise?await accessibilityAudit(win):null;
const resources=win.performance.getEntriesByType('resource').map(e=>String(e.name));
const assetRequests={deep:resources.filter(x=>/\/(deep|bla|color)-/.test(x)),allWasm:resources.filter(x=>/\.wasm(?:$|\?)/.test(x))};
await wait(2000);
const before=win.__MANDEL_DIAG__.snapshot();
await wait(1000);
const after=win.__MANDEL_DIAG__.snapshot();
const idle={
raf:after.scheduler.raf,timer:after.scheduler.timer,pointerSettle:after.scheduler.pointerSettle,
unknownTimer:after.scheduler.unknownTimer,backgroundJobs:after.scheduler.backgroundJobs,
canvasWrites:after.runtimeMetrics.canvasWrites-before.runtimeMetrics.canvasWrites,
domWrites:after.runtimeMetrics.domWrites-before.runtimeMetrics.domWrites,
renderStarts:after.runtimeMetrics.renderStarts-before.runtimeMetrics.renderStarts
};
return{
id:scene.id,profile:profile.id,target,requestedViewport:profile,
actualViewport:{cssWidth:win.innerWidth,cssHeight:win.innerHeight},actualDevicePixelRatio:win.devicePixelRatio,
totalMs:performance.now()-started,previewMs:preview.ms,coveredMs:covered.ms,
preview:preview.value,initial,final:after,idle,visual,assetRequests,interaction,modes,recolor,
export:exportResult,accessibility,memoryObserved:await observedMemory(win)
}
}
function evaluate(results,profile){
const exercise=results.find(x=>x.interaction);
const checks={
idle:results.every(x=>!x.idle.raf&&!x.idle.timer&&!x.idle.pointerSettle&&!x.idle.unknownTimer&&x.idle.backgroundJobs===0&&x.idle.canvasWrites===0&&x.idle.domWrites===0&&x.idle.renderStarts===0),
covered:results.every(x=>x.initial.coverage>=1&&x.initial.hqTarget===x.initial.frame),
managedMemory:results.every(x=>x.final.memory.managedBytes<=x.final.memory.budget),
visual:results.every(x=>x.visual.pass),
startup:results.filter(x=>x.id==='z0').every(x=>target==='hosted'?x.assetRequests.deep.length===0:x.initial.deepAssets.workers===0),
interaction:!!exercise&&exercise.interaction.paintP95Ms<16&&exercise.interaction.renderStartsDuringGesture===0&&exercise.interaction.renderStartsBeforeSettle===0&&exercise.interaction.longTasks===0,
preview:exercise?.modes?.previewTrials>=30&&exercise?.modes?.previewP95AfterSettleMs<120,
modes:exercise?.modes?.pass===true,
recolor:exercise?.recolor?.pass===true,
export:exercise?.export?.pass===true,
accessibilityAutomated:exercise?.accessibility?.pass===true,
profileFidelity:results.every(x=>x.actualViewport.cssWidth===profile.cssWidth&&x.actualViewport.cssHeight===profile.cssHeight&&Math.abs(x.actualDevicePixelRatio-profile.dpr)<.01)
};
const failures=Object.entries(checks).filter(([,pass])=>!pass).map(([name])=>name);
const pendingExternal=[
'observed browser/GPU peak memory review',
'visible-focus and rendered-output visual review',
'page zoom and canvas pinch coexistence',
'screen-reader live announcement order'
];
return{pass:false,automatedPass:failures.length===0,checks,failures,pendingExternal}
}
document.querySelector('#run').onclick=async()=>{
document.querySelector('#run').disabled=true;
try{
const corpus=await(await fetch('./scenes.json',{cache:'no-store'})).json();
const profile=corpus.viewports.find(x=>x.id===selectedProfile);
if(!profile)throw new Error('Unknown profile: '+selectedProfile);
const results=[];
for(const scene of corpus.scenes){
out.textContent='Running '+profile.id+'/'+scene.id+' on '+target+'…\n'+JSON.stringify(results,null,2);
results.push(await runScene(scene,profile))
}
const report={
format:'mandelbrot-browser-baseline-v23',generatedUtc:new Date().toISOString(),target,
userAgent:navigator.userAgent,hostDevicePixelRatio:devicePixelRatio,profile,results,
acceptance:evaluate(results,profile)
};
out.textContent=JSON.stringify(report,null,2);
globalThis.__BENCHMARK_RESULT__=report
}catch(error){
out.textContent=String(error?.stack||error)
}finally{
document.querySelector('#run').disabled=false
}
};
</script>

133
tests/document-contract.ps1 Normal file
View file

@ -0,0 +1,133 @@
$ErrorActionPreference = 'Stop'
$workspace = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path
function Assert-Contract([bool]$Condition, [string]$Message) {
if (-not $Condition) { throw $Message }
}
function Decimal-Fraction([string]$Text) {
if ($Text -notmatch '^([+-]?)(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$') {
throw "Invalid decimal: $Text"
}
$negative = $Matches[1] -eq '-'
$fraction = if ($null -eq $Matches[3]) { '' } else { $Matches[3] }
$digits = ($Matches[2] + $fraction).TrimStart('0')
if (-not $digits) { $digits = '0' }
$numerator = [System.Numerics.BigInteger]::Parse($digits)
if ($negative) { $numerator = -$numerator }
$exponent = if ($Matches[4]) { [int]$Matches[4] } else { 0 }
$places = $fraction.Length - $exponent
if ($places -ge 0) {
$denominator = [System.Numerics.BigInteger]::Pow([System.Numerics.BigInteger]10, $places)
} else {
$numerator *= [System.Numerics.BigInteger]::Pow([System.Numerics.BigInteger]10, -$places)
$denominator = [System.Numerics.BigInteger]1
}
[pscustomobject]@{ numerator=$numerator; denominator=$denominator }
}
function Assert-ExactCoordinate($Scene, [string]$Axis) {
$decimal = Decimal-Fraction ([string]$Scene.$Axis)
$prefix = if ($Axis -eq 're') { 're' } else { 'im' }
$numerator = [System.Numerics.BigInteger]::Parse([string]$Scene.coordinate.($prefix + 'Numerator'))
$denominator = [System.Numerics.BigInteger]::Parse([string]$Scene.coordinate.($prefix + 'Denominator'))
Assert-Contract ($denominator -gt 0) "$($Scene.id): $Axis denominator must be positive."
Assert-Contract ($decimal.numerator * $denominator -eq $numerator * $decimal.denominator) "$($Scene.id): $Axis decimal does not match its exact rational."
}
$proposal = Get-Content -LiteralPath (Join-Path $workspace 'IMPROVEMENT_PROPOSAL.md') -Raw -Encoding UTF8
Assert-Contract ($proposal -match 'Document status: historical-v22 / implemented-v23') 'Proposal status/provenance is missing.'
Assert-Contract ($proposal -match 'sample coverage.+UNRESOLVED.+resolved coverage') 'Coverage semantics are not separated.'
Assert-Contract ($proposal -match 'Balanced Export.+Validated Export') 'Export precision tiers are not separated.'
Assert-Contract ($proposal -match 'exact half.+away-from-zero') 'Fixed-point tie rounding is not specified.'
Assert-Contract ($proposal -match 'Phase 0A' -and $proposal -match 'Phase 0B') 'Browserless and browser gates are not separated.'
Assert-Contract ($proposal -match 'threshold.+pass') 'Undefined acceptance thresholds must fail.'
$scenePath = Join-Path $workspace 'tests\scenes.json'
$scenes = Get-Content -LiteralPath $scenePath -Raw -Encoding UTF8 | ConvertFrom-Json
$policy = Get-Content -LiteralPath (Join-Path $workspace 'tests\numeric-policy-v23.json') -Raw -Encoding UTF8 | ConvertFrom-Json
Assert-Contract ($scenes.format -eq 'mandelbrot-scene-corpus-v2') 'Unexpected scene corpus version.'
Assert-Contract ($scenes.pixelContract -eq 'centered-rational-ties-away-from-zero') 'Unexpected scene pixel contract.'
Assert-Contract ($scenes.numericPolicy -eq $policy.id) 'Scene corpus and numeric policy IDs differ.'
Assert-Contract ($policy.validated.falseEscapedMax -eq 0 -and $policy.validated.falseInteriorProvenMax -eq 0 -and $policy.validated.unresolvedMax -eq 0) 'Validated zero-error thresholds changed.'
$ids = @{}
$maxViewportPixels = ($scenes.viewports | ForEach-Object { [double]$_.cssWidth * [double]$_.dpr } | Measure-Object -Maximum).Maximum
$viewportDigits = [Math]::Ceiling([Math]::Log10($maxViewportPixels))
foreach ($scene in $scenes.scenes) {
Assert-Contract (-not $ids.ContainsKey($scene.id)) "Duplicate scene ID: $($scene.id)"
$ids[$scene.id] = $true
Assert-Contract ($null -ne $scene.coordinate -and $null -ne $scene.oracle) "$($scene.id): coordinate/oracle metadata missing."
if ($scene.coordinate.kind -eq 'exact-rational') {
Assert-ExactCoordinate $scene 're'
Assert-ExactCoordinate $scene 'im'
} elseif ($scene.coordinate.kind -eq 'decimal') {
$reDigits = ([regex]::Replace([string]$scene.re, '[^0-9]', '')).TrimStart('0').Length
$imDigits = ([regex]::Replace([string]$scene.im, '[^0-9]', '')).TrimStart('0').Length
$actualDigits = [Math]::Min($reDigits, $imDigits)
Assert-Contract ($actualDigits -eq [int]$scene.coordinate.significantDigits) "$($scene.id): significantDigits metadata is stale."
$span = [Math]::Abs([double]::Parse([string]$scene.span, [Globalization.CultureInfo]::InvariantCulture))
$depthDigits = [Math]::Ceiling(-[Math]::Log10($span))
$guard = $actualDigits - $depthDigits - $viewportDigits
Assert-Contract ($guard -eq [int]$scene.coordinate.guardDigitsAtViewport -and $guard -ge 4) "$($scene.id): coordinate guard digits are insufficient or stale."
} else {
throw "$($scene.id): unsupported coordinate kind $($scene.coordinate.kind)"
}
}
foreach ($required in @('z0','period2-cusp-z14','swirly-seahorses-z12','period2-cusp-z20','period2-cusp-z100','period3-interior','period2-cusp-e280')) {
Assert-Contract ($ids.ContainsKey($required)) "Required scene missing: $required"
}
$runtimeTest = Get-Content -LiteralPath (Join-Path $workspace 'tests\runtime-budget.mjs') -Raw -Encoding UTF8
Assert-Contract ($runtimeTest -match "sceneById\('swirly-seahorses-z12'\)" -and $runtimeTest -match "acceptance:false") 'Runtime characterization must use the published mixed-boundary scene and remain non-acceptance evidence.'
$runner = Get-Content -LiteralPath (Join-Path $workspace 'tests\browser-benchmark.html') -Raw -Encoding UTF8
Assert-Contract ($runner -match "target==='hosted'.+dist/hosted/index\.html") 'Browser runner does not target the Hosted build.'
Assert-Contract ($runner -match "selectedProfile=query\.get\('profile'\)" -and $runner -notmatch 'for\s*\(const profile of corpus\.viewports\)') 'Browser runner must execute one real-DPR profile per run.'
Assert-Contract ($runner -notmatch 'deltaY\s*:\s*0') 'Wheel audit still uses a zero-delta event.'
Assert-Contract ($runner -match 'previewTrials>=30' -and $runner -match "drawState==='REFINED'") 'Preview p95 or actual Refined checks are missing.'
Assert-Contract ($runner -match 'visualGoldens' -and $runner -match 'hashValue===expected') 'Visual acceptance lacks a golden comparison.'
Assert-Contract ($runner -match 'interaction\.longTasks===0') 'Long tasks are not part of browser acceptance.'
Assert-Contract ($runner -match 'completed&&noEarlyDownload&&deterministic&&cancelledWithoutDownload') 'Export evidence is not part of the pass condition.'
Assert-Contract ($runner -match 'pendingExternal' -and $runner -match 'return\{pass:false,automatedPass') 'Browser-only/manual residual gates can be falsely marked complete.'
$baseline = Get-Content -LiteralPath (Join-Path $workspace 'audit\v23-source-baseline.json') -Raw -Encoding UTF8 | ConvertFrom-Json
Assert-Contract ($baseline.hosted.deepRequestMeasurementStatus -eq 'not-run') 'Static deep-request contract is mislabeled as a measurement.'
$records = @($baseline.hosted.firstViewFiles) + @($baseline.standalone.files) + @($baseline.sourceBuild.files)
foreach ($record in $records) {
$path = Join-Path $workspace ([string]$record.file).Replace('/', [System.IO.Path]::DirectorySeparatorChar)
Assert-Contract (Test-Path -LiteralPath $path) "Baseline file missing: $($record.file)"
$item = Get-Item -LiteralPath $path
$hash = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant()
Assert-Contract ($item.Length -eq [long]$record.bytes -and $hash -eq [string]$record.sha256) "Baseline bytes/hash mismatch: $($record.file)"
}
$implementation = Get-Content -LiteralPath (Join-Path $workspace 'IMPLEMENTATION_REPORT.md') -Raw -Encoding UTF8
Assert-Contract ($implementation -match 'Hosted first view ([\d,]+) bytes') 'Hosted implementation size statement missing.'
$hostedDoc = [long](($Matches[1] -replace ',', ''))
Assert-Contract ($implementation -match 'Standalone ([\d,]+) bytes') 'Standalone implementation size statement missing.'
$standaloneDoc = [long](($Matches[1] -replace ',', ''))
Assert-Contract ($hostedDoc -eq [long]$baseline.hosted.firstViewUncompressedBytes -and $standaloneDoc -eq [long]$baseline.standalone.uncompressedBytes) 'Implementation size statement is stale.'
$completion = Get-Content -LiteralPath (Join-Path $workspace 'COMPLETION_AUDIT.md') -Raw -Encoding UTF8
Assert-Contract ($completion -notmatch '0\.91|36\.6') 'Completion audit cites performance values absent from the saved v23 JSON.'
$buildDoc = Get-Content -LiteralPath (Join-Path $workspace 'BUILD_REPRODUCIBILITY.md') -Raw -Encoding UTF8
Assert-Contract ($buildDoc -match 'Provenance status: tool archive hash not retained') 'Toolchain provenance boundary is overstated.'
$browserless = Get-Content -LiteralPath (Join-Path $workspace 'audit\v23-browserless-baseline.json') -Raw -Encoding UTF8 | ConvertFrom-Json
Assert-Contract ($browserless.status -eq 'pass' -and $browserless.scope -eq 'browserless-current-artifacts' -and -not $browserless.fullAcceptance) 'Browserless audit scope/status is misleading.'
Assert-Contract ($browserless.node.version -eq 'v22.18.0' -and $browserless.node.archive.sha256 -eq 'c95d8a7e1c99e669cc08c9f1176e068c1f50847c37908fcb8c35b62482366511') 'Pinned Node provenance is missing.'
foreach ($test in $browserless.executableTests.psobject.Properties) {
Assert-Contract ($test.Value.status -eq 'pass' -or $test.Name -eq 'runtime-budget.mjs') "Browserless executable gate failed: $($test.Name)"
}
Assert-Contract ($browserless.runtime.status -eq 'measured-not-acceptance' -and $browserless.v22Comparison.status -eq 'not-verifiable' -and $browserless.browserAcceptance.status -eq 'not-run') 'Residual gates are overstated.'
[ordered]@{
status='pass'
proposalContract='revised-v23'
scenes=@($scenes.scenes).Count
viewports=@($scenes.viewports).Count
numericPolicy=$policy.id
hashedFiles=$records.Count
browserRunner='static-contract-pass'
browserlessEvidence='pass-with-explicit-limits'
} | ConvertTo-Json

View file

@ -3,7 +3,8 @@ import vm from 'node:vm';
const root = new URL('../', import.meta.url);
const kernels = await fs.readFile(new URL('kernels.js', root), 'utf8');
let app = await fs.readFile(new URL('script.js', root), 'utf8');
const appSource = await fs.readFile(new URL('script.js', root), 'utf8');
let app = appSource;
const browserHarness = await fs.readFile(new URL('tests/browser-benchmark.html', root), 'utf8');
const browserModule = browserHarness.match(/<script type="module">([\s\S]*?)<\/script>/)?.[1];
if (!browserModule) throw new Error('Browser benchmark module not found.');
@ -148,5 +149,9 @@ console.log(JSON.stringify({
referenceCheckpoints,
deepWorkerModuleInit: true,
browserHarnessSyntax: true,
sources: { appBytes: app.length, shallowWorkerBytes: sources.shallow.length, deepWorkerBytes: sources.deep.length }
sources: {
appBytes: Buffer.byteLength(appSource),
shallowWorkerCharacters: sources.shallow.length,
deepWorkerCharacters: sources.deep.length
}
}));

View file

@ -0,0 +1,30 @@
{
"format": "mandelbrot-numeric-policy-v1",
"id": "numeric-policy-v23",
"rendererVersion": 23,
"pixelContract": {
"mapping": "centered-rational",
"fixedPointRounding": "nearest-ties-away-from-zero",
"fixedPointIntegerMismatchMax": 0,
"float64CoordinateUlpMax": 4,
"tileSeamPixelMismatchMax": 0
},
"validated": {
"falseEscapedMax": 0,
"falseInteriorProvenMax": 0,
"unresolvedMax": 0,
"escapeIterationAbsMax": 0,
"magnitudeRelativeErrorMax": 1e-12,
"referenceGuardBits": 64
},
"balanced": {
"falseEscapedMax": 0,
"falseInteriorProvenMax": 0,
"interiorLikelyMismatch": "record",
"unresolved": "record"
},
"color": {
"smoothCorrectionRelativeErrorMax": 0.00003,
"simdScalarRelativeErrorMax": 1e-7
}
}

View file

@ -0,0 +1,30 @@
{
"format": "mandelbrot-numeric-policy-v1",
"id": "numeric-policy-v23",
"rendererVersion": 23,
"pixelContract": {
"mapping": "centered-rational",
"fixedPointRounding": "nearest-ties-away-from-zero",
"fixedPointIntegerMismatchMax": 0,
"float64CoordinateUlpMax": 4,
"tileSeamPixelMismatchMax": 0
},
"validated": {
"falseEscapedMax": 0,
"falseInteriorProvenMax": 0,
"unresolvedMax": 0,
"escapeIterationAbsMax": 0,
"magnitudeRelativeErrorMax": 1e-12,
"referenceGuardBits": 64
},
"balanced": {
"falseEscapedMax": 0,
"falseInteriorProvenMax": 0,
"interiorLikelyMismatch": "record",
"unresolved": "record"
},
"color": {
"smoothCorrectionRelativeErrorMax": 0.00003,
"simdScalarRelativeErrorMax": 1e-7
}
}

View file

@ -1,12 +1,79 @@
const assert=(ok,message)=>{if(!ok)throw new Error(message)};
const close=(a,b)=>Math.abs(a-b)<=1e-15*Math.max(1,Math.abs(a),Math.abs(b));
const bitsBuffer=new ArrayBuffer(8),bitsView=new DataView(bitsBuffer);
function ulp(value){
if(!Number.isFinite(value))return Infinity;
if(value===0)return Number.MIN_VALUE;
bitsView.setFloat64(0,value,false);
let bits=bitsView.getBigUint64(0,false);
bits+=value>0?1n:-1n;
bitsView.setBigUint64(0,bits,false);
return Math.abs(bitsView.getFloat64(0,false)-value)
}
const closeUlp=(a,b,limit=4)=>Math.abs(a-b)<=limit*Math.max(Number.MIN_VALUE,ulp(a),ulp(b));
const roundDiv=(value,denominator)=>{
const negative=value<0n,absolute=negative?-value:value;
const quotient=(absolute+denominator/2n)/denominator;
return negative?-quotient:quotient
};
assert(roundDiv(1n,2n)===1n&&roundDiv(-1n,2n)===-1n,'half ties must round away from zero');
assert(roundDiv(3n,2n)===2n&&roundDiv(-3n,2n)===-2n,'roundDiv must be sign symmetric');
const view={re:-.743643887037151,im:.13182590420533,span:3.4e-12,w:37,h:23};
const world=(x,y,w=view.w,h=view.h,span=view.span)=>[view.re+(x+.5-w*.5)*span/w,view.im+(h*.5-y-.5)*span/w];
const shallow=(x,y)=>{const scale=view.span/view.w,shiftedRe=view.re+scale*.5,shiftedIm=view.im-scale*.5;return[shiftedRe+(x-view.w*.5)*scale,shiftedIm+(view.h*.5-y)*scale]};
const deep=(x,y)=>{const scale=view.span/view.w,offR=scale*.5,offI=-scale*.5;return[view.re+offR+scale*(x-view.w*.5),view.im+offI+scale*(view.h*.5-y)]};
const bla=(x,y)=>{const scale=view.span/view.w,offR=scale*.5,offI=-scale*.5;return[view.re+offR+view.span*(x/view.w-.5),view.im+offI+view.span*((.5*view.h-y)/view.w)]};
let samples=0;for(let y=0;y<view.h;y++)for(let x=0;x<view.w;x++){const expected=world(x,y);for(const [name,actual]of[['shallow',shallow(x,y)],['deep',deep(x,y)],['bla',bla(x,y)]]){assert(close(expected[0],actual[0])&&close(expected[1],actual[1]),`${name} mapping mismatch ${x},${y}`)}samples++}
for(const sampleScale of[2,4]){const tile={x:7,y:5,w:11,h:9},W=view.w*sampleScale,H=view.h*sampleScale;for(let phase=0;phase<sampleScale*sampleScale;phase++){const left=(phase%sampleScale)*tile.w,top=((phase/sampleScale)|0)*tile.h;for(let yy=0;yy<tile.h;yy++)for(let xx=0;xx<tile.w;xx++){const gx=tile.x*sampleScale+left+xx,gy=tile.y*sampleScale+top+yy,expected=world(gx,gy,W,H,view.span);const direct=[view.re+(gx+.5-W*.5)*view.span/W,view.im+(H*.5-gy-.5)*view.span/W];assert(close(expected[0],direct[0])&&close(expected[1],direct[1]),`tile seam ${sampleScale}x phase ${phase}`)}}}
const first=world(0,0),last=world(view.w-1,view.h-1);assert(close((first[0]+last[0])/2,view.re),'view center moved on x');assert(close((first[1]+last[1])/2,view.im),'view center moved on y');
const result={status:'pass',samples,backends:['shallow','deep','bla'],subsampleScales:[2,4],contract:'(x+0.5,y+0.5)'};
console.log(JSON.stringify(result));export default result;
const world=(x,y,w=view.w,h=view.h,span=view.span)=>[
view.re+(x+.5-w*.5)*span/w,
view.im+(h*.5-y-.5)*span/w
];
const shallow=(x,y)=>{
const scale=view.span/view.w,shiftedRe=view.re+scale*.5,shiftedIm=view.im-scale*.5;
return[shiftedRe+(x-view.w*.5)*scale,shiftedIm+(view.h*.5-y)*scale]
};
const deep=(x,y)=>{
const scale=view.span/view.w,offR=scale*.5,offI=-scale*.5;
return[view.re+offR+scale*(x-view.w*.5),view.im+offI+scale*(view.h*.5-y)]
};
const bla=(x,y)=>{
const scale=view.span/view.w,offR=scale*.5,offI=-scale*.5;
return[view.re+offR+view.span*(x/view.w-.5),view.im+offI+view.span*((.5*view.h-y)/view.w)]
};
let samples=0;
for(let y=0;y<view.h;y++)for(let x=0;x<view.w;x++){
const expected=world(x,y);
for(const [name,actual]of[['shallow',shallow(x,y)],['deep',deep(x,y)],['bla',bla(x,y)]]){
assert(closeUlp(expected[0],actual[0])&&closeUlp(expected[1],actual[1]),name+' mapping mismatch '+x+','+y)
}
samples++
}
for(const sampleScale of[2,4]){
const tile={x:7,y:5,w:11,h:9},W=view.w*sampleScale,H=view.h*sampleScale;
for(let phase=0;phase<sampleScale*sampleScale;phase++){
const left=(phase%sampleScale)*tile.w,top=((phase/sampleScale)|0)*tile.h;
for(let yy=0;yy<tile.h;yy++)for(let xx=0;xx<tile.w;xx++){
const gx=tile.x*sampleScale+left+xx,gy=tile.y*sampleScale+top+yy,expected=world(gx,gy,W,H,view.span);
const direct=[view.re+(gx+.5-W*.5)*view.span/W,view.im+(H*.5-gy-.5)*view.span/W];
assert(closeUlp(expected[0],direct[0])&&closeUlp(expected[1],direct[1]),'tile seam '+sampleScale+'x phase '+phase)
}
}
}
const first=world(0,0),last=world(view.w-1,view.h-1);
assert(closeUlp((first[0]+last[0])/2,view.re),'view center moved on x');
assert(closeUlp((first[1]+last[1])/2,view.im),'view center moved on y');
const fixedBits=320n,fixedOne=1n<<fixedBits;
const fixedView={re:-3n*fixedOne/4n,im:0n,span:1n<<220n,w:37,h:23};
const fixedWorld=(x,y,w=fixedView.w,h=fixedView.h)=>[
fixedView.re+roundDiv(fixedView.span*BigInt(2*x+1-w),BigInt(2*w)),
fixedView.im+roundDiv(fixedView.span*BigInt(h-2*y-1),BigInt(2*w))
];
for(let y=0;y<fixedView.h;y++)for(let x=0;x<fixedView.w;x++){
const point=fixedWorld(x,y),mirror=fixedWorld(fixedView.w-1-x,fixedView.h-1-y);
assert(point[0]+mirror[0]===2n*fixedView.re,'fixed x symmetry mismatch '+x+','+y);
assert(point[1]+mirror[1]===2n*fixedView.im,'fixed y symmetry mismatch '+x+','+y)
}
const result={
status:'pass',samples,backends:['shallow','deep','bla'],subsampleScales:[2,4],
contract:'centered-rational',float64UlpMax:4,fixedPointRounding:'nearest-ties-away-from-zero'
};
console.log(JSON.stringify(result));
export default result;

View file

@ -5,6 +5,12 @@ import { fileURLToPath } from 'node:url';
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
const wasmDir=path.join(root,'build','wasm-v23');
const corpus=JSON.parse(await fs.readFile(path.join(root,'tests','scenes.json'),'utf8'));
const sceneById=id=>{
const scene=corpus.scenes.find(candidate=>candidate.id===id);
if(!scene)throw new Error('Scene missing: '+id);
return scene
};
const load=async name=>(await WebAssembly.instantiate(await fs.readFile(path.join(wasmDir,name)),{})).instance.exports;
const median=values=>values.slice().sort((a,b)=>a-b)[values.length>>1];
const assert=(ok,message)=>{if(!ok)throw new Error(message)};
@ -20,7 +26,7 @@ async function benchmarkShallow(){
function reference(cr,ci,limit){const rr=new Float64Array(limit+1),ri=new Float64Array(limit+1);let zr=0,zi=0;for(let n=0;n<=limit;n++){rr[n]=zr;ri[n]=zi;const nr=zr*zr-zi*zi+cr;zi=2*zr*zi+ci;zr=nr}return{rr,ri}}
async function benchmarkDeep(){
const ex=await load('bla-simd.wasm'),c={id:'seahorse-bla-2000',re:-.743643887037151,im:.13182590420533,span:3.4e-14,w:256,h:144,iter:2000},ref=reference(c.re,c.im,c.iter),scale=c.span/c.w;
const scene=sceneById('swirly-seahorses-z12'),ex=await load('bla-simd.wasm'),c={id:'swirly-seahorses-z12-bla-2000',re:Number(scene.re),im:Number(scene.im),span:Number(scene.span),w:256,h:144,iter:2000},ref=reference(c.re,c.im,c.iter),scale=c.span/c.w;
new Float64Array(ex.memory.buffer,ex.refs_r_ptr(),150001).set(ref.rr);new Float64Array(ex.memory.buffer,ex.refs_i_ptr(),150001).set(ref.ri);
const tBuild=performance.now(),entries=ex.build_bla(c.iter,Math.hypot(c.span*.5,c.span*c.h/(2*c.w)),2**-32),buildMs=performance.now()-tBuild;assert(entries>0,'BLA table build failed');
const run=()=>ex.render_bla_rect_v2(c.span,scale*.5,-scale*.5,c.re,c.im,c.iter,c.w,c.h,0,0,c.w,c.h,c.iter,0,0,1);run();const samples=[];for(let i=0;i<5;i++){const t=performance.now();assert(run()===c.w*c.h,'BLA output size');samples.push(performance.now()-t)}const ms=median(samples),counts=new Uint32Array(ex.memory.buffer,ex.counts_ptr(),c.w*c.h),unresolved=counts.reduce((n,v)=>n+(v>=0xfffffffe),0);return{id:c.id,width:c.w,height:c.h,iterations:c.iter,pixels:c.w*c.h,blaEntries:entries,buildMs,medianMs:ms,msPerMegapixel:ms*1e6/(c.w*c.h),unresolved,samplesMs:samples}
@ -36,6 +42,6 @@ const source=await fs.readFile(path.join(root,'script.js'),'utf8'),contracts={
viewportIndependentFloor:/minDpr=Math\.min\(1,64\/Math\.max\(cssW,cssH\)\)/.test(source)
};
assert(Object.values(contracts).every(Boolean),`runtime budget contract failed: ${JSON.stringify(contracts)}`);
const report={format:'mandelbrot-node-runtime-budget-v23',generatedUtc:new Date().toISOString(),node:process.version,contracts,shallow:await benchmarkShallow(),deep:await benchmarkDeep()};
const report={format:'mandelbrot-node-runtime-budget-v23',generatedUtc:new Date().toISOString(),node:process.version,status:'measured',acceptance:false,sceneCorpus:corpus.format,measurementNote:'Runtime characterization only; browser acceptance has separate thresholds and evidence.',contracts,shallow:await benchmarkShallow(),deep:await benchmarkDeep()};
const outputArg=process.argv.indexOf('--out');if(outputArg>=0){const target=path.resolve(process.argv[outputArg+1]);await fs.writeFile(target,JSON.stringify(report,null,2)+'\n','utf8')}
console.log(JSON.stringify(report));

View file

@ -1,14 +1,72 @@
{
"format": "mandelbrot-scene-corpus-v1",
"format": "mandelbrot-scene-corpus-v2",
"rendererVersion": 23,
"pixelContract": "centered",
"pixelContract": "centered-rational-ties-away-from-zero",
"numericPolicy": "numeric-policy-v23",
"scenes": [
{"id":"z0","re":"-0.5","im":"0","span":"3.4","tags":["shallow","overview"]},
{"id":"seahorse-z14","re":"-0.743643887037151","im":"0.13182590420533","span":"3.4e-14","tags":["deep","boundary"]},
{"id":"seahorse-z20","re":"-0.743643887037151","im":"0.13182590420533","span":"3.4e-20","tags":["deep","boundary","warm-reference"]},
{"id":"seahorse-z100","re":"-0.743643887037151","im":"0.13182590420533","span":"3.4e-100","tags":["deep","precision"]},
{"id":"period3-interior","re":"-0.122561166876","im":"0.744861766619","span":"1e-8","tags":["interior","periodic"]},
{"id":"deep-cliff-e280","re":"-0.743643887037151","im":"0.13182590420533","span":"1e-280","tags":["deep","scaled-bla-boundary"]}
{
"id": "z0",
"re": "-0.5",
"im": "0",
"span": "3.4",
"coordinate": {"kind":"exact-rational","reNumerator":"-1","reDenominator":"2","imNumerator":"0","imDenominator":"1"},
"oracle": {"kind":"overview","membership":"mixed"},
"tags": ["shallow","overview"]
},
{
"id": "period2-cusp-z14",
"re": "-0.75",
"im": "0",
"span": "3.4e-14",
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
"tags": ["deep","boundary","parabolic"]
},
{
"id": "swirly-seahorses-z12",
"re": "-0.7453983606667815",
"im": "0.1125046349959942",
"span": "3.52e-12",
"coordinate": {"kind":"exact-rational","reNumerator":"-7453983606667815","reDenominator":"10000000000000000","imNumerator":"1125046349959942","imDenominator":"10000000000000000"},
"oracle": {"kind":"published-performance-scene","membership":"mixed","iterations":2000,"source":"https://github.com/ckormanyos/mandelbrot#swirly-seahorses-and-mini-mandelbrot"},
"tags": ["deep","boundary","performance"]
},
{
"id": "period2-cusp-z20",
"re": "-0.75",
"im": "0",
"span": "3.4e-20",
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
"tags": ["deep","boundary","warm-reference","parabolic"]
},
{
"id": "period2-cusp-z100",
"re": "-0.75",
"im": "0",
"span": "3.4e-100",
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
"tags": ["deep","precision","parabolic"]
},
{
"id": "period3-interior",
"re": "-0.122561166876653619975245551820735654052",
"im": "0.744861766619744236593170428604392367240",
"span": "1e-8",
"coordinate": {"kind":"decimal","significantDigits":39,"guardDigitsAtViewport":27},
"oracle": {"kind":"polynomial-root","polynomial":"c^3+2c^2+c+1","membership":"interior","period":3},
"tags": ["interior","periodic"]
},
{
"id": "period2-cusp-e280",
"re": "-0.75",
"im": "0",
"span": "1e-280",
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
"tags": ["deep","deep-scaled-boundary","parabolic"]
}
],
"viewports": [
{"id":"mobile","cssWidth":390,"cssHeight":844,"dpr":3},

View file

@ -47,6 +47,8 @@ Assert-NotContains $script 'n<36' 'Fixed 36-tile refinement cap returned.'
Assert-Contains $script 'detailCacheBudget\(\)' 'Byte-budget detail cache is missing.'
Assert-Contains $script 'function memoryLedger' 'Logical memory ledger is missing.'
Assert-Contains $script 'function deepWisdomStorageKey' 'Versioned per-device wisdom persistence is missing.'
Assert-Contains $script 'DEEP_WISDOM_TTL_MS=30\*24\*60\*60\*1000' 'Persisted wisdom has no 30-day TTL.'
Assert-Contains $script 'savedAt:Date\.now\(\)' 'Persisted wisdom has no save timestamp.'
Assert-Contains $script 'promoteState\(required\+32-available\);invalidateReferenceOrbit\(\)' 'Orbit-condition precision promotion does not rebuild the reference.'
Assert-Contains $script 'function verifyReferenceCheckpoints' 'P/P+64 reference-orbit checkpoint verification is missing.'
Assert-Contains $script 'state\.processMode!==''validate''' 'Reference-orbit checkpoint verification is not gated to precision-first rendering.'
@ -58,6 +60,7 @@ Assert-NotContains $script 'const SIMD=\$\{JSON\.stringify\(DEEP_SIMD_B64\)\}' '
Assert-Contains $script 'highPrecisionDirectPixelAsync' 'Yielding high-precision direct verifier is missing.'
Assert-Contains $script "state\.processMode==='validate'\|\|task\.tile\.score>1\.35" 'Validated detail subsamples are not guarded-direct.'
Assert-Contains $script 'kernelSha256:globalThis\.MANDEL_KERNEL_META' 'Export kernel identity metadata is missing.'
Assert-Contains $script 'exportForcePrecision&&unresolvedSamples' 'Validated Export can emit a final file with unresolved samples.'
Assert-NotContains $script 'exactDeepPixel' 'Misleading exactDeepPixel alias returned.'
Assert-Contains $script 'deepEngineNeeded' 'ULP-based engine selection is missing.'
Assert-NotContains $script 'scheduleRealWisdom' 'Default active Real Wisdom benchmark returned.'

View file

@ -0,0 +1,10 @@
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(/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(/uncapturedErrors/.test(s),'uncaptured WebGPU error gate missing');
console.log(JSON.stringify({status:'pass',checks:6,stableUnknownMax:0,swirlyKnownMin:12},null,2));

4
tests/v24-bla-model.mjs Normal file
View file

@ -0,0 +1,4 @@
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');
const failures=[];if(/buildBla\(|BlaNode|useBla|blaLevels/.test(script+kernels))failures.push('BLA code remains in production');if(!/blaEnabled:false/.test(script))failures.push('sidecar/diagnostics do not declare BLA disabled');if(failures.length)throw new Error(failures.join('; '));
console.log(JSON.stringify({status:'pass',kind:'bla-disabled-contract',reason:'dense swirly regression demonstrated unsafe f32-quantized BLA classifications',productionBla:false},null,2));

View file

@ -0,0 +1,7 @@
function rd(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
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);if(neg)n=-n;return[n,10n**BigInt(places)]}
function fixed(t,b){const[n,d]=decRatio(t);return rd(n*(1n<<BigInt(b)),d)}
function fmt(v,bits){let neg=v<0n;if(neg)v=-v;const scale=10n**BigInt(bits),q=v*scale>>BigInt(bits);let s=q.toString().padStart(bits+1,'0');s=s.slice(0,-bits)+'.'+s.slice(-bits);s=s.replace(/(\.\d*?)0+$/,'$1').replace(/\.$/,'');return(neg?'-':'')+s}
function parseFixed(s,bits){const[n,d]=decRatio(s);return n*(1n<<BigInt(bits))/d}
for(const [text,bits] of [['1e-400',1569],['1e-1000',3562],['-0.75',1569]]){const v=fixed(text,bits),s=fmt(v,bits);if(s==='0'||s==='-0')throw new Error('deep formatter collapsed '+text);const r=parseFixed(s,bits);if(r!==v)throw new Error('exact decimal roundtrip failed '+text);}
console.log(JSON.stringify({status:'pass',kind:'exact-fixed-decimal-format',deep:['1e-400','1e-1000']},null,2));

View file

@ -0,0 +1,47 @@
import fs from 'node:fs/promises';
const corpus=JSON.parse(await fs.readFile(new URL('./scenes.json',import.meta.url),'utf8'));const map=new Map(corpus.scenes.map(s=>[s.id,s]));map.set('period2-cusp-e400',{id:'period2-cusp-e400',re:'-0.75',im:'0',span:'1e-400'});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 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: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 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'}}}
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=[];
for(const mode of [{name:'balanced',strict:false},{name:'strict',strict:true}]){
for(const[id,limit]of cfg){
const sc=map.get(id),s=snap(sc),ref=buildRef(s,limit);
let guardMismatch=0,falseEscaped=0,falseBounded=0,unknown=0,exactEscape=0;
const mismatch=[];
for(const[x,y]of pts){
const p=pixel(s,w,h,x,y),g=pixel(s,w,h,x,y,s.bits+64),a=direct(p[0],p[1],s.bits,limit),ag=direct(g[0],g[1],s.bits+64,limit);
if(a!==ag){guardMismatch++;continue}
const r=perturb(ref,s,w,h,x,y,limit,mode.strict);
if(r.kind==='unknown'){unknown++;continue}
if(r.kind==='escaped'&&a===limit)falseEscaped++;
if(r.kind==='bounded'&&a<limit)falseBounded++;
if(r.kind==='escaped'&&a<limit&&r.n===a)exactEscape++;
if(((r.kind==='escaped'&&a<limit&&r.n!==a)||(r.kind==='bounded'&&a<limit))&&mismatch.length<5)mismatch.push({x,y,oracle:a,gpu:r});
}
report.push({mode:mode.name,id,bits:s.bits,limit,guardMismatch,falseEscaped,falseBounded,unknown,known:pts.length-unknown,exactEscape,mismatch});
}
}
const denseScene=map.get('swirly-seahorses-z12'),denseS=snap(denseScene),denseRef=buildRef(denseS,2000),densePoints=[[25,12],[26,15],[27,25]],dense=[];
for(const[x,y]of densePoints){
const p=pixel(denseS,61,39,x,y),g=pixel(denseS,61,39,x,y,denseS.bits+64),a=direct(p[0],p[1],denseS.bits,2000),ag=direct(g[0],g[1],denseS.bits+64,2000);
if(a!==ag)throw new Error('dense oracle guard mismatch '+x+','+y);
for(const mode of [{name:'balanced',strict:false},{name:'strict',strict:true}]){
const r=perturb(denseRef,denseS,61,39,x,y,2000,mode.strict);
if(r.kind==='escaped'&&a===2000)throw new Error('dense false escape '+JSON.stringify({mode:mode.name,x,y,a,r}));
if(r.kind==='bounded'&&a<2000)throw new Error('dense false bounded '+JSON.stringify({mode:mode.name,x,y,a,r}));
dense.push({mode:mode.name,x,y,oracle:a,gpu:r});
}
}
if(report.some(r=>r.guardMismatch||r.falseEscaped||r.falseBounded))throw new Error('numeric classification failure '+JSON.stringify(report));
const allUnknownStrict=report.filter(r=>r.mode==='strict'&&r.unknown===pts.length);
if(allUnknownStrict.length)throw new Error('strict mode became all UNKNOWN: '+JSON.stringify(allUnknownStrict));
console.log(JSON.stringify({status:'pass',kind:'guarded-production-equation-cpu-f32-model-not-real-gpu',thresholds:{balanced:1e-3,strict:1e-4},report,denseRegression:dense},null,2));

View file

@ -0,0 +1,13 @@
import fs from 'node:fs/promises';
const corpus=JSON.parse(await fs.readFile(new URL('./scenes.json',import.meta.url),'utf8'));
const scene=corpus.scenes.find(s=>s.id==='z0'); const F=Math.fround;
function rd(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
function rs(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
function dec(t,b){let s=String(t).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 dg=(i+f).replace(/^0+(?=\d)/,'')||'0',p=f.length-e;if(p<0){dg+='0'.repeat(-p);p=0}let n=BigInt(dg);if(neg)n=-n;return rd(n*(1n<<BigInt(b)),10n**BigInt(p))}
function pixel(re,im,span,b,w,h,x,y){const den=BigInt(2*w);return[re+rd(span*BigInt(2*x+1-w),den),im+rd(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 a=rs(zr*zr,b),c=rs(zi*zi,b);zi=rs(2n*zr*zi,b)+ci;zr=a-c+cr;if(rs(zr*zr,b)+rs(zi*zi,b)>bail)return n+1}return limit}
function direct(cr,ci,limit){cr=F(cr);ci=F(ci);const U=2**-24,y2=F(ci*ci),x=F(cr-.25),q=F(F(x*x)+y2),lhs=F(q*F(q+x)),rhs=F(.25*y2),margin=F(16*U*(Math.abs(lhs)+Math.abs(rhs)+1));if(lhs<F(rhs-margin))return limit;const x2=F(cr+1),bulb=F(F(x2*x2)+y2),bulbMargin=F(16*U*(Math.abs(bulb)+.0625+1));if(bulb<F(.0625-bulbMargin))return limit;let zr=0,zi=0;for(let n=0;n<limit;n++){const a=F(zr*zr),c=F(zi*zi);zi=F(F(F(2*zr)*zi)+ci);zr=F(F(a-c)+cr);if(F(F(zr*zr)+F(zi*zi))>4)return n+1}return limit}
const bits=256,re=dec(scene.re,bits),im=dec(scene.im,bits),span=dec(scene.span,bits),W=33,H=21,limit=800;let falseEscaped=0,falseBounded=0,guardMismatch=0,countMismatch=0,tested=0;
for(let y=0;y<H;y++)for(let x=0;x<W;x++){const [cr,ci]=pixel(re,im,span,bits,W,H,x,y),[gr,gi]=pixel(re<<64n,im<<64n,span<<64n,bits+64,W,H,x,y),a=oracle(cr,ci,bits,limit),g=oracle(gr,gi,bits+64,limit);if(a!==g){guardMismatch++;continue}const rr=Number(cr)/2**bits,ii=Number(ci)/2**bits,r=direct(rr,ii,limit);tested++;if(r<limit&&a===limit)falseEscaped++;if(r===limit&&a<limit)falseBounded++;if(r<limit&&a<limit&&r!==a)countMismatch++}
if(falseEscaped||falseBounded)throw new Error(JSON.stringify({falseEscaped,falseBounded,guardMismatch,countMismatch}));
console.log(JSON.stringify({status:'pass',kind:'direct-f32-cpu-model-not-real-gpu',tested,guardMismatch,falseEscaped,falseBounded,countMismatch},null,2));

View file

@ -0,0 +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');
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 @@
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));

View file

@ -0,0 +1,40 @@
import assert from 'node:assert/strict';
import {inflateSync} from 'node:zlib';
const CRC_TABLE=(()=>{const t=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?0xedb88320^(c>>>1):c>>>1;t[n]=c>>>0}return t})();
function crc32Parts(parts){let c=0xffffffff;for(const part of parts)for(const b of part)c=CRC_TABLE[(c^b)&255]^(c>>>8);return(c^0xffffffff)>>>0}
function pngChunk(type,data=new Uint8Array()){const tb=new TextEncoder().encode(type),out=new Uint8Array(12+data.length),dv=new DataView(out.buffer);dv.setUint32(0,data.length,false);out.set(tb,4);out.set(data,8);dv.setUint32(8+data.length,crc32Parts([tb,data]),false);return out}
class StreamingPng{
constructor(w,h){this.w=w;this.h=h;this.cs=new CompressionStream('deflate');this.writer=this.cs.writable.getWriter();this.compressed=(async()=>{const r=this.cs.readable.getReader(),chunks=[];for(;;){const q=await r.read();if(q.done)break;chunks.push(q.value)}return chunks})()}
async rows(filteredRows){await this.writer.write(filteredRows)}
async finish(){await this.writer.close();const chunks=await this.compressed,ihdr=new Uint8Array(13),dv=new DataView(ihdr.buffer);dv.setUint32(0,this.w,false);dv.setUint32(4,this.h,false);ihdr[8]=8;ihdr[9]=6;const parts=[new Uint8Array([137,80,78,71,13,10,26,10]),pngChunk('IHDR',ihdr)];for(const c of chunks)parts.push(pngChunk('IDAT',c));parts.push(pngChunk('IEND'));return new Blob(parts,{type:'image/png'})}
async abort(reason){try{await this.writer.abort(reason)}catch{}try{await this.compressed}catch{}}
}
assert.equal(typeof CompressionStream,'function','CompressionStream required for this model');
const p=new StreamingPng(2,2);
const row0=Uint8Array.from([255,0,0,255, 0,255,0,255]);
const row1=Uint8Array.from([0,0,255,255, 255,255,255,128]);
const filtered=new Uint8Array(18);filtered.set(row0,1);filtered.set(row1,10);await p.rows(filtered);
const bytes=new Uint8Array(await (await p.finish()).arrayBuffer());
assert.deepEqual(Array.from(bytes.subarray(0,8)),[137,80,78,71,13,10,26,10]);
let off=8, width=0, height=0, idats=[], seenIend=false;
while(off<bytes.length){
const dv=new DataView(bytes.buffer,bytes.byteOffset+off);
const len=dv.getUint32(0,false); const type=new TextDecoder().decode(bytes.subarray(off+4,off+8));
const data=bytes.subarray(off+8,off+8+len); const got=dv.getUint32(8+len,false);
assert.equal(got,crc32Parts([bytes.subarray(off+4,off+8),data]),`CRC ${type}`);
if(type==='IHDR'){const h=new DataView(data.buffer,data.byteOffset,data.byteLength);width=h.getUint32(0,false);height=h.getUint32(4,false);assert.equal(data[8],8);assert.equal(data[9],6)}
if(type==='IDAT')idats.push(data);
if(type==='IEND')seenIend=true;
off += 12+len;
}
assert.equal(width,2); assert.equal(height,2); assert.ok(idats.length>=1); assert.ok(seenIend);
const packed=Buffer.concat(idats.map(x=>Buffer.from(x)));
const raw=new Uint8Array(inflateSync(packed));
assert.equal(raw.length,2*(1+2*4));
assert.equal(raw[0],0); assert.deepEqual(Array.from(raw.subarray(1,9)),Array.from(row0));
assert.equal(raw[9],0); assert.deepEqual(Array.from(raw.subarray(10,18)),Array.from(row1));
const aborted=new StreamingPng(1,1); await aborted.rows(Uint8Array.from([0,0,0,0,255])); await aborted.abort(new Error('cancelled'));
console.log(JSON.stringify({status:'pass',png:{width,height,idatChunks:idats.length,rawBytes:raw.length},abort:'settled'},null,2));

View file

@ -0,0 +1,3 @@
import fs from 'node:fs/promises';import vm from 'node:vm';import {performance} from 'node:perf_hooks';
const source=await fs.readFile(new URL('../script.js',import.meta.url),'utf8'),prefix="function referenceWorkerSource(){return String.raw`",start=source.indexOf(prefix)+prefix.length,end=source.indexOf("`}\nclass ReferenceService",start);if(start<prefix.length||end<0)throw new Error('worker source not found');const workerSource=source.slice(start,end);let message=null;const self={postMessage:m=>{message=m},onmessage:null};const ctx={self,postMessage:(...a)=>self.postMessage(...a),performance,Math,Number,BigInt,ArrayBuffer,DataView,Float32Array,Float64Array,Uint32Array,Set,String,Error};vm.createContext(ctx);vm.runInContext(workerSource,ctx,{timeout:5000});if(typeof self.onmessage!=='function')throw new Error('worker handler missing');
self.onmessage({data:{type:'build',id:1,key:'test',bits:256,re:(-3n*(1n<<256n)/4n).toString(),im:'0',iter:800}});if(!message)throw new Error('worker produced no message');if(message.type==='error')throw new Error(message.error);if(message.checkpointMismatch)throw new Error('reference +64 guard checkpoint mismatch');if(message.refLen<3||message.refs.byteLength!==(message.refLen+1)*16)throw new Error('reference packing invalid');for(const forbidden of ['nodes','levels','nodeCount','levelCount','invalidNodes'])if(forbidden in message)throw new Error('BLA payload leaked from quarantined reference worker: '+forbidden);const primary=message;message=null;const b=1536,R=(-3n*(1n<<BigInt(b))/4n).toString();self.onmessage({data:{type:'build',id:2,key:'e400',bits:b,re:R,im:'0',iter:120}});if(!message||message.type==='error')throw new Error(message?.error||'e400 worker produced no message');if(message.checkpointMismatch||message.refs.byteLength!==(message.refLen+1)*16)throw new Error('e400 reference contract failed');console.log(JSON.stringify({status:'pass',refLen:primary.refLen,precisionBits:primary.precisionBits,checkpointCount:primary.checkpointCount,buildMs:primary.buildMs,productionBla:false,e400:{checkpointMismatch:message.checkpointMismatch,refLen:message.refLen}},null,2));

View file

@ -0,0 +1,32 @@
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(/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(/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(/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(/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(/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');
must(/this\.context=null/.test(script)&&/webgpuCanvasClaimed=true;this\.configure\(\)/.test(script)&&/if\(webgpuCanvasClaimed\)return null/.test(script),'delayed WebGPU canvas claim/fallback guard missing');
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(/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(/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));

View file

@ -0,0 +1,5 @@
import fs from 'node:fs/promises';
const root=new URL('../',import.meta.url);const exists=async p=>{try{await fs.stat(new URL(p,root));return true}catch{return false}};
for(const banned of ['kernels.js','src/deep_kernel.c','src/bla_kernel_v18.c','src/color_kernel.c','src/shallow_kernel.c','build/wasm-v23'])if(await exists(banned))throw new Error('legacy renderer asset remains: '+banned);
for(const required of ['index.html','script.js','gpu-kernels.js','tests/webgpu-acceptance.html','tests/webgpu-acceptance.js'])if(!await exists(required))throw new Error('required v24 asset missing: '+required);
console.log(JSON.stringify({status:'pass',legacyDeepAssets:0,requiredAssets:5},null,2));

View file

@ -0,0 +1,21 @@
import fs from 'node:fs/promises';
import vm from 'node:vm';
const root=new URL('../',import.meta.url);
const src=await fs.readFile(new URL('gpu-kernels.js',root),'utf8');
const context={};context.globalThis=context;vm.runInNewContext(src,context,{filename:'gpu-kernels.js'});
const kernels=context.MANDEL_WEBGPU_KERNELS;
if(!kernels)throw new Error('kernel bundle did not initialize');
// WGSL 16.2 Reserved Words. A module must not contain one of these tokens.
const reserved=`NULL Self abstract active alignas alignof as asm asm_fragment async attribute auto await become cast catch class co_await co_return co_yield coherent column_major common compile compile_fragment concept const_cast consteval constexpr constinit crate debugger decltype delete demote demote_to_helper do dynamic_cast enum explicit export extends extern external fallthrough filter final finally friend from fxgroup get goto groupshared highp impl implements import inline instanceof interface layout lowp macro macro_rules match mediump meta mod module move mut mutable namespace new nil noexcept noinline nointerpolation non_coherent noncoherent noperspective null nullptr of operator package packoffset partition pass patch pixelfragment precise precision premerge priv protected pub public readonly ref regardless register reinterpret_cast require resource restrict self set shared sizeof smooth snorm static static_assert static_cast std subroutine super target template this thread_local throw trait try type typedef typeid typename typeof union unless unorm unsafe unsized use using varying virtual volatile wgsl where with writeonly yield`.split(/\s+/);
const strip=s=>s.replace(/\/\*[\s\S]*?\*\//g,' ').replace(/\/\/.*$/gm,' ');
const failures=[];
for(const [name,code] of Object.entries(kernels)){
if(typeof code!=='string'||!name.endsWith('_WGSL'))continue;
const clean=strip(code);
for(const word of reserved){
const re=new RegExp(`\\b${word}\\b`);
if(re.test(clean))failures.push(`${name}: reserved token ${word}`);
}
}
if(failures.length)throw new Error(failures.join('\n'));
console.log(JSON.stringify({status:'pass',check:'wgsl-reserved-words',shaderVersion:kernels.version,kernels:Object.keys(kernels).filter(k=>k.endsWith('_WGSL')).length},null,2));

View file

@ -0,0 +1,4 @@
<!doctype html><meta charset="utf-8"><title>v24 WebGPU acceptance</title>
<style>body{font:13px ui-monospace,monospace;background:#09101d;color:#e9efff;margin:16px}iframe{width:320px;height:200px;border:1px solid #445}pre{white-space:pre-wrap}</style>
<h1>v24 WebGPU acceptance</h1><iframe id="app" src="../index.html"></iframe><pre id="out">starting…</pre>
<script src="webgpu-acceptance.js"></script>

View file

@ -0,0 +1,35 @@
(()=>{'use strict';
const out=document.querySelector('#out'),frame=document.querySelector('#app');
const scenes=[
['z0','-0.5','0','3.4',120],
['period2-cusp-z14','-0.75','0','3.4e-14',1200],
['swirly-seahorses-z12','-0.7453983606667815','0.1125046349959942','3.52e-12',2000],
['period2-cusp-z20','-0.75','0','3.4e-20',1800],
['period2-cusp-z100','-0.75','0','3.4e-100',2400],
['period3-interior','-0.122561166876653619975245551820735654052','0.744861766619744236593170428604392367240','1e-8',1200],
['period2-cusp-e280','-0.75','0','1e-280',2400],
['period2-cusp-e400','-0.75','0','1e-400',1600]
];
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;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 roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
function pixel(st,x,y,bits=st.bits){const re=align(BigInt(st.re),st.bits,bits),im=align(BigInt(st.im),st.bits,bits),span=align(BigInt(st.span),st.bits,bits),den=BigInt(2*st.width);return[re+roundDiv(span*BigInt(2*x+1-st.width),den),im+roundDiv(span*BigInt(st.height-2*y-1),den)]}
function orbit(cr,ci,bits,limit){const bail=4n<<BigInt(bits);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,bits),zi2=roundShift(zi*zi,bits);zi=roundShift(2n*zr*zi,bits)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,bits)+roundShift(zi*zi,bits)>bail)return n+1}return limit}
function points(w,h){const xs=[0,.25,.5,.75,1].map(t=>Math.min(w-1,Math.max(0,Math.round(t*(w-1))))),ys=[0,.25,.5,.75,1].map(t=>Math.min(h-1,Math.max(0,Math.round(t*(h-1)))));return ys.flatMap(y=>xs.map(x=>[x,y]))}
async function waitApp(){if(frame.contentWindow&&frame.contentWindow.__MANDEL_TEST__)return;await new Promise(r=>frame.addEventListener('load',r,{once:true}));const t=performance.now();while(!frame.contentWindow.__MANDEL_TEST__&&performance.now()-t<15000)await new Promise(r=>setTimeout(r,50));if(!frame.contentWindow.__MANDEL_TEST__)throw new Error('test hook not available')}
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++}
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));
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});
})();