// rl_signal_variance_update.cu — Welford online variance for controller inputs (2026-05-30).
//
// Per `feedback_adaptive_not_tuned`, `feedback_isv_for_adaptive_bounds`,
// `pearl_controller_anchors_isv_driven`: every adaptive controller's noise
// floor / target / threshold must derive from observed signal statistics,
// not from hardcoded constants. This kernel maintains a running mean and
// M² (sum of squared deviations from mean) for each controller-input EMA,
// using Welford's online algorithm for numerical stability with float32.
//
// Welford recurrence (numerically stable, single-pass):
//   n_new   = n_prev + 1
//   delta   = x - mean_prev
//   mean    = mean_prev + delta / n_new
//   delta2  = x - mean             // recomputed against NEW mean
//   m2      = m2_prev + delta * delta2
//
// Sample variance = m2 / (n - 1)   when n > 1; undefined when n ≤ 1.
//
// Per `pearl_first_observation_bootstrap`: skip the update when input ==
// sentinel 0.0. The first non-zero observation initializes mean directly
// (delta against mean_prev=0 produces mean = x for n=1; delta against the
// new mean is 0; m2 stays 0; subsequent observations build real variance).
//
// Per `feedback_cpu_is_read_only`: pure device kernel. ISV is the contract
// surface; no host computation, no host control. Caller threads ISV bus
// pointer + four slot indices (input + count/mean/M² triple). Caller is
// responsible for launching once per controller input per step (usually
// via fused-kernel orchestration).
//
// Per `feedback_no_atomicadd`, `feedback_nvidia_grade_perf_for_kernels`:
// single-thread block. Each Welford triple is owned by exactly one input
// slot, accessed sequentially across steps — no concurrent writers, no
// reduction, no atomic.
//
// Launch config:
//   grid  = (1, 1, 1)
//   block = (1, 1, 1)
//   smem  = 0
//   stream = main RL stream (sequenced after EMA producers and before
//            controllers consume the variance)

extern "C" __global__ void rl_signal_variance_update(
    float* __restrict__ isv,
    int input_slot,
    int count_slot,
    int mean_slot,
    int m2_slot
) {
    // Single-thread guard. Block shape is (1,1,1) by contract but defend
    // against a mis-configured launcher.
    if (threadIdx.x != 0 || threadIdx.y != 0 || threadIdx.z != 0) return;
    if (blockIdx.x  != 0 || blockIdx.y  != 0 || blockIdx.z  != 0) return;

    // Sentinel-zero skip per pearl_first_observation_bootstrap.
    // Producers (EMA kernels) hold their slot at 0.0 until the first
    // real observation. Welford should NOT count those zero reads as
    // genuine observations of "the signal is zero".
    const float x = isv[input_slot];
    if (x == 0.0f) return;

    // Welford online update.
    const float n_prev    = isv[count_slot];
    const float n_new     = n_prev + 1.0f;
    const float mean_prev = isv[mean_slot];
    const float delta     = x - mean_prev;
    const float mean_new  = mean_prev + delta / n_new;
    const float delta2    = x - mean_new;   // against new mean — load-bearing for stability
    const float m2_new    = isv[m2_slot] + delta * delta2;

    isv[count_slot] = n_new;
    isv[mean_slot]  = mean_new;
    isv[m2_slot]    = m2_new;
}
