prometeu-runtime/discussion/workflow/agendas/AGD-0025-scene-bank-and-viewport-cache-refactor.md
2026-04-13 20:13:16 +01:00

15 KiB

id ticket title status created resolved decision tags
AGD-0025 scene-bank-and-viewport-cache-refactor Agenda - Scene Bank and Viewport Cache Refactor open 2026-04-11
gfx
tilemap
runtime
render

Agenda - Scene Bank and Viewport Cache Refactor

Contexto

Hoje o runtime mistura diretamente:

  • o mapa canonico da layer;
  • o estado de scroll;
  • e o consumo direto pelo renderer.

Na implementacao atual, os 64x64 das ScrollableTileLayer criadas em Gfx::new() sao o proprio mapa residente da layer que o renderer consulta diretamente. Nao existe hoje separacao explicita entre:

  • fonte canonica do mundo carregado;
  • layer canonica da scene;
  • view/cache materializada para render.

Ao longo desta discussao, o foco deixou de ser ringbuffer como tema principal. A convergencia real passou a ser um refactor do sistema de tilemap para separar melhor:

  • modelo canonico do mundo;
  • viewport materializada;
  • camera;
  • composicao incremental no back.

Tambem ficou claro que:

  • Tile nao carrega RGB; ele referencia glyph/palette/flags e continua relativamente leve;
  • por isso, manter o tilemap canonico inteiro em memoria e defensavel no baseline atual;
  • o ganho mais promissor parece estar em reduzir recomposicao bruta, nao em residentizar parcialmente o mundo logo de inicio.

Problema

Ainda nao esta definido como reorganizar o sistema de tilemap para:

  • manter um modelo canonico simples e previsivel;
  • materializar apenas o necessario para render;
  • permitir camera desacoplada do tilemap;
  • evitar full redraw bruto sempre que a camera ou o mundo se movem;
  • preparar o renderer para compor a world a partir de cache de viewport.

Pontos Criticos

  1. O SceneBank precisa ser a fonte da verdade. Gameplay, fisica e futuras extensoes nao devem depender do cache de viewport.

  2. O SceneViewportCache precisa ser operacional, nao semantico. Ele existe para render, nao para redefinir o contrato do mundo.

  3. O refactor pode quebrar o shape atual. Nao ha necessidade de preservar ScrollableTileLayer nem compatibilidade com a modelagem existente.

  4. A camera deve ficar desacoplada. O bank nao deve conhecer detalhes de camera; um componente intermediario resolve camera -> viewport -> cache.

  5. O custo principal a atacar continua sendo composicao. A materializacao do cache precisa conversar com uma estrategia de redraw menos destrutiva no back.

Opcoes

Opcao A - Manter o modelo atual e otimizar o renderer em volta

  • Abordagem: preservar TileLayer/ScrollableTileLayer como hoje e atacar apenas dirtying, blit e composicao no back.
  • Pros: menor refactor estrutural.
  • Contras: mantem o acoplamento entre mapa, scroll e renderer; pior base para evolucao arquitetural.

Opcao B - Refatorar para SceneBank + SceneViewportCache

  • Abordagem: separar modelo canonico da scene e cache materializado de viewport, com camera desacoplada e algoritmo proprio de rematerializacao.
  • Pros: arquitetura mais clara; melhor separacao de responsabilidades; base melhor para dirtying e composicao incremental.
  • Contras: refactor maior; exige redefinir tipos e fluxo do renderer.

Sugestao / Recomendacao

A recomendacao atual e seguir com a Opcao B.

O refactor deve:

  1. manter o mundo canonico inteiro no SceneBank;
  2. substituir o consumo direto de layer/mapa por um SceneViewportCache;
  3. desacoplar camera do modelo canonico;
  4. preparar o renderer para compor a world a partir de cache materializado;
  5. preservar sprites, HUD e fades como sistemas separados da invalidacao do cache de viewport.

Direcao Atual Consolidada

Fica aceito, a menos de revisao explicita posterior, que:

  • o runtime podera carregar ate 16 entradas em BankType::TILEMAP;
  • cada entrada carregada representa um SceneBank;
  • cada SceneBank contem as 4 layers da scene;
  • background e parallax passam a ser tratados dentro das proprias SceneLayers, nao como tipo separado desta V1;
  • cada SceneLayer porta seu proprio TileMap;
  • cada TileMap e um conjunto de Tile;
  • o renderer nao deve consumir o SceneBank diretamente;
  • o renderer deve consumir um SceneViewportCache derivado do SceneBank;
  • ScrollableTileLayer morre nesta arquitetura;
  • nao ha obrigacao de compatibilidade com o shape atual.

Modelo Alvo de V1

Hierarquia conceitual

  • Scene (BankType::SCENE)

    • agregado canonico carregado
    • contem as SceneLayers da scene
  • SceneLayer

    • layer canonica da scene
    • contem metadados da layer e seu TileMap
    • pode representar layer normal, background ou parallax pela sua configuracao
  • TileMap

    • grade de tiles de uma layer
  • Tile

    • unidade basica de conteudo por celula
  • SceneViewportCache

    • cache/view materializada para render
    • derivada da Scene
    • contem ringbuffers internos, um por layer

Papel de cada tipo

  • Scene

    • fonte da verdade
    • usado por gameplay, fisica e materializacao
  • SceneViewportCache

    • usado pelo renderer
    • sabe scroll fino em pixels
    • nao e fonte da verdade do mundo
    • e a fonte imediata de copia para o blit
  • SceneLayer

    • passa a carregar tambem metadados de movimento relativos ao totem mestre
    • isso permite compor layers normais e parallax sob o mesmo contrato base

O que um tile do cache pode guardar

V1 deve partir de cache leve. Alem do Tile cru, ele pode guardar alguns dados derivados para acelerar raster:

  • glyph_id pronto para consulta;
  • palette_id pronto para uso;
  • flags de flip compactadas;
  • marca rapida de active/empty;
  • referencia do glyph bank ja resolvida por layer;
  • metadados de dirty local do proprio cache.

V1 nao deve:

  • guardar pixels RGB resolvidos por tile;
  • duplicar desnecessariamente fisica no cache de render;
  • virar um mini-framebuffer por tile.

Movimento por layer

Fica aceito que cada SceneLayer pode carregar um fator de movimento relativo ao totem mestre.

Leitura:

  • existe um totem mestre resolvido pelo sistema de camera/viewport;
  • cada layer resolve seu proprio deslocamento efetivo a partir desse totem mestre;
  • layers com fator 1.0 acompanham o mundo principal;
  • layers com fator diferente de 1.0 permitem efeito de parallax;
  • isso mantem background e parallax dentro do mesmo modelo estrutural de SceneLayer.

Camera e Resolver

Fica aceito que:

  • a camera existe em pixel space;
  • a Scene nao conhece camera;
  • um componente externo faz a ligacao entre camera e cache materializado;
  • esse componente calcula viewport logica, totem mestre, drift, clamp e rematerializacao.

Nome conceitual provisório:

  • SceneViewportResolver
  • ou equivalente com a mesma responsabilidade.

Direcao aceita para o SceneViewportResolver:

  • ele e responsavel pelos totens:
    • totem mestre
    • totens derivados por layer
  • ele recebe posicao de camera de uma entidade externa;
  • internamente, a posicao da camera e tratada como insumo para o totem mestre;
  • ele clampa o totem mestre;
  • ele decide se o cache precisa ou nao ser atualizado;
  • se necessario, ele dispara atualizacao do SceneViewportCache;
  • ele tambem deve saber instrumentalizar requests de copia por layer a partir da posicao da camera e do estado do cache;
  • ele nao executa a copia no back, apenas informa o que deve ser copiado e de onde.

Viewport, Halo e Tamanho da Janela

Dados atuais do runtime:

  • resolucao interna: 320x180
  • world layers: 16x16
  • HUD: 8x8

Para world 16x16 em 320x180, a viewport raster minima atual equivale a:

  • 21x12 tiles

Direcao aceita para V1:

  • o SceneViewportCache materializa mais do que a viewport raster minima;
  • o tamanho alvo de V1 passa a ser 25x16 tiles;
  • isso da folga suficiente para velocidades agressivas de camera sem inflar demais o cache;
  • 64x64 deixa de ser tamanho alvo e passa a ser apenas referencia historica do modelo atual.

Ordem de grandeza de memoria para 25x16:

  • 400 tiles
  • usando 36 bytes/tile como estimativa conservadora com margem, o cache fica em ~14.1 KiB

Algoritmo de Totem, Drift e Histerese

Modelo aceito

  • existe um totem mestre em tile space: (i, j)
  • existe uma camera em pixel space: (x, y)
  • com tile 16x16, o centro do totem mestre em pixels e:
    • cx = 16 * i + 8
    • cy = 16 * j + 8
  • o drift por eixo e:
    • dx = x - cx
    • dy = y - cy

Histerese aceita para V1

Fica aceito como baseline:

  • safe = 12 px
  • trigger = 20 px

Interpretacao:

  • dentro de [-12, +12], nada acontece;
  • entre 12 e 20, existe tolerancia sem rematerializacao;
  • ao ultrapassar 20, o totem anda em tiles e o cache rematerializa as faixas necessarias.

Regra por eixo

Horizontal:

  • se dx > +20, o totem anda +1 tile em x
  • se dx < -20, o totem anda -1 tile em x

Vertical:

  • se dy > +20, o totem anda +1 tile em y
  • se dy < -20, o totem anda -1 tile em y

Depois de cada movimento:

  • recalcula-se o centro do totem;
  • recalcula-se o drift;
  • se ainda houver excesso, o processo repete.

Motivo da histerese

Histerese fica cravada na agenda como tecnica explicita para evitar flick/thrash de borda.

Ela existe para:

  • evitar vai-e-volta quando camera/player oscilam perto do limite;
  • manter o SceneViewportCache estavel;
  • disparar rematerializacao por eventos discretos, nao por ruido de borda.

Politica de atualizacao do cache

Fica aceito que:

  • a atualizacao normal do cache ocorre por linha/coluna;
  • se houver trigger simultaneo em x e y, o algoritmo deve permitir atualizacao por area/regiao para evitar carregar tiles ja presentes;
  • troca de Scene invalida o SceneViewportCache completamente;
  • scroll/camera nao devem invalidar a janela inteira como regra normal.

SceneViewportCache e ringbuffer

Leitura atual da agenda:

  • sim, o SceneViewportCache deve preferencialmente usar ringbuffer internamente;
  • nao deve virar contrato semantico do mundo nem da Scene;
  • a principal vantagem aparece justamente no padrao aceito de atualizacao por linha/coluna e por area nos cantos;
  • com layers de parallax, esse beneficio aumenta, porque cada layer pode materializar sua propria janela efetiva a partir do totem mestre sem precisar copiar janelas inteiras sempre.

Direcao aceita:

  • SceneViewportCache usa armazenamento em anel internamente como implementacao preferida;
  • existe um ringbuffer por layer;
  • isso fica encapsulado dentro do cache;
  • Scene, SceneLayer e TileMap continuam semanticamente simples;
  • o renderer deve consumir o cache como view materializada, nao como estrutura ciclica exposta.
  • na primeira leva, o blit/composite no back ainda pode continuar destrutivo;
  • o ganho esperado do ringbuffer fica principalmente em evitar copias internas repetidas do proprio cache antes do blit final.

Clamp de borda

Direcao atual:

  • o totem e um valor de referencia em x/y;
  • o clamp inicial pode ser pensado como:
    • minimos em torno de w/2 e h/2
    • maximos em torno de layer_size - (w/2, h/2)
  • nos cantos, camera e cache nao precisam coincidir de forma simetrica;
  • o cache pode ficar clampado ao limite do SceneBank.

Composicao no Back

Fica aceito que:

  • HUD continua sempre por cima;
  • sprites podem aparecer entre layers;
  • a ordem observavel de composicao continua sendo algo como:
    • layer 0
    • sprites relevantes
    • layer 1
    • sprites relevantes
    • layer 2
    • sprites relevantes
    • layer 3
    • sprites relevantes
    • HUD

Leitura atual:

  • ainda pode haver redraw completo da ordem de composicao observavel;
  • o ganho principal esperado vem de parar de resolver o tilemap bruto toda vez e passar a compor a partir do SceneViewportCache;
  • sprites, HUD e fades ficam separados da politica de invalidacao do cache de viewport.

Invariantes aceitos:

  1. SceneViewportCache nao e fonte da verdade de dados, mas e a fonte imediata de copia para o blit;
  2. sprites, HUD e fades nao entram no cache;
  3. a ordem observavel continua:
    • layer 0
    • sprites intermediarios
    • layer 1
    • sprites intermediarios
    • layer 2
    • sprites intermediarios
    • layer 3
    • sprites intermediarios
    • HUD

Open Questions Prioritarias

1. Shape do SceneBank

  • Direcao aceita: o agregado canonico passa a se chamar Scene, com BankType::SCENE.
  • Direcao revisada: background e parallax entram no mesmo contrato de SceneLayer, diferenciados por seus metadados e fator de movimento relativo ao totem mestre.
  • Direcao aceita: a Scene deve expor leitura/escrita por tile e operacoes por regiao/faixa.

2. Shape do SceneViewportCache

  • Direcao aceita: o cache e um unico agregado por scene, contendo as 4 layers internamente.
  • Direcao aceita: entram na V1 campos derivados leves para acelerar raster, incluindo glyph_id, palette_id, flags de flip, marca de active/empty e referencia rapida de bank por layer.
  • Direcao aceita: o cache usara ringbuffer internamente desde a V1 como implementacao preferida, com um ringbuffer por layer.

3. Resolver camera -> cache

  • Direcao aceita: drift, trigger, clamp e posicao do totem moram no resolver.
  • Direcao aceita: o resolver trabalha com um totem mestre e deriva deslocamentos efetivos por layer quando houver fator de movimento diferente.
  • Direcao aceita: o cache permanece focado em armazenar a materializacao para render.

4. Composicao incremental

  • Leitura aceita desta agenda: neste momento nao ha seguranca para assumir algo mais sofisticado do que composicao destrutiva total da ordem observavel no back.
  • Direcao atual: o ganho esperado continua vindo de deixar de resolver o tilemap bruto toda vez, mesmo que a composicao no back continue destrutiva.

5. Camera

  • Direcao aceita: camera completa fica fora desta decisao.
  • Direcao aceita: esta decisao trabalha apenas com uma API que tenta mover o totem; o contrato completo de camera sera discutido separadamente.

Criterio para Encerrar

Esta agenda pode ser encerrada quando houver alinhamento explicito sobre:

  • shape minimo de Scene, SceneLayer, TileMap, Tile, SceneViewportCache e SceneViewportResolver;
  • contrato do resolver camera -> viewport cache;
  • politica de rematerializacao por faixa/regiao;
  • relacao entre SceneViewportCache e composicao no back;
  • invariantes que devem aparecer na decisao posterior para renderer, camera e scene loading.

Estado Atual da Agenda

Leitura consolidada desta agenda neste momento:

  • a direcao arquitetural principal ja esta aceita;
  • o tema deixou de ser ringbuffer generico e virou refactor do sistema de tilemap/render para Scene + SceneViewportCache;
  • background e parallax passam a ser absorvidos pelo proprio contrato de SceneLayer, via fator de movimento relativo ao totem mestre;
  • ScrollableTileLayer deixa de existir na arquitetura alvo;
  • a decisao seguinte deve cristalizar tipos, responsabilidades e invariantes, em vez de reabrir o debate macro.