feat(flashcards): Anki-стиль интервалов — кнопки различаются

Раньше на новой карте Снова/Трудно/Знаю/Легко все давали 1 день (чистый SM-2:
оценка влияла только на ease factor). Теперь интервал зависит от оценки:
новая карта Легко=4д (остальные 1д), на повторах Трудно ×1.2 / Знаю ×ef /
Легко ×ef×1.3 (easy-бонус). Серверный sm2() и клиентское превью fcNextInterval
синхронны — проверено 0 расхождений на 256 комбинациях.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-12 23:27:40 +03:00
parent cd9f2d5efa
commit ad7265d553
2 changed files with 27 additions and 16 deletions
+17 -7
View File
@@ -10,10 +10,14 @@ function safeImg(url) {
return /^\/uploads\/flashcards\/[A-Za-z0-9._-]+$/.test(u) ? u : '';
}
/* ── SM-2 algorithm ───────────────────────────────────────────────────────
quality: 0 = blackout, 1 = wrong, 2 = wrong but familiar,
3 = correct with difficulty, 4 = correct, 5 = perfect
/* ── SM-2 (Anki-стиль: кнопки различаются) ─────────────────────────────────
quality: 0/2 = Снова, 3 = Трудно, 4 = Знаю, 5 = Легко.
В отличие от чистого SM-2, интервал зависит от оценки уже на первых повторах:
на новой карте Снова/Трудно/Знаю → 1д, Легко → 4д; на зрелых — Трудно ×1.2,
Знаю ×ef, Легко ×ef×1.3 (easy-бонус). ВАЖНО: клиентское превью
fcNextInterval() в flashcards.html — точная копия этой логики интервалов.
─────────────────────────────────────────────────────────────────────── */
const FC_HARD_MULT = 1.2, FC_EASY_BONUS = 1.3;
function sm2(easeFactor, intervalDays, repetitions, quality) {
let ef = easeFactor;
let n = repetitions;
@@ -21,11 +25,17 @@ function sm2(easeFactor, intervalDays, repetitions, quality) {
if (quality < 3) {
n = 0;
iv = 1;
iv = 1; // Снова — сброс
} else {
if (n === 0) iv = 1;
else if (n === 1) iv = 6;
else iv = Math.round(iv * ef);
if (n === 0) {
iv = (quality === 5) ? 4 : 1; // выпуск: Легко 4д, иначе 1д
} else if (n === 1) {
iv = (quality === 3) ? 3 : (quality === 4) ? 6 : Math.round(6 * FC_EASY_BONUS);
} else {
if (quality === 3) iv = Math.max(iv + 1, Math.round(iv * FC_HARD_MULT));
else if (quality === 4) iv = Math.round(iv * ef);
else iv = Math.round(iv * ef * FC_EASY_BONUS);
}
n++;
}
ef = Math.max(1.3, ef + 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));