Host the experimental Warcraft III browser build on GitHub Pages
Status: deployed by the repository's GitHub Pages workflow.
OpenRealm's python3 port/web/serve.py is a convenient local test server that
adds Cross-Origin-Opener-Policy: same-origin and
Cross-Origin-Embedder-Policy: require-corp. GitHub Pages is a static host and
does not offer repository-level control of those response headers. A scoped
Service Worker can add the headers to same-origin responses after it controls
the page. Initial navigation cannot rely on a not-yet-installed worker.
Build and deployment outline
- The GitHub Actions workflow prepares the embedded font, builds the
engine (not the foundation smoke test) with Emscripten, and runs
mkdocs build. port/web/publish-pages.pycopies allowed runtime assets tosite/play/before uploading the Pages artifact. It excludes generated unity C and object files. Do not publish Blizzard MPQs or imported disc contents.- The game-scoped
coi-serviceworker.jsand startup gate inpages-isolation.jsare shipped with the game. The worker scope is/open-realm/play/, matching the repository Pages URL. - Pushes to
mainthat change documentation or browser-build inputs trigger the Pages deployment workflow. No game link is added to documentation navigation. - Visit
https://corepunch.github.io/open-realm/play/. On the first visit, the page installs the worker and reloads once under its control. VerifycrossOriginIsolated === truebefore the threaded engine starts. Ordinaryfile://URLs do not work.
A repository project site has a path prefix; avoid root-absolute URLs such
as /index.wasm or /sw.js. Relative URLs resolve within that project's
Pages origin and path. Emscripten-generated asset paths must follow the same
rule. The deploy workflow uses the game-scoped worker described below; the
sample worker remains a separate reference for a cache-enabled deployment.
Diagnosing a failed docs build
The Deploy Docs / build job compiles the browser engine before running
MkDocs. A compiler or linker failure therefore skips both documentation
generation and the dependent deploy job. Inspect the first failed step with
gh run view <run-id> --repo corepunch/open-realm --log-failed before changing
Pages permissions or Markdown.
Run 39110161645 at commit 082f398303bd failed at the Emscripten link:
setting GROWABLE_ARRAYBUFFERS expects bool but got int. Commit
39d7d2d41 had added -sGROWABLE_ARRAYBUFFERS=2, but the pinned SDK 4.0.21
defines that setting as an experimental boolean (see its
settings source).
Remove the override and retain -sALLOW_MEMORY_GROWTH=1; changing it to 1
would enable an experimental browser feature rather than restore the default.
After removing that flag, the same commit also failed with duplicate
FDF_ParseLayerStyle definitions in game.o and menu.o. The parser added
in 8d5a74dff must follow the existing module namespaces in
port/web/src/web_fdf_namespace.h, just like FDF_ParseControlStyle.
Keep the game and menu FDF instances separate; sharing their registries or
weakening duplicate-symbol checks would break their native ownership model.
SDK 4.0.21 can emit -Wpthreads-mem-growth with this supported memory-growth
path. It warns about JavaScript performance, not a failed link; the engine
still needs pthreads and memory growth. Do not enable an experimental memory
feature merely to suppress that diagnostic.
To verify the full artifact locally with Emscripten 4.0.21 active:
python3 -m pip install -r requirements-docs.txt
make font
make web-warcraft3-engine
mkdocs build --strict
python3 port/web/publish-pages.py build/web/engine site/play
python3 -m unittest discover -s port/web/tests -p 'test_*.py'
MkDocs and its Material theme are pinned in requirements-docs.txt.
Strict builds reject missing navigation pages, document links and anchors. Source files and
guides outside docs/ must use GitHub links, because MkDocs does not publish
them; paths to ignored local investigation artifacts should be plain text.
Registration and startup gate (illustrative)
<script>
async function enablePagesIsolation() {
if (!('serviceWorker' in navigator)) {
throw new Error('Service Workers are unavailable');
}
const scope = new URL('./', location.href);
const worker = new URL('sw.js', scope);
await navigator.serviceWorker.register(worker.href, {scope: scope.pathname});
await navigator.serviceWorker.ready;
if (!navigator.serviceWorker.controller) {
// The first navigation is not isolated: perform one deliberate reload.
// Fail visibly instead of looping if the worker still cannot control it.
const reloadKey = 'openrealm-pages-worker-reload';
if (sessionStorage.getItem(reloadKey))
throw new Error('Service Worker did not take control after reload');
sessionStorage.setItem(reloadKey, '1');
location.reload();
return false;
}
sessionStorage.removeItem('openrealm-pages-worker-reload');
if (!crossOriginIsolated || typeof SharedArrayBuffer === 'undefined')
throw new Error('Cross-origin isolation unavailable after worker control');
return true;
}
</script>
Important integration detail: port/web/site/filesystem-bridge.js can
start the game immediately when it finds OPFS data. The deployed page therefore
loads pages-isolation.js before the filesystem bridge, and
openrealmStartEngine waits for its result before loading the Emscripten
engine. A failed isolation check reports an error and releases the archive
generation lease instead of leaving an unusable tab holding it.
The deployed bootstrap waits for controllerchange, reloads once, and reports
a visible failure if isolation is still unavailable after that reload. It does
not initialize pthreads, WasmFS or the game renderer before isolation succeeds.
Worker and security rules
The example in examples/github-pages-sw.js handles only same-origin GET
requests inside its own registration scope. It injects COOP/COEP on HTML,
JavaScript, WASM and related same-origin responses, and uses Cache Storage for
the web application files. It does not cache ISO or MPQ user data:
Warcraft III archives remain in browser-local OPFS and are never uploaded.
Service Workers cannot bypass CORS. An opaque cross-origin response cannot be made into a readable cross-origin-isolated resource by adding headers. Host all required engine assets on the same origin or explicitly configure third-party assets with compatible CORS/CORP policies. Cross-origin resources needed by a COEP page may be blocked.
Cache Storage/OPFS are origin-scoped and may be evicted. This example uses best-effort network-first caching and deliberately does not assert that a first launch is fully offline capable. Full offline support needs a complete asset inventory, cache-install validation, quota handling and real-browser verification. The worker never caches POST requests or imports. Preserve the normal browser's service-worker update behavior and use a versioned cache name so changed engine assets cannot silently mix with earlier versions.
Local validation without serve.py
For local development, start the header-enabled repository server from the repository root:
Open http://127.0.0.1:8000/. The server provides isolation headers directly,
so the Pages Service Worker path is not involved. Check in developer tools:
The expected values are true, true, and "function".
Also confirm a threaded engine launches and reads an OPFS installation.
Neither loading the page nor seeing a registered worker proves full game
compatibility. serve.py remains useful for native header-driven local
development and debugging without Service Workers.