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

409 lines
15 KiB
Markdown

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