Skip to main content

truce_params/
range.rs

1/// Defines how a parameter maps between plain and normalized values.
2#[derive(Clone, Debug)]
3pub enum ParamRange {
4    Linear { min: f64, max: f64 },
5    Logarithmic { min: f64, max: f64 },
6    Discrete { min: i64, max: i64 },
7    Enum { count: usize },
8}
9
10impl ParamRange {
11    /// Map a plain value to 0.0–1.0.
12    pub fn normalize(&self, plain: f64) -> f64 {
13        match self {
14            Self::Linear { min, max } => ((plain - min) / (max - min)).clamp(0.0, 1.0),
15            Self::Logarithmic { min, max } => {
16                if *min <= 0.0 || *max <= 0.0 {
17                    return 0.0;
18                }
19                let min_log = min.ln();
20                let max_log = max.ln();
21                ((plain.ln() - min_log) / (max_log - min_log)).clamp(0.0, 1.0)
22            }
23            Self::Discrete { min, max } => {
24                ((plain - *min as f64) / (*max as f64 - *min as f64)).clamp(0.0, 1.0)
25            }
26            Self::Enum { count } => {
27                if *count <= 1 {
28                    return 0.0;
29                }
30                (plain / (*count as f64 - 1.0)).clamp(0.0, 1.0)
31            }
32        }
33    }
34
35    /// Map 0.0–1.0 back to a plain value.
36    pub fn denormalize(&self, normalized: f64) -> f64 {
37        let n = normalized.clamp(0.0, 1.0);
38        match self {
39            Self::Linear { min, max } => min + n * (max - min),
40            Self::Logarithmic { min, max } => {
41                if *min <= 0.0 || *max <= 0.0 {
42                    return *min;
43                }
44                let min_log = min.ln();
45                let max_log = max.ln();
46                (min_log + n * (max_log - min_log)).exp()
47            }
48            Self::Discrete { min, max } => {
49                ((*min as f64) + n * (*max as f64 - *min as f64)).round()
50            }
51            Self::Enum { count } => (n * (*count as f64 - 1.0)).round(),
52        }
53    }
54
55    /// Plain-value minimum.
56    pub fn min(&self) -> f64 {
57        match self {
58            Self::Linear { min, .. } | Self::Logarithmic { min, .. } => *min,
59            Self::Discrete { min, .. } => *min as f64,
60            Self::Enum { .. } => 0.0,
61        }
62    }
63
64    /// Plain-value maximum.
65    pub fn max(&self) -> f64 {
66        match self {
67            Self::Linear { max, .. } | Self::Logarithmic { max, .. } => *max,
68            Self::Discrete { max, .. } => *max as f64,
69            Self::Enum { count } => (*count as f64 - 1.0).max(0.0),
70        }
71    }
72
73    /// Number of discrete steps (0 = continuous).
74    pub fn step_count(&self) -> u32 {
75        match self {
76            Self::Linear { .. } | Self::Logarithmic { .. } => 0,
77            Self::Discrete { min, max } => (*max - *min) as u32,
78            Self::Enum { count } => (*count as u32).saturating_sub(1),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn linear_round_trip() {
89        let range = ParamRange::Linear {
90            min: -60.0,
91            max: 24.0,
92        };
93        for plain in [-60.0, -30.0, 0.0, 12.0, 24.0] {
94            let norm = range.normalize(plain);
95            let back = range.denormalize(norm);
96            assert!(
97                (back - plain).abs() < 1e-10,
98                "plain={plain}, norm={norm}, back={back}"
99            );
100        }
101    }
102
103    #[test]
104    fn log_round_trip() {
105        let range = ParamRange::Logarithmic {
106            min: 20.0,
107            max: 20000.0,
108        };
109        for plain in [20.0, 100.0, 1000.0, 10000.0, 20000.0] {
110            let norm = range.normalize(plain);
111            let back = range.denormalize(norm);
112            assert!(
113                (back - plain).abs() < 0.01,
114                "plain={plain}, norm={norm}, back={back}"
115            );
116        }
117    }
118
119    #[test]
120    fn enum_round_trip() {
121        let range = ParamRange::Enum { count: 4 };
122        for idx in 0..4 {
123            let norm = range.normalize(idx as f64);
124            let back = range.denormalize(norm);
125            assert_eq!(back as usize, idx);
126        }
127    }
128}