--- id: AGD-0005 ticket: variable-tile-bank-palette-serialization title: Variable Glyph Bank Palette Serialization status: accepted created: 2026-03-26 resolved: 2026-07-14 decision: DEC-0038 tags: - packer - legacy-import - glyph-bank - palette-serialization - rgba8888 - runtime-alignment --- ## Pain Domain owner: `packer`, with runtime-facing impact through `assets.pa`. The current `GLYPH/indexed_v1` glyph-bank contract serializes a fixed block of `64` palettes even when an asset authors far fewer palettes. After `DSC-0038`, each palette entry is RGBA8888, so the fixed palette block is now `64 * 16 * 4 = 4096` bytes. That keeps decoding simple, but it wastes cartridge space and leaves a deliberate mismatch between authored palette count and serialized palette count. ## Context Legacy source: `docs/packer/agendas/Variable Tile Bank Palette Serialization Agenda.md` Cross-domain dependency: - runtime Runtime follow-up: - `../runtime/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md` is accepted and normative for the runtime side. - Runtime `DSC-0046` keeps `GLYPH/indexed_v1`, adopts variable glyph-bank palette serialization, and treats this as an incompatible v1 correction. - Runtime plans `PLN-0167` through `PLN-0172` are done and publish the downstream handoff. Current runtime-owned `GLYPH/indexed_v1` contract: - pixels are serialized as packed `u4`; - palettes are serialized as RGBA8888 values in runtime `R`, `G`, `B`, `A` byte order; - `palette_count` is the number of serialized and resident palettes; - `palette_count` must be in `1..=64`; - payload size includes exactly `palette_count * 16 * 4` palette bytes; - palette index `0` is ordinary; - each palette has 16 usable colors; - transparency is represented by alpha, not by a reserved index or magenta color-key; - resident runtime memory materializes exactly `palette_count` palettes; - `palette_id` is a direct palette identity inside the loaded glyph bank and is valid only when `palette_id < palette_count`; - sparse-to-dense palette remapping is not part of v1; - missing palettes must fail explicitly in scene/sprite/composer use, not silently resolve to a default color. Downstream producers must emit: - root effective metadata fields `tile_size`, `width`, `height`, and `palette_count`; - `size = ceil(width * height / 2) + palette_count * 16 * 4`; - `decoded_size = width * height + palette_count * 16 * 4`; - exactly `palette_count * 16 * 4` RGBA8888 palette bytes; - no `GLYPH/indexed_v2` payload for this change; - no fixed 64-palette padding unless `palette_count = 64` and the payload directly satisfies the runtime v1 contract; - no effective runtime behavior based on `palette_authored`. `DSC-0038` intentionally did not solve variable palette-count serialization. It changed the color encoding from RGB565-era semantics to RGBA8888 and kept the fixed 64-palette model stable for that wave. Runtime `DEC-0041` now closes the cross-domain protocol questions that this agenda previously carried as open. The remaining Studio/packer work is downstream alignment with the runtime-owned contract. ## Open Questions - [x] Should variable palette serialization arrive as a new payload version or as an incompatible change to the current one? - Runtime `DEC-0041` resolves this as an incompatible correction to `GLYPH/indexed_v1`, with no v2 format. - [x] If `palette_count` becomes variable, should runtime still materialize a 64-slot resident bank, or truly shrink in-memory representation too? - Runtime `DEC-0041` resolves this: resident runtime memory materializes exactly `palette_count` palettes. - [x] Should sparse authored palette indices remain sparse in serialization, or be canonicalized into a dense runtime block? - Runtime `DEC-0041` rejects sparse-to-dense remapping. `palette_id` remains direct identity inside the loaded glyph bank. - [x] Should this repository create the owner agenda/decision for the runtime side, or should a matching runtime `discussion/` artifact be opened before implementation planning? - Runtime `DSC-0046` now owns the protocol through accepted `DEC-0041`. Studio/packer must follow that handoff. - [x] Which domain owns compatibility for existing cartridges: `packer`, `runtime`, or `shipper`? - Runtime `DEC-0041` introduces no compatibility mode. Old payloads with `palette_count = 64` remain valid only when they satisfy the new contract directly. ## Options ### Option A - Keep fixed 64-palette serialization in v1 - **Approach:** Preserve the current fixed v1 payload with zero-filled unused palette slots. - **Pro:** No runtime change and current specs/tests remain stable. - **Con:** Every glyph bank keeps paying the full 4096-byte palette cost. - **Maintainability:** Medium. ### Option B - Align packer output with runtime variable palette serialization - **Approach:** Keep the `GLYPH/indexed_v1` format name, serialize only `palette_count` palettes, and make packer metadata, payload sizes, fixtures, and Studio projections follow runtime `DEC-0041`. - **Pro:** Removes fixed padding while the project is still in v1, keeps the public format vocabulary stable, and makes runtime metadata match the payload. - **Con:** Requires coordinated packer/spec/test changes and fixture regeneration because the current producer still emits the old fixed-padding shape. - **Maintainability:** Strong if implemented as an explicit contract update, weak if done as an incidental size tweak. Runtime has already closed the protocol questions. ### Option C - Keep fixed external metadata but trim trailing palettes - **Approach:** Keep some 64-slot assumptions in metadata or runtime memory, but serialize only a shortened trailing subset of palettes in the payload. - **Pro:** Can reduce cartridge size while limiting some runtime changes. - **Con:** Creates a split-brain contract where serialized count, logical count, and resident count can disagree. - **Maintainability:** Weak. It preserves the ambiguity this agenda is trying to remove. ## Discussion The retained concern is still valid after `DSC-0038`, but runtime `DEC-0041` has now closed the protocol. The problem is no longer an open cross-domain design question; it is a downstream producer-alignment task for Studio/packer. The old recommendation favored a versioned follow-up because compatibility and runtime stability were treated as constraints. Runtime `DEC-0041` removes that constraint: the project is still in v1, keeps `GLYPH/indexed_v1`, and does not need compatibility for the old fixed 64-palette payload. That makes Option B the required direction for this repository. Runtime already changed the contract directly; Studio/packer must now emit the runtime-owned shape and stop treating `palette_authored` or the old 64-padding model as effective runtime behavior. Option C should be avoided because it preserves multiple meanings for palette count. If the payload becomes variable, `palette_count` should mean the number of serialized palettes, not an unrelated fixed resident capacity. ## Resolution Recommended direction: adopt **Option B**. Updated recommendation: 1. keep the format name `GLYPH/indexed_v1`; 2. follow runtime `DEC-0041` as the protocol authority; 3. remove the fixed 64-palette payload block from packer output; 4. make packer `palette_count` describe the serialized and resident RGBA8888 palette count; 5. emit exactly `palette_count * 16 * 4` palette bytes; 6. update `size` and `decoded_size` formulas to use variable `palette_count`; 7. do not create a v2 format solely for this change; 8. do not preserve compatibility with the old fixed-padding payload; 9. do not introduce sparse-to-dense palette remapping metadata; 10. treat `palette_authored` as informative only unless a later runtime decision promotes it to effective metadata; 11. add downstream fixture coverage for `palette_count = 1`, an intermediate count, and `palette_count = 64`; 12. add a negative producer validation case for metadata/payload mismatch. Next step suggestion: convert this agenda into a Studio/packer decision that references runtime `DEC-0041` and scopes implementation to specs, packer payload emission, Studio projections if needed, tests, and fixtures.