# Web Export Export any SimVX game as a standalone HTML file that runs entirely in the browser. No server required — the game logic runs in [Pyodide](https://pyodide.org/) (CPython compiled to WebAssembly) and renders via WebGPU. Both 2D and 3D games are supported — 3D node usage is auto-detected. ## Quick Start ```bash uv run python -m simvx.graphics.web_export my_game.py \ --output game.html --width 800 --height 600 \ --title "My Game" --root MyGameNode ``` Open `game.html` in Chrome or Edge. The file is fully self-contained — host it on any static site. ## How It Works The export tool bundles everything into a single HTML file: ``` HTML file ├─ Pyodide runtime (loaded from CDN, ~15 MB, cached by browser) ├─ simvx.core engine (Python source, bundled inline) ├─ Game code (Python source, bundled inline) ├─ MSDF font atlas (pre-baked PNG, base64-encoded) ├─ WebGPU renderer (renderer2d.js + WGSL shaders, inlined) └─ Input capture (keyboard, mouse, touch → Python Input singleton) ``` Each frame: 1. JavaScript calls `WebApp.tick(dt)` via Pyodide 2. Python runs the scene tree: `physics_process()` → `process()` → `draw()` 3. `Draw2D` commands are serialized to a compact binary format 4. JavaScript passes the binary data to the WebGPU renderer 5. Four GPU pipelines render: filled shapes, lines, MSDF text, textured quads ## Requirements **Export machine:** Standard SimVX dev environment (freetype-py for atlas generation). **Browser:** WebGPU support required — Chrome 113+, Edge 113+, or Firefox Nightly with `dom.webgpu.enabled`. Safari support is in progress. ## Declaring Dependencies Games that import packages beyond `numpy` (which is always loaded) need to declare them so the export tool includes them in the Pyodide bundle. There are three ways, checked in priority order: ### PEP 723 Inline Script Metadata (single-file games) Add a `# /// script` block at the top of your game file. This is the [standard Python mechanism](https://peps.python.org/pep-0723/) for single-file scripts: ```python # /// script # dependencies = ["pillow>=10.0", "scipy"] # /// from simvx.core import Node class MyGame(Node): ... ``` ### pyproject.toml (project-based games) For games organised as a project with a `pyproject.toml`, declare dependencies in the standard `[project]` table: ```toml [project] name = "my-game" dependencies = [ "pillow>=10.0", "scipy", ] ``` The export tool reads `pyproject.toml` from the same directory as the game file. PEP 723 metadata takes precedence if both are present. ### CLI / API Override Pass additional packages directly, regardless of what the game declares: ```bash uv run python -m simvx.graphics.web_export my_game.py \ --packages requests aiohttp ``` ```python export_web("my_game.py", "game.html", extra_packages=["requests", "aiohttp"]) ``` These are merged with any declared dependencies. Version specifiers and `simvx-*` packages are automatically stripped. ## Command-Line Reference ``` uv run python -m simvx.graphics.web_export [options] ``` | Option | Default | Description | |--------|---------|-------------| | `--output`, `-o` | `game.html` | Output HTML file path | | `--width` | `800` | Engine viewport width | | `--height` | `600` | Engine viewport height | | `--title` | `SimVX` | Browser page title | | `--root` | auto-detect | Root `Node` subclass name | | `--fps` | `60` | Physics tick rate | | `--responsive` | off | Adapt engine viewport to browser window size | | `--packages` | none | Additional Pyodide packages to load | Root class is auto-detected from the first class definition in the game module. Specify `--root` if the file contains multiple classes. ## Python API ```python from simvx.graphics.web_export import export_web export_web( "my_game.py", "game.html", width=800, height=600, title="My Game", root_class="MyGameNode", physics_fps=60, extra_packages=["pillow"], ) ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `game_path` | `str \| Path` | required | Path to the game's Python module | | `output` | `str \| Path` | `"game.html"` | Output HTML file path | | `width` | `int` | `800` | Engine viewport width | | `height` | `int` | `600` | Engine viewport height | | `title` | `str` | `"SimVX"` | Browser page title | | `root_class` | `str \| None` | `None` | Root Node subclass (auto-detected if None) | | `physics_fps` | `int` | `60` | Physics tick rate | | `charset` | `str \| None` | `None` | Characters to pre-bake in the MSDF atlas | | `responsive` | `bool` | `False` | Adapt viewport to browser window size | | `pyodide_version` | `str` | `"0.29.3"` | Pyodide CDN version | | `extra_packages` | `list[str] \| None` | `None` | Additional Pyodide packages to load | ## Example: Tic Tac Toe The tictactoe example exports to a 256 KB HTML file (before Pyodide CDN): ```bash uv run python -m simvx.graphics.web_export \ packages/graphics/examples/game_tictactoe/game.py \ --output tictactoe.html --width 400 --height 550 \ --title "Tic Tac Toe" --root TicTacToeGame ``` The game renders identically to the desktop version — same UI widgets, same layout, same input handling. ## Font Atlas Text rendering uses MSDF (Multi-channel Signed Distance Field) font atlases. The export tool: 1. Finds a system font on the export machine 2. Scans your game's string literals to determine which characters are needed 3. Pre-renders the MSDF atlas at export time 4. Embeds the atlas as a base64 PNG in the HTML This eliminates the freetype-py dependency at runtime. If your game generates text dynamically (e.g., user input), ensure the charset covers the expected characters. ## Comparison: Web Export vs Video Streaming SimVX offers two ways to run games in a browser: | | Web Export | Video Streaming (`run_streaming`) | |---|---|---| | **Server** | None (static HTML) | Python server + GPU | | **Rendering** | WebGPU in browser | Vulkan on server, JPEG to browser | | **Latency** | Zero (local) | Network round-trip | | **3D support** | Yes (WebGPU) | Yes (Vulkan) | | **Browser** | WebGPU required | Any modern browser | | **Deployment** | Any static host | Needs running server + GPU | | **Startup** | ~5s (Pyodide load) | Instant | Use **web export** for distributing games on the web. Use **video streaming** when you need broader browser support or server-side computation. ## Limitations - **WebGPU required** — no Canvas2D or WebGL fallback. - **First load** — Pyodide runtime (~15 MB) is downloaded from CDN on first visit. Subsequent visits use the browser cache. - **Performance** — Python in WebAssembly is ~2-5x slower than native. UI-widget games run at 60fps easily; compute-heavy games may need optimisation. - **No audio** — `miniaudio` is not available in the browser. Audio support is planned via the Web Audio API. - **No filesystem** — `open()`, `subprocess`, and filesystem operations are not available. - **Pyodide packages only** — declared dependencies must be [available in Pyodide](https://pyodide.org/en/stable/usage/packages-in-pyodide.html). Pure-Python packages work; C-extension packages need Pyodide-specific builds.