prometeu-studio/docs/packer/decisions/002-asset-specification-raw-assets-and-virtual-asset-contract-decision.md
2026-03-24 13:42:38 +00:00

283 lines
7.8 KiB
Markdown

# 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
}
}
```