// rl_fused_controllers.cu — single-kernel fusion of 13 RL ISV controllers.
//
// Eliminates 12 kernel-launch overheads (~55-100μs/step) by running all
// controllers sequentially in one thread. Each controller's logic is
// MIRRORED from its standalone .cu file (post-2026-05-30 adaptive
// controller-floor + risk-management refactors): adaptive noise floor
// derived from Welford variance, asymmetric Schulman patience counters,
// ISV-driven clamp bounds + Wiener-α floor. Per `feedback_no_partial_refactor`
// and the "fused kernel must be semantically equivalent to running all
// individual controllers in sequence" contract, every branch below matches
// its standalone counterpart's ISV reads, math, and counter semantics.
//
// Controllers fused (in execution order):
//   1. rl_gamma_controller           ISV[400] ← ISV[input_slots[0]]
//   2. rl_target_tau_controller      ISV[401] ← ISV[input_slots[1]]
//   3. rl_ppo_clip_controller        ISV[402] ← ISV[input_slots[2]]
//   4. rl_entropy_coef_controller    ISV[403] ← ISV[input_slots[3]]
//   5. rl_rollout_steps_controller   ISV[404] ← ISV[input_slots[4]]  (caller passes ADV_VAR_PRE_NORM=612)
//   6. rl_per_alpha_controller       ISV[405] ← ISV[input_slots[5]]
//   7. rl_reward_scale_controller    ISV[406] ← ISV[input_slots[6]]
//   8. rl_ppo_ratio_clamp_controller ISV[440] ← ISV[402] (reads ε just written)
//   9. rl_gate_threshold_controller  ISV[512,516,517] ← dones[B]
//  10. rl_q_distill_lambda_controller ISV[486] ← ISV[488]
//  11. rl_iqn_action_tau_controller   ISV[671] ← ISV[662]  (Layer 2, spec 2026-05-30-adaptive-risk-management)
//  12. rl_inventory_beta_controller   ISV[674] ← ISV[675,614]  (Layer 3)
//  13. rl_kelly_fraction_controller   ISV[676] ← ISV[677,678,679,680,681,660]  (Layer 4)
//
// Per `feedback_no_atomicadd`: single thread, no atomics.
// Per `feedback_cpu_is_read_only`: all state in ISV, no host roundtrip.

#include <stdint.h>

// ═══════════════════════════════════════════════════════════════════════
// ISV slot indices — mirrored from individual controller headers.
// All MIN/MAX/threshold/rate constants are ISV-resident per the 2026-05-30
// adaptive controller-floor design. The few remaining `#define`s below are
// architectural exemptions (physics/math constants, structural safeguards).
// ═══════════════════════════════════════════════════════════════════════

// --- Output slots (controller emits) ---
#define RL_GAMMA_INDEX                     400
#define RL_TARGET_TAU_INDEX                401
#define RL_PPO_CLIP_INDEX                  402
#define RL_ENTROPY_COEF_INDEX              403
#define RL_N_ROLLOUT_STEPS_INDEX           404
#define RL_PER_ALPHA_INDEX                 405
#define RL_REWARD_SCALE_INDEX              406
#define RL_PPO_RATIO_CLAMP_MAX_INDEX       440
#define RL_Q_DISTILL_LAMBDA_INDEX          486

// --- 1. Gamma controller (Special G) ---
// γ MIN now adaptive via RL_GAMMA_MIN_ADAPTIVE_INDEX (slot 613) derived
// from the Welford mean of trade duration. γ MAX is ISV-resident.
#define RL_GAMMA_MAX_INDEX                 649
#define RL_GAMMA_MIN_ADAPTIVE_INDEX        613
#define RL_TRADE_DUR_VAR_COUNT_INDEX       608
// Reward-floor fix (2026-05-30 follow-up): track REAL cumulative closed
// trades via dones-sum accumulation. The Welford trade-duration counter
// increments per closed-trade STEP, not per closed trade — at b=1024 with
// ~7 dones/step this released the floor after only ~700 actual trades.
#define RL_CUMULATIVE_DONES_INDEX          660
#define RL_MIN_TRADES_FOR_RELEASE_INDEX    661
// Monotonic 0→1 latch (2026-05-31 eval-boundary addendum). Set ONCE when
// cumulative_dones first crosses min_trades; persists across reset_session_state
// (NEVER reset). Replaces the prior `(cumulative_dones < min_trades)` test in
// reward_scale's boot_floor gate so the floor doesn't re-fire at fold boundaries
// when cumulative_dones is reset for Kelly's predictive-warmup purpose.
#define RL_REWARD_SCALE_CONTROLLER_WARMED_FLAG_INDEX  716
#define RL_TRADE_DUR_VAR_MEAN_INDEX        609
#define RL_MEAN_TRADE_DURATION_EMA_INDEX   417
#define GAMMA_MIN_ABSOLUTE                 0.9f      // architectural: don't degenerate to bandit
#define HORIZON_MULTIPLIER                 2.0f      // architectural: effective horizon = 2× transaction horizon
#define WELFORD_WARMUP_OBS                 100.0f    // architectural: confidence threshold, not magnitude calibration
#define HORIZON_FLOOR                      10.0f     // architectural: `1/d` numerical-stability floor

// --- 2. Target tau controller ---
#define RL_TARGET_TAU_MIN_INDEX            642
#define RL_TARGET_TAU_MAX_INDEX            573
#define RL_TAU_BOOTSTRAP_INDEX             473
#define RL_DIV_TARGET_INDEX                457
#define RL_Q_DIV_VAR_COUNT_INDEX           592
#define RL_Q_DIV_VAR_M2_INDEX              594
#define RL_Q_DIV_BELOW_COUNT_INDEX         595

// --- 3. PPO clip controller (canonical pattern) ---
#define RL_PPO_CLIP_EPS_MIN_INDEX          640
#define RL_PPO_CLIP_EPS_MAX_INDEX          641
#define RL_EPS_BOOTSTRAP_INDEX             474
#define RL_KL_TARGET_INDEX                 454
#define RL_KL_PI_VAR_COUNT_INDEX           588
#define RL_KL_PI_VAR_M2_INDEX              590
#define RL_KL_PI_BELOW_COUNT_INDEX         591

// --- 4. Entropy coef controller ---
#define N_ACTIONS                          11        // architectural: matches Q head / action space dim
#define RL_ENTROPY_COEF_MIN_INDEX          645
#define RL_ENTROPY_COEF_MAX_INDEX          646
#define RL_ENTROPY_TARGET_FRAC_INDEX       458
#define RL_ENTROPY_OBS_VAR_COUNT_INDEX     600
#define RL_ENTROPY_OBS_VAR_M2_INDEX        602
#define RL_ENTROPY_OBS_BELOW_COUNT_INDEX   603

// --- 5. Rollout steps controller ---
// Input slot is now RL_ADV_VAR_PRE_NORM_INDEX=612 (caller-supplied via
// input_slots[4]) since post-Phase-4.5 advantage variance is definitionally
// near-zero and useless. See `rl_rollout_steps_controller.cu` comments.
#define RL_ROLLOUT_MIN_INDEX               643
#define RL_ROLLOUT_MAX_INDEX               644
#define RL_ROLLOUT_BOOTSTRAP_INDEX         475
#define RL_ADV_VAR_RATIO_TARGET_INDEX      449
#define RL_ADV_VAR_VAR_COUNT_INDEX         596
#define RL_ADV_VAR_VAR_M2_INDEX            598
#define RL_ADV_VAR_BELOW_COUNT_INDEX       599

// --- 6. PER alpha controller ---
#define RL_PER_ALPHA_MIN_INDEX             647
#define RL_PER_ALPHA_MAX_INDEX             648
#define RL_KURT_GAUSSIAN_INDEX             471
#define RL_KURT_NOISE_FLOOR_INDEX          472
#define RL_KURT_LIFT_SCALE_INDEX           459
#define RL_TD_KURT_VAR_COUNT_INDEX         604
#define RL_TD_KURT_VAR_M2_INDEX            606
#define RL_TD_KURT_BELOW_COUNT_INDEX       607

// --- 7. Reward scale controller (Special R) ---
#define RL_REWARD_SCALE_MIN_INDEX          492
#define RL_REWARD_SCALE_BOOTSTRAP_INDEX    476
#define RL_REWARD_MAGNITUDE_EMA_INDEX      614
#define EPS_PNL                            1e-3f     // architectural: div-by-zero guard
#define REWARD_SCALE_MAX                   1e3f      // architectural: 5-order-of-magnitude runaway guard
#define BOOTSTRAP_FRACTION_FLOOR           0.1f      // architectural: early-training spiral guard (spec §Special R)
#define MIN_TRADES_FOR_RELEASE             100.0f    // architectural: confidence threshold for releasing bootstrap floor
#define ASYM_DECREASE_RATE                 1.05f     // architectural: asymmetric per-step decrease cap

// --- 8. PPO ratio clamp controller ---
// MIN_OUT now adaptive from observed ratio variance; MAX_OUT is ISV-resident.
#define RL_PPO_RATIO_CLAMP_MAX_ADAPTIVE_INDEX 629
#define RL_PPO_RATIO_CLAMP_BOOTSTRAP_INDEX    477
#define RL_PPO_CLAMP_MARGIN_INDEX             460
#define RL_PPO_RATIO_VAR_COUNT_INDEX          630
#define RL_PPO_RATIO_VAR_M2_INDEX             632
#define PPO_RATIO_MIN_ABSOLUTE                2.0f   // architectural exemption: vanilla-PG safeguard
#define PPO_RATIO_MIN_STD_SCALE               3.0f   // architectural: 3σ healthy-update coverage
#define PPO_RATIO_MAX_OVER_MIN_FACTOR         5.0f   // architectural: ensure max ≥ 5× min

// --- 9. Gate threshold controller ---
#define RL_CONF_GATE_THRESHOLD_INDEX       512
#define RL_FRD_GATE_THR_LONG_INDEX         516
#define RL_FRD_GATE_THR_SHORT_INDEX        517
#define RL_GATE_WARMUP_STEPS_INDEX         524
#define RL_GATE_DONES_EMA_INDEX            525
#define RL_GATE_DONES_TARGET_INDEX         526
#define RL_GATE_CONF_MIN_INDEX             527
#define RL_GATE_CONF_MAX_INDEX             528
#define RL_GATE_FRD_MIN_INDEX              529
#define RL_GATE_FRD_MAX_INDEX              530
#define RL_GATE_ADJUST_RATE_INDEX          531
#define RL_GATE_EMA_ALPHA_INDEX            638

// --- 10. Q distill lambda controller (Special Q) ---
#define RL_Q_DISTILL_KL_EMA_INDEX          488
#define RL_Q_DISTILL_KL_TARGET_INDEX       491
#define RL_Q_DISTILL_KL_VAR_COUNT_INDEX    618
#define RL_Q_DISTILL_KL_VAR_M2_INDEX       620
#define RL_Q_DISTILL_LAMBDA_MIN_ADAPTIVE_INDEX 639
#define RL_Q_DISTILL_LAMBDA_MAX_INDEX      650
#define RL_Q_DISTILL_KL_TOLERANCE_INDEX    651
#define RL_Q_DISTILL_LAMBDA_RAMP_RATE_INDEX  652
#define RL_Q_DISTILL_LAMBDA_DECAY_RATE_INDEX 653
#define ADAPTIVE_MIN_ABSOLUTE              0.001f    // architectural: q_distill_lambda safety floor
#define ADAPTIVE_MIN_STD_SCALE             0.05f    // architectural: signal-noise proportional scale

// --- Shared Schulman params ---
#define RL_SCHULMAN_TOLERANCE_INDEX        468
#define RL_SCHULMAN_ADJUST_RATE_INDEX      469

// --- Shared adaptive-floor constants (spec 2026-05-30) ---
#define NOISE_FLOOR_TARGET_FRAC            0.5f      // floor ≥ 50% of target
#define NOISE_FLOOR_STD_MULTIPLIER         2.0f      // floor ≥ 2σ of observed signal
#define WIDEN_PATIENCE_CONSECUTIVE         3.0f      // widen/descend requires N below-band steps

// --- Shared Wiener-α floor ---
#define RL_WIENER_ALPHA_FLOOR_INDEX        659

// --- Step counter (device-resident) ---
#define RL_STEP_COUNTER_ISV_INDEX          548

// ═══════════════════════════════════════════════════════════════════════
// Adaptive risk management — spec 2026-05-30-adaptive-risk-management.
// Layers 2/3/4 controllers fused below. Layer 1 (CMDP gates) runs as a
// separate kernel because it needs per-step `realized_pnl` + `outcome`
// arrays not available here. Layer D (trail factors) is read-only ISV.
// ═══════════════════════════════════════════════════════════════════════

// --- 11. Layer 2: IQN action τ controller ---
#define RL_SESSION_PNL_USD_INDEX                  662
#define RL_IQN_ACTION_TAU_INDEX                   671
#define RL_IQN_ACTION_TAU_MIN_INDEX               672
#define RL_IQN_ACTION_TAU_DD_SENSITIVITY_INDEX    673
#define RL_REGIME_TAIL_EVENT_RECENCY_INDEX        701
#define RL_IQN_TAU_TAIL_BOOST_FACTOR_INDEX        711
#define RL_IQN_TAU_TAIL_BOOST_N_WINDOW_INDEX      712
#define DEFAULT_STARTING_CAPITAL_USD              35000.0f

// --- 12. Layer 3: Inventory β controller ---
#define RL_INVENTORY_PENALTY_BETA_INDEX           674
#define RL_INVENTORY_VARIANCE_EMA_INDEX           675
// (RL_REWARD_MAGNITUDE_EMA_INDEX 614 already defined above)
#define BETA_TARGET_FRAC_OF_REWARD                0.01f
#define BETA_SIGMA_MULTIPLIER                     2.0f

// --- 13. Layer 4: Kelly fraction controller ---
#define RL_KELLY_FRACTION_INDEX                   676
#define RL_WIN_RATE_EMA_INDEX                     677
#define RL_AVG_WIN_USD_EMA_INDEX                  678
#define RL_AVG_LOSS_USD_EMA_INDEX                 679
#define RL_KELLY_SAFETY_FRAC_INDEX                680
#define RL_KELLY_MIN_TRADES_FOR_RELEASE_INDEX     681
#define RL_KELLY_BOOTSTRAP_FLOOR_INDEX            720  // B-3 fractional-trust floor
// (RL_CUMULATIVE_DONES_INDEX 660 already defined above)
#define RL_REGIME_DEAD_ZONE_FLAG_INDEX            696
#define RL_REGIME_DEAD_ZONE_TIMEOUT_FLAG_INDEX    698
#define RL_KELLY_EPS_RECOVERY_LIVE_INDEX          706


// ═══════════════════════════════════════════════════════════════════════
// Fused kernel: all 10 controllers in one launch.
//
// input_slots[7]: ISV indices for the 7 base controllers' EMA inputs.
//   [0] = RL_MEAN_TRADE_DURATION_EMA_INDEX (417) — gamma
//   [1] = RL_Q_DIVERGENCE_EMA_INDEX        (418) — target_tau
//   [2] = RL_KL_PI_EMA_INDEX               (419) — ppo_clip
//   [3] = RL_ENTROPY_OBSERVED_EMA_INDEX     (420) — entropy_coef
//   [4] = RL_ADV_VAR_PRE_NORM_INDEX         (612) — rollout_steps (post-Phase-4.5)
//   [5] = RL_TD_KURTOSIS_EMA_INDEX          (422) — per_alpha
//   [6] = RL_MEAN_ABS_PNL_EMA_INDEX         (423) — reward_scale
// ═══════════════════════════════════════════════════════════════════════
extern "C" __global__ void rl_fused_controllers(
    float*       __restrict__ isv,
    const float* __restrict__ dones,         // [b_size] for gate threshold
    float                     alpha,         // Wiener-α for the 7 base + ratio clamp
    int                       b_size,        // batch size for gate threshold dones scan
    const int*   __restrict__ input_slots    // [7] ISV indices for base controllers
) {
    if (threadIdx.x != 0 || blockIdx.x != 0) return;

    const int current_step = (int)isv[RL_STEP_COUNTER_ISV_INDEX];
    const float wiener_floor = isv[RL_WIENER_ALPHA_FLOOR_INDEX];

    // ═══════════════════════════════════════════════════════════════════
    // 1. Gamma controller → ISV[400]  (Special G)
    // ═══════════════════════════════════════════════════════════════════
    {
        const float gamma_prev = isv[RL_GAMMA_INDEX];

        // Adaptive GAMMA_MIN: Welford mean of trade duration after warmup,
        // EMA before warmup. See rl_gamma_controller.cu Special case G.
        const float d_welford_count = isv[RL_TRADE_DUR_VAR_COUNT_INDEX];
        const float d_welford_mean  = isv[RL_TRADE_DUR_VAR_MEAN_INDEX];
        const float d_smoothed = (d_welford_count >= WELFORD_WARMUP_OBS)
            ? d_welford_mean
            : isv[RL_MEAN_TRADE_DURATION_EMA_INDEX];
        const float adaptive_min = fmaxf(GAMMA_MIN_ABSOLUTE,
                                          1.0f - 1.0f / fmaxf(d_smoothed * HORIZON_MULTIPLIER,
                                                              HORIZON_FLOOR));
        isv[RL_GAMMA_MIN_ADAPTIVE_INDEX] = adaptive_min;

        const float mean_trade_duration_events = isv[input_slots[0]];
        const float d            = fmaxf(mean_trade_duration_events, 1.0f);
        const float gamma_max    = isv[RL_GAMMA_MAX_INDEX];
        float       gamma_target = powf(0.5f, 1.0f / d);
        gamma_target = fmaxf(adaptive_min, fminf(gamma_target, gamma_max));

        if (gamma_prev == 0.0f) {
            isv[RL_GAMMA_INDEX] = gamma_target;
        } else {
            const float a         = fmaxf(alpha, wiener_floor);
            float       gamma_new = (1.0f - a) * gamma_prev + a * gamma_target;
            gamma_new = fmaxf(adaptive_min, fminf(gamma_new, gamma_max));
            isv[RL_GAMMA_INDEX] = gamma_new;
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 2. Target tau controller → ISV[401]  (canonical pattern)
    // ═══════════════════════════════════════════════════════════════════
    {
        const float tau_prev = isv[RL_TARGET_TAU_INDEX];
        if (tau_prev == 0.0f) {
            isv[RL_TARGET_TAU_INDEX] = isv[RL_TAU_BOOTSTRAP_INDEX];
        } else {
            const float q_divergence_norm = isv[input_slots[1]];
            if (q_divergence_norm != 0.0f) {
                const float div_target = isv[RL_DIV_TARGET_INDEX];

                // Adaptive noise floor — signal-driven via Welford variance.
                const float div_count = isv[RL_Q_DIV_VAR_COUNT_INDEX];
                const float div_var = (div_count > 1.0f)
                    ? isv[RL_Q_DIV_VAR_M2_INDEX] / (div_count - 1.0f)
                    : 0.0f;
                const float div_std = sqrtf(div_var);
                const float div_noise_floor = fmaxf(div_target * NOISE_FLOOR_TARGET_FRAC,
                                                     div_std * NOISE_FLOOR_STD_MULTIPLIER);

                if (q_divergence_norm >= div_noise_floor) {
                    // Asymmetric Schulman: raise τ on single observation,
                    // lower τ requires N consecutive below-band observations.
                    const float tolerance   = isv[RL_SCHULMAN_TOLERANCE_INDEX];
                    const float adjust_rate = isv[RL_SCHULMAN_ADJUST_RATE_INDEX];
                    float ratio;
                    if (q_divergence_norm > div_target * tolerance) {
                        ratio = adjust_rate;
                        isv[RL_Q_DIV_BELOW_COUNT_INDEX] = 0.0f;
                    } else if (q_divergence_norm < div_target / tolerance) {
                        const float new_count = isv[RL_Q_DIV_BELOW_COUNT_INDEX] + 1.0f;
                        isv[RL_Q_DIV_BELOW_COUNT_INDEX] = new_count;
                        ratio = (new_count >= WIDEN_PATIENCE_CONSECUTIVE) ? (1.0f / adjust_rate) : 1.0f;
                    } else {
                        isv[RL_Q_DIV_BELOW_COUNT_INDEX] = 0.0f;
                        ratio = 1.0f;
                    }
                    float tau_target = tau_prev * ratio;
                    const float tau_min = isv[RL_TARGET_TAU_MIN_INDEX];
                    const float tau_max = isv[RL_TARGET_TAU_MAX_INDEX];
                    tau_target = fmaxf(tau_min, fminf(tau_target, tau_max));

                    if (tau_prev == isv[RL_TAU_BOOTSTRAP_INDEX]) {
                        isv[RL_TARGET_TAU_INDEX] = tau_target;
                    } else {
                        const float a       = fmaxf(alpha, wiener_floor);
                        float       tau_new = (1.0f - a) * tau_prev + a * tau_target;
                        tau_new = fmaxf(tau_min, fminf(tau_new, tau_max));
                        isv[RL_TARGET_TAU_INDEX] = tau_new;
                    }
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 3. PPO clip controller → ISV[402]  (canonical pattern — reference)
    // ═══════════════════════════════════════════════════════════════════
    {
        const float eps_prev = isv[RL_PPO_CLIP_INDEX];
        if (eps_prev == 0.0f) {
            isv[RL_PPO_CLIP_INDEX] = isv[RL_EPS_BOOTSTRAP_INDEX];
        } else {
            const float kl_ema = isv[input_slots[2]];
            if (kl_ema != 0.0f) {
                const float kl_target = isv[RL_KL_TARGET_INDEX];

                // Adaptive noise floor — signal-driven via Welford variance.
                const float kl_count = isv[RL_KL_PI_VAR_COUNT_INDEX];
                const float kl_var = (kl_count > 1.0f)
                    ? isv[RL_KL_PI_VAR_M2_INDEX] / (kl_count - 1.0f)
                    : 0.0f;
                const float kl_std = sqrtf(kl_var);
                const float kl_noise_floor = fmaxf(kl_target * NOISE_FLOOR_TARGET_FRAC,
                                                    kl_std * NOISE_FLOOR_STD_MULTIPLIER);

                if (kl_ema >= kl_noise_floor) {
                    // Asymmetric Schulman: tighten on single observation,
                    // widen requires N consecutive below-band observations.
                    const float tolerance   = isv[RL_SCHULMAN_TOLERANCE_INDEX];
                    const float adjust_rate = isv[RL_SCHULMAN_ADJUST_RATE_INDEX];
                    float ratio;
                    if (kl_ema > kl_target * tolerance) {
                        ratio = 1.0f / adjust_rate;
                        isv[RL_KL_PI_BELOW_COUNT_INDEX] = 0.0f;
                    } else if (kl_ema < kl_target / tolerance) {
                        const float new_count = isv[RL_KL_PI_BELOW_COUNT_INDEX] + 1.0f;
                        isv[RL_KL_PI_BELOW_COUNT_INDEX] = new_count;
                        ratio = (new_count >= WIDEN_PATIENCE_CONSECUTIVE) ? adjust_rate : 1.0f;
                    } else {
                        isv[RL_KL_PI_BELOW_COUNT_INDEX] = 0.0f;
                        ratio = 1.0f;
                    }
                    const float eps_min = isv[RL_PPO_CLIP_EPS_MIN_INDEX];
                    const float eps_max = isv[RL_PPO_CLIP_EPS_MAX_INDEX];
                    float eps_target = eps_prev * ratio;
                    eps_target = fmaxf(eps_min, fminf(eps_target, eps_max));

                    if (eps_prev == isv[RL_EPS_BOOTSTRAP_INDEX]) {
                        isv[RL_PPO_CLIP_INDEX] = eps_target;
                    } else {
                        const float a       = fmaxf(alpha, wiener_floor);
                        float       eps_new = (1.0f - a) * eps_prev + a * eps_target;
                        eps_new = fmaxf(eps_min, fminf(eps_new, eps_max));
                        isv[RL_PPO_CLIP_INDEX] = eps_new;
                    }
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 4. Entropy coef controller → ISV[403]
    //    Asymmetric: raise coef on single below-floor observation,
    //    drop coef requires N consecutive above-band observations.
    // ═══════════════════════════════════════════════════════════════════
    {
        const float coef_prev = isv[RL_ENTROPY_COEF_INDEX];
        const float entropy_observed_ema = isv[input_slots[3]];
        const float h_max       = logf((float)N_ACTIONS);
        const float target_frac = isv[RL_ENTROPY_TARGET_FRAC_INDEX];
        const float h_target    = target_frac * h_max;
        const float COEF_FLOOR  = 0.01f;  // architectural: maintenance pressure baseline
        const float coef_min    = isv[RL_ENTROPY_COEF_MIN_INDEX];
        const float coef_max    = isv[RL_ENTROPY_COEF_MAX_INDEX];
        const float deficit_frac = fmaxf(0.0f,
            fminf((h_target - entropy_observed_ema) / fmaxf(h_target, 1e-6f), 1.0f));
        float coef_target = COEF_FLOOR + (coef_max - COEF_FLOOR) * deficit_frac;
        coef_target = fmaxf(coef_min, fminf(coef_target, coef_max));

        if (coef_prev == 0.0f) {
            isv[RL_ENTROPY_COEF_INDEX] = coef_target;
        } else {
            // Adaptive emergency floor (signal-driven via Welford variance).
            const float h_count = isv[RL_ENTROPY_OBS_VAR_COUNT_INDEX];
            const float h_var = (h_count > 1.0f)
                ? isv[RL_ENTROPY_OBS_VAR_M2_INDEX] / (h_count - 1.0f)
                : 0.0f;
            const float h_std = sqrtf(h_var);
            const float emergency_floor = fmaxf(h_target * NOISE_FLOOR_TARGET_FRAC,
                                                 h_target - h_std * NOISE_FLOOR_STD_MULTIPLIER);

            if (entropy_observed_ema > 0.0f && entropy_observed_ema < emergency_floor) {
                // Single-observation emergency raise; reset patience.
                isv[RL_ENTROPY_COEF_INDEX] = coef_target;
                isv[RL_ENTROPY_OBS_BELOW_COUNT_INDEX] = 0.0f;
            } else {
                const float tolerance = isv[RL_SCHULMAN_TOLERANCE_INDEX];
                const float a         = fmaxf(alpha, wiener_floor);
                float       coef_new  = (1.0f - a) * coef_prev + a * coef_target;
                coef_new = fmaxf(coef_min, fminf(coef_new, coef_max));

                if (coef_new < coef_prev && entropy_observed_ema > h_target * tolerance) {
                    // Healthy-entropy descent direction — require N consecutive observations
                    // before letting Wiener drag coef downward.
                    const float new_count = isv[RL_ENTROPY_OBS_BELOW_COUNT_INDEX] + 1.0f;
                    isv[RL_ENTROPY_OBS_BELOW_COUNT_INDEX] = new_count;
                    if (new_count < WIDEN_PATIENCE_CONSECUTIVE) {
                        isv[RL_ENTROPY_COEF_INDEX] = coef_prev;
                    } else {
                        isv[RL_ENTROPY_COEF_INDEX] = coef_new;
                    }
                } else {
                    isv[RL_ENTROPY_OBS_BELOW_COUNT_INDEX] = 0.0f;
                    isv[RL_ENTROPY_COEF_INDEX] = coef_new;
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 5. Rollout steps controller → ISV[404]
    //    Input now is RL_ADV_VAR_PRE_NORM_INDEX=612 (caller via input_slots[4]).
    // ═══════════════════════════════════════════════════════════════════
    {
        const float prev = isv[RL_N_ROLLOUT_STEPS_INDEX];
        if (prev == 0.0f) {
            isv[RL_N_ROLLOUT_STEPS_INDEX] = isv[RL_ROLLOUT_BOOTSTRAP_INDEX];
        } else {
            const float advantage_var_over_abs_mean = isv[input_slots[4]];
            if (advantage_var_over_abs_mean != 0.0f) {
                const float adv_var_target = isv[RL_ADV_VAR_RATIO_TARGET_INDEX];

                // Adaptive noise floor — signal-driven via Welford variance.
                const float adv_count = isv[RL_ADV_VAR_VAR_COUNT_INDEX];
                const float adv_var = (adv_count > 1.0f)
                    ? isv[RL_ADV_VAR_VAR_M2_INDEX] / (adv_count - 1.0f)
                    : 0.0f;
                const float adv_std = sqrtf(adv_var);
                const float adv_var_noise_floor = fmaxf(adv_var_target * NOISE_FLOOR_TARGET_FRAC,
                                                         adv_std * NOISE_FLOOR_STD_MULTIPLIER);

                if (advantage_var_over_abs_mean >= adv_var_noise_floor) {
                    // Asymmetric Schulman: widen rollout on single above-band observation,
                    // shrink requires N consecutive below-band observations.
                    const float tolerance   = isv[RL_SCHULMAN_TOLERANCE_INDEX];
                    const float adjust_rate = isv[RL_SCHULMAN_ADJUST_RATE_INDEX];
                    float scale;
                    if (advantage_var_over_abs_mean > adv_var_target * tolerance) {
                        scale = adjust_rate;
                        isv[RL_ADV_VAR_BELOW_COUNT_INDEX] = 0.0f;
                    } else if (advantage_var_over_abs_mean < adv_var_target / tolerance) {
                        const float new_count = isv[RL_ADV_VAR_BELOW_COUNT_INDEX] + 1.0f;
                        isv[RL_ADV_VAR_BELOW_COUNT_INDEX] = new_count;
                        scale = (new_count >= WIDEN_PATIENCE_CONSECUTIVE) ? (1.0f / adjust_rate) : 1.0f;
                    } else {
                        isv[RL_ADV_VAR_BELOW_COUNT_INDEX] = 0.0f;
                        scale = 1.0f;
                    }
                    const float rollout_min = isv[RL_ROLLOUT_MIN_INDEX];
                    const float rollout_max = isv[RL_ROLLOUT_MAX_INDEX];
                    float target = prev * scale;
                    target = fmaxf(rollout_min, fminf(target, rollout_max));

                    if (prev == isv[RL_ROLLOUT_BOOTSTRAP_INDEX]) {
                        isv[RL_N_ROLLOUT_STEPS_INDEX] = target;
                    } else {
                        const float a   = fmaxf(alpha, wiener_floor);
                        float       out = (1.0f - a) * prev + a * target;
                        out = fmaxf(rollout_min, fminf(out, rollout_max));
                        isv[RL_N_ROLLOUT_STEPS_INDEX] = out;
                    }
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 6. PER alpha controller → ISV[405]
    //    Asymmetric Schulman patience on descent direction (light tails).
    // ═══════════════════════════════════════════════════════════════════
    {
        const float prev = isv[RL_PER_ALPHA_INDEX];
        const float td_kurtosis_ema = isv[input_slots[5]];

        // Adaptive noise floor: combines absolute ISV floor with Welford-derived
        // adaptive component (std-scaled).
        const float kurt_count = isv[RL_TD_KURT_VAR_COUNT_INDEX];
        const float kurt_var = (kurt_count > 1.0f)
            ? isv[RL_TD_KURT_VAR_M2_INDEX] / (kurt_count - 1.0f)
            : 0.0f;
        const float kurt_std = sqrtf(kurt_var);
        const float adaptive_noise_floor = fmaxf(isv[RL_KURT_NOISE_FLOOR_INDEX],
                                                  kurt_std * NOISE_FLOOR_STD_MULTIPLIER);

        // Hold-at-prev when signal present but below adaptive noise floor
        // (matches standalone semantics: cold-start prev==0 takes the
        // bootstrap path below, otherwise return).
        if (td_kurtosis_ema > 0.0f && td_kurtosis_ema < adaptive_noise_floor) {
            if (prev != 0.0f) goto per_alpha_done;
        }

        {
            const float per_alpha_min = isv[RL_PER_ALPHA_MIN_INDEX];
            const float per_alpha_max = isv[RL_PER_ALPHA_MAX_INDEX];
            const float kurt_excess = fmaxf(0.0f, td_kurtosis_ema - isv[RL_KURT_GAUSSIAN_INDEX]);
            const float kurt_lift_scale = isv[RL_KURT_LIFT_SCALE_INDEX];
            float       target      = 0.4f + 0.2f * (kurt_excess / kurt_lift_scale);
            target = fmaxf(per_alpha_min, fminf(target, per_alpha_max));

            if (prev == 0.0f) {
                isv[RL_PER_ALPHA_INDEX] = target;
            } else {
                const float a   = fmaxf(alpha, wiener_floor);
                float       out = (1.0f - a) * prev + a * target;
                out = fmaxf(per_alpha_min, fminf(out, per_alpha_max));

                // Asymmetric Schulman patience on the DESCENT direction:
                // α rises on a single above-band observation, falls only
                // after N consecutive below-band observations.
                const float kurt_gaussian = isv[RL_KURT_GAUSSIAN_INDEX];
                const float tolerance     = isv[RL_SCHULMAN_TOLERANCE_INDEX];
                if (out < prev && td_kurtosis_ema < kurt_gaussian / tolerance) {
                    const float new_count = isv[RL_TD_KURT_BELOW_COUNT_INDEX] + 1.0f;
                    isv[RL_TD_KURT_BELOW_COUNT_INDEX] = new_count;
                    if (new_count < WIDEN_PATIENCE_CONSECUTIVE) {
                        isv[RL_PER_ALPHA_INDEX] = prev;
                    } else {
                        isv[RL_PER_ALPHA_INDEX] = out;
                    }
                } else {
                    isv[RL_TD_KURT_BELOW_COUNT_INDEX] = 0.0f;
                    isv[RL_PER_ALPHA_INDEX] = out;
                }
            }
        }
        per_alpha_done:;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 7a. Cumulative dones accumulator → ISV[660]  (reward-floor-fix follow-up)
    //
    // Sums `dones[b]` across this step's batch and accumulates into the
    // cumulative-closed-trades slot. Used by the reward_scale bootstrap-
    // floor gate below. Replaces the prior `RL_TRADE_DUR_VAR_COUNT_INDEX`
    // gate which counted closed-trade STEPS (not closed trades) and
    // released the floor ~100× too early in real-trade terms.
    //
    // Per `feedback_no_atomicadd`: single-thread sum-and-write — no atomic
    // needed since the fused kernel is single-thread / single-block.
    // ═══════════════════════════════════════════════════════════════════
    {
        float dones_this_step = 0.0f;
        for (int b = 0; b < b_size; b++) {
            dones_this_step += dones[b];
        }
        isv[RL_CUMULATIVE_DONES_INDEX] = isv[RL_CUMULATIVE_DONES_INDEX] + dones_this_step;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 7. Reward scale controller → ISV[406]  (Special R)
    //    Asymmetric per-step decrease cap (5%) + bootstrap-fraction floor
    //    (10% of bootstrap) until N=100 closed trades.
    // ═══════════════════════════════════════════════════════════════════
    {
        const float prev = isv[RL_REWARD_SCALE_INDEX];
        if (prev == 0.0f) {
            isv[RL_REWARD_SCALE_INDEX] = isv[RL_REWARD_SCALE_BOOTSTRAP_INDEX];
        } else {
            const float mean_abs_pnl_ema = isv[input_slots[6]];
            if (mean_abs_pnl_ema != 0.0f) {
                // Emit per-batch pnl magnitude EMA to the public slot for
                // downstream Welford-variance kernel consumption.
                isv[RL_REWARD_MAGNITUDE_EMA_INDEX] = mean_abs_pnl_ema;

                const float denom  = fmaxf(mean_abs_pnl_ema, EPS_PNL);
                float       target = 1.0f / denom;
                const float scale_min = isv[RL_REWARD_SCALE_MIN_INDEX];
                target = fmaxf(scale_min, fminf(target, REWARD_SCALE_MAX));

                // Bootstrap-fraction floor: at COLD START, scale cannot drop below
                // 10% of bootstrap until the controller has demonstrated it can adapt
                // to real PnL magnitudes (i.e. cumulative_dones first crosses min_trades).
                // After that one-time warmup, the floor relaxes to scale_min permanently.
                //
                // Audit 2026-05-31 (eval-boundary addendum): the prior test
                // `(cumulative_dones < min_trades)` fired spuriously at every fold
                // boundary because `reset_session_state` zeroes cumulative_dones to
                // re-enter Kelly's predictive-safety warmup. The boot_floor's intent
                // is "is this the cold-start regime where scale could crash before any
                // trade ground truth?" — NOT "have we accumulated trades in the current
                // session?". Decouple via a persistent latch (slot 716): set once when
                // cumulative_dones FIRST crosses min_trades, never reset. Past that
                // point the floor stays at scale_min regardless of session resets.
                const float trade_count  = isv[RL_CUMULATIVE_DONES_INDEX];
                const float min_trades   = isv[RL_MIN_TRADES_FOR_RELEASE_INDEX];
                const float warmed_flag  = isv[RL_REWARD_SCALE_CONTROLLER_WARMED_FLAG_INDEX];
                const float boot         = isv[RL_REWARD_SCALE_BOOTSTRAP_INDEX];
                const float boot_floor   = (warmed_flag == 0.0f)
                    ? boot * BOOTSTRAP_FRACTION_FLOOR
                    : scale_min;
                // Latch the warmed flag exactly once when cumulative_dones first
                // crosses the threshold. Monotonic 0→1 — never cleared by any path.
                if (warmed_flag == 0.0f && trade_count >= min_trades) {
                    isv[RL_REWARD_SCALE_CONTROLLER_WARMED_FLAG_INDEX] = 1.0f;
                }

                if (prev == boot) {
                    // First-observation replace-directly with asymmetric DECREASE
                    // rate-limit applied (per spec §Special R bullet (a)).
                    float first = target;
                    if (first < prev) {
                        first = fmaxf(first, prev / ASYM_DECREASE_RATE);
                    }
                    first = fmaxf(boot_floor, fminf(first, REWARD_SCALE_MAX));
                    isv[RL_REWARD_SCALE_INDEX] = first;
                } else {
                    const float a   = fmaxf(alpha, wiener_floor);
                    float       out = (1.0f - a) * prev + a * target;

                    // Per-step ±2% symmetric movement clamp.
                    const float max_move = prev * 1.02f;
                    const float min_move = prev * 0.98f;
                    out = fmaxf(min_move, fminf(out, max_move));

                    // Asymmetric DECREASE rate limit (belt-and-braces — documents
                    // the controller invariant even if 2% symmetric clamp relaxes).
                    if (out < prev) {
                        out = fmaxf(out, prev / ASYM_DECREASE_RATE);
                    }

                    out = fmaxf(boot_floor, fminf(out, REWARD_SCALE_MAX));
                    isv[RL_REWARD_SCALE_INDEX] = out;
                }
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 8. PPO ratio clamp controller → ISV[440]
    //    Reads ε from ISV[402] which was just written above.
    //    MIN_OUT adaptive from observed ratio variance; MAX_OUT ISV-driven.
    // ═══════════════════════════════════════════════════════════════════
    {
        const float prev = isv[RL_PPO_RATIO_CLAMP_MAX_INDEX];
        if (prev == 0.0f) {
            isv[RL_PPO_RATIO_CLAMP_MAX_INDEX] = isv[RL_PPO_RATIO_CLAMP_BOOTSTRAP_INDEX];
        } else {
            const float eps          = isv[RL_PPO_CLIP_INDEX];
            const float clamp_margin = isv[RL_PPO_CLAMP_MARGIN_INDEX];
            float       target       = (1.0f + eps) * clamp_margin;

            // Adaptive MIN/MAX from observed log-ratio variance.
            const float ratio_count = isv[RL_PPO_RATIO_VAR_COUNT_INDEX];
            const float ratio_var = (ratio_count > 1.0f)
                ? isv[RL_PPO_RATIO_VAR_M2_INDEX] / (ratio_count - 1.0f)
                : 0.0f;
            const float ratio_std = sqrtf(ratio_var);
            const float adaptive_min = fmaxf(PPO_RATIO_MIN_ABSOLUTE,
                                              1.0f + ratio_std * PPO_RATIO_MIN_STD_SCALE);
            const float adaptive_max = fmaxf(adaptive_min * PPO_RATIO_MAX_OVER_MIN_FACTOR,
                                              isv[RL_PPO_RATIO_CLAMP_MAX_ADAPTIVE_INDEX]);

            target = fmaxf(adaptive_min, fminf(target, adaptive_max));

            if (prev == isv[RL_PPO_RATIO_CLAMP_BOOTSTRAP_INDEX]) {
                isv[RL_PPO_RATIO_CLAMP_MAX_INDEX] = target;
            } else {
                const float a   = fmaxf(alpha, wiener_floor);
                float       out = (1.0f - a) * prev + a * target;
                out = fmaxf(adaptive_min, fminf(out, adaptive_max));
                isv[RL_PPO_RATIO_CLAMP_MAX_INDEX] = out;
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 9. Gate threshold controller → ISV[512,516,517]
    //    Scans dones[B] to compute done_rate EMA → ISV[525].
    //    EMA-α is now ISV-resident (slot 638) per spec §Group B Task 13.
    // ═══════════════════════════════════════════════════════════════════
    {
        const int warmup = (int)isv[RL_GATE_WARMUP_STEPS_INDEX];
        if (current_step >= warmup) {
            float done_count = 0.0f;
            for (int b = 0; b < b_size; ++b) {
                if (dones[b] > 0.5f) done_count += 1.0f;
            }
            const float done_rate = done_count / (float)b_size;
            const float gate_alpha = isv[RL_GATE_EMA_ALPHA_INDEX];
            const float prev_ema = isv[RL_GATE_DONES_EMA_INDEX];
            const float dones_ema = (prev_ema == 0.0f && done_rate > 0.0f)
                ? done_rate
                : (1.0f - gate_alpha) * prev_ema + gate_alpha * done_rate;
            isv[RL_GATE_DONES_EMA_INDEX] = dones_ema;

            const float target = isv[RL_GATE_DONES_TARGET_INDEX];
            const float adjust = isv[RL_GATE_ADJUST_RATE_INDEX];

            float conf_thr  = isv[RL_CONF_GATE_THRESHOLD_INDEX];
            float frd_long  = isv[RL_FRD_GATE_THR_LONG_INDEX];
            float frd_short = isv[RL_FRD_GATE_THR_SHORT_INDEX];

            if (dones_ema < target * 0.2f) {
                conf_thr  *= adjust;
                frd_long  *= adjust;
                frd_short *= adjust;
            } else if (dones_ema > target * 5.0f) {
                conf_thr  /= adjust;
                frd_long  /= adjust;
                frd_short /= adjust;
            }

            const float conf_min = isv[RL_GATE_CONF_MIN_INDEX];
            const float conf_max = isv[RL_GATE_CONF_MAX_INDEX];
            const float frd_min  = isv[RL_GATE_FRD_MIN_INDEX];
            const float frd_max  = isv[RL_GATE_FRD_MAX_INDEX];

            isv[RL_CONF_GATE_THRESHOLD_INDEX] = fmaxf(conf_min, fminf(conf_thr, conf_max));
            isv[RL_FRD_GATE_THR_LONG_INDEX]   = fmaxf(frd_min, fminf(frd_long, frd_max));
            isv[RL_FRD_GATE_THR_SHORT_INDEX]  = fmaxf(frd_min, fminf(frd_short, frd_max));
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 10. Q→π distill lambda controller → ISV[486]  (Special Q)
    //     Adaptive MIN from Welford-var of q_distill_kl_ema; MAX + rates
    //     ISV-driven (slots 650/651/652/653).
    // ═══════════════════════════════════════════════════════════════════
    {
        const float kl_observed = isv[RL_Q_DISTILL_KL_EMA_INDEX];
        const float kl_target   = isv[RL_Q_DISTILL_KL_TARGET_INDEX];
        float       lambda      = isv[RL_Q_DISTILL_LAMBDA_INDEX];

        if (kl_observed > 0.0f && kl_target > 0.0f && lambda > 0.0f) {
            // Adaptive MIN: max(0.001, sqrt(var) × 0.05).
            const float kl_var_count = isv[RL_Q_DISTILL_KL_VAR_COUNT_INDEX];
            const float kl_var = (kl_var_count > 1.0f)
                ? isv[RL_Q_DISTILL_KL_VAR_M2_INDEX] / (kl_var_count - 1.0f)
                : 0.0f;
            const float kl_std = sqrtf(kl_var);
            const float adaptive_min = fmaxf(ADAPTIVE_MIN_ABSOLUTE,
                                              kl_std * ADAPTIVE_MIN_STD_SCALE);
            isv[RL_Q_DISTILL_LAMBDA_MIN_ADAPTIVE_INDEX] = adaptive_min;

            const float max_lambda        = isv[RL_Q_DISTILL_LAMBDA_MAX_INDEX];
            const float kl_tolerance      = isv[RL_Q_DISTILL_KL_TOLERANCE_INDEX];
            const float lambda_ramp_rate  = isv[RL_Q_DISTILL_LAMBDA_RAMP_RATE_INDEX];
            const float lambda_decay_rate = isv[RL_Q_DISTILL_LAMBDA_DECAY_RATE_INDEX];

            const float upper = kl_target * kl_tolerance;
            const float lower = kl_target / kl_tolerance;

            if (kl_observed > upper) {
                lambda = fminf(max_lambda, lambda * lambda_ramp_rate);
            } else if (kl_observed < lower) {
                lambda = fmaxf(adaptive_min, lambda * lambda_decay_rate);
            }

            isv[RL_Q_DISTILL_LAMBDA_INDEX] = lambda;
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 11. Layer 2 — IQN action τ controller → ISV[671]
    //     τ = clamp(τ_min, 0.5 - sensitivity × drawdown_frac, 1.0)
    //     drawdown_frac = max(0, -session_pnl) / starting_capital
    // ═══════════════════════════════════════════════════════════════════
    {
        const float session_pnl   = isv[RL_SESSION_PNL_USD_INDEX];
        const float drawdown_usd  = fmaxf(0.0f, -session_pnl);
        const float drawdown_frac = drawdown_usd / DEFAULT_STARTING_CAPITAL_USD;

        const float tau_min     = isv[RL_IQN_ACTION_TAU_MIN_INDEX];
        const float sensitivity = isv[RL_IQN_ACTION_TAU_DD_SENSITIVITY_INDEX];
        float       tau_action  = 0.5f - sensitivity * drawdown_frac;
        tau_action = fmaxf(tau_min, fminf(tau_action, 1.0f));

        // Tail-recency τ_min boost (defense in depth, F5 / spec v3)
        const float recency     = isv[RL_REGIME_TAIL_EVENT_RECENCY_INDEX];
        const float tail_window = isv[RL_IQN_TAU_TAIL_BOOST_N_WINDOW_INDEX];
        const float boost       = isv[RL_IQN_TAU_TAIL_BOOST_FACTOR_INDEX];

        float tau_min_eff = tau_min;
        if (recency < tail_window) {
            tau_min_eff *= boost;
        }
        tau_action = fmaxf(tau_action, tau_min_eff);

        isv[RL_IQN_ACTION_TAU_INDEX] = tau_action;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 12. Layer 3 — Inventory β controller → ISV[674]
    //     β_target = 0.01 × E[|reward|] / (2 × σ_inventory)
    //     Sentinel-zero bootstrap; Wiener-α blend with floor 0.4 after.
    // ═══════════════════════════════════════════════════════════════════
    {
        const float reward_mag = isv[RL_REWARD_MAGNITUDE_EMA_INDEX];
        const float inv_var    = isv[RL_INVENTORY_VARIANCE_EMA_INDEX];
        if (inv_var > 0.0f && reward_mag > 0.0f) {
            const float inv_std     = sqrtf(inv_var);
            const float beta_target = BETA_TARGET_FRAC_OF_REWARD * reward_mag
                                    / (BETA_SIGMA_MULTIPLIER * inv_std);
            const float prev = isv[RL_INVENTORY_PENALTY_BETA_INDEX];
            if (prev == 0.0f) {
                isv[RL_INVENTORY_PENALTY_BETA_INDEX] = beta_target;
            } else {
                const float a = fmaxf(alpha, wiener_floor);
                isv[RL_INVENTORY_PENALTY_BETA_INDEX] =
                    (1.0f - a) * prev + a * beta_target;
            }
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // 13. Layer 4 — Kelly fraction controller → ISV[676]
    //     B-3 fractional-trust schedule (2026-05-31):
    //       trust(n) = max(f_floor, min(1, n/N_full))
    //       f_safe   = max(f_floor·safety, f_kelly · trust · safety)
    //     Replaces binary warmup gate which forced f=1.0 (max sizing) at
    //     every fold boundary (4xmxm eval[1] → 53% positions → -$100M).
    //     Math: Hoeffding ε(n=200) ≈ 0.10 → N_full=200 gives 10% precision.
    //     f_floor=0.05 prevents Kelly trade-stream death.
    // ═══════════════════════════════════════════════════════════════════
    {
        const float p        = isv[RL_WIN_RATE_EMA_INDEX];
        const float avg_win  = isv[RL_AVG_WIN_USD_EMA_INDEX];
        const float avg_loss = isv[RL_AVG_LOSS_USD_EMA_INDEX];
        const float safety   = isv[RL_KELLY_SAFETY_FRAC_INDEX];

        const float n_trades = isv[RL_CUMULATIVE_DONES_INDEX];
        const float n_full   = isv[RL_KELLY_MIN_TRADES_FOR_RELEASE_INDEX];
        const float f_floor  = isv[RL_KELLY_BOOTSTRAP_FLOOR_INDEX];
        const float trust    = (n_full > 0.0f)
            ? fmaxf(f_floor, fminf(1.0f, n_trades / n_full))
            : 1.0f;

        if (avg_loss <= 0.0f || avg_win <= 0.0f) {
            // Dead-signal: trust × safety floor keeps trades flowing.
            isv[RL_KELLY_FRACTION_INDEX] = fmaxf(0.0f, fminf(trust * safety, 1.0f));
        } else {
            const float b_ratio = avg_win / avg_loss;
            const float q       = 1.0f - p;
            const float f_kelly = (p * b_ratio - q) / b_ratio;
            const float f_raw   = f_kelly * trust * safety;
            const float f_min   = f_floor * safety;
            float       f       = fmaxf(f_min, f_raw);
            f = fmaxf(0.0f, fminf(f, 1.0f));
            isv[RL_KELLY_FRACTION_INDEX] = f;
        }

        // Kelly resurrection (Theorem 1): override analytic kelly if DEAD_ZONE_FLAG fires.
        //
        // Two safety checks:
        //   1. DEAD_ZONE_TIMEOUT_FLAG: if dead-zone has persisted > MAX_DURATION steps,
        //      stop trying to resurrect (let kelly stay at 0; halt the bleed).
        //      The trainer monitors TIMEOUT_FLAG as a halt-training signal.
        //   2. Otherwise: kelly_f overridden with ε_recovery_live (computed by regime_observer).
        const int dead_zone = (int)isv[RL_REGIME_DEAD_ZONE_FLAG_INDEX];
        const int timeout   = (int)isv[RL_REGIME_DEAD_ZONE_TIMEOUT_FLAG_INDEX];
        if (dead_zone && !timeout) {
            isv[RL_KELLY_FRACTION_INDEX] = isv[RL_KELLY_EPS_RECOVERY_LIVE_INDEX];
        }
    }
}
