good
This commit is contained in:
parent
18de4054a7
commit
f65054da0c
11 changed files with 2648 additions and 3 deletions
|
|
@ -22,3 +22,7 @@ The optional real-browser WebGPU test is `tests/browser_smoke.mjs`; it requires
|
|||
- `docs/removal-pass/integrated-bench.json` — post-removal CPU/surrogate benchmark
|
||||
|
||||
Historical investigation records remain under `docs/performance-cost-audit/` and `docs/integrated-improvements/`.
|
||||
|
||||
## Reported deep-view artifact fix (2026-09-29)
|
||||
|
||||
A reported deep URL exposed an unsafe 4th-order series jump and premature publication of FAST numerical failures. Production series acceleration is disabled pending a propagated error-bound redesign, and FAST provisional publication now occurs only after numerical failure repair. See `docs/reported-view-bugfix/IMPLEMENTATION.md`.
|
||||
|
|
|
|||
123
docs/reported-view-bugfix/IMPLEMENTATION.md
Normal file
123
docs/reported-view-bugfix/IMPLEMENTATION.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Reported deep-view artifact investigation and fix
|
||||
|
||||
Date: 2026-09-29
|
||||
|
||||
## Purpose
|
||||
|
||||
Investigate the reported URL state that displayed large yellow/black geometric regions and sometimes an almost entirely black frame. The production source before this pass is preserved as `index.before.html`. The fix is intentionally narrow: do not restore previously removed expensive work unless evidence points to it.
|
||||
|
||||
Reported state:
|
||||
|
||||
- bits: 273
|
||||
- re: `-15958919693715511752069626580195447581643016686947107473855747766272418724092005269`
|
||||
- im: `3884297345146583343999140717464554120497735725078805729405733239132420430333375719`
|
||||
- span: `35679807704506851074208413824041445358585910697267232113437882789120793`
|
||||
- palette: 0
|
||||
- cycle: 0.008
|
||||
- shift: 0.18
|
||||
- base iteration: 350, adaptive enabled, quality high
|
||||
|
||||
At 1108×1582, the adaptive initial target for this region is approximately 1536 iterations and the renderer takes the `fast-extended` path.
|
||||
|
||||
## Root cause 1: unsafe series jump
|
||||
|
||||
The integrated fourth-order series approximation was enabled for the reported view. The fast-reference worker selected:
|
||||
|
||||
- reference pixel ≈ `(69.5, 922.5)`
|
||||
- reference length: 1285
|
||||
- series jump: 773
|
||||
- fourth-order coefficient components up to approximately `2.35e38`, close to the maximum finite f32 magnitude
|
||||
|
||||
The series validity test checked truncation terms in unscaled orbit space, but did not bound the *intermediate scaled f32 polynomial products* used by WGSL. At screen-domain deltas away from the selected reference, `a4 * d^4` can overflow before the later `ldexp` rescales the term.
|
||||
|
||||
The reproducible probe in `experiments/reported-view/analyze.mjs` evaluates the exact worker output from the preserved pre-fix source. On a 32×32 viewport sample grid:
|
||||
|
||||
- pre-fix: 290 / 1024 series starts produce a non-finite fourth-order intermediate
|
||||
- fixed: 0 / 1024, because series acceleration is disabled
|
||||
|
||||
This is sufficient to generate `RANGE` failures and black unknown pixels. Separate CPU surrogate comparison also showed large escape-iteration deviations in this state, so merely adding an overflow clamp would not make the current jump trustworthy enough for publication.
|
||||
|
||||
### Fix
|
||||
|
||||
`SERIES_APPROXIMATION_ENABLED=false` now disables production series construction. The code remains present for future work, but the fast-reference worker emits `jump=0` and zero coefficients.
|
||||
|
||||
Re-enabling series should require at minimum:
|
||||
|
||||
1. viewport/reference-relative bounds on all scaled polynomial intermediates,
|
||||
2. a propagated truncation/rounding error bound carried into the perturbation path,
|
||||
3. a corpus test that checks false escapes, not only absolute approximation error,
|
||||
4. strict handling when the selected reference is off-center.
|
||||
|
||||
## Root cause 2: publishing numerical failures before repair
|
||||
|
||||
The FAST primary frame was colored and presented immediately after the primary perturbation pass, before `recoverNumericalCostOrdered()` repaired `REFERENCE_END`, `RANGE`, and other numerical failures.
|
||||
|
||||
`COLOR_WGSL` writes alpha zero for `FIELD_UNKNOWN`. Therefore an early provisional frame can appear:
|
||||
|
||||
- black when there is no usable history,
|
||||
- mixed with reprojected old colors when stable history exists,
|
||||
- as large geometric regions reflecting reference validity / numerical-failure domains rather than Mandelbrot geometry.
|
||||
|
||||
### Fix
|
||||
|
||||
For `fast-extended`:
|
||||
|
||||
- primary colorization is deferred (`deferPrimaryColor = deferColor || fastExtended`),
|
||||
- unresolved statistics are read,
|
||||
- numerical failures are repaired,
|
||||
- only then is the first provisional recolor/presentation allowed.
|
||||
|
||||
Direct rendering keeps its existing provisional behavior.
|
||||
|
||||
## What was *not* reverted
|
||||
|
||||
The previous FAST coverage-repair dispatch was not restored. The existing full-frame unknown reduction and final invariant still detect untouched reason-0 pixels, and prior A/B coverage tests found no holes. The reported state produced a concrete series failure mechanism, so restoring an O(pixels) repair pass would treat the symptom without addressing the cause.
|
||||
|
||||
Other removal-pass optimizations are unchanged.
|
||||
|
||||
## Verification
|
||||
|
||||
### Regression
|
||||
|
||||
`node tests/regression.mjs`
|
||||
|
||||
Result: PASS, 19 WGSL kernels.
|
||||
|
||||
Additional regression pins were added for:
|
||||
|
||||
- `SERIES_APPROXIMATION_ENABLED=false`
|
||||
- FAST primary-color deferral via `deferPrimaryColor=deferColor||fastExtended`
|
||||
|
||||
### Reported-view worker probe
|
||||
|
||||
`node experiments/reported-view/analyze.mjs`
|
||||
|
||||
See `results.json`.
|
||||
|
||||
Key result:
|
||||
|
||||
| Metric | Before | After |
|
||||
|---|---:|---:|
|
||||
| reference length | 1285 | 1285 |
|
||||
| series jump | 773 | 0 |
|
||||
| non-finite series-start samples | 290 / 1024 | 0 / 1024 |
|
||||
|
||||
### Existing integrated CPU benchmark
|
||||
|
||||
`node experiments/integrated_bench.mjs`
|
||||
|
||||
Completed successfully. The series section now reports jump 0 at all tested spans, while periodicity, sparse active state, multi-reference, and CPU fallback tests continue to run.
|
||||
|
||||
### Browser/WebGPU smoke
|
||||
|
||||
`tests/browser_smoke.mjs` could not run because its expected external Chromium remote-debug endpoint at `127.0.0.1:9333` was not available in this environment. This is an environment limitation, not a test assertion failure. Actual WebGPU visual confirmation of the supplied URL remains the most important local-device check.
|
||||
|
||||
## Build summary
|
||||
|
||||
See `build-summary.json`.
|
||||
|
||||
- before SHA-256: `e69b91ead027927d977a065d27d9e27629110122df20aa470eab877e23f77ac7`
|
||||
- fixed SHA-256: `c53c0dc4cb9fa2d2046aaa12ef03cc79c61a710b0c8db36dea1a87be2d9aedd8`
|
||||
- HTML size delta: +396 bytes (comments/guard and publication-order logic)
|
||||
|
||||
The WGSL kernels themselves were not changed in this pass, so kernel hashes remain pinned to the prior 19-kernel set.
|
||||
8
docs/reported-view-bugfix/README.md
Normal file
8
docs/reported-view-bugfix/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Reported-view bugfix artifacts
|
||||
|
||||
- `IMPLEMENTATION.md` — diagnosis, changes, verification, and limitations.
|
||||
- `results.json` — reproducible before/after fast-reference worker analysis for the reported view.
|
||||
- `integrated-bench.json` — existing CPU-side integrated benchmark after the fix.
|
||||
- `build-summary.json` — file sizes and SHA-256 hashes.
|
||||
- `index.diff` — exact production source diff.
|
||||
- `index.before.html` — preserved pre-fix production source.
|
||||
9
docs/reported-view-bugfix/build-summary.json
Normal file
9
docs/reported-view-bugfix/build-summary.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"beforeBytes": 224559,
|
||||
"afterBytes": 224955,
|
||||
"deltaBytes": 396,
|
||||
"beforeGzip": 57744,
|
||||
"afterGzip": 57958,
|
||||
"beforeSha256": "e69b91ead027927d977a065d27d9e27629110122df20aa470eab877e23f77ac7",
|
||||
"afterSha256": "c53c0dc4cb9fa2d2046aaa12ef03cc79c61a710b0c8db36dea1a87be2d9aedd8"
|
||||
}
|
||||
1970
docs/reported-view-bugfix/index.before.html
Normal file
1970
docs/reported-view-bugfix/index.before.html
Normal file
File diff suppressed because it is too large
Load diff
28
docs/reported-view-bugfix/index.diff
Normal file
28
docs/reported-view-bugfix/index.diff
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
--- /mnt/data/mandelbrot_bugfix_reported_view/docs/reported-view-bugfix/index.before.html 2026-09-29 10:14:53.060514133 +0000
|
||||
+++ /mnt/data/mandelbrot_bugfix_reported_view/index.html 2026-09-29 10:15:44.235560513 +0000
|
||||
@@ -973,8 +973,13 @@
|
||||
function cmuln(a,b){return[a[0]*b[0]-a[1]*b[1],a[0]*b[1]+a[1]*b[0]]}
|
||||
function cscale(a,k){return[a[0]*k,a[1]*k]}
|
||||
function clog2(a){const m=Math.hypot(a[0],a[1]);return m>0?Math.log2(m):-Infinity}
|
||||
+const SERIES_APPROXIMATION_ENABLED=false;
|
||||
function seriesForOrbit(rr,ri,refLen,span,bits,w,h){
|
||||
- if(!span||refLen<16)return{jump:0,coeffs:Array(8).fill(0),errorLog2:0};
|
||||
+ // Disabled after a reported deep-view failure: the current 4th-order jump
|
||||
+ // has no per-pixel propagated error bound and can overflow scaled f32
|
||||
+ // polynomial intermediates near viewport edges. Keep the implementation
|
||||
+ // available for future guarded reintroduction, but never publish it now.
|
||||
+ if(!SERIES_APPROXIMATION_ENABLED||!span||refLen<16)return{jump:0,coeffs:Array(8).fill(0),errorLog2:0};
|
||||
const aspect=Math.max(1e-9,h/Math.max(1,w)),logDelta=fixedLog2Abs(BigInt(span),bits)+Math.log2(Math.hypot(1,aspect));
|
||||
let a=Array.from({length:7},()=>[0,0]),best={jump:0,coeffs:Array(8).fill(0),errorLog2:0},limit=Math.min(refLen,4096);
|
||||
for(let n=0;n<limit;n++){
|
||||
@@ -1776,8 +1781,8 @@
|
||||
if(proof.pixels||historySeed){certified=await r.certifyInterior(snap,token,true,proof,historySeed);if(token!==state.token)return null;preserveInterior=certified>0||!!historySeed}
|
||||
if(certified===canvas.width*canvas.height){decision={backend:'exact-interior',fastExtended:false,reason:'exact-full-interior'};const stats={total:0,heuristicInterior:0,reasons:{errorBound:0,escapeUncertain:0,referenceEnd:0,rebaseGap:0,range:0,operationLimit:0}};state.unresolved=0;state.unknownReasons=stats.reasons;state.heuristicInterior=0;const painted=await r.recolor(token,iter);if(!painted||token!==state.token)return null;const frontier={stats,iter,minimumIter:iter,rounds:0,processed:0,escaped:0,converged:true,finiteBudgetComplete:true,policy:'exact-interior-proof',membershipCertified:true,applied:true};state.frameView=snap;state.fieldView={...snap,iter,frontier,w:canvas.width,h:canvas.height,fastExtended:false,backend:'exact-interior',referenceKey:'',referenceReused:false,complete:true,membershipCertified:true};r.captureColorSource(snap,state.colorAuto||state.recolorPending);r.presentFrame({scaleX:1,scaleY:1,offsetX:0,offsetY:0});return{decision,referenceReused:false,referencePixel:null,certified}}
|
||||
let fastCtx=null,referencePixel=null,referenceReused=false;if(fastExtended){const reuse=reusableFastReference(snap,iter,canvas.width,canvas.height);if(reuse){fastCtx=reuse.ctx;referencePixel=reuse.pixel;referenceReused=true}else{fastCtx=await fastRefs.request(snap,iter,canvas.width,canvas.height);if(token!==state.token)return null;referencePixel=referencePixelForSource(fastCtx.source,snap,canvas.width,canvas.height)}}
|
||||
- const deferColor=!!(r.historyReady&&canUseStableReprojection());state.deferNumericPublish=deferColor;const ok=await r.computeFrame(snap,iter,token,referencePixel,fastExtended,fastCtx,preserveInterior,!!historySeed,deferColor);if(!ok||token!==state.token){state.deferNumericPublish=false;return null}if(!deferColor&&!await presentProvisional(r,snap,iter,token,true)){state.deferNumericPublish=false;return null}
|
||||
- let stats=await r.readUnresolvedStats();if(token!==state.token)return null;if(fastExtended){const initialFailed=numericalFailureCount(stats);if(initialFailed>0){const ordered=await recoverNumericalCostOrdered(r,snap,iter,token,stats);if(!ordered||token!==state.token){state.deferNumericPublish=false;return null}stats=ordered.stats}state.unresolved=stats.total;state.unknownReasons=stats.reasons;state.heuristicInterior=stats.heuristicInterior||0;if(initialFailed>0&&!deferColor){const painted=await r.recolor(token,iter);if(!painted||token!==state.token){state.deferNumericPublish=false;return null}}}else{state.unresolved=stats.total;state.unknownReasons=stats.reasons}
|
||||
+ const deferColor=!!(r.historyReady&&canUseStableReprojection()),deferPrimaryColor=deferColor||fastExtended;state.deferNumericPublish=deferColor;const ok=await r.computeFrame(snap,iter,token,referencePixel,fastExtended,fastCtx,preserveInterior,!!historySeed,deferPrimaryColor);if(!ok||token!==state.token){state.deferNumericPublish=false;return null}if(!fastExtended&&!deferColor&&!await presentProvisional(r,snap,iter,token,true)){state.deferNumericPublish=false;return null}
|
||||
+ let stats=await r.readUnresolvedStats();if(token!==state.token)return null;if(fastExtended){const initialFailed=numericalFailureCount(stats);if(initialFailed>0){const ordered=await recoverNumericalCostOrdered(r,snap,iter,token,stats);if(!ordered||token!==state.token){state.deferNumericPublish=false;return null}stats=ordered.stats}state.unresolved=stats.total;state.unknownReasons=stats.reasons;state.heuristicInterior=stats.heuristicInterior||0;if(!deferColor&&!await presentProvisional(r,snap,iter,token,true)){state.deferNumericPublish=false;return null}}else{state.unresolved=stats.total;state.unknownReasons=stats.reasons}
|
||||
const frontier=await refinePixelFrontier(r,snap,iter,token,{total:state.unresolved,reasons:{...(state.unknownReasons||{})}},certified);if(!frontier||token!==state.token){state.deferNumericPublish=false;return null}const effectiveIter=frontier.iter;if(!frontier.converged)throw new Error('全画素反復が安全上限 '+PIXEL_FRONTIER_MAX_ITER+' までに収束しなかったためframeを公開しません');if(frontier.applied&&!deferColor){const painted=await r.recolor(token,effectiveIter);if(!painted||token!==state.token){state.deferNumericPublish=false;return null}}if(deferColor){const painted=await r.recolor(token,effectiveIter);if(!painted||token!==state.token){state.deferNumericPublish=false;return null}}
|
||||
const remaining=numericalFailureCount({reasons:state.unknownReasons||{}}),unprocessed=Math.max(0,state.unresolved-remaining-(state.unknownReasons?.operationLimit||0));if(unprocessed)throw new Error('未計算画素が '+unprocessed+' px 残っています');if(remaining>0)throw new Error('画素精度補修後も数値未確定が '+remaining+' px 残ったためframeを公開しません');state.frameView=snap;state.fieldView={...snap,iter:effectiveIter,frontier,w:canvas.width,h:canvas.height,fastExtended,backend:frontier.applied?'pixel-frontier-'+decision.backend:decision.backend,referenceKey:fastCtx?.key||'',referenceReused,complete:true,membershipCertified:frontier.membershipCertified};r.captureColorSource(snap,state.colorAuto||state.recolorPending);r.presentFrame({scaleX:1,scaleY:1,offsetX:0,offsetY:0});state.deferNumericPublish=false;return{decision,referenceReused,referencePixel,certified}
|
||||
}
|
||||
381
docs/reported-view-bugfix/integrated-bench.json
Normal file
381
docs/reported-view-bugfix/integrated-bench.json
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
{
|
||||
"date": "2026-09-29T10:16:09.784Z",
|
||||
"periodicity": {
|
||||
"w": 96,
|
||||
"h": 64,
|
||||
"maxIter": 8192,
|
||||
"oracleIter": 65536,
|
||||
"rows": [
|
||||
{
|
||||
"view": "overview",
|
||||
"productionBackend": "direct",
|
||||
"pixels": 6144,
|
||||
"baseWork": 884420,
|
||||
"heuristicWork": 133404,
|
||||
"workReduction": 0.849162162773343,
|
||||
"candidates": 96,
|
||||
"falseCandidates": 0,
|
||||
"baseLimit": 104,
|
||||
"heuristicLimit": 8,
|
||||
"diagnosticIfForcedDirect": null,
|
||||
"ms": 73.54993999999999
|
||||
},
|
||||
{
|
||||
"view": "period3-bulb",
|
||||
"productionBackend": "direct",
|
||||
"pixels": 6144,
|
||||
"baseWork": 50331648,
|
||||
"heuristicWork": 460156,
|
||||
"workReduction": 0.9908575216929117,
|
||||
"candidates": 6144,
|
||||
"falseCandidates": 0,
|
||||
"baseLimit": 6144,
|
||||
"heuristicLimit": 0,
|
||||
"diagnosticIfForcedDirect": null,
|
||||
"ms": 2914.8620730000002
|
||||
},
|
||||
{
|
||||
"view": "period3-core",
|
||||
"productionBackend": "direct",
|
||||
"pixels": 6144,
|
||||
"baseWork": 50331648,
|
||||
"heuristicWork": 442395,
|
||||
"workReduction": 0.991210401058197,
|
||||
"candidates": 6144,
|
||||
"falseCandidates": 0,
|
||||
"baseLimit": 6144,
|
||||
"heuristicLimit": 0,
|
||||
"diagnosticIfForcedDirect": null,
|
||||
"ms": 2906.390313
|
||||
},
|
||||
{
|
||||
"view": "seahorse-boundary",
|
||||
"productionBackend": "direct",
|
||||
"pixels": 6144,
|
||||
"baseWork": 410111,
|
||||
"heuristicWork": 410111,
|
||||
"workReduction": 0,
|
||||
"candidates": 0,
|
||||
"falseCandidates": 0,
|
||||
"baseLimit": 0,
|
||||
"heuristicLimit": 0,
|
||||
"diagnosticIfForcedDirect": null,
|
||||
"ms": 18.368617999999515
|
||||
},
|
||||
{
|
||||
"view": "seahorse-deep",
|
||||
"productionBackend": "fast-extended",
|
||||
"pixels": 6144,
|
||||
"baseWork": 36579905,
|
||||
"heuristicWork": 6167354,
|
||||
"workReduction": null,
|
||||
"candidates": 0,
|
||||
"falseCandidates": 0,
|
||||
"baseLimit": 4304,
|
||||
"heuristicLimit": 4304,
|
||||
"diagnosticIfForcedDirect": {
|
||||
"workReduction": 0.8314004916087125,
|
||||
"candidates": 4125,
|
||||
"falseCandidates": 12
|
||||
},
|
||||
"ms": 2205.721136
|
||||
},
|
||||
{
|
||||
"view": "exterior",
|
||||
"productionBackend": "direct",
|
||||
"pixels": 6144,
|
||||
"baseWork": 686432,
|
||||
"heuristicWork": 137063,
|
||||
"workReduction": 0.8003254510279241,
|
||||
"candidates": 72,
|
||||
"falseCandidates": 0,
|
||||
"baseLimit": 79,
|
||||
"heuristicLimit": 7,
|
||||
"diagnosticIfForcedDirect": null,
|
||||
"ms": 38.21316800000022
|
||||
}
|
||||
],
|
||||
"note": "Production DIRECT_F32 rule with two attracting-cycle confirmations. FAST/perturbation periodic early-stop is intentionally disabled."
|
||||
},
|
||||
"series": {
|
||||
"rows": [
|
||||
{
|
||||
"span": 0.00001,
|
||||
"jump": 0,
|
||||
"errorLog2": 0,
|
||||
"maxAbsError": 0,
|
||||
"maxRelativeError": 0,
|
||||
"refLen": 1200,
|
||||
"buildMs": 9.239160999999513
|
||||
},
|
||||
{
|
||||
"span": 1e-7,
|
||||
"jump": 0,
|
||||
"errorLog2": 0,
|
||||
"maxAbsError": 0,
|
||||
"maxRelativeError": 0,
|
||||
"refLen": 1200,
|
||||
"buildMs": 6.685975000000326
|
||||
},
|
||||
{
|
||||
"span": 1e-9,
|
||||
"jump": 0,
|
||||
"errorLog2": 0,
|
||||
"maxAbsError": 0,
|
||||
"maxRelativeError": 0,
|
||||
"refLen": 1200,
|
||||
"buildMs": 7.990567000000738
|
||||
},
|
||||
{
|
||||
"span": 1e-11,
|
||||
"jump": 0,
|
||||
"errorLog2": 0,
|
||||
"maxAbsError": 0,
|
||||
"maxRelativeError": 0,
|
||||
"refLen": 1200,
|
||||
"buildMs": 7.234717999999702
|
||||
}
|
||||
],
|
||||
"note": "Production fast-reference worker output. Error compares packed 4th-order series against direct perturbation over 8 screen-domain deltas."
|
||||
},
|
||||
"sparseActiveState": {
|
||||
"rows": [
|
||||
{
|
||||
"preset": "fast",
|
||||
"pixels": 524288,
|
||||
"activeRatio": 1,
|
||||
"capacity": 524288,
|
||||
"oldBytes": 23068672,
|
||||
"newBytes": 23134208,
|
||||
"reduction": -0.0028409090909091717
|
||||
},
|
||||
{
|
||||
"preset": "fast",
|
||||
"pixels": 524288,
|
||||
"activeRatio": 0.25,
|
||||
"capacity": 164096,
|
||||
"oldBytes": 23068672,
|
||||
"newBytes": 7285760,
|
||||
"reduction": 0.6841708096590908
|
||||
},
|
||||
{
|
||||
"preset": "fast",
|
||||
"pixels": 524288,
|
||||
"activeRatio": 0.1,
|
||||
"capacity": 65792,
|
||||
"oldBytes": 23068672,
|
||||
"newBytes": 2960384,
|
||||
"reduction": 0.8716708096590909
|
||||
},
|
||||
{
|
||||
"preset": "fast",
|
||||
"pixels": 524288,
|
||||
"activeRatio": 0.01,
|
||||
"capacity": 6810,
|
||||
"oldBytes": 23068672,
|
||||
"newBytes": 365176,
|
||||
"reduction": 0.9841700467196378
|
||||
},
|
||||
{
|
||||
"preset": "standard",
|
||||
"pixels": 1048576,
|
||||
"activeRatio": 1,
|
||||
"capacity": 1048576,
|
||||
"oldBytes": 46137344,
|
||||
"newBytes": 46268416,
|
||||
"reduction": -0.0028409090909091717
|
||||
},
|
||||
{
|
||||
"preset": "standard",
|
||||
"pixels": 1048576,
|
||||
"activeRatio": 0.25,
|
||||
"capacity": 327936,
|
||||
"oldBytes": 46137344,
|
||||
"newBytes": 14560256,
|
||||
"reduction": 0.6844149502840908
|
||||
},
|
||||
{
|
||||
"preset": "standard",
|
||||
"pixels": 1048576,
|
||||
"activeRatio": 0.1,
|
||||
"capacity": 131328,
|
||||
"oldBytes": 46137344,
|
||||
"newBytes": 5909504,
|
||||
"reduction": 0.8719149502840909
|
||||
},
|
||||
{
|
||||
"preset": "standard",
|
||||
"pixels": 1048576,
|
||||
"activeRatio": 0.01,
|
||||
"capacity": 13364,
|
||||
"oldBytes": 46137344,
|
||||
"newBytes": 719088,
|
||||
"reduction": 0.9844141873446378
|
||||
},
|
||||
{
|
||||
"preset": "high",
|
||||
"pixels": 4194304,
|
||||
"activeRatio": 1,
|
||||
"capacity": 4194304,
|
||||
"oldBytes": 184549376,
|
||||
"newBytes": 185073664,
|
||||
"reduction": -0.0028409090909091717
|
||||
},
|
||||
{
|
||||
"preset": "high",
|
||||
"pixels": 4194304,
|
||||
"activeRatio": 0.25,
|
||||
"capacity": 1310976,
|
||||
"oldBytes": 184549376,
|
||||
"newBytes": 58207232,
|
||||
"reduction": 0.6845980557528408
|
||||
},
|
||||
{
|
||||
"preset": "high",
|
||||
"pixels": 4194304,
|
||||
"activeRatio": 0.1,
|
||||
"capacity": 524544,
|
||||
"oldBytes": 184549376,
|
||||
"newBytes": 23604224,
|
||||
"reduction": 0.8720980557528409
|
||||
},
|
||||
{
|
||||
"preset": "high",
|
||||
"pixels": 4194304,
|
||||
"activeRatio": 0.01,
|
||||
"capacity": 52685,
|
||||
"oldBytes": 184549376,
|
||||
"newBytes": 2842428,
|
||||
"reduction": 0.9845980080691251
|
||||
}
|
||||
],
|
||||
"note": "Production allocation: 32 B state + 2x4 B queues + 4 B pixel map per active slot, plus 1-bit membership bitmap. Capacity adds 25% + 256 headroom."
|
||||
},
|
||||
"multiReference": {
|
||||
"maxGuidedPasses": 3,
|
||||
"w": 64,
|
||||
"h": 40,
|
||||
"maxIter": 1200,
|
||||
"rows": [
|
||||
{
|
||||
"view": "seahorse-1e-5",
|
||||
"grid": 1,
|
||||
"references": 1,
|
||||
"total": 2560,
|
||||
"refEnd": 0,
|
||||
"mismatchClass": 3,
|
||||
"mismatchIter": 155,
|
||||
"mismatchRate": 0.001171875
|
||||
},
|
||||
{
|
||||
"view": "seahorse-1e-5",
|
||||
"grid": 2,
|
||||
"references": 5,
|
||||
"total": 2560,
|
||||
"refEnd": 0,
|
||||
"mismatchClass": 3,
|
||||
"mismatchIter": 155,
|
||||
"mismatchRate": 0.001171875
|
||||
},
|
||||
{
|
||||
"view": "seahorse-1e-5",
|
||||
"grid": 3,
|
||||
"references": 9,
|
||||
"total": 2560,
|
||||
"refEnd": 0,
|
||||
"mismatchClass": 3,
|
||||
"mismatchIter": 155,
|
||||
"mismatchRate": 0.001171875
|
||||
},
|
||||
{
|
||||
"view": "seahorse-1e-7",
|
||||
"grid": 1,
|
||||
"references": 1,
|
||||
"total": 2560,
|
||||
"refEnd": 0,
|
||||
"mismatchClass": 66,
|
||||
"mismatchIter": 74,
|
||||
"mismatchRate": 0.02578125
|
||||
},
|
||||
{
|
||||
"view": "seahorse-1e-7",
|
||||
"grid": 2,
|
||||
"references": 5,
|
||||
"total": 2560,
|
||||
"refEnd": 0,
|
||||
"mismatchClass": 37,
|
||||
"mismatchIter": 41,
|
||||
"mismatchRate": 0.014453125
|
||||
},
|
||||
{
|
||||
"view": "seahorse-1e-7",
|
||||
"grid": 3,
|
||||
"references": 9,
|
||||
"total": 2560,
|
||||
"refEnd": 0,
|
||||
"mismatchClass": 50,
|
||||
"mismatchIter": 67,
|
||||
"mismatchRate": 0.01953125
|
||||
}
|
||||
],
|
||||
"note": "CPU f32 perturbation locality surrogate. Production selects up to three additional references from failure clusters instead of fixed grids."
|
||||
},
|
||||
"cpuFallback": {
|
||||
"w": 640,
|
||||
"h": 360,
|
||||
"iter": 350,
|
||||
"threads": 4,
|
||||
"one": {
|
||||
"medianMs": 92.0384610000001,
|
||||
"times": [
|
||||
92.0384610000001,
|
||||
92.44781700000021,
|
||||
93.7905269999992,
|
||||
88.7301699999989,
|
||||
99.79122599999937,
|
||||
82.51707699999861,
|
||||
82.31785099999979
|
||||
],
|
||||
"work": 2658084,
|
||||
"escaped": 176648,
|
||||
"operationLimit": 3394,
|
||||
"heuristicInterior": 1654
|
||||
},
|
||||
"multi": {
|
||||
"medianMs": 35.13209400000051,
|
||||
"times": [
|
||||
90.21825200000058,
|
||||
47.29191700000047,
|
||||
35.13209400000051,
|
||||
34.57788200000141,
|
||||
32.72723099999894,
|
||||
35.70634799999971,
|
||||
32.441507999999885
|
||||
],
|
||||
"work": 2658084,
|
||||
"escaped": 176648,
|
||||
"operationLimit": 3394,
|
||||
"heuristicInterior": 1654
|
||||
},
|
||||
"speedup": 2.6197829540134663,
|
||||
"workMatch": true,
|
||||
"escapedMatch": true,
|
||||
"operationLimitMatch": true,
|
||||
"heuristicMatch": true,
|
||||
"note": "Production CPU fallback Worker source executed under node:worker_threads shim."
|
||||
},
|
||||
"sourceAudit": {
|
||||
"heuristicClass": true,
|
||||
"strictDisablesPeriodicity": true,
|
||||
"seriesBinding": true,
|
||||
"compactPixelMap": true,
|
||||
"multiReferencePasses": true,
|
||||
"cpuFallback": true
|
||||
},
|
||||
"environment": {
|
||||
"node": "v22.16.0",
|
||||
"cpus": 5,
|
||||
"platform": "linux",
|
||||
"arch": "x64"
|
||||
}
|
||||
}
|
||||
68
docs/reported-view-bugfix/results.json
Normal file
68
docs/reported-view-bugfix/results.json
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"view": {
|
||||
"bits": 273,
|
||||
"re": "-15958919693715511752069626580195447581643016686947107473855747766272418724092005269",
|
||||
"im": "3884297345146583343999140717464554120497735725078805729405733239132420430333375719",
|
||||
"span": "35679807704506851074208413824041445358585910697267232113437882789120793",
|
||||
"width": 1108,
|
||||
"height": 1582,
|
||||
"iter": 1536
|
||||
},
|
||||
"before": {
|
||||
"sha256": "e69b91ead027927d977a065d27d9e27629110122df20aa470eab877e23f77ac7",
|
||||
"targetBits": 96,
|
||||
"refLen": 1285,
|
||||
"selectionEscape": 1285,
|
||||
"referencePixel": {
|
||||
"x": 69.5,
|
||||
"y": 922.4999999999999
|
||||
},
|
||||
"series": {
|
||||
"jump": 773,
|
||||
"coeffs": [
|
||||
40452732,
|
||||
196035184,
|
||||
-3191889952015122400,
|
||||
2277982484077478000,
|
||||
-7.321407650201077e+27,
|
||||
3.7598990810493897e+28,
|
||||
-2.345861669341488e+38,
|
||||
2.300172499347822e+38
|
||||
],
|
||||
"errorLog2": -27.69705218182358
|
||||
},
|
||||
"nonFiniteSeriesStartSamples": 290,
|
||||
"seriesSampleCount": 1024
|
||||
},
|
||||
"after": {
|
||||
"sha256": "c53c0dc4cb9fa2d2046aaa12ef03cc79c61a710b0c8db36dea1a87be2d9aedd8",
|
||||
"targetBits": 96,
|
||||
"refLen": 1285,
|
||||
"selectionEscape": 1285,
|
||||
"referencePixel": {
|
||||
"x": 69.5,
|
||||
"y": 922.4999999999999
|
||||
},
|
||||
"series": {
|
||||
"jump": 0,
|
||||
"coeffs": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"errorLog2": 0
|
||||
},
|
||||
"nonFiniteSeriesStartSamples": 0,
|
||||
"seriesSampleCount": 1024
|
||||
},
|
||||
"checks": {
|
||||
"baselineSeriesActive": true,
|
||||
"baselineHasNonFiniteSeriesStarts": true,
|
||||
"patchedSeriesDisabled": true
|
||||
}
|
||||
}
|
||||
47
experiments/reported-view/analyze.mjs
Normal file
47
experiments/reported-view/analyze.mjs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import vm from 'node:vm';
|
||||
import {createHash} from 'node:crypto';
|
||||
|
||||
const beforeUrl=new URL('../../docs/reported-view-bugfix/index.before.html',import.meta.url);
|
||||
const afterUrl=new URL('../../index.html',import.meta.url);
|
||||
const view={
|
||||
bits:273,
|
||||
re:'-15958919693715511752069626580195447581643016686947107473855747766272418724092005269',
|
||||
im:'3884297345146583343999140717464554120497735725078805729405733239132420430333375719',
|
||||
span:'35679807704506851074208413824041445358585910697267232113437882789120793',
|
||||
width:1108,height:1582,iter:1536,
|
||||
};
|
||||
|
||||
function extract(html){return [...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1])}
|
||||
function appInternal(html){
|
||||
const scripts=extract(html);const kc={};vm.runInNewContext(scripts[0],kc);const kernels=kc.MANDEL_WEBGPU_KERNELS;
|
||||
const element={width:view.width,height:view.height,clientWidth:view.width,clientHeight:view.height,style:{},hidden:false,value:'',textContent:'',disabled:false,addEventListener(){},classList:{toggle(){}},setAttribute(){},removeAttribute(){},showModal(){},close(){},toBlob(){},getBoundingClientRect(){return{left:0,top:0,width:view.width,height:view.height}}};
|
||||
const context={MANDEL_WEBGPU_KERNELS:kernels,document:{querySelector:()=>element,getElementById:()=>element,documentElement:{classList:{toggle(){}}},body:{classList:{toggle(){}},appendChild(){}}},navigator:{hardwareConcurrency:4,deviceMemory:8},location:{search:'',hash:''},history:{replaceState(){}},performance,URLSearchParams,URL:{createObjectURL:()=>`blob:test`,revokeObjectURL(){}},TextEncoder,Blob:class{},console,addEventListener(){},requestAnimationFrame:()=>1,cancelAnimationFrame(){},setTimeout,clearTimeout,setInterval,clearInterval,innerWidth:view.width,innerHeight:view.height,devicePixelRatio:1,localStorage:{getItem(){return null},setItem(){},removeItem(){}}};
|
||||
const cut=scripts[1].indexOf('// ── boot / teardown');
|
||||
const code=scripts[1].slice(0,cut)+`\nglobalThis.internal={fastReferenceWorkerSource,pixelPrecisionBits};})();`;
|
||||
vm.runInNewContext(code,context);return context.internal;
|
||||
}
|
||||
function runWorker(source,message){let result;const wc={self:{},postMessage:r=>{result=r},performance};vm.runInNewContext(source,wc);wc.self.onmessage({data:message});if(!result)throw new Error('worker returned nothing');if(result.type==='error')throw new Error(result.error);return result}
|
||||
function bitLen(v){v=v<0n?-v:v;return v===0n?0:v.toString(2).length}
|
||||
function fixedLog2Abs(v,bits){v=v<0n?-v:v;if(v===0n)return-Infinity;const bl=bitLen(v),take=Math.min(53,bl),sh=bl-take,top=Number(v>>BigInt(sh));return Math.log2(top)+sh-bits}
|
||||
function fixedRatio(a,b){if(!b||!a)return 0;let neg=a<0n;if(neg)a=-a;const q=(a<<52n)/b,v=Number(q)/4503599627370496;return neg?-v:v}
|
||||
function spanMantExp(span,bits){const l=fixedLog2Abs(span,bits),exp=Math.floor(l);return{mant:Math.pow(2,l-exp),exp}}
|
||||
function complexMul(ar,ai,br,bi){return [Math.fround(Math.fround(ar*br)-Math.fround(ai*bi)),Math.fround(Math.fround(ar*bi)+Math.fround(ai*br))]}
|
||||
function nonFiniteSeriesStarts(series,refX,refY,spanMant,width,height,cols=32,rows=32){
|
||||
if(!series?.jump)return 0;let bad=0;const c=series.coeffs.map(Math.fround);
|
||||
for(let jy=0;jy<rows;jy++)for(let ix=0;ix<cols;ix++){
|
||||
const gx=(ix+.5)*width/cols,gy=(jy+.5)*height/rows,dx=Math.fround((gx-refX)/width),dy=Math.fround((refY-gy)/width),d=[Math.fround(spanMant*dx),Math.fround(spanMant*dy)];
|
||||
const d2=complexMul(d[0],d[1],d[0],d[1]),d4=complexMul(d2[0],d2[1],d2[0],d2[1]);
|
||||
const t4=complexMul(c[6],c[7],d4[0],d4[1]);
|
||||
if(!Number.isFinite(t4[0])||!Number.isFinite(t4[1]))bad++;
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
async function analyze(path){
|
||||
const html=await fs.readFile(path,'utf8'),a=appInternal(html);const snap={bits:view.bits,re:BigInt(view.re),im:BigInt(view.im),span:BigInt(view.span)};const targetBits=a.pixelPrecisionBits(snap,view.width,40);
|
||||
const result=runWorker(a.fastReferenceWorkerSource(),{type:'build',id:1,key:'reported-view',sourceBits:view.bits,targetBits,re:view.re,im:view.im,span:view.span,width:view.width,height:view.height,iter:view.iter});
|
||||
const refRe=BigInt(result.referenceRe),refIm=BigInt(result.referenceIm),span=BigInt(view.span),refX=view.width*.5+fixedRatio(refRe-BigInt(view.re),span)*view.width,refY=view.height*.5-fixedRatio(refIm-BigInt(view.im),span)*view.width,se=spanMantExp(span,view.bits);
|
||||
return {sha256:createHash('sha256').update(html).digest('hex'),targetBits,refLen:result.refLen,selectionEscape:result.selectionEscape,referencePixel:{x:refX,y:refY},series:result.series,nonFiniteSeriesStartSamples:nonFiniteSeriesStarts(result.series,refX,refY,se.mant,view.width,view.height),seriesSampleCount:32*32};
|
||||
}
|
||||
const before=await analyze(beforeUrl),after=await analyze(afterUrl);const output={view,before,after,checks:{baselineSeriesActive:before.series.jump>0,baselineHasNonFiniteSeriesStarts:before.nonFiniteSeriesStartSamples>0,patchedSeriesDisabled:after.series.jump===0&&after.nonFiniteSeriesStartSamples===0}};
|
||||
console.log(JSON.stringify(output,null,2));
|
||||
11
index.html
11
index.html
|
|
@ -973,8 +973,13 @@ function cadd(a,b){return[a[0]+b[0],a[1]+b[1]]}
|
|||
function cmuln(a,b){return[a[0]*b[0]-a[1]*b[1],a[0]*b[1]+a[1]*b[0]]}
|
||||
function cscale(a,k){return[a[0]*k,a[1]*k]}
|
||||
function clog2(a){const m=Math.hypot(a[0],a[1]);return m>0?Math.log2(m):-Infinity}
|
||||
const SERIES_APPROXIMATION_ENABLED=false;
|
||||
function seriesForOrbit(rr,ri,refLen,span,bits,w,h){
|
||||
if(!span||refLen<16)return{jump:0,coeffs:Array(8).fill(0),errorLog2:0};
|
||||
// Disabled after a reported deep-view failure: the current 4th-order jump
|
||||
// has no per-pixel propagated error bound and can overflow scaled f32
|
||||
// polynomial intermediates near viewport edges. Keep the implementation
|
||||
// available for future guarded reintroduction, but never publish it now.
|
||||
if(!SERIES_APPROXIMATION_ENABLED||!span||refLen<16)return{jump:0,coeffs:Array(8).fill(0),errorLog2:0};
|
||||
const aspect=Math.max(1e-9,h/Math.max(1,w)),logDelta=fixedLog2Abs(BigInt(span),bits)+Math.log2(Math.hypot(1,aspect));
|
||||
let a=Array.from({length:7},()=>[0,0]),best={jump:0,coeffs:Array(8).fill(0),errorLog2:0},limit=Math.min(refLen,4096);
|
||||
for(let n=0;n<limit;n++){
|
||||
|
|
@ -1776,8 +1781,8 @@ async function renderFinalFrame(r,snap,iter,token){
|
|||
if(proof.pixels||historySeed){certified=await r.certifyInterior(snap,token,true,proof,historySeed);if(token!==state.token)return null;preserveInterior=certified>0||!!historySeed}
|
||||
if(certified===canvas.width*canvas.height){decision={backend:'exact-interior',fastExtended:false,reason:'exact-full-interior'};const stats={total:0,heuristicInterior:0,reasons:{errorBound:0,escapeUncertain:0,referenceEnd:0,rebaseGap:0,range:0,operationLimit:0}};state.unresolved=0;state.unknownReasons=stats.reasons;state.heuristicInterior=0;const painted=await r.recolor(token,iter);if(!painted||token!==state.token)return null;const frontier={stats,iter,minimumIter:iter,rounds:0,processed:0,escaped:0,converged:true,finiteBudgetComplete:true,policy:'exact-interior-proof',membershipCertified:true,applied:true};state.frameView=snap;state.fieldView={...snap,iter,frontier,w:canvas.width,h:canvas.height,fastExtended:false,backend:'exact-interior',referenceKey:'',referenceReused:false,complete:true,membershipCertified:true};r.captureColorSource(snap,state.colorAuto||state.recolorPending);r.presentFrame({scaleX:1,scaleY:1,offsetX:0,offsetY:0});return{decision,referenceReused:false,referencePixel:null,certified}}
|
||||
let fastCtx=null,referencePixel=null,referenceReused=false;if(fastExtended){const reuse=reusableFastReference(snap,iter,canvas.width,canvas.height);if(reuse){fastCtx=reuse.ctx;referencePixel=reuse.pixel;referenceReused=true}else{fastCtx=await fastRefs.request(snap,iter,canvas.width,canvas.height);if(token!==state.token)return null;referencePixel=referencePixelForSource(fastCtx.source,snap,canvas.width,canvas.height)}}
|
||||
const deferColor=!!(r.historyReady&&canUseStableReprojection());state.deferNumericPublish=deferColor;const ok=await r.computeFrame(snap,iter,token,referencePixel,fastExtended,fastCtx,preserveInterior,!!historySeed,deferColor);if(!ok||token!==state.token){state.deferNumericPublish=false;return null}if(!deferColor&&!await presentProvisional(r,snap,iter,token,true)){state.deferNumericPublish=false;return null}
|
||||
let stats=await r.readUnresolvedStats();if(token!==state.token)return null;if(fastExtended){const initialFailed=numericalFailureCount(stats);if(initialFailed>0){const ordered=await recoverNumericalCostOrdered(r,snap,iter,token,stats);if(!ordered||token!==state.token){state.deferNumericPublish=false;return null}stats=ordered.stats}state.unresolved=stats.total;state.unknownReasons=stats.reasons;state.heuristicInterior=stats.heuristicInterior||0;if(initialFailed>0&&!deferColor){const painted=await r.recolor(token,iter);if(!painted||token!==state.token){state.deferNumericPublish=false;return null}}}else{state.unresolved=stats.total;state.unknownReasons=stats.reasons}
|
||||
const deferColor=!!(r.historyReady&&canUseStableReprojection()),deferPrimaryColor=deferColor||fastExtended;state.deferNumericPublish=deferColor;const ok=await r.computeFrame(snap,iter,token,referencePixel,fastExtended,fastCtx,preserveInterior,!!historySeed,deferPrimaryColor);if(!ok||token!==state.token){state.deferNumericPublish=false;return null}if(!fastExtended&&!deferColor&&!await presentProvisional(r,snap,iter,token,true)){state.deferNumericPublish=false;return null}
|
||||
let stats=await r.readUnresolvedStats();if(token!==state.token)return null;if(fastExtended){const initialFailed=numericalFailureCount(stats);if(initialFailed>0){const ordered=await recoverNumericalCostOrdered(r,snap,iter,token,stats);if(!ordered||token!==state.token){state.deferNumericPublish=false;return null}stats=ordered.stats}state.unresolved=stats.total;state.unknownReasons=stats.reasons;state.heuristicInterior=stats.heuristicInterior||0;if(!deferColor&&!await presentProvisional(r,snap,iter,token,true)){state.deferNumericPublish=false;return null}}else{state.unresolved=stats.total;state.unknownReasons=stats.reasons}
|
||||
const frontier=await refinePixelFrontier(r,snap,iter,token,{total:state.unresolved,reasons:{...(state.unknownReasons||{})}},certified);if(!frontier||token!==state.token){state.deferNumericPublish=false;return null}const effectiveIter=frontier.iter;if(!frontier.converged)throw new Error('全画素反復が安全上限 '+PIXEL_FRONTIER_MAX_ITER+' までに収束しなかったためframeを公開しません');if(frontier.applied&&!deferColor){const painted=await r.recolor(token,effectiveIter);if(!painted||token!==state.token){state.deferNumericPublish=false;return null}}if(deferColor){const painted=await r.recolor(token,effectiveIter);if(!painted||token!==state.token){state.deferNumericPublish=false;return null}}
|
||||
const remaining=numericalFailureCount({reasons:state.unknownReasons||{}}),unprocessed=Math.max(0,state.unresolved-remaining-(state.unknownReasons?.operationLimit||0));if(unprocessed)throw new Error('未計算画素が '+unprocessed+' px 残っています');if(remaining>0)throw new Error('画素精度補修後も数値未確定が '+remaining+' px 残ったためframeを公開しません');state.frameView=snap;state.fieldView={...snap,iter:effectiveIter,frontier,w:canvas.width,h:canvas.height,fastExtended,backend:frontier.applied?'pixel-frontier-'+decision.backend:decision.backend,referenceKey:fastCtx?.key||'',referenceReused,complete:true,membershipCertified:frontier.membershipCertified};r.captureColorSource(snap,state.colorAuto||state.recolorPending);r.presentFrame({scaleX:1,scaleY:1,offsetX:0,offsetY:0});state.deferNumericPublish=false;return{decision,referenceReused,referencePixel,certified}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ assert.match(kernels.DIRECT_F32_WGSL,/candPeriod/);
|
|||
assert.match(kernels.DIRECT_F32_WGSL,/FIELD_INTERIOR_HEURISTIC/);
|
||||
assert.doesNotMatch(kernels.FAST_PERTURB_WGSL,/pack_meta\(n,FIELD_INTERIOR_HEURISTIC\)/);
|
||||
assert.match(kernels.FAST_PERTURB_WGSL,/series_start/);
|
||||
assert.match(prodHtml,/SERIES_APPROXIMATION_ENABLED=false/);
|
||||
assert.match(prodHtml,/deferPrimaryColor=deferColor\|\|fastExtended/);
|
||||
assert.doesNotMatch(kernels.FAST_PERTURB_WGSL,/unknownOnly==2u/);
|
||||
assert.match(prodHtml,/knownInteriorViewportMayOverlap/);
|
||||
assert.match(prodHtml,/numericHistoryScaleEligible/);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue