// crates/ml/src/cuda_pipeline/sp15_isv_slots.rs
//! SP15 ISV slot constants — slot allocation map per spec §4.3.
//!
//! Pre-allocates disjoint slot ranges per phase to enable Approach B
//! parallel-dispatch sub-worktrees without index collisions. SP14 occupied
//! slots 383-395 (`ISV_TOTAL_DIM = 396` post-merge `0275a25d9`). SP15
//! starts at 397 and ends at 443 (46 slots total).

// === Phase 0.B: SP14 EGF retune anchors (4 slots) ===
pub const EGF_SCHMITT_HI_INDEX: usize = 397;
pub const EGF_SCHMITT_LO_INDEX: usize = 398;
pub const EGF_VAR_AUX_REF_INDEX: usize = 399;
pub const EGF_VAR_Q_REF_INDEX: usize = 400;

// === Phase 1.3: drawdown reporting (6 slots) ===
pub const DD_CURRENT_INDEX: usize = 401;
pub const DD_MAX_INDEX: usize = 402;
pub const DD_RECOVERY_BARS_INDEX: usize = 403;
pub const DD_PERSISTENCE_INDEX: usize = 404;
pub const CALMAR_INDEX: usize = 405;
pub const DD_PCT_INDEX: usize = 406;

// === Phase 1.2: cost kernel (2 slots) ===
pub const OFI_IMPACT_LAMBDA_INDEX: usize = 407;
pub const COST_PER_BAR_AVG_INDEX: usize = 408;

// === Phase 1.4 + Wave 3a: 8 counterfactual baseline slots (4 retired, 4 reserved) ===
//
// Wave 3a (2026-05-06): the 4 constant-policy baselines (buyhold,
// hold_only, naive_momentum, naive_reversion) had their own ISV slots
// (409, 410, 412, 416) for a single-fold-aggregate sharpe scalar. Wave 3a
// migrates those baselines to per-window output buffers (matching the
// 1.1.b sharpe_per_bar shape) — the kernels now write `[mean, std,
// raw_sharpe]` per window to a caller-provided `MappedF32Buffer`. The
// 4 ISV slot constants are retired here per `feedback_no_legacy_aliases`
// (no ghost constants left in the codebase). The slot indices 409/410/
// 412/416 themselves remain reserved (gap in the SP15 layout) per the
// layout fingerprint contract — re-allocation is left to a future SP
// to keep the fingerprint stable across this transitional release.
//
// Trunk-shared baseline slots (411, 413, 414, 415) remain allocated:
// random_dir_kelly, aux_only, mag_quarter_fixed, trail_only need
// partial-policy-forward access from the main eval pass and stay on
// the ISV-scalar path until follow-up Task 1.4.b. They sit at sentinel
// 0.0 in the meantime.
pub const BASELINE_RANDOM_DIR_KELLY_SHARPE_INDEX: usize = 411;
pub const BASELINE_AUX_ONLY_SHARPE_INDEX: usize = 413;
pub const BASELINE_MAG_QUARTER_FIXED_SHARPE_INDEX: usize = 414;
pub const BASELINE_TRAIL_ONLY_SHARPE_INDEX: usize = 415;

// === Phase 3.1: r_quality + r_discipline split (3 slots) ===
pub const ALPHA_SPLIT_INDEX: usize = 417;
pub const GRAD_NORM_QUALITY_INDEX: usize = 418;
pub const GRAD_NORM_DISCIPLINE_INDEX: usize = 419;

// === Phase 3.3: quadratic DD penalty (3 slots) ===
pub const LAMBDA_DD_INDEX: usize = 420;
pub const DD_THRESHOLD_INDEX: usize = 421;
pub const DD_PENALTY_GRAD_NORM_INDEX: usize = 422;

// === Phase 3.4: regret signal (3 slots) ===
pub const REGRET_EMA_INDEX: usize = 423;
pub const LAMBDA_REGRET_INDEX: usize = 424;
pub const REGRET_GRAD_NORM_INDEX: usize = 425;

// === Phase 3.5: confidence-aware Hold floor (4 slots) ===
pub const HOLD_FLOOR_ALPHA_INDEX: usize = 426;
pub const HOLD_FLOOR_K_INDEX: usize = 427;
pub const HOLD_FLOOR_EPS0_INDEX: usize = 428;
pub const ENTROPY_DIST_REF_INDEX: usize = 429;

// === Phase 3.5.2: DD asymmetric reward (3 slots) ===
pub const DD_ASYMMETRY_LAMBDA_INDEX: usize = 430;
pub const R_GAIN_DD_BOOST_INDEX: usize = 431;
pub const DD_DIST_VAR_INDEX: usize = 432;

// === Phase 3.5.3: cooldown gate (3 slots) ===
pub const COOLDOWN_K_THRESHOLD_INDEX: usize = 433;
pub const COOLDOWN_M_BARS_INDEX: usize = 434;
pub const COOLDOWN_BARS_REMAINING_INDEX: usize = 435;

// === Phase 3.5.4: plasticity injection (3 slots) ===
pub const PLASTICITY_FIRED_THIS_FOLD_INDEX: usize = 436;
pub const PLASTICITY_PERSISTENCE_THRESHOLD_INDEX: usize = 437;
pub const PLASTICITY_WARM_BARS_REMAINING_INDEX: usize = 438;

// === Phase 3.5.5: recovery curriculum in PER (2 slots) ===
pub const DD_TRAJECTORY_DECREASING_INDEX: usize = 439;
pub const RECOVERY_OVERSAMPLE_WEIGHT_INDEX: usize = 440;

// === Phase 3.5 deferred ISV anchors (2 slots) ===
pub const DD_TRAJECTORY_FLOOR_INDEX: usize = 441;
pub const MEDIAN_STREAK_LENGTH_INDEX: usize = 442;

// === Phase 1.3.b-followup (1 slot) ===
// Per-env DD redesign: max-aggregate of per-env DD_PERSISTENCE across all
// envs, written by `dd_state_reduce_kernel` from the per-env tile. Read by
// `plasticity_injection_kernel` so the global advantage-head reset fires
// when ANY env's persistence exceeds the threshold (one set of advantage
// weights = global firing semantics, the worst-env governs). The mean-
// aggregate of DD_PERSISTENCE remains at the existing slot 404 for
// HEALTH_DIAG diagnostic reporting (smooth across-env summary). See
// `docs/dqn-wire-up-audit.md` Phase 1.3.b-followup entry for the
// aggregation-rule rationale and the Path A → Path B migration detail.
pub const DD_PERSISTENCE_MAX_INDEX: usize = 443;

pub const SP15_SLOT_BASE: usize = 397;
pub const SP15_SLOT_END: usize = 444;
pub const SP15_SLOT_COUNT: usize = SP15_SLOT_END - SP15_SLOT_BASE;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cuda_pipeline::gpu_dqn_trainer::ISV_TOTAL_DIM;

    /// Regression test: every SP15 slot fits within ISV_TOTAL_DIM.
    /// Paired contract with the ISV_TOTAL_DIM bump in gpu_dqn_trainer.rs.
    #[test]
    fn all_sp15_slots_fit_within_isv_total_dim() {
        assert!(DD_PERSISTENCE_MAX_INDEX < ISV_TOTAL_DIM,
            "DD_PERSISTENCE_MAX_INDEX={} >= ISV_TOTAL_DIM={}; bump ISV_TOTAL_DIM",
            DD_PERSISTENCE_MAX_INDEX, ISV_TOTAL_DIM);
        assert_eq!(SP15_SLOT_END, DD_PERSISTENCE_MAX_INDEX + 1);
        assert_eq!(SP15_SLOT_COUNT, 47);
    }

    /// Layout fingerprint regression: each named slot at its allocated index.
    #[test]
    fn sp15_slot_layout_locked() {
        assert_eq!(EGF_SCHMITT_HI_INDEX, 397);
        assert_eq!(EGF_SCHMITT_LO_INDEX, 398);
        assert_eq!(EGF_VAR_AUX_REF_INDEX, 399);
        assert_eq!(EGF_VAR_Q_REF_INDEX, 400);
        assert_eq!(DD_CURRENT_INDEX, 401);
        assert_eq!(DD_PCT_INDEX, 406);
        assert_eq!(OFI_IMPACT_LAMBDA_INDEX, 407);
        // Wave 3a (2026-05-06): BASELINE_BUYHOLD_SHARPE_INDEX (409) +
        // BASELINE_HOLD_ONLY_SHARPE_INDEX (410) + BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX
        // (412) + BASELINE_NAIVE_REVERSION_SHARPE_INDEX (416) retired
        // alongside the kernel contract change to per-window output buffers
        // — slot indices remain reserved gaps for layout fingerprint stability.
        assert_eq!(BASELINE_RANDOM_DIR_KELLY_SHARPE_INDEX, 411);
        assert_eq!(BASELINE_AUX_ONLY_SHARPE_INDEX, 413);
        assert_eq!(BASELINE_MAG_QUARTER_FIXED_SHARPE_INDEX, 414);
        assert_eq!(BASELINE_TRAIL_ONLY_SHARPE_INDEX, 415);
        assert_eq!(ALPHA_SPLIT_INDEX, 417);
        assert_eq!(LAMBDA_DD_INDEX, 420);
        assert_eq!(REGRET_EMA_INDEX, 423);
        assert_eq!(HOLD_FLOOR_ALPHA_INDEX, 426);
        assert_eq!(DD_ASYMMETRY_LAMBDA_INDEX, 430);
        assert_eq!(COOLDOWN_K_THRESHOLD_INDEX, 433);
        assert_eq!(PLASTICITY_FIRED_THIS_FOLD_INDEX, 436);
        assert_eq!(PLASTICITY_WARM_BARS_REMAINING_INDEX, 438);
        assert_eq!(DD_TRAJECTORY_DECREASING_INDEX, 439);
        assert_eq!(DD_TRAJECTORY_FLOOR_INDEX, 441);
        assert_eq!(MEDIAN_STREAK_LENGTH_INDEX, 442);
        assert_eq!(DD_PERSISTENCE_MAX_INDEX, 443);
    }
}
