-
Notifications
You must be signed in to change notification settings - Fork 0
CalcGen UserGuide
This is the complete authoring reference for your own fractal formulas in Fracturing Fog. It documents the equation language (the DSL) in full — every operator, function, and constant, the mathematical grammar, how an equation is built and iterated, which features are available on which rendering path — and closes with an exhaustive cookbook of ready-to-paste recipes.
It is written for authors, not implementers. If you want the generator internals (AST, simplifier, Taylor/BLA expansion, distance-estimate derivation), read the technical companions:
Companion pages: User Index · Fractal Equation Design Guide (technical) · CalculatorGen Authoring (technical) · User Bulb 3D Guide · ColorGen User Guide
- What "your own equation" means
- Two engines, one language
- The editor at a glance
- Mathematical grammar — how an equation is built
- Language reference — operators
- Language reference — the function catalogue
- Constants, variables, and state
- Structure:
let, statement blocks, and conditionals - Holomorphic vs non-holomorphic — why it matters
- Execution paths and what gates them
- Migrating C# equations to the DSL
- The Cookbook
- Editor workflow
- Command-line generation
- Troubleshooting
- Reference card
- See also
A fractal equation in Fracturing Fog is the single line of math the app runs once per pixel, per iteration. The classic Mandelbrot recipe is:
z starts at zero, c is the pixel's coordinate on the complex plane. Repeat
that line hundreds or thousands of times for every pixel, ask whether |z| ever
runs away past a bailout radius, and colour each pixel by how fast it escaped.
That is the whole idea behind every escape-time fractal.
The User Equation type lets you replace that one line with anything the DSL can express. In the DSL, the Mandelbrot recipe is simply:
z*z + c
You are always writing the right-hand side of z_{n+1} = …. You never write
z_{n+1} = yourself — the app supplies the loop, the escape test, the precision
management, the colouring, and the deep-zoom machinery. You supply the math.
Note
The DSL is deliberately small and pure: it has numbers, the two fractal
variables, arithmetic, a fixed catalogue of mathematical functions, and simple
structure (let, conditionals). It has no file access, no loops, no
reflection, nothing from the .NET runtime. That is what makes an equation safe
to save, share, and open from someone else's region file.
The same equation can be run by two different engines, and it is worth understanding the split because it decides how deep you can zoom and how fast the render is.
| Live interpreter | CalcGen compiler | |
|---|---|---|
| Where | The editor's live preview + the User Equation fractal type |
The DSL tab → Compile & Load / Generate via CalcGen |
| How it runs | Interprets the equation directly, one pixel at a time | Generates typed C#, compiles it, and hot-loads a real calculator |
| Speed | Good for authoring; scalar only | Fast: scalar + AVX2 SIMD + GPU |
| Deep zoom | Shallow (roughly to the limit of double) |
Deep — perturbation, BLA, Series Approximation, DD/QD high precision |
| Surface normals / distance estimate | No | Yes, where the math allows |
| Language | The DSL (this document) | The DSL (this document) |
Both engines speak the same DSL, with two small dialect differences noted
throughout (the live interpreter additionally understands let … in, ?:,
&& || !, and multi-statement blocks; CalcGen understands if … then … else
and integer-only ^). Everything in the function catalogue
works in both.
The practical workflow: author in the live editor (instant feedback), then, when you want speed or a deep dive, Compile & Load through CalcGen.
Note
Older versions accepted raw C# that was compiled with full .NET access. That
path has been retired for safety. The User Equation tab still accepts
C#-style text (return z*z + c;) and automatically translates it to the DSL
for you (see §11), but the DSL is now
the real language underneath. New equations should be written directly in DSL.
Open it with toolbar Type → User Equation, then Params. The editor has two input tabs and a live analysis panel.
| Control | What it does |
|---|---|
| User Equation tab | Accepts DSL or C#-style (return z*z + c;). Runs live on the interpreter and auto-renders as you type. C# is translated to DSL automatically. |
| DSL tab | Bare DSL (z*z + c). Feeds the CalcGen buttons. |
| Compile & Load | CalcGen-compiles the DSL and swaps it onto the live render (unlocks SIMD / GPU / deep zoom / normals). |
| Compile + Save | The same, and stores the equation in your library. |
| Generate via CalcGen | Writes Calculators/Generated/{Name}Calculator.cs for a permanent, build-time calculator. |
| Validate for CalcGen | Parses without rendering — tells you whether the DSL compiles and what paths it will support. |
| Equation Guide | Opens this document. |
| Rotation° | Rotates the sampling plane for display only; the equation is unchanged. |
The live analysis panel mirrors what CalcGen deduces from your equation:
| Readout | Meaning |
|---|---|
| AST | The parsed equation, normalised and pretty-printed. |
| dz/dz, dz/dc | The symbolic derivatives used for normals and distance estimate. |
| SA | Series-Approximation degree (0 = unavailable). |
| Perturbation | Whether deep-zoom perturbation is available. |
| DE / normals | Whether the distance estimate / surface normals are available. |
| Flags | The feature flags (conj, div, trans, cond, …) the equation tripped. |
Use the panel as a live gating check: if DE / normals reads off and you wanted lit relief, you know a construct in your equation disabled it — see §9 and §10.
Every value in the DSL is a complex number a + b·i, where a is the real
part and b the imaginary part. A plain number like 2 or 0.5 is the complex
value (2, 0) — a real value, which the engine tracks specially so it can skip
the dead imaginary half of the arithmetic. The pixel coordinate c and the
iterate z are full complex numbers.
i is the imaginary unit (0, 1). So 3 + 4*i is the complex number with real
part 3 and imaginary part 4, and i*z rotates z by 90°.
An equation defines one step. The host wraps it in the loop:
z ← 0 (or a seed)
repeat:
z ← <your equation> # c is the pixel; z is the running value
if |z| > bailout: escaped
Because the equation is re-evaluated every iteration, the same short formula produces unlimited detail — the feedback is where the fractal comes from. The Design Guide §1–2 explains the dynamics in depth.
Some functions return a real value lifted back into the complex plane as
(value, 0):
-
re(z),im(z),abs(z),arg(z)— extract a real scalar. -
min,max,mod,clamp,atan2— operate on real parts. -
floor,ceil,round,trunc,fract,sign— act per component.
Everything else is fully complex. Mixing is fine: z*z + abs(z) + c adds the
complex z*z, the real-lifted abs(z), and the complex c.
From loosest to tightest binding:
| Level | Operators | Associativity |
|---|---|---|
| 1 |
+ -
|
left |
| 2 |
* /
|
left |
| 3 |
^ (power) |
CalcGen: applies to the preceding factor, integer exponent. Live: right-associative, any exponent |
| 4 | unary -
|
prefix |
| 5 | atoms: numbers, z, c, (…), f(…)
|
— |
So 2*z^2 is 2*(z^2), and -z*z is -(z*z) is (-z)*z — all the same by
sign rules, but be explicit with parentheses when in doubt. a - b - c is
(a - b) - c.
Note
Power differs between the two engines. CalcGen's ^ takes an integer
exponent 0–64 and applies to the immediately preceding factor (z^3,
c^2); for anything else — negative, fractional, or complex exponents — use
the pow(base, exp) function. The live interpreter's ^ is right-associative
and accepts any exponent (z^2.5, z^-3). For portability across both
engines, prefer pow() whenever the exponent is not a small non-negative
integer.
Start from the Mandelbrot baseline and layer ideas:
z*z + c # 1. Mandelbrot
z*z*z + c # 2. raise the power → Multibrot
z*z*z - z + c # 3. add a linear term → a new critical structure
z*z*z - z + 0.5*conj(z) + c # 4. mix in a conjugate → break the symmetry
Each change is a hypothesis; Compile & Load (or just watch the live render) is the experiment. Keep the ones that surprise you and Save them with a descriptive name.
| Operator | Example | Meaning |
|---|---|---|
+ |
z + c |
Complex addition |
- |
z - c, -z
|
Complex subtraction; unary negation |
* |
z*z, 2*c
|
Complex multiplication |
/ |
z / (z + 1) |
Complex division (see gating in §10) |
^ |
z^2, z^3
|
Power. CalcGen: integer 0–64. Live: any exponent, right-assoc. Prefer pow() for non-integer. |
( )
|
(z + c)*(z - c) |
Grouping |
Comparisons — only inside a condition (if … in CalcGen, ?:/&&/|| live):
| Operator | Meaning |
|---|---|
< <= > >=
|
ordered comparison of real scalars |
== !=
|
equality / inequality |
Boolean operators (live interpreter only): && (and), || (or), ! (not).
Ternary (live interpreter only): cond ? a : b.
Warning
A bare = is not an operator in an expression — use == to compare. In
the live interpreter, = only appears in a statement block or a let binding
(let k = … in …). In CalcGen, use if cond then … else ….
Every function below works in both engines unless noted. Argument counts are fixed; names are case-insensitive.
| Function | Signature | Meaning & notes |
|---|---|---|
sqr(x) |
1-arg |
x*x. A convenience; identical to writing x*x (and still deep-zoomable). |
pow(x, y) |
2-arg | General power x^y. If both operands are real, real Math.Pow (so pow(-2, 3) = -8); otherwise the principal complex power, zero-guarded so pow(0, 0) = 1 and pow(0, k) = 0 (no NaN at the z = 0 seed). Use for negative or fractional exponents. |
sqrt(x) |
1-arg | Principal square root. In CalcGen it desugars to exp(0.5*log(x)), matching Complex.Sqrt's branch. |
sqr(z) + c # = z*z + c
pow(z, 3) + c # cubic Multibrot, general-power form
pow(z, -2) + c # negative power — a "Donut"/inverse map (finite at z=0)
z*pow(z, -3) + c*pow(c, -2) # "Movie Reel" — mixed inverse powers
pow(z, 2.5) + c # fractional power (live: z^2.5 also works)
sqrt(z*z - 1) + c # square-root shell
Note
Why pow(z, -3) instead of 1/z^3? At the Mandelbrot seed z = 0, the
form 1/z^3 is 1/0 = NaN and blanks the image; pow(0, -3) is defined as
0 by the zero-guard, so negative-power maps render correctly. Always express
negative powers with pow().
| Function | Meaning |
|---|---|
exp(x) |
e^x (complex exponential) |
log(x) |
Natural log, principal branch (pole at 0) |
sin(x) cos(x) tan(x)
|
Circular trig (tan = sin/cos) |
sinh(x) cosh(x) tanh(x)
|
Hyperbolic (built from exp) |
exp(z) + c # exponential map
sin(z) + c # sinusoidal
log(z*z) + c # logarithmic shell
sin(z)*cos(c) + c # mixed trig "petals"
tanh(z*z) + c # hyperbolic
sin(pi*z) + c # constants in action
| Function | Meaning |
|---|---|
asin(x) acos(x) atan(x)
|
Inverse circular functions (principal branch) |
asinh(x) acosh(x) atanh(x)
|
Inverse hyperbolic functions |
These are holomorphic. Surface normals and the distance estimate work for
them — their analytic derivative rules are built in (e.g.
d/dz asin(u) = u'/√(1 − u²)). They still run on the shallow direct paths (no
deep-zoom perturbation), which is where their parity holds.
atan(z) + c # bounded inverse-tangent map
asinh(z*z) + c # chain rule exercised in the normals
z*z + asin(c) + c # inverse-trig term with full distance estimate
Note
asin, acos, atanh are bounded maps — orbits stay small and may never
exceed the bailout, so a solid "all inside the set" image can be the correct
result, not a blank. Drive them with an escaping term (e.g. a z*z) if you
want classic escape banding.
Applied to the real and imaginary parts independently: f(a + b·i) = f(a) + f(b)·i.
| Function | Meaning |
|---|---|
floor(x) |
Round toward −∞, each component |
ceil(x) |
Round toward +∞ |
round(x) |
Round half-to-even |
trunc(x) |
Round toward zero |
fract(x) |
Fractional part x − floor(x)
|
sign(x) |
−1 / 0 / +1 per component |
fold(x) |
`( |
fold(z)*fold(z) + c # Burning Ship
z*z + fract(z) + c # domain-warped / tiled Mandelbrot
z*z + 0.1*floor(z*4) + c # quantised feedback ("pixelated" bands)
These return a real scalar lifted to (value, 0).
| Function | Meaning |
|---|---|
re(x) |
Real part |
im(x) |
Imaginary part |
abs(x) |
Magnitude ` |
arg(x) |
Principal argument (angle) in (−π, π]
|
conj(x) |
Complex conjugate (Re, −Im) — this one stays complex |
min(a, b) max(a, b)
|
Min / max of the real parts |
mod(x, p) |
Real modulo, centered, per component |
clamp(x, lo, hi) |
Clamp the real part to [lo, hi]
|
atan2(y, x) |
Two-argument arctangent of the real parts |
conj(z)*conj(z) + c # Tricorn
z*z + 0.1*arg(z) + c # spiral biased by orbit angle
min(z*z, max(z, -1.0)) + c # clamp-like feedback
z*z + mod(z, 1.0) + c # periodic wrap
clamp(z, -2.0, 2.0) + c # bounded feedback
Warning
abs has two meanings depending on where it appears. As a value in an
expression, abs(x) is the magnitude |x|. Inside an if condition, the
shorthand abs(x) means the squared magnitude |x|² — it saves a square
root and matches the bailout-threshold form (if abs(z) > 4 … is really
|z|² > 4). If you need the true magnitude inside a condition, compare against
the squared threshold, or use re/im explicitly.
| Name | Meaning | Availability |
|---|---|---|
z |
Current iterate (complex) | both engines |
c |
Pixel coordinate (complex) | both engines |
n / iter
|
Current iteration index (real scalar) | both engines |
prev |
The previous iterate z_{n-1} (Phoenix coupling) |
CalcGen only |
pi |
π | both |
e |
Euler's number | both |
i |
Imaginary unit (0, 1)
|
both |
z*z + c + 0.5*prev # Phoenix (CalcGen)
z*z + c + 0.001*n # slow iteration drift
i*z + c # 90° rotation via the imaginary unit
sin(pi*z) + e*c # π and e as literals
Note
prev is only available through the CalcGen path (it needs an extra state
slot the live interpreter does not carry). If you author a Phoenix equation,
use Compile & Load to see it.
CalcGen uses an if … then … else expression — both branches must produce
a value:
if abs(z) < 1 then z*z + c else z*z - c
if re(z) > 0 then z*z + c else conj(z)*conj(z) + c
if im(z) > 0 then z^3 + c else z^2 + c
Each side of the comparison is a single condition term: re(…), im(…),
abs(…) (remember: |x|² here), arg(…), or a numeric literal. You cannot
combine terms inside a CalcGen condition (re(z)*im(z) > 0 is not valid there,
and there is no &&/||). For compound conditions, use the live interpreter's
ternary (next section).
The live interpreter additionally supports C-style ternary and boolean operators:
abs(z) < 1 ? z*z + c : z*z - c
(re(z) > 0 && im(z) > 0) ? z^3 + c : z*z + c
Name a sub-expression and reuse it:
let w = z*z in w*w + c # z^4 + c, computed once
let r = abs(z) in z*z + r*c # magnitude-weighted forcing
A saved C#-style equation can be a short sequence of statements — declarations,
reassignments, an early guard, and a final return. Each desugars to a let;
reassignment shadows the previous binding. There are still no loops, no braces,
and no side effects.
var w = z*z;
var d = w - 1;
return w*w + d + c;
if (n == 0) z = c; // seed on the first iteration
return z*z + c;
Blocks are how legacy multi-line C# equations keep working; when authoring fresh,
a single DSL expression (optionally with let) is usually clearer.
A function is holomorphic (complex-differentiable) if it bends the plane smoothly without folding or reflecting it. Holomorphicity is not academic here — it decides which rendering features your equation can use:
-
Holomorphic (polynomials,
exp,log,sin/cos/tan,sinh/cosh/tanh,sqrt, inverse trig, division): the app can trackdz/dcanalytically, so surface normals and the distance estimate work, and (for polynomials) the deep-zoom accelerators are available. -
Non-holomorphic (
conj,fold,re,im,abs,arg,atan2,min,max,mod,clamp, the per-component rounding functions): these fold or reflect the plane. They render beautifully, but the derivative chain becomes meaningless, so the distance estimate / normals turn off for equations that use them.
The one deliberate exception the app makes: pow and the per-component
functions are transcendental/kinked, so they disable the deep-zoom accelerators
and the distance estimate, while the inverse-trig functions keep the distance
estimate (their derivatives are known) even though they too disable deep-zoom
perturbation.
The live analysis panel's DE / normals and Perturbation readouts tell you exactly what your current equation supports.
CalcGen emits every path and the runtime picks the best for the current view. Some constructs switch specific paths off:
| Construct in your equation | Scalar | AVX2 | Perturbation | BLA / SA | DE (normals) | DD/QD deep | GPU |
|---|---|---|---|---|---|---|---|
Polynomial in z (+ c) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Division /
|
✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ |
conj / fold
|
✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
exp log sin cos tan sinh cosh tanh sqrt
|
✓ | ✓ | ✗ | ✗ | ✓ | ✓ (degraded) | ✓ |
asin acos atan asinh acosh atanh
|
✓ | ✓ | ✗ | ✗ | ✓ | ✓ (degraded) | ✓ |
pow |
✓ | ✓ | ✗ | ✗ | ✗ | ✓ (degraded) | ✓ |
re im abs arg atan2 min max mod clamp
|
✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
floor round ceil trunc fract sign
|
✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
if … then … else |
✓ | ✓ | ✗ | ✗ | ✓ per branch | ✗ | ✓ |
prev (Phoenix) |
✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
n / iter
|
✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
Rules of thumb:
-
Stay polynomial in
z(+c) to keep every accelerator — that is the only shape that gets perturbation + BLA + Series Approximation, so it is the one you can dive into past 10¹⁵. - Transcendental holomorphic functions keep the distance estimate (so you can light them as relief) but drop deep-zoom perturbation.
- Inverse trig is the sweet spot for lit non-polynomial art: normals on, perturbation off.
- The status bar shows the active path (
SP/AVX2/PT/DD-HP/QD-PT).
The User Equation tab still accepts C#-style text and translates it
automatically; the table below is the exact mapping so you can convert by hand or
understand what the translator did. Write new equations in DSL directly.
| C# form | DSL form |
|---|---|
return z*z + c; |
z*z + c |
Complex.Conjugate(z) |
conj(z) |
Complex.Pow(z, 3) |
z^3 (or pow(z, 3)) |
Complex.Pow(z, -3) |
pow(z, -3) |
Complex.Pow(z, expr) |
pow(z, expr) |
Complex.Sqrt(z) |
sqrt(z) |
Complex.Divide(a, b) |
(a)/(b) |
Complex.Sin/Cos/Tan/Exp/Log(z) |
sin/cos/tan/exp/log(z) |
Complex.ImaginaryOne |
i |
new Complex(a, b) |
(a + (b)*i) |
new Complex(a, 0) |
a |
z.Real |
re(z) |
z.Imaginary |
im(z) |
z.Magnitude |
abs(z) (or sqrt(z*conj(z))) |
z.Phase |
arg(z) |
Math.Abs(x) |
abs(x) |
Math.PI / Math.E
|
pi / e
|
Complex.Zero / Complex.One
|
0 / 1
|
Worked conversions:
// Tricorn — C#: var zb = Complex.Conjugate(z); return zb*zb + c;
conj(z)*conj(z) + c
// Hybrid — C#: return z*z + c + 0.25*Complex.Conjugate(z);
z*z + c + 0.25*conj(z)
// Newton-ish — C#: return z - Complex.Divide(z*z*z - 1, 3*z*z);
z - (z*z*z - 1)/(3*z*z)
Note
Saved equations from older versions are migrated to DSL automatically on first launch, with a timestamped backup of your library kept alongside. Your edits are preserved; only exact unmodified legacy sources are rewritten.
Each recipe is a complete equation you can paste into the DSL tab. Grouped by family; every one is DSL (no C#).
z*z + c # classic Mandelbrot
z^3 + c # cubic Multibrot
z^4 + c
z^5 + c
z^6 + c
z^3 - z + c # cubic with a linear term
z*z + 0.5*z + c # quadratic with linear coupling
(z^4 + z^2)/2 + c # mixed-degree (division → shallow only)
i*z*z + c # complex leading coefficient
0.5*z*z + 0.3*i*z + c # mixed real/imaginary coefficients
fold(z)*fold(z) + c # Burning Ship
conj(z)*conj(z) + c # Tricorn (Mandelbar)
conj(fold(z))^2 + c # Burning-Tricorn hybrid
fold(z)^3 + c # cubic Burning Ship
z*z + c + 0.25*conj(z) # Mandelbrot / Tricorn blend
(z*z - 1)/(z + 1) + c # Mandelbrot-on-a-shell
z - (z*z*z - 1)/(3*z*z) # Newton-shaped iteration
(z*z + c)/(1 + 0.1*z) # damped feedback with a c-independent pole
1/(z*z) + c # inverse-square (prefer pow(z,-2)+c)
pow(z, -2) + c # "Donut" inverse map (finite at z=0)
z*pow(z, -3) + c*pow(c, -2) # "Movie Reel"
pow(z, 2.5) + c # fractional Multibrot
pow(z, 3) + pow(z, -1) + c # mixed positive/negative powers
exp(z) + c
sin(z) + c
cos(z)*z + c
log(z*z) + c
sin(z)*cos(c) + c
0.5*z + sin(z) + c # damped oscillator
tan(z) + c
sinh(z) + c
cosh(z) - 0.5*c
tanh(z*z) + c
sqrt(z) + c
sqrt(z*z - 1) + c
sin(pi*z) + c
e*z*z + c
atan(z) + c # bounded; drive harder for banding
z*z + asin(c) + c # escaping term + inverse-trig, with normals
asinh(z*z) + c
z*z + 0.3*atan(z) + c # gentle inverse-tangent forcing
acosh(z) + c
z*z + c + 0.5*prev # classic Phoenix
z*z - 0.5*prev + c # negative feedback
z^3 + 0.4*prev + c # cubic Phoenix
z*z + 0.3*prev - 0.1*prev*prev + c # two-tap Phoenix
z*z + fract(z) + c # tiled / Kali-style warp
z*z + 0.1*floor(z*4) + c # quantised bands
z*z + 0.2*sign(re(z)) + c # sign-driven asymmetry
z*z + round(z) - z + c # snap-to-lattice feedback
z*z + 0.1*arg(z) + c # spiral bias by orbit angle
z*z + 0.05*atan2(z, c) + c # branch by relative angle
if arg(z) > 0 then z*z + c else z*z - c # phase-split map
min(z*z, max(z, -1.0)) + c # clamped feedback
z*z + mod(z, 1.0) + c # periodic wrap
max(z*z, sqr(z)) + c # hybrid step
clamp(z, -2.0, 2.0) + c # bounded orbit
z*z + clamp(re(z), -1.0, 1.0)*i + c # clamp only the real drive
if abs(z) < 1 then z*z + c else z*z - c
if re(z) > 0 then z*z + c else conj(z)*conj(z) + c
if im(z) > 0 then z^3 + c else z^2 + c
Live-interpreter ternary equivalents — and compound conditions the CalcGen if
cannot express (a product of terms, &&/||):
abs(z) < 1 ? z*z + c : z*z - c
(re(z) > 0 && im(z) > 0) ? z^3 + c : z*z + c
re(z)*im(z) > 0 ? z^3 + c : z^2 + c
z*z + c + 0.001*n # slow drift (breaks scale invariance — by design)
sin(z + 0.01*n) + c # phase that winds with depth
(1 - n*0.001)*(z*z) + n*0.001*(z^3) + c # crossfade quadratic→cubic
if (n mod 4) < 2 then z*z + c else z*z - c # alternate every few iters
let w = z*z in w*w + c # z^4, computed once
let r = abs(z) in z*z + 0.2*r*c
var w = z*z;
var d = w*w - w;
return d + c;
- Type → User Equation, then Params.
- Type in the User Equation tab (DSL or C#-style) — it renders live.
- When you like it, switch to the DSL tab, click Validate for CalcGen to confirm it compiles and see which paths it supports, then Compile & Load for the fast/deep engine.
-
Compile + Save (or Save…) to store it in your library
(
%APPDATA%\FracturingFog\userequations.json). - Promote to fractal list to surface it in the Type dropdown next launch.
| Path | When to use |
|---|---|
| Compile & Load | Iterative authoring; preview an idea with the full engine in one click. |
| Generate via CalcGen | Promote a keeper into a permanent C# calculator that builds into the app. |
Generated files land at Calculators/Generated/{Name}Calculator.cs (plus a
self-test); the next dotnet build compiles them in and the calculator appears
under Type → "{Name} (Generated)".
The status line shows ✓ Compiled (a green/neutral confirmation) or the parse
error with line, col. Live re-compile debounces about half a second after you
stop typing. Advisory messages render in yellow (#FFCC00), never red.
dotnet build CalculatorGen\CalculatorGen.csproj -c Release
dotnet run --project CalculatorGen -c Release -- `
--equation "z*z + c" `
--name MandelbrotZ2 `
--out Calculators\Generated `
--selftestFlags:
-
--equation "..."— the RHS only (noz_{n+1} =). -
--name <Name>— base class name (Calculatorsuffix added automatically). -
--out <dir>— output directory (default: current directory). -
--selftest— also emit{Name}CalculatorSelfTest.cs. -
--bailout <R>— bailout radius (default 512; the generator squares it, so--bailout 512means "escape when|z|² ≥ 262144"). Raise it for smoother transcendental gradients; drop it to2for the classic Mandelbrot contract.
| Symptom | Likely cause / fix |
|---|---|
Unknown identifier 'X' |
Typo. The allowed names are the operators, the functions in §6, the constants pi e i, and the variables z c n/iter prev. The lexer suggests the closest match. |
Unexpected '=' |
Use == to compare. = only appears in a let/statement-block binding. |
Exponent … must be a non-negative integer ≤ 64 |
CalcGen's ^ caps at 64 and takes integer exponents. Use pow(x, y) for anything else. |
Expected ',' … after a function name |
Multi-arg functions (pow, atan2, min, max, mod, clamp) need their arguments comma-separated. |
Equation is empty |
The editor is blank after stripping return/;. |
| DE / normals reads off when you wanted relief | A non-holomorphic construct (conj, fold, re/im/abs/arg, min/max/mod/clamp, per-component, pow, prev, n) disabled it — see §9. |
| Deep zoom drops to scalar / DD | A non-polynomial construct disabled perturbation; only z^d + c-shaped maps deep-zoom with acceleration. |
A bounded map (asin, atanh) renders all-inside |
That is correct — those orbits never escape. Add an escaping term (z*z). |
| Negative-power map is blank | Use pow(z, -k) (zero-guarded), not 1/z^k (NaN at z = 0). |
Variables / state z c n (or iter) prev (CalcGen)
Constants pi e i (i = imaginary unit (0,1))
Operators + - * / ^ (^ = int 0..64 in CalcGen; any exp live)
Compare (in cond) < <= > >= == !=
Boolean (live) && || !
Conditional CalcGen: if <cmp> then <expr> else <expr>
Live: <cmp> ? <expr> : <expr>
Binding (live) let <name> = <expr> in <expr> | statement blocks
Powers / roots sqr(x) pow(x,y) sqrt(x)
Exp / log / trig exp log sin cos tan sinh cosh tanh
Inverse trig asin acos atan asinh acosh atanh (normals ON)
Per-component floor round ceil trunc fract sign fold
Real extractors re(x) im(x) abs(x) arg(x) conj(x)
Real reducers min(a,b) max(a,b) mod(x,p) clamp(x,lo,hi) atan2(y,x)
Reminders:
-
abs= magnitude|x|in an expression, but|x|²inside anifcondition. - Prefer
pow()over^whenever the exponent is not a small non-negative integer, so the equation behaves the same on both engines. - Stay polynomial in
z(+c) to keep deep zoom + normals.
- Fractal Equation Design Guide (technical) — the math of each family, deep-zoom tables, and per-family modification cookbook.
- CalculatorGen Authoring (technical) — generator internals.
- ColorGen User Guide — the sibling DSL for algorithmic colour themes.
- User Bulb 3D Guide — the 3-D analogue (Vec3 / Quat raymarched DSL).
- Colour Theme Editor Guide — paint what your equation produces.
- Relief 3D Guide — turn a distance-estimate-capable equation into lit relief.