ADR-009: WASM Build Smoke Test
Status: Accepted
Date: 2026-07-04
Context
build-wasm.yml compiles OrcaSlicer, links it into slicer.wasm, and — until
this change — went straight from a successful compile to packaging and
publishing a GitHub Release. Compiling cleanly is not the same as working: this
project has twice shipped an engine that compiled fine but trapped on a real
slice —
- The Voron Design Cube v7 STL triggered a "memory access out of bounds" trap
in Arachne's wall generator (uninitialized
WallToolPathsParamsfields — seeorca-wasm/patches/apply.pysections 8/8c–8f and the--profiling-funcscomment inorca-wasm/wasm/CMakeLists.txt, which references ascripts/repro-real-stl.mjsreproduction script that was used locally but never committed to the repo). Print::get_hrc_by_nozzle_type()'sBOOST_LOG_TRIVIAL(error)call trapped on every slice untilensure_nozzle_info_json()was added (seeorca-wasm/bridge/slicer.cpp).
Neither of these would have been caught by emmake cmake --build succeeding.
They were only found by someone manually slicing a real file against a real
deployed build.
Decision
Add orca-wasm/scripts/smoke-test.mjs: a plain Node script (no TS build step,
matching scripts/download-wasm.mjs's style) that loads the just-built
slicer.js + slicer.wasm and runs real orc_init/orc_slice/orc_slice_multi
calls end-to-end:
- default — a representative config (Generic 0.4mm nozzle, PLA, 0.2mm
layers), run against two meshes: a synthetic torture-test mesh (a
subdivided icosphere, ~5120 triangles — generated in-memory, no
redistribution question, runs fully offline) and the real Voron Design
Cube v7 (
e2e/fixtures/voron-design-cube-v7.stl, vendored under GPL-3.0 — see ADR-010 for why that's safe alongside this repo's AGPL-3.0-or-later). The real mesh is not optional window-dressing: it's the exact model that found both crashes in this ADR's own Context section, and later a Boost.Log-amplified hang/trap that the synthetic mesh never triggered (see the "disable Boost.Log core" fix inorca-wasm/bridge/slicer.cpp) — synthetic-only coverage would have shipped that regression again.--fixture <path>replaces both meshes with one specific file, for a closer repro of a particular case. - fuzzy skin = all — exercises the libnoise-backed FuzzySkin path (ADR
context:
mkdocs-docs/architecture.md's "libnoise" section). - classic wall generator — a regression control against the Arachne default, so a future Arachne-specific regression doesn't take down the classic path's coverage with it.
- plate, 2 objects, per-object
extruderoverride — probes theextruder_idsplumbing added in ADR-008 on its own: both objects take the same slot andnozzle_diameterstays length 1, so this is the AMS-style single-nozzle path. - plate, 2 objects on a real dual-nozzle machine — added in
#160, when real multi-nozzle
profiles stopped being a known crash risk. A Bambu Lab H2D-shaped config
(
nozzle_diameterlength 2, one printable area and one filament per nozzle,filament_map[1,2]) with the two objects on different slots, asserting bothT0andT1appear. This is the scenario the whole class of bugs hid in: a config that silently collapses to a single filament still slices and still passes the sanity assertions — it just prints everything with one tool.
Scenarios 2–5 also run against both meshes, not just the synthetic one — every scenario × mesh combination is exercised (10 total by default).
Each scenario asserts the return code is 0 and the resulting G-code is
non-trivially sized and contains real G1 extrusion moves — not just "didn't
crash" but "produced something that looks like a slice." On failure, the
script prints the engine's own orc_decode_exception() message, not just a
generic Node stack trace, and exits non-zero.
Wired into build-wasm.yml as a step between "Package WASM artifacts" and
"Upload artifacts"/the GitHub Release step, so a broken build never reaches
either. Also appended to orca-wasm/scripts/build-local-wsl.sh (regenerated
via scripts/gen-wsl-build-script.mjs — that file is generated, not
hand-edited) and exposed as npm run smoke-test for ad-hoc use, e.g. after
npm run setup downloads a pre-built engine.
Consequences
- Positive: A build that compiles but traps (or silently produces garbage) is caught in CI, before anyone downloads it.
- Positive: Directly exercises the ADR-008 session-handle refactor and the
new
extruder_idsparameter — the first real compiler+runtime check either gets, since this sandbox has no Emscripten toolchain to build against locally. - Negative / accepted scope: Verifies "doesn't crash and looks like a real slice," not slice quality (wall placement, timing, exact G-code content). A golden-file G-code comparison would catch quality regressions too, but needs regenerating on every legitimate engine/profile change — deferred until the crash-class of regression (the one that has actually bitten this project twice) is reliably covered.
- Negative: Adds Node + a few seconds of slicing to
build-wasm.yml, negligible next to the ~1–3 hour dependency build. Running every scenario against both meshes roughly doubles this step's time — still seconds, not a meaningful cost.