Sigil 0.17.11
The graphics-and-audio-on-the-web release. A Sigil program that draws with (sigil graphics) and plays sound through (sigil audio sink) now compiles to native WebAssembly and runs in a browser, with no Emscripten anywhere in the toolchain and no compile-C-directly workarounds in the build. This is the foundation the Cinder Cadence web port stands on: the full music-reactive game, native-compiled to a single WebAssembly module, rendering and playing in the browser. Two native-backend correctness fixes round out the release. Existing projects build without changes.
Graphics in the browser through your own GL binding
The new sigil-wasm-gles3 package renders reused sokol_gfx in the browser through a raw WebGL2 binding, with zero Emscripten. It exposes the full set of gl* entry points sokol's GLES3 backend calls (around 122 of them) as a WebAssembly import module, backed by a WebGL2 context and integer-to-object handle tables on the JavaScript side. The surface is complete by construction: real implementations for the entry points WebGL2 actually uses, plus warn-once stubs filling the rest, so the wasm always instantiates.
On top of the binding sits an app-shell, the browser counterpart to sokol_app. A Sigil graphics program drives a render loop (gles3-use-canvas, resize-to-display, start-loop / stop-loop) and loads textures over the network with an async image fetch (gles3-fetch-image), decoding the fetched bytes with stb_image through (sigil graphics image), no filesystem required. The import surface of a built web app stays gl.* plus wasi plus the binding's own module, nothing else.
Audio in the browser
sigil-wasm-audio gains an AudioWorkletNode path: streaming audio now drains on the audio thread over a SharedArrayBuffer when the page is cross-origin isolated, and degrades to a ScriptProcessorNode over a plain ArrayBuffer when it is not. Both paths share one atomic ring buffer, so the sink's synchronous per-frame backpressure behaves identically either way.
For sound effects there is play-audio-sample, a fire-and-forget one-shot that reads a short PCM bytevector and plays it through a WebAudio buffer source. WebAudio mixes it with the streaming music and any other concurrent one-shots, and reclaims the node when it ends, so overlapping effects like rapid fire and hits need no mixing on the Sigil side. The platform does it.
First-class web builds
Dependencies can now be gated by build config. A configs: field on a from-git, from-path, or from-workspace dependency names the configs it applies to; a dependency whose list omits the active config is dropped from the build. That makes sigil build --config web first-class: a native-only C dependency like sigil-app (whose SOKOL_GLCORE and X11 sources cannot build for wasm32-wasi) is simply excluded from a web build rather than worked around. Fetching a dependency's source stays config-agnostic, so deps install is unaffected; the filtering happens in the build and link path where the active config is known.
Native-backend correctness
Two fixes for the native backend, both surfaced by real programs:
- Aborting to a prompt no longer corrupts a neighboring frame's registers. The 0.17.9 preemptive-yield work parked an abort's tag and values at the current frame's register-window boundary, which was unsound: on the bridged path a call return between the abort and its relay could leave that pointer aimed at the wrong register file, and consumed values could linger as stack residue exactly where an overlapping register window places a later frame's registers. The symptom was a "call: not a procedure" crash when a second async session reused a slot the first session's abort had left dirty. Abort values now travel out of band, off the value stack entirely.
- A suspended fiber's exception handlers no longer catch raises from other code. A native
call-with-promptbody runs on a fiber and pushes no bytecode frames, so guard handlers it installed lingered on the shared handler stack while the fiber was suspended, and a raise from anywhere else (the prompt handler, or code that ran after the prompt returned) could be caught by the suspended fiber's guard. Native fibers now save and restore their own slice of the handler stack, mirroring the existing per-fiber treatment of dynamic-wind winders.