// crates/ml/src/cuda_pipeline/bw_d_h_s2_p99_kernel.cu
//
// SP4 Layer C #260 (2026-05-01) — bw_d_h_s2_p99 producer for
// ISV[BW_D_H_S2_BOUND_INDEX=171].
//
// Single-block, 256-thread kernel that `#include "sp4_histogram_p99.cuh"`
// directly, reads `bw_d_h_s2 [B × SH2]` (the IQN trunk-backward d_h_s2 buffer
// post-DtoD copy from `iqn_d_h_s2_ptr` and post slot-35 NaN check), computes
// p99(|bw_d_h_s2|) via `sp4_histogram_p99<256>`, and writes the per-step
// observation to `producer_step_scratch_buf[scratch_idx]` with
// `__threadfence_system()` so the chained `apply_pearls_ad_kernel` reads the
// fresh value.
//
// Migrated from SP1-Phase-C hardcoded formula
// `1e6 × ISV[H_S2_RMS_EMA_INDEX].max(1.0)` per
// `feedback_isv_for_adaptive_bounds`. The hardcoded multiplier was the last
// remaining `1e6 × isv` constant in the cuBLAS backward-grad sanitiser path
// after Layer A/B closed out the rest of SP4.
//
// The host launcher (`GpuDqnTrainer::launch_sp4_bw_d_h_s2_p99`) follows up
// with the GPU `apply_pearls_ad_kernel` on the same stream which writes the
// new x_mean to ISV[BW_D_H_S2_BOUND_INDEX] + Wiener state at the slot's
// scratch index plus a wiener offset of
// `(BW_D_H_S2_BOUND_INDEX - SP4_SLOT_BASE) * 3` floats. Graph-capture-
// compatible (no host sync; same-stream ordering with the producer kernel).
//
// Mirrors `h_s2_p99_kernel.cu` exactly — single-buffer histogram p99
// producer; the only difference is the input pointer (`bw_d_h_s2` vs
// `save_h_s2`) and the consumer ISV slot/scratch slot.

#include "sp4_histogram_p99.cuh"

extern "C" __global__ void bw_d_h_s2_p99_update(
    const float* __restrict__ buf,
    int          count,
    float*       __restrict__ scratch_buf,
    int          scratch_idx
) {
    if (blockIdx.x != 0) return;
    float p99 = sp4_histogram_p99<256>(buf, count);
    if (threadIdx.x == 0) {
        scratch_buf[scratch_idx] = p99;
        __threadfence_system();   // make host-mapped write visible
    }
}
