/* ══════════════════════════════════════════════════════════════════════════
 * SP20 Phase 3 Task 3.2 (2026-05-10) — sp20_hold_baseline GPU oracle test wrappers.
 *
 * Standalone test kernels exercising the two device helpers in
 * `sp20_hold_baseline.cuh` so the Rust GPU oracle tests in
 * `tests/sp20_hold_baseline_test.rs` can drive synthetic inputs through
 * the EXACT same code paths the production caller
 * (`experience_env_step` per-bar branches + segment_complete trade-close
 * site) executes — no CPU reference impl per
 * `feedback_no_cpu_test_fallbacks`.
 *
 * Test-only: no production callers. Cubins built by
 * `crates/ml/build.rs` (kernel names listed below in the order they
 * appear in this file).
 *
 * Two wrappers shipped:
 *
 *   1. `sp20_per_bar_aux_conf_k2_test_kernel(logits[2], out)`:
 *        Calls `sp20_compute_per_bar_aux_conf_k2` once. Single-thread
 *        launch — the helper is pure register-only arithmetic.
 *
 *   2. `sp20_sum_hold_baseline_over_trade_test_kernel(buf, env,
 *                                                     hold_buffer_size,
 *                                                     current_t,
 *                                                     segment_hold_time,
 *                                                     out)`:
 *        Calls `sp20_sum_hold_baseline_over_trade` once. Single-thread
 *        launch.
 *
 *   3. `sp20_dual_emission_test_kernel(...)`:
 *        Mini-fixture that mirrors the production per-bar emission
 *        site (positioned-non-event branch line ~3650 of
 *        experience_kernels.cu). Synthetic single-env inputs:
 *        per_bar_opp_cost = -aux_conf × cost_scale; if action == Hold,
 *        emit centered = per_bar_opp_cost - hold_reward_ema; always
 *        write per_bar_opp_cost to buf[0, current_t % size]. Output
 *        captures BOTH centered + the buffer slot for assertion. This
 *        wrapper validates the full dual-emission contract on a tight
 *        synthetic fixture.
 *
 * Outputs go to mapped-pinned buffers with `__threadfence_system()`
 * before thread exit so the host can read via `read_volatile` after a
 * stream sync. Same pattern as `sp20_event_reward_test_kernel.cu`.
 * ══════════════════════════════════════════════════════════════════════════ */

#include <cuda_runtime.h>

#include "sp20_hold_baseline.cuh"

/* ── Test 1: sp20_compute_per_bar_aux_conf_k2 ─────────────────────────── */

extern "C" __global__ void sp20_per_bar_aux_conf_k2_test_kernel(
    const float* __restrict__ logits,    /* [K=2]                 */
    float*       __restrict__ out_aux_conf)
{
    if (threadIdx.x != 0 || blockIdx.x != 0) return;
    out_aux_conf[0] = sp20_compute_per_bar_aux_conf_k2(logits);
    __threadfence_system();
}

/* ── Test 2: sp20_sum_hold_baseline_over_trade ────────────────────────── */

extern "C" __global__ void sp20_sum_hold_baseline_over_trade_test_kernel(
    const float* __restrict__ buf,
    int    env,
    int    hold_buffer_size,
    int    current_t,
    float  segment_hold_time,
    float* __restrict__ out_sum)
{
    if (threadIdx.x != 0 || blockIdx.x != 0) return;
    out_sum[0] = sp20_sum_hold_baseline_over_trade(
        buf, env, hold_buffer_size, current_t, segment_hold_time
    );
    __threadfence_system();
}

/* ── Test 3: full dual-emission shape ─────────────────────────────────────
 *
 * Mirrors the production per-bar emission site (positioned-non-event
 * branch ~line 3650 of experience_kernels.cu) on a single env. Inputs:
 *   - logits[2]            — aux head logits (drives aux_conf).
 *   - cost_scale           — ISV[HOLD_COST_SCALE_INDEX] (synthetic).
 *   - hold_reward_ema      — ISV[HOLD_REWARD_EMA_INDEX] (synthetic).
 *   - dir_idx              — action direction (DIR_HOLD = 1 fires Path 1).
 *   - hold_action_id       — DIR_HOLD constant (= 1 in production).
 *   - current_t            — bar index for buffer slot.
 *   - hold_buffer_size     — circular ring size (= 30 in production).
 *
 * Outputs:
 *   - out_centered[0]      — Path 1 result: 0 if not Hold;
 *                            (per_bar_opp_cost - hold_reward_ema) if Hold.
 *   - out_buf[0]           — Path 2 result: per_bar_opp_cost
 *                            (regardless of action) at slot
 *                            [0 × hold_buffer_size + current_t % size].
 *
 * Asserts the spec §4.2 dual-emission contract:
 *   per_bar_opp_cost = -aux_conf × cost_scale
 *   Path 1 (Hold-only):  centered = per_bar_opp_cost - hold_reward_ema
 *   Path 2 (always):     buf[..] = per_bar_opp_cost (UNcentered)
 *
 * The `out_buf` arg is a [hold_buffer_size] device-resident array
 * (env_count = 1 in this fixture). The kernel writes the slot for
 * env=0 at `current_t % hold_buffer_size`.
 */
extern "C" __global__ void sp20_dual_emission_test_kernel(
    const float* __restrict__ logits,        /* [K=2]                 */
    float        cost_scale,
    float        hold_reward_ema,
    int          dir_idx,
    int          hold_action_id,
    int          current_t,
    int          hold_buffer_size,
    float*       __restrict__ out_centered,  /* [1]                   */
    float*       __restrict__ out_buf)       /* [hold_buffer_size]    */
{
    if (threadIdx.x != 0 || blockIdx.x != 0) return;

    const float aux_conf = sp20_compute_per_bar_aux_conf_k2(logits);
    const float per_bar_opp_cost = -aux_conf * cost_scale;

    /* Path 2 — always */
    const int slot_idx = current_t % hold_buffer_size;
    out_buf[slot_idx] = per_bar_opp_cost;

    /* Path 1 — Hold-only, centered */
    if (dir_idx == hold_action_id) {
        out_centered[0] = per_bar_opp_cost - hold_reward_ema;
    } else {
        out_centered[0] = 0.0f;
    }

    __threadfence_system();
}
