// grad_h_accumulate.cu — element-wise scaled gradient accumulator
// (Phase E.2 of the integrated RL trainer).
//
// Combines per-head encoder-input gradients into the shared encoder
// grad slot via scaled +=:
//
//   grad_h_encoder[i] += lambda × grad_h_head[i]
//
// Used by IntegratedTrainer to fold the Q / π / V heads'
// `grad_h_t [B × HIDDEN_DIM]` outputs into the encoder backward
// input, on top of the BCE + aux contribution already deposited by
// the PerceptionTrainer's own grad chain.
//
// One thread per element. Each thread is the sole writer of its
// destination slot (within a single launch) — no atomicAdd per
// `feedback_no_atomicadd.md`. Multiple launches in sequence on the
// same destination are safe because each launch reads-modifies-writes
// the same slot serially via stream ordering.
//
// Constraints honoured:
//   * `feedback_no_atomicadd.md`     — sole-writer per (i)
//   * `feedback_no_nvrtc.md`         — pre-compiled cubin via build.rs
//   * `pearl_no_host_branches_in_captured_graph` — `n` is a captured
//     scalar; `lambda` is a captured scalar (the trainer reads it from
//     the host ISV mirror BEFORE the capture region).
//
// Launch: grid=(ceil(n/256), 1, 1); block=(256, 1, 1); shared=0.

extern "C" __global__ void grad_h_accumulate_scaled(
    const float* __restrict__ grad_h_head,    // [n] — input head gradient
    float        lambda,                       // scalar weight (host-passed)
    int          n,                            // total elements
    float*       __restrict__ grad_h_encoder   // [n] — encoder grad accumulator (additive)
) {
    const int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= n) return;
    grad_h_encoder[i] += lambda * grad_h_head[i];
}
