Shipping a Game

A SimVX game reaches players by three routes. None of them is a fallback for the others: pick the one that matches your audience.

Route

What the player gets

Install burden

Source and wheel

pip install your-game, then simvx run game.py

Needs Python

Standalone executable

A folder (or one file) they double-click

None

Web export

A URL

None; also the mobile answer

Web export is covered in its own page, Web Export. This page covers the other two.

Source and wheel

The natural path for a Python engine. Build a wheel for your game, publish it, and a player with Python installs it like any other package:

uv build --wheel
pip install dist/your_game-1.0.0-py3-none-any.whl
your-game

This is the path with the least that can go wrong, because nothing repackages your dependencies: numpy, the Vulkan loader and the engine’s native extensions are installed by pip exactly as their authors built them.

Standalone executable

For players who will not install Python. The editor’s Export dialog offers a Standalone Executable target, and simvx uses PyInstaller underneath.

The engine has four native pieces that a bundler has to carry: numpy, the Vulkan loader, the CFFI audio extension, and (if you use it) the Jolt physics .so. It also loads shaders, fonts and default assets through importlib.resources, which a bundler cannot see by reading import statements. That second point is the one that bites: a bundler invoked with no options produces a binary that dies on its first frame with ModuleNotFoundError: No module named 'simvx.graphics.shaders'.

So the collection flags are not optional:

pyinstaller --collect-all simvx --collect-all vulkan --collect-all glfw --collect-all freetype \
            --hidden-import miniaudio --hidden-import _cffi_backend \
            game.py

Expect roughly 155 MB for a one-directory build of a small game, most of it numpy, Pillow and the engine’s own assets. It starts in about the same time as running the game from a virtualenv: the engine’s Vulkan setup dominates, and the bundling costs nothing measurable.

--onefile compresses those 155 MB to about 62 MB, at the cost of unpacking into a temporary directory on every launch: measured on Linux, that adds roughly 0.6 s to each start. Ship one-directory unless the single file genuinely matters to you.

One-directory writes a folder named after your game, with the executable inside it next to an _internal/ directory. dist/mygame/mygame is the thing you run; dist/mygame/ is the thing you ship, and it must be shipped whole.

A bundle is host-only. PyInstaller does not cross-compile, so a Windows .exe is built on Windows and a macOS app on macOS.

A bundle does not keep your code secret

Godot ships a .pck with optional encryption and Unity ships compiled assemblies; SimVX does neither, and will not. Two different things happen to the two halves of a bundle, and it is worth being precise about both:

  • The engine ships as plain readable source. A one-directory bundle contains around 440 .py files under _internal/simvx/, plus shaders and default assets as ordinary files. Anyone can open them in an editor. That is the collection step doing its job.

  • Your own modules are byte-compiled into the executable, in one-file and one-directory alike. You will not find game.py in the bundle, and searching it for a string from your source finds nothing.

The second point is not protection, and treating it as protection would be a mistake. The bundled code objects come back out with a few lines of standard library:

from PyInstaller.archive.readers import CArchiveReader
import marshal, dis

code = marshal.loads(CArchiveReader("dist/mygame/mygame").extract("game"))  # "game" = your entry module
dis.dis(code)   # docstrings, every string constant, the whole disassembly

That recovers your strings, your API keys if you put any there, and the full logic in disassembled form. A decompiler is only needed to turn it back into readable Python. Byte-compiling raises the effort of reading your code from “open the file” to “spend an afternoon”; it does not raise it to “cannot”.

That is a position, not an omission. An engine that promotes open source and treats source distribution as a first-class shipping path is not in the business of hiding the game’s source from the person running it.

Choose knowingly:

  • If your game’s value is in its content, its feel, or its multiplayer service, this costs you nothing.

  • If your business model depends on the client being opaque, use a different engine. Do not ship a SimVX bundle and assume the code inside it is private.

Packing assets into a single archive may still arrive, for load time and tidiness. Obfuscation is not a goal and is not on the roadmap.

Mobile

Play the web export in a mobile browser. That is the supported mobile path today, and it works: touch input is captured and the WebGPU renderer runs on current mobile browsers.

A native Android target is best-effort future work, not a scheduled feature. If you need a store listing rather than a URL, that gap is real and current.