float rtao(vec3 N, vec3 P)
{
    /* constants ------------------------------------------------------ */
    const float PHI = 1.61803398875;
    const float TWO_PI = 6.28318530718;

    const int RAYS = 4;

    const float DEPTH_EPS = 0.007;
    const float SQRT3 = 1.73205080757;
    const float RAY_MIN = 0.0;
    const float RAY_MAX = PI * 0.75;

    /* tangent frame -------------------------------------------------- */
    vec3 nV = worldToView(N);
    vec3 up = (abs(nV.z) < 0.999) ? vec3(0, 0, 1) : vec3(1, 0, 0);
    vec3 T = normalize(cross(up, nV));
    vec3 B = cross(nV, T);

    float dith = blueNoise(gl_FragCoord.xy);

    float selfBias = mix(0.001, 0.01, clamp(P.z / far, 0.0, 1.0));

    float occ = 0.0; // accumulated occlusion

    /* ray loop ------------------------------------------------------- */
    for (int r = 0; r < RAYS; ++r)
    {
        /* Vogel hemisphere sample ------------------------------------ */
        float idx = float(r) + dith;
        float u = idx * (1.0 / float(RAYS));
        float phi = TWO_PI * fract(idx * PHI);
        float cosA = u;
        float sinA = sqrt(max(0.0, 1.0 - cosA * cosA));

        vec3 dir = normalize(T * (sinA * cos(phi)) + B * (sinA * sin(phi)) + nV * cosA);

        /* frustum-bound length --------------------------------------- */
        float maxLen = (dir.z > 0.0) ? (SQRT3 * far - P.z) / dir.z : (-SQRT3 * near - P.z) / dir.z;
        maxLen = abs(maxLen);

        float lenBeg = RAY_MIN;
        float lenEnd = min(RAY_MAX, maxLen);
        float rayLen = lenEnd - lenBeg;

        /* ─────── stochastic 1-tap trace ───────────────────────────── */

        /* pick ONE random point along the segment -------------------- */
        float tRay = (fract(idx * PI - dith) /* extra hash */);
        vec3 posW = P + dir * (lenBeg + tRay * rayLen);
        vec3 clip = toClipSpace3(posW);

        // Convert [0,1] clip coordinates to screen pixel coordinates
        vec2 screenPos = clip.xy * vec2(viewWidth, viewHeight);
        // Screen edge rejection: skip if outside screen bounds
        if (screenPos.x < 0.0 || screenPos.x > viewWidth || screenPos.y < 0.0 || screenPos.y > viewHeight)
            continue;

        float zs = ld(texelFetch(depthtex2, ivec2(screenPos), 0).r);
        float zr = ld(clip.z);
        float dz = zr - zs;

        float tol = DEPTH_EPS;
        float hitWeight = RAY_MAX / rayLen; // ≥1 for short rays

        if (dz > selfBias && dz <= tol)
            occ += hitWeight;
    }
    /* binary accumulation → divide by RAYS */
    float ao = clamp(1.0 - occ / float(RAYS), 0.0, 1.0);

    return ao;
}
