Skip to content

Tracking guide

You author tracking once, declaratively, and the runtime projects it onto whichever target you package for. You don’t write per-target code.

oelt.track.score(0.85);

That one call becomes cmi.core.score.raw on SCORM 1.2, score.scaled on SCORM 2004, result.score.scaled in a cmi5 statement, or a stored number with no LMS at all.

The global oelt is available after the page loads. Most courses never call it directly — components and the declared rules drive it — but it’s there for the manual completion rule and bespoke interactions.

CallWhat it does
oelt.track.complete()Mark the course complete (used by the manual rule).
oelt.track.score(0.85)Report a 0–1 score.
oelt.track.progress(0.4)Report 0–1 progress.
oelt.track.interaction({ id, type, result, score?, response? })Report a question-level interaction. resultpassed | failed | completed; score is 0–1.
oelt.state.get(key) / oelt.state.set(key, value)Suspend/resume key-value store (see the budget below).
oelt.nav.pages, oelt.nav.current(), oelt.nav.go(i), oelt.nav.next(), oelt.nav.prev()Navigation derived from the manifest.

Tracking is configured in the tracking block of course.json. Omit it for the zero-config default — complete when all pages are viewed, no score — which is impossible to misconfigure.

{
"tracking": {
"completion": { "rule": "required-interactions-passed" },
"score": { "rule": "weighted-interactions", "mastery": 0.8 },
"progress": { "rule": "pages-viewed" }
}
}

Completion rules:

  • all-pages-viewed — complete when every page has been viewed (default).
  • pages-viewed — requires threshold (0–1), e.g. view 80% of pages.
  • required-interactions-completed — every required: true interaction answered.
  • required-interactions-passed — every required: true interaction passed.
  • manual — your code calls oelt.track.complete().

Score rules:

  • none — no score (default).
  • single-interaction — requires source (an interaction id).
  • weighted-interactions — weighted mean using each interaction’s weight.
  • mastery (0–1) may be set on a scoring rule to decide pass/fail.

Progress rules: pages-viewed (default) or none.

SCORM 1.2 has one status field (cmi.core.lesson_status) for both completion and success. OELTKit collapses your richer model into it with a fixed, normative rule — you don’t implement it, but you should know what survives. This is the exact rule from the toolkit’s tracking-semantics spec (§4.2), quoted verbatim:

On SCORM 2004 and cmi5 no collapse happens: completion and success are reported on their own channels (completion_status + success_status; completed then passed/failed statements).

ConceptSCORM 1.2SCORM 2004cmi5 / xAPIStandalone web
Completioncmi.core.lesson_status (collapsed — see above)completion_status + success_status (separate)completed / passed / failed statementslocalStorage record
Scorescore.raw = round(scaled × 100)score.scaled (also raw/min/max)result.score.scalednumber 0–1
Progressnone (omitted)progress_measure 0–1progress extensionstored number
Interactionscmi.interactions.n.* (tight limits)cmi.interactions.n.* (richer)one statement per interactionlocal event log

Interaction reporting is independent of your completion and score rules: a declared interaction that fires is recorded wherever the target supports it — free analytics that costs you nothing.

For cmi5/xAPI, OELTKit uses the cmi5-defined verbs plus ADL verbs (answered, interacted, …); it does not mint its own. A dedicated OELT xAPI Profile is deferred to a later phase.

All persisted state goes through oelt.state — never call the LMS API directly, or you bypass size-guarding and break resume.

For a one-off interaction, emit the same payload the components use; the runtime forwards it:

<button onclick="oelt.track.interaction({ id: 'final-quiz', type: 'quiz', result: 'passed', score: 1 })">
Submit
</button>