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 (CPython compiled to WebAssembly) and renders via WebGPU. Both 2D and 3D games are supported — 3D node usage is auto-detected.

Quick Start

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 for single-file scripts:

# /// 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:

[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:

uv run python -m simvx.graphics.web_export my_game.py \
    --packages requests aiohttp
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 <game.py> [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

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):

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 audiominiaudio is not available in the browser. Audio support is planned via the Web Audio API.

  • No filesystemopen(), subprocess, and filesystem operations are not available.

  • Pyodide packages only — declared dependencies must be available in Pyodide. Pure-Python packages work; C-extension packages need Pyodide-specific builds.