pub const HYSTERESIS: f32 = 1.15;
pub const FADE_SECONDS: f32 = 0.35;
pub fn hand_off(held_distance: f32, challenger_distance: f32, hysteresis: f32) -> bool {
if challenger_distance.is_nan() {
return false;
}
if held_distance.is_nan() {
return true;
}
preferred(held_distance.max(0.0).sqrt(), challenger_distance.max(0.0).sqrt(), hysteresis)
}
pub fn preferred(held: f32, challenger: f32, hysteresis: f32) -> bool {
if challenger.is_nan() {
return false;
}
if !held.is_finite() {
return true;
}
if held.is_nan() {
return true;
}
let margin = if hysteresis.is_nan() { 1.0 } else { hysteresis.max(1.0) };
challenger.max(0.0) * margin < held.max(0.0)
}
pub fn fade_out_factor(since: f32, fade_seconds: f32) -> f32 {
1.0 - fade_factor(since, fade_seconds)
}
pub fn fade_factor(since: f32, fade_seconds: f32) -> f32 {
if since.is_nan() || fade_seconds <= 0.0 {
return if since.is_nan() { 0.0 } else { 1.0 };
}
let s = (since / fade_seconds).clamp(0.0, 1.0);
s * s * (3.0 - 2.0 * s)
}
pub fn shadow_set(holders: &[(f32, bool)], budget: usize, hysteresis: f32) -> Vec<bool> {
let mut out: Vec<bool> = holders.iter().map(|(_, shadowed)| *shadowed).collect();
let mut count = out.iter().filter(|s| **s).count();
for shadowed in out.iter_mut().rev() {
if count <= budget {
break;
}
if *shadowed {
*shadowed = false;
count -= 1;
}
}
for shadowed in out.iter_mut() {
if count >= budget {
break;
}
if !*shadowed {
*shadowed = true;
count += 1;
}
}
loop {
let best_without = out.iter().position(|s| !*s);
let worst_with = out.iter().rposition(|s| *s);
let (Some(challenger), Some(held)) = (best_without, worst_with) else { break };
if challenger > held || !preferred(holders[held].0, holders[challenger].0, hysteresis) {
break;
}
out[challenger] = true;
out[held] = false;
}
out
}
pub fn focus_quota(size: usize, foci: usize) -> usize {
if size == 0 || foci == 0 { 0 } else { size.div_ceil(foci) }
}
pub fn quota_cutoff(values: &mut [f32], quota: usize) -> Option<f32> {
if values.is_empty() {
return None;
}
let at = quota.clamp(1, values.len()) - 1;
let (_, cutoff, _) = values.select_nth_unstable_by(at, f32::total_cmp);
Some(*cutoff)
}
pub fn shared_value(value: f32, cutoff: f32) -> f32 {
let cutoff = f64::from(cutoff.max(f32::MIN_POSITIVE));
(f64::from(value.max(0.0)) / cutoff).min(f64::from(f32::MAX)) as f32
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn a_held_light_stays_until_a_challenger_is_meaningfully_nearer() {
let held = 16.0;
assert!(!hand_off(held, 15.5, HYSTERESIS), "3.937 m does not win");
assert!(!hand_off(held, 12.25, HYSTERESIS), "3.5 m does not win");
assert!(hand_off(held, 12.0, HYSTERESIS), "3.464 m wins");
assert!(hand_off(held, 4.0, HYSTERESIS), "2 m wins");
}
#[test]
fn no_held_candidate_takes_the_nearest_at_once() {
assert!(hand_off(f32::INFINITY, 100.0, HYSTERESIS));
assert!(hand_off(f32::NAN, 100.0, HYSTERESIS));
assert!(!hand_off(1.0, f32::NAN, HYSTERESIS), "a broken distance does not displace a held light");
}
#[test]
fn a_hysteresis_of_one_is_the_old_nearest_wins() {
assert!(hand_off(16.0, 15.99, 1.0), "any nearer challenger wins at 1.0");
assert!(!hand_off(16.0, 16.01, 1.0), "a further one never does");
assert!(hand_off(16.0, 15.99, 0.5), "below 1.0 clamps to the nearest-wins rule");
}
#[test]
fn a_cross_fade_sums_to_one_at_every_point() {
let mut since = 0.0;
while since <= FADE_SECONDS {
let out = fade_out_factor(since, FADE_SECONDS);
let incoming = fade_factor(since, FADE_SECONDS);
assert!((out + incoming - 1.0).abs() < 1e-6, "at {since}: {out} + {incoming}");
assert!((0.0..=1.0).contains(&out), "{out} at {since}");
since += 0.005;
}
assert_eq!(fade_out_factor(0.0, FADE_SECONDS), 1.0, "a departing light starts whole");
assert_eq!(fade_out_factor(FADE_SECONDS, FADE_SECONDS), 0.0, "and ends gone");
}
#[test]
fn the_fade_runs_from_nothing_to_full_and_stays_there() {
assert_eq!(fade_factor(0.0, FADE_SECONDS), 0.0);
assert!((fade_factor(FADE_SECONDS / 2.0, FADE_SECONDS) - 0.5).abs() < 1e-5, "half at half time");
assert_eq!(fade_factor(FADE_SECONDS, FADE_SECONDS), 1.0);
assert_eq!(fade_factor(FADE_SECONDS * 3.0, FADE_SECONDS), 1.0);
assert_eq!(fade_factor(-1.0, FADE_SECONDS), 0.0, "a negative age is the start of the fade");
assert!(fade_factor(f32::NAN, FADE_SECONDS) == 0.0);
assert_eq!(fade_factor(0.0, 0.0), 1.0);
let mut last = 0.0;
let mut t = 0.0;
while t <= FADE_SECONDS {
let f = fade_factor(t, FADE_SECONDS);
assert!(f >= last, "the fade fell at {t}");
assert!((0.0..=1.0).contains(&f), "{f} at {t}");
last = f;
t += 0.005;
}
assert!(fade_factor(0.001, FADE_SECONDS) < 1e-4, "the start has no step");
assert!(fade_factor(FADE_SECONDS - 0.001, FADE_SECONDS) > 1.0 - 1e-4, "nor the end");
}
#[test]
fn the_shadow_budget_goes_to_the_best_ranked_and_holds_against_a_near_tie() {
let unshadowed = [(1.0, false), (2.0, false), (3.0, false), (4.0, false)];
assert_eq!(shadow_set(&unshadowed, 2, HYSTERESIS), [true, true, false, false], "fill best first");
assert_eq!(shadow_set(&unshadowed, 0, HYSTERESIS), [false; 4], "a zero budget shadows nothing");
assert_eq!(shadow_set(&unshadowed, 9, HYSTERESIS), [true; 4], "a budget past the pool shadows all");
let held = [(1.0, false), (2.0, true), (3.6, false), (4.0, true)];
assert_eq!(shadow_set(&held, 2, HYSTERESIS), [true, true, false, false]);
let near_tie = [(2.0, true), (3.6, false), (4.0, true)];
assert_eq!(shadow_set(&near_tie, 2, HYSTERESIS), [true, false, true], "a 10 % challenger waits");
let clear = [(2.0, true), (3.3, false), (4.0, true)];
assert_eq!(shadow_set(&clear, 2, HYSTERESIS), [true, true, false], "a 21 % challenger wins");
assert_eq!(shadow_set(&[(1.0, true), (2.0, true), (3.0, true)], 1, HYSTERESIS), [true, false, false]);
assert_eq!(shadow_set(&[(1.0, true), (5.0, false)], 1, 1.0), [true, false]);
}
fn pool_values(viewers: &[&[f32]], size: usize) -> Vec<Vec<f32>> {
let quota = focus_quota(size, viewers.len());
viewers
.iter()
.map(|values| {
let cutoff = quota_cutoff(&mut values.to_vec(), quota).unwrap_or(1.0);
values.iter().map(|v| shared_value(*v, cutoff)).collect()
})
.collect()
}
#[test]
fn a_viewers_order_and_ratios_survive_the_division_by_its_cutoff() {
let values = [0.4, 1.0, 1.15, 3.0, 9.0];
for cutoff in [1e-6_f32, 0.5, 1.0, 7.0, 1e6] {
let shared: Vec<f32> = values.iter().map(|v| shared_value(*v, cutoff)).collect();
assert!(shared.windows(2).all(|w| w[0] < w[1]), "order kept at cutoff {cutoff}: {shared:?}");
for pair in 0..values.len() - 1 {
let before = preferred(values[pair + 1], values[pair], HYSTERESIS);
let after = preferred(shared[pair + 1], shared[pair], HYSTERESIS);
assert_eq!(before, after, "the overlap rule's verdict is the same at cutoff {cutoff}");
}
}
}
#[test]
fn a_sparse_viewer_is_not_starved_by_a_dense_one() {
let dense = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9];
let sparse = [50.0, 60.0, 70.0];
let shared = pool_values(&[&dense, &sparse], 8);
let mut ranked: Vec<(f32, &str)> = shared[0].iter().map(|v| (*v, "dense")).collect();
ranked.extend(shared[1].iter().map(|v| (*v, "sparse")));
ranked.sort_by(|a, b| a.0.total_cmp(&b.0));
let first_eight: Vec<&str> = ranked.iter().take(8).map(|(_, who)| *who).collect();
assert_eq!(
first_eight.iter().filter(|w| **w == "sparse").count(),
3,
"all three of the sparse viewer's: {ranked:?}"
);
assert_eq!(
first_eight.iter().filter(|w| **w == "dense").count(),
5,
"the dense viewer's share and what is left"
);
}
#[test]
fn a_viewers_quota_is_exactly_the_candidates_at_or_inside_its_share() {
let values = [3.0, 1.0, 2.0, 5.0, 4.0];
let cutoff = quota_cutoff(&mut values.to_vec(), 3).unwrap();
let at_or_inside = values.iter().filter(|v| shared_value(**v, cutoff) <= 1.0).count();
assert_eq!(at_or_inside, 3, "exactly the quota is at or inside the share");
}
#[test]
fn a_cutoff_on_the_viewer_does_not_make_every_other_value_the_same() {
let values = [0.0, 1e-3, 1.0, 1e4];
let shared: Vec<f32> = values.iter().map(|v| shared_value(*v, 0.0)).collect();
assert_eq!(shared[0], 0.0);
assert!(shared.windows(2).all(|w| w[0] < w[1]), "{shared:?}");
assert!(shared.iter().all(|v| v.is_finite()));
}
#[test]
fn the_quota_is_an_equal_share_rounded_up() {
for size in 1..40 {
for foci in 1..=size.min(5) {
let quota = focus_quota(size, foci);
assert!(quota * foci >= size, "{foci} shares of {quota} cover {size} lights");
assert!(
(quota - 1) * foci < size,
"and it is the smallest share that does: {size}/{foci} gave {quota}"
);
}
}
}
}