#模型概覽
FSRS 模型依據評分和經過時間計算新的記憶狀態,透過遺忘曲線估算回憶機率,並依據記憶狀態與目標保留率計算基礎間隔。
本節的 FSRS-3 至 FSRS-6 術語與公式依據 awesome-fsrs Wiki 校對,主要參考其中的 The Algorithm。TypeScript API 與框架職責邊界以 ts-fsrs 本身為準。
模型只負責記憶狀態更新、遺忘曲線計算和基礎間隔計算。scheduler 負責組織複習流程,chrono 負責日期與經過時間,middleware 負責學習步驟、模糊處理、最大間隔限制和佇列狀態策略。
複製這段提示詞,讓 AI Agent 遵守 Model 契約,避免將排程策略混入模型。
#共用符號
| 符號 | 含義 |
|---|---|
| 難度,範圍為 1(最簡單)至 10(最困難)。 | |
| FSRS-3 至 FSRS-6 的穩定性,單位為天;當 時回憶機率為 90%。 | |
| 經過 天後能夠回憶的估計機率。 | |
| 評分:1 為 Again、2 為 Hard、3 為 Good、4 為 Easy。 | |
| 距離上次複習經過的天數。 | |
nextInterval 使用的目標保留率。 | |
| 依據穩定性 和目標保留率 計算的基礎間隔,單位為天,尚未套用 middleware 策略。 | |
| 、 | 模型允許的穩定性下限和上限。 |
| 、 | 模型允許的難度下限和上限。 |
| 訓練得到的模型權重。 |
FSRS-7 依據其實作快照另行說明:它增加快穩定性,使用混合曲線並回傳小數天間隔。上表中穩定性的 90% 定義和 I(r, S) 記法適用於 FSRS-3 至 FSRS-6。
#使用版本化模型
DefaultScheduler 預設使用 FSRS-7,可透過 version 選擇任一支援的 FSRS 版本。也可以直接建立並呼叫各版本模型;下例以 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
)
)
範例先依據 90% 的目標保留率計算複習間隔,再透過 forgettingCurve 估算到達該間隔時的回憶機率。由於 FSRS-6 的 nextInterval 回傳整數天,結果可能與 90% 略有差異。
在 FSRS-6 設定中,enableShortTerm 控制模型的同日穩定性更新。numRelearningSteps 只參與權重裁剪與檢查;重新學習步驟仍由 middleware 排程。
#定義模型
defineModel 會保留具體的設定、記憶狀態和演算法型別,並將 schema、預設值與模型核心工廠組織為一個定義。它本身不會解析設定:
以下透過實作一個 SM-2 變體,示範如何定義模型。
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.
Schema 描述公開邊界,並在 scheduler 組合 schema 時使用。直接呼叫 create 時,具體實作必須先完成所需的解析和正規化,再回傳 config、bounds、algorithm、step、nextInterval、forgettingCurve 和 forward。該核心應保持確定性,且不包含排程策略。
只有呼叫方已明確完成該模型的 migrate、clip、schema 驗證和範圍檢查後,才能使用 FSRS6Model.create({ config, bypass: true })。它會略過這些 create 階段的處理以及凍結。不要對來自使用者、儲存空間或網路的原始設定使用 bypass。