An open laboratory for complex dynamics.
Note: I am considering moving this new born project toward something like Ultra Fractal, something artistic, not "scientific".
IteraScope is a GPU-first scientific laboratory for exploring iterated maps. Its reference instrument links the parameter and dynamical planes of the quadratic family
[ f_c(z) = z^2 + c, ]
with an arbitrary-precision deep-zoom path. Around it sits a catalogue of
twenty-five further escape-time, convergence-time and root-finding
instruments, from the Burning Ship and Magnet maps to the Lyapunov plane of
the forced logistic map, all rendered by the same WGSL shader and all backed by
a CPU f64 reference implementation of the same recurrence.
The same Rust application runs natively and in a WebGPU-capable browser. egui
and the fractal renderer share one wgpu device; every per-pixel iteration
runs entirely in WGSL. CPU calculations are reserved for pointwise
diagnostics, precision validation and arbitrary-precision reference orbits
rather than full-frame pixel rendering.
Two instrument layouts exist. Parameter/dynamical instruments colour the
left pane by the fate of a critical (or otherwise distinguished) orbit for
each parameter c, and the right pane by the fate of every starting value
z0 under the selected parameter; clicking the left pane selects c.
Overview/detail instruments show the same plane in both panes; clicking
the overview selects a point and opens a linked, magnified detail region.
| Family | Recurrence | Layout | Start of the left-pane orbit | Terminates on | Deep zoom |
|---|---|---|---|---|---|
| Quadratic | z ← z2 + c |
parameter/dynamical | z0 = 0 |
|z| > bailout |
f64 + AP perturbation |
| Newton cubic | z ← z − (z3−1)/(3z2) |
overview/detail | pixel | residual < 10−6 |
f64 + AP perturbation |
| Multibrot | z ← zd + c, 2 ≤ d ≤ 8 |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Tricorn | z ← z̄2 + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Perpendicular Mandelbrot | (x2 − y2, −2|x|y) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Burning Ship | (x2 − y2, 2|xy|) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Perpendicular Burning Ship | (x2 − y2, −2x|y|) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Celtic | (|x2 − y2|, 2xy) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Perpendicular Celtic | (|x2 − y2|, −2|x|y) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Buffalo | (|x2 − y2|, 2|xy|) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Perpendicular Buffalo | (|x2 − y2|, −2x|y|) + c |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Lambda (logistic) | z ← λ z (1 − z) |
parameter/dynamical | z0 = 1⁄2 |
bailout | f64 + AP perturbation |
| Phoenix | zn+1 = zn2 + Re c + (Im c) zn−1 |
parameter/dynamical | z0 = z−1 = 0 |
bailout | f64 + AP perturbation |
| Manowar | zn+1 = zn2 + zn−1 + c |
parameter/dynamical | z0 = z−1 = c |
bailout | f64 + AP perturbation |
| Spider | z ← z2 + c, c ← c/2 + z |
parameter/dynamical | z0 = 0 |
bailout | f64 + AP perturbation |
| Magnet I | z ← ((z2 + c − 1)/(2z + c − 2))2 |
parameter/dynamical | z0 = 0 |
bailout or |z − 1| < 10−4 |
f64 + AP perturbation |
| Magnet II | z ← ((z3 + 3(c−1)z + (c−1)(c−2)) / (3z2 + 3(c−2)z + (c−1)(c−2) + 1))2 |
parameter/dynamical | z0 = 0 |
bailout or |z − 1| < 10−4 |
f64 + AP perturbation |
| Exponential | z ← c ez |
parameter/dynamical | z0 = 0 |
Re z > 50 |
f32 only |
| Sine | z ← c sin z |
parameter/dynamical | z0 = π/2 |
|Im z| > 50 |
f32 only |
| Cosine | z ← c cos z |
parameter/dynamical | z0 = 0 |
|Im z| > 50 |
f32 only |
| Collatz | z ← 1⁄4(2 + 7z − (2 + 5z) cos πz) |
overview/detail | pixel | |Im z| > 20 or |z| > 106 |
f32 only |
| Lyapunov (Markus) | xn+1 = rn xn(1 − xn), rn ∈ {a, b} by sequence |
overview/detail | x0 = 1⁄2 |
Lyapunov exponent after n iterations |
f32 only |
| Nova | z ← z − R (zp − 1)/(p zp−1) + c |
parameter/dynamical | z0 = 1 |
|Δz| < 10−5 or |z| > 106 |
f64 + AP perturbation |
| Barnsley 1 | z ← (z ∓ 1) c by sign of Re z |
parameter/dynamical | z0 = c |
bailout | f64 + AP perturbation |
| Barnsley 2 | z ← (z ∓ 1) c by sign of Im(zc) |
parameter/dynamical | z0 = c |
bailout | f64 + AP perturbation |
| Mandelbox (2D) | v ← s · ballfold(boxfold(v)) + c |
parameter/dynamical | v0 = 0 |
|v| > 4 ×ばつ bailout |
f64 + AP perturbation |
x and y denote Re z and Im z. The sign conventions of the
absolute-value variants follow the Kalles Fraktaler family; the opposite sign
of an imaginary part produces the mirror image in the conjugate parameter, not
a different set. Buffalo uses the componentwise absolute value of z2. The
Manowar map has complex Jacobian determinant −1, so its bounded set has no
attracting interior; it is rendered for its escape-time level sets. Magnet,
Nova and Newton orbits finish by converging, so their default colouring is the
argument of the limit (the basin) darkened by convergence time; the Lyapunov
plane is coloured by the sign and size of the exponent, the stable regions
through the gradient and the chaotic ones in darkening blues.
The Deep zoom column lists the precision paths available past plain
f32: every listed family switches to GPU perturbation around a CPU
reference orbit — f64 below the 1.14e×ばつ handoff, arbitrary precision
beyond it — up to the ×ばつ navigation ceiling. The quadratic
instrument additionally keeps its compensated double-single recurrence as a
fallback and as the subject of its orbit probes. The
transcendental families and the Lyapunov plane stop at f32: their reference
orbits would need arbitrary-precision exponentials and trigonometry, which the
decimal arithmetic layer does not yet provide.
Every instrument links a global classification view to a local diagnostic
view and exports the selected value, viewports, family parameters, numerical
settings and display state together as a versioned experiment document. The
control panel always shows a CPU f64 diagnostic for the selected point
computed from the reference implementation in src/family.rs, which is the
definition of record for each recurrence; the shader branches are transcribed
from it.
- Linked Mandelbrot parameter plane and Julia dynamical plane
- Newton basin instrument for
z3 - 1, with three-root classification and a linked convergence-detail pane - Twenty-four further escape-time and convergence-time families (Multibrot,
eight absolute-value variants, Lambda, Phoenix, Manowar, Spider, Magnet I/II,
exponential, sine, cosine, Collatz, Lyapunov, Nova, Barnsley 1/2 and the 2D
Mandelbox) with per-family parameters, presets and a CPU
f64orbit diagnostic for the selected point - Deep zoom (arbitrary-precision navigation plus GPU perturbation) for every family except the transcendental ones and the Lyapunov plane
- Headless GPU tests that render every family through the real pipeline
(
gpu_family_gallery), compare the perturbation path against the double-single path and a CPUf64raster at×ばつ(gpu_perturbation_matches_double_single), render seven families at×ばつaround arbitrary-precision repelling fixed points (gpu_deep_zoom_resolves_structure), and check that compensated arithmetic survives the shader compiler (gpu_double_single_self_test); all are#[ignore]d and run withITERASCOPE_RENDER_DIR=out cargo test --release <name> -- --ignored - Pointwise Newton diagnostics reporting the attracting root, iteration count, polynomial residual, final value, last step and derivative singularities
- Click-to-centre ×ばつ zoom; parameter-plane clicks also select the Julia parameter; right-click recentres without zooming
- Wheel, trackpad pinch and drag navigation in either plane
- Shift-modified click, wheel or pinch for accelerated logarithmic zoom in either direction
- Automatic progressive Julia navigation to a selected
10^ntarget, up to×ばつ, with visible intermediate renders and Start/Stop controls - Keyboard arrows and four on-screen buttons for deterministic fine panning by one tenth of the displayed range (Shift + arrows: one hundredth)
- Adjustable iteration limit up to 50,000 and escape radius up to
1e10 - A colour stage in the Ultra Fractal mould: cyclic gradients
(control points, RGB or HSL blending, cubic smoothing, rotation, presets,
random generation,
.ugrand Fractint.mapimport,.ugrexport) and independent outside/inside colouring algorithms — (smooth) iteration count, decomposition by argument, triangle-inequality average, stripe average, exterior distance estimate and orbit traps (point, cross, circle, square, lines) — each with density, offset, transfer curve and iteration shading. Every algorithm works through the perturbation paths at any depth; the distance estimate is evaluated in logarithms and verified at1e×ばつ(Ultra Fractal 5's limit; IteraScope navigates to1e×ばつ) - Layers: up to eight complete colour stages composited over one iteration
pass — each layer has its own gradient, algorithms, opacity and merge mode
(Normal, Add, Multiply, Screen, Overlay, Darken, Lighten, Difference), and
any layer can act as a mask: it paints nothing and its luminance (times
its opacity) multiplies the opacity of the layer above it, so any
colouring algorithm can shape where another layer shows — and any layer
can detach into its own scene: its own formula, plane, parameter and
location, rendered as a separate pass through the same engine and
composited over the stack — exact at any magnification: shallow scenes
use
f64reference orbits and deep captures carry their location at arbitrary precision — with a single-image workspace (the default layout) that gives the composited image the full window. Layers share the image's location and family, so one reference orbit serves the whole stack and the deep-zoom engine is untouched; a single-layer stack renders byte-identically to, and as fast as, the pre-layer renderer - An Ultra Fractal-style switch picker: while the single image shows the
dynamical plane, Pick c... opens a parameter-plane window with
crosshair, scroll zoom and a live Julia thumbnail of the hovered
parameter; clicking sets
cand the composited image follows immediately - Still-image export on both targets: the current view rendered to PNG at up to ×ばつ16384 with ×ばつ2 or ×ばつ3 supersampled anti-aliasing box-filtered in linear light; sizes whose supersampled frame exceeds the 8192-pixel texture limit render as tiles around the same reference orbit, seamless at any magnification. The browser build renders through an asynchronous GPU readback and downloads the PNG
- In-browser video: the browser build encodes the zoom animation to MP4 (H.264) with WebCodecs and downloads it — no server, no ffmpeg — falling back to a ZIP of PNG frames where WebCodecs is unavailable
- Two independently collapsible, drag-resizable control panes: the left Instrument pane (family, document, parameters, computation, navigation, diagnostics) and the right Studio pane (layers, colouring, still and animation export)
- Optional scale-aware coordinate grid
- Zoom-path animation: a dive to the current centre between two magnification
exponents at constant or eased logarithmic speed, with an optional gradient
sweep, exported (native app) as a PNG image sequence at up to ×ばつ8192 and
optionally encoded to MP4 with ffmpeg. One reference orbit serves every
frame — the arbitrary-precision orbit of the centre is re-described per
frame, so a
×ばつdive costs one orbit, not one per frame - Live magnification plus navigation, rendered-coordinate, rounding-delta and pixel-scale readouts
- Versioned JSON experiment documents with cross-platform copy/paste import and export
- Cached
f64critical-orbit inspector with escape, smooth-escape and parameter-sensitivity diagnostics - Synchronized selected-step overlay in the Julia plane, using a short fading critical-orbit tail rather than an unreadable full trajectory
- Automatic GPU precision switching between fast
f32and a centred double-single recurrence with adaptive per-pixel rebasing (approximately 48-bit reference coordinates) - A cached ×ばつ3 CPU instability probe comparing both GPU arithmetic paths with
f64, including adaptive rebasing and non-finite detection - Explicit
DS STABLE,DS RISK, andDS LIMITstates with diagnostic reasons - Responsive side-by-side or stacked layout
- Native and WASM entry points from the same codebase
- Offline WGSL parsing and validation using the exact Naga version used by wgpu
Click a point in either plane to make it the centre and immediately zoom ×ばつ; right-click to make it the centre without zooming, which is the way to
frame a region precisely before magnifying it. Clicking in the parameter
plane additionally sets c, so the Julia plane on the right updates to the
corresponding dynamical system. Drag to pan, use the wheel or trackpad to
zoom, or select a Fine pan target and use the arrow keys or < ^ v >
controls. Each fine-pan step moves exactly one tenth of the currently
displayed horizontal or vertical range; holding Shift with the arrow keys
moves one hundredth.
The Still image section renders the active pane's current view to a PNG at a chosen resolution (up to ×ばつ16384) with supersampled anti-aliasing: the frame is rendered at two or three times the requested size and box-filtered down in linear light, so gradient edges keep their brightness. When the supersampled frame exceeds the GPU's 8192-pixel texture limit it is rendered as tiles, one per interface update with a progress bar. Tiles render around the same reference orbit as the whole frame — the frame centre simply becomes an off-centre reference — so the perturbation deltas are algebraically identical to the whole frame's and tiling is exact at any magnification, with no seams. It uses the same frozen-scene machinery as the animation exporter: one reference orbit at the current centre, valid at the full depth of the view.
The controls are split across two independently collapsible panes: the left Instrument pane holds the scientific state (family, document, parameters, computation, navigation and diagnostics), the right Studio pane the artistic state (layers, colouring, still and animation export). The chevron in each pane's header collapses it to a slim strip, and each pane can be widened by dragging its inner edge.
The Animation section renders the classic deep-zoom video: a dive to the
active pane's centre, from a start to an end magnification exponent (defaults:
10^0 to the current view via End = view) at constant logarithmic speed,
optionally eased at both ends, with an optional constant-speed gradient sweep.
The exporter writes frame-00000.png ... at the chosen resolution and frame
rate, one frame per interface update so the application stays live. The
native app writes frame-00000.png ... into a new directory under the
configured folder and — when ffmpeg is installed — encodes zoom.mp4 when
the sequence completes. The browser build encodes the video directly with
WebCodecs (H.264, muxed to MP4 in the app) and downloads it when the last
frame is in; without WebCodecs it downloads the frames as a ZIP of PNGs.
Because the centre is fixed, the arbitrary-precision reference orbit is
computed once and re-described in scale for every frame; frames below the
1.14e×ばつ handoff use the same orbit projected to f64, and the switch
between the paths is seamless. Scale iterations with zoom (on by
default) applies the configured iteration budget to the deepest frame and
gives shallower frames proportionally fewer — escape times near a boundary
grow roughly linearly with the zoom exponent, so the early frames of a deep
dive render several times faster with no visible cost. While any export
runs, the live view drops to reduced resolution so it does not compete with
the export for the GPU.
The Layers section holds the image's layer stack, shown top first. The orbit is iterated once per pixel; every visible layer then colours that same result with its own gradient and algorithms and is merged over the layers beneath it by its opacity and merge mode (the bottom layer composites over black, its mode ignored). Duplicate the active layer, restyle it — a stripe average multiplied over an iteration-count base, an orbit trap screened on top — and reorder or hide layers freely; the Colouring section always edits the active layer. The M toggle in a layer's blend row turns it into a mask: it stops painting and its luminance, scaled by its opacity, controls the opacity of the layer above it — a stripe-average mask carving windows into a top layer, an orbit-trap mask haloing the set. Consecutive masks multiply.
Own formula and location in the Colouring section detaches the active
layer into its own scene: it captures the current view (family, plane,
parameter, centre and zoom — including arbitrary-precision deep locations,
stored as exact decimals) and from then on renders that scene regardless of
where the image navigates — a Burning Ship backdrop
behind a Mandelbrot dive, a Julia overlay masked into a parameter-plane
image. Detached scenes are edited numerically (family, c, centre, zoom,
iterations, bailout) or re-captured from the view; deep captures show their
magnification and are re-captured or cleared rather than edited digit by
digit. They render as separate passes through the unchanged deep-zoom
engine — an f64 reference orbit below the 1.14e×ばつ handoff, an
arbitrary-precision one beyond it — and composite with the layer's usual
opacity, merge mode and masks. In animations they hold their location while
the shared-scene layers dive. A stack whose layers all share the image's
scene keeps the single-pass compositor, byte-identical to before.
Each layer also has a skip first N iterations control (shown when a
trap, stripe or triangle-inequality algorithm is selected). At deep
magnifications every pixel's orbit shares hundreds of identical leading
iterations with its neighbours, so those accumulators collapse to a single
value; skipping the shared prefix — raise it until structure appears, which
happens as the skip nears the view's typical escape time — restores their
full variety. The GPU suite demonstrates the effect at 1e×ばつ: the outside
point trap goes from 2 distinct colours at skip 0 to ~1900 at skip 496. Layers currently share the image's location, family
and iteration settings (per-layer formulas and locations are future work),
which is what keeps the whole stack exact at any magnification: one
reference orbit drives every layer. The view
selector in the top bar chooses the view: Parameter or Julia alone,
full-window (the default layout), or Both linked panes side by side.
Pick c... opens the switch picker in the single Julia view: the parameter
plane with a crosshair, scroll zoom, a marker on the current c and a live
Julia thumbnail of the hovered parameter — click to choose and the composited
image follows immediately.
The Colouring section holds one gradient and two colouring algorithms, as
in Ultra Fractal: Outside colours orbits that escaped or converged,
Inside colours orbits that reached the iteration limit. Each algorithm
reduces the orbit to a value — the smoothed iteration count, the argument of
the final z (continuous or in sectors; two sectors is binary decomposition),
the triangle-inequality or stripe average over the orbit, the exterior
distance estimate in pixels, or the closest approach to an orbit trap — and
maps it to a gradient position through a transfer curve, a density and an
offset. The averages interpolate their final term by the family's own
smoothing so they stay continuous across iteration bands; the distance
estimate follows the orbit's derivative in scaled arithmetic and is evaluated
in logarithms, so it is exact at any depth for the quadratic, Multibrot and
lambda families. Large escape radii (up to 1e10, under Computation) give
the averages their smoothest results.
Click the gradient bar to open the editor: drag the markers to move control
points, double-click the bar to add one, pick the colour and position of the
selected point, choose RGB or HSL blending, cubic smoothing, rotation, and
reverse or redistribute the points. The editor offers presets, a random
generator, Ultra Fractal .ugr and Fractint .map import (paste the text or
drop the file) and .ugr export.
The accumulators behind orbit traps, the averages and the distance estimate live inside every iteration loop, including the perturbation paths. They are compiled into a second shader variant that is only used while one of those algorithms is selected, so the default configuration renders exactly as fast as before the colour stage existed — the deep-zoom engine is not the price of the artistic features.
The Critical orbit section computes
[ z_0 = 0, \qquad z_{n+1} = z_n^2 + c. ]
Select an iteration to inspect its complex coordinate, magnitude, argument and
derivative with respect to c. The Julia overlay connects only the selected
point and its eight immediate predecessors. These straight segments indicate
iteration order; they are not continuous curves along the Julia set. The
selected orbit point can also become the centre of the Julia view.
Choose Newton in the Experiment section to study Newton's method applied to
[ p(z) = z^3 - 1, \qquad N(z) = z - \frac{z^3 - 1}{3z^2}. ]
By default the left pane colours each starting value by the argument of the
root to which it converges (the Decomposition colouring), with brightness
encoding convergence speed; switch the outside colouring to Iteration
count to emphasize convergence time and expose sensitive basin boundaries.
Clicking the overview selects z0 and opens a linked region in the right
pane. Convergence time is a continuous estimate of where the residual crossed
the convergence threshold, avoiding false bands from whole iteration counts. The CPU f64 diagnostic independently reports
the exact integer iteration count alongside the selected orbit's root,
residual, last Newton step and final complex value; z0 = 0 is explicitly
identified as a derivative singularity.
Newton mode is currently limited to 2,048 iterations and the stable
f32/double-single viewport range. Its double-single path keeps the starting
coordinate, polynomial, derivative, complex division and Newton update in
compensated arithmetic so magnified basin boundaries do not collapse onto an
f32 coordinate grid. The arbitrary-precision perturbation path remains
specific to the quadratic family.
Choose any other family from the Experiment drop-down. Parameter/dynamical
instruments behave like the quadratic one: click the left pane to choose the
parameter, adjust it numerically or through presets, and read the Critical
orbit diagnostic, which iterates the same recurrence on the CPU in f64
and reports whether the orbit escapes, converges, becomes non-finite or stays
bounded through the iteration limit. Overview/detail instruments (Newton,
Collatz, Lyapunov) select a starting point instead. Families with parameters
expose them in Family parameters: the Multibrot and Nova degree, the Nova
relaxation R, the Lyapunov forcing sequence over {A, B} (up to 32
symbols, with the first quarter of the iterations discarded as a transient)
and the Mandelbox scale, minimum radius and fixed radius.
Default parameters for the dynamical planes were chosen just inside the boundary of each family's connectedness locus, so the default Julia sets are thin and filamentary rather than filled discs; the presets offer a few alternatives, and clicking anywhere in the parameter plane selects another. Bounded orbits take the Inside colouring, a dark solid by default; an inside Orbit trap (point at the origin) reveals the basins of attracting cycles.
Precision handling is deliberately explicit. Once the f32 coordinate grid
becomes coarser than a pixel, every family renders by GPU perturbation
around a reference orbit: below the 1.14e×ばつ handoff the reference is
computed on the CPU in f64 (F64 PERT), beyond it in arbitrary precision
(AP PERT). (The GPU double-single recurrence that previously carried the
quadratic instrument through that range was found, on Metal, to lose
structure well before the handoff — a flat image at ×ばつ where the
f64 reference resolves over a thousand distinct escape times — so it now
serves only as a fallback; the CPU probes still characterise it.) In both cases
the navigation layer keeps exact coordinates, the CPU builds the reference
with family::reference_orbit_f64 or arbitrary::deep_step (exact
transcriptions of the f64 definition, checked by test), and the shader
iterates an exact delta recurrence for that family in scaled
mantissa/exponent arithmetic —
binomial expansions for zd, the diffabs identity for every absolute-value
variant, exact rational differences for the Magnet and Nova maps, branch-aware
differences for the Barnsley and Mandelbox folds. Newton's basins reuse the
Nova recurrence. The transcendental, Collatz and Lyapunov families render in
f32 only and show F32 LIMIT once the pixel grid collapses.
A note on compensated (double-single) arithmetic on the GPU: Metal compiles
WGSL with fast math, and its reassociation silently reduced every compensated
sum back to f32 (the GPU self-test in render/mod.rs reproduces this). The
DS primitives now route each rounded intermediate through one of four opaque
1.0 uniforms so no two adjacent terms share a factor the optimizer can pull
out, and the self-test verifies addition, multiplication, division and the
view transform on the real device. Because that protection is only as good
as the compiler's behaviour at each inlined call site, the generic families
no longer depend on it for image coherence: their remaining DS path (used
only when a reference orbit ends early at the handoff) is a centred
recurrence like the quadratic one — a shared centre orbit plus exact
per-pixel deltas with rebasing — rather than a plain per-pixel DS orbit.
IteraScope renders on demand. An unchanged view does not continuously consume CPU and GPU resources. A repaint is requested for user input, the settled replacement following deep navigation, each active progressive-zoom stage, and each slice of an arbitrary-precision reference orbit that is still being extended. The previous completed deep image remains visible while its replacement reference is prepared. Progressive stages are spaced by 750 ms so WebGPU is not continuously fed full-screen deep renders faster than they can be presented.
While input is active (dragging, zooming, stepping, clicking) each pane is rendered at one third of its resolution into a texture and scaled up, so a frame stays cheap at any depth and the view follows the pointer instead of lurching after long frames; the settled frame renders at full resolution (a milder half-resolution preview is used while a reference orbit is still being extended).
Reference orbits are built so that navigation stays responsive. Below the
handoff the f64 reference is chosen as the longest-lived orbit among the
view centre and a coarse grid of candidates, so few pixels outlive it (those
that do continue in plain f32 from their already-separated state).
Arbitrary-precision references are extended across frames under a 6 ms
budget: the GPU renders with the points available so far and refines as the
orbit grows, instead of freezing the interface for a long high-precision
orbit. While a deep view is being dragged, zoomed or stepped, its existing
reference orbit is kept and merely re-described relative to the moved view
(perturbation does not require a centred reference), so the image follows
the input immediately; a fresh centred reference is built once input
settles and swapped in when complete. The delta recurrence itself runs in plain f32 below the handoff and
in scaled mantissa/exponent arithmetic only at arbitrary-precision depth;
the two are instantiated from one template at shader-load time.
The top-right ON DEMAND indicator reports the smoothed CPU time spent in the most recent UI update. It is not a refresh interval, frame rate, or direct measurement of shader execution time. WebGPU command completion is asynchronous, so separate reference-orbit timings, GPU timestamps where supported, and presented-frame timings are still planned for detailed performance diagnostics.
The interface reports the active arithmetic for each pane. Viewport navigation
starts in CPU f64 and moves to arbitrary-precision decimal coordinates for
quadratic deep zoom. Rendering starts with fast GPU f32 and switches
automatically to perturbation around a CPU f64 reference orbit when
coordinate resolution is at risk or the lightweight orbit probe detects
divergent escape behaviour. The
DS STABLE, DS RISK and DS LIMIT labels describe agreement with sampled
f64 orbits and coordinate resolution—they are more meaningful than visual
smoothness alone.
The current stable raster path hands off at the experimentally confirmed
1.14e×ばつ boundary. IteraScope now has pure-Rust arbitrary-precision decimal
coordinates, a zoom-dependent precision policy and arbitrary-precision
reference orbits for every perturbation-capable family, sized for the
1e×ばつ acceptance target. At the
handoff, those cached reference orbits now drive an exponent-scaled GPU
perturbation path; an early-ending reference falls back per fragment to the
stable DS renderer at the handoff. Beyond it, the pane centre, scale, click
coordinates and tenth-range pans remain in arbitrary precision through the
1e×ばつ acceptance target, with navigation currently available through
1e×ばつ; experiment JSON records exact decimal values and the configured
progressive Julia target. CPU work scales with reference precision and
iteration count rather than pixel count. Automatic reference rebasing and
formal glitch validation remain in development.
At extreme depth, a uniformly dark view (or, with an inside orbit trap, a smoothly shaded one) is not yet proof that the sampled region is mathematically interior. Progressive zoom retains a finite selected centre, which can eventually fall entirely to one side of a boundary, and the current perturbation path does not yet implement automatic rebasing or glitch detection. Increasing the iteration limit also rebuilds the arbitrary- precision reference orbit synchronously and can ask every displayed pixel to execute up to 50,000 shader iterations. High iteration counts can therefore temporarily make the application unresponsive, especially for non-escaping views.
cargo run
Install the WASM target and Trunk, then:
rustup target add wasm32-unknown-unknown cargo install trunk trunk serve
Open http://127.0.0.1:8080 in a browser with WebGPU enabled.
For a production bundle, with Binaryen's wasm-opt installed when available:
./build-release.sh
IteraScope deploys as a static bundle served by Caddy. The multi-stage
Dockerfile compiles the WASM application, optimizes it with
Binaryen, and precompresses the browser assets. fly.toml runs the
small serving container in Toronto (yyz), enforces HTTPS for WebGPU, performs
HTTP health checks, and stops the Machine when it is idle.
For the first deployment:
fly auth login fly apps create iterascope fly deploy
Subsequent deployments only require:
fly deploy
The app will be available at https://iterascope.fly.dev. If the global Fly
app name is no longer available, choose another name with fly apps create and
change the app value in fly.toml to match.
cargo check
cargo test
cargo check --target wasm32-unknown-unknownThe ignored gpu_* tests in src/render/mod.rs run on the real GPU and
write PPM renders to $ITERASCOPE_RENDER_DIR: a gallery of every family, the
perturbation-versus-double-single-versus-CPU comparison, deep-zoom structure
at 1e×ばつ, preview-blit and pan consistency, a gallery of every colouring
algorithm at the default view and around an f64 reference at 1e×ばつ, the
iteration, distance, stripe and triangle colourings around an
arbitrary-precision reference at 1e×ばつ, a five-frame zoom-path export
crossing every precision path at a row-padded width, a layer-compositing test
(a single-layer stack must reproduce the pre-layer renderer byte for byte,
every merge mode must be distinct, white/black/gradient masks must behave,
an eight-layer stack must survive, and skipping leading iterations must
revive the orbit trap at depth), a GPU-versus-CPU compositor equivalence
test over a stack mixing shared, mask and detached layers (and a second at
1e30 through an arbitrary-precision detached reference),
and a timing probe that also records the cost of the orbit-statistics
variant:
ITERASCOPE_RENDER_DIR=out cargo test --release gpu_ -- --ignored --nocaptureDocument → Save... writes the complete reproducible experiment state to a
.json file — through the native file dialog, or as a download in the
browser build — and Open... loads one back through the file picker on both
targets. The versioned document (format version 7) records the
active family, both plane centres and scales, the selected parameter or
starting value, the family parameters the active family uses, computation
limits, the layer stack with each layer's gradient and colouring algorithms,
and display settings, including critical-orbit overlay visibility. Version-5
documents load their single colouring as a one-layer stack; documents from
before version 5 load with the default colouring (their palette phase becomes
the outside offset). Runtime
diagnostics, selected inspector step and frame timing are deliberately not
stored. Opened documents are validated before any live state is changed.
- Automatic reference rebasing and perturbation glitch detection
- Deterministic validation against direct arbitrary-precision sample orbits
- Progressive/background reference-orbit generation at very high iteration counts
- Parameterized Newton polynomials and rational maps with critical-point analysis
- Orbit probes for the non-quadratic escape-time families, and arbitrary-precision exponential/trigonometric reference orbits so the transcendental families can join the deep-zoom path
- Towards generative fractal art: keyframed centre drift and per-parameter animation curves; register-resident accumulators for the first statistics-bearing layer (the per-layer arrays cost the opt-in statistics variant roughly ×ばつ today); derivatives for the remaining families' distance estimates
IteraScope is an executable scientific prototype. Its numerical results are exploratory, not certified. Precision modes, thresholds and algorithmic assumptions remain visible so a plausible-looking image is not silently presented as a trustworthy computation.
IteraScope is open-source software released under the MIT License.