spacr.qt.widgets.fractal_cascade

The fold-inversion cascade: the second spaceout pattern.

Ported from the fold-inversion fractal cascade v1.0.0 to PySide6. It is a genuinely different fractal from the orbit-fold in spacr.qt.widgets.fractal_travel, not the same one with other numbers:

  • the map is a Kaliset-like fold/inversion – reflect, swap, rotate, invert through a sphere, displace – and the colour comes from THREE ORBIT TRAPS through that set (a ring, a diagonal and a slant) rather than from an escape count;

  • the endless inward travel is two overlapping logarithmic scale windows. The outgoing scale is dropped only once the incoming one has reached the same view, so there is no reset and no microscopic number to lose precision on;

  • the CPU renderer does TRUE SPATIAL 2x2 supersampling inside one frame – four samples of the same instant, averaged. The orbit pattern’s CPU path jitters across four frames instead, which is cheaper but blends different animation times. That is the reason this one costs four times as much per pixel and renders a quarter of the pixels to pay for it.

Classes

CascadeEngine

One supersampled buffer. NO temporal history at all.

Module Contents

class spacr.qt.widgets.fractal_cascade.CascadeEngine(thread_count: int)[source]

One supersampled buffer. NO temporal history at all.

The orbit pattern keeps a four-frame ring because its antialiasing walks the sub-pixel grid over time. This one takes all four samples inside a single frame, so there is nothing to remember between frames – which is also why pausing and resuming it cannot show a seam.

Parameters:

thread_count – worker threads to render with. Clamped to at least one, so a caller that computed zero from an unavailable CPU count still renders.

samples is how many samples a side each pixel takes, set by the widget from the Supersampling setting; two is the published 2x2, and an engine nobody configured keeps it.

Create the cascade engine without allocating its buffer yet.

Parameters:

thread_count – worker threads to render with; clamped to at least one.

render(width: int, height: int, t: float, speed: float, dream: float, iterations: int, pointer_x: float = 0.0, pointer_y: float = 0.0, pull: float = 0.0, push: float = 0.0) → numpy.ndarray[source]

One frame, four spatial samples per pixel.

THE POINTER ARGUMENTS ARE NOT OPTIONAL IN PRACTICE. The worker passes all ten positionally to whichever engine the pattern chose, and this signature stopped at six – so every frame raised TypeError, the failure signal fired, no image ever arrived, and the widget painted its fallback colour. That is why the CPU cascade was black while the GPU one was fine and the CPU orbit followed the mouse.

Parameters:
  • width – frame width in pixels; the buffer is reallocated when the size changes.

  • height – frame height in pixels.

  • t – the animation time to render.

  • speed – zoom-speed multiplier.

  • dream – strength of the camera wandering and the image warp.

  • iterations – iteration budget of the fractal layer at each sample.

spacr.qt.widgets.fractal_cascade.render_into(output, t, speed, dream, iterations, pointer_x=0.0, pointer_y=0.0, pull=0.0, push=0.0, samples=2)

One complete frame, samples x samples samples per pixel.

Every sample is of the SAME instant, so the result is a true supersample rather than a blend across animation times.

When Numba is unavailable, the public render_into name instead accepts arbitrary positional and keyword arguments and raises RuntimeError.

Parameters:
  • output – preallocated uint8 RGB buffer of shape (height, width, 3), overwritten in place; its shape sets the frame size.

  • t – the animation time to render; the camera drift, palette and zoom depth are all functions of it.

  • speed – zoom-speed multiplier; the zoom depth advances as t * speed / 12.

  • dream – strength of the camera wandering and the image warp; larger values wander further.

  • iterations – iteration budget of the fractal layer at each sample.

  • samples – samples a side, from the Supersampling setting (item 531); two is the published 2x2, one takes the pixel centre.

Refuse: the cascade CPU backend needs Numba, which is absent.

RAISES RATHER THAN RETURNING AN EMPTY BUFFER. A blank frame would look like a backdrop that renders nothing, and the reader would go looking for the bug in the shader; the name of the missing dependency is the whole answer.

Parameters:
  • _args – whatever the caller would have rendered.

  • _kwargs – likewise.

Raises:

RuntimeError – always.

render_into(*_args, **_kwargs) spacr/qt/widgets/fractal_cascade.py:375 spacr/qt/widgets/fractal_cascade.py:467