# Build and packaging

## One conductor: Kaji

Kaji is the only product build tool. Run it from the repository root:

```bash
# Game, monolithic debug build (staged under bin/game/)
bin/kaji/kaji game --workspace="$PWD" --project="$PWD/../hikari-games/example" --type=monolithic --config=debug

# Project-bound editor: develop a game with Play/Edit, scene authoring, recompile
bin/kaji/kaji editor --workspace="$PWD" --project="$PWD/../hikari-games/example" --type=dynamic --config=debug --run

# Standalone editor (no project; boots the project selector)
bin/kaji/kaji editor --workspace="$PWD" --type=dynamic --config=debug --run

# Shippable artifacts
bin/kaji/kaji game --workspace="$PWD" --project="$PWD/../hikari-games/example" --type=monolithic --config=release --package=bundle
bin/kaji/kaji editor --workspace="$PWD" --type=monolithic --config=release --vendor:zig=fetch+bundle --package=bundle
```

Kaji builds vendor units, the native bridge, Opus, Kawa, Shinra, Akari, the engine core, shader packages, and the native frontends as one dependency graph. Units that do not read each other's outputs build concurrently. Zig emits application and plugin objects; Kaji owns the final executable link on every host. Build units are `*.kaji.cs` files stored beside their source; vendors enter the graph only through `KajiUnitCatalog.RegisterVendor` (Tenkai3D).

The platform scripts under `build/` (`build-game-macos.sh`, `build-editor-windows.ps1`, …) only bootstrap Kaji and forward options. Product policy lives in Kaji, never in those scripts.

## Product options

| Option | Meaning |
|---|---|
| `game` / `editor` | Product frontend |
| `--workspace=<repo>` | Workspace root (auto-discovered inside an engine workspace) |
| `--project=<dir\|hikari.project.json>` | Game project. Required for `game`; optional for `editor` (omit = standalone host + project selector). Flag form only; bare positional paths are rejected |
| `--type=monolithic\|dynamic` | Driver/application/platform linkage (default `monolithic`). The editor always loads game code dynamically regardless of this flag |
| `--config=debug\|release` | Product configuration (default `debug`) |
| `--run` | Launch the staged product from `bin/game/` after success, with live stdio |
| `--log=<path>` | With `--run`, forward as the product's engine log file path |
| `--leak-policy=default\|fail\|report` | With `--run`, choose leak handling: Debug failure by default, explicit failure, or reporting without failure. Tracking remains build-dependent |
| `--deep-validate` | With `--run`, enable deep mesh validation |
| `--break-on-scene-load-error` | With `--run`, break on scene-load errors in Debug |
| `--d3d12-gbv-pass=<label>` | With `--run`, enable GPU validation for one exact pass label (requires the D3D12 debug layer) |
| `--zig=<path>` | Editor with `--run`: explicit compiler for game-module builds |
| `--trust-project=<digest>` | Editor with `--run`: approve the reviewed project/plugin digest; changes invalidate the grant |
| `--source-revision=<revision>` / `--source-vcs=<label>` | Explicit build provenance; VCS defaults to `external` when a revision is supplied |
| `--stacktrace` | Include exception stacks in Kaji diagnostics; available on all command option tables |
| `--clean` | Wipe `bin/*` and `build/cache/*` (except Kaji) plus disposable `<project>/.engine/*` (keeps `.engine/user/`). Asks for confirmation unless `--yes` / `--agent` |
| `--yes` / `--silent` / `--agent` | Skip the `--clean` confirmation; `--silent` and `--agent` also quiet conductor output |
| `--ui=auto\|log\|live` | Console frontend. `live` redraws a status block in place; `log` prints one line per action (CI, pipes). `auto` picks `live` on an interactive terminal |
| `--trace=<path>` | Write a Chrome trace-event file of every action and unit (open in Perfetto or `chrome://tracing`). The finish line also prints the critical path: the dependency chain that bounded the wall clock |
| `--until=<unit,…>` | Build the named units and everything they depend on, nothing after them. Ids are what `kaji plan` prints: `hikari`, `kawa`, `shaders`, `package-host`, `assets`, … |
| `--only=<unit,…>` | Build just the named units; what they depend on must already be built |
| `--events=<path>` | Stream build events as NDJSON (one object per line) for the editor console and CI |
| `--parallel=<jobs>` | Job slots shared by every action in the build (default: host CPU count). Cargo joins the same pool through the GNU make jobserver; Zig and Odin take one slot plus whatever is free when they start |
| `--dep-config=<unit>:debug\|release` | Override the configuration of a vendor unit, `shinra`, or `akari` |
| `--shinra:config=debug\|release` | Shinra tool configuration. Default `release` even under product debug; flip only when debugging Shinra |
| `--shaders:config=debug\|release` | Akari shader compile profile (defaults to product `--config`; explicit value overrides it) |
| `--resources=bundles\|loose` (alias `--data-layout=`) | **`game` and `kaji assets` only.** Product data layout under `data/` (default `bundles`). Rejected on `kaji editor`, which never seals packs. Pack membership and delivery come from `configs/layout.json` ([Project file](/docs/guides/project-file.md#example-configslayoutjson-product-pack-layout)) |
| `--package[=bundle\|loose]` | After staging, emit a distribution artifact under `bin/packages/` ([Distribution packages](/docs/guides/build-and-packaging.md#distribution-packages)) |
| `--vendor:zig=fetch\|bundle\|fetch+bundle` | **Editor only.** `fetch` downloads the pinned Zig archive into `3rd-party/zig/` when missing; `bundle` stages it into `bin/game/tools/zig/` and fails on a missing archive or version mismatch. Without it the editor discovers a toolchain at runtime ([Zig toolchain discovery](/docs/systems/editor-project-selector.md#zig-toolchain-discovery)) |
| `--vendor:engine=bundle` | **Editor only.** Stage `src/hikari/sdk/` into `bin/game/sdk/` with the shader contract overlaid into `sdk/shaders/`. A leftover `bin/game/sdk` wins over the source tree, so restage or `--clean` after editing the SDK |
| `--memory:asan` | AddressSanitizer for supported native builds. Rejected on macOS with Zig (shadow-mapping conflict with Zig's allocator) |
| `--memory-leak-check` | With `--run`, forward `--leak-policy=fail` so tracked subsystem leaks fail the process (Debug/ReleaseSafe; ReleaseFast does not track) |

> [!CAUTION]
> `--clean` deletes build outputs under `bin/*` and `build/cache/*` (except Kaji), plus disposable project `.engine/*` data. It preserves `.engine/user/`. Adding `--yes`, `--silent`, or `--agent` skips the confirmation; check the workspace and project paths first.

### Profiler and diagnostics switches

| Option | Meaning |
|---|---|
| `--profiler-timing[=true\|false]` | Compile the CPU profiler (default: on in debug, off in release). Runtime Recording is off until enabled; the editor Profiler tab works in Edit mode |
| `--profiler-timing:modules=markers,pix,xcode` | Profiler modules: `markers` (GPU pass labels and debug names for encoders, buffers, textures, pipelines; default in debug), `pix` (Windows PIX), `xcode` (Instruments) |
| `--profiler-physics[=true\|false]` | Physics memory/state diagnostics for the editor Physics tab (default off) |
| `--profiler-audio[=true\|false]` | Audio bus/voice/worker diagnostics for the editor Audio tab (default off) |
| `--profiler-residency[=cpu\|gpu\|cpu+gpu\|off]` | Residency diagnostics (default off). `cpu` adds recent-free history and editor snapshots; `gpu` adds renderer snapshots. Refcounts, budgets, and eviction are always compiled ([asset residency](/docs/systems/asset-residency.md#diagnostics)) |

Older spellings (`--profiler`, `--profiler:modules`, `--residency`) are rejected.

### Plan and explain

Host packaging, editor shader staging, the asset toolchain, the game cook and seal, and the distribution package are nodes of the same graph with declared inputs and outputs, so a rebuild that changed nothing runs none of them and `explain` reports why one would.

> [!TIP]
> Run `kaji doctor [--workspace=…]` before your first build or when setting up another machine. Without building, it checks the declared toolchain minimums, host tools (Metal toolchain and Xcode utilities, Visual Studio, patchelf), open-file limit, vendored Zig pin, workspace lock, and build cache. Exit status 1 means a build would fail.

`kaji localization [--project=<dir>]` runs only the `localization` unit: it compiles `assets/localization/<domain>/<locale>.localization.json` into `.engine/localization/` for the editor and, with `--project`, the game. `zig build` roots `editor_text`/`game_text` at those generated files and fails configure when a generated `Id` enum no longer matches its source catalog, naming this command; it never regenerates them itself.

`kaji plan game|editor [options]` prints the lowered action graph (every command, input, and output) without running anything. `kaji explain …` adds each action's cache verdict, so "why did that rebuild" is answered before the build. Both take the product options above plus `--json`.

### Graphics diagnostics (D3D12 / Metal)

`--d3d12-*` and `--metal-*` are aliases for the same knobs; only the host backend emits defines. The flags in this table are build-time, accept `--flag`, `--flag=true`, or `--flag=false`, and work in either configuration.

| Flag | Default | D3D12 | Metal |
|---|---|---|---|
| `--d3d12-debug-layer` / `--metal-debug-layer` | on in debug, off in release | Debug layer + DRED; validation info queue drained into the log once per present | `MTL_DEBUG_LAYER=1` set before Metal initializes |
| `--d3d12-trace` / `--metal-trace` | off | Verbose native tracing (`HIKARI_D3D12_LOG_TRACE`) | Verbose native tracing |
| `--d3d12-gbv` / `--metal-gbv` | off | GPU-Based Validation (very slow) | `MTL_SHADER_VALIDATION=1` |
| `--metal-warning-mode=nslog\|assert\|ignore` | `ignore` | — | `MTL_DEBUG_LAYER_WARNING_MODE`; implies `--metal-debug-layer` |

Notes:

- Metal validation is process-wide and also wraps AppKit/QuartzCore encoders. On macOS 26 those emit false `unused binding` warnings at startup, so the warning channel defaults to `ignore`. Validation **errors** always log. Use `--metal-warning-mode=nslog` for an audit, or `assert` to halt inside the first offending call under a debugger. The mode is compiled in with `setenv(…, 0)`, so `MTL_DEBUG_LAYER_WARNING_MODE=ignore ./hikari-editor …` overrides it per run.
- Two known false positives in Metal's own bookkeeping (acceleration-structure encoders and MetalFX-opened encoders) are corrected by shims in `src/hikari/src/native/macOS/src/MetalDebug.m`. Each logs a one-shot "obsolete on this OS, delete it" line if an OS update fixes the layer itself.
- Attribution help in a markers build: the once-a-second encoder census (encoders opened by kind, how many were empty, how many vendor encodes ran) and the per-pass empty-encoder report. If Metal prints an empty-encoder warning and the report does not name a pass, the encoder is not ours.
- On Windows the native logger also appends to `%TEMP%\hikari-d3d12-debug.log` when the debug layer is compiled in, since a windowed process has no console. D3D12 INFO chatter is filtered at the info-queue storage filter.
- Changing any of these flags rewrites the product-settings stamp and rebuilds the native bridge, so a toggle always takes effect. `.cpp`/`.m` edits are tracked build inputs and do not need `--clean`.

### Windows: D3D12 Agility SDK

Windows stages copy the pinned Agility redistributable into `bin/game/D3D12/` (`D3D12Core.dll`, `d3d12SDKLayers.dll`) and `WinPixEventRuntime.dll` beside the executables. The frontends export `D3D12SDKVersion` / `D3D12SDKPath` so the system loader picks them up. Pins: `3rd-party/d3d12-agility/`, `3rd-party/winpix-event-runtime/`.

## Logging under `--run`

1. **Terminal:** product stdio is inherited. Build steps capture their own output; only the final process streams live.
2. **Editor UI:** the bottom console sink stays registered.
3. **File:** NDJSON. The editor defaults to `<project>/.engine/logs/editor-latest.json` once a project is open; the game needs `--log=<path>`.

Engine-side contract: [Frontends and drivers](/docs/systems/frontends-and-drivers.md#logging).

## Configuration and environment

Product behavior uses explicit options. Native frontends parse launch arguments, copy them into validated startup configuration, and share the host-owned configuration with dynamic drivers. They reject unknown arguments. The same runtime flags above work when launching the executable directly; omit Kaji's `--run`. The game frontend's `--write-package-manifest` is a standalone packaging command and cannot be combined with launch flags.

Shinra carries compiler paths in pipeline configuration (`--akari-sc=<path>`, `--kawac=<path>`) and passes shader profiles explicitly (`--shaders-config=debug|release`). Akari accepts `compile --dxc=<path>` and `--config=debug|release`; its cache includes the Akari executable identity and native compiler version. Compiler overrides and profiles no longer use project-specific environment aliases.

Child processes do not inherit your shell. Each receives only host plumbing (`PATH`, home and temp directories, proxies, `CARGO_HOME`/`RUSTUP_HOME`, `DEVELOPER_DIR`, Windows system variables) plus the overrides its toolchain declares, and only those overrides enter the action cache digest. Variables such as `CPATH`, `SDKROOT`, `RUSTFLAGS`, or a Developer prompt's `INCLUDE` never reach a tool, so they cannot change an output without a rebuild. Toolchains are keyed by their resolved executable and full version line, so a patch release, a dev build, or a different install of the same version rebuilds. Parallel actions never mutate the conductor's environment.

The remaining environment interfaces serve the operating system, external tools, or isolated test harnesses:

| Interface | Purpose |
|---|---|
| `PATH`, `HOME`, `USERPROFILE`, `APPDATA`, `LOCALAPPDATA`, `XDG_DATA_HOME`, `XDG_CONFIG_HOME`, `TMPDIR`, `TEMP` / `TMP`, `COMSPEC`, `ProgramFiles` / `ProgramFiles(x86)` | Host paths, executable discovery, and shell integration |
| `DEVELOPER_DIR`, `TOOLCHAINS`, `CLANG_MODULE_CACHE_PATH`, Visual Studio's toolchain environment, `LIBCLANG_PATH`, `CMAKE` | Native toolchain discovery and child-process configuration |
| `ZIG_LOCAL_CACHE_DIR`, `ZIG_GLOBAL_CACHE_DIR` | Zig 0.17 build caches, set by Kaji per child process |
| `MTL_DEBUG_LAYER`, `MTL_DEBUG_LAYER_ERROR_MODE`, `MTL_DEBUG_LAYER_WARNING_MODE`, `MTL_SHADER_VALIDATION` | Apple's process-level graphics diagnostics |
| `P4PORT`, `P4CLIENT`, `P4CONFIG` | Perforce provenance discovery |
| `NO_COLOR` | Standard terminal color preference |
| `NVIM_APPNAME` | Neovim plugin installer application directory |
| Cargo's `CARGO_MANIFEST_DIR`, `OUT_DIR` and compile-time Cargo metadata | Rust build protocol |
| `ODIN` | Physics harness compiler default; `--odin=<path>` overrides it |
| `HIKARI_AREA_TEST_OUTPUT`, `HIKARI_VSM_TEST_OUTPUT` | Optional shader-test artifacts; no effect on product behavior |
| `DYLD_FRAMEWORK_PATH`, `HIKARI_GPUTRACE_INSPECT_REEXEC` | GPU-trace inspection tool framework loading and internal re-exec guard |

Source generation is explicit: `zig build generate-document-schemas` and `zig build generate-cooked-layouts` from `src/hikari`; Shinra fixture regeneration uses its named ignored tests. Ordinary freshness tests never rewrite source fixtures. Build smoke scripts accept `--games=<path>`, native-style wrappers accept a leading `--zig=<path>` and `--llvm=<root>`, and the shader formatter harness accepts its shader tree as an argument.

## Stage model

Typed plans in `src/kaji/src/Conductors/HikariBuildPlans.cs` choose the host and build units. Engine build products install under `bin/hikari/`; the runnable stage is `bin/game/`; distribution artifacts land under `bin/packages/`.

- **Dynamic** stages separate application, platform, windowing, input, renderer, physics, scripting, and game-UI libraries. **Monolithic** links them into the executable. The two modes exercise different ownership and loading paths; a monolithic success is not evidence for a dynamic module lifecycle change.
- The editor always dlopens the project's game module after open/recompile. `--type=` never bakes game code into the editor.
- `ProjectConfig` emits the driver package manifest from the same typed recipes runtime selection uses, so the stage list cannot drift from configuration.
- Game modules built under the editor pass `-Deditor-contrib=true`, exposing `hi.editor`. `kaji game` omits it, so editor-only game code stays out of shipping binaries ([Game editor SDK](/docs/systems/ui-and-editor.md#game-editor-sdk-hieditor)).
- Plugins are source-composed into the game module from `hikari.plugins.json`; native link and runtime staging inputs come only from the generated `hikari-plugin-artifacts.json` ([Plugins](/docs/guides/plugins.md)).

## Distribution packages

Without `--package`, Kaji stops at the staged `bin/game/` tree. With it, Kaji filters the stage into a shippable payload and asks the host packager for an artifact:

| Flag | macOS | Windows | Linux |
|---|---|---|---|
| `--package` / `--package=bundle` | `.app` (ad-hoc signed) | `.msix` (Windows SDK `MakeAppx`) | not implemented |
| `--package=loose` | `.zip` | `.zip` | `.zip` |

```text
bin/packages/<slug>-<version>-<platform>-<arch>-<config>/
  Sample.app | Editor.app | *.msix | *.zip
```

Game slug is the packaging title; the editor slug is `editor`. A standalone editor package ships the host plus project selector. A project-bound editor package (`--project=<game> --package`) uses that game's `packaging.editor` block.

| Product | Keeps | Strips |
|---|---|---|
| `game` | `hikari`, game application/module, sealed `data/` (packs plus loose configs, the project descriptor and `product.json` — the resolved identifier/title/publisher the engine names the player's settings folder from; every content dir ships, packable ones inside the content pack, others loose for modding). `configs/packaging.json` never ships | editor frontend and application, `tools/` |
| `editor` | `hikari-editor`, editor application, backends, `tools/` (Shinra, Zig SDK), loose `data/_engine` | game frontend, game application/module, game packs |

**Product resource layout.** After cook and shaders, Kaji seals packs under `data/bundles/` with the catalog `product.shinbundle.map.json` (bundles mode) or stages the same paths loose. Without `configs/layout.json` there is one `game` pack plus product-sealed `engine` shaders. With one, each pack declares membership globs, `delivery` (`packed` or `loose`), `deps`, and `labels`; a loose pack stages as a directory and stays moddable. Cooked art no pack claims joins `default_pack` and is reported, never a build failure. Content dirs marked `bundled: true` may enter the `content` pack; `bundled: false` always stages loose under `data/<path>/`. Details: [Data, JSON, and modding](/docs/guides/data-and-modding.md#staging-kaji-not-zig), [Project file](/docs/guides/project-file.md#content-dirs-loose-data), [Product pack layout](/docs/guides/project-file.md#example-configslayoutjson-product-pack-layout).

**Zig bundling** for self-contained editor packages that compile game modules offline:

| `--vendor:zig=` | Effect |
|---|---|
| *(omitted)* | No download, no `tools/zig/` staging; runtime discovery uses env/PATH |
| `fetch` | Download the pinned host archive into `3rd-party/zig/` if missing |
| `bundle` | Stage into `tools/zig/`; fail if missing or version mismatch |
| `fetch+bundle` | Both |

Pin: [`3rd-party/zig/VERSION`](https://github.com/Flying-Rat/hikari/blob/develop/3rd-party/zig/VERSION), which must match the Zig minimum in `src/kaji/src/Build/KajiVocabulary.cs` (0.17.0).

**Host details.** Loose and MSIX staging hardlink where the volume allows; the macOS `.app` copies because `codesign` rewrites Mach-O in place. Inside an `.app`, binaries live in `Contents/MacOS/` and content in `Contents/Resources/data/` with a `MacOS/data` symlink so `exe_dir/data/…` lookups keep working. A PNG or `.icns` icon becomes `AppIcon.icns`; an Icon Composer `.icon` (`packaging.macos.icon`) is compiled with `actool` into `Assets.car` plus a fallback `.icns` and `CFBundleIconName`, so macOS 26 renders Liquid Glass. Windows `.msix` requires the Windows 10/11 SDK; certificate selection is left to your release pipeline. Packagers live under `src/kaji/src/Packaging/` (`IPlatformPackager` per host).

### Packaging identity

Shipping identity is product-owned JSON that Kaji reads without compiling the game:

| Product | Descriptor |
|---|---|
| `kaji game … --package` | Game `configs/packaging.json` (required) |
| `kaji editor … --package` (no project) | [`src/hikari/editor.project.json`](https://github.com/Flying-Rat/hikari/blob/develop/src/hikari/editor.project.json) → `packaging` |
| `kaji editor --project=<game> … --package` | Game `configs/packaging.json` → `editor` (falls back to the shared fields) |

Fields: `title`, `product_version`, `identifier`, `icon`, authors/publisher, `macos` / `windows` blocks, optional `startup_background_color` (`#RRGGBB`, default black; the solid cover a standalone game shows while the first scene loads; embedded at build time, so rebuild after changing it). See [Project file](/docs/guides/project-file.md#packaging-game).

## Host support

- **macOS:** Apple Silicon (`aarch64`) only, Metal 4 MSL, product floor macOS 26. Tenkai3D compiles with Homebrew Odin plus `llvm@22` / `lld@22`. An unversioned `brew install llvm` produces a stub `libtenkai3d.dylib` (every export is `brk`); Kaji fails that at the Odin step. Repair with `brew install llvm@22 && brew reinstall odin`.
- **Windows:** `x86_64`, D3D12 with the Agility SDK staged beside the exe.
- **Linux:** `x86_64` build plumbing exists, but the runtime is incomplete; Vulkan shader cooking is available. Not a product peer ([Platforms](/docs/systems/platforms.md)).

Smoke matrices: `build/test-build-macos.sh` and `build/test-build-windows.ps1` run clean game/editor × debug/release × dynamic/monolithic builds, always passing the Example project explicitly. Pass `--games=<path>` to select a different games checkout. Game cells verify the cooked bundle catalog; editor cells verify the staged toolchain and reloadable game module without starting the editor. Both first run the Tenkai3D physics gate (`python3 src/tenkai3d/tools/test_physics.py --soak`; needs Python 3 and Odin on PATH) and stop on a physics failure ([physics verification](/docs/systems/physics.md#automated-physics-gate)).

## Local engine build tasks

Zig 0.17 uses `-Doptimize=debug|safe|fast|small` for direct build tasks. Windows/MSVC bindings use explicit C preprocessing in `src/hikari/build/c_bindings.zig` before translation to preserve pointer qualifiers; included headers remain tracked dependencies.

From `src/hikari`, direct Zig tasks remain useful for focused validation: `zig build test -Dgame-src=<abs game src>`, `zig build test-graphics …`, `zig build entity-bench …`, `zig build check-windows …`. Use Kaji for real game and editor products; do not assemble ad hoc links. See [Development → Validation](/docs/guides/development.md#validation).

### Rust dependency resolution

Akari and Shinra keep their `Cargo.lock` files in version control. Kaji invokes Cargo with `--locked`, so a build fails if a manifest change requires a different dependency resolution. Update the affected lockfile explicitly with the dependency change and include it in the same commit.
