diag(rl): emit v9 eval_warmup state to JSONL + cleanup lints

Diag: surfaces `risk_stack.eval_warmup.{remaining,active,blend,
floor_*,target_*}` so the v9 defensive-warmup window is observable
in diag.jsonl. `remaining` is the counter; `blend` is the
defensive-vs-normal mix coefficient (1.0 = full defensive, 0.0 =
normal); `floor_*` reflect the LIVE override values (read AFTER the
warmup kernel ran). Pre-warmup the kernel is a no-op (remaining=-1),
so v9 train-phase diag is bit-identical to v8.

Cleanup: tightens unreachable_pub items in tests/behavioral/* and
tests/sp5_producer_unit_tests.rs (pub → pub(crate)), removes
unused_mut on 6 sp5 scratch buffers, renames unused `step` loop
counter in alpha_baseline example, and explicitly discards an
intentionally-no-op `Command::assert` in cli_integration_test.
Reduces lint count by ~25; remaining 3 dead_code warnings flag
SP15 Phase 2A behavioral scaffolding (Phase 2B never landed —
deliberate signal, not noise).

Pre-existing pearl per feedback_no_hiding: do NOT suppress these
with #[allow]; the warnings ARE the design call surface.
This commit is contained in:
jgrusewski
2026-05-31 02:12:04 +02:00
parent 0c8cb6ad5b
commit baf971ba54
7 changed files with 72 additions and 16 deletions

View File

@@ -215,10 +215,11 @@ fn test_tune_status_invalid_uuid() {
#[ignore = "Ignored because it tries to launch terminal UI"]
fn test_dashboard_command() {
let mut cmd = Command::cargo_bin("fxt").unwrap();
cmd.arg("dashboard")
let _ = cmd.arg("dashboard")
.timeout(std::time::Duration::from_secs(2))
.assert();
// Will timeout or fail trying to connect, but that's expected
// Will timeout or fail trying to connect, but that's expected — assert result
// intentionally discarded; this test verifies arg parsing reaches launch.
}
/// Test version flag

View File

@@ -110,6 +110,15 @@ use ml_alpha::rl::isv_slots::{
RL_KELLY_SAFETY_FRAC_INDEX, RL_KELLY_MIN_TRADES_FOR_RELEASE_INDEX,
RL_TRAIL_TIGHTEN_FACTOR_INDEX, RL_TRAIL_LOOSEN_FACTOR_INDEX,
RL_CUMULATIVE_DONES_INDEX,
// v9 defensive eval-boundary calibration (spec
// 2026-05-31-v9-defensive-eval-boundary-calibration.md).
RL_EVAL_WARMUP_REMAINING_INDEX, RL_EVAL_WARMUP_STEPS_CONFIG_INDEX,
RL_EVAL_WARMUP_DECAY_STEPS_CONFIG_INDEX,
RL_EVAL_KELLY_SAFETY_DEFENSIVE_INDEX, RL_EVAL_IQN_TAU_MIN_DEFENSIVE_INDEX,
RL_EVAL_ENTROPY_COEF_MIN_DEFENSIVE_INDEX, RL_EVAL_PPO_CLIP_EPS_MIN_DEFENSIVE_INDEX,
RL_EVAL_KELLY_SAFETY_NORMAL_INDEX, RL_EVAL_IQN_TAU_MIN_NORMAL_INDEX,
RL_EVAL_ENTROPY_COEF_MIN_NORMAL_INDEX, RL_EVAL_PPO_CLIP_EPS_MIN_NORMAL_INDEX,
RL_ENTROPY_COEF_MIN_INDEX, RL_PPO_CLIP_EPS_MIN_INDEX,
};
use ml_alpha::trainer::diag_staging::DiagStaging;
use ml_alpha::trainer::integrated::{
@@ -760,6 +769,22 @@ fn main() -> Result<()> {
})
};
// v9 eval_warmup pre-compute (json! macro can't take Rust blocks/if-else).
let eval_warmup_remaining = isv[RL_EVAL_WARMUP_REMAINING_INDEX];
let eval_warmup_decay_steps = isv[RL_EVAL_WARMUP_DECAY_STEPS_CONFIG_INDEX];
let eval_warmup_active = if eval_warmup_remaining > 0.0 { 1.0_f32 } else { 0.0_f32 };
let eval_warmup_blend = if eval_warmup_remaining < 0.0 {
0.0_f32
} else if eval_warmup_remaining > eval_warmup_decay_steps {
1.0_f32
} else if eval_warmup_decay_steps > 0.0 {
eval_warmup_remaining / eval_warmup_decay_steps
} else if eval_warmup_remaining > 0.0 {
1.0_f32
} else {
0.0_f32
};
let record = json!({
"step": step,
"elapsed_s": t_start.elapsed().as_secs_f32(),
@@ -886,6 +911,36 @@ fn main() -> Result<()> {
"tighten": isv[RL_TRAIL_TIGHTEN_FACTOR_INDEX],
"loosen": isv[RL_TRAIL_LOOSEN_FACTOR_INDEX],
},
// v9 — defensive eval-boundary calibration ([[pearl_adaptive_carryover_discipline]]).
// `remaining` is the per-step counter: ≥ decay_steps → full
// defensive blend=1.0; (0, decay_steps] → linear decay to 0;
// 0 → final boundary write of normal values; -1 → kernel
// no-op (post-warmup steady state). The four `floor_*`
// entries are the LIVE values (read AFTER the warmup
// kernel ran this step, so they reflect any override).
"eval_warmup": {
"remaining": eval_warmup_remaining,
"active": eval_warmup_active,
"blend": eval_warmup_blend,
"warmup_steps_config": isv[RL_EVAL_WARMUP_STEPS_CONFIG_INDEX],
"decay_steps_config": eval_warmup_decay_steps,
"floor_kelly_safety": isv[RL_KELLY_SAFETY_FRAC_INDEX],
"floor_iqn_tau_min": isv[RL_IQN_ACTION_TAU_MIN_INDEX],
"floor_entropy_min": isv[RL_ENTROPY_COEF_MIN_INDEX],
"floor_ppo_eps_min": isv[RL_PPO_CLIP_EPS_MIN_INDEX],
"target_defensive": {
"kelly_safety": isv[RL_EVAL_KELLY_SAFETY_DEFENSIVE_INDEX],
"iqn_tau_min": isv[RL_EVAL_IQN_TAU_MIN_DEFENSIVE_INDEX],
"entropy_min": isv[RL_EVAL_ENTROPY_COEF_MIN_DEFENSIVE_INDEX],
"ppo_eps_min": isv[RL_EVAL_PPO_CLIP_EPS_MIN_DEFENSIVE_INDEX],
},
"target_normal": {
"kelly_safety": isv[RL_EVAL_KELLY_SAFETY_NORMAL_INDEX],
"iqn_tau_min": isv[RL_EVAL_IQN_TAU_MIN_NORMAL_INDEX],
"entropy_min": isv[RL_EVAL_ENTROPY_COEF_MIN_NORMAL_INDEX],
"ppo_eps_min": isv[RL_EVAL_PPO_CLIP_EPS_MIN_NORMAL_INDEX],
},
},
// Calibration diagnostics — atom-span vs. Bellman dynamic
// bound. Per docs/superpowers/specs/2026-05-31-c51-atom-
// span-math-validation.md: the structural constraint is

View File

@@ -907,7 +907,7 @@ fn main() -> Result<()> {
.context("reset vol_obs min slot")?;
}
// Lockstep step loop.
for step in 0..cli.horizon {
for _ in 0..cli.horizon {
// Gather current states (CPU; <100μs for N=500).
let mut batched_states_host = vec![0.0_f32; n_par * STATE_DIM];
for i in 0..n_par {

View File

@@ -9,7 +9,7 @@ use ml::cuda_pipeline::lob_bar::LobBar;
use std::collections::HashMap;
#[derive(Debug, Default)]
pub struct BehavioralResult {
pub(crate) struct BehavioralResult {
/// Action distribution keys: "hold", "flat", "long_quarter",
/// "long_half", "long_full", "short_quarter", "short_half", "short_full".
pub action_dist: HashMap<&'static str, f32>,
@@ -27,7 +27,7 @@ pub struct BehavioralResult {
/// each Phase 2B test rewrites the body to call the trainer methods it
/// requires. The signature here is the load-bearing piece — keep it stable.
#[allow(dead_code, unused_variables)]
pub fn evaluate_policy_on_market(
pub(crate) fn evaluate_policy_on_market(
// Phase 2B swaps `&mut ()` for `&mut GpuDqnTrainer` once the per-test
// trainer methods land. Until then the unit param keeps the signature
// callable in tests that don't yet exercise the trainer path.

View File

@@ -5,10 +5,10 @@
use ml::cuda_pipeline::lob_bar::LobBar;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MagBucket { Quarter, Half, Full }
pub(crate) enum MagBucket { Quarter, Half, Full }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OracleAction {
pub(crate) enum OracleAction {
Hold,
Long(MagBucket),
Short(MagBucket),
@@ -16,12 +16,12 @@ pub enum OracleAction {
}
/// Oracle for flat markets: always Hold.
pub fn flat_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
pub(crate) fn flat_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
vec![OracleAction::Hold; bars.len()]
}
/// Oracle for drift markets: take direction-of-trend, mag = Half.
pub fn drift_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
pub(crate) fn drift_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
let mut actions = Vec::with_capacity(bars.len());
actions.push(OracleAction::Hold); // first bar: no prior
for w in bars.windows(2) {
@@ -38,7 +38,7 @@ pub fn drift_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
}
/// Oracle for OU markets: reverse at ±2σ extremes.
pub fn ou_market_oracle(bars: &[LobBar], mu: f32, sigma: f32) -> Vec<OracleAction> {
pub(crate) fn ou_market_oracle(bars: &[LobBar], mu: f32, sigma: f32) -> Vec<OracleAction> {
bars.iter().map(|b| {
let z = (b.price - mu) / sigma.max(1e-6);
if z > 2.0 { OracleAction::Short(MagBucket::Half) }

View File

@@ -6,7 +6,7 @@ use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
/// Flat market: constant base price + Gaussian noise.
pub fn flat_market(n: usize, noise_sigma: f32, mean_spread: f32, seed: u64) -> Vec<LobBar> {
pub(crate) fn flat_market(n: usize, noise_sigma: f32, mean_spread: f32, seed: u64) -> Vec<LobBar> {
let mut rng = StdRng::seed_from_u64(seed);
(0..n).map(|_| {
let noise: f32 = rng.gen_range(-1.0..1.0) * noise_sigma;
@@ -18,7 +18,7 @@ pub fn flat_market(n: usize, noise_sigma: f32, mean_spread: f32, seed: u64) -> V
}
/// Drift market: drift μ + noise σ.
pub fn drift_market(n: usize, mu: f32, sigma: f32, mean_spread: f32, seed: u64) -> Vec<LobBar> {
pub(crate) fn drift_market(n: usize, mu: f32, sigma: f32, mean_spread: f32, seed: u64) -> Vec<LobBar> {
let mut rng = StdRng::seed_from_u64(seed);
let mut price: f32 = 4500.0;
(0..n).map(|_| {
@@ -31,7 +31,7 @@ pub fn drift_market(n: usize, mu: f32, sigma: f32, mean_spread: f32, seed: u64)
}
/// OU process: mean-reverting around equilibrium.
pub fn ou_market(n: usize, theta: f32, sigma_eq: f32, mu: f32,
pub(crate) fn ou_market(n: usize, theta: f32, sigma_eq: f32, mu: f32,
mean_spread: f32, seed: u64) -> Vec<LobBar> {
let mut rng = StdRng::seed_from_u64(seed);
let mut price: f32 = mu;
@@ -45,7 +45,7 @@ pub fn ou_market(n: usize, theta: f32, sigma_eq: f32, mu: f32,
}
/// Regime-switch: Markov chain over `n_regimes`, each with (mu, sigma).
pub fn regime_switch_market(
pub(crate) fn regime_switch_market(
n: usize,
regime_params: &[(f32, f32)],
transition_matrix: &[Vec<f32>],

View File

@@ -1980,7 +1980,7 @@ fn pearl_1_ext_num_atoms_threshold_cascade() {
let scratch_size: usize = 4;
let base: i32 = 0;
let mut scratch = unsafe { MappedF32Buffer::new(scratch_size) }.expect("scratch alloc");
let scratch = unsafe { MappedF32Buffer::new(scratch_size) }.expect("scratch alloc");
let isv_dev = isv_buf.dev_ptr;
let scr_dev = scratch.dev_ptr;
@@ -2048,7 +2048,7 @@ fn pearl_1_ext_num_atoms_exact_threshold_falls_to_next_branch() {
let scratch_size: usize = 4;
let base: i32 = 0;
let mut scratch = unsafe { MappedF32Buffer::new(scratch_size) }.expect("scratch alloc");
let scratch = unsafe { MappedF32Buffer::new(scratch_size) }.expect("scratch alloc");
let isv_dev = isv_buf.dev_ptr;
let scr_dev = scratch.dev_ptr;