comment {
  Minimal subset of UF's Standard.ucl transcribed from UF's published
  algorithm descriptions. Suitable for the MegaFractal test corpus;
  replace with your UF install's Standard.ucl for production use.
}

Default {
; UF's iterations coloring: `#index = 0.01 * #numiter`. With repeat=yes
; (gradient default) the index wraps every 100 iterations, producing the
; classic banded look. For inside pixels at maxiter, index = 0.01*maxiter;
; the wrap puts them at a deterministic LUT position determined by maxiter.
; Layers using Default-as-inside need inside pixels at LUT pos =
; (0.01 * maxiter) mod 1 * GRADIENT_SIZE, NOT LUT pos 0 (which is what
; `numiter / maxiter` produced).
final:
  #index = 0.01 * #numiter
default:
  title = "Default"
}


BinaryDecomposition {
; One-bit angle decomposition: pick one of two gradient colours based on
; whether z's argument is in the upper or lower half-plane (Type 2) or
; whether real(z)*imag(z) >= 0 (Type 1).
final:
  if @type == "Type 1"
    if real(#z) * imag(#z) >= 0
      #index = 0.5
    else
      #index = 0
    endif
  else
    if atan2(#z) > 0
      #index = 0.5
    else
      #index = 0
    endif
  endif
default:
  title = "Binary Decomposition"
  param type
    caption = "Decomposition Type"
    enum = "Type 1" "Type 2"
    default = 0
  endparam
}


Gradient {
; Render the gradient itself, ignoring the fractal formula. Useful for
; seeing what the gradient looks like irrespective of the iteration math.
final:
  if @type == "Linear"
    #index = 16.5 + 0.25 * real(#pixel)
  elseif @type == "Radial"
    #index = 0.2 * |#pixel|
  else
    #index = 0.5 - atan2(#pixel) / (2 * #pi)
  endif
default:
  title = "Gradient"
  param type
    caption = "Gradient Type"
    enum = "Linear" "Radial" "Cone"
  endparam
}


Decomposition {
; Angle-of-z decomposition across the whole gradient.
final:
  float d = atan2(#z)
  IF (d < 0)
    d = d + #pi * 2
  ENDIF
  #index = d / (#pi * 2)
default:
  title = "Decomposition"
}


Triangle {
; Triangle Inequality Average (Kerry Mitchell) with Smooth-style
; continuous-iteration blending. Only correct for z^n + c divergent
; fractals; @power and @bailout should match the formula's.
init:
  float sum = 0.0
  float sum2 = 0.0
  float ac = cabs(#pixel)
  float il = 1/log(@power)
  float lp = log(log(@bailout)/2.0)
  float az2 = 0.0
  float lowbound = 0.0
  float f = 0.0
  BOOL first = true
loop:
  sum2 = sum
  IF (!first)
    az2 = cabs(#z - #pixel)
    lowbound = abs(az2 - ac)
    sum = sum + ((cabs(#z) - lowbound) / (az2+ac - lowbound))
  ELSE
    first = false
  ENDIF
final:
  sum = sum / (#numiter)
  sum2 = sum2 / (#numiter-1)
  f = il*lp - il*log(log(cabs(#z)))
  #index = sum2 + (sum-sum2) * (f+1)
default:
  title = "Triangle Inequality Average"
  param power
    caption = "Exponent"
    default = 2.0
  endparam
  param bailout
    caption = "Bailout"
    default = 1e20
    min = 1
  endparam
}


DistanceEstimator {
; Distance-estimator coloring for z^n + c Julia/Mandelbrot: tracks dz/dc
; through the iterate loop and emits a log-length estimate at escape.
init:
  complex dz = (0,0)
loop:
  dz = @power * #z^(@power-1) * dz + 1
final:
  #index = (@power*log(cabs(#z)) * cabs(#z) / cabs(dz))^(1/@power)
default:
  title = "Distance Estimator"
  param power
    caption = "Exponent"
    default = 2.0
  endparam
}


Emboss {
; 3D-ish emboss using fixed gradient slots at 0.2, 0.5, 0.8. Uses real/
; imag components of #z as a proxy for surface orientation; intended for
; the Slope-family fractals but degrades gracefully elsewhere.
final:
  if (real(#z) < imag(#z))
    #index = 0.2
  elseif (imag(#z) < real(#z))
    #index = 0.8
  else
    #index = 0.5
  endif
default:
  title = "Emboss"
}


ExponentialSmoothing {
; Combined convergent/divergent exponential smoothing (Ron Barnett via
; Damien M. Jones). @diverge/@converge toggle which branches accumulate,
; and the final step picks the right sum based on whether the last step
; converged (|#z - z_old| < 0.5) or diverged.
init:
  float sum = 0.0
  float sum2 = 0.0
  complex zold = (0,0)
loop:
  IF (@diverge)
    sum = sum + exp(-cabs(#z))
  ENDIF
  IF (@converge)
    sum2 = sum2 + exp(-1/cabs(zold-#z))
  ENDIF
  zold = #z
final:
  IF (|#z - zold| < 0.5)
    IF (@converge)
      #index = sum2
    ELSE
      #index = 0
    ENDIF
  ELSE
    IF (@diverge)
      #index = sum * @divergescale
    ELSE
      #index = 0
    ENDIF
  ENDIF
default:
  title = "Exponential Smoothing"
  param diverge
    caption = "Color Divergent"
    default = FALSE
  endparam
  param converge
    caption = "Color Convergent"
    default = TRUE
  endparam
  param divergescale
    caption = "Divergent Density"
    default = 1.0
  endparam
}


Smooth {
; Continuous-iteration smooth coloring for z^n + c Julia/Mandelbrot (Linas
; Vepstas, via Damien M. Jones): #numiter + log-correction brings the raw
; iteration count into a smooth real so the gradient doesn't banded.
init:
  complex il = 1/log(@power)
  float lp = log(log(@bailout))
final:
  #index = 0.05 * real(#numiter + il*lp - il*log(log(cabs(#z))))
default:
  title = "Smooth (Mandelbrot)"
  param power
    caption = "Exponent"
    default = (2,0)
  endparam
  param bailout
    caption = "Bail-out value"
    default = 128.0
    min = 1
  endparam
}


GaussianInteger {
; Kerry Mitchell's Gaussian-integer distance coloring, as shipped in UF's
; Standard.ucl. Tracks the orbit of z against the lattice of complex
; integers; `colorby` picks one of nine scalar outputs (min/max distance,
; iteration at min/max, angle, average, ratio, etc.).
init:
  float r = 0.0
  float rmin = 1.0e12
  float rmax = 0.0
  float rave = 0.0
  float total = 0.0
  float t = 0.0
  int itermin = 0
  int itermax = 0
  complex zmin = (0, 0)
  complex zmax = (0, 0)
  complex temp = (0, 0)
  complex temp2 = (0, 0)
  complex remain = (0, 0)
  complex normfac = (1, 0)
  if @norm == 1
    normfac = #pixel
  elseif @norm == 2
    normfac = @fac
  elseif @norm == 3
    normfac = @normfunc(#z)
  endif
  float logfac = @logseed
loop:
  temp2 = #z
  if @randomize
    logfac = 4 * logfac * (1 - logfac)
    temp2 = temp2 * (1 - @randomsize * logfac)
  endif
  if @inttype == 0
    temp = round(temp2 / normfac)
  elseif @inttype == 1
    temp = floor(temp2 / normfac)
  elseif @inttype == 2
    temp = ceil(temp2 / normfac)
  else
    temp = trunc(temp2 / normfac)
  endif
  remain = temp2 - temp * normfac
  r = cabs(remain)
  total = total + r
  if r < rmin
    rmin = r
    zmin = temp2
    itermin = #numiter
  endif
  if r > rmax
    rmax = r
    zmax = temp2
    itermax = #numiter
  endif
final:
  if #numiter > 0
    rave = total / #numiter
  endif
  if @colorby == 1
    #index = 0.01 * itermin
  elseif @colorby == 2
    t = atan2(zmin) / #pi
    if t < 0
      t = t + 2.0
    endif
    #index = 0.5 * t
  elseif @colorby == 3
    #index = rmax
  elseif @colorby == 4
    #index = 0.01 * itermax
  elseif @colorby == 5
    t = atan2(zmax) / #pi
    if t < 0
      t = t + 2.0
    endif
    #index = 0.5 * t
  elseif @colorby == 6
    #index = rave
  elseif @colorby == 7
    zmax = (rave - rmin) + flip(rmax - rave)
    t = atan2(zmax) / #pi
    if t < 0
      t = t + 2.0
    endif
    #index = 0.5 * t
  elseif @colorby == 8
    #index = rmax / (rmin + 1.0e-12)
  else
    #index = rmin
  endif
default:
  title = "Gaussian Integer"
  param inttype
    caption = "Integer Type"
    enum = "round(z)" "floor(z)" "ceil(z)" "truncate(z)"
    default = 0
  endparam
  param colorby
    caption = "Color By"
    default = 0
    enum = "minimum distance" "iteration @ min" "angle @ min" "maximum distance" "iteration @ max" "angle @ max" "average distance" "min/mean/max angle" "max/min ratio"
  endparam
  param norm
    caption = "Normalization"
    default = 0
    enum = "none" "pixel" "factor" "f(z)"
  endparam
  param fac
    caption = "Factor"
    default = (2.0, 1.0)
  endparam
  func normfunc
    caption = "Function"
    default = ident()
  endfunc
  param randomize
    caption = "Randomize"
    default = false
  endparam
  param randomsize
    caption = "Random Size"
    default = (0.1, 0)
  endparam
  param logseed
    caption = "Random Seed"
    default = 0.1
  endparam
}


OrbitTraps {
; Direct port of UltraFractal's Standard.ucl OrbitTraps, restructured to compile
; through our pipeline: enum order matches canonical UF, shape distances
; cover all 23 shapes, all 9 trap-color modes, and all 19 trap-type modes
; (second/two variants degrade to closest with a one-iter offset).
init:
  float d = 0.0
  float d2 = 0.0
  complex z2 = (0,0)
  int iter = 0
  float diameter2 = sqr(@diameter)
  complex r = (0,1) ^ (@angle / 90.0)
  complex r0 = (0,0)
  complex rh = (0,1) ^ (@traporder / 8)
  complex zh = (0,0)
  complex trapcenter2 = @trapcenter

  if @trapshape == "ring ripples" || @trapshape == "grid ripples" ||      @trapshape == "radial ripples"
    diameter2 = #pi / @diameter
  endif

  float closest = 1e38
  float closest1 = 1e38
  complex point = (0,0)
  complex point1 = (0,0)
  complex point2 = (0,0)
  complex point3 = (0,0)
  bool done = false
  int i = 0
  int i1 = 0

  if @traptype == "farthest" || @traptype == "sum" ||      @traptype == "average" || @traptype == "sign average" ||      @traptype == "alternating average" || @traptype == "alternating average 2" ||      @traptype == "inverted sum" || @traptype == "exponential average" ||      @traptype == "average change" || @traptype == "inverted sum squared" ||      @traptype == "trap only"
    closest = 0.0
  elseif @traptype == "product"
    closest = 1.0
  elseif @traptype == "second farthest" || @traptype == "two farthest"
    closest = 0.0
    closest1 = 0.0
  endif

  bool usesolid = true

loop:
  iter = iter + 1
  if @traptype == "trap only"
    z2 = #pixel
  else
    z2 = #z
  endif

  z2 = (z2 - trapcenter2) * r

  if @aspect != 1.0
    z2 = real(z2) + flip(imag(z2) * @aspect)
  endif

  if @trapshape == "point"
    d = cabs(z2)
  elseif @trapshape == "ring"
    d = abs(cabs(z2) - @diameter)
  elseif @trapshape == "ring 2"
    d = abs(|z2| - diameter2)
  elseif @trapshape == "egg"
    d = (cabs(z2-flip(@diameter)*2) + cabs(z2)*@traporder*0.5) * 0.25
  elseif @trapshape == "hyperbola"
    d = abs(imag(z2) * real(z2) - @diameter)
  elseif @trapshape == "hypercross"
    d = abs(imag(z2) * real(z2))
  elseif @trapshape == "cross"
    d = abs(real(z2))
    d2 = abs(imag(z2))
    if d2 < d
      d = d2
    endif
  elseif @trapshape == "astroid"
    d = abs(real(z2))^@traporder + abs(imag(z2))^@traporder
    if @traporder < 0
      d = 1/d
    endif
  elseif @trapshape == "diamond"
    d = abs(real(z2)) + abs(imag(z2))
  elseif @trapshape == "rectangle"
    d = abs(real(z2))
    d2 = abs(imag(z2))
    if d2 > d
      d = d2
    endif
  elseif @trapshape == "box"
    d = abs(real(z2))
    d2 = abs(imag(z2))
    if d2 > d
      d = d2
    endif
    d = abs(d - @diameter)
  elseif @trapshape == "lines"
    d = abs(abs(imag(z2)) - @diameter)
  elseif @trapshape == "waves"
    d = abs(abs(imag(z2) + sin(real(z2)*@trapfreq)*@traporder*0.25) - @diameter)
  elseif @trapshape == "mirrored waves"
    d = abs(abs(imag(z2)) - @diameter + sin(real(z2)*@trapfreq)*@traporder*0.25)
  elseif @trapshape == "mirrored waves 2"
    d2 = @diameter - sin(real(z2)*@trapfreq)*@traporder*0.25
    d = abs(abs(imag(z2)) - d2)
    d2 = abs(abs(imag(z2)) + d2)
    if d2 < d
      d = d2
    endif
  elseif @trapshape == "radial waves"
    d2 = atan2(z2)
    d = abs(cabs(z2) * (1 - sin(d2*@trapfreq)*@traporder*0.125) - @diameter)
  elseif @trapshape == "radial waves 2"
    d2 = atan2(z2)
    d2 = sin(d2*@trapfreq)*@traporder*0.125
    d = abs(cabs(z2) * (1 - d2) - @diameter)
    d2 = abs(cabs(z2) * (1 + d2) - @diameter)
    if d2 < d
      d = d2
    endif
  elseif @trapshape == "ring ripples"
    d = cabs(z2)
    if d < @traporder
      d = cos(d * diameter2 * @trapfreq) * sqr(1-d/@traporder)
    else
      d = 0
    endif
  elseif @trapshape == "grid ripples"
    d = cabs(z2)
    if d < @traporder
      d = (cos(real(z2)*diameter2*@trapfreq) + cos(imag(z2)*diameter2*@trapfreq)) * sqr(1-d/@traporder) * 0.5
    else
      d = 0
    endif
  elseif @trapshape == "radial ripples"
    d = atan2(z2)
    d2 = cabs(z2)
    if d2 < @traporder
      d = cos(4 * d * @trapfreq) * sqr(1-d2/@traporder)
    else
      d = 0
    endif
  elseif @trapshape == "pinch"
    d2 = atan2(z2)
    if d2 < 0
      d2 = d2 + 2*#pi
    endif
    d = sqrt(cabs(z2)) / abs(sin(d2*@traporder*0.5))
  elseif @trapshape == "spiral"
    d = 1/(cabs(z2)) * @diameter
    r0 = (0,1) ^ d
    z2 = z2 * r0
    d = atan(abs(imag(z2)/real(z2)))
  elseif @trapshape == "heart"
    zh = real(z2) + flip(abs(imag(z2)))
    zh = zh*rh * 3 / @diameter
    d = abs(real(zh) - sqr(imag(zh)) + 3)
  endif

  ; trap-type accumulators - port of UltraFractal's Standard.ucl lines 821-989.
  ; second/two closest/farthest variants share the closest/farthest path
  ; (second-rank tracking would need a parallel slot; degrades to first-rank).
  if @traptype == "closest" || @traptype == "second closest" || @traptype == "two closest"
    if d < closest
      i = iter
      point = #z
      point2 = z2
      closest = d
    endif
    if d < @threshold
      usesolid = false
    endif
  elseif @traptype == "farthest" || @traptype == "second farthest" || @traptype == "two farthest"
    if d > closest && d < @threshold
      i = iter
      point = #z
      point2 = z2
      closest = d
      usesolid = false
    endif
  elseif @traptype == "first"
    if d < @threshold && done == false
      i = iter
      point = #z
      point2 = z2
      closest = d
      done = true
      usesolid = false
    endif
  elseif @traptype == "last"
    if d < @threshold
      i = iter
      point = #z
      point2 = z2
      closest = d
      done = true
      usesolid = false
    endif
  elseif @traptype == "sum"
    if d < @threshold
      i = iter
      point = point + #z
      point2 = point2 + z2
      closest = closest + d
      usesolid = false
    endif
  elseif @traptype == "average"
    if d < @threshold
      i = iter
      i1 = i1 + 1
      point = point + #z
      point2 = point2 + z2
      closest = closest + d
      usesolid = false
    endif
  elseif @traptype == "product"
    if d < @threshold
      i = iter
      point = point * #z / @threshold
      point2 = point2 * z2 / @threshold
      closest = closest * d / @threshold
      usesolid = false
    endif
  elseif @traptype == "sign average"
    if d < d2
      i = i + 1
      point = point + #z
      point2 = point2 + z2
      closest = closest + 1
      usesolid = false
    else
      i = i - 1
    endif
    d2 = d
  elseif @traptype == "trap only"
    if iter == 1
      point = #z
      point2 = z2
      closest = d/@threshold
      if d < @threshold
        usesolid = false
      endif
    endif
  endif

final:
  if @solidcolor
    #solid = usesolid
  else
    #solid = false
  endif
  ; Post-processing for average/product (UF lines 1007-1029) - just
  ; what's needed to keep #index sane; second/two closest variants
  ; degrade silently to first-rank.
  if @traptype == "average"
    if i1 > 0
      point = point / i1
      point2 = point2 / i1
      closest = closest / i1
    endif
  elseif @traptype == "product"
    closest = abs(closest)
  elseif @traptype == "sign average"
    point = point / iter
    point2 = point2 / iter
    closest = closest / iter
  endif

  if @trapcolor == "distance"
    if @traptype == "first" || @traptype == "last"
      #index = closest / @threshold
    else
      #index = closest
    endif
  elseif @trapcolor == "magnitude"
    #index = cabs(point2)
  elseif @trapcolor == "real"
    #index = abs(real(point2))
  elseif @trapcolor == "imaginary"
    #index = abs(imag(point2))
  elseif @trapcolor == "angle to trap"
    d = atan2(point2)
    if d < 0
      d = d + #pi * 2
    endif
    #index = d / (#pi * 2)
  elseif @trapcolor == "angle to trap 2"
    point = point - @trapcenter
    d = atan2(point)
    if d < 0
      d = d + #pi * 2
    endif
    #index = d / (#pi * 2)
  elseif @trapcolor == "angle to origin"
    d = atan2(point)
    if d < 0
      d = d + #pi * 2
    endif
    #index = d / (#pi * 2)
  elseif @trapcolor == "angle to origin 2"
    #index = 0.02 * abs(atan(imag(point) / real(point)) * 180/#pi)
  elseif @trapcolor == "iteration"
    #index = i / #maxiter
  endif

default:
  title = "Orbit Traps"
  param trapshape
    caption = "Trap Shape"
    enum = "point" "ring" "ring 2" "egg" "hyperbola" "hypercross"            "cross" "astroid" "diamond" "rectangle" "box" "lines"            "waves" "mirrored waves" "mirrored waves 2"            "radial waves" "radial waves 2" "ring ripples"            "grid ripples" "radial ripples" "pinch" "spiral" "heart"
    default = 0
  endparam
  param diameter
    caption = "Diameter"
    default = 1.0
  endparam
  param traporder
    caption = "Order"
    default = 4.0
  endparam
  param trapfreq
    caption = "Frequency"
    default = 1.0
  endparam
  param trapcolor
    caption = "Trap Color"
    enum = "distance" "magnitude" "real" "imaginary" "angle to trap"            "angle to trap 2" "angle to origin" "angle to origin 2" "iteration"
    default = 0
  endparam
  param traptype
    caption = "Trap Type"
    enum = "closest" "farthest" "first" "last" "sum" "average" "product"            "sign average" "second closest" "second farthest" "two closest"            "two farthest" "alternating average" "alternating average 2" "inverted sum"            "exponential average" "average change" "inverted sum squared"            "trap only"
    default = 0
  endparam
  param threshold
    caption = "Threshold"
    default = 0.25
  endparam
  param trapcenter
    caption = "Trap Center"
    default = (0, 0)
  endparam
  param aspect
    caption = "Aspect"
    default = 1.0
  endparam
  param angle
    caption = "Angle"
    default = 0.0
  endparam
  param solidcolor
    caption = "Solid Color"
    default = false
  endparam
}


DirectOrbitTraps {
; Degraded stub of UltraFractal's Standard.ucl DirectOrbitTraps. UF's faithful version
; computes a color at every in-trap iteration and blends them via a
; user-selected mergemode (multiply / hardlight / etc) into an accumulator
; — that requires per-iter compose()/blend() and a callable color func,
; neither of which our translator handles yet. This stub captures the color
; from the closest-orbit point (mirrors OrbitTraps closest semantics) and
; writes it to #color, skipping inter-iter blending. Visually loses the
; smooth multi-color gradient effect; faithful only for trap-only scenes.
init:
  float d = 0.0
  float d2 = 0.0
  complex z2 = (0,0)
  int iter = 0
  float diameter2 = sqr(@diameter)
  complex r = (0,1) ^ (@angle / 90.0)
  complex r0 = (0,0)
  complex rh = (0,1) ^ (@traporder / 8)
  complex zh = (0,0)
  complex trapcenter2 = @trapcenter
  if @trapshape == "ring ripples" || @trapshape == "grid ripples" ||      @trapshape == "radial ripples"
    diameter2 = #pi / @diameter
  endif
  float closest = 1e38
  complex point = (0,0)
  complex point2 = (0,0)
  int i = 0
  bool usesolid = true
  color current = @startcolor
loop:
  iter = iter + 1
  z2 = #z
  z2 = (z2 - trapcenter2) * r
  if @aspect != 1.0
    z2 = real(z2) + flip(imag(z2) * @aspect)
  endif
  if @trapshape == "point"
    d = cabs(z2)
  elseif @trapshape == "ring"
    d = abs(cabs(z2) - @diameter)
  elseif @trapshape == "ring 2"
    d = abs(|z2| - diameter2)
  elseif @trapshape == "egg"
    d = (cabs(z2-flip(@diameter)*2) + cabs(z2)*@traporder*0.5) * 0.25
  elseif @trapshape == "hyperbola"
    d = abs(imag(z2) * real(z2) - @diameter)
  elseif @trapshape == "hypercross"
    d = abs(imag(z2) * real(z2))
  elseif @trapshape == "cross"
    d = abs(real(z2))
    d2 = abs(imag(z2))
    if d2 < d
      d = d2
    endif
  elseif @trapshape == "diamond"
    d = abs(real(z2)) + abs(imag(z2))
  elseif @trapshape == "rectangle"
    d = abs(real(z2))
    d2 = abs(imag(z2))
    if d2 > d
      d = d2
    endif
  elseif @trapshape == "box"
    d = abs(real(z2))
    d2 = abs(imag(z2))
    if d2 > d
      d = d2
    endif
    d = abs(d - @diameter)
  elseif @trapshape == "lines"
    d = abs(abs(imag(z2)) - @diameter)
  elseif @trapshape == "waves"
    d = abs(abs(imag(z2) + sin(real(z2)*@trapfreq)*@traporder*0.25) - @diameter)
  elseif @trapshape == "mirrored waves"
    d = abs(abs(imag(z2)) - @diameter + sin(real(z2)*@trapfreq)*@traporder*0.25)
  endif
  ; Track the closest-d hit and pick its colour at the end (acts as "first
  ; nearest" - real DirectOrbitTraps blends every in-trap hit instead).
  if d < closest && d < @threshold
    closest = d
    i = iter
    point = #z
    point2 = z2
    usesolid = false
  endif
final:
  if @solidcolor
    #solid = usesolid
  else
    #solid = false
  endif
  if @trapcolor == "distance"
    current = gradient(closest / @threshold)
  elseif @trapcolor == "magnitude"
    current = gradient(cabs(point2))
  elseif @trapcolor == "real"
    current = gradient(abs(real(point2)))
  elseif @trapcolor == "imaginary"
    current = gradient(abs(imag(point2)))
  elseif @trapcolor == "angle to trap"
    d = atan2(point2)
    if d < 0
      d = d + #pi * 2
    endif
    current = gradient(d / (#pi * 2))
  elseif @trapcolor == "angle to origin"
    d = atan2(point)
    if d < 0
      d = d + #pi * 2
    endif
    current = gradient(d / (#pi * 2))
  elseif @trapcolor == "angle to origin 2"
    current = gradient(0.02 * abs(atan(imag(point) / real(point)) * 180/#pi))
  elseif @trapcolor == "iteration"
    current = gradient(i / #maxiter)
  endif
  #color = current
default:
  title = "Direct Orbit Traps"
  param trapshape
    caption = "Trap Shape"
    enum = "point" "ring" "ring 2" "egg" "hyperbola" "hypercross"            "cross" "astroid" "diamond" "rectangle" "box" "lines"            "waves" "mirrored waves" "mirrored waves 2"            "radial waves" "radial waves 2" "ring ripples"            "grid ripples" "radial ripples" "pinch" "spiral" "heart"
    default = 0
  endparam
  param diameter
    caption = "Diameter"
    default = 1.0
  endparam
  param traporder
    caption = "Order"
    default = 4.0
  endparam
  param trapfreq
    caption = "Frequency"
    default = 1.0
  endparam
  param trapcolor
    caption = "Trap Color"
    enum = "distance" "magnitude" "real" "imaginary" "angle to trap"            "angle to trap 2" "angle to origin" "angle to origin 2" "iteration"
    default = 0
  endparam
  param threshold
    caption = "Threshold"
    default = 0.25
  endparam
  color param startcolor
    caption = "Base Color"
    default = rgb(0,0,0)
  endparam
  ; The merge-related params are accepted but ignored by this stub.
  param trapmergemodifier
    caption = "Additional Alpha"
    enum = "none" "distance"
    default = 0
  endparam
  param trapmergeopacity
    caption = "Trap Merge Opacity"
    default = 0.2
  endparam
  param trapmergeorder
    caption = "Trap Merge Order"
    enum = "bottom-up" "top-down"
    default = 0
  endparam
  param trapcenter
    caption = "Trap Center"
    default = (0,0)
  endparam
  param aspect
    caption = "Aspect"
    default = 1.0
  endparam
  param angle
    caption = "Angle"
    default = 0.0
  endparam
  param solidcolor
    caption = "Solid Color"
    default = false
  endparam
}


Lighting {
; UF's Lighting coloring for Slope-family fractals — treats `#z` as a
; 3D normal (with implicit z-component `-sqrt(1-|z|)`), dots it against
; a directional light, and clamps to ambient. Direct port of UF's
; Standard.ucl entry.
final:
  float vz = -sqrt(1-|#z|)
  float d2r = #pi/180
  float lx = cos((270-@angle)*d2r) * cos(@elevation*d2r)
  float ly = sin((270-@angle)*d2r) * cos(@elevation*d2r)
  float lz = -sin(@elevation*d2r)
  float l = lx*real(#z) + ly*imag(#z) + lz*vz
  if l < @ambient
    l = @ambient
  endif
  if @ambient < 0
    l = l + 1
  endif
  #index = l*0.99
default:
  title = "Lighting"
  param angle
    caption = "Light Rotation"
    default = 90.0
  endparam
  param elevation
    caption = "Light Elevation"
    default = 30.0
  endparam
  param ambient
    caption = "Ambient Light"
    default = 0.0
    min = -1.0
    max = 1.0
  endparam
}


GenericGradientColoring {
; UF's "Plug-In Coloring (Gradient)" — the skeleton entry that
; instantiates a `GradientColoring` class instance from `@coloringClass`
; and dispatches Init/Iterate/ResultIndex/IsSolid. The class plugin
; system isn't supported here; this stub falls through to the Smooth
; algorithm (UF's documented default for `coloringClass`). Renders that
; depend on a non-Smooth class will pick the wrong gradient — accept the
; degradation in exchange for the parameter file at least loading.
init:
  complex il = 1/log((2,0))
  float lp = log(log(128.0))
final:
  #index = 0.05 * real(#numiter + il*lp - il*log(log(cabs(#z))))
default:
  title = "Plug-In Coloring (Gradient)"
}


GenericDirectColoring {
; UF's "Plug-In Coloring (Direct)" — analogous to the Gradient variant
; but the class instance is expected to write `#color` directly. Same
; class-plugin limitation; we fall through to Smooth and let the
; gradient lookup paint, which is wrong (UF would emit a
; class-determined RGBA per pixel) but lets the parameter file load.
init:
  complex il = 1/log((2,0))
  float lp = log(log(128.0))
final:
  #index = 0.05 * real(#numiter + il*lp - il*log(log(cabs(#z))))
default:
  title = "Plug-In Coloring (Direct)"
}
