---
id: AGD-0004
ticket: tilemap-and-metatile-runtime-binary-layout
title: Tilemap and Metatile Runtime Binary Layout
status: open
created: 2026-03-26
resolved:
decision:
tags:
  - packer
  - legacy-import
  - tilemap
  - metatile
  - runtime-layout
---

## Pain

The repository has conceptual alignment around map responsibility split and active-window residency, but still lacks a closed runtime binary contract for handheld maps.

Without a canonical decision:

- packer cannot close the output specification for map artifacts;
- runtime lacks one stable reading baseline;
- Studio cannot align authoring and preview schemas with one binary target.

## Context

Legacy source: `docs/packer/agendas/Tilemap and Metatile Runtime Binary Layout Agenda.md`

Domain owner: `docs/packer`
Cross-domain impact:

- `docs/vm-arch`
- `docs/studio`
- `docs/compiler/pbs`

Current discussion premises retained from source:

- visual and collision concerns should remain separated;
- map cells should not duplicate large metadata per tile;
- only a `3x3` active window of maps should remain resident in memory;
- a `64 KiB` target budget per map bank has been discussed;
- `map bank` and `tileset bank` do not need identical size rules.

## Open Questions

- [ ] Is `collisionId` with `5` bits (`32` classes) sufficient for expected projects?
- [ ] Should `flag0` be reserved for fast trigger semantics or contextual visual variation?
- [ ] Which optional chunks belong in `V1`, and which should wait for a later version?
- [ ] Must `map bank` stay strictly at `64 KiB`, or should capacity be variable with explicit metadata?
- [ ] What compression policy should the packer adopt by default for the stream?

## Options

### Option A - `u8` cells with auxiliary tables
- **Approach:** Store only one `metatileId` per cell and derive collision/flags from side tables.
- **Pro:** Smallest RAM and I/O footprint.
- **Con:** Pushes too much semantic meaning into external lookup tables.
- **Maintainability:** Medium.

### Option B - `u16` bit-packed cells
- **Approach:** Use `16` bits per cell, with a compact split such as `visualId`, `collisionId`, and `flag0`.
- **Pro:** Balances compactness, deterministic decoding, and enough expressivity for many handheld cases.
- **Con:** Leaves less room for inline per-cell growth than `u32`.
- **Maintainability:** Strong.

### Option C - `u32` cells
- **Approach:** Use larger cells to keep more event/flag space inline.
- **Pro:** Highest flexibility for per-cell triggers and future expansion.
- **Con:** Doubles memory cost versus `u16` without current evidence that it is needed.
- **Maintainability:** Weak for the current budget target.

## Discussion

The retained source already converged toward `u16` as the best baseline.

The suggested runtime shape is:

- fixed map header;
- contiguous cell stream;
- optional future-proof chunks.

The proposed `U16_PACKED_V1` split keeps visual and collision references compact while preserving predictable memory usage and straightforward streaming behavior for a handheld target.

## Resolution

Recommended direction: adopt **Option B** as the baseline.

Imported retained recommendation:

1. authorship stays JSON-oriented around compact ids;
2. build output packs deterministically into `U16_PACKED_V1`;
3. runtime active-window budget remains explicit;
4. any move to `u32` should happen only through a later versioned encoding with evidence of need.

Next step suggestion: convert this agenda into a decision that closes `U16_PACKED_V1`, propagation to `docs/vm-arch`, Studio editing schema alignment, and implementation planning for roundtrip tests.
