Skip to content

Open Work Plan

Bradley Brown edited this page Aug 13, 2026 · 1 revision

Open Work Plan — 2026-06-20

Master execution plan rolling up every open item across the project's roadmap docs. Source roadmaps surveyed:

  • Docs/Performance-Roadmap.md
  • Docs/Lighting-FX-Roadmap.md
  • Docs/Fractal-Expansion-Roadmap.md
  • Docs/Technical/CalculatorGen-Roadmap.md
  • Docs/Technical/Performance-DevelopmentPlan.md
  • Docs/Technical/PHASE2_AVALONIA_MIGRATION.md
  • Docs/Technical/CrossPlatform-Roadmap.md
  • Docs/Technical/CrossPlatform-ImplementationPlan.md
  • Docs/Technical/UserBulb3D-DevelopmentPlan.md
  • Docs/Technical/UserBulbSandbox-DevPlan.md
  • Docs/Documentation-Plan.md

Strategy: ship-blockers first → cheap perf wins → feature lifts → polish. Critical path = Cross-Platform roadmap (whole roadmap blocks Linux/macOS launch).


Wave 0 — Quick wins (cheap, low-risk, < 1 day each)

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.


Wave 1 — Cross-Platform foundation (BLOCKS LAUNCH)

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 ⚠️ Code path shipped (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.


Wave 2 — CalcGen high-impact

# 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)

Wave 3 — Perf Tier 2 + 3 tail

# 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

Wave 4 — Lighting/FX + UserBulb features

# 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

Wave 5 — Fractal Expansion polish

# 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)

Wave 6 — Multi-cluster glitch rebase

# 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

Wave 7 — Docs

# 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.

Wave 8 — WinForms retirement (USER-GATED)

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

Parallelisation map

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

Estimates (single dev)

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.


Deferred follow-ups — deep-region smoke test (2026-07-05)

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).

Remaining deferred work — research-complete handoff (2026-07-06)

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.FastTrigger(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.

Status log

  • 2026-07-06 — SM-11b (recycle-during-drag) tested → not needed; SM-11 RESOLVED as sound. Added per-render MandelbrotCalculator.AllowRecycleThisRender + host RecyclePreviewOrbit so a drag's progressive PREVIEW sidecars reuse one reference orbit. New --panjitter probe (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 --navrepro 16px 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). RecyclePreviewOrbit left 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 a ForceScalarPtPath toggle to route ALL pixels through it. --navrepro ... scalar ddref on 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 new Docs/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) --navrepro centre-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. ViewCamera header 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. --navrepro on 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 uses Trigger(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 orbit Z_n at double (_refZr[m] = X0 only) even though the full 8-limb OD orbit is stored (_refZrLo.._refZrX7 sit unused) — two nearby centres' double-rounded Z chains diverge over ~4200 iters of chaotic amplification. Filed SM-11. --navrepro gained 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-limb cx=/cy=, zoom=, dim=, click=), renders the frame, runs the real FractalInputController double-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.txt is 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 --focusprobe renders a deep frame, double-click focuses via the real FractalInputController, 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 --inputprobe extended 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: MaxUsefulZoomLog10 computed free in all three ref-orbit builds, carried on RenderFrameInfo, 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 was OD.FromCenterOffset adding 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 --qdfloorsweep OD 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). Regression FromCenterOffset_DeepOffset_SeparatesAdjacentPixels added; 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 read Extreme.ZoomMax directly. Going past 1e64 filed as SM-6 (OD reference orbit + new tier). Stale QD-ceiling comments corrected in MandelbrotCalculator.

  • 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 build kept 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:Rebuild resolved it. Also fixed a real render bug found in passing: ApplyView copied 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 (MirrorMandelbrotState already 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/SetCenter typed accessor. All six FractalInputController sites now delegate to ViewCamera — the per-tier cascades + DD/QD/OD pan-start caches + Store* deleted (~150 lines). New precision tiers extend DeepComplex only, 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 — --rebaseprobe PASS. New MandelbrotCalculator.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) when AllowPtRebasing is 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 AllowPtRebasing on 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 at ComputePixelPT), 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: commits baade13 (qdfloorprobe) + this entry's sweep.
  • 2026-07-05 — Deep-region smoke-test triage + --regionprobe diagnostic. 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) gates TryRecycleReferenceOrbit: when the view centre moved by less than RecycleMaxShiftFactor (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/_refRecycleDy in 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. Public RefRecycleHits/RefRecycleMisses diagnostics. 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.ProbeDE test 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.md landing 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 in Open-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 the ConcurrentBag<(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 below QdDirectZoomThreshold = 1e25, DD-direct cheaper there), bbox- cohesion guard (skip long-thin tendrils with density < 2%), centroid build of shared QD reference orbit via BuildRebaseRefOrbitQd (no BLA, no SA), 4-slot LRU cache lookup via TryGetCachedRebaseOrbit keyed by centroid within scale·16 tolerance + maxIt, sample-probe of first 8 pixels (commit only when ≥ 50% land), parallel TryIterateRebasePixel over remainder. Failures route to HpDirectGlitchPixel.
    • UseClusterRebase default flipped false → true this 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 MandelbrotZ2 0-diff scalar↔AVX2↔GPU↔perturbation↔ BLA↔QD-ref-orbit at 4096 pixels. --saprobe histogram 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 (canonical FractalType.Mandelbrot path) has no cluster rebase at all — glitched lanes in PT4 / PT8 / PT8-512 fall straight to per-pixel ComputePixelOD /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-aware BuildRebaseRefOrbitOD extension (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.md Item 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 > 1 after 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 / MandelboxRot enum values, ComboBox row in FractalParamsView.axaml, DispatchDE switch in KifsCalculator.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 MandelboxCalculator shape. Filing as 5.9.f1: replace the three current DE bodies with Mandelbulber-ported versions and extend KifsCalculator with a dr-magnitude accumulator threaded through DispatchDE. Estimated 1-2 days.
    • 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 record when ready to absorb the ~10 min cold-build cost.
    • 5.1 audit conclusion — theme gating is implicit (per-calculator capability + EquationProfile feature-bit recommender), not a central FractalType→tag registry. New families pick up sane defaults via interface gating (e.g. IInteriorAwareColorMap only runs inside MandelbrotCalculator.RunInteriorPass; alt calcs silently skip). No new tag table needed. Documented + closed.
    • 5.2 — Engine/Models/FractalRegion.cs _builtIns extended 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 (/, ^2 work) but the pole-clamp on the denominator has no DSL operator → NaN blow-up on pole pixels. Glynn needs fractional z^1.5 — DSL ^ is integer-only. Spider mutates c per iteration — DSL state model assumes constant c. All four stay hand-written scalar / SIMD kernels in Engine/Models/FractalKernels/. No [assembly: GeneratedCalculator] entries added.
    • 5.4 — Two-level Mathematics-tab grouping. New HelpSubTabGroup record in Abstractions/Help/IHelpContentProvider.cs. Default interface impl wraps the flat MathSubTabs into a single "All" group so legacy hosts stay compatible. HostHelpContentProvider overrides with a 7-group layout: Overview / 2D escape-time / Histogram / Procedural / 3D + 4D / Authoring / Generated. FloatingHelpView.axaml now 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.cs gained 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 ApplyVariation switch 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 BicomplexSliceAxis enum {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 in FractalParamsView.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's Grid.Row was "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).
  • 2026-06-23 — Wave 4.12 shipped — Marching Cubes mesh export (OBJ smooth + binary STL).

    • Export/UserBulbMeshExporter.cs rewritten. 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//c lines). Legacy ExportObjVoxelSurface kept 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) and TriTable[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.5 to match the surface band the raymarcher considers solid (the User Bulb DE estimator clamps positive outside; matches the legacy voxel path's surfaceEps).
    • MC sweep over the n³ cells: 8-corner sign comparison against iso → ci ∈ [0,256), fetch em = EdgeTable[ci], materialise the up-to-12 edge vertices via linear interp toward iso, then emit triangles by indexing through TriTable[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 + vn lines (1-indexed) and faces as f a//a b//b c//c so DCC tools (Blender, MeshLab) pick the smooth normals up directly. STL writer emits the standard 80-byte header (FracturingFog UserBulb MC), uint32 triangle 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:1741 to call ExportMarchingCubes (extension-dispatched) instead of ExportObjVoxelSurface. UserBulbViewModel.OnExportMesh widened 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 the i/j/k triple loop (CA2014 "potential stack overflow"). Unset entries from prior cells are never read because TriTable only indexes edges marked in EdgeTable[ci], which is exactly the bitmap that gates the conditional GetOrCreateEdgeVert writes.
    • Build clean (0 errors, 0 warnings — the 4 pre-existing AVLN5001 TextBox.Watermark Avalonia warnings stay on UI.Avalonia / PaletteBuilder.Lib, unrelated). 140/140 Server.Tests pass. 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.

    • GpuRenderParams extended with JuliaMode (int 0/1), JuliaCW/CX/CY/CZ (double), JacH (double), UseAnalyticDE (int 0/1). Default-zero values are inert for legacy UserBulbGpuCalculator.BulbKernel (it never reads them), so the Roslyn-source / hand-written GPU path keeps its bit-identity.
    • UserBulbSandboxGpuCompiler kernel scaffolding refactored. The DE + Kernel string bodies extracted to constants (VecSandboxDESource, QuatSandboxDESource, KernelBodySource)
      • helpers (AppendKernelPrelude, AppendStepFn). Both BuildKernelSource and BuildChainKernelSource now compose from the same parts; previously the chain version was a verbatim duplicate of the single-step kernel scaffolding.
    • Unified Quat DE: branches on p.UseAnalyticDE (power-map vs 5-trajectory Jacobian) and on p.JuliaMode (per-pixel c vs Julia parameter from p.JuliaC* with z₀ taking the pixel coord). Numerical-Jacobian mirrors CPU UserBulbQuatDE: 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.
    • SandboxDE signature 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. All KernelBodySource callsites updated.
    • UserBulbCalculator.Calculate GPU 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 CPU useAnalytic gate of !juliaMode && analytic, so Julia falls into the numerical branch).
    • GpuRenderParams populated 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. --ubtest quat 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.Calculate GPU branch was _compiledCompiler == Sandbox && !useChainPath — chain mode locked to CPU. Now useChainPath selects between UserBulbSandboxGpuCompiler.TryCompileChain(...) and the existing TryCompile(...).
    • UserBulbSandboxEmitter.Emit gained a 5-arg overload accepting IReadOnlyDictionary<int, (string Name, SbxEmitKind Kind)> extraSlots. EmitCtx stores the map and EmitSlot checks it after the let-substitution probe and after the z/c/n/params/t lookup — slots that survive to the fall-through branch are chain prior-step outputs. The original "extras past paramCount collapse to t" bug fixed; unbound slots now throw NotSupportedException rather than silently aliasing t. Slot kind also seeded into _slotKinds so step0.x member access infers Real correctly.
    • UserBulbSandboxGpuCompiler.TryCompileChain(steps, paramNames, quatMode) — new entry point parallel to TryCompile. Reuses SandboxBulbChain.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 from steps[i].OutputName sanitised via SanitizeIdent (leading char letter/underscore, non-ident chars replaced with _).
    • BuildChainKernelSource mirrors BuildKernelSource but the Step body 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. Surrounding SandboxDE + Kernel raymarch 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. --ubtest chain 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).
  • 2026-06-22 — Wave 4.4 shipped — Sandbox interpreter opcode-flat dispatch.

    • Hot path was Sbx3Binary.Eval / Sbx3Call.Eval running string switches on Op ("+", "&&", "<=", …) and Name ("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) and SbxFuncId (34 values covering every built-in vec/quat/scalar function). Resolved at AST construction time — Sbx3Binary ctor calls ResolveOp(op) once, Sbx3Call ctor calls ResolveFunc(name) once. Original string Op / string Name fields retained so UserBulbAnalyticDE.DetectSandbox pattern matchers (Sbx3Binary { Op: "+" } / call.Name == "triplex") and UserBulbSandboxEmitter.EmitBinary / EmitCall (which read b.Op / call.Name) continue to work bit-identically.
    • Sbx3Binary.Eval now switches on OpKind (SbxBinOp). 13-arm enum switch lowers to a dense jump table.
    • Sbx3Call.Eval now switches on Func (SbxFuncId). Two-stage structure preserved: multi-arg ops in the first switch, scalar unary transcendentals in the trailing enum switch (handles Quat-rejection via ApplyScalar).
    • 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. --ubtest runs 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).
  • 2026-06-22 — Wave 4.1 + 4.2 shipped.

    • 4.1 — ScreenSpacePost.ApplyHdrDof now gates on fx.UseGpuPost and routes the three skewed-box blurs through new ILGPU kernels in GpuPostKernels.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 CPU SkewedBoxBlur — foreground neighbours contribute only when their CoC reaches the centre), and DofMinBlendKernel (composites three skewed passes back into hdrBuffer via 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 accept float.IsNaN). Falls back to the CPU path on any init / OOM / kernel throw — no new toggle, the existing UseGpuPost knob covers it.
    • 4.2 — LightingFxData.UseGgxSampling (default false preserves 16b mirror-reflect bit-identity). When on, both reflect-direction sites in ShadingPipeline.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); and SampleGgxReflect(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 existing Roughness field (alpha = roughness²); Roughness = 0 collapses to mirror by definition.
    • 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).
  • 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 at Export/UserBulbMeshExporter.cs shipped, real Marching Cubes (256-entry triangulation table) + STL writer + normal smoothing still open.
    • 4.3 HDRI auto-preload — HdriProbe.Preload action added (Abstractions/Rendering/Lighting/HdriProbe.cs). HdriRegistry static ctor wires it to Task.Run(() => TryLoadFromFile(...)). HdriRegistry.TryLoadFromFile rerouted through a _parseGate ConcurrentDictionary<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.EnvironmentName setter (UI-driven changes); (b) ShellViewModel theme + region preset apply sites that assign FractalParameters.Lighting wholesale (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 is parse_ms + 1 frame not parse_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).
  • 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 via StagePerf static hook + PerfStats.RecordStage ring + 5 Apply* 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 AcceleratorProbe CPU fallback, missing per-RID device-kind smoke assert. CI green for all cross-platform assemblies; release.yml draft-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-probe flag wired in both Program.cs (WinExe) and FracturingFog.App/Program.cs; runs AcceleratorProbe.RunSmoke to 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 InputSponge PointerPressed handler (AttachToySpongeDrag in MainWindow.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.cs re-included in cross-platform FracturingFog.Hosting.dll (Avalonia, no Audio/Views/Palette deps). AvaloniaDialogs.cs deferred — pulls FracturingFog.Views.*, PaletteBuilder.*, and AvaloniaShellBootstrap statics; needs broader carve.
    • 2.3 ✅ CalculatorGenHotLoad.PersistAndLoad + LoadAllPersisted; HotLoadAndPersistRequested event in VM; "Compile + Save" button in UserEquationView.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 MandelbrotZ2 PASS; --saprobe shows healthy distribution at zoom 1e9-1e16 (gen vs legacy color counts within ±10).
    • 2.6 ✅ QualityPreset.AaSamples field (Standard=1, High=4, Ultra/Extreme=16) + render-host RunMsaaAccumulateMandelbrot averages N sub-pixel jittered passes for the canonical MandelbrotCalculator path. Alt calcs (user-equation / sandbox) currently skip AA pending interface broadening.
  • 2026-06-21 — Wave 2.2 (D-3.16) shipped — Phoenix proper DE + scalar perturbation tier.

    • PhoenixKernel.StepWithPrevDeriv carries (dr, di, dprev_r, dprev_i) via recurrence D_{n+1} = 2·z·D + 1 + p·Dp. Init (0,0,0,0).
    • EscapeTimeCalculator.CalculatePhoenix now passes real derivative to FillAuxAndColor — distance estimate + normal shading active for Phoenix where they were zeroed before (visible: edge glow, proper DE-based contrast).
    • New CalculatePhoenixPerturb tier gated at Zoom >= 1e10. Builds double-precision reference orbit at frame centre, then per-pixel δ + δ_prev recurrence: δ_{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.cs left untouched — unused by dispatch (FractalType.PhoenixEscapeTimeCalculator); 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.TaaMaxSamples field — 1 = off (Draft/Standard), 8 (High), 16 (Ultra), 32 (Extreme). Caps total accumulated samples per still-camera spell so the loop terminates.
    • FractalRenderHost carries per-pixel R/G/B/A long[] 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 new FrameJob with TaaSampleIndex > 0. The calc thread branches: jitter Center{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.
    • Resize drops the accumulator (sized for old buffers). View changes self-invalidate via the fingerprint check; any user Trigger() drains the bounded(1) calc queue, so a queued TAA continuation gets replaced by the fresh frame before it runs.
    • Bypassed when useAlt is 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's Center{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). MergeDd is ~7× the flops of MergeDouble (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: added DdBlaZoomThreshold = 1e15 constant in MandelbrotCalculator.cs. EnsureBlaTable now picks DD merge only when Zoom > 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: SetSelectionBoxRepaintWithPostFx shares the _uploadGate lock with Calculate — 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.cs had RequiresQD

    • RequiresDD pan/zoom branches but no RequiresOD branch — at zoom > 1e50 (OD threshold) the pan fell into the plain-double else arm and ClearLowLimbs() wiped CenterX/Y_Lo..X3. Even at QD zoom the StoreQD helper never touched CenterX/Y_4..7, so stale OD limbs from a Copy/Paste survived a pan-zoom cycle. Fix:
    • Added _panStartODCX/CY cache, initialised in OnPointerDown.
    • Added OD branch 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 / StoreQD to 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+51
    

    Threshold 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 reused ThreeSum residual variable names (r1a/r1b overwritten by tier-2 ThreeSum, r3a/r3b by tier-3 TwoSum), 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 / AddProduct push partial products into the expansion via TwoSum cascade, residuals propagate forward without name reuse).

    • Threshold restored: ODZoomThreshold = 1e50 in both MandelbrotCalculator.cs and FractalViewState.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.
  • 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 or Renorm9 redistribution. Restored prior behaviour by setting ODZoomThreshold = 1e100 in both MandelbrotCalculator.cs and FractalViewState.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 MandelbrotCalculator zoom ceiling past 1e50 toward the ~10¹¹⁶ OD limit. OdEmitter for generated calcs is a deferred follow-up (large mechanical port of the QD path; not blocking).

    • Abstractions/Math/OctupleDouble.cs — new OD readonly struct (X0..X7), mirrors QD API. HLB primitives TwoSum / QuickTwoSum / TwoProduct / ThreeSum carried 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). Implicit OD ← double, explicit (double)OD, ToDD(), ToQD(), FromCenterOffset(center, pixel, scale) for the per-pixel coord factory.
    • Abstractions/ViewState/FractalViewState.csCenterX4..X7 / CenterY4..Y7 properties, ODZoomThreshold = 1e50 const, RequiresOD flag. ResetView / SnapToFractalDefault clear all 8 limbs. RequiresQD now gated && !RequiresOD so QD doesn't fire when OD is required.
    • Engine/Calculators/MandelbrotCalculator.cs — adds the same 4-limb center props (CenterX4..X7 / CenterY4..Y7), ODZoomThreshold const, OD limbs 4..7 of the reference orbit storage (_refZrX4..X7 / _refZiX4..X7), and _refCx4..X7 / _refCy4..Y7 for the centerSame cache check. New EnsureRefOrbitCapacity(maxIter) — single allocation point for all 8-limb arrays so QD/OD paths share storage. DD and QD centerSame updated to also require X4..X7 zero (avoids stale OD orbit reuse when zoom drops back through 1e50). New ComputeReferenceOrbitOD(OD cx, OD cy, maxIter) writes all 8 limbs per slot. New ComputePixelOD mirrors ComputePixelQD with 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 / ComputeRowPT8 all detect useOD separately, build cy_od, and route glitched/tail pixels through OD instead of QD when active.
    • Program.cs--saprobe ladder 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. OdEmitter is the next sub-task — large mechanical port mirroring QdEmitter + 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.csBla struct now stores A, B as double-double pairs (AReHi/AReLo, AImHi/AImLo, BReHi/BReLo, BImHi/BImLo). Public ARe / AIm / BRe / BIm properties return Hi + Lo collapsed, 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·Z with A.Lo = 2·refZLo (multiply by 2 is exact in FP, so Lo carries through unchanged), B = 1 exactly. Merge math (MergeDd) runs in DD throughout using TwoSum/TwoProduct primitives mirroring Abstractions/Math/DoubleDouble.cs — complex DD × DD for A_m = A2·A1 and B_m = A2·B1 + B2. Validity radius uses collapsed magnitudes (radius precision not load-bearing).
    • Legacy single-precision ctor still emits Lo=0 for all limbs; the generic BlaTable(Bla[] level0, …) overload picks the single-precision merge path (MergeDouble) so generated calcs are bit-identical to pre-2.10. New DdPrecision flag exposes which merge ran for the diagnostic log (BLA-DD: vs BLA:).
    • MandelbrotCalculator.EnsureBlaTable always picks the DD ctor when in the HP path — _refZrLo / _refZiLo are 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: --saprobe deep-zoom histogram at z=1.08e12…1e16 — distinct colour counts stable, legacy calc tracks generated MandelbrotZ2 within ~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) with t baked as a numeric literal. Endpoint shortcut (t=0 → A verbatim, t=1 → B verbatim) skips the wrap-around so the parser never sees 0.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 via RenderAndSaveRequested(synth, outPath, ct) → Task<string?> event.
    • Hosting/AvaloniaShellBootstrap — wires MorphRequested to open EquationMorphView modeless under the UE editor. RenderAndSaveRequested handler hot-compiles the synth DSL via CalculatorGenHotLoad.TryCompileAndLoad, installs the result via SetDynamicAltCalculator (which triggers a render internally), subscribes a one-shot AnimationFrameUploaded handler to await upload (30 s timeout per frame), then calls SaveLastFrameToPng(outPath). Output sequence is morph_0000.pngmorph_NNNN.png; user assembles into MP4/GIF with ffmpeg/OBS.
    • UserEquationViewModelOpenMorphCommand + MorphRequested event.
    • 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 curated CookbookEntry rows: 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.
    • UserEquationViewModelOpenCookbookCommand + CookbookRequested event opens the picker; ApplyCookbookEntry(entry) writes the source into DslSource, snaps to the DSL tab, and fires CookbookCentreRequested(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, and CookbookCentreRequested writes ViewState.{CenterX,CenterY,Zoom} then calls Trigger().
    • 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 sidecar MandelbrotCalculator instances (_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. Resize updates both in step.
    • FrameJob.ProgressiveStage int (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 (no useAlt, 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 ColorBuffer at its smaller dims; DirectXRenderer.EnsureTexture recreates 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, no FrameCompleted event for non-final stages — those run only on the final full-res stage as before.
    • UI.Avalonia/ViewModels/MainViewModel.csRenderHint.Fast now calls Trigger(progressive: true) instead of TriggerFast(). 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 of Abstractions/Math/QuadDouble.cs). Tuple-returning primitives, AggressiveInlining. Uses Dekker split-based TwoProduct instead of Math.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 into RefOrbitSlot so the typed kernel loader stays at 4 generic params (8 parallel ArrayView<double> blew past the loader's practical ceiling — kernel JIT failed before any math ran). First cut iterates Hi-only doubles; QD body wired via GpuQDMath lives behind a TODO because ILGPU 1.5.3's IR-inliner trips on Renorm5/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. Bypasses GpuAcceleratorHost for 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). Exposes SelectedDeviceLabel for --gpurefprobe.
    • Engine/Calculators/MandelbrotCalculator.csUseGpuReferenceOrbit static toggle (default off). When on, CalculateHighPrecision QD branch routes through TryComputeReferenceOrbitQDGpu which mirrors the centre-cache short-circuit in ComputeReferenceOrbitQD, 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 via Debug.WriteLine. Default off keeps every existing call site bit-identical to pre-2.12.
    • Program.cs--gpurefprobe flag. 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. Writes gpurefprobe.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) → PreviewResult returns the parsed AST in printed form (AstPrinter.Print), symbolic dz/dc and dz/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.
    • UserEquationViewModel adds observable preview state and calls UpdatePreview from 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.axaml adds an Expander between 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).
  • 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.cs has Vector256 ProcessRowSimd (8 BGRA pixels/step, chunked Partitioner, pooled LOH) + _recordingActive gate at the snapshot site. Closed both as completed.

  • 2026-06-22 — Wave 3.8 superseded. FractalInputController.cs already carries OD/QD/DD/SP branches on all 6 pan/zoom sites (OnPointerMove, OnPointerDoubleClick, OnWheel, ApplyBoxZoom, PanByPixels) with StoreOD/StoreQD/StoreDD writing 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 file Engine/Models/HlslPaletteHelpers.cs exposes two static prelude strings: HsvAndMods (cg_mods + cg_hsv_to_rgb that mirrors Fractals.HsvToRgb exactly — no input clamping; cg_pack_bgra saturates at pack time) and ModsOnly (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 unique PaletteId so the kernel caches its compiled shader per theme. End-to-end GPU palette path (EscapeTimeCalculator.TryDispatchGpuGpuKernel.SetPalettegColor UAV 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. HsvPalette now implements IGpuHlslPalette as the canonical hand-port template. HlslPaletteBody mirrors Map() 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 — HlslPrelude returns empty. PaletteId = "HsvPalette/v1" so the kernel caches the compiled shader per id. Picked up automatically by EscapeTimeCalculator.TryDispatchGpu's ColorMap as IGpuHlslPalette check (and the matching MandelbrotCalculator path); when the toggle is on and the kernel attached, the GPU writes ColorBuffer end-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 _lastUploadedBuffer snapshot source had HE applied (calc-completion path bakes HE before UploadProcessedBuffer) but RecolorActiveToBuffer returned the raw post-Calculate / post-ApplyBandDitherRecolor buffer with no HE. FadeAsync per-pixel lerp ended in the pre-HE target state; the post-fade RepaintWithPostFx then snapped HE onto the visible frame — the "HE pops on at end" jump. Fix: after the recolor compute, BuildHistogramCdf + ApplyHistogramEqualizationWithCdf apply HE to the recolor target when ViewState.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 — .fbulb snapshot import/export. Pre-4.13 UserBulbStore.ExportEntry/ImportEntry only round-tripped UserBulbEntry (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, .fbulb is 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 mirroring FractalParameters.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 with JsonIgnoreCondition.WhenWritingNull so emitted JSON only contains what the producer actually set; keeps .fbulb files 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 for Version + Entry; absent → parses as bare UserBulbEntry, wraps in a snapshot with Version = 0 (sentinel for "legacy, no knobs to apply"). Pre-4.13 .fbulb files written by ExportEntry round-trip unchanged.
    • UserBulbViewModel.OnExport builds the snapshot from _params via new BuildSnapshotFromParams(entry). OnImport calls ImportSnapshot, applies non-null knobs through ApplySnapshotToParams, then SyncMirrorFromParams re-pulls every VM mirror field + raises PropertyChanged for the bound views — suppress flag stays on through the bulk update so no per-property render fires; the final LoadEquationByName triggers one compile + render.
    • ExportEntry/ImportEntry retained 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: .fbulb registered 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:SeedDefaults found 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 new Equations.Add calls. Menger / Sierpinski reuse the bodies already centralised in UserBulbChainPrimitives.GetById(Id{Menger,Sierpinski}) so the standalone preset and the hybrid-chain step are bit-equal. Quaternion Julia uses the same triplex-squared body as Square triplex with an inline // Switch Axis Mode → Quat + Julia Mode comment — neither Source nor Chain carries 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 matching Ensure(name, factory) calls so pre-existing %APPDATA%/FracturingFog/userbulbs.json files 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 consts IdKifsRot / IdKifsScale so the kernel caches per step. The Kaleidoscopic IFS preset's Source is a single-pass fallback for legacy chain-less loaders; Chain is the canonical form and overrides at runtime per the UserBulbEntry doc-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), UserBulbCalculator compile sig appends double t = __p[__p.Length - 1], UserBulbView.axaml animation row (Play/Pause / Speed / t), UserBulbViewModel.AnimationTick

    • NotifyRenderDone gating, and AvaloniaShellBootstrap 30 Hz DispatcherTimer pumping vm.AnimationTick(dt) while gated on AnimationFrameUploaded. 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, AnimationTick wraps t into [0, L) via next -= L * floor(next/L). Default 0 = no loop, preserving the prior monotonic-advance behaviour.
    • UserBulbView.axaml animation 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 / BulbTimeEnd into VideoZoomRequest so the video pipeline can lerp UserBulbTime per frame. Not blocking; filed as 4.7.f1.
  • 2026-06-22 — Wave 0.5b shipped — Visual-regression baseline recorded. Tools/VisualRegression/Program.cs needed four pre-flight fixes before the record run could succeed:

    • Shelled the cross-platform FracturingFog.App stub which doesn't yet handle --batch (the BatchEntry CLI lives in the WinExe). Constant BatchProject = "FracturingFogCLD.csproj" flips the target.
    • MagnetOne / MagnetTwo corrected to Magnet1 / Magnet2 matching FractalType enum literals in Abstractions/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 to Tools/VisualRegression/baseline.json. Curiosity: newton-default and nova-default share 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 uses PlasmaSeed field default so its output stayed stable.
  • 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.SupportsZoomPan is true — escape-time families (Newton/Nova/Halley/Secant/Magnet1/Magnet2/Glynn/Spider, Phoenix via EscapeTimeCalculator), 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 the SupportsZoomPan check — their Calculate() ignores centre+zoom so jitter would just re-roll noise.

    • New RunMsaaAccumulateAlt(IFractalCalculator, aaSamples, token) helper mirrors RunMsaaAccumulateMandelbrot shape: sub-pixel jitter on (CenterX, CenterY) over a √N×√N grid at 3.5/max(W,H)/Zoom pixel scale, re-runs Calculate(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 an IFractalCalculator (carries QD/DD/OD limb fields the interface doesn't expose).
    • Call-site at FractalRenderHost.cs:1038 branches: !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.
  • 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.cs FormHelpers (WinForms-bound) to new Abstractions/Math/QdCoordCodec.cs. FormHelpers retains the legacy FracturingFog.Views.FormHelpers API as thin delegating shims for in-tree WinForms callers.
    • AvaloniaShellBootstrap.AudioCapabilities static dependency replaced with FracturingFog.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.MainWindow available cross-platform since Wave 1.8 (PaletteBuilder.Lib TFM = net10.0). Added PaletteBuilder.Lib.csproj ProjectReference to FracturingFog.Hosting.csproj.
    • FracturingFog.Hosting.csproj drops <Compile Remove="AvaloniaDialogs.cs" />; FracturingFogCLD.csproj adds it (WinExe consumes across the ProjectReference). AvaloniaDialogs visibility flipped internal → public because 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.
  • 2026-06-22 — Wave 3.4 (T3.3) shipped — Non-temporal AVX writes. FractalRenderHost.ProcessRowSimd (brightness/contrast Vector256 inner loop) now uses Vector256<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 fallback StoreUnsafe loops, 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/Sin per 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. SimdSupported flag drives dispatch: EscapeTimeCalculator.Calculate picks DispatchByColorMapSimd for d∈{3,4,5}, scalar DispatchByColorMap otherwise. Interface comment in IFractalKernel.cs updated to reflect the new coverage. Build clean (0 errors), 140/140 server tests pass.
  • 2026-06-22 — Wave 2.13 (D-7.29) shipped — Roslyn source generator. Replaces the legacy dotnet run -p CalculatorGen step with a compile-time IIncrementalGenerator. 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\ (excluding Program.cs and CalculatorGenHotLoad.cs which need net10.0 + Roslyn Scripting). Polyfills.cs shims IsExternalInit + System.Index/Range for 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 via RegisterPostInitializationOutput. Assembly-level
      • AllowMultiple=true so one registry file can declare every calc. Generator pulls all attribute instances from Compilation.Assembly.GetAttributes(), runs CalculatorGenApi.Generate, emits {Name}.g.cs (and {Name}SelfTest.g.cs when requested) via context.AddSource.
    • Templates updated: // <auto-generated /> + #nullable enable as lines 1-2 of both Calculator.template.cs and SelfTest.template.cs. Suppresses CS8669 on emitted source; applies equally to any legacy CLI regeneration.
    • Engine/FracturingFog.Engine.csproj references the SourceGen project with OutputItemType="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.csproj got <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 MandelbrotZ2 reports 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.

Clone this wiki locally