Changelog¶
All notable changes to RevLearning are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
Correctness and hardening pass following a full review. Several fixes change behaviour that courses may have been relying on — read Changed and Fixed before upgrading a live course.
Added¶
CompletionStatusandSuccessStatusonIScormDataModel, so SCORM 2004's two axes can be reported independently.Cmi.Entry,Cmi.CreditandCmi.PassingScore: whether the launch is a resume, whether it counts, and the score the LMS treats as a pass.XApiActor.FromLmsLearner,FromAccount, andaccountsupport, so xAPI can identify the learner the LMS reported.PendingStatementCount,FlushPendingAsyncandClearPendingStatementsonIXApiRuntime.PassCourseAsyncandFailCourseAsync.DescribeLastError(), which reads the SCORM version from the runtime.- JavaScript test suites for the WebGL bridge and page helper (
Tests~/js), and a CI workflow. LmsObjectives.SetCompletionoverload takingCompletionStatus, which cannot express a value the completion axis has no meaning for.- Declared package
dependenciesfor the three Unity modules the runtime uses (unitywebrequest,jsonserialize,imgui). They were previously assumed present. - Validation panel controls the conformance checklist needs and did not have: an incorrect interaction, split objective score/success/completion writes, exit-mode selection including
TimeOut, an out-of-range score, and a launch context readout. - JavaScript suites now run against both standards (
standards.test.mjs). The SCORM 2004 branch of the bridge previously had no test coverage at all. - A README in every source folder, describing what lives there and the reasoning that cannot be recovered from the code: why a SCORM 1.2
Completedwrite is suppressed once success is recorded whileBrowsedis not, why the xAPI delivery tri-state stays internal while callers seeTask<bool>, why oversized suspend data is refused rather than truncated, and whyLessonStatus.UnknownandLessonMode.Normalsit at zero. - A documentation site generated from those READMEs (
mkdocs.yml,Build-Docs.ps1,requirements.txt). CI builds it with--strict, so an internal link that stops resolving fails a pull request instead of rotting silently, and regenerates the mirror to fail if it has drifted from the READMEs it is generated from. - XML doc summaries on the three public types that had none, so they say something useful in IntelliSense.
IScormDataModel.GetElement/SetElement, a raw accessor for data-model elements the typed members do not wrap —cmi.progress_measure,cmi.comments_from_lms, anything else a course turns out to need. Previously there was no route to an unwrapped element at all, which made a missing one a blocker rather than an inconvenience. It goes through the session guard and reports refusals with the decoded error like every other write, and it applies no translation or validation: the caller spells the element for the standard they are on.
Changed¶
Breaking changes are marked. This is the release that freezes the public API, so several of them exist only because they would cost more to make later.
- Breaking.
LessonStatusis reordered soUnknownis the zero value.Passedheld it before, which madedefault(LessonStatus)the strongest possible claim about a learner nobody had said anything about. Migration: a[SerializeField]of this enum in your own scenes or assets stores the ordinal and will remap on upgrade — check any inspector-setLessonStatusfield. - Breaking.
LessonModeis reordered soNormalis the zero value, matching what both standards treat as the default launch mode.Browseheld it before, so an unassigned field claimed the learner was browsing untracked — the one value that tells a course to stop recording. Same migration note. - Breaking.
Objective.successis nowSuccessStatus?andObjective.completionis nowCompletionStatus?, instead of both beingLessonStatus?. Out-of-axis values used to compile and were then silently dropped on SCORM 2004 while being written on SCORM 1.2. - Breaking.
IXApiTransport,XApiDeliveryResult,XApiDeliveryOutcomeandScormTimeare now internal. Nothing public ever accepted a transport, so the extension point they advertised could not be reached; publishing it later, with a factory overload that takes one, is additive. The supported contract isIXApiRuntime.SendStatementAsync. - Behaviour. An over-limit
suspend_datawrite is now refused and reported on both standards, leaving the last good value intact. It used to fall back to truncation on the helper and SCORM 2004 paths, which overwrote a working save with a prefix of JSON that cannot be deserialized — so on the next launch the learner restarted from nothing. SCORM 1.2's envelope path already refused; the two now agree, and the FAQ has always described this behaviour. - Behaviour. A 401, 403 or 407 from the LRS now halts delivery and keeps the whole backlog rather than discarding it. It used to be treated as per-statement poison, so one flush with an expired token attempted each statement once, dropped it, and erased the persisted queue.
- Behaviour.
XApiRuntimeFactory.Create(null)now returns a fail-closed runtime in player builds instead of the success-faking Editor stub. A build with an unassigned endpoint reported healthy delivery while nothing left the device. - Behaviour. An out-of-range xAPI
rawscore is clamped into[min, max]and reported. An LRS is obliged to reject the whole statement otherwise, soPassed(105)against the default 0–100 range lost the pass entirely. - Behaviour. A WebGL build with compression enabled and Decompression Fallback off now produces no SCORM package, on every build path rather than only from the RevLearning menu. It used to emit one and log "SCORM package created"; that package hangs at the loader on any LMS that does not set
Content-Encoding, which is most of them. The build menu offers to fix the setting, or to package anyway as an explicit choice. - Minimum Unity version lowered from 6000.3 to 2021.3, now verified: the full suite compiles and passes on 2021.3.45f2 as well as 6000.3.5f2.
- Manifests no longer carry
xsi:schemaLocation. It named ADL and IMS control files by relative path that the package does not ship, and a hint pointing at an absent file is worse than none. Both manifests remain schema-valid. - "Enable Build Injection" is now "Inject Page Helper (unload commit)", and the project settings, post-build log and validator all say what it gates. All three called it diagnostics, while it carries the only commit-on-tab-close there is.
commitOnEachInteractionis labelled SCORM 1.2 only, which is all it ever was; the SCORM 2004 runtime does not read it.- Samples moved to
Samples~and are imported on demand rather than compiled into every project. - Sample scenes no longer reference URP-only components. They were serialized with
UniversalAdditionalCameraDataandUniversalAdditionalLightDataby GUID with no URP dependency declared, so first contact on a Built-in-RP project was a pair of missing-script warnings. CompleteCourseAsyncnow marks the course completed, not passed. UsePassCourseAsyncfor assessed content.SetScorereportscmi.score.scaledon SCORM 2004, and rescales an out-of-0-100 range to a percentage on SCORM 1.2.CommitAsyncandFinishAsynccomplete synchronously.AnsweredAsyncno longer marks the activity complete.
Fixed¶
- SCORM 1.2:
MarkCompleted()afterMarkPassed()destroyed the pass. The guard that stops completion retracting a recorded outcome existed only onCompletionStatus, andMarkCompletedwrites throughLessonStatus— so SCORM 2004 and the Editor stub kept the pass while every real 1.2 session replaced it withcompleted, following the finish sequence the FAQ recommends. A learner who passed was recorded as merely complete, and platforms gating certification onlesson_status='passed'withheld it. - Interaction results were invalid on SCORM 2004. An incorrect answer was written as
wrong, which is the SCORM 1.2 spelling; 2004 requiresincorrectand refuses the element as a type mismatch. Every incorrect answer lost its result while correct ones recorded fine — selectively erasing the failure data interaction tracking exists to capture. The result token is now translated per standard, and either spelling is accepted as input. - Interaction types were invalid on SCORM 1.2.
RecordChoicewrotemultiple_choice, a token in neither standard's vocabulary, so strict platforms refused the type element and recorded typeless questions. Both standards now getchoice, which is what the vocabulary actually calls a multiple-choice question. - SCORM 2004 wrote
timeoutfor a timed-out exit. The RTE data model hyphenates it in both standards, so the write was refused and the launch-time resumable-exit default stood — leaving an expired attempt resumable. - SCORM 2004 objective and interaction writes discarded every result with no logging, so a refused element vanished silently while
GetInteractions()reported it anyway. Refusals are now warnings naming the element and the decoded LMS error, on both standards. LmsObjectives.SetCompletionsilently did nothing on SCORM 1.2. It now says so: the standard gives an objective one status element and RevLearning uses it for success.- Both runtimes parsed numbers with the current culture while writing them invariant. Under a comma-decimal culture
"0.8"read back as8, corruptingPassingScore, score readback,cmi.score.scaled,scaled_passing_scoreandprogress_measure. Cmi.SuspendDatareturned RevLearning's internalREVLEARNING:GZIP64:encoding when the runtime had compressed the value to make it fit.- The xAPI delivery contract was inverted in both directions.
SendStatementAsyncreturned false for a statement safely queued offline — prompting callers to retry, which mints a new statement id the LRS cannot deduplicate — and true for one the LRS had permanently refused and the queue had discarded. It now returns true when the statement is accepted for delivery (delivered or durably queued) and false only when it will never arrive, and that contract is documented on the interface and on all nineXApiStatementshelpers. - Re-initializing one xAPI runtime for a second learner copied the first learner's undelivered statements — name and email included — into the second learner's storage and sent them during their session. The kiosk and shared-classroom case the per-learner queue scoping exists to prevent.
- An empty xAPI verb or activity id serialized as
"id":"", which is not an IRI, so the LRS rejected the whole statement. Refused up front instead. context.registrationwas serialized verbatim although the specification types it as a UUID. A malformed one is now warned about and omitted rather than risking the statement.- The injected diagnostics proxy silently emptied
ILmsRuntime.LastErrorandDescribeLastError(). It was built withObject.create(api)and given own properties only for the calls it wraps, so every other method ran withthisbound to the proxy — and an adapter that keeps error state onthis, as SCORM Cloud's does, then had its state shadowed by the proxy's own copy after the first read of the session. A documented public API reported "no error" for a write that had just failed, in the default configuration, and every message that decodes it said "OK" too. Found on a live LMS; no offline test could have seen it, because the test harness kept state in a closure. - Re-answering a question failed on SCORM 2004.
cmi.interactions.n.correct_responsesis a collection, and writing an index that already holds a pattern is refused with 351. The pattern is now written only when the record is created, which is the only time it can change. - The page helper was not injected into a template whose closing body tag was written
</BODY>: the tag was found case-insensitively and then spliced case-sensitively, so the file was rewritten unchanged with the append fallback unreachable and no warning. Since that snippet is the only commit-on-tab-close in the package, a learner closing the tab lost everything since their last explicit save. A duplicated closing tag also installed the handler more than once. - Objectives were recorded as passed and completed by any write, so a score of 20 was stored as a pass.
- Reporting completion on SCORM 2004 overwrote a recorded pass with "unknown".
- The page helper's unload commit never registered, so nothing was flushed when a learner closed the tab.
- Writes after
FinishAsyncreached a terminated API and were discarded. - SCORM 1.2
suspend_datacould exceed 4096 characters once wrapped, and the rejected write was ignored. - Project settings were never written to disk, so batch builds used defaults.
- Rebuilding into the same folder nested the previous package inside the new one.
LmsErrordescribed SCORM 2004 codes using the SCORM 1.2 table.- xAPI statements had no
id, so retries duplicated them; a permanently rejected statement blocked the queue forever; requests had no timeout; and the persisted queue was shared between learners on the same machine. cmi.total_timeover 24 hours parsed as zero.- Re-answering a question created a second SCORM 1.2 interaction record.
- Manifest
schemaLocationpointed at the wrong schema, hrefs were not percent-encoded, and every package shared one identifier.
Security¶
- The xAPI backlog is now scoped per learner, so one learner's undelivered statements — including their name and email — are no longer restored and sent from another learner's session on a shared machine. This release closes the remaining path: re-initializing a single runtime for a second learner used to carry the first learner's queue across in memory, which the storage scoping alone did not prevent.
0.1.0 - 2026-06-21¶
First packaged release. The runtime, packaging workflow, and test suite are in place; validated end-to-end on Thrive LXP. Versioned 0.x while LMS coverage continues to expand.
Added¶
ILmsRuntimeabstraction with a unified CMI data model (IScormDataModel) over SCORM 1.2 and SCORM 2004.- Editor / non-WebGL stub runtime so learning flows can be developed and tested without an LMS.
- Status, score, bookmark, completion, suspend-data, objective, and interaction helpers via extension methods on
ILmsRuntime. - SCORM 2004 scaled score support; session exit/resume control (
SuspendAsync,LmsExit) for resumable courses. - xAPI runtime foundation: statement model, verbs, JSON writer, and a resilient queued transport (FIFO, exponential-backoff retry,
PlayerPrefs/IndexedDB persistence) viaXApiRuntimeFactory. LastErrorsurfacing of SCORMGetLastError/LMSGetLastError.- Editor tooling: single-SCO
imsmanifest.xmlbuilder, index injector, post-build packaging, and an LMS validator window. - Runtime configuration asset synchronized from project settings.
- Sample scenes and quickstart examples for SCORM and xAPI.
Security¶
- WebGL bridge no longer logs learner data-model values to the browser console by default; verbose logging is opt-in via
window.__RevLearning_debug.