I’m not sure I follow. In the example I gave, I have 3 Rating every time after 2022-12-24 @ 00:11 within the Review state. During this period where the interval increases then decreases, I never used the Hard button (Rating 2).

Reading through the code a bit again, I think I found a few possible hypotheses. In the review.rs there appear to be two possible locations that the interval is updated in response to a Good (Rating 3) response.

The first being when `self.days_late() < 0`

within the `passing_early_review_intervals()`

function:

```
let good_interval = constrain_passing_interval(
ctx,
(elapsed * self.ease_factor).max(scheduled),
0,
false);
```

and the second being `self.days_late() >= 0`

within the `passing_nonearly_review_intervals()`

function.

```
let good_interval = constrain_passing_interval(
ctx,
(current_interval + days_late / 2.0) * self.ease_factor,
hard_interval + 1,
true,
);
```

The definition of the `constrain_passing_interval()`

function is also of importance here:

```
/// Transform the provided hard/good/easy interval.
/// - Apply configured interval multiplier.
/// - Apply fuzz.
/// - Ensure it is at least `minimum`, and at least 1.
/// - Ensure it is at or below the configured maximum interval.
fn constrain_passing_interval(ctx: &StateContext, interval: f32, minimum: u32, fuzz: bool) -> u32 {
let interval = interval * ctx.interval_multiplier;
let (minimum, maximum) = ctx.min_and_max_review_intervals(minimum);
if fuzz {
ctx.with_review_fuzz(interval, minimum, maximum)
} else {
(interval.round() as u32).clamp(minimum, maximum)
}
}
```

So I can expand the assignments to `interval`

and `good_interval`

and summarize them below for easier comparison:

```
interval = (elapsed * self.ease_factor).max(scheduled) * ctx.interval_multiplier;
good_interval = (interval.round() as u32).clamp(minimum, maximum)
```

and

```
interval = (current_interval + days_late / 2.0) * self.ease_factor * ctx.interval_multiplier;
good_interval = ctx.with_review_fuzz(interval, minimum, maximum)
```

I need to confirm still, but I assume `ctx.interval_multiplier`

refers to the **interval modifier** in the deck settings. Also I’m not sure which gets updated first `scheduled`

(aka `self.scheduled_days`

) or `interval`

but based on what I see here I’d guess interval is updated, then scheduled is set to that new updated interval sometime later.

In the first case of `passing_early_review_intervals()`

, `interval`

cannot decrease before jumping into `constrain_passing_interval`

, even if `self_ease_factor`

< 100% or `elapsed`

being potentially negative, since the `max(scheduled)`

will hold it to its previously scheduled value (or previous interval) rather than decrease it.

On the other hand, the direct multiplication with `ctx.interval_multiplier`

after jumping into `constrain_passing_interval`

doesn’t seem appropriate here, as anything that satisfies `self.ease_factor * ctx.interval_multiplier < 1`

would lead to either a decrease in interval or at best a rounding up to the previous interval. You might want to either do the `.max(scheduled)`

operation after multiplying with `ctx.interval_modifier`

or change the `minimum`

from 0 to `scheduled`

as I would never expect to press Good and have the interval decrease.

Also the application of `round`

could potentially cause the interval to get stuck forever on a single interval (in the case where interval does not increase beyond 0.5 above the previous interval). Why don’t you use a ceiling function (always round up) rather than `round()`

? That way it will be guaranteed to increase by 1 without relaying on the `hard_interval`

(as you do in the `passing_nonearly_review_intervals`

version of the update). But maybe you want it to get stuck when reviewing early so as not to have the potential to indefinitely increase the interval when reviewing ahead.

Here the days_late is guaranteed to be >= 0, thus the only thing that could cause `interval`

to decrease would be if `self.ease_factor`

were less than 100%. This was not the case based on the info provided in the screenshot as ease_factor appears to be a constant 145% the whole time, so I don’t think this had anything to do with my issue.

Again though, after jumping into `constrain_passing_interval`

you multiply by `ctx.interval_multiplier`

which can cause interval to decrease as per the above comments. This time around it is not so straight forward as we have to consider fuzz, and you also change the `minimum`

to be `hard_interval +1`

(which I think is what you previous comment was regarding now that I reread it).

So entering into `ctx.with_review_fuzz(interval, minimum, maximum)`

there appears to be another function call to `constrained_fuzz_bound`

but after that there is a line that doesn’t appear to modify `interval`

before returning a `u32`

value. Within `constrained_fuzz_bound`

I also don’t see any modification of `interval`

so I am a bit confused as to where fuzz is actually applied to the interval. So I’m not sure if fuzz has any potential contribution to this issue or not.

##
Summary

The direct multiplication of `ctx.interval_multiplier`

definitely did if I am assuming correctly that this is the `interval modifier`

from the deck settings. If `self.ease_factor * ctx.interval_multiplier < 1`

my interval would decrease. In the example screenshot from the last posts, I had `ease_factor=1.45`

and I believe I had a `interval modifier`

of 0.65. Assuming I was within the `passing_nonearly_review_intervals()`

that would lead to an update of:

```
interval = (15 + 0 / 2.0) * 1.45 * 0.65; // 14.1375, a decrease of almost 1 day.
good_interval = ctx.with_review_fuzz(interval, minimum, maximum)
```

Now I’m not sure how fuzz actually modifies the value of interval, but assuming it adds some potential range of values and rounds. If that fuzz were near zero (or potentially even negative) then one can easily see an issue.

Continuing down my screenshot data for another example:

```
interval = (11 + 0 / 2.0) * 1.45 * 0.65; // 10.3675, a decrease of almost 1 day.
good_interval = ctx.with_review_fuzz(interval, minimum, maximum)
```

Essentially I will have a linear slope downward of 1.45*0.65=0.9425. Eventually the interval gets stuck at some low value because between the rounding/fuzz and the decrease of 5.75%, it will never decrease enough to get below 4.5 (in this example) and continue to round back up to 5.

Then, after I increased the `interval modifier`

back to 1.00, the combined value of the `ease_factor`

and `interval modifier`

became positive and began to increase again.

I’d say this is very opposed to what is written in the documentation regarding the interval modifier in that anything below 1.0 has the potential to have this issue occur.