Skip to main content

git_koi/
markings.rs

1//! How strongly the current and longest streaks show on the koi.
2
3use crate::koi::{KoiDevelopment, RecentStreak};
4
5const STREAK_FIN_FULL_WEEKS: f64 = 13.0;
6const FADE_RATE_PER_WEEK: f64 = 0.5;
7const FADE_WEEKS: f64 = 6.5;
8/// Shapes the longest-streak log curve: smaller values rise faster at first.
9const RECORD_CURVE_WEEKS: f64 = 0.6;
10const NORMAL_COLOR_WEEKS: f64 = 4.0;
11const VIVID_COLOR_WEEKS: f64 = 52.0;
12/// A placeholder for when glimmer reaches full strength.
13const FULL_GLIMMER_WEEKS: f64 = 208.0;
14
15/// Streak mark strengths for the renderer.
16#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct StreakMarks {
18    /// Share of fin pixels in the body's streak color, from zero through one.
19    pub streak_fin_strength: f64,
20    /// Language color vibrancy: 0 dull, 1 normal, 2 extra bright and vivid.
21    pub color_vibrancy: f64,
22    /// Gin-rin glimmer on the color patches, from zero through one.
23    pub glimmer_strength: f64,
24}
25
26/// Derive the streak marks from a development, measuring time against its
27/// observation date.
28///
29/// Streak fins build linearly to full after STREAK_FIN_FULL_WEEKS of the recent
30/// streak. Once it ends, they fade from the strength they reached on a negative
31/// exponential that reaches zero FADE_WEEKS after the recorded end date. The
32/// longest streak sets the other marks on one log curve: colors go from dull to
33/// normal by NORMAL_COLOR_WEEKS and to vivid by VIVID_COLOR_WEEKS, then glimmer
34/// builds until FULL_GLIMMER_WEEKS. The longest streak never shrinks, so neither do
35/// these marks. This reads no clock.
36pub fn calculate_streak_marks(koi_development: &KoiDevelopment) -> StreakMarks {
37    let streak_fin_strength = match &koi_development.streaks.recent {
38        None => 0.0,
39        Some(RecentStreak::Current(streak_weeks)) => calculate_fin_build(streak_weeks.len()),
40        Some(RecentStreak::Ended {
41            weeks: streak_weeks,
42            ended_on,
43        }) => {
44            let weeks_since_end =
45                (koi_development.observed_on - *ended_on).num_days().max(0) as f64 / 7.0;
46            calculate_fin_build(streak_weeks.len()) * calculate_fade(weeks_since_end)
47        }
48    };
49    let record_strength = calculate_record_strength(koi_development.streaks.longest.len() as f64);
50    let normal_strength = calculate_record_strength(NORMAL_COLOR_WEEKS);
51    let vivid_strength = calculate_record_strength(VIVID_COLOR_WEEKS);
52    let color_vibrancy = if record_strength < normal_strength {
53        record_strength / normal_strength
54    } else {
55        1.0 + ((record_strength - normal_strength) / (vivid_strength - normal_strength)).min(1.0)
56    };
57    StreakMarks {
58        streak_fin_strength,
59        color_vibrancy,
60        glimmer_strength: ((record_strength - vivid_strength) / (1.0 - vivid_strength))
61            .clamp(0.0, 1.0),
62    }
63}
64
65fn calculate_fin_build(streak_week_count: usize) -> f64 {
66    (streak_week_count as f64 / STREAK_FIN_FULL_WEEKS).min(1.0)
67}
68
69/// Remaining share after a streak ends: a negative exponential shifted and scaled
70/// so it starts at one and reaches zero at FADE_WEEKS.
71fn calculate_fade(weeks_since_end: f64) -> f64 {
72    let fade_floor = (-FADE_RATE_PER_WEEK * FADE_WEEKS).exp();
73    (((-FADE_RATE_PER_WEEK * weeks_since_end).exp() - fade_floor) / (1.0 - fade_floor)).max(0.0)
74}
75
76fn calculate_record_strength(longest_weeks: f64) -> f64 {
77    ((1.0 + longest_weeks / RECORD_CURVE_WEEKS).ln()
78        / (1.0 + FULL_GLIMMER_WEEKS / RECORD_CURVE_WEEKS).ln())
79    .min(1.0)
80}