// Organic family: a reaction–diffusion skin. Short-range activation and
// long-range inhibition, run in the feedback buffer, so spots and stripes form
// themselves, drift, split, and settle into a living equilibrium.
//
// Ink Bleed is the other feedback organic, and it only ever spreads: ink goes
// outward, fades, and the frame tends to uniform. This tends to the opposite —
// a pattern that will not go uniform, because every blob is actively suppressing
// its neighbours. Left alone it converges to a scale and then keeps rearranging
// at that scale forever, which is the one thing a diffusion scene cannot do.
//
// A base field is always drawn, so the scene stands alone before the pattern has
// grown and survives a seek.
//
// NO DECLARED SLOW AXIS, deliberately. Phase 11 measures an axis by comparing
// ten-second averages, and this scene's own path noise is 0.10 of full range —
// the reaction converges to a different arrangement from every start, which is
// what a reaction–diffusion system is. Every candidate axis measured 0.08-0.11
// against that floor, so a declaration would be a claim the measurement cannot
// support. The arc driver still walks a guessed axis, which is the fallback
// that case exists for. What develops here over the track is the reaction
// itself, and the check has no way to see that.

export const turingBloom = {
    name: 'Turing Bloom',
    family: 'organic',
    kind: 'fragment',
    traits: ['camera', 'style'],

    params: {
        grain:   { type: 'float', range: [0.004, 0.03], default: 0.012, uniform: 'u_grainR' },
        gain:    { type: 'float', range: [0.4, 3.0],    default: 1.5,   uniform: 'u_gain', bias: 'energy' },
        sharpen: { type: 'float', range: [0.1, 1.2],    default: 0.55,  uniform: 'u_sharpen' },
        seedIn:  { type: 'float', range: [0.003, 0.04], default: 0.008, uniform: 'u_seedIn', bias: 'density' },
        settle:  { type: 'float', range: [0.95, 0.999], default: 0.985, uniform: 'u_settle' },
        fill:    { type: 'float', range: [0.15, 0.85],  default: 0.5,   uniform: 'u_fill' },
        pace:    { type: 'float', range: [0.006, 0.06], default: 0.015, uniform: 'u_pace', bias: 'motion', rate: true },
        glow:    { type: 'float', range: [0, 1.3],      default: 0.5,   uniform: 'u_glow' },
        palette: { type: 'palette', count: 5 },
    },

    reactive: {
        sharpen: { feature: 'bandLow', amount: 0.3, response: 'smooth' },
        seedIn: { feature: 'flux',     amount: 0.25, response: 'spike' },
        glow:   { feature: 'loudness', amount: 0.3,  response: 'smooth' },
    },

    shader: `
// The substrate the pattern grows on. Drawn every frame regardless of what the
// feedback buffer holds, so the scene is never waiting on it.
vec3 bedColour(vec2 p, float t) {
    // The substrate is drawn at the pattern's own scale. That is what makes the
    // reaction radius the scene's long journey and not just a detail setting:
    // walking it takes the whole image from fine speckled tissue to a coarse
    // one, feedback or no feedback.
    float k = 0.012 / clamp(u_grainR, 0.004, 0.03);
    float bed = fbm(p * 2.4 * k + vec2(t * 0.3, -t * 0.2), 4);
    // Veined tissue rather than a wash: the pattern has to look like it is
    // growing on something, and the first seconds after a cut are all substrate.
    float veins = abs(fbm(p * 3.6 * k + bed * 1.8, 3) - 0.5);
    vec3 col = mix(pal(0) * 0.1, pal(1) * 0.34, bed);
    col += pal(2) * smoothstep(0.1, 0.0, veins) * 0.28;
    return col;
}

// Ring average of the previous frame at radius r, in uv units so the pattern
// scale is the same at 720p and at 4K.
float ring(vec2 uv, float r) {
    float s = 0.0;
    // lint: fixed-cost — an eight-tap ring, not a search
    for (int i = 0; i < 8; i++) {
        float a = float(i) * 0.7853981634;
        vec3 c = prev(uv + vec2(cos(a), sin(a) * u_aspect) * r);
        s += dot(c, vec3(0.333));
    }
    return s * 0.125;
}

vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_pace + u_seed;
    p = sigCamera(p);

    vec3 col = bedColour(p, t);

    float here = dot(prev(uv), vec3(0.333));

    // Activation minus inhibition: the near ring feeds the pattern, the far ring
    // starves it. The whole of Turing's argument, in two texture rings.
    float near = ring(uv, u_grainR);
    float far  = ring(uv, u_grainR * 3.2);
    float react = (near - far) * u_gain;

    // Fresh substrate keeps being stirred in, so a seek always has something to
    // converge from. It is deliberately STILL in scene space rather than
    // scrolling: a moving seed field means the pattern that grows is whatever
    // noise happened to be passing, and the same scene converges somewhere
    // different every time it is cut to. Pinned, the skin settles into the same
    // arrangement the substrate implies, and then rearranges within it.
    float feed = (fbm(p * 6.0 * (0.012 / clamp(u_grainR, 0.004, 0.03)) + 19.0, 3) - 0.45) * u_seedIn;

    // Growth bias: whether the skin is sparse spots on bare substrate or a
    // covered surface with holes punched in it. Small per frame, but it
    // accumulates through the feedback, which is what makes it the scene's long
    // journey rather than a brightness knob.
    float a = here * u_settle + react * 0.25 + feed + (u_fill - 0.5) * 0.03;
    // Push toward the two states — hard for a track drawn with a sharp edge,
    // barely at all for a soft one, so the skin is crisp scales or wet blobs
    // depending on the track's hand rather than on this scene's taste.
    a = sat(a + (a - 0.5) * u_sharpen * (1.6 - u_sigSoft));

    vec3 skin = palRamp(0.35 + a * 0.45);
    col = mix(col, skin, sat(a * 1.6));

    // Rims: where the pattern is changing fastest, which is the edge of every
    // blob and the seam of every stripe.
    float rim = sat(abs(near - far) * 6.0);
    col += pal(4) * rim * u_glow * (0.15 + u_sigLine * 1.8);

    col *= 0.75 + 0.25 * exp(-dot(p, p) * 0.2);
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default turingBloom;
