# ADR 0029: Validation-gated build profiles and packaging

## Status

Accepted

## Context

Blacksite builds previously required users to know Cargo commands and manually assemble assets. That
made it easy to ship broken references, editor caches, recovery data, or an executable without the
settings and content that produced it. Ordinary debug binaries are also too large for practical
distribution, while release LTO is too expensive for every development package.

## Decision

Projects define versioned RON profiles under `assets/build_profiles/`. A profile selects the Cargo
package profile, optional target triple and feature set, rendering tier, default scene, and a safe
project-relative output directory. Development and QA exports use dedicated optimized non-LTO Cargo
profiles; QA retains line tables, while release exports retain thin LTO and strip symbols. The
development rendering tier forces the portable Forward GI path in the packaged project settings;
representative and shipping tiers preserve the authored rendering policy. Windows MSVC QA packages
include Cargo's PDB sidecar and record it in the content manifest.

`cargo package-project` is the authoritative build/package entry point for editor and CI. It runs
`scene::validate_project` before compiling, builds `game`, reads the executable path from Cargo's
JSON artifact stream, then validates again against a BLAKE3 snapshot of the runtime inputs. A profile
default scene must be a regular `.scn.ron` file under the fully audited `assets/levels/` tree and pass
schema plus composition validation. The selected default scene and rendering tier are applied to the
packaged `project.ron`. Metadata records profile, target, features, rendering tier, default scene,
exact engine/project commit and
dirty state, Rust/Cargo versions, and a sorted BLAKE3 content manifest. Features that require the
development or hot-reload dynamic libraries are rejected because those libraries are not shipping
artifacts.

Runtime packaging excludes the editor asset registry, trash, thumbnails, build profiles, recovery
and autosave data, temporary/backup files, and source-authoring formats. Traversal errors and runtime
asset symlinks are blocking instead of silently omitting content. Output is restricted to a dedicated
non-symlinked `dist/<name>/` tree. A non-empty existing directory must carry recognized package
metadata before it can be seeded or pruned, preventing a profile typo from replacing project source
or an unrelated user directory.

Assembly happens in a unique sibling staging directory copied from the previous package. Staging has
no shared file inodes with the published output, and its complete content is rehashed before
promotion, so a failed build or external output edit cannot mutate the candidate package. An
OS-backed per-output lock prevents concurrent publication on Unix and Windows. Versioned
ownership records authenticate lock/staging cleanup; an interrupted run removes only owned partial
staging and restores a strictly validated prior backup before retrying. Only a complete,
unchanged-input package is promoted; stale files and obsolete directory shapes are pruned in staging.
The editor Build panel invokes the same engine-workspace Cargo alias for the active external project,
streams stdout/stderr, owns and cancels its worker/process tree, and exposes Build, Build and Run,
and Reveal Output. Dirty scene tabs block a build. Cross-target packages are produced but Build and
Run refuses to execute a binary for a different host operating system or CPU architecture.

## Consequences

- Editor and headless builds have identical validation, profile, inclusion, and metadata semantics.
- Blocking content errors fail before compiler or output work and are checked again after a long
  compile. Concurrent asset mutation aborts staging without replacing the previous package.
- The first build of a package profile compiles optimized intermediates; later unchanged builds reuse
  Cargo intermediates and avoid rewriting byte-identical files after seeding the staged copy.
- Cross-compilation still requires the selected Rust target and platform linker/toolchain to be
  installed; missing tools are reported by Cargo in the Build panel. The native layout currently
  accepts desktop Linux and Windows targets only; web, mobile, and platform-bundle formats require
  dedicated future packagers rather than being mislabeled as ready native packages.
- `--skip-build` requires an explicit matching `game` artifact, avoiding guesses about Cargo target
  directories or configured default targets.
