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
+10 -9
View File
@@ -1569,20 +1569,21 @@ function finishStudy() {
</div>`).join('');
}
/* ── estimated next interval preview for sq buttons ──
ВАЖНО: точная копия серверного sm2() (flashcardController.js), иначе
превью врёт. В чистом SM-2 интервал для q>=3 НЕ зависит от значения q
(q влияет только на ease factor), поэтому «Трудно/Знаю/Легко» при первых
повторениях дают одинаковый интервал — это корректно.
(Дифференциация интервалов по кнопкам — кандидат на Фазу 4.) */
/* ── превью следующего интервала для кнопок Снова/Трудно/Знаю/Легко ──
ВАЖНО: точная копия логики интервалов серверного sm2() (flashcardController.js),
иначе превью врёт. Anki-стиль: на новой карте Легко=4д выделяется, на зрелых
Трудно ×1.2 / Знаю ×ef / Легко ×ef×1.3. */
const FC_HARD_MULT = 1.2, FC_EASY_BONUS = 1.3;
function fcNextInterval(card, q) {
const ef = card.ease_factor || 2.5;
const iv = card.interval_days || 1;
const rep = card.repetitions || 0;
if (q < 3) return 1;
if (rep === 0) return 1;
if (rep === 1) return 6;
return Math.round(iv * ef);
if (rep === 0) return q === 5 ? 4 : 1;
if (rep === 1) return q === 3 ? 3 : q === 4 ? 6 : Math.round(6 * FC_EASY_BONUS);
if (q === 3) return Math.max(iv + 1, Math.round(iv * FC_HARD_MULT));
if (q === 4) return Math.round(iv * ef);
return Math.round(iv * ef * FC_EASY_BONUS);
}
function fcDaysLabel(n) {
if (n <= 1) return '1 день';