// rl_band_mask.cu — Phase 4-A no-transaction-band action override.
//
// When the current position lies inside the learned band `[b_l, b_u]`,
// force `actions[b] = Hold` regardless of the sampled / argmax action.
// This is the architectural default of the Davis-Norman (1990) no-trade
// region (extended to deep RL by Imaki-Imajo-Ito 2021, arXiv:2103.01775).
//
// Layout matches `rl_confidence_gate.cu`:
//   * Grid=(B, 1, 1), Block=(1, 1, 1).
//   * One block per batch, single thread — pos_state read + 2-float band
//     read + scalar compare; no reduction.
//   * Master gate at `RL_BAND_ENABLED_INDEX` (slot 799). When ≤ 0.5f the
//     kernel returns immediately, preserving Phase 3D bit-equality.
//   * No atomicAdd; no shared mem; no PRNG; pure device-side, fully
//     graph-capturable BUT launched OUTSIDE graph capture per spec §9.5
//     so the host can branch on the master gate value without runtime
//     graph rebuild.
//
// Per `feedback_no_atomicadd`, `feedback_cpu_is_read_only`,
// `pearl_fleet_fraction_not_aggregate`.

#include <stdint.h>

#define ACTION_HOLD                       2
#define RL_BAND_ENABLED_INDEX             799
#define RL_BAND_MAX_MASK_FRAC_INDEX       813

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

    // Master gate — bootstrap default is OFF (slot 799 = 0.0). When the
    // operator flips it to 1.0 the band defaults engage.
    const float enabled = isv[RL_BAND_ENABLED_INDEX];
    if (enabled <= 0.5f) return;

    // Phase 4-A2 exploration bypass — guarantee at least
    // `(1 − max_mask) · B` batches are NEVER masked, so positions vary
    // and the sigmoid surrogate gradient on (b_l, b_u) can flow into the
    // band head. Without this the band collapses to the dead-signal trap
    // observed at 552d91bf4 (Phase 4-B smoke): all positions stuck at 0,
    // deep-in-band sigmoid argument, ~zero gradient, band stays wide.
    //
    // Bypass is DETERMINISTIC (lowest-index batches bypass) — random
    // sampling would break `pearl_determinism_achieved` bit-equality.
    // Spec §2.2 (kernel) + §9.1 Mitigation 2 (justification).
    const float max_mask = isv[RL_BAND_MAX_MASK_FRAC_INDEX];
    const int min_explore = (int)((float)b_size * (1.0f - max_mask));
    if (b < min_explore) return;

    // Position layout: pos_state[b * pos_bytes + 0..4] = position_lots:i32
    // (canonical foxhunt offset; see PosFlat at
    // crates/ml-backtesting/src/lob/mod.rs and the rl_confidence_gate
    // reader at line 72).
    const int position_lots =
        *reinterpret_cast<const int*>(pos_state + b * pos_bytes);

    // Phase 4-A3 (2026-06-04): Davis-Norman position-zero exception.
    // The Davis-Norman (1990) no-transaction band is a theorem about
    // MANAGING an EXISTING hedge position — it tells the agent NOT to
    // micro-adjust within the band. It says nothing about whether to
    // open a position from flat.
    //
    // The `±|tanh|` activation in `rl_band_head_forward.cu` guarantees
    // `b_l ≤ 0 ≤ b_u` (the Davis-Norman invariant). Combined with the
    // foxhunt invariant that agents start FLAT (position = 0), every
    // single flat-batch would be masked to Hold — agents never open,
    // positions never move, the sigmoid surrogate on (b_l, b_u) sits
    // deep-in-band where its derivative ≈ 0, and the band loss has no
    // gradient signal. Verified empirically at 1c23ff368 (Phase 4-A2):
    // action_hist collapsed to bimodal [0,0,109,0,0,0,0,0,19,0,0] from
    // step 100 onward, total_trades = 8 over 2000 steps.
    //
    // Flat positions get free choice — opens are NOT band-constrained.
    if (position_lots == 0) return;

    const float b_l = band_outputs[b * 2 + 0];
    const float b_u = band_outputs[b * 2 + 1];

    const float pos_f = (float)position_lots;
    if (pos_f >= b_l && pos_f <= b_u) {
        actions[b] = ACTION_HOLD;
    }
}
