# Zeitgeist Anki · FSRS-4.5 Implementation Reference

Condensed reference for the Anki implementation inside `/media/HDD_1TB/Zeitgeist/anki.html`. Useful when debugging card scheduling, importing decks, or extending the algorithm.

---

## Data Location

| Item | Path |
|---|---|
| App source | `/media/HDD_1TB/Zeitgeist/anki.html` |
| Data file | `/media/HDD_1TB/Zeitgeist/data/data.json` |
| Anki data key | `zeitgeist-anki-v2` (JSON-string, double-decode) |
| Shared runtime | `/media/HDD_1TB/Zeitgeist/shared.js` |

---

## FSRS Object Constants

```javascript
const FSRS = {
  W: [0.4072,1.1829,3.1262,15.4722,7.2102,0.5316,1.0651,0.0234,1.616,
      0.1544,1.0824,1.9813,0.0953,0.2975,2.2042,0.2407,2.9466,0.5034,0.6567],
  DESIRED_R:    0.9,
  MAX_INTERVAL: 365,
  FUZZ:         0.05,
  LEECH_AT:     8,
  STEPS_LEARN:   [2, 1440],
  STEPS_RELEARN: [15],
  HARD_LEARN_MIN:  10,
  HARD_MULTIPLIER: 1.5,
  GRAD_INTERVAL:   2,
  EASY_INTERVAL:   4,
};
```

---

## Card Fields

```javascript
{
  id, front, back,
  state: 'new' | 'learning' | 'review' | 'relearn',
  interval, stability, difficulty,
  lastReview, dueDate,
  lapses, reviews,
  learningStep,
  easeFactor, // legacy, only used by _migrate
  leech
}
```

---

## Scheduling Rules

### New / Learning cards

| Rating | Action |
|---|---|
| 1 Nochmal | back to learning step 0, due in `STEPS_LEARN[0]` minutes |
| 2 Schwer | stay on current step, due in `max(HARD_LEARN_MIN, step * HARD_MULTIPLIER)` minutes |
| 3 Gut | next step; if last step → graduate to `review` with `GRAD_INTERVAL` days |
| 4 Einfach | graduate immediately to `review` with `EASY_INTERVAL` days |

### Review cards

1. Compute elapsed days `t` since `lastReview`.
2. Compute retrievability: `R = (1 + t / (9 * S)) ^ -1`.
3. Update difficulty: `D = D - W[6] * (rating - 3)` with mean-reversion toward easy default.
4. If rating 1 (lapse): increment `lapses`, compute new stability with `_stabilityForget`, state → `relearn`, due in 15 min.
5. If rating 2–4: compute new stability with `_stabilityRecall`, set `interval = interval(S)` with fuzz, advance `dueDate`.

### Relearn cards

| Rating | Action |
|---|---|
| 1 | step 0, 15 min |
| 2 | current step × 1.5 |
| 3/4 | next step or return to `review` with new interval from stability |

---

## Key Formulas

```javascript
// Retrievability
retr(t, s) => Math.pow(1 + t / (9 * s), -1);

// Interval from stability (days), fuzzed + capped
interval(s, fuzz) {
  const raw = 9 * s * (1 / DESIRED_R - 1);
  const f = fuzz === false ? 1 : 1 + (Math.random() * 2 - 1) * FUZZ;
  return Math.max(1, Math.min(MAX_INTERVAL, Math.round(raw * f)));
}

// Stability after successful review
_stabilityRecall(d, s, r, g) {
  const hardPen = (g === 2) ? W[15] : 1;
  const easyBon = (g === 4) ? W[16] : 1;
  const factor = Math.exp(W[8]) * (11 - d) * Math.pow(s, -W[9])
                 * (Math.exp(W[10] * (1 - r)) - 1) * hardPen * easyBon;
  return clamp(s * (1 + factor));
}

// Stability after lapse
_stabilityForget(d, s, r) {
  return clamp(W[11] * Math.pow(d, -W[12])
               * (Math.pow(s + 1, W[13]) - 1)
               * Math.exp(W[14] * (1 - r)));
}
```

---

## Legacy SM-2 Migration

When a card has no `stability`/`difficulty`, the first rating transparently migrates:

```javascript
if (state === 'review' || state === 'relearn') {
  stability  = max(0.5, interval || 1);
  difficulty = clamp(((5.0 - easeFactor) / 3.7) * 9 + 1);
  lastReview = (dueDate || now) - (interval || 1) * 86400000;
}
```

---

## Deck Stats Interpretation

The app computes:
- **Matured** = cards in `review` with `interval > 21` days.
- **Leeches** = `leech === true` or `lapses >= 8`.
- Average stability and difficulty are shown for `review`/`relearn` cards only.

---

## Gotchas

- `zeitgeist-anki-v2` is a JSON-string inside `data.json` → `json.load(file)` then `json.loads(value)`.
- Cards are stored as an array directly inside `deck.cards[]`; there is no separate `notes` table.
- IDs are generated as `Date.now().toString(36) + Math.random().toString(36).slice(2,6)`.
- `MAX_INTERVAL` is capped at 365 days by default — lower than typical Anki defaults.
- `.apkg` import in the browser lazily loads JSZip + sql.js from CDN and reads `collection.anki21` or `collection.anki2`.
