// Szenen-Pipeline: die PS1-Eigenheiten aus dem Renderer-Plan, authentisch
// gerechnet statt nachgestellt:
//   - Pixel-Snap im Vertex-Shader → Vertex-Jitter
//   - RGB555-Quantisierung + 4×4-Bayer-Dither im Fragment-Shader
//   - 1-Bit-Alpha-Test (Cutouts), kein Blending
//
// UVs werden perspektivkorrekt interpoliert (@interpolate(perspective)). Das
// klassische PS1-Textur-Wobbeln (affin, @interpolate(linear)) ist mit einer
// frei beweglichen Kamera und großen Faces zu störend — den Look tragen
// Pixel-Snap und Dither.

struct Uniforms {
    mvp: mat4x4f,
    // Halbe interne Auflösung (NDC läuft -1..1, Spanne 2 → Skalierung mit
    // der halben Auflösung trifft das Pixelraster). Kommt aus dem Renderer.
    half_res: vec2f,
};
@group(0) @binding(0) var<uniform> u: Uniforms;

@group(1) @binding(0) var tex: texture_2d<f32>;
@group(1) @binding(1) var smp: sampler;

struct VsOut {
    @builtin(position) pos: vec4f,
    // perspektivkorrekt interpoliert (WGSL-Default) — kein Affin-Wobbeln.
    @location(0) @interpolate(perspective) uv: vec2f,
};

@vertex
fn vs_main(@location(0) pos: vec3f, @location(1) uv: vec2f) -> VsOut {
    var clip = u.mvp * vec4f(pos, 1.0);
    // Pixel-Snap: xy nach der Projektion aufs interne Raster runden und
    // zurück in den Clip-Raum. Nur vor der Kamera (w>0) — dahinter würde
    // die Division Unsinn liefern, das Hardware-Clipping übernimmt.
    if clip.w > 0.0 {
        let ndc = clip.xy / clip.w;
        clip = vec4f(round(ndc * u.half_res) / u.half_res * clip.w, clip.zw);
    }
    var out: VsOut;
    out.pos = clip;
    out.uv = uv;
    return out;
}

// 4×4-Bayer-Matrix (Werte 0..15) für Ordered Dithering.
fn bayer4(px: vec2u) -> f32 {
    var m = array<u32, 16>(
         0u,  8u,  2u, 10u,
        12u,  4u, 14u,  6u,
         3u, 11u,  1u,  9u,
        15u,  7u, 13u,  5u,
    );
    return f32(m[(px.y % 4u) * 4u + (px.x % 4u)]);
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4f {
    let texel = textureSample(tex, smp, in.uv);
    if texel.a < 0.5 { discard; } // 1-Bit-Alpha (Cutouts), noch ungenutzt

    // RGB555: 31 Stufen pro Kanal. Bayer-Schwelle vor dem Abrunden →
    // Verläufe zerfallen ins typische Dither-Muster.
    let t = (bayer4(vec2u(in.pos.xy)) + 0.5) / 16.0;
    let c = clamp(texel.rgb, vec3f(0.0), vec3f(1.0));
    return vec4f(floor(c * 31.0 + t) / 31.0, 1.0);
}
