prometeu-studio/docs/packer/agendas/Tilemap and Metatile Runtime Binary Layout Agenda.md
2026-03-24 13:42:49 +00:00

5.6 KiB

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).