ドキュメント › ブラウザビルド
WebAssembly(ブラウザ)ビルド
Phosphoric は Emscripten 経由で WebAssembly にコンパイルされます:完全なエミュレータ(6502 CPU、メモリ、VIA、PSG、ULA、キーボード、カセット、ディスク)がタブ内で動作し、<canvas> に描画、音声は Web Audio、キーボードは DOM 経由 — 既存の SDL2 パス(Emscripten の SDL ポート)を再利用します。
前提
有効な Emscripten SDK(emcc が PATH に):
git clone https://github.com/emscripten-core/emsdk
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh
ビルドと実行
make wasm
(cd web && python3 -m http.server 8000)
# http://localhost:8000/phosphoric.html を開く
make wasm は web/ に生成します:phosphoric.html(ページ + canvas)、phosphoric.js、phosphoric.wasm、phosphoric.data(roms/ の ROM を仮想ファイルシステムにプリロード)。ページは Atmos を起動します(-r /roms/basic11b.rom);キーボードフォーカスと音声のため画面をクリックしてください。
配置:必要なアセット + CSP
ページはロジック(Module の定義、UI、キーボード、ドラッグ&ドロップ)を web/shell.js から読み込みます — これは phosphoric.html/.js/.wasm/.data と並べて配置する外部ファイルです。shell.js はバージョン管理下(ソース)にあり、phosphoric.html から <script src="shell.js"> で参照されます。
この外部化により、バンドルは厳格な Content-Security-Policy と互換になります。script-src 'self' 'wasm-unsafe-eval' でページを配信するホストは、インラインの <script> をブロックし(script-src-elem)→ Module が未定義 → Module.canvas が undefined → WebGL の createContext で致命的エラー、となります。すべての JS を外部化し(canvas のインライン oncontextmenu 属性を DOM リスナに置換)、phosphoric.html はインラインのスクリプト/ハンドラを一切持ちません:厳格な CSP でも寛容なポリシー(GitHub Pages)でも同様に動作します。
必要最小限の CSP
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'
'self'—shell.jsとphosphoric.js(ともに外部)を許可。'wasm-unsafe-eval'— 必須:phosphoric.jsはWebAssembly.instantiateStreaming/instantiateでモジュールをコンパイルし、これはscript-src 'self'のみではブロックされます。これは WASM 用のサブトークン('unsafe-eval'よりはるかに狭い)で安全です。
⚠️ 'wasm-unsafe-eval' を外さないでください:素の script-src 'self' では WebAssembly のコンパイルが拒否され、エミュレータは起動しません。外部化の修正後は 'unsafe-inline' もハッシュ/nonce も不要 — WASM トークンのみで十分です。
ソースが sandbox eval code(phosphoric.html ではない)の script-src-elem ブロックは、Phosphoric ではなくブラウザ拡張由来です:ページはインラインスクリプトや eval を注入しません。エミュレータへの影響はありません。
インターフェース(Web ページ)
ページは左側に縦のアイコンレール(JOric 風)と、オーバーレイの ORIC キーボードを表示します:
- MODEL — ORIC-1 / Atmos を切替(バッジ
1/A、選択した ROM でコールドリスタート)。 - LOAD + 画面への
.tap/.dskのドラッグ&ドロップ:ファイルが挿入され、マシンがそれで再起動。EJECT で取り出し。 - ディープリンク(URL パラメータ) —
?rom=oric1|atmosでマシンを選択、?media=<ファイル>で初回読み込み時にメディアをロード。種類は拡張子から判定:.tap→ カセット、.dsk→ ディスケット(起動時に Microdisc コントローラを有効化)。対象ファイルはバイナリで配信する必要があります。 - RESET — ROM とメディアを保持したままコールドリスタート。
- KEYS — 仮想キーボードの表示/非表示。
- FULL — フルスクリーン(canvas を中央に、240/224 の比率を保持)。
- CRT — スキャンライン + ビネットのフィルタ(状態を記憶)。
- SAVE / REST — 状態を
.ostに保存(ダウンロード)/.ostを再起動なしでその場で復元。 - TAPE / DISK LED — CLOAD(カセット)やディスクアクセス(WD1793 BUSY)中に点灯。
- 忠実な仮想 ORIC キーボード、半透明オーバーレイ:実配列(ESC、CTRL、FUNCT、SHIFT ×2、RETURN、DEL、SPACE、矢印)とスティッキーな CTRL / FUNCT / SHIFT 修飾。ORIC-1 では FUNCT キー(Atmos 専用)はありません。
CTRL+T などのコード入力:ブラウザは一部のショートカット(CTRL+T = 新規タブ)を OS レベルで予約します。仮想キーボードの CTRL キーを使ってください:直接の C 呼び出しで ORIC マトリクスに書き込むため、ブラウザは横取りしません。
ファイルは HTTP で配信してください(file:// は不可):ブラウザはローカルファイルシステムからの .wasm 読み込みを拒否します。
技術的な詳細
- メインループ:ブラウザでは C の
whileループがフレームごとにイベントループへ制御を返します。Asyncify(-sASYNCIFY)+ フレームリミッタ内のemscripten_sleep()で実現 — これが ~50 Hz のペースも与えます。 - スタック:
emulator_tは大きく(フレームバッファ + メモリ)、main()のスタックに置かれます;ビルドは-sSTACK_SIZE=8MBを強制します。 - ネットワーク:ネイティブのソケット/スレッドを要する機能(TCP/PTY/COM シリアル、GDB スタブ、Cast サーバ、TLS)はブラウザでは no-op としてリンク — マシンコア、映像、音声、キーボード、カセット、ディスクは動作します。
忠実性 — 検証済み
WASM 出力は同一入力に対してネイティブビルドとバイト単位で同一です:WASM にコンパイルし Node.js で実行した headless の Atmos 起動は、ネイティブバイナリとまったく同じ PPM スクリーンショットを生みます(200 万・500 万サイクルで検証)。コアのサイクル精度の決定論は WebAssembly コンパイルを通して保たれます。
ブラウザレンダリングも検証済み:headless Chromium で読み込んだページは正しい Atmos 起動画面を表示し、キーボード入力も動作します(PRINT 6*7 + RETURN の入力で 42 を表示)— ROM 起動 → キーボード注入 → BASIC 実行 → 描画まで、すべてブラウザ内で。
docs/wasm.md から再掲(正となる最新版は GitHub)。