diff --git a/crates/ml-alpha/cuda/rl_ppo_clip_controller.cu b/crates/ml-alpha/cuda/rl_ppo_clip_controller.cu index 97416164b..9422691d0 100644 --- a/crates/ml-alpha/cuda/rl_ppo_clip_controller.cu +++ b/crates/ml-alpha/cuda/rl_ppo_clip_controller.cu @@ -83,6 +83,16 @@ extern "C" __global__ void rl_ppo_clip_controller( float eps_target = eps_prev * ratio; eps_target = fmaxf(EPS_MIN, fminf(eps_target, EPS_MAX)); + // First-observation replace-directly per + // `pearl_first_observation_bootstrap`. See rl_target_tau_controller + // for the rationale — the Wiener blend's bootstrap contamination + // is what produced the step-7 l_v=249,782 spike in alpha-rl-qzstj + // (R9 audit 2026-05-23). + if (eps_prev == EPS_BOOTSTRAP) { + isv[RL_PPO_CLIP_INDEX] = eps_target; + return; + } + // Wiener-α blend with floor per pearl_wiener_alpha_floor_for_nonstationary. const float a = fmaxf(alpha, WIENER_ALPHA_FLOOR); float eps_new = (1.0f - a) * eps_prev + a * eps_target; diff --git a/crates/ml-alpha/cuda/rl_reward_scale_controller.cu b/crates/ml-alpha/cuda/rl_reward_scale_controller.cu index dea590886..728cebceb 100644 --- a/crates/ml-alpha/cuda/rl_reward_scale_controller.cu +++ b/crates/ml-alpha/cuda/rl_reward_scale_controller.cu @@ -107,6 +107,19 @@ extern "C" __global__ void rl_reward_scale_controller( float target = 1.0f / denom; target = fmaxf(REWARD_SCALE_MIN, fminf(target, REWARD_SCALE_MAX)); + // First-observation replace-directly per + // `pearl_first_observation_bootstrap`. This is the load-bearing + // fix for the step-7 contamination cascade in alpha-rl-qzstj + // (R9 audit 2026-05-23): with prev=1.0 (bootstrap) and target= + // 1/832=0.0012, the Wiener blend at α=0.4 produced scale=0.600 + // — applying 0.6 × $832 = $499 to V regression → MSE = 249,000. + // Replace-directly: scale = 0.0012 immediately → V target = 1.0, + // MSE = 1.0. Three orders of magnitude reduction. + if (prev == REWARD_SCALE_BOOTSTRAP) { + isv[RL_REWARD_SCALE_INDEX] = target; + return; + } + // Wiener-α blend with floor per pearl_wiener_alpha_floor_for_nonstationary. const float a = fmaxf(alpha_step, WIENER_ALPHA_FLOOR); float out = (1.0f - a) * prev + a * target; diff --git a/crates/ml-alpha/cuda/rl_rollout_steps_controller.cu b/crates/ml-alpha/cuda/rl_rollout_steps_controller.cu index 334fb5dcf..2dd9cf5d3 100644 --- a/crates/ml-alpha/cuda/rl_rollout_steps_controller.cu +++ b/crates/ml-alpha/cuda/rl_rollout_steps_controller.cu @@ -92,6 +92,13 @@ extern "C" __global__ void rl_rollout_steps_controller( float target = prev * scale; target = fmaxf(ROLLOUT_MIN, fminf(target, ROLLOUT_MAX)); + // First-observation replace-directly per + // `pearl_first_observation_bootstrap`. + if (prev == ROLLOUT_BOOTSTRAP) { + isv[RL_N_ROLLOUT_STEPS_INDEX] = target; + return; + } + // Wiener-α blend with floor per pearl_wiener_alpha_floor_for_nonstationary. const float a = fmaxf(alpha, WIENER_ALPHA_FLOOR); float out = (1.0f - a) * prev + a * target; diff --git a/crates/ml-alpha/cuda/rl_target_tau_controller.cu b/crates/ml-alpha/cuda/rl_target_tau_controller.cu index c959e8d70..680dc0c88 100644 --- a/crates/ml-alpha/cuda/rl_target_tau_controller.cu +++ b/crates/ml-alpha/cuda/rl_target_tau_controller.cu @@ -85,6 +85,20 @@ extern "C" __global__ void rl_target_tau_controller( float tau_target = tau_prev * ratio; tau_target = fmaxf(TAU_MIN, fminf(tau_target, TAU_MAX)); + // First-observation replace-directly (R9 audit 2026-05-23): if + // prev is still exactly the hardcoded bootstrap value, this is + // the FIRST per-step fire that has real input signal. Per + // `pearl_first_observation_bootstrap`, "first observation + // replaces directly" — the Wiener blend `(1-α)·bootstrap + + // α·target` would leave 60% bootstrap contamination, distorting + // the controller's first emit. Write target directly instead. + // Subsequent steps (where prev has drifted off bootstrap via + // earlier blends) take the Wiener path below. + if (tau_prev == TAU_BOOTSTRAP) { + isv[RL_TARGET_TAU_INDEX] = tau_target; + return; + } + // Wiener-α blend with floor per pearl_wiener_alpha_floor_for_nonstationary. const float a = fmaxf(alpha, WIENER_ALPHA_FLOOR); float tau_new = (1.0f - a) * tau_prev + a * tau_target;