From 8a2b1f6d4b928bd99cd691823b59fce0d751259a Mon Sep 17 00:00:00 2001 From: 33333-33333 Date: Sun, 23 Aug 2026 23:27:38 +0900 Subject: [PATCH] e --- AUDIT_V24.md | 229 ++-- CROSSOVER_BENCHMARK.md | 62 ++ FIXED96_EXPERIMENT.md | 70 ++ HYBRID_EXPERIMENT.md | 69 ++ IMPLEMENTATION_REPORT.md | 172 +-- MIGRATION_V24.md | 200 ++-- README.md | 194 ++-- audit/v24-backend-policy.json | 9 + audit/v24-cpu-numeric-model.json | 70 +- audit/v24-crossover-gpu-status.json | 25 + audit/v24-crossover-hotfix.json | 34 + audit/v24-crossover-quality.json | 766 +++++++++++++ audit/v24-fixed96-hotfix.json | 19 + audit/v24-fixed96-model.json | 182 +++ audit/v24-fixed96-preparation.json | 31 + audit/v24-fixed96-sparse-queue.json | 40 + audit/v24-hybrid-preparation.json | 63 ++ audit/v24-hybrid-risk-model.json | 56 + audit/v24-hybrid-ui-hotfix.json | 29 + audit/v24-real-webgpu-status.json | 61 +- audit/v24-release-manifest.json | 148 ++- audit/v24-release-verification.json | 33 +- audit/v24-sparse-correction-model.json | 26 + audit/v24-test-summary.json | 116 +- dist/hosted/gpu-kernels-v24.2.6.js | 598 ++++++++++ dist/hosted/gpu-kernels.js | 590 +++++++++- dist/hosted/index.html | 30 +- dist/hosted/script-v24.2.6.js | 454 ++++++++ dist/hosted/script.js | 270 ++++- dist/standalone/gpu-kernels-v24.2.6.js | 598 ++++++++++ dist/standalone/gpu-kernels.js | 396 ++++++- dist/standalone/index.html | 1275 +++++++++++++++++++++- dist/standalone/script-v24.2.6.js | 454 ++++++++ dist/standalone/script.js | 282 ++++- gpu-kernels-v24.2.6.js | 598 ++++++++++ gpu-kernels.js | 590 +++++++++- index.external.html | 93 ++ index.html | 1275 +++++++++++++++++++++- package.json | 2 +- script-v24.2.6.js | 454 ++++++++ script.js | 270 ++++- scripts/build.mjs | 23 +- scripts/test-all.mjs | 2 +- tests/v24-acceptance-contract.mjs | 12 +- tests/v24-backend-policy.mjs | 10 + tests/v24-cpu-numeric-model.mjs | 4 +- tests/v24-crossover-contract.mjs | 16 + tests/v24-crossover-quality.mjs | 106 ++ tests/v24-fixed96-contract.mjs | 24 + tests/v24-fixed96-limb-model.mjs | 15 + tests/v24-fixed96-limb-orbit-model.mjs | 24 + tests/v24-fixed96-model.mjs | 39 + tests/v24-fixed96-queue-model.mjs | 22 + tests/v24-fixed96-shader-range-model.mjs | 24 + tests/v24-geometry-contract.mjs | 2 +- tests/v24-hybrid-contract.mjs | 15 + tests/v24-hybrid-risk-model.mjs | 39 + tests/v24-hybrid-ui-runtime.mjs | 28 + tests/v24-index-contract.mjs | 8 +- tests/v24-source-contract.mjs | 25 +- tests/v24-sparse-correction-model.mjs | 45 + tests/v24-standalone-ui-contract.mjs | 10 + tests/v24-ui-cacheproof.mjs | 13 + tests/webgpu-acceptance.js | 12 +- 64 files changed, 10727 insertions(+), 724 deletions(-) create mode 100644 CROSSOVER_BENCHMARK.md create mode 100644 FIXED96_EXPERIMENT.md create mode 100644 HYBRID_EXPERIMENT.md create mode 100644 audit/v24-backend-policy.json create mode 100644 audit/v24-crossover-gpu-status.json create mode 100644 audit/v24-crossover-hotfix.json create mode 100644 audit/v24-crossover-quality.json create mode 100644 audit/v24-fixed96-hotfix.json create mode 100644 audit/v24-fixed96-model.json create mode 100644 audit/v24-fixed96-preparation.json create mode 100644 audit/v24-fixed96-sparse-queue.json create mode 100644 audit/v24-hybrid-preparation.json create mode 100644 audit/v24-hybrid-risk-model.json create mode 100644 audit/v24-hybrid-ui-hotfix.json create mode 100644 audit/v24-sparse-correction-model.json create mode 100644 dist/hosted/gpu-kernels-v24.2.6.js create mode 100644 dist/hosted/script-v24.2.6.js create mode 100644 dist/standalone/gpu-kernels-v24.2.6.js create mode 100644 dist/standalone/script-v24.2.6.js create mode 100644 gpu-kernels-v24.2.6.js create mode 100644 index.external.html create mode 100644 script-v24.2.6.js create mode 100644 tests/v24-backend-policy.mjs create mode 100644 tests/v24-crossover-contract.mjs create mode 100644 tests/v24-crossover-quality.mjs create mode 100644 tests/v24-fixed96-contract.mjs create mode 100644 tests/v24-fixed96-limb-model.mjs create mode 100644 tests/v24-fixed96-limb-orbit-model.mjs create mode 100644 tests/v24-fixed96-model.mjs create mode 100644 tests/v24-fixed96-queue-model.mjs create mode 100644 tests/v24-fixed96-shader-range-model.mjs create mode 100644 tests/v24-hybrid-contract.mjs create mode 100644 tests/v24-hybrid-risk-model.mjs create mode 100644 tests/v24-hybrid-ui-runtime.mjs create mode 100644 tests/v24-sparse-correction-model.mjs create mode 100644 tests/v24-standalone-ui-contract.mjs create mode 100644 tests/v24-ui-cacheproof.mjs diff --git a/AUDIT_V24.md b/AUDIT_V24.md index 7eaa139..604abaf 100644 --- a/AUDIT_V24.md +++ b/AUDIT_V24.md @@ -1,95 +1,182 @@ -# v24.1.3 completion audit +# v24.2.3 crossover-benchmark audit ## 判定 -**v24.1.2で実ブラウザからWGSL reserved-word parse errorが報告されたため、従来のrelease判定を撤回。v24.1.3でshader命名とfallback負荷を修正し、静的・CPU数値モデル・build gateを再実行してPASS。実WebGPU adapter acceptanceはこの環境では引き続き未実行。** +静的contract、Node数値モデル、build/reproducibility gateはPASS。実WebGPU adapterでのv24.2.3 compile/dispatchはこのコンテナではadapterを取得できないため未実行であり、`tests/webgpu-acceptance.html`を外部実機gateとして残す。 -実GPU未実行を「PASS」とは扱いません。 +## 主な修正 -## 修正済みの重大事項 +1. 深部の黒面積/黒点 + - UNKNOWN reasonを保持。 + - guarded f32 primary後にUNKNOWN限定double-single correctionを追加。 + - interior黒とUNKNOWN表示を分離。 +2. 標準描画範囲 + - direct/deep overlapを導入。 + - current-view quality/speed probeを本描画前に実行。 + - deepが15%以上高速な場合だけdeepを採用。 +3. Multi-reference + - 全画面モデルで費用対効果が低いため撤去。 +4. Export + - 1×/2×2 deep ExportにもDS correctionを統合。 + - 2×2はsample別unresolved counterを使用。 +5. 旧BLA + - membership反転の既知反例があるため引き続き未搭載。 -1. f32量子化BLAによるmembership反転 - - production BLAを完全除去。 -2. BLAなしperturbationのfalse bounded反例 - - roundoff error boundを導入し、既知反例を`UNKNOWN`へ退避。 -3. recolor/render token race - - `recolorPending`方式へ変更。 -4. redundant Strict retry - - 1-pass policyへ整理し、大画面queue/indirect dispatchを削除。 -5. Exportの4 readback/tile - - 4 sampleを1 command streamで処理しGPU resolve後1 readback。 -6. Exportのtile resource churn - - 512² reusable GPU workspaceへ変更。 -7. 16K巨大Canvas - - streaming PNGへ変更。 -8. deep JS fallback誤描画 - - deep fallbackを明示拒否。 -9. WGSL parse error (`meta` / `smooth` reserved words) - - storage変数を`fieldMeta` / `fieldSmooth`へ改名。WGSL予約語を全shaderで走査する回帰テストを追加。 -10. WebGPU shader/pipeline failure時のCPU暴走 - - shader/pipelineエラーではJavaScript full-frame fallbackを起動せず停止。WebGPU自体がない場合だけ軽量fallbackを使用。 -11. 画面描画負荷 - - standard screen budgetをdesktop約4M→1.5M、小型端末約2M→0.75Mへ低減。power約0.5M、fine最大約3M、Strict最大約2M。 -12. WebGPU canvas context claim - - pipeline成功後へ遅延。 -13. e340以深の座標表示0化 - - fixed-point exact decimal formatterの340桁capを撤去。 +## CPU numeric model results -## CPU numeric regression +### Primary guarded perturbation -`guarded-production-equation-cpu-f32-model-not-real-gpu`: +canonical 17×11 corpus: -- period2-cusp-z14: false escaped 0 / false bounded 0 / UNKNOWN 0 -- period2-cusp-z20: 0 / 0 / 0 -- period2-cusp-z100: 0 / 0 / 0 -- period3-interior: 0 / 0 / 0 -- period2-cusp-e280: 0 / 0 / 0 -- period2-cusp-e400: 0 / 0 / 0 -- swirly-seahorses-z12: false escaped 0 / false bounded 0 / UNKNOWN 71 of 187 +- z14: false escaped 0 / false bounded 0 / UNKNOWN 0 +- z20: 0 / 0 / 0 +- z100: 0 / 0 / 0 +- period3: 0 / 0 / 0 +- e280: 0 / 0 / 0 +- e400: 0 / 0 / 0 +- swirly: false escaped 0 / false bounded 0 / UNKNOWN 49 of 187 -BalancedとStrictの両方で上記membership gateを満たします。swirlyの未確定率は既知の制約で、誤分類より保守退避を優先しています。 +Dense known regression pixels `(25,12)`, `(26,15)`, `(27,25)`はprimaryでは誤分類せずUNKNOWNへ退避。 -Shallow f32 modelは693 sampleでfalse escaped 0 / false bounded 0。escape iteration count差は2 sampleあり、membership certificationを名乗らない理由の一つです。 +### Sparse double-single correction -## BLA status +`swirly-seahorses-z12`, 61×39, 2000 iter: -production sourceにBLA builder/evaluator/node/levelはありません。再導入にはf32係数量子化を含む誤差上界付き受入条件が必要です。 +- oracle guard mismatch: 0 +- primary UNKNOWN: 478 / 2379 +- primary false escaped: 0 +- primary false bounded: 0 +- corrected: 478 +- remaining: 0 +- correction false escaped: 0 +- correction false bounded: 0 +- escape iteration mismatch: 5 +- max escape iteration difference: 374 -## Export audit +DS passはfinite-iteration visual classification recoveryとして有効だが、escape iteration equalityは保証しない。 -- tile max 512² -- reusable GPU workspace -- 2×2 AA: one readback/tile -- unresolved counterをRGBA readback末尾へ同梱 -- PNG signature / IHDR / CRC / multiple IDAT / inflate scanlineをNode modelで検証 -- abort path settles without unhandled rejection +## Router contract + +- direct coordinate floor: ratio 8 +- no-probe direct margin: ratio 128 +- overlap: 8–128 +- deep required speed advantage: 15% +- corrected deep probe UNKNOWN max: 0.5% +- class disagreement tolerance: 0 +- current view probe required; depth profile aloneではrouteしない + +## Performance/迂遠処理監査 + +- full-frame high-precision CPU renderer: なし +- multi-reference reference build loop: なし +- redundant primary→same-f32 strict retry: なし +- BLA precompute/evaluate: なし +- deep correction: full dispatchだがKNOWN pixelは即return。追加reference生成なし。 +- Export: reusable 512² workspace、1 readback/tile。 +- 2×2 Export: 4 numerical samplesを1 command stream内で処理し、1 resolve/1 readback。 +- presentation/recolor: GPU field常駐。 ## Real WebGPU acceptance -`tests/webgpu-acceptance.html`は以下を実adapterで検証します。 +`tests/webgpu-acceptance.html`は以下を要求する。 -- shader compilation -- standard / Strict deep dispatch -- BigInt reference guard -- sampled membership -- dense known regressions -- Export 1× / 2×2 readback -- uncaptured validation errors +- production 6 WGSL + benchmark DS Direct 1本の計7 WGSL modules compile/pipeline creation +- stable corpus corrected UNKNOWN = 0 on sampled pixels +- BigInt P/P+64 guard mismatch = 0 +- false escaped / false bounded = 0 +- dense 3 regression pixelsをDS correctionでKNOWNへ回収 +- overlap view-specific routerがquality gateを通過し、15% speed ruleどおりroute +- 1× / 2×2 Export unresolved = 0 +- uncaptured WebGPU validation/runtime error = 0 -stable scenesはsampled UNKNOWNを許容しません。swirlyは25 sample中12以上の確定を要求します。 +この環境では`navigator.gpu`/adapterを実利用できないため、上記hardware gateのみ未判定。 -この作業環境のChromiumは`navigator.gpu`を公開しないため、このgateは未実行です。`audit/v24-real-webgpu-status.json`に環境結果を記録します。 -## v24.1.3 hotfix verification +## v24.2.3 crossover追加監査 -- WGSL 16.2 reserved-word scan: PASS (5 shader modules) -- `meta` / `smooth` WGSL identifier occurrences: 0 -- source contract: PASS (28 checks) -- `npm test`: PASS -- clean `npm run build`: PASS -- source ↔ standalone/hosted hashes: identical -- shader/pipeline init failure: CPU full-frame fallback is not entered and failed init is not retried every render -- normal desktop screen budget: ~1.5M px (v24.1.2 ~4M px) -- no-WebGPU/no-adapter fallback budget: ~0.25M px +- DS Direct WGSLはproduction routerへ未接続。 +- Round-depth benchmark: z0,2,4,6,8,10,12,14,16,20,30,40。 +- CPU BigInt oracle sweep: `audit/v24-crossover-quality.json`。 +- Seahorse / 1600×900相当ではf32 material degradation ≈ z6、DS ≈ z10。 +- corrected Deep CPU modelはz8で1/315 false escapeを検出したため、依然membership certificateではない。 +- 実GPU timingはこのcontainerではWebGPU adapter不成立のため未測定。実機では診断情報→Backend比較で取得する。 -実WebGPU adapter上のv24.1.3 compile/dispatchだけは、このコンテナではadapterを得られないため未実行です。v24.1.2で報告されたreserved-word parse failureそのものは、仕様予約語の除去とreserved-token回帰gateによってsource上修正済みです。 + +## v24.2.3 z12 benchmark hang hotfix + +ユーザー実機でBackend比較が `z12 · deep` から終了しない事象を確認。原因はproduction rendererの無限loopではなく、benchmarkが現在の全画面をDeep primary + DS correctionでwarmup 1回 + 2〜5回反復し、各回を単一の大規模GPU jobとして待っていたこと。1600×900級・z12では1測定だけでも最大10億回規模の反復候補となり、queue completion待ちが極端に長くなり得た。 + +修正: + +- speed benchmarkを代表3tile(最大256×144)へ縮小。 +- actual full-resolution座標を維持したtileX/tileYで測定。 +- aggregate `ms/pixel` からcurrent canvasのfull-frame時間を換算。 +- quality gateは従来どおり5 spatial tiles。 +- GPU batch/reference buildに15 s watchdog。timeout時は明示的errorとし、GPU deviceを破棄して再初期化可能にする。 +- progress表示を `deep sample 2/3` のように細分化。 +- crossover JSON formatをv2へ更新し、`speedTiles`, `speedPixels`, `msPerPixel`, `fullMedianMs`を記録。 + +このhotfixはproduction backend routing/rendering mathを変更しない。 + + +## v24.2.4 Hybrid experiment preparation + +- Added benchmark-only `DIRECT_DS_GUARDED_WGSL`; production router is unchanged. +- Guard metric: approximate `|dz/dc|`; threshold sweep `1e12 / 1e13 / 1e14`. +- Added `benchmarkHybrid()` / `renderTileMetaHybrid()` using the existing Deep double-single correction only for DS-marked UNKNOWN pixels. +- Added z6/z8/z10/z12 experiment UI and JSON export. +- Added CPU BigInt P/P+64 risk-model gate. At risk=1e13, observed DS class errors are all caught at z6/z8/z10 with correction rates 2.8% / 11.1% / 34.6%; z12 correction rate is 91.2%, intentionally indicating the Full-Deep crossover. +- Added real-WebGPU acceptance smoke for the Hybrid diagnostic path. + + +## v24.2.5 Sparse Direct experiment (Audit) + +- The previous sparse-Full-Deep diagnostic is superseded and is not exposed in the UI. +- Added `DIRECT_TS_CORRECT_WGSL`, a reference-free three-term f32 expansion correction shader that only processes DS sensitivity UNKNOWN pixels. +- Added `benchmarkSparseDirect()` / `renderTileMetaSparseDirect()` and a z6/z8/z10/z12 risk sweep. +- Full Deep remains the quality/timing baseline; the Sparse Direct candidate itself does not build or bind a reference orbit. +- Production backend routing is unchanged. +- Root `index.html` is now a single-file bundle with no external script dependency, specifically to keep diagnostic UI controls functional when the HTML is opened from an archive/temp extraction. +- Static single-file UI contract and real-browser click smoke are separate gates; real WebGPU arithmetic still requires a WebGPU-capable browser. + +- Sparse Direct coordinate packing uses exact residual subtraction from BigInt fixed-point into three f32 components; this removed the CPU TD-model class errors caused by the earlier host-Number split. + +## v24.2.6 Fixed96 experiment audit + +- v24.2.5 three-term f32 expansion correction is removed from the active experiment because the real-GPU quality gate produced no viable candidate at z6/z8/z10/z12. +- New correction shader: `DIRECT_FIXED96_CORRECT_WGSL`. +- Arithmetic is signed Q8.88 in three `u32` limbs; no floating-point expansion is used by the correction pass. +- Exact BigInt ViewState coordinates are converted directly to Q8.88 tile base + pixel step on the host; no JS `Number` coordinate round-trip is used. +- `tests/v24-fixed96-limb-model.mjs` verifies the limb multiplication/round-shift algorithm against BigInt over 20,000 deterministic products. +- `tests/v24-fixed96-model.mjs` verifies the DS sensitivity mask + Q8.88 correction against the BigInt P/P+64 oracle on a 1600×900-equivalent Seahorse grid. Safe thresholds exist at z6/z8/z10/z12 in the CPU preflight. +- Experiment output reports every risk threshold, including rejected candidates, with correction rate, remaining UNKNOWN, class mismatch and timing. The former `best risk = none` information loss is removed. +- Production routing/rendering remains unchanged. +- Root `index.html` remains a single-file bundle; the Fixed96 button handler is embedded and has no sibling-JS dependency when opened from an extracted ZIP. +- Real-GPU WGSL compilation/timing remains the decisive acceptance gate. + + +## v24.2.7 Fixed96 range hotfix audit + +- Root cause: `fx_bad_range()` incorrectly required the Q8.88 top integer byte to be `0x00` or `0xff`, rejecting valid values with magnitude >= 1. +- Symptom match: real-GPU experiment reported `correction ~= remain` across z6-z12. +- Fix: remove the invalid sign-extension test; use a certain-escape component test at `|component| > 2` before fixed-point magnitude squaring. +- Added BigInt coordinate range/step-underflow checks before GPU packing. +- Added regression tests for valid Q8.88 values above 1, 20,000 limb multiplication cases, and full limb-orbit iteration equality against exact BigInt Q8.88. + + +## v24.2.8 Fixed96 true sparse queue audit + +Purpose: remove the v24.2.7 full-screen Fixed96 dispatch and make the diagnostic correction genuinely proportional to risky-pixel count. + +Implementation checks: + +- DS risk shader appends local pixel indices to a storage queue with atomics. +- queue capacity equals tile pixel count; overflow is still counted and treated as fatal. +- a dedicated finalize shader writes indirect dispatch arguments from the GPU-written queue count. +- Fixed96 uses `@workgroup_size(64)` and `dispatchWorkgroupsIndirect`; it no longer launches 8x8 workgroups over the full tile. +- Fixed96 validates queue indices and verifies the queued pixel is still UNKNOWN(reason 7). +- benchmark and quality paths both run `fixed96QueueIntegrity()` and fail closed on any counter/invariant mismatch. +- static queue-integrity model covers zero, 20, 65-entry, overflow, wrong-indirect, processed mismatch and stale-entry cases. +- Q8.88 limb multiplication/orbit tests remain unchanged and passing; this revision changes scheduling only. + +Real-GPU timing remains the decisive performance gate. diff --git a/CROSSOVER_BENCHMARK.md b/CROSSOVER_BENCHMARK.md new file mode 100644 index 0000000..8c4b6fe --- /dev/null +++ b/CROSSOVER_BENCHMARK.md @@ -0,0 +1,62 @@ +# v24.2.3 Crossover Benchmark + +## 目的 + +標準 `f32 Direct` が十分に速いのにBigInt referenceを伴うDeepへ早く切り替わっている可能性を検証する。Deepを速度目的ではなく「品質上必要になった地点」でのみ使えるよう、`f32 Direct / experimental DS Direct / corrected Deep`を同じ深度列で比較する。 + +## 品質モデル + +- Seahorse center: `-0.7453983606667815 + 0.1125046349959942i` +- viewport: 1600×900。21×15点を画面全域から間引き、隣接21 pixelの座標分解能も別途検査。 +- 深度: z0,2,4,6,8,10,12,14,16,20,30,40。`span = 3.4×10^-z`。 +- oracle: BigInt fixed-point P / P+64一致。 +- Deep: guarded rescaled f32 perturbation + UNKNOWNのみDS correction。 +- DS Direct: GPU候補と同じdouble-single arithmeticのCPU `Math.fround`モデル。 + +| z | f32 class error | f32 adjacent unique | DS class error | DS adjacent unique | Deep error/UNKNOWN | +|---:|---:|---:|---:|---:|---:| +| 0 | 0.00% | 21/21 | 0.00% | 21/21 | 0 / 0 | +| 2 | 0.63% | 21/21 | 0.00% | 21/21 | 0 / 0 | +| 4 | 0.63% | 21/21 | 0.00% | 21/21 | 0 / 0 | +| 6 | 1.59% | 2/21 | 0.32% | 21/21 | 0 / 0 | +| 8 | 1.27% | 1/21 | 0.63% | 21/21 | 1 / 0 | +| 10 | 3.17% | 1/21 | 3.81% | 21/21 | 0 / 0 | +| 12 | 17.14% | 1/21 | 8.57% | 21/21 | 0 / 0 | +| 14 | 100.00% | 1/21 | 0.00% | 1/21 | 0 / 0 | +| 16 | 100.00% | 1/21 | 0.00% | 1/21 | 0 / 0 | +| 20 | 100.00% | 1/21 | 0.00% | 1/21 | 0 / 0 | +| 30 | 100.00% | 1/21 | 0.00% | 1/21 | 0 / 0 | +| 40 | 100.00% | 1/21 | 0.00% | 1/21 | 0 / 0 | + +## 暫定結論 + +1. **f32 Directはz4付近までは実用候補。z6では隣接21 pixelが2値まで潰れ、品質上の限界が明確。** 現在の `ratio < 8` 強制Deepは1600px幅・この中心ではおおむねz4前後で発動するため、少なくとも「Deepを速度で選ぶ」には早すぎる。 +2. **DS Directはz8まで中間backendとして有望。z10ではclass errorが約3.8%に増え、本命Deepへ移るべき。** 隣接pixel分解能自体はz12まで維持するが、反復誤差が先に品質を制限する。 +3. **Deepは廃止できない。** f32はz6、DSもz10付近から品質が崩れる。Deepは極端な固定費を持つが、より深い領域では唯一の実用候補。 +4. Deep CPUモデルにはz8で1/315のfalse escapeが残るため、Deepもmembership certificateではない。これは既存の`membershipCertified:false`方針と整合する。 + +## 実GPU速度ベンチ + +このコンテナではWebGPU adapterを取得できないため、GPU時間は測定できない。代わりに本体の診断情報へ **Backend比較** を追加した。 + +v24.2.2では各深度を現在のcanvas全画面でwarmup+複数回測定していたため、z12 Deepで巨大dispatchが長時間GPU queueを占有するケースがあった。v24.2.3では速度計測を現在のview座標上の代表3タイル(各最大256×144)に限定し、合計時間から `ms/pixel` を算出して現在の全画面へ換算する。品質は別の5タイルで比較する。各GPU batch/reference buildには15秒watchdogがあり、永久待ちはしない。 + +実機では同じ深度列を次の3方式により測定する。 + +- `f32`: 標準numeric kernel。 +- `DS`: experimental whole-frame double-single direct kernel。production routerには未接続。 +- `Deep warm`: reference cache済みのDeep primary + DS correction。 +- `Deep cold`: `Deep warm + verified BigInt reference build`。 +各候補のqualityはactual full-resolution座標上の5領域でcorrected Deepと比較し、class mismatch / escape-iteration mismatch / UNKNOWN率をJSONへ記録する。速度JSONにはspeedTiles / speedPixels / msPerPixel / fullMedianMsを保存する。Deep自体の絶対品質は上記BigInt CPU oracle reportを参照する。 + +## 次のrouter候補 + +実GPU結果が得られたら、backendは「速度だけ」ではなく次で選ぶ。 + +```text +f32 quality OK → f32/DS/Deep のうち最速 +f32 NG, DS quality OK → DS/Deep のうち最速 +DS NG → Deep +``` + +DeepにBLAがない現在、同じiteration数で`f32 Direct`よりDeep numeric kernelが速くなる理由はほぼない。Deepの役割は速度ではなく座標/反復精度であり、速度比較の本命は **DS cold cost vs Deep cold/warm cost** になる。 \ No newline at end of file diff --git a/FIXED96_EXPERIMENT.md b/FIXED96_EXPERIMENT.md new file mode 100644 index 0000000..288ac57 --- /dev/null +++ b/FIXED96_EXPERIMENT.md @@ -0,0 +1,70 @@ +# v24.2.8 DS + True Sparse Fixed96 Direct Experiment + +## Purpose + +The previous three-term f32 expansion correction failed the real-GPU quality gate on every tested depth. v24.2.6 keeps the DS sensitivity gate but replaces the correction arithmetic with deterministic integer fixed-point operations. + +Pipeline: + +```text +DS Direct + |dz/dc| risk gate + -> risky pixel indices compacted into GPU u32 queue + -> GPU finalize writes indirect workgroup count + -> dispatchWorkgroupsIndirect + -> signed Q8.88 Direct runs only queued pixels + -> compare against corrected Full Deep +``` + +The Fixed96 correction does not bind or build a deep reference. + +## Arithmetic + +- width: 96 bits +- format: signed Q8.88 two's complement +- storage: 3 × `u32` +- multiplication: exact 32×32→64 decomposition from 16-bit partial products, 96×96 accumulation into six 32-bit limbs, then round-and-shift by 88 bits +- coordinates: packed directly from the BigInt ViewState into Q8.88; JavaScript `Number` is not used as an intermediate + +Q8.88 is intended only for the z6–z12 transition experiment. It is not proposed as an unlimited deep renderer. + +## Measurements + +For z6 / z8 / z10 / z12 and risk `1e11 / 1e12 / 1e13 / 1e14`, the UI reports every candidate row: + +- extrapolated Fixed96 full-frame time, +- Full Deep cold estimate, +- speedup, +- correction rate, +- remaining UNKNOWN, +- escaped/bounded class mismatch, +- actual Fixed96 dispatch rate, +- queue integrity status, +- status/failure reason. + +A failed candidate is never collapsed into a single `none` row. + +## CPU preflight + +`tests/v24-fixed96-model.mjs` compares the DS risk gate + exact Q8.88 Direct model with the BigInt P/P+64 oracle on a 1600×900-equivalent Seahorse sample grid. `tests/v24-fixed96-limb-model.mjs` separately verifies the six-limb multiplication algorithm against BigInt for 20,000 deterministic cases. + +These are preflight tests. Real-GPU shader compilation, timing and class comparison remain decisive. + +## Production status + +Diagnostic only. The production router remains unchanged. + + +## v24.2.7 range hotfix + +v24.2.6 contained a shader bug in `fx_bad_range()`: the top Q8.88 integer byte was treated as if it had to be pure sign-extension. Valid values such as `+1.2` were therefore rejected. Escaping Mandelbrot orbits commonly pass through component magnitudes above 1, so selected correction pixels were counted as `remaining` instead of being written back. + +v24.2.7 removes that invalid test. While the prior state is inside the bailout disk, Q8.88 multiplication is bounded. After each iteration the shader first tests `|Re(z)| > 2 || |Im(z)| > 2` as a mathematically certain escape, and only squares components when both are within ±2. This prevents both the false range rejection and large-value fixed-point wraparound. + + +## v24.2.8 true sparse queue + +v24.2.7 still launched a 2D Fixed96 workgroup over the whole benchmark tile and returned immediately for non-risk pixels. On SIMD/SIMT hardware this was not sparse enough: a few risky lanes could keep most of a workgroup occupied by the long integer loop. + +v24.2.8 changes the execution topology, not the Q8.88 arithmetic. The guarded DS pass atomically appends each risky local pixel index to a queue. A one-invocation finalize shader writes `ceil(queueCount/64),1,1` to an indirect-argument buffer. The Fixed96 kernel is one-dimensional with `@workgroup_size(64)` and reads exactly one queue entry per invocation. No CPU map/readback occurs between these passes. + +Every timing and quality tile validates queue invariants before its result is accepted. Overflow, wrong indirect arguments, invalid/stale indices or any disagreement between selected/enqueued/processed/corrected counts aborts the experiment rather than producing a misleading timing row. diff --git a/HYBRID_EXPERIMENT.md b/HYBRID_EXPERIMENT.md new file mode 100644 index 0000000..63a7c1c --- /dev/null +++ b/HYBRID_EXPERIMENT.md @@ -0,0 +1,69 @@ +# v24.2.6 DS + sparse Deep Hybrid Experiment + +## 目的 + +v24.2.3実機計測では、Seahorse trackでDS Directはz10/z12でもFull Deepより速い一方、分類品質が不足した。次の実験ではDS Direct全画面を捨てず、数値的に危険なpixelだけDeep補正へ送ることで、z6〜z10の中間帯を高速化できるか測る。 + +production routerは変更しない。本ファイルの実験結果が採用条件を満たした場合にのみ次版で `f32 → DS/Hybrid → Full Deep` のrouterを検討する。 + +## 実験backend + +`DS guarded direct → UNKNOWN only → existing Deep DS correction` + +DS guarded directはorbitと同時に複素微分 `D = dz/dc` を近似追跡する。 + +``` +D[n+1] = 2 z[n] D[n] + 1 +``` + +`|D| > riskLimit` になったpixelはDS Directの分類を採用せず `FIELD_UNKNOWN / ds-sensitivity` とする。そのpixelだけ既存のBigInt reference + double-single perturbation correctionで再評価する。 + +この判定はmembership証明ではなく、DS Directの不安定化を早期に検出する実験用heuristicである。 + +## sweep + +- 深度: z6 / z8 / z10 / z12 +- riskLimit: `1e12`, `1e13`, `1e14` +- speed: actual full-resolution座標上の代表3tile(各最大256×144) +- quality: 5 spatial tiles +- quality reference: corrected Full Deep +- cold time: verified BigInt reference buildをHybrid / Full Deep双方へ加算 + +診断情報 → **Hybrid実験を開始** を押すとダイアログ表示後に自動開始する。ダイアログ内の **再実行** で同じ設定を再度測定でき、結果はJSON保存できる。 + +## CPU事前モデル + +1600×900相当、Seahorse、31×21点、BigInt P/P+64 oracle。`riskLimit=1e13`: + +| depth | raw DS class error | Deep補正対象 | DS class error捕捉率 | +|---:|---:|---:|---:| +| z6 | 4 / 651 | 18 / 651 = 2.8% | 100% | +| z8 | 4 / 651 | 72 / 651 = 11.1% | 100% | +| z10 | 10 / 651 | 225 / 651 = 34.6% | 100% | +| z12 | 51 / 651 | 594 / 651 = 91.2% | 100% | + +このため事前仮説は次の通り。 + +- z6: Hybrid有望。 +- z8: Hybrid有望。 +- z10: 補正率約35%でもFull Deepより速いかが主要な計測点。 +- z12: 約91%をDeepへ送るためHybridの意味が薄く、Full Deepへ切り替える候補。 + +## 採用gate + +Hybridをproduction候補にするのは、各viewで以下を全て満たす場合のみ。 + +1. corrected Full Deepとのescape/bounded class mismatch = 0。 +2. correction後remaining UNKNOWN = 0。 +3. Hybrid cold estimateがFull Deep coldより10%以上高速。 +4. correction対象率 < 60%。60%以上ならFull Deepを優先。 +5. 実GPU acceptanceでuncaptured validation/runtime error = 0。 + +最終production routerへ昇格する前には、BigInt oracleでdense boundary corpusを別途再検証する。 + +## 注意 + +- `riskLimit=1e13`はCPU事前モデルから得た候補で、固定仕様ではない。 +- `|dz/dc|`はcondition proxyであり厳密なroundoff error boundではない。 +- Hybrid correctionはreference buildを必要とする。ただしv24.2.3実機値ではreference buildはDeep全時間の小部分だったため、主要比較はGPU numeric costとなる。 +- z12でHybrid補正率が高いことを「失敗」とは扱わない。Full Deepへ切り替えるべき境界を検出することも実験目的である。 diff --git a/IMPLEMENTATION_REPORT.md b/IMPLEMENTATION_REPORT.md index f3b0553..dd43bc4 100644 --- a/IMPLEMENTATION_REPORT.md +++ b/IMPLEMENTATION_REPORT.md @@ -1,92 +1,118 @@ -# Mandelbrot Deep Zoom v23 実装報告 +# v24.2.3 crossover benchmark implementation report -## 結論 +## ファイルの役割 -`IMPROVEMENT_PROPOSAL.md`のv22向け設計判断をrenderer v23へ反映し、ローカル数値検証、WASM再現build、Hosted/Standalone成果物の生成まで実施した。提案は履歴文書であり、修正版のcoverage/Export/受入契約を現行基準とする。実ブラウザでのみ測定できる性能・表示・操作・download検証は、今回の環境にin-app browser bindingがないため未実行である。したがって、実装と確認済みの非ブラウザgate、未確認のbrowser acceptanceを分けて扱う。 +- `index.html`: UI、canvas、Export dialog。WebGPU実装ロジックは持たない。 +- `script.js`: BigInt ViewState、reference Worker、WebGPU resource orchestration、backend router、screen/Export pipeline。 +- `gpu-kernels.js`: production 6 WGSL source + benchmark-only `DS Direct` 1本。DS Directはproduction routerから参照しない。 +- `tests/v24-sparse-correction-model.mjs`: WGSLと同じf32/double-single式をCPU `Math.fround`で再現し、BigInt oracleと比較する品質gate。 +- `tests/v24-backend-policy.mjs`: view-specific route policyの純粋ロジック検査。 +- `tests/webgpu-acceptance.*`: 実GPU compile/dispatch/quality/export gate。 +- `AUDIT_V24.md`: 現releaseの検査結果と未検証事項。 +- `MIGRATION_V24.md`: v23からの設計変更と、廃案を含む理由。 -## 実装済み +## 今回作成/変更した理由 -- 常時RAFを廃止し、invalidate時だけ動くevent-driven schedulerへ変更した。 -- wheel/pinch/pan中は再投影だけを行い、settle後に計算を1回開始する。 -- Preview時間予算を110msへ下げ、cold deep Previewは幅48px以下から開始する。以後は実測MPPでPreview寸法を決める。 -- 標準deepのCoveredは実測MPPから約1.4秒のpixel budgetを算出する。未計測・再投影のみの場合は保守的MPPを使い、DPR下限は固定比率ではなく長辺64pxとするため4Kでも予算が効く。精細・精度優先ではこの時間適応を行わない。 -- 処理モードの自動到達点を固定し、省電力はPreview、標準はCovered、精細はRefined、精度優先はValidatedで停止する。標準では境界AAと未確定追加反復を既定で実行しない。 -- 精細の未確定追加反復は全未確定pixelではなくescape境界候補に限定し、1 viewあたりdeep 384点/shallow 4,096点を上限にする。 -- `Reprojected → Preview → Covered → Refined → Validated/検証未完了`を分離した。 -- Coveredはeffective DPRの表示Canvas全域をpixel中心`(x + 0.5, y + 0.5)`で計算する。 -- escape field、分類、confidenceと彩色を分離し、palette変更時の反復再計算を廃止した。 -- fieldへ各sampleの実escape iterationを保持し、再着色・未確定継続・Export監査で近似値ではなく同じ数値結果を使う。 -- adaptive 2×/4× AA、linear-light resolve、byte-budget detail cacheを実装した。 -- 深部Worker、deep/BLA/color WASM、reference資産を遅延生成し、Worker数を実測とmemory budgetで増減する。 -- deep/BLA/colorはmainで各1回だけcompileし、compile済み`WebAssembly.Module`をWorkerへstructured cloneする。Worker sourceから埋込payload、palette LUT、RGBA生成の重複を除き、Workerは数値fieldだけを返す。 -- 通常jobから学習したstrip時間とWorker数をkernel version・端末帯域keyで永続化し、30日TTL、schema version、storage拒否fallbackを付けて次回起動で再利用する。 -- fixed-point direct、perturbation、rebase、BLA、局所guarded directを共通tile schedulerへ接続した。 -- deep切替を固定zoom閾値ではなくpixel stepとULPの比、hysteresisで決める。 -- deep切替とguard bitsへ直近orbitのglitch/未確定/補修率を加え、精度昇格時は低精度で作った基準軌道を破棄して再構築する。 -- 高精度directを約6msでyieldする非同期処理へ変更し、取消可能にした。 -- f64解析・収縮判定は`INTERIOR_LIKELY`のまま保持し、ValidatedではP/P+64双方の固定小数点整数不等式で主カージオイド/周期2球を証明できたsampleだけを`INTERIOR_PROVEN`へ昇格する。 -- 精度優先のdeep描画では、基準軌道もP/P+64で独立再計算して疎なcheckpointとescape位置を照合し、不一致時はglobal reference全体を32 bit昇格して作り直す。 -- 1×/2×/4×/custom、1×/2×2 AA、Balanced/Validated、進捗、取消に対応する独立tile exportを実装した。 -- Balanced Exportは未確定数をmetadataへ記録して完了可能とし、Validated Exportは未確定sampleが残る場合にfinal PNGを出さず明示失敗する。 -- Export sidecarへViewSpec、precision/iteration policy、未確定sample数、backend、全kernel SHA-256、色空間、encoder、paletteを記録する。 -- version付きURL、戻る/進む、座標・spanの正確値入力/copy、Undo/Redoを実装した。 -- 初回UI、一般statusと診断status、mobile compact status、keyboard操作、live region、visible focus、44px target、reduced motion/transparencyを実装した。 -- Hosted版はcontent-hashed外部WASMとstreaming compile、Standalone版は3ファイル直開きを維持した。 -- cacheは件数ではなくmobile 96 MiB/desktop 192 MiBのlogical byte ledgerで管理する。 +深部primaryのUNKNOWNを黒として残すことが閲覧品質を下げていたため、追加referenceではなく同じreference上のdouble-single correctionへ変更した。またdirect利用範囲を固定閾値だけで狭めず、actual view上のqualityとGPU速度を測ってdeep採用を限定するためrouterを変更した。 -## 再現buildと配布 +## 機能検査 -- `src/shallow_kernel.c`、`src/deep_kernel.c`、`src/bla_kernel_v18.c`、`src/color_kernel.c`、`src/abi.json`を配置した。 -- `toolchain.lock.json`でclang 17.0.6、target、SIMD/scalar flagsを固定した。 -- clang 17.0.6とNode.js v22.18.0で8 WASMを再buildした。tool配布archiveのURL/SHA-256はrepositoryに保持されていないため、再監査可能な証拠範囲はversion/flags、source hash、生成manifest、golden結果までとする。 -- shallow、deep、BLA、colorのgoldenとSIMD/scalar同値gateに合格後、payloadと`kernels.js`を昇格した。 -- `scripts/build-kernels.ps1`は同一payloadだけを自動deduplicateし、異なるCOLOR variantsも正しく保持する。 -- `scripts/build-hosted.ps1`は現manifest以外の古いcontent-hashed WASMを削除する。 +`npm test`で以下を自動実行する。 -生成先: +- JS syntax +- WGSL reserved identifiers +- source/tree/index contracts +- reference Worker +- direct f32 model +- backend router +- guarded deep model +- sparse DS correction model +- BLA disabled contract +- geometry/export contract +- exact deep coordinate formatter +- streaming PNG +- real-GPU acceptance source contract -- Hosted: `dist/hosted/`と`dist/wasm/` -- Standalone: `dist/standalone/` -- 再現build: `build/wasm-v23/` +## 性能設計 -現在の非圧縮基準値は、Hosted first view 144,044 bytes、Standalone 172,591 bytesである。詳細なfile hashは`audit/v23-source-baseline.json`に記録した。v22成果物が残っていないため、v22の転送bytes/compile timeに対するnon-regressionは判定不能であり、このv23値を今後の比較起点とする。 +- overlap probeは本描画前に1回/current-view。 +- speed probeは最大224×160。 +- quality probeは最大72×54を5領域。 +- backend profileは補助診断であり、現在viewのquality probeを省略しない。 +- DS correctionはUNKNOWNのみ数値ループへ入り、KNOWN pixelは即return。 +- Export workspaceは512×512固定で再利用。 -## 合格済みgate +## 既知の制約 -- source contract: pass -- kernel source/ABI contract: pass -- fixed-point precision 256/320 bit: pass -- 基準軌道P/P+64 checkpoint一致と故意の不一致検出: pass -- analytic interior integer proof 256/320 bit: pass -- pixel mapping 851 samples、shallow/deep/BLA、2×/4× tile seam: pass -- 配布shallow WASM pixel contract 1,440 samples、mismatch 0: pass -- generated-WASM shallow/color/deep/BLA golden: pass -- SIMD/scalar結果比較: pass -- 本体、shallow Worker、deep WorkerのJavaScript構文: pass -- mock DOM初期化とv23 diagnostics: pass -- compile済みdeep/BLA/color Moduleのstructured clone・instantiate: pass -- Node固定sceneの計測記録と処理量予算式の構造契約: pass(`audit/v23-node-performance.json`)。browser時間目標の達成を意味しない -- Hosted/Standalone buildと全payload SHA-256: pass +- double-singleはWGSLの実装差を含めmembership certificateとして扱わない。 +- 実GPU acceptanceはWebGPU adapterを取得できる環境で別途実行が必要。 -toolchainを利用できる環境では次で省略なしに再実行できる。 -```powershell -powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/test-all.ps1 -RequireToolchain -``` +## v24.2.3追加作業 -## Browser acceptance gate +- `DIRECT_DS_WGSL`: 全画面double-single Directの速度/品質候補。 +- `runCrossoverBenchmark()`: z0〜z40のround-depth自動比較。 +- `Backend比較` dialog: 実GPUでf32 / DS / Deep warm / Deep coldを同一解像度で計時しJSON保存。 +- `tests/v24-crossover-quality.mjs`: 1600×900相当のBigInt P/P+64 oracle品質掃引。 +- `CROSSOVER_BENCHMARK.md`: 実装意図、CPU品質結果、次routerの判定基準。 -`tests/browser-benchmark.html`は1 runを1 profile・1 buildへ限定し、`?profile=desktop&target=hosted`のように実行する。iframe寸法によるDPR模擬を廃止し、非zero wheelのtransform-to-paint、30回以上のPreview、実際のFine→Refined、raw pixel golden、long task、Export metadata/完了前download/再現性、keyboard操作を自動判定する。HostedとStandaloneを別に開く。visual golden未登録、observed browser/GPU memory、visible focus、page zoom/pinch、screen-reader順序は外部gateとして残り、自動項目が通ってもfull acceptanceを返さない。今回の環境ではbrowser bindingがなかったため、`audit/v23-browser-baseline.json`は`not-run`のままである。 +現時点ではDS Directは診断専用であり、本体のbackend選択は変更していない。実GPU速度結果なしにproductionへ昇格させないための措置。 -残る実機確認: -- mobile/desktop/4Kの表示画像とnative coverage -- 操作反映p95、Preview/Covered/Refined所要時間 -- idle時の予約RAF/timer/background job/Canvas・DOM write 0 -- browser/GPUを含むobserved peak memory -- Export寸法、完了前downloadなし、取消、PNG/JSON内容 -- keyboard-only、focus、44px target、live status、page zoomとcanvas pinchの操作確認 +## v24.2.4 Hybrid experiment preparation -このgateが合格するまで、v23の実装完了と数値gate合格は主張できるが、固定実機に対する性能目標の達成は主張しない。 +- Added benchmark-only `DIRECT_DS_GUARDED_WGSL`; production router is unchanged. +- Guard metric: approximate `|dz/dc|`; threshold sweep `1e12 / 1e13 / 1e14`. +- Added `benchmarkHybrid()` / `renderTileMetaHybrid()` using the existing Deep double-single correction only for DS-marked UNKNOWN pixels. +- Added z6/z8/z10/z12 experiment UI and JSON export. +- Added CPU BigInt P/P+64 risk-model gate. At risk=1e13, observed DS class errors are all caught at z6/z8/z10 with correction rates 2.8% / 11.1% / 34.6%; z12 correction rate is 91.2%, intentionally indicating the Full-Deep crossover. +- Added real-WebGPU acceptance smoke for the Hybrid diagnostic path. -browser非依存の現行成果物監査は`audit/v23-browserless-baseline.json`に保存した。公式SHA-256を照合したNode.js v22.18.0 portable binaryで7 executable testsが合格し、source/document contract、851 pixel mapping、1,440 pixel contract、Module cloneを再確認した。deep runtimeは出典付きの混在境界sceneで特性を測るが、値は`measured-not-acceptance`である。v22比較は`not-verifiable`、source-WASM再buildは保存済みprior evidenceの再利用として明示している。 + +## v24.2.5 Sparse Direct experiment (Implementation) + +- The previous sparse-Full-Deep diagnostic is superseded and is not exposed in the UI. +- Added `DIRECT_TS_CORRECT_WGSL`, a reference-free three-term f32 expansion correction shader that only processes DS sensitivity UNKNOWN pixels. +- Added `benchmarkSparseDirect()` / `renderTileMetaSparseDirect()` and a z6/z8/z10/z12 risk sweep. +- Full Deep remains the quality/timing baseline; the Sparse Direct candidate itself does not build or bind a reference orbit. +- Production backend routing is unchanged. +- Root `index.html` is now a single-file bundle with no external script dependency, specifically to keep diagnostic UI controls functional when the HTML is opened from an archive/temp extraction. +- Static single-file UI contract and real-browser click smoke are separate gates; real WebGPU arithmetic still requires a WebGPU-capable browser. + +- Sparse Direct coordinate packing uses exact residual subtraction from BigInt fixed-point into three f32 components; this removed the CPU TD-model class errors caused by the earlier host-Number split. + +## v24.2.6 Fixed96 experiment implementation + +Purpose: test a deterministic reference-free correction tier between DS Direct and Full Deep without relying on WGSL float-expansion behavior. + +Implemented: +- `DIRECT_FIXED96_CORRECT_WGSL`: signed Q8.88 Direct iteration using 3×u32 values. +- exact 32×32→64 multiplication from 16-bit partial products; +- 96×96→192 accumulation and rounded Q8.88 reduction; +- BigInt→Q8.88 tile coordinate packing; +- DS risk gate + Fixed96 UNKNOWN-only correction benchmark; +- per-risk failure reporting instead of collapsing all failures to `none`; +- single-file ZIP UI contract; +- CPU oracle and limb-arithmetic tests. + +Not implemented: production router integration. The experiment must first pass on a real WebGPU adapter. + + +## v24.2.7 Fixed96 hotfix + +The Fixed96 experiment's real-GPU `remaining` counter exposed an implementation bug rather than an arithmetic-quality limitation. The correction shader's range check confused the 8-bit Q8.88 integer field with redundant sign-extension. The hotfix removes that check and makes bailout ordering overflow-safe: component-certain escape first, exact fixed-point magnitude only inside the ±2 component box. + + +## v24.2.8 — Fixed96 GPU compact queue + +### Files changed + +- `gpu-kernels.js`: DS risk queue output, indirect-args finalize shader, 1D queue-indexed Fixed96 correction kernel. +- `script.js`: queue/indirect buffers, indirect dispatch encoding, per-sample queue integrity verification, queue/dispatch diagnostics in the experiment result. +- `tests/v24-fixed96-contract.mjs`: static true-sparse contract. +- `tests/v24-fixed96-queue-model.mjs`: fail-closed queue counter/indirect argument model. +- `tests/webgpu-acceptance.js`: real-GPU Fixed96 smoke now requires queue integrity and dispatch rate equality. + +### Performance intent + +The previous implementation's numerical correction fraction was sparse, but its GPU invocation topology was not. v24.2.8 makes integer iteration work proportional to the actual queue length. The DS guard remains a full-tile pass because it is the cheap classifier; only the expensive Q8.88 loop is sparse. diff --git a/MIGRATION_V24.md b/MIGRATION_V24.md index 47de5d5..0cf0571 100644 --- a/MIGRATION_V24.md +++ b/MIGRATION_V24.md @@ -1,115 +1,157 @@ -# v23 → v24.1.3 WebGPU migration -## v24.1.3 browser hotfix +# v23 → v24.2.3 WebGPU migration / crossover study -実WebGPUブラウザで`direct` shaderのparse時に、WGSL予約語`meta` / `smooth`をstorage変数名へ使用していることが判明しました。両識別子を`fieldMeta` / `fieldSmooth`へ改名し、WGSL 16.2 reserved-word listを全shader sourceへ機械照合するtestを追加しました。 +## 目的 -同時に、shader初期化失敗を通常のWebGPU非対応と同じ扱いにしてCPU f64 rendererへ落としていた挙動を廃止しました。旧挙動は数百万pixel × 数百iterationをmain-thread fallbackで実行し、shader errorの直後にUIが極端に重くなる原因でした。v24.1.3ではshader/pipeline failureは描画を停止して明示し、CPU fallbackは`navigator.gpu`がない/adapterがない場合だけ使います。screen pixel budgetも標準約1.5M(desktop)へ縮小しました。 +v23のCPU/WASM deep pixel rendererとWorker poolを廃止し、画素単位のmassively parallel処理をWebGPUへ集約する。CPU高精度処理はBigInt ViewStateとreference orbit生成に限定する。 - -## 最終アーキテクチャ +## 最終構成 ```text -Main thread - UI / BigInt ViewState +BigInt ViewState │ - ├─ shallow ─────────────────────────────┐ - │ │ - └─ deep → Reference Worker │ - BigInt orbit P+64 │ - checkpoint guard P+128 │ - hi/lo f32 packing │ - │ │ - └──────────────┐ │ - ▼ ▼ - WebGPU numeric - f32 direct / guarded - rescaled perturbation - │ - fieldMeta + fieldSmooth field - │ - GPU color / AA - │ - GPU presentation + ├─ Direct安全域 ─────────────── WebGPU f32 direct + │ + └─ Deep/overlap + │ + ├─ overlap: current-view GPU quality/speed probe + │ ├─ direct比較 + │ └─ corrected deep比較 + │ + └─ deep: BigInt reference Worker + ↓ + guarded rescaled f32 perturbation + ↓ + UNKNOWN? + / \ + no yes + │ ↓ + │ double-single correction + └────────────┘ + ↓ + GPU field + ↓ + color / AA / present ``` -CPU側に残る高精度処理はview座標とreference orbit生成だけで、pixel rendererではありません。 +## v24.1系からの品質修正 -## BLAを最終版から外した理由 +v24.1のguarded f32 perturbationは誤分類を避けるためUNKNOWNへ保守退避する設計だった。境界sceneではUNKNOWNが大きく、interiorに近い暗色表示と相まって黒面積増大/黒点として見えた。 -初期WebGPU版ではCPU f64でBLAを作り、GPU用f32係数へ量子化して評価していました。しかし`swirly-seahorses-z12`を61×39へ高密度化した回帰で、BLAあり経路がbounded pixelをescapedへ反転する反例を検出しました。 +### Multi-reference評価 -f64で導出した受入半径へf32係数量子化誤差を安全に織り込む設計が未確立だったため、閾値調整で残すのではなくproduction BLAを完全に除去しました。 +最初にUNKNOWN macro tileへsecondary BigInt referenceを追加する方式をモデル化した。しかし複数referenceを追加しても`swirly-seahorses-z12`全画面モデルのUNKNOWN回収率が低く、reference生成・転送・再dispatchのコストに見合わなかった。そのためproductionへは採用しない。 -## Deep numeric policy +### Sparse double-single correction -Deepではpixel offsetを +代わりにUNKNOWNだけをdouble-single complex perturbationで再評価する。reference orbitはprimaryと共用するため追加BigInt orbitは不要。 -```text -delta = w * 2^scaleExp -``` +61×39 dense model / 2000 iter: -として保持します。通常反復はf32の正規化座標で行い、値域が偏った時だけ16 bit単位でrescaleします。 +- primary UNKNOWN 478 +- primary membership reversal 0 +- correction recovered 478 +- remaining UNKNOWN 0 +- correction membership reversal 0 +- escape iteration mismatch 5(最大374) -さらにroundoff上界を追跡します。 +したがってcorrectionは表示品質passとして採用し、membership certificateには使わない。 -- escape候補で `|z| - error > 2` の時だけescapedを確定 -- max iteration到達時もabsolute errorがpolicy閾値以下の場合だけ`INTERIOR_LIKELY` -- 不確かな場合は`UNKNOWN` +## Backend router再設計 -閾値: +旧設計のzoom/ULP単一閾値はdeepを早く使いすぎる一方、閾値を下げるだけではdirectの境界誤分類リスクがある。 -```text -Balanced: 1e-3 -Strict: 1e-4 -``` +v24.2.1は三領域に分ける。 -Strictは同じ1-pass perturbationの受入閾値を厳しくするpolicyです。以前の「Fast → unresolved queue → Strict再実行」は、BLA撤去後には同じ式を二度計算するだけだったため削除しました。 +- ratio < 8: deep必須 +- ratio >= 128: direct +- 8 <= ratio < 128: overlap -## 既知反例の固定 +Overlapは本描画前にcurrent-view probeを行う。probeは中心のspeed tileと、画面5領域のquality tileをactual full-resolution pixel座標で評価する。directとDS補正済みdeepのescape/bounded分類差が0、補正後deep UNKNOWN率0.5%以下であることを要求する。そのうえでreference buildを含むdeep予測時間がdirectの85%未満ならdeep、それ以外はdirect。 -`swirly-seahorses-z12`の61×39 gridで旧版が誤った3 pixelをregressionへ固定しています。 +深度binの過去profileだけでbackendを決定しない。 -```text -(25,12) oracle bounded -(26,15) oracle bounded -(27,25) oracle escaped @ 1957 -``` +## BLAを戻さない理由 -v24.1.3ではBalanced/Strictともこれらを誤分類せず`UNKNOWN`へ退避します。 - -## Presentation / recolor - -数値field (`fieldMeta`, `fieldSmooth`) はGPU常駐です。色変更はGPU recolorだけで、Mandelbrot反復を再実行しません。 - -描画中にrecolor操作が入った場合はrender tokenを破壊せず`recolorPending`へ集約し、数値frame完了後に最新色を適用します。旧版にあった`rendering=true`残留レースを避けています。 +f64 BLAをf32係数へ量子化した旧試作は、dense swirly回帰でbounded/escaped membership反転を起こした。量子化誤差を含む安全な受入半径が未確立のため、BLA builder/evaluator/nodeはproduction treeに存在しない。 ## Export -旧巨大Canvas方式は廃止しました。 +v24.2.1ではscreenだけでなくExportにもdeep correctionを適用する。 -- max side 16,384 -- 512² tile -- reusable GPU export workspace -- 2×2 AA: 4 GPU samples → GPU linear-light resolve → 1 readback/tile -- PNG: scanline bands → deflate stream → PNG chunks -- sidecar: exact BigInt view + `unresolvedSamples` +1×: -raw 16K RGBA全体をCanvasへ保持しないため、GPU texture limit回避だけでなくCPU raw-imageメモリも抑えます。 +```text +primary deep → clear stats → DS correction → color → 1 readback +``` -## Fallback +2×2: -WebGPU contextを早期取得すると、pipeline初期化失敗後に同じCanvasを2Dへ切り替えられません。v24.1.3ではshader/pipeline作成を先に行い、成功後にWebGPU canvas contextを取得します。 +```text +4 samples × (primary deep → DS correction → color) +→ GPU linear-light resolve +→ 1 readback / tile +``` -WebGPU unavailable時はshallow f64 fallbackのみです。deepをNumberへunderflowさせて描画する経路は削除しました。 +4 sampleのunresolved counterを分離し、sidecarの`unresolvedSamples`へ合計する。 -## 削除したv23 production資産 +## 削除済み - deep WASM renderer / Worker pool -- BLA WASM renderer -- color WASM -- BigInt direct pixel renderer -- CPU field → ImageData hot path -- Canvas detail tile cache -- Validated direct Export +- BLA WASM / GPU BLA試作 +- BigInt direct production pixel renderer +- multi-reference production試作 +- redundant strict retry queue +- Canvas2D deep hot path +- giant full-image Canvas Export +- Validated direct UI/Export -BigInt direct相当はテストoracleにのみ存在します。 +## Validation contract + +productionは`membershipCertified:false`。Strictはprimary error-bound閾値を厳しくするpolicyで、任意精度direct証明ではない。DS correctionもvisual-quality recovery passである。 + + +## v24.2.3 crossover study + +BigInt Deepを廃止するのではなく、使用開始点を後ろへ送れるか検証するためDS Direct候補を追加した。DSはreference構築が不要なためcold latency削減の可能性があるが、WGSL double-singleのコストと品質はGPU依存である。したがってこのreleaseではbenchmark専用とし、実GPU結果を得てからrouterへの昇格を判断する。 + +## v24.2.4 diagnostic Hybrid tier + +Crossover実機値でDS Directはz10/z12でもFull Deepより高速だったが、単独では分類品質が不足した。そこでproduction routerを変更する前に、DS Directのcondition proxy `|dz/dc|` を追跡し、危険pixelだけ既存Deep double-single correctionへ送るHybridをbenchmark専用で実装した。 + +候補routerはまだ採用しない。実験で `class mismatch=0`, `remaining UNKNOWN=0`, `Hybrid cold < 0.9 × Full Deep cold`, `correction rate < 60%` を満たすviewだけを将来のHybrid overlap候補とする。z12のCPU事前モデルではrisk=1e13で約91%が補正対象となるため、Full Deepへ切り替える境界を検出できる見込み。 + + +## v24.2.5 Sparse Direct experiment (Migration) + +- The previous sparse-Full-Deep diagnostic is superseded and is not exposed in the UI. +- Added `DIRECT_TS_CORRECT_WGSL`, a reference-free three-term f32 expansion correction shader that only processes DS sensitivity UNKNOWN pixels. +- Added `benchmarkSparseDirect()` / `renderTileMetaSparseDirect()` and a z6/z8/z10/z12 risk sweep. +- Full Deep remains the quality/timing baseline; the Sparse Direct candidate itself does not build or bind a reference orbit. +- Production backend routing is unchanged. +- Root `index.html` is now a single-file bundle with no external script dependency, specifically to keep diagnostic UI controls functional when the HTML is opened from an archive/temp extraction. +- Static single-file UI contract and real-browser click smoke are separate gates; real WebGPU arithmetic still requires a WebGPU-capable browser. + +- Sparse Direct coordinate packing uses exact residual subtraction from BigInt fixed-point into three f32 components; this removed the CPU TD-model class errors caused by the earlier host-Number split. + +## v24.2.6 experiment direction + +The float-expansion sparse correction branch is superseded by an integer fixed-point experiment. The transition strategy under test is now: + +```text +f32 Direct + -> DS Direct + -> DS risk gate + sparse Q8.88 Direct + -> Full Deep +``` + +Only the diagnostic branch is changed. Production remains on the existing direct/deep router until real-GPU timing and class gates justify promotion. + + +## v24.2.7 experiment correction + +Do not interpret v24.2.6 Fixed96 `remaining-unknown` measurements as a Fixed96 feasibility result. They were dominated by an invalid Q8.88 range guard. v24.2.7 is the first build in this branch whose real-GPU Fixed96 correction measurements are suitable for the crossover experiment. + + +## v24.2.8 experiment scheduling correction + +Fixed96 remains diagnostic and is not part of the production backend router. The correction execution changed from full-tile 2D dispatch with early returns to GPU compact-queue + indirect 1D dispatch. This isolates the experiment's arithmetic feasibility from an avoidable SIMD divergence/scheduling penalty. Adoption decisions must use v24.2.8-or-later timings; v24.2.7 Fixed96 timings are not representative of a sparse implementation. diff --git a/README.md b/README.md index c64d820..a10fdb5 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,136 @@ -# Mandelbrot Deep Zoom v24.1.3 — WebGPU +# Mandelbrot Deep Zoom v24.2.8 — Fixed96 True Sparse Queue Experiment -v23のCPU/WASM deep pixel rendererを撤去し、画素計算・field・彩色・再投影・高解像度ExportをWebGPUへ移した版です。 +v24.2.8は、v24.2.7で数値バグを修正したFixed96実験を**真のsparse GPU実行**へ変更した診断版です。DS guarded passが危険pixel indexをGPU queueへcompactし、1-thread finalize passがqueue件数からindirect dispatchを生成、Fixed96 Q8.88 kernelはqueueに入ったpixelだけを処理します。guard→queue→indirect Fixed96の間にCPU readbackはありません。production routerはまだ変更していません。 -## 数値構成 +## 数値backend -- ViewState: BigInt固定小数点。深度に応じてbit数を自動拡張。 -- Shallow: WebGPU `f32` direct iteration。f32座標分解能に十分な余裕があるviewだけで使用。 -- Deep reference: 専用WorkerでBigInt固定小数点orbitを1本生成。referenceはview精度より64 bit高く生成し、さらに+64 bitのguard orbitでcheckpoint照合。 -- Deep pixels: WebGPU `guarded rescaled f32 perturbation`。spanはmantissa + exponentへ分離し、`1e-400`級でもpixel offsetをf32 absolute値へ潰さない。 -- Error handling: roundoff上界がescape/bounded判定へ影響し得るpixelは `FIELD_UNKNOWN` にする。UNKNOWNを内部点へ偽装しない。 -- BLA: **productionでは無効・未搭載**。旧f32量子化BLAで境界pixelのmembership反転を再現したため、再導入していない。 +- **Direct**: WebGPU `f32` direct iteration。 +- **Deep primary**: BigInt reference orbit + WebGPU guarded rescaled `f32` perturbation。 +- **Deep correction**: primaryで`FIELD_UNKNOWN`になったpixelだけ、同じreferenceを用いるdouble-single perturbationで再評価。 +- **BLA**: productionでは未搭載。旧f32量子化BLAでmembership反転を再現したため再導入していません。 +- **Multi-reference**: v24.2開発中に全画面モデルを評価したが、追加referenceの生成/dispatchコストに対してUNKNOWN回収が小さかったためproductionから撤去しました。 -旧v23の`Validated direct`は削除しています。v24.1.3のStrictは誤差許容閾値を厳しくする保守的GPU policyで、任意精度direct全画素証明ではありません。PNG sidecarは常に `membershipCertified:false` です。 +Deep primaryはroundoff上界を追跡し、escape/bounded判定を安全に受理できないpixelを`UNKNOWN`にします。double-single correctionは表示品質回復用であり、任意精度membership証明ではありません。PNG sidecarの`membershipCertified`は常に`false`です。 -## 描画パイプライン +## Direct / Deep router -通常表示ではfieldをGPUに保持します。 +単一のzoom閾値でdeepへ切り替えません。 -```text -BigInt ViewState - └─ deep時: BigInt reference Worker - ↓ -WebGPU direct / guarded perturbation - ↓ -fieldMeta + fieldSmooth GPU buffers - ↓ -GPU color / optional boundary smoothing - ↓ -offscreen texture - ↓ -GPU reprojection / canvas -``` +1. `pixel step / f32 ULP < 8`ならdirect座標分解能が不足するためdeep必須。 +2. 比率が128以上ならdirectを即採用。 +3. 8以上128未満のoverlap帯では、本描画前に**現在のview専用GPU probe**を実行。 +4. probeではactual full-resolution座標上の複数領域をdirectと「DS補正済みdeep」で比較。 +5. deep側UNKNOWN率が0.5%以下、escape/bounded分類差0を満たした場合だけ速度比較へ進む。 +6. reference生成時間を含めてもdeepがdirectより15%以上高速な場合だけdeepを採用。それ以外はdirect。 -パレット・cycle・shift・HQ変更は数値fieldを再計算せずrecolorします。pan / wheel / pinch操作中は直前frameをGPUで再投影し、settle後に新しい数値frameを計算します。 +過去の同深度profileだけで現在viewをdeepへ振ることはありません。profileは診断/平滑化用で、backend選択には現在viewのquality probeを要求します。 + + +## Crossover benchmark + +診断情報の **Backend比較** から、現在のGPUで z0 / 2 / 4 / 6 / 8 / 10 / 12 / 14 / 16 / 20 / 30 / 40 を自動測定できます。 + +速度測定は全画面を反復実行しません。現在のview座標上の代表3タイル(最大256×144)を測定して `ms/pixel` を求め、現在のcanvas全体へ換算します。品質比較は別の5領域で実施します。これによりz12以降で全画面Deepを何度も実行してGPU queueが長時間塞がる問題を避けます。reference生成または1回のGPU測定が15秒を超えた場合は明示的に中断します。 + +比較するbackend: + +- `f32`: 現行WebGPU標準。 +- `DS`: experimental whole-frame double-single Direct。**production routerには未接続**。 +- `Deep warm`: BigInt referenceをcache済みとしたDeep primary + DS correction。 +- `Deep cold`: verified BigInt reference構築 + Deep warm。 + +結果の時間欄は代表tileからの**全画面換算値**です。画面表示に加えJSON保存できます。品質のBigInt oracle掃引結果は `CROSSOVER_BENCHMARK.md` と `audit/v24-crossover-quality.json` を参照してください。CPUモデルではSeahorse trackでf32がz6付近、DS Directがz10付近からmaterial degradationを示し、DSを中間backendとして試す価値があるという結果です。 + +## Fixed96 Direct experiment + +診断情報の **Fixed96 Direct実験** から z6 / z8 / z10 / z12 を測定できます。DS Directで `|dz/dc|` を追跡し、`1e11 / 1e12 / 1e13 / 1e14` の各risk thresholdを超えたpixelだけ、reference不要の**96-bit符号付きQ8.88固定小数点Direct**で再評価します。 + +v24.2.8では補正対象を`u32` pixel-index queueへcompactします。queue lengthから`dispatchWorkgroupsIndirect()`用の1D workgroup数をGPU内で生成し、Fixed96は**queued pixel数だけ**実行します。従来の「全画面dispatchして非対象laneをreturn」は廃止しました。 + +実験システムは各GPU sampleで次をfail-closed検証します。 + +- `selected == enqueued == dispatchCount == processed` +- queue overflow = 0 +- indirect workgroup数 = `ceil(enqueued / 64)` +- invalid/stale queue entry = 0 +- `corrected + remain == processed` + +1項目でも崩れれば測定値を採用せず実験をエラー停止します。結果表には`correction`と実際の`dispatch`率を別々に表示し、`queue=ok`を確認できます。速度は代表3tileから全画面換算し、品質は5 spatial tilesでcorrected Full Deepと比較します。production routerへは未接続です。 + +### 単一ファイル起動 + +rootの `index.html` は `gpu-kernels.js` と `script.js` を内包した単一HTMLです。ZIP内のHTMLをOSが一時展開して開く場合でも、隣接JSファイルに依存しません。 + +## Deep correction + +primary deepでUNKNOWNが残った場合、通常/精細/Strictではsparse double-single correctionを1回実行します。KNOWN pixelはshader冒頭で即returnするため、数値反復はUNKNOWNだけです。省電力モードはUNKNOWNが一定量を超えた場合のみ補正します。 + +`swirly-seahorses-z12`の61×39 CPU `Math.fround`モデルでは: + +- primary UNKNOWN: 478 / 2379 +- primary false escape: 0 +- primary false bounded: 0 +- DS correction回収: 478 / 478 +- correction後UNKNOWN: 0 +- correction false escape / false bounded: 0 / 0 + +escape iterationは5 pixelでBigInt oracleと差があり、最大差374 iterationでした。このためDS correctionは「見た目と有限iterationの分類回復」であり、Validated相当の保証には使用しません。 + +## UNKNOWN診断 + +内部metadataではUNKNOWN理由を分離しています。 + +- error-bound +- escape-uncertain +- reference-end +- rebase-gap +- range +- operation-limit + +未補正UNKNOWNはinteriorの完全な黒と別色で表示され、診断欄に理由別件数が出ます。 ## Export -高解像度PNGは最大辺16,384 pxです。 +- 512×512 reusable GPU workspace。 +- deep Exportでもprimary後にDS correctionを実行。 +- 2×2 AAは各4 sampleをGPUで数値計算→補正→彩色し、GPU resolve後に1回だけreadback。 +- 最終画像全体の巨大Canvasは作らず、scanline bandを`CompressionStream('deflate')`へ流すstreaming PNG。 +- 16384px級の片辺をtile処理可能。 +- sidecarに`unresolvedSamples`を記録。 -- 512×512以下のGPU tileで計算。 -- Export用GPU buffers/textures/readback bufferは固定workspaceを再利用し、tileごとの大量生成を避ける。 -- 2×2 AAは4 sampleをGPUで計算し、GPUでlinear-light resolveした後、tileにつき1回だけreadback。 -- 全画像Canvasを確保せず、scanline bandを`CompressionStream('deflate')`へ送りPNGを構築。 -- sidecar JSONへ`unresolvedSamples`を記録。 +## Fallback -`unresolvedSamples > 0`は、数値policyがそのsampleを安全に分類できなかったことを意味します。 +WebGPUそのものが利用できない場合だけ軽量JavaScript f64 fallbackを使います。deep zoomはNumber underflow等で誤画像を出さず、「このズーム深度はWebGPUが必要です」と表示して停止します。shader/pipeline failureではCPU全画面rendererへ自動fallbackしません。 -## WebGPU unavailable - -WebGPUが利用できない場合は浅部のみJavaScript f64 fallbackを使います。deep zoomは誤画像を出さず、「このズーム深度はWebGPUが必要です」と表示します。 - -WebGPU自体が存在しない場合だけ軽量な浅部JavaScript fallbackを使います。**WGSL/shader/pipeline初期化エラー時はCPU全画面fallbackへ自動移行しません**。エラーを表示して停止し、shader不具合を隠したままCPUを占有しない設計です。`webgpu` canvas contextはpipeline作成成功後に取得します。high-performance adapterが得られない場合は通常のadapter requestも再試行します。 - -## v24.1.3 hotfix - -実ブラウザで発覚したWGSL parse errorを修正しました。WGSL 16.2で予約されている`meta` / `smooth`をstorage-buffer変数名に使っていたため、`fieldMeta` / `fieldSmooth`へ変更しています。全shaderをWGSL reserved-word一覧へ照合するNode gate `tests/v24-wgsl-reserved.mjs`も追加しました。 - -表示負荷も見直し、標準モードのscreen pixel budgetをdesktop約1.5M / 小型端末約0.75Mへ縮小しました(旧版は約4M / 2M)。省電力は約0.5M、精細は最大約3M、Strictは最大約2Mです。WebGPU非対応時のCPU fallbackは約0.25M pixelに制限します。 - -## Standalone - -`index.html`, `gpu-kernels.js`, `script.js`の3ファイルで動作します。外部WGSL fetchはありません。 - -```text -index.html -gpu-kernels.js -script.js -``` - -ブラウザが`file://`上でWebGPUを許可しない構成ではlocalhost/HTTPSで開いてください。 - -## Test +## Build / test ```bash npm test npm run build ``` -`npm test`は以下を検査します。 +`npm test`には以下を含みます。 -- JS syntax / source contracts -- WGSL reserved-word token audit (`meta`, `smooth`, `ref`等を含む仕様予約語) -- 旧C/WASM deep assetがproduction treeに残っていないこと -- BigInt reference Worker + guard checkpoints -- shallow f32 CPU model vs BigInt oracle -- guarded perturbation CPU f32 model vs BigInt oracle -- `swirly-seahorses-z12`高密度回帰と既知3反例 -- BLAがproductionから除去されていること -- tile/pixel geometry -- `1e-400` / `1e-1000` coordinate formatting -- streaming PNG structure / CRC / inflate / abort -- real-WebGPU acceptance harnessのJS syntax +- WGSL reserved-word scan(9 shader。DS Direct / guarded DS / Fixed96 Direct correctionは実験専用) +- production source/tree contract +- BigInt reference Worker + P/P+64 checkpoint +- f32 direct CPU model +- view固有adaptive backend router policy +- guarded primary deep CPU model +- sparse double-single correction CPU model +- BLA disabled contract +- pixel/export geometry +- e400/e1000 exact coordinate formatting +- streaming PNG CRC/deflate/abort +- real-WebGPU acceptance harnessの静的contract -### 実GPU acceptance +実GPU受入は `tests/webgpu-acceptance.html` をWebGPU対応ブラウザで開いて実行します。これはdirect/deep/DS/Fixed96 Direct shader compile、DS correction、Fixed96 smoke、dense既知反例、view-specific router、1×/2×2 Export readback、uncaptured WebGPU errorを検査します。 -`tests/webgpu-acceptance.html`をWebGPU対応browserで開きます。これは実adapter上でshader compile / compute / readbackを行い、BigInt oracleと比較します。 +## 配布 -必須gate: +`npm run build`で以下を生成します。 -- stable corpus scene: sampled `UNKNOWN = 0` -- swirly scene: 25 sample中12以上を確定 -- `falseEscaped = 0` -- `falseBounded = 0` -- reference guard mismatch = 0 -- 既知dense反例で誤分類しない -- 1× / 2×2 Export smokeの未確定sample = 0 -- uncaptured WebGPU validation error = 0 +- `dist/standalone/` +- `dist/hosted/` -このリポジトリを生成した実行環境では`navigator.gpu`が公開されなかったため、実adapter gateだけは未実行です。静的/CPU-model gateの代替ではありません。 +`dist/standalone/` は単一 `index.html`、`dist/hosted/` は `index.html`, `gpu-kernels.js`, `script.js` です。root `index.html` も単一ファイルで、ZIPから展開してそのまま開けます。 diff --git a/audit/v24-backend-policy.json b/audit/v24-backend-policy.json new file mode 100644 index 0000000..94b7d06 --- /dev/null +++ b/audit/v24-backend-policy.json @@ -0,0 +1,9 @@ +{ + "status": "pass", + "kind": "view-specific-adaptive-backend-router", + "directRequiredRatio": 8, + "directDefaultRatio": 128, + "deepSpeedMargin": 0.85, + "deepProbeUnknownMax": 0.005, + "cases": 10 +} diff --git a/audit/v24-cpu-numeric-model.json b/audit/v24-cpu-numeric-model.json index 4de2c0c..bdc0d80 100644 --- a/audit/v24-cpu-numeric-model.json +++ b/audit/v24-cpu-numeric-model.json @@ -27,10 +27,31 @@ "guardMismatch": 0, "falseEscaped": 0, "falseBounded": 0, - "unknown": 71, - "known": 116, - "exactEscape": 110, - "mismatch": [] + "unknown": 49, + "known": 138, + "exactEscape": 130, + "mismatch": [ + { + "x": 5, + "y": 1, + "oracle": 675, + "gpu": { + "kind": "escaped", + "n": 674, + "err": 0.06557769039826006 + } + }, + { + "x": 6, + "y": 1, + "oracle": 636, + "gpu": { + "kind": "escaped", + "n": 635, + "err": 0.12117791268198698 + } + } + ] }, { "mode": "balanced", @@ -118,10 +139,31 @@ "guardMismatch": 0, "falseEscaped": 0, "falseBounded": 0, - "unknown": 71, - "known": 116, - "exactEscape": 110, - "mismatch": [] + "unknown": 49, + "known": 138, + "exactEscape": 130, + "mismatch": [ + { + "x": 5, + "y": 1, + "oracle": 675, + "gpu": { + "kind": "escaped", + "n": 674, + "err": 0.06557769039826006 + } + }, + { + "x": 6, + "y": 1, + "oracle": 636, + "gpu": { + "kind": "escaped", + "n": 635, + "err": 0.12117791268198698 + } + } + ] }, { "mode": "strict", @@ -199,7 +241,7 @@ "kind": "unknown", "n": 2000, "reason": "error-bound", - "err": 2.1991906643574973e+21 + "err": 34362354130585895000 } }, { @@ -211,7 +253,7 @@ "kind": "unknown", "n": 2000, "reason": "error-bound", - "err": 2.1991906643574973e+21 + "err": 34362354130585895000 } }, { @@ -223,7 +265,7 @@ "kind": "unknown", "n": 2000, "reason": "error-bound", - "err": 7.768308137076909e+21 + "err": 121379814641826710000 } }, { @@ -235,7 +277,7 @@ "kind": "unknown", "n": 2000, "reason": "error-bound", - "err": 7.768308137076909e+21 + "err": 121379814641826710000 } }, { @@ -247,7 +289,7 @@ "kind": "unknown", "n": 2000, "reason": "error-bound", - "err": 2084483903264.6707 + "err": 32570060988.51048 } }, { @@ -259,7 +301,7 @@ "kind": "unknown", "n": 2000, "reason": "error-bound", - "err": 2084483903264.6707 + "err": 32570060988.51048 } } ] diff --git a/audit/v24-crossover-gpu-status.json b/audit/v24-crossover-gpu-status.json new file mode 100644 index 0000000..d8d9601 --- /dev/null +++ b/audit/v24-crossover-gpu-status.json @@ -0,0 +1,25 @@ +{ + "status": "not-run", + "reason": "container Chromium does not expose a working WebGPU adapter / GPU process initialization fails", + "benchmark": "UI Diagnostics > Backend比較", + "depths": [ + 0, + 2, + 4, + 6, + 8, + 10, + 12, + 14, + 16, + 20, + 30, + 40 + ], + "timings": [ + "f32 numeric", + "DS direct numeric", + "Deep warm numeric", + "Deep cold = verified BigInt reference + warm numeric" + ] +} \ No newline at end of file diff --git a/audit/v24-crossover-hotfix.json b/audit/v24-crossover-hotfix.json new file mode 100644 index 0000000..147e999 --- /dev/null +++ b/audit/v24-crossover-hotfix.json @@ -0,0 +1,34 @@ +{ + "version": "24.2.3", + "issue": "Backend comparison could appear to hang at z12 deep because the benchmark repeated corrected Deep over the entire current canvas and waited for monolithic GPU queue completion.", + "fix": { + "speedTile": [256, 144], + "speedTileCount": 3, + "timingModel": "sum representative tile GPU queue completion time, derive msPerPixel, extrapolate to current full canvas", + "qualityTileCount": 5, + "gpuTimeoutMs": 15000, + "referenceTimeoutMs": 15000, + "productionRendererChanged": false, + "benchmarkFormat": "mandelbrot-webgpu-crossover-v2" + }, + "verification": { + "wgslReserved": "pass", + "sourceContract": "pass", + "indexContract": "pass", + "treeContract": "pass", + "crossoverQuality": "pass", + "crossoverContract": "pass", + "referenceWorker": "pass", + "directModel": "pass", + "backendPolicy": "pass", + "cpuNumericModel": "pass (individual run)", + "sparseCorrectionModel": "pass (individual run)", + "blaDisabledContract": "pass", + "geometryContract": "pass", + "coordinateFormat": "pass", + "pngStream": "pass", + "acceptanceContract": "pass", + "build": "pass", + "realWebGPU": "not available in this container" + } +} diff --git a/audit/v24-crossover-quality.json b/audit/v24-crossover-quality.json new file mode 100644 index 0000000..82fdda4 --- /dev/null +++ b/audit/v24-crossover-quality.json @@ -0,0 +1,766 @@ +{ + "format": "mandelbrot-crossover-quality-v1", + "kind": "cpu-f32-model + BigInt P/P+64 oracle; not real GPU", + "viewport": [ + 1600, + 900 + ], + "sampleGrid": [ + 21, + 15 + ], + "depths": [ + 0, + 2, + 4, + 6, + 8, + 10, + 12, + 14, + 16, + 20, + 30, + 40 + ], + "tracks": [ + "cusp", + "seahorse" + ], + "rows": [ + { + "track": "cusp", + "z": 0, + "limit": 350, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 21, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 188 + }, + { + "track": "cusp", + "z": 2, + "limit": 478, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 21, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1107 + }, + { + "track": "cusp", + "z": 4, + "limit": 550, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 21, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 851 + }, + { + "track": "cusp", + "z": 6, + "limit": 611, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 761 + }, + { + "track": "cusp", + "z": 8, + "limit": 667, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 920 + }, + { + "track": "cusp", + "z": 10, + "limit": 721, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 998 + }, + { + "track": "cusp", + "z": 12, + "limit": 772, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1005 + }, + { + "track": "cusp", + "z": 14, + "limit": 821, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1099 + }, + { + "track": "cusp", + "z": 16, + "limit": 870, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1134 + }, + { + "track": "cusp", + "z": 20, + "limit": 963, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1271 + }, + { + "track": "cusp", + "z": 30, + "limit": 1183, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1589 + }, + { + "track": "cusp", + "z": 40, + "limit": 1392, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 15 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1856 + }, + { + "track": "seahorse", + "z": 0, + "limit": 350, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 21, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 103 + }, + { + "track": "seahorse", + "z": 2, + "limit": 478, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 21, + "ds": 21 + }, + "f32": { + "falseEscaped": 2, + "falseBounded": 0, + "escapeIterationMismatch": 2, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 366 + }, + { + "track": "seahorse", + "z": 4, + "limit": 550, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 21, + "ds": 21 + }, + "f32": { + "falseEscaped": 0, + "falseBounded": 2, + "escapeIterationMismatch": 36, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 2, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 1 + }, + "modelMs": 161 + }, + { + "track": "seahorse", + "z": 6, + "limit": 611, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 2, + "ds": 21 + }, + "f32": { + "falseEscaped": 5, + "falseBounded": 0, + "escapeIterationMismatch": 104, + "uniqueCoords": 315 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 1, + "escapeIterationMismatch": 2, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 230 + }, + { + "track": "seahorse", + "z": 8, + "limit": 667, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 4, + "falseBounded": 0, + "escapeIterationMismatch": 307, + "uniqueCoords": 8 + }, + "ds": { + "falseEscaped": 1, + "falseBounded": 1, + "escapeIterationMismatch": 14, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 1, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 2 + }, + "modelMs": 376 + }, + { + "track": "seahorse", + "z": 10, + "limit": 721, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 10, + "falseBounded": 0, + "escapeIterationMismatch": 305, + "uniqueCoords": 2 + }, + "ds": { + "falseEscaped": 6, + "falseBounded": 6, + "escapeIterationMismatch": 34, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 512 + }, + { + "track": "seahorse", + "z": 12, + "limit": 772, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 21 + }, + "f32": { + "falseEscaped": 54, + "falseBounded": 0, + "escapeIterationMismatch": 261, + "uniqueCoords": 1 + }, + "ds": { + "falseEscaped": 14, + "falseBounded": 13, + "escapeIterationMismatch": 103, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 3 + }, + "modelMs": 919 + }, + { + "track": "seahorse", + "z": 14, + "limit": 821, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 1 + }, + "f32": { + "falseEscaped": 315, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 315 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1158 + }, + { + "track": "seahorse", + "z": 16, + "limit": 870, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 1 + }, + "f32": { + "falseEscaped": 315, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1211 + }, + { + "track": "seahorse", + "z": 20, + "limit": 963, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 1 + }, + "f32": { + "falseEscaped": 315, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1374 + }, + { + "track": "seahorse", + "z": 30, + "limit": 1183, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 1 + }, + "f32": { + "falseEscaped": 315, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 1968 + }, + { + "track": "seahorse", + "z": 40, + "limit": 1392, + "tested": 315, + "guardMismatch": 0, + "adjacentUniqueX": { + "f32": 1, + "ds": 1 + }, + "f32": { + "falseEscaped": 315, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "ds": { + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 0, + "uniqueCoords": 1 + }, + "deep": { + "falseEscaped": 0, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 0 + }, + "modelMs": 2066 + } + ], + "summary": { + "firstMaterialF32DegradationDepth": 6, + "firstMaterialDsDegradationDepth": 10, + "deepIssueRows": [ + { + "z": 8, + "falseEscaped": 1, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 2 + } + ], + "interpretation": "On the Seahorse track at a 1600x900 viewport, f32 direct becomes materially unsuitable around z6; DS direct remains a plausible bridge through roughly z8 and degrades by z10. Corrected deep is the best of the modeled backends, but one z8 false escape means it is not a membership certificate." + } +} \ No newline at end of file diff --git a/audit/v24-fixed96-hotfix.json b/audit/v24-fixed96-hotfix.json new file mode 100644 index 0000000..1745b16 --- /dev/null +++ b/audit/v24-fixed96-hotfix.json @@ -0,0 +1,19 @@ +{ + "version": "24.2.7-fixed96-hotfix", + "rootCause": "fx_bad_range treated the Q8.88 integer byte as redundant sign extension and rejected valid values such as +1.2", + "symptom": "correction rate approximately equaled remaining-unknown rate on the real GPU", + "fix": [ + "removed invalid fx_bad_range check", + "classify |Re(z)|>2 or |Im(z)|>2 as certain escape before squaring", + "square for exact |z|^2>4 only while both components are within +/-2", + "validate Q8.88 coordinate range and pixel-step underflow before packing" + ], + "regressions": [ + "v24-fixed96-contract.mjs", + "v24-fixed96-limb-model.mjs", + "v24-fixed96-limb-orbit-model.mjs", + "v24-fixed96-shader-range-model.mjs", + "v24-fixed96-model.mjs" + ], + "realWebGPU": "pending user rerun" +} diff --git a/audit/v24-fixed96-model.json b/audit/v24-fixed96-model.json new file mode 100644 index 0000000..2598b19 --- /dev/null +++ b/audit/v24-fixed96-model.json @@ -0,0 +1,182 @@ +{ + "format": "mandelbrot-fixed96-q88-model-v1", + "kind": "Exact BigInt model of signed Q8.88 direct + DS risk gate + BigInt P/P+64 oracle; integer GPU arithmetic should be deterministic modulo implementation bugs", + "viewport": [ + 1600, + 900 + ], + "sampleGrid": [ + 31, + 21 + ], + "rows": [ + { + "z": 6, + "limit": 611, + "variants": [ + { + "risk": 100000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 43, + "uncertainRate": 0.06605222734254992, + "wrongBefore": 4, + "wrongAfter": 0 + }, + { + "risk": 1000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 23, + "uncertainRate": 0.03533026113671275, + "wrongBefore": 4, + "wrongAfter": 0 + }, + { + "risk": 10000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 18, + "uncertainRate": 0.027649769585253458, + "wrongBefore": 4, + "wrongAfter": 0 + }, + { + "risk": 100000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 10, + "uncertainRate": 0.015360983102918587, + "wrongBefore": 4, + "wrongAfter": 1 + } + ] + }, + { + "z": 8, + "limit": 667, + "variants": [ + { + "risk": 100000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 202, + "uncertainRate": 0.3102918586789555, + "wrongBefore": 4, + "wrongAfter": 0 + }, + { + "risk": 1000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 126, + "uncertainRate": 0.1935483870967742, + "wrongBefore": 4, + "wrongAfter": 0 + }, + { + "risk": 10000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 72, + "uncertainRate": 0.11059907834101383, + "wrongBefore": 4, + "wrongAfter": 0 + }, + { + "risk": 100000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 46, + "uncertainRate": 0.0706605222734255, + "wrongBefore": 4, + "wrongAfter": 0 + } + ] + }, + { + "z": 10, + "limit": 721, + "variants": [ + { + "risk": 100000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 648, + "uncertainRate": 0.9953917050691244, + "wrongBefore": 10, + "wrongAfter": 0 + }, + { + "risk": 1000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 398, + "uncertainRate": 0.6113671274961597, + "wrongBefore": 10, + "wrongAfter": 0 + }, + { + "risk": 10000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 225, + "uncertainRate": 0.3456221198156682, + "wrongBefore": 10, + "wrongAfter": 0 + }, + { + "risk": 100000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 153, + "uncertainRate": 0.2350230414746544, + "wrongBefore": 10, + "wrongAfter": 0 + } + ] + }, + { + "z": 12, + "limit": 772, + "variants": [ + { + "risk": 100000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 602, + "uncertainRate": 0.9247311827956989, + "wrongBefore": 51, + "wrongAfter": 0 + }, + { + "risk": 1000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 600, + "uncertainRate": 0.9216589861751152, + "wrongBefore": 51, + "wrongAfter": 0 + }, + { + "risk": 10000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 594, + "uncertainRate": 0.9124423963133641, + "wrongBefore": 51, + "wrongAfter": 0 + }, + { + "risk": 100000000000000, + "tested": 651, + "guardMismatch": 0, + "uncertain": 488, + "uncertainRate": 0.7496159754224271, + "wrongBefore": 51, + "wrongAfter": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/audit/v24-fixed96-preparation.json b/audit/v24-fixed96-preparation.json new file mode 100644 index 0000000..89ff547 --- /dev/null +++ b/audit/v24-fixed96-preparation.json @@ -0,0 +1,31 @@ +{ + "status": "prepared", + "version": "24.2.6-fixed96-experiment", + "purpose": "DS risk gate -> sparse signed Q8.88 u32-limb direct correction -> compare with Full Deep", + "productionRouterChanged": false, + "standaloneUi": { + "rootIndexSingleFile": true, + "externalScriptTags": 0, + "button": "Fixed96 Direct実験", + "allRiskRowsVisible": true + }, + "fixedPoint": { + "bits": 96, + "fractionBits": 88, + "format": "signed Q8.88", + "limbs": 3, + "limbType": "u32" + }, + "cpuPreflight": { + "status": "pass", + "report": "audit/v24-fixed96-model.json", + "limbArithmetic": "tests/v24-fixed96-limb-model.mjs", + "safeCandidateAtDepths": [ + 6, + 8, + 10, + 12 + ] + }, + "realWebGPU": "pending: run tests/webgpu-acceptance.html and Diagnostics > Fixed96 Direct実験 on a WebGPU-capable browser" +} \ No newline at end of file diff --git a/audit/v24-fixed96-sparse-queue.json b/audit/v24-fixed96-sparse-queue.json new file mode 100644 index 0000000..809db86 --- /dev/null +++ b/audit/v24-fixed96-sparse-queue.json @@ -0,0 +1,40 @@ +{ + "version": "24.2.8-fixed96-sparse-queue", + "purpose": "Make the diagnostic Fixed96 correction truly sparse on GPU and fail closed on experiment-system counter/dispatch inconsistencies.", + "productionRouterChanged": false, + "execution": { + "guard": "full-tile DS sensitivity pass; risky local pixel indices atomically compacted", + "finalize": "1 invocation writes indirect args from GPU queue length", + "correction": "1D @workgroup_size(64), dispatchWorkgroupsIndirect, one queued pixel per invocation", + "cpuReadbackBetweenPasses": false + }, + "integrityGate": [ + "selected == enqueued == dispatchCount == processed", + "overflow == 0", + "indirectWorkgroups == ceil(enqueued/64)", + "invalidIndex == 0", + "staleEntry == 0", + "corrected + remain == processed" + ], + "tests": { + "fixed96Contract": "pass", + "queueIntegrityModel": "pass (7 cases)", + "wgslReserved": "pass (10 shaders)", + "fixed96LimbMultiply": "pass (20000 cases)", + "fixed96LimbOrbit": "pass", + "fixed96RangeRegression": "pass", + "singleFileUiContract": "pass", + "sourceContract": "pass", + "acceptanceContract": "pass", + "realWebGPU": "pending external run", + "samplingConsistencyGate": "pass (speed queue rate vs quality correction rate)" + }, + "sourceSha256": { + "script.js": "0104d437471b9ad1c3a9bc69206e1562b6841846e0e2343cde2c9ce5632d0a4d", + "gpu-kernels.js": "364e2babe5e986dc02d17c3e8897891e34d73498cc4c84d93c7f09cc2b6f3bd6", + "index.external.html": "23527ec2708d162bfe40c285952e170ce783cfafb63f37a5c7b5c7ecbfdec80c", + "tests/v24-fixed96-contract.mjs": "a85f569764a5623720614c79503398e5993846fa28d0a8aec45979e0edc225ce", + "tests/v24-fixed96-queue-model.mjs": "e29f84189314a9a15ae3e08da85f5bcf2d990ec9c6ab2e9c731ae4639d8f3d22", + "tests/webgpu-acceptance.js": "e5580f7cfc402aa5b533169cda4a088d3f10e0086002c508bf989b1ffff19cb5" + } +} diff --git a/audit/v24-hybrid-preparation.json b/audit/v24-hybrid-preparation.json new file mode 100644 index 0000000..574bbca --- /dev/null +++ b/audit/v24-hybrid-preparation.json @@ -0,0 +1,63 @@ +{ + "format": "mandelbrot-v24.2.4-hybrid-preparation-v1", + "createdAt": "2026-08-23T11:36:39Z", + "shaderVersion": "24.2.4-hybrid-experiment", + "productionRouterUnchanged": true, + "hybridDepths": [ + 6, + 8, + 10, + 12 + ], + "riskLimits": [ + 1000000000000.0, + 10000000000000.0, + 100000000000000.0 + ], + "cpuPriorRisk1e13": { + "z6": { + "correctionRate": 0.027649769585253458, + "caughtClassErrors": "4/4" + }, + "z8": { + "correctionRate": 0.11059907834101383, + "caughtClassErrors": "4/4" + }, + "z10": { + "correctionRate": 0.3456221198156682, + "caughtClassErrors": "10/10" + }, + "z12": { + "correctionRate": 0.9124423963133641, + "caughtClassErrors": "51/51" + } + }, + "tests": { + "wgslReserved": "pass", + "hybridContract": "pass", + "hybridRiskModel": "pass", + "crossoverContract": "pass", + "sourceContract": "pass", + "treeContract": "pass", + "referenceWorker": "pass", + "directModel": "pass", + "backendPolicy": "pass", + "cpuDeepModel": "pass", + "sparseCorrection": "pass", + "blaDisabled": "pass", + "geometry": "pass", + "coordinateFormat": "pass", + "pngStreaming": "pass", + "acceptanceContract": "pass" + }, + "realWebGPU": "not-run-in-container; run Hybrid実験 on target GPU", + "hashes": { + "source/index.html": "39427d275732e5bbce8d06253ce5f3e0a07e173081fc230cd34292050ec29827", + "source/gpu-kernels.js": "2773c39e416c52a7919aad0146cd95cf6fc108847ba3718db9253986a4f489e5", + "source/script.js": "d354ff6ce0937c616a02d34ed0accebe5c8c5640cc6ac98720d66f8d784359be", + "standalone/index.html": "39427d275732e5bbce8d06253ce5f3e0a07e173081fc230cd34292050ec29827", + "standalone/gpu-kernels.js": "2773c39e416c52a7919aad0146cd95cf6fc108847ba3718db9253986a4f489e5", + "standalone/script.js": "d354ff6ce0937c616a02d34ed0accebe5c8c5640cc6ac98720d66f8d784359be" + }, + "sourceStandaloneMatch": true +} \ No newline at end of file diff --git a/audit/v24-hybrid-risk-model.json b/audit/v24-hybrid-risk-model.json new file mode 100644 index 0000000..5d62939 --- /dev/null +++ b/audit/v24-hybrid-risk-model.json @@ -0,0 +1,56 @@ +{ + "format": "mandelbrot-hybrid-risk-model-v1", + "kind": "CPU Math.fround DS + BigInt P/P+64 oracle; not real GPU", + "viewport": [ + 1600, + 900 + ], + "sampleGrid": [ + 31, + 21 + ], + "riskLimit": 10000000000000, + "rows": [ + { + "z": 6, + "tested": 651, + "guardMismatch": 0, + "wrong": 4, + "uncertain": 18, + "caught": 4, + "uncertainRate": 0.027649769585253458, + "catchRate": 1 + }, + { + "z": 8, + "tested": 651, + "guardMismatch": 0, + "wrong": 4, + "uncertain": 72, + "caught": 4, + "uncertainRate": 0.11059907834101383, + "catchRate": 1 + }, + { + "z": 10, + "tested": 651, + "guardMismatch": 0, + "wrong": 10, + "uncertain": 225, + "caught": 10, + "uncertainRate": 0.3456221198156682, + "catchRate": 1 + }, + { + "z": 12, + "tested": 651, + "guardMismatch": 0, + "wrong": 51, + "uncertain": 594, + "caught": 51, + "uncertainRate": 0.9124423963133641, + "catchRate": 1 + } + ], + "interpretation": "At riskLimit=1e13 the derivative guard catches every observed DS classification error at z6/z8/z10 while sending about 3%/11%/35% of samples to Deep correction. At z12 it sends over 75%, which is a deliberate signal that Full Deep should replace the hybrid." +} \ No newline at end of file diff --git a/audit/v24-hybrid-ui-hotfix.json b/audit/v24-hybrid-ui-hotfix.json new file mode 100644 index 0000000..a77e1c6 --- /dev/null +++ b/audit/v24-hybrid-ui-hotfix.json @@ -0,0 +1,29 @@ +{ + "format": "mandelbrot-v24.2.5-hybrid-ui-hotfix-v1", + "createdAt": "2026-08-23T11:54:13Z", + "rootCause": "Toolbar Hybrid experiment button opened the dialog only; actual measurement required a second click on the dialog start button.", + "fix": [ + "Toolbar click opens the dialog and auto-starts on the next animation frame.", + "Dialog primary button is a rerun action.", + "Concurrent Backend benchmark state is surfaced explicitly instead of silently doing nothing.", + "Hybrid running state is surfaced explicitly.", + "Static contract tests assert toolbar auto-start and rerun wiring." + ], + "tests": { + "syntax": "pass", + "hybridContract": "pass", + "sourceContract": "pass", + "wgslReserved": "pass", + "cpuNumeric": "pass", + "sparseCorrection": "pass", + "build": "pass" + }, + "realWebGPU": "not-run-in-container; UI execution contract fixed independently of GPU availability", + "sha256": { + "script.js": "b02a47d6b6b8ea7c95e85ee26e69f877ef5c8c9cffb34e68ca37121c2fa652ac", + "gpu-kernels.js": "6e00107df84dd6e5427774ffca2cfdf6c7bfae0989fd69054b11f500a97e048e", + "index.html": "d12a742455fa2febf3639beddad063a3217901923b65927aeb1c21b224c00f3a", + "package.json": "93662efdf99f04b4bb2497f6f9512ad3c617fef7209843469f8a50ed5d6a6786", + "tests/v24-hybrid-contract.mjs": "18cb1e4b5fd9656285b021de6bea02ca767c7531cb9ee57b3cde3c38f3e887c0" + } +} diff --git a/audit/v24-real-webgpu-status.json b/audit/v24-real-webgpu-status.json index 52bee48..d4b8891 100644 --- a/audit/v24-real-webgpu-status.json +++ b/audit/v24-real-webgpu-status.json @@ -1,24 +1,43 @@ { - "status": "v24.1.3-real-webgpu-rerun-pending", - "gate": "real-webgpu-compile-dispatch-numeric-acceptance", - "environment": { - "browser": "Chromium 144.0.7559.96 Debian GNU/Linux 13", - "display": "container/Xvfb", - "navigatorGpuPreviouslyObserved": false, - "latestAttempt": "GPU command-buffer/context initialization failed before acceptance completed", - "adapterAccepted": false - }, - "attemptedModes": [ - "headless Chromium default", - "--enable-unsafe-webgpu", - "--enable-unsafe-webgpu --use-angle=swiftshader", - "Xvfb + unsafe WebGPU" + "status": "v24.2.8-fixed96-sparse-queue-real-webgpu-pending", + "reason": "container browser does not expose a usable WebGPU adapter / GPU process initialization fails", + "requiredExternalRuns": [ + "tests/webgpu-acceptance.html", + "Diagnostics > Fixed96 Direct実験" ], - "interpretation": "The reported real-browser v24.1.2 parse failure is fixed in source in v24.1.3. This container still cannot obtain a real WebGPU adapter, so v24.1.3 compile/dispatch must be rerun on a WebGPU-capable browser/device.", - "externalObservation": { - "version": "24.1.2", - "result": "shader-parse-failed", - "error": "WGSL reserved identifiers meta and smooth in direct shader", - "hotfix": "renamed to fieldMeta/fieldSmooth; reserved-word static gate added" - } + "experimentDepths": [ + 6, + 8, + 10, + 12 + ], + "riskLimits": [ + 100000000000.0, + 1000000000000.0, + 10000000000000.0, + 100000000000000.0 + ], + "outputColumns": [ + "z", + "risk", + "fixed96 est", + "deep cold", + "speedup", + "correction", + "speed q", + "dispatch", + "remain", + "Δclass", + "queue", + "status" + ], + "queueAcceptance": [ + "selected == enqueued == dispatchCount == processed", + "overflow == 0", + "indirectWorkgroups == ceil(enqueued/64)", + "invalidIndex == 0", + "staleEntry == 0", + "corrected + remain == processed" + ], + "cpuQualityReport": "audit/v24-fixed96-model.json" } diff --git a/audit/v24-release-manifest.json b/audit/v24-release-manifest.json index 7a52b74..1d78837 100644 --- a/audit/v24-release-manifest.json +++ b/audit/v24-release-manifest.json @@ -1,27 +1,42 @@ { - "format": "mandelbrot-v24.1.3-release-manifest", - "generatedAt": "2026-08-23T16:17:33+09:00", + "format": "mandelbrot-v24.2.2-crossover-release-manifest", + "generatedAt": "2026-08-23T10:22:38.264933+00:00", "files": [ { "path": "AUDIT_V24.md", - "bytes": 4405, - "sha256": "ef09e435b24a64bdbf0872e9f182c31ab2edea5afd0c959a5681d2b9be87ddbf" + "bytes": 3906, + "sha256": "0ac5444e8a798e183312cf4a9c4dbe08133bccf471feac13d5e6ed06c91e5133" + }, + { + "path": "CROSSOVER_BENCHMARK.md", + "bytes": 3766, + "sha256": "5f72a5a73cc73b2308e14b4a100bfd546f7067433e24db8b2b33b809f535c629" + }, + { + "path": "IMPLEMENTATION_REPORT.md", + "bytes": 2869, + "sha256": "983e5c8b217b957612f7bbabdda04c7df8da16ba28941c16a4a4b5f8e85e7cc5" }, { "path": "MIGRATION_V24.md", - "bytes": 5185, - "sha256": "2a4fe1754c521901b0e1f2c99d46426055fd3dc2932da93df12ea01a15469839" + "bytes": 4710, + "sha256": "b4d55a30f38ba4c56e0aace0e69f00714878f5098f88d5c9a4f9e76546b24c16" }, { "path": "README.md", - "bytes": 5532, - "sha256": "5d3e7db74400db1fb0585f23b966d1eab08f3729fe5f253ef533060f65f1790d" + "bytes": 6040, + "sha256": "c651c78374c5288cd2d5739d9cd166d38840db6069d0b80b262888d333ce5c24" }, { "path": "audit/legacy-v23/final-browser-benchmark.json", "bytes": 2686, "sha256": "bca2e141c101722f8a077d43346c2c28b5d8d7c99e70415730031065c8ab5a1c" }, + { + "path": "audit/v24-backend-policy.json", + "bytes": 205, + "sha256": "d34d894ab9d062439e7cfe5074e47ab295defa74044ed3e2619ef0289759b2fe" + }, { "path": "audit/v24-bla-status.json", "bytes": 177, @@ -29,23 +44,38 @@ }, { "path": "audit/v24-cpu-numeric-model.json", - "bytes": 5391, - "sha256": "f25e830d831f828b84f327670b5be5e9afcf6118164b2d217a9eae24443f84bf" + "bytes": 6217, + "sha256": "8c8f7320ae29f367d4eea111eeb658a861dd0e001e5fc2653857c24b3baf71c1" + }, + { + "path": "audit/v24-crossover-gpu-status.json", + "bytes": 441, + "sha256": "dbd94c4b034c2ae521c5f591dfd745c40c9cf9d17bc08dbc00f4017216949f90" + }, + { + "path": "audit/v24-crossover-quality.json", + "bytes": 16247, + "sha256": "063deef5079d077d35d5cf349186405edcc0f17c5812f38051735a455de3ea20" }, { "path": "audit/v24-real-webgpu-status.json", - "bytes": 1066, - "sha256": "9791925d9ddb890644b895d4cd659d7377d7c48dd94df86495fef04ee1325c8a" + "bytes": 601, + "sha256": "ec5171b4f059f4f810b64db1ba82d83979571001f17f564919322c69e59ba721" }, { "path": "audit/v24-release-verification.json", - "bytes": 290, - "sha256": "6cd0dd2b293bae482e8cbd511caa50aebe34597ee2c12b442de75fa7a937dccd" + "bytes": 944, + "sha256": "b54797dfdc9e99c3e981ac39b45ef92281af5da5bb89d0ef21b837b41e4c4463" + }, + { + "path": "audit/v24-sparse-correction-model.json", + "bytes": 600, + "sha256": "0202491ac101543055d9a70c11ab3ed7bf7d253617875271c9d88c7ccd1f2acb" }, { "path": "audit/v24-test-summary.json", - "bytes": 3373, - "sha256": "260173d15640a9681ae049f3e1c2707c774cf2af7a4fc4d6475115e7be344e8c" + "bytes": 1665, + "sha256": "b7afe3205c814318d2d7646bbb235b2e4c8411ecedf746e72924cb20cfcc562b" }, { "path": "dist/hosted/_headers", @@ -54,53 +84,53 @@ }, { "path": "dist/hosted/gpu-kernels.js", - "bytes": 12196, - "sha256": "aab457adce3cf1bf52eca6416c7c9570f4feb3579580a8cc95ae2425f8aea68a" + "bytes": 22425, + "sha256": "51fec9480aef5c463c7b6053d156808f8033f22dd181e89329836580afc4d329" }, { "path": "dist/hosted/index.html", - "bytes": 11416, - "sha256": "adf0dc65d2cfbb6ca56494651d40fc6874aedcd088f4170650d97fdde9c0e5dc" + "bytes": 13092, + "sha256": "8f954af7fe06b611e10fecdd67fc83db0fc6b4d291baa2d2e74693dbb7c85ca6" }, { "path": "dist/hosted/script.js", - "bytes": 58865, - "sha256": "eca75aaba2e859ab73d79a46a76329ffb41c438e413bf24f7a4b21328f8f0e61" + "bytes": 85737, + "sha256": "d730983d488805268605176353049d34052026621a7473ada8603db2a0cc05b2" }, { "path": "dist/standalone/gpu-kernels.js", - "bytes": 12196, - "sha256": "aab457adce3cf1bf52eca6416c7c9570f4feb3579580a8cc95ae2425f8aea68a" + "bytes": 22425, + "sha256": "51fec9480aef5c463c7b6053d156808f8033f22dd181e89329836580afc4d329" }, { "path": "dist/standalone/index.html", - "bytes": 11416, - "sha256": "adf0dc65d2cfbb6ca56494651d40fc6874aedcd088f4170650d97fdde9c0e5dc" + "bytes": 13092, + "sha256": "8f954af7fe06b611e10fecdd67fc83db0fc6b4d291baa2d2e74693dbb7c85ca6" }, { "path": "dist/standalone/script.js", - "bytes": 58865, - "sha256": "eca75aaba2e859ab73d79a46a76329ffb41c438e413bf24f7a4b21328f8f0e61" + "bytes": 85737, + "sha256": "d730983d488805268605176353049d34052026621a7473ada8603db2a0cc05b2" }, { "path": "gpu-kernels.js", - "bytes": 12196, - "sha256": "aab457adce3cf1bf52eca6416c7c9570f4feb3579580a8cc95ae2425f8aea68a" + "bytes": 22425, + "sha256": "51fec9480aef5c463c7b6053d156808f8033f22dd181e89329836580afc4d329" }, { "path": "index.html", - "bytes": 11416, - "sha256": "adf0dc65d2cfbb6ca56494651d40fc6874aedcd088f4170650d97fdde9c0e5dc" + "bytes": 13092, + "sha256": "8f954af7fe06b611e10fecdd67fc83db0fc6b4d291baa2d2e74693dbb7c85ca6" }, { "path": "package.json", "bytes": 199, - "sha256": "0fa1b8155f09356fba73e94c8db6428f1367aefef40efd116ad89c89a8a8a13f" + "sha256": "7c8ccf08afdaef9dc91c9041a5f9b9e1d730a1a9ba237f11d46ec293dc9690ab" }, { "path": "script.js", - "bytes": 58865, - "sha256": "eca75aaba2e859ab73d79a46a76329ffb41c438e413bf24f7a4b21328f8f0e61" + "bytes": 85737, + "sha256": "d730983d488805268605176353049d34052026621a7473ada8603db2a0cc05b2" }, { "path": "scripts/build.mjs", @@ -109,8 +139,8 @@ }, { "path": "scripts/test-all.mjs", - "bytes": 818, - "sha256": "1fbebd942d5c3ee94a1cbb449a29168c50b0bc8d3285cbae1c7c01eb188b29ec" + "bytes": 934, + "sha256": "c3ec078b2591cba429eef90cc4f58a7c55e97756c083ed7b2d9437342ad450b4" }, { "path": "tests/legacy-v23/numeric-policy-v23.json", @@ -124,8 +154,13 @@ }, { "path": "tests/v24-acceptance-contract.mjs", - "bytes": 818, - "sha256": "4bc444c694934d8bfd4a56ab597ab9fe834867ed6bc97a36c8182f99c25e39a6" + "bytes": 1035, + "sha256": "114d90b92f89b8b27f6ba35b904464908ec0e1e6393adb02ade8a8dfe5a7bbf0" + }, + { + "path": "tests/v24-backend-policy.mjs", + "bytes": 1192, + "sha256": "81cffe6276ed660210646067502ab9edcce7d0fc608048a98c06633760714b5a" }, { "path": "tests/v24-bla-model.mjs", @@ -139,8 +174,18 @@ }, { "path": "tests/v24-cpu-numeric-model.mjs", - "bytes": 8197, - "sha256": "8f24ccd80bf00b5b13e09a1b31bddf6df1c17dc8d7541e04ea1e7421165a2ce0" + "bytes": 8452, + "sha256": "2ee7a2ef16357db9b537da4534611ea59c80d7279980cf8957adf069093f7fd6" + }, + { + "path": "tests/v24-crossover-contract.mjs", + "bytes": 1460, + "sha256": "1b11958712e27308074123dea0376e067e4cb5dd29cf28a68d87bcf42b7e06ff" + }, + { + "path": "tests/v24-crossover-quality.mjs", + "bytes": 13215, + "sha256": "8ab9ca4da65aee229d0b5d6bdf4a068923a337ed75263514678d4b71a8273e41" }, { "path": "tests/v24-direct-model.mjs", @@ -150,12 +195,12 @@ { "path": "tests/v24-geometry-contract.mjs", "bytes": 1177, - "sha256": "9403a8d1efe6d0d5547bd7ebaa966578dea1ba7630f094fc117e0ae4fe395996" + "sha256": "2e5e6fb87f4b7130eff8fff72fd776ef8b0d0e70e91845136a3b18cbf6c86701" }, { "path": "tests/v24-index-contract.mjs", - "bytes": 461, - "sha256": "ffeeccba4b72613f061c7add2f8cdecc03a6c01e2e5a01d4f2a2cf53f94345ef" + "bytes": 504, + "sha256": "90f52bb91f12db47da1ebf39a6770cb58d5c4e2809f4d6aa2fdb1263d77cee57" }, { "path": "tests/v24-png-stream-model.mjs", @@ -169,8 +214,13 @@ }, { "path": "tests/v24-source-contract.mjs", - "bytes": 3805, - "sha256": "ce3e8dc4be17f19af362087772e7a17b59d7e8e007c5e19f00153899fe0a20af" + "bytes": 4239, + "sha256": "349c6d344f5f9325cb811cbb9f63017cb2d27139e7557be829f7c85f13bb39d4" + }, + { + "path": "tests/v24-sparse-correction-model.mjs", + "bytes": 9810, + "sha256": "eb72f0aa062bcf8bf915c048ba89e58a7ad5ab338ea559cfc467ba260255c241" }, { "path": "tests/v24-tree-contract.mjs", @@ -189,8 +239,8 @@ }, { "path": "tests/webgpu-acceptance.js", - "bytes": 6072, - "sha256": "c38fbeca176a464bfb6d4a0685f66469065ba3392a65f0617215a85e3fccbeb0" + "bytes": 6884, + "sha256": "e1ef5b21906b4da0fb7154f1e3e555c36267d8a5a7a1bd7ed0474eee147bfd13" } ] -} +} \ No newline at end of file diff --git a/audit/v24-release-verification.json b/audit/v24-release-verification.json index 751d71f..b6afccc 100644 --- a/audit/v24-release-verification.json +++ b/audit/v24-release-verification.json @@ -1,12 +1,25 @@ { - "status": "pass", - "version": "24.1.3", - "generatedAt": "2026-08-23T16:17:15+09:00", - "preZipCleanCopy": { - "npmTest": "pass", - "npmBuild": "pass" + "format": "mandelbrot-v24.2.2-crossover-release-verification", + "generatedAt": "2026-08-23T10:22:38.262457+00:00", + "version": "24.2.2", + "sourceHashes": { + "script.js": "d730983d488805268605176353049d34052026621a7473ada8603db2a0cc05b2", + "gpu-kernels.js": "51fec9480aef5c463c7b6053d156808f8033f22dd181e89329836580afc4d329", + "index.html": "8f954af7fe06b611e10fecdd67fc83db0fc6b4d291baa2d2e74693dbb7c85ca6" }, - "wgslReservedWords": "pass", - "sourceDistHashesMatch": true, - "realWebGpu": "pending: container has no usable adapter" -} + "standaloneMatchesSource": true, + "hostedMatchesSource": true, + "qualitySweep": "audit/v24-crossover-quality.json", + "gpuTiming": "pending external WebGPU adapter; use Diagnostics > Backend比較", + "gates": { + "wgslReserved": "pass", + "sourceContract": "pass", + "indexContract": "pass", + "crossoverContract": "pass", + "crossoverQuality": "pass", + "cpuNumericModel": "pass individually", + "sparseCorrectionModel": "pass individually", + "build": "pass", + "realWebGPU": "pending" + } +} \ No newline at end of file diff --git a/audit/v24-sparse-correction-model.json b/audit/v24-sparse-correction-model.json new file mode 100644 index 0000000..e9bc342 --- /dev/null +++ b/audit/v24-sparse-correction-model.json @@ -0,0 +1,26 @@ +{ + "status": "pass", + "kind": "sparse-double-single-correction-cpu-model-not-real-gpu", + "scene": "swirly-seahorses-z12", + "grid": [ + 61, + 39 + ], + "limit": 2000, + "referenceLength": 2000, + "guardMismatch": 0, + "primary": { + "unknown": 478, + "falseEscaped": 0, + "falseBounded": 0 + }, + "correction": { + "corrected": 478, + "remaining": 0, + "falseEscaped": 0, + "falseBounded": 0, + "escapeIterationMismatch": 5, + "maxEscapeIterationDifference": 374 + }, + "note": "DS correction is a visual-quality recovery pass; escape iteration equality is not certified." +} diff --git a/audit/v24-test-summary.json b/audit/v24-test-summary.json index 037fb19..c8396c1 100644 --- a/audit/v24-test-summary.json +++ b/audit/v24-test-summary.json @@ -1,94 +1,48 @@ { - "status": "v24.1.3-static-cpu-build-pass-real-webgpu-rerun-pending", + "status": "v24.2.2-static-cpu-build-pass-real-webgpu-timing-pending", "rendererVersion": 24, - "shaderVersion": "24.1.3", - "packageVersion": "24.1.3", + "shaderVersion": "24.2.2-benchmark", + "packageVersion": "24.2.2", + "generatedAt": "2026-08-23T10:22:11.466258+00:00", "production": { "legacyDeepAssets": 0, "bigIntPixelRenderer": false, - "referenceWorker": "BigInt fixed-point reference at viewBits+64 with viewBits+128 guard checkpoints", - "gpuDeep": "guarded rescaled f32 perturbation", - "productionBla": false, - "strictRetryPass": false, - "gpuFieldColorPresentation": true, - "recolorWithoutNumericRecompute": true, - "reusableExportWorkspace": true, - "streamingPng": true, - "gpuAaResolve2x2": true, - "maxExportSide": 16384, - "membershipCertified": false + "bla": false, + "multiReference": false, + "sparseDoubleSingleCorrection": true, + "viewSpecificBackendProbe": true, + "correctedDeepExport": true, + "experimentalWholeFrameDsDirect": "benchmark-only" }, - "nodeGates": { - "status": "pass", - "directFalseEscaped": 0, - "directFalseBounded": 0, - "deepFalseEscaped": 0, - "deepFalseBounded": 0, - "deepGuardMismatch": 0, - "swirlyUnknownBalanced": 71, - "swirlyKnownBalanced": 116, - "denseKnownCounterexamples": "all conservatively UNKNOWN", - "coordinateDepths": [ - "1e-400", - "1e-1000" + "crossoverQuality": { + "firstMaterialF32DegradationDepth": 6, + "firstMaterialDsDegradationDepth": 10, + "deepIssueRows": [ + { + "z": 8, + "falseEscaped": 1, + "falseBounded": 0, + "unknown": 0, + "escapeIterationMismatch": 2 + } ], - "pngStreamModel": "pass", - "acceptanceHarnessContract": "pass" + "interpretation": "On the Seahorse track at a 1600x900 viewport, f32 direct becomes materially unsuitable around z6; DS direct remains a plausible bridge through roughly z8 and degrades by z10. Corrected deep is the best of the modeled backends, but one z8 false escape means it is not a membership certificate." + }, + "sparseCorrection": { + "primaryUnknown": 478, + "corrected": 478, + "remaining": 0, + "falseEscaped": 0, + "falseBounded": 0 }, "build": { "status": "pass", - "standaloneFiles": [ - "index.html", - "gpu-kernels.js", - "script.js" - ], - "hostedFiles": [ - "index.html", - "gpu-kernels.js", - "script.js", - "_headers" - ] + "sourceDistHashMatch": true }, "realWebGPU": { - "status": "rerun-pending-for-v24.1.3", - "v24.1.2ObservedFailure": "reserved-word parse error from real WebGPU browser", - "v24.1.3SourceFix": "static reserved-word gate passes", - "containerAdapterAccepted": false + "status": "not-run-in-container", + "benchmark": "Diagnostics > Backend比較", + "hardwareTimingRequired": true }, - "generatedAt": "2026-08-23T16:17:15+09:00", - "hotfix": { - "wgslReservedWords": "pass: all 5 WGSL modules contain no WGSL 16.2 reserved-word tokens", - "renamedIdentifiers": [ - "meta -> fieldMeta", - "smooth -> fieldSmooth" - ], - "shaderFailureFallback": "CPU full-frame fallback disabled for shader/pipeline failures", - "screenPixelBudget": { - "power": 524288, - "standardDesktop": 1572864, - "standardSmall": 786432, - "fineDesktop": 3145728, - "strictDesktop": 2097152, - "noWebGpuFallback": 262144 - }, - "errorBadge": "compiler/runtime errors truncated to 120 chars in status UI; full diagnostics retained" - }, - "hashes": { - "index.html": "adf0dc65d2cfbb6ca56494651d40fc6874aedcd088f4170650d97fdde9c0e5dc", - "script.js": "eca75aaba2e859ab73d79a46a76329ffb41c438e413bf24f7a4b21328f8f0e61", - "gpu-kernels.js": "aab457adce3cf1bf52eca6416c7c9570f4feb3579580a8cc95ae2425f8aea68a", - "dist/standalone/index.html": "adf0dc65d2cfbb6ca56494651d40fc6874aedcd088f4170650d97fdde9c0e5dc", - "dist/standalone/script.js": "eca75aaba2e859ab73d79a46a76329ffb41c438e413bf24f7a4b21328f8f0e61", - "dist/standalone/gpu-kernels.js": "aab457adce3cf1bf52eca6416c7c9570f4feb3579580a8cc95ae2425f8aea68a", - "dist/hosted/index.html": "adf0dc65d2cfbb6ca56494651d40fc6874aedcd088f4170650d97fdde9c0e5dc", - "dist/hosted/script.js": "eca75aaba2e859ab73d79a46a76329ffb41c438e413bf24f7a4b21328f8f0e61", - "dist/hosted/gpu-kernels.js": "aab457adce3cf1bf52eca6416c7c9570f4feb3579580a8cc95ae2425f8aea68a" - }, - "cleanCopyVerification": { - "directory": "v24_hotfix_verify_pre", - "npmTest": "pass", - "npmBuild": "pass", - "sourceHashesMatch": true, - "sourceDistHashesMatch": true - } -} + "note": "Heavy CPU model gates were also run individually. The aggregate npm test wrapper can exceed the execution harness time budget even though its constituent gates pass." +} \ No newline at end of file diff --git a/dist/hosted/gpu-kernels-v24.2.6.js b/dist/hosted/gpu-kernels-v24.2.6.js new file mode 100644 index 0000000..ddc62b8 --- /dev/null +++ b/dist/hosted/gpu-kernels-v24.2.6.js @@ -0,0 +1,598 @@ +(()=>{'use strict'; +const COMMON=String.raw` +const FIELD_UNKNOWN:u32=0u; +const FIELD_ESCAPED:u32=1u; +const FIELD_INTERIOR_LIKELY:u32=2u; +const FIELD_INTERIOR_PROVEN:u32=3u; +const ITER_MASK:u32=0x000fffffu; +const REASON_SHIFT:u32=20u; +const REASON_MASK:u32=0x0ff00000u; +const REASON_NONE:u32=0u; +const REASON_ERROR_BOUND:u32=1u; +const REASON_ESCAPE_UNCERTAIN:u32=2u; +const REASON_REFERENCE_END:u32=3u; +const REASON_REBASE_GAP:u32=4u; +const REASON_RANGE:u32=5u; +const REASON_OPERATION_LIMIT:u32=6u; + +fn pack_meta(n:u32, cls:u32)->u32 { return (n & ITER_MASK) | ((cls & 3u) << 28u); } +fn pack_unknown(n:u32, reason:u32)->u32 { return (n & ITER_MASK) | ((reason & 0xffu) << REASON_SHIFT); } +fn cmul(a:vec2, b:vec2)->vec2{ + return vec2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); +} +fn maxabs(v:vec2)->f32 { return max(abs(v.x),abs(v.y)); } +const F32_U:f32=5.960464477539063e-8; +fn pow2_safe(e:i32)->f32 { + if(e < -126){ return 0.0; } + if(e > 126){ return 8.507059e37; } + return ldexp(1.0,e); +} +fn safe_abs_error(errScaled:f32, scaleExp:i32, z:vec2, delta:vec2)->f32{ + let propagated=abs(errScaled*pow2_safe(scaleExp)); + let reconstruction=1.0*F32_U*(maxabs(z)+maxabs(delta)+1.0e-30); + return propagated+reconstruction; +} +fn scaled_to_f32(v:vec2, e:i32)->vec2{ + if(e < -126){ return vec2(0.0); } + if(e > 126){ return vec2(8.507059e37); } + return ldexp(v,vec2(e)); +} +fn smooth_escape(n:u32, mag2:f32)->f32{ + let u=log2(max(4.0000005,mag2)); + return f32(n)+1.0-log2(max(1.0e-20,0.5*u)); +} +`; + +const DIRECT_F32_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, strict:u32, + centerRe:f32, centerIm:f32, span:f32, sampleX:f32, + sampleY:f32, _p0:f32, _p1:f32, _p2:f32, +}; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +fn analytic(cr:f32,ci:f32)->bool{ + let y2=ci*ci; let x=cr-0.25; let q=x*x+y2; + let lhs=q*(q+x); let rhs=0.25*y2; + let margin=16.0*F32_U*(abs(lhs)+abs(rhs)+1.0); + if(lhs){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=gid.y*p.tileW+gid.x; + let gx=f32(p.tileX+gid.x)+p.sampleX; + let gy=f32(p.tileY+gid.y)+p.sampleY; + let scale=p.span/f32(p.fullW); + let cr=p.centerRe+(gx-0.5*f32(p.fullW))*scale; + let ci=p.centerIm+(0.5*f32(p.fullH)-gy)*scale; + if(analytic(cr,ci)){ + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_PROVEN); fieldSmooth[out]=0.0; return; + } + var zr=0.0; var zi=0.0; var n=0u; + loop{ + if(n>=p.maxIter){break;} + let zr2=zr*zr; let zi2=zi*zi; + zi=2.0*zr*zi+ci; zr=zr2-zi2+cr; n+=1u; + let mag=zr*zr+zi*zi; + if(mag>4.0){fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); fieldSmooth[out]=smooth_escape(n,mag); return;} + } + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY); fieldSmooth[out]=0.0; +} +`; + + +// Benchmark candidate: whole-frame double-single direct iteration. +// This intentionally has no CPU reference-build cost. It is not selected by +// the production router until the crossover benchmark justifies it. +const DIRECT_DS_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, _flags:u32, + centerReHi:f32, centerReLo:f32, centerImHi:f32, centerImLo:f32, + spanHi:f32, spanLo:f32, sampleX:f32, sampleY:f32, + invFullWHi:f32, invFullWLo:f32, _p0:f32, _p1:f32, + _p2:f32, _p3:f32, _p4:f32, _p5:f32, +}; +struct DS{ h:f32, l:f32 }; +struct CDS{ r:DS, i:DS }; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +fn ds_quick(a:f32,b:f32)->DS{ + let q=a+b; let e=b-(q-a); return DS(q,e); +} +fn ds_sum(a:f32,b:f32)->DS{ + let q=a+b; let bb=q-a; let e=(a-(q-bb))+(b-bb); return DS(q,e); +} +fn ds_prod(a:f32,b:f32)->DS{ + let q=a*b; + let ca=4097.0*a; let ah=ca-(ca-a); let al=a-ah; + let cb=4097.0*b; let bh=cb-(cb-b); let bl=b-bh; + var e=ah*bh-q; e=e+ah*bl; e=e+al*bh; e=e+al*bl; + return DS(q,e); +} +fn ds_add(a:DS,b:DS)->DS{ + let q=ds_sum(a.h,b.h); return ds_quick(q.h,q.l+(a.l+b.l)); +} +fn ds_neg(a:DS)->DS{return DS(-a.h,-a.l);} +fn ds_sub(a:DS,b:DS)->DS{return ds_add(a,ds_neg(b));} +fn ds_mul(a:DS,b:DS)->DS{ + let q=ds_prod(a.h,b.h); + var e=q.l+a.h*b.l; e=e+a.l*b.h; e=e+a.l*b.l; + return ds_quick(q.h,e); +} +fn ds_scale(a:DS,b:f32)->DS{ + let q=ds_prod(a.h,b); return ds_quick(q.h,q.l+a.l*b); +} +fn ds_cmp(a:DS,b:DS)->i32{ + if(a.hb.h){return 1;} + if(a.lb.l){return 1;} return 0; +} +fn ds_value(a:DS)->f32{return a.h+a.l;} +fn cds_add(a:CDS,b:CDS)->CDS{return CDS(ds_add(a.r,b.r),ds_add(a.i,b.i));} +fn cds_mul(a:CDS,b:CDS)->CDS{ + let rr=ds_sub(ds_mul(a.r,b.r),ds_mul(a.i,b.i)); + let ii=ds_add(ds_mul(a.r,b.i),ds_mul(a.i,b.r)); + return CDS(rr,ii); +} +fn cds_mag2(a:CDS)->DS{return ds_add(ds_mul(a.r,a.r),ds_mul(a.i,a.i));} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=gid.y*p.tileW+gid.x; + let invW=DS(p.invFullWHi,p.invFullWLo); + let span=DS(p.spanHi,p.spanLo); + let ox=f32(p.tileX+gid.x)+p.sampleX-0.5*f32(p.fullW); + let oy=0.5*f32(p.fullH)-(f32(p.tileY+gid.y)+p.sampleY); + let dx=ds_scale(invW,ox); let dy=ds_scale(invW,oy); + let cr=ds_add(DS(p.centerReHi,p.centerReLo),ds_mul(span,dx)); + let ci=ds_add(DS(p.centerImHi,p.centerImLo),ds_mul(span,dy)); + let c=CDS(cr,ci); + var z=CDS(DS(0.0,0.0),DS(0.0,0.0)); + var n=0u; + loop{ + if(n>=p.maxIter){break;} + z=cds_add(cds_mul(z,z),c); n+=1u; + let mag=cds_mag2(z); + if(ds_cmp(mag,DS(4.0,0.0))>0){ + fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); + fieldSmooth[out]=smooth_escape(n,max(4.0000005,ds_value(mag))); + return; + } + } + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY); fieldSmooth[out]=0.0; +} +`; + + + +// Diagnostic hybrid candidate: DS direct with a sensitivity guard. Pixels +// whose coordinate derivative grows beyond riskLimit are marked UNKNOWN and +// can be re-evaluated by the existing deep double-single correction pass. +// This shader is benchmark-only and is not selected by the production router. +const DIRECT_DS_GUARDED_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, _flags:u32, + centerReHi:f32, centerReLo:f32, centerImHi:f32, centerImLo:f32, + spanHi:f32, spanLo:f32, sampleX:f32, sampleY:f32, + invFullWHi:f32, invFullWLo:f32, riskLimit:f32, _p1:f32, + _p2:f32, _p3:f32, _p4:f32, _p5:f32, +}; +struct DS{ h:f32, l:f32 }; +struct CDS{ r:DS, i:DS }; +struct HybridStats{ + remaining:atomic, _a:atomic, _b:atomic, _c:atomic, + _d:atomic, _e:atomic, _f:atomic, corrected:atomic, +}; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +@group(0) @binding(3) var hybridStats:HybridStats; +fn ds_quick(a:f32,b:f32)->DS{let q=a+b;let e=b-(q-a);return DS(q,e);} +fn ds_sum(a:f32,b:f32)->DS{let q=a+b;let bb=q-a;let e=(a-(q-bb))+(b-bb);return DS(q,e);} +fn ds_prod(a:f32,b:f32)->DS{ + let q=a*b;let ca=4097.0*a;let ah=ca-(ca-a);let al=a-ah; + let cb=4097.0*b;let bh=cb-(cb-b);let bl=b-bh; + var e=ah*bh-q;e=e+ah*bl;e=e+al*bh;e=e+al*bl;return DS(q,e); +} +fn ds_add(a:DS,b:DS)->DS{let q=ds_sum(a.h,b.h);return ds_quick(q.h,q.l+(a.l+b.l));} +fn ds_neg(a:DS)->DS{return DS(-a.h,-a.l);} +fn ds_sub(a:DS,b:DS)->DS{return ds_add(a,ds_neg(b));} +fn ds_mul(a:DS,b:DS)->DS{ + let q=ds_prod(a.h,b.h);var e=q.l+a.h*b.l;e=e+a.l*b.h;e=e+a.l*b.l;return ds_quick(q.h,e); +} +fn ds_scale(a:DS,b:f32)->DS{let q=ds_prod(a.h,b);return ds_quick(q.h,q.l+a.l*b);} +fn ds_cmp(a:DS,b:DS)->i32{if(a.hb.h){return 1;}if(a.lb.l){return 1;}return 0;} +fn ds_value(a:DS)->f32{return a.h+a.l;} +fn cds_add(a:CDS,b:CDS)->CDS{return CDS(ds_add(a.r,b.r),ds_add(a.i,b.i));} +fn cds_mul(a:CDS,b:CDS)->CDS{ + let rr=ds_sub(ds_mul(a.r,b.r),ds_mul(a.i,b.i)); + let ii=ds_add(ds_mul(a.r,b.i),ds_mul(a.i,b.r));return CDS(rr,ii); +} +fn cds_mag2(a:CDS)->DS{return ds_add(ds_mul(a.r,a.r),ds_mul(a.i,a.i));} +fn mark_sensitive(out:u32,n:u32){ + fieldMeta[out]=pack_unknown(n,7u);fieldSmooth[out]=0.0;atomicAdd(&hybridStats.remaining,1u); +} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=gid.y*p.tileW+gid.x; + let invW=DS(p.invFullWHi,p.invFullWLo);let span=DS(p.spanHi,p.spanLo); + let ox=f32(p.tileX+gid.x)+p.sampleX-0.5*f32(p.fullW); + let oy=0.5*f32(p.fullH)-(f32(p.tileY+gid.y)+p.sampleY); + let dx=ds_scale(invW,ox);let dy=ds_scale(invW,oy); + let cr=ds_add(DS(p.centerReHi,p.centerReLo),ds_mul(span,dx)); + let ci=ds_add(DS(p.centerImHi,p.centerImLo),ds_mul(span,dy)); + let c=CDS(cr,ci);var z=CDS(DS(0.0,0.0),DS(0.0,0.0)); + var deriv=vec2(0.0);var n=0u; + loop{ + if(n>=p.maxIter){break;} + let zv=vec2(ds_value(z.r),ds_value(z.i)); + deriv=2.0*cmul(zv,deriv)+vec2(1.0,0.0); + if(length(deriv)>p.riskLimit || any(deriv!=deriv)){mark_sensitive(out,n);return;} + z=cds_add(cds_mul(z,z),c);n+=1u; + let mag=cds_mag2(z); + if(ds_cmp(mag,DS(4.0,0.0))>0){ + fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); + fieldSmooth[out]=smooth_escape(n,max(4.0000005,ds_value(mag)));return; + } + } + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY);fieldSmooth[out]=0.0; +} +`; + +// Deep path: high-precision CPU reference + guarded rescaled f32 perturbation. +const DEEP_PERTURB_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, refLen:u32, + strict:u32, unknownOnly:u32, outputStride:u32, outputBase:u32, + spanMantHi:f32, spanExp:i32, sampleX:f32, sampleY:f32, + refPixelX:f32, refPixelY:f32, spanMantLo:f32, invFullWHi:f32, + invFullWLo:f32, _numeric0:f32, _numeric1:f32, _numeric2:f32, +}; +struct RefPoint{ hi:vec2, lo:vec2 }; +struct UnresolvedHead{ + remaining:atomic, errorBound:atomic, escapeUncertain:atomic, referenceEnd:atomic, + rebaseGap:atomic, rangeFailure:atomic, operationLimit:atomic, corrected:atomic, +}; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var refs:array; +@group(0) @binding(2) var fieldMeta:array; +@group(0) @binding(3) var fieldSmooth:array; +@group(0) @binding(4) var unresolved:UnresolvedHead; + +fn mark_unresolved(out:u32,n:u32,reason:u32){ + fieldMeta[out]=pack_unknown(n,reason); fieldSmooth[out]=0.0; + atomicAdd(&unresolved.remaining,1u); + if(reason==REASON_ERROR_BOUND){atomicAdd(&unresolved.errorBound,1u);} + else if(reason==REASON_ESCAPE_UNCERTAIN){atomicAdd(&unresolved.escapeUncertain,1u);} + else if(reason==REASON_REFERENCE_END){atomicAdd(&unresolved.referenceEnd,1u);} + else if(reason==REASON_REBASE_GAP){atomicAdd(&unresolved.rebaseGap,1u);} + else if(reason==REASON_RANGE){atomicAdd(&unresolved.rangeFailure,1u);} + else if(reason==REASON_OPERATION_LIMIT){atomicAdd(&unresolved.operationLimit,1u);} +} +fn render_pixel(out:u32,gx:f32,gy:f32,strictMode:bool){ + let dx=(gx-p.refPixelX)/f32(p.fullW); + let dy=(p.refPixelY-gy)/f32(p.fullW); + // dc = d * 2^scaleExp. Keep d and w in one shared scale. + var d=vec2(p.spanMantHi*dx,p.spanMantHi*dy); + var w=vec2(0.0); + var scaleExp=p.spanExp; + var n=0u; var m=0u; var operations=0u; + var errScaled=1.0*F32_U*maxabs(d); + loop{ + if(n>=p.maxIter){ + let rpEnd=refs[min(m,p.refLen)]; + let deltaEnd=scaled_to_f32(w,scaleExp); + let zEnd=rpEnd.hi+(rpEnd.lo+deltaEnd); + let errAbs=safe_abs_error(errScaled,scaleExp,zEnd,deltaEnd); + let limit=select(1.0e-3,1.0e-4,strictMode); + if(errAbs<=limit){fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY);fieldSmooth[out]=0.0;}else{mark_unresolved(out,n,REASON_ERROR_BOUND);} + return; + } + if(m>p.refLen){mark_unresolved(out,n,REASON_REFERENCE_END);return;} + let rp=refs[m]; + let delta=scaled_to_f32(w,scaleExp); + let z=rp.hi+(rp.lo+delta); + let mag=dot(z,z); + if(mag>4.0){ + let errAbs=safe_abs_error(errScaled,scaleExp,z,delta); + if(length(z)-errAbs>2.0){fieldMeta[out]=pack_meta(n,FIELD_ESCAPED);fieldSmooth[out]=smooth_escape(n,mag);return;} + mark_unresolved(out,n,REASON_ESCAPE_UNCERTAIN);return; + } + + // Rebase only when dc remains numerically representable in the new scale. + if(m>0u && dot(delta,delta)>0.0 && mag(p.spanMantHi*dx,p.spanMantHi*dy),p.spanExp); scaleExp=0; m=0u; + errScaled+=1.0*F32_U*maxabs(d); + continue; + } + + if(m>=p.refLen){mark_unresolved(out,n,REASON_REFERENCE_END);return;} + let r=refs[m]; + let refAbs=maxabs(r.hi)+maxabs(r.lo); + let wAbs=maxabs(w); let dAbs=maxabs(d); let p2=abs(pow2_safe(scaleExp)); + let gain=2.0*refAbs+2.0*wAbs*p2; + let roundErr=1.0*F32_U*(2.0*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1.0e-30); + errScaled=gain*errScaled+roundErr; + let linear=2.0*(cmul(r.hi,w)+cmul(r.lo,w)); + // delta^2 / 2^scaleExp = w^2 * 2^scaleExp + let sq=cmul(w,w)*pow2_safe(scaleExp); + w=linear+sq+d; m+=1u; n+=1u; operations+=1u; + if(maxabs(w)>=1.0e30 || maxabs(d)>=1.0e30){mark_unresolved(out,n,REASON_RANGE);return;} + let mm=max(maxabs(w),maxabs(d)); + if(mm>65536.0){ + w*=0.0000152587890625; d*=0.0000152587890625; errScaled*=0.0000152587890625; scaleExp+=16; + }else if(mm>0.0 && mm<0.0000152587890625 && scaleExp>p.spanExp){ + w*=65536.0; d*=65536.0; errScaled*=65536.0; scaleExp-=16; + } + if(scaleExp>126 || errScaled!=errScaled || errScaled>1.0e35){mark_unresolved(out,n,REASON_RANGE);return;} + if(operations>p.maxIter*2u+2048u){mark_unresolved(out,n,REASON_OPERATION_LIMIT);return;} + } +} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=p.outputBase+gid.y*p.outputStride+gid.x; + if(p.unknownOnly!=0u && ((fieldMeta[out]>>28u)&3u)!=FIELD_UNKNOWN){return;} + let gx=f32(p.tileX+gid.x)+p.sampleX; let gy=f32(p.tileY+gid.y)+p.sampleY; + render_pixel(out,gx,gy,p.strict!=0u); +} +`; + + +// Sparse correction: only UNKNOWN pixels are re-evaluated with double-single +// perturbation. It is a visual-quality pass, not a membership certificate. +const DEEP_CORRECT_WGSL=COMMON+String.raw` +const CORRECTION_MARK:u32=128u; +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, refLen:u32, + strict:u32, unknownOnly:u32, outputStride:u32, outputBase:u32, + spanMantHi:f32, spanExp:i32, sampleX:f32, sampleY:f32, + refPixelX:f32, refPixelY:f32, spanMantLo:f32, invFullWHi:f32, + invFullWLo:f32, _numeric0:f32, _numeric1:f32, _numeric2:f32, +}; +struct RefPoint{ hi:vec2, lo:vec2 }; +struct CorrectionStats{ + remaining:atomic, errorBound:atomic, escapeUncertain:atomic, referenceEnd:atomic, + rebaseGap:atomic, rangeFailure:atomic, operationLimit:atomic, corrected:atomic, +}; +struct DS{ h:f32, l:f32 }; +struct CDS{ r:DS, i:DS }; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var refs:array; +@group(0) @binding(2) var fieldMeta:array; +@group(0) @binding(3) var fieldSmooth:array; +@group(0) @binding(4) var stats:CorrectionStats; + +fn count_remaining(out:u32){ + let reason=(fieldMeta[out]>>REASON_SHIFT)&0xffu; + atomicAdd(&stats.remaining,1u); + if(reason==REASON_ERROR_BOUND){atomicAdd(&stats.errorBound,1u);} + else if(reason==REASON_ESCAPE_UNCERTAIN){atomicAdd(&stats.escapeUncertain,1u);} + else if(reason==REASON_REFERENCE_END){atomicAdd(&stats.referenceEnd,1u);} + else if(reason==REASON_REBASE_GAP){atomicAdd(&stats.rebaseGap,1u);} + else if(reason==REASON_RANGE){atomicAdd(&stats.rangeFailure,1u);} + else if(reason==REASON_OPERATION_LIMIT){atomicAdd(&stats.operationLimit,1u);} +} +fn accept_corrected(out:u32,n:u32,cls:u32,sm:f32){ + fieldMeta[out]=corrected(n,cls); fieldSmooth[out]=sm; atomicAdd(&stats.corrected,1u); +} + +fn ds_quick(a:f32,b:f32)->DS{ + let q=a+b; + let e=b-(q-a); + return DS(q,e); +} +fn ds_sum(a:f32,b:f32)->DS{ + let q=a+b; + let bb=q-a; + let e=(a-(q-bb))+(b-bb); + return DS(q,e); +} +fn ds_prod(a:f32,b:f32)->DS{ + let q=a*b; + let ca=4097.0*a; + let ah=ca-(ca-a); + let al=a-ah; + let cb=4097.0*b; + let bh=cb-(cb-b); + let bl=b-bh; + var e=ah*bh-q; + e=e+ah*bl; + e=e+al*bh; + e=e+al*bl; + return DS(q,e); +} +fn ds_add(a:DS,b:DS)->DS{ + let q=ds_sum(a.h,b.h); + return ds_quick(q.h,q.l+(a.l+b.l)); +} +fn ds_neg(a:DS)->DS{return DS(-a.h,-a.l);} +fn ds_sub(a:DS,b:DS)->DS{return ds_add(a,ds_neg(b));} +fn ds_mul(a:DS,b:DS)->DS{ + let q=ds_prod(a.h,b.h); + var e=q.l+a.h*b.l; + e=e+a.l*b.h; + e=e+a.l*b.l; + return ds_quick(q.h,e); +} +fn ds_scale(a:DS,b:f32)->DS{ + let q=ds_prod(a.h,b); + return ds_quick(q.h,q.l+a.l*b); +} +fn ds_pow2(a:DS,e:i32)->DS{ + if(e < -126){return DS(0.0,0.0);} + if(e > 126){return DS(8.507059e37,0.0);} + return DS(ldexp(a.h,e),ldexp(a.l,e)); +} +fn ds_cmp(a:DS,b:DS)->i32{ + if(a.hb.h){return 1;} + if(a.lb.l){return 1;} return 0; +} +fn ds_value(a:DS)->f32{return a.h+a.l;} +fn cds_add(a:CDS,b:CDS)->CDS{return CDS(ds_add(a.r,b.r),ds_add(a.i,b.i));} +fn cds_mul(a:CDS,b:CDS)->CDS{ + let rr=ds_sub(ds_mul(a.r,b.r),ds_mul(a.i,b.i)); + let ii=ds_add(ds_mul(a.r,b.i),ds_mul(a.i,b.r)); + return CDS(rr,ii); +} +fn cds_scale(a:CDS,b:f32)->CDS{return CDS(ds_scale(a.r,b),ds_scale(a.i,b));} +fn cds_pow2(a:CDS,e:i32)->CDS{return CDS(ds_pow2(a.r,e),ds_pow2(a.i,e));} +fn cds_mag2(a:CDS)->DS{return ds_add(ds_mul(a.r,a.r),ds_mul(a.i,a.i));} +fn cds_maxabs(a:CDS)->f32{return max(abs(ds_value(a.r)),abs(ds_value(a.i)));} +fn corrected(n:u32,cls:u32)->u32{return pack_meta(n,cls)|(CORRECTION_MARK<=p.maxIter){accept_corrected(out,p.maxIter,FIELD_INTERIOR_LIKELY,0.0);return;} + if(m>p.refLen){count_remaining(out);return;} + let rp=refs[m]; + let r=CDS(DS(rp.hi.x,rp.lo.x),DS(rp.hi.y,rp.lo.y)); + let delta=cds_pow2(w,scaleExp); + let z=cds_add(r,delta); + let mag=cds_mag2(z); + if(ds_cmp(mag,DS(4.0,0.0))>0){ + accept_corrected(out,n,FIELD_ESCAPED,smooth_escape(n,max(4.0000005,ds_value(mag)))); + return; + } + let dmag=cds_mag2(delta); + if(m>0u && ds_cmp(dmag,DS(0.0,0.0))>0 && ds_cmp(mag,dmag)<0){ + if(p.spanExp-scaleExp < -96){count_remaining(out);return;} + w=z; d=cds_pow2(d0,p.spanExp); scaleExp=0; m=0u; + continue; + } + if(m>=p.refLen){count_remaining(out);return;} + let linear=cds_scale(cds_mul(r,w),2.0); + let sq=cds_pow2(cds_mul(w,w),scaleExp); + w=cds_add(cds_add(linear,sq),d); + m+=1u; n+=1u; operations+=1u; + let mm=max(cds_maxabs(w),cds_maxabs(d)); + if(mm>=1.0e30 || mm!=mm){count_remaining(out);return;} + if(mm>65536.0){w=cds_scale(w,0.0000152587890625);d=cds_scale(d,0.0000152587890625);scaleExp+=16;} + else if(mm>0.0 && mm<0.0000152587890625 && scaleExp>p.spanExp){w=cds_scale(w,65536.0);d=cds_scale(d,65536.0);scaleExp-=16;} + if(scaleExp>126 || operations>p.maxIter*2u+2048u){count_remaining(out);return;} + } +} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=p.outputBase+gid.y*p.outputStride+gid.x; + if(((fieldMeta[out]>>28u)&3u)!=FIELD_UNKNOWN){return;} + let gx=f32(p.tileX+gid.x)+p.sampleX; + let gy=f32(p.tileY+gid.y)+p.sampleY; + correct_pixel(out,gx,gy); +} +`; + +const COLOR_WGSL=String.raw` +struct Params{ + width:u32,height:u32,palette:u32,edgeAA:u32, + cycle:f32,shift:f32,_p0:f32,_p1:f32, +}; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +@group(0) @binding(3) var outTex:texture_storage_2d; +fn hsv(h:f32,s:f32,v:f32)->vec3{ + let x=fract(h)*6.0; let i=i32(floor(x)); let f=x-floor(x); let pp=v*(1.0-s); let q=v*(1.0-s*f); let t=v*(1.0-s*(1.0-f)); + if(i==0){return vec3(v,t,pp);} if(i==1){return vec3(q,v,pp);} if(i==2){return vec3(pp,v,t);} if(i==3){return vec3(pp,q,v);} if(i==4){return vec3(t,pp,v);} return vec3(v,pp,q); +} +fn current_palette(t0:f32)->vec3{ + let t=select(2.0-2.0*t0,2.0*t0,t0<=0.5); + if(t<0.11){return mix(vec3(4,10,27),vec3(12,53,79),smoothstep(0.0,0.11,t))/255.0;} + if(t<0.25){return mix(vec3(12,53,79),vec3(31,156,184),smoothstep(0.11,0.25,t))/255.0;} + if(t<0.38){return mix(vec3(31,156,184),vec3(91,226,234),smoothstep(0.25,0.38,t))/255.0;} + if(t<0.50){return mix(vec3(91,226,234),vec3(66,53,151),smoothstep(0.38,0.50,t))/255.0;} + if(t<0.62){return mix(vec3(66,53,151),vec3(139,49,170),smoothstep(0.50,0.62,t))/255.0;} + if(t<0.73){return mix(vec3(139,49,170),vec3(232,72,145),smoothstep(0.62,0.73,t))/255.0;} + if(t<0.84){return mix(vec3(232,72,145),vec3(255,137,64),smoothstep(0.73,0.84,t))/255.0;} + if(t<0.93){return mix(vec3(255,137,64),vec3(255,211,99),smoothstep(0.84,0.93,t))/255.0;} + return mix(vec3(255,211,99),vec3(255,250,223),smoothstep(0.93,1.0,t))/255.0; +} +fn base_color(i:u32)->vec3{ + let m=fieldMeta[i]; let cls=(m>>28u)&3u; + if(cls==0u){return vec3(43,20,58)/255.0;} if(cls!=1u){return vec3(0.0);} + let sm=fieldSmooth[i]; let phase=fract(p.shift+sm*p.cycle); var c=vec3(0.0); + if(p.palette==1u){c=hsv(phase,0.92,1.0);}else if(p.palette==2u){let g=(22.0+233.0*(0.5-0.5*cos(6.283185307*phase)))/255.0;c=vec3(g);}else{c=current_palette(phase);} + let n=f32(m&0x000fffffu); let edge=clamp(log(1.0+n)/log(1.0+max(8.0,n+32.0)),0.0,1.0); let mixv=0.34+0.66*pow(edge,0.38); + let floorc=select(vec3(2,5,15)/255.0,vec3(8.0/255.0),p.palette==2u); return mix(floorc,c,mixv); +} +fn linearize(c:vec3)->vec3{return pow(c,vec3(2.2));} +fn delinearize(c:vec3)->vec3{return pow(max(c,vec3(0.0)),vec3(1.0/2.2));} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.width||gid.y>=p.height){return;} let i=gid.y*p.width+gid.x; var c=base_color(i); + if(p.edgeAA!=0u){ + let m=fieldMeta[i]; let cls=(m>>28u)&3u; var boundary=false; var sum=linearize(c); var cnt=1.0; + let x=i32(gid.x); let y=i32(gid.y); + for(var oy=-1;oy<=1;oy+=1){for(var ox=-1;ox<=1;ox+=1){if(ox==0&&oy==0){continue;} let xx=x+ox;let yy=y+oy;if(xx<0||yy<0||xx>=i32(p.width)||yy>=i32(p.height)){continue;}let j=u32(yy)*p.width+u32(xx);let mj=fieldMeta[j];let cj=(mj>>28u)&3u;if(cj!=cls||abs(i32(mj&0x000fffffu)-i32(m&0x000fffffu))>2){boundary=true;}sum+=linearize(base_color(j));cnt+=1.0;}} + if(boundary){c=delinearize(sum/cnt);} + } + textureStore(outTex,vec2(gid.xy),vec4(c,1.0)); +} +`; + + +const AA_RESOLVE_WGSL=String.raw` +@group(0) @binding(0) var a:texture_2d; +@group(0) @binding(1) var b:texture_2d; +@group(0) @binding(2) var c:texture_2d; +@group(0) @binding(3) var d:texture_2d; +@group(0) @binding(4) var outTex:texture_storage_2d; +fn to_linear(x:f32)->f32{return select(x/12.92,pow((x+0.055)/1.055,2.4),x>0.04045);} +fn to_srgb(x0:f32)->f32{let x=clamp(x0,0.0,1.0);return select(12.92*x,1.055*pow(x,1.0/2.4)-0.055,x>0.0031308);} +fn lin3(v:vec3)->vec3{return vec3(to_linear(v.x),to_linear(v.y),to_linear(v.z));} +fn srgb3(v:vec3)->vec3{return vec3(to_srgb(v.x),to_srgb(v.y),to_srgb(v.z));} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + let size=textureDimensions(a); if(gid.x>=size.x||gid.y>=size.y){return;} + let q=vec2(gid.xy); + let sum=lin3(textureLoad(a,q,0).rgb)+lin3(textureLoad(b,q,0).rgb)+lin3(textureLoad(c,q,0).rgb)+lin3(textureLoad(d,q,0).rgb); + textureStore(outTex,q,vec4(srgb3(sum*0.25),1.0)); +} +`; + +const PRESENT_WGSL=String.raw` +struct Params{scaleX:f32,scaleY:f32,offsetX:f32,offsetY:f32}; +@group(0) @binding(0) var samp:sampler; +@group(0) @binding(1) var tex:texture_2d; +@group(0) @binding(2) var p:Params; +struct VSOut{@builtin(position) pos:vec4,@location(0) uv:vec2}; +@vertex fn vs(@builtin(vertex_index) i:u32)->VSOut{ + var pos=array,3>(vec2(-1.0,-1.0),vec2(3.0,-1.0),vec2(-1.0,3.0)); + var uv=array,3>(vec2(0.0,1.0),vec2(2.0,1.0),vec2(0.0,-1.0)); + var o:VSOut;o.pos=vec4(pos[i],0.0,1.0);o.uv=uv[i];return o; +} +@fragment fn fs(in:VSOut)->@location(0) vec4{ + let uv=vec2(0.5)+(in.uv-vec2(0.5))*vec2(p.scaleX,p.scaleY)+vec2(p.offsetX,p.offsetY); + if(any(uv(0.0))||any(uv>vec2(1.0))){return vec4(0.0196,0.0314,0.0745,1.0);} return textureSampleLevel(tex,samp,uv,0.0); +} +`; + +globalThis.MANDEL_WEBGPU_KERNELS=Object.freeze({ + version:'24.2.6-hybrid-ui-cacheproof',DIRECT_F32_WGSL,DIRECT_DS_WGSL,DIRECT_DS_GUARDED_WGSL,DEEP_PERTURB_WGSL,DEEP_CORRECT_WGSL,COLOR_WGSL,AA_RESOLVE_WGSL,PRESENT_WGSL +}); +})(); diff --git a/dist/hosted/gpu-kernels.js b/dist/hosted/gpu-kernels.js index 93b7e33..9bcd272 100644 --- a/dist/hosted/gpu-kernels.js +++ b/dist/hosted/gpu-kernels.js @@ -4,10 +4,19 @@ const FIELD_UNKNOWN:u32=0u; const FIELD_ESCAPED:u32=1u; const FIELD_INTERIOR_LIKELY:u32=2u; const FIELD_INTERIOR_PROVEN:u32=3u; -const ITER_MASK:u32=0x0fffffffu; -const STATUS_UNRESOLVED:u32=0xfffffffeu; +const ITER_MASK:u32=0x000fffffu; +const REASON_SHIFT:u32=20u; +const REASON_MASK:u32=0x0ff00000u; +const REASON_NONE:u32=0u; +const REASON_ERROR_BOUND:u32=1u; +const REASON_ESCAPE_UNCERTAIN:u32=2u; +const REASON_REFERENCE_END:u32=3u; +const REASON_REBASE_GAP:u32=4u; +const REASON_RANGE:u32=5u; +const REASON_OPERATION_LIMIT:u32=6u; fn pack_meta(n:u32, cls:u32)->u32 { return (n & ITER_MASK) | ((cls & 3u) << 28u); } +fn pack_unknown(n:u32, reason:u32)->u32 { return (n & ITER_MASK) | ((reason & 0xffu) << REASON_SHIFT); } fn cmul(a:vec2, b:vec2)->vec2{ return vec2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); } @@ -20,7 +29,7 @@ fn pow2_safe(e:i32)->f32 { } fn safe_abs_error(errScaled:f32, scaleExp:i32, z:vec2, delta:vec2)->f32{ let propagated=abs(errScaled*pow2_safe(scaleExp)); - let reconstruction=64.0*F32_U*(maxabs(z)+maxabs(delta)+1.0e-30); + let reconstruction=1.0*F32_U*(maxabs(z)+maxabs(delta)+1.0e-30); return propagated+reconstruction; } fn scaled_to_f32(v:vec2, e:i32)->vec2{ @@ -77,35 +86,402 @@ fn main(@builtin(global_invocation_id) gid:vec3){ } `; + +// Benchmark candidate: whole-frame double-single direct iteration. +// This intentionally has no CPU reference-build cost. It is not selected by +// the production router until the crossover benchmark justifies it. +const DIRECT_DS_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, _flags:u32, + centerReHi:f32, centerReLo:f32, centerImHi:f32, centerImLo:f32, + spanHi:f32, spanLo:f32, sampleX:f32, sampleY:f32, + invFullWHi:f32, invFullWLo:f32, _p0:f32, _p1:f32, + _p2:f32, _p3:f32, _p4:f32, _p5:f32, +}; +struct DS{ h:f32, l:f32 }; +struct CDS{ r:DS, i:DS }; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +fn ds_quick(a:f32,b:f32)->DS{ + let q=a+b; let e=b-(q-a); return DS(q,e); +} +fn ds_sum(a:f32,b:f32)->DS{ + let q=a+b; let bb=q-a; let e=(a-(q-bb))+(b-bb); return DS(q,e); +} +fn ds_prod(a:f32,b:f32)->DS{ + let q=a*b; + let ca=4097.0*a; let ah=ca-(ca-a); let al=a-ah; + let cb=4097.0*b; let bh=cb-(cb-b); let bl=b-bh; + var e=ah*bh-q; e=e+ah*bl; e=e+al*bh; e=e+al*bl; + return DS(q,e); +} +fn ds_add(a:DS,b:DS)->DS{ + let q=ds_sum(a.h,b.h); return ds_quick(q.h,q.l+(a.l+b.l)); +} +fn ds_neg(a:DS)->DS{return DS(-a.h,-a.l);} +fn ds_sub(a:DS,b:DS)->DS{return ds_add(a,ds_neg(b));} +fn ds_mul(a:DS,b:DS)->DS{ + let q=ds_prod(a.h,b.h); + var e=q.l+a.h*b.l; e=e+a.l*b.h; e=e+a.l*b.l; + return ds_quick(q.h,e); +} +fn ds_scale(a:DS,b:f32)->DS{ + let q=ds_prod(a.h,b); return ds_quick(q.h,q.l+a.l*b); +} +fn ds_cmp(a:DS,b:DS)->i32{ + if(a.hb.h){return 1;} + if(a.lb.l){return 1;} return 0; +} +fn ds_value(a:DS)->f32{return a.h+a.l;} +fn cds_add(a:CDS,b:CDS)->CDS{return CDS(ds_add(a.r,b.r),ds_add(a.i,b.i));} +fn cds_mul(a:CDS,b:CDS)->CDS{ + let rr=ds_sub(ds_mul(a.r,b.r),ds_mul(a.i,b.i)); + let ii=ds_add(ds_mul(a.r,b.i),ds_mul(a.i,b.r)); + return CDS(rr,ii); +} +fn cds_mag2(a:CDS)->DS{return ds_add(ds_mul(a.r,a.r),ds_mul(a.i,a.i));} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=gid.y*p.tileW+gid.x; + let invW=DS(p.invFullWHi,p.invFullWLo); + let span=DS(p.spanHi,p.spanLo); + let ox=f32(p.tileX+gid.x)+p.sampleX-0.5*f32(p.fullW); + let oy=0.5*f32(p.fullH)-(f32(p.tileY+gid.y)+p.sampleY); + let dx=ds_scale(invW,ox); let dy=ds_scale(invW,oy); + let cr=ds_add(DS(p.centerReHi,p.centerReLo),ds_mul(span,dx)); + let ci=ds_add(DS(p.centerImHi,p.centerImLo),ds_mul(span,dy)); + let c=CDS(cr,ci); + var z=CDS(DS(0.0,0.0),DS(0.0,0.0)); + var n=0u; + loop{ + if(n>=p.maxIter){break;} + z=cds_add(cds_mul(z,z),c); n+=1u; + let mag=cds_mag2(z); + if(ds_cmp(mag,DS(4.0,0.0))>0){ + fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); + fieldSmooth[out]=smooth_escape(n,max(4.0000005,ds_value(mag))); + return; + } + } + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY); fieldSmooth[out]=0.0; +} +`; + + + +// Diagnostic hybrid candidate: DS direct with a sensitivity guard. Pixels +// whose coordinate derivative grows beyond riskLimit are marked UNKNOWN and +// can be re-evaluated by the existing deep double-single correction pass. +// This shader is benchmark-only and is not selected by the production router. +const DIRECT_DS_GUARDED_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, _flags:u32, + centerReHi:f32, centerReLo:f32, centerImHi:f32, centerImLo:f32, + spanHi:f32, spanLo:f32, sampleX:f32, sampleY:f32, + invFullWHi:f32, invFullWLo:f32, riskLimit:f32, _p1:f32, + _p2:f32, _p3:f32, _p4:f32, _p5:f32, +}; +struct DS{ h:f32, l:f32 }; +struct CDS{ r:DS, i:DS }; +struct HybridStats{ + remaining:atomic, overflow:atomic, enqueued:atomic, dispatchCount:atomic, + _d:atomic, _e:atomic, _f:atomic, corrected:atomic, +}; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +@group(0) @binding(3) var hybridStats:HybridStats; +@group(0) @binding(4) var riskQueue:array; +fn ds_quick(a:f32,b:f32)->DS{let q=a+b;let e=b-(q-a);return DS(q,e);} +fn ds_sum(a:f32,b:f32)->DS{let q=a+b;let bb=q-a;let e=(a-(q-bb))+(b-bb);return DS(q,e);} +fn ds_prod(a:f32,b:f32)->DS{ + let q=a*b;let ca=4097.0*a;let ah=ca-(ca-a);let al=a-ah; + let cb=4097.0*b;let bh=cb-(cb-b);let bl=b-bh; + var e=ah*bh-q;e=e+ah*bl;e=e+al*bh;e=e+al*bl;return DS(q,e); +} +fn ds_add(a:DS,b:DS)->DS{let q=ds_sum(a.h,b.h);return ds_quick(q.h,q.l+(a.l+b.l));} +fn ds_neg(a:DS)->DS{return DS(-a.h,-a.l);} +fn ds_sub(a:DS,b:DS)->DS{return ds_add(a,ds_neg(b));} +fn ds_mul(a:DS,b:DS)->DS{ + let q=ds_prod(a.h,b.h);var e=q.l+a.h*b.l;e=e+a.l*b.h;e=e+a.l*b.l;return ds_quick(q.h,e); +} +fn ds_scale(a:DS,b:f32)->DS{let q=ds_prod(a.h,b);return ds_quick(q.h,q.l+a.l*b);} +fn ds_cmp(a:DS,b:DS)->i32{if(a.hb.h){return 1;}if(a.lb.l){return 1;}return 0;} +fn ds_value(a:DS)->f32{return a.h+a.l;} +fn cds_add(a:CDS,b:CDS)->CDS{return CDS(ds_add(a.r,b.r),ds_add(a.i,b.i));} +fn cds_mul(a:CDS,b:CDS)->CDS{ + let rr=ds_sub(ds_mul(a.r,b.r),ds_mul(a.i,b.i)); + let ii=ds_add(ds_mul(a.r,b.i),ds_mul(a.i,b.r));return CDS(rr,ii); +} +fn cds_mag2(a:CDS)->DS{return ds_add(ds_mul(a.r,a.r),ds_mul(a.i,a.i));} +fn mark_sensitive(out:u32,n:u32){ + fieldMeta[out]=pack_unknown(n,7u);fieldSmooth[out]=0.0; + let slot=atomicAdd(&hybridStats.remaining,1u); + let capacity=p.tileW*p.tileH; + if(slot){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=gid.y*p.tileW+gid.x; + let invW=DS(p.invFullWHi,p.invFullWLo);let span=DS(p.spanHi,p.spanLo); + let ox=f32(p.tileX+gid.x)+p.sampleX-0.5*f32(p.fullW); + let oy=0.5*f32(p.fullH)-(f32(p.tileY+gid.y)+p.sampleY); + let dx=ds_scale(invW,ox);let dy=ds_scale(invW,oy); + let cr=ds_add(DS(p.centerReHi,p.centerReLo),ds_mul(span,dx)); + let ci=ds_add(DS(p.centerImHi,p.centerImLo),ds_mul(span,dy)); + let c=CDS(cr,ci);var z=CDS(DS(0.0,0.0),DS(0.0,0.0)); + var deriv=vec2(0.0);var n=0u; + loop{ + if(n>=p.maxIter){break;} + let zv=vec2(ds_value(z.r),ds_value(z.i)); + deriv=2.0*cmul(zv,deriv)+vec2(1.0,0.0); + if(length(deriv)>p.riskLimit || any(deriv!=deriv)){mark_sensitive(out,n);return;} + z=cds_add(cds_mul(z,z),c);n+=1u; + let mag=cds_mag2(z); + if(ds_cmp(mag,DS(4.0,0.0))>0){ + fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); + fieldSmooth[out]=smooth_escape(n,max(4.0000005,ds_value(mag)));return; + } + } + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY);fieldSmooth[out]=0.0; +} +`; + + +// Converts the GPU-written risk queue length into indirect dispatch arguments. +// The correction pass therefore launches one lane per enqueued risky pixel, +// instead of one lane per screen pixel. +const DIRECT_FIXED96_QUEUE_ARGS_WGSL=COMMON+String.raw` +struct HybridStats{ + remaining:atomic, overflow:atomic, enqueued:atomic, dispatchCount:atomic, + _d:atomic, _e:atomic, _f:atomic, corrected:atomic, +}; +struct IndirectArgs{ x:u32, y:u32, z:u32, _pad:u32 }; +@group(0) @binding(0) var hybridStats:HybridStats; +@group(0) @binding(1) var indirectArgs:IndirectArgs; +@compute @workgroup_size(1) +fn main(){ + let n=atomicLoad(&hybridStats.enqueued); + atomicStore(&hybridStats.dispatchCount,n); + indirectArgs.x=(n+63u)/64u; + indirectArgs.y=1u; + indirectArgs.z=1u; + indirectArgs._pad=0u; +} +`; + + + +// Diagnostic sparse fixed-point direct correction. It does not consume a +// deep reference. The guarded DS pass marks uncertain pixels with reason 7; +// this shader re-evaluates only those pixels using signed Q8.88 arithmetic +// implemented entirely with u32 limbs. Integer arithmetic avoids the +// reassociation/fusion sensitivity of float-expansion correction kernels. +const DIRECT_FIXED96_CORRECT_WGSL=COMMON+String.raw` +struct Params{ + tileW:u32, tileH:u32, maxIter:u32, _flags:u32, + baseReLo:u32, baseReMid:u32, baseReHi:u32, + baseImLo:u32, baseImMid:u32, baseImHi:u32, + stepLo:u32, stepMid:u32, stepHi:u32, + _pad0:u32, _pad1:u32, _pad2:u32, +}; +struct FX96{ lo:u32, mid:u32, hi:u32 }; +struct FixedStats{ + remaining:atomic, invalidIndex:atomic, processed:atomic, staleEntry:atomic, + _d:atomic, _e:atomic, _f:atomic, corrected:atomic, +}; +struct HybridStats{ + remaining:atomic, overflow:atomic, enqueued:atomic, dispatchCount:atomic, + _d:atomic, _e:atomic, _f:atomic, corrected:atomic, +}; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var fieldMeta:array; +@group(0) @binding(2) var fieldSmooth:array; +@group(0) @binding(3) var fixedStats:FixedStats; +@group(0) @binding(4) var hybridStats:HybridStats; +@group(0) @binding(5) var riskQueue:array; + +fn fx_add(a:FX96,b:FX96)->FX96{ + let lo=a.lo+b.lo; let c0=select(0u,1u,loFX96{ + let lo=(~a.lo)+1u; let c0=select(0u,1u,lo==0u); + let mid=(~a.mid)+c0; let c1=select(0u,1u,c0!=0u && mid==0u); + return FX96(lo,mid,(~a.hi)+c1); +} +fn fx_sub(a:FX96,b:FX96)->FX96{return fx_add(a,fx_neg(b));} +fn fx_negative(a:FX96)->bool{return (a.hi&0x80000000u)!=0u;} +fn fx_abs(a:FX96)->FX96{if(fx_negative(a)){return fx_neg(a);}return a;} +fn mul32(a:u32,b:u32)->vec2{ + let a0=a&0xffffu; let a1=a>>16u; let b0=b&0xffffu; let b1=b>>16u; + let p0=a0*b0; let p1=a0*b1; let p2=a1*b0; let p3=a1*b1; + let middle=(p0>>16u)+(p1&0xffffu)+(p2&0xffffu); + let lo=(p0&0xffffu)|(middle<<16u); + let hi=p3+(p1>>16u)+(p2>>16u)+(middle>>16u); + return vec2(lo,hi); +} +fn add_at(a:ptr>,idx0:u32,value0:u32){ + var idx=idx0; var value=value0; + loop{ + if(value==0u || idx>=6u){break;} + let old=(*a)[idx]; let sum=old+value; (*a)[idx]=sum; + value=select(0u,1u,sum>,idx:u32,x:u32,y:u32){ + let q=mul32(x,y); add_at(a,idx,q.x); add_at(a,idx+1u,q.y); +} +fn fx_mul(a0:FX96,b0:FX96)->FX96{ + let neg=fx_negative(a0)!=fx_negative(b0); let a=fx_abs(a0); let b=fx_abs(b0); + var w=array(0u,0u,0u,0u,0u,0u); + add_product(&w,0u,a.lo,b.lo); add_product(&w,1u,a.lo,b.mid); add_product(&w,2u,a.lo,b.hi); + add_product(&w,1u,a.mid,b.lo); add_product(&w,2u,a.mid,b.mid);add_product(&w,3u,a.mid,b.hi); + add_product(&w,2u,a.hi,b.lo); add_product(&w,3u,a.hi,b.mid); add_product(&w,4u,a.hi,b.hi); + // Q8.88 * Q8.88 -> Q16.176. Shift right by 88 with one-bit rounding. + var r=FX96((w[2]>>24u)|(w[3]<<8u),(w[3]>>24u)|(w[4]<<8u),(w[4]>>24u)|(w[5]<<8u)); + if(((w[2]>>23u)&1u)!=0u){r=fx_add(r,FX96(1u,0u,0u));} + if(neg){return fx_neg(r);}return r; +} +fn fx_mul_small(a0:FX96,k:u32)->FX96{ + let neg=fx_negative(a0); let a=fx_abs(a0); + let q0=mul32(a.lo,k); let q1=mul32(a.mid,k); let q2=mul32(a.hi,k); + let mid=q1.x+q0.y; let cm=select(0u,1u,midi32{ + if(a.hib.hi){return 1;} + if(a.midb.mid){return 1;} + if(a.lob.lo){return 1;} return 0; +} +fn fx_to_f32_positive(a:FX96)->f32{ + return f32(a.hi)*5.960464477539063e-8 + f32(a.mid)*1.3877787807814457e-17 + f32(a.lo)*3.2311742677852644e-27; +} +fn fx_to_f32(a:FX96)->f32{ + if(fx_negative(a)){return -fx_to_f32_positive(fx_neg(a));} + return fx_to_f32_positive(a); +} +fn write_escape(out:u32,n:u32,zr:FX96,zi:FX96){ + // Once either component exceeds 2 in magnitude, escape is mathematically + // certain. Use f32 only for the smooth-color estimate; membership remains + // decided by the integer fixed-point state. This avoids squaring a large + // escaped value and therefore avoids Q8.88 wraparound. + let zrf=fx_to_f32(zr); let zif=fx_to_f32(zi); + let mag=max(4.0000005,zrf*zrf+zif*zif); + fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); + fieldSmooth[out]=smooth_escape(n,mag); + atomicAdd(&fixedStats.corrected,1u); +} + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) gid:vec3){ + let qi=gid.x; + let queued=atomicLoad(&hybridStats.dispatchCount); + if(qi>=queued){return;} + atomicAdd(&fixedStats.processed,1u); + let out=riskQueue[qi]; + let capacity=p.tileW*p.tileH; + if(out>=capacity){ + atomicAdd(&fixedStats.invalidIndex,1u); + atomicAdd(&fixedStats.remaining,1u); + return; + } + let old=fieldMeta[out]; + let cls=(old>>28u)&3u; let reason=(old&REASON_MASK)>>REASON_SHIFT; + if(cls!=FIELD_UNKNOWN || reason!=7u){ + atomicAdd(&fixedStats.staleEntry,1u); + atomicAdd(&fixedStats.remaining,1u); + return; + } + let px=out%p.tileW; let py=out/p.tileW; + let baseRe=FX96(p.baseReLo,p.baseReMid,p.baseReHi); + let baseIm=FX96(p.baseImLo,p.baseImMid,p.baseImHi); + let step=FX96(p.stepLo,p.stepMid,p.stepHi); + let cr=fx_add(baseRe,fx_mul_small(step,px)); + let ci=fx_sub(baseIm,fx_mul_small(step,py)); + var zr=FX96(0u,0u,0u); var zi=FX96(0u,0u,0u); var n=0u; + let TWO=FX96(0u,0u,0x02000000u); + let FOUR=FX96(0u,0u,0x04000000u); + loop{ + if(n>=p.maxIter){break;} + // The previous state is inside the bailout disk, so these products are + // bounded and cannot overflow Q8.88. + let zr2=fx_mul(zr,zr); let zi2=fx_mul(zi,zi); let zri=fx_mul(zr,zi); + let nextR=fx_add(fx_sub(zr2,zi2),cr); + let nextI=fx_add(fx_add(zri,zri),ci); + zr=nextR; zi=nextI; n+=1u; + // Do not mistake the Q8.88 integer byte for sign-extension. Values such + // as +1.2 are valid. The old range guard rejected them and left virtually + // every risky escaping pixel UNKNOWN. + if(fx_cmp_unsigned(fx_abs(zr),TWO)>0 || fx_cmp_unsigned(fx_abs(zi),TWO)>0){ + write_escape(out,n,zr,zi); return; + } + let rr=fx_mul(zr,zr); let ii=fx_mul(zi,zi); let mag=fx_add(rr,ii); + if(fx_cmp_unsigned(mag,FOUR)>0){ + fieldMeta[out]=pack_meta(n,FIELD_ESCAPED); + fieldSmooth[out]=smooth_escape(n,max(4.0000005,fx_to_f32_positive(mag))); + atomicAdd(&fixedStats.corrected,1u); return; + } + } + fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY); fieldSmooth[out]=0.0; + atomicAdd(&fixedStats.corrected,1u); +} +`; + + // Deep path: high-precision CPU reference + guarded rescaled f32 perturbation. const DEEP_PERTURB_WGSL=COMMON+String.raw` struct Params{ tileW:u32, tileH:u32, fullW:u32, fullH:u32, tileX:u32, tileY:u32, maxIter:u32, refLen:u32, - strict:u32, _numeric0:u32, _numeric1:u32, _numeric2:u32, - spanMant:f32, spanExp:i32, sampleX:f32, sampleY:f32, + strict:u32, unknownOnly:u32, outputStride:u32, outputBase:u32, + spanMantHi:f32, spanExp:i32, sampleX:f32, sampleY:f32, + refPixelX:f32, refPixelY:f32, spanMantLo:f32, invFullWHi:f32, + invFullWLo:f32, _numeric0:f32, _numeric1:f32, _numeric2:f32, }; struct RefPoint{ hi:vec2, lo:vec2 }; -struct UnresolvedHead{ remaining:atomic, _p0:u32, _p1:u32, _p2:u32 }; +struct UnresolvedHead{ + remaining:atomic, errorBound:atomic, escapeUncertain:atomic, referenceEnd:atomic, + rebaseGap:atomic, rangeFailure:atomic, operationLimit:atomic, corrected:atomic, +}; @group(0) @binding(0) var p:Params; @group(0) @binding(1) var refs:array; @group(0) @binding(2) var fieldMeta:array; @group(0) @binding(3) var fieldSmooth:array; @group(0) @binding(4) var unresolved:UnresolvedHead; -fn mark_unresolved(out:u32,n:u32){ - fieldMeta[out]=pack_meta(n,FIELD_UNKNOWN); fieldSmooth[out]=0.0; +fn mark_unresolved(out:u32,n:u32,reason:u32){ + fieldMeta[out]=pack_unknown(n,reason); fieldSmooth[out]=0.0; atomicAdd(&unresolved.remaining,1u); + if(reason==REASON_ERROR_BOUND){atomicAdd(&unresolved.errorBound,1u);} + else if(reason==REASON_ESCAPE_UNCERTAIN){atomicAdd(&unresolved.escapeUncertain,1u);} + else if(reason==REASON_REFERENCE_END){atomicAdd(&unresolved.referenceEnd,1u);} + else if(reason==REASON_REBASE_GAP){atomicAdd(&unresolved.rebaseGap,1u);} + else if(reason==REASON_RANGE){atomicAdd(&unresolved.rangeFailure,1u);} + else if(reason==REASON_OPERATION_LIMIT){atomicAdd(&unresolved.operationLimit,1u);} } fn render_pixel(out:u32,gx:f32,gy:f32,strictMode:bool){ - let dx=(gx-0.5*f32(p.fullW))/f32(p.fullW); - let dy=(0.5*f32(p.fullH)-gy)/f32(p.fullW); + let dx=(gx-p.refPixelX)/f32(p.fullW); + let dy=(p.refPixelY-gy)/f32(p.fullW); // dc = d * 2^scaleExp. Keep d and w in one shared scale. - var d=vec2(p.spanMant*dx,p.spanMant*dy); + var d=vec2(p.spanMantHi*dx,p.spanMantHi*dy); var w=vec2(0.0); var scaleExp=p.spanExp; var n=0u; var m=0u; var operations=0u; - var errScaled=64.0*F32_U*maxabs(d); + var errScaled=1.0*F32_U*maxabs(d); loop{ if(n>=p.maxIter){ let rpEnd=refs[min(m,p.refLen)]; @@ -113,10 +489,10 @@ fn render_pixel(out:u32,gx:f32,gy:f32,strictMode:bool){ let zEnd=rpEnd.hi+(rpEnd.lo+deltaEnd); let errAbs=safe_abs_error(errScaled,scaleExp,zEnd,deltaEnd); let limit=select(1.0e-3,1.0e-4,strictMode); - if(errAbs<=limit){fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY);fieldSmooth[out]=0.0;}else{mark_unresolved(out,n);} + if(errAbs<=limit){fieldMeta[out]=pack_meta(p.maxIter,FIELD_INTERIOR_LIKELY);fieldSmooth[out]=0.0;}else{mark_unresolved(out,n,REASON_ERROR_BOUND);} return; } - if(m>p.refLen){mark_unresolved(out,n);return;} + if(m>p.refLen){mark_unresolved(out,n,REASON_REFERENCE_END);return;} let rp=refs[m]; let delta=scaled_to_f32(w,scaleExp); let z=rp.hi+(rp.lo+delta); @@ -124,49 +500,207 @@ fn render_pixel(out:u32,gx:f32,gy:f32,strictMode:bool){ if(mag>4.0){ let errAbs=safe_abs_error(errScaled,scaleExp,z,delta); if(length(z)-errAbs>2.0){fieldMeta[out]=pack_meta(n,FIELD_ESCAPED);fieldSmooth[out]=smooth_escape(n,mag);return;} - mark_unresolved(out,n);return; + mark_unresolved(out,n,REASON_ESCAPE_UNCERTAIN);return; } // Rebase only when dc remains numerically representable in the new scale. if(m>0u && dot(delta,delta)>0.0 && mag(p.spanMant*dx,p.spanMant*dy),p.spanExp); scaleExp=0; m=0u; - errScaled+=64.0*F32_U*maxabs(d); + w=z; d=scaled_to_f32(vec2(p.spanMantHi*dx,p.spanMantHi*dy),p.spanExp); scaleExp=0; m=0u; + errScaled+=1.0*F32_U*maxabs(d); continue; } - if(m>=p.refLen){mark_unresolved(out,n);return;} + if(m>=p.refLen){mark_unresolved(out,n,REASON_REFERENCE_END);return;} let r=refs[m]; let refAbs=maxabs(r.hi)+maxabs(r.lo); let wAbs=maxabs(w); let dAbs=maxabs(d); let p2=abs(pow2_safe(scaleExp)); let gain=2.0*refAbs+2.0*wAbs*p2; - let roundErr=64.0*F32_U*(2.0*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1.0e-30); + let roundErr=1.0*F32_U*(2.0*refAbs*wAbs+wAbs*wAbs*p2+dAbs+1.0e-30); errScaled=gain*errScaled+roundErr; let linear=2.0*(cmul(r.hi,w)+cmul(r.lo,w)); // delta^2 / 2^scaleExp = w^2 * 2^scaleExp let sq=cmul(w,w)*pow2_safe(scaleExp); w=linear+sq+d; m+=1u; n+=1u; operations+=1u; - if(maxabs(w)>=1.0e30 || maxabs(d)>=1.0e30){mark_unresolved(out,n);return;} + if(maxabs(w)>=1.0e30 || maxabs(d)>=1.0e30){mark_unresolved(out,n,REASON_RANGE);return;} let mm=max(maxabs(w),maxabs(d)); if(mm>65536.0){ w*=0.0000152587890625; d*=0.0000152587890625; errScaled*=0.0000152587890625; scaleExp+=16; }else if(mm>0.0 && mm<0.0000152587890625 && scaleExp>p.spanExp){ w*=65536.0; d*=65536.0; errScaled*=65536.0; scaleExp-=16; } - if(scaleExp>126 || errScaled!=errScaled || errScaled>1.0e35){mark_unresolved(out,n);return;} - if(operations>p.maxIter*2u+2048u){mark_unresolved(out,n);return;} + if(scaleExp>126 || errScaled!=errScaled || errScaled>1.0e35){mark_unresolved(out,n,REASON_RANGE);return;} + if(operations>p.maxIter*2u+2048u){mark_unresolved(out,n,REASON_OPERATION_LIMIT);return;} } } @compute @workgroup_size(8,8) fn main(@builtin(global_invocation_id) gid:vec3){ if(gid.x>=p.tileW||gid.y>=p.tileH){return;} - let out=gid.y*p.tileW+gid.x; + let out=p.outputBase+gid.y*p.outputStride+gid.x; + if(p.unknownOnly!=0u && ((fieldMeta[out]>>28u)&3u)!=FIELD_UNKNOWN){return;} let gx=f32(p.tileX+gid.x)+p.sampleX; let gy=f32(p.tileY+gid.y)+p.sampleY; render_pixel(out,gx,gy,p.strict!=0u); } `; + +// Sparse correction: only UNKNOWN pixels are re-evaluated with double-single +// perturbation. It is a visual-quality pass, not a membership certificate. +const DEEP_CORRECT_WGSL=COMMON+String.raw` +const CORRECTION_MARK:u32=128u; +struct Params{ + tileW:u32, tileH:u32, fullW:u32, fullH:u32, + tileX:u32, tileY:u32, maxIter:u32, refLen:u32, + strict:u32, unknownOnly:u32, outputStride:u32, outputBase:u32, + spanMantHi:f32, spanExp:i32, sampleX:f32, sampleY:f32, + refPixelX:f32, refPixelY:f32, spanMantLo:f32, invFullWHi:f32, + invFullWLo:f32, _numeric0:f32, _numeric1:f32, _numeric2:f32, +}; +struct RefPoint{ hi:vec2, lo:vec2 }; +struct CorrectionStats{ + remaining:atomic, errorBound:atomic, escapeUncertain:atomic, referenceEnd:atomic, + rebaseGap:atomic, rangeFailure:atomic, operationLimit:atomic, corrected:atomic, +}; +struct DS{ h:f32, l:f32 }; +struct CDS{ r:DS, i:DS }; +@group(0) @binding(0) var p:Params; +@group(0) @binding(1) var refs:array; +@group(0) @binding(2) var fieldMeta:array; +@group(0) @binding(3) var fieldSmooth:array; +@group(0) @binding(4) var stats:CorrectionStats; + +fn count_remaining(out:u32){ + let reason=(fieldMeta[out]>>REASON_SHIFT)&0xffu; + atomicAdd(&stats.remaining,1u); + if(reason==REASON_ERROR_BOUND){atomicAdd(&stats.errorBound,1u);} + else if(reason==REASON_ESCAPE_UNCERTAIN){atomicAdd(&stats.escapeUncertain,1u);} + else if(reason==REASON_REFERENCE_END){atomicAdd(&stats.referenceEnd,1u);} + else if(reason==REASON_REBASE_GAP){atomicAdd(&stats.rebaseGap,1u);} + else if(reason==REASON_RANGE){atomicAdd(&stats.rangeFailure,1u);} + else if(reason==REASON_OPERATION_LIMIT){atomicAdd(&stats.operationLimit,1u);} +} +fn accept_corrected(out:u32,n:u32,cls:u32,sm:f32){ + fieldMeta[out]=corrected(n,cls); fieldSmooth[out]=sm; atomicAdd(&stats.corrected,1u); +} + +fn ds_quick(a:f32,b:f32)->DS{ + let q=a+b; + let e=b-(q-a); + return DS(q,e); +} +fn ds_sum(a:f32,b:f32)->DS{ + let q=a+b; + let bb=q-a; + let e=(a-(q-bb))+(b-bb); + return DS(q,e); +} +fn ds_prod(a:f32,b:f32)->DS{ + let q=a*b; + let ca=4097.0*a; + let ah=ca-(ca-a); + let al=a-ah; + let cb=4097.0*b; + let bh=cb-(cb-b); + let bl=b-bh; + var e=ah*bh-q; + e=e+ah*bl; + e=e+al*bh; + e=e+al*bl; + return DS(q,e); +} +fn ds_add(a:DS,b:DS)->DS{ + let q=ds_sum(a.h,b.h); + return ds_quick(q.h,q.l+(a.l+b.l)); +} +fn ds_neg(a:DS)->DS{return DS(-a.h,-a.l);} +fn ds_sub(a:DS,b:DS)->DS{return ds_add(a,ds_neg(b));} +fn ds_mul(a:DS,b:DS)->DS{ + let q=ds_prod(a.h,b.h); + var e=q.l+a.h*b.l; + e=e+a.l*b.h; + e=e+a.l*b.l; + return ds_quick(q.h,e); +} +fn ds_scale(a:DS,b:f32)->DS{ + let q=ds_prod(a.h,b); + return ds_quick(q.h,q.l+a.l*b); +} +fn ds_pow2(a:DS,e:i32)->DS{ + if(e < -126){return DS(0.0,0.0);} + if(e > 126){return DS(8.507059e37,0.0);} + return DS(ldexp(a.h,e),ldexp(a.l,e)); +} +fn ds_cmp(a:DS,b:DS)->i32{ + if(a.hb.h){return 1;} + if(a.lb.l){return 1;} return 0; +} +fn ds_value(a:DS)->f32{return a.h+a.l;} +fn cds_add(a:CDS,b:CDS)->CDS{return CDS(ds_add(a.r,b.r),ds_add(a.i,b.i));} +fn cds_mul(a:CDS,b:CDS)->CDS{ + let rr=ds_sub(ds_mul(a.r,b.r),ds_mul(a.i,b.i)); + let ii=ds_add(ds_mul(a.r,b.i),ds_mul(a.i,b.r)); + return CDS(rr,ii); +} +fn cds_scale(a:CDS,b:f32)->CDS{return CDS(ds_scale(a.r,b),ds_scale(a.i,b));} +fn cds_pow2(a:CDS,e:i32)->CDS{return CDS(ds_pow2(a.r,e),ds_pow2(a.i,e));} +fn cds_mag2(a:CDS)->DS{return ds_add(ds_mul(a.r,a.r),ds_mul(a.i,a.i));} +fn cds_maxabs(a:CDS)->f32{return max(abs(ds_value(a.r)),abs(ds_value(a.i)));} +fn corrected(n:u32,cls:u32)->u32{return pack_meta(n,cls)|(CORRECTION_MARK<=p.maxIter){accept_corrected(out,p.maxIter,FIELD_INTERIOR_LIKELY,0.0);return;} + if(m>p.refLen){count_remaining(out);return;} + let rp=refs[m]; + let r=CDS(DS(rp.hi.x,rp.lo.x),DS(rp.hi.y,rp.lo.y)); + let delta=cds_pow2(w,scaleExp); + let z=cds_add(r,delta); + let mag=cds_mag2(z); + if(ds_cmp(mag,DS(4.0,0.0))>0){ + accept_corrected(out,n,FIELD_ESCAPED,smooth_escape(n,max(4.0000005,ds_value(mag)))); + return; + } + let dmag=cds_mag2(delta); + if(m>0u && ds_cmp(dmag,DS(0.0,0.0))>0 && ds_cmp(mag,dmag)<0){ + if(p.spanExp-scaleExp < -96){count_remaining(out);return;} + w=z; d=cds_pow2(d0,p.spanExp); scaleExp=0; m=0u; + continue; + } + if(m>=p.refLen){count_remaining(out);return;} + let linear=cds_scale(cds_mul(r,w),2.0); + let sq=cds_pow2(cds_mul(w,w),scaleExp); + w=cds_add(cds_add(linear,sq),d); + m+=1u; n+=1u; operations+=1u; + let mm=max(cds_maxabs(w),cds_maxabs(d)); + if(mm>=1.0e30 || mm!=mm){count_remaining(out);return;} + if(mm>65536.0){w=cds_scale(w,0.0000152587890625);d=cds_scale(d,0.0000152587890625);scaleExp+=16;} + else if(mm>0.0 && mm<0.0000152587890625 && scaleExp>p.spanExp){w=cds_scale(w,65536.0);d=cds_scale(d,65536.0);scaleExp-=16;} + if(scaleExp>126 || operations>p.maxIter*2u+2048u){count_remaining(out);return;} + } +} +@compute @workgroup_size(8,8) +fn main(@builtin(global_invocation_id) gid:vec3){ + if(gid.x>=p.tileW||gid.y>=p.tileH){return;} + let out=p.outputBase+gid.y*p.outputStride+gid.x; + if(((fieldMeta[out]>>28u)&3u)!=FIELD_UNKNOWN){return;} + let gx=f32(p.tileX+gid.x)+p.sampleX; + let gy=f32(p.tileY+gid.y)+p.sampleY; + correct_pixel(out,gx,gy); +} +`; + const COLOR_WGSL=String.raw` struct Params{ width:u32,height:u32,palette:u32,edgeAA:u32, @@ -194,10 +728,10 @@ fn current_palette(t0:f32)->vec3{ } fn base_color(i:u32)->vec3{ let m=fieldMeta[i]; let cls=(m>>28u)&3u; - if(cls==0u){return vec3(20,22,30)/255.0;} if(cls!=1u){return vec3(0.0);} + if(cls==0u){return vec3(43,20,58)/255.0;} if(cls!=1u){return vec3(0.0);} let sm=fieldSmooth[i]; let phase=fract(p.shift+sm*p.cycle); var c=vec3(0.0); if(p.palette==1u){c=hsv(phase,0.92,1.0);}else if(p.palette==2u){let g=(22.0+233.0*(0.5-0.5*cos(6.283185307*phase)))/255.0;c=vec3(g);}else{c=current_palette(phase);} - let n=f32(m&0x0fffffffu); let edge=clamp(log(1.0+n)/log(1.0+max(8.0,n+32.0)),0.0,1.0); let mixv=0.34+0.66*pow(edge,0.38); + let n=f32(m&0x000fffffu); let edge=clamp(log(1.0+n)/log(1.0+max(8.0,n+32.0)),0.0,1.0); let mixv=0.34+0.66*pow(edge,0.38); let floorc=select(vec3(2,5,15)/255.0,vec3(8.0/255.0),p.palette==2u); return mix(floorc,c,mixv); } fn linearize(c:vec3)->vec3{return pow(c,vec3(2.2));} @@ -208,7 +742,7 @@ fn main(@builtin(global_invocation_id) gid:vec3){ if(p.edgeAA!=0u){ let m=fieldMeta[i]; let cls=(m>>28u)&3u; var boundary=false; var sum=linearize(c); var cnt=1.0; let x=i32(gid.x); let y=i32(gid.y); - for(var oy=-1;oy<=1;oy+=1){for(var ox=-1;ox<=1;ox+=1){if(ox==0&&oy==0){continue;} let xx=x+ox;let yy=y+oy;if(xx<0||yy<0||xx>=i32(p.width)||yy>=i32(p.height)){continue;}let j=u32(yy)*p.width+u32(xx);let mj=fieldMeta[j];let cj=(mj>>28u)&3u;if(cj!=cls||abs(i32(mj&0x0fffffffu)-i32(m&0x0fffffffu))>2){boundary=true;}sum+=linearize(base_color(j));cnt+=1.0;}} + for(var oy=-1;oy<=1;oy+=1){for(var ox=-1;ox<=1;ox+=1){if(ox==0&&oy==0){continue;} let xx=x+ox;let yy=y+oy;if(xx<0||yy<0||xx>=i32(p.width)||yy>=i32(p.height)){continue;}let j=u32(yy)*p.width+u32(xx);let mj=fieldMeta[j];let cj=(mj>>28u)&3u;if(cj!=cls||abs(i32(mj&0x000fffffu)-i32(m&0x000fffffu))>2){boundary=true;}sum+=linearize(base_color(j));cnt+=1.0;}} if(boundary){c=delinearize(sum/cnt);} } textureStore(outTex,vec2(gid.xy),vec4(c,1.0)); @@ -253,6 +787,6 @@ struct VSOut{@builtin(position) pos:vec4,@location(0) uv:vec2}; `; globalThis.MANDEL_WEBGPU_KERNELS=Object.freeze({ - version:'24.1.3',DIRECT_F32_WGSL,DEEP_PERTURB_WGSL,COLOR_WGSL,AA_RESOLVE_WGSL,PRESENT_WGSL + version:'24.2.8-fixed96-sparse-queue',DIRECT_F32_WGSL,DIRECT_DS_WGSL,DIRECT_DS_GUARDED_WGSL,DIRECT_FIXED96_QUEUE_ARGS_WGSL,DIRECT_FIXED96_CORRECT_WGSL,DEEP_PERTURB_WGSL,DEEP_CORRECT_WGSL,COLOR_WGSL,AA_RESOLVE_WGSL,PRESENT_WGSL }); })(); diff --git a/dist/hosted/index.html b/dist/hosted/index.html index 186da65..78d2818 100644 --- a/dist/hosted/index.html +++ b/dist/hosted/index.html @@ -4,7 +4,7 @@ -Mandelbrot Deep Zoom v24.1.3 WebGPU +Mandelbrot Deep Zoom v24.2.8 Fixed96 Sparse Queue Experiment + + + +
MANDELBROT DEEP ZOOM
中心
倍率  表示幅
計算 起動中… 描画
+
起動中
+
+
+
+
+
+
0.0080
+
.18
+
詳細設定・正確な座標 +
350
+
+
+
+
+
診断情報
engine: …
numeric: …
memory: …
+
+
起動中
+
ホイール / ピンチでズーム、ドラッグで移動。HキーでUI表示を切り替え。
+
+ +
+ +

高解像度 PNG 出力

+
+ + + + +
+ +
+
+
+ +

標準 / DS / 深部 比較

+
+ + +
+
z0,2,4,6,8,10,12,14,16,20,30,40を測定します。速度は現在のview上の代表3タイルを測って全画面相当に換算し、Deep coldにはBigInt reference構築を加算します。1測定が15秒を超えた場合は停止してエラー表示します。
+ +
+ +
+
+ +

DS + Sparse Fixed96 Direct 実験

+
+ + +
+
z6 / z8 / z10 / z12で、DS Directに感度ガード |dz/dc| を追加し、危険pixelだけreference不要の96-bit符号付きQ8.88整数Directで再計算します。risk=1e11 / 1e12 / 1e13 / 1e14を全候補表示し、失敗時も correction / remain / Δclass / time を隠しません。production routerは変更しません。
+ +
+ +
+
+ + + + diff --git a/index.html b/index.html index 186da65..dfc7837 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,11 @@ + -Mandelbrot Deep Zoom v24.1.3 WebGPU +Mandelbrot Deep Zoom v24.2.8 Fixed96 Sparse Queue Experiment