# Asset Specification, Raw Assets, and Virtual Asset Contract Decision ## Status Accepted ## Date 2026-03-11 ## Context This decision closes the architectural questions raised in [`01.2. Asset Specification, Raw Assets, and Virtual Asset Contract Agenda`](../agendas/01.2.%20Asset%20Specification,%20Raw%20Assets,%20and%20Virtual%20Asset%20Contract%20Agenda.md). The packer needs a stable baseline contract for `asset.json` before detailed format-specific specs are written. This contract must: - support one managed asset root with many internal inputs; - distinguish raw assets from virtual assets without making the common schema monolithic; - expose runtime-relevant output information explicitly; - declare preload intent deterministically; - leave room for future format families without coupling all formats together. ## Decision The packer adopts a compact common `asset.json` contract with explicit separation between: - authoring asset family; - grouped input declaration; - runtime-facing output contract; - preload declaration; - optional build/process hints. ### 1. Common top-level shape The common `asset.json` contract includes these required top-level fields: - `schema_version` - `name` - `type` - `inputs` - `output` - `preload` The common contract may additionally include: - `build` `build` is optional in the shared baseline schema. ### 2. Meaning of `type` `type` identifies the authoring-side asset family. It is not the runtime bank target. Examples of valid family-style values: - `image_bank` - `sound_bank` The runtime-facing technical target belongs in `output.format`, not in `type`. `name` remains the required logical asset reference label. Meaning: - `name` is not the stable artifact identity; - `name` is the human-facing and code-facing reference used by asset-oriented APIs unless a future compile-time rewrite model replaces it; - renaming `name` is an API-visible change even though it does not change `asset_id`. ### 3. Input declaration model `inputs` is a structured object keyed by semantic role. Rules: - each key identifies an input role such as `sprites`, `palettes`, or `sources`; - each value is a list of paths; - paths are relative to the asset root; - even a single input is represented as a list, not as a scalar. This avoids shape ambiguity and supports grouped virtual assets cleanly. Example: ```json "inputs": { "sprites": [ "sprites/confirm.png", "sprites/cancel.png" ], "palettes": [ "palettes/ui_main.pal" ] } ``` ### 4. Output contract `output` is the runtime-relevant output declaration. The common baseline requires: - `output.format` - `output.codec` `output.metadata` is optional in the common schema, but becomes required whenever the selected format spec requires additional normative parameters. Meaning: - `output.format` identifies the semantic/runtime format contract; - `output.codec` identifies how payload bytes are stored for extraction and materialization; - `output.metadata` carries format-specific runtime-relevant details. Codec must remain explicit. It must not be hidden inside format naming when it represents a distinct storage concern. ### 5. Raw and virtual asset contract The common schema must support both raw and virtual assets. Rules: - raw assets may declare a direct input-to-output path with minimal build metadata; - virtual assets may declare multiple grouped inputs and optional build/process configuration; - the common schema remains small, while detailed format behavior is defined in dedicated format specs. ### 6. Build/process hints `build` is optional and carries process-oriented configuration. Rules: - `build` may describe how the packer should transform or organize authoring inputs; - if a parameter affects the runtime-facing output contract, it belongs in `output.metadata`; - `build` must not become a hidden substitute for runtime-relevant output definition. Practical interpretation: - `output.metadata` describes the contract of the produced payload; - `build` describes how the packer gets there. ### 7. Preload declaration Each managed asset must declare preload intent explicitly. The baseline shape is: ```json "preload": { "enabled": true } ``` Rules: - `preload.enabled` is required and boolean; - preload participation is declared, not inferred; - packer build uses this field to determine whether preload data is emitted into the runtime-facing artifact contract; - richer preload policy fields are deferred to future decisions and must not be implied silently now. ### 8. Defaults and materialization Defaults may exist in specs, but defaults that affect reproducibility, compatibility, preload, or runtime-visible output must not remain invisible. Rules: - runtime-relevant defaults should be materialized in `asset.json` whenever practical; - if materialization occurs later in the pipeline, the resulting artifact must still expose the effective value explicitly; - packer must not rely on hidden defaults to produce runtime-visible behavior. ### 9. Versioning boundary Versioning is split by concern. Rules: - `asset.json` carries its own `schema_version`; - registry schemas are versioned independently; - runtime-facing artifact schemas are versioned independently; - format contracts are versioned independently through `output.format` values such as `TILES/indexed_v1` or `SOUNDS/pcm16le_v1`. This prevents unrelated schema evolution from being coupled together. ## Invariants and Constraints The following invariants now apply: 1. The common `asset.json` contract remains compact. 2. `type` is authoring-side family identity, not runtime bank identity. 3. `inputs` is structured by semantic role. 4. Input path values are always lists. 5. `name` remains a logical asset reference label even though it is not the stable artifact identity. 6. `output.format` and `output.codec` are separate required concerns. 7. Runtime-relevant format details belong in `output.metadata`. 8. `build` is optional and must not hide runtime-facing output semantics. 9. `preload.enabled` is the only preload field closed in the current baseline. 10. Defaults affecting runtime-visible behavior must be materialized explicitly. 11. Format evolution is versioned independently from registry and artifact schema evolution. ## Explicit Non-Decisions This decision does not yet define: - the full field-level schema of each format family; - richer preload policies beyond `preload.enabled`; - globbing support, input discovery shortcuts, or non-path input locators; - CLI or Studio editor UX for authoring `asset.json`; - exact `assets.pa` payload layout details; - exact runtime preload record encoding. Those belong to later decisions and specs. ## Propagation Targets This decision must propagate to: - common asset declaration spec; - virtual asset contract spec; - format-specific specs such as `TILES/indexed_v1` and `SOUNDS/pcm16le_v1`; - preload emission and artifact mapping specs; - Studio service contracts for asset authoring and validation; - future implementation of asset parsing, validation, and deterministic build planning. ## Validation Notes Example: grouped image asset ```json { "schema_version": 1, "name": "ui_atlas", "type": "image_bank", "inputs": { "sprites": [ "sprites/confirm.png", "sprites/cancel.png" ], "palettes": [ "palettes/ui_main.pal" ] }, "output": { "format": "TILES/indexed_v1", "codec": "RAW", "metadata": { "tile_size": [8, 8] } }, "preload": { "enabled": true }, "build": { "layout": "atlas" } } ``` Example: grouped sound asset ```json { "schema_version": 1, "name": "ui_sounds", "type": "sound_bank", "inputs": { "sources": [ "wav/click.wav", "wav/confirm.wav" ] }, "output": { "format": "SOUNDS/pcm16le_v1", "codec": "RAW" }, "preload": { "enabled": false } } ```