Building the browser editor¶
The Pages application compiles apps/editor with Qt Widgets for WebAssembly.
The UI, project/configuration models and serializers are shared with the desktop
editor. Browser file import/download and explicitly unavailable native-process/audio-playback
adapters are private implementations selected by the editor target. The game
viewport remains a separate graphics concern; this port does not replace Qt
with the engine's unfinished UI toolkit.
Qt can remain the common editor shell for Linux, macOS and the browser. macOS editor setup, launching and native validation remain separate work, as does its Metal game viewport. Platform-specific shells would duplicate commands and workspace behavior without solving the renderer/backend gap.
Reproduce the Pages payload¶
Use Python 3.12 or newer on Linux x64, with Clang/LLD 18 installed:
python scripts/build-web-editor --bootstrap
python scripts/build-web-editor --check-package
python -m unittest discover -s scripts/python -p test_editor_web.py -v
The bootstrap installs the pinned CMake/Ninja, Emscripten SDK, official Qt host
tools and verified Qt source archive into ignored out/ directories. Qt's pin
is in config/editor_web_toolchain.json; the compiler/WebGPU pins remain in
config/web_toolchain.json. Qt is built from source with the engine SDK because
prebuilt WebAssembly Qt requires its matching Emscripten version. A source build
with Ludus's newer pinned SDK needs validation whenever either pin changes.
The same build also prepares the pinned Luau compiler/analyzer and shader tools, then builds Cornell Box, Live Edit Game and Scripted Game as standalone static web players. These compile the existing sample C++ implementations; Scripted Game cooks its actual Luau package on the build host and links the Wasm provider. Cornell Box yields asynchronous device/pipeline creation through Asyncify. The players use the public browser RHI with WebGPU/WebGL 2 fallback and independent canvases; closing their iframe releases the entire player runtime. Native GameHost processes and dynamic module loading are not needed for web playback.
Player assets under players/<sample>/ are required and hash-checked alongside
the editor payload. Every Wasm module uses the bounded single-threaded memory
policy. The package includes Luau and WebGPU notices. A project opts into a
shipped player with ludus.web.json (version: 1, player equal to a shipped
sample identifier). Arbitrary URLs and unknown identifiers are rejected.
This static Qt build uses no C++ exceptions, one thread and Asyncify for the
existing modal dialogs. It needs no cross-origin isolation headers. The editor
starts with 32 MiB of WebAssembly memory and may grow to 2 GiB (QT_WASM_MAXIMUM_MEMORY), enough to cook and
preview photographic scenes in process. Do not silently
switch it to shared memory: Pages cannot supply arbitrary response headers.
An existing host Qt installation of the pinned version can be supplied with
--qt-host /absolute/path/to/Qt/6.10.3/gcc_64 (or macos). To rebuild modified
Qt sources in out/editor-tools/src/qtbase, use --rebuild-qt. The source and
rebuild scripts are available to users of the statically linked LGPL Qt build;
license texts and bundled dependency notices accompany the payload.
The bounded import wrapper uses Qt 6.10.3's private WebAssembly event callbacks
and file reader behind one private .cpp boundary. Its admission callback checks size
before reading, and Qt owns the suspend/resume dispatch. This is an intentional
version coupling: editor upgrades must verify this adapter in a browser. Browser
downloads use a separate JavaScript bridge with copied bytes and explicit names.
Static analysis still uses Clang 23. web_build.analysis_commands enables
-frelaxed-template-template-args for the standard template-matching rule that
is already the default in the newer SDK compiler; this is required to parse
Emscripten's val.h. The language mode remains C++23 and diagnostics remain errors.
Scene import and viewport¶
World authoring works on the session's in-memory (MEMFS) project. Import
scene⦠offers a scene folder, or one .zip (stored or deflated entries, no
ZIP64) or a .pbrt with its companion files. The selection is copied into
MEMFS, then the linked SceneCook importer cooks it to LUSCN in process, exactly
as on the desktop; MEMFS has no flock, so the cook lock is skipped there
(one thread owns the file system). MEMFS also has no hard links: immutable
artifacts use an existence check followed by rename without yielding. Existing
identical artifacts are reused, while conflicting contents reject publication.
These writes publish session memory and promise no disk durability. Native cooks
retain their atomic hard-link publication. The build is single-threaded, so the cook
runs on the UI thread after the status text has painted; large scenes block
input until the cook finishes.
The interactive viewport renders imported scenes through the engine's GPU raster renderer on a separate WebGPU canvas. Qt retains its own canvas for widgets. Scene meshes, textures and materials use the same GPU generation and camera path as the native editor; the renderer tone-maps its display image and composites it to the browser surface in one submission, without CPU pixel readback or image upload. Orbit, pan, zoom, focus, shading and resize update the current scene. Output is bounded to 1024 physical pixels on its longest side.
Solid is the neutral navigation visualization; Albedo shows unlit surface colors; Lit uses admitted authored direct lights, emission and environment. Authored raster shadows use the shared shadow atlas path. Shadow preparation records offscreen WebGPU submissions without acquiring or clearing the visible canvas, so pending candidates retain the last presented view. The browser profile does not run the native worker-based static lighting bake. Emissive geometry does not imply real-time indirect lighting. Pending resource preparation retains the last presented view; rejected candidates report a failure without replacing it. A completed image is required before an import is published.
WebGPU requires a supported browser/device and a secure context (HTTPS or localhost). If device admission fails, the viewport explains that WebGPU is unavailable and document editing remains usable; it does not silently rasterize on the CPU. Device loss stops the GPU session and preserves documents. Reopen the scene or restart the preview to request a fresh device. The separate canvas follows the Qt viewport rectangle and hides with its tab, popups and modal dialogs; mouse and keyboard navigation continue through Qt.
Preview the complete website¶
Build MkDocs and the API reference first, then copy the editor payload; MkDocs cleans its output directory on each build:
python -m mkdocs build --strict
python scripts/build-api --bootstrap
cp -R out/editor-pages/. out/wiki/editor/
python scripts/check-wiki
Serve the complete site beneath /Ludus/ to match the production project path.
Test loading, editing, importing and downloaded JSON in an actual browser after
changing Qt, the SDK or the editor. Artifact checks cannot prove the application
runs. The browser editor guide describes the
supported workflows and session storage contract.
Pages and caching¶
The wiki workflow validates the editor independently and downloads its artifact only after MkDocs/API generation. The final editor cache key includes editor, engine/dependency, CMake/toolchain, loader and HTML sources. Ordinary guide and API prose updates reuse that payload. A second cache retains the source-built Qt and SDK for editor source changes. Each cache hit still runs package validation; only the main Pages deployment publishes the combined artifact.