kanji-buddy
SRS engine
Spaced repetition, unlocking, and grading an answer. The three pieces that make it a trainer rather than a dictionary.
Stages
An item sits at a stage from 0 to 9. Answer correctly and it moves up one, waiting longer each time; answer wrongly and it falls back. Reach stage 9 and it is burned — learned, never asked again.
| Stage | Name | Then wait |
|---|---|---|
| 0 | Locked / lesson | — |
| 1–4 | Apprentice | 4h · 8h · 23h · 47h |
| 5–6 | Guru | 1 week · 2 weeks |
| 7 | Master | 1 month |
| 8 | Enlightened | 4 months |
| 9 | Burned | never again |
Those intervals are read from the spaced_repetition_systems table, not hard-coded. There are two systems — the standard one and an accelerated one for levels 1–2 — and the engine simply uses whichever the subject names.
The penalty formula
new_stage = current − ceil(incorrect / 2) × penalty
penalty = 2 when current ≥ passing stage, else 1
floored at the starting stageGetting something wrong twice costs the same as getting it wrong once — ceil(2/2) = 1 — but a third mistake costs another step. And once an item has reached Guru, every step back costs double: the further you have come, the more a lapse means you had not actually learned it.
This is documented, not guessed
It is published on knowledge.wanikani.com. I initially wrote in a comment that the formula was undocumented and reverse-engineered; that was wrong, and the comment now carries the source. Worth stating because the alternative — an SRS whose arithmetic nobody can check — is a bad thing to have in a learning tool.
Unlocking
This is what makes the ordering feel earned rather than arbitrary: you meet a kanji only once you know its parts. It lives in lib/progression.ts, runs inside the same transaction as the review that triggered it, and is also what bootstraps a brand-new account — otherwise you would sign in to an app with nothing to study.
Grading an answer
Three verdicts, not two. lib/answer.ts:
| Verdict | When | Effect |
|---|---|---|
| correct | Exact match, an accepted synonym, or close enough | Advances |
| incorrect | Wrong, or a blacklisted near-synonym | Counts against the stage |
| retry | Not an attempt — nudged instead | Nothing at all |
Meanings tolerate typos; readings do not. A learner shouldn’t lose a stage to “grond”, but accepting a near-miss kana would teach the wrong word. The distance is Damerau-Levenshtein, which scores swapped adjacent letters as one edit rather than two — transposition being the commonest typing slip, and two edits putting it beyond every tolerance allowed.
Tolerance scales with length: three characters or fewer must be exact, or “no” would match “go”.
Retry covers the cases that are neither right nor wrong: typing the reading when asked for the meaning, giving a valid kun’yomi when the on’yomi was wanted, or romaji that never converted to kana. None of them costs a stage — you knew something, just not the thing being asked.
The review queue
An item leaves the queue only when every aspect is correct. Radicals have one question (meaning); kanji and vocabulary have two (meaning and reading), asked back to back. A wrong answer sends the item to the back to be asked again later in the session.
The rule is a pure function in lib/quiz.ts, which is why it can be tested at all — it lived inside the component until a bug that re-asked the same radical three times made the case for pulling it out. See Web app & quiz.
A consequence worth knowing
Because a result is only written once every aspect is correct, leaving mid-item discards it — and that includes the mistake. Bailing out before finishing a kanji dodges the penalty. WaniKani behaves the same way; it is a known quirk rather than an oversight here.