@demystify/play-learn
v0.2.0
Published
The Demystify Play learning engine — FSRS scheduling, item-Elo selection and Bayesian Knowledge Tracing mastery, all on-device.
Maintainers
Readme
@demystify/play-learn
The on-device learning engine for Demystify Play: item selection, mastery estimation and review scheduling, all running locally with no server.
npm install @demystify/play-learnThree algorithms, three questions — deliberately not conflated
| Question | Algorithm | |---|---| | Which item next? | Elo | | Has this skill been mastered? | BKT | | When should this come back? | FSRS-6 |
Conflating any two is how spaced repetition earns a reputation for feeling arbitrary.
Elo — which item next
import { selectItem, updateAbility, newAbility, TARGET_SUCCESS } from "@demystify/play-learn";
const next = selectItem(pool, ability); // targets ~80% success
const after = updateAbility(ability, item.d, correct);The target is 80–85% success, not 50%. A 50% rate maximises information about the learner,
which is what an exam wants and what a game must not do. K decays with evidence (1/(1+0.05n)), so
a single unlucky slip on question 200 does not undo what 199 answers established.
Item difficulty ships pre-computed inside each pack, so the whole selection loop runs with no server. Selection is deterministic, including tie-breaks — a daily puzzle must be identical for everyone, and a replay has to reproduce the exact sequence of questions a player saw.
BKT — has it been mastered
import { observe, isMastered, guessRateFor } from "@demystify/play-learn";
const next = observe(skill, correct, { g: guessRateFor(item.options.length) });BKT is unusually trustworthy here because its guess parameter is structurally known for a multiple-choice item: four options means a 1-in-4 floor, not a fudge. Mastery requires both a probability threshold and a minimum number of attempts — P(known) alone can cross 95% after two lucky answers through the transition term, and telling someone they have mastered a skill on that basis is a claim the product cannot support.
Mastery is deliberately sticky: the slip parameter says even someone who knows a skill fails 10% of the time, so two failures are a bad session rather than forgetting. A sustained run does revoke it.
FSRS — when should it come back
import { review, gradeFor, studyQueue } from "@demystify/play-learn";
const card = review(item.card, gradeFor(correct, elapsedMs), now);
const queue = studyQueue(items, now, 10); // due before new, alwaysFSRS-6 via ts-fsrs (MIT). The scheduling
formulas are short enough to reimplement; the fitted 21-parameter default weight vector is not, and
inventing those numbers would be inventing a dataset.
Two configuration choices worth stating:
enable_fuzz: false. Fuzz jitters intervals to stop reviews clumping, which is right for a 10,000-card Anki deck and wrong here: it makes the schedule non-reproducible, so the same history on two devices would produce different due dates and a synced profile would look corrupted.- The grade is derived from the answer, not asked.
gradeFor(correct, elapsedMs)uses time as a proxy for retrieval strength — recalling something in two seconds and dredging it up in fifteen are not the same memory — so the scheduler gets four levels of signal from an interaction the player was going to have anyway, with no survey interrupting play.
studyQueue puts due items before new ones, always. A learner shown new material while overdue items
pile up ends up with a backlog they cannot clear, which is the most common way people abandon spaced
repetition.
Licence
Apache-2.0 · Demystify Systems
