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 |
|
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:
Tilenao 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
-
O
SceneBankprecisa ser a fonte da verdade. Gameplay, fisica e futuras extensoes nao devem depender do cache de viewport. -
O
SceneViewportCacheprecisa ser operacional, nao semantico. Ele existe para render, nao para redefinir o contrato do mundo. -
O refactor pode quebrar o shape atual. Nao ha necessidade de preservar
ScrollableTileLayernem compatibilidade com a modelagem existente. -
A camera deve ficar desacoplada. O bank nao deve conhecer detalhes de camera; um componente intermediario resolve camera -> viewport -> cache.
-
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/ScrollableTileLayercomo hoje e atacar apenas dirtying, blit e composicao noback. - 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:
- manter o mundo canonico inteiro no
SceneBank; - substituir o consumo direto de layer/mapa por um
SceneViewportCache; - desacoplar camera do modelo canonico;
- preparar o renderer para compor a world a partir de cache materializado;
- 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
SceneBankcontem as 4 layers da scene; backgroundeparallaxpassam a ser tratados dentro das propriasSceneLayers, nao como tipo separado desta V1;- cada
SceneLayerporta seu proprioTileMap; - cada
TileMape um conjunto deTile; - o renderer nao deve consumir o
SceneBankdiretamente; - o renderer deve consumir um
SceneViewportCachederivado doSceneBank; ScrollableTileLayermorre 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_idpronto para consulta;palette_idpronto 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.0acompanham o mundo principal; - layers com fator diferente de
1.0permitem efeito de parallax; - isso mantem
backgroundeparallaxdentro do mesmo modelo estrutural deSceneLayer.
Camera e Resolver
Fica aceito que:
- a camera existe em pixel space;
- a
Scenenao 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
SceneViewportCachematerializa 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;
64x64deixa 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/tilecomo 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 + 8cy = 16 * j + 8
- o drift por eixo e:
dx = x - cxdy = y - cy
Histerese aceita para V1
Fica aceito como baseline:
safe = 12 pxtrigger = 20 px
Interpretacao:
- dentro de
[-12, +12], nada acontece; - entre
12e20, 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+1tile emx - se
dx < -20, o totem anda-1tile emx
Vertical:
- se
dy > +20, o totem anda+1tile emy - se
dy < -20, o totem anda-1tile emy
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
SceneViewportCacheestavel; - 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
xey, o algoritmo deve permitir atualizacao por area/regiao para evitar carregar tiles ja presentes; - troca de
Sceneinvalida oSceneViewportCachecompletamente; - scroll/camera nao devem invalidar a janela inteira como regra normal.
SceneViewportCache e ringbuffer
Leitura atual da agenda:
- sim, o
SceneViewportCachedeve 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:
SceneViewportCacheusa armazenamento em anel internamente como implementacao preferida;- existe um ringbuffer por layer;
- isso fica encapsulado dentro do cache;
Scene,SceneLayereTileMapcontinuam semanticamente simples;- o renderer deve consumir o cache como view materializada, nao como estrutura ciclica exposta.
- na primeira leva, o blit/composite no
backainda 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/2eh/2 - maximos em torno de
layer_size - (w/2, h/2)
- minimos em torno de
- 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:
HUDcontinua sempre por cima;spritespodem aparecer entre layers;- a ordem observavel de composicao continua sendo algo como:
layer 0sprites relevanteslayer 1sprites relevanteslayer 2sprites relevanteslayer 3sprites relevantesHUD
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,HUDefadesficam separados da politica de invalidacao do cache de viewport.
Invariantes aceitos:
SceneViewportCachenao e fonte da verdade de dados, mas e a fonte imediata de copia para o blit;sprites,HUDefadesnao entram no cache;- 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, comBankType::SCENE. - Direcao revisada:
backgroundeparallaxentram no mesmo contrato deSceneLayer, diferenciados por seus metadados e fator de movimento relativo ao totem mestre. - Direcao aceita: a
Scenedeve 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 deactive/emptye 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
backcontinue 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,SceneViewportCacheeSceneViewportResolver; - contrato do resolver camera -> viewport cache;
- politica de rematerializacao por faixa/regiao;
- relacao entre
SceneViewportCachee composicao noback; - 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; backgroundeparallaxpassam a ser absorvidos pelo proprio contrato deSceneLayer, via fator de movimento relativo ao totem mestre;ScrollableTileLayerdeixa de existir na arquitetura alvo;- a decisao seguinte deve cristalizar tipos, responsabilidades e invariantes, em vez de reabrir o debate macro.