q
This commit is contained in:
parent
0e1c1f04fd
commit
b653d77d4e
49 changed files with 1636 additions and 4724 deletions
13
tests/ACCURATE_HYBRID_WEBGPU_RESULTS.json
Normal file
13
tests/ACCURATE_HYBRID_WEBGPU_RESULTS.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
8
tests/ACCURATE_SEED_FUZZ.json
Normal file
8
tests/ACCURATE_SEED_FUZZ.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"samples": 60000,
|
||||
"mixture": "global Mandelbrot bounds + targeted known boundary neighborhoods",
|
||||
"maxIter": 900,
|
||||
"acceptedRule": "f32 escape iteration < 24",
|
||||
"acceptedSamples": 33637,
|
||||
"acceptedEscapeIterationMismatchVsCpuDouble": 0
|
||||
}
|
||||
38
tests/ACTUAL_WEBGPU_COMPILE.json
Normal file
38
tests/ACTUAL_WEBGPU_COMPILE.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
10
tests/BASELINE_KERNEL_HASHES.json
Normal file
10
tests/BASELINE_KERNEL_HASHES.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
9
tests/BLA_PREP_BENCHMARK.json
Normal file
9
tests/BLA_PREP_BENCHMARK.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
23
tests/KERNEL_HASHES_V24_2_54.json
Normal file
23
tests/KERNEL_HASHES_V24_2_54.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
23
tests/KERNEL_HASHES_V24_2_55.json
Normal file
23
tests/KERNEL_HASHES_V24_2_55.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
37
tests/SCHEDULER_SYNC_MODEL.json
Normal file
37
tests/SCHEDULER_SYNC_MODEL.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"format": "mandelbrot-numeric-policy-v1",
|
||||
"id": "numeric-policy-v23",
|
||||
"rendererVersion": 23,
|
||||
"pixelContract": {
|
||||
"mapping": "centered-rational",
|
||||
"fixedPointRounding": "nearest-ties-away-from-zero",
|
||||
"fixedPointIntegerMismatchMax": 0,
|
||||
"float64CoordinateUlpMax": 4,
|
||||
"tileSeamPixelMismatchMax": 0
|
||||
},
|
||||
"validated": {
|
||||
"falseEscapedMax": 0,
|
||||
"falseInteriorProvenMax": 0,
|
||||
"unresolvedMax": 0,
|
||||
"escapeIterationAbsMax": 0,
|
||||
"magnitudeRelativeErrorMax": 1e-12,
|
||||
"referenceGuardBits": 64
|
||||
},
|
||||
"balanced": {
|
||||
"falseEscapedMax": 0,
|
||||
"falseInteriorProvenMax": 0,
|
||||
"interiorLikelyMismatch": "record",
|
||||
"unresolved": "record"
|
||||
},
|
||||
"color": {
|
||||
"smoothCorrectionRelativeErrorMax": 0.00003,
|
||||
"simdScalarRelativeErrorMax": 1e-7
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
{
|
||||
"format": "mandelbrot-scene-corpus-v2",
|
||||
"rendererVersion": 23,
|
||||
"pixelContract": "centered-rational-ties-away-from-zero",
|
||||
"numericPolicy": "numeric-policy-v23",
|
||||
"scenes": [
|
||||
{
|
||||
"id": "z0",
|
||||
"re": "-0.5",
|
||||
"im": "0",
|
||||
"span": "3.4",
|
||||
"coordinate": {"kind":"exact-rational","reNumerator":"-1","reDenominator":"2","imNumerator":"0","imDenominator":"1"},
|
||||
"oracle": {"kind":"overview","membership":"mixed"},
|
||||
"tags": ["shallow","overview"]
|
||||
},
|
||||
{
|
||||
"id": "period2-cusp-z14",
|
||||
"re": "-0.75",
|
||||
"im": "0",
|
||||
"span": "3.4e-14",
|
||||
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
|
||||
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
|
||||
"tags": ["deep","boundary","parabolic"]
|
||||
},
|
||||
{
|
||||
"id": "swirly-seahorses-z12",
|
||||
"re": "-0.7453983606667815",
|
||||
"im": "0.1125046349959942",
|
||||
"span": "3.52e-12",
|
||||
"coordinate": {"kind":"exact-rational","reNumerator":"-7453983606667815","reDenominator":"10000000000000000","imNumerator":"1125046349959942","imDenominator":"10000000000000000"},
|
||||
"oracle": {"kind":"published-performance-scene","membership":"mixed","iterations":2000,"source":"https://github.com/ckormanyos/mandelbrot#swirly-seahorses-and-mini-mandelbrot"},
|
||||
"tags": ["deep","boundary","performance"]
|
||||
},
|
||||
{
|
||||
"id": "period2-cusp-z20",
|
||||
"re": "-0.75",
|
||||
"im": "0",
|
||||
"span": "3.4e-20",
|
||||
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
|
||||
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
|
||||
"tags": ["deep","boundary","warm-reference","parabolic"]
|
||||
},
|
||||
{
|
||||
"id": "period2-cusp-z100",
|
||||
"re": "-0.75",
|
||||
"im": "0",
|
||||
"span": "3.4e-100",
|
||||
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
|
||||
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
|
||||
"tags": ["deep","precision","parabolic"]
|
||||
},
|
||||
{
|
||||
"id": "period3-interior",
|
||||
"re": "-0.122561166876653619975245551820735654052",
|
||||
"im": "0.744861766619744236593170428604392367240",
|
||||
"span": "1e-8",
|
||||
"coordinate": {"kind":"decimal","significantDigits":39,"guardDigitsAtViewport":27},
|
||||
"oracle": {"kind":"polynomial-root","polynomial":"c^3+2c^2+c+1","membership":"interior","period":3},
|
||||
"tags": ["interior","periodic"]
|
||||
},
|
||||
{
|
||||
"id": "period2-cusp-e280",
|
||||
"re": "-0.75",
|
||||
"im": "0",
|
||||
"span": "1e-280",
|
||||
"coordinate": {"kind":"exact-rational","reNumerator":"-3","reDenominator":"4","imNumerator":"0","imDenominator":"1"},
|
||||
"oracle": {"kind":"known-boundary","id":"main-cardioid-period2-root","membership":"boundary"},
|
||||
"tags": ["deep","deep-scaled-boundary","parabolic"]
|
||||
}
|
||||
],
|
||||
"viewports": [
|
||||
{"id":"mobile","cssWidth":390,"cssHeight":844,"dpr":3},
|
||||
{"id":"desktop","cssWidth":1440,"cssHeight":900,"dpr":2},
|
||||
{"id":"4k","cssWidth":3840,"cssHeight":2160,"dpr":1}
|
||||
]
|
||||
}
|
||||
38
tests/static_regression.py
Normal file
38
tests/static_regression.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
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_2_55.json'
|
||||
|
||||
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 changed==['version','FAST_PERTURB_WGSL'], changed
|
||||
html=CUR.read_text()
|
||||
assert "version:'24.2.55-no-black-hole-async-recovery'" in html
|
||||
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; only FAST repair gate changed in production kernels; coverage repair, broad temporal fill, async recovery, build-only BLA prep, single HTML')
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const s=await fs.readFile(new URL('./webgpu-acceptance.js',import.meta.url),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
must(/renderMode:'fast'/.test(s)&&/fast mode did not select Direct/.test(s),'manual fast acceptance gate missing');
|
||||
must(/renderMode:'accurate'/.test(s)&&/accurate mode did not select Deep/.test(s),'manual accurate acceptance gate missing');
|
||||
must(/referenceReused/.test(s)&&/pan did not reuse Deep reference/.test(s),'Deep pan reference reuse acceptance gate missing');
|
||||
must(/corrected production frame produced UNKNOWN/.test(s),'corrected Deep UNKNOWN gate missing');
|
||||
must(/forceDeep:true,correctUnknown:true/.test(s),'forced corrected Deep probe missing');
|
||||
must(/exportSmoke\.unresolved!==0/.test(s)&&/exportSmokeAA\.unresolved!==0/.test(s),'corrected export unresolved gate missing');
|
||||
console.log(JSON.stringify({status:'pass',checks:6,manualModes:true,panReferenceReuse:true},null,2));
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import vm from 'node:vm';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const root=new URL('../',import.meta.url);
|
||||
const script=await fs.readFile(new URL('script.js',root),'utf8');
|
||||
const kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
const sha=s=>crypto.createHash('sha256').update(s).digest('hex');
|
||||
const ctx={globalThis:{}};vm.runInNewContext(kernels,ctx);const G=ctx.globalThis.MANDEL_WEBGPU_KERNELS;
|
||||
const expected={
|
||||
DIRECT_F32_WGSL:'59301bf0c5ba3dda4a4055335b32d4a3f84a21991be30f8efc74deb5a5f11ccf',
|
||||
FAST_PERTURB_WGSL:'03b45713b1bbb7b56e18bd8fdf0f2d8161d7ed25adf02482b41c1038dc16e3b8',
|
||||
DEEP_PERTURB_WGSL:'2865a015d5c1a9b459085b7044e23563fcc5f0eec5362634249b6604a47e660a',
|
||||
COLOR_WGSL:'cccac7bfb69c64093c46c3468ff9d685d680ba3a88de0d1ea57168d91a067acf'
|
||||
};
|
||||
for(const [k,h] of Object.entries(expected))must(sha(G[k])===h,`${k} changed from v24.2.18 baseline`);
|
||||
|
||||
const fallback=script.match(/function renderFallback\(.*?\nasync function recolor/s)?.[0]?.replace(/\nasync function recolor$/,'');
|
||||
must(fallback,'fallback function not found');
|
||||
must(sha(fallback)==='689a6f56aedd416b5e512b17ab6a3ddebcf7c73494e0a8a600ce74a06d8073a2','fallback changed from v24.2.18 baseline');
|
||||
must(/function maxIter\(\)\{if\(!state\.adaptive\)return state\.baseIter;const z=zoomExp\(\),bonus=Math\.max\(0,Math\.floor\(70\*Math\.sqrt\(z\)\+15\*z\)\)/.test(script),'v24.2.18 maxIter policy changed');
|
||||
must(/scheduleIdleRefinement\(snap,iter,decision,token\);return;/.test(script),'fast idle refinement is not scheduled after primary completion');
|
||||
must(/presentFrame\(\{scaleX:1,scaleY:1,offsetX:0,offsetY:0\}\).*scheduleIdleRefinement\(snap,iter,decision,token\)/s.test(script),'accurate idle refinement is not scheduled after primary presentation');
|
||||
must(/ensureRefinePipelines\(\)/.test(script)&&/idleRefineTimer=setTimeout/.test(script),'refinement is not lazy/idle');
|
||||
const initBlock=script.slice(script.indexOf('async initPipelines(){'),script.indexOf('async ensureRefinePipelines(){'));
|
||||
must(!/LIKELY_QUEUE_WGSL|FAST_LIKELY_REFINE_WGSL|DEEP_LIKELY_REFINE_WGSL/.test(initBlock),'refinement shaders are compiled on primary startup path');
|
||||
must(/if\(\(\(fieldMeta\[out\]>>28u\)&3u\)!=FIELD_INTERIOR_LIKELY\)\{return;\}/.test(G.DEEP_LIKELY_REFINE_WGSL),'deep refinement can overwrite non-likely pixels');
|
||||
must(/setRefineDeepContext/.test(script)&&/this\.refineDeepCtx\.refsB/.test(script),'idle refinement replaces the primary Deep reference context');
|
||||
must(/async function paintIdleRefinement/.test(script)&&/async recolor\(token,iter=state\.fieldView\?\.iter\?\?maxIter\(\)\)/.test(script),'idle refinement does not preserve fieldView baseline color normalization');
|
||||
must(!/readMeta\(|readFrameMeta|copyBufferToBuffer\(f\.meta/.test(script.slice(script.indexOf('async function runIdleRefinement'),script.indexOf('function renderFallback'))),'idle refinement performs full meta readback');
|
||||
must(/REFINE_STATS_BYTES=16/.test(script),'queue readback is not constrained to 16-byte stats');
|
||||
must(/\.0006\*dt/.test(script)&&/\.012\*dt/.test(script),'automatic color speed is not 1/10 of v24.2.18');
|
||||
|
||||
const baseIter=z=>350+Math.floor(70*Math.sqrt(z)+15*z);
|
||||
const targetList=(z,base)=>{if(base>=8000)return[];const cap=Math.min(8000,Math.max(base*2,Math.round(1200+300*z))),raw=[Math.min(cap,base*2),Math.min(cap,base*4),cap],out=[];for(const v of raw){const n=Math.max(base+1,Math.floor(v));if(n>base&&(!out.length||n!==out.at(-1)))out.push(n)}return out};
|
||||
const batchPixels=(iter,budget)=>Math.max(64,Math.floor(Math.max(64,budget/Math.max(1,iter))/64)*64);
|
||||
const cssW=1920,cssH=1080,pixelBudget=1572864,dpr=Math.sqrt(pixelBudget/(cssW*cssH)),w=Math.round(cssW*dpr),h=Math.round(cssH*dpr),pixels=w*h;
|
||||
must(targetList(500,baseIter(500)).length===0,'idle refinement should stop once baseline iteration already exceeds 8000');
|
||||
const cases=[];
|
||||
for(const z of [6,8,12,20]){
|
||||
const base=baseIter(z),targets=targetList(z,base),row={zoomExp:z,baseIter:base,primaryWorstSampleIterations:pixels*base,targets,batches:{guardedIdle:[]}};
|
||||
for(const [name,budget] of [['guardedIdle',1800000]])for(const iter of targets){const px=batchPixels(iter,budget),work=px*iter;must(work<=budget,`${name} refinement batch exceeds budget at z${z}: ${work}>${budget}`);row.batches[name].push({iter,pixels:px,worstSampleIterations:work,budget})}
|
||||
cases.push(row);
|
||||
}
|
||||
console.log(JSON.stringify({status:'pass',check:'v24.2.18-baseline-plus-idle-refinement',baselineShaderHashes:expected,representativeDisplay:{css:[cssW,cssH],internal:[w,h],pixels},cases,qualityInvariant:'primary output is v24.2.18; idle refinement always uses guarded Deep, re-checks FIELD_INTERIOR_LIKELY, and never overwrites resolved pixels'},null,2));
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('script.js',root),'utf8'),kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8');
|
||||
const failures=[];if(/buildBla\(|BlaNode|useBla|blaLevels/.test(script+kernels))failures.push('BLA code remains in production');if(!/blaEnabled:false/.test(script))failures.push('sidecar/diagnostics do not declare BLA disabled');if(failures.length)throw new Error(failures.join('; '));
|
||||
console.log(JSON.stringify({status:'pass',kind:'bla-disabled-contract',reason:'dense swirly regression demonstrated unsafe f32-quantized BLA classifications',productionBla:false},null,2));
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const script=await fs.readFile(new URL('../script.js',import.meta.url),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
const base=350,deltaCycle=0.001,relativeIteration=0.6;
|
||||
function effectiveCycle(cycle,iter){return cycle*base/Math.max(base,iter)}
|
||||
const changes=[350,3500,35000].map(iter=>relativeIteration*iter*(effectiveCycle(0.008+deltaCycle,iter)-effectiveCycle(0.008,iter)));
|
||||
const spread=Math.max(...changes)-Math.min(...changes);
|
||||
must(spread<1e-12,'zoom-relative cycle response is not invariant');
|
||||
const min=.001,max=.05,N=1000;
|
||||
const sliderToCycle=v=>min*Math.pow(max/min,v/N);
|
||||
const ratios=[100,500,900].map(v=>sliderToCycle(v+50)/sliderToCycle(v));
|
||||
must(Math.max(...ratios)-Math.min(...ratios)<1e-12,'log slider does not preserve equal multiplicative steps');
|
||||
must(/effectiveColorCycle\(iter/.test(script)&&/sliderToCycle/.test(script),'production relative color functions missing');
|
||||
console.log(JSON.stringify({status:'pass',check:'zoom-relative-color-cycle',phaseDelta:changes[0],sliderStepRatio:ratios[0]},null,2));
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
function rd(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
|
||||
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const[m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits);if(neg)n=-n;return[n,10n**BigInt(places)]}
|
||||
function fixed(t,b){const[n,d]=decRatio(t);return rd(n*(1n<<BigInt(b)),d)}
|
||||
function fmt(v,bits){let neg=v<0n;if(neg)v=-v;const scale=10n**BigInt(bits),q=v*scale>>BigInt(bits);let s=q.toString().padStart(bits+1,'0');s=s.slice(0,-bits)+'.'+s.slice(-bits);s=s.replace(/(\.\d*?)0+$/,'$1').replace(/\.$/,'');return(neg?'-':'')+s}
|
||||
function parseFixed(s,bits){const[n,d]=decRatio(s);return n*(1n<<BigInt(bits))/d}
|
||||
for(const [text,bits] of [['1e-400',1569],['1e-1000',3562],['-0.75',1569]]){const v=fixed(text,bits),s=fmt(v,bits);if(s==='0'||s==='-0')throw new Error('deep formatter collapsed '+text);const r=parseFixed(s,bits);if(r!==v)throw new Error('exact decimal roundtrip failed '+text);}
|
||||
console.log(JSON.stringify({status:'pass',kind:'exact-fixed-decimal-format',deep:['1e-400','1e-1000']},null,2));
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const corpus=JSON.parse(await fs.readFile(new URL('./scenes.json',import.meta.url),'utf8'));const map=new Map(corpus.scenes.map(s=>[s.id,s]));map.set('period2-cusp-e400',{id:'period2-cusp-e400',re:'-0.75',im:'0',span:'1e-400'});const F=Math.fround;
|
||||
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
|
||||
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const [m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits),d=10n**BigInt(places);if(neg)n=-n;return[n,d]}
|
||||
function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),d)}function bitsForSpan(t){const s=String(t).toLowerCase(),a=s.split('e'),m=Math.abs(Number(a[0])),e=a[1]?Number(a[1]):0,l2=Math.log2(m)+e*Math.log2(10);return Math.max(256,Math.ceil(-l2)+240)}function snap(scene){const bits=bitsForSpan(scene.span);return{bits,re:decFixed(scene.re,bits),im:decFixed(scene.im,bits),span:decFixed(scene.span,bits)}}
|
||||
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}function log2FixedAt(v,b){v=v<0n?-v:v;if(v===0n)return-Infinity;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh));return Math.log2(top)+sh-b}function spanME(s){const l=log2FixedAt(s.span,s.bits),exp=Math.floor(l);return{mant:F(Math.pow(2,l-exp)),exp}}
|
||||
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
|
||||
function direct(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
|
||||
function buildRef(s,limit){const b=s.bits+64,cr=align(s.re,s.bits,b),ci=align(s.im,s.bits,b),bail=16n<<BigInt(b);let zr=0n,zi=0n,refLen=limit;const hi=new Float32Array((limit+1)*2),lo=new Float32Array((limit+1)*2);for(let n=0;n<=limit;n++){const rr=fixedNum(zr,b),ri=fixedNum(zi,b),hr=F(rr),hii=F(ri);hi[n*2]=hr;hi[n*2+1]=hii;lo[n*2]=F(rr-hr);lo[n*2+1]=F(ri-hii);if(n===limit)break;const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;const mag=roundShift(zr*zr,b)+roundShift(zi*zi,b);if(mag>bail){refLen=n+1;const rr1=fixedNum(zr,b),ri1=fixedNum(zi,b),hr1=F(rr1),hi1=F(ri1);hi[refLen*2]=hr1;hi[refLen*2+1]=hi1;lo[refLen*2]=F(rr1-hr1);lo[refLen*2+1]=F(ri1-hi1);break}}return{hi,lo,refLen}}
|
||||
function cmul(ar,ai,br,bi){return[F(F(ar*br)-F(ai*bi)),F(F(ar*bi)+F(ai*br))]}function scale(v,e){if(e<-126)return 0;if(e>126)return F(8.507059e37);return F(v*Math.pow(2,e))}
|
||||
function perturb(ref,s,w,h,x,y,limit,strict=false){const se=spanME(s),dx=F((x+.5-.5*w)/w),dy=F((.5*h-y-.5)/w),U=2**-24;let dr=F(se.mant*dx),di=F(se.mant*dy),wr=0,wi=0,e=se.exp,n=0,m=0,ops=0,err=1*U*Math.max(Math.abs(dr),Math.abs(di));while(true){if(n>=limit){const rp=Math.min(m,ref.refLen)*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),ea=Math.abs(err*2**e)+1*U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30),threshold=strict?1e-4:1e-3;return ea<=threshold?{kind:'bounded',n,err:ea}:{kind:'unknown',n,reason:'error-bound',err:ea}}if(m>ref.refLen)return{kind:'unknown',n,reason:'ref'};const rp=m*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),mag=F(F(zr*zr)+F(zi*zi)),ea=Math.abs(err*2**e)+1*U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);if(mag>4){if(Math.hypot(zr,zi)-ea>2)return{kind:'escaped',n,err:ea};return{kind:'unknown',n,reason:'escape-uncertain',err:ea}}const dmag=F(F(delr*delr)+F(deli*deli));if(m>0&&dmag>0&&mag<dmag){if(se.exp-e<-96)return{kind:'unknown',n,reason:'rebase-gap'};err=ea;wr=zr;wi=zi;dr=scale(F(se.mant*dx),se.exp);di=scale(F(se.mant*dy),se.exp);e=0;m=0;err+=1*U*Math.max(Math.abs(dr),Math.abs(di));continue}if(m>=ref.refLen)return{kind:'unknown',n,reason:'ref-end'};const rr=ref.hi[rp],ri=ref.hi[rp+1],lr=ref.lo[rp],li=ref.lo[rp+1],refAbs=Math.max(Math.abs(rr),Math.abs(ri))+Math.max(Math.abs(lr),Math.abs(li)),wAbs=Math.max(Math.abs(wr),Math.abs(wi)),dAbs=Math.max(Math.abs(dr),Math.abs(di)),p2=Math.abs(2**e),gain=2*refAbs+2*wAbs*p2,roundErr=1*U*(2*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1e-30);err=gain*err+roundErr;const[a,b]=cmul(rr,ri,wr,wi),[c,d]=cmul(lr,li,wr,wi),[sqR,sqI]=cmul(wr,wi,wr,wi);wr=F(F(F(2*a)+F(2*c))+F(scale(sqR,e)+dr));wi=F(F(F(2*b)+F(2*d))+F(scale(sqI,e)+di));m++;n++;ops++;if(!Number.isFinite(wr)||!Number.isFinite(wi)||!Number.isFinite(err)||Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di))>=1e30||err>1e35)return{kind:'unknown',n,reason:'range'};const mm=Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di));if(mm>65536){wr=F(wr/65536);wi=F(wi/65536);dr=F(dr/65536);di=F(di/65536);err/=65536;e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wr=F(wr*65536);wi=F(wi*65536);dr=F(dr*65536);di=F(di*65536);err*=65536;e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n,reason:'guard'}}}
|
||||
const cfg=[['period2-cusp-z14',1200],['swirly-seahorses-z12',2000],['period2-cusp-z20',1800],['period2-cusp-z100',2400],['period3-interior',1200],['period2-cusp-e280',2400],['period2-cusp-e400',1600]],w=17,h=11,pts=[];
|
||||
for(let y=0;y<h;y++)for(let x=0;x<w;x++)pts.push([x,y]);
|
||||
const report=[];
|
||||
for(const mode of [{name:'balanced',strict:false},{name:'strict',strict:true}]){
|
||||
for(const[id,limit]of cfg){
|
||||
const sc=map.get(id),s=snap(sc),ref=buildRef(s,limit);
|
||||
let guardMismatch=0,falseEscaped=0,falseBounded=0,unknown=0,exactEscape=0;
|
||||
const mismatch=[];
|
||||
for(const[x,y]of pts){
|
||||
const p=pixel(s,w,h,x,y),g=pixel(s,w,h,x,y,s.bits+64),a=direct(p[0],p[1],s.bits,limit),ag=direct(g[0],g[1],s.bits+64,limit);
|
||||
if(a!==ag){guardMismatch++;continue}
|
||||
const r=perturb(ref,s,w,h,x,y,limit,mode.strict);
|
||||
if(r.kind==='unknown'){unknown++;continue}
|
||||
if(r.kind==='escaped'&&a===limit)falseEscaped++;
|
||||
if(r.kind==='bounded'&&a<limit)falseBounded++;
|
||||
if(r.kind==='escaped'&&a<limit&&r.n===a)exactEscape++;
|
||||
if(((r.kind==='escaped'&&a<limit&&r.n!==a)||(r.kind==='bounded'&&a<limit))&&mismatch.length<5)mismatch.push({x,y,oracle:a,gpu:r});
|
||||
}
|
||||
report.push({mode:mode.name,id,bits:s.bits,limit,guardMismatch,falseEscaped,falseBounded,unknown,known:pts.length-unknown,exactEscape,mismatch});
|
||||
}
|
||||
}
|
||||
const denseScene=map.get('swirly-seahorses-z12'),denseS=snap(denseScene),denseRef=buildRef(denseS,2000),densePoints=[[25,12],[26,15],[27,25]],dense=[];
|
||||
for(const[x,y]of densePoints){
|
||||
const p=pixel(denseS,61,39,x,y),g=pixel(denseS,61,39,x,y,denseS.bits+64),a=direct(p[0],p[1],denseS.bits,2000),ag=direct(g[0],g[1],denseS.bits+64,2000);
|
||||
if(a!==ag)throw new Error('dense oracle guard mismatch '+x+','+y);
|
||||
for(const mode of [{name:'balanced',strict:false},{name:'strict',strict:true}]){
|
||||
const r=perturb(denseRef,denseS,61,39,x,y,2000,mode.strict);
|
||||
if(r.kind==='escaped'&&a===2000)throw new Error('dense false escape '+JSON.stringify({mode:mode.name,x,y,a,r}));
|
||||
if(r.kind==='bounded'&&a<2000)throw new Error('dense false bounded '+JSON.stringify({mode:mode.name,x,y,a,r}));
|
||||
dense.push({mode:mode.name,x,y,oracle:a,gpu:r});
|
||||
}
|
||||
}
|
||||
if(report.some(r=>r.guardMismatch||r.falseEscaped||r.falseBounded))throw new Error('numeric classification failure '+JSON.stringify(report));
|
||||
const allUnknownStrict=report.filter(r=>r.mode==='strict'&&r.unknown===pts.length);
|
||||
if(allUnknownStrict.length)throw new Error('strict mode became all UNKNOWN: '+JSON.stringify(allUnknownStrict));
|
||||
console.log(JSON.stringify({status:'pass',kind:'guarded-production-equation-cpu-f32-model-not-real-gpu',thresholds:{balanced:1e-3,strict:1e-4},report,denseRegression:dense},null,2));
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
|
||||
await import('../gpu-kernels.js');
|
||||
const G=globalThis.MANDEL_WEBGPU_KERNELS;
|
||||
const script=await fs.readFile(new URL('../script.js',import.meta.url),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
|
||||
must(!G.DEEP_PERTURB_POSTSTATS_WGSL.includes('bucketState'),'production post-stats perturbation shader contains bucket state');
|
||||
must(!G.DEEP_PERTURB_POSTSTATS_WGSL.includes('unknownQueue'),'production post-stats perturbation shader writes the UNKNOWN queue');
|
||||
must(!G.DEEP_PERTURB_POSTSTATS_WGSL.includes('atomicAdd(&unresolved.remaining'),'production post-stats perturbation still performs per-UNKNOWN stats atomics');
|
||||
must(G.DEEP_BUCKET_HIST_STATS_WGSL.includes('localReasons'),'post-stats histogram lacks workgroup reason aggregation');
|
||||
must(G.DEEP_BUCKET_HIST_WGSL.includes('array<atomic<u32>,8>'),'bucket histogram lacks 8-bin workgroup aggregation');
|
||||
must(G.DEEP_BUCKET_HIST_WGSL.includes('(n*8u)/max(1u,p.maxIter)'),'bucket histogram does not use normalized UNKNOWN iteration');
|
||||
must(G.DEEP_BUCKET_PREFIX_WGSL.includes('atomicStore(&bucketState[8u+b],total)'),'bucket prefix does not initialize scatter cursors');
|
||||
must(G.DEEP_BUCKET_PREFIX_WGSL.includes('atomicStore(&bucketState[16u+b],total)'),'bucket prefix does not retain diagnostic offsets');
|
||||
must(G.DEEP_BUCKET_SCATTER_WGSL.includes('atomicAdd(&bucketState[8u+lane],c)'),'bucket scatter lacks workgroup range reservation');
|
||||
must(G.DEEP_BUCKET_SCATTER_WGSL.includes('atomicAdd(&localRanks[bucket],1u)'),'bucket scatter lacks workgroup-local rank allocation');
|
||||
must(G.DEEP_CORRECT_QUEUE_WGSL.includes('@compute @workgroup_size(64)'),'queued correction is not a 1D 64-lane kernel');
|
||||
must(G.DEEP_CORRECT_QUEUE_WGSL.includes('let out=unknownQueue[qi]'),'queued correction does not consume queue indices');
|
||||
must(/this\.deepPostStats/.test(script)&&/this\.deepBucketHistStats/.test(script)&&/this\.deepBucketPrefix/.test(script)&&/this\.deepBucketScatter/.test(script)&&/this\.correctQueued/.test(script),'production post-stats/bucket pipelines missing');
|
||||
must(/encodeDeepPostStatsNumeric\(encoder,[\s\S]*encodeDeepBucketHistogramStats\(encoder,/.test(script),'production frame order is not post-stats primary -> histogram/stats');
|
||||
must(/correctUnknownFrame[\s\S]*encodeDeepBucketPrefix\(e,[\s\S]*encodeDeepBucketScatter\(e,[\s\S]*encodeQueuedDeepCorrection\(e,/.test(script),'production correction order is not prefix -> scatter -> indirect correction');
|
||||
must(!/correctUnknownFrame[\s\S]{0,1600}encodeDeepBucketHistogram\(e/.test(script),'production correction redundantly rescans the histogram');
|
||||
must((script.match(/dispatchWorkgroups\(Math\.ceil\(w\/64\),h\)/g)||[]).length>=2,'bucket histogram/scatter are not dispatched as 64-pixel row groups');
|
||||
must(/dispatchWorkgroupsIndirect\(indirect,0\)/.test(script),'production indirect correction dispatch missing');
|
||||
must(/encodeCorrectionNumeric/.test(script),'dense correction path for probes/exports was removed');
|
||||
|
||||
const FIELD_UNKNOWN=0,ITER_MASK=0x000fffff;
|
||||
const packUnknown=n=>(n&ITER_MASK)>>>0;
|
||||
const packKnown=n=>(((1<<28)|(n&ITER_MASK))>>>0);
|
||||
const bucketOf=(meta,maxIter)=>Math.min(7,Math.floor(((meta&ITER_MASK)*8)/Math.max(1,maxIter)));
|
||||
let seed=0x9e3779b9;
|
||||
const rnd=()=>{seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed/0x100000000};
|
||||
const shuffle=a=>{for(let i=a.length-1;i>0;i--){const j=Math.floor(rnd()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a};
|
||||
const cases=[];
|
||||
for(const [w,h,rate,maxIter] of [[1,1,1,350],[17,9,.01,413],[64,64,.05,521],[65,7,.2,613],[257,131,.2,721],[320,180,.8,721],[511,257,0,901]]){
|
||||
const n=w*h,meta=new Uint32Array(n);
|
||||
for(let i=0;i<n;i++){
|
||||
const it=Math.min(maxIter,Math.floor(rnd()*(maxIter+1)));
|
||||
meta[i]=rnd()<rate?packUnknown(it):packKnown(it);
|
||||
}
|
||||
const expected=Array.from({length:8},()=>[]);
|
||||
for(let i=0;i<n;i++)if((meta[i]>>>28)===FIELD_UNKNOWN)expected[bucketOf(meta[i],maxIter)].push(i);
|
||||
const counts=expected.map(x=>x.length),offsets=[];let total=0;
|
||||
for(const c of counts){offsets.push(total);total+=c}
|
||||
const cursors=[...offsets],queue=new Array(total),groups=[];
|
||||
for(let y=0;y<h;y++)for(let gx=0;gx<Math.ceil(w/64);gx++)groups.push([y,gx]);
|
||||
shuffle(groups);
|
||||
let globalReservations=0;
|
||||
for(const [y,gx] of groups){
|
||||
const local=Array.from({length:8},()=>[]);
|
||||
for(let lane=0;lane<64;lane++){
|
||||
const x=gx*64+lane;if(x>=w)continue;const out=y*w+x;
|
||||
if((meta[out]>>>28)===FIELD_UNKNOWN)local[bucketOf(meta[out],maxIter)].push(out);
|
||||
}
|
||||
for(let b=0;b<8;b++)if(local[b].length){
|
||||
globalReservations++;
|
||||
const base=cursors[b];cursors[b]+=local[b].length;
|
||||
const lanes=shuffle([...local[b]]); // workgroup atomic rank order is unspecified.
|
||||
for(let j=0;j<lanes.length;j++)queue[base+j]=lanes[j];
|
||||
}
|
||||
}
|
||||
must(total===expected.reduce((a,x)=>a+x.length,0),`total mismatch ${w}x${h}`);
|
||||
must(queue.length===total,`queue length mismatch ${w}x${h}`);
|
||||
must(new Set(queue).size===queue.length,`duplicate queue entry ${w}x${h}`);
|
||||
for(let b=0;b<8;b++){
|
||||
must(cursors[b]===offsets[b]+counts[b],`cursor mismatch bucket ${b} ${w}x${h}`);
|
||||
const got=queue.slice(offsets[b],offsets[b]+counts[b]).sort((a,c)=>a-c);
|
||||
const exp=[...expected[b]].sort((a,c)=>a-c);
|
||||
must(got.length===exp.length&&got.every((v,i)=>v===exp[i]),`bucket membership mismatch b${b} ${w}x${h}`);
|
||||
must(got.every(i=>bucketOf(meta[i],maxIter)===b),`bucket range contamination b${b} ${w}x${h}`);
|
||||
}
|
||||
const workgroups=Math.ceil(total/64),launched=workgroups*64,visited=[];
|
||||
for(let qi=0;qi<launched;qi++)if(qi<total)visited.push(queue[qi]);
|
||||
must(visited.length===total,`queued dispatch count mismatch ${w}x${h}`);
|
||||
cases.push({w,h,pixels:n,maxIter,unknown:total,rate:total/Math.max(1,n),counts,offsets,histogramWorkgroups:Math.ceil(w/64)*h,scatterWorkgroups:Math.ceil(w/64)*h,globalReservations,correctionWorkgroups:workgroups});
|
||||
}
|
||||
console.log(JSON.stringify({status:'pass',check:'deep-8-bucket-correction-queue-model',bucketCount:8,workgroupSize:64,cases},null,2));
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const corpus=JSON.parse(await fs.readFile(new URL('./scenes.json',import.meta.url),'utf8'));
|
||||
const scene=corpus.scenes.find(s=>s.id==='z0'); const F=Math.fround;
|
||||
function rd(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
|
||||
function rs(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
|
||||
function dec(t,b){let s=String(t).toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const[m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let dg=(i+f).replace(/^0+(?=\d)/,'')||'0',p=f.length-e;if(p<0){dg+='0'.repeat(-p);p=0}let n=BigInt(dg);if(neg)n=-n;return rd(n*(1n<<BigInt(b)),10n**BigInt(p))}
|
||||
function pixel(re,im,span,b,w,h,x,y){const den=BigInt(2*w);return[re+rd(span*BigInt(2*x+1-w),den),im+rd(span*BigInt(h-2*y-1),den)]}
|
||||
function oracle(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const a=rs(zr*zr,b),c=rs(zi*zi,b);zi=rs(2n*zr*zi,b)+ci;zr=a-c+cr;if(rs(zr*zr,b)+rs(zi*zi,b)>bail)return n+1}return limit}
|
||||
function direct(cr,ci,limit){cr=F(cr);ci=F(ci);const U=2**-24,y2=F(ci*ci),x=F(cr-.25),q=F(F(x*x)+y2),lhs=F(q*F(q+x)),rhs=F(.25*y2),margin=F(16*U*(Math.abs(lhs)+Math.abs(rhs)+1));if(lhs<F(rhs-margin))return limit;const x2=F(cr+1),bulb=F(F(x2*x2)+y2),bulbMargin=F(16*U*(Math.abs(bulb)+.0625+1));if(bulb<F(.0625-bulbMargin))return limit;let zr=0,zi=0;for(let n=0;n<limit;n++){const a=F(zr*zr),c=F(zi*zi);zi=F(F(F(2*zr)*zi)+ci);zr=F(F(a-c)+cr);if(F(F(zr*zr)+F(zi*zi))>4)return n+1}return limit}
|
||||
const bits=256,re=dec(scene.re,bits),im=dec(scene.im,bits),span=dec(scene.span,bits),W=33,H=21,limit=800;let falseEscaped=0,falseBounded=0,guardMismatch=0,countMismatch=0,tested=0;
|
||||
for(let y=0;y<H;y++)for(let x=0;x<W;x++){const [cr,ci]=pixel(re,im,span,bits,W,H,x,y),[gr,gi]=pixel(re<<64n,im<<64n,span<<64n,bits+64,W,H,x,y),a=oracle(cr,ci,bits,limit),g=oracle(gr,gi,bits+64,limit);if(a!==g){guardMismatch++;continue}const rr=Number(cr)/2**bits,ii=Number(ci)/2**bits,r=direct(rr,ii,limit);tested++;if(r<limit&&a===limit)falseEscaped++;if(r===limit&&a<limit)falseBounded++;if(r<limit&&a<limit&&r!==a)countMismatch++}
|
||||
if(falseEscaped||falseBounded)throw new Error(JSON.stringify({falseEscaped,falseBounded,guardMismatch,countMismatch}));
|
||||
console.log(JSON.stringify({status:'pass',kind:'direct-f32-cpu-model-not-real-gpu',tested,guardMismatch,falseEscaped,falseBounded,countMismatch},null,2));
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('script.js',root),'utf8'),kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
function f32Ulp(x){x=Math.abs(Math.fround(x));if(x===0)return 2**-149;if(x<2**-126)return 2**-149;return 2**(Math.floor(Math.log2(x))-23)}
|
||||
function ratio(center,span,w){return (span/w)/f32Ulp(Math.max(Math.abs(center),span*.75,2**-126))}
|
||||
must(ratio(-0.7436,3.4e-3,1600)>4,'shallow Direct should remain active');
|
||||
must(ratio(-0.7436,3.4e-5,1600)<4,'collapsed f32 coordinate regime should use fast extension');
|
||||
must(/fastReferenceBits\(snap\)/.test(script)&&/Math\.max\(96,Math\.min\(snap\.bits,need\)\)/.test(script),'adaptive reduced-precision reference policy missing');
|
||||
must(/FAST_PERTURB_WGSL/.test(kernels)&&/render_fast/.test(kernels),'fast perturbation shader missing');
|
||||
must(/fastExtended=!deep&&fastNeedsExtended/.test(script),'fast extension routing missing');
|
||||
console.log(JSON.stringify({status:'pass',check:'fast-precision-extension',shallowRatio:ratio(-0.7436,3.4e-3,1600),deepRatio:ratio(-0.7436,3.4e-5,1600)},null,2));
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
const must=(x,m)=>{if(!x)throw new Error(m)};
|
||||
for(const [W,H] of [[17,11],[1024,768],[16384,9216]])for(const tile of [64,512])for(const sx of [.25,.5,.75])for(const sy of [.25,.5,.75])for(const [tx,ty,lx,ly] of [[0,0,0,0],[tile,0,3,5],[tile*2,tile,7,11]]){if(tx+lx>=W||ty+ly>=H)continue;const gx=tx+lx+sx,gy=ty+ly+sy,fullX=(gx-.5*W)/W,fullY=(.5*H-gy)/W,tiledX=((tx+lx+sx)-.5*W)/W,tiledY=(.5*H-(ty+ly+sy))/W;must(Object.is(fullX,tiledX)&&Object.is(fullY,tiledY),'tile mapping seam')}
|
||||
const maxFramePixels=8*1048576,fieldBytes=maxFramePixels*4;must(fieldBytes<=128*1024*1024,'default storage binding budget exceeded');
|
||||
const oldQueueBytes=maxFramePixels*4,newCounterBytes=32;must(newCounterBytes<oldQueueBytes/100000,'unresolved queue was not reduced to a counter');
|
||||
for(const [cw,ch] of [[390,844],[1920,1080],[1080,1920]]){const aspect=ch/cw,w0=16384,h0=Math.round(w0*aspect),h=Math.min(16384,h0),w=h0>16384?Math.max(64,Math.round(h/aspect)):w0;must(w<=16384&&h<=16384,'export dimension clamp failed')}
|
||||
console.log(JSON.stringify({status:'pass',pixelContract:'centered',exportTile:512,strictRetryPass:false,unresolvedCounterBytes:newCounterBytes,maxExportSide:16384},null,2));
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const h=await fs.readFile(new URL('../index.html',import.meta.url),'utf8');
|
||||
const req=['id="view"','id="renderMode"','>高速<','>正確<','id="processMode"','id="palette"','id="cycle"','id="shift"','id="colorAuto"','id="exportDialog"','mandelbrot-bundle','data-bundle="gpu-kernels"','data-bundle="app"'];
|
||||
for(const x of req)if(!h.includes(x))throw new Error('missing '+x);
|
||||
for(const banned of ['詳細設定・正確な座標','診断情報','id="backendBench"','id="sparseBench"','id="benchmarkDialog"','id="sparseDialog"','id="coordReInput"'])if(h.includes(banned))throw new Error('obsolete UI remains: '+banned);
|
||||
if(/<script\s+src=/.test(h))throw new Error('standalone index still depends on external scripts');
|
||||
if(!/select option\{background:#000;color:#fff\}/.test(h))throw new Error('dropdown theme not embedded');
|
||||
if(!/overflow-y:auto/.test(h))throw new Error('panel scroll fix not embedded');
|
||||
console.log(JSON.stringify({status:'pass',checks:req.length+9,singleFile:true},null,2));
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const script=await fs.readFile(new URL('../script.js',import.meta.url),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
must(/source:\{bits:snap\.bits,re:snap\.re,im:snap\.im,span:snap\.span,iter\}/.test(script),'reference source metadata is not retained');
|
||||
must(/newSpan!==oldSpan/.test(script),'span equality reuse guard missing');
|
||||
must(/src\.iter!==iter/.test(script),'iteration reuse guard missing');
|
||||
must(/pixel\.x<0\|\|pixel\.x>w\|\|pixel\.y<0\|\|pixel\.y>h/.test(script),'reference viewport reuse guard missing');
|
||||
must(/referencePixelForSource/.test(script),'selected reference pixel mapping missing');
|
||||
const W=800,H=500;
|
||||
for(const [dx,dy] of [[0,0],[24,0],[-100,30],[399,-249]]){
|
||||
// Pure pan: new center = old center - dx*span/W (real), +dy*span/W (imag).
|
||||
// Mapping old center into new view must recover center+pointer displacement.
|
||||
const refX=W/2+dx,refY=H/2+dy;
|
||||
if(refX<0||refX>W||refY<0||refY>H)throw new Error('test vector outside viewport');
|
||||
}
|
||||
console.log(JSON.stringify({status:'pass',check:'pan-reference-reuse-model',vectors:4,policy:'same span + same iter + old reference inside new viewport'},null,2));
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import {inflateSync} from 'node:zlib';
|
||||
|
||||
const CRC_TABLE=(()=>{const t=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?0xedb88320^(c>>>1):c>>>1;t[n]=c>>>0}return t})();
|
||||
function crc32Parts(parts){let c=0xffffffff;for(const part of parts)for(const b of part)c=CRC_TABLE[(c^b)&255]^(c>>>8);return(c^0xffffffff)>>>0}
|
||||
function pngChunk(type,data=new Uint8Array()){const tb=new TextEncoder().encode(type),out=new Uint8Array(12+data.length),dv=new DataView(out.buffer);dv.setUint32(0,data.length,false);out.set(tb,4);out.set(data,8);dv.setUint32(8+data.length,crc32Parts([tb,data]),false);return out}
|
||||
class StreamingPng{
|
||||
constructor(w,h){this.w=w;this.h=h;this.cs=new CompressionStream('deflate');this.writer=this.cs.writable.getWriter();this.compressed=(async()=>{const r=this.cs.readable.getReader(),chunks=[];for(;;){const q=await r.read();if(q.done)break;chunks.push(q.value)}return chunks})()}
|
||||
async rows(filteredRows){await this.writer.write(filteredRows)}
|
||||
async finish(){await this.writer.close();const chunks=await this.compressed,ihdr=new Uint8Array(13),dv=new DataView(ihdr.buffer);dv.setUint32(0,this.w,false);dv.setUint32(4,this.h,false);ihdr[8]=8;ihdr[9]=6;const parts=[new Uint8Array([137,80,78,71,13,10,26,10]),pngChunk('IHDR',ihdr)];for(const c of chunks)parts.push(pngChunk('IDAT',c));parts.push(pngChunk('IEND'));return new Blob(parts,{type:'image/png'})}
|
||||
async abort(reason){try{await this.writer.abort(reason)}catch{}try{await this.compressed}catch{}}
|
||||
}
|
||||
|
||||
assert.equal(typeof CompressionStream,'function','CompressionStream required for this model');
|
||||
const p=new StreamingPng(2,2);
|
||||
const row0=Uint8Array.from([255,0,0,255, 0,255,0,255]);
|
||||
const row1=Uint8Array.from([0,0,255,255, 255,255,255,128]);
|
||||
const filtered=new Uint8Array(18);filtered.set(row0,1);filtered.set(row1,10);await p.rows(filtered);
|
||||
const bytes=new Uint8Array(await (await p.finish()).arrayBuffer());
|
||||
assert.deepEqual(Array.from(bytes.subarray(0,8)),[137,80,78,71,13,10,26,10]);
|
||||
let off=8, width=0, height=0, idats=[], seenIend=false;
|
||||
while(off<bytes.length){
|
||||
const dv=new DataView(bytes.buffer,bytes.byteOffset+off);
|
||||
const len=dv.getUint32(0,false); const type=new TextDecoder().decode(bytes.subarray(off+4,off+8));
|
||||
const data=bytes.subarray(off+8,off+8+len); const got=dv.getUint32(8+len,false);
|
||||
assert.equal(got,crc32Parts([bytes.subarray(off+4,off+8),data]),`CRC ${type}`);
|
||||
if(type==='IHDR'){const h=new DataView(data.buffer,data.byteOffset,data.byteLength);width=h.getUint32(0,false);height=h.getUint32(4,false);assert.equal(data[8],8);assert.equal(data[9],6)}
|
||||
if(type==='IDAT')idats.push(data);
|
||||
if(type==='IEND')seenIend=true;
|
||||
off += 12+len;
|
||||
}
|
||||
assert.equal(width,2); assert.equal(height,2); assert.ok(idats.length>=1); assert.ok(seenIend);
|
||||
const packed=Buffer.concat(idats.map(x=>Buffer.from(x)));
|
||||
const raw=new Uint8Array(inflateSync(packed));
|
||||
assert.equal(raw.length,2*(1+2*4));
|
||||
assert.equal(raw[0],0); assert.deepEqual(Array.from(raw.subarray(1,9)),Array.from(row0));
|
||||
assert.equal(raw[9],0); assert.deepEqual(Array.from(raw.subarray(10,18)),Array.from(row1));
|
||||
|
||||
const aborted=new StreamingPng(1,1); await aborted.rows(Uint8Array.from([0,0,0,0,255])); await aborted.abort(new Error('cancelled'));
|
||||
console.log(JSON.stringify({status:'pass',png:{width,height,idatChunks:idats.length,rawBytes:raw.length},abort:'settled'},null,2));
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
await import('../gpu-kernels.js');
|
||||
const G=globalThis.MANDEL_WEBGPU_KERNELS;
|
||||
const script=await fs.readFile(new URL('../script.js',import.meta.url),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
const ITER_MASK=0x000fffff, REASON_SHIFT=20, FIELD_ESCAPED=1;
|
||||
const packUnknown=(n,r)=>((n&ITER_MASK)|((r&0xff)<<REASON_SHIFT))>>>0;
|
||||
const packKnown=n=>((FIELD_ESCAPED<<28)|(n&ITER_MASK))>>>0;
|
||||
const bucket=(m,maxIter)=>Math.min(7,Math.floor(((m&ITER_MASK)*8)/Math.max(1,maxIter)));
|
||||
let seed=0x31415926; const rnd=()=>{seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed/0x100000000};
|
||||
const cases=[];
|
||||
for(const [w,h,maxIter,rate] of [[1,1,350,1],[65,7,611,.14],[320,180,667,.18],[257,131,721,.03],[511,23,901,.65]]){
|
||||
const meta=new Uint32Array(w*h),direct=new Uint32Array(8),wg=new Uint32Array(8),directBuckets=new Uint32Array(8),wgBuckets=new Uint32Array(8);
|
||||
for(let i=0;i<meta.length;i++){
|
||||
if(rnd()<rate){const reason=1+Math.floor(rnd()*6),n=Math.floor(rnd()*(maxIter+1));meta[i]=packUnknown(n,reason);direct[0]++;direct[reason]++;directBuckets[bucket(meta[i],maxIter)]++;}
|
||||
else meta[i]=packKnown(Math.floor(rnd()*(maxIter+1)));
|
||||
}
|
||||
for(let y=0;y<h;y++)for(let gx=0;gx<Math.ceil(w/64);gx++){
|
||||
const localReasons=new Uint32Array(8),localBuckets=new Uint32Array(8);
|
||||
for(let lane=0;lane<64;lane++){const x=gx*64+lane;if(x>=w)continue;const m=meta[y*w+x];if((m>>>28)===0){localReasons[0]++;const r=(m>>>REASON_SHIFT)&0xff;if(r>=1&&r<=6)localReasons[r]++;localBuckets[bucket(m,maxIter)]++;}}
|
||||
for(let i=0;i<8;i++){wg[i]+=localReasons[i];wgBuckets[i]+=localBuckets[i];}
|
||||
}
|
||||
must([...direct].every((v,i)=>v===wg[i]),`reason stats mismatch ${w}x${h}`);
|
||||
must([...directBuckets].every((v,i)=>v===wgBuckets[i]),`bucket stats mismatch ${w}x${h}`);
|
||||
cases.push({w,h,maxIter,unknown:direct[0],reasons:Array.from(direct.slice(1,7)),buckets:Array.from(directBuckets)});
|
||||
}
|
||||
must(G.DEEP_PERTURB_POSTSTATS_WGSL.includes('fieldMeta[out]=pack_unknown')&&!G.DEEP_PERTURB_POSTSTATS_WGSL.includes('atomicAdd(&unresolved.remaining'),'post-stats primary contract broken');
|
||||
must(G.DEEP_BUCKET_HIST_STATS_WGSL.includes('localReasons')&&G.DEEP_BUCKET_HIST_STATS_WGSL.includes('unresolvedStats'),'histogram stats aggregation contract broken');
|
||||
must(/ensureDeepBucketState/.test(script)&&/encodeDeepBucketHistogramStats/.test(script),'production integration contract broken');
|
||||
console.log(JSON.stringify({status:'pass',check:'primary-post-stats-workgroup-model',workgroupSize:64,cases},null,2));
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import fs from 'node:fs/promises';import vm from 'node:vm';import {performance} from 'node:perf_hooks';
|
||||
const source=await fs.readFile(new URL('../script.js',import.meta.url),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
function extract(prefix,suffix){const start=source.indexOf(prefix)+prefix.length,end=source.indexOf(suffix,start);if(start<prefix.length||end<0)throw new Error('worker source not found');return source.slice(start,end)}
|
||||
function runWorker(workerSource,data){let message=null;const self={postMessage:m=>{message=m},onmessage:null};const ctx={self,postMessage:(...a)=>self.postMessage(...a),performance,Math,Number,BigInt,ArrayBuffer,DataView,Float32Array,Float64Array,Uint32Array,Set,String,Error};vm.createContext(ctx);vm.runInContext(workerSource,ctx,{timeout:5000});self.onmessage({data});if(!message)throw new Error('worker produced no message');if(message.type==='error')throw new Error(message.error);return message}
|
||||
const bits=256,ONE=1n<<256n,re=ONE/2n,im=0n,span=34n*ONE/10n,iter=700,width=800,height=600;
|
||||
const deep=runWorker(extract("function referenceWorkerSource(){return String.raw`","`}\nclass ReferenceService"),{type:'build',id:1,key:'deep-select',bits,re:re.toString(),im:im.toString(),span:span.toString(),width,height,iter});
|
||||
must(BigInt(deep.referenceRe)!==re,'Deep candidate selection stayed on short-lived center');
|
||||
must(deep.selectionEscape===0,'Deep candidate selection did not find a full-length reference');
|
||||
must(deep.refLen===iter,'Deep selected reference is not full-length');
|
||||
const fast=runWorker(extract("function fastReferenceWorkerSource(){return String.raw`","`}\nclass FastReferenceService"),{type:'build',id:2,key:'fast-select',sourceBits:bits,targetBits:128,re:re.toString(),im:im.toString(),span:span.toString(),width,height,iter});
|
||||
must(BigInt(fast.referenceRe)!==re,'Fast candidate selection stayed on short-lived center');
|
||||
must(fast.selectionEscape===0,'Fast candidate selection did not find a full-length reference');
|
||||
must(fast.refLen===iter,'Fast selected reference is not full-length');
|
||||
console.log(JSON.stringify({status:'pass',check:'reference-recovery-selection',deepReference:deep.referenceRe,fastReference:fast.referenceRe,iter},null,2));
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import fs from 'node:fs/promises';import vm from 'node:vm';import {performance} from 'node:perf_hooks';
|
||||
const source=await fs.readFile(new URL('../script.js',import.meta.url),'utf8'),prefix="function referenceWorkerSource(){return String.raw`",start=source.indexOf(prefix)+prefix.length,end=source.indexOf("`}\nclass ReferenceService",start);if(start<prefix.length||end<0)throw new Error('worker source not found');const workerSource=source.slice(start,end);let message=null;const self={postMessage:m=>{message=m},onmessage:null};const ctx={self,postMessage:(...a)=>self.postMessage(...a),performance,Math,Number,BigInt,ArrayBuffer,DataView,Float32Array,Float64Array,Uint32Array,Set,String,Error};vm.createContext(ctx);vm.runInContext(workerSource,ctx,{timeout:5000});if(typeof self.onmessage!=='function')throw new Error('worker handler missing');
|
||||
self.onmessage({data:{type:'build',id:1,key:'test',bits:256,re:(-3n*(1n<<256n)/4n).toString(),im:'0',iter:800}});if(!message)throw new Error('worker produced no message');if(message.type==='error')throw new Error(message.error);if(message.checkpointMismatch)throw new Error('reference +64 guard checkpoint mismatch');if(message.refLen<3||message.refs.byteLength!==(message.refLen+1)*16)throw new Error('reference packing invalid');for(const forbidden of ['nodes','levels','nodeCount','levelCount','invalidNodes'])if(forbidden in message)throw new Error('BLA payload leaked from quarantined reference worker: '+forbidden);const primary=message;message=null;const b=1536,R=(-3n*(1n<<BigInt(b))/4n).toString();self.onmessage({data:{type:'build',id:2,key:'e400',bits:b,re:R,im:'0',iter:120}});if(!message||message.type==='error')throw new Error(message?.error||'e400 worker produced no message');if(message.checkpointMismatch||message.refs.byteLength!==(message.refLen+1)*16)throw new Error('e400 reference contract failed');console.log(JSON.stringify({status:'pass',refLen:primary.refLen,precisionBits:primary.precisionBits,checkpointCount:primary.checkpointCount,buildMs:primary.buildMs,productionBla:false,e400:{checkpointMismatch:message.checkpointMismatch,refLen:message.refLen}},null,2));
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const root=new URL('../',import.meta.url),script=await fs.readFile(new URL('script.js',root),'utf8'),kernels=await fs.readFile(new URL('gpu-kernels.js',root),'utf8'),dev=await fs.readFile(new URL('index.external.html',root),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
must(/renderMode:'fast'/.test(script)&&/manual-accurate/.test(script)&&/manual-fast/.test(script),'manual fast/accurate backend policy missing');
|
||||
must(/\$\('#renderMode'\)\.onchange/.test(script)&&/mandelbrot\.renderMode/.test(script),'manual render mode control/persistence missing');
|
||||
must(!/ensureBackendProbe|scheduleBackendProbeAfterPaint|backendProfiles|backendViewProbes|runCrossoverBenchmark|runFixed96Experiment|__MANDEL_DIAG__/.test(script),'obsolete diagnostic/router system remains');
|
||||
must(!/DIRECT_DS_WGSL|DIRECT_DS_GUARDED_WGSL|DIRECT_FIXED96/.test(kernels),'diagnostic Direct DS/Fixed96 kernels remain');
|
||||
must(/正確 perturbation|DEEP_PERTURB_WGSL/.test(script+kernels)&&/DEEP_CORRECT_WGSL/.test(kernels)&&/correctUnknownFrame/.test(script),'accurate perturbation + sparse DS correction path missing');
|
||||
must(/DEEP_BUCKET_HIST_WGSL/.test(kernels)&&/DEEP_BUCKET_PREFIX_WGSL/.test(kernels)&&/DEEP_BUCKET_SCATTER_WGSL/.test(kernels)&&/DEEP_CORRECT_QUEUE_WGSL/.test(kernels),'production bucketed Deep queue path missing');
|
||||
must(/DEEP_PERTURB_POSTSTATS_WGSL/.test(kernels)&&/DEEP_BUCKET_HIST_STATS_WGSL/.test(kernels)&&/encodeDeepPostStatsNumeric/.test(script),'post-stats Deep primary missing');
|
||||
must(/presentTransform/.test(script)&&/前フレーム再利用/.test(script),'pan frame reprojection reuse missing');
|
||||
must(/FAST_PERTURB_WGSL/.test(kernels)&&/fastNeedsExtended/.test(script)&&/FastReferenceService/.test(script)&&/manual-fast-extended/.test(script),'fast deep-coordinate precision extension missing');
|
||||
must(/reusableDeepReference/.test(script)&&/referencePixel/.test(script)&&/reference再利用/.test(script),'Deep pan reference reuse missing');
|
||||
must(/colorAutoStep/.test(script)&&/colorCycleDir/.test(script)&&/colorShiftDir/.test(script)&&/state\.cycle>=cmax/.test(script)&&/state\.shift>=smax/.test(script),'bouncing color auto-slide missing');
|
||||
must(/effectiveColorCycle/.test(script)&&/state\.cycle\*base\/effective/.test(script)&&/sliderToCycle/.test(script)&&/cycleToSlider/.test(script),'zoom-relative/logarithmic color-period control missing');
|
||||
must(/mark_fast_unresolved/.test(kernels)&&/REASON_REFERENCE_END/.test(kernels)&&/REASON_REBASE_GAP/.test(kernels)&&/@group\(0\) @binding\(4\) var<storage,read_write> unresolved/.test(kernels),'Fast unresolved accounting missing');
|
||||
must(/provisional_unknown/.test(kernels)&&/fillCount>0\.0/.test(kernels),'UNKNOWN visual neighbor fill missing');
|
||||
must(/if\(!deep\)\{const cbg/.test(script)&&/if\(!deep\)\[f\.front,f\.back\]/.test(script)&&/const painted=await r\.recolor\(token,iter\)/.test(script),'deferred Accurate color/commit missing');
|
||||
must(/chooseReference/.test(script)&&/referenceCandidates/.test(script)&&/selectionEscape/.test(script),'long-lived reference selection missing');
|
||||
must(/max-height:calc\(100dvh - 20px\)/.test(dev)&&/overflow-y:auto/.test(dev),'scrollable bottom panel missing');
|
||||
must(/select option\{background:#000;color:#fff\}/.test(dev),'black/white dropdown styling missing');
|
||||
must(!/詳細設定・正確な座標|診断情報|backendBench|sparseBench/.test(dev),'removed settings/diagnostics UI remains');
|
||||
must(/referenceWorkerSource/.test(script)&&/BigInt\(d\.re\)/.test(script),'BigInt reference worker missing');
|
||||
must(/AA_RESOLVE_WGSL/.test(kernels)&&/renderTileRGBA2x/.test(script),'GPU 2x2 export resolve missing');
|
||||
must(/class StreamingPng/.test(script)&&/CompressionStream\('deflate'\)/.test(script),'streaming PNG export missing');
|
||||
console.log(JSON.stringify({status:'pass',shaderVersion:'24.2.26-baseline-idle-refinement',checks:22},null,2));
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const corpus=JSON.parse(await fs.readFile(new URL('./scenes.json',import.meta.url),'utf8'));
|
||||
const sc=corpus.scenes.find(s=>s.id==='swirly-seahorses-z12');
|
||||
if(!sc)throw new Error('swirly scene missing');
|
||||
const F=Math.fround,U=2**-24;
|
||||
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
|
||||
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
|
||||
function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
|
||||
function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
|
||||
function decRatio(t){let s=String(t).trim().toLowerCase(),neg=s.startsWith('-');if(neg)s=s.slice(1);if(s.startsWith('+'))s=s.slice(1);const [m,e0='0']=s.split('e'),e=Number(e0),[i='0',f='']=m.split('.');let digits=(i+f).replace(/^0+(?=\d)/,'')||'0',places=f.length-e;if(places<0){digits+='0'.repeat(-places);places=0}let n=BigInt(digits),d=10n**BigInt(places);if(neg)n=-n;return[n,d]}
|
||||
function decFixed(t,b){const[n,d]=decRatio(t);return roundDiv(n*(1n<<BigInt(b)),d)}
|
||||
function bitsForSpan(t){const s=String(t).toLowerCase(),a=s.split('e'),m=Math.abs(Number(a[0])),e=a[1]?Number(a[1]):0,l2=Math.log2(m)+e*Math.log2(10);return Math.max(256,Math.ceil(-l2)+240)}
|
||||
function snap(scene){const bits=bitsForSpan(scene.span);return{bits,re:decFixed(scene.re,bits),im:decFixed(scene.im,bits),span:decFixed(scene.span,bits)}}
|
||||
function fixedNum(v,bits){if(v===0n)return 0;let neg=v<0n;if(neg)v=-v;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh)),x=top*Math.pow(2,sh-bits);return neg?-x:x}
|
||||
function log2FixedAt(v,b){v=v<0n?-v:v;if(v===0n)return-Infinity;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh));return Math.log2(top)+sh-b}
|
||||
function spanME(s){const l=log2FixedAt(s.span,s.bits),exp=Math.floor(l);return{mant:Math.pow(2,l-exp),exp}}
|
||||
function pixel(s,w,h,x,y,b=s.bits){const re=align(s.re,s.bits,b),im=align(s.im,s.bits,b),span=align(s.span,s.bits,b),den=BigInt(2*w);return[re+roundDiv(span*BigInt(2*x+1-w),den),im+roundDiv(span*BigInt(h-2*y-1),den)]}
|
||||
function direct(cr,ci,b,limit){const bail=4n<<BigInt(b);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,b)+roundShift(zi*zi,b)>bail)return n+1}return limit}
|
||||
function buildRef(s,limit){const b=s.bits+64,cr=align(s.re,s.bits,b),ci=align(s.im,s.bits,b),bail=16n<<BigInt(b);let zr=0n,zi=0n,refLen=limit;const hi=new Float32Array((limit+1)*2),lo=new Float32Array((limit+1)*2);for(let n=0;n<=limit;n++){const rr=fixedNum(zr,b),ri=fixedNum(zi,b),hr=F(rr),hii=F(ri);hi[n*2]=hr;hi[n*2+1]=hii;lo[n*2]=F(rr-hr);lo[n*2+1]=F(ri-hii);if(n===limit)break;const zr2=roundShift(zr*zr,b),zi2=roundShift(zi*zi,b);zi=roundShift(2n*zr*zi,b)+ci;zr=zr2-zi2+cr;const mag=roundShift(zr*zr,b)+roundShift(zi*zi,b);if(mag>bail){refLen=n+1;const rr1=fixedNum(zr,b),ri1=fixedNum(zi,b),hr1=F(rr1),hi1=F(ri1);hi[2*refLen]=hr1;hi[2*refLen+1]=hi1;lo[2*refLen]=F(rr1-hr1);lo[2*refLen+1]=F(ri1-hi1);break}}return{hi,lo,refLen}}
|
||||
function cmul(ar,ai,br,bi){return[F(F(ar*br)-F(ai*bi)),F(F(ar*bi)+F(ai*br))]}
|
||||
function scale(v,e){if(e<-126)return 0;if(e>126)return F(8.507059e37);return F(v*Math.pow(2,e))}
|
||||
function primary(ref,s,w,h,x,y,limit){const se0=spanME(s),se={mant:F(se0.mant),exp:se0.exp},dx=F((x+.5-.5*w)/w),dy=F((.5*h-y-.5)/w);let dr=F(se.mant*dx),di=F(se.mant*dy),wr=0,wi=0,e=se.exp,n=0,m=0,ops=0,err=U*Math.max(Math.abs(dr),Math.abs(di));while(true){if(n>=limit){const rp=Math.min(m,ref.refLen)*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),ea=Math.abs(err*2**e)+U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);return ea<=1e-3?{kind:'bounded',n}:{kind:'unknown',n}}if(m>ref.refLen)return{kind:'unknown',n};const rp=m*2,delr=scale(wr,e),deli=scale(wi,e),zr=F(ref.hi[rp]+F(ref.lo[rp]+delr)),zi=F(ref.hi[rp+1]+F(ref.lo[rp+1]+deli)),mag=F(F(zr*zr)+F(zi*zi)),ea=Math.abs(err*2**e)+U*(Math.max(Math.abs(zr),Math.abs(zi))+Math.max(Math.abs(delr),Math.abs(deli))+1e-30);if(mag>4){if(Math.hypot(zr,zi)-ea>2)return{kind:'escaped',n};return{kind:'unknown',n}}const dmag=F(F(delr*delr)+F(deli*deli));if(m>0&&dmag>0&&mag<dmag){if(se.exp-e<-96)return{kind:'unknown',n};err=ea;wr=zr;wi=zi;dr=scale(F(se.mant*dx),se.exp);di=scale(F(se.mant*dy),se.exp);e=0;m=0;err+=U*Math.max(Math.abs(dr),Math.abs(di));continue}if(m>=ref.refLen)return{kind:'unknown',n};const rr=ref.hi[rp],ri=ref.hi[rp+1],lr=ref.lo[rp],li=ref.lo[rp+1],refAbs=Math.max(Math.abs(rr),Math.abs(ri))+Math.max(Math.abs(lr),Math.abs(li)),wAbs=Math.max(Math.abs(wr),Math.abs(wi)),dAbs=Math.max(Math.abs(dr),Math.abs(di)),p2=Math.abs(2**e),gain=2*refAbs+2*wAbs*p2,roundErr=U*(2*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1e-30);err=gain*err+roundErr;const[a,b]=cmul(rr,ri,wr,wi),[c,d]=cmul(lr,li,wr,wi),[sqR,sqI]=cmul(wr,wi,wr,wi);wr=F(F(F(2*a)+F(2*c))+F(scale(sqR,e)+dr));wi=F(F(F(2*b)+F(2*d))+F(scale(sqI,e)+di));m++;n++;ops++;if(!Number.isFinite(wr)||!Number.isFinite(wi)||!Number.isFinite(err)||Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di))>=1e30||err>1e35)return{kind:'unknown',n};const mm=Math.max(Math.abs(wr),Math.abs(wi),Math.abs(dr),Math.abs(di));if(mm>65536){wr=F(wr/65536);wi=F(wi/65536);dr=F(dr/65536);di=F(di/65536);err/=65536;e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wr=F(wr*65536);wi=F(wi*65536);dr=F(dr*65536);di=F(di*65536);err*=65536;e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n}}}
|
||||
function q(a,b){const x=F(a+b),e=F(b-F(x-a));return[x,e]}
|
||||
function sum(a,b){const x=F(a+b),bb=F(x-a),e=F(F(a-F(x-bb))+F(b-bb));return[x,e]}
|
||||
function prod(a,b){const x=F(a*b),ca=F(4097*a),ah=F(ca-F(ca-a)),al=F(a-ah),cb=F(4097*b),bh=F(cb-F(cb-b)),bl=F(b-bh);let e=F(F(ah*bh)-x);e=F(e+F(ah*bl));e=F(e+F(al*bh));e=F(e+F(al*bl));return[x,e]}
|
||||
function add(a,b){const t=sum(a[0],b[0]);return q(t[0],F(t[1]+F(a[1]+b[1])))}
|
||||
function neg(a){return[F(-a[0]),F(-a[1])]}
|
||||
function sub(a,b){return add(a,neg(b))}
|
||||
function mul(a,b){const t=prod(a[0],b[0]);let e=F(t[1]+F(a[0]*b[1]));e=F(e+F(a[1]*b[0]));e=F(e+F(a[1]*b[1]));return q(t[0],e)}
|
||||
function scaleDS(a,b){const t=prod(a[0],b);return q(t[0],F(t[1]+F(a[1]*b)))}
|
||||
function pow2DS(a,e){if(e<-126)return[0,0];if(e>126)return[F(8.507059e37),0];return[F(a[0]*2**e),F(a[1]*2**e)]}
|
||||
function cmp(a,b){if(a[0]<b[0])return-1;if(a[0]>b[0])return 1;if(a[1]<b[1])return-1;if(a[1]>b[1])return 1;return 0}
|
||||
function cadd(a,b){return[add(a[0],b[0]),add(a[1],b[1])]}
|
||||
function cmulDS(a,b){return[sub(mul(a[0],b[0]),mul(a[1],b[1])),add(mul(a[0],b[1]),mul(a[1],b[0]))]}
|
||||
function cscale(a,b){return[scaleDS(a[0],b),scaleDS(a[1],b)]}
|
||||
function cpow2(a,e){return[pow2DS(a[0],e),pow2DS(a[1],e)]}
|
||||
function mag2(a){return add(mul(a[0],a[0]),mul(a[1],a[1]))}
|
||||
function val(a){return F(a[0]+a[1])}
|
||||
function maxabsDS(a){return Math.max(Math.abs(val(a[0])),Math.abs(val(a[1])))}
|
||||
function correction(ref,s,w,h,x,y,limit){const se=spanME(s),mh=F(se.mant),sm=[mh,F(se.mant-mh)],iw=1/w,iwh=F(iw),inv=[iwh,F(iw-iwh)],ox=F(x+.5-.5*w),oy=F(.5*h-y-.5),dx=scaleDS(inv,ox),dy=scaleDS(inv,oy),d0=[mul(sm,dx),mul(sm,dy)];let d=d0,wv=[[0,0],[0,0]],e=se.exp,n=0,m=0,ops=0;while(true){if(n>=limit)return{kind:'bounded',n};if(m>ref.refLen)return{kind:'unknown',n};const rp=m*2,r=[[ref.hi[rp],ref.lo[rp]],[ref.hi[rp+1],ref.lo[rp+1]]],delta=cpow2(wv,e),z=cadd(r,delta),mg=mag2(z);if(cmp(mg,[4,0])>0)return{kind:'escaped',n};const dm=mag2(delta);if(m>0&&cmp(dm,[0,0])>0&&cmp(mg,dm)<0){if(se.exp-e<-96)return{kind:'unknown',n};wv=z;d=cpow2(d0,se.exp);e=0;m=0;continue}if(m>=ref.refLen)return{kind:'unknown',n};const linear=cscale(cmulDS(r,wv),2),sq=cpow2(cmulDS(wv,wv),e);wv=cadd(cadd(linear,sq),d);m++;n++;ops++;const mm=Math.max(maxabsDS(wv),maxabsDS(d));if(mm>=1e30||!Number.isFinite(mm))return{kind:'unknown',n};if(mm>65536){wv=cscale(wv,1/65536);d=cscale(d,1/65536);e+=16}else if(mm>0&&mm<1/65536&&e>se.exp){wv=cscale(wv,65536);d=cscale(d,65536);e-=16}if(e>126||ops>limit*2+2048)return{kind:'unknown',n}}}
|
||||
const s=snap(sc),w=61,h=39,limit=2000,ref=buildRef(s,limit);let guardMismatch=0,primaryUnknown=0,primaryFalseEscaped=0,primaryFalseBounded=0,corrected=0,remaining=0,correctionFalseEscaped=0,correctionFalseBounded=0,countMismatch=0,maxCountDiff=0;
|
||||
for(let y=0;y<h;y++)for(let x=0;x<w;x++){const p=pixel(s,w,h,x,y),g=pixel(s,w,h,x,y,s.bits+64),oracle=direct(p[0],p[1],s.bits,limit),guard=direct(g[0],g[1],s.bits+64,limit);if(oracle!==guard){guardMismatch++;continue}const a=primary(ref,s,w,h,x,y,limit);if(a.kind==='escaped'&&oracle===limit)primaryFalseEscaped++;if(a.kind==='bounded'&&oracle<limit)primaryFalseBounded++;if(a.kind!=='unknown')continue;primaryUnknown++;const c=correction(ref,s,w,h,x,y,limit);if(c.kind==='unknown'){remaining++;continue}corrected++;if(c.kind==='escaped'&&oracle===limit)correctionFalseEscaped++;if(c.kind==='bounded'&&oracle<limit)correctionFalseBounded++;if(c.kind==='escaped'&&oracle<limit&&c.n!==oracle){countMismatch++;maxCountDiff=Math.max(maxCountDiff,Math.abs(c.n-oracle))}}
|
||||
const report={status:'pass',kind:'sparse-double-single-correction-cpu-model-not-real-gpu',scene:sc.id,grid:[w,h],limit,referenceLength:ref.refLen,guardMismatch,primary:{unknown:primaryUnknown,falseEscaped:primaryFalseEscaped,falseBounded:primaryFalseBounded},correction:{corrected,remaining,falseEscaped:correctionFalseEscaped,falseBounded:correctionFalseBounded,escapeIterationMismatch:countMismatch,maxEscapeIterationDifference:maxCountDiff},note:'DS correction is a visual-quality recovery pass; escape iteration equality is not certified.'};
|
||||
if(guardMismatch||primaryFalseEscaped||primaryFalseBounded||correctionFalseEscaped||correctionFalseBounded||remaining)throw new Error(JSON.stringify(report));
|
||||
console.log(JSON.stringify(report,null,2));
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const root=new URL('../',import.meta.url),h=await fs.readFile(new URL('index.html',root),'utf8'),dev=await fs.readFile(new URL('index.external.html',root),'utf8'),script=await fs.readFile(new URL('script.js',root),'utf8');
|
||||
const must=(ok,msg)=>{if(!ok)throw new Error(msg)};
|
||||
must(!/<script\s+src=/.test(h),'root standalone HTML has external script dependency');
|
||||
must(/data-bundle="gpu-kernels"/.test(h)&&/data-bundle="app"/.test(h),'inline bundle markers missing');
|
||||
must(/id="renderMode"/.test(h)&&/>高速<\//.test(h)&&/>正確<\//.test(h)&&!/高速(標準)|正確(深部)/.test(h)&&/id="colorAuto"/.test(h),'manual render labels/color auto UI contract failed');
|
||||
must(!/診断情報|benchmarkDialog|sparseDialog|coordReInput/.test(h),'removed diagnostic/exact-coordinate UI still embedded');
|
||||
must(/<script src="gpu-kernels\.js"><\/script>/.test(dev)&&/<script src="script\.js"><\/script>/.test(dev),'hosted/dev template no longer has explicit source scripts');
|
||||
must(/\$\('#renderMode'\)\.onchange/.test(script)&&/colorAutoStep/.test(script),'source UI handlers missing');
|
||||
console.log(JSON.stringify({status:'pass',check:'single-file-ui-contract',checks:6},null,2));
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
const root=new URL('../',import.meta.url);const exists=async p=>{try{await fs.stat(new URL(p,root));return true}catch{return false}};
|
||||
for(const banned of ['kernels.js','src/deep_kernel.c','src/bla_kernel_v18.c','src/color_kernel.c','src/shallow_kernel.c','build/wasm-v23'])if(await exists(banned))throw new Error('legacy renderer asset remains: '+banned);
|
||||
for(const required of ['index.html','script.js','gpu-kernels.js','tests/webgpu-acceptance.html','tests/webgpu-acceptance.js'])if(!await exists(required))throw new Error('required v24 asset missing: '+required);
|
||||
console.log(JSON.stringify({status:'pass',legacyDeepAssets:0,requiredAssets:5},null,2));
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import vm from 'node:vm';
|
||||
const root=new URL('../',import.meta.url);
|
||||
const src=await fs.readFile(new URL('gpu-kernels.js',root),'utf8');
|
||||
const context={};context.globalThis=context;vm.runInNewContext(src,context,{filename:'gpu-kernels.js'});
|
||||
const kernels=context.MANDEL_WEBGPU_KERNELS;
|
||||
if(!kernels)throw new Error('kernel bundle did not initialize');
|
||||
// WGSL 16.2 Reserved Words. A module must not contain one of these tokens.
|
||||
const reserved=`NULL Self abstract active alignas alignof as asm asm_fragment async attribute auto await become cast catch class co_await co_return co_yield coherent column_major common compile compile_fragment concept const_cast consteval constexpr constinit crate debugger decltype delete demote demote_to_helper do dynamic_cast enum explicit export extends extern external fallthrough filter final finally friend from fxgroup get goto groupshared highp impl implements import inline instanceof interface layout lowp macro macro_rules match mediump meta mod module move mut mutable namespace new nil noexcept noinline nointerpolation non_coherent noncoherent noperspective null nullptr of operator package packoffset partition pass patch pixelfragment precise precision premerge priv protected pub public readonly ref regardless register reinterpret_cast require resource restrict self set shared sizeof smooth snorm static static_assert static_cast std subroutine super target template this thread_local throw trait try type typedef typeid typename typeof union unless unorm unsafe unsized use using varying virtual volatile wgsl where with writeonly yield`.split(/\s+/);
|
||||
const strip=s=>s.replace(/\/\*[\s\S]*?\*\//g,' ').replace(/\/\/.*$/gm,' ');
|
||||
const failures=[];
|
||||
for(const [name,code] of Object.entries(kernels)){
|
||||
if(typeof code!=='string'||!name.endsWith('_WGSL'))continue;
|
||||
const clean=strip(code);
|
||||
for(const word of reserved){
|
||||
const re=new RegExp(`\\b${word}\\b`);
|
||||
if(re.test(clean))failures.push(`${name}: reserved token ${word}`);
|
||||
}
|
||||
}
|
||||
if(failures.length)throw new Error(failures.join('\n'));
|
||||
console.log(JSON.stringify({status:'pass',check:'wgsl-reserved-words',shaderVersion:kernels.version,kernels:Object.keys(kernels).filter(k=>k.endsWith('_WGSL')).length},null,2));
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<!doctype html><meta charset="utf-8"><title>v24 WebGPU acceptance</title>
|
||||
<style>body{font:13px ui-monospace,monospace;background:#09101d;color:#e9efff;margin:16px}iframe{width:320px;height:200px;border:1px solid #445}pre{white-space:pre-wrap}</style>
|
||||
<h1>v24 WebGPU acceptance</h1><iframe id="app" src="../index.html"></iframe><pre id="out">starting…</pre>
|
||||
<script src="webgpu-acceptance.js"></script>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
(()=>{'use strict';
|
||||
const out=document.querySelector('#out'),frame=document.querySelector('#app');
|
||||
function roundDiv(v,d){const neg=v<0n,a=neg?-v:v,q=(a+d/2n)/d;return neg?-q:q}
|
||||
function align(v,a,b){const d=b-a;return d===0?v:d>0?v<<BigInt(d):v>>BigInt(-d)}
|
||||
function roundShift(v,b){const neg=v<0n,a=neg?-v:v,q=(a+(1n<<(BigInt(b)-1n)))>>BigInt(b);return neg?-q:q}
|
||||
function pixel(st,x,y,bits=st.bits){const re=align(BigInt(st.re),st.bits,bits),im=align(BigInt(st.im),st.bits,bits),span=align(BigInt(st.span),st.bits,bits),den=BigInt(2*st.width);return[re+roundDiv(span*BigInt(2*x+1-st.width),den),im+roundDiv(span*BigInt(st.height-2*y-1),den)]}
|
||||
function orbit(cr,ci,bits,limit){const bail=4n<<BigInt(bits);let zr=0n,zi=0n;for(let n=0;n<limit;n++){const zr2=roundShift(zr*zr,bits),zi2=roundShift(zi*zi,bits);zi=roundShift(2n*zr*zi,bits)+ci;zr=zr2-zi2+cr;if(roundShift(zr*zr,bits)+roundShift(zi*zi,bits)>bail)return n+1}return limit}
|
||||
function points(w,h){const xs=[.15,.35,.5,.65,.85].map(t=>Math.min(w-1,Math.max(0,Math.round(t*(w-1))))),ys=[.15,.35,.5,.65,.85].map(t=>Math.min(h-1,Math.max(0,Math.round(t*(h-1)))));return ys.flatMap(y=>xs.map(x=>[x,y]))}
|
||||
async function wait(ms){return new Promise(r=>setTimeout(r,ms))}
|
||||
async function waitApp(){if(frame.contentWindow&&frame.contentWindow.__MANDEL_TEST__)return;await new Promise(r=>frame.addEventListener('load',r,{once:true}));const t=performance.now();while(!frame.contentWindow.__MANDEL_TEST__&&performance.now()-t<15000)await wait(50);if(!frame.contentWindow.__MANDEL_TEST__)throw new Error('test hook not available')}
|
||||
async function numericDeep(api,id,re,im,span,iter){await api.setView({re,im,span,baseIter:iter,adaptive:false,processMode:'standard',renderMode:'accurate'});const st=api.state();if(st.renderMode!=='accurate'||!/正確 reference自動選択|正確 reference再利用/.test(st.engine))throw new Error('accurate mode did not select Deep: '+JSON.stringify(st));const ps=points(st.width,st.height),meta=await api.sampleMeta(ps);let guardMismatch=0,falseEscaped=0,falseBounded=0,unknown=0;for(let i=0;i<ps.length;i++){const[x,y]=ps[i],p=pixel(st,x,y),pg=pixel(st,x,y,st.bits+64),a=orbit(p[0],p[1],st.bits,st.iter),g=orbit(pg[0],pg[1],st.bits+64,st.iter);if(a!==g){guardMismatch++;continue}const m=meta[i],cls=(m>>>28)&3;if(cls===0){unknown++;continue}if(cls===1&&a===st.iter)falseEscaped++;if(cls!==1&&a<st.iter)falseBounded++}const row={id,guardMismatch,falseEscaped,falseBounded,unknown};if(guardMismatch||falseEscaped||falseBounded)throw new Error(id+' numeric gate failed: '+JSON.stringify(row));if(unknown!==0)throw new Error(id+': corrected production frame produced UNKNOWN: '+JSON.stringify(row));return row}
|
||||
async function run(){await waitApp();const api=frame.contentWindow.__MANDEL_TEST__,report=[];
|
||||
await api.setView({re:'-0.5',im:'0',span:'3.4',baseIter:350,adaptive:false,processMode:'standard',renderMode:'fast'});let st=api.state();if(st.renderMode!=='fast'||st.backend!=='direct')throw new Error('shallow fast mode did not select Direct: '+JSON.stringify(st));
|
||||
await api.setView({re:'-0.7453983606667815',im:'0.1125046349959942',span:'3.52e-8',baseIter:1200,adaptive:false,processMode:'standard',renderMode:'fast'});st=api.state();if(st.renderMode!=='fast'||st.backend!=='fast-extended'||!st.fastExtended||!/高速拡張/.test(st.engine))throw new Error('deep fast mode did not select precision extension: '+JSON.stringify(st));const fm=await api.sampleMeta(points(st.width,st.height));if(new Set(fm).size<2)throw new Error('fast precision extension collapsed sampled field');
|
||||
report.push(await numericDeep(api,'seahorse-z12','-0.7453983606667815','0.1125046349959942','3.52e-12',2000));
|
||||
const before=api.state();await api.panPixels(24,0);st=api.state();if(!st.referenceReused)throw new Error('pan did not reuse Deep reference: '+JSON.stringify({before,after:st}));
|
||||
const denseMeta=await api.probeMeta({w:61,h:39,strict:true,forceDeep:true,correctUnknown:true});if(denseMeta.some(m=>((m>>>28)&3)===0))throw new Error('forced corrected Deep probe left UNKNOWN');
|
||||
await api.setView({re:'-0.75',im:'0',span:'3.4e-20',baseIter:1000,adaptive:false,processMode:'standard',renderMode:'accurate'});const exportSmoke=await api.smokeExportTile({w:48,h:32,strict:true,ss:1}),exportSmokeAA=await api.smokeExportTile({w:48,h:32,strict:true,ss:2});if(exportSmoke.length!==exportSmoke.expected||exportSmokeAA.length!==exportSmokeAA.expected)throw new Error('GPU export tile readback length mismatch');if(exportSmoke.unresolved!==0||exportSmokeAA.unresolved!==0)throw new Error('GPU export smoke left unresolved samples: '+JSON.stringify({exportSmoke,exportSmokeAA}));
|
||||
const result={status:'pass',kind:'real-webgpu-acceptance',date:new Date().toISOString(),manualModes:true,fastPrecisionExtension:true,panReferenceReuse:true,exportSmoke,exportSmokeAA,report};out.textContent=JSON.stringify(result,null,2);document.title='PASS v24.2.26 WebGPU acceptance';globalThis.__WEBGPU_ACCEPTANCE__=result}
|
||||
run().catch(e=>{const result={status:'fail',error:String(e&&e.stack||e)};out.textContent=JSON.stringify(result,null,2);document.title='FAIL v24.2.26 WebGPU acceptance';globalThis.__WEBGPU_ACCEPTANCE__=result});
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue