Packages
A package is a distributable unit of functionality — any mix of assets, Zig source, native libraries, and runtime plugins — that you can install into a project without manually copying files. It is Turian's answer to Unity's Package Manager and Asset Store, built on top of Zig's own package manager.
Status: asset, source, native, and plugin packages are all implemented end-to-end (install → build → run). The registry (remote search/publish) is on the roadmap.
Concepts
| Term | Meaning |
|---|---|
| Package | The authoring/distribution unit: a turian-package.json manifest plus assets, source, native libs, and/or a plugin entry point. |
OAP (.oap) |
The cooked, runtime asset container your game ships. A package's assets are cooked into the project's game.oap at build time. |
| Module | A Zig compilation unit (b.addModule) — the Unity asmdef equivalent. |
| Plugin | A package's runtime/editor registration entry point. |
The full rationale and schema live in ADR 0001.
project.json is the source of truth
Every Turian project has a project.json that records the project's identity and its declared dependencies. The project's build.zig.zon is generated from project.json — like Unity and Flax regenerate their .csproj/.sln. You never hand-edit build.zig.zon; the CLI and Studio mutate project.json and regenerate the ZON on every build. This keeps dependency management in safe, familiar JSON and avoids ZON syntax mistakes.
{
"turian_version": "0.16",
"name": "My Game",
"version": "0.0.0",
"dependencies": {
"com.acme.physics": { "url": "git+https://example.com/physics#v1.2.0", "hash": "..." }
}
}
Dependency resolution (versions, conflicts, hashing, the lockfile) is delegated to Zig's package manager. Turian's layer adds the engine-integration manifest, discovery, asset-pipeline injection, and engine-compatibility checks.
The manifest: turian-package.json
A package declares itself with a turian-package.json at its root:
{
"name": "com.example.color-palette",
"version": "1.0.0",
"author": "Example",
"description": "A sample asset-only package",
"license": "MIT",
"engine_compat": ">=1.0.0",
"types": ["asset"],
"assets": ["assets"]
}
| Field | Required | Notes |
|---|---|---|
name |
✓ | Reverse-DNS, globally unique. |
version |
✓ | Semver. |
types |
✓ | One or more of asset, source, native, plugin. |
assets |
Asset directories relative to the package root (default ["assets"]). |
|
modules |
Exported Zig modules: { "name", "root" } (source packages). |
|
native |
Native libraries: { "name", "kind", "lib", "include" }. |
|
plugin |
Runtime entry point: { "register", "entry" }. |
|
engine_compat |
Semver range checked against the running engine version. | |
dependencies |
Map of package name → semver range. |
Installing a package
# From a local directory
turian-cli package install ./path/to/package --project my-game
# From a git repository
turian-cli package install git+https://example.com/my-package --project my-game
By default, packages install into a central store shared across all your projects —
$TURIAN_PACKAGE_HOME, else ~/.cache/turian/packages (%APPDATA%\turian\packages on
Windows) — laid out <name>/<version>/ so multiple versions coexist. The project records
which packages+versions it uses in project.json; nothing is duplicated per project. This is
also how a company can stand up an internal asset-store: one shared store, many projects.
Pass --vendored to copy a package into <project>/packages/ instead (for committing it
alongside the project, or working offline). Vendored copies take precedence over the store.
turian-cli package install ./pkg # → central store (default)
turian-cli package install ./pkg --vendored # → <project>/packages/
turian-cli package list --project my-game
turian-cli package info com.example.color-palette --project my-game
turian-cli package remove com.example.color-palette --project my-game # keeps the shared store copy
The next turian-cli build discovers every installed package (store + vendored) and cooks its
assets into game.oap automatically — no manual copying.
Installed package assets appear in Studio → Asset Browser under a read-only Packages section: you edit them in the package, not in the consuming project.
Source packages
A source package exports one or more Zig modules that user scripts (and other packages) can @import. The build system emits a b.addModule(...) for each module and wires it into the game and play-mode builds automatically.
Example turian-package.json:
{
"name": "com.example.spawner",
"version": "1.0.0",
"types": ["source"],
"modules": [
{ "name": "spawner", "root": "src/root.zig" }
]
}
Usage from a user script:
const spawner = @import("spawner");
pub fn update(self: *@This(), frame: engine.Frame) void {
const cfg = frame.services.get(spawner.SpawnerConfig) orelse return;
_ = cfg;
}
A working example lives at examples/packages/com.example.spawner/.
Plugin packages
A plugin package adds a runtime entry point called before the boot scene loads. The entry function receives *engine.Services so the package can register services that scripts retrieve via frame.services.get(T).
Manifest addition:
{
"name": "com.example.spawner",
"version": "1.0.0",
"types": ["source", "plugin"],
"modules": [{ "name": "spawner", "root": "src/root.zig" }],
"plugin": {
"register": "spawner",
"entry": "register"
}
}
register is the import name of the module that contains the entry function. entry is the function name. The generated main.zig calls:
@import("spawner").register(&g_services);
The entry function signature must be pub fn register(services: *engine.Services) void.
Collisions & compatibility
- Duplicate package names and GUIDs shared across two packages are hard errors that fail the build with a clear diagnostic — package authors must keep asset GUIDs globally unique.
- A package whose
engine_compatrange excludes the running engine version produces a warning (the build still proceeds).