dreamwell-matter 1.0.0

DreamMatter benchmark — GPU physics materialization demo and profiler
Documentation
// Benchmark configuration — runtime-detected defaults with safe bounds.

/// Benchmark configuration with sensible industry-standard defaults.
/// All values are auto-detected or bounded to safe ranges.
pub struct BenchmarkConfig {
    /// Window width in pixels. Default: 1920.
    pub width: u32,
    /// Window height in pixels. Default: 1080.
    pub height: u32,
    /// Benchmark duration in seconds. Default: 60.
    pub duration_secs: f32,
    /// Maximum VRAM budget in bytes. Default: 2 GiB.
    pub vram_budget_bytes: u64,
    /// VSync mode. Default: off (Immediate) for raw performance measurement.
    pub vsync: bool,

    // Phase timing (seconds into benchmark)
    /// End of warmup phase. Default: 4.0.
    pub warmup_end: f32,
    /// End of particle phase. Default: 10.0.
    pub particle_end: f32,
    /// End of convergence phase. Default: 18.0.
    pub convergence_end: f32,
    /// End of settled phase. Default: 22.0.
    pub settled_end: f32,
    /// End of materialized phase. Default: 27.0.
    pub materialized_end: f32,

    /// Maximum frame delta time (seconds). Clamps spiral-of-death. Default: 0.1 (100ms).
    pub max_dt: f32,
    /// CSV export path. Default: "dreamwell_matter_benchmark.csv".
    pub csv_path: String,
}

impl Default for BenchmarkConfig {
    fn default() -> Self {
        Self {
            width: 1920,
            height: 1080,
            duration_secs: 60.0,
            vram_budget_bytes: 8 * 1024 * 1024 * 1024, // 8 GiB default
            vsync: false,
            warmup_end: 5.0,
            particle_end: 15.0,
            convergence_end: 30.0,
            settled_end: 40.0,
            materialized_end: 52.0,
            max_dt: 0.1,
            csv_path: "dreamwell_matter_benchmark.csv".into(),
        }
    }
}

impl BenchmarkConfig {
    /// Aspect ratio (width / height). Returns 16:9 fallback if height is zero.
    pub fn aspect_ratio(&self) -> f32 {
        if self.height > 0 {
            self.width as f32 / self.height as f32
        } else {
            16.0 / 9.0
        }
    }

    /// Present mode based on vsync setting.
    pub fn present_mode(&self) -> wgpu::PresentMode {
        if self.vsync {
            wgpu::PresentMode::AutoVsync
        } else {
            wgpu::PresentMode::Immediate
        }
    }

    /// Validate configuration. Returns list of warnings.
    pub fn validate(&self) -> Vec<String> {
        let mut warnings = Vec::new();
        if self.width < 640 || self.height < 480 {
            warnings.push(format!("Resolution {}x{} below minimum 640x480", self.width, self.height));
        }
        if self.duration_secs < 5.0 {
            warnings.push(format!("Duration {:.1}s too short for meaningful measurement", self.duration_secs));
        }
        if self.warmup_end >= self.particle_end {
            warnings.push("Warmup phase overlaps particle phase".into());
        }
        if self.materialized_end >= self.duration_secs {
            warnings.push("Materialized phase extends beyond benchmark duration".into());
        }
        if self.max_dt <= 0.0 || self.max_dt > 1.0 {
            warnings.push(format!("max_dt {:.3}s outside safe range (0, 1]", self.max_dt));
        }
        warnings
    }
}

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

    #[test]
    fn default_config_is_valid() {
        let config = BenchmarkConfig::default();
        let warnings = config.validate();
        assert!(warnings.is_empty(), "Default config should have no warnings: {:?}", warnings);
    }

    #[test]
    fn aspect_ratio_correct() {
        let config = BenchmarkConfig::default();
        let aspect = config.aspect_ratio();
        assert!((aspect - 16.0 / 9.0).abs() < 0.01);
    }

    #[test]
    fn zero_height_fallback() {
        let config = BenchmarkConfig { height: 0, ..Default::default() };
        assert!((config.aspect_ratio() - 16.0 / 9.0).abs() < 0.01);
    }

    #[test]
    fn validation_catches_bad_resolution() {
        let config = BenchmarkConfig { width: 100, height: 100, ..Default::default() };
        let warnings = config.validate();
        assert!(!warnings.is_empty());
    }

    #[test]
    fn validation_catches_short_duration() {
        let config = BenchmarkConfig { duration_secs: 2.0, ..Default::default() };
        let warnings = config.validate();
        assert!(!warnings.is_empty());
    }

    #[test]
    fn validation_catches_overlapping_phases() {
        let config = BenchmarkConfig { warmup_end: 15.0, particle_end: 10.0, ..Default::default() };
        let warnings = config.validate();
        assert!(!warnings.is_empty());
    }

    #[test]
    fn present_mode_selection() {
        assert_eq!(BenchmarkConfig::default().present_mode(), wgpu::PresentMode::Immediate);
        assert_eq!(BenchmarkConfig { vsync: true, ..Default::default() }.present_mode(), wgpu::PresentMode::AutoVsync);
    }

    #[test]
    fn vram_budget_is_8gib() {
        let config = BenchmarkConfig::default();
        assert_eq!(config.vram_budget_bytes, 8 * 1024 * 1024 * 1024);
    }

    #[test]
    fn max_dt_is_safe() {
        let config = BenchmarkConfig::default();
        assert!(config.max_dt > 0.0);
        assert!(config.max_dt <= 0.5); // never allow > 500ms frame
    }

    #[test]
    fn phase_boundaries_monotonic() {
        let config = BenchmarkConfig::default();
        assert!(config.warmup_end < config.particle_end);
        assert!(config.particle_end < config.convergence_end);
        assert!(config.convergence_end < config.settled_end);
        assert!(config.settled_end < config.materialized_end);
        assert!(config.materialized_end < config.duration_secs);
    }

    #[test]
    fn validation_catches_bad_max_dt() {
        let config = BenchmarkConfig { max_dt: -1.0, ..Default::default() };
        assert!(!config.validate().is_empty());
    }

    #[test]
    fn csv_path_is_nonempty() {
        let config = BenchmarkConfig::default();
        assert!(!config.csv_path.is_empty());
    }
}