Tachibana

Documentation › Browser build

WebAssembly (browser) build

Phosphoric compiles to WebAssembly via Emscripten: the full emulator (6502 CPU, memory, VIA, PSG, ULA, keyboard, cassette, disk) runs in a tab, rendered on a <canvas>, audio through Web Audio, keyboard through the DOM — reusing the existing SDL2 path (Emscripten's SDL port).

Prerequisites

An active Emscripten SDK (emcc on the PATH):

git clone https://github.com/emscripten-core/emsdk
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh

Build and run

make wasm
(cd web && python3 -m http.server 8000)
# open http://localhost:8000/phosphoric.html

make wasm produces, in web/: phosphoric.html (page + canvas), phosphoric.js, phosphoric.wasm, and phosphoric.data (the roms/ ROMs preloaded into the virtual filesystem). The page boots the Atmos (-r /roms/basic11b.rom); click the screen for keyboard focus and audio.

Deployment: required assets + CSP

The page loads its logic (the Module definition, UI, keyboard, drag-and-drop) from web/shell.js — an external file that must be deployed alongside phosphoric.html/.js/.wasm/.data. shell.js is versioned (source) and referenced by phosphoric.html via <script src="shell.js">.

This externalisation makes the bundle compatible with a strict Content-Security-Policy. A host serving the page under script-src 'self' 'wasm-unsafe-eval' would block an inline <script> (script-src-elem) → Module never defined → Module.canvas undefined → a fatal error at the WebGL createContext. By externalising all the JS (and replacing the canvas's inline oncontextmenu attribute with a DOM listener), phosphoric.html has no inline script/handler at all: it runs under a strict CSP just as under a permissive one (GitHub Pages).

Minimum required CSP

Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'

⚠️ Do not remove 'wasm-unsafe-eval': under a bare script-src 'self', WebAssembly compilation is refused and the emulator does not start. After the externalisation fix, neither 'unsafe-inline' nor hash/nonce are needed — only the WASM token is.

A script-src-elem block whose source is sandbox eval code (not phosphoric.html) comes from a browser extension, not Phosphoric: the page injects no inline script or eval. No effect on the emulator.

Interface (web page)

The page shows a vertical icon rail on the left (JOric-style) and the ORIC keyboard as an overlay:

CTRL+T and other chords: the browser reserves some shortcuts (CTRL+T = new tab) at the OS level. Use the virtual keyboard's CTRL key: it writes the ORIC matrix via a direct C call, so the browser does not intercept it.

Serve files over HTTP (not file://): the browser refuses to load a .wasm from the local filesystem.

Technical details

Fidelity — verified

The WASM output is byte-identical to the native build for identical inputs: a headless Atmos boot compiled to WASM and run under Node.js produces the exact same PPM screenshot as the native binary (tested at 2M and 5M cycles). The core's cycle-accurate determinism is preserved across the WebAssembly compilation.

Browser rendering was also validated: the page loaded in headless Chromium shows the correct Atmos boot screen, and keyboard input works (typing PRINT 6*7 + RETURN shows 42) — ROM boot → keyboard injection → BASIC execution → rendering, entirely in the browser.

Reproduced from docs/wasm.md (authoritative, up-to-date version on GitHub).

← Documentation · Open the emulator →