# Smooth Out the Bands

*Task 4 of 5 · [Escape-Time Fractals](https://gpu.rocks/learn/escape-time-fractals-0de4764c.md) · GPU.js Learn*

Look closely at task 3's exterior and you'll see hard rings: iteration counts are
integers, so neighboring pixels jump from shade 6 straight to shade 7. But the kernel knows
more than the count — it knows **how far past the escape radius** z flew on its
final step. A barely-escaped z and one that rocketed to |z| = 50 both count the same pass;
that overshoot is the missing fraction.

The classic fix is the *normalized iteration count*:
`count + 1 − log2(log2|z|)`. When z barely clears the radius the correction is
near 1, when it overshoots hugely it's near 0 — and the bands blend into a continuous ramp.
(With `|z|² = zr² + zi²` in hand, use `log2|z| = 0.5 · log2(zr² + zi²)`
and skip the square root.)

## Goal

**Goal:** return a *fractional* escape value — interior points
return exactly 100, escaped points return
`count + 1 − Math.log2(0.5 * Math.log2(zr² + zi²))`.

## Requirements

- Keep the guarded loop; after it, branch on `count < 100`
- Escaped: return the fractional escape value — the normalized iteration count from the intro
- Interior: return `100` exactly — no correction for points that never escaped

## Hint 1 — why z is still usable after the loop

The guard freezes z the moment it escapes, so after the loop `zr, zi`
hold the *first* value with `|z|² ≥ 4` — exactly the overshoot the
formula needs. Math.log2 works inside kernels on both backends.

## Hint 2 — the ending

```js
if (count < 100) {
  return count + 1
    - Math.log2(0.5 * Math.log2(zr * zr + zi * zi));
}
return 100;
```

## Same idea elsewhere

Fighting quantization with a fractional correction is a graphics evergreen:
trilinear blending between mipmap levels, `smoothstep` edges, ordered dithering.
The same normalized-iteration formula runs unchanged in a CUDA kernel or a WGSL fragment
shader — it's pure float math.

## Starter code

```js
// Counts are integers — that's why the shading shows rings.
// Return a FRACTIONAL escape value instead.
const gpu = new GPU({ mode });

const smoothField = gpu.createKernel(function (xMin, yMin, step) {
  const x = this.thread.x;
  const y = this.thread.y;
  const cr = xMin + x * step;
  const ci = yMin + y * step;
  let zr = 0;
  let zi = 0;
  let count = 0;
  for (let i = 0; i < 100; i++) {
    if (zr * zr + zi * zi < 4) {
      const zrNext = zr * zr - zi * zi + cr;
      zi = 2 * zr * zi + ci;
      zr = zrNext;
      count = count + 1;
    }
  }
  // TODO: escaped pixels (count < 100) should return
  //   count + 1 - Math.log2(0.5 * Math.log2(zr² + zi²))
  // interior pixels return 100 exactly.
  return count;
}, { output: [64, 64] });

const field = await smoothField(3, 1, 0.01);
console.log('a fractional escape value:', field[0][0]);
```

---

Interactive version: https://gpu.rocks/learn/escape-time-fractals-0de4764c/4

[Previous task](https://gpu.rocks/learn/escape-time-fractals-0de4764c/3.md) · [Next task](https://gpu.rocks/learn/escape-time-fractals-0de4764c/5.md)
