// crates/ml/src/cuda_pipeline/pearl_2_budget_kernel.cu
//
// SP5 Task A3 + SP7 (2026-05-03): Pearl 2 IQN budget + flatness producer.
//
// Reads Q_VAR_PER_BRANCH[b] (ISV[222..226), Pearl 1's q_branch_stats output)
// and NOISY_SIGMA[b] (ISV[210..214), Pearl 3 output). Writes:
//
//   flatness[b]   = clamp(var_q[b] / (σ[b]² + EPS_DIV), 0, 1)
//   budget_iqn[b] = BASE_IQN + (1 - flatness[b]) × (1 - BASE_IQN)
//
// SP7 takeover: BUDGET_C51_BASE, BUDGET_CQL_BASE, BUDGET_ENS_BASE are
// now driven by `loss_balance_controller_kernel.cu` (outcome-driven
// ratio controller using flatness as the target modulator). Pearl 2's
// kernel signature is reduced to drop the c51/cql/ens scratch-index
// arguments — the launcher in gpu_dqn_trainer.rs is updated atomically.
//
// 4-thread single-block (one thread per branch). No atomicAdd
// (feedback_no_atomicadd). __threadfence_system() after writes.
//
// BASE_IQN=0.11 and EPS_DIV=1e-8 are Invariant 1 structural anchors.

#include <cuda_runtime.h>

extern "C" __global__ void pearl_2_budget_update(
    const float* __restrict__ isv_signals,
    int   q_var_isv_base,         // Q_VAR_PER_BRANCH_BASE = 222
    int   sigma_isv_base,         // NOISY_SIGMA_BASE = 210
    float* __restrict__ scratch_out,
    int   iqn_idx_base,
    int   flatness_idx_base
) {
    int b = threadIdx.x;
    if (blockIdx.x != 0 || b >= 4) return;

    const float BASE_IQN = 0.11f;
    const float EPS_DIV  = 1e-8f;

    float var_q     = isv_signals[q_var_isv_base + b];
    float sigma     = isv_signals[sigma_isv_base + b];
    float sigma_sq  = sigma * sigma + EPS_DIV;
    float flatness  = fminf(1.0f, fmaxf(0.0f, var_q / sigma_sq));

    float budget_iqn = BASE_IQN + (1.0f - flatness) * (1.0f - BASE_IQN);

    scratch_out[iqn_idx_base + b]      = budget_iqn;
    scratch_out[flatness_idx_base + b] = flatness;

    __threadfence_system();
}
