RevLearning
SCORM and xAPI for Unity content, behind one API
- Available
- Library
- Unity 6000.3+ · WebGL for LMS deployment · v1.0, API frozen
- Licensed — enquire
- C# · SCORM 1.2 / 2004 · xAPI · WebGL
A bridge between your Unity content and the LMS. Your content decides what happened; RevLearning reports it; the LMS records it.
A learning management system, an authoring tool, or a course template. It has no opinion about what your content teaches.
Version 1.0 — the API is frozen
Released 2026-10-02. Everything in it landed as a correctness and hardening pass after a full review, so several fixes change behaviour a course may have been relying on — the changelog’s Changed and Fixed sections are worth reading before upgrading a live course rather than after.
What the release added, in the order a course author would meet it:
CompletionStatusandSuccessStatusseparately, so SCORM 2004’s two axes can be reported independently instead of collapsed into one.Cmi.Entry,Cmi.CreditandCmi.PassingScore— whether this launch is a resume, whether it counts, and the score the LMS treats as a pass.PassCourseAsyncandFailCourseAsync, for the two endings most courses actually need.- xAPI queue control —
PendingStatementCount,FlushPendingAsyncandClearPendingStatements, so you can see and drive what has not been delivered yet. XApiActor.FromLmsLearner, so xAPI identifies the learner the LMS reported rather than one you invented.
One API per family
Content written against ILmsRuntime and IScormDataModel runs under SCORM
1.2 or SCORM 2004 without being rewritten. xAPI sits alongside them
through IXApiRuntime.
Your code depends on RevLearning rather than on any one standard, and certainly not on any one LMS.
_runtime = await RevLearningRuntime.InitializeAsync(version);
if (_runtime?.Cmi == null) return; // not connected — handle it gracefully
_runtime.Cmi.LessonLocation = "Module1/Scene1";
_runtime.Cmi.ScoreMax = 100;
_runtime.Cmi.ScoreRaw = 85;
_runtime.Cmi.LessonStatus = LessonStatus.Completed;
if (!await _runtime.CommitAsync())
Debug.LogError($"progress was not saved: {_runtime.DescribeLastError()}");
Develop without an LMS
An editor stub runtime is used automatically in the Editor and on non-WebGL platforms, so learning flows can be built and tested without standing up a course package and uploading it somewhere first.
The parts that are harder than they look
Suspend data. Learner state is stored as JSON against the standard’s
suspend_data size limit, compressed to fit. A payload that still will not
fit is refused and reported rather than trimmed — because a trimmed
payload will not deserialize, and writing it would destroy the last save that
did. Failing loudly is the only safe behaviour, and it is the one most
implementations get wrong.
Error visibility. LastError mirrors the SCORM GetLastError value and
reflects the most recent call only, so it is checked after the write you
care about rather than once at the end of a batch. A write RevLearning blocks
before it reaches the LMS does not refresh it — those are reported as warnings
instead, because the LMS was never asked. The documentation is explicit about
this, including the trap: a single LastError check after several writes can
read clean precisely when writes are being discarded.
xAPI delivery. A FIFO queue with exponential-backoff retry and persistence — PlayerPrefs, or IndexedDB on WebGL — so statements survive a flaky connection and a closed tab.
Resumable sessions, exit and resume control, and a page helper that commits progress when the tab closes.
Documentation
A full MkDocs site, same as the other products: getting started, the mental model, both SCORM standards and what actually differs between them once a real LMS is reading, xAPI delivery, WebGL, packaging, the editor tooling, and the conformance results.
Read the documentation ↗ (opens in a new tab)
Packaging
A single-SCO imsmanifest.xml builder, post-build packaging, and an LMS
validator window.
It has been run against a real LMS
Offline tests cannot answer whether an LMS accepts your package or agrees with how you report learner state. That needs a live run, and it was the last unverified thing about the SCORM path.
Both standards were driven end to end on SCORM Cloud, reading the actual
LMSSetValue / SetValue traffic out of the registration debug log. Nineteen
steps, eighteen executed on both standards, every one mapped to a control in
the shipped validation panel — several of which had to be built, because the
checklist was asking for things the panel could not yet do.
The nineteenth is the interesting one. Step 17 — writes after finish — is not
reachable on that platform, because SCORM Cloud navigates the window to its
return URL about a second after Terminate, unloading the SCO before a
post-finish write can be attempted. That is recorded as not run, with what
covers it instead, rather than quietly counted as a pass.
Some of what the live runs caught:
- On SCORM 1.2 a pass was being overwritten with
completed, following the finish sequence the FAQ recommends. Set Passed then Set Completed now leaves the pass standing on both standards. - An objective score of 20 was once recorded as a pass. Writing an objective score now writes score elements only — no success, no completion.
cmi.score.scaledis derived bySetScore; setting the three score properties directly does not derive it, and a run against a panel that did so measuredscaledat 403, value not initialized.PassingScorecomes back null, because the in-box packer emits no mastery score. That is a documented limitation, confirmed in the run rather than assumed away.
Package validation runs in CI on every push, against packages the EditMode
suite generates through the shipped packaging code. It checks what an importer
inspects before it will accept a package: manifest well-formedness, identifier
uniqueness, identifierref resolution, href safety, declared-versus-present
file parity, the launch file and the injected page helper.
It is not schema validation and it is not the ADL Test Suite. It says
nothing about runtime behaviour. The full record, including the steps and what
each one guards, is in Tests~/CONFORMANCE.md.
Compatibility, stated honestly
The declared floor is Unity 6000.3, because that is the version CI holds. Every commit runs the full EditMode suite, the JavaScript suites and the package validator on it.
2021.3 LTS almost certainly works, and is not claimed. It was exercised by hand — 336/336 EditMode tests, a WebGL player and a SCORM package at 23/23 on the validator, recorded with its date. Nothing in the package uses a Unity API newer than 2020.2 or a C# feature above version 9.
What is missing is automation: Unity licence activation fails on 2021.3 in CI with the credentials that work on 6000.3, so there is no 2021.3 leg to hold that result on the next commit. A claim held up by a memory is not a support commitment, and passing once is not the same as being supported.
So the Package Manager will show a compatibility warning on 2021.3 and you would be installing deliberately. If that matters to you, say so — lowering a declared floor is an additive change, and restoring the claim with a real CI leg behind it is the right shape for a later release.