pub fn scarcity_factor(fill: f32) -> f32 {
let r = fill.clamp(0.0, 1.0);
if r < 0.5 { -2.0 * (r - 1.0) } else { 1.5 - r }
}
pub fn storage_price(total_held: f32, people: usize, full_store: f32, nominal: f32) -> f32 {
let level = people as f32 * full_store.max(0.0);
if level <= 0.0 {
return nominal;
}
let mean_held = total_held.max(0.0) / people.max(1) as f32;
nominal * scarcity_factor(mean_held / full_store.max(0.0))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_papers_three_named_points() {
assert!((scarcity_factor(0.0) - 2.0).abs() < 1e-6, "empty is double");
assert!((scarcity_factor(0.5) - 1.0).abs() < 1e-6, "half is nominal");
assert!((scarcity_factor(1.0) - 0.5).abs() < 1e-6, "full is half");
}
#[test]
fn it_is_continuous_and_falls_with_fill() {
let mut previous = scarcity_factor(0.0);
for step in 1..=1000 {
let fill = step as f32 / 1000.0;
let factor = scarcity_factor(fill);
assert!(
factor <= previous + 1e-6,
"a fuller store must not be dearer: fill {fill} scored {factor} against {previous}"
);
previous = factor;
}
let left = scarcity_factor(0.5 - 1e-4);
let right = scarcity_factor(0.5 + 1e-4);
assert!(
(left - right).abs() < 1e-3,
"the branches must meet at half full: {left} against {right}"
);
}
#[test]
fn the_same_store_is_dearer_per_head() {
let one = storage_price(6.0, 1, 1.0, 1.0);
let thirty = storage_price(6.0, 30, 1.0, 1.0);
assert!((one - 0.5).abs() < 1e-6, "one person holding six: {one}");
assert!(
(thirty - 1.6).abs() < 1e-6,
"six loaves for thirty people: {thirty}"
);
assert!(thirty > one, "the bigger village must price it dearer");
}
#[test]
fn over_a_full_store_is_clamped_and_an_empty_village_is_nominal() {
assert!((storage_price(20.0, 1, 1.0, 1.0) - 0.5).abs() < 1e-6);
assert_eq!(storage_price(3.0, 0, 1.0, 1.0), 1.0, "no people, no ratio");
assert_eq!(storage_price(3.0, 4, 0.0, 1.0), 1.0, "no level, no ratio");
}
#[test]
fn a_day_a_head_is_a_range_and_a_clamp_is_not() {
let with_clamp: Vec<f32> = [0.08, 0.25, 0.71]
.iter()
.map(|held| storage_price(held * 24.0, 24, 6.0, 1.0))
.collect();
let with_day: Vec<f32> = [0.08, 0.25, 0.71]
.iter()
.map(|held| storage_price(held * 24.0, 24, 1.0, 1.0))
.collect();
let clamp_span = with_clamp[0] - with_clamp[2];
let day_span = with_day[0] - with_day[2];
assert!(
clamp_span < 0.25,
"the clamp pins the price into {clamp_span} of range, which is the saturation: {with_clamp:?}"
);
assert!(
day_span > 0.7,
"a day a head must let the same stores move the price visibly: {with_day:?}"
);
}
}