// rl_gate_lr_multiplier_controller.cu — adaptive gate-LR multiplier (B1.3).
//
// Spec: docs/superpowers/specs/2026-06-02-multi-head-policy-with-r-multiple.md
//       Phase 2A-D fix B1.3 dispatch (2026-06-03).
//
// Reads (from ISV):
//   RL_POLICY_GATE_ENTROPY_MEAN_INDEX (781)             — produced per-step
//                                                          by `multi_head_policy_aggregate_diag.cu`
//                                                          (Phase 2A-D); mean over batches
//                                                          of `−Σ_k p[b,k]·log p[b,k]`.
//   RL_POLICY_GATE_ENTROPY_EMA_INDEX (791)              — EMA state; this kernel
//                                                          owns it.
//   RL_POLICY_GATE_CONTROLLER_BOOTSTRAP_DONE_INDEX (792) — monotonic 0→1 latch.
//   RL_POLICY_GATE_LR_MULTIPLIER_INDEX (790)             — output, also the
//                                                          previous-step value.
//   RL_POLICY_NUM_HEADS_INDEX (761)                      — K (active head count),
//                                                          drives regime-adaptive
//                                                          entropy thresholds.
//
// Writes (to ISV):
//   slot 791 — updated EMA
//   slot 792 — bootstrap-done flag (latched to 1.0 on first observation)
//   slot 790 — updated multiplier (clamped to [MIN_FLOOR, MAX_CEIL])
//
// Control law (per `pearl_dead_signal_resurrection_discipline`):
//   • EMA Wiener-α = 0.02 (~50-step horizon) — smooths per-step noise.
//   • Escalate +0.3 %/step (compounds to ≈+50 % over 137 steps) when
//     `entropy_ema > 0.85·log(K)` — gate is "under-learning" / stuck
//     near uniform.
//   • Decay −5 %/step (emergency back-off) when
//     `entropy_ema < 0.20·log(K)` — gate is at risk of collapse to
//     single-head routing.
//   • Healthy band [0.20·log(K), 0.85·log(K)]: leave multiplier alone
//     (current value is working — no need to hunt).
//   • Clamp to [1.0, 50.0] per `pearl_bootstrap_must_respect_clamp_range`
//     so the bootstrap 5.0 lies strictly inside the range.
//
// Per `pearl_bootstrap_must_respect_clamp_range`: bootstrap 5.0 ∈ [1.0,
// 50.0]; controller never snaps to a clamp boundary on its first signal.
//
// Per `pearl_welford_trade_count_is_step_not_trade`: the first-observation
// EMA initialisation is gated by a dedicated `bootstrap_done` flag at
// slot 792; the controller cannot rely on `prev == bootstrap` because 5.0
// is also a valid steady-state value the controller may visit after
// escalating from a lower point.
//
// Per `feedback_no_atomicadd`: single-thread launch (1×1×1), no atomics.
// Per `feedback_cpu_is_read_only`: pure device-side; reads + writes are
// all ISV slots.
// Per `feedback_no_nvrtc`: pre-compiled cubin via build.rs.

#include <stdint.h>
#include <math_constants.h>

// ISV slot indices (must match crates/ml-alpha/src/rl/isv_slots.rs).
#define RL_POLICY_NUM_HEADS_INDEX                           761
#define RL_POLICY_GATE_ENTROPY_MEAN_INDEX                   781
#define RL_POLICY_GATE_LR_MULTIPLIER_INDEX                  790
#define RL_POLICY_GATE_ENTROPY_EMA_INDEX                    791
#define RL_POLICY_GATE_CONTROLLER_BOOTSTRAP_DONE_INDEX      792

// Tuning constants (B1.3 design — anchored on the B1.2 dose-response).
// All values are dimensionless ratios or rate scalars.
#define GATE_LR_CTRL_EMA_ALPHA       0.02f   // Wiener-α — ~50-step horizon
#define GATE_LR_CTRL_OVER_FRAC       0.85f   // multiplier of log(K) — under-learning threshold
#define GATE_LR_CTRL_COLLAPSE_FRAC   0.20f   // multiplier of log(K) — collapse threshold
#define GATE_LR_CTRL_ESCALATE_RATE   1.003f  // +0.3 %/step (≈+50 % in 137 steps)
#define GATE_LR_CTRL_DECAY_RATE      0.95f   // −5 %/step (emergency back-off)
#define GATE_LR_CTRL_MIN_FLOOR       1.0f    // never below 1× (effectively off)
#define GATE_LR_CTRL_MAX_CEIL        50.0f   // saturation cap (B1.2 plateau ≈20×)

extern "C" __global__ void rl_gate_lr_multiplier_controller(float* isv) {
    // Single-thread kernel — launched (1,1,1)/(1,1,1).
    if (threadIdx.x != 0 || blockIdx.x != 0) return;

    const float entropy_now = isv[RL_POLICY_GATE_ENTROPY_MEAN_INDEX];
    const float boot_done   = isv[RL_POLICY_GATE_CONTROLLER_BOOTSTRAP_DONE_INDEX];
    const float k_active    = isv[RL_POLICY_NUM_HEADS_INDEX];

    // ── EMA update (first-observation bootstrap pattern) ─────────────
    float entropy_ema;
    if (boot_done < 0.5f) {
        // First call: replace EMA with current observation; latch flag.
        // Necessary because slot 791 sentinel-zeroes at trainer init and
        // blending zero with the first real measurement (~log(K) ≈ 1.10
        // for K=3) would lag the controller by ~50 steps before it ever
        // sees a representative value.
        entropy_ema = entropy_now;
        isv[RL_POLICY_GATE_CONTROLLER_BOOTSTRAP_DONE_INDEX] = 1.0f;
    } else {
        entropy_ema = (1.0f - GATE_LR_CTRL_EMA_ALPHA) * isv[RL_POLICY_GATE_ENTROPY_EMA_INDEX]
                    + GATE_LR_CTRL_EMA_ALPHA * entropy_now;
    }
    isv[RL_POLICY_GATE_ENTROPY_EMA_INDEX] = entropy_ema;

    // ── Regime-adaptive thresholds (scale with K) ────────────────────
    // K ≥ 1 by construction (multi-head policy bootstraps NUM_HEADS to 3
    // or higher in the same flag-on block); guard with fmaxf so logf is
    // well-defined even if a future caller writes K=0 (would degenerate
    // to single-head and the controller becomes a no-op via the healthy
    // band → multiplier left alone, which is the right behaviour).
    const float log_k = logf(fmaxf(k_active, 1.0f));
    const float over_threshold     = GATE_LR_CTRL_OVER_FRAC * log_k;
    const float collapse_threshold = GATE_LR_CTRL_COLLAPSE_FRAC * log_k;

    // ── Multiplier update ─────────────────────────────────────────────
    float multiplier = isv[RL_POLICY_GATE_LR_MULTIPLIER_INDEX];
    if (entropy_ema > over_threshold) {
        // Under-learning: slowly compound up.
        multiplier *= GATE_LR_CTRL_ESCALATE_RATE;
    } else if (entropy_ema < collapse_threshold) {
        // Collapse risk: fast back-off.
        multiplier *= GATE_LR_CTRL_DECAY_RATE;
    }
    // Healthy band — leave multiplier alone.

    // Clamp; bootstrap 5.0 sits strictly inside [1.0, 50.0].
    multiplier = fmaxf(GATE_LR_CTRL_MIN_FLOOR,
                       fminf(multiplier, GATE_LR_CTRL_MAX_CEIL));
    isv[RL_POLICY_GATE_LR_MULTIPLIER_INDEX] = multiplier;
}
