-
Notifications
You must be signed in to change notification settings - Fork 0
Open Work Plan
Master execution plan rolling up every open item across the project's roadmap docs. Source roadmaps surveyed:
Docs/Performance-Roadmap.mdDocs/Lighting-FX-Roadmap.mdDocs/Fractal-Expansion-Roadmap.mdDocs/Technical/CalculatorGen-Roadmap.mdDocs/Technical/Performance-DevelopmentPlan.mdDocs/Technical/PHASE2_AVALONIA_MIGRATION.mdDocs/Technical/CrossPlatform-Roadmap.mdDocs/Technical/CrossPlatform-ImplementationPlan.mdDocs/Technical/UserBulb3D-DevelopmentPlan.mdDocs/Technical/UserBulbSandbox-DevPlan.mdDocs/Documentation-Plan.md
Strategy: ship-blockers first → cheap perf wins → feature lifts → polish. Critical path = Cross-Platform roadmap (whole roadmap blocks Linux/macOS launch).
Land first to (a) collect cheap CPU/IO wins, (b) give every later wave a measurement baseline, (c) shake out renderer hooks needed elsewhere.
| # | Item | Status |
|---|---|---|
| 0.1 | Perf T1.1 — strip new StackTrace() from Calculate
|
✅ Already shipped — Engine/Calculators/MandelbrotCalculator.cs:371 gated #if DEBUG
|
| 0.2 | Perf T1.2 — VSync toggle on DirectXRenderer.Render
|
✅ Already shipped — IFractalRenderer.VSync + Rendering.D3D/DirectXRenderer.cs:449,475
|
| 0.3 | Perf T1.3 — single-shot MemoryCopy fast path in UpdateTexture
|
✅ Already shipped — Rendering.D3D/DirectXRenderer.cs:397-415
|
| 0.4 | Perf T1.4 — EscapeTimeCalculator concrete-colormap dispatch |
✅ Already shipped — concrete switch on both DispatchByColorMap + DispatchByColorMapSimd (~21 cases each) |
| 0.5 | Visual-regression harness — --batch per-fractal SHA256 baseline |
✅ Tool shipped (Tools/VisualRegression/). Baseline recorded 2026-06-22 (0.5b) |
| 0.6 | Per-stage frame-time HUD microbar | ✅ Shipped — PostStage enum + StagePerf static hook + 5 Apply* sites wrapped + PerfStats.RecordStage ring + HUD micro-rows |
Re-survey note (2026-06-20): Repo geometry already split per Phase X.0 (Engine/, FracturingFog.App/, FracturingFog.Win/, Hosting/, Rendering.D3D/, Audio/, Audio.Win/, Rendering.Silk/, Rendering.Skia/). Update Wave 1 tracking after auditing what X.A/X.B/X.1/X.2/X.3 still need.
Exit: Release build, FPS up, no visual regression on the 8 stock regions.
Re-audit 2026-06-20: All code-side phases shipped on
feature/cross-platform-full. Outstanding items are manual smoke runs on real Linux/macOS hardware + two cleanup items (ToyDragWindow Avalonia rewrite, FfmpegSetupDialog Avalonia rewrite). CI builds green for every cross-platform assembly; release workflow drafts artifacts per RID (zip / AppImage / .app). NETSDK1150 publish blocker resolved.
| # | Phase | Item | Status |
|---|---|---|---|
| 1.1 | X.0 Slice 0.1 | Carve FracturingFog.Engine csproj (net10.0) |
✅ Shipped |
| 1.2 | X.0 Slice 0.2 | Carve FracturingFog.Audio csproj |
✅ Shipped (Audio/ + Win fragment Audio.Win/) |
| 1.3 | X.0 Slice 0.3 | Carve FracturingFog.Hosting csproj |
✅ Shipped |
| 1.4 | X.0 Slice 0.4 | Carve FracturingFog.Rendering.D3D Win fragment |
✅ Shipped (incl. MP4Writer.cs move) |
| 1.5 | X.0 Slice 0.5 | New FracturingFog.App exe target |
✅ Shipped |
| 1.6 | X.A | Image I/O SkiaSharp swap | ✅ Shipped — Engine.csproj comment confirms Slice A.2 + A.7. System.Drawing.Common dropped; engine uses System.Drawing.Primitives (BCL, x-plat) only. GDI tail moved to FracturingFog.Win.ImageExportGdi
|
| 1.7 | X.B | Audio backend abstraction | ✅ Shipped — Abstractions/Audio/IAudioCaptureBackend.cs + Audio/NoopAudioBackend.cs + Audio.Win/WindowsNAudioBackend.cs + Audio/AudioCaptureDriver.cs
|
| 1.8 | X.1 | Palette demotion | ✅ Shipped — PaletteBuilder.Lib.csproj TFM = net10.0, QuestPDF replaces PDFsharp-gdi, System.Drawing.Common dropped |
| 1.9 | X.2 | Video export — IVideoWriter
|
✅ Shipped — Engine/Imaging/IVideoWriter.cs + FfmpegVideoWriter.cs; MP4Writer.cs moved to Rendering.D3D Win fragment |
| 1.10 | X.3 | P/Invoke + IsOSPlatform sweep | ✅ Shipped — BatchEntry.cs / ServerEntry.cs / MandelbrotBench.cs / MainWindow.ToyDragWindow all gated OperatingSystem.IsWindows(). CA1416 clean. Linux/macOS Toy-mode drag covered by the Avalonia BeginMoveDrag path (see 1.C2); Win+DX retains the WM_NCLBUTTONDOWN trick as the fallback for the swap-chain HWND case. |
| 1.11 | X.4 | Bootstrap polish — --renderer CLI |
✅ Shipped — --renderer dx|silk|skia|auto in Program.cs:241; CI has Linux Wayland leg via weston --backend=headless
|
| 1.12 | X.5 | Compute fallback smoke on Apple Silicon | AcceleratorProbe CPU fallback). Open: per-RID device-kind smoke assert + manual run on real Apple Silicon |
| 1.13 | X.6 | Packaging | ✅ Shipped — 5 pubxml profiles under FracturingFog.App/Properties/PublishProfiles/; release.yml workflow zips Win + AppImage Linux + .app macOS, sha256-sums, drafts GH release |
| 1.14 | X.7 | Docs | ✅ Shipped — FEATURES.md (cross-platform overview), Docs/User/CrossPlatform-UserGuide.md, README; CrossPlatform-SmokeTests.md enumerates per-phase manual checks |
Remaining Wave 1 work (not blockers for code-complete):
| # | Item | Effort |
|---|---|---|
| 1.S1 | Run CrossPlatform-SmokeTests.md manual checks on real Ubuntu 24.04 (X11 + Wayland), macOS Sonoma arm64, Raspberry Pi OS arm64 |
1 d per host |
| 1.S2 | Tag a v0.7.0-rc1 to fire release.yml; triage drafted artifacts; verify install + launch on each host |
1 d |
| 1.C1 | Avalonia FfmpegSetupDialog rewrite — remove WinForms drag from cross-platform Hosting/ (currently WinForms shell only) |
✅ Shipped 2026-06-22 |
| 1.C2 | Toy-mode drag cross-platform — BeginMoveDrag(e) via InputSponge PointerPressed handles Linux/macOS + Windows-under-Skia. Win+DX retains the Win32 WM_NCLBUTTONDOWN fallback because the swap-chain HWND swallows Avalonia pointer events before the sponge sees them. |
✅ Shipped 2026-06-23 (S-X5) |
| 1.C3 | X.5 per-RID device-kind smoke — assert AcceleratorProbe.Chosen.Kind matches expectation in --batch --self-test
|
½ d |
| 0.5b | Wave 0.5 follow-up — dotnet run --project Tools/VisualRegression -- record to populate baseline.json
|
✅ Shipped 2026-06-22 |
Exit per CrossPlatform-Roadmap.md Definition of Done: install on Ubuntu /
Fedora / macOS 14 / Win11, launch self-contained, render Mandelbrot in 5 s,
pan/zoom/switch, export PNG, export MP4 (Win+Linux required), open
audio-reactive dialog without crash.
| # | Item | Lift |
|---|---|---|
| 2.1 | D-2.8 — SA orders 8 → 16 | 1 d |
| 2.2 | D-3.16 — Phoenix proper DE + perturbation (δ_prev + dprev/dc) | ✅ Shipped 2026-06-21 |
| 2.3 | D-6.26 — Save hot-loaded calc to permanent .cs
|
½ d |
| 2.4 | D-6.24 — Live equation preview (AST + dz/dc + SA flag as user types) | ✅ Shipped 2026-06-21 |
| 2.5 | D-5.20 — Progressive rendering ¼→½→full | ✅ Shipped 2026-06-22 |
| 2.6 | D-5.19 — Anti-aliasing 2×2/4×4 (Quality gate) | ✅ Shipped 2026-06-22 (broadened to alt calcs gated on SupportsZoomPan) |
| 2.7 | D-5.21 — TAA temporal accumulation | ✅ Shipped 2026-06-21 |
| 2.8 | D-6.23 — Equation cookbook + gallery | ✅ Shipped 2026-06-21 |
| 2.9 | D-6.25 — Animation: morph equations | ✅ Shipped 2026-06-21 |
| 2.10 | D-4.18 — DD-precision BLA tables | ✅ Shipped 2026-06-21 |
| 2.11 | D-4.17 — Octuple-double (OD) ref orbit — past 1e50 zoom | ✅ Shipped 2026-06-21; OD arithmetic fixed + re-enabled 2026-06-22 (op* rewrite + 23 xUnit OD parity tests in Server.Tests/OctupleDoubleTests.cs). UI navigation past 1e58 still pending — see status log |
| 2.12 | D-6.27 — GPU reference orbit (QD on GPU) | 🟡 Scaffold shipped 2026-06-22 (Hi-only kernel works on CUDA). QD upgrade + perf-win analysis deferred as non-blocking follow-on — toggle off by default, no other wave depends on it |
| 2.13 | D-7.29 — Roslyn source generator | ✅ Shipped 2026-06-22 |
| 2.14 | D-4.19 — QD δ-chain precision floor — fix pixelation at zoom 1e40–1e58 | ⚫ Closed obsolete 2026-07-05 — premise disproven by --qdfloorsweep (QD separates 128/128 pixels through 1e64; no arithmetic floor in the 1e40–1e58 band). Original report predates the 2.11 OD-arith fix + SM-1 iter-cap finding. Intent folded into SM-2 (rebasing). See status log. |
| 2.15 | D-4.20 — OD-aware UI navigation — populate CenterX4..X7 from pan/zoom |
✅ Shipped 2026-06-22 (FractalInputController.cs — 6 pan/zoom sites + OD pan-start cache + StoreOD helper) |
| # | Item |
|---|---|
| 3.1 | T2.1 — SIMD brightness/contrast |
| 3.2 | T2.2 — Suppress pre-overlay snapshot during video record |
| 3.3 | T2.3 — EscapeTimeCalculator SIMD inner loop (Mandelbrot/Julia/BurningShip/Tricorn/Multibrot) |
| 3.4 | T3.3 — non-temporal Avx.Store* writes |
| 3.5 | T3.2 — ref-orbit recycling across video frames |
| 3.6 | T3.1 ext — HLSL palette codegen for hand-written IColorMap; GPU ColorBuffer for orbit-aware themes |
| 3.7 | Finding D — Adaptive HE crossfade lerp |
| 3.8 | Pan/keyboard input fails at zoom ≥ 1e24 — QD-limb update in pan-zoom command pipeline |
| # | Item |
|---|---|
| 4.1 | Lighting-FX 21b GPU port — HDR DoF skewed blurs on ILGPU |
| 4.2 | Lighting-FX 16b GGX importance sampling per bounce |
| 4.3 | Lighting-FX — HDRI auto-preload on param change |
| 4.4 | Sandbox 3C — interpreter perf (opcode-flat dispatch or DynamicMethod IL emit) |
| 4.5 | Sandbox chain mode GPU dispatch |
| 4.6 | Sandbox Quat-mode Julia + numerical-Jacobian DE on GPU |
| 4.7 | UserBulb 3.4 — time global t + animate bar |
| 4.8 | UserBulb 3.7 — color drivers |
| 4.9 | UserBulb 3.9 — FOV / DoF / clip / SS + viewport orbit |
| 4.10 | UserBulb 3.6 — multi-equation chain w/ named outputs |
| 4.11 | UserBulb 3.10 — preset library seed |
| 4.12 | UserBulb 3.11 — marching-cubes mesh export OBJ/STL |
| 4.13 | UserBulb 3.12 — .fbulb import/export |
| 4.14 | UserBulb 3.5 — Julia mode Vec3 path |
| # | Item |
|---|---|
| 5.1 | Theme compatibility matrix audit (A.1–D.2 new families) |
| 5.2 | Region preset coverage audit |
| 5.3 | CalcGen reach verification (A.1/A.2/A.5/A.6 5-path) |
| 5.4 | Math help 2-level grouping (>25 sub-tabs) |
| 5.5 |
FEATURES.md "20+ families" → ~38; README badge counter |
| 5.6 | Allowlist negative tests for 19 new types |
| 5.7 | Headless visual-regression baseline (golden PNG per type) |
| 5.8 |
FractalParamsView.axaml per-type extract |
| 5.9 | B.2 KIFS new folds (Mandelbox-rot / Octahedron / Dodecahedron) |
| 5.10 | D.5 L-System 5 more presets |
| 5.11 | D.4 Flame next 8-16 Apophysis variations |
| 5.12 | B.4 Kleinian user-editable sphere list + Möbius composition + analytic DE |
| 5.13 | D.2 DLA cached-blit pan/zoom + multi-seed + sticky-coef |
| 5.14 | C.3 Bicomplex 2nd-slice-axis + split-complex variant |
| 5.15 | D.1 Apollonian sub-gasket filled rendering (low pri) |
| # | Item | Status |
|---|---|---|
| 6.1 | Multi-cluster spatial partitioning for perturbation rebase | ✅ Shipped 2026-06-23 — CalculatorGen/Templates/Calculator.template.cs carries MVP (Item 7) + guards A/B/C + multi-cluster D (16×16-cell occupancy grid + 8-conn BFS flood-fill) + cross-frame cache E (4-slot LRU). Multi-cluster + cache landed in commits 2912309 + bcea672; default flipped to UseClusterRebase = true this turn (AVX-2 perturbation lane parity reached, commits cea9cc8 + b0abc68). Legacy Engine/Calculators/MandelbrotCalculator.cs port filed as 6.1.f1 follow-up. |
| 6.1.f1 | Port cluster rebase pipeline (Item 7 MVP + A/B/C/D/E) into legacy Engine/Calculators/MandelbrotCalculator.cs — canonical Mandelbrot path glitched lanes currently fall straight to per-pixel HP-direct (ComputePixelOD/QD/HP). ~2-3 d: needs SP/DD/QD/OD precision-tier branches, 4 perturbation-path glitch-enqueue sites (scalar + PT4 + PT8 + PT8-512), OD-aware rebase orbit build (template only handles DD+QD). |
🟡 Deferred — non-blocking |
| # | Item | Status |
|---|---|---|
| 7.1 | Top-level Docs/_Index.md landing page for both audiences |
✅ Shipped 2026-06-23 — top-level router page routes by audience (User → User/_Index.md, Technical → Technical/_Index.md) plus project-wide roadmap quick-links. Root README.md + both sub-indices wired to point at the new landing page. |
Do not start without explicit user signal — current note: "user wants legacy intact."
| # | Item |
|---|---|
| 8.1 | Phase 2.3 G — delete MainForm.cs + Slideshow.cs + VideoZoom.cs + MainForm.resx + WinForms entry |
| 8.2 | Phase 2.1 deferred — move DirectXRenderer.cs to Rendering.D3D + HwndGpuSurface
|
| 8.3 | B4 — retire Views/ImagePaletteDialog.cs GdiToSkia bridge |
Track A (lead dev):
Wave 0 → Wave 1.1-1.5 → Wave 1.6-1.9 → Wave 1.10-1.14
Track B (second dev, after Wave 0):
Wave 2 CalcGen (independent files)
Track C (third dev, after Wave 0):
Wave 4 UserBulb + Sandbox (independent files)
Convergence after Wave 1:
Wave 3 perf → Wave 5 polish → Wave 6 → Wave 7
| Wave | Days | Notes |
|---|---|---|
| 0 | 3 → 0 | Shipped |
| 1 | 25-30 → ~4 | Re-audit: code shipped; cleanup + smoke only (1.S1/1.S2/1.C1-3/0.5b) |
| 2 | 20-25 | CalcGen high-impact |
| 3 | 10 | Perf Tier 2 + 3 tail |
| 4 | 15 | Lighting/FX + UserBulb |
| 5 | 10 | Fractal Expansion polish |
| 6 | 3 → 0 | Shipped in template (Wave 6 closeout); legacy port = 6.1.f1 deferred |
| 7 | 1 → 0 | Shipped 2026-06-23 |
| 8 (when greenlit) | 2 | WinForms retirement |
| Total | ~65 dev-days | Down from 90 after Wave 0+1 re-audit |
3 parallel tracks → ~9 cal weeks. Single dev → ~13 weeks.
| ID | Item | Status |
|---|---|---|
| SM-1 | Deep regions render solid when saved/auto MaxIterations < escape band |
🟡 Deferred 2026-07-05 — root-caused via --regionprobe: not a precision bug, purely iter-count (regions need 379–3940 iters; rendered under band → 100% in-set → flat). Fix = trace region-load iteration path (where a loaded region sets MaxIterations) and either raise the saved values or add auto-iter that climbs until in-set fraction stabilises. Verify with --regionprobe 20000 (all clean) vs --regionprobe 300 (all SOLID). |
| SM-3 | Status bar reports "done" while TAA refinement is still running | 🟡 Deferred 2026-07-05 — surfaced once rebasing went default-ON: only the first full-res sample (TaaSampleIndex==0) updates the status bar (S-X8, to avoid per-sample ms oscillation); pre-rebasing that sample was slow so status tracked the real work, now it finishes fast while ≤15 TAA continuations keep computing. Fix = keep "Calculating…" (or "Refining n/N") while continuations are pending and fire the authoritative FrameCompleted when TAA settles (_taaSampleCount >= taaMax). Touches the TAA/upload timing loop — needs the running GUI to verify. |
| SM-4 | Right-click selection-box outline redraws slowly at deep zoom | 🟡 Deferred 2026-07-05 — also a rebasing-default side effect: the overlay repaint (RepaintWithPostFx) shares _uploadGate with Calculate; rebasing lets deep frames complete and spawn TAA continuations that hold the gate, so overlay repaints block behind them. Real fix = decouple the overlay repaint from the calc gate (or suppress TAA continuations while a box-select drag is active). |
| SM-8 | Detail-warning bounced the status bar; render-context overlay; deep-zoom TAA no-op churn | 🟢 2026-07-06. (a) The SM-7 detail notice appended to the status bar wrapped and oscillated the panel height (image edge bounced) — removed from the status bar. (b) Added a render-context block to the perf HUD (ShowPerfHud): fractal type, centre, zoom, iter, reference-orbit escape/length, max-detail-zoom estimate, active toggles, and the detail-limit warning in yellow (#FFCC00). Host builds the lines (FractalRenderHost.BuildRenderContextOverlay), compositor draws them (CompositePerfHud gained contextLines + warningLine). Status bar stays simple. (c) Deep-zoom TAA no-op guard: RunOneTaaSample jitters only the DOUBLE CenterX/CenterY by ~scale, which rounds away once scale < centre ULP (~past zoom 1e10), so every continuation renders a byte-identical frame — 31 wasted passes that re-run the upload/HUD path (the "status flushing after render finished" at deep zoom). TryScheduleNextTaaSample now skips continuations there. Follow-ups: real deep-zoom TAA (SM-9); the E+64 status-flash was confirmed fixed by the user (SM-8c), and outline-zoom/pan-at-depth was resolved (SM-11 — navigation core proven sound). |
| SM-11 | Deep-zoom nav "close but not exact" / pan jitter past ~1e63 | 🟢 Resolved 2026-07-06 — navigation core is SOUND; residual is cosmetic. Input is provably exact (9.5e-15px). Both candidate fixes tested and NOT needed: SM-11a (DD reference/δ) — forced across all pixels, focus-err byte-identical (--navrepro scalar ddref), so never a precision issue; SM-11b (reference recycling during drag) — --panjitter shows the FRESH per-frame render is already reference-consistent during a drag (inter-frame Δiter ≈0-6/px at 2-40px steps), recycle vs fresh identical. The --navrepro 16px focus-err was a measurement artefact (frame B centres on the clicked point = its reference pixel). Residual "jumping" = ¼-res progressive drag preview + fast rainbow palette amplifying ≤6-iter differences — both cosmetic. Levers if pursued: ½-res preview floor at deep zoom; smoother/less-sensitive palette. Recycle plumbing (RecyclePreviewOrbit, AllowRecycleThisRender) kept, default OFF. Full trail in Docs/Deep-Zoom-Perturbation.md §5-6. New gate: --panjitter (artefact-free). |
| SM-9 | Deep-zoom TAA disabled (jitter below double ULP) — no sub-pixel AA past ~1e10 | 🟡 Deferred 2026-07-06 — TAA sub-pixel jitter is added to the double CenterX/CenterY; past ~1e10 it is lost, so the SM-8 guard now skips it (was silently producing identical samples). To restore anti-aliasing at depth, feed the Halton jitter through the OD centre limbs or the per-pixel dc offset (both carry precision below the double ULP) instead of the top double limb. |
| SM-7 | "Controls break past ~1e63" — actually the POINT's detail-depth limit | 🟢 Diagnosed + guarded 2026-07-06. User reported double-click missing / pan overshooting just past 1e63. NOT an input bug: the new --focusprobe (end-to-end render + double-click focus + patch-match) shows focus is pixel-perfect (0.00px) through 1e70 and --inputprobe extended to 1e70 shows 0.00px anchor drift. The real cause: past ~1e60 the RENDER collapses to a flat frame (frameDistinct 163→3→1, inSet 0%), so navigation has no visible structure to land on and reads as broken. Root: perturbation resolves detail only while the pixel offset δ (∝1/zoom), amplified by ∏|2·Zₙ| over the reference orbit, reaches O(1). The user's centre's orbit escapes at iter 4060 ⇒ only ~62 decades of amplification ⇒ intrinsic depth floor ~1e62 for THAT point (matched the collapse exactly). Property of the location, not precision — to go deeper you must recentre on a longer-orbit point. Guard: MandelbrotCalculator.MaxUsefulZoomLog10 (Σlog₁₀|2·Zₙ| to the |Z|=2 crossing, +∞ if bounded) computed free during every ref-orbit build (OD/QD/DD), carried on RenderFrameInfo, and surfaced as a status-bar notice ("⚠ detail limit for this point (~1eNN) — recenter on visible structure to zoom deeper") when the live zoom passes it. Gate: --focusprobe (maxUseful≈1e62 vs collapse 1e62). |
| SM-5 | Extreme-tier zoom wall (5e58 → 1e63 → 1e100) | 🟢 Superseded by SM-6 — cap now 1e100 on 2026-07-05. User hit a hard wall at exactly 5e58 (top-tier QualityPreset.Extreme.ZoomMax); wheel/outline zoom stopped. First raised to 1e63 assuming a QD floor — but the OD ref orbit + OD per-pixel coords already engage above 1e50 (ODZoomThreshold), so the true limiter was the OD path, not QD (see SM-6). Cap now 1e100. Comments in MandelbrotCalculator (135, 2040) corrected. |
| SM-6 | Zoom beyond ~1e64 needs the OD coordinate path fixed | 🟢 Fixed on 2026-07-05 — cap raised to 1e100. The 1e64 wall was NOT the QD ref-orbit floor (OD ref orbit + OD per-pixel coords already run above 1e50). Real bug: OD.FromCenterOffset added the pixel offset via the OD+OD sloppy operator, whose 3-level carry cascade only propagates a residual ~3 limbs; a deep offset landing at limb X4+ (|off| ~1e-64, zoom > ~1e64) got parked against X3 and rounded away → every pixel collapsed to the centre (the --qdfloorsweep OD column's 37/128 at 1e66 — real, not a probe artifact). Fix = route through the OD+double full-cascade operator ((center + offHi) + offLo), which places the offset at its true limb through all 8 limbs. --qdfloorsweep now shows OD 128/128 through 1e120 (top of sweep). Raised Extreme.ZoomMax = 1e100 (20 decades under the measured-clean coord floor; OD ref-orbit accuracy is the soft limit past there, chaos-dominated as on QD). Regression test OctupleDoubleTests.FromCenterOffset_DeepOffset_SeparatesAdjacentPixels (128 distinct + pixel-step recovered at zoom 1e70). Remaining: direct OD ref-orbit accuracy at 1e70+ over ~100k iters is unmeasured (no 16-double ground truth) — a future --odorbitprobe could cross-check OD-vs-QD at a shared depth. |
| SM-2 | Deep-QD extreme-region render is slow (minutes at full window × AA16) | 🟢 Rebasing shipped opt-in 2026-07-05 — --rebaseprobe PASS. Root cause: SIMD PT δ-loop bails ~1e30 (glitch check z==Z && δ!=0), so deep frames ran per-pixel direct-QD ComputePixelQD. Fix: ComputePixelPTRebased (Zhuoran rebasing) — ref index m tracked separately, z = Z[m] + δ reconstructed, rebase δ := z; m := 0 when |z| < |δ| or ref exhausted. Replaces the QD/OD/HP glitch fallback (all 6 sites) when AllowPtRebasing on. Stays in double — a DD δ/ref/dc variant gave byte-identical iteration counts (precision is not the limiter; the ~50 % divergence from a QD render is chaotic sensitivity the QD path shares with itself, QDself ≈ reb-vs-QD). Probe: 91–142× speedup, rebasing tracks QD within 0.05 % of QD's own SA-off/SA-on reproducibility. Default flipped ON 2026-07-05 after user confirmed the speed win; the debug toggle is now "Bypass Rebasing" (checked = off) to A/B against legacy QD/OD. Later mitigations: SIMD rebasing (reclaim vector throughput), lower AA for preview, adaptive iter cap (ties to SM-1). |
Session intent (2026-07-05 → 07-06): work through the deferred deep-zoom /
navigation / render items in the Mandelbrot explorer. That thread is now largely
closed — SM-2 (rebasing), SM-5/6 (zoom cap → 1e100), SM-7 (detail floor + guard),
SM-8 (overlay + status/flash), SM-10 (nav tooling), SM-11 (nav proven sound) all
🟢. The single biggest takeaway for any future deep-zoom work: the navigation
input is provably exact to 1e70 and the deep render is reference-consistent —
do not re-investigate either. Start from Docs/Deep-Zoom-Perturbation.md
(model, tools, rules) before touching the deep-zoom render or input path.
Everything still OPEN, each with enough context to act without re-deriving:
| ID | What & why | Files / symbols | Verify with | Concrete next step |
|---|---|---|---|---|
| SM-1 | Loaded/auto deep regions render SOLID — pure iteration-count, not precision (region needs 379–3940 iters; rendered under band ⇒ 100 % in-set ⇒ flat). | Region-load path that assigns MandelbrotCalculator.MaxIterations / ViewState.PreferredIterations (see FractalRenderHost.ApplyView iter block); region store deserialize. |
--regionprobe 20000 (all clean) vs --regionprobe 300 (all SOLID). |
Trace where a loaded region sets MaxIterations; either raise saved values or add auto-iter that climbs until the in-set fraction stabilises. Ties to adaptive-iter (SM-2 tail). |
| SM-3 | Status bar shows "done" while TAA continuations still refine. Largely masked at deep zoom by SM-8c (continuations skipped past ~1e10), still visible at shallow/mid zoom. |
FractalRenderHost FrameCompleted gate (TaaSampleIndex==0, ~line 1717), TryScheduleNextTaaSample, _taaSampleCount/taaMax; consumer MainViewModel.OnFrameCompleted. |
Running GUI — watch status vs TAA settle at ~1e6 zoom (TAA active). | Keep "Calculating…/Refining n/N" while continuations pending; fire authoritative FrameCompleted when _taaSampleCount >= taaMax. GUI timing verify. |
| SM-4 | Right-click selection-box outline redraws slowly at deep zoom — overlay repaint shares _uploadGate with Calculate; TAA continuations hold the gate. |
RepaintWithPostFx, _uploadGate, box-select drag path in FractalRenderHost + the input controller's SelectionBoxChanged. |
Running GUI — draw an outline mid-deep-render. | Decouple overlay repaint from the calc gate, OR suppress TAA continuations while _boxSelecting. |
| SM-9 | No sub-pixel TAA anti-aliasing past ~1e10 — RunOneTaaSample jitters the DOUBLE CenterX/CenterY, lost below ULP; SM-8c now skips it (was byte-identical samples). |
RunOneTaaSample (jitter jx*scale added to calc.CenterX), TryScheduleNextTaaSample guard. |
New probe or GUI AA inspection at 1e20+. | Feed the Halton jitter through the OD centre limbs (DeepComplex/FromCenterOffset) or the per-pixel dc, not the top double limb. |
| SM-6 tail | OD reference-orbit accuracy at 1e70+ over ~100k iters is unmeasured (coordinate separation is proven clean to 1e120; orbit accuracy is the soft cap under the 1e100 zoom limit). |
ComputeReferenceOrbitOD; no 16-double ground truth exists. |
Proposed --odorbitprobe: cross-check OD vs QD reference orbit at a depth both resolve (e.g. 1e40) over many iters; extrapolate. |
Build the probe; if OD tracks QD long, the 1e100 cap is safe; else lower it. |
| SM-11 residual (cosmetic) | Deep-zoom drag "jumping" = ¼-res progressive preview block edges + fast rainbow palette amplifying ≤6-iter frame-to-frame differences. NOT a navigation fault (proven). |
RenderHint.Fast→Trigger(progressive), _previewCalcQuarter/_previewCalcHalf; palette selection. |
--panjitter (render already consistent); visual. |
½-res preview floor at deep zoom; smoother / less iteration-sensitive palette. Low priority. |
| HiDPI unify | Render computes its OWN scale (3.5/dim/zoom); a constant-offset concern if input logical-DIP dims ever diverge from render device-px dims. NOT the drift bug (that was fixed). Native path currently consistent (device-px both sides). |
ViewCamera.Scale vs MandelbrotCalculator scale; GpuSurfaceControl.CurrentScaling, NativeMouseForwarder.GetClientSize. |
--inputprobe (0.00px), plus a dims-mismatch check if a fractional-DPI repro appears. |
Only if a fractional-DPI (125/150 %) repro shows a constant few-px offset. Unify render onto ViewCamera. |
| SIMD rebasing (perf) | Rebasing (SM-2) runs the scalar ComputePixelPTRebased for glitched lanes; a vectorised rebased loop would reclaim throughput at deep zoom. |
ComputeRowPT4/8, ComputePixelPTRebased. |
--rebaseprobe timing. |
Vectorise the rebase branch. Perf-only; correctness already shipped. |
| 6.1.f1 | Remaining perturbation tail item (pre-session backlog). | — | — | Re-scope when reached; likely subsumed by the SM-2..SM-11 work. |
Cross-thread (NOT this session's deep-zoom line, tracked in memory): KIFS folds
5.9.f1 (Octahedron/Dodecahedron/MandelboxRot broken, --kifsprobe); Animation
roadmap D.1-D.6; Scene Engine IBL-rotation / rack-focus / audio-reactive.
-
2026-07-06 — SM-11b (recycle-during-drag) tested → not needed; SM-11 RESOLVED as sound. Added per-render
MandelbrotCalculator.AllowRecycleThisRender+ hostRecyclePreviewOrbitso a drag's progressive PREVIEW sidecars reuse one reference orbit. New--panjitterprobe (artefact-free: consecutive OFF-centre pan frames, compared beyond the pure translation) shows the FRESH render is already reference-consistent during a drag — inter-frame Δiter ≈ 0/1/3/6 per px at 2/8/20/40 px steps, and RECYCLE is identical to FRESH. So recycling doesn't change the pixels; the earlier--navrepro16px was the centre-on-reference measurement artefact (frame B centres on the clicked point). Conclusion: the deep-zoom navigation core is sound (input exact + render consistent); the user-perceived "jumping" is the ¼-res progressive drag preview + palette sensitivity (both cosmetic).RecyclePreviewOrbitleft default OFF (plumbing kept).Docs/Deep-Zoom-Perturbation.md§5-6 rewritten with the resolution. -
2026-07-06 — SM-11a (DD reference) tested → DOES NOT fix it; comprehensive doc written. Implemented
ComputePixelPTRebasedDD(DD reference orbit + DD δ-chain,UseDdRebaseReference) and aForceScalarPtPathtoggle to route ALL pixels through it.--navrepro ... scalar ddrefon the broken 4.65e64 coord:rebasedPx=942079/942080, focus-err still exactly (-16,3) = 16 px, byte-identical to double. So the deep-zoom reference-dependence is NOT double-rounding of the reference/δ in the 16→31-digit range (echoes the old "DD δ byte-identical at 1e47" result). Open hypotheses recorded in the newDocs/Deep-Zoom-Perturbation.md(the future-proofing deliverable — full model, tools, findings, rules): (1) precision floor above DD → try a QD-δ variant; (2) rebasing-decision divergence; (3)--navreprocentre-on-reference measurement caveat (frame B centres on the clicked point = its reference pixel) — tighten the test with an offset focus before investing. Practical UX fix independent of all this = reference recycling during a drag (SM-11b). DD variant + toggles kept behind flags (default off) for the QD follow-up.ViewCameraheader now points at the doc so the "input is exact" conclusion is not re-derived. No default behaviour change. -
2026-07-06 — Deep-zoom nav "close but not exact" ROOT-CAUSED via user repro coords: reference-dependent perturbation render, NOT input (SM-11 filed). User supplied a working (1.32701e63) and a broken (4.65087e64) coordinate, both detailed (below their 1e67 floor), both 5-limb centres.
--navreproon both: the input math is exact — controller centre vs ideal-OD centre error is 9.5e-15 px at 4.65e64 (machine zero), path-independent. Yet the double-click focus patch-matches ~16px off at 4.65e64 vs ~2px at 1.32e63. Cause: the two frames (centre C_A vs C_B = C_A+offset) genuinely differ for the same world region — best-alignment SAD ≈ 700–890 (≈18 iteration-counts/pixel), i.e. the deep-zoom perturbation render is reference-dependent: recentring recomputes the reference orbit and the region redraws with slightly different iteration counts, which the rainbow palette amplifies into apparent position drift + the live pan "jumping" (pan usesTrigger(progressive)= ¼-res FULL-iter, so it is the same effect, not under-iteration). Divergence is path-independent (rebase on/off, accel/SA on/off all ~16px) and precision-limited (scales smoothly with zoom, 2→16px), so it is fixable, not fundamental chaos. Mechanism: the rebased δ-loop (ComputePixelPTRebased) reads the reference orbitZ_nat double (_refZr[m]= X0 only) even though the full 8-limb OD orbit is stored (_refZrLo.._refZrX7sit unused) — two nearby centres' double-rounded Z chains diverge over ~4200 iters of chaotic amplification. Filed SM-11.--navreprogained an input-math check, SAD(0,0) vs SAD(min), and path toggles (norebase|acceloff|saoff). -
2026-07-06 — Deep-zoom nav evidence tooling:
--navrepro+ overlay limb counts (SM-10). User still reports outline-zoom / pan / double-click "close but not exact" past ~E+64, but words alone haven't pinned it and the synthetic probes are self-referential (controller OD vs OD truth) or can't measure on a flat frame. Added--navrepro [file]: reads a coordinate file the user fills straight from the floating menu (full-limbcx=/cy=,zoom=,dim=,click=), renders the frame, runs the realFractalInputControllerdouble-click focus, re-renders, and patch-matches the clicked feature to report focus error IN PIXELS (0 = perfect, >1 = the reported bug reproduced). Validated on the user's own 3E47 coordinate: focus 0.00px at 5e58 (textured), and at 1e64 the frame is flat (distinctIters=1, maxUseful=1e62) — i.e. that coordinate's reported "past-E+64 breakage" IS the SM-7 detail floor, not an input fault. To catch a genuine error the user must supply a coordinate that still has DETAIL past E+64. Also added centre limb-counts (limbs X:n/8 Y:n/8) + render px to the perf-HUD render-context block — a truncated centre would show instantly.Docs/Nav-Repro-Template.txtis the fill-in form. Awaiting a detailed-frame repro coordinate. -
2026-07-06 — "Controls break past 1e63" root-caused: it's the point's detail floor, not input (SM-7). New
--focusproberenders a deep frame, double-click focuses via the realFractalInputController, re-renders, and patch-matches to measure where the clicked feature lands. Result: focus is 0.00px through 1e70; input is provably exact (also confirmed by--inputprobeextended to 1e70, 0.00px anchor drift). What breaks is the RENDER — past ~1e60 the frame collapses to flat (frameDistinct 163→3→1, inSet 0%). Localised through rebasing-on/off and accel-on/off (all identical) to the shared δ core, then to the reference orbit: the user's centre escapes at iteration 4060, giving only ~62 decades of ∏|2·Zₙ| δ-amplification, so its intrinsic perturbation depth is ~1e62 — matched the collapse exactly. This is a property of the POINT (must recentre on a longer-orbit location to go deeper), the same limit every perturbation zoomer has. Shipped a guard:MaxUsefulZoomLog10computed free in all three ref-orbit builds, carried onRenderFrameInfo, surfaced as a status-bar notice when the live zoom exceeds it — so a flat deep frame reads as a location depth limit, not broken navigation. Note the earlier--qdfloorsweep(SM-6) validated OD.FromCenterOffset coordinate separation, but the live deep render uses the double-δ path, so coordinate separation was necessary but the amplification floor is the operative limit for a given point. 547/547 tests pass. -
2026-07-05 — OD coordinate path fixed → Extreme cap 1e63 → 1e100 (SM-6). Follow-up to SM-5: the 5e58/1e64 walls were NOT a QD limit — OD ref orbit + OD per-pixel coordinates already engage above
ODZoomThreshold(1e50). The real bug wasOD.FromCenterOffsetadding the pixel offset through the OD+OD sloppy operator, whose 3-level carry cascade can't reach limb X4+; a deep offset (zoom > ~1e64) was parked against X3 and rounded away, collapsing all pixels to the centre. That is exactly the--qdfloorsweepOD column's 37/128 at 1e66 (real, not the probe's zeroed-limb artifact I'd assumed). Fix = route through the OD+double full-cascade add ((center + offHi) + offLo). Sweep now: OD 128/128 through 1e120. Cap raised to 1e100 (20 decades of coordinate margin; OD ref-orbit accuracy is the soft limit past there, chaos-dominated as on QD). RegressionFromCenterOffset_DeepOffset_SeparatesAdjacentPixelsadded; all 24 OD tests pass. Note SM-5's 1e63 was a wrong-mechanism stopgap, now superseded. -
2026-07-05 — Extreme zoom wall raised 5e58 → 1e63 (SM-5). User navigated to exactly 5e58 and could zoom no further (neither wheel nor box). Root cause: that value is
QualityPreset.Extreme.ZoomMax, the top tier's hard cap — a stale-conservative number ~5 decades below the real QD floor. Reran--qdfloorsweep: QD coords stay 128/128 distinct through 1e64, collapse to 3/128 at 1e66. Raised the cap to 1e63 (one-decade margin under the clean 1e64 for QD reference-orbit accuracy in deep filaments; coordinate separation is necessary-not-sufficient). Iter budget fine (66 560 < 131 072 cap at 1e63). Cap propagates automatically — the input promotion sites readExtreme.ZoomMaxdirectly. Going past 1e64 filed as SM-6 (OD reference orbit + new tier). Stale QD-ceiling comments corrected inMandelbrotCalculator. -
2026-07-05 — Input rework CONFIRMED FIXED after a clean rebuild (commit 168eedc). Smoke retest initially still failed — root cause was a stale binary:
dotnet buildkept reporting success without relinking the exe, so the tested build lacked the ViewCamera change (rebasing, a runtime static flag, was present — hence its effects showed but the input fix did not). A--no-incremental -t:Rebuildresolved it. Also fixed a real render bug found in passing:ApplyViewcopied only the QD centre limbs (X0..X3) into the render calculator, dropping OD (X4..X7) — past 1e50 the render sat at a QD-truncated centre while the view state held full OD, so deep frames rendered at a wrong centre and navigation compounded against the mis-placed image; now copies all eight limbs (MirrorMandelbrotStatealready did). Rebasing default flipped ON (see SM-2); two rebasing-default side effects filed as SM-3 (status-bar premature done) + SM-4 (outline redraw slow), deferred. -
2026-07-05 — Deep-zoom input rework — ViewCamera + DeepComplex (commit 901b641). User report: approaching ~9e49, keyboard/mouse lose precision (double-click mis-focuses, drag pans the wrong amount, box-zoom lands wrong). Recurring — the input layer had been rewritten per precision tier several times.
--inputprobe(new headless gate) root-caused it: single-op anchoring is exact, but a cumulative wheel zoom-in DRIFTS — the centre was carried in plain double until the HP threshold (1e12), where promotion froze a ~1e-16 world error that bloomed ∝ zoom (3000px off at 1e17, astronomically off by 1e49). Fix (chosen: full ViewCamera, OD-always):DeepComplex(OD-backed complex, precision is an internal detail) +ViewCamera(single screen↔world authority) in Abstractions;FractalViewState.GetCenter/SetCentertyped accessor. All sixFractalInputControllersites now delegate to ViewCamera — the per-tier cascades + DD/QD/OD pan-start caches + Store* deleted (~150 lines). New precision tiers extendDeepComplexonly, never the input handlers. Gate: anchor drift 0.00px through 1e6→8e49 (was 3000px@1e17); 546/546 tests pass. Follow-up: unify the render onto ViewCamera + reconcile logical-vs-device pixel dims (HiDPI) — separate constant-offset concern. -
2026-07-05 — SM-2 rebasing shipped opt-in —
--rebaseprobePASS. NewMandelbrotCalculator.ComputePixelPTRebased(Zhuoran rebasing) resolves any pixel in double precision from the single shared reference orbit at any depth, replacing the per-pixel QD/OD/HP glitch fallback at all six sites (scalar row + PT4/PT8 vector-extract + PT4/PT8 scalar tail) whenAllowPtRebasingis set. Default OFF ⇒ the render path is bit-identical to pre-SM-2 (if (AllowPtRebasing) … else <existing fallback>). Probe A/Bs it against the per-pixel QD truth on the deep smoke regions:- 91–142× faster (3E47: 12740 ms → 101 ms at 128²/20 k iters).
- Accuracy = QD. reb-vs-QD tracks QDself (QD SA-off vs SA-on) to within 0.05 pt (51.63/51.68, 58.98/58.97, 96.77/96.77, 100/100). The ~50 % "miss" on the two deepest regions is chaotic sensitivity of deep filamentary structure at high iter — the QD render disagrees with itself by the same amount, so there is no tighter truth to hit.
- A DD δ-chain + DD reference + DD dc variant was tried and produced
byte-identical iteration counts to the double path (and 50× slower):
precision is not the limiter here, so the fix stays in double. This is why
2.14's "DD δ-chain" would have bought nothing.
Remaining: flip
AllowPtRebasingon after a visual sign-off (3.5-style gate); optional SIMD rebasing later to reclaim vector throughput at deep zoom.
-
2026-07-05 — Wave 2.14 investigated — premise not reproducible; recommend reframe/close. Two headless probes added to
Program.cs:-
--qdfloorprobe [maxIter]— renders each QD-band smoke region twice (SA on vs off) and reports a neighbour-collapse metric. Result: SA on/off is near-identical (41.0/39.3 %, 24.4/24.7 %, 83.6/83.6 %) ⇒ the double SA seed is not the pixelation floor. Also confirmed the SIMD PT δ-loop bails ~1e30 (glitch check atComputePixelPT), so deep frames run per-pixel direct-QD — that is the SM-2 slowness, not a δ-chain the plan's "DD δ" framing assumed. -
--qdfloorsweep— builds the 128 per-pixel X coords the QD/OD path uses (QD.FromCenterOffset, |c|≈2 centre) across a zoom sweep and counts bit-distinct values. QD separates all 128/128 pixels through 1e64, cliffing only at 1e66. So there is no QD arithmetic pixelation floor in the stated 1e40–1e58 band — QD headroom runs ~6 orders past the band and ~37 orders past the video zoom cap (5e27). Conclusion: the 2026-06-22 "pixelation 1e40–1e58" report predates the 2.11 OD-arithmetic fix and the SM-1 iteration-cap finding; it is not a live QD precision bug. The real remaining deep-zoom lever is SM-2 (PT δ-loop bails ~1e30 → slow per-pixel direct-QD; fix = rebasing to keep cheap SIMD PT viable at any depth). Recommend closing 2.14 as obsolete and folding its intent into SM-2. Probes: commitsbaade13(qdfloorprobe) + this entry's sweep.
-
-
2026-07-05 — Deep-region smoke-test triage +
--regionprobediagnostic. New headless renderer (Program.cs --regionprobe [maxIter]) renders the reported deep regions at 128² single-sample and reports tier / wall-clock / in-set% / distinct-iter / distinct-colour. Dispositions of the smoke report:- "renders solid colour" (Deeper and Deeper, Deep Lightning in Space) — iteration-count issue, NOT a precision bug. At maxIter=8192 all four deep regions render valid fractals (distColour 550–1527, 0% in-set); at maxIter=300 all collapse to 100% in-set / one colour. Escape bands are 328–3940 iters, so any effective cap below a region's band paints it flat. The reported-solid regions (need 379 / 808 iters) were rendered under their band. Fix path: the region's saved MaxIterations (or the app's per-region auto-iter) is too low — not the QD path.
- "takes minutes" (3E47, E45Test04) — inherent deep-QD-perturbation cost: 3E47 = 11.2 s at 128² single-sample → minutes at full window × AA16 (Extreme). Not a defect.
-
Video zoom clamp at E+27 — by design (
VideoZoom.cs:241, Ultra cap; Extreme-regime pixelation). Not a bug. - Video Settings phantom modal (Windows) — nested modal-of-a-modal failed to front on Win32 (+ ShowInTaskbar=false → unreachable). Fixed 43168a9: Activate-on-Opened for the Video + Audio dialogs. Needs Windows verification.
- Wave 3.5 confirmed inert in production (flag set only in the probe).
-
2026-07-05 — Wave 3.5 shipped opt-in — reference-orbit recycling across frames.
MandelbrotCalculator.AllowRefOrbitRecycle(static, default OFF) gatesTryRecycleReferenceOrbit: when the view centre moved by less thanRecycleMaxShiftFactor(0.25) of the frame's corner-dc — same precision tier, cached maxIter covers the frame — the cached reference orbit is reused instead of rebuilt. The centre shift Δc = newCentre − cachedCentre (computed at the tier's DD/QD/OD precision, rounded to double) is injected into the SIMD PT dc via_refRecycleDx/_refRecycleDyin the scalar / PT4 / PT8 paths; the DD/QD/OD glitch fallbacks need no change because they already derive δc = absoluteWorldCoord − storedRefCentre. The expensive orbit build is skipped; the cheap BLA/SA tables rebuild for the widened dc (the kept orbit is a valid perturbation base well past the BLA linearisation radius). Default path is bit-identical (x + 0.0 == x). New headless gate--reforbitrecycle(Program.cs) renders each target centre twice — a fresh orbit vs a recycled one — and PASSes: recycling engages every case, ≤ 3 / 16384 pixels differ (escape- boundary flips, inherent to any reference change), zero large-area divergence at DD-tier zooms 1e13–1e22. PublicRefRecycleHits/RefRecycleMissesdiagnostics. Not yet production-on — needs the video pipeline to opt in + a deep-zoom visual flicker sign-off; QD/OD tiers run the identical code path but the probe only exercises DD tier (representable pan). See item 3.5. -
2026-07-05 — Wave 5.9.f1 attempted — KIFS fold fixes + headless probe. Added
--kifsprobe(Program.cs) +KifsCalculator.ProbeDEtest hook: a headless geometric self-test that sphere-traces the DE inward along a Fibonacci direction set and reports hit-fraction + surface radii (axis / face-diagonal / body-diagonal), detecting the two documented failure modes (all-black = hitFrac≈0; cube = radius signature 1:√2:√3) without a GUI. Faithful ports of all three broken folds were tried and the probe disproved each:- Octahedron — Mandelbulber2 apex-fold port → solid cube (hitFrac 1.0, radii 1:√2:√3). Reverted to the shipped rotated-Menger approximation.
- Dodecahedron — exact Coxeter [5,3] icosahedral mirror fold → all-black (hitFrac 0.0; scale-from-vertex diverges because the user offset isn't an icosahedron vertex). Reverted to the shipped rotated-Sierpinski (which at least renders a visible shape — the icosa port was a regression).
- MandelboxRot — the documented dr-accumulator fix (DE = length/dr) → object spans to radius ~6, past this fold's camera setRadius (3.5), so the camera sits inside the body. Reverted rather than ship an unverifiable framing regression. Net: all three fold bodies stay at their shipped state; the probe + hook + honest per-fold NOTE doc-comments land so the eventual fix has a headless gate. 5.9.f1 remains open — a correct fix needs reference-sourced formulas (Octahedron / IcosaFold) + a matched camera retune (MandelboxRot), verified visually, which is not reliably doable headlessly.
-
2026-06-23 — Wave 7.1 shipped — Top-level
Docs/_Index.mdlanding page. Routes by audience (User →User/_Index.md, Technical →Technical/ _Index.md) and surfaces the project-wide roadmap layer (Open-Work-Plan, Performance-Roadmap, Lighting-FX-Roadmap, Fractal-Expansion-Roadmap, CalculatorGen-Roadmap, Documentation-Plan, Resources-Bibliography) as a third bucket — the existing sub-indices only show their own audience's pages, so cross-cutting roadmap docs had no canonical entry. Style matches the two sub-indices (table-of-routes pattern, terse one-line "what it covers" cells). Wires:-
README.md— new "Documentation landing page" line between feature tour and per-OS install caveats so the top-level entry is one click from the project landing page. -
Docs/User/_Index.md+Docs/Technical/_Index.md— opening paragraph extended to point upward at the new landing page so readers who arrive mid-tree can navigate up to the bridge. - Roadmap effort table bumped 1 → 0 day. Wave 7 closed.
-
-
2026-06-23 — Wave 6.1 shipped — Multi-cluster spatial partitioning for perturbation rebase. Audit found the work already in the CalculatorGen Roslyn template (
CalculatorGen/Templates/Calculator.template.cs): Item 7 MVP (commit f54ffe7), guards A+B+C hardening (8d7cf75), multi-cluster D (2912309), cross-frame orbit cache E (bcea672) — all shipped pre-Wave-6 but never tracked inOpen-Work-Plan.md. Plus Wave 2.13 source-gen (f5935c3) deleted the in-tree generated calcs; template now emits at build time via Roslyn analyzer, so multi-cluster flows to all 10 generated calcs (MandelbrotZ2..5, Tricorn, MandelbrotTricorn, BurningShip, MandelbrotBurningShip, MandelbrotPhoenix, UserDslEquation).-
ProcessClusterRebase(template line 2108) — spatial-partitions theConcurrentBag<(int x, int y)>from the main perturbation pass via a 16×16-cell occupancy grid + 8-conn BFS flood-fill on occupied cells. 1920×1080 frame → ~8K cells; cluster count typically 1-20 per deep-zoom frame. Sequential per-cluster dispatch (inner rebase pass is itself Parallel.For — nesting would oversubscribe threadpool). -
ProcessSingleCluster(template line 2234) — zoom gate (skip belowQdDirectZoomThreshold = 1e25, DD-direct cheaper there), bbox- cohesion guard (skip long-thin tendrils with density < 2%), centroid build of shared QD reference orbit viaBuildRebaseRefOrbitQd(no BLA, no SA), 4-slot LRU cache lookup viaTryGetCachedRebaseOrbitkeyed by centroid withinscale·16tolerance + maxIt, sample-probe of first 8 pixels (commit only when ≥ 50% land), parallelTryIterateRebasePixelover remainder. Failures route toHpDirectGlitchPixel. -
UseClusterRebasedefault flippedfalse → truethis turn. Original off-by-default per commit 6d6db7f "Default UseClusterRebase off until AVX-2 perturbation lane lands"; AVX-2 lane shipped commits cea9cc8 + b0abc68 so the guard is stale. XML doc rewritten to reflect Wave 6 closeout state (multi-cluster + cache reduce wasted work, three guards kill bad-fit clusters early). - Smoke: full solution build clean (0 errors, 24 baseline warnings —
CS0219 in generator output + AVLN5001 obsolete). 156/156 Server.Tests
pass.
--gentest MandelbrotZ20-diff scalar↔AVX2↔GPU↔perturbation↔ BLA↔QD-ref-orbit at 4096 pixels.--saprobehistogram across 1e9 → 1e60 zoom tiers: gen-vs-legacy colour counts within ±10 across SP / QD-PT-SA range (saprobe coords stay on main cardioid so don't fire rebase, but no regression in non-glitch path). - Legacy
Engine/Calculators/MandelbrotCalculator.cs(canonicalFractalType.Mandelbrotpath) has no cluster rebase at all — glitched lanes in PT4 / PT8 / PT8-512 fall straight to per-pixelComputePixelOD /QD/HP. Filed as 6.1.f1 follow-up: needs SP/DD/QD/OD precision-tier branches, 4 glitch-enqueue sites across the 4 perturbation paths, and OD-awareBuildRebaseRefOrbitODextension (template only handles DD+QD). ~2-3 d when prioritised; non-blocking — canonical Mandelbrot's own per-pixel HP-direct fallback stays correct, just slower than cluster rebase would be on cohesive mini-Julia scenes. -
Docs/Technical/CalculatorGen-Roadmap.mdItem 7 + Known-Issues entry updated to reflect the multi-cluster + cache state (was still documented as MVP single-centroid with multi-cluster as follow-up).
-
-
2026-06-23 — Wave 5.9 KIFS folds bugged + deferred. All three new folds (Octahedron, Dodecahedron, MandelboxRot) render incorrectly. Iteration
- rebuild + smoke confirmed code path runs; math itself wrong. Three successive rewrites all failed to produce correct shapes. Deferring rather than burning more time on derivation.
-
Octahedron — current impl is Menger sort-3 abs-fold with a 30°
Y-axis pre-rotation. User reports solid cube. Earlier variant
(Menger minus z-mirror) also rendered cube — both leave the orbit
bounded to roughly the unit cube under abs+sort+scale, no
octahedral self-similarity emerges. True octahedron IFS needs a
face-fold across the (1,1,1)/√3 face-normal plane that actually
fires for typical iterates; my versions either folded too rarely
(
y+z > 1after sort puts max in x → rarely true) or used the wrong scale/offset combination that collapsed orbits to origin. -
Dodecahedron — current impl is Sierpinski tetra fold with 36°
rotation around (1,1,1) diagonal — should render something but
not the intended dodecahedral / icosahedral shape. Earlier variants
using Knighty's three φ-derived mirror planes (n1=(-φ,-1,φ-1),
n2=(-1,φ-1,-φ), n3=(φ-1,-φ,-1)) diverged for every traced pixel —
if (d < 0)reflections never produced a bounded attractor, so DE stayed huge-positive everywhere → no ray hits → all-black render. Adding the canonical abs(z) first-octant prefix made the +++ octant all-positive against any (-,-,+) normal, so mirrors fired the wrong way. The Wave 5.9 "next 36° rotated Sierp" tactic ships but does NOT match the dodecahedral spec. -
MandelboxRot — current impl is box-fold-at-±1 + sphere-fold +
π/48 Y-axis rotation + scale. User reports cube-like shape with
stepped ridges along oblong slightly curved sides. The fixed-dr
KIFS DE scheme (no per-iter |dz| magnitude tracking) is the root
cause — real Mandelbox DE needs the dr update from the sphere-fold
factored into the distance return. Without it, the DE produced is
geometrically incorrect → only the bounding-cube approximation
renders. The proper sphere-fold + dr-magnitude update lives in
MandelboxCalculator, separate code path. - UI hooks shipped:
KifsFoldKind.Octahedron / Dodecahedron / MandelboxRotenum values, ComboBox row inFractalParamsView.axaml,DispatchDEswitch inKifsCalculator.cs. GPU path correctly gates new folds to CPU fallback. Build clean, 156/156 tests pass — the bug is mathematical, not structural. -
Fix plan (deferred) — proper fixes require porting battle-tested
formulas from Mandelbulber2's
fractal_formulas.cpp(specifically its "Octahedron", "IcosaFold", and "AmazingBoxMod1" entries), each of which is ~50 lines of carefully-tuned axis swaps + plane mirrors- dr-tracking arithmetic. None of those formulas fit the simple
fixed-dr KIFS DE scheme used by the existing Menger / Sierpinski
paths — they need a dr accumulator and bailout management closer
to the
MandelboxCalculatorshape. Filing as 5.9.f1: replace the three current DE bodies with Mandelbulber-ported versions and extendKifsCalculatorwith a dr-magnitude accumulator threaded throughDispatchDE. Estimated 1-2 days.
- dr-tracking arithmetic. None of those formulas fit the simple
fixed-dr KIFS DE scheme used by the existing Menger / Sierpinski
paths — they need a dr accumulator and bailout management closer
to the
- Interim user-facing behaviour: the three new fold options remain pickable in the ComboBox and render something (their distinct incorrect shapes), so they don't crash or block. Recommend leaving selection on Menger / Sierpinski until 5.9.f1 lands.
-
2026-06-23 — Wave 5 closeout — 10 of 15 items shipped (5.1–5.7, 5.10, 5.11, 5.14). Five items deferred — 5.8 / 5.12 / 5.13 / 5.15 are heavier than polish-wave scope; 5.9 (KIFS folds) ships UI/enum scaffolding but DE bodies bugged — see separate 5.9 entry. Fix tracked as 5.9.f1.
- 5.5 / 5.6 / 5.7 — doc + test polish. FEATURES.md / README bumped to
"~38 families" with category breakdown; README gained shields.io
badges (fractals / themes / platforms / .NET). Allowlist test suite
grew from 39 → 55 with full enum-classification coverage assertion.
Visual-regression case set 22 → 41 (every new FractalType + every
Generated variant + every 3D raymarcher). Baseline.json stays the
earlier 22-entry record; user runs
recordwhen ready to absorb the ~10 min cold-build cost. - 5.1 audit conclusion — theme gating is implicit (per-calculator
capability +
EquationProfilefeature-bit recommender), not a central FractalType→tag registry. New families pick up sane defaults via interface gating (e.g.IInteriorAwareColorMaponly runs insideMandelbrotCalculator.RunInteriorPass; alt calcs silently skip). No new tag table needed. Documented + closed. - 5.2 —
Engine/Models/FractalRegion.cs_builtInsextended with 5 new entries: Plasma (default seed framing), Flame (default chaos framing), Logistic (r ∈ [2.9, 4.0] bifurcation window), TearDrop (default centre), Mandelbulb power-8 (default camera). Roadmap target was ≥1 built-in per family; the four families above were bare. Other families already had ≥1. - 5.3 — CalcGen DSL inspected. Magnet 1/2 rational expressions are
DSL-expressible (
/,^2work) but the pole-clamp on the denominator has no DSL operator → NaN blow-up on pole pixels. Glynn needs fractionalz^1.5— DSL^is integer-only. Spider mutatescper iteration — DSL state model assumes constant c. All four stay hand-written scalar / SIMD kernels inEngine/Models/FractalKernels/. No[assembly: GeneratedCalculator]entries added. - 5.4 — Two-level Mathematics-tab grouping. New
HelpSubTabGrouprecord inAbstractions/Help/IHelpContentProvider.cs. Default interface impl wraps the flatMathSubTabsinto a single "All" group so legacy hosts stay compatible.HostHelpContentProvideroverrides with a 7-group layout: Overview / 2D escape-time / Histogram / Procedural / 3D + 4D / Authoring / Generated.FloatingHelpView.axamlnow renders an outer TabControl over the groups and an inner TabControl (TabStripPlacement="Left") over each group's sub-tabs. Tab strip no longer wraps; total 35 sub-tabs split across the 7 groups. - 5.9 — UI / enum scaffolding shipped (KifsFoldKind.Octahedron / Dodecahedron / MandelboxRot, ComboBox row, DispatchDE switch, GPU gate to CPU fallback) but DE math bugged across all three variants. Three rewrite attempts failed to produce correct shapes. Marked 🔴 bugged + deferred — see dedicated 5.9 status-log entry above for diagnosis and 5.9.f1 fix plan.
- 5.10 —
LSystemPresets.csgained 5 new entries: Crystal (Koch-square variant), Quadratic Koch Island, Twindragon, Bush (Plant variant), Sierpinski Carpet. Built-in library now 16 entries. - 5.11 — Flame gained 10 new Apophysis stock variations: Horseshoe (v4),
Spiral (v9), Hyperbolic (v10), Diamond (v11), Ex (v12), Bent (v14),
Fisheye (v16), Exponential (v18), Power (v19), Cosine (v20). Enum
values use Apophysis-canonical IDs; the
ApplyVariationswitch arm guards origin-singular variations with the same r ≥ 1e-12 bailout the existing Spherical / Julia / Disc arms use. - 5.14 — Bicomplex Mandelbrot 4D slice-axis selector. New
BicomplexSliceAxisenum {K, J, I, R}. The DE now packs (sx, sy, sz, sliceW) into (c1..c4) according to the selected axis, routing the slice constant to the chosen algebra basis vector. Default K preserves legacy behaviour bit-exactly. GPU kernel still hardcodes K-axis assignment — non-K selections fall back to CPU. UI gains a ComboBox row inFractalParamsView.axaml. The coquaternion / split-complex variant from the roadmap stays deferred — needs a new calculator with swapped product table. - Bug fix piggyback in
FractalParamsView.axaml: the Bicomplex "Cam dist" NumericUpDown'sGrid.Rowwas "4" against a TextBlock at row "5" → cam-dist value overlapped cam-φ. Fixed to row "5". - Build clean (0 errors, 20 pre-existing warnings — same baseline as Wave 4 closeout). 156/156 Server.Tests pass (140 baseline + 16 new allowlist tests).
- 5.5 / 5.6 / 5.7 — doc + test polish. FEATURES.md / README bumped to
"~38 families" with category breakdown; README gained shields.io
badges (fractals / themes / platforms / .NET). Allowlist test suite
grew from 39 → 55 with full enum-classification coverage assertion.
Visual-regression case set 22 → 41 (every new FractalType + every
Generated variant + every 3D raymarcher). Baseline.json stays the
earlier 22-entry record; user runs
-
2026-06-23 — Wave 4.12 shipped — Marching Cubes mesh export (OBJ smooth + binary STL).
-
Export/UserBulbMeshExporter.csrewritten. Public surface now:ExportMarchingCubes(filePath, sample, cx, cy, cz, range, n, ct)— dispatches on file extension (.stl→ binary STL with per-face normals; anything else → OBJ with smooth per-vertex normals +v/vn/f a//a b//b c//clines). LegacyExportObjVoxelSurfacekept verbatim for the WinForms shell wire-up + as a low-N fallback. - Lorensen-Cline tables embedded inline: 256-entry
EdgeTable[int](12-bit crossed-edge mask per cube index) andTriTable[int,16](-1-terminated triangle edge lists, up to 5 tris per cube). Layout matches Paul Bourke's reference. - Sampling: uniform (n+1)³ scalar field on a cube of side 2·range,
centred on (cx,cy,cz). Iso-level held at
step·0.5to match the surface band the raymarcher considers solid (the User Bulb DE estimator clamps positive outside; matches the legacy voxel path'ssurfaceEps). - MC sweep over the n³ cells: 8-corner sign comparison against iso
→
ci ∈ [0,256), fetchem = EdgeTable[ci], materialise the up-to-12 edge vertices via linear interp toward iso, then emit triangles by indexing throughTriTable[ci,*]in triples until the -1 sentinel. - Edge-vertex dedup: each MC edge is uniquely owned by the
lower-coord corner + an axis (X/Y/Z). Packed into a single
int[(n+1)³ · 3]-1-seeded lookup, keyed((i·side + j)·side + k)·3 + axis. Each crossed edge produces exactly one shared vertex regardless of how many of the (up to 4) incident cells reach it. - Smooth normals: per-vertex normal = sum of incident triangle face
normals (computed as
(b-a) × (c-a), which already carries area weighting in its magnitude), normalised at the end. Zero-length vertices fall back to(0,0,1)to keep OBJ readers happy. - OBJ writer emits
v+vnlines (1-indexed) and faces asf a//a b//b c//cso DCC tools (Blender, MeshLab) pick the smooth normals up directly. STL writer emits the standard 80-byte header (FracturingFog UserBulb MC),uint32triangle count, then 50 bytes per triangle (12 B face normal + 3×12 B verts + 2 B attribute word = 0). Output is little-endian, matches Wikipedia STL spec. - Host gate updated in
Hosting/AvaloniaShellBootstrap.cs:1741to callExportMarchingCubes(extension-dispatched) instead ofExportObjVoxelSurface.UserBulbViewModel.OnExportMeshwidened its save-filter to"OBJ (smooth)|*.obj|STL (binary)|*.stl". The WinForms shell (MainForm.cs:1841/Views/UserBulbDialog.cs:682) kept on the legacy voxel path per the CLAUDE.md "WinForms deprecated" guidance — no new features land there. -
Span<int> edgeIdx = stackalloc int[12]hoisted out of thei/j/ktriple loop (CA2014 "potential stack overflow"). Unset entries from prior cells are never read becauseTriTableonly indexes edges marked inEdgeTable[ci], which is exactly the bitmap that gates the conditionalGetOrCreateEdgeVertwrites. - Build clean (0 errors, 0 warnings — the 4 pre-existing
AVLN5001 TextBox.WatermarkAvalonia warnings stay on UI.Avalonia / PaletteBuilder.Lib, unrelated). 140/140Server.Testspass. No new unit-test fixture: Server.Tests doesn't reference Engine / Export, and adding a project reference for one smoke is heavier than the standard-table verification is worth. - Wave 4 backlog now empty.
-
-
2026-06-22 — Wave 4.6 shipped — Sandbox quat Julia + numerical- Jacobian DE on GPU.
-
GpuRenderParamsextended withJuliaMode(int 0/1),JuliaCW/CX/CY/CZ(double),JacH(double),UseAnalyticDE(int 0/1). Default-zero values are inert for legacyUserBulbGpuCalculator.BulbKernel(it never reads them), so the Roslyn-source / hand-written GPU path keeps its bit-identity. -
UserBulbSandboxGpuCompilerkernel scaffolding refactored. The DE + Kernel string bodies extracted to constants (VecSandboxDESource,QuatSandboxDESource,KernelBodySource)- helpers (
AppendKernelPrelude,AppendStepFn). BothBuildKernelSourceandBuildChainKernelSourcenow compose from the same parts; previously the chain version was a verbatim duplicate of the single-step kernel scaffolding.
- helpers (
- Unified Quat DE: branches on
p.UseAnalyticDE(power-map vs 5-trajectory Jacobian) and onp.JuliaMode(per-pixel c vs Julia parameter fromp.JuliaC*with z₀ taking the pixel coord). Numerical-Jacobian mirrors CPUUserBulbQuatDE: four perturbed trajectories along {W, X, Y, Z}; max |z_pert − z|/h gives the conservative spectral-radius proxy. Vec mode kept analytic-only — vec-Julia / vec-numerical GPU support is out of scope for 4.6. -
SandboxDEsignature collapsed to(double cx, double cy, double cz, GpuRenderParams p, ArrayView<double> __p)so the branch fields live on the struct, not the param list. AllKernelBodySourcecallsites updated. -
UserBulbCalculator.CalculateGPU gate now allows the Sandbox-quat route through Julia mode + non-analytic sources. Old gate:!juliaMode && analyticPattern != None; new gate:sandboxQuatGpu || vecAnalyticGpuOk. Julia + analytic-power case still uses the analytic branch (matches CPUuseAnalyticgate of!juliaMode && analytic, so Julia falls into the numerical branch). -
GpuRenderParamspopulated with the new fields at the same site:JuliaMode,JuliaC{W,X,Y,Z},JacH,UseAnalyticDE. - Build clean (0 errors, 24 pre-existing warnings — same baseline as 4.5).
140/140 Server.Tests pass.
--ubtestquat compile + chain + triplex- emitter parity scenarios all green.
- Wave 4 remaining: 4.12 follow-on (real MC + STL).
-
-
2026-06-22 — Wave 4.5 shipped — Sandbox chain GPU dispatch.
- Prior gate in
UserBulbCalculator.CalculateGPU branch was_compiledCompiler == Sandbox && !useChainPath— chain mode locked to CPU. NowuseChainPathselects betweenUserBulbSandboxGpuCompiler.TryCompileChain(...)and the existingTryCompile(...). -
UserBulbSandboxEmitter.Emitgained a 5-arg overload acceptingIReadOnlyDictionary<int, (string Name, SbxEmitKind Kind)> extraSlots.EmitCtxstores the map andEmitSlotchecks it after the let-substitution probe and after the z/c/n/params/tlookup — slots that survive to the fall-through branch are chain prior-step outputs. The original "extras past paramCount collapse tot" bug fixed; unbound slots now throwNotSupportedExceptionrather than silently aliasingt. Slot kind also seeded into_slotKindssostep0.xmember access infers Real correctly. -
UserBulbSandboxGpuCompiler.TryCompileChain(steps, paramNames, quatMode)— new entry point parallel toTryCompile. ReusesSandboxBulbChain.Parse(shares scope across steps + assigns output slots). Per-step emit walks each step's root AST through the 5-arg emitter overload, populating the slot map after each step with(localName, stepKind). Local name comes fromsteps[i].OutputNamesanitised viaSanitizeIdent(leading char letter/underscore, non-ident chars replaced with_). -
BuildChainKernelSourcemirrorsBuildKernelSourcebut theStepbody inlines each emitted step expression into a typed local (Vec3 step0 = (body);/Vec3 myname = (body);…) so later steps reference earlier ones by their declared name. Returns the last step's local. SurroundingSandboxDE+Kernelraymarch scaffolding identical to single-step path, so existing fp64 fallback + Roslyn compile + ILGPU JIT plumbing- Render shim all reused unchanged.
- Chain key includes
CHAIN|prefix so the kernel cache distinguishes chain compiles from single-step with the same source string. - Build clean (0 errors, 24 pre-existing warnings — same baseline as
4.4). 140/140 Server.Tests pass.
--ubtestchain compile + chain-analytic detect both green (Pattern=MandelbulbN power=8). - Wave 4 remaining: 4.6 Sandbox Quat-mode Julia + numerical-Jacobian DE on GPU; 4.12 follow-on (real MC + STL).
- Prior gate in
-
2026-06-22 — Wave 4.4 shipped — Sandbox interpreter opcode-flat dispatch.
- Hot path was
Sbx3Binary.Eval/Sbx3Call.Evalrunningstringswitches onOp("+","&&","<=", …) andName("vec","triplex","qmul", …) per AST step. C# string switches lower to chained equality probes, not jump tables — every interior node paid an O(n) string-compare cost per Eval call. With Sandbox at ~10–15× slower than Roslyn for non-analytic sources per Stage 3C notes, this was the dominant tax. - Added two internal enums in
Engine/Models/SandboxBulbExpression.cs:SbxBinOp(13 values Add/Sub/Mul/Div/Pow/Lt/Gt/Le/Ge/Eq/Ne/And/Or) andSbxFuncId(34 values covering every built-in vec/quat/scalar function). Resolved at AST construction time —Sbx3Binaryctor callsResolveOp(op)once,Sbx3Callctor callsResolveFunc(name)once. Originalstring Op/string Namefields retained soUserBulbAnalyticDE.DetectSandboxpattern matchers (Sbx3Binary { Op: "+" }/call.Name == "triplex") andUserBulbSandboxEmitter.EmitBinary/EmitCall(which readb.Op/call.Name) continue to work bit-identically. -
Sbx3Binary.Evalnow switches onOpKind(SbxBinOp). 13-arm enum switch lowers to a dense jump table. -
Sbx3Call.Evalnow switches onFunc(SbxFuncId). Two-stage structure preserved: multi-arg ops in the firstswitch, scalar unary transcendentals in the trailing enum switch (handles Quat-rejection viaApplyScalar). - Out of scope for this slice — DynamicMethod IL emit. Opcode-flat
dispatch was the cheaper of the two options on Stage 3C's list
and removes the bulk of the string-compare tax without
introducing a JIT-emit pipeline. If sandbox perf needs another
pass after this, the next move is
Eval(env)virtual call flattening (compile AST into a linear opcode array walked by a single while loop) or DynamicMethod IL. - Build clean (0 errors, 24 pre-existing warnings — same baseline
as 4.2). 140/140 Server.Tests pass.
--ubtestruns full sandbox- chain + quat + emitter parity matrix, all green.
- Wave 4 remaining: 4.5 Sandbox chain GPU dispatch; 4.6 Sandbox Quat-mode Julia + numerical-Jacobian DE on GPU; 4.12 follow-on (real MC + STL).
- Hot path was
-
2026-06-22 — Wave 4.1 + 4.2 shipped.
- 4.1 —
ScreenSpacePost.ApplyHdrDofnow gates onfx.UseGpuPostand routes the three skewed-box blurs through new ILGPU kernels inGpuPostKernels.cs. Kernels:DofCocKernel(per-pixel CoC from depth + focus + cocScale),DofSkewedBoxKernel(1D box blur along (dx, dy) with width = per-pixel CoC and the same bleed-control behaviour as the CPUSkewedBoxBlur— foreground neighbours contribute only when their CoC reaches the centre), andDofMinBlendKernel(composites three skewed passes back intohdrBuffervia per-channel min, skipping sub-CoC and sky pixels). Float-precision NaN sky detection uses the self-compare trick the bloom kernels already use (ILGPU doesn't acceptfloat.IsNaN). Falls back to the CPU path on any init / OOM / kernel throw — no new toggle, the existingUseGpuPostknob covers it. - 4.2 —
LightingFxData.UseGgxSampling(defaultfalsepreserves 16b mirror-reflect bit-identity). When on, both reflect-direction sites inShadingPipeline.Shade<TDe>(initial mirror from view ray- per-bounce re-reflect against newly-hit normal) replace the
mirror reflect with a GGX VNDF sample (Heitz 2018). New helpers
in
ShadingPipeline.cs:HashPair(x, y, z, bounce)returns two deterministic Wang-hashed uniforms seeded by world position + bounce index (stable across frames, decorrelated per pixel); andSampleGgxReflect(V, N, roughness, u1, u2)builds a Frisvad TBN, stretches view dir to the unit hemisphere per Heitz, samples the visible-normal lobe, unstretches, and returns the reflected direction. One sample per bounce — temporal/spatial decorrelation spreads the lobe across the screen so we don't need Monte Carlo averaging. Below-horizon samples (g·n ≤ 0) fall back to mirror. Knob ties into the existingRoughnessfield (alpha = roughness²);Roughness = 0collapses to mirror by definition.
- per-bounce re-reflect against newly-hit normal) replace the
mirror reflect with a GGX VNDF sample (Heitz 2018). New helpers
in
- Build clean (0 errors, 24 pre-existing warnings — same baseline as 4.3). 140/140 Server.Tests pass. No commit made.
- Wave 4 remaining: 4.4 Sandbox interp perf; 4.5/4.6 Sandbox chain + Quat-Julia GPU dispatch; 4.12 follow-on (real MC + STL).
- 4.1 —
-
2026-06-22 — Wave 4 audit + Wave 4.3 shipped.
- Audit: 4.8 color drivers, 4.9 FOV/DoF/clip/SS, 4.10 multi-equation
chain (
UserBulbChain+CompileSandboxChain+WrapUserSourceChain), 4.14 Julia mode Vec3 (UserBulbDE juliaMode branch) all found wired in code but unmarked in plan — marked ✅ Shipped (audited 2026-06-22). 4.12 (mesh export) re-classified 🟡: voxel-cube OBJ exporter atExport/UserBulbMeshExporter.csshipped, real Marching Cubes (256-entry triangulation table) + STL writer + normal smoothing still open. - 4.3 HDRI auto-preload —
HdriProbe.Preloadaction added (Abstractions/Rendering/Lighting/HdriProbe.cs).HdriRegistrystatic ctor wires it toTask.Run(() => TryLoadFromFile(...)).HdriRegistry.TryLoadFromFilererouted through a_parseGateConcurrentDictionary<string, Lazy<HdriImage?>>so concurrent first-hits funnel through a single Lazy parse instead of N pixel-worker threads each opening the file + parsing the RGBE stream + writing to the cache. Gate entry removed after parse so a fixed file can be retried. - Preload kicked from three seams covering every parameter-change
route: (a)
FractalParamsViewModel.EnvironmentNamesetter (UI-driven changes); (b)ShellViewModeltheme + region preset apply sites that assignFractalParameters.Lightingwholesale (bypass the VM setter); (c)LightingFxPresetData.ApplyTo(preset DTO apply path that fires for theme JSON loads). Fire- and-forget on background thread; render trigger continues concurrently, but with the per-path lock guaranteeing single parse the worst-case race isparse_ms + 1 framenotparse_ms · N_threads. - Build clean (0 errors, 24 pre-existing warnings — same baseline as 4.13). 140/140 Server.Tests pass. No commit made.
- Wave 4 remaining: 4.1 Lighting-FX GPU port (big — ILGPU port of HDR DoF skewed blurs); 4.2 GGX importance sampling per bounce; 4.4 Sandbox interpreter perf (opcode-flat dispatch or DynamicMethod IL emit); 4.5/4.6 Sandbox chain + Quat-Julia GPU dispatch; 4.12 follow-on (real MC + STL).
- Audit: 4.8 color drivers, 4.9 FOV/DoF/clip/SS, 4.10 multi-equation
chain (
-
2026-06-20 — Plan drafted from full roadmap survey. Wave 0 in progress.
-
2026-06-20 — Wave 0 complete. 0.1–0.4 found already shipped (re-survey). 0.5 visual-regression tool added at
Tools/VisualRegression/. 0.6 per-stage HUD microbar wired viaStagePerfstatic hook +PerfStats.RecordStagering + 5Apply*sites + HUD micro-rows. Full solution builds clean (dotnet build FracturingFogCLD.sln). -
2026-06-20 — Repo re-survey shows Phase X.0 geometry split largely shipped already (Engine/, FracturingFog.App/, FracturingFog.Win/, Hosting/, Rendering.D3D/, Audio/, Audio.Win/, Rendering.Silk/, Rendering.Skia/). Wave 1 needs re-audit before resuming.
-
2026-06-20 — Wave 1 re-audit complete. Code-side all 9 phases shipped (X.0 / X.A / X.B / X.1 / X.2 / X.3 / X.4 / X.6 / X.7). X.5 has code path via
AcceleratorProbeCPU fallback, missing per-RID device-kind smoke assert. CI green for all cross-platform assemblies;release.ymldraft-publishes per RID (zip / AppImage /.app). Remaining: manual smoke runs (1.S1/1.S2), two Avalonia rewrites (1.C1 FfmpegSetupDialog, 1.C2 ToyDragWindow), X.5 device-kind assert (1.C3), Wave 0.5 baseline-record (0.5b). Wave 1 effort drops from 25-30 d to ~4 d of cleanup + smoke. Wave 1 is no longer the launch blocker it was estimated as; Waves 2-7 unblocked. -
2026-06-20 — Wave 1 cleanup + Wave 2 partial complete.
- 1.C3 ✅
--ilgpu-probeflag wired in bothProgram.cs(WinExe) andFracturingFog.App/Program.cs; runsAcceleratorProbe.RunSmoketo enumerate devices + assert CPU fallback; per-RID expectation gates CUDA-on-ARM as a packaging bug. - 1.C2 ✅ Toy-mode drag wires
BeginMoveDrag(e)via InputSpongePointerPressedhandler (AttachToySpongeDraginMainWindow.axaml.cs). Win-only Win32 trick stays as fallback for the DX-HWND case where Avalonia events don't reach the sponge. - 1.C1 ✅ partial —
FfmpegSetupDialog.csre-included in cross-platformFracturingFog.Hosting.dll(Avalonia, no Audio/Views/Palette deps).AvaloniaDialogs.csdeferred — pullsFracturingFog.Views.*,PaletteBuilder.*, andAvaloniaShellBootstrapstatics; needs broader carve. - 2.3 ✅
CalculatorGenHotLoad.PersistAndLoad+LoadAllPersisted;HotLoadAndPersistRequestedevent in VM; "Compile + Save" button inUserEquationView.axaml; host warm-loads persisted .cs on boot from%LOCALAPPDATA%/FracturingFog/UserCalculators/. - 2.1 ✅ SA orders bumped 8 → 16 in template + emitter; all 10
generated calculators (
MandelbrotZ{2..5},MandelbrotPhoenix,MandelbrotTricorn,MandelbrotBurningShip,BurningShip,Tricorn,UserDslEquation) regenerated.--gentest MandelbrotZ2PASS;--saprobeshows healthy distribution at zoom 1e9-1e16 (gen vs legacy color counts within ±10). - 2.6 ✅
QualityPreset.AaSamplesfield (Standard=1, High=4, Ultra/Extreme=16) + render-hostRunMsaaAccumulateMandelbrotaverages N sub-pixel jittered passes for the canonicalMandelbrotCalculatorpath. Alt calcs (user-equation / sandbox) currently skip AA pending interface broadening.
- 1.C3 ✅
-
2026-06-21 — Wave 2.2 (D-3.16) shipped — Phoenix proper DE + scalar perturbation tier.
-
PhoenixKernel.StepWithPrevDerivcarries(dr, di, dprev_r, dprev_i)via recurrenceD_{n+1} = 2·z·D + 1 + p·Dp. Init (0,0,0,0). -
EscapeTimeCalculator.CalculatePhoenixnow passes real derivative toFillAuxAndColor— distance estimate + normal shading active for Phoenix where they were zeroed before (visible: edge glow, proper DE-based contrast). - New
CalculatePhoenixPerturbtier gated atZoom >= 1e10. Builds double-precision reference orbit at frame centre, then per-pixelδ + δ_prevrecurrence:δ_{n+1} = 2·Z·δ + δ² + ε + p · δ_prev. Reconstructs true(z, prev_z)each step so derivative tracking stays exact. Scalar only — no AVX2 / QD / BLA / glitch detection (deferred follow-on); enough headroom to unstick the SP-only iteration-count collapse Phoenix hits at deep zoom. - Generated
MandelbrotPhoenixCalculator.csleft untouched — unused by dispatch (FractalType.Phoenix→EscapeTimeCalculator); banner still says "DO NOT HAND-EDIT — copy + rename for divergent math". Future Roslyn-source-gen path (Wave 2.13) will regenerate it correctly once the emitter handles prev-z as a function of c.
-
-
2026-06-21 — Wave 2.7 (D-5.21) shipped — TAA temporal accumulation.
-
QualityPreset.TaaMaxSamplesfield — 1 = off (Draft/Standard), 8 (High), 16 (Ultra), 32 (Extreme). Caps total accumulated samples per still-camera spell so the loop terminates. -
FractalRenderHostcarries per-pixel R/G/B/Along[]accumulator + view fingerprint (Cx/Cy/Zoom/Iter/Width/Height/FractalType). First frame of a given view seeds the accumulator from the freshly-computed (and optionally MSAA-averaged)ColorBuffer. After each upload, if the fingerprint still matches and count < TaaMaxSamples, the host enqueues a newFrameJobwithTaaSampleIndex > 0. The calc thread branches: jitterCenter{X,Y}by Halton(2,3) sub-pixel,Calculate(), restore Center, blend the new colors into the sums, average →ColorBuffer, hand off to the standard upload path. Brightness/contrast + grid/watermark composite still apply to the averaged buffer. -
Resizedrops the accumulator (sized for old buffers). View changes self-invalidate via the fingerprint check; any userTrigger()drains the bounded(1) calc queue, so a queued TAA continuation gets replaced by the fresh frame before it runs. - Bypassed when
useAltis set (Burning Ship / Tricorn / user-equation hot load all run alt calcs). Only the canonical Mandelbrot path gets TAA; extending to alt calcs needs each one'sCenter{X,Y}plumbed the same way — deferred follow-on. - Build clean (0 errors, 4 pre-existing AVLN5001 Watermark warnings).
-
-
2026-06-22 — DD-BLA perf gating. User report: "performance at medium depth seems empirically worse once DD kicks in"; right-click outline- to-zoom lagging at ~1e18. Root cause: Wave 2.10 ran the DD-precision BLA merge unconditionally across the entire DD tier (1e12 → 1e25).
MergeDdis ~7× the flops ofMergeDouble(4 DdComplexMul + 2 DdAdd vs 10 muls/adds), and BLA rebuilds on every centre / orbit change — every pan event paid the full DD merge cost. Below ~1e15 the merged A_n ULP error hasn't yet accumulated to visible iteration banding. Fix: addedDdBlaZoomThreshold = 1e15constant inMandelbrotCalculator.cs.EnsureBlaTablenow picks DD merge only whenZoom > 1e15 && !DisableDdBla; below that, legacy single- precision merge (restoring pre-2.10 pan-responsive perf at the Standard / shallow-zoom tier). Above 1e15 DD merge fires unchanged. Outline lag at 1e18 is a downstream symptom:SetSelectionBox→RepaintWithPostFxshares the_uploadGatelock withCalculate— while a slow deep-zoom Calculate is in flight, every drag-event overlay update blocks behind it. Real fix is a perf budget that keeps Calculate under ~50 ms; tracked as 2.14 (DD δ-chain) + separate per-pixel QD/OD optimization wave. -
2026-06-22 — Wave 2.15 shipped — OD-aware UI navigation. User report: click-drag pan at deep QD/OD zoom collapsed CX/CY to 2 limbs in the main menu. Root cause:
FractalInputController.cshadRequiresQD-
RequiresDDpan/zoom branches but noRequiresODbranch — at zoom > 1e50 (OD threshold) the pan fell into the plain-doubleelsearm andClearLowLimbs()wiped CenterX/Y_Lo..X3. Even at QD zoom theStoreQDhelper never touched CenterX/Y_4..7, so stale OD limbs from a Copy/Paste survived a pan-zoom cycle. Fix:
- Added
_panStartODCX/CYcache, initialised inOnPointerDown. - Added
ODbranch as first arm of all six pan/zoom sites (pan move, box zoom, double-click pan, wheel zoom, key-pan, all threading through the same RequiresOD → QD → DD → SP precedence). - New
StoreOD(OD cx, OD cy)helper writes all 8 limbs from an OD. - Extended
ClearLowLimbs/StoreDD/StoreQDto also clear X4..X7 (consistent with their tier — DD has no X2/3 either). - Build clean, 140/140 tests pass.
-
-
2026-06-22 — Wave 2.14 filed. Path B (DD-precision PT δ chain) required to push usable zoom past 1e58. Path A (OD arithmetic fix)
- Path C (OD-aware navigation, shipped today as 2.15) are necessary but not sufficient by themselves — see 2.11 entry below.
-
2026-06-22 — User test of OD path. Coord:
CX = -1.9918151296901943|-7.8219844803880472E-17|1.6601399303929208E-34|-5.8601391417687406E-51 CY = -5.5240415753972429E-06|-2.8659813126937928E-22|6.6910924132216174E-39|-2.0109018297360669E-55 Zoom = 1.0E+51Threshold engagement: zoom > 1e50 enters OD path. Expected: no solid-colour render, no NaN, no all-black; visual quality similar to QD at same zoom (Path A only fixes the OD-arithmetic regression, not the underlying QD precision floor — those need 2.14 / 2.15).
-
2026-06-22 — Wave 2.11 OD arithmetic fix (Path A) —
operator *rewritten. Root cause: original tier-by-tier accumulator reusedThreeSumresidual variable names (r1a/r1boverwritten by tier-2ThreeSum,r3a/r3bby tier-3TwoSum), silently dropping tier-1 + tier-3 residual mass on every multiply. ~1e-32 noise per multiply compounded through the ref orbit and bubbled into X0 at iter ~127, collapsing every pixel to one colour at zoom ≥ 1e40. Fix: replaced with stackalloc 9-slot expansion accumulator (AddPair/AddProductpush partial products into the expansion via TwoSum cascade, residuals propagate forward without name reuse).- Threshold restored:
ODZoomThreshold = 1e50in bothMandelbrotCalculator.csandFractalViewState.cs. - Tests: 23 xUnit OD parity + stress tests in
Server.Tests/OctupleDoubleTests.cs. Key invariant — at user's pixelating coord (zoom 7.14E48), OD/QD agree to better than 1e-55 on X0 through iter 200 (beyond which QD's own precision floor swamps the comparison; deep-iter test verifies OD stays finite to iter 5000). - Saprobe smoke (1e9 → 1e60) runs without crash / NaN. Visual parity past 1e50 requires UI navigation populating CenterX4..X7 — the saprobe coord has zero X2..X7, so OD behaves identically to QD there regardless of fix.
- Pre-existing QD-floor pixelation at user's 7.14E48 coord remains open. OD threshold engages only past 1e50, so QD path still runs at that exact zoom; user-reported pixelation there is the QD precision wall on a specific orbit (chaos amplifies QD's 1e-62 ULP over ~600 iters to swamp the 5e-52 pixel scale). Path B (DD-precision PT δ chain) or full OD navigation pipeline needed — separate wave.
- Threshold restored:
-
2026-06-21 — Wave 2.11 regression. OD engagement at zoom 7e48 (after threshold lowered from 1e50 → 1e40) produced solid-colour render at user's prior-working 1e58 location. Suspect bug in
OD operator+carry cascade orRenorm9redistribution. Restored prior behaviour by settingODZoomThreshold = 1e100in bothMandelbrotCalculator.csandFractalViewState.cs— OD code stays compiled but inert; QD path runs at any practical zoom (verified user-side to ~1e58). Resolved 2026-06-22 — see entry above. -
2026-06-21 — Wave 2.11 (D-4.17) shipped (engine MVP) — Octuple-double (OD) reference orbit, 8-limb extended precision, ~124 decimal digits. Pushes the legacy
MandelbrotCalculatorzoom ceiling past 1e50 toward the ~10¹¹⁶ OD limit.OdEmitterfor generated calcs is a deferred follow-up (large mechanical port of the QD path; not blocking).-
Abstractions/Math/OctupleDouble.cs— newODreadonly struct (X0..X7), mirrorsQDAPI. HLB primitivesTwoSum / QuickTwoSum / TwoProduct / ThreeSumcarried over.Renorm9— 9-term QuickTwoSum cascade reducing to canonical 8-term form. Add (sloppy two-pass residual sweep), Sub, Mul (diagonal-by-diagonal partial product accumulation across 8 tiers — tier 6 keeps Hi-only on outer terms, tier 7 collapses to scalar mul; sufficient for ~124-digit retention after renormalize), Square (= this·this), Div (long-division by 8× Newton refinement on Hi limb). ImplicitOD ← double, explicit(double)OD,ToDD(),ToQD(),FromCenterOffset(center, pixel, scale)for the per-pixel coord factory. -
Abstractions/ViewState/FractalViewState.cs—CenterX4..X7/CenterY4..Y7properties,ODZoomThreshold = 1e50const,RequiresODflag.ResetView/SnapToFractalDefaultclear all 8 limbs.RequiresQDnow gated&& !RequiresODso QD doesn't fire when OD is required. -
Engine/Calculators/MandelbrotCalculator.cs— adds the same 4-limb center props (CenterX4..X7/CenterY4..Y7),ODZoomThresholdconst, OD limbs 4..7 of the reference orbit storage (_refZrX4..X7/_refZiX4..X7), and_refCx4..X7/_refCy4..Y7for the centerSame cache check. NewEnsureRefOrbitCapacity(maxIter)— single allocation point for all 8-limb arrays so QD/OD paths share storage. DD and QDcenterSameupdated to also require X4..X7 zero (avoids stale OD orbit reuse when zoom drops back through 1e50). NewComputeReferenceOrbitOD(OD cx, OD cy, maxIter)writes all 8 limbs per slot. NewComputePixelODmirrorsComputePixelQDwith OD subtraction in the SA prelude + OD inner iteration (per- pixel HP fallback when PT glitches at zoom > 1e50).Calculate()branches three-way: OD > 1e50 → QD > 1e25 → DD ≤ 1e25.ComputeRowPTScalar/ComputeRowPT4/ComputeRowPT8all detectuseODseparately, buildcy_od, and route glitched/tail pixels through OD instead of QD when active. -
Program.cs—--saprobeladder extended with 1e30 + 1e60 cases to exercise QD and OD code paths through the legacy calculator. At those zooms the probe coords (DD-only precision) are below pixel scale, so the result collapses to a single colour as expected; the verification is that the code path runs without crash, NaN, or memory issue. Real visual verification past 1e50 waits on the pan-zoom OD limb plumbing (out of scope this wave — requires UI cursor → OD-limb propagation, similar to the existing QD limb pan handling). - Build clean (0 errors, 24 pre-existing warnings).
- Known limitation: pan/zoom UI does not yet promote a screen
cursor to non-zero CenterX4..X7. Until that lands, OD ref orbit
runs at DD-precision center → output collapses past zoom 1e16.
Filing as follow-up wave (similar shape to the existing QD limb
pan handling at
Engine/Calculators/MandelbrotCalculator.cs:1450). - Known limitation: generated calcs (
Engine/Calculators/Generated/*) still cap at QD.OdEmitteris the next sub-task — large mechanical port mirroringQdEmitter+QdDirectEmitter. Deferred to keep Wave 2.11 shippable.
-
-
2026-06-21 — Wave 2.10 (D-4.18) shipped — DD-precision BLA tables.
-
Engine/Math/Bla.cs—Blastruct now stores A, B as double-double pairs (AReHi/AReLo, AImHi/AImLo, BReHi/BReLo, BImHi/BImLo). PublicARe / AIm / BRe / BImproperties returnHi + Locollapsed, so all 11 apply sites (SIMD broadcasts + scalar reads) work unchanged — one add per skip, negligible vs the merge-precision win. - New
BlaTable(refZr, refZrLo, refZi, refZiLo, refLen, dcMaxAbs)constructor seeds level-0 from the DD reference orbit:A = 2·ZwithA.Lo = 2·refZLo(multiply by 2 is exact in FP, so Lo carries through unchanged),B = 1exactly. Merge math (MergeDd) runs in DD throughout usingTwoSum/TwoProductprimitives mirroringAbstractions/Math/DoubleDouble.cs— complex DD × DD forA_m = A2·A1andB_m = A2·B1 + B2. Validity radius uses collapsed magnitudes (radius precision not load-bearing). - Legacy single-precision ctor still emits
Lo=0for all limbs; the genericBlaTable(Bla[] level0, …)overload picks the single-precision merge path (MergeDouble) so generated calcs are bit-identical to pre-2.10. NewDdPrecisionflag exposes which merge ran for the diagnostic log (BLA-DD:vsBLA:). -
MandelbrotCalculator.EnsureBlaTablealways picks the DD ctor when in the HP path —_refZrLo / _refZiLoare populated unconditionally (DD low limb for Zoom ≤ 1e25, QD X1 limb for Zoom > 1e25) so DD-BLA fires across the entire perturbation regime, not just near the QD threshold. - Smoke:
--saprobedeep-zoom histogram at z=1.08e12…1e16 — distinct colour counts stable, legacy calc tracks generatedMandelbrotZ2within ~5%, no iteration banding or solid-blob collapse. - Build clean (0 errors).
-
-
2026-06-21 — Wave 2.9 (D-6.25) shipped — Animation: morph equations.
-
UI.Avalonia/ViewModels/EquationMorph.cs— synth helper. Wraps two DSL sources A and B into(1-t)*(A) + t*(B)withtbaked as a numeric literal. Endpoint shortcut (t=0→ A verbatim,t=1→ B verbatim) skips the wrap-around so the parser never sees0.0 * (foo)noise. Validate helper parses A, B, and the mid-morph string (defensive — catches the rare case where both sides parse but the wrap trips a limit). -
UI.Avalonia/ViewModels/EquationMorphViewModel.cs+Views/EquationMorphView.axaml— modeless dialog. Two cookbook combos populate A + B (also free-edit TextBox),FrameCount(default 60, clamp 2..600),OutputDir(defaults to%PICTURES%/FracturingFog/Morph). Start/Stop + progress bar + cancellation. VM drives the loop; per-frame work delegated to host viaRenderAndSaveRequested(synth, outPath, ct) → Task<string?>event. -
Hosting/AvaloniaShellBootstrap— wiresMorphRequestedto openEquationMorphViewmodeless under the UE editor.RenderAndSaveRequestedhandler hot-compiles the synth DSL viaCalculatorGenHotLoad.TryCompileAndLoad, installs the result viaSetDynamicAltCalculator(which triggers a render internally), subscribes a one-shotAnimationFrameUploadedhandler to await upload (30 s timeout per frame), then callsSaveLastFrameToPng(outPath). Output sequence ismorph_0000.png…morph_NNNN.png; user assembles into MP4/GIF with ffmpeg/OBS. -
UserEquationViewModel—OpenMorphCommand+MorphRequestedevent. -
UserEquationView.axaml— new "Morph…" button in the action row, between "Cookbook…" and "Compile & Load". - SA is implicitly off across the sweep — the cross term
(1-t)*(A) + t*(B)almost always trips at least one of CalcGen's SA gates (conj/sin/fold/div etc.) once either A or B contains a non-polynomial op, and the polynomial structure changes per frame anyway. Spec calls this out as required; enforcement falls out of existing gating. - Build clean (0 errors).
-
-
2026-06-21 — Wave 2.8 (D-6.23) shipped — Equation cookbook + gallery.
-
UI.Avalonia/ViewModels/EquationCookbook.cs— 14 curatedCookbookEntryrows: Mandelbrot z²/z³/z⁴/z⁵, Tricorn, Burning Ship, Phoenix, Sin / Cos / Exp Mandelbrot, Lambda (Logistic), Newton z³−1, Magnet 1, mixed quadratic. Each carries a hand-tuned (centre, zoom) framing. -
CookbookViewModel+CookbookView.axaml— modeless picker dialog. Left column lists entries by name; right column shows DSL source + centre/zoom- description. Enter / "Use this equation" accepts; Escape / Cancel
closes. Selection-driven properties (
SelectedName/SelectedDescription/SelectedSourceDisplay/SelectedCentreDisplay) keep XAML bindings off the nullable struct, which Avalonia x:DataType doesn't traverse.
- description. Enter / "Use this equation" accepts; Escape / Cancel
closes. Selection-driven properties (
-
UserEquationViewModel—OpenCookbookCommand+CookbookRequestedevent opens the picker;ApplyCookbookEntry(entry)writes the source intoDslSource, snaps to the DSL tab, and firesCookbookCentreRequested(cx, cy, zoom)so the host re-centres the view. Editor preview panel picks up the new source via the existing DSL-validate path (no new wiring). -
AvaloniaShellBootstrap.OpenUserEquationEditor— wires both events; the cookbook window is shown modeless as a child of the equation editor, andCookbookCentreRequestedwritesViewState.{CenterX,CenterY,Zoom}then callsTrigger(). -
UserEquationView.axaml— new "Cookbook…" button in the action row. - Build clean (0 errors).
-
-
2026-06-22 — Wave 2.5 (D-5.20) shipped — Progressive rendering ¼ → ½ → full.
-
Engine/Rendering/FractalRenderHost.cs— two new sidecarMandelbrotCalculatorinstances (_previewCalcQuarter,_previewCalcHalf) permanently sized to (W/4, H/4) and (W/2, H/2), floor 64×64. Memory cost ~25 MB pinned LOH on top of the main calc's ~80 MB at 1080p.Resizeupdates both in step. -
FrameJob.ProgressiveStageint (0 = final, 2 = half, 4 = quarter).Trigger(progressive: true)enqueues a quarter-stage job; the upload tail schedules the next stage (4 → 2 → 0). Gated on the canonical Mandelbrot path (nouseAlt, no_dynamicAltCalculator) and surface ≥ 256×256 — alt calcs and tiny windows fall back to a single full render. -
MirrorMandelbrotState(src, dst)copies all 8 centre limbs + zoom + iter + quality + colour map + acceleration flags onto the sidecar so the preview reproduces the main view at downsample. - Preview upload pushes the sidecar's
ColorBufferat its smaller dims;DirectXRenderer.EnsureTexturerecreates the texture at those dims and the full-screen quad sampler stretches to the back buffer. No overlay composite, no TAA seed, no MSAA, no SSAO, no CDF rebuild, noFrameCompletedevent for non-final stages — those run only on the final full-res stage as before. -
UI.Avalonia/ViewModels/MainViewModel.cs—RenderHint.Fastnow callsTrigger(progressive: true)instead ofTriggerFast(). Each pan / wheel event cancels the in-flight chain (shared CTS) and restarts at ¼ res. Pan-stop debounce kept as backstop for single-Fast callers that don't follow up with a Full hint. - Build clean (0 errors). 140/140 server tests pass.
-
-
2026-06-22 — Wave 2.12 (D-6.27) — GPU reference orbit scaffold landed. Hi-only kernel runs end-to-end on CUDA; QD upgrade deferred to next slice.
-
Engine/Calculators/Gpu/GpuQD.cs— ILGPU-friendly QD math (mirror ofAbstractions/Math/QuadDouble.cs). Tuple-returning primitives,AggressiveInlining. Uses Dekker split-basedTwoProductinstead ofMath.FusedMultiplyAdd— ILGPU 1.5.3 doesn't intercept the BCL FMA intrinsic, so the FMA form throws "internal compiler error" during JIT. Built but not yet invoked by the kernel. -
Engine/Calculators/Gpu/MandelbrotRefOrbitGpu.cs— single-thread sequential kernel + host shim. Packs the 8 output limbs per slot intoRefOrbitSlotso the typed kernel loader stays at 4 generic params (8 parallelArrayView<double>blew past the loader's practical ceiling — kernel JIT failed before any math ran). First cut iterates Hi-only doubles; QD body wired viaGpuQDMathlives behind a TODO because ILGPU 1.5.3's IR-inliner trips onRenorm5/ThreeSum's deep(s, e1, e2) = ThreeSum(...)deconstruction cascades (kernel JIT failed identically to the FMA case). Two options for the QD upgrade slice: (a) rewrite GpuQDMath primitives to return mutable struct outputs instead of value tuples; (b) bump ILGPU to 2.x (different IR pipeline, tuples handled). -
Engine/Calculators/Gpu/MandelbrotRefOrbitGpu.cs— private FP64- capable accelerator (TryAcquireFp64). Walks devices CUDA → CPU. BypassesGpuAcceleratorHostfor the ref orbit because that picks ILGPU's preferred non-CPU device, which on this dev machine landed on Intel UHD OpenCL — "Float64 (double) type is not supported on this device", kernel can't compile. Skips OpenCL entirely (no cheap pre-flight FP64 probe; CPU is the only universally-FP64 fallback). ExposesSelectedDeviceLabelfor--gpurefprobe. -
Engine/Calculators/MandelbrotCalculator.cs—UseGpuReferenceOrbitstatic toggle (default off). When on,CalculateHighPrecisionQD branch routes throughTryComputeReferenceOrbitQDGpuwhich mirrors the centre-cache short-circuit inComputeReferenceOrbitQD, runs the GPU compute, and updates_refZr/_refZrLo/_refZrX2/_refZrX3(and zi counterparts) plus the cache fields. Failure falls back silently to the CPU path; failure reason logged viaDebug.WriteLine. Default off keeps every existing call site bit-identical to pre-2.12. -
Program.cs—--gpurefprobeflag. Runs three implementations side-by-side at QD-tier coord/zoom (1e15 + 1e30 saprobe coords): CPU-QD (truth), CPU-Hi (Hi-only baseline matching kernel math), GPU-Hi (kernel). Reports ms + Δ(GPU vs CPU-Hi) — should be FP64 round-off; Δ(GPU vs CPU-QD) — chaos-amplified, expected large until QD kernel slice lands. Writesgpurefprobe.out. - Smoke result on dev hardware (GeForce GT 710, FP64 1/24-rate): CUDA picked; kernel JITs (~490 ms first call, cached after); second call 1.54 ms vs CPU-QD 0.54 ms. Δ(GPU-Hi vs CPU-Hi)=346 — differs by CUDA's fused-mul-add rounding vs x86's two-step mul+add; not a bug, IEEE-FP64 semantics differ between backends. GT 710 isn't the target perf hardware — Wave 6 multi- cluster will need a modern CUDA card for the GPU path to beat CPU on sequential ref-orbit work; current win is offload + CPU overlap potential, not raw throughput.
- Build clean (0 errors); 140/140 server tests pass (toggle off, no behaviour change to default path).
- Open follow-ons: QD-body kernel (struct-output rewrite or ILGPU upgrade); benchmark on RTX-class CUDA card; integrate toggle into a host-side perf decision (auto-enable when measured GPU < CPU at rebuild time); promote to OD ref orbit once QD lands.
-
-
2026-06-21 — Wave 2.4 (D-6.24) shipped — Live equation preview.
- New
CalculatorGenApi.Preview(equation) → PreviewResultreturns the parsed AST in printed form (AstPrinter.Print), symbolicdz/dcanddz/dz(AstDifferentiator.{DpDc,DpDz}), SA gating (both fast and generic detectors), perturbation + DE feature flags, plus per-node presence flags (prev / iter / conj / fold / div / trans / cond). Same analysis pass the generator runs, exposed as a read-only projection with no file I/O so it's safe per keystroke. -
UserEquationViewModeladds observable preview state and callsUpdatePreviewfrom both the UE-tab CalcGen validator and the DSL-tab validator after a successful parse. Last-good values stay pinned on transient parse failures so the panel doesn't flicker. Dialog seeds the preview from the current source on open. -
UserEquationView.axamladds anExpanderbetween the editor and the status row showing AST / dz/dc / dz/dz / SA / Perturbation / DE / Flags. SelectableTextBlocks, monospace. Window grew to 700×680 to accommodate. - Build clean (0 errors, only pre-existing AVLN5001 Watermark warnings).
- New
-
2026-06-22 — Wave 3 kickoff. 2.14 (QD δ-chain) deferred — 3-5 d, no blocker. 3.1 (SIMD brightness/contrast) + 3.2 (suppress pre-overlay snapshot) found already shipped —
FractalRenderHost.cshas Vector256ProcessRowSimd(8 BGRA pixels/step, chunked Partitioner, pooled LOH) +_recordingActivegate at the snapshot site. Closed both as completed. -
2026-06-22 — Wave 3.8 superseded.
FractalInputController.csalready carries OD/QD/DD/SP branches on all 6 pan/zoom sites (OnPointerMove,OnPointerDoubleClick,OnWheel,ApplyBoxZoom,PanByPixels) withStoreOD/StoreQD/StoreDDwriting every limb — Wave 2.15's work covered the perf-plan finding.QDZoomThreshold= 1e25; DD branch handles 1e12–1e25 via Hi+Lo. Pan no longer collapses limbs in any tier. -
2026-06-22 — Wave 3.6 follow-on shipped — 19 remaining hand-written themes ported to
IGpuHlslPalette. New shared fileEngine/Models/HlslPaletteHelpers.csexposes two static prelude strings:HsvAndMods(cg_mods + cg_hsv_to_rgb that mirrorsFractals.HsvToRgbexactly — no input clamping;cg_pack_bgrasaturates at pack time) andModsOnly(for themes that need only the GLSL-style mod helper). Themes ported:GrayscalePalette,RainbowColorMap,FirePalette,Painted,PaintedReversed,Pastelly,WarpedHsvMap,GoldenRatioMap,MonoBandMap,BernsteinMap,RedAndBlack(Radio Interference),NebulaDustMap,DigitalMatrixMap,PsychedelicMap,TwilightCyclicMap,SolarWindMap,SolarWindMapMOD,CopperSheenMap,VintageSepiaMap,DistanceGlowMap. Each carries uniquePaletteIdso the kernel caches its compiled shader per theme. End-to-end GPU palette path (EscapeTimeCalculator.TryDispatchGpu→GpuKernel.SetPalette→gColorUAV write) now applies to every algorithmic theme in the EscapeTime dispatch table. Build clean, 140/140 tests pass. -
2026-06-22 — Wave 3.6 (T3.1 ext) shipped — HLSL palette codegen for hand-written
IColorMap.HsvPalettenow implementsIGpuHlslPaletteas the canonical hand-port template.HlslPaletteBodymirrorsMap()but with saturation=1 baked in, so the per-sector colour blend simplifies to (v,t,0)/(q,v,0)/(0,v,t)/(0,q,v)/(t,0,v)/(v,0,q) and the shader needs no helpers —HlslPreludereturns empty.PaletteId="HsvPalette/v1"so the kernel caches the compiled shader per id. Picked up automatically byEscapeTimeCalculator.TryDispatchGpu'sColorMap as IGpuHlslPalettecheck (and the matchingMandelbrotCalculatorpath); when the toggle is on and the kernel attached, the GPU writesColorBufferend-to-end and the CPU writeback skips. 3.5 deferred per user — needs orbit-validity infrastructure (~1-2 d). Follow-on (mechanical port using HsvPalette as template): GrayscalePalette, RainbowColorMap, FirePalette, Painted, PaintedReversed, Pastelly, WarpedHsvMap, GoldenRatioMap, MonoBandMap, BernsteinMap, RedAndBlack, NebulaDustMap, DigitalMatrixMap, PsychedelicMap, TwilightCyclicMap, SolarWindMap, SolarWindMapMOD, CopperSheenMap, VintageSepiaMap, DistanceGlowMap. Build clean, 140/140 tests pass. -
2026-06-22 — Wave 3.7 (Finding D) shipped — Adaptive HE crossfade. Root cause: slideshow
_lastUploadedBuffersnapshot source had HE applied (calc-completion path bakes HE beforeUploadProcessedBuffer) butRecolorActiveToBufferreturned the raw post-Calculate / post-ApplyBandDitherRecolorbuffer with no HE.FadeAsyncper-pixel lerp ended in the pre-HE target state; the post-fadeRepaintWithPostFxthen snapped HE onto the visible frame — the "HE pops on at end" jump. Fix: after the recolor compute,BuildHistogramCdf+ApplyHistogramEqualizationWithCdfapply HE to the recolor target whenViewState.HistogramEq > 0, matching the upload-path output so both fade endpoints are post-HE and the RepaintWithPostFx is now a no-op visually. Mandelbrot path only (alt calcs already bypass HE in the upload path). Build clean, 140/140 tests pass. -
2026-06-22 — Wave 4.13 shipped —
.fbulbsnapshot import/export. Pre-4.13UserBulbStore.ExportEntry/ImportEntryonly round-trippedUserBulbEntry(Name/Source/Promoted/Chain) — the preset's axis mode / Julia c / camera / lights / colour driver / render budget were lost on export. Per the Wave 4.11 follow-on,.fbulbis the schema-extension point that finalises per-entry persistence so Quaternion-Julia + KIFS presets reload exactly the way they were saved.-
Abstractions/Models/UserBulbSnapshot.cs— new versioned envelope.Version = 1,Entry(UserBulbEntry), plus ~30 nullable knobs mirroringFractalParameters.UserBulb*: axis mode, compiler, DE mode, backend, QuatSliceW, Julia mode + (Cx,Cy,Cz,Cw), camera distance / theta / phi, light theta / phi, light 1-3 intensity, AO samples, fog density, colour driver, orbit-trap (X,Y,Z), iter-component axis, iterations, max steps, epsilon, bailout, Jacobian h, cull radius, FOV, clip-plane, super-sample, time, named params list. Every knob nullable → missing fields leave the target slot untouched on import, so older + newer producers interoperate without breaking changes. -
UserBulbStore.ExportSnapshot(snapshot, path)— writes the envelope withJsonIgnoreCondition.WhenWritingNullso emitted JSON only contains what the producer actually set; keeps.fbulbfiles small.ImportSnapshot(path)reads the envelope, merges the entry into the store (collision rename(N)), returns the parsed snapshot so the caller can apply the runtime knobs. - Legacy fallback in
TryParseSnapshot:JsonDocument-peeks the root forVersion+Entry; absent → parses as bareUserBulbEntry, wraps in a snapshot withVersion = 0(sentinel for "legacy, no knobs to apply"). Pre-4.13.fbulbfiles written byExportEntryround-trip unchanged. -
UserBulbViewModel.OnExportbuilds the snapshot from_paramsvia newBuildSnapshotFromParams(entry).OnImportcallsImportSnapshot, applies non-null knobs throughApplySnapshotToParams, thenSyncMirrorFromParamsre-pulls every VM mirror field + raisesPropertyChangedfor the bound views — suppress flag stays on through the bulk update so no per-property render fires; the finalLoadEquationByNametriggers one compile + render. -
ExportEntry/ImportEntryretained for legacy callers (none in tree besides the now-rewritten VM). Doc-comments flag them as pre-4.13. - Build clean (0 errors, 24 pre-existing warnings). 140/140 Server.Tests pass.
- Follow-on:
.fbulbregistered as an OS file-association handler (double-click → open in FF). Not in 4.13 scope — file format works end-to-end via the editor's Import/Export buttons. Filed as 4.13.f1.
-
-
2026-06-22 — Wave 4.11 shipped — UserBulb preset library seed. Audit of
Abstractions/Models/UserBulbStore.cs:SeedDefaultsfound 6/10 of the spec list (Docs/Technical/UserBulb3D-DevelopmentPlan.md:387-398) already seeded — Mandelbulb p=8, Square triplex (squared variant), Sin-bulb, Abs-bulb p=8, Mandelbox, Animated breathing bulb. Four missing: Menger sponge step, Sierpinski tetrahedron, Kaleidoscopic IFS chain, Quaternion Julia. Added this turn:-
UserBulbStore.SeedDefaults— 4 newEquations.Addcalls. Menger / Sierpinski reuse the bodies already centralised inUserBulbChainPrimitives.GetById(Id{Menger,Sierpinski})so the standalone preset and the hybrid-chain step are bit-equal. Quaternion Julia uses the same triplex-squared body asSquare triplexwith an inline// Switch Axis Mode → Quat + Julia Modecomment — neitherSourcenorChaincarries axis-mode / Julia-mode flags today, so the user toggles them in the editor; per-entry persistence is the Wave 4.13 (.fbulb) schema work. -
UserBulbStore.TopUpBuiltins— 4 matchingEnsure(name, factory)calls so pre-existing%APPDATA%/FracturingFog/userbulbs.jsonfiles pick up the new entries on next launch (mirrors how the B.3 hybrid chains were retro-fitted). -
UserBulbChainPrimitives.KaleidoscopicIfsChain()— new 3-step factory (Sierpinski fold → Y-axis rotation → scale-2 + translation), matching the spec's "chain: fold → rot → scale, 3 steps". Two new id constsIdKifsRot/IdKifsScaleso the kernel caches per step. The Kaleidoscopic IFS preset'sSourceis a single-pass fallback for legacy chain-less loaders;Chainis the canonical form and overrides at runtime per theUserBulbEntrydoc-comment contract. - Build clean (0 errors, 24 pre-existing warnings — CS0219 in generator output + AVLN5001 Watermark obsolete). 140/140 Server.Tests pass.
- Follow-on: per-entry axis-mode / Julia-c / camera persistence — Wave 4.13 (.fbulb single-equation import/export) is the place for the schema extension; Wave 4.11 deliberately scoped to chain-source seeds only to avoid churning UserBulbEntry mid-wave.
-
-
2026-06-22 — Wave 4.7 shipped — UserBulb 3.4 time global + animate bar. Audit revealed engine-side + ViewModel-side already shipped earlier:
FractalParameters.UserBulbTime(cloned),UserBulbCalculatorcompile sig appendsdouble t = __p[__p.Length - 1],UserBulbView.axamlanimation row (Play/Pause / Speed / t),UserBulbViewModel.AnimationTick-
NotifyRenderDonegating, andAvaloniaShellBootstrap30 HzDispatcherTimerpumpingvm.AnimationTick(dt)while gated onAnimationFrameUploaded. Outstanding piece per the original 3.4 spec (Docs/Technical/UserBulb3D-DevelopmentPlan.md:250): loop-length knob. Added this turn:
-
UserBulbViewModel.AnimLoopSeconds(clamp 0..600). When > 0,AnimationTickwrapstinto[0, L)vianext -= L * floor(next/L). Default 0 = no loop, preserving the prior monotonic-advance behaviour. -
UserBulbView.axamlanimation row gains "Loop s:" NumericUpDown between Speed and t. - Build clean (0 errors, 4 pre-existing AVLN5001 Watermark warnings). 140/140 Server.Tests pass.
- Open follow-on: video time-sweep mode (spec 3.4 line 263) — wire
BulbTimeSweepEnabled/BulbTimeStart/BulbTimeEndintoVideoZoomRequestso the video pipeline can lerpUserBulbTimeper frame. Not blocking; filed as 4.7.f1.
-
-
2026-06-22 — Wave 0.5b shipped — Visual-regression baseline recorded.
Tools/VisualRegression/Program.csneeded four pre-flight fixes before the record run could succeed:- Shelled the cross-platform
FracturingFog.Appstub which doesn't yet handle--batch(the BatchEntry CLI lives in the WinExe). ConstantBatchProject = "FracturingFogCLD.csproj"flips the target. -
MagnetOne/MagnetTwocorrected toMagnet1/Magnet2matchingFractalTypeenum literals inAbstractions/Models/Enums.cs. - Each case now passes
--x 0 --y 0 --zoom 0.5(BatchOptions validator requires region or coords; procedural / non-escape-time families ignore them and use family-internal framing). - Per-case timeout bumped 120 s → 600 s to absorb the cold-cache rebuild on the first invocation; subsequent cases skip rebuild and render in seconds at 256² Standard.
- Redirected stdout/stderr drained via
BeginOutputReadLine/BeginErrorReadLine(no-op handlers). Initial record-attempt wedged on the Flame case — child blocked writing into a full pipe buffer because the harness never read the redirected streams. Standalone Flame batch completed in seconds; only the un-drained pipeline blocked. Other 21 cases passed at first attempt because their per-case chatter stayed under the pipe-buffer ceiling. Recorded 22 SHA256 entries toTools/VisualRegression/baseline.json. Curiosity:newton-defaultandnova-defaultshare a hash at the (0,0) centre — either Nova falls back to Newton-rendering at default knobs or both produce identical output at this framing. Baseline pins current behaviour; future regression run will fire if either changes. Filed separately as an audit (not in 0.5b scope). Non-determinism note: Buddhabrot + IFS hashes shifted across two record runs (RNG-driven Monte Carlo). These two cases won't gate as bit-equality regressions until their RNG is explicitly seeded — separate follow-on (deferred). Plasma usesPlasmaSeedfield default so its output stayed stable.
- Shelled the cross-platform
-
2026-06-22 — Wave 2.6 broadening shipped — AA for alt calcs. Wave 2.6 originally landed canonical Mandelbrot AA only ("alt calcs (user- equation / sandbox) currently skip AA pending interface broadening" per status entry 2026-06-20). Now extended to every alt calc whose
IFractalCalculator.SupportsZoomPanis true — escape-time families (Newton/Nova/Halley/Secant/Magnet1/Magnet2/Glynn/Spider, Phoenix viaEscapeTimeCalculator), user-equation hot-load path, sandbox, and 3D raymarchers (Mandelbulb/UserBulb/Mandelbox/KIFS/QuaternionJulia/ QuaternionMandelbrot/Bicomplex/Kleinian). Procedural / non-escape-time families (IFS/LSystem/Plasma/Flame/DLA/Apollonian/StrangeAttractor/ Buddhabrot/Nebulabrot/Anti*) gate out at theSupportsZoomPancheck — theirCalculate()ignores centre+zoom so jitter would just re-roll noise.- New
RunMsaaAccumulateAlt(IFractalCalculator, aaSamples, token)helper mirrorsRunMsaaAccumulateMandelbrotshape: sub-pixel jitter on (CenterX, CenterY) over a √N×√N grid at 3.5/max(W,H)/Zoom pixel scale, re-runsCalculate(token)per sample, accumulates BGRA channel sums in pinned int arrays, writes the weighted-mean colour back to ColorBuffer. MandelbrotCalculator stays on the typed helper because it isn't anIFractalCalculator(carries QD/DD/OD limb fields the interface doesn't expose). - Call-site at
FractalRenderHost.cs:1038branches:!useAlt→ existing Mandelbrot helper;useAlt && altCalc.SupportsZoomPan→ new alt helper;useAlt && !SupportsZoomPan→ AA skipped (procedural family). - Heavy delegate overhead on UserEquation/Sandbox/UserBulb Calculate() remains; user opts into 4×/16× cost by picking High/Ultra/Extreme QualityPreset. Standard stays 1× AA across the board (unchanged default).
- Build clean (0 errors). 140/140 Server.Tests pass.
- New
-
2026-06-22 — Wave 1.C1 closure shipped — AvaloniaDialogs.cs carved into cross-platform
FracturingFog.Hosting.dll. Three blockers resolved:- QD coord codec (FormatCoordSingle / TryParseCoordSingle / TryParseCoordAny
- DecomposeDouble / ExactSum / RationalToDouble) relocated from
Views/Controls.csFormHelpers (WinForms-bound) to newAbstractions/Math/QdCoordCodec.cs. FormHelpers retains the legacyFracturingFog.Views.FormHelpersAPI as thin delegating shims for in-tree WinForms callers.
- DecomposeDouble / ExactSum / RationalToDouble) relocated from
-
AvaloniaShellBootstrap.AudioCapabilitiesstatic dependency replaced withFracturingFog.Audio.AudioCapabilityProbe.Detect()in the cross-platform Audio assembly. AvaloniaShellBootstrap now delegates to the probe; AvaloniaDialogs calls the probe directly so the carve has no remaining reference to the WinExe-pinned bootstrap. -
PaletteBuilder.Views.MainWindowavailable cross-platform since Wave 1.8 (PaletteBuilder.Lib TFM = net10.0). AddedPaletteBuilder.Lib.csprojProjectReference toFracturingFog.Hosting.csproj. -
FracturingFog.Hosting.csprojdrops<Compile Remove="AvaloniaDialogs.cs" />;FracturingFogCLD.csprojadds it (WinExe consumes across the ProjectReference). AvaloniaDialogs visibility flippedinternal → publicbecause AvaloniaShellBootstrap (still WinExe-only) calls into it across the new assembly boundary. - Full solution builds clean (0 errors, 0 warnings). 140/140 Server.Tests pass. Wave 1 launch blockers now reduce to manual smoke runs (1.S1/1.S2) + per-RID device-kind assert wired (1.C3) + Wave 0.5b baseline-record.
- QD coord codec (FormatCoordSingle / TryParseCoordSingle / TryParseCoordAny
-
2026-06-22 — Wave 3.4 (T3.3) shipped — Non-temporal AVX writes.
FractalRenderHost.ProcessRowSimd(brightness/contrast Vector256 inner loop) now usesVector256<uint>.StoreAlignedNonTemporal(uint*)when the dst buffer (pinned POH_uploadDstPool) is 32-byte aligned at start. Each step writes 32 bytes (vecLen=8 uints) so alignment is preserved across the loop — one pre-loop alignment check splits the hot path into NT vs fallbackStoreUnsafeloops, no per-iteration branch. Bypasses L2/L3 cache eviction for the post-FX buffer that GPU upload consumes immediately without CPU re-read. Build clean, 140/140 tests pass. -
2026-06-22 — Wave 3.3 (T2.3) shipped — Multibrot SIMD. Mandelbrot/Julia/BurningShip/Tricorn already SIMD via
ISimdFractalKernel. Multibrot previously stayed scalar with polar form (Math.Atan2/Pow/Cos/Sinper step). Added direct complex-multiplication scalar + SIMD paths for d∈{3,4,5}:- d=3:
z³ = zr(zr² − 3 zi²) + i zi(3 zr² − zi²); 3·z² derivative. - d=4:
z² = u + iv,z⁴ = (u² − v²) + 2 u v i; 4·z³ derivative. - d=5:
z⁴ = U + iV,z⁵ = (zr·U − zi·V) + i(zr·V + zi·U); 5·z⁴. Each Step/StepSimd branches on_d— predictable, hoist-friendly. d≥6 keeps polar fallback.SimdSupportedflag drives dispatch:EscapeTimeCalculator.CalculatepicksDispatchByColorMapSimdfor d∈{3,4,5}, scalarDispatchByColorMapotherwise. Interface comment inIFractalKernel.csupdated to reflect the new coverage. Build clean (0 errors), 140/140 server tests pass.
- d=3:
-
2026-06-22 — Wave 2.13 (D-7.29) shipped — Roslyn source generator. Replaces the legacy
dotnet run -p CalculatorGenstep with a compile-timeIIncrementalGenerator. Deleted ~33 K lines of hand-checked-in generated source.- New
CalculatorGen.SourceGen.csproj— netstandard2.0 Roslyn analyzer. Compile-includes the existing Parser/Emitters/Api tree from..\CalculatorGen\(excludingProgram.csandCalculatorGenHotLoad.cswhich need net10.0 + Roslyn Scripting).Polyfills.csshimsIsExternalInit+System.Index/Rangefor the netstandard2.0 TFM so the original net10.0 source compiles unmodified. Diagnostics: CG001 empty-equation, CG002 parse-fail, CG003 missing-name. -
[assembly: GeneratedCalculator(equation, name, IncludeSelfTest?, Bailout?)]attribute injected viaRegisterPostInitializationOutput. Assembly-level-
AllowMultiple=trueso one registry file can declare every calc. Generator pulls all attribute instances fromCompilation.Assembly.GetAttributes(), runsCalculatorGenApi.Generate, emits{Name}.g.cs(and{Name}SelfTest.g.cswhen requested) viacontext.AddSource.
-
- Templates updated:
// <auto-generated />+#nullable enableas lines 1-2 of bothCalculator.template.csandSelfTest.template.cs. Suppresses CS8669 on emitted source; applies equally to any legacy CLI regeneration. -
Engine/FracturingFog.Engine.csprojreferences the SourceGen project withOutputItemType="Analyzer" ReferenceOutputAssembly="false"— runs inside the compiler, no runtime dep added. -
Engine/Calculators/Generated/GeneratedCalculatorAttributes.cs— central registry. 10[assembly:]declarations cover all stock calcs: MandelbrotZ2..5, Tricorn, MandelbrotTricorn, BurningShip, MandelbrotBurningShip, MandelbrotPhoenix, UserDslEquation. - Deleted 20 files (10 calc + 10 selftest) = ~33 K lines. Build artifacts
now flow
obj/.../FracturingFog.CalculatorGen.SourceGen/.../*.g.cs. - WinExe
FracturingFogCLD.csprojgot<Compile Remove="CalculatorGen.SourceGen\**" />matching the sibling Lib exclusions — sidesteps the CS0579 duplicate-AssemblyInfo cascade that hits every sibling project under the WinExe root. - Smoke: full solution build clean (0 errors, 0 warnings). 140/140
Server.Tests pass.
FracturingFog.exe --gentest MandelbrotZ2reports PASS across scalar↔AVX2↔GPU↔perturbation↔BLA↔QD-ref-orbit; 0 mismatches at 4096 pixels. - Legacy
dotnet run -p CalculatorGen --equation … --name …CLI still works (sibling Lib unchanged) and writes byte-equivalent source to disk — useful for inspecting generator output or hot-loading user equations.CalculatorGenHotLoad(user-equation Compile-&-Load runtime path) also unaffected.
- New