Skip to content

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

  • CompletionStatus and SuccessStatus on IScormDataModel, so SCORM 2004's two axes can be reported independently.
  • Cmi.Entry, Cmi.Credit and Cmi.PassingScore: whether the launch is a resume, whether it counts, and the score the LMS treats as a pass.
  • XApiActor.FromLmsLearner, FromAccount, and account support, so xAPI can identify the learner the LMS reported.
  • PendingStatementCount, FlushPendingAsync and ClearPendingStatements on IXApiRuntime.
  • PassCourseAsync and FailCourseAsync.
  • 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.SetCompletion overload taking CompletionStatus, which cannot express a value the completion axis has no meaning for.
  • Declared package dependencies for 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 Completed write is suppressed once success is recorded while Browsed is not, why the xAPI delivery tri-state stays internal while callers see Task<bool>, why oversized suspend data is refused rather than truncated, and why LessonStatus.Unknown and LessonMode.Normal sit 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. LessonStatus is reordered so Unknown is the zero value. Passed held it before, which made default(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-set LessonStatus field.
  • Breaking. LessonMode is reordered so Normal is the zero value, matching what both standards treat as the default launch mode. Browse held 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.success is now SuccessStatus? and Objective.completion is now CompletionStatus?, instead of both being LessonStatus?. 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, XApiDeliveryOutcome and ScormTime are 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 is IXApiRuntime.SendStatementAsync.
  • Behaviour. An over-limit suspend_data write 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 raw score is clamped into [min, max] and reported. An LRS is obliged to reject the whole statement otherwise, so Passed(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.
  • commitOnEachInteraction is 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 UniversalAdditionalCameraData and UniversalAdditionalLightData by GUID with no URP dependency declared, so first contact on a Built-in-RP project was a pair of missing-script warnings.
  • CompleteCourseAsync now marks the course completed, not passed. Use PassCourseAsync for assessed content.
  • SetScore reports cmi.score.scaled on SCORM 2004, and rescales an out-of-0-100 range to a percentage on SCORM 1.2.
  • CommitAsync and FinishAsync complete synchronously.
  • AnsweredAsync no longer marks the activity complete.

Fixed

  • SCORM 1.2: MarkCompleted() after MarkPassed() destroyed the pass. The guard that stops completion retracting a recorded outcome existed only on CompletionStatus, and MarkCompleted writes through LessonStatus — so SCORM 2004 and the Editor stub kept the pass while every real 1.2 session replaced it with completed, following the finish sequence the FAQ recommends. A learner who passed was recorded as merely complete, and platforms gating certification on lesson_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 requires incorrect and 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. RecordChoice wrote multiple_choice, a token in neither standard's vocabulary, so strict platforms refused the type element and recorded typeless questions. Both standards now get choice, which is what the vocabulary actually calls a multiple-choice question.
  • SCORM 2004 wrote timeout for 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.SetCompletion silently 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 as 8, corrupting PassingScore, score readback, cmi.score.scaled, scaled_passing_score and progress_measure.
  • Cmi.SuspendData returned RevLearning's internal REVLEARNING:GZIP64: encoding when the runtime had compressed the value to make it fit.
  • The xAPI delivery contract was inverted in both directions. SendStatementAsync returned 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 nine XApiStatements helpers.
  • 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.registration was 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.LastError and DescribeLastError(). It was built with Object.create(api) and given own properties only for the calls it wraps, so every other method ran with this bound to the proxy — and an adapter that keeps error state on this, 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_responses is 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 FinishAsync reached a terminated API and were discarded.
  • SCORM 1.2 suspend_data could 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.
  • LmsError described 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_time over 24 hours parsed as zero.
  • Re-answering a question created a second SCORM 1.2 interaction record.
  • Manifest schemaLocation pointed 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

  • ILmsRuntime abstraction 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) via XApiRuntimeFactory.
  • LastError surfacing of SCORM GetLastError / LMSGetLastError.
  • Editor tooling: single-SCO imsmanifest.xml builder, 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.