--- id: AGD-0025 ticket: scene-bank-and-viewport-cache-refactor title: Agenda - Scene Bank and Viewport Cache Refactor status: open created: 2026-04-11 resolved: decision: tags: [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 `SceneLayer`s, 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 `SceneLayer`s 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.