# Tilemap and Metatile Runtime Binary Layout Agenda ## Status Open ## Purpose Convergir a discussão sobre o contrato de mapa para handheld em duas camadas: - formato de autoria/edição (JSON legível), - formato de execução (layout binário compacto em runtime). O objetivo é fechar qual estrutura será adotada para `tilemap bank`, `tileset bank` e dados de colisão/flags, com foco em previsibilidade de memória e streaming de mapas. ## Domain Owner `docs/packer` Este tema é owner de packer porque envolve transformação de artefatos (`JSON -> BIN`), contrato de build e layout de saída para consumo do runtime. Domínios impactados (cross-domain): - `docs/vm-arch` (contrato de leitura em runtime e limites de memória), - `docs/studio` (edição/preview de mapas no workspace), - `docs/compiler/pbs` (integração futura com referências de assets em código, quando aplicável). ## Problem Hoje existe alinhamento conceitual de que: 1. visual e colisão devem ser separados por responsabilidade; 2. o mapa não deve repetir metadados extensos por célula; 3. apenas uma janela ativa de até `9` mapas deve ficar residente em memória. Mas ainda não existe decisão formal sobre: - layout binário final por célula em runtime, - orçamento por mapa e por janela ativa, - responsabilidade exata entre `map bank` e `tileset bank`. Sem esse contrato, o packer não fecha especificação de saída e o runtime/studio ficam sem baseline único. ## Context Premissas atuais da discussão: - `tileset bank` pode ter tamanho diferente dos demais banks; - `map bank` não precisa seguir o mesmo tamanho de `tileset bank`; - mapa deve referenciar IDs compactos (visual, colisão e flags), em vez de duplicar estrutura completa por célula; - paletas por bank continuam sendo opção válida para preservar decisões artísticas locais; - orçamento alvo foi discutido no contexto de `64 KiB` por map bank e janela ativa de `3x3` mapas (`9` residentes). ## Options ### Option A — Célula `u8` (mapa ultra-compacto) - Cada célula armazena apenas `metatileId` (`0..255`). - Colisão/flags vêm de tabela auxiliar por `metatileId`. ### Option B — Célula `u16` bit-packed (recomendação inicial) - Cada célula usa `16 bits` com divisão sugerida: - `visualId`: `10 bits` (`0..1023`), - `collisionId`: `5 bits` (`0..31`), - `flags`: `1 bit` (`0..1`) ou reservado para evolução. - Permite desacoplamento visual/lógico sem custo de `u32`. ### Option C — Célula `u32` (maior flexibilidade) - Exemplo de divisão: - `visualId`: `12 bits`, - `collisionId`: `8 bits`, - `flags/event`: `12 bits`. - Ganho de expressividade para triggers/eventos inline; custo de memória dobra vs `u16`. ## Tradeoffs - Option A minimiza RAM e I/O, mas limita variedade visual e desloca muita semântica para tabelas externas. - Option B oferece bom equilíbrio para handheld: compacta, previsível e com espaço suficiente para muitos casos de mapa. - Option C simplifica evolução funcional (eventos por célula), mas pressiona memória e banda de streaming sem necessidade comprovada agora. ## Runtime Binary Structure (focus) Estrutura sugerida para runtime (baseada em Option B): 1. **Map Header (fixo)** - `magic` (`4 bytes`) - `version` (`u16`) - `width` (`u16`) - `height` (`u16`) - `cellEncoding` (`u8`) — ex.: `1 = U16_PACKED_V1` - `visualBankId` (`u16`) - `collisionBankId` (`u16`) - `reserved` / `checksum` (conforme decisão posterior) 2. **Cell Stream** - vetor contínuo com `width * height` células `u16`, little-endian; - leitura linear favorece cache e descompressão simples. 3. **Optional Chunks (future-proof)** - bloco opcional de `eventTriggers`; - bloco opcional de `spawnPoints`; - bloco opcional de `navHints`. Packing de célula (`U16_PACKED_V1`): - `bits 0..9` => `visualId` - `bits 10..14` => `collisionId` - `bit 15` => `flag0` Decodificação de referência: - `visualId -> metatile visual table -> 4 subtiles (8x8) + palette/flip/priority` - `collisionId -> collision/material table -> walk/solid/swim/damage/etc.` ## Memory Notes for Active Window (`9` maps) Assumindo `64 KiB` por map bank: - `1` mapa residente: `64 KiB` - `9` mapas residentes: `576 KiB` Capacidade por encoding dentro de `64 KiB`: - `u8`: `65,536` células (`256x256` máximo quadrado) - `u16`: `32,768` células (`~181x181` máximo quadrado, ou retângulos equivalentes) - `u32`: `16,384` células (`128x128` máximo quadrado) ## Recommendation Adotar `Option B` como baseline para decisão: 1. autoria em JSON orientada a IDs (`visualId`, `collisionId`, `flags`); 2. empacotamento determinístico para `U16_PACKED_V1` no build; 3. janela ativa de runtime limitada a `9` mapas com budget explícito; 4. extensão para `u32` somente via nova versão de encoding e evidência de necessidade. ## Open Questions 1. `collisionId` com `5 bits` (`32` classes) é suficiente para os biomas/projetos previstos? 2. `flag0` deve ser reservado para trigger rápido ou para variação visual contextual? 3. Quais chunks opcionais entram já em `V1` e quais ficam para `V2`? 4. O `map bank` seguirá estritamente `64 KiB` ou terá tamanho variável com metadado de capacidade? 5. Qual política de compressão do stream (`none`, `LZ4`, etc.) será padrão no packer? ## Expected Follow-up 1. Abrir `decision` em `docs/packer/decisions` fechando o encoding `U16_PACKED_V1`. 2. Propagar contrato de leitura para `docs/vm-arch`. 3. Definir no `docs/studio/specs` o schema de edição JSON correspondente. 4. Planejar PR de implementação (`packer` + `runtime`) com testes de roundtrip (`JSON -> BIN -> decode`).