eee
This commit is contained in:
parent
0be5ce3ce3
commit
178cac581c
28 changed files with 1695 additions and 2746 deletions
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"environment": "Chromium 144 + SwiftShader/Vulkan, compute-only WebGPU",
|
||||
"size": [320, 220],
|
||||
"iterations": 750,
|
||||
"lateEscapeThreshold": 24,
|
||||
"cases": [
|
||||
{"name": "reset", "center": [-0.5, 0.0], "span": 3.4, "gpuMs": 203.3, "sampledPixels": 1200, "classificationMismatchVsCpuDouble": 0, "escapeIterationMismatchVsCpuDouble": 2},
|
||||
{"name": "seahorse-low", "center": [-0.743643887037151, 0.13182590420533], "span": 0.1, "gpuMs": 160.7, "sampledPixels": 1200, "classificationMismatchVsCpuDouble": 0, "escapeIterationMismatchVsCpuDouble": 0},
|
||||
{"name": "cusp-low", "center": [0.25, 0.0], "span": 0.85, "gpuMs": 87.8, "sampledPixels": 1200, "classificationMismatchVsCpuDouble": 0, "escapeIterationMismatchVsCpuDouble": 0}
|
||||
],
|
||||
"stripOrderTest": {"pixels": 6144, "fullPassVs4RowQueuedStripsMismatch": 0},
|
||||
"note": "GPU time is software-GPU time and is only a relative engineering check."
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"samples": 60000,
|
||||
"mixture": "global Mandelbrot bounds + targeted known boundary neighborhoods",
|
||||
"maxIter": 900,
|
||||
"acceptedRule": "f32 escape iteration < 24",
|
||||
"acceptedSamples": 33637,
|
||||
"acceptedEscapeIterationMismatchVsCpuDouble": 0
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"DIRECT_F32_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"DIRECT_ACCURATE_SEED_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"DIRECT_DS_CORRECT_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"FAST_PERTURB_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"DEEP_PERTURB_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"DEEP_CORRECT_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"COLOR_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"PRESENT_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
"BLA_GPU_FAST_FRAME_WGSL": {
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"DIRECT_F32_WGSL": "7146fb3a0969a4f29b3e47d194e5263f8f7da017d84dcf98bddcb29d3c057922",
|
||||
"FAST_PERTURB_WGSL": "b9db140f03fbd0aa36a5ca7dbbcbfec471e1587ebc632953f9eb73a35fb9a978",
|
||||
"DEEP_PERTURB_WGSL": "a631d814fd1c1aa3d4f8af31dcbabaebc5795355e9d72a223a000beb16f96a65",
|
||||
"DEEP_CORRECT_WGSL": "112d09df6e7fa9b7aab9e43da2f38c9b0aad45e44296f40054d0e9a7c6077b88",
|
||||
"COLOR_WGSL": "4ef141430a5255cc8b78459b1a50fdd2ff31cf37eeb872a957d43794c59b09a3",
|
||||
"PRESENT_WGSL": "651bf13de25b023c3f0ab0d0287b1d27cfd8e11677aca53629ce0a8526c7c5bc",
|
||||
"BLA_GPU_FAST_CANDIDATE_WGSL": "2a1d2d6ee1fe2ecc55822e58131519f1385d82bcdbd7fc852dc2e39d67ff0019",
|
||||
"BLA_GPU_FAST_FRAME_WGSL": "2e4cdbd31d60c22d87fb04847af4d2c1d6de7d213da5cdd27234e5d8f9672553"
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"scenario": "classic Seahorse Valley, 654x690, span 3.4e-13, 2900 iterations, 64-point production gate equivalent",
|
||||
"oldValidate64Ms": 446.9,
|
||||
"oldTableBuildMs": 58.4,
|
||||
"newBuildOnlyMs": 60.0,
|
||||
"newTableBuildMs": 59.4,
|
||||
"estimatedSynchronousPrepSavedMs": 386.9,
|
||||
"note": "Node/V8 worker-model timing, not GPU frame time. The production path now builds the table only; the 64-point decision remains on the GPU probe."
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"version": "9af1429215fe356104bcbe991c98d101bbf241a68a38232533918454505014d3",
|
||||
"DIRECT_F32_WGSL": "7146fb3a0969a4f29b3e47d194e5263f8f7da017d84dcf98bddcb29d3c057922",
|
||||
"DIRECT_ACCURATE_SEED_WGSL": "478146d2f8a0bb5608850dcbbdfab183bc6e9105eab2afb4122892f7353349c3",
|
||||
"DIRECT_DS_CORRECT_WGSL": "6c61934c796c67f08ebdee0b7156c8c8d891104f3fd1d2408bad16d9a76f7c00",
|
||||
"FAST_PERTURB_WGSL": "b9db140f03fbd0aa36a5ca7dbbcbfec471e1587ebc632953f9eb73a35fb9a978",
|
||||
"DEEP_PERTURB_WGSL": "a631d814fd1c1aa3d4f8af31dcbabaebc5795355e9d72a223a000beb16f96a65",
|
||||
"DEEP_PERTURB_POSTSTATS_WGSL": "364d916da3d90b049ac24423a8112dd587ebd756fa97c99a6717f869c6b6cfa1",
|
||||
"DEEP_BUCKET_HIST_WGSL": "3458f908fa239b16667e5c82dae25274d5493d647595230574a3d65622a54aaf",
|
||||
"DEEP_BUCKET_HIST_STATS_WGSL": "b464d503703646e3c990e10a0cb40dacdc3677447b7a35e03c80a50bfca23824",
|
||||
"DEEP_BUCKET_PREFIX_WGSL": "e8fb3f566d69ef87a61f1aa61f4c56ec26614711d7897afa2f8bfdcf5088c3da",
|
||||
"DEEP_BUCKET_SCATTER_WGSL": "7575c4fc5f4d5674dfc3997189212ffc6fbe240a9d8b8b3f8ef883cd4eae47a1",
|
||||
"DEEP_CORRECT_WGSL": "112d09df6e7fa9b7aab9e43da2f38c9b0aad45e44296f40054d0e9a7c6077b88",
|
||||
"DEEP_CORRECT_QUEUE_WGSL": "1337469b0673a4da20670daf220bec5883366e2454acc8c7923e472a5cec7a7f",
|
||||
"COLOR_WGSL": "4ef141430a5255cc8b78459b1a50fdd2ff31cf37eeb872a957d43794c59b09a3",
|
||||
"AA_RESOLVE_WGSL": "f353adafc837c170da1627f8b0cfd924d9748954376e4a427190917da4c92342",
|
||||
"PRESENT_WGSL": "651bf13de25b023c3f0ab0d0287b1d27cfd8e11677aca53629ce0a8526c7c5bc",
|
||||
"BLA_GPU_SHADOW_WGSL": "e830339a150dd5722ca7c58674193174606adaf488bfd817408f2eeb4583514f",
|
||||
"BLA_GPU_CANARY_WGSL": "3c219f657b2890b40f7cfa0297e13a670819d4fed333fcb30cb37196c5755f55",
|
||||
"BLA_GPU_CANARY_APPLY_WGSL": "d02bb3ef007680a687960125ee9dded1fc4b8a4b091a6d924408a2e1f94a05e1",
|
||||
"BLA_GPU_FAST_CANDIDATE_WGSL": "2a1d2d6ee1fe2ecc55822e58131519f1385d82bcdbd7fc852dc2e39d67ff0019",
|
||||
"BLA_GPU_FAST_FRAME_WGSL": "2e4cdbd31d60c22d87fb04847af4d2c1d6de7d213da5cdd27234e5d8f9672553"
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"version": "62ebee65d5378e9379394b24229266de255fa2f241a2eecd31fbab0721d2ae00",
|
||||
"DIRECT_F32_WGSL": "7146fb3a0969a4f29b3e47d194e5263f8f7da017d84dcf98bddcb29d3c057922",
|
||||
"DIRECT_ACCURATE_SEED_WGSL": "478146d2f8a0bb5608850dcbbdfab183bc6e9105eab2afb4122892f7353349c3",
|
||||
"DIRECT_DS_CORRECT_WGSL": "6c61934c796c67f08ebdee0b7156c8c8d891104f3fd1d2408bad16d9a76f7c00",
|
||||
"FAST_PERTURB_WGSL": "2cc5841740456f10ed3f64005e72ac6ff87d2c38926260a1fcb1521a389013a0",
|
||||
"DEEP_PERTURB_WGSL": "a631d814fd1c1aa3d4f8af31dcbabaebc5795355e9d72a223a000beb16f96a65",
|
||||
"DEEP_PERTURB_POSTSTATS_WGSL": "364d916da3d90b049ac24423a8112dd587ebd756fa97c99a6717f869c6b6cfa1",
|
||||
"DEEP_BUCKET_HIST_WGSL": "3458f908fa239b16667e5c82dae25274d5493d647595230574a3d65622a54aaf",
|
||||
"DEEP_BUCKET_HIST_STATS_WGSL": "b464d503703646e3c990e10a0cb40dacdc3677447b7a35e03c80a50bfca23824",
|
||||
"DEEP_BUCKET_PREFIX_WGSL": "e8fb3f566d69ef87a61f1aa61f4c56ec26614711d7897afa2f8bfdcf5088c3da",
|
||||
"DEEP_BUCKET_SCATTER_WGSL": "7575c4fc5f4d5674dfc3997189212ffc6fbe240a9d8b8b3f8ef883cd4eae47a1",
|
||||
"DEEP_CORRECT_WGSL": "112d09df6e7fa9b7aab9e43da2f38c9b0aad45e44296f40054d0e9a7c6077b88",
|
||||
"DEEP_CORRECT_QUEUE_WGSL": "1337469b0673a4da20670daf220bec5883366e2454acc8c7923e472a5cec7a7f",
|
||||
"COLOR_WGSL": "4ef141430a5255cc8b78459b1a50fdd2ff31cf37eeb872a957d43794c59b09a3",
|
||||
"AA_RESOLVE_WGSL": "f353adafc837c170da1627f8b0cfd924d9748954376e4a427190917da4c92342",
|
||||
"PRESENT_WGSL": "651bf13de25b023c3f0ab0d0287b1d27cfd8e11677aca53629ce0a8526c7c5bc",
|
||||
"BLA_GPU_SHADOW_WGSL": "e830339a150dd5722ca7c58674193174606adaf488bfd817408f2eeb4583514f",
|
||||
"BLA_GPU_CANARY_WGSL": "3c219f657b2890b40f7cfa0297e13a670819d4fed333fcb30cb37196c5755f55",
|
||||
"BLA_GPU_CANARY_APPLY_WGSL": "d02bb3ef007680a687960125ee9dded1fc4b8a4b091a6d924408a2e1f94a05e1",
|
||||
"BLA_GPU_FAST_CANDIDATE_WGSL": "2a1d2d6ee1fe2ecc55822e58131519f1385d82bcdbd7fc852dc2e39d67ff0019",
|
||||
"BLA_GPU_FAST_FRAME_WGSL": "2e4cdbd31d60c22d87fb04847af4d2c1d6de7d213da5cdd27234e5d8f9672553"
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"version": "7d56b1b4e3b54eff64b84e98607abb32eb23ae3ab60e1cd6f66f3223993d8c9f",
|
||||
"DIRECT_F32_WGSL": "7146fb3a0969a4f29b3e47d194e5263f8f7da017d84dcf98bddcb29d3c057922",
|
||||
"ACTIVE_PREPARE_WGSL": "27bb3af8310bd0784888f2a648896e1fcf5872b4a9a3ade883ebe95a4868fa6e",
|
||||
"DIRECT_ACTIVE_INIT_WGSL": "a57887e12e91ecdc0272c97e2097268f4b5f05cb467e8a77adfc5263331b2b9b",
|
||||
"DIRECT_ACTIVE_CONTINUE_WGSL": "b0fbfcb9ffccf37b96947ec21c883ad4ce984bbb331c7a3b59a54cdc3e300f93",
|
||||
"ACTIVE_COMPARE_WGSL": "81c37558fd8937fd0560ccb41f73ed66ac5d98d023153ed0b82d8b5a075c3de9",
|
||||
"DIRECT_ACCURATE_SEED_WGSL": "247e1ff1c3d400daf358144b55c6f50c99d337b4e886e0b957e371d39f9c87cf",
|
||||
"DIRECT_DS_CORRECT_WGSL": "e31fa2f8347acc5ea2eb11bbcb3b05a36a3ba72cc1446ded141b1b4207b873e3",
|
||||
"DIRECT_DS_QUEUE_BUILD_WGSL": "95cfcf9cb80e53bd112b0868c035564cb812bdd957de583e2d3ee052bceb1a15",
|
||||
"DIRECT_DS_CORRECT_QUEUE_WGSL": "bb76e1ae09c7494a53e2ae4cad46807f2c671c5afa5bf5649981e1ab60cc2f3e",
|
||||
"FAST_PERTURB_WGSL": "2cc5841740456f10ed3f64005e72ac6ff87d2c38926260a1fcb1521a389013a0",
|
||||
"FAST_PERTURB_POSTSTATS_WGSL": "3680d561d80af6327640d9b5caa127ad67f6a6fc9e499af6dbb98ccdaa261d0c",
|
||||
"DEEP_PERTURB_WGSL": "a631d814fd1c1aa3d4f8af31dcbabaebc5795355e9d72a223a000beb16f96a65",
|
||||
"DEEP_PERTURB_POSTSTATS_WGSL": "364d916da3d90b049ac24423a8112dd587ebd756fa97c99a6717f869c6b6cfa1",
|
||||
"DEEP_ACTIVE_INIT_WGSL": "756373375ad9b21a884ac6906ecf72990612a86ecfde77caf5150fb37cf75604",
|
||||
"DEEP_ACTIVE_RESUME_INIT_WGSL": "a25c0b74bc66b7b91269bc6195ddde8b295f659ea72ec415fcf3fda3d66c126e",
|
||||
"DEEP_ACTIVE_CONTINUE_WGSL": "afe49e748e34d78c01090015c0e2ce5971c0ec6ab7587bba6c25631266acb4f0",
|
||||
"PERTURB_CERTIFIED_INTERIOR_WGSL": "3bd61a7f5cf96e4e52797f5f00a20c848a01fd3ff8eca9a939e8ae87f37568a5",
|
||||
"DEEP_BUCKET_HIST_WGSL": "3458f908fa239b16667e5c82dae25274d5493d647595230574a3d65622a54aaf",
|
||||
"DEEP_BUCKET_HIST_STATS_WGSL": "b464d503703646e3c990e10a0cb40dacdc3677447b7a35e03c80a50bfca23824",
|
||||
"DEEP_BUCKET_PREFIX_WGSL": "e8fb3f566d69ef87a61f1aa61f4c56ec26614711d7897afa2f8bfdcf5088c3da",
|
||||
"DEEP_BUCKET_SCATTER_WGSL": "7575c4fc5f4d5674dfc3997189212ffc6fbe240a9d8b8b3f8ef883cd4eae47a1",
|
||||
"DEEP_CORRECT_WGSL": "112d09df6e7fa9b7aab9e43da2f38c9b0aad45e44296f40054d0e9a7c6077b88",
|
||||
"DEEP_CORRECT_QUEUE_WGSL": "1580c6bab7d1aa2c4eb2c7e8d6f3cc4d32b90b86b9264561269e1857d2826bae",
|
||||
"UNKNOWN_STATS_WGSL": "df1967bcdacf29831403fa53aadaae17ed138b36d1988cf20f6a6ed3e43e519a",
|
||||
"SYMMETRY_COPY_WGSL": "658e1cf2b9f40d8e759abe2269c041f582a80727cb24e2f48a405cb8676d70e1",
|
||||
"FAILURE_TILE_MAP_WGSL": "8308f57151b119363ad456a7e1255c5445ed5e022ccf53e923a4b13ab2a1ca91",
|
||||
"COLOR_WGSL": "4ef141430a5255cc8b78459b1a50fdd2ff31cf37eeb872a957d43794c59b09a3",
|
||||
"AA_RESOLVE_WGSL": "f353adafc837c170da1627f8b0cfd924d9748954376e4a427190917da4c92342",
|
||||
"PRESENT_WGSL": "651bf13de25b023c3f0ab0d0287b1d27cfd8e11677aca53629ce0a8526c7c5bc",
|
||||
"BLA_GPU_SHADOW_WGSL": "e830339a150dd5722ca7c58674193174606adaf488bfd817408f2eeb4583514f",
|
||||
"BLA_GPU_CANARY_WGSL": "3c219f657b2890b40f7cfa0297e13a670819d4fed333fcb30cb37196c5755f55",
|
||||
"BLA_GPU_CANARY_APPLY_WGSL": "d02bb3ef007680a687960125ee9dded1fc4b8a4b091a6d924408a2e1f94a05e1",
|
||||
"BLA_GPU_FAST_CANDIDATE_WGSL": "2a1d2d6ee1fe2ecc55822e58131519f1385d82bcdbd7fc852dc2e39d67ff0019",
|
||||
"BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL": "f55368ed55cda77af550ed2bf38b4027b21e08b6c17d67997e61fcbea17a6c84",
|
||||
"BLA_GPU_FAST_FRAME_WGSL": "1d4f2c2105f0c50a3b5fb1368b262cfe6e0114e20333acdd1b6d8e8093434290",
|
||||
"BLA_GPU_FAST_FRAME_POSTSTATS_WGSL": "6e1d0ac58a1b785075eddb525f565ca8349f50bab2189a537f62bec6a987f6ea"
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"date": "2026-08-25",
|
||||
"scope": "BigInt reference-worker recurrence only; not a browser, GPU, canvas, or end-to-end benchmark",
|
||||
"runtime": "Node worker model",
|
||||
"corpus": {
|
||||
"points": 240,
|
||||
"iterations": 700,
|
||||
"precisionBits": "192..320",
|
||||
"runs": 7
|
||||
},
|
||||
"equivalence": {
|
||||
"checksumMismatch": 0,
|
||||
"separateStateRegressionCases": 2000,
|
||||
"separateStateRegressionMismatch": 0
|
||||
},
|
||||
"median": {
|
||||
"oldMs": 201.5509,
|
||||
"newMs": 115.3558,
|
||||
"speedup": 1.7472,
|
||||
"timeReduction": 0.4277
|
||||
},
|
||||
"runs": [
|
||||
{"oldMs": 222.9260, "newMs": 137.0568},
|
||||
{"oldMs": 206.2790, "newMs": 113.4711},
|
||||
{"oldMs": 218.0533, "newMs": 146.6120},
|
||||
{"oldMs": 201.5509, "newMs": 120.1586},
|
||||
{"oldMs": 163.7775, "newMs": 115.3558},
|
||||
{"oldMs": 166.6713, "newMs": 111.5755},
|
||||
{"oldMs": 172.1890, "newMs": 107.0833}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
[
|
||||
{
|
||||
"iter": 900,
|
||||
"baselineRows": 31,
|
||||
"v53Waits": 31,
|
||||
"v54FastWaits": 8,
|
||||
"v54BlaWaits": 2
|
||||
},
|
||||
{
|
||||
"iter": 1500,
|
||||
"baselineRows": 19,
|
||||
"v53Waits": 50,
|
||||
"v54FastWaits": 13,
|
||||
"v54BlaWaits": 4
|
||||
},
|
||||
{
|
||||
"iter": 3000,
|
||||
"baselineRows": 9,
|
||||
"v53Waits": 105,
|
||||
"v54FastWaits": 27,
|
||||
"v54BlaWaits": 7
|
||||
},
|
||||
{
|
||||
"iter": 6000,
|
||||
"baselineRows": 4,
|
||||
"v53Waits": 235,
|
||||
"v54FastWaits": 59,
|
||||
"v54BlaWaits": 15
|
||||
},
|
||||
{
|
||||
"iter": 12000,
|
||||
"baselineRows": 2,
|
||||
"v53Waits": 470,
|
||||
"v54FastWaits": 118,
|
||||
"v54BlaWaits": 30
|
||||
}
|
||||
]
|
||||
307
tests/browser_smoke.mjs
Normal file
307
tests/browser_smoke.mjs
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
// Connect to a separate Chromium/Edge instance with remote debugging enabled.
|
||||
// Usage: node tests/browser_smoke.mjs [port=9333] [reset|fast|mid|legacy|axis|black|ui|all]
|
||||
import assert from 'node:assert/strict';
|
||||
import {setTimeout as sleep} from 'node:timers/promises';
|
||||
|
||||
const port=Number(process.argv[2]||9333),selection=process.argv[3]||'all';
|
||||
const url=new URL('../index.html?test',import.meta.url).href;
|
||||
const response=await fetch(`http://127.0.0.1:${port}/json/list`);
|
||||
assert.ok(response.ok,'Remote debugging endpoint unavailable');
|
||||
const pages=(await response.json()).filter(t=>t.type==='page');
|
||||
assert.equal(pages.length,1,'Use one dedicated test tab');
|
||||
const target=pages[0],ws=new WebSocket(target.webSocketDebuggerUrl);
|
||||
await new Promise((resolve,reject)=>{ws.addEventListener('open',resolve,{once:true});ws.addEventListener('error',reject,{once:true})});
|
||||
let serial=0;const pending=new Map(),errors=[];
|
||||
ws.addEventListener('message',event=>{
|
||||
const m=JSON.parse(event.data);
|
||||
if(m.method==='Runtime.exceptionThrown')errors.push(m.params.exceptionDetails.exception?.description||m.params.exceptionDetails.text);
|
||||
const p=pending.get(m.id);if(!p)return;pending.delete(m.id);clearTimeout(p.timer);
|
||||
if(m.error)p.reject(new Error(JSON.stringify(m.error)));else p.resolve(m.result);
|
||||
});
|
||||
function command(method,params={}){
|
||||
const id=++serial;
|
||||
return new Promise((resolve,reject)=>{const timer=setTimeout(()=>{pending.delete(id);reject(new Error(method+' timeout'))},240000);pending.set(id,{resolve,reject,timer});ws.send(JSON.stringify({id,method,params}))});
|
||||
}
|
||||
async function evaluate(expression){
|
||||
const result=await command('Runtime.evaluate',{expression,awaitPromise:true,returnByValue:true});
|
||||
if(result.exceptionDetails)throw new Error(result.exceptionDetails.exception?.description||result.exceptionDetails.text);
|
||||
return result.result.value;
|
||||
}
|
||||
const cases=[
|
||||
{id:'reset',re:'-0.5',im:'0',span:'3.4',renderMode:'fast',backend:'direct'},
|
||||
{id:'fast',re:'-0.743643887037151',im:'0.13182590420533',span:'0.00000000000034',renderMode:'fast',backend:'fast-extended'},
|
||||
{id:'mid',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000001',renderMode:'fast',backend:'fast-extended'},
|
||||
{id:'legacy',re:'-0.743643887037151',im:'0.13182590420533',span:'0.00000000000034',renderMode:'accurate',backend:'fast-extended'},
|
||||
{id:'axis',re:'-0.75',im:'0',span:'0.02',renderMode:'fast',backend:'direct'},
|
||||
{id:'black',re:'-29466147000382485924219538765424656674342940730553342389512209954887705938978413587',im:'5179018789925933872641942859702187326563010594882200888329237785122225251258352',span:'250637324516887125119843167990565159991251418774166207381437546036409',bits:273,baseIter:350,adaptive:true,fixed:true,renderMode:'accurate',backend:'fast-extended'}
|
||||
];
|
||||
assert.ok(['all','kernels','cancel','bench','budget','ui'].includes(selection)||cases.some(c=>c.id===selection),'Unknown case');
|
||||
|
||||
// Execute the real production kernels on small, fully specified states.
|
||||
// This isolates terminal and queue behavior from render convergence heuristics.
|
||||
async function kernelCases(){
|
||||
const {renderer:r}=globalThis.__MANDEL_TEST__.kernelAccess();
|
||||
await r.ensureDeepActivePipelines();
|
||||
const d=r.device,B=GPUBufferUsage,created=[],reports=[];
|
||||
const check=(value,message)=>{if(!value)throw new Error(message)};
|
||||
const buffer=(size,usage)=>{const b=d.createBuffer({size:Math.max(4,size),usage});created.push(b);return b};
|
||||
async function read(b,bytes){const staging=buffer(bytes,B.COPY_DST|B.MAP_READ),e=d.createCommandEncoder();e.copyBufferToBuffer(b,0,staging,0,bytes);d.queue.submit([e.finish()]);await staging.mapAsync(GPUMapMode.READ);const out=staging.getMappedRange().slice(0);staging.unmap();return out}
|
||||
async function run({count=1,target=3,chunk=1,passes=3,orbit=[0,0,0,0],error=0,m=0,n=0}){
|
||||
const capacity=Math.max(1,count),params=new ArrayBuffer(96),p=new DataView(params);
|
||||
[capacity,1,capacity,1,0,0,150000,orbit.length-1,0,0,capacity,0].forEach((v,i)=>p.setUint32(i*4,v,true));
|
||||
p.setInt32(52,0,true);p.setUint32(84,target,true);p.setUint32(88,chunk,true);
|
||||
const pb=buffer(96,B.UNIFORM|B.COPY_DST);d.queue.writeBuffer(pb,0,params);
|
||||
const refs=buffer(orbit.length*16,B.STORAGE|B.COPY_DST),refData=new Float32Array(orbit.length*4);orbit.forEach((v,i)=>refData[i*4]=v);d.queue.writeBuffer(refs,0,refData);
|
||||
const states=buffer(capacity*32,B.STORAGE|B.COPY_DST|B.COPY_SRC),initial=new ArrayBuffer(capacity*32),sv=new DataView(initial);
|
||||
for(let i=0;i<count;i++){sv.setUint32(i*32+20,n,true);sv.setUint32(i*32+24,m,true);sv.setFloat32(i*32+28,error,true)}d.queue.writeBuffer(states,0,initial);
|
||||
const queues=[0,1].map(()=>buffer(capacity*4,B.STORAGE|B.COPY_DST|B.COPY_SRC)),counts=[0,1].map(()=>buffer(4,B.STORAGE|B.COPY_DST|B.COPY_SRC)),indirect=buffer(12,B.STORAGE|B.INDIRECT|B.COPY_DST);
|
||||
if(count)d.queue.writeBuffer(queues[0],0,Uint32Array.from({length:count},(_,i)=>i));d.queue.writeBuffer(counts[0],0,Uint32Array.of(count));
|
||||
const meta=buffer(capacity*4,B.STORAGE|B.COPY_SRC|B.COPY_DST),smooth=buffer(capacity*4,B.STORAGE|B.COPY_SRC);
|
||||
const e=d.createCommandEncoder();let input=r.encodeDeepActiveChunks(e,{pbuf:pb,stateB:states,queues,counts,indirect,meta,smooth,input:0,passes,refsB:refs});d.queue.submit([e.finish()]);
|
||||
const active=new Uint32Array(await read(counts[input],4))[0],field=new Uint32Array(await read(meta,capacity*4));
|
||||
const live=active?Array.from(new Uint32Array(await read(queues[input],active*4))).sort((a,b)=>a-b):[];
|
||||
return{active,field,live,states:new DataView(await read(states,capacity*32)),async resume(nextTarget,nextPasses){p.setUint32(84,nextTarget,true);d.queue.writeBuffer(pb,0,params);const e=d.createCommandEncoder();input=r.encodeDeepActiveChunks(e,{pbuf:pb,stateB:states,queues,counts,indirect,meta,smooth,input,passes:nextPasses,refsB:refs});d.queue.submit([e.finish()]);return{active:new Uint32Array(await read(counts[input],4))[0],field:new Uint32Array(await read(meta,capacity*4))}}};
|
||||
}
|
||||
try{
|
||||
let x=await run({orbit:[0,1,2,5]});check(x.active===0&&x.field[0]===(1<<28|3),'escape exactly at target');
|
||||
x=await run({target:1,n:1,m:1,passes:1,error:.002});check(x.active===0&&x.field[0]===(1<<20|1),'terminal error guard');
|
||||
x=await run({target:2,passes:2,orbit:[0]});check(x.active===0&&x.field[0]===(3<<20),'reference exhaustion');
|
||||
x=await run({target:1,passes:1,orbit:[0],m:1});check(x.active===0&&x.field[0]===(3<<20),'reference index guard');
|
||||
for(const count of [0,1,63,64,65])for(const passes of [1,2,3,4]){
|
||||
const target=passes;x=await run({count,target,passes,orbit:Array(10).fill(0)});
|
||||
check(x.active===count,'queue count');check(x.live.every((v,i)=>v===i),'queue membership');
|
||||
for(let i=0;i<count;i++){check(x.field[i]===(6<<20|target),'target metadata');check(x.states.getUint32(i*32+20,true)===target,'retained iteration')}
|
||||
const next=await x.resume(target+1,1);check(next.active===count,'target resubmission');
|
||||
for(let i=0;i<count;i++)check(next.field[i]===(6<<20|target+1),'next target metadata');
|
||||
}
|
||||
reports.push('target escape/error/reference bounds; 0/1/63/64/65 queues; 1–4 passes; retained states');
|
||||
await r.ensureCorrectionPipelines();await r.ensureCompactActivePipelines();
|
||||
{
|
||||
const width=17,height=5,n=width*height,initial=Uint32Array.from({length:n},(_,i)=>i%7===6?1<<28:(i%7)<<20|i);
|
||||
const p=new Uint32Array(24);p.set([9,3,width,height,3,1,100,0,0,2,width,width+3]);
|
||||
const pb=buffer(96,B.UNIFORM|B.COPY_DST),meta=buffer(n*4,B.STORAGE|B.COPY_SRC|B.COPY_DST),buckets=buffer(96,B.STORAGE|B.COPY_DST),stats=buffer(32,B.STORAGE|B.COPY_SRC|B.COPY_DST),queue=buffer(n*4,B.STORAGE|B.COPY_SRC),indirect=buffer(16,B.STORAGE|B.INDIRECT);
|
||||
d.queue.writeBuffer(pb,0,p);d.queue.writeBuffer(meta,0,initial);const e=d.createCommandEncoder();
|
||||
r.encodeDeepBucketHistogram(e,{pbuf:pb,meta,bucketState:buckets,w:9,h:3});r.encodeDeepBucketPrefix(e,{bucketState:buckets,queueStats:stats,indirect});r.encodeDeepBucketScatter(e,{pbuf:pb,meta,bucketState:buckets,queueStats:stats,queue,w:9,h:3});d.queue.submit([e.finish()]);
|
||||
const q=new Uint32Array(await read(stats,32)),expected=[];
|
||||
for(let y=1;y<4;y++)for(let x=3;x<12;x++){const i=y*width+x;if(i%7>=1&&i%7<=5)expected.push(i)}
|
||||
check(q[0]===expected.length&&q[1]===0&&q[2]===expected.length,'tile queue counts');
|
||||
const actual=Array.from(new Uint32Array(await read(queue,q[0]*4))).sort((a,b)=>a-b);check(JSON.stringify(actual)===JSON.stringify(expected),'tile queue exact membership');
|
||||
if(!r.precisionScatter)r.precisionScatter=await r.makeCompute(await r.module('precision-scatter-test',globalThis.MANDEL_WEBGPU_KERNELS.PRECISION_SCATTER_WGSL));
|
||||
const results=new Uint32Array(n*4);for(let i=0;i<n;i++)results.set([i,1<<28|7,0,i%2],i*4);
|
||||
const input=buffer(results.byteLength,B.STORAGE|B.COPY_DST),sm=buffer(n*4,B.STORAGE);d.queue.writeBuffer(input,0,results);
|
||||
const enc=d.createCommandEncoder(),bg=d.createBindGroup({layout:r.precisionScatter.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:input}},{binding:1,resource:{buffer:meta}},{binding:2,resource:{buffer:sm}}]}),pass=enc.beginComputePass();pass.setPipeline(r.precisionScatter);pass.setBindGroup(0,bg);pass.dispatchWorkgroups(2);pass.end();d.queue.submit([enc.finish()]);
|
||||
const applied=new Uint32Array(await read(meta,n*4));for(let i=0;i<n;i++)check(applied[i]===(i%2&&i%7>=1&&i%7<=5?(1<<28|7):initial[i]),'scatter preserves completed/operation-limit pixels');
|
||||
}
|
||||
{
|
||||
const pb=buffer(96,B.UNIFORM|B.COPY_DST),p=new Uint32Array(24);p.set([9,1,9,1,0,0,150000,4,0,3,9,0]);p[21]=3;p[22]=1;d.queue.writeBuffer(pb,0,p);
|
||||
const stateB=buffer(3*40,B.STORAGE|B.COPY_SRC),queues=[0,1].map(()=>buffer(12,B.STORAGE|B.COPY_SRC|B.COPY_DST)),counts=[0,1].map(()=>buffer(4,B.STORAGE|B.COPY_SRC|B.COPY_DST)),indirect=buffer(12,B.STORAGE|B.INDIRECT),refsB=buffer(5*16,B.STORAGE),meta=buffer(9*4,B.STORAGE|B.COPY_SRC),sm=buffer(9*4,B.STORAGE);
|
||||
d.queue.writeBuffer(queues[0],0,Uint32Array.of(8,2,5));d.queue.writeBuffer(counts[0],0,Uint32Array.of(3));
|
||||
const e=d.createCommandEncoder();r.encodeDeepActiveResumeInit(e,{pbuf:pb,stateB,queue:queues[0],count:3,pipeline:r.deepCompactInit});
|
||||
const output=r.encodeDeepActiveChunks(e,{pbuf:pb,stateB,queues,counts,indirect,meta,smooth:sm,input:0,passes:3,refsB,pipeline:r.deepCompactContinue});d.queue.submit([e.finish()]);
|
||||
const field=new Uint32Array(await read(meta,36)),states=new DataView(await read(stateB,120));
|
||||
for(let i=0;i<9;i++)check(field[i]===([8,2,5].includes(i)?6<<20|3:0),'compact pixel mapping');
|
||||
for(let i=0;i<3;i++){check(states.getUint32(i*40+32,true)===[8,2,5][i],'compact slot identity');check(states.getUint32(i*40+20,true)===3,'compact state retained')}
|
||||
check(new Uint32Array(await read(counts[output],4))[0]===3,'compact queue retained');
|
||||
}
|
||||
reports.push('tile failure queue exact membership; scatter ownership; compact slot identity');
|
||||
{
|
||||
const width=17,height=5,n=width*height,whole=buffer(n*4,B.STORAGE|B.COPY_SRC),pieces=buffer(n*4,B.STORAGE|B.COPY_SRC),sm=buffer(n*4,B.STORAGE);
|
||||
const unit=1n<<256n,snap={bits:256,re:-unit/2n,im:0n,span:34n*unit/10n},e=d.createCommandEncoder();
|
||||
const encode=(meta,x,y,w,h)=>{const pb=buffer(96,B.UNIFORM|B.COPY_DST);d.queue.writeBuffer(pb,0,r.directParams(w,h,width,height,x,y,100,snap,.5,.5,0,width,y*width+x));r.encodeDirectNumeric(e,{pbuf:pb,meta,smooth:sm,w,h})};
|
||||
encode(whole,0,0,width,height);
|
||||
for(let y=0;y<height;y+=2)for(let x=0;x<width;x+=6)encode(pieces,x,y,Math.min(6,width-x),Math.min(2,height-y));
|
||||
d.queue.submit([e.finish()]);const expected=new Uint32Array(await read(whole,n*4)),actual=new Uint32Array(await read(pieces,n*4));
|
||||
check(expected.every((v,i)=>v!==0&&v===actual[i]),'direct tiles reproduce whole-frame coordinates');
|
||||
}
|
||||
reports.push('direct tile coordinates and full pixel coverage');
|
||||
return reports;
|
||||
}finally{await d.queue.onSubmittedWorkDone();for(const b of created)b.destroy()}
|
||||
}
|
||||
// Read the actual texture selected by presentation, including animation during
|
||||
// numeric work. UI text alone cannot prove that colors are changing.
|
||||
async function displayedColorHash(){
|
||||
const r=globalThis.__MANDEL_TEST__.kernelAccess().renderer,f=r.frame,d=r.device;
|
||||
const texture=r.colorSourceVisible?r.colorSource.texture:f.front;
|
||||
const pitch=Math.ceil(f.w*4/256)*256,b=d.createBuffer({size:pitch*f.h,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ});
|
||||
try{
|
||||
const e=d.createCommandEncoder();e.copyTextureToBuffer({texture},{buffer:b,bytesPerRow:pitch},[f.w,f.h]);d.queue.submit([e.finish()]);
|
||||
await b.mapAsync(GPUMapMode.READ);const data=new Uint8Array(b.getMappedRange()),pixels=new Uint8Array(f.n*4);
|
||||
for(let y=0;y<f.h;y++)pixels.set(data.subarray(y*pitch,y*pitch+f.w*4),y*f.w*4);
|
||||
const digest=await crypto.subtle.digest('SHA-256',pixels);b.unmap();return Array.from(new Uint8Array(digest),x=>x.toString(16).padStart(2,'0')).join('');
|
||||
}finally{b.destroy()}
|
||||
}
|
||||
try{
|
||||
await command('Runtime.enable');
|
||||
await command('Emulation.setDeviceMetricsOverride',{width:320,height:240,deviceScaleFactor:1,mobile:false});
|
||||
await command('Page.navigate',{url});
|
||||
for(let i=0;i<100;i++){if(await evaluate('!!globalThis.__MANDEL_TEST__'))break;await sleep(100)}
|
||||
const ready=await evaluate('globalThis.__MANDEL_TEST__.ensureGpuReady()');
|
||||
assert.equal(ready.ready,true,JSON.stringify(ready));
|
||||
if(selection==='ui'||selection==='all'){
|
||||
const initial=await evaluate('__MANDEL_TEST__.waitForNumericComplete()');
|
||||
assert.equal(initial.renderClock.status,'complete');
|
||||
assert.ok(initial.lastRender>=initial.previewMs+initial.refineMs);
|
||||
if(initial.previewMs)assert.ok(initial.lastRender>=initial.previewMs+initial.refineMs+100,'include refinement delay');
|
||||
assert.equal(await evaluate('!!document.querySelector("#renderMode")'),false);
|
||||
const original=await evaluate('__MANDEL_TEST__.fieldHashes()');
|
||||
const originalColor=await evaluate(`(${displayedColorHash.toString()})()`);
|
||||
await evaluate('document.querySelector("#colorAuto").click()');
|
||||
await sleep(350);
|
||||
assert.notEqual(await evaluate(`(${displayedColorHash.toString()})()`),originalColor,'animation changes GPU pixels');
|
||||
assert.deepEqual(await evaluate('__MANDEL_TEST__.fieldHashes()'),original,'animation preserves numeric data');
|
||||
const idle=await evaluate('__MANDEL_TEST__.state()');
|
||||
assert.equal(idle.cycle,initial.cycle);assert.equal(idle.lastRender,initial.lastRender);
|
||||
await evaluate('document.querySelector("#colorAuto").click()');
|
||||
await sleep(150);
|
||||
const stopped=await evaluate('__MANDEL_TEST__.state().shift');await sleep(150);
|
||||
assert.equal(await evaluate('__MANDEL_TEST__.state().shift'),stopped);
|
||||
assert.equal(await evaluate('!!__MANDEL_TEST__.kernelAccess().renderer.colorSource'),false,'release animation memory');
|
||||
await evaluate('document.querySelector("#colorAuto").click()');
|
||||
const ongoing=evaluate(`__MANDEL_TEST__.setView(${JSON.stringify({...cases[1],bits:448,baseIter:512,adaptive:false})})`);
|
||||
let active=false;
|
||||
for(let i=0;i<1000;i++){
|
||||
const state=await evaluate('__MANDEL_TEST__.state()');
|
||||
if(state.rendering&&state.provisional&&state.engine.includes('数値補修')){active=true;break}
|
||||
await sleep(20);
|
||||
}
|
||||
assert.ok(active,'animate during expensive repair');
|
||||
const before=await evaluate(`(${displayedColorHash.toString()})()`);
|
||||
await sleep(350);
|
||||
assert.equal(await evaluate('__MANDEL_TEST__.state().rendering'),true);
|
||||
assert.equal(await evaluate('!!__MANDEL_TEST__.kernelAccess().renderer.colorSourceVisible'),true);
|
||||
assert.notEqual(await evaluate(`(${displayedColorHash.toString()})()`),before,'color changes while numeric work is running');
|
||||
assert.match(await evaluate('document.querySelector("#render").textContent'),/^描画中… /);
|
||||
// A manual change made while rendering must be drained at completion.
|
||||
await evaluate('document.querySelector("#colorAuto").click();const shift=document.querySelector("#shift");shift.value="0.65";shift.dispatchEvent(new Event("input",{bubbles:true}))');
|
||||
await ongoing;const final=await evaluate('__MANDEL_TEST__.waitForNumericComplete()');
|
||||
for(let i=0;i<100;i++){if(await evaluate('!__MANDEL_TEST__.state().recolorPending&&!__MANDEL_TEST__.state().recoloring'))break;await sleep(20)}
|
||||
assert.equal(final.renderClock.status,'complete');assert.ok(final.lastRender>2000);
|
||||
assert.ok(final.lastRender>=final.refineMs);
|
||||
const timing=await evaluate('({label:document.querySelector("#render").textContent,now:performance.now(),state:__MANDEL_TEST__.state(),front:__MANDEL_TEST__.kernelAccess().renderer.frontColorKey})');
|
||||
assert.equal(timing.label,(final.lastRender/1000).toFixed(2)+' s');
|
||||
assert.ok(timing.now-final.renderClock.startedAt-final.lastRender<1000,'time is current without starting another render');
|
||||
assert.equal(Number(timing.front.split(':')[2]),0.65);assert.equal(timing.state.recolorPending,false);
|
||||
const hash=await evaluate('__MANDEL_TEST__.fieldHashes()');
|
||||
const view={...cases[1],bits:448,baseIter:512,adaptive:false};
|
||||
await evaluate(`__MANDEL_TEST__.setView(${JSON.stringify(view)})`);await evaluate('__MANDEL_TEST__.waitForNumericComplete()');
|
||||
assert.deepEqual(await evaluate('__MANDEL_TEST__.fieldHashes()'),hash,'animated and ordinary renders agree');
|
||||
// Old preferences and shared URLs migrate to the sole supported renderer.
|
||||
const legacyUrl=await evaluate('localStorage.setItem("mandelbrot.renderMode","accurate");const u=new URL(location.href),p=new URLSearchParams(u.hash.slice(1));p.set("rm","accurate");u.hash=p.toString();u.href');
|
||||
await command('Page.navigate',{url:legacyUrl});await command('Page.reload');await sleep(150);
|
||||
await evaluate('__MANDEL_TEST__.waitForNumericComplete()');
|
||||
assert.equal(await evaluate('__MANDEL_TEST__.state().renderMode'),'fast');
|
||||
assert.equal(await evaluate('localStorage.getItem("mandelbrot.renderMode")'),null);
|
||||
console.log(JSON.stringify({ui:'PASS: animation idle/during repair, stop/manual color, numeric equality, total time, legacy migration',renderMs:final.lastRender}));
|
||||
}
|
||||
if(selection==='kernels'||selection==='all'){
|
||||
await evaluate('globalThis.__MANDEL_TEST__.waitForNumericComplete()');
|
||||
console.log(JSON.stringify({kernels:await evaluate(`(${kernelCases.toString()})()`)}));
|
||||
}
|
||||
for(const scenario of cases.filter(c=>selection==='all'||c.id===selection)){
|
||||
const small=['axis','black'].includes(scenario.id);
|
||||
await command('Emulation.setDeviceMetricsOverride',{width:small?64:320,height:small?48:240,deviceScaleFactor:1,mobile:false});
|
||||
const view={bits:448,baseIter:512,adaptive:false,processMode:'standard',...scenario};
|
||||
const result=await evaluate(`(async()=>{const t=globalThis.__MANDEL_TEST__;await t.setView(${JSON.stringify(view)});const state=await t.waitForNumericComplete({timeout:180000});return {state,hashes:await t.fieldHashes(),diagnostics:t.gpuDiagnostics()}})()`);
|
||||
assert.equal(result.state.numericalFailures,0);
|
||||
assert.equal(result.state.frontierConverged,true);
|
||||
assert.equal(result.state.historyReady,true);
|
||||
assert.ok(result.state.backend.endsWith(scenario.backend),result.state.backend);
|
||||
assert.ok(result.state.frontierMaxDispatchWork<=4000000);
|
||||
assert.equal(result.diagnostics.lossReason,'');
|
||||
assert.deepEqual(result.diagnostics.uncapturedErrors,[]);
|
||||
assert.equal(result.diagnostics.compilation.flatMap(c=>c.messages).filter(m=>m.type==='error').length,0);
|
||||
console.log(JSON.stringify({case:scenario.id,backend:result.state.backend,iter:result.state.effectiveIter,ms:result.state.lastRender,hashes:result.hashes}));
|
||||
if(scenario.id!=='reset'){
|
||||
const n=result.hashes.pixels,indices=Array.from(new Set([0,n-1,Math.floor(n/2),...Array.from({length:9},(_,i)=>(i*7919+127)%n)]));
|
||||
const samples=await evaluate(`globalThis.__MANDEL_TEST__.independentSamples(${JSON.stringify(indices)})`);
|
||||
let maxSmoothError=0;
|
||||
for(const p of samples){
|
||||
assert.equal(p.accepted,1,'independent sample inconclusive');
|
||||
assert.equal((p.packed>>>28)===1,(p.oracle>>>28)===1,JSON.stringify(p));
|
||||
if((p.oracle>>>28)===1){
|
||||
assert.equal(p.packed&0xfffff,p.oracle&0xfffff,JSON.stringify(p));
|
||||
// WGSL reconstructs magnitude and evaluates log2 in f32. Compare
|
||||
// smoothing in representable units, separately from exact escape n.
|
||||
const ulp=2**(Math.floor(Math.log2(Math.max(1,Math.abs(p.oracleSmooth))))-23);
|
||||
const error=Math.abs(p.smoothed-p.oracleSmooth);maxSmoothError=Math.max(maxSmoothError,error);
|
||||
// Keep ULP checks for the established FAST/axis samples. The new
|
||||
// cap case and former Deep-only black case use approximate smoothing
|
||||
// in the unified renderer; classification and escape n remain exact
|
||||
// checks, and their smoothing deviations are explicitly reported.
|
||||
if(!['mid','black'].includes(scenario.id))assert.ok(error<=8*ulp,JSON.stringify(p));
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify({case:scenario.id,independentSamples:samples.length,maxSmoothError}));
|
||||
if(scenario.id==='mid'){
|
||||
const cap=await evaluate('(async()=>{const r=__MANDEL_TEST__.kernelAccess().renderer,field=await r.readFieldAll();let below=0,remaining=0;for(const p of field.meta){if((p>>>28)===0){remaining++;if((p>>>20)!==6||(p&0xfffff)!==150000)below++}}return {below,remaining,png:await __MANDEL_TEST__.pngRoundTrip()}})()');
|
||||
assert.equal(result.state.effectiveIter,150000);assert.equal(cap.below,0);assert.ok(cap.remaining>0);assert.equal(cap.png.mismatches,0);
|
||||
}
|
||||
}
|
||||
if(scenario.id==='reset'){
|
||||
// Returning to the same view must reproduce both numerical buffers.
|
||||
await evaluate('(async()=>{const t=globalThis.__MANDEL_TEST__;await t.panPixels(8,0);await t.waitForNumericComplete();await t.panPixels(-8,0);await t.waitForNumericComplete()})()');
|
||||
assert.deepEqual(await evaluate('globalThis.__MANDEL_TEST__.fieldHashes()'),result.hashes);
|
||||
const exports=await evaluate('(async()=>{const t=globalThis.__MANDEL_TEST__;return [await t.smokeExportTile({w:32,h:24}),await t.smokeExportTile({w:32,h:24,ss:2})]})()');
|
||||
for(const item of exports){assert.equal(item.length,item.expected);assert.ok(item.checksum)}
|
||||
const png=await evaluate('globalThis.__MANDEL_TEST__.pngRoundTrip()');
|
||||
assert.equal(png.mismatches,0);assert.equal(png.width,320);assert.equal(png.height,240);
|
||||
console.log(JSON.stringify({png}));
|
||||
}
|
||||
}
|
||||
if(selection==='all'||selection==='budget'){
|
||||
await command('Emulation.setDeviceMetricsOverride',{width:64,height:48,deviceScaleFactor:1,mobile:false});
|
||||
for(const baseIter of [512,4096]){
|
||||
const view={...cases[3],bits:448,baseIter,adaptive:false,continuationBudget:4096};
|
||||
const result=await evaluate(`(async()=>{const t=globalThis.__MANDEL_TEST__;await t.setView(${JSON.stringify(view)});const state=await t.waitForNumericComplete();const r=t.kernelAccess().renderer,field=await r.readFieldAll();let below=0,above=0;for(const p of field.meta){if((p>>>28)===0&&(p>>>20)===6&&(p&0xfffff)!==4096)below++;if((p&0xfffff)>4096)above++}return {state,below,above,png:await t.pngRoundTrip()}})()`);
|
||||
assert.equal(result.state.effectiveIter,4096);assert.equal(result.state.continuationBudget,4096);assert.equal(result.below,0);assert.equal(result.above,0);assert.equal(result.png.mismatches,0);assert.equal(result.png.iter,4096);
|
||||
}
|
||||
console.log('PASS: explicit finite budget, all retained pixels reach target, PNG uses that budget');
|
||||
}
|
||||
if(selection==='all'||selection==='cancel'){
|
||||
await command('Emulation.setDeviceMetricsOverride',{width:320,height:240,deviceScaleFactor:1,mobile:false});
|
||||
const normal={...cases[0],bits:448,baseIter:512,adaptive:false};
|
||||
await evaluate(`globalThis.__MANDEL_TEST__.setView(${JSON.stringify(normal)})`);
|
||||
const expected=await evaluate('globalThis.__MANDEL_TEST__.fieldHashes()');
|
||||
const ongoing=evaluate(`globalThis.__MANDEL_TEST__.setView(${JSON.stringify({...cases[1],bits:448,baseIter:512,adaptive:false})})`).then(value=>({value}),error=>({error:String(error)}));
|
||||
let during=false;
|
||||
for(let i=0;i<1000;i++){
|
||||
const s=await evaluate('globalThis.__MANDEL_TEST__.state()');
|
||||
if(s.provisional&&(s.engine.includes('数値補修')||s.gpuStage==='precision scatter')){during=true;break}
|
||||
await sleep(20);
|
||||
}
|
||||
assert.ok(during,'exercise cancellation during CPU repair, not after completion');
|
||||
const provisionalPng=await evaluate('globalThis.__MANDEL_TEST__.pngRoundTrip().then(()=>false,()=>true)');
|
||||
assert.equal(provisionalPng,true,'unfinished field must not enter complete PNG path');
|
||||
await evaluate(`globalThis.__MANDEL_TEST__.setView(${JSON.stringify(normal)})`);
|
||||
await ongoing;
|
||||
await evaluate('globalThis.__MANDEL_TEST__.waitForNumericComplete()');
|
||||
assert.deepEqual(await evaluate('globalThis.__MANDEL_TEST__.fieldHashes()'),expected);
|
||||
assert.deepEqual((await evaluate('globalThis.__MANDEL_TEST__.gpuDiagnostics()')).uncapturedErrors,[]);
|
||||
console.log('PASS: cancel during CPU repair, preserve newest frame, reject provisional PNG');
|
||||
}
|
||||
assert.deepEqual(errors,[]);
|
||||
if(selection==='bench'){
|
||||
await evaluate('globalThis.__MANDEL_TEST__.waitForNumericComplete()');
|
||||
for(const scenario of cases.filter(c=>['reset','fast'].includes(c.id))){
|
||||
let expected;
|
||||
for(let run=0;run<4;run++){
|
||||
const view={bits:448,baseIter:512,adaptive:false,processMode:'standard',...scenario};
|
||||
const record=await evaluate(`(async()=>{const t=globalThis.__MANDEL_TEST__,before=t.efficiency();await t.setView(${JSON.stringify(view)});const s=await t.waitForNumericComplete(),after=t.efficiency(),metrics={};for(const k of Object.keys(after))metrics[k]=k==='activeGpuBytes'?after[k]:after[k]-before[k];return {ms:s.lastRender,iter:s.effectiveIter,numericalFailures:s.numericalFailures,operationLimit:s.unknownReasons.operationLimit,firstDisplayMs:s.generationMetrics.at(-1)?.firstDisplayMs,metrics,hashes:await t.fieldHashes()}})()`);
|
||||
if(expected)assert.deepEqual(record.hashes,expected,'repeated view changed numeric output');expected=record.hashes;
|
||||
console.log(JSON.stringify({benchmark:scenario.id,run,warmup:run===0,...record}));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ordinary visits should neither expose diagnostics nor invoke legacy gates.
|
||||
await command('Page.navigate',{url:new URL('../index.html',import.meta.url).href});
|
||||
for(let i=0;i<100;i++){if(await evaluate("!!globalThis.MANDEL_WEBGPU_KERNELS && !!document.querySelector('#engine')"))break;await sleep(100)}
|
||||
assert.equal(await evaluate("typeof globalThis.__MANDEL_TEST__"),'undefined');
|
||||
console.log(`PASS: ${selection}; normal startup exposes no diagnostics`);
|
||||
}catch(error){
|
||||
try{console.error(JSON.stringify(await evaluate('({state:globalThis.__MANDEL_TEST__?.state(),gpu:globalThis.__MANDEL_TEST__?.gpuDiagnostics()})')))}catch{}
|
||||
throw error;
|
||||
}finally{
|
||||
try{await command('Page.navigate',{url:'about:blank'})}catch{}
|
||||
for(const p of pending.values())clearTimeout(p.timer);
|
||||
ws.close();
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import process from 'node:process';
|
||||
|
||||
const port=Number(process.argv[2]||9333);
|
||||
const url=process.argv[3]||'file:///C:/Users/%E3%81%82/Desktop/mandelbrot/index.html?frontier-gate=reset';
|
||||
const timeoutMs=Number(process.argv[4]||300000);
|
||||
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
|
||||
async function endpoint(path,options){
|
||||
const response=await fetch(`http://127.0.0.1:${port}${path}`,options);
|
||||
if(!response.ok)throw new Error(`CDP HTTP ${response.status}: ${await response.text()}`);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
const deadline=Date.now()+timeoutMs;
|
||||
let version;
|
||||
while(Date.now()<deadline){
|
||||
try{version=await endpoint('/json/version');break}catch{await sleep(100)}
|
||||
}
|
||||
if(!version)throw new Error('Edge remote debugging endpoint did not start');
|
||||
|
||||
let target;
|
||||
try{target=await endpoint(`/json/new?${encodeURIComponent(url)}`,{method:'PUT'})}
|
||||
catch{const targets=await endpoint('/json/list');target=targets.find(item=>item.type==='page')}
|
||||
if(!target?.webSocketDebuggerUrl)throw new Error('No debuggable page target');
|
||||
|
||||
const ws=new WebSocket(target.webSocketDebuggerUrl);
|
||||
await new Promise((resolve,reject)=>{ws.addEventListener('open',resolve,{once:true});ws.addEventListener('error',reject,{once:true})});
|
||||
let sequence=0;
|
||||
const pending=new Map();
|
||||
ws.addEventListener('message',event=>{
|
||||
const message=JSON.parse(event.data);
|
||||
if(!message.id)return;
|
||||
const waiter=pending.get(message.id);if(!waiter)return;
|
||||
pending.delete(message.id);
|
||||
if(message.error)waiter.reject(new Error(JSON.stringify(message.error)));else waiter.resolve(message.result);
|
||||
});
|
||||
function command(method,params={}){
|
||||
const id=++sequence;
|
||||
return new Promise((resolve,reject)=>{pending.set(id,{resolve,reject});ws.send(JSON.stringify({id,method,params}))});
|
||||
}
|
||||
|
||||
await command('Runtime.enable');
|
||||
await command('Page.enable');
|
||||
await command('Page.navigate',{url});
|
||||
let result;
|
||||
while(Date.now()<deadline){
|
||||
const evaluated=await command('Runtime.evaluate',{expression:`JSON.stringify({gate:document.documentElement.getAttribute('data-frontier-gate'),body:document.body?.textContent||'',href:location.href})`,returnByValue:true});
|
||||
const value=JSON.parse(evaluated.result.value);
|
||||
if(value.gate){result=value;break}
|
||||
await sleep(250);
|
||||
}
|
||||
try{await command('Browser.close')}catch{}
|
||||
ws.close();
|
||||
if(!result)throw new Error('Frontier gate timed out');
|
||||
process.stdout.write(JSON.stringify(result));
|
||||
26
tests/kernel_hashes.json
Normal file
26
tests/kernel_hashes.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": "4a38cadd59b0d30a42b1230a7fe646bb5972c9d49c5b66b3bb908458e3603fa6",
|
||||
"DEEP_COMPACT_INIT_WGSL": "29ee3883412dba774798bbeab8ba9c8744e7e7d3035d7eef1d81f78f9f959e79",
|
||||
"DEEP_COMPACT_CONTINUE_WGSL": "248311ff0acf69b3e7db057ccac08b3aded486cec3f718c0a2afce7de4e28419",
|
||||
"INTERIOR_MASK_WGSL": "00e8a502c23fe3468abab23365bc13cae3e35ecf401dcfe6e9a2f92f810d1eeb",
|
||||
"PRECISION_SCATTER_WGSL": "428ff66314ed0d69bc3bef83d5d0adc554bd89eb4a7137a4a8910f2d1016b03a",
|
||||
"DIRECT_F32_WGSL": "1d5ea335eb8aae5adb9e6d9bcdd46c3054159a83b30e5cf87a4c35aef51edf60",
|
||||
"ACTIVE_PREPARE_WGSL": "27bb3af8310bd0784888f2a648896e1fcf5872b4a9a3ade883ebe95a4868fa6e",
|
||||
"FAST_PERTURB_WGSL": "9f249f71619b2b9e8eff2e34699e8c8075a30feb9169774453953c82597b5f85",
|
||||
"FAST_PERTURB_POSTSTATS_WGSL": "8031e332beeb7b4d5c06b785e86e62ae6e0629a35d307a7480d974dd16e79029",
|
||||
"DEEP_PERTURB_WGSL": "0f42cd0ed34d02a4c563ebec1ae8fe5f09b45386073aed109c0e6542077a29dd",
|
||||
"DEEP_PERTURB_POSTSTATS_WGSL": "ef3e0bc2b1205747569b6aa17db3deb847a66787b0ef68219aa25bd0061aa5bb",
|
||||
"DEEP_ACTIVE_RESUME_INIT_WGSL": "a25c0b74bc66b7b91269bc6195ddde8b295f659ea72ec415fcf3fda3d66c126e",
|
||||
"DEEP_ACTIVE_CONTINUE_WGSL": "c4afcb504ec0fdc8fe18c67d8dced6e45d99cba0ec2e4dfc015ac6eb36856edd",
|
||||
"DEEP_BUCKET_HIST_WGSL": "3458f908fa239b16667e5c82dae25274d5493d647595230574a3d65622a54aaf",
|
||||
"DEEP_BUCKET_PREFIX_WGSL": "e8fb3f566d69ef87a61f1aa61f4c56ec26614711d7897afa2f8bfdcf5088c3da",
|
||||
"DEEP_BUCKET_SCATTER_WGSL": "7575c4fc5f4d5674dfc3997189212ffc6fbe240a9d8b8b3f8ef883cd4eae47a1",
|
||||
"DEEP_CORRECT_WGSL": "1ee151d376f4c56c870bb7ba5811b3d015bb93a02c9e3e02e9be96d673311eda",
|
||||
"DEEP_CORRECT_QUEUE_WGSL": "3ae43622225872a255dd96bf35becaca6bd44f558852b71ec93d13c02b57732b",
|
||||
"UNKNOWN_STATS_WGSL": "df1967bcdacf29831403fa53aadaae17ed138b36d1988cf20f6a6ed3e43e519a",
|
||||
"SYMMETRY_COPY_WGSL": "658e1cf2b9f40d8e759abe2269c041f582a80727cb24e2f48a405cb8676d70e1",
|
||||
"FAILURE_TILE_MAP_WGSL": "8308f57151b119363ad456a7e1255c5445ed5e022ccf53e923a4b13ab2a1ca91",
|
||||
"COLOR_WGSL": "4ef141430a5255cc8b78459b1a50fdd2ff31cf37eeb872a957d43794c59b09a3",
|
||||
"AA_RESOLVE_WGSL": "f353adafc837c170da1627f8b0cfd924d9748954376e4a427190917da4c92342",
|
||||
"PRESENT_WGSL": "651bf13de25b023c3f0ab0d0287b1d27cfd8e11677aca53629ce0a8526c7c5bc"
|
||||
}
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs/promises';
|
||||
import vm from 'node:vm';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {correctnessCases,performanceCases,interactionStabilityViews,productionPerformanceThresholds,evaluateProductionPerformance,runCorrectnessCorpus,runPerformanceCorpus,runInteractionStabilityCorpus} from './optimization_webgpu_corpus.mjs';
|
||||
|
||||
const root=fileURLToPath(new URL('../',import.meta.url));
|
||||
const html=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');
|
||||
const scripts=[...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]);
|
||||
assert.equal(scripts.length,2);
|
||||
for(const code of scripts)new vm.Script(code);
|
||||
const precisionWorker=scripts[1].match(/function precisionFallbackWorkerSource\(\)\{return String\.raw`([\s\S]*?)`\}/);
|
||||
assert.ok(precisionWorker);
|
||||
new vm.Script(precisionWorker[1]);
|
||||
|
||||
const context={globalThis:null};context.globalThis=context;vm.createContext(context);
|
||||
new vm.Script(scripts[0]).runInContext(context);
|
||||
const kernels=context.MANDEL_WEBGPU_KERNELS;
|
||||
const expected=JSON.parse(await fs.readFile(new URL('./KERNEL_HASHES_V24_3_0.json',import.meta.url),'utf8'));
|
||||
const actual={};
|
||||
for(const [key,value] of Object.entries(kernels)){
|
||||
if(typeof value==='string')actual[key]=crypto.createHash('sha256').update(value).digest('hex');
|
||||
}
|
||||
assert.deepEqual(actual,expected);
|
||||
assert.equal(correctnessCases.length,12);
|
||||
assert.equal(performanceCases.length,6);
|
||||
assert.equal(interactionStabilityViews.length,3);
|
||||
assert.ok(correctnessCases.some(x=>x.id==='reported-black-regression'&&x.fixed&&x.frontierCheck));
|
||||
assert.ok(correctnessCases.some(x=>x.id==='moderate-1e5-fast'));
|
||||
assert.ok(correctnessCases.some(x=>x.id==='moderate-1e5-accurate'));
|
||||
assert.ok(correctnessCases.some(x=>x.id==='moderate-1e8-fast'));
|
||||
assert.ok(correctnessCases.some(x=>x.id==='moderate-1e8-accurate'));
|
||||
assert.ok(performanceCases.some(x=>x.id==='fast-1e14-800x600'&&x.absoluteP95Ms<2000));
|
||||
assert.equal(productionPerformanceThresholds.targetP50Reduction,.20);
|
||||
assert.equal(productionPerformanceThresholds.targetP95Reduction,.15);
|
||||
assert.equal(typeof runCorrectnessCorpus,'function');
|
||||
assert.equal(typeof runPerformanceCorpus,'function');
|
||||
assert.equal(typeof runInteractionStabilityCorpus,'function');
|
||||
assert.equal(evaluateProductionPerformance({records:[{id:'target',target:true,summary:{recovery:{p50:79,p95:84}}}]},{records:[{id:'target',summary:{recovery:{p50:100,p95:100}}}]}).pass,true);
|
||||
assert.equal(evaluateProductionPerformance({records:[{id:'target',target:true,summary:{recovery:{p50:90,p95:90}}}]},{records:[{id:'target',summary:{recovery:{p50:100,p95:100}}}]}).pass,false);
|
||||
const corpusSource=await fs.readFile(new URL('./optimization_webgpu_corpus.mjs',import.meta.url),'utf8');
|
||||
assert.match(corpusSource,/assert\.equal\(result\.numericComplete,true/);
|
||||
assert.match(corpusSource,/assert\.equal\(numeric,0,testCase\.id\+' final numeric UNKNOWN'/);
|
||||
assert.match(corpusSource,/assert\.equal\(result\.state\.historyReady,true/);
|
||||
assert.match(corpusSource,/assert\.equal\(compile\.productionEligible,expectProduction\)/);
|
||||
assert.match(corpusSource,/assert\.equal\(compile\.candidateEnabled,true\)/);
|
||||
assert.match(corpusSource,/await t\.waitForNumericComplete\(\{timeout\}\)/);
|
||||
|
||||
assert.match(kernels.DIRECT_ACCURATE_SEED_WGSL,/if\(n>=p\.lateThreshold\)\{fieldMeta\[out\]=pack_unknown/);
|
||||
assert.match(kernels.DIRECT_DS_CORRECT_WGSL,/var zr2=DS\(0\.0,0\.0\);var zi2=DS\(0\.0,0\.0\)/);
|
||||
assert.match(kernels.DIRECT_DS_QUEUE_BUILD_WGSL,/atomicAdd\(&queueStats\[2\],1u\)/);
|
||||
assert.match(kernels.DIRECT_DS_CORRECT_QUEUE_WGSL,/atomicAdd\(&queueStats\[6\],1u\)/);
|
||||
assert.match(kernels.DIRECT_DS_CORRECT_QUEUE_WGSL,/let zri=ds_mul\(zr,zi\)/);
|
||||
assert.match(html,/accurateQueuedPixels/);
|
||||
assert.match(html,/q\.selected===q\.enqueued&&q\.enqueued===q\.dispatchCount&&q\.dispatchCount===q\.processed/);
|
||||
assert.match(html,/accurateQueueFallbacks/);
|
||||
assert.match(html,/encodeDeepBucketHistogram\(e/);
|
||||
assert.match(html,/q\.selected===q\.enqueued&&q\.enqueued===q\.dispatchCount&&q\.dispatchCount===q\.processed/);
|
||||
assert.match(html,/reusePreview/);
|
||||
assert.match(html,/canUseStableReprojection\(\)\|\|fastNeedsExtended\(snapshot\(\),canvas\.width\)\)return\[finalIter\]/);
|
||||
assert.match(html,/numericInFlightBudgetMs/);
|
||||
assert.match(html,/const ADAPTIVE_ITER_QUANTUM=64/);
|
||||
assert.match(html,/function pixelPrecisionBits\(snap=snapshot\(\),w=canvas\.width,guard=32\)/);
|
||||
assert.match(html,/pixelLog2=log2FixedAt\(snap\.span,snap\.bits\)-Math\.log2\(Math\.max\(1,w\)\)/);
|
||||
assert.match(html,/targetBits=pixelPrecisionBits\(snap,width,40\)/);
|
||||
assert.match(html,/function fastReferenceBits\(snap,w=canvas\.width\)\{return pixelPrecisionBits\(snap,w,32\)\}/);
|
||||
assert.match(html,/const bits=baseBits\+attempt\*32,a=pixel\(s,indices\[i\],bits\),b=pixel\(s,indices\[i\],bits\+32\)/);
|
||||
assert.match(html,/for\(let attempt=0;attempt<4&&!accepted\[i\];attempt\+\+\)/);
|
||||
assert.match(html,/for\(let i=0;i<meta\.length;i\+\+\)/);
|
||||
assert.match(html,/for\(let start=0;start<indices\.length;start\+=chunkSize\)/);
|
||||
assert.doesNotMatch(html,/UNRESOLVED_BLACK_MIN_ZOOM_EXP|colorUnresolvedInsteadOfBlack|guardedFastDeep|fast-quality-floor-before-1e100/);
|
||||
assert.match(html,/Math\.ceil\(raw\/quantum\)\*quantum/);
|
||||
assert.match(html,/this\.blaFastDisabled=true;this\.blaFastOptIn=false/);
|
||||
assert.doesNotMatch(html,/deep-stable-bootstrap|forceStableDeep/);
|
||||
assert.match(html,/A FAST frame is not publishable while any numeric UNKNOWN remains/);
|
||||
assert.match(html,/if\(initialFailed>0\)\{const mr=await recoverNumericalUnknownTiles/);
|
||||
assert.match(html,/if\(numericalFailureCount\(stats\)>0\)\{const pf=await recoverResidualPrecision/);
|
||||
assert.match(html,/if\(isFinal\)\{const remaining=numericalFailureCount.*frameを公開しません/);
|
||||
assert.match(html,/viewRe:snap\.re,viewIm:snap\.im/);
|
||||
assert.match(html,/function referenceOriginMatchesView\(/);
|
||||
assert.match(html,/!referenceOriginMatchesView\(src,snap\)/);
|
||||
assert.match(html,/stabilityPolicy:/);
|
||||
assert.match(html,/fastBackgroundDeepOnFailureOnly:true,deepProductionActive:false,deepCorrectionDispatchWork:PIXEL_FRONTIER_WORK/);
|
||||
const fastStableStart=html.indexOf('}else if(isFinal&&fastExtended){');
|
||||
const fastStableSource=html.slice(fastStableStart,html.indexOf('}else if(isFinal){',fastStableStart));
|
||||
assert.doesNotMatch(fastStableSource,/scheduleFastNumericRecovery/);
|
||||
assert.match(fastStableSource,/recoverNumericalUnknownTiles/);
|
||||
assert.match(fastStableSource,/recoverResidualPrecision/);
|
||||
assert.match(html,/ensureAccuratePipelines/);
|
||||
assert.match(html,/ensureCorrectionPipelines/);
|
||||
assert.match(html,/precisionFallbackWorkerSource/);
|
||||
assert.match(html,/recoverResidualPrecision/);
|
||||
assert.match(html,/estimatedNetMs/);
|
||||
assert.match(kernels.BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL,/fn baseline_from\(s0:S,pre:u32\)->Result/);
|
||||
assert.match(kernels.BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL,/var anchor=s;loop/);
|
||||
assert.match(kernels.BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL,/anchor=s;\}\}/);
|
||||
assert.doesNotMatch(kernels.BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL,/baseline\(g,st\)/);
|
||||
assert.equal((kernels.BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL.match(/baseline_from\(anchor,st\)/g)||[]).length,7);
|
||||
assert.ok(kernels.BLA_GPU_FAST_FRAME_WGSL.includes('baseline_from(anchor,st)'));
|
||||
assert.match(kernels.ACTIVE_PREPARE_WGSL,/indirect\[0\]=\(n\+63u\)\/64u/);
|
||||
assert.match(kernels.DIRECT_ACTIVE_CONTINUE_WGSL,/struct DirectState\{zr:f32,zi:f32,n:u32/);
|
||||
assert.match(kernels.DIRECT_ACTIVE_CONTINUE_WGSL,/atomicLoad\(&countIn\[0\]\)/);
|
||||
assert.match(kernels.DEEP_ACTIVE_CONTINUE_WGSL,/struct DeepState\{d:vec2<f32>,w:vec2<f32>,scaleExp:i32,n:u32,m:u32,errScaled:f32\}/);
|
||||
assert.match(kernels.DEEP_ACTIVE_INIT_WGSL,/states\[local\]=DeepState/);
|
||||
assert.match(kernels.DEEP_ACTIVE_CONTINUE_WGSL,/let out=p\.outputBase\+\(local\/p\.tileW\)\*p\.outputStride/);
|
||||
assert.match(kernels.DEEP_ACTIVE_CONTINUE_WGSL,/if\(s\.m>p\.refLen\)/);
|
||||
assert.match(kernels.DEEP_ACTIVE_CONTINUE_WGSL,/if\(s\.m>=p\.refLen\)/);
|
||||
assert.match(kernels.PERTURB_CERTIFIED_INTERIOR_WGSL,/struct I\{lo:f32,hi:f32\}/);
|
||||
assert.match(kernels.PERTURB_CERTIFIED_INTERIOR_WGSL,/fn outward\(/);
|
||||
assert.match(kernels.PERTURB_CERTIFIED_INTERIOR_WGSL,/lhs\.hi<rhs\.lo/);
|
||||
assert.match(kernels.PERTURB_CERTIFIED_INTERIOR_WGSL,/bulb\.hi<0\.0625/);
|
||||
assert.match(kernels.PERTURB_CERTIFIED_INTERIOR_WGSL,/if\(p\.refLen<1u\)\{proven\[i\]=0u;return;\}/);
|
||||
assert.match(kernels.PERTURB_CERTIFIED_INTERIOR_WGSL,/let ref=refs\[1u\]/);
|
||||
assert.match(html,/runDirectActiveShadow/);
|
||||
assert.match(html,/dispatchWorkgroupsIndirect\(indirect,0\)/);
|
||||
assert.match(html,/direct-active-shadow-resume/);
|
||||
assert.match(html,/resumedFromPreview:previewIter>0/);
|
||||
assert.match(html,/runDeepActiveShadow/);
|
||||
assert.match(html,/computeDeepActiveFrame/);
|
||||
assert.match(html,/workPerDispatch=PIXEL_FRONTIER_WORK,maxPassesPerSubmit=1/);
|
||||
assert.match(html,/const workBudget=deep\?4000000:48000000/);
|
||||
assert.match(html,/const PIXEL_FRONTIER_PRODUCTION=true,PIXEL_FRONTIER_WORK=4000000,PIXEL_FRONTIER_MAX_ITER=150000/);
|
||||
assert.match(html,/PIXEL_FRONTIER_CANDIDATE=.*frontier-gate/);
|
||||
assert.match(html,/PIXEL_FRONTIER_ENABLED=PIXEL_FRONTIER_PRODUCTION\|\|PIXEL_FRONTIER_CANDIDATE/);
|
||||
assert.match(html,/function operationLimitFrontier\(meta,w,h\)/);
|
||||
assert.match(html,/function pixelFrontierIterationFloor\(snap,w,initialIter\)/);
|
||||
assert.match(html,/iter>=minimumIter&&stableRounds>=2/);
|
||||
assert.match(html,/count\*iter/);
|
||||
assert.match(html,/pixel frontier queue invariant failed/);
|
||||
assert.match(html,/compareCurrentToFrontierBaseline/);
|
||||
assert.match(html,/productionEligible:PIXEL_FRONTIER_PRODUCTION/);
|
||||
assert.match(kernels.DEEP_CORRECT_QUEUE_WGSL,/bitcast<u32>\(p\._numeric2\)\+localQi/);
|
||||
assert.match(html,/const maxBatch=Math\.max\(1,Math\.floor\(PIXEL_FRONTIER_WORK\/Math\.max\(1,iter\)\)\);let offset=0/);
|
||||
assert.match(html,/this\.deepParams\(f\.w,f\.h,f\.w,f\.h,0,0,iter,snap,\.5,\.5,0,refX,refY,2,f\.w,0,offset\)/);
|
||||
assert.match(html,/maxPixels=Math\.max\(1,Math\.floor\(4000000\/Math\.max\(1,iter\)\)\),tileW=Math\.min\(f\.w,maxPixels\)/);
|
||||
assert.match(html,/correctUnknownFrame\(snap,iter,token,referencePixel,numericalFailureCount\(stats\)\)/);
|
||||
assert.match(html,/ensureDeepActiveWorkspace/);
|
||||
assert.doesNotMatch(html,/return this\.computeDeepActiveFrame\(snap,iter,token,forceStrict,referencePixel\)/);
|
||||
assert.match(html,/return this\.computePerturbFrameTiled\(snap,iter,true,token,forceStrict,referencePixel,false,colorStyle,colorKey\)/);
|
||||
assert.match(html,/state\.gpuInitFailed=true;markDirty\(false\);setTimeout/);
|
||||
assert.match(html,/deep-active-shadow-resume/);
|
||||
assert.match(html,/deep-active-compare-shadow/);
|
||||
assert.match(html,/runtime\.deepActiveShadowLatest=result/);
|
||||
assert.match(html,/runCertifiedInteriorShadow/);
|
||||
assert.match(html,/precisionFallback\.request\(snap,iter,w,h,gpu\.provenIndices,FIELD_INTERIOR_PROVEN\)/);
|
||||
assert.match(html,/falsePositive===0&&inconclusive===0/);
|
||||
assert.match(html,/oracleBits:\[oracleBits,oracleBits\+32\]/);
|
||||
assert.match(html,/productionEligible:false/);
|
||||
assert.match(html,/waitForPrimary\(/);
|
||||
assert.match(html,/waitForFinal\(/);
|
||||
assert.match(html,/waitForNumericComplete\(/);
|
||||
assert.match(html,/fieldHashes\(\)/);
|
||||
assert.match(html,/readFieldAll\(\)/);
|
||||
assert.match(html,/generationMetrics:\[\]/);
|
||||
assert.match(html,/primaryNumericalFailures/);
|
||||
assert.match(html,/numericQuiescentMs/);
|
||||
assert.match(html,/historyCommitMs/);
|
||||
assert.match(html,/SYMMETRY_COPY_WGSL/);
|
||||
assert.match(html,/canUseRealAxisSymmetry/);
|
||||
assert.match(html,/encodeSymmetryCopy/);
|
||||
assert.match(html,/FAILURE_TILE_MAP_WGSL/);
|
||||
assert.match(html,/readNumericalFailureTiles/);
|
||||
assert.match(html,/tileReferencePool=Array\.from\(\{length:3\}/);
|
||||
assert.match(html,/Promise\.all\(batch\.map/);
|
||||
assert.match(html,/if\(p\.key===key\)return p\.promise/);
|
||||
assert.match(html,/prefetchFinalReference/);
|
||||
assert.match(html,/this\.exportWs=\[\]/);
|
||||
assert.match(html,/exportRingDepth\(tile=this\.exportTileSize\(\)\)/);
|
||||
assert.match(html,/Promise\.allSettled\(batch\.map\(item=>item\.promise\)\)/);
|
||||
assert.match(html,/if\(failed\)throw failed\.reason/);
|
||||
assert.match(html,/performance\.now\(\)-lastYield<24/);
|
||||
assert.doesNotMatch(html,/await png\.rows\(band\);await new Promise\(requestAnimationFrame\)/);
|
||||
assert.match(html,/frameReadback\(kind,size\)/);
|
||||
assert.match(html,/length:3.*kind\+'-readback-'/);
|
||||
const statsReadbackSource=html.slice(html.indexOf('async readUnresolvedStats'),html.indexOf('async readUnresolved(){'));
|
||||
assert.match(statsReadbackSource,/this\.frameReadback\('unknown-stats'/);
|
||||
assert.doesNotMatch(statsReadbackSource,/destroy\(r\)/);
|
||||
assert.match(html,/cachedBindGroup\(pipeline,entries\)/);
|
||||
assert.match(html,/cachedTextureView\(texture\)/);
|
||||
assert.match(html,/this\.cachedBindGroup\(this\.direct/);
|
||||
assert.match(html,/this\.cachedBindGroup\(this\.present/);
|
||||
assert.match(html,/this\.clearBindGroupCache\(\)/);
|
||||
const historyCommitSource=html.slice(html.indexOf('commitHistory(view='),html.indexOf('invalidateHistory(){'));
|
||||
assert.match(historyCommitSource,/f\.history=f\.front/);
|
||||
assert.doesNotMatch(historyCommitSource,/copyTextureToTexture/);
|
||||
assert.match(html,/colorTarget\(f=this\.frame\)/);
|
||||
{
|
||||
const roles={front:'a',back:'b',history:'c',spare:'c'};
|
||||
const commit=()=>{roles.history=roles.front};
|
||||
const render=()=>{if(roles.back===roles.history){const old=roles.history;roles.back=roles.spare;roles.spare=old}assert.notEqual(roles.back,roles.history);assert.notEqual(roles.back,roles.front);[roles.front,roles.back]=[roles.back,roles.front]};
|
||||
for(let i=0;i<1000;i++){if(i%7===0||i%11===0)commit();else render();assert.equal(new Set([roles.front,roles.back,roles.history,roles.spare]).size,3)}
|
||||
}
|
||||
const recoverySource=html.slice(html.indexOf('async function recoverNumericalUnknownTiles'),html.indexOf('async function recoverResidualPrecision'));
|
||||
assert.match(recoverySource,/readNumericalFailureTiles/);
|
||||
assert.match(recoverySource,/const currentMeta=await r\.readMetaAll\(\)/);
|
||||
assert.match(recoverySource,/Uint32Array\.from\(tileIndices\)/);
|
||||
assert.match(html,/p\.unknownOnly==2u && reason!=0u/);
|
||||
assert.match(html,/fast-coverage-repair-and-color/);
|
||||
assert.match(html,/reprojectionSafe\(rawHistory,2\.25,\.45\)/);
|
||||
assert.match(html,/scheduleFastNumericRecovery/);
|
||||
assert.match(html,/runFastNumericRecovery/);
|
||||
assert.match(html,/numericFrameComplete\(\)/);
|
||||
assert.match(html,/requestBuild\(\{ctx,snap,iter,width,height,refPixel,safety=1\/64\}\)/);
|
||||
assert.match(html,/type:'build'/);
|
||||
assert.match(html,/buildOnly=d\.type==='build'/);
|
||||
assert.equal((await fs.readdir(root)).filter(name=>name.startsWith('index.baseline')&&name.endsWith('.html')).length,0);
|
||||
|
||||
let randomState=0x12345678;
|
||||
function randomU32(){randomState=(Math.imul(randomState,1664525)+1013904223)>>>0;return randomState}
|
||||
function roundShift(value,bits){const negative=value<0n,absolute=negative?-value:value,half=1n<<(BigInt(bits)-1n),rounded=(absolute+half)>>BigInt(bits);return negative?-rounded:rounded}
|
||||
function orbitOld(bits,re,im,iterations){const bailout=16n*(1n<<BigInt(bits));let zr=0n,zi=0n;const states=[];for(let n=0;n<iterations;n++){const zr2=roundShift(zr*zr,bits),zi2=roundShift(zi*zi,bits);zi=roundShift(2n*zr*zi,bits)+im;zr=zr2-zi2+re;const mag=roundShift(zr*zr,bits)+roundShift(zi*zi,bits);states.push([zr,zi,mag]);if(mag>bailout)return{escape:n+1,states}}return{escape:0,states}}
|
||||
function orbitNew(bits,re,im,iterations){const bailout=16n*(1n<<BigInt(bits));let zr=0n,zi=0n,zr2=0n,zi2=0n;const states=[];for(let n=0;n<iterations;n++){zi=roundShift(2n*zr*zi,bits)+im;zr=zr2-zi2+re;zr2=roundShift(zr*zr,bits);zi2=roundShift(zi*zi,bits);const mag=zr2+zi2;states.push([zr,zi,mag]);if(mag>bailout)return{escape:n+1,states}}return{escape:0,states}}
|
||||
|
||||
for(let sample=0;sample<2000;sample++){
|
||||
const bits=80+(randomU32()%177),one=1n<<BigInt(bits);
|
||||
const re=(BigInt(randomU32()%7000001)-3500000n)*one/1000000n;
|
||||
const im=(BigInt(randomU32()%4000001)-2000000n)*one/1000000n;
|
||||
const iterations=20+(randomU32()%180);
|
||||
assert.deepEqual(orbitNew(bits,re,im,iterations),orbitOld(bits,re,im,iterations));
|
||||
}
|
||||
|
||||
const f=Math.fround;
|
||||
function ds(h,l=0){return{h:f(h),l:f(l)}}
|
||||
function quick(a,b){const q=f(a+b),e=f(b-f(q-a));return ds(q,e)}
|
||||
function sum(a,b){const q=f(a+b),bb=f(q-a),e=f(f(a-f(q-bb))+f(b-bb));return ds(q,e)}
|
||||
function product(a,b){const q=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)-q);e=f(e+f(ah*bl));e=f(e+f(al*bh));e=f(e+f(al*bl));return ds(q,e)}
|
||||
function add(a,b){const q=sum(a.h,b.h);return quick(q.h,f(f(q.l+a.l)+b.l))}
|
||||
function neg(a){return ds(-a.h,-a.l)}
|
||||
function sub(a,b){return add(a,neg(b))}
|
||||
function mul(a,b){const q=product(a.h,b.h),e=f(f(f(q.l+f(a.h*b.l))+f(a.l*b.h))+f(a.l*b.l));return quick(q.h,e)}
|
||||
function scale(a,b){const q=product(a.h,b);return quick(q.h,f(q.l+f(a.l*b)))}
|
||||
function sameDs(a,b){return Object.is(a.h,b.h)&&Object.is(a.l,b.l)}
|
||||
function dsOrbit(cr,ci,iterations,carrySquares){let zr=ds(0),zi=ds(0),zr2=ds(0),zi2=ds(0);const states=[];for(let n=0;n<iterations;n++){if(!carrySquares){zr2=mul(zr,zr);zi2=mul(zi,zi)}const zri=mul(zr,zi);zr=add(sub(zr2,zi2),cr);zi=add(scale(zri,2),ci);const nextZr2=mul(zr,zr),nextZi2=mul(zi,zi),mag=add(nextZr2,nextZi2);states.push({zr,zi,mag});zr2=nextZr2;zi2=nextZi2}return states}
|
||||
|
||||
for(let sample=0;sample<600;sample++){
|
||||
const cr=ds(f((randomU32()/0xffffffff)*3.5-2.5),f((randomU32()/0xffffffff-.5)*1e-7));
|
||||
const ci=ds(f((randomU32()/0xffffffff)*2.4-1.2),f((randomU32()/0xffffffff-.5)*1e-7));
|
||||
const iterations=8+(randomU32()%64),oldStates=dsOrbit(cr,ci,iterations,false),newStates=dsOrbit(cr,ci,iterations,true);
|
||||
assert.equal(newStates.length,oldStates.length);
|
||||
for(let i=0;i<oldStates.length;i++){
|
||||
assert.ok(sameDs(newStates[i].zr,oldStates[i].zr));
|
||||
assert.ok(sameDs(newStates[i].zi,oldStates[i].zi));
|
||||
assert.ok(sameDs(newStates[i].mag,oldStates[i].mag));
|
||||
}
|
||||
}
|
||||
|
||||
function directChunkOrbit(cr,ci,iterations,chunk){let zr=f(0),zi=f(0),n=0,escape=0,mag=f(0);while(n<iterations&&!escape){const stop=Math.min(iterations,n+chunk);while(n<stop){const zr2=f(zr*zr),zi2=f(zi*zi);zi=f(f(f(2*zr)*zi)+ci);zr=f(f(zr2-zi2)+cr);n++;mag=f(f(zr*zr)+f(zi*zi));if(mag>4){escape=n;break}}}return{zr,zi,n,escape,mag}}
|
||||
for(let sample=0;sample<1000;sample++){
|
||||
const cr=f((randomU32()/0xffffffff)*3.5-2.5),ci=f((randomU32()/0xffffffff)*2.4-1.2),iterations=64+(randomU32()%900),chunk=[64,128,192,256][randomU32()%4];
|
||||
assert.deepEqual(directChunkOrbit(cr,ci,iterations,chunk),directChunkOrbit(cr,ci,iterations,iterations));
|
||||
}
|
||||
|
||||
function deepChunkState(reference,d,scaleExp,iterations,chunk){let w=[f(0),f(0)],n=0,m=0,err=f(Math.pow(2,-24)*Math.max(Math.abs(d[0]),Math.abs(d[1])));while(n<iterations){const stop=Math.min(iterations,n+chunk);while(n<stop){const r=reference[m],delta=[f(w[0]*Math.pow(2,scaleExp)),f(w[1]*Math.pow(2,scaleExp))],sq=[f(f(w[0]*w[0])-f(w[1]*w[1])),f(f(2*w[0])*w[1])],linear=[f(2*f(f(r[0]*w[0])-f(r[1]*w[1]))),f(2*f(f(r[0]*w[1])+f(r[1]*w[0])))],refAbs=Math.max(Math.abs(r[0]),Math.abs(r[1])),wAbs=Math.max(Math.abs(w[0]),Math.abs(w[1])),dAbs=Math.max(Math.abs(d[0]),Math.abs(d[1])),deltaAbs=Math.max(Math.abs(delta[0]),Math.abs(delta[1])),sqAbs=Math.max(Math.abs(f(sq[0]*Math.pow(2,scaleExp))),Math.abs(f(sq[1]*Math.pow(2,scaleExp))));err=f(f(f(2*refAbs)+f(2*deltaAbs))*err+f(Math.pow(2,-24)*f(f(f(2*refAbs)*wAbs)+sqAbs+dAbs+1e-30)));w=[f(f(linear[0]+f(sq[0]*Math.pow(2,scaleExp)))+d[0]),f(f(linear[1]+f(sq[1]*Math.pow(2,scaleExp)))+d[1])];m++;n++;const mm=Math.max(Math.abs(w[0]),Math.abs(w[1]),Math.abs(d[0]),Math.abs(d[1]));if(mm>65536){w=[f(w[0]/65536),f(w[1]/65536)];d=[f(d[0]/65536),f(d[1]/65536)];err=f(err/65536);scaleExp+=16}}}return{w,d,scaleExp,n,m,err}}
|
||||
for(let sample=0;sample<1000;sample++){
|
||||
const iterations=32+(randomU32()%480),reference=Array.from({length:iterations+1},()=>[f((randomU32()/0xffffffff-.5)*2),f((randomU32()/0xffffffff-.5)*2)]),d=[f((randomU32()/0xffffffff-.5)*1e-3),f((randomU32()/0xffffffff-.5)*1e-3)],scaleExp=-20-(randomU32()%60),chunk=1+(randomU32()%256);
|
||||
assert.deepEqual(deepChunkState(reference,[...d],scaleExp,iterations,chunk),deepChunkState(reference,[...d],scaleExp,iterations,iterations));
|
||||
}
|
||||
|
||||
for(let sample=0;sample<500;sample++){
|
||||
const w=64+(randomU32()%512),h=32+(randomU32()%256),iterations=350+(randomU32()%5000),maxPixels=Math.max(1,Math.floor(4000000/iterations)),tileW=Math.min(w,maxPixels),rows=Math.max(1,Math.min(128,Math.floor(maxPixels/tileW))),coverage=new Uint8Array(w*h);
|
||||
for(let y=0;y<h;y+=rows){const th=Math.min(rows,h-y);for(let x=0;x<w;x+=tileW){const tw=Math.min(tileW,w-x);assert.ok(tw*th*iterations<=4000000);for(let yy=0;yy<th;yy++)for(let xx=0;xx<tw;xx++)coverage[(y+yy)*w+x+xx]++}}
|
||||
for(const count of coverage)assert.equal(count,1);
|
||||
}
|
||||
|
||||
function adaptiveBudget(screenBits,q,base=350,quantum=64){const raw=48*Math.max(0,q)*Math.max(0,screenBits),bonus=raw>0?Math.ceil(raw/quantum)*quantum:0;return Math.min(12000,Math.max(base,base+bonus))}
|
||||
for(const q of [.72,1,1.25,1.5]){
|
||||
let prior=0;
|
||||
for(let i=0;i<=2000;i++){
|
||||
const screenBits=i/20,value=adaptiveBudget(screenBits,q);
|
||||
assert.ok(value>=350+Math.floor(48*q*screenBits));
|
||||
assert.equal((value-350)%64,0);
|
||||
assert.ok(value>=prior);prior=value;
|
||||
}
|
||||
}
|
||||
assert.equal(adaptiveBudget(Math.log2(800)+12*Math.LOG2E*Math.LN10,1),adaptiveBudget(Math.log2(800)+12.05*Math.LOG2E*Math.LN10,1));
|
||||
|
||||
function frontierModel(meta,w,h){const out=[];for(let y=0;y<h;y++)for(let x=0;x<w;x++){const i=y*w+x,m=meta[i]>>>0;if(((m>>>28)&3)!==0||((m>>>20)&255)!==6)continue;let edge=false;for(let oy=-1;oy<=1&&!edge;oy++)for(let ox=-1;ox<=1;ox++){if(!ox&&!oy)continue;const xx=x+ox,yy=y+oy;if(xx>=0&&xx<w&&yy>=0&&yy<h&&((meta[yy*w+xx]>>>28)&3)===1){edge=true;break}}if(edge)out.push(i)}return out}
|
||||
const opLimit=(6<<20)|350,escaped=(1<<28)|12,interior=(3<<28)|350,frontierFixture=new Uint32Array(25).fill(interior);frontierFixture[12]=escaped;for(const i of [6,7,8,11,13,16,17,18])frontierFixture[i]=opLimit;frontierFixture[0]=opLimit;
|
||||
assert.deepEqual(frontierModel(frontierFixture,5,5),[6,7,8,11,13,16,17,18]);
|
||||
for(let sample=0;sample<500;sample++){const iter=1+(randomU32()%150000),count=1+(randomU32()%200000),chunk=Math.max(1,Math.floor(4000000/iter));for(let start=0;start<count;start+=chunk)assert.ok(Math.min(chunk,count-start)*iter<=4000000)}
|
||||
|
||||
function pixelBitsAt(spanLog2,width,sourceBits=512,guard=32){const pixelLog2=spanLog2-Math.log2(Math.max(1,width)),need=Math.ceil(Math.max(0,-pixelLog2))+Math.max(16,guard|0);return Math.max(96,Math.min(sourceBits+64,need))}
|
||||
assert.equal(pixelBitsAt(Math.log2(3.4)-8*Math.LOG2E*Math.LN10,1920),96);
|
||||
assert.ok(pixelBitsAt(Math.log2(3.4)-100*Math.LOG2E*Math.LN10,1920)>350);
|
||||
assert.ok(pixelBitsAt(-400,3840)>pixelBitsAt(-400,960));
|
||||
assert.equal(pixelBitsAt(-400,1920,256),320);
|
||||
|
||||
export const result={
|
||||
syntaxScripts:scripts.length,
|
||||
precisionWorkerSyntax:true,
|
||||
kernelHashes:Object.keys(actual).length,
|
||||
bigintOrbitCases:2000,
|
||||
dsOrbitCases:600,
|
||||
directActiveChunkCases:1000,
|
||||
deepActiveChunkCases:1000,
|
||||
boundedCorrectionTileCases:500,
|
||||
adaptiveIterationCases:8004,
|
||||
pixelPrecisionCases:4,
|
||||
frontierAdjacencyCases:1,
|
||||
frontierWorkBoundCases:500,
|
||||
webGpuCorrectnessCases:correctnessCases.length,
|
||||
webGpuPerformanceCases:performanceCases.length,
|
||||
webGpuStabilityViews:interactionStabilityViews.length,
|
||||
status:'pass'
|
||||
};
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
import assert from 'node:assert/strict';
|
||||
|
||||
export const correctnessCases=[
|
||||
{id:'reset',re:'-0.5',im:'0',span:'3.4',bits:256,baseIter:750,adaptive:false,processMode:'validate',renderMode:'fast',activeDirect:true,certified:true,frontierCheck:true},
|
||||
{id:'cusp-low',re:'0.25',im:'0',span:'0.85',bits:256,baseIter:900,adaptive:false,processMode:'validate',renderMode:'accurate',certified:true},
|
||||
{id:'seahorse-low',re:'-0.743643887037151',im:'0.13182590420533',span:'0.1',bits:320,baseIter:1200,adaptive:false,processMode:'validate',renderMode:'accurate'},
|
||||
{id:'moderate-1e5-fast',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000034',bits:384,baseIter:350,adaptive:true,processMode:'standard',renderMode:'fast',frontierCheck:true},
|
||||
{id:'moderate-1e5-accurate',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000034',bits:384,baseIter:350,adaptive:true,processMode:'standard',renderMode:'accurate',frontierCheck:true},
|
||||
{id:'moderate-1e8-fast',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000000034',bits:384,baseIter:350,adaptive:true,processMode:'standard',renderMode:'fast',frontierCheck:true},
|
||||
{id:'moderate-1e8-accurate',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000000034',bits:384,baseIter:350,adaptive:true,processMode:'standard',renderMode:'accurate',frontierCheck:true},
|
||||
{id:'seahorse-deep',re:'-0.743643887037151',im:'0.13182590420533',span:'0.00000000000034',bits:448,baseIter:2900,adaptive:false,processMode:'validate',renderMode:'accurate',activeDeep:true,frontierCheck:true},
|
||||
{id:'interior-heavy',re:'0',im:'0',span:'0.5',bits:320,baseIter:1500,adaptive:false,processMode:'validate',renderMode:'accurate',certified:true},
|
||||
{id:'real-axis-boundary',re:'-0.75',im:'0',span:'0.02',bits:384,baseIter:3000,adaptive:false,processMode:'validate',renderMode:'accurate',activeDeep:true,certified:true,frontierCheck:true},
|
||||
{id:'misiurewicz-hard',re:'-0.10109636384562',im:'0.95628651080914',span:'0.00000002',bits:384,baseIter:3000,adaptive:false,processMode:'validate',renderMode:'accurate',activeDeep:true,frontierCheck:true},
|
||||
{id:'reported-black-regression',re:'-29466147000382485924219538765424656674342940730553342389512209954887705938978413587',im:'5179018789925933872641942859702187326563010594882200888329237785122225251258352',span:'250637324516887125119843167990565159991251418774166207381437546036409',bits:273,baseIter:350,adaptive:true,processMode:'standard',renderMode:'accurate',fixed:true,frontierCheck:true}
|
||||
];
|
||||
|
||||
export const performanceCases=[
|
||||
{id:'accurate-320x220-750',viewport:{width:320,height:220},view:correctnessCases[2],iterations:750},
|
||||
{id:'bla-654x690-2900',viewport:{width:654,height:690},view:correctnessCases[7],iterations:2900},
|
||||
{id:'fast-1e14-800x600',viewport:{width:800,height:600},view:{id:'fast-1e14',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000000000000034',bits:448,baseIter:350,adaptive:true,processMode:'standard',renderMode:'fast'},target:true,absoluteP50Ms:1000,absoluteP95Ms:1500},
|
||||
{id:'accurate-1e14-800x600',viewport:{width:800,height:600},view:{id:'accurate-1e14',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000000000000034',bits:448,baseIter:350,adaptive:true,processMode:'standard',renderMode:'accurate'},target:true,absoluteP50Ms:1500,absoluteP95Ms:2000},
|
||||
{id:'hd-reset-1500',viewport:{width:1920,height:1080},view:correctnessCases[0],iterations:1500},
|
||||
{id:'hd-boundary-3000',viewport:{width:1920,height:1080},view:correctnessCases[9],iterations:3000}
|
||||
];
|
||||
|
||||
export const productionPerformanceThresholds={targetP50Reduction:.20,targetP95Reduction:.15,nonTargetP50Regression:.05,nonTargetP95Regression:.10};
|
||||
|
||||
export const interactionStabilityViews=[
|
||||
{id:'near-a',re:'-0.743643887037151',im:'0.13182590420533',span:'0.0000000000034',bits:448,baseIter:350,adaptive:true,processMode:'standard',renderMode:'fast'},
|
||||
{id:'near-b',re:'-0.743643887037151',im:'0.13182590420533',span:'0.000000000003030253189',bits:448,baseIter:350,adaptive:true,processMode:'standard',renderMode:'fast'},
|
||||
{id:'near-a-repeat',re:'-0.743643887037151',im:'0.13182590420533',span:'0.0000000000034',bits:448,baseIter:350,adaptive:true,processMode:'standard',renderMode:'fast'}
|
||||
];
|
||||
|
||||
function numericalReasonTotal(reasons={}){return (reasons.errorBound||0)+(reasons.escapeUncertain||0)+(reasons.referenceEnd||0)+(reasons.rebaseGap||0)+(reasons.range||0)}
|
||||
function operationLimit(reasons={}){return reasons.operationLimit||0}
|
||||
|
||||
async function evaluateCase(page,testCase,options){
|
||||
return page.evaluate(async({testCase,options})=>{
|
||||
const t=globalThis.__MANDEL_TEST__;if(!t)throw new Error('__MANDEL_TEST__ is unavailable');
|
||||
await t.setView(testCase);await t.waitForFinal({timeout:options.timeout});
|
||||
let numericComplete=true,numericError='';try{await t.waitForNumericComplete({timeout:options.timeout})}catch(error){numericComplete=false;numericError=String(error&&error.message||error)}
|
||||
const state=t.state(),hashes=await t.fieldHashes(),diagnostics=t.gpuDiagnostics();
|
||||
let activeDirect=null,activeDeep=null,certified=null,frontier=null;
|
||||
if(testCase.activeDirect)activeDirect=await t.runActiveDirectShadow({chunkSize:128});
|
||||
if(testCase.activeDeep)activeDeep=await t.runActiveDeepShadow({width:options.shadowWidth,height:options.shadowHeight,chunkSize:128,strict:true});
|
||||
if(testCase.certified)certified=await t.runCertifiedInteriorShadow({width:options.oracleWidth,height:options.oracleHeight,samples:options.oracleSamples});
|
||||
if(testCase.frontierCheck)frontier=await t.compareCurrentToFrontierBaseline({strict:true});
|
||||
return{state,hashes,diagnostics,numericComplete,numericError,activeDirect,activeDeep,certified,frontier};
|
||||
},{testCase,options});
|
||||
}
|
||||
|
||||
function auditCase(testCase,result,expected){
|
||||
const reasons=result.state.unknownReasons||{},numeric=numericalReasonTotal(reasons),op=operationLimit(reasons),coverage=Math.max(0,(result.state.unresolved||0)-numeric-op),errors=(result.diagnostics?.compilation||[]).flatMap(x=>x.messages||[]).filter(x=>x.type==='error');
|
||||
assert.equal(result.state.kernelVersion,'24.3.0-optimized-unknown-pipeline',testCase.id+' kernel version');
|
||||
assert.equal(coverage,0,testCase.id+' coverage UNKNOWN');
|
||||
assert.equal(errors.length,0,testCase.id+' WGSL compilation errors');
|
||||
assert.equal(result.diagnostics?.uncapturedErrors?.length||0,0,testCase.id+' uncaptured GPU errors');
|
||||
assert.equal(result.diagnostics?.lossReason||'','',testCase.id+' device loss');
|
||||
assert.equal(result.numericComplete,true,testCase.id+' numeric completion: '+result.numericError);
|
||||
assert.equal(numeric,0,testCase.id+' final numeric UNKNOWN');
|
||||
assert.equal(result.state.historyReady,true,testCase.id+' completed history commit');
|
||||
if(result.activeDirect)assert.equal(result.activeDirect.gatePassed,true,testCase.id+' Direct active mismatch');
|
||||
if(result.activeDeep)assert.equal(result.activeDeep.gatePassed,true,testCase.id+' Deep active mismatch');
|
||||
if(result.certified)assert.equal(result.certified.gatePassed,true,testCase.id+' certified interior oracle mismatch');
|
||||
if(result.frontier)assert.equal(result.frontier.gatePassed,true,testCase.id+' production frontier mismatch');
|
||||
assert.equal(result.state.frontierConverged,true,testCase.id+' frontier convergence');
|
||||
assert.ok((result.state.frontierMaxDispatchWork||0)<=4000000,testCase.id+' frontier TDR work bound');
|
||||
if(expected){assert.equal(result.hashes.metaSha256,expected.metaSha256,testCase.id+' meta hash');assert.equal(result.hashes.smoothSha256,expected.smoothSha256,testCase.id+' smooth hash')}
|
||||
return{coverageUnknown:coverage,numericalUnknown:numeric,operationLimit:op};
|
||||
}
|
||||
|
||||
export async function runCorrectnessCorpus(page,{timeout=180000,viewport={width:480,height:320},shadowWidth=160,shadowHeight=112,oracleWidth=320,oracleHeight=220,oracleSamples=289,expectedHashes={},expectProduction=false}={}){
|
||||
if(page.setViewportSize)await page.setViewportSize(viewport);
|
||||
const compile=await page.evaluate(()=>globalThis.__MANDEL_TEST__.compileActiveShadow()),records=[];
|
||||
assert.equal(compile.productionEligible,expectProduction);
|
||||
assert.equal(compile.candidateEnabled,true);
|
||||
assert.equal(compile.pixelFrontier,true);
|
||||
assert.equal(compile.frontierWorkLimit,4000000);
|
||||
for(const testCase of correctnessCases){const started=Date.now(),result=await evaluateCase(page,testCase,{timeout,shadowWidth,shadowHeight,oracleWidth,oracleHeight,oracleSamples}),audit=auditCase(testCase,result,expectedHashes[testCase.id]);records.push({id:testCase.id,elapsedMs:Date.now()-started,audit,...result})}
|
||||
const adapter=await page.evaluate(()=>globalThis.__MANDEL_TEST__.gpuDiagnostics()?.adapterInfo||{});
|
||||
return{version:'24.3.0',kind:'correctness',date:new Date().toISOString(),expectProduction,compile,adapter,records};
|
||||
}
|
||||
|
||||
function percentile(values,p){const sorted=[...values].sort((a,b)=>a-b);if(!sorted.length)return Infinity;const index=Math.min(sorted.length-1,Math.max(0,Math.ceil(sorted.length*p)-1));return sorted[index]}
|
||||
function summarizeRuns(samples){const primary=samples.map(x=>Number(x.metric?.primaryMs??x.wallMs)),recovery=samples.map(x=>Number(x.metric?.numericQuiescentMs??x.metric?.historyCommitMs??x.metric?.finalMs??x.wallMs));return{runs:samples.length,primary:{p50:percentile(primary,.50),p95:percentile(primary,.95)},recovery:{p50:percentile(recovery,.50),p95:percentile(recovery,.95)}}}
|
||||
export function evaluateProductionPerformance(candidate,baseline,{thresholds=productionPerformanceThresholds}={}){const failures=[],byId=new Map((baseline?.records||[]).map(x=>[x.id,x]));for(const record of candidate?.records||[]){const base=byId.get(record.id),summary=record.summary||summarizeRuns(record.runs||[]);if(record.absoluteP50Ms&&summary.recovery.p50>record.absoluteP50Ms)failures.push(record.id+' recovery p50 '+summary.recovery.p50+' > '+record.absoluteP50Ms);if(record.absoluteP95Ms&&summary.recovery.p95>record.absoluteP95Ms)failures.push(record.id+' recovery p95 '+summary.recovery.p95+' > '+record.absoluteP95Ms);if(!base){failures.push(record.id+' missing baseline');continue}const bs=base.summary||summarizeRuns(base.runs||[]),p50=(summary.recovery.p50-bs.recovery.p50)/Math.max(1,bs.recovery.p50),p95=(summary.recovery.p95-bs.recovery.p95)/Math.max(1,bs.recovery.p95);if(record.target){if(p50>-thresholds.targetP50Reduction)failures.push(record.id+' target p50 reduction '+p50);if(p95>-thresholds.targetP95Reduction)failures.push(record.id+' target p95 reduction '+p95)}else{if(p50>thresholds.nonTargetP50Regression)failures.push(record.id+' non-target p50 regression '+p50);if(p95>thresholds.nonTargetP95Regression)failures.push(record.id+' non-target p95 regression '+p95)}}return{pass:failures.length===0,failures}}
|
||||
|
||||
export async function runPerformanceCorpus(page,{runs=10,timeout=240000,includeLarge=false}={}){
|
||||
const cases=includeLarge?performanceCases:performanceCases.slice(0,2),records=[];
|
||||
for(const scenario of cases){if(page.setViewportSize)await page.setViewportSize(scenario.viewport);const view=scenario.iterations?{...scenario.view,baseIter:scenario.iterations,adaptive:false,processMode:'standard'}:{...scenario.view},samples=[];for(let run=0;run<runs+1;run++){const started=Date.now();await page.evaluate(async({view,timeout})=>{const t=globalThis.__MANDEL_TEST__;await t.setView(view);await t.waitForNumericComplete({timeout})},{view,timeout});const state=await page.evaluate(()=>globalThis.__MANDEL_TEST__.state()),metric=state.generationMetrics.at(-1),diagnostics=await page.evaluate(()=>globalThis.__MANDEL_TEST__.gpuDiagnostics());assert.equal(state.numericalFailures,0,scenario.id+' numerical failures');assert.equal(diagnostics?.uncapturedErrors?.length||0,0,scenario.id+' uncaptured GPU errors');assert.equal(diagnostics?.lossReason||'','',scenario.id+' device loss');if(run>0)samples.push({wallMs:Date.now()-started,metric})}records.push({id:scenario.id,viewport:scenario.viewport,target:!!scenario.target,absoluteP50Ms:scenario.absoluteP50Ms||0,absoluteP95Ms:scenario.absoluteP95Ms||0,runs:samples,summary:summarizeRuns(samples)})}
|
||||
const exports=await page.evaluate(async()=>({ss1:await globalThis.__MANDEL_TEST__.smokeExportTile({w:128,h:96,strict:true,ss:1}),ss2:await globalThis.__MANDEL_TEST__.smokeExportTile({w:128,h:96,strict:true,ss:2})}));
|
||||
assert.equal(exports.ss1.length,exports.ss1.expected);assert.equal(exports.ss2.length,exports.ss2.expected);
|
||||
const adapter=await page.evaluate(()=>globalThis.__MANDEL_TEST__.gpuDiagnostics()?.adapterInfo||{});
|
||||
return{version:'24.3.0',kind:'performance',date:new Date().toISOString(),adapter,records,exports};
|
||||
}
|
||||
|
||||
export async function runInteractionStabilityCorpus(page,{timeout=240000,expectProduction=false}={}){
|
||||
if(page.setViewportSize)await page.setViewportSize({width:800,height:600});
|
||||
const policy=await page.evaluate(()=>globalThis.__MANDEL_TEST__.stabilityPolicy());
|
||||
assert.equal(policy.adaptiveIterationQuantum,64);
|
||||
assert.equal(policy.blaFastProductionOptIn,false);
|
||||
assert.equal(policy.blaFastDisabled,true);
|
||||
assert.equal(policy.pixelFrontierProduction,expectProduction);
|
||||
assert.equal(policy.pixelFrontierCandidate,true);
|
||||
assert.equal(policy.pixelFrontierEnabled,true);
|
||||
assert.equal(policy.pixelFrontierWorkLimit,4000000);
|
||||
const records=[];
|
||||
for(const view of interactionStabilityViews){
|
||||
const result=await page.evaluate(async({view,timeout})=>{const t=globalThis.__MANDEL_TEST__;await t.setView(view);await t.waitForNumericComplete({timeout});return{state:t.state(),hashes:await t.fieldHashes(),policy:t.stabilityPolicy()}},{view,timeout});
|
||||
assert.notEqual(result.state.backend,'fast-bla',view.id+' timing-selected backend');
|
||||
assert.equal(result.state.numericalFailures,0,view.id+' numerical failures');
|
||||
records.push({id:view.id,...result});
|
||||
}
|
||||
assert.equal(records[0].state.iter,records[1].state.iter,'nearby zoom iteration envelope');
|
||||
assert.equal(records[0].hashes.metaSha256,records[2].hashes.metaSha256,'repeat meta hash');
|
||||
assert.equal(records[0].hashes.smoothSha256,records[2].hashes.smoothSha256,'repeat smooth hash');
|
||||
const adapter=await page.evaluate(()=>globalThis.__MANDEL_TEST__.gpuDiagnostics()?.adapterInfo||{});
|
||||
return{version:'24.3.0',kind:'interaction-stability',date:new Date().toISOString(),expectProduction,policy,adapter,records};
|
||||
}
|
||||
|
||||
export async function runWebGpuCorpus(page,options={}){const correctness=await runCorrectnessCorpus(page,options.correctness),stability=await runInteractionStabilityCorpus(page,options.stability),performance=await runPerformanceCorpus(page,options.performance);return{correctness,stability,performance}}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {correctnessCases,performanceCases,interactionStabilityViews,evaluateProductionPerformance} from './optimization_webgpu_corpus.mjs';
|
||||
|
||||
const root=fileURLToPath(new URL('../',import.meta.url));
|
||||
const artifactPath=process.argv[2];
|
||||
const phase=process.argv[3]||'preflight';
|
||||
assert.ok(artifactPath,'usage: node tests/production_gate.mjs <gate-bundle.json> [preflight|production]');
|
||||
assert.ok(phase==='preflight'||phase==='production','phase must be preflight or production');
|
||||
|
||||
const bundle=JSON.parse(await fs.readFile(artifactPath,'utf8'));
|
||||
const index=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');
|
||||
const production=/const PIXEL_FRONTIER_PRODUCTION=true,PIXEL_FRONTIER_WORK=4000000/.test(index);
|
||||
assert.equal(production,phase==='production','index production flag does not match gate phase');
|
||||
assert.ok(Array.isArray(bundle.candidateRuns)&&bundle.candidateRuns.length>=2,'at least two candidate adapter runs are required');
|
||||
assert.ok(Array.isArray(bundle.baselineRuns)&&bundle.baselineRuns.length>=2,'matching baseline runs are required');
|
||||
|
||||
function adapterKey(run){const a=run?.correctness?.adapter||run?.performance?.adapter||{};return[a.vendor,a.architecture,a.device,a.description].map(x=>String(x||'')).join('|')}
|
||||
function gpuErrors(diagnostics){return(diagnostics?.compilation||[]).flatMap(x=>x.messages||[]).filter(x=>x.type==='error').length+(diagnostics?.uncapturedErrors?.length||0)+(diagnostics?.lossReason?1:0)}
|
||||
function auditCandidate(run){
|
||||
assert.equal(run?.correctness?.expectProduction,phase==='production','correctness phase mismatch');
|
||||
assert.equal(run?.correctness?.compile?.productionEligible,phase==='production','compile production eligibility mismatch');
|
||||
assert.equal(run?.correctness?.compile?.candidateEnabled,true,'candidate path was not enabled');
|
||||
assert.equal(run?.correctness?.records?.length,correctnessCases.length,'incomplete correctness corpus');
|
||||
for(const record of run.correctness.records){
|
||||
assert.equal(record.audit?.coverageUnknown,0,record.id+' coverage UNKNOWN');
|
||||
assert.equal(record.audit?.numericalUnknown,0,record.id+' numeric UNKNOWN');
|
||||
assert.equal(record.numericComplete,true,record.id+' numeric completion');
|
||||
assert.equal(record.state?.historyReady,true,record.id+' history commit');
|
||||
assert.equal(record.state?.frontierConverged,true,record.id+' frontier convergence');
|
||||
assert.ok((record.state?.frontierMaxDispatchWork||0)<=4000000,record.id+' dispatch work limit');
|
||||
if((record.audit?.operationLimit||0)>0)assert.ok(record.state.frontierIter>=record.state.frontierMinimumIter,record.id+' stopped below pixel-scale iteration floor');
|
||||
assert.equal(gpuErrors(record.diagnostics),0,record.id+' GPU diagnostics');
|
||||
if(record.frontier)assert.equal(record.frontier.gatePassed,true,record.id+' frontier oracle');
|
||||
if(record.activeDirect)assert.equal(record.activeDirect.gatePassed,true,record.id+' Direct active shadow');
|
||||
if(record.activeDeep)assert.equal(record.activeDeep.gatePassed,true,record.id+' Deep active shadow');
|
||||
if(record.certified)assert.equal(record.certified.gatePassed,true,record.id+' certified interior shadow');
|
||||
}
|
||||
assert.equal(run?.stability?.expectProduction,phase==='production','stability phase mismatch');
|
||||
assert.equal(run?.stability?.records?.length,interactionStabilityViews.length,'incomplete interaction corpus');
|
||||
const stability=run.stability.records;
|
||||
assert.equal(stability[0].hashes.metaSha256,stability[2].hashes.metaSha256,'round-trip meta instability');
|
||||
assert.equal(stability[0].hashes.smoothSha256,stability[2].hashes.smoothSha256,'round-trip smooth instability');
|
||||
assert.equal(run?.performance?.records?.length,performanceCases.length,'full performance corpus is required');
|
||||
for(const record of run.performance.records){
|
||||
assert.ok((record.runs?.length||0)>=10,record.id+' requires 10 measured runs');
|
||||
assert.ok(Number.isFinite(record.summary?.recovery?.p50)&&Number.isFinite(record.summary?.recovery?.p95),record.id+' summary missing');
|
||||
}
|
||||
}
|
||||
|
||||
const candidateKeys=new Set();
|
||||
const baselineByAdapter=new Map(bundle.baselineRuns.map(run=>[adapterKey(run),run]));
|
||||
const performance=[];
|
||||
for(const run of bundle.candidateRuns){
|
||||
auditCandidate(run);
|
||||
const key=adapterKey(run);assert.ok(key.replaceAll('|',''),'candidate adapter identity is empty');candidateKeys.add(key);
|
||||
const baseline=baselineByAdapter.get(key);assert.ok(baseline,'missing same-adapter baseline for '+key);
|
||||
const result=evaluateProductionPerformance(run.performance,baseline.performance);assert.equal(result.pass,true,result.failures.join('\n'));
|
||||
performance.push({adapter:key,...result});
|
||||
}
|
||||
assert.ok(candidateKeys.size>=2,'candidate evidence must cover at least two distinct adapters');
|
||||
|
||||
const output={pass:true,phase,adapters:[...candidateKeys],correctnessCases:correctnessCases.length,performanceCases:performanceCases.length,interactionCases:interactionStabilityViews.length,performance};
|
||||
process.stdout.write(JSON.stringify(output,null,2)+'\n');
|
||||
189
tests/regression.mjs
Normal file
189
tests/regression.mjs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import vm from 'node:vm';
|
||||
import {createHash} from 'node:crypto';
|
||||
|
||||
const html=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');
|
||||
const scripts=[...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]);
|
||||
assert.equal(scripts.length,2);
|
||||
for(const script of scripts)new vm.Script(script);
|
||||
assert.doesNotMatch(html,/\bBLA\b|bla[A-Z]|useBla|run\w*Shadow|Canary|frontier-gate/);
|
||||
|
||||
// Pin the reviewed kernels; numerical behavior is also exercised on the GPU.
|
||||
const kernelContext={};vm.runInNewContext(scripts[0],kernelContext);
|
||||
const kernels=kernelContext.MANDEL_WEBGPU_KERNELS;
|
||||
const expected=JSON.parse(await fs.readFile(new URL('./kernel_hashes.json',import.meta.url),'utf8'));
|
||||
assert.deepEqual(Object.fromEntries(Object.entries(kernels).map(([k,v])=>[k,createHash('sha256').update(v).digest('hex')])),expected);
|
||||
|
||||
function appContext(search=''){
|
||||
const element={width:800,height:600,clientWidth:800,clientHeight:600,style:{},addEventListener(){},classList:{toggle(){}},setAttribute(){}};
|
||||
const context={MANDEL_WEBGPU_KERNELS:kernels,document:{querySelector:()=>element},navigator:{hardwareConcurrency:4},location:{search},performance,URLSearchParams,TextEncoder,console,addEventListener(){}};
|
||||
const code=scripts[1].slice(0,scripts[1].indexOf('// ── boot / teardown'))+
|
||||
'globalThis.internal={state,runtime,refs,refinePixelFrontier,setTestPresentation:fn=>{presentProvisional=fn},WebGpuRenderer,chooseBackend,startRenderClock,finishRenderClock,renderTimeLabel,fromDec,operationLimitIndices,numericalFailureIndices,pixelPrecisionBits,precisionBatchSize,certifiedInteriorTiles,referenceWorkerSource,fastReferenceWorkerSource,precisionFallbackWorkerSource};})();';
|
||||
vm.runInNewContext(code,context);
|
||||
return context;
|
||||
}
|
||||
const normal=appContext(),diagnostic=appContext('?test');
|
||||
assert.equal(normal.__MANDEL_TEST__,undefined);
|
||||
assert.equal(typeof diagnostic.__MANDEL_TEST__.state,'function');
|
||||
const a=normal.internal;
|
||||
assert.equal(a.runtime.generationMetrics.length,0);
|
||||
|
||||
// Wall time survives stage boundaries; stale completions cannot finish a new
|
||||
// generation. No real timer remains running after completion or cancellation.
|
||||
{
|
||||
const c=appContext(),clock=c.internal;let now=100,live=0;
|
||||
c.performance={now:()=>now};c.setInterval=()=>{live++;return live};c.clearInterval=()=>{live--};
|
||||
clock.startRenderClock(1,now);now=2240;
|
||||
assert.equal(clock.renderTimeLabel(),'描画中… 2.14 s');
|
||||
clock.finishRenderClock(1);assert.equal(live,0);assert.equal(clock.state.lastRender,2140);
|
||||
now=5000;assert.equal(clock.renderTimeLabel(),'2.14 s');
|
||||
clock.startRenderClock(2,now);now=5400;clock.finishRenderClock(1);
|
||||
assert.equal(clock.state.renderClock.status,'running');assert.equal(live,1);
|
||||
clock.finishRenderClock(2,'cancelled');assert.equal(clock.renderTimeLabel(),'中断 400 ms');assert.equal(live,0);
|
||||
}
|
||||
|
||||
// Exercise actual backend selection, including both perturbation paths.
|
||||
const backendCases=[['3.4','fast','direct'],['0.00000000000034','fast','fast-extended'],['3.4','accurate','direct'],['0.00000000000034','accurate','fast-extended']];
|
||||
for(const [span,mode,expectedBackend] of backendCases){
|
||||
a.state.renderMode=mode;
|
||||
const snap={bits:256,re:a.fromDec('-0.743643887037151'),im:a.fromDec('0.13182590420533'),span:a.fromDec(span)};
|
||||
assert.equal(a.chooseBackend(snap,800).backend,expectedBackend);
|
||||
}
|
||||
|
||||
// Recovery must distinguish iteration limits from numerical failures.
|
||||
const fixture=Uint32Array.from([6<<20,1<<20,2<<20,3<<20,4<<20,5<<20,1<<28,3<<28,0]);
|
||||
assert.deepEqual(Array.from(a.operationLimitIndices(fixture)),[0]);
|
||||
assert.deepEqual(Array.from(a.numericalFailureIndices(fixture)),[1,2,3,4,5]);
|
||||
|
||||
const renderer=Object.create(a.WebGpuRenderer.prototype);
|
||||
for(const width of [320,800,1920,3840])for(const iter of [350,2900,12000,150000])for(const deep of [false,true]){
|
||||
const {width:tileWidth,rows}=renderer.numericTileShape(width,iter,deep);
|
||||
assert.ok(rows>=1&&tileWidth>=1);
|
||||
assert.ok(tileWidth*rows*iter<=(deep?4000000:48000000));
|
||||
assert.ok(renderer.adaptNumericRows(rows,100,600,deep)<=rows);
|
||||
const coverage=new Uint8Array(width*3);
|
||||
for(let y=0;y<3;y+=rows)for(let x=0;x<width;x+=tileWidth)
|
||||
for(let dy=0;dy<Math.min(rows,3-y);dy++)for(let dx=0;dx<Math.min(tileWidth,width-x);dx++)coverage[(y+dy)*width+x+dx]++;
|
||||
assert.ok(coverage.every(n=>n===1),'tile coverage');
|
||||
assert.ok(a.precisionBatchSize(iter,256)<=64);
|
||||
}
|
||||
|
||||
// Catch dangling renderer calls after removing whole diagnostic subsystems.
|
||||
const rendererSource=scripts[1].slice(scripts[1].indexOf('class WebGpuRenderer{'),scripts[1].indexOf('function numericalFailureCount'));
|
||||
for(const [,method] of rendererSource.matchAll(/this\.(\w+)\(/g))assert.equal(typeof a.WebGpuRenderer.prototype[method],'function',method);
|
||||
|
||||
// Test actual frame routing, especially the argument following removed BLA plans.
|
||||
{
|
||||
const r=Object.create(a.WebGpuRenderer.prototype);let actualContext;
|
||||
Object.assign(r,{ready:Promise.resolve(),device:{},ensureFastPipelines:async()=>{},ensureFrame:()=>({}),setFastContext(ctx){actualContext=ctx},computePerturbFrameTiled:async(...args)=>{assert.equal(args[2],false);return true}});
|
||||
const ctx={referenceId:'fast-test'};
|
||||
assert.equal(await r.computeFrame({},350,0,null,true,ctx),true);
|
||||
assert.equal(actualContext,ctx);
|
||||
}
|
||||
|
||||
// Run every surviving Worker, including real reference generation and fallback.
|
||||
function worker(source,message){
|
||||
let result;const c={self:{},postMessage:r=>{result=r},performance};
|
||||
vm.runInNewContext(source,c);c.self.onmessage({data:message});
|
||||
assert.ok(result);assert.notEqual(result.type,'error',result.error);return result;
|
||||
}
|
||||
const scale=1n<<256n;
|
||||
for(const center of [0n,-scale,scale]){
|
||||
const message={type:'build',id:1,key:'test',bits:256,sourceBits:256,targetBits:128,re:center.toString(),im:'0',span:'0',width:1,height:1,iter:64};
|
||||
const deep=worker(a.referenceWorkerSource(),message),fast=worker(a.fastReferenceWorkerSource(),message);
|
||||
assert.equal(deep.checkpointMismatch,false);
|
||||
assert.equal(deep.refLen,fast.refLen);
|
||||
assert.deepEqual(Buffer.from(deep.refs),Buffer.from(fast.refs));
|
||||
assert.equal(deep.escape,center===scale?3:0);
|
||||
}
|
||||
for(const [center,escaped] of [[0n,false],[scale*3n,true]]){
|
||||
const result=worker(a.precisionFallbackWorkerSource(),{type:'solve',id:1,bits:256,targetBits:128,re:center.toString(),im:'0',span:'0',width:1,height:1,iter:64,terminalClass:-1,indices:Uint32Array.of(0).buffer});
|
||||
assert.equal(new Uint8Array(result.accepted)[0],1);
|
||||
assert.equal((new Uint32Array(result.meta)[0]>>>28)===1,escaped);
|
||||
}
|
||||
// Reuse a live reference Worker across increasing and decreasing budgets.
|
||||
{
|
||||
let result;const c={self:{},postMessage:r=>{result=r},performance};
|
||||
vm.runInNewContext(a.referenceWorkerSource(),c);
|
||||
const message={type:'build',id:1,bits:256,targetBits:128,re:'0',im:'0',span:'0',width:1,height:1,fixedRe:'0',fixedIm:'0'};
|
||||
let id;
|
||||
for(const iter of [64,256,512,128]){
|
||||
c.self.onmessage({data:{...message,iter,key:String(iter)}});
|
||||
assert.equal(result.checkpointMismatch,false);assert.equal(result.refLen,iter);
|
||||
const independent=worker(a.referenceWorkerSource(),{...message,iter});
|
||||
assert.deepEqual(Buffer.from(result.refs),Buffer.from(independent.refs));
|
||||
if(id)assert.equal(result.referenceId,id);id=result.referenceId;
|
||||
if(iter===512){assert.equal(result.extendedFrom,256);assert.ok(result.checkpointCount<=3)}
|
||||
}
|
||||
c.self.onmessage({data:{...message,iter:512,targetBits:192}});
|
||||
assert.notEqual(result.referenceId,id);assert.equal(result.extendedFrom,0);
|
||||
}
|
||||
// Instrument the actual fallback recurrence: failed comparisons must reuse
|
||||
// the preceding high-precision result (five orbits instead of eight).
|
||||
{
|
||||
let result;const calls=[];const c={self:{},performance,postMessage:r=>{result=r},record:b=>calls.push(b)};
|
||||
vm.runInNewContext(a.precisionFallbackWorkerSource(),c);
|
||||
vm.runInNewContext('pixel=(s,i,bits)=>{record(bits);return {escape:bits,smooth:bits}}',c);
|
||||
c.self.onmessage({data:{type:'solve',id:1,bits:256,targetBits:128,re:'0',im:'0',span:'0',width:1,height:1,iter:64,indices:Uint32Array.of(0).buffer}});
|
||||
assert.deepEqual(calls,[128,160,192,224,256]);assert.equal(new Uint8Array(result.accepted)[0],0);
|
||||
}
|
||||
// A late CPU completion must be rejected before any GPU write or allocation.
|
||||
assert.equal(await renderer.applyPrecisionFallback({indices:Uint32Array.of(0)},a.state.token-1),null);
|
||||
// Exact rational certificates include only strict interior rectangles. Probe
|
||||
// both sides of the cardioid cusp and the period-2 boundary at 256-bit depth.
|
||||
{
|
||||
const one=1n<<256n;
|
||||
for(const [re,expected] of [[0n,true],[-one,true],[one/4n,false],[one/4n-1n,true],[one/4n+1n,false],[-5n*one/4n,false],[-5n*one/4n+1n,true],[-5n*one/4n-1n,false]]){
|
||||
const result=a.certifiedInteriorTiles({bits:256,re,im:0n,span:0n},1,1);
|
||||
assert.equal(result.pixels===1,expected,re.toString());
|
||||
}
|
||||
const touching=a.certifiedInteriorTiles({bits:256,re:one/4n,im:0n,span:one/100n},32,32);
|
||||
assert.equal(touching.pixels,0,'rectangle crossing cusp must stay in the normal queue');
|
||||
}
|
||||
// Exercise the actual frontier controller with one repaired pixel and one
|
||||
// healthy survivor. GPU state/queue correctness is tested in browser_smoke.
|
||||
{
|
||||
const snap={bits:256,re:0n,im:0n,span:a.fromDec('3.4')};Object.assign(a.state,snap);
|
||||
const ctx={referenceId:'fixture',key:'fixture',source:snap,refLen:4096};
|
||||
a.refs.request=async()=>ctx;a.refs.requestFixed=async()=>ctx;a.setTestPresentation(async()=>true);
|
||||
const field=Uint32Array.of(6<<20|512,6<<20|512),replayed=[];let initializations=0,advances=0;
|
||||
const stats=()=>({total:2,reasons:{errorBound:(field[0]>>20)===1?1:0,operationLimit:(field[0]>>20)===1?1:2}});
|
||||
const r={certifyInterior:async()=>0,readMetaAll:async()=>field.slice(),readUnresolvedStats:async()=>stats(),setDeepContext(){},
|
||||
beginOperationLimitContinuation:async()=>{initializations++;return{active:2,pixelIterations:0,dispatches:0}},
|
||||
advancePriorityTarget:async(session,_snap,target)=>{advances++;assert.ok(target>512);field[0]=advances===1?1<<20:field[0];field[1]=6<<20|target;session.active=1;session.pixelIterations+=1;session.dispatches++;return session},
|
||||
correctUnknownFrame:async()=>{field[0]=6<<20|1024;return{mode:'queue'}},
|
||||
readActiveIndices:async()=>Uint32Array.of(1),
|
||||
refineOperationLimitIndices:async(_snap,target,_token,_ctx,_pixel,indices)=>{replayed.push(Array.from(indices));for(const i of indices)field[i]=6<<20|target;return{stats:stats()}}
|
||||
};
|
||||
const result=await a.refinePixelFrontier(r,snap,512,a.state.token,stats());
|
||||
assert.equal(initializations,1,'a repair must not reset the healthy session');
|
||||
assert.equal(advances,2);assert.deepEqual(replayed,[[0]]);assert.equal(result.converged,true);
|
||||
assert.equal(field[0]&0xfffff,result.iter);assert.equal(field[1]&0xfffff,result.iter);
|
||||
}
|
||||
{
|
||||
const prior=new Uint32Array(5100).fill(6<<20|512);prior[0]=1<<28|10;
|
||||
const q={kind:'queue'},c={kind:'count'},steps=[];let entries=Uint32Array.from({length:5000},(_,i)=>i+1);
|
||||
const r=Object.create(a.WebGpuRenderer.prototype),session={active:5000,progress:512,f:{w:100,h:51},queues:[q,q],counts:[c,c],input:0};
|
||||
r.device={queue:{writeBuffer:(target,_offset,data)=>{if(target.kind==='queue')entries=Uint32Array.from(data)}}};
|
||||
r.readActiveIndices=async()=>entries.slice();
|
||||
r.continueOperationLimitActive=async(s,_snap,target)=>{steps.push({count:s.active,target});s.progress=target;return s};
|
||||
const result=await r.advancePriorityTarget(session,{},4096,a.state.token,{}, {},prior);
|
||||
assert.equal(steps.length,2);assert.ok(steps[0].count<steps[1].count);
|
||||
assert.equal(steps[0].count+steps[1].count,5000);assert.ok(steps.every(s=>s.target===4096));
|
||||
assert.equal(result.active,5000);assert.equal(result.progress,4096);
|
||||
assert.deepEqual(Array.from(entries).sort((x,y)=>x-y),Array.from({length:5000},(_,i)=>i+1));
|
||||
}
|
||||
// Exhausting the automatic finite budget is a valid image only when every
|
||||
// remaining pixel reached it. A stale lower-iteration field is still rejected.
|
||||
{
|
||||
const snap={bits:256,re:0n,im:0n,span:a.fromDec('3.4')};Object.assign(a.state,snap,{continuationBudget:0});
|
||||
const stats={total:1,reasons:{operationLimit:1}};
|
||||
for(const [n,complete] of [[150000,true],[149999,false]]){
|
||||
const r={certifyInterior:async()=>0,readMetaAll:async()=>Uint32Array.of(6<<20|n),readUnresolvedStats:async()=>stats};
|
||||
const result=await a.refinePixelFrontier(r,snap,150000,a.state.token,stats);
|
||||
assert.equal(result.converged,complete);assert.equal(result.membershipCertified,false);
|
||||
assert.equal(result.policy,'automatic-finite-cap');
|
||||
}
|
||||
}
|
||||
console.log(`PASS: scripts, ${Object.keys(kernels).length-1} pinned shaders, routing, tile coverage/work caps, reference extension, precision reuse, cancellation, generation timing, finite cap`);
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
from pathlib import Path
|
||||
import re, subprocess, tempfile, json
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
CUR=ROOT/'index.html'
|
||||
OLD=ROOT/'tests'/'KERNEL_HASHES_V24_2_54.json'
|
||||
NEW=ROOT/'tests'/'KERNEL_HASHES_V24_3_0.json'
|
||||
CORPUS=ROOT/'tests'/'optimization_webgpu_corpus.mjs'
|
||||
|
||||
def scripts(path): return re.findall(r'<script[^>]*>(.*?)</script>',path.read_text(),re.S)
|
||||
|
||||
def syntax(path):
|
||||
for i,code in enumerate(scripts(path)):
|
||||
q=Path(tempfile.gettempdir())/f'mandel_v55_check_{i}.js'; q.write_text(code)
|
||||
r=subprocess.run(['node','--check',str(q)],capture_output=True,text=True)
|
||||
assert r.returncode==0, r.stderr
|
||||
|
||||
def kernels(path):
|
||||
code=scripts(path)[0]
|
||||
q=Path(tempfile.gettempdir())/'mandel_v55_kernel_dump.js'
|
||||
q.write_text(code+"\nconst crypto=require('crypto');const G=globalThis.MANDEL_WEBGPU_KERNELS;const out={};for(const k of Object.keys(G)){if(typeof G[k]==='string')out[k]=crypto.createHash('sha256').update(G[k]).digest('hex')}console.log(JSON.stringify(out));")
|
||||
r=subprocess.run(['node',str(q)],capture_output=True,text=True,check=True)
|
||||
return json.loads(r.stdout.strip().splitlines()[-1])
|
||||
|
||||
syntax(CUR)
|
||||
old=json.loads(OLD.read_text()); cur=kernels(CUR); expected=json.loads(NEW.read_text())
|
||||
assert cur==expected
|
||||
changed=[k for k,v in old.items() if cur.get(k)!=v]
|
||||
assert {'version','DIRECT_ACCURATE_SEED_WGSL','DIRECT_DS_CORRECT_WGSL','FAST_PERTURB_WGSL'}.issubset(changed), changed
|
||||
html=CUR.read_text()
|
||||
assert CORPUS.exists() and 'runCorrectnessCorpus' in CORPUS.read_text() and 'runPerformanceCorpus' in CORPUS.read_text() and 'runInteractionStabilityCorpus' in CORPUS.read_text()
|
||||
assert "version:'24.3.0-optimized-unknown-pipeline'" in html
|
||||
assert 'if(n>=p.lateThreshold){fieldMeta[out]=pack_unknown' in html
|
||||
assert 'var zr2=DS(0.0,0.0);var zi2=DS(0.0,0.0)' in html
|
||||
assert 'DIRECT_DS_QUEUE_BUILD_WGSL' in html and 'DIRECT_DS_CORRECT_QUEUE_WGSL' in html
|
||||
assert 'accurateQueuedPixels' in html and 'accurateQueueFallbacks' in html
|
||||
assert 'UNKNOWN_STATS_WGSL' in html and 'encodeUnknownStats' in html
|
||||
assert 'encodeDeepBucketHistogram(e' in html and 'encodeQueuedDeepCorrection(e' in html
|
||||
assert 'q.selected===q.enqueued&&q.enqueued===q.dispatchCount&&q.dispatchCount===q.processed' in html
|
||||
assert 'reusePreview' in html and 'numericInFlightBudgetMs' in html
|
||||
assert 'canUseStableReprojection()||fastNeedsExtended(snapshot(),canvas.width))return[finalIter]' in html
|
||||
assert 'const ADAPTIVE_ITER_QUANTUM=64' in html and 'Math.ceil(raw/quantum)*quantum' in html
|
||||
assert 'function pixelPrecisionBits(snap=snapshot(),w=canvas.width,guard=32)' in html
|
||||
assert 'targetBits=pixelPrecisionBits(snap,width,40)' in html and 'fastReferenceBits(snap,w=canvas.width)' in html
|
||||
assert 'const bits=baseBits+attempt*32,a=pixel(s,indices[i],bits),b=pixel(s,indices[i],bits+32)' in html
|
||||
assert 'for(let attempt=0;attempt<4&&!accepted[i];attempt++)' in html
|
||||
assert 'for(let start=0;start<indices.length;start+=chunkSize)' in html
|
||||
assert 'UNRESOLVED_BLACK_MIN_ZOOM_EXP' not in html and 'colorUnresolvedInsteadOfBlack' not in html and 'guardedFastDeep' not in html
|
||||
assert 'this.blaFastDisabled=true;this.blaFastOptIn=false' in html
|
||||
assert 'deep-stable-bootstrap' not in html and 'forceStableDeep' not in html
|
||||
assert 'A FAST frame is not publishable while any numeric UNKNOWN remains' in html
|
||||
assert 'if(initialFailed>0){const mr=await recoverNumericalUnknownTiles' in html
|
||||
assert 'if(numericalFailureCount(stats)>0){const pf=await recoverResidualPrecision' in html
|
||||
assert "if(isFinal){const remaining=numericalFailureCount" in html and 'frameを公開しません' in html
|
||||
assert 'viewRe:snap.re,viewIm:snap.im' in html and 'referenceOriginMatchesView(src,snap)' in html
|
||||
assert 'fastBackgroundDeepOnFailureOnly:true,deepProductionActive:false,deepCorrectionDispatchWork:PIXEL_FRONTIER_WORK' in html
|
||||
assert 'ensureAccuratePipelines' in html and 'ensureCorrectionPipelines' in html
|
||||
assert 'precisionFallbackWorkerSource' in html and 'recoverResidualPrecision' in html
|
||||
assert 'estimatedNetMs' in html and "reason:enabled?'probe-pass':!operationGate?'probe-cost':'probe-net-time'" in html
|
||||
replay=cur['BLA_GPU_FAST_REPLAY_CANDIDATE_WGSL']
|
||||
assert 'fn baseline_from(s0:S,pre:u32)->Result' in replay and 'var anchor=s;loop' in replay
|
||||
assert 'baseline(g,st)' not in replay and replay.count('baseline_from(anchor,st)')==7
|
||||
assert 'anchor=s;}}' in replay and 'baseline_from(anchor,st)' in cur['BLA_GPU_FAST_FRAME_WGSL']
|
||||
assert 'SYMMETRY_COPY_WGSL' in html and 'canUseRealAxisSymmetry' in html and 'encodeSymmetryCopy' in html
|
||||
assert 'FAILURE_TILE_MAP_WGSL' in html and 'readNumericalFailureTiles' in html
|
||||
assert "tileReferencePool=Array.from({length:3}" in html and 'Promise.all(batch.map' in html
|
||||
assert 'if(p.key===key)return p.promise' in html and 'prefetchFinalReference' in html
|
||||
assert 'this.exportWs=[]' in html and 'exportRingDepth(tile=this.exportTileSize())' in html
|
||||
assert 'Promise.allSettled(batch.map(item=>item.promise))' in html and 'if(failed)throw failed.reason' in html and 'performance.now()-lastYield<24' in html
|
||||
assert 'await png.rows(band);await new Promise(requestAnimationFrame)' not in html
|
||||
assert 'frameReadback(kind,size)' in html and "this.frameReadback('unknown-stats'" in html
|
||||
stats_readback=html[html.index('async readUnresolvedStats'):html.index('async readUnresolved(){')]
|
||||
assert 'destroy(r)' not in stats_readback
|
||||
assert 'cachedBindGroup(pipeline,entries)' in html and 'cachedTextureView(texture)' in html
|
||||
assert 'this.cachedBindGroup(this.direct' in html and 'this.cachedBindGroup(this.present' in html
|
||||
history_commit=html[html.index('commitHistory(view='):html.index('invalidateHistory(){')]
|
||||
assert 'f.history=f.front' in history_commit and 'copyTextureToTexture' not in history_commit
|
||||
assert 'colorTarget(f=this.frame)' in html and 'spare:history' in html
|
||||
assert 'ACTIVE_PREPARE_WGSL' in html and 'DIRECT_ACTIVE_CONTINUE_WGSL' in html
|
||||
assert 'DEEP_ACTIVE_CONTINUE_WGSL' in html and 'PERTURB_CERTIFIED_INTERIOR_WGSL' in html
|
||||
assert 'runDirectActiveShadow' in html and 'dispatchWorkgroupsIndirect(indirect,0)' in html
|
||||
assert 'direct-active-shadow-resume' in html and 'resumedFromPreview:previewIter>0' in html
|
||||
assert 'runDeepActiveShadow' in html and 'deep-active-shadow-resume' in html
|
||||
assert 'deep-active-compare-shadow' in html and 'runtime.deepActiveShadowLatest=result' in html
|
||||
assert 'computeDeepActiveFrame' in html and 'workPerDispatch=PIXEL_FRONTIER_WORK,maxPassesPerSubmit=1' in html
|
||||
assert 'const workBudget=deep?4000000:48000000' in html
|
||||
assert 'maxPixels=Math.max(1,Math.floor(4000000/Math.max(1,iter))),tileW=Math.min(f.w,maxPixels)' in html
|
||||
assert 'PIXEL_FRONTIER_PRODUCTION=true,PIXEL_FRONTIER_WORK=4000000,PIXEL_FRONTIER_MAX_ITER=150000' in html
|
||||
assert 'PIXEL_FRONTIER_ENABLED=PIXEL_FRONTIER_PRODUCTION||PIXEL_FRONTIER_CANDIDATE' in html
|
||||
assert 'function operationLimitFrontier(meta,w,h)' in html and 'refineOperationLimitIndices' in html
|
||||
assert 'function pixelFrontierIterationFloor(snap,w,initialIter)' in html and 'iter>=minimumIter&&stableRounds>=2' in html
|
||||
assert 'pixel frontier queue invariant failed' in html and 'compareCurrentToFrontierBaseline' in html
|
||||
assert 'correctUnknownFrame(snap,iter,token,referencePixel,numericalFailureCount(stats))' in html
|
||||
assert 'ensureDeepActiveWorkspace' in html and 'return this.computeDeepActiveFrame(snap,iter,token,forceStrict,referencePixel)' not in html
|
||||
assert 'return this.computePerturbFrameTiled(snap,iter,true,token,forceStrict,referencePixel,false,colorStyle,colorKey)' in html
|
||||
assert 'states[local]=DeepState' in html and 'let out=p.outputBase+(local/p.tileW)*p.outputStride' in html
|
||||
assert 'state.gpuInitFailed=true;markDirty(false);setTimeout' in html
|
||||
assert 'runCertifiedInteriorShadow' in html and 'gpu.provenIndices,FIELD_INTERIOR_PROVEN' in html
|
||||
assert 'falsePositive===0&&inconclusive===0' in html and 'oracleBits:[oracleBits,oracleBits+32]' in html
|
||||
assert 'waitForPrimary(' in html and 'waitForFinal(' in html and 'waitForNumericComplete(' in html
|
||||
assert 'fieldHashes()' in html and 'readFieldAll()' in html
|
||||
assert 'generationMetrics:[]' in html and 'primaryNumericalFailures' in html
|
||||
assert 'numericQuiescentMs' in html and 'historyCommitMs' in html
|
||||
recovery=html[html.index('async function recoverNumericalUnknownTiles'):html.index('async function recoverResidualPrecision')]
|
||||
assert 'readNumericalFailureTiles' in recovery and 'const currentMeta=await r.readMetaAll()' in recovery
|
||||
assert 'Uint32Array.from(tileIndices)' in recovery
|
||||
assert 'p.unknownOnly==2u && reason!=0u' in html
|
||||
assert "fast-coverage-repair-and-color" in html
|
||||
assert 'reprojectionSafe(rawHistory,2.25,.45)' in html
|
||||
assert 'scheduleFastNumericRecovery' in html and 'runFastNumericRecovery' in html
|
||||
assert 'numericFrameComplete()' in html
|
||||
assert "requestBuild({ctx,snap,iter,width,height,refPixel,safety=1/64})" in html
|
||||
assert "type:'build'" in html and 'buildOnly=d.type===\'build\'' in html
|
||||
assert not list(ROOT.glob('index.baseline*.html'))
|
||||
print('PASS: syntax; bounded Accurate seed; DS square reuse; shared UNKNOWN stats; sparse correction invariants; preview/reference reuse; bounded GPU bursts')
|
||||
Loading…
Add table
Add a link
Reference in a new issue