Documentación › Compilación de navegador
Compilación WebAssembly (navegador)
Phosphoric compila a WebAssembly mediante Emscripten: el emulador completo (CPU 6502, memoria,
VIA, PSG, ULA, teclado, casete, disco) funciona en una pestaña, renderizado en un <canvas>, audio
por Web Audio, teclado por el DOM — reutilizando la ruta SDL2 existente (puerto SDL de Emscripten).
Requisitos
Emscripten SDK activo (emcc en el
PATH):
git clone https://github.com/emscripten-core/emsdk
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh
Compilar y ejecutar
make wasm
(cd web && python3 -m http.server 8000)
# abrir http://localhost:8000/phosphoric.html
make wasm produce en web/: phosphoric.html (página + canvas),
phosphoric.js, phosphoric.wasm y phosphoric.data (las ROM de roms/
precargadas en el sistema de archivos virtual). La página arranca el Atmos (-r /roms/basic11b.rom); haz
clic en la pantalla para el foco de teclado y el audio.
Despliegue: assets necesarios + CSP
La página carga su lógica (definición de Module, UI, teclado, arrastrar y soltar) desde
web/shell.js — un archivo externo que hay que desplegar junto a
phosphoric.html/.js/.wasm/.data. shell.js está
versionado (fuente) y referenciado por phosphoric.html mediante
<script src="shell.js">.
Esta externalización hace el paquete compatible con una Content-Security-Policy estricta. Un host
que sirva la página bajo script-src 'self' 'wasm-unsafe-eval' bloquearía un <script>
inline (script-src-elem) → Module nunca definido → Module.canvas
undefined → error fatal en createContext WebGL. Al externalizar todo el JS (y reemplazar el
atributo inline oncontextmenu del canvas por un listener del DOM), phosphoric.html no tiene
ningún script/manejador inline: funciona bajo CSP estricta igual que bajo una permisiva (GitHub
Pages).
CSP mínima requerida
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'
'self'— permiteshell.jsyphosphoric.js(ambos externos).'wasm-unsafe-eval'— obligatorio:phosphoric.jscompila el módulo medianteWebAssembly.instantiateStreaming/instantiate, bloqueados bajoscript-src 'self'solo. Es el subtoken WASM (≠'unsafe-eval', mucho más amplio) — seguro.
⚠️ No quitar 'wasm-unsafe-eval': bajo un script-src 'self'
desnudo, la compilación WebAssembly se rechaza y el emulador no arranca. Tras la corrección de externalización, no se
necesitan ni 'unsafe-inline' ni hash/nonce — solo el token WASM.
Un bloqueo script-src-elem cuya fuente sea sandbox eval code (y no
phosphoric.html) proviene de una extensión del navegador, no de Phosphoric: la página no
inyecta ningún script inline ni eval. Sin efecto sobre el emulador.
Interfaz (página web)
La página muestra un raíl de iconos vertical a la izquierda (estilo JOric) y el teclado ORIC en overlay:
- MODEL — conmuta la máquina ORIC-1 / Atmos (insignia
1/A, reinicio en frío con la ROM elegida). - LOAD + arrastrar y soltar un
.tap/.dsksobre la pantalla: el archivo se inserta y la máquina reinicia sobre él. Botón EJECT para retirarlo. - Enlaces profundos (parámetros de URL) —
?rom=oric1|atmoselige la máquina y?media=<archivo>carga un medio en la primera carga. El tipo se deduce de la extensión:.tap→ casete,.dsk→ disquete (el controlador Microdisc se activa al arrancar). El archivo destino debe servirse en binario. - RESET — reinicio en frío conservando ROM y medio.
- KEYS — muestra/oculta el teclado virtual.
- FULL — pantalla completa (canvas centrado, relación 240/224 conservada).
- CRT — filtro de scanlines + viñeta (estado recordado).
- SAVE / REST — guarda el estado en un
.ost(descargado) / restaura un.osten caliente, sin reinicio. - LED TAPE / DISK — se encienden durante un CLOAD (casete) o un acceso a disco (WD1793 BUSY).
- Teclado virtual ORIC fiel, overlay semitransparente: disposición real (ESC, CTRL, FUNCT, 2× SHIFT, RETURN, DEL, SPACE, flechas) con modificadores pegajosos CTRL / FUNCT / SHIFT. En ORIC-1, la tecla FUNCT (solo Atmos) está ausente.
CTRL+T y otros acordes: el navegador reserva ciertos atajos (CTRL+T = nueva pestaña) a nivel del SO. Usa la tecla CTRL del teclado virtual: escribe la matriz ORIC mediante una llamada C directa, así que el navegador no la intercepta.
Sirve los archivos por HTTP (no file://): el navegador se niega a cargar
un .wasm desde el sistema de archivos local.
Detalles técnicos
- Bucle principal: en el navegador, el bucle
whilede C cede el control al bucle de eventos en cada frame, mediante Asyncify (-sASYNCIFY) +emscripten_sleep()en el limitador de frame — que también marca ~50 Hz. - Pila:
emulator_tes voluminoso (framebuffer + memoria) y vive en la pila demain(); la compilación fuerza-sSTACK_SIZE=8MB. - Red: las funciones que exigen sockets/hilos nativos (serie TCP/PTY/COM, stub GDB, servidor Cast, TLS) se enlazan como no-op en el navegador — el núcleo de la máquina, el vídeo, el audio, el teclado, el casete y el disco funcionan.
Fidelidad — verificado
La salida WASM es idéntica byte a byte a la compilación nativa con entradas idénticas: un arranque Atmos headless compilado a WASM y ejecutado bajo Node.js produce la misma captura PPM exacta que el binario nativo (probado a 2M y 5M ciclos). El determinismo de ciclo exacto del núcleo se preserva a través de la compilación WebAssembly.
El renderizado en navegador también se validó: la página cargada en Chromium headless muestra la pantalla de
arranque Atmos correcta, y la entrada de teclado funciona (escribir PRINT 6*7 + RETURN muestra
42) — arranque de ROM → inyección de teclado → ejecución BASIC → renderizado, enteramente en el navegador.
Reproducido de docs/wasm.md (versión de referencia actualizada en GitHub).