// scene.metal — fond reactif aux flux data_feeds + skeleton overlay.
//
// 9 modes visuels style demoscene 2023+ (raymarching SDF, fractales,
// parallax, palette IQ). Reactivite open-data via SceneUniforms.
//   0 storm     fbm tissu palette Kp/Bz + lightning flash
//   1 tunnel    raymarched tube avec anneaux translucents (wind, RMS)
//   2 plasma    volumetric noise palette IQ (Kp, social_rate)
//   3 kaleido   fractal KIFS 6-fold rotation 3D (flare, time)
//   4 voronoi   cellular 3D crystal sphere (lightning, RMS)
//   5 metaballs raymarched SDF metaballs colored shading (RMS, beat)
//   6 starfield galaxy spiral parallax + god rays (wind, kp)
//   7 bars      3D pillars en perspective avec depth fog (RMS+social)
//   8 hands3d   raymarching mandelbox-like + hands camera control

#include <metal_stdlib>
using namespace metal;

struct SceneUniforms {
    float time;
    float rms;
    float kp_norm;
    float netz_dev;
    float lightning_flash;
    float flare;
    float wind_norm;
    float bz_norm;
    float social_rate;
    float pose_alive;
    float pose_count;
    float width;
    float height;
    float viz_mode;
    float hand_l_x;
    float hand_l_y;
    float hand_r_x;
    float hand_r_y;
    // ---- 2026-05-14 face / hand / body3d derivatives ----
    // mouth_open      : |lip51.y - lip57.y| normalized 0..~0.15
    // eye_open_l/r    : H/W ratio of eye bbox, ~0.15 (closed) .. 0.45 (open)
    // head_tilt       : atan2(eye_R.y-eye_L.y, eye_R.x-eye_L.x) rad
    // head_yaw        : nose.y - eyes_mid.y (proxy pitch, normalized)
    // finger_pinch_l/r: simd_distance(thumb_tip[4], index_tip[8]) px-norm
    // body_x/y/z      : pelvis world meters (MediaPipe hip-relative)
    // body_height     : |hip.y - head.y| world meters
    // arm_spread      : |wristL.x - wristR.x| world meters
    // pose_velocity   : EMA(|pelvis(t) - pelvis(t-1)|), alpha=0.3
    float mouth_open;
    float eye_open_l;
    float eye_open_r;
    float head_tilt;
    float head_yaw;
    float finger_pinch_l;
    float finger_pinch_r;
    float body_x;
    float body_y;
    float body_z;
    float body_height;
    float arm_spread;
    float pose_velocity;
    float _pad0;
    float _pad1;
    float _pad2;
    float _pad3;
    float _pad4;
};

struct VsOut {
    float4 position [[position]];
    float2 uv;
};

vertex VsOut bg_vertex(uint vid [[vertex_id]]) {
    float2 p = float2((vid << 1) & 2, vid & 2);
    VsOut o;
    o.position = float4(p * 2.0 - 1.0, 0.0, 1.0);
    o.uv = p;
    return o;
}

// ===== Helpers ====================================================

float hash21(float2 p) {
    p = fract(p * float2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}
float hash31(float3 p) {
    p = fract(p * 0.1031);
    p += dot(p, p.yzx + 33.33);
    return fract((p.x + p.y) * p.z);
}
float noise2(float2 p) {
    float2 i = floor(p);
    float2 f = fract(p);
    float a = hash21(i);
    float b = hash21(i + float2(1, 0));
    float c = hash21(i + float2(0, 1));
    float d = hash21(i + float2(1, 1));
    float2 u = f * f * (3.0 - 2.0 * f);
    return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(float2 p) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 5; ++i) { v += a * noise2(p); p *= 2.13; a *= 0.5; }
    return v;
}

// Palette cosinusoidale IQ : 3 tons doux
float3 palIQ(float t, float3 a, float3 b, float3 c, float3 d) {
    return a + b * cos(6.28318 * (c * t + d));
}

// Rotations
float3 rotY(float3 p, float a) {
    float c = cos(a), s = sin(a);
    return float3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z);
}
float3 rotX(float3 p, float a) {
    float c = cos(a), s = sin(a);
    return float3(p.x, c * p.y - s * p.z, s * p.y + c * p.z);
}
float3 rotZ(float3 p, float a) {
    float c = cos(a), s = sin(a);
    return float3(c * p.x - s * p.y, s * p.x + c * p.y, p.z);
}

// SDF primitives
float sdSphere(float3 p, float r) { return length(p) - r; }
float sdBox(float3 p, float3 b) {
    float3 q = abs(p) - b;
    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
float sdTorus(float3 p, float2 t) {
    float2 q = float2(length(p.xz) - t.x, p.y);
    return length(q) - t.y;
}
float smin(float a, float b, float k) {
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    return mix(b, a, h) - k * h * (1.0 - h);
}

float vignette(float2 p) {
    return 1.0 - smoothstep(0.6, 1.5, length(p));
}

// ===== Modes =======================================================

// ---- 0 storm : tissu fbm reactif + bloom-fake ----
// Pose mods : pose_velocity boost intensity, head_tilt shifts hue.
float3 mode_storm(float2 p, constant SceneUniforms& U) {
    float storm = saturate(U.kp_norm * 1.0 + max(-U.bz_norm, 0.0) * 0.5
                           + U.pose_velocity * 2.0);
    float speed = 0.08 + U.wind_norm * 1.5 + U.pose_velocity * 3.0;
    float zoom = 1.8 - U.rms * 1.2;
    float n = fbm(p * zoom + float2(U.time * speed, U.time * speed * 0.7));
    n = pow(n, 1.2 - U.rms * 0.5);
    float netz = sin(U.time * 50.0 + U.netz_dev * 800.0) * 0.06;
    float hue_shift = U.head_tilt * 0.25;
    float3 base = palIQ(n + storm * 0.5 + hue_shift,
        float3(0.10, 0.05, 0.20),
        float3(0.40, 0.30, 0.55),
        float3(1.0,  1.0,  1.0),
        float3(0.0, 0.33, 0.67));
    float bloom = smoothstep(0.7, 1.0, n);
    float velocity_boost = 1.0 + U.pose_velocity * 1.5;
    return (base * (n * 1.4 + 0.3) + netz + U.rms * 1.2
         + bloom * 0.7) * velocity_boost
         + float3(1.0, 0.55, 0.1) * U.flare * 1.4
         + float3(U.lightning_flash * 0.7);
}

// ---- 1 tunnel : raymarched cylindrical tube avec anneaux ----
float3 mode_tunnel(float2 p, constant SceneUniforms& U) {
    // Pseudo-3D tunnel: r/theta + scrolling z
    float r = length(p);
    float a = atan2(p.y, p.x);
    float z = U.time * (1.5 + U.wind_norm * 8.0 + U.rms * 4.0);
    // Repeat depth
    float d = 1.0 / max(r, 0.04) + z;
    // anneaux + spirale
    float ring = sin(d * 4.0) * 0.5 + 0.5;
    float spiral = sin(a * (8.0 + U.kp_norm * 6.0) + d * 0.6);
    float v = ring * (0.4 + 0.6 * spiral);
    // Iris central
    v *= smoothstep(0.05, 0.20, r);
    float3 base = palIQ(d * 0.06 + U.time * 0.05,
        float3(0.15, 0.05, 0.35),
        float3(0.55, 0.25, 0.35),
        float3(1.0,  1.0,  0.8),
        float3(0.0, 0.10, 0.20));
    float3 col = base * v;
    // Chromatic aberration fake : sample displaced
    float chrom = U.lightning_flash * 0.15;
    col.r *= 1.0 + chrom; col.b *= 1.0 - chrom;
    return col + float3(1.0, 0.7, 0.3) * U.flare * 1.5
              + float3(U.lightning_flash * 0.6);
}

// ---- 2 plasma : volumetric noise palette IQ ----
// Pose mods : mouth_open modulates spatial frequency; head_yaw shifts hue.
float3 mode_plasma(float2 p, constant SceneUniforms& U) {
    float t = U.time * (0.5 + U.rms * 1.5);
    float freq_boost = 1.0 + U.mouth_open * 12.0;
    // 3 octaves de sin/cos en composition
    float v = sin(p.x * 4.0 * freq_boost + t)
            + sin(p.y * 5.0 * freq_boost - t * 1.2)
            + sin((p.x + p.y) * 3.5 + t * 0.7)
            + sin(length(p) * (8.0 + U.kp_norm * 4.0) - t * 1.8);
    v = v * 0.25 + 0.5;
    // Fake volumetric "depth" : repeat layers
    float layer2 = sin(p.x * 2.0 - t * 0.5) * sin(p.y * 2.5 + t * 0.7);
    v = mix(v, v * 0.5 + 0.5 * (layer2 + 1.0) * 0.5, 0.35);
    float hue_offset = U.head_yaw * 0.5;
    float3 col = palIQ(v + hue_offset,
        float3(0.5),
        float3(0.5),
        float3(1.0, 1.0, 1.0),
        float3(0.0, 0.33, 0.67));
    col *= 0.8 + U.kp_norm * 0.7 + U.social_rate * 0.5
         + U.mouth_open * 1.0;
    return col + float3(0.6, 0.3, 1.0) * U.lightning_flash * 0.5;
}

// ---- 3 kaleido : KIFS fractal 6-fold avec rot 3D fake ----
// Pose mods : arm_spread drives segment count (4..16).
float3 mode_kaleido(float2 p, constant SceneUniforms& U) {
    float ang = U.time * 0.15 + U.flare * 2.0;
    float c = cos(ang), s = sin(ang);
    p = float2(c * p.x - s * p.y, s * p.x + c * p.y);
    float r = length(p);
    float a = atan2(p.y, p.x);
    float seg_count = clamp(ceil(4.0 + U.arm_spread * 8.0), 3.0, 16.0);
    float seg = 6.28318 / seg_count;
    a = abs(fmod(a + seg * 0.5, seg) - seg * 0.5);
    float2 q = float2(cos(a), sin(a)) * r;
    // Iteration KIFS-like
    float scale = 1.0;
    for (int i = 0; i < 4; ++i) {
        q = abs(q) - 0.35;
        if (q.y > q.x) q = q.yx;
        q *= 1.5; scale *= 1.5;
    }
    float v = length(q) / scale;
    float n = fbm(q * 3.0 + U.time * 0.2);
    float3 col = palIQ(v + n * 0.3,
        float3(0.20, 0.10, 0.30),
        float3(0.55, 0.40, 0.50),
        float3(1.0, 1.0, 0.5),
        float3(0.0, 0.25, 0.50));
    col = col * (1.0 - exp(-v * 6.0));
    return col * (0.8 + U.rms * 1.0)
         + float3(1.0, 0.6, 0.2) * U.flare * 1.2;
}

// ---- 4 voronoi : 3D crystalline cellular ----
float3 mode_voronoi(float2 p, constant SceneUniforms& U) {
    // 3D voronoi : on echantillonne dans une grille 3D animee
    float t = U.time * (0.4 + U.rms * 1.0);
    float3 P = float3(p * 3.5, t);
    float3 ip = floor(P);
    float3 fp = fract(P);
    float d1 = 10.0, d2 = 10.0;
    for (int z = -1; z <= 1; ++z)
    for (int y = -1; y <= 1; ++y)
    for (int x = -1; x <= 1; ++x) {
        float3 g = float3(float(x), float(y), float(z));
        float3 o = float3(hash31(ip + g + 13.0),
                          hash31(ip + g + 71.0),
                          hash31(ip + g + 47.0));
        o = 0.5 + 0.5 * sin(t + 6.28 * o);
        float3 dv = g + o - fp;
        float d = dot(dv, dv);
        if (d < d1) { d2 = d1; d1 = d; }
        else if (d < d2) { d2 = d; }
    }
    d1 = sqrt(d1); d2 = sqrt(d2);
    float edge = smoothstep(0.0, 0.04, d2 - d1);  // walls between cells
    float face = smoothstep(0.0, 0.6, d1);
    float3 base = palIQ(d1,
        float3(0.05, 0.08, 0.20),
        float3(0.45, 0.35, 0.55),
        float3(1.0, 1.0, 0.6),
        float3(0.2, 0.3, 0.0));
    return base * (1.0 - face) + float3(1.0) * (1.0 - edge) * 0.5
         + U.lightning_flash * 0.8;
}

// ---- 5 metaballs : raymarched SDF ----
float metaballs_dist(float3 p, constant SceneUniforms& U) {
    float t = U.time * 0.7;
    float d = 100.0;
    for (int k = 0; k < 5; ++k) {
        float fk = float(k);
        float3 c = float3(
            sin(t * (0.6 + 0.13 * fk) + fk * 1.7) * 1.2,
            cos(t * (0.5 + 0.11 * fk) + fk * 2.1) * 1.0,
            sin(t * (0.4 + 0.09 * fk) + fk * 3.0) * 0.8
        );
        float radius = 0.45 + 0.15 * U.rms + 0.05 * sin(t + fk);
        d = smin(d, sdSphere(p - c, radius), 0.45);
    }
    return d;
}
float3 mode_metaballs(float2 p, constant SceneUniforms& U) {
    float3 ro = float3(0, 0, -3.5);
    float3 rd = normalize(float3(p, 1.5));
    float t = 0.0;
    float glow = 0.0;
    int i;
    for (i = 0; i < 64; ++i) {
        float3 pos = ro + rd * t;
        float d = metaballs_dist(pos, U);
        if (d < 0.01) break;
        glow += 0.02 / (1.0 + d * d * 4.0);
        t += d * 0.9;
        if (t > 8.0) break;
    }
    float3 col = float3(0);
    if (t < 8.0) {
        float3 pos = ro + rd * t;
        // normal via gradient
        float2 e = float2(0.001, 0);
        float3 n = normalize(float3(
            metaballs_dist(pos + e.xyy, U) - metaballs_dist(pos - e.xyy, U),
            metaballs_dist(pos + e.yxy, U) - metaballs_dist(pos - e.yxy, U),
            metaballs_dist(pos + e.yyx, U) - metaballs_dist(pos - e.yyx, U)));
        float3 lightDir = normalize(float3(0.6, 0.8, -0.5));
        float lambert = max(0.0, dot(n, lightDir));
        float fres = pow(1.0 - max(0.0, dot(n, -rd)), 2.0);
        col = palIQ(pos.x * 0.3 + pos.y * 0.2 + U.time * 0.1,
            float3(0.2, 0.0, 0.3),
            float3(0.5, 0.5, 0.4),
            float3(1.0),
            float3(0.0, 0.33, 0.67)) * lambert;
        col += float3(0.3, 0.7, 1.0) * fres * (0.7 + U.kp_norm);
    }
    col += float3(0.2, 0.6, 1.0) * glow * 1.5;
    return col + U.lightning_flash * 0.6;
}

// ---- 6 starfield : galaxy spiral + parallax ----
// Pose mods : finger_pinch L+R drives star density per layer.
float3 mode_starfield(float2 p, constant SceneUniforms& U) {
    float warp = U.time * (1.5 + U.wind_norm * 6.0);
    float pinch = saturate((U.finger_pinch_l + U.finger_pinch_r) * 2.0);
    int stars_per_layer = 30 + int(pinch * 70.0);  // 30..100
    // 3 layers of stars at different speeds
    float3 col = float3(0);
    for (int L = 0; L < 3; ++L) {
        float speed = (1.0 + float(L) * 0.5);
        float scale = 6.0 + float(L) * 4.0;
        for (int k = 0; k < 100; ++k) {
            if (k >= stars_per_layer) break;
            float fk = float(k + L * 50);
            float r0 = hash21(float2(fk, 7.0 + float(L)));
            float a0 = hash21(float2(fk, 17.0 + float(L))) * 6.28;
            // Spirale galactique
            float angle = a0 + r0 * 4.0;
            float dist = fract(r0 + warp * 0.04 * speed) * 1.6;
            float2 q = float2(cos(angle + dist * 1.5),
                              sin(angle + dist * 1.5)) * dist;
            float d = length(p - q);
            float bright = smoothstep(0.012 / speed, 0.0, d);
            col += float3(0.5 + r0 * 0.5, 0.7 - r0 * 0.3, 1.0) * bright
                 * (1.4 - dist) * (1.0 / speed);
        }
    }
    // God rays subtils depuis le centre
    float ang = atan2(p.y, p.x);
    float rays = 0.5 + 0.5 * sin(ang * 8.0 + U.time);
    col += float3(0.3, 0.4, 0.7) * rays * (1.0 - length(p)) * 0.15
         * (0.5 + U.kp_norm);
    return col + U.flare * float3(1.0, 0.5, 0.2) * 0.4;
}

// ---- 7 bars : 3D pillars en perspective ----
float3 mode_bars(float2 p, constant SceneUniforms& U) {
    // Pseudo-3D : barres "horizontales" qui s'eloignent
    int nbars = 24;
    float t = U.time * 0.4;
    float3 col = float3(0);
    // Sky gradient
    float3 sky = mix(float3(0.05, 0.0, 0.15), float3(0.25, 0.1, 0.35),
                     p.y * 0.5 + 0.5);
    col = sky;
    for (int i = 0; i < nbars; ++i) {
        float fi = float(i) / float(nbars);
        // Position en profondeur (z = 0 proche, 1 loin)
        float z = fract(fi + t * (0.15 + U.rms * 0.3));
        float perspective = 1.0 / (z + 0.1);
        float y_base = -0.6 + z * 1.2;   // ligne d'horizon
        // Hauteur barre depend du bin "i" via hash + RMS
        float h0 = hash21(float2(float(i), 0.0));
        float h = sin(t * (0.5 + h0 * 4.0) + float(i)) * 0.5 + 0.5;
        // body_height (0..~1.8 m) + eye_open avg modulate bar height
        float eyes = (U.eye_open_l + U.eye_open_r) * 0.5;
        h = h * (0.3 + U.rms * 1.5 + U.social_rate * 0.4
                 + U.body_height * 0.6 + eyes * 0.8);
        h = clamp(h, 0.02, 0.95);
        float bar_top = y_base + h * perspective * 0.3;
        // Largeur = 1 / nbars perspective
        float bx = (fi - 0.5) * perspective * 1.5;
        float bw = 0.5 / float(nbars) * perspective;
        if (abs(p.x - bx) < bw &&
            p.y > y_base && p.y < bar_top) {
            float3 c = palIQ(fi,
                float3(0.5), float3(0.5),
                float3(1.0, 1.0, 0.5),
                float3(0.0, 0.33, 0.67));
            // Fog selon z
            c *= 1.0 - z * 0.6;
            col = mix(col, c, 1.0 - z * 0.3);
        }
    }
    // Grille du sol scanline
    float floor_y = -0.6;
    if (p.y < floor_y) {
        float depth = (floor_y - p.y) * 4.0;
        float grid = step(0.95, fract(p.x * 8.0 / max(depth, 0.1)));
        grid += step(0.95, fract(depth * 4.0 + t));
        col += float3(0.2, 0.3, 0.6) * grid * 0.4;
    }
    return col + U.flare * float3(1.0, 0.5, 0.2) * 0.3;
}

// ---- 8 hands3d : voyage 3D pilote par les mains ----
float map_hands(float3 p, constant SceneUniforms& U) {
    float3 q = fmod(p + 2.0, 4.0) - 2.0;
    float d = length(q) - 0.6;
    float pulse = 0.8 + U.rms * 0.6;
    d = min(d, length(p) - pulse);
    d += sin(p.x * 2.0 + U.time) * 0.15 * U.kp_norm;
    return d;
}
float3 mode_hands3d(float2 p, constant SceneUniforms& U) {
    float hl_active = (abs(U.hand_l_x) + abs(U.hand_l_y)) > 0.01 ? 1.0 : 0.0;
    float hr_active = (abs(U.hand_r_x) + abs(U.hand_r_y)) > 0.01 ? 1.0 : 0.0;
    float3 cam_pos = float3(
        U.hand_l_x * 5.0,
        U.hand_l_y * 3.0,
        -U.time * (1.5 + U.hand_l_y * 4.0 * hl_active)
    );
    float yaw   = U.hand_r_x * 1.2 * hr_active;
    float pitch = -U.hand_r_y * 0.8 * hr_active;
    float3 rd = normalize(float3(p.x, p.y, 1.5));
    rd = rotX(rd, pitch);
    rd = rotY(rd, yaw);
    float t = 0.0, glow = 0.0;
    for (int i = 0; i < 64; ++i) {
        float3 pos = cam_pos + rd * t;
        float d = map_hands(pos, U);
        if (d < 0.005) break;
        glow += 0.02 / (1.0 + d * d * 8.0);
        t += d * 0.85;
        if (t > 30.0) break;
    }
    float3 col = float3(0);
    if (t < 30.0) {
        float3 pos = cam_pos + rd * t;
        float fog = 1.0 - saturate(t / 30.0);
        col = float3(
            0.5 + 0.5 * sin(pos.x * 0.4 + U.time),
            0.5 + 0.5 * sin(pos.y * 0.5 + U.time * 1.3),
            0.5 + 0.5 * sin(pos.z * 0.3 + U.time * 0.7)
        ) * fog;
    }
    col += float3(0.2, 0.6, 1.0) * glow * 1.5;
    col += float3(1.0, 0.5, 0.0) * U.flare * 0.8;
    return col;
}

// ---- 9 openpos : fond minimal radial pour faire ressortir le squelette ----
// Le rendu des joints + bones se fait par le skel_pipeline rendu PAR-DESSUS
// (cf renderer.py). On laisse juste un degrade radial sombre pour le contraste.
float3 mode_openpos(float2 p, constant SceneUniforms& U) {
    float r = length(p);
    // Centre legerement plus clair, bords sombres. Touche de couleur
    // chaude au centre selon rms pour reagir a la musique.
    float3 inner = float3(0.05, 0.05, 0.10) + float3(0.30, 0.12, 0.18) * U.rms;
    float3 outer = float3(0.01, 0.01, 0.02);
    float3 col = mix(inner, outer, smoothstep(0.0, 1.4, r));
    // Grille de points discrete pour donner une ref de profondeur
    float2 g = fmod(p * 12.0, 2.0) - 1.0;
    float dot_grid = exp(-dot(g, g) * 6.0) * 0.04;
    col += float3(dot_grid);
    // Pulsation legere sur le kick / drop
    col *= 1.0 + U.rms * 0.4;
    return col;
}

// ===== Fragment dispatcher =========================================

fragment float4 bg_fragment(VsOut in [[stage_in]],
                            constant SceneUniforms& U [[buffer(0)]]) {
    float2 uv = in.uv;
    float2 p = uv * 2.0 - 1.0;
    p.x *= U.width / U.height;

    int mode = int(U.viz_mode + 0.5);
    float3 color;
    if      (mode == 1) color = mode_tunnel(p, U);
    else if (mode == 2) color = mode_plasma(p, U);
    else if (mode == 3) color = mode_kaleido(p, U);
    else if (mode == 4) color = mode_voronoi(p, U);
    else if (mode == 5) color = mode_metaballs(p, U);
    else if (mode == 6) color = mode_starfield(p, U);
    else if (mode == 7) color = mode_bars(p, U);
    else if (mode == 8) color = mode_hands3d(p, U);
    else if (mode == 9) color = mode_openpos(p, U);
    else                color = mode_storm(p, U);

    // Flash global + vignette
    color += float3(U.lightning_flash * 1.2);
    color *= vignette(p);

    // Tone mapping doux (Reinhard)
    color = color / (1.0 + color);
    // Gamma
    color = pow(color, float3(0.85));

    // Alpha pour transparence quand pose active (webcam visible dessous)
    // Overlay vidéo : translucide même sans pose (la webcam doit rester
    // visible en fond). Pose active = encore plus translucide.
    float alpha = mix(0.55, 0.25, U.pose_alive);
    alpha = max(alpha, U.lightning_flash * 0.8);
    alpha = max(alpha, U.flare * 0.6);
    return float4(color, alpha);
}

// ===== Skeleton overlay ============================================

struct SkelIn {
    float3 pos      [[attribute(0)]];   // x,y dans NDC, z profondeur (~ -0.5..+0.5)
    float  conf     [[attribute(1)]];
    float  pid      [[attribute(2)]];   // person_id (0..9)
};
struct SkelOut {
    float4 position [[position]];
    float  conf;
    float  pid;
    float  depth;
};

// Projection perspective douce : eloigne avec z, garde NDC en x,y
vertex SkelOut skel_vertex(SkelIn in [[stage_in]],
                           constant SceneUniforms& U [[buffer(1)]]) {
    SkelOut o;
    float z = clamp(in.pos.z, -1.0, 1.0);
    // Perspective : plus z augmente, plus le point est loin → scale < 1
    // RMS pulse fait respirer la profondeur
    float pulse = 1.0 + U.rms * 0.25;
    float persp = 1.0 / (1.0 + z * 0.8);
    float2 xy = in.pos.xy * persp * pulse;
    o.position = float4(xy, 0.0, 1.0);
    o.conf = in.conf;
    o.pid = in.pid;
    o.depth = z;
    return o;
}

// Palette 6 couleurs par personne (turquoise, magenta, jaune, ambre, lilas, vert)
constant float3 PERSON_COLORS[6] = {
    float3(0.0, 1.0, 0.85),    // 0 turquoise
    float3(1.0, 0.3, 0.7),     // 1 magenta
    float3(1.0, 0.9, 0.2),     // 2 jaune
    float3(1.0, 0.55, 0.1),    // 3 ambre
    float3(0.7, 0.5, 1.0),     // 4 lilas
    float3(0.4, 1.0, 0.3),     // 5+ vert (mains)
};

// ===== Mesh overlay (triangles face/hand/body) =====================
// Reuse meme layout que skel : pos.xyz + conf + pid.

vertex SkelOut mesh_vertex(SkelIn in [[stage_in]],
                           constant SceneUniforms& U [[buffer(1)]]) {
    SkelOut o;
    float z = clamp(in.pos.z, -1.0, 1.0);
    float pulse = 1.0 + U.rms * 0.25;
    float persp = 1.0 / (1.0 + z * 0.8);
    float2 xy = in.pos.xy * persp * pulse;
    o.position = float4(xy, 0.0, 1.0);
    o.conf = in.conf;
    o.pid = in.pid;
    o.depth = z;
    return o;
}

fragment float4 mesh_fragment(SkelOut in [[stage_in]]) {
    int pid = int(in.pid + 0.5);
    pid = ((pid % 6) + 6) % 6;
    float3 col = PERSON_COLORS[pid];
    float c = saturate(in.conf);
    // Saturation boost : couleurs vives quand pose detectee
    col = mix(col, col * 1.6, c);
    // Fog par profondeur (proche = plus lumineux)
    float depth_fog = 1.0 - clamp(in.depth + 0.5, 0.0, 1.0) * 0.5;
    col *= depth_fog;
    // Alpha TRES VISIBLE quand confiance haute : 0.85 sur skin, 0.3 fade
    return float4(col, mix(0.3, 0.85, c));
}

fragment float4 skel_fragment(SkelOut in [[stage_in]]) {
    // Skeleton ULTRA visible quand pose detectee : couleur vive + opaque
    int pid = int(in.pid + 0.5);
    pid = ((pid % 6) + 6) % 6;   // modulo positif
    float3 col = PERSON_COLORS[pid] * 1.4;  // saturation boost
    float c = saturate(in.conf);
    // Depth fog : eclaircit ce qui est proche, eteint ce qui est loin
    float depth_fog = 1.0 - clamp(in.depth + 0.5, 0.0, 1.0) * 0.6;
    col *= depth_fog * (0.5 + 0.5 * c);
    // Alpha plein-opaque quand confiance haute (= squelette ultra net)
    return float4(col, mix(0.5, 1.0, c));
}
