// rl_v_blend_alpha_controller.cu — Phase 4.4 ISV-adaptive V blend (2026-05-30).
//
// Drives α ∈ [0, 1] for `V_used = α × V_scalar + (1−α) × V_dq` based
// on observed V_dq vs V_scalar tracking ratio:
//
//   track_ratio = EMA(|V_dq − V_scalar|) / EMA(|V_scalar|)
//
//   if track_ratio > 1.5 × TARGET:   α ← min(α + step, 1.0)  ↑ V_scalar
//   if track_ratio < TARGET / 1.5:   α ← max(α - step, 0.0)  ↑ V_dq
//   else:                            hold α
//
// Per pearl_wiener_alpha_floor_for_nonstationary: Schulman bounded
// discrete step, no Wiener-α blending of the controller variable
// itself (α is the controlled quantity).
//
// Per pearl_first_observation_bootstrap: bootstrap α = 1.0 on
// sentinel input (ISV[alpha_slot] == 0), EMAs use first observation
// directly. After bootstrap, α never naturally returns to exactly 0
// because Schulman step (0.01) is unlikely to land on it; if it
// does, controller re-bootstraps harmlessly.
//
// Per pearl_blend_formulas_must_have_permanent_floor: dead-signal
// guard — if EMA(|V_scalar|) < adaptive floor, hold α (no V signal
// to calibrate against; the trainer hasn't seen meaningful rewards
// yet).
//
// Per feedback_cpu_is_read_only: pure device kernel; reads V_scalar,
// V_dq, ISV; emits α + EMAs to ISV. No host control.
//
// Adaptive controller floors (spec 2026-05-30-adaptive-controller-floor-design):
// the 5 design constants below become ISV-driven so they can be tuned
// at runtime without recompile. The dead-signal floor additionally
// adapts to observed |V_scalar| variance via Welford slots 626/627/628
// (Welford updates wired by the trainer pass alongside the other
// controller Welford triples).
//
// Block layout: grid=(1, 1, 1), block=(BLOCK_X=1024, 1, 1). Single
// block does parallel reduction over batch up to B=1024. Thread 0
// performs the controller update.

#define BLOCK_X 1024
// ISV-driven slots — replace prior hardcoded design constants per the
// 2026-05-30 adaptive-controller-floor spec.
#define RL_V_BLEND_DEAD_SIGNAL_FLOOR_INDEX     621
#define RL_V_BLEND_TARGET_TRACK_RATIO_INDEX    622
#define RL_V_BLEND_SCHULMAN_STEP_INDEX         623
#define RL_V_BLEND_EMA_ALPHA_INDEX             624
#define RL_V_BLEND_BOOTSTRAP_ALPHA_INDEX       625
#define RL_V_BLEND_SCALAR_VAR_COUNT_INDEX      626
#define RL_V_BLEND_SCALAR_VAR_M2_INDEX         628
// Adaptive-dead-floor scaling for the Welford std term — keeps the
// floor at least one σ_observed × ADAPTIVE_DEAD_FLOOR_STD_SCALE above
// numerical noise even when the ISV-resident absolute floor is tighter.
#define ADAPTIVE_DEAD_FLOOR_STD_SCALE          0.1f

extern "C" __global__ void rl_v_blend_alpha_controller(
    const float* __restrict__ v_scalar,        // [B]
    const float* __restrict__ v_dq,            // [B]
    float*       __restrict__ isv,             // ISV bus
    int          B,
    int          alpha_slot,                   // ISV[α]
    int          trackerr_ema_slot,            // ISV[|V_dq − V_scalar|_ema]
    int          v_scalar_mag_ema_slot         // ISV[|V_scalar|_ema]  (dead-signal floor)
) {
    const int tid = threadIdx.x;
    if (tid >= BLOCK_X) return;

    // ── Per-thread partial sums over strided batch ──
    float track_partial = 0.0f;
    float mag_partial = 0.0f;
    for (int b = tid; b < B; b += BLOCK_X) {
        const float vs = v_scalar[b];
        const float vd = v_dq[b];
        track_partial += fabsf(vd - vs);
        mag_partial += fabsf(vs);
    }

    // ── Tree-reduce in shared mem ──
    __shared__ float s_t[BLOCK_X];
    __shared__ float s_m[BLOCK_X];
    s_t[tid] = track_partial;
    s_m[tid] = mag_partial;
    __syncthreads();

    for (int stride = BLOCK_X / 2; stride > 0; stride >>= 1) {
        if (tid < stride) {
            s_t[tid] += s_t[tid + stride];
            s_m[tid] += s_m[tid + stride];
        }
        __syncthreads();
    }

    if (tid == 0) {
        const float inv_B = 1.0f / (float)B;
        const float track_mean = s_t[0] * inv_B;
        const float mag_mean   = s_m[0] * inv_B;

        // ── ISV-driven design constants (spec 2026-05-30) ──
        const float ema_alpha          = isv[RL_V_BLEND_EMA_ALPHA_INDEX];
        const float target_track_ratio = isv[RL_V_BLEND_TARGET_TRACK_RATIO_INDEX];
        const float schulman_step      = isv[RL_V_BLEND_SCHULMAN_STEP_INDEX];
        const float bootstrap_alpha    = isv[RL_V_BLEND_BOOTSTRAP_ALPHA_INDEX];

        // ── Adaptive dead-signal floor ─────────────────────────────────
        // Combines the ISV-resident absolute floor with an observed
        // |V_scalar| variance term (Welford sample variance =
        // M² / (count − 1) when count > 1). Cold-start safe: count=0
        // → var=0 → adaptive = isv[621] absolute default. Once Welford
        // accumulates observations, the floor scales with σ_observed so
        // the dead-signal gate adapts to whatever magnitude regime
        // |V_scalar| settles into post-Phase-4.5.
        const float vmag_count = isv[RL_V_BLEND_SCALAR_VAR_COUNT_INDEX];
        const float vmag_var = (vmag_count > 1.0f)
            ? isv[RL_V_BLEND_SCALAR_VAR_M2_INDEX] / (vmag_count - 1.0f)
            : 0.0f;
        const float vmag_std = sqrtf(vmag_var);
        const float dead_floor_abs = isv[RL_V_BLEND_DEAD_SIGNAL_FLOOR_INDEX];
        const float adaptive_dead_floor = fmaxf(dead_floor_abs,
                                                 vmag_std * ADAPTIVE_DEAD_FLOOR_STD_SCALE);

        // ── Bootstrap α on sentinel ──
        float alpha = isv[alpha_slot];
        if (alpha == 0.0f) {
            alpha = bootstrap_alpha;
        }

        // ── First-observation bootstrap on EMAs ──
        float prev_track_ema = isv[trackerr_ema_slot];
        float prev_mag_ema   = isv[v_scalar_mag_ema_slot];
        const float track_ema = (prev_track_ema == 0.0f)
            ? track_mean
            : (1.0f - ema_alpha) * prev_track_ema + ema_alpha * track_mean;
        const float mag_ema = (prev_mag_ema == 0.0f)
            ? mag_mean
            : (1.0f - ema_alpha) * prev_mag_ema + ema_alpha * mag_mean;

        // ── Dead-signal guard: V_scalar magnitude too small to calibrate against ──
        if (mag_ema < adaptive_dead_floor) {
            isv[trackerr_ema_slot] = track_ema;
            isv[v_scalar_mag_ema_slot] = mag_ema;
            isv[alpha_slot] = alpha;  // hold (write bootstrap if needed)
            return;
        }

        // ── Track ratio + Schulman-bounded step on α ──
        const float track_ratio = track_ema / mag_ema;
        if (track_ratio > 1.5f * target_track_ratio) {
            // V_dq diverged from V_scalar → raise α toward V_scalar
            alpha = fminf(alpha + schulman_step, 1.0f);
        } else if (track_ratio < target_track_ratio / 1.5f) {
            // V_dq tracks V_scalar well → lower α toward V_dq
            alpha = fmaxf(alpha - schulman_step, 0.0f);
        }
        // else: hold α (within band)

        isv[alpha_slot] = alpha;
        isv[trackerr_ema_slot] = track_ema;
        isv[v_scalar_mag_ema_slot] = mag_ema;
    }
}
