diff --git a/crates/ml/src/trainers/dqn/trainer/action.rs b/crates/ml/src/trainers/dqn/trainer/action.rs index 4db11fcd9..4e7171fbf 100644 --- a/crates/ml/src/trainers/dqn/trainer/action.rs +++ b/crates/ml/src/trainers/dqn/trainer/action.rs @@ -164,8 +164,18 @@ impl DQNTrainer { use rand::rngs::StdRng; let epsilon = self.get_epsilon().await? as f32; let mut rng = StdRng::seed_from_u64(0xAC7_DEF0); - if rng.gen::() < epsilon { Ok(rng.gen_range(0..5)) } - else { + if rng.gen::() < epsilon { + // Task 2.5 Bug #2: sample 4-branch factored action space + // (dir × mag × ord × urg = 3×3×3×3 = 81), matching MEMORY.md + // DQN architecture (updated 2026-04-08). The legacy 0..5 range + // predated the 4-branch refactor and was a latent foot-gun. + // Encoding: action = dir*27 + mag*9 + ord*3 + urg. + let dir = rng.gen_range(0..3usize); + let mag = rng.gen_range(0..3usize); + let ord = rng.gen_range(0..3usize); + let urg = rng.gen_range(0..3usize); + Ok(dir * 27 + mag * 9 + ord * 3 + urg) + } else { let agent = self.agent.read().await; let indices = agent.batch_greedy_actions(state)?; let best_action = *indices.first().ok_or_else(|| anyhow::anyhow!("argmax on empty Q-values"))? as usize;