//! SP14 ISV slot constants.
//!
//! 13 slots for the Aux→Q Wire + Earned Gradient Flow pearl. See
//! `docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md`
//! for the full design rationale; in brief:
//!
//! - Q_DISAGREEMENT_*: K=4↔K=2 mapped per-step argmax mismatch (B.2.3)
//! - K_*_ADAPTIVE: ISV-driven sigmoid steepness from Welford variance (B.2.5)
//! - BETA_RATE_LIMITER_ADAPTIVE: ISV-driven rate-limiter β (B.2.8)
//! - AUX_DIR_ACC_VARIANCE_EMA + Q_DISAGREEMENT_VARIANCE_EMA + ALPHA_GRAD_RAW_VARIANCE_EMA:
//!   Welford variance EMAs driving the three adaptive scalars
//! - GATE1_OPEN_STATE: Schmitt-trigger persistent state (0=closed, 1=open)
//! - ALPHA_GRAD_RAW: raw gate output (HEALTH_DIAG visibility)
//! - ALPHA_GRAD_SMOOTHED: rate-limited gate consumed by backward path
//! - AUX_DIR_ACC_POST_OPEN_MIN + GRADIENT_HACK_LOCKOUT_REMAINING: anti-gradient-hacking circuit breaker
//!
//! Slots [383..396) allocated here. SP13 ended at slot 382 (HOLD_RATE_OBSERVED_EMA_INDEX=382).
//! The plan originally documented [381..394), but Phase 0 verification found SP13 closeout
//! added HOLD_RATE_TARGET_INDEX=381 and HOLD_RATE_OBSERVED_EMA_INDEX=382 after the plan was
//! written, so the range shifts by +2.
//!
//! Spec: docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md

// Q-disagreement EMAs (K=4↔K=2 mapping with Hold/Flat masked)
pub const Q_DISAGREEMENT_SHORT_EMA_INDEX:        usize = 383;
pub const Q_DISAGREEMENT_LONG_EMA_INDEX:         usize = 384;

// Adaptive sigmoid steepness (variance-driven)
pub const K_AUX_ADAPTIVE_INDEX:                  usize = 385;
pub const K_Q_ADAPTIVE_INDEX:                    usize = 386;

// Adaptive rate-limiter β (variance-driven; B.2.8)
pub const BETA_RATE_LIMITER_ADAPTIVE_INDEX:      usize = 387;

// Welford variance EMAs (drivers for adaptive k_aux, k_q, β)
pub const AUX_DIR_ACC_VARIANCE_EMA_INDEX:        usize = 388;
pub const Q_DISAGREEMENT_VARIANCE_EMA_INDEX:     usize = 389;
pub const ALPHA_GRAD_RAW_VARIANCE_EMA_INDEX:     usize = 390;

// Schmitt-trigger persistent state (0=closed, 1=open)
pub const GATE1_OPEN_STATE_INDEX:                usize = 391;

// α_grad outputs (raw + rate-limited)
pub const ALPHA_GRAD_RAW_INDEX:                  usize = 392;
pub const ALPHA_GRAD_SMOOTHED_INDEX:             usize = 393;

// Anti-gradient-hacking circuit breaker
pub const AUX_DIR_ACC_POST_OPEN_MIN_INDEX:       usize = 394;
pub const GRADIENT_HACK_LOCKOUT_REMAINING_INDEX: usize = 395;

// ── Sentinels (initial values; reset at fold boundary) ─────────────────────
pub const SENTINEL_Q_DISAGREEMENT: f32 = 0.5;  // analytic K=4↔K=2 random alignment
pub const SENTINEL_VARIANCE:       f32 = 0.0;  // → initial k = k_base, β = β_base
pub const SENTINEL_GATE1_CLOSED:   f32 = 0.0;
pub const SENTINEL_ALPHA_GRAD:     f32 = 0.0;
pub const SENTINEL_POST_OPEN_MIN:  f32 = 1.0;  // "no min observed"
pub const SENTINEL_LOCKOUT:        f32 = 0.0;

// ── Structural constants (numerical-stability anchors per Invariant 1) ──────
/// Sigmoid steepness base for Gate 1 (aux competence). At reliable signal
/// (variance ≈ 0), k_aux = K_BASE_AUX. Decays toward K_MIN under noise.
pub const K_BASE_AUX: f32 = 20.0;
/// Sigmoid steepness base for Gate 2 (Q-head disagreement).
pub const K_BASE_Q: f32 = 15.0;
/// Lower bound for adaptive k (numerical floor; prevents flat sigmoid).
pub const K_MIN: f32 = 1.0;
/// Variance reference for k_aux adaptation (1% deviation → noisy regime).
pub const VARIANCE_REF_AUX: f32 = 0.01;
/// Variance reference for k_q adaptation (5% deviation → noisy regime).
pub const VARIANCE_REF_Q: f32 = 0.05;
/// Variance reference for β adaptation (1% α_grad_raw variance → smoother).
pub const VARIANCE_REF_ALPHA: f32 = 0.01;

/// Rate-limiter β base (light smoothing; ~2-step half-life on stable signal).
pub const BETA_BASE: f32 = 0.5;
/// Rate-limiter β max (heavy smoothing under volatile α_grad_raw).
pub const BETA_MAX: f32 = 0.95;

/// Schmitt-trigger hysteresis half-band: open at target+0.03, close at target-0.03.
pub const SCHMITT_BAND: f32 = 0.03;

// SP14 L1+L2 follow-up (2026-05-05): WARMUP_STEPS_FALLBACK constant deleted
// per `feedback_isv_for_adaptive_bounds`. The previous design had
// `warmup_gate = (steps_in_fold / WARMUP_STEPS_FALLBACK).min(1.0)` multiplied
// into α_raw — a hardcoded step-counter ramp. The variance-driven k_aux/k_q
// adaptive sigmoid steepness already provides warmup behavior (high variance
// during cold-start collapses k → K_MIN → flat sigmoid → gate ≈ 0.5; once
// EMAs settle, k → K_BASE → sharp sigmoid → gates respond correctly).
// Adding a separate hardcoded warmup multiplier was double-counting.

/// Anti-gradient-hacking circuit breaker: aux_dir_acc must drop more than
/// LOCKOUT_TRIGGER_DROP below open-threshold to suspect hacking.
pub const LOCKOUT_TRIGGER_DROP: f32 = 0.05;
/// q_disagreement must rise more than LOCKOUT_TRIGGER_DIS_RISE above baseline
/// concurrently with the aux drop to confirm hacking suspicion.
pub const LOCKOUT_TRIGGER_DIS_RISE: f32 = 0.10;
/// Lockout duration (epochs).
pub const LOCKOUT_EPOCHS: f32 = 2.0;

/// Q-disagreement baseline (analytic random alignment under Hold/Flat masking).
pub const Q_DISAGREEMENT_BASELINE: f32 = 0.5;

pub const SP14_SLOT_BASE: usize = 383;
pub const SP14_SLOT_END:  usize = 396;

#[cfg(test)]
mod tests {
    use super::*;

    /// Mirrors the SP5 contract test (`all_sp5_slots_fit_within_isv_total_dim`).
    /// Catches the exact bug Smoke A2-B exposed: the SP14 implementer extended
    /// slot indices to 395 but never bumped `ISV_TOTAL_DIM`, so the EGF gate
    /// kernels read/wrote out-of-bounds memory (gate1 stuck closed, post_open_min
    /// accumulating garbage). With this test guarding the boundary, future SP
    /// authors get a hard-fail at unit-test time instead of silent OOB at
    /// runtime.
    #[test]
    fn all_sp14_slots_fit_within_isv_total_dim() {
        use crate::cuda_pipeline::gpu_dqn_trainer::ISV_TOTAL_DIM;
        assert!(
            SP14_SLOT_END <= ISV_TOTAL_DIM,
            "SP14_SLOT_END={} exceeds ISV_TOTAL_DIM={} — bus too small for SP14 EGF slots; \
             bump ISV_TOTAL_DIM in gpu_dqn_trainer.rs (and update layout_fingerprint_seed()).",
            SP14_SLOT_END, ISV_TOTAL_DIM,
        );
    }

    #[test]
    fn sp14_slot_layout_locked() {
        // Lock the slot layout against silent renumbering. Mirrors the SP5
        // assert_eq! pattern. Bumping any of these requires a coordinated
        // update to the cubin header constants in
        // `alpha_grad_compute_kernel.cu` and `gradient_hack_detect_kernel.cu`,
        // plus the layout-fingerprint entries in `gpu_dqn_trainer.rs` (and a
        // checkpoint-recompatibility decision).
        assert_eq!(SP14_SLOT_BASE, 383);
        assert_eq!(SP14_SLOT_END, 396);
        assert_eq!(Q_DISAGREEMENT_SHORT_EMA_INDEX, 383);
        assert_eq!(GATE1_OPEN_STATE_INDEX, 391);
        assert_eq!(ALPHA_GRAD_SMOOTHED_INDEX, 393);
        assert_eq!(AUX_DIR_ACC_POST_OPEN_MIN_INDEX, 394);
        assert_eq!(GRADIENT_HACK_LOCKOUT_REMAINING_INDEX, 395);
    }
}
