// Minimal family: a nearly empty volume with a shaft of light through it and a
// few motes suspended in the beam.
//
// Distinct from Firefly Drift (a swarm advected along a flow) and Slow Orb (one
// body): almost nothing moves here. The motes hold position and only breathe;
// what changes is the light. That makes this one of the very few scenes in the
// library that can hold a thirty-second intro without asking for attention.
//
// The beam lands on the track's horizon, so the room is the same room every
// other scene with a floor is standing in.

export const dustChamber = {
    name: 'Dust Chamber',
    family: 'minimal',
    kind: 'fragment',
    consumes: ['cast', 'ink'],
    traits: ['shape', 'camera', 'space', 'style'],

    params: {
        motes:    { type: 'int',   range: [6, 40],     default: 18,   uniform: 'u_motes', bias: 'density' },
        moteSize: { type: 'float', range: [0.004, 0.05], default: 0.014, uniform: 'u_moteSize' },
        beam:     { type: 'float', range: [0.1, 1.0],  default: 0.45, uniform: 'u_beam', bias: 'energy' },
        beamWidth:{ type: 'float', range: [0.15, 1.2], default: 0.5,  uniform: 'u_beamWidth' },
        sway:     { type: 'float', range: [0, 0.12],   default: 0.04, uniform: 'u_sway' },
        drift:    { type: 'float', range: [0.01, 0.3], default: 0.06, uniform: 'u_drift', bias: 'motion', rate: true },
        palette:  { type: 'palette', count: 4 },
    },

    reactive: {
        beam:     { feature: 'loudness', amount: 0.25, response: 'smooth' },
        moteSize: { feature: 'beat',     amount: 0.2,  response: 'spike' },
    },

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

    float floorY = sigHorizonY() * 0.7 - 0.6;

    // The shaft: a soft wedge widening as it falls, cut off at the floor.
    float axis = sin(u_seed * 0.7) * 0.35;
    float down = sat((1.0 - (p.y - floorY)) * 0.6);
    float halfWidth = u_beamWidth * (0.25 + down * 0.75);
    float inBeam = exp(-pow((p.x - axis) / max(halfWidth, 1e-3), 2.0) * 2.2);
    inBeam *= smoothstep(floorY - 0.05, floorY + 0.5, p.y);

    // The light is the thing that moves here, and it has to move on its own
    // clock rather than on u_drift: at a low drift the motes barely breathe and
    // the whole frame measured as static. Fixed rate, so it is not a param and
    // cannot be reacted into a phase jump.
    float breath = 0.82 + 0.18 * sin(u_time * 0.4 + u_seed);
    // Floored, because the shaft IS the scene. An intro sampling u_beam near
    // its minimum with a narrow beam produced a frame dark enough to fail the
    // live-frame gate — a dim room is the point, an empty one is a bug.
    float beam = (0.22 + u_beam * 0.78) * breath;

    // Ambient fill. It carries the whole frame wherever the shaft is not, so it
    // is what decides whether "almost black" stays on the right side of black.
    vec3 col = pal(0) * 0.11;
    col += pal(1) * inBeam * beam * 0.5;

    // The pool where the shaft meets the floor.
    float pool = exp(-abs(p.y - floorY) * 14.0)
               * exp(-pow((p.x - axis) / max(halfWidth * 1.3, 1e-3), 2.0));
    col += pal(2) * pool * beam * 0.7;

    // Motes: fixed positions, breathing brightness, only visible in the light.
    for (int i = 0; i < 40; i++) {
        if (i >= u_motes) break;
        float fi = float(i);
        float s = u_seed + fi * 53.7;

        vec2 at = vec2(
            (hash11(s) - 0.5) * 2.4 + sin(t * 0.8 + s) * u_sway,
            (hash11(s + 9.1) - 0.5) * 1.8 + cos(t * 0.6 + s * 1.3) * u_sway
        );

        float lit = exp(-pow((at.x - axis) / max(halfWidth, 1e-3), 2.0) * 2.0);
        float pulse = 0.55 + 0.45 * sin(t * 2.0 + s * 3.0);
        float m = castForm(p, at, u_moteSize * (0.6 + hash11(s + 3.3)));
        col += pal(3) * m * lit * pulse * (0.5 + beam);
    }

    col = sigAir(col, p, smoothstep(0.0, 1.5, length(p)));
    return vec4(inkValue(col), 1.0);
}
`,
};

export default dustChamber;
