// Organic family: a packed tissue of cells that drift, crowd and split.
//
// Distinct from Metaballs (separate bodies merging in empty space) and Tide
// Rings (interference across a continuous surface): this is a PARTITION. Every
// pixel belongs to exactly one cell, the boundaries are hard, and there is no
// background — which is what makes it read as tissue rather than as objects.
// Each nucleus is stamped in the track's signature form.
//
// Splits step on the phrase grid, so the tissue reorganises on the music
// instead of crawling.
//
// Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md.

export const cellDivide = {
    name: 'Cell Divide',
    family: 'organic',
    kind: 'fragment',
    consumes: ['cast', 'ink'],
    traits: ['shape', 'camera', 'style'],

    params: {
        cells:    { type: 'int',   range: [3, 16],     default: 7,    uniform: 'u_cells', bias: 'density' },
        wander:   { type: 'float', range: [0.02, 0.5], default: 0.18, uniform: 'u_wander' },
        speed:    { type: 'float', range: [0.05, 1.0], default: 0.22, uniform: 'u_speed', bias: 'motion', rate: true },
        wall:     { type: 'float', range: [0.01, 0.14],default: 0.045,uniform: 'u_wall' },
        nucleus:  { type: 'float', range: [0.0, 0.5],  default: 0.22, uniform: 'u_nucleus' },
        split:    { type: 'float', range: [0, 1],      default: 0.4,  uniform: 'u_split' },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        nucleus: { feature: 'beat',    amount: 0.2,  response: 'spike' },
    },

    shader: `
vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_speed + u_seed;
    p = sigCamera(p);

    // Splits are quantised: on a phrase line a share of the cells jump to a new
    // resting place, which reads as division rather than as constant drift.
    float era = floor(u_phrasePhase * 2.0);

    // Nearest and second-nearest seed. The DIFFERENCE between the two distances
    // is the classic membrane term: it goes to zero exactly on the boundary
    // between two cells, whatever their sizes, so walls come out even.
    float d1 = 1e3, d2 = 1e3;
    vec2 nearestAt = vec2(0.0);
    float nearestId = 0.0;
    float secondId = 0.0;

    for (int i = 0; i < 16; i++) {
        if (i >= u_cells) break;
        float fi = float(i);
        float s = u_seed + fi * 47.3;

        float jump = step(1.0 - u_split, hash12(vec2(fi, era)));
        vec2 home = (hash22(vec2(fi, floor(era * jump))) - 0.5) * 1.9;
        vec2 at = home + vec2(sin(t * 0.7 + s), cos(t * 0.55 + s * 1.6)) * u_wander;

        float d = length(p - at);
        if (d < d1) {
            d2 = d1; secondId = nearestId;
            d1 = d;
            nearestAt = at; nearestId = fi;
        } else if (d < d2) {
            d2 = d; secondId = fi;
        }
    }

    float membrane = d2 - d1;

    // Cytoplasm, tinted by which cell owns the pixel — but BLENDED with the
    // runner-up across the membrane rather than switched at it.
    //
    // Switching is the obvious way and it is not deterministic enough: exactly
    // on a tie, which seed is nearest comes down to the last bit of a distance,
    // and the two GPUs' answers disagree. The tint then jumps a whole palette
    // step and the per-scene determinism gate measured 6/255 on a repeat render
    // where the ceiling is 1. Blending makes the two answers agree in the limit,
    // and it also looks better: cell walls read as membranes rather than as
    // cuts.
    vec3 mine = palRamp(hash11(nearestId * 13.7 + u_seed) * 0.55 + 0.1);
    vec3 theirs = palRamp(hash11(secondId * 13.7 + u_seed) * 0.55 + 0.1);
    float own = smoothstep(0.0, u_wall * 2.5, membrane) * 0.5 + 0.5;
    vec3 col = mix(theirs, mine, own) * 0.35;
    col *= 0.55 + 0.45 * smoothstep(0.0, 0.35, membrane);   // darker toward the wall

    // The wall itself.
    float wall = smoothstep(u_wall * (0.5 + u_sigLine), 0.0, membrane);
    col = mix(col, pal(4), wall * 0.8);

    // Nucleus, in the track's signature form.
    if (u_nucleus > 0.01) {
        float size = u_nucleus * 0.35;
        float d = castMain((p - nearestAt) / max(size, 1e-3)) * size;
        col = mix(col, pal(1), smoothstep(0.008, -0.008, d) * 0.85);
        col += pal(2) * inkStroke(d) * (0.35 + 0.5 * 0.4);
    }

    return vec4(inkValue(col), 1.0);
}
`,
};

export default cellDivide;
