From b0665a8ef1abefa3f1dfe2dcc5c3834b4c40f2e0 Mon Sep 17 00:00:00 2001 From: user1823 <92206575+user1823@users.noreply.github.com> Date: Sat, 27 Sep 2025 12:13:34 +0530 Subject: [PATCH] Fix/Ensure fuzz doesn't go backward during rescheduling (#4364) * Fix/Ensure fuzz doesn't go backward during rescheduling Fixes https://github.com/ankitects/anki/issues/2694 * Fix * Get previous_interval from LastRevlogInfo * Fix * Format * Format * Exclude lapses * Force reconfigure in CI The cached build.ninja may reference files that don't exist in the PR. On a local build this tends to auto-fix itself as the build scripts detect a quick failure and re-run the configure, but CI tends to be too slow. https://github.com/ankitects/anki/pull/4364#issuecomment-3338026129 * Rename min/max to make it clear they restrict interval, not fuzz * Wording tweaks/comments for clarity --------- Co-authored-by: Damien Elmes --- .buildkite/linux/entrypoint | 1 + rslib/src/scheduler/fsrs/memory_state.rs | 35 ++++++++++++++++++++++-- rslib/src/scheduler/fsrs/rescheduler.rs | 7 +++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.buildkite/linux/entrypoint b/.buildkite/linux/entrypoint index a519cfc3d..4c656aa5c 100755 --- a/.buildkite/linux/entrypoint +++ b/.buildkite/linux/entrypoint @@ -16,6 +16,7 @@ if [ "$CLEAR_RUST" = "1" ]; then rm -rf $BUILD_ROOT/rust fi +rm -f out/build.ninja ./ninja pylib qt check echo "--- Ensure libs importable" diff --git a/rslib/src/scheduler/fsrs/memory_state.rs b/rslib/src/scheduler/fsrs/memory_state.rs index 420ead5a3..303bbfd91 100644 --- a/rslib/src/scheduler/fsrs/memory_state.rs +++ b/rslib/src/scheduler/fsrs/memory_state.rs @@ -136,6 +136,19 @@ impl Collection { let deckconfig_id = deck.config_id().unwrap(); // reschedule it let original_interval = card.interval; + let min_interval = |interval: u32| { + let previous_interval = + last_info.previous_interval.unwrap_or(0); + if interval > previous_interval { + // interval grew; don't allow fuzzed interval to + // be less than previous+1 + previous_interval + 1 + } else { + // interval shrunk; don't restrict negative fuzz + 0 + } + .max(1) + }; let interval = fsrs.next_interval( Some(state.stability), desired_retention, @@ -146,7 +159,7 @@ impl Collection { .and_then(|r| { r.find_interval( interval, - 1, + min_interval(interval as u32), req.max_interval, days_elapsed as u32, deckconfig_id, @@ -157,7 +170,7 @@ impl Collection { with_review_fuzz( card.get_fuzz_factor(true), interval, - 1, + min_interval(interval as u32), req.max_interval, ) }); @@ -310,6 +323,9 @@ pub(crate) struct LastRevlogInfo { /// reviewed the card and now, so that we can determine an accurate period /// when the card has subsequently been rescheduled to a different day. pub(crate) last_reviewed_at: Option, + /// The interval before the latest review. Used to prevent fuzz from going + /// backwards when rescheduling the card + pub(crate) previous_interval: Option, } /// Return a map of cards to info about last review. @@ -321,14 +337,27 @@ pub(crate) fn get_last_revlog_info(revlogs: &[RevlogEntry]) -> HashMap= 0 && e.button_chosen > 1 { + Some(e.last_interval as u32) + } else { + None + }; } else if e.is_reset() { last_reviewed_at = None; + previous_interval = None; } } - out.insert(card_id, LastRevlogInfo { last_reviewed_at }); + out.insert( + card_id, + LastRevlogInfo { + last_reviewed_at, + previous_interval, + }, + ); }); out } diff --git a/rslib/src/scheduler/fsrs/rescheduler.rs b/rslib/src/scheduler/fsrs/rescheduler.rs index db490b3e4..37c824230 100644 --- a/rslib/src/scheduler/fsrs/rescheduler.rs +++ b/rslib/src/scheduler/fsrs/rescheduler.rs @@ -115,13 +115,14 @@ impl Rescheduler { pub fn find_interval( &self, interval: f32, - minimum: u32, - maximum: u32, + minimum_interval: u32, + maximum_interval: u32, days_elapsed: u32, deckconfig_id: DeckConfigId, fuzz_seed: Option, ) -> Option { - let (before_days, after_days) = constrained_fuzz_bounds(interval, minimum, maximum); + let (before_days, after_days) = + constrained_fuzz_bounds(interval, minimum_interval, maximum_interval); // Don't reschedule the card when it's overdue if after_days < days_elapsed {