#Model overview
An FSRS model turns a rating and elapsed time into a new memory state, estimates recall probability with a forgetting curve, and converts memory state plus desired retention into a base interval.
The FSRS-3 through FSRS-6 terminology and equations in this section are reviewed against the awesome-fsrs Wiki, especially The Algorithm. The TypeScript APIs and framework boundaries describe ts-fsrs itself.
A model owns memory-state updates, forgetting-curve evaluation, and base-interval calculation. The scheduler orchestrates reviews, chrono handles dates and elapsed time, and middleware applies learning steps, fuzzing, maximum-interval limits, and queue-status policies.
Copy this prompt to keep an AI agent inside the Model contract instead of mixing in scheduling policy.
#Shared symbols
| Symbol | Meaning |
|---|---|
| Difficulty from 1 (easiest) to 10 (hardest). | |
| FSRS-3–6 stability in days; recall probability is 90% when . | |
| Estimated probability of recall after days. | |
| Grade: 1 Again, 2 Hard, 3 Good, or 4 Easy. | |
| Elapsed days since the previous review. | |
Desired retention used by nextInterval. | |
| Base interval in days for stability and desired retention , before middleware policies. | |
| , | Lower and upper stability bounds of the model. |
| , | Lower and upper difficulty bounds of the model. |
| A trained model weight. |
FSRS-7 is documented separately from its implementation snapshot: it adds a fast stability, uses a mixed curve, and returns fractional-day intervals. The 90% definition of stability and I(r, S) notation above apply to FSRS-3–6.
#Use a versioned model
DefaultScheduler defaults to FSRS-7 and can select any supported FSRS version through its version option. Every versioned model can also be created and called directly; the following example uses FSRS-6:
import { type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating } from 'ts-fsrs'
import { const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS , const FSRS6Model: Model<{
readonly name: "fsrs-6";
readonly config: SRSSchema<{
input: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
output: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
}>;
readonly memoryState: SRSSchema<{
input: {
stability: number;
difficulty: number;
};
output: {
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model } from 'ts-fsrs/models/fsrs-6'
const const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model = const FSRS6Model: Model<{
readonly name: "fsrs-6";
readonly config: SRSSchema<{
input: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
output: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
}>;
readonly memoryState: SRSSchema<{
input: {
stability: number;
difficulty: number;
};
output: {
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model .Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>.create: (ctx: {
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
} | {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly migrate?: boolean;
readonly clip?: boolean;
readonly check?: boolean;
readonly bypass?: boolean;
}) => ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
create ({
config: {
weights: const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS ,
enableShortTerm: true,
numRelearningSteps: 1, // Constrains weights; middleware runs the steps.
},
})
const memoryState = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.step: (input: ModelStepInput<{
stability: number;
difficulty: number;
}>) => {
stability: number;
difficulty: number;
}
step ({
ModelStepInput<{ stability: number; difficulty: number; }>.memoryState: Readonly<{
stability: number;
difficulty: number;
}> | null
memoryState : null,
ModelStepInput<{ stability: number; difficulty: number; }>.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good,
ModelStepInput<{ stability: number; difficulty: number; }>.elapsedDays: number elapsedDays : 0,
})
const desiredRetention = 0.9
const interval = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.nextInterval: (memoryState: Readonly<{
stability: number;
difficulty: number;
}>, desiredRetention: number) => number
nextInterval (memoryState, desiredRetention)
const retrievabilityAtInterval = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.forgettingCurve: (memoryState: Readonly<{
stability: number;
difficulty: number;
}>, elapsedDays: number) => number
forgettingCurve (memoryState, interval)
console.log(
JSON.stringify(
{
memoryState,
desiredRetention,
interval,
retrievabilityAtInterval,
},
null,
2
)
)
The example first calculates an interval for 90% desired retention, then uses forgettingCurve to estimate the recall probability at that interval. The result may differ slightly from 90% because the FSRS-6 nextInterval returns whole days.
In the FSRS-6 config, enableShortTerm controls the model's same-day stability update. numRelearningSteps only informs weight clipping and checking; relearning-step scheduling remains middleware behavior.
#Define a model
defineModel preserves the concrete config, memory-state, and algorithm types while packaging schemas, defaults, and a model-core factory into one definition. It does not parse config by itself:
The following example implements an SM-2 variant to demonstrate how to define a model.
import {
function clamp(value: number, min: number, max: number): number clamp ,
function defineModel<const Name extends string | symbol, const ConfigSchema extends AnySchema, const MemoryStateSchema extends AnyObjectSchema, const Algorithm>(definition: {
readonly name: Name;
readonly schema: ModelSchema<{
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
}>;
readonly defaultValue: {
readonly memoryState: (ctx: {
readonly config: SchemaOutput<ConfigSchema>;
}) => SchemaInput<MemoryStateSchema>;
};
readonly create: ModelCreate<{
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
}, Algorithm>;
}): Model<{
readonly name: Name;
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
readonly algorithm: Algorithm;
}>
defineModel ,
type interface ModelForwardInput<MemoryState extends object> ModelForwardInput ,
type interface ModelStepInput<MemoryState extends object> ModelStepInput ,
type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating ,
} from 'ts-fsrs'
import { z } from 'zod'
const configSchema = z.object({
weights: z.tuple([
z.number().min(0.01).max(100),
z.number().min(0.01).max(100),
z.number().min(1.3).max(10),
z.number().nonnegative(),
z.number().min(5),
z.number(),
]),
})
const memoryStateSchema = z.object({
interval: z.number().min(0).max(36500),
easeFactor: z.number().min(1.3).max(10),
repetitions: z.number().int().nonnegative(),
})
type type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState = z.output<typeof memoryStateSchema>
const const SM2Model: Model<{
readonly name: "sm2";
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
readonly algorithm: null;
}>
SM2Model = defineModel<"sm2", z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>, z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>, null>(definition: {
readonly name: "sm2";
readonly schema: ModelSchema<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
...;
}, z.core.$strip>;
}>;
readonly defaultValue: {
...;
};
readonly create: ModelCreate<...>;
}): Model<...>
defineModel ({
name: 'sm2',
schema: ModelSchema<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
}>
schema : { ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>
config : configSchema, ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>
memoryState : memoryStateSchema },
defaultValue: {
memoryState: ({ config }) => ({
interval: 0,
easeFactor: config.weights[2],
repetitions: 0,
}),
},
create: ModelCreate<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
}, null>
create ({ config }) {
const parsed = configSchema.ZodType<any, any, $ZodObjectInternals<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>>.parse(data: unknown, params?: z.core.ParseContext<z.core.$ZodIssue>): {
weights: [number, number, number, number, number, number];
}
parse (config)
const w = parsed.weights
const const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step = ({ memoryState, rating }: interface ModelStepInput<MemoryState extends object> ModelStepInput <type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >) => {
const previous = memoryState ?? {
interval: 0,
easeFactor: parsed.weights[2],
repetitions: 0,
}
// Again / Hard / Good / Easy -> SM-2 quality 2 / 3 / 4 / 5.
const quality = rating + 1
const repetitions = rating > const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again ? previous.repetitions + 1 : 0
const interval = function clamp(value: number, min: number, max: number): number clamp (
repetitions <= 1
? w[0]
: repetitions === 2
? w[1]
: previous.interval * previous.easeFactor,
0.01,
36500
)
const easeFactor = function clamp(value: number, min: number, max: number): number clamp (
previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5],
1.3,
10
)
return { interval, easeFactor, repetitions }
}
const const forward: ({ history, initialState, }: ModelForwardInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward = ({
history: readonly ModelReview[] history ,
initialState,
}: interface ModelForwardInput<MemoryState extends object> ModelForwardInput <type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >) => {
const const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states : type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState [] = []
let memoryState = initialState ?? null
for (const const review: ModelReview review of history: readonly ModelReview[] history ) {
memoryState = const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step ({
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}> | null
memoryState ,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.rating: 1 | 2 | 3 | 4 rating : const review: ModelReview review .ModelReview.rating: 1 | 2 | 3 | 4 rating ,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.elapsedDays: number elapsedDays : const review: ModelReview review .ModelReview.deltaT: number deltaT ,
})
const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states .push(memoryState)
}
return const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states
}
return {
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.config: {
weights: [number, number, number, number, number, number];
}
config : parsed,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.bounds: ModelBoundsShape<{
interval: number;
easeFactor: number;
repetitions: number;
}>
bounds : {
iMin: 0.01,
iMax: 36500,
eMin: 1.3,
eMax: 10,
rMin: 0,
rMax: Infinity,
},
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.algorithm: null algorithm : null,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.step: (input: ModelStepInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step ,
// Invert the benchmark curve to satisfy the Model interval contract.
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.nextInterval: (memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}>, desiredRetention: number) => number
nextInterval : (state: Readonly<type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >, desiredRetention: number) =>
Math.max(
1,
Math.round(
(state.interval * Math.log(desiredRetention)) / Math.log(0.9)
)
),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forgettingCurve: (memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}>, elapsedDays: number) => number
forgettingCurve : (state: Readonly<type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >, elapsedDays: number) =>
0.9 ** (elapsedDays / state.interval),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward ,
}
},
})
const const model: ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
model = const SM2Model: Model<{
readonly name: "sm2";
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
readonly algorithm: null;
}>
SM2Model .Model<{ readonly name: "sm2"; readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; readonly algorithm: null; }>.create: (ctx: {
readonly config: {
weights: [number, number, number, number, number, number];
} | {
weights: [number, number, number, number, number, number];
};
readonly migrate?: boolean;
readonly clip?: boolean;
readonly check?: boolean;
readonly bypass?: boolean;
}) => ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
create ({
config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] },
})
const const states: readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states = const model: ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
model .ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward ({
ModelForwardInput<{ interval: number; easeFactor: number; repetitions: number; }>.history: readonly ModelReview[] history : [
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 0 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 6 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again, ModelReview.deltaT: number deltaT : 15 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
],
})
console.log(JSON.stringify(const states: readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states , null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days.
The schemas describe the public boundary and are consumed when a scheduler creates its schema. A direct create implementation must perform any required parsing and normalization before returning config, bounds, algorithm, step, nextInterval, forgettingCurve, and forward. Keep that core deterministic and free of scheduler policy.
Only use FSRS6Model.create({ config, bypass: true }) when the caller has explicitly completed that model's migration, clipping, schema validation, and range checks. It skips those create-time steps and freezing. Never use bypass for raw user, storage, or network input.
#Model versions
- FSRS-7 — dual-trace implementation reference; 34 weights and numerical intervals.
- FSRS-6 — trainable decay and same-day updates.
- FSRS-5 — fixed power forgetting curve with same-day updates.
- FSRS-4.5 — fixed power curve without a same-day formula.
- FSRS-4 — the first power-curve version in this package.
- FSRS-3 — the earlier exponential forgetting curve.