def g54_diffuse(rng):
"""Diffuse — an error-diffusion quantiser; each cell's index is carried forward, not rounded in place.
A continuous field is sampled once per cell. Instead of snapping each sample
to its nearest ramp step, the leftover quantisation error is pushed into the
cells ahead of it (a Floyd-Steinberg kernel, serpentine so it alternates
direction row to row), so the field's true average survives as a diffused
texture rather than a set of hard, banded steps.
"""
N = rng.choice([20, 25, 30, 40, 50])
steps = rng.randint(6, 12)
palette = rng.choice(PALETTE_NAMES)
c = Canvas(N, palette, steps, "diffuse", "mosaic", tiles=False)
field = Field(rng, warp=rng.uniform(0.0, 0.35), steps=steps)
field.calibrate(N)
levels = steps - 1
vals = [[field.raw((i + 0.5) / N, (j + 0.5) / N) * levels for i in range(N)]
for j in range(N)]
state = [[0] * N for _ in range(N)]
for j in range(N):
left_to_right = (j % 2 == 0)
cols = range(N) if left_to_right else range(N - 1, -1, -1)
ahead = 1 if left_to_right else -1
for i in cols:
v = vals[j][i]
k = max(0, min(levels, int(round(v))))
state[j][i] = k
err = v - k
ni = i + ahead
if 0 <= ni < N:
vals[j][ni] += err * (7 / 16)
if j + 1 < N:
bi = i - ahead
if 0 <= bi < N:
vals[j + 1][bi] += err * (3 / 16)
vals[j + 1][i] += err * (5 / 16)
if 0 <= ni < N:
vals[j + 1][ni] += err * (1 / 16)
c.ground(lambda i, j: state[j][i])
return c