diff --git a/rslib/src/scheduler/states/steps.rs b/rslib/src/scheduler/states/steps.rs index bf0ed39ea..77ce872ce 100644 --- a/rslib/src/scheduler/states/steps.rs +++ b/rslib/src/scheduler/states/steps.rs @@ -47,19 +47,29 @@ impl<'a> LearningSteps<'a> { // if current is invalid, try first step .or_else(|| self.steps.first().copied().map(to_secs)) .map(|current| { - // special case to avoid Hard and Again showing same interval if idx == 0 { - // if there is no next step, simulate one with twice the interval of `current` - let next = self - .secs_at_index(idx + 1) - .unwrap_or_else(|| current.saturating_mul(2)); - maybe_round_in_days(current.saturating_add(next) / 2) + self.hard_delay_secs_for_first_step(current) } else { current } }) } + /// Special case the hard interval for the first step to avoid equality with the again interval. + /// Also ensure it's smaller than the good interval, at least with reasonable settings. + fn hard_delay_secs_for_first_step(self, again_secs: u32) -> u32 { + if let Some(next) = self.secs_at_index(1) { + // average of first (again) and second (good) steps + maybe_round_in_days(again_secs.saturating_add(next) / 2) + } else { + // 50% more than the again secs, but at most one day more + // otherwise, a learning step of 3 days and a graduating interval of 4 days e.g. + // would lead to the hard interval being larger than the good interval + let secs = (again_secs.saturating_mul(3) / 2).min(again_secs + DAY); + maybe_round_in_days(secs) + } + } + pub(crate) fn good_delay_secs(self, remaining: u32) -> Option { let idx = self.get_index(remaining); self.secs_at_index(idx + 1) @@ -106,7 +116,10 @@ mod test { #[test] fn delay_secs() { + // if no other step, hard delay is 50% above again secs assert_delay_secs!([10.0], 1, 600, Some(900), None); + // but at most one day more than again secs + assert_delay_secs!([(3 * DAY / 60) as f32], 1, 3 * DAY, Some(4 * DAY), None); assert_delay_secs!([1.0, 10.0], 2, 60, Some(330), Some(600)); assert_delay_secs!([1.0, 10.0], 1, 60, Some(600), None);