/* aux_heads_loss_ema — GPU-driven step observation for the two aux-head
 * loss scalars, written into producer_step_scratch_buf[scratch_base..+2)
 * for Pearls A+D consumption. SP4 Layer A Task A13.1 retrofit (2026-05-01).
 *
 * Plan 4 Task 6 Commit A. Producer-only kernel — HEALTH_DIAG emission +
 * aux-loss attribution monitor read ISV[AUX_NEXT_BAR_MSE_EMA_INDEX] /
 * ISV[AUX_REGIME_CE_EMA_INDEX]; only the EMA mechanism behind the slots
 * changes here.
 *
 * Mirrors `h_s2_rms_ema_kernel.cu`'s retrofit shape — single thread,
 * single block, cold-path-cadence (one launch per training step alongside
 * the other Plan 4 ISV producers in `training_loop.rs`).
 *
 * The two scalar input buffers are produced by `aux_next_bar_loss_reduce`
 * and `aux_regime_loss_reduce` (in `aux_heads_kernel.cu`); they are
 * always already a scalar mean — this kernel forwards each scalar to its
 * scratch slot. The GPU `apply_pearls_ad_kernel` (2026-05-01) then maps
 * each scratch slot to its ISV bound slot on the same stream, no host
 * sync. **α dropped per SP4 — Pearls A+D adapt α from per-slot
 * signal-vs-noise variance.**
 *
 * GPU-drives-CPU-reads (spec §4.C.6) and pearl_cold_path_no_exception_to
 * _gpu_drives.md / feedback_no_cpu_compute_strict.md compliant.
 * No atomicAdd (per feedback_no_atomicadd.md).
 *
 * SP13 B1.0 (2026-05-05): the sibling `aux_label_scale_ema_update` kernel
 * (formerly in this same file, slot 117 producer) was retired together
 * with the ISV-driven label-scale division in `aux_next_bar_loss_reduce`
 * / `aux_next_bar_backward`. Labels are already z-normalised at the data
 * layer (`label_scale_ema ≈ 1.0` empirically), so the divisor became a
 * no-op. B1.0 is a bridge — B1.1 replaces the entire MSE chain with CE.
 */

extern "C" __global__ void aux_heads_loss_ema_update(
    const float* __restrict__ next_bar_loss_scalar,    /* [1] from aux_next_bar_loss_reduce */
    const float* __restrict__ regime_loss_scalar,      /* [1] from aux_regime_loss_reduce  */
    float*       __restrict__ scratch_buf,             /* producer_step_scratch_buf [SP4_PRODUCER_COUNT] */
    int          scratch_idx_next_bar,                 /* slot 41 — next-bar loss step obs */
    int          scratch_idx_regime                    /* slot 42 — regime loss step obs */
) {
    /* Single-block, single-thread contract. */
    if (blockIdx.x != 0) return;
    if (threadIdx.x != 0) return;

    scratch_buf[scratch_idx_next_bar] = next_bar_loss_scalar[0];
    scratch_buf[scratch_idx_regime]   = regime_loss_scalar[0];
    __threadfence_system();   /* PCIe-visible writes for mapped pinned host_ptr */
}
