RGreview-game-corethe engine behind concept-first review games

Feature detail

Guided repetition ladder

Concept-first rep phases — light, hard, and recovery-light — with explicit attempt budgets and selection reasons so apps can teach first, then fade to independent proof.

schedulerconcept

Worked example

Rep-phase ladder

Four clean lights clear the light phase; an assisted hard attempt opens recovery-lights with a same-concept detour.

Input

{
  "before": {
    "conceptId": "factoring",
    "independentPassCount": 0,
    "supportedPassCount": 0,
    "nextEligibleTurn": 1,
    "lastSeenTurn": null,
    "attempts": 0,
    "supplementalExposureCount": 0,
    "assistedCount": 0,
    "skippedCount": 0,
    "recentStruggleCount": 0,
    "recoveryDue": false,
    "retentionCheckEligibleTurn": null,
    "retentionCheckPassed": false,
    "mastered": false,
    "lastOutcome": null,
    "subskillStats": {
      "recognition": {
        "attempts": 0,
        "cleanPasses": 0,
        "supportedPasses": 0,
        "misses": 0,
        "lastMissedTurn": null
      },
      "setup": {
        "attempts": 0,
        "cleanPasses": 0,
        "supportedPasses": 0,
        "misses": 0,
        "lastMissedTurn": null
      }
    },
    "lightPassCount": 0,
    "hardPassCount": 0,
    "recoveryLightRemaining": 0,
    "recoverySupportMode": "none"
  },
  "actions": [
    {
      "turn": 1,
      "outcome": "independent_correct"
    },
    {
      "turn": 2,
      "outcome": "independent_correct"
    },
    {
      "turn": 3,
      "outcome": "independent_correct"
    },
    {
      "turn": 4,
      "outcome": "independent_correct"
    },
    {
      "turn": 5,
      "outcome": "assisted"
    }
  ]
}

Output

{
  "afterLightSweep": {
    "state": {
      "conceptId": "factoring",
      "independentPassCount": 0,
      "supportedPassCount": 0,
      "nextEligibleTurn": 6,
      "lastSeenTurn": 4,
      "attempts": 4,
      "supplementalExposureCount": 0,
      "assistedCount": 0,
      "skippedCount": 0,
      "recentStruggleCount": 0,
      "recoveryDue": false,
      "retentionCheckEligibleTurn": null,
      "retentionCheckPassed": false,
      "mastered": false,
      "lastOutcome": "independent_correct",
      "subskillStats": {
        "recognition": {
          "attempts": 0,
          "cleanPasses": 0,
          "supportedPasses": 0,
          "misses": 0,
          "lastMissedTurn": null
        },
        "setup": {
          "attempts": 0,
          "cleanPasses": 0,
          "supportedPasses": 0,
          "misses": 0,
          "lastMissedTurn": null
        }
      },
      "lightPassCount": 4,
      "hardPassCount": 0,
      "recoveryLightRemaining": 0,
      "recoverySupportMode": "none"
    },
    "plan": {
      "repPhase": "hard",
      "repIndex": 5,
      "supportMode": "none",
      "hardAttemptLimit": 3
    }
  },
  "afterHardStumble": {
    "state": {
      "conceptId": "factoring",
      "independentPassCount": 0,
      "supportedPassCount": 0,
      "nextEligibleTurn": 6,
      "lastSeenTurn": 5,
      "attempts": 5,
      "supplementalExposureCount": 0,
      "assistedCount": 1,
      "skippedCount": 0,
      "recentStruggleCount": 1,
      "recoveryDue": true,
      "retentionCheckEligibleTurn": null,
      "retentionCheckPassed": false,
      "mastered": false,
      "lastOutcome": "assisted",
      "subskillStats": {
        "recognition": {
          "attempts": 0,
          "cleanPasses": 0,
          "supportedPasses": 0,
          "misses": 0,
          "lastMissedTurn": null
        },
        "setup": {
          "attempts": 0,
          "cleanPasses": 0,
          "supportedPasses": 0,
          "misses": 0,
          "lastMissedTurn": null
        }
      },
      "lightPassCount": 4,
      "hardPassCount": 0,
      "recoveryLightRemaining": 2,
      "recoverySupportMode": "same-concept-recovery"
    },
    "plan": {
      "repPhase": "recovery-light",
      "repIndex": 3,
      "supportMode": "same-concept-recovery",
      "hardAttemptLimit": null
    },
    "nextConceptAtTurn6": "factoring"
  }
}

Real source excerpt

Repetition phases, attempt budgets, and selection reasons

src/scheduler/guided.ts
export type RepetitionPhase = 'light' | 'hard' | 'recovery-light'
export type RecoverySupportMode = 'none' | 'same-concept-recovery' | 'support-concept-recovery'
export type ConceptSelectionReason =
  | 'new_concept'
  | 'guided_mastery'
  | 'recovery_due'
  | 'retention_due'
  | `weakest_subskill:${string}`

export interface GuidedConceptProgressState<TSubskill extends string = string> extends ConceptScheduleState<TSubskill> {
  lightPassCount: number
  hardPassCount: number
  recoveryLightRemaining: number
  recoverySupportMode: RecoverySupportMode
}

export type GuidedConceptProgressMap<TSubskill extends string = string> =
  Record<string, GuidedConceptProgressState<TSubskill>>

export interface ConceptRepetitionPlan {
  repPhase: RepetitionPhase
  repIndex: 1 | 2 | 3 | 4 | 5 | 6
  supportMode: RecoverySupportMode
  hardAttemptLimit: number | null
}

export type ConceptStateBadge =
  | 'Emerging'
  | 'Supported'
  | 'Independent'
  | 'Mastered'
  | 'Retention due'
  | 'Recovery due'

export const LIGHT_REP_TARGET = 4
export const HARD_REP_TARGET = 2
export const HARD_ATTEMPT_LIMIT = 3
export const HARD_FAILURE_RECOVERY_LIGHTS = 2