Build¶
Turning a WebGL build into something an LMS will accept.
| File | Job |
|---|---|
ScormBuildMenu | The menu items, and the build-then-package sequence |
ScormManifestBuilder | Generates imsmanifest.xml |
ScormPostBuild | Assembles the zip |
IndexInjector | Injects the page helper into the generated index.html |
The manifest¶
Single-SCO only, both standards. Multi-SCO packages, sequencing and navigation are out of scope — see the FAQ for the full list of what is not covered.
No mastery score is written, deliberately. There is no setting for one, which is why Cmi.PassingScore reads null for in-box packages: the manifest carries nothing, so the LMS has nothing to report. Pass and fail are decided in your content.
Compression is the one that bites¶
A WebGL build with Brotli or gzip compression enabled produces files the browser can only decompress if the server sends the matching Content-Encoding header. An LMS serving a package from a zip usually does not. The result is a course that works perfectly from a local folder and shows a blank screen on the LMS.
CompressionIsLmsSafe checks the Player settings before packaging and refuses with an explanation rather than producing a package that fails on upload. CompressionOverrideAcknowledged is the escape hatch for someone who knows their hosting does send the header.
IndexInjector¶
Adds a small script to index.html that commits learner progress when the tab closes, so a learner who closes the browser mid-course does not lose the session.
Two details in here were real bugs and are worth not reintroducing:
The injection is a single splice at a case-insensitively located anchor. It used to test for the anchor with Contains and then insert with Replace, which are not the same search — Replace is case-sensitive and replaces every occurrence. A template whose tag case differed got either no injection or several.
The API proxy binds every method it can reach, including inherited ones. The page helper wraps the LMS API object in a proxy. Copying only the object's own enumerable methods left inherited ones unbound, and the proxy then accumulated its own ErrorNumber/ErrorString properties that shadowed the adapter's — so GetLastError() through the proxy returned 0 while the real API had 351 waiting. ILmsRuntime.LastError read "no error" for an entire session. That was found on SCORM Cloud, not in a test, and Tests~/js/injector.test.mjs now has identity-sensitive fakes that reproduce it.