# Tile Bank Packing Materialization Decision Status: Accepted Date: 2026-03-20 Domain Owner: `docs/packer` Cross-Domain Impact: `../runtime`, `docs/studio` ## Context The packer execution contract for `packWorkspace(...)` is already closed. What remained open for `tile bank` was the format-specific producer contract: - which selected files become part of the packed asset; - how those files become the canonical `TILES/indexed_v1` payload; - how `AssetEntry` fields are derived for runtime consumption; - how bank palettes are declared and normalized; - which diagnostics block the build for this format. The agenda that closed this discussion is: - [`../agendas/Tile Bank Packing Materialization Agenda.md`](../agendas/Tile%20Bank%20Packing%20Materialization%20Agenda.md) Relevant upstream references are: - [`../decisions/Pack Wizard Pack Execution Semantics Decision.md`](../decisions/Pack%20Wizard%20Pack%20Execution%20Semantics%20Decision.md) - [`../specs/3. Asset Declaration and Virtual Asset Contract Specification.md`](../specs/3.%20Asset%20Declaration%20and%20Virtual%20Asset%20Contract%20Specification.md) - [`../specs/4. Build Artifacts and Deterministic Packing Specification.md`](../specs/4.%20Build%20Artifacts%20and%20Deterministic%20Packing%20Specification.md) - [`../pull-requests/PR-32-palette-declarations-with-explicit-index-contract.md`](../pull-requests/PR-32-palette-declarations-with-explicit-index-contract.md) - [`../../../runtime/docs/runtime/specs/04-gfx-peripheral.md`](../../../runtime/docs/runtime/specs/04-gfx-peripheral.md) - [`../../../runtime/docs/runtime/specs/15-asset-management.md`](../../../runtime/docs/runtime/specs/15-asset-management.md) - [`../../../runtime/docs/runtime/agendas/024-asset-entry-metadata-normalization-contract.md`](../../../runtime/docs/runtime/agendas/024-asset-entry-metadata-normalization-contract.md) ## Decision The first-wave packer materialization contract for `tile bank` adopts the following direction: 1. `TILES/indexed_v1` emits one canonical payload per asset. 2. `1 artifact = 1 tile` in the first wave. 3. Artifacts are ordered by normalized `artifacts[*].index`. 4. Tile placement is row-major in one fixed `256 x 256` sheet. 5. `tile_id` is the linear row-major slot and therefore matches the normalized artifact index. 6. Tile pixels are serialized as packed `u4` indices. 7. Bank palettes are serialized as `RGB565` `u16` values. 8. Palette declarations use explicit semantic identity through `{ index, palette }`. 9. `AssetEntry.metadata` keeps required runtime fields readable at the root while preserving segmented codec and pipeline subtrees. 10. Format-structural diagnostics are raised in the walker/materialization path and therefore participate in validation before pack execution reruns the gate. ## Adopted Constraints ### 1. Canonical Payload Shape - the payload is produced from build-selected artifacts only; - the payload is the canonical bank sheet raster, not a concatenation of per-artifact binary fragments; - for `TILES/indexed_v1`, the serialized payload shape is: 1. packed `u4` pixel indices for the full emitted sheet; 2. one palette block of `64 * 16 * 2` bytes; - palettes are always materialized to `RGB565` during pack emission; - the producer-side payload contract is aligned to what the runtime loader already requires. ### 2. Artifact-to-Tile Contract - `1 artifact = 1 tile` in v1; - artifacts are normalized by `artifacts[*].index`; - `tile_id = artifacts[*].index` after normalization; - the canonical emitted sheet is always `256 x 256`; - placement is row-major within that fixed sheet. Resulting tile capacities: - `tile_size = 8` -> `32 x 32 = 1024` tiles - `tile_size = 16` -> `16 x 16 = 256` tiles - `tile_size = 32` -> `8 x 8 = 64` tiles ### 3. Runtime Entry Contract Each emitted `tile bank` runtime entry must populate: - `asset_id` - `asset_name` - `bank_type = TILES` - `offset` - `size` - `decoded_size` - `codec = NONE` - `metadata` For v1: - `size = ceil(width * height / 2) + 2048` - `decoded_size = (width * height) + 2048` - `width = 256` - `height = 256` - `palette_count = 64` ### 4. Palette Contract - bank palettes are declared in `asset.json.output.pipeline.palettes`; - each palette declaration must use explicit shape `{ index, palette }`; - semantic ordering is ascending numeric `index`, never raw array position; - palette ids are the normalized declared `index` values; - any tile in the bank may be rendered with any palette in the bank; - palette choice is a runtime draw-time concern and is not embedded per tile in the packed payload. ### 5. Metadata Normalization Contract `AssetEntry.metadata` keeps runtime-required fields readable while preserving authoring segmentation: - `asset.json.output.metadata` -> `AssetEntry.metadata` - `asset.json.output.codec_configuration` -> `AssetEntry.metadata.codec` - `asset.json.output.pipeline` -> `AssetEntry.metadata.pipeline` For tile-bank v1 this means: - `tile_size`, `width`, `height`, and `palette_count` remain directly readable at the metadata root; - codec-specific data is nested under `metadata.codec`; - pipeline-derived data is nested under `metadata.pipeline`. This packer decision intentionally aligns with the runtime-side follow-up agenda rather than flattening everything into one ambiguous map. ### 6. Diagnostics and Failure Semantics The following conditions are blocking for tile-bank v1: - duplicate `artifacts[*].index` - gaps in normalized `artifacts[*].index` - fixed-sheet capacity overflow - bank without declared palettes - palette declaration count above `64` - malformed palette declarations - missing or invalid required format metadata - any failure to normalize artifacts into one deterministic payload The following condition is warning-only in the first wave: - fragile tile indices, meaning tile indices that are not safely represented across the full declared bank palette set ### 7. Walker and Validation Boundary - family walkers remain discovery-oriented; - tile-bank structural diagnostics must be produced in the walker/materialization path; - validation consumes those diagnostics naturally; - pack execution reruns the validation gate on a newly created frozen execution snapshot before materialization begins. ## Why This Direction Was Chosen - It matches the runtime consumer contract instead of inventing a producer-local interpretation. - It keeps `tile bank` payload semantics explicit and deterministic. - It avoids embedding per-artifact or per-draw palette assignment into the packed bytes. - It preserves stable palette identity through explicit `index` declarations. - It keeps validation early without weakening the pack-time rerun gate. - It gives the runtime a metadata shape that is readable and still semantically segmented. ## Explicit Non-Decisions This decision does not yet define: - future support for `1 artifact -> many tiles`; - non-`256 x 256` tile-bank targets; - alternative tile-bank codecs beyond `NONE`; - future palette compaction or palette-id remapping strategies; - the runtime-side final decision for general `AssetEntry.metadata` normalization helpers; - future sprite or tilemap semantic adjustments unrelated to packer-owned payload production. ## Implications - tile-bank packing implementation must materialize a full sheet raster rather than artifact fragments; - tile-bank payload generation must pack `u4` indices explicitly in the packer; - palette declarations and palette overhauling flows must preserve explicit palette `index`; - runtime-entry metadata emission must preserve both root-required fields and segmented nested maps; - tile-bank validation logic belongs in the walker/materialization path and must be reused by pack execution gate reruns. ## Propagation Targets Specs: - [`../specs/3. Asset Declaration and Virtual Asset Contract Specification.md`](../specs/3.%20Asset%20Declaration%20and%20Virtual%20Asset%20Contract%20Specification.md) - [`../specs/4. Build Artifacts and Deterministic Packing Specification.md`](../specs/4.%20Build%20Artifacts%20and%20Deterministic%20Packing%20Specification.md) Plans: - [`../pull-requests/PR-32-palette-declarations-with-explicit-index-contract.md`](../pull-requests/PR-32-palette-declarations-with-explicit-index-contract.md) - future packer PR for tile-bank payload materialization - future packer PR for tile-bank validation diagnostics Cross-domain references: - [`../../../runtime/docs/runtime/specs/04-gfx-peripheral.md`](../../../runtime/docs/runtime/specs/04-gfx-peripheral.md) - [`../../../runtime/docs/runtime/specs/15-asset-management.md`](../../../runtime/docs/runtime/specs/15-asset-management.md) - [`../../../runtime/docs/runtime/agendas/023-tilemap-empty-cell-vs-tile-id-zero.md`](../../../runtime/docs/runtime/agendas/023-tilemap-empty-cell-vs-tile-id-zero.md) - [`../../../runtime/docs/runtime/agendas/024-asset-entry-metadata-normalization-contract.md`](../../../runtime/docs/runtime/agendas/024-asset-entry-metadata-normalization-contract.md) Implementation surfaces: - `prometeu-packer-v1` tile-bank payload materializer - `prometeu-packer-v1` tile-bank diagnostics in walker/materialization path - `prometeu-packer-v1` metadata convergence for `AssetEntry` - Studio tile-bank authoring and inspection surfaces that expose palettes or bank composition ## Validation Notes This decision is correctly implemented only when all of the following are true: - artifact normalization produces one deterministic row-major `256 x 256` bank sheet; - emitted tile ids match normalized artifact indices; - emitted pixel bytes are packed as `u4`; - emitted palette bytes are `RGB565` `u16`; - palette declarations are read and written by explicit `index`; - runtime-required metadata fields remain readable at the root; - codec and pipeline metadata survive under `metadata.codec` and `metadata.pipeline`; - structural blockers are visible during validation and are rerun by pack execution before emission.