Packaging & distribution
A Plume3D app is a plain folder — a game.toml, a main.wren, compiled shaders under
shaders/bin/, and assets. To hand it to a player you turn that folder into a single
distributable: a .p3d package wrapped in a native shell for the target platform. The
plume3d-pack tool does this in one command.
The .p3d package
Section titled “The .p3d package”A .p3d is a zip of the cleaned app directory plus a package.toml manifest at its
root. The runtime mounts it exactly like a folder — a bundled .p3d sitting next to the
runtime is loaded automatically, so a packaged app just runs when launched.
Packaging cleans the app: it drops the plume3d binary/symlinks, *.log, __pycache__/,
.DS_Store, and each app’s shaders/compile.sh, and keeps your main.wren, game.toml,
shaders/bin/*.spv, and assets. Compiled SPIR-V (shaders/bin/*.spv) must be present — the
runtime loads the compiled shaders, not the .slang sources. Compile them before packaging, or
pass --compile-shaders to have plume3d-pack run slangc for you (see below); without it the
tool only warns when a .slang has no compiled output.
plume3d-pack
Section titled “plume3d-pack”plume3d-pack is a separate host tool, not part of the runtime. Point it at an app folder and
a target, and give it the shipping runtime binary to wrap (build one with
-DPLUME_VARIANT=shipping — see Command line → Build variants):
plume3d-pack <appDir> --target macos|windows|linux --runtime <plume3d-binary> [options]| Option | Meaning |
|---|---|
--target macos|windows|linux | which platform shell to produce (required) |
--runtime <path> | the plume3d binary to bundle — use a shipping build (required) |
--variant shipping|development | recorded in package.toml; default shipping |
--id <reverse-dns> | app id, e.g. com.studio.game; default derived from the name |
--name <name> | display name; default from the app’s game.toml |
--entry <script> | entry script; default main.wren |
--compile-shaders | run slangc on shaders/src/*.slang → shaders/bin/*.spv before packaging |
--shader-include <dir> | override the auto-detected engine/shaders/include (the shader ABI headers) |
--out <dir> | output directory; default the current directory |
It produces:
- macOS —
<Name>.app(unsigned):Contents/MacOS/plume3d,Contents/Resources/<id>.p3d, and anInfo.plist. Code signing and notarization are a separate, credential-gated step. - Windows — a folder (
plume3d.exe+<id>.p3d) zipped to<Name>-windows.zip. - Linux — a folder (
plume3d+<id>.p3d) as<Name>-linux.tar.gz.
Example — package the triangle sample as a macOS app:
plume3d-pack apps/plume_triangle --target macos \ --runtime build/shipping/plume3d --out dist/Compiling shaders during packaging
Section titled “Compiling shaders during packaging”Pass --compile-shaders and plume3d-pack runs slangc on every shaders/src/*.slang
into shaders/bin/*.spv before it bundles, so you can package straight from sources:
plume3d-pack apps/my_game --target linux \ --runtime build/shipping/plume3d --compile-shaders --out dist/It resolves slangc from $VULKAN_SDK, common Vulkan SDK install paths, then your PATH
(install the Vulkan SDK if it isn’t found — the tool never
downloads a toolchain). App shaders that #include the engine’s shared shader headers
resolve automatically; pass --shader-include <dir> if your engine/shaders/include lives
somewhere the auto-detect misses. Without the flag, packaging leaves your existing
shaders/bin/*.spv untouched (and warns if a .slang has none).
package.toml
Section titled “package.toml”The manifest is written for you by plume3d-pack; you normally never edit it. It is the
frozen schema the runtime reads at mount:
[package]id = "com.studio.game"name = "My Game"version = "1.0.0"entry = "main.wren" # the script loaded on run, --server, and --check alike
[engine]variant = "shipping"version = "1.0.0"
[window]width = 1280height = 720resizable = truefullscreen = falseborderless = falsescale_mode = "letterbox"package.toml is authoritative for the app’s name and version. Its [window] is used only
when the bundle ships no game.toml; when a game.toml is present its own [Window]
(including design resolution) drives the window. A .p3d is untrusted input, so the runtime
validates the manifest on load: an entry that is absolute or contains .. is rejected back
to main.wren (it can never point outside the package), the id charset is restricted, and
window dimensions are clamped.
Package integrity (signed builds)
Section titled “Package integrity (signed builds)”A .p3d can be signed so the engine verifies it on every launch and refuses to run if the
package was modified after packaging — if a player edits your scripts, models, or maps, the game
fails to launch. Signing is the paid licensing tier; the engine itself is free, and an
unsigned package runs normally (without this verification).
A licensed developer generates an Ed25519 key pair and receives a developer certificate signed
by WyldMagic. plume3d-pack then embeds a signed integrity chain in the .p3d:
plume3d-pack <appDir> --target macos --runtime <plume3d> \ --dev-key <your-ed25519-private-key> --dev-cert <developer.cert> ...This writes four files at the package root — developer.cert (+ its WyldMagic signature) and an
integrity.manifest of per-file SHA-256 hashes (+ your signature). At launch the runtime verifies
the chain against the WyldMagic root key baked into the engine, then checks every file’s hash, and
refuses to load on any mismatch.
This is tamper-evidence, not encryption — it detects modification of a shipped package; it does not prevent someone extracting assets from it (no client-side scheme can — see the licensing docs). It pairs with OS code signing (which the OS enforces at first launch); the in-engine check runs at every launch.
What is not covered yet
Section titled “What is not covered yet”OS code signing / notarization (Apple Developer ID + notarize, Windows Authenticode) is the
credential-gated follow-up that removes Gatekeeper/SmartScreen warnings — separate from the in-engine
integrity above. Native installers (Windows NSIS/WiX, Linux AppImage) and mobile/web shells are also
follow-ups. (The earlier macOS OpenSSL-dylib gap is resolved — the shipping runtime static-links
OpenSSL, so a packaged .app is self-contained on a clean Mac.)
See also: Command line, Configuration, Get Plume3D.