# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is A deep-zoom fractal explorer (Rust + wgpu + egui + WGSL). It zooms past the ~10¹³× limit of plain `f64` using **perturbation theory**: one high-precision reference orbit is computed on the CPU (arbitrary precision via `dashu-float`), and every pixel is rendered on the GPU as a cheap `f32` delta from it, with rebasing to avoid glitches. The `f32` GPU tier reaches roughly 10³⁰×. Runs natively (Vulkan/Metal/DX12) and in the browser (WebGPU only — WebGL2 can't do storage buffers, which the fragment shader needs for the reference orbit). ## Commands ```sh cargo run --release # native, run (release matters: fractal math is hot) cargo test # reference-orbit math, share-link round-trip, WGSL validation cargo test --test shader_valid # just the WGSL parse/validate tests (naga, no GPU needed) cargo clippy cargo fmt # rustfmt.toml just pins edition = "2024" ``` Web build (WebGPU): ```sh rustup target add wasm32-unknown-unknown cargo install wasm-bindgen-cli --version 0.2.128 # must match the wasm-bindgen crate version ./build-web.sh # -> ./dist python3 -m http.server -d dist 8080 ``` Native CLI flags (`src/cli.rs`, applied in `FractalApp::apply_cli`): `--kind`, `--power`, `--julia re,im`, `--share `, `--view re,im,half_height[,iterations]`, `--de`, `--buddhabrot`, `--buddha-palette`, `--export` (+ `--export-path out.png`). `--headless` (`src/headless.rs`) skips the window entirely: it builds the same view from the other flags, creates its own offscreen wgpu device, and renders straight to a PNG (`--width`/`--height`, default 1920×1080) — implies `--export`'s save behavior without needing a GPU-backed window/event loop. Not yet supported with `--buddhabrot`. Run `mandelbrot --help` for the full list. There's no GPU in most sandboxes: `cargo check`/`cargo test --test shader_valid` are the fast, headless way to validate a change. `cargo test` also runs but doesn't touch the GPU — the reference-orbit tests are pure CPU math (see below), and `shader_valid` parses/validates WGSL with `naga` statically instead of creating a pipeline. ## Architecture ### The perturbation pipeline (the core mechanism, spans several files) For a pixel at parameter `c = C_ref + dc`, its orbit is written as `y_n = X_n + e_n`, where `X_n` is the (shared, high-precision) reference orbit and `e_n` is a small `f32` delta. Whenever `|y_n| < |e_n|` (or the reference runs out), rebase: `e ← y_n − X_0`, restart the reference index at 0. This is what makes deep zoom cheap — one expensive high-precision orbit, then every pixel is a handful of `f32` complex multiplies. - `src/view.rs` — `ViewState`; center is arbitrary-precision `FBig` (`Big` type alias), pixel scale stays `f64` (still in-range at 10³⁰×). Precision (bits) scales with zoom depth (`precision_for`). - `src/fractal/reference.rs` — `FractalKind` enum (Mandelbrot, Burning Ship, Tricorn, Multibrot, Celtic, Perpendicular, Buffalo, Phoenix, Lambda) and `compute_reference`/`compute_set_reference`: iterate the chosen formula at high precision on the CPU, emitting `Z_n` as `f32` pairs — that's the reference orbit the GPU perturbs from. - `src/shaders/mandelbrot.wgsl` — the perturbation fragment shader. `advance_delta(z, e)` is the per-kind delta step (`z` = reference point, `e` = current delta); the caller adds `step_add` (= `dc`) afterward — this relies on `c` being additive in every current kind's formula (a kind where it isn't, e.g. a rational map with `c` in a denominator, would need its own step function that consumes `dc` internally instead, plus extra per-step reference data since the orbit point alone wouldn't be enough to recover an exact delta). `fprime(z)` is the derivative used for distance-estimation (DE) shading; exact for holomorphic kinds, an approximation (`~2Z`) for the abs-based ones. A `KIND_*` constant here must match the matching `FractalKind` variant's discriminant exactly. - `src/fractal/renderer.rs` — `FractalRenderer` (wgpu pipelines, uniform + storage buffers, bind groups), `Uniforms` (repr(C) layout that must match the WGSL `Uniforms` struct field-for-field, including padding), and `FractalCallback` (the `egui_wgpu::CallbackTrait` impl: `prepare()` uploads changed buffers and decides whether to re-run the iterate pass, the cheap colourise pass, or just blit the cached texture). Also `ExportRender`, a self-contained tiled renderer used for PNG export off the UI thread. - `src/worker.rs` — native background thread for reference-orbit computation (coalesces bursts of requests so a fast drag doesn't compute every intermediate view). The wasm32 build computes inline instead (see the `#[cfg(target_arch = "wasm32")]` branch in `app.rs::ensure_reference`) — **any signature change to `compute_reference`/`compute_set_reference` or `RefRequest`/`RefResult` must be applied to both call sites.** - `src/app.rs` — `FractalApp` (the egui app + all UI). Key methods: `should_request`/`ensure_reference` (decide when the reference is stale and dispatch/collect it), `make_uniforms` (assemble the per-frame `Uniforms`), `tick_animations` (drives the "morph c/p/λ" and auto-zoom animations), `default_view_for` (per-kind starting view). `KINDS`, `JULIA_PRESETS`, and `SET_PRESETS` are sized as `[T; FractalKind:: as usize + 1]` — adding a new `FractalKind` means bumping all three (and adding an empty `&[]` slot to the two preset arrays if the kind has none). - `src/fractal/share.rs` — `ShareState`: encodes the full view (mode, kind, full-precision decimal center, zoom, iterations, per-kind constants, coloring) as a `#`-fragment URL for bookmarking/sharing deep-zoom locations. ### Adding a new `FractalKind` Touches, in order: `reference.rs` (enum variant + CPU iteration formula, and a test comparing against a naive `f64` iteration), `mandelbrot.wgsl` (matching `KIND_*` const + `advance_delta`/`fprime` arms), `buddhabrot.wgsl` (matching arm in `advance()`, if the kind makes sense as a Buddhabrot), `renderer.rs` `Uniforms` (only if the kind needs a new per-kind constant, e.g. Phoenix's `phoenix_p`), `share.rs` (encode/decode string tag), `app.rs` (`KINDS` label, `JULIA_PRESETS`/`SET_PRESETS` slot, `default_view_for` entry, and optionally a UI control for its constant + an animation toggle, following the Phoenix/Lambda pattern). If `c` doesn't enter the formula additively (e.g. a rational map with `c` in a denominator), the `advance_delta`/`step_add` split doesn't work — that needs its own step function plus extra per-step reference data uploaded in a second GPU buffer alongside the orbit. ### Buddhabrot is a separate pipeline `src/fractal/buddhabrot.rs` + `src/shaders/buddhabrot.wgsl` implement the Monte-Carlo orbit-density histogram. It does **not** use the perturbation/ reference-orbit machinery: a Buddhabrot sample's orbit scatters across the whole image rather than staying in one pixel, so it's plain `f32` iteration from the live view (no deep zoom) via a compute pass that accumulates into a histogram buffer, tone-mapped by a fragment pass every frame. Its own `KIND_*` iteration formulas in `advance()` must be kept in sync with `reference.rs` by hand (there's no shared code path). ### Two-pass render + caching (`renderer.rs`) The interactive path splits iteration (expensive, perturbation) from colourising (cheap, palette remap) into separate offscreen textures, so palette/color-scale/offset tweaks skip re-iteration entirely (`geom_differs` vs `color_differs` in `renderer.rs` decide which pass reruns). While the user is actively panning/zooming, the app renders downscaled with AA off (`INTERACT_DOWNSCALE`) and snaps back to full resolution once input settles (`INTERACT_SETTLE`).