dreamwell-matter 1.0.0

DreamMatter benchmark — GPU physics materialization demo and profiler
Documentation
// Benchmark state machine — timelapse through DreamMatter phases.
// Phase boundaries are configurable via BenchmarkConfig.

use crate::config::BenchmarkConfig;

/// DreamMatter benchmark phase.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BenchmarkPhase {
    Warmup,
    Particle,
    Convergence,
    Settled,
    Materialized,
    Dissolution,
    Complete,
}

impl BenchmarkPhase {
    pub fn label(self) -> &'static str {
        match self {
            Self::Warmup => "Warmup",
            Self::Particle => "Particle",
            Self::Convergence => "Convergence",
            Self::Settled => "Settled",
            Self::Materialized => "Materialized",
            Self::Dissolution => "Dissolution",
            Self::Complete => "Complete",
        }
    }
}

/// Benchmark state: tracks elapsed time, current phase, and phase transitions.
pub struct BenchmarkState {
    elapsed: f32,
    duration: f32,
    phase: BenchmarkPhase,
    active: bool,
    phase_transitions: Vec<(f32, BenchmarkPhase)>,
    // Phase boundary times (from config)
    warmup_end: f32,
    particle_end: f32,
    convergence_end: f32,
    settled_end: f32,
    materialized_end: f32,
}

impl BenchmarkState {
    pub fn new(duration: f32) -> Self {
        let config = BenchmarkConfig::default();
        Self {
            elapsed: 0.0,
            duration,
            phase: BenchmarkPhase::Warmup,
            active: false,
            phase_transitions: Vec::with_capacity(8),
            warmup_end: config.warmup_end,
            particle_end: config.particle_end,
            convergence_end: config.convergence_end,
            settled_end: config.settled_end,
            materialized_end: config.materialized_end,
        }
    }

    pub fn from_config(config: &BenchmarkConfig) -> Self {
        Self {
            elapsed: 0.0,
            duration: config.duration_secs,
            phase: BenchmarkPhase::Warmup,
            active: false,
            phase_transitions: Vec::with_capacity(8),
            warmup_end: config.warmup_end,
            particle_end: config.particle_end,
            convergence_end: config.convergence_end,
            settled_end: config.settled_end,
            materialized_end: config.materialized_end,
        }
    }

    pub fn start(&mut self) {
        self.elapsed = 0.0;
        self.active = true;
        self.phase = BenchmarkPhase::Warmup;
        self.phase_transitions.clear();
        self.phase_transitions.push((0.0, BenchmarkPhase::Warmup));
    }

    pub fn advance(&mut self, dt: f32) {
        if !self.active {
            return;
        }
        self.elapsed += dt;

        let new_phase = if self.elapsed < self.warmup_end {
            BenchmarkPhase::Warmup
        } else if self.elapsed < self.particle_end {
            BenchmarkPhase::Particle
        } else if self.elapsed < self.convergence_end {
            BenchmarkPhase::Convergence
        } else if self.elapsed < self.settled_end {
            BenchmarkPhase::Settled
        } else if self.elapsed < self.materialized_end {
            BenchmarkPhase::Materialized
        } else if self.elapsed < self.duration {
            BenchmarkPhase::Dissolution
        } else {
            BenchmarkPhase::Complete
        };

        if new_phase != self.phase {
            log::info!(
                "Phase transition: {} -> {} at {:.1}s",
                self.phase.label(), new_phase.label(), self.elapsed
            );
            self.phase_transitions.push((self.elapsed, new_phase));
            self.phase = new_phase;
        }

        if self.phase == BenchmarkPhase::Complete {
            self.active = false;
        }
    }

    pub fn phase(&self) -> BenchmarkPhase {
        self.phase
    }

    pub fn phase_label(&self) -> &str {
        self.phase.label()
    }

    pub fn elapsed(&self) -> f32 {
        self.elapsed
    }

    pub fn is_complete(&self) -> bool {
        self.phase == BenchmarkPhase::Complete
    }

    pub fn transitions(&self) -> &[(f32, BenchmarkPhase)] {
        &self.phase_transitions
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn phase_progression() {
        let config = BenchmarkConfig::default();
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        assert_eq!(state.phase(), BenchmarkPhase::Warmup);

        state.advance(6.0);
        assert_eq!(state.phase(), BenchmarkPhase::Particle);

        state.advance(10.0);
        assert_eq!(state.phase(), BenchmarkPhase::Convergence);

        state.advance(15.0);
        assert_eq!(state.phase(), BenchmarkPhase::Settled);

        state.advance(10.0);
        assert_eq!(state.phase(), BenchmarkPhase::Materialized);

        state.advance(13.0);
        assert_eq!(state.phase(), BenchmarkPhase::Dissolution);

        state.advance(10.0);
        assert_eq!(state.phase(), BenchmarkPhase::Complete);
        assert!(state.is_complete());
    }

    #[test]
    fn transitions_logged() {
        let config = BenchmarkConfig::default();
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        for _ in 0..700 {
            state.advance(0.1);
        }
        assert_eq!(state.transitions().len(), 7, "Expected 7 transitions, got {}", state.transitions().len());
    }

    #[test]
    fn not_started_is_noop() {
        let mut state = BenchmarkState::new(30.0);
        state.advance(10.0);
        assert_eq!(state.phase(), BenchmarkPhase::Warmup);
        assert_eq!(state.elapsed(), 0.0);
    }

    #[test]
    fn restart_resets_state() {
        let mut state = BenchmarkState::new(30.0);
        state.start();
        state.advance(15.0);
        state.start(); // restart
        assert_eq!(state.phase(), BenchmarkPhase::Warmup);
        assert_eq!(state.elapsed(), 0.0);
        assert_eq!(state.transitions().len(), 1);
    }

    #[test]
    fn zero_dt_doesnt_transition() {
        let mut state = BenchmarkState::new(30.0);
        state.start();
        state.advance(0.0);
        assert_eq!(state.phase(), BenchmarkPhase::Warmup);
    }

    #[test]
    fn phase_labels_nonempty() {
        let phases = [
            BenchmarkPhase::Warmup, BenchmarkPhase::Particle,
            BenchmarkPhase::Convergence, BenchmarkPhase::Settled,
            BenchmarkPhase::Materialized, BenchmarkPhase::Dissolution,
            BenchmarkPhase::Complete,
        ];
        for p in phases {
            assert!(!p.label().is_empty());
        }
    }

    #[test]
    fn complete_stops_advancing() {
        let config = BenchmarkConfig::default();
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        for _ in 0..700 { state.advance(0.1); }
        assert!(state.is_complete());
        let elapsed_at_complete = state.elapsed();
        state.advance(10.0);
        assert_eq!(state.elapsed(), elapsed_at_complete);
    }

    #[test]
    fn negative_dt_ignored() {
        let config = BenchmarkConfig::default();
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        state.advance(-5.0);
        assert_eq!(state.phase(), BenchmarkPhase::Warmup);
    }

    #[test]
    fn large_dt_jumps_to_complete() {
        let config = BenchmarkConfig::default();
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        state.advance(100.0);
        assert!(state.is_complete());
    }

    #[test]
    fn elapsed_tracks_cumulative() {
        let config = BenchmarkConfig::default();
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        state.advance(1.0);
        state.advance(2.0);
        state.advance(3.0);
        assert!((state.elapsed() - 6.0).abs() < 0.001);
    }

    #[test]
    fn custom_config_phase_boundaries() {
        let config = BenchmarkConfig {
            warmup_end: 2.0,
            particle_end: 5.0,
            convergence_end: 8.0,
            settled_end: 10.0,
            materialized_end: 12.0,
            duration_secs: 15.0,
            ..Default::default()
        };
        let mut state = BenchmarkState::from_config(&config);
        state.start();
        state.advance(3.0);
        assert_eq!(state.phase(), BenchmarkPhase::Particle);
        state.advance(3.0);
        assert_eq!(state.phase(), BenchmarkPhase::Convergence);
    }
}