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

7.8 KiB

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.

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:

"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:

"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

{
  "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

{
  "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
  }
}