# Package a playtest

> Build a release package of a game with verified assets, checksums and a macOS app bundle, using scripts/package_game.py.

Source: https://feather-engine.com/docs/shipping/packaging/

`scripts/package_game.py` packages a saved project with a release build of the player for a local
playtest. It builds optimised binaries, verifies every asset against its recorded hash, produces a
macOS app on Macs, and writes a release manifest and checksums.

:::note
For most games, use [Build a game](/docs/shipping/export/) instead: the editor's **Build** window and
`feather-cli build` produce the same layout for Windows, macOS and Linux, and can build on GitHub and
upload to Steam. `package_game.py` is the older packager, and needs a source checkout and Python.
:::

:::caution
Packages are for local playtests. On macOS they are ad-hoc signed by default and are not notarised.
Developer ID signing with notarisation, installers and testing on a clean machine are still separate
steps you must complete before distributing a game widely. Windows and Linux layouts are covered by
tests, but no native builds have been run on those platforms.
:::

## Build a package

You need Python 3.12 and the native build prerequisites. On macOS this includes the Xcode Command
Line Tools, which provide `codesign`. Run from the engine repository root:

```bash
python3 scripts/package_game.py \
  --project path/to/my-game/project.feather.json \
  --output dist/my-game-playtest
```

1. The script builds `feather-player` and `feather-cli` with `cargo build --release --locked
   --no-default-features`, unless you supply existing binaries.
2. It stages the project and only the models, textures, sounds and fonts it declares.
3. It checks each file's size, SHA-256 name and file type, then validates the staged project with
   `feather-cli` and `project.validate`.
4. It writes the package and prints the output folder.

Packaging never launches the game, and it never overwrites an existing output: an existing folder,
even an empty one, is refused. Without `--output`, a uniquely named folder is created under `dist/`.
On failure, the staging folder is removed.

## Choose the asset source

| Priority | Source |
| --- | --- |
| 1 | `--assets DIR`, if given. It is authoritative |
| 2 | The `assets/` folder beside the project, if it exists |
| 3 | `--cache-assets DIR`, an absolute `FEATHER_ASSET_ROOT`, or the per-user asset cache |

Point these options at the folder that contains `imported/`, not at `imported/` itself. An incomplete
or corrupt `assets/` folder fails verification; it is not filled in from another source.

## Package contents

On macOS the output folder contains `<name>.app`:

```text
<name>.app/Contents/Info.plist
<name>.app/Contents/MacOS/feather-player
<name>.app/Contents/Resources/game/project.feather.json
<name>.app/Contents/Resources/game/assets/imported/...
```

On other platforms, `feather-player` (or `feather-player.exe`), `project.feather.json` and `assets/`
sit together in the output folder.

Every package also contains:

| File | Purpose |
| --- | --- |
| `README.txt` | Launch instructions |
| `release-manifest.json` | Release metadata and file hashes |
| `SHA256SUMS` | Checksums for the whole package |
| `licenses/Inter-OFL.txt` | Licence of the font embedded in the player |
| `PLAYTEST.txt` | Copied from the project folder when present |
| `licenses/project/` | The project's own `licenses/*.txt` notices, when present |

These records sit outside the app bundle, so they can be written after signing. To verify a package,
run `shasum -a 256 -c SHA256SUMS` on macOS or `sha256sum -c SHA256SUMS` on Linux from inside the
output folder.

## Options

| Option | Description |
| --- | --- |
| `--project FILE` | The project to package |
| `--assets DIR` | Authoritative asset folder |
| `--cache-assets DIR` | Fallback asset folder, used only if the project has no sibling `assets/` |
| `--output DIR` | A new output folder |
| `--player FILE`, `--cli FILE` | Reuse existing binaries instead of building them |
| `--name` | Package and app name; defaults to the project name |
| `--version` | Version as `x.y.z`; defaults to the engine's Cargo version |
| `--bundle-id` | macOS bundle identifier. Keep it stable between builds of the same game |
| `--build-number` | macOS build number; default `1` |
| `--sign-identity` | macOS signing identity. `-` (default) signs ad hoc; pass a Developer ID Application identity to use it |
| `--target-platform` | `macos`, `linux` or `windows`. A different platform requires a matching `--player`; there is no cross-compilation |

When you pass your own binaries, you are responsible for their source revision, release flags and
architecture.

## Signing

Signing runs offline with `--timestamp=none`. A named Developer ID identity also enables the hardened
runtime:

```bash
python3 scripts/package_game.py \
  --project path/to/my-game/project.feather.json \
  --sign-identity 'Developer ID Application: Your Name (TEAMID)'
```

Neither mode notarises the app. Review content licences, notices and platform requirements before
sharing a package beyond your intended testers.

## Player data

The packaged game is read-only. Settings, key bindings, language, saves and crash reports are written
to a per-game folder in the Feather data folder; see [Saves and
settings](/docs/shipping/saves-and-settings/). Keep the project name stable between builds, because
the folder name is derived from it.

## Package the editor

To package the editor itself as a macOS app, use `scripts/package_macos.py`; see
[Install](/docs/getting-started/install/). The app registers the `.feather` project type, so
double-clicking a project in Finder opens it in the editor.

## Automate it

The packaging tests use temporary fixtures and need neither licensed content nor a Rust build:

```bash
python3 -m unittest discover -s scripts/tests -p 'test_package_game.py'
```
