153 lines
5.6 KiB
Markdown
153 lines
5.6 KiB
Markdown
# 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`).
|