rosu-pp 4.0.1

Difficulty and performance calculation for osu!
Documentation
use crate::util::{
    difficulty::logistic,
    float_ext::FloatExt,
    traits::{IEnumerable, IOrderedEnumerable},
};

pub trait OsuStrainSkill {
    const REDUCED_SECTION_COUNT: usize = 10;
    const REDUCED_STRAIN_BASELINE: f64 = 0.75;

    fn difficulty_to_performance(difficulty: f64) -> f64 {
        difficulty_to_performance(difficulty)
    }
}

pub fn difficulty_value(
    current_strain_peaks: Vec<f64>,
    reduced_section_count: usize,
    reduced_strain_baseline: f64,
    decay_weight: f64,
) -> f64 {
    let mut difficulty = 0.0;
    let mut weight = 1.0;

    // * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
    // * These sections will not contribute to the difficulty.
    let peaks = current_strain_peaks.cs_where(|&p| p > 0.0);

    let mut strains = peaks.cs_order_descending();

    for (i, strain) in strains.iter_mut().take(reduced_section_count).enumerate() {
        let clamped = f64::from((i as f32 / reduced_section_count as f32).clamp(0.0, 1.0));
        let scale = f64::log10(lerp(1.0, 10.0, clamped));
        *strain *= lerp(reduced_strain_baseline, 1.0, scale);
    }

    for strain in strains.cs_order_descending() {
        difficulty += strain * weight;
        weight *= decay_weight;
    }

    difficulty
}

pub fn count_top_weighted_sliders(slider_strains: &[f64], difficulty_value: f64) -> f64 {
    if slider_strains.is_empty() {
        return 0.0;
    }

    // * What would the top strain be if all strain values were identical
    let consistent_top_strain = difficulty_value / 10.0;

    if FloatExt::eq(consistent_top_strain, 0.0) {
        return 0.0;
    }

    slider_strains
        .iter()
        .map(|s| logistic(*s / consistent_top_strain, 0.88, 10.0, Some(1.1)))
        .sum()
}

pub fn difficulty_to_performance(difficulty: f64) -> f64 {
    f64::powf(5.0 * f64::max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100_000.0
}

const fn lerp(start: f64, end: f64, amount: f64) -> f64 {
    start + (end - start) * amount
}