// rl_trail_mutate.cu — SP20 P5 trail mutation kernel.
//
// Handles a7 (TrailTighten) and a8 (TrailLoosen) — closes
// `pearl_dead_trail_stop_actions_a7_a8`. Mutates each active unit's
// trail_distance bounded by ISV-driven MIN/MAX. Each direction has its
// own independent factor per spec 2026-05-30-adaptive-risk-management:
//
//   a7 (tighten): trail = max(MIN, trail × TIGHTEN_FACTOR)   (default 0.9)
//   a8 (loosen):  trail = min(MAX, trail × LOOSEN_FACTOR)    (default 1.1)
//
// Tighten and loosen are NO LONGER strictly reciprocal — the agent can
// learn (via the ISV slots' controllers, if any) to bias one direction
// or the other. Bootstrap factors (0.9 / 1.1) preserve the prior
// approximately-symmetric behavior.
//
// Slot migration: previously read `RL_TRAIL_ADJUST_RATE_INDEX = 497`
// and used `trail × rate` for tighten and `trail / rate` for loosen.
// Now reads independent `RL_TRAIL_TIGHTEN_FACTOR_INDEX = 682` and
// `RL_TRAIL_LOOSEN_FACTOR_INDEX = 683` from the risk-management spec.
//
// Phase 5 (2026-06-04): adds a per-unit absolute ceiling on the loosen
// action. The TrailLoosen pathology surfaced by the Phase 4-A3
// ultrathink investigation showed mean trail 376 → max 2959 ticks
// (~$37k risk per position) because the multiplicative loosen had no
// per-unit anchor — only the global slot 495 cap (100.0 price units).
// The fix: clamp `unit_trail_distance ≤ unit_initial_r ·
// RL_TRAIL_MAX_INITIAL_R_RATIO` after the multiply. Ratio bootstrap
// 4.0 — agent can widen ≤ 4× from open, far below the smoke fleet's
// 2959-tick worst case.
//
// One thread per (batch, unit). Mutates ALL active units uniformly
// when the batch chose a7/a8 — matches SP20 §2.2 Tier 2 design.
//
// Per `feedback_no_atomicadd`: per-(batch, unit) independent writes.
// Per `feedback_cpu_is_read_only`: pure device-side.
// Per `feedback_isv_for_adaptive_bounds`: MIN/MAX/factors all ISV slots.

#include <stdint.h>

#define MAX_UNITS                                4
#define ACTION_TRAIL_TIGHTEN                     7
#define ACTION_TRAIL_LOOSEN                      8
#define RL_TRAIL_MIN_INDEX                       494
#define RL_TRAIL_MAX_INDEX                       495
#define RL_TRAIL_TIGHTEN_FACTOR_INDEX            682
#define RL_TRAIL_LOOSEN_FACTOR_INDEX             683
#define RL_TRAIL_MAX_INITIAL_R_RATIO_INDEX       814

extern "C" __global__ void rl_trail_mutate(
    const int*           __restrict__ actions,             // [B]
    const float*         __restrict__ isv,
    const unsigned char* __restrict__ unit_active,         // [B * MAX_UNITS]
    const float*         __restrict__ unit_initial_r,      // [B * MAX_UNITS]
    float*               __restrict__ unit_trail_distance, // [B * MAX_UNITS] IN/OUT
    int                                b_size
) {
    const int b = blockIdx.x;
    const int u = threadIdx.x;
    if (b >= b_size || u >= MAX_UNITS) return;

    const int action = actions[b];
    if (action != ACTION_TRAIL_TIGHTEN && action != ACTION_TRAIL_LOOSEN) return;

    const int idx = b * MAX_UNITS + u;
    if (unit_active[idx] == 0) return;

    const float trail_min = isv[RL_TRAIL_MIN_INDEX];
    const float trail_max = isv[RL_TRAIL_MAX_INDEX];
    const float current   = unit_trail_distance[idx];

    float updated;
    if (action == ACTION_TRAIL_TIGHTEN) {
        const float factor = isv[RL_TRAIL_TIGHTEN_FACTOR_INDEX];  // bootstrap 0.9
        updated = fmaxf(trail_min, current * factor);
    } else {  // ACTION_TRAIL_LOOSEN
        const float factor = isv[RL_TRAIL_LOOSEN_FACTOR_INDEX];   // bootstrap 1.1
        updated = fminf(trail_max, current * factor);

        // Phase 5 per-unit absolute ceiling: trail ≤ initial_r · ratio.
        // Guards against the bimodal Hold+TrailLoosen risk-shifting
        // pathology (Phase 4-A3 diagnosis 2026-06-04).
        const float initial_r = unit_initial_r[idx];
        if (initial_r > 1e-8f) {
            const float ratio = isv[RL_TRAIL_MAX_INITIAL_R_RATIO_INDEX];
            const float per_unit_cap = initial_r * ratio;
            updated = fminf(updated, per_unit_cap);
        }
    }
    unit_trail_distance[idx] = updated;
}
