"""Primordial: nucleation of the first crystal. Seeded generative artwork.

One nucleus seeds faceted growth on a dark field. Fifteen hexagonal fronts
advance outward, each broken by a gap that rotates ring to ring, so the closed
rings unwind into a spiral. Fronts are bands of hairline strokes in a warming
orange ramp; the active front is reserved in saturated red and sits inside the
frame, with the newest reach receding as single hairlines. The surrounding
field is a fine stipple that depletes near the fronts and densifies away from
them, with weak subcritical fluctuations keeping the far field structural.

Deterministic: SEED below is the only seed tried for this piece. Requires
numpy and Pillow. Writes render-1.png beside itself. No network, no file
reads, no absolute paths.
"""
import math

import numpy as np
from PIL import Image, ImageDraw

SEED = 801
PX = 1080
GRID = 270

PALETTE = {
    "base-950": "#1C1B1A",
    "base-200": "#CECDC3",
    "orange-300": "#EC8B49",
    "orange-400": "#DA702C",
    "orange-500": "#CB6120",
    "orange-600": "#BC5215",
    "orange-700": "#9D4310",
    "orange-800": "#71320D",
    "yellow-400": "#D0A215",
    "red-500": "#C03E35",
}

NUC = (0.38, 0.58)
N_SIDES = 6
K = 15
A0 = 34.0
GROW = 1.28
ACTIVE_K = 9
RAMP = ["orange-800", "orange-700", "orange-600", "orange-500",
        "orange-400", "orange-300", "yellow-400"]


def hx(name):
    h = PALETTE[name]
    return (int(h[1:3], 16), int(h[3:5], 16), int(h[5:7], 16))


def chamfer(mask):
    INF = 1e9
    d = np.where(mask, 0.0, INF).astype(np.float64)
    h, w = d.shape
    for y in range(h):
        prow = d[y - 1] if y > 0 else None
        row = d[y]
        for x in range(w):
            v = row[x]
            if prow is not None:
                if x > 0:
                    nv = prow[x - 1] + 4
                    if nv < v:
                        v = nv
                nv = prow[x] + 3
                if nv < v:
                    v = nv
                if x < w - 1:
                    nv = prow[x + 1] + 4
                    if nv < v:
                        v = nv
            if x > 0:
                nv = row[x - 1] + 3
                if nv < v:
                    v = nv
            row[x] = v
    for y in range(h - 1, -1, -1):
        nrow = d[y + 1] if y < h - 1 else None
        row = d[y]
        for x in range(w - 1, -1, -1):
            v = row[x]
            if nrow is not None:
                if x < w - 1:
                    nv = nrow[x + 1] + 4
                    if nv < v:
                        v = nv
                nv = nrow[x] + 3
                if nv < v:
                    v = nv
                if x > 0:
                    nv = nrow[x - 1] + 4
                    if nv < v:
                        v = nv
            if x < w - 1:
                nv = row[x + 1] + 3
                if nv < v:
                    v = nv
            row[x] = v
    return d * (4.0 / 3.0)


def main():
    rng = np.random.default_rng(SEED)
    cx, cy = NUC[0] * PX, NUC[1] * PX
    n = N_SIDES
    img = Image.new("RGB", (PX, PX), hx("base-950"))
    dr = ImageDraw.Draw(img)
    mask = Image.new("L", (GRID, GRID), 0)
    dm = ImageDraw.Draw(mask)
    sc = GRID / PX

    q = 0.85 + 0.30 * rng.random(n)
    corner_adv = 1.0 + 0.03 * (rng.random(n) - 0.5)
    phi0 = rng.random() * math.pi
    apothems = [A0 * (GROW ** k) for k in range(K)]

    def poly(k, off):
        pts, ptsm = [], []
        for j in range(n):
            th = phi0 + k * 0.02 + j * 2 * math.pi / n
            ap = (apothems[k] + off) * q[j]
            r = ap / math.cos(math.pi / n) * corner_adv[j]
            x, y = cx + r * math.cos(th), cy + r * math.sin(th)
            pts.append((x, y))
            ptsm.append((x * sc, y * sc))
        return pts, ptsm

    def hue(k):
        if k == ACTIVE_K:
            return "red-500"
        if k > ACTIVE_K:
            return "orange-800"
        return RAMP[min(len(RAMP) - 1, int(k / ACTIVE_K * len(RAMP)))]

    def in_gap(th, gk):
        da = (th - gk + math.pi) % (2 * math.pi) - math.pi
        return abs(da) < 0.5

    pts, _ = poly(0, 0)
    dr.polygon(pts, fill=hx("orange-700"))

    for k in range(K):
        h = hx(hue(k))
        gk = phi0 + k * 0.02 + k * 0.45
        offs = (-3, 0, 3) if k <= ACTIVE_K else (0,)
        for off in offs:
            pts, _ = poly(k, off)
            for i in range(n):
                p0, p1 = pts[i], pts[(i + 1) % n]
                th = math.atan2((p0[1] + p1[1]) / 2 - cy,
                                (p0[0] + p1[0]) / 2 - cx)
                if in_gap(th, gk):
                    continue
                dr.line([p0, p1], fill=h, width=1)
        for off in (-3, 0, 3):
            _, ptsm = poly(k, off)
            dm.line([p for p in ptsm] + [ptsm[0]], fill=255, width=1)
        if k < K - 1:
            amid = (apothems[k] + apothems[k + 1]) / 2
            hh = hx(hue(k + 1))
            for t in range(48):
                th = phi0 + k * 0.02 + t * 2 * math.pi / 48 + rng.random() * 0.05
                j = int(((th - phi0 - k * 0.02) / (2 * math.pi) * n)) % n
                r0 = amid * q[j] / math.cos(math.pi / n)
                ln = (apothems[k + 1] - apothems[k]) * (0.25 + 0.25 * rng.random())
                x0, y0 = cx + r0 * math.cos(th), cy + r0 * math.sin(th)
                x1, y1 = cx + (r0 + ln) * math.cos(th), cy + (r0 + ln) * math.sin(th)
                if k < ACTIVE_K and not in_gap(th, gk):
                    dr.line([x0, y0, x1, y1], fill=hh, width=1)

    dmask = chamfer(np.asarray(mask) > 128) * (PX / GRID)
    step = 7
    xs = np.arange(step // 2, PX, step)
    yy, xx = np.meshgrid(xs, xs)
    jx = (rng.random(xx.shape) - 0.5) * step * 0.9
    jy = (rng.random(yy.shape) - 0.5) * step * 0.9
    px = np.clip((xx + jx).astype(int), 0, PX - 1)
    py = np.clip((yy + jy).astype(int), 0, PX - 1)
    dd = dmask[np.clip((py * sc).astype(int), 0, GRID - 1),
               np.clip((px * sc).astype(int), 0, GRID - 1)]
    sub = rng.random(px.shape)
    draw_p = np.where(dd < 28, 0.04, np.where(dd < 90, 0.35, 0.80))
    keep = rng.random(px.shape) < draw_p
    subcrit = (sub > 0.74) & (dd > 60)
    stip_c = hx("base-200")
    sub_c = hx("yellow-400")
    ang = rng.random(px.shape) * math.pi
    L = 3
    for i in range(px.shape[0]):
        for jj in range(px.shape[1]):
            if not keep[i, jj]:
                continue
            x0, y0 = px[i, jj], py[i, jj]
            a = ang[i, jj]
            x1, y1 = x0 + L * math.cos(a), y0 + L * math.sin(a)
            dr.line([x0, y0, x1, y1],
                    fill=sub_c if subcrit[i, jj] else stip_c, width=1)

    img.save("render-1.png")
    print("saved render-1.png", img.size)


if __name__ == "__main__":
    main()
