// rl_frd_gate.cu — override opening actions to Hold when the FRD
// (Forward Return Distribution) head predicts insufficient favorable
// probability mass in the entry direction.
//
// For each batch where position==0 and the chosen action is an opening:
//   - Long (a5/a6): compute entry_quality = Σ softmax(h2_logits)[i]
//     for atoms i where bucket_center > +0.5σ. If < threshold → Hold.
//   - Short (a0/a1): compute entry_quality = Σ softmax(h2_logits)[i]
//     for atoms i where bucket_center < -0.5σ. If < threshold → Hold.
//
// FRD logits layout: [B, FRD_N_HORIZONS × FRD_N_ATOMS] where horizons
// are stacked contiguously. Horizon 2 (medium, ~300 ticks) is at
// offset h=1 (0-based) × FRD_N_ATOMS.
//
// Bucket centers span [-RANGE_SIGMA, +RANGE_SIGMA] uniformly over
// FRD_N_ATOMS atoms. With RANGE_SIGMA=3.0 and N=21:
//   atom_i center = -3.0 + i × (6.0 / 20) = -3.0 + i × 0.3
//   > +0.5 when i >= 12 (center = -3.0 + 12*0.3 = +0.6)
//   < -0.5 when i <= 8  (center = -3.0 + 8*0.3 = -0.6)
//
// One block per batch, single thread. No shared memory.
// Per `feedback_no_atomicadd`, `feedback_cpu_is_read_only`.

#include <stdint.h>
#include <math.h>

#define FRD_N_HORIZONS                 3
#define FRD_N_ATOMS                    21
#define FRD_H2_OFFSET                  (1 * FRD_N_ATOMS)
#define FRD_FAVORABLE_LONG_START       12
#define FRD_FAVORABLE_SHORT_END        9
#define ACTION_HOLD                    2
#define RL_FRD_GATE_THR_LONG_INDEX     516
#define RL_FRD_GATE_THR_SHORT_INDEX    517
#define RL_FRD_GATE_FIRED_COUNT_INDEX  518
#define RL_GATE_WARMUP_STEPS_INDEX    524
#define RL_STEP_COUNTER_ISV_INDEX     548
// Phase 7b F5 (2026-06-05) Option A — interop with state-conditional
// action availability mask. When F5 master gate (slot 823) is engaged
// AND position is flat, F5 has DELIBERATELY forced the agent into an
// opening action by masking Hold (and other invalid actions) in
// pi_logits. Overriding that back to Hold here neutralizes F5's
// surfer→trend choice-set forcing and prevents F5-G1 from achieving
// `hold_frac_flat == 0`. The suppression is gated on F5-engaged AND
// flat — in-position safety overrides are preserved verbatim.
#define RL_F5_STATE_MASK_ENABLED_INDEX 823

extern "C" __global__ void rl_frd_gate(
    int*                 __restrict__ actions,      // [B] IN/OUT
    const float*         __restrict__ frd_logits,   // [B × FRD_N_HORIZONS × FRD_N_ATOMS]
    const unsigned char* __restrict__ pos_state,    // [B × pos_bytes]
    float*               __restrict__ isv,
    int                                b_size,
    int                                pos_bytes
) {
    const int b = blockIdx.x;
    if (b >= b_size) return;

    const int current_step = (int)isv[RL_STEP_COUNTER_ISV_INDEX];
    const int warmup = (int)isv[RL_GATE_WARMUP_STEPS_INDEX];
    if (current_step < warmup) return;

    const int action = actions[b];

    const bool is_long_open  = (action == 5 || action == 6);
    const bool is_short_open = (action == 0 || action == 1);
    if (!is_long_open && !is_short_open) return;

    const int position_lots = *(const int*)(pos_state + b * pos_bytes);
    if (position_lots != 0) return;

    // Phase 7b F5 (2026-06-05) Option A — short-circuit before quality
    // computation when F5 is engaged and we are flat. F5 has masked
    // Hold in pi_logits so the sampled opening is deliberate; overriding
    // here would neutralize F5-G1 (`hold_frac_flat == 0` from flat).
    // The early `position_lots != 0 → return` above already restricts
    // us to flat, so checking the F5 master gate alone is sufficient.
    const bool f5_engaged = (isv[RL_F5_STATE_MASK_ENABLED_INDEX] > 0.5f);
    if (f5_engaged) return;

    // Horizon 2 logits for this batch.
    const float* h2 = frd_logits + b * (FRD_N_HORIZONS * FRD_N_ATOMS) + FRD_H2_OFFSET;

    // Numerically-stable softmax over h2 atoms.
    float max_l = h2[0];
    for (int i = 1; i < FRD_N_ATOMS; ++i) {
        if (h2[i] > max_l) max_l = h2[i];
    }
    float sum_exp = 0.0f;
    float probs[FRD_N_ATOMS];
    for (int i = 0; i < FRD_N_ATOMS; ++i) {
        probs[i] = expf(h2[i] - max_l);
        sum_exp += probs[i];
    }
    const float inv_sum = 1.0f / sum_exp;
    for (int i = 0; i < FRD_N_ATOMS; ++i) {
        probs[i] *= inv_sum;
    }

    float entry_quality = 0.0f;
    if (is_long_open) {
        // Favorable for long: atoms where bucket > +0.5σ.
        for (int i = FRD_FAVORABLE_LONG_START; i < FRD_N_ATOMS; ++i) {
            entry_quality += probs[i];
        }
        const float thr = isv[RL_FRD_GATE_THR_LONG_INDEX];
        if (entry_quality < thr) {
            actions[b] = ACTION_HOLD;
            isv[RL_FRD_GATE_FIRED_COUNT_INDEX] += 1.0f;
        }
    } else {
        // Favorable for short: atoms where bucket < -0.5σ.
        for (int i = 0; i < FRD_FAVORABLE_SHORT_END; ++i) {
            entry_quality += probs[i];
        }
        const float thr = isv[RL_FRD_GATE_THR_SHORT_INDEX];
        if (entry_quality < thr) {
            actions[b] = ACTION_HOLD;
            isv[RL_FRD_GATE_FIRED_COUNT_INDEX] += 1.0f;
        }
    }
}
