406 lines
17 KiB
Markdown
406 lines
17 KiB
Markdown
---
|
|
id: AGD-0026
|
|
ticket: render-all-scene-cache-and-camera-integration
|
|
title: Agenda - Integrate render_all with Scene Cache and Camera
|
|
status: accepted
|
|
created: 2026-04-14
|
|
updated: 2026-04-15
|
|
tags: [gfx, runtime, render, camera, scene]
|
|
---
|
|
|
|
## Contexto
|
|
|
|
A thread `DSC-0025` fechou a base arquitetural para `SceneBank`, `SceneViewportCache`, `SceneViewportResolver` e o decoder binário de `SCENE`. O renderer já possui um caminho explícito `render_scene_from_cache(&SceneViewportCache, &ResolverUpdate)`, mas o loop operacional do runtime ainda chama apenas `render_all()`.
|
|
|
|
Hoje, em [tick.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs:148), o frame segue pelo `hw.gfx_mut().render_all()`. Isso significa que o caminho novo de world render ainda não está integrado ao ciclo normal do runtime.
|
|
|
|
Ao mesmo tempo, a integração correta depende de fechar o contrato mínimo da câmera, porque o `SceneViewportResolver` já assume uma posição de câmera em pixel space e produz `ResolverUpdate` a partir dela. Sem essa integração, o runtime fica com duas verdades práticas:
|
|
|
|
- a arquitetura aceita para world rendering;
|
|
- o caminho ainda efetivamente usado pelo frame loop.
|
|
|
|
## Problema
|
|
|
|
Precisamos integrar `render_scene_from_cache()` ao `render_all()` e ao ciclo real do runtime sem reabrir a arquitetura já aceita para `SceneBank` / `SceneViewportCache` / `SceneViewportResolver`.
|
|
|
|
O problema concreto não é só “chamar uma função”. É decidir:
|
|
|
|
- quem é dono do estado de câmera mínimo;
|
|
- onde `SceneBank`, `SceneViewportCache` e `SceneViewportResolver` passam a residir em runtime;
|
|
- quando o cache é atualizado;
|
|
- como o `render_all()` deixa de ser um caminho “scene-blind” e vira o entrypoint normal da composição final.
|
|
|
|
## Pontos Criticos
|
|
|
|
- `render_all()` deve continuar funcional mesmo quando nenhuma scene estiver carregada.
|
|
- O `render_all()` atual não desenha world layers; ele só compõe sprites de prioridade 0 e fades.
|
|
- O modelo atual de `Sprite.priority` mistura duas responsabilidades:
|
|
- em qual faixa de composição o sprite entra;
|
|
- qual a ordem relativa entre sprites naquela faixa.
|
|
- `render_scene_from_cache()` existe, mas exige `SceneViewportCache` e `ResolverUpdate` já preparados por fora.
|
|
- O modelo atual de sprites ainda é slot-first para o chamador:
|
|
- há armazenamento fixo;
|
|
- o dev informa índice;
|
|
- e o renderer precisa filtrar `active`.
|
|
- O `SceneViewportResolver` já carrega política importante:
|
|
- câmera em pixel space
|
|
- anchors
|
|
- clamp
|
|
- histerese
|
|
- refresh requests
|
|
- copy requests
|
|
- Ainda não existe um dono explícito do estado operacional:
|
|
- cena ativa
|
|
- cache ativo
|
|
- resolver ativo
|
|
- câmera ativa
|
|
- O `FrameComposer` não pode regredir o contrato já aceito no scene model:
|
|
- `SceneLayer.tile_size` já é por-layer e aceita `8x8`, `16x16` e `32x32`;
|
|
- o decoder de `SCENE` já materializa esses tamanhos;
|
|
- fixar o pipeline em `16x16` dentro do orquestrador de frame criaria uma restrição artificial que não existe no modelo canônico.
|
|
- Se a integração for mal feita, o renderer pode voltar a misturar:
|
|
- política de câmera
|
|
- atualização de cache
|
|
- composição final
|
|
|
|
## Opcoes
|
|
|
|
### Opcao 1 - Integrar tudo diretamente dentro de `Gfx`
|
|
|
|
**Como seria:**
|
|
`Gfx` passa a possuir a scene ativa, o cache, o resolver e a câmera; `render_all()` atualiza resolver/cache e já compõe tudo.
|
|
|
|
**Vantagens:**
|
|
- caminho curto de integração;
|
|
- menos objetos atravessando o runtime;
|
|
- fácil de chamar a partir do tick.
|
|
|
|
**Desvantagens:**
|
|
- empurra para `Gfx` responsabilidade demais;
|
|
- mistura composição com estado de cena/câmera;
|
|
- reduz clareza para testes e evolução futura.
|
|
|
|
### Opcao 2 - Integrar no runtime com um controlador explícito de scene viewport
|
|
|
|
**Como seria:**
|
|
O runtime ou um pequeno controlador operacional passa a possuir:
|
|
- scene ativa
|
|
- cache
|
|
- resolver
|
|
- câmera
|
|
|
|
Esse controlador atualiza o cache quando a câmera muda e entrega ao `Gfx` apenas o que ele precisa para compor.
|
|
|
|
**Vantagens:**
|
|
- separa melhor estado operacional de composição;
|
|
- mantém `Gfx` mais focado em render;
|
|
- preserva a ideia de que o resolver é dono da política de movimento/rematerialização.
|
|
- permite manter um caminho explícito de `render_all()` sem scene carregada.
|
|
|
|
**Desvantagens:**
|
|
- adiciona mais um objeto operacional no runtime;
|
|
- exige definir uma superfície clara entre runtime e renderer.
|
|
|
|
### Opcao 3 - Fazer uma integração mínima temporária em `render_all()` e postergar a arquitetura do dono da câmera
|
|
|
|
**Como seria:**
|
|
Criar um caminho temporário para que `render_all()` receba ou consulte estado suficiente para chamar `render_scene_from_cache()`, mas sem ainda fechar onde mora a câmera a longo prazo.
|
|
|
|
**Vantagens:**
|
|
- acelera a ligação do caminho novo ao frame loop;
|
|
- destrava testes end-to-end rapidamente.
|
|
|
|
**Desvantagens:**
|
|
- alto risco de solução transitória virar definitiva;
|
|
- deixa ambiguidade operacional exatamente no ponto mais sensível da integração.
|
|
|
|
## Sugestao / Recomendacao
|
|
|
|
Seguir com a **Opcao 2**.
|
|
|
|
Ou seja:
|
|
|
|
- `FrameComposer` passa a ser o orquestrador de frame/scene;
|
|
- `FrameComposer` deve morar em `hardware/drivers`;
|
|
- `Hardware` passa a agregar `FrameComposer` ao lado de `Gfx`;
|
|
- `Gfx` permanece como backend de composição e blit;
|
|
- a política do frame não deve ficar presa ao hardware atual;
|
|
- isso preserva espaço para:
|
|
- fast paths com diretivas/capacidades de GPU quando existirem;
|
|
- uma futura implementação mais próxima de PPU / bare metal;
|
|
- `render_all()` deve continuar sendo o entrypoint normal de composição;
|
|
- `render_all()` deve continuar funcionando mesmo sem scene ativa;
|
|
- mas ele não deve virar dono da câmera nem do ciclo de atualização do cache;
|
|
- precisamos de um orquestrador operacional no runtime, ou imediatamente adjacente a ele, que:
|
|
- mantenha a scene ativa opcional;
|
|
- mantenha a câmera / viewport mínima;
|
|
- mantenha o controlador de sprites do frame;
|
|
- atualize o `SceneViewportResolver` quando houver scene;
|
|
- aplique refreshes ao `SceneViewportCache` quando houver scene;
|
|
- e entregue ao `Gfx` o estado pronto para compor.
|
|
|
|
Mais explicitamente:
|
|
|
|
- `FrameComposer` passa a ser dono de:
|
|
- scene ativa;
|
|
- câmera / viewport;
|
|
- `SceneViewportCache`;
|
|
- `SceneViewportResolver`;
|
|
- sprites emitidos no frame;
|
|
- o state de scene/sprite que hoje esteja em `Gfx` deve migrar para `FrameComposer`;
|
|
- `Gfx` deve ficar focado em:
|
|
- composição;
|
|
- blit;
|
|
- raster;
|
|
- execução visual do frame preparado.
|
|
|
|
Para V1, o contrato mínimo de câmera pode continuar pequeno:
|
|
|
|
- `camera_x_px: i32`
|
|
- `camera_y_px: i32`
|
|
- representando o canto superior esquerdo da viewport no mundo
|
|
|
|
Sem follow/smoothing/shake/cut nesta etapa.
|
|
|
|
O comportamento mínimo recomendado fica:
|
|
|
|
- sem scene ativa:
|
|
- `FrameComposer` continua válido;
|
|
- `render_all()` compõe apenas o que já existe fora do pipeline de world (`sprites`, `fades`, e futuramente `HUD` quando aplicável);
|
|
- não existe `clear` implícito;
|
|
- limpar o `back` continua sendo responsabilidade explícita do chamador / dev;
|
|
- com scene ativa:
|
|
- `FrameComposer` atualiza resolver/cache;
|
|
- `render_all()` compõe o world a partir do cache e preserva a ordem já aceita.
|
|
|
|
Esta direção é provisoriamente aceita mesmo sem a figura final completa, justamente para permitir que a integração avance e revele os pontos onde a separação runtime/backend ainda precise de ajuste.
|
|
|
|
Para sprites, a direção provisória recomendada fica:
|
|
|
|
- cada `Sprite` deve carregar:
|
|
- `layer`
|
|
- `priority`
|
|
- `Sprite.active` deve ser removido;
|
|
- `layer` define em qual faixa de composição o sprite entra;
|
|
- `priority` define a ordenação entre sprites daquela mesma faixa;
|
|
- a composição observável passa a ser por camada:
|
|
- `(sprites -> scene) layer_0`
|
|
- `(sprites -> scene) layer_1`
|
|
- `(sprites -> scene) layer_2`
|
|
- `(sprites -> scene) layer_3`
|
|
|
|
Isso substitui o modelo atual em que um único `priority` tenta representar ao mesmo tempo posição macro na composição e ordenação fina.
|
|
|
|
O modelo operacional recomendado para sprites passa a ser:
|
|
|
|
- capacidade máxima interna de `512` sprites por frame;
|
|
- contador zerado a cada frame;
|
|
- o dev não informa mais índice de sprite;
|
|
- cada emissão ocupa o próximo slot interno disponível;
|
|
- o registro já coloca o sprite no bucket correto da layer;
|
|
- a composição consome apenas os sprites emitidos naquele frame.
|
|
|
|
## Perguntas em Aberto
|
|
|
|
- Fechado provisoriamente:
|
|
- `FrameComposer` em `hardware/drivers`;
|
|
- `Hardware` agrega `FrameComposer` e `Gfx`;
|
|
- `Gfx` atua como backend operacional de composição.
|
|
- O contrato mínimo do `FrameComposer` precisa ser fechado normativamente.
|
|
- Fechado:
|
|
- o subsistema interno de sprites se chama `SpriteController`.
|
|
- `Sprite.layer` deve ser um enum fechado (`Layer0..Layer3`) ou um tipo mais genérico?
|
|
- fechado provisoriamente:
|
|
- manter numérico;
|
|
- usar o mesmo tipo/referência de layer do `SceneBank`.
|
|
- A composição por camada deve ser:
|
|
- `sprites -> scene` dentro de cada layer, como direção inicial,
|
|
- ou `scene -> sprites` para alguma camada específica?
|
|
- A ordenação entre sprites de uma mesma layer será:
|
|
- fechado:
|
|
- `priority` menor blita primeiro;
|
|
- em empate, FIFO por ordem de registro.
|
|
- Overflow de sprite no frame:
|
|
- fechado: excedentes são ignorados;
|
|
- deve existir espaço para log/telemetria;
|
|
- futuramente isso pode virar sinal negativo para certificação.
|
|
- `emit_sprite(...)` precisa retornar algo, ou ter reset separado além de `begin_frame()`?
|
|
- fechado por enquanto:
|
|
- não;
|
|
- usar apenas log do sistema para overflow/eventos operacionais;
|
|
- não introduzir reset extra além do fluxo normal do frame.
|
|
- `render_all()` deve:
|
|
- continuar sem parâmetros e consultar estado já preparado,
|
|
- ou ganhar uma nova superfície interna para receber o scene state preparado?
|
|
- direção aceita:
|
|
- `FrameComposer` chama o entrypoint de composição do backend visual;
|
|
- `Gfx.render_all()` deve morrer;
|
|
- o serviço deve migrar para `FrameComposer.renderFrame()`.
|
|
|
|
## Contrato Minimo Proposto
|
|
|
|
Direção proposta para V1 do `FrameComposer`:
|
|
|
|
- `bind_scene(...)`
|
|
- recebe um `scene bank id`;
|
|
- `FrameComposer` deve possuir acesso a `SceneBankPoolAccess`;
|
|
- resolve a scene ativa através do pool;
|
|
- o acesso ao bank deve ser sempre por ponteiro / referência compartilhada, nunca por cópia;
|
|
- ao bindar, o compositor guarda:
|
|
- `scene_bank_id`;
|
|
- `Arc<SceneBank>` já resolvido;
|
|
- consegue verificar se a scene está carregada;
|
|
- inicializa ou reinicializa cache/resolver conforme necessário.
|
|
|
|
- `unbind_scene()`
|
|
- remove a scene ativa;
|
|
- invalida o pipeline de world;
|
|
- descarta o cache associado à scene bindada;
|
|
- mantém o compositor funcional para `sprites + fades`.
|
|
|
|
- `set_camera(x, y)`
|
|
- atualiza a posição da câmera em pixel space;
|
|
- `x, y` representam o canto superior esquerdo da viewport no mundo.
|
|
|
|
- `begin_frame()`
|
|
- zera o contador de sprites emitidos;
|
|
- limpa buckets internos de sprite;
|
|
- prepara o estado transitório do frame.
|
|
|
|
- `emit_sprite(...)`
|
|
- registra um sprite no próximo slot interno disponível;
|
|
- associa o sprite à sua `layer`;
|
|
- insere no bucket correspondente;
|
|
- overflow é ignorado com espaço para log/telemetria.
|
|
|
|
- `compose_frame()`
|
|
- se houver scene ativa:
|
|
- atualiza `SceneViewportResolver`;
|
|
- aplica `CacheRefreshRequest`s ao `SceneViewportCache`;
|
|
- aciona o caminho de composição world + sprites;
|
|
- se não houver scene ativa:
|
|
- aciona o caminho `sprites + fades`;
|
|
- delega a composição efetiva ao `Gfx`.
|
|
|
|
### Observacoes
|
|
|
|
- `end_frame()` não parece obrigatório na V1.
|
|
- `begin_frame()` + `compose_frame()` já cobrem o ciclo mínimo.
|
|
- `FrameComposer` decide e prepara;
|
|
`Gfx` executa a composição.
|
|
- o binding de scene deve ser por `scene bank id`, não por ownership direto de `SceneBank`.
|
|
- o `SceneViewportCache` vive dentro do `FrameComposer` enquanto a scene estiver bindada.
|
|
- troca do conteúdo do slot/bank exige novo `bind_scene(...)`;
|
|
o `FrameComposer` não deve ficar fazendo polling constante do pool para revalidar a scene ativa.
|
|
- o fluxo operacional aceito é:
|
|
- `FrameComposer.compose_frame()`
|
|
- chama o serviço `FrameComposer.renderFrame()`.
|
|
- `FrameComposer` deve ser capaz de renderizar algo 100% do tempo:
|
|
- cache/resolver ficam `None` sem bind;
|
|
- deve existir uma forma explícita de saber se a scene está disponível para render.
|
|
- `bind_scene(...)` substitui completamente a scene anterior.
|
|
|
|
## Sugestao / Recomendacao Atualizada
|
|
|
|
Aceitar o contrato mínimo acima como base de fechamento da agenda, a menos que apareça alguma necessidade concreta de:
|
|
|
|
- separar `compose_frame()` em múltiplas fases públicas;
|
|
- expor refresh manual de cache para o chamador;
|
|
- ou introduzir um `end_frame()` com semântica real além do reset que já ocorre em `begin_frame()`.
|
|
- manter o binding de scene como:
|
|
- `scene_bank_id + Arc<SceneBank>`;
|
|
- com rebind explícito quando o slot mudar.
|
|
- Quem é responsável por aplicar `CacheRefreshRequest` ao `SceneViewportCache`:
|
|
- fechado: sempre o `FrameComposer`.
|
|
- Qual é o contrato explícito de “nenhuma scene carregada”:
|
|
- fechado: `sprites + fades`, sem `clear` implícito.
|
|
- Como a cena ativa é selecionada e trocada no ciclo real:
|
|
- fechado: `bind_scene(scene_bank_id)` com resolução através de `SceneBankPoolAccess`.
|
|
- O HUD entra nesta integração já agora, ou o foco da primeira integração é apenas world + sprites + fades?
|
|
- fechado: sem HUD nesta primeira integração.
|
|
|
|
## Reabertura 2026-04-15
|
|
|
|
### Contexto adicional
|
|
|
|
Ao revisitar a thread, apareceu uma restrição indevida: tratar o `FrameComposer` como se aceitasse apenas tilesets `16x16`.
|
|
|
|
Isso conflita com o estado atual do runtime:
|
|
|
|
- `TileSize` no HAL já enumera `Size8`, `Size16` e `Size32`;
|
|
- `SceneLayer` carrega `tile_size` por layer;
|
|
- o decoder de `SCENE` já aceita `8`, `16` e `32`;
|
|
- `SceneViewportResolver` e `SceneViewportCache` já calculam offsets, anchors e cópia a partir do `tile_size` da própria layer.
|
|
|
|
O risco aqui não é apenas de implementação. Se o contrato do `FrameComposer` assumir `16x16` como pré-condição, ele quebra a neutralidade do orquestrador e reabre uma limitação artificial acima do scene model.
|
|
|
|
### Problema reaberto
|
|
|
|
Precisamos fechar explicitamente que o `FrameComposer` aceita cenas/layers com `tile_size` `8x8` e não impõe `16x16` como tamanho mínimo ou obrigatório para o world path.
|
|
|
|
### Opcoes adicionais
|
|
|
|
### Opcao 4 - Fixar `16x16` no `FrameComposer` e tratar `8x8` como fora de escopo
|
|
|
|
**Vantagens:**
|
|
- reduz casos de teste imediatos;
|
|
- simplifica implementação inicial se alguém estiver assumindo viewport/caches calibrados manualmente para `16`.
|
|
|
|
**Desvantagens:**
|
|
- contradiz o scene model já aceito;
|
|
- introduz restrição artificial no orquestrador;
|
|
- obriga futura revisão de contrato para reaceitar algo que a base já suporta.
|
|
|
|
### Opcao 5 - Manter `FrameComposer` tile-size agnostic e aceitar `8x8` desde V1
|
|
|
|
**Vantagens:**
|
|
- preserva o contrato canônico já existente em `SceneLayer`;
|
|
- mantém o `FrameComposer` como orquestrador, não como redefinidor de formato;
|
|
- evita bifurcação entre pipeline de scene e pipeline de composição.
|
|
|
|
**Desvantagens:**
|
|
- exige deixar isso explícito na decisão e nos planos;
|
|
- aumenta a exigência de testes para viewport/cache/cópia com `8x8`.
|
|
|
|
### Recomendacao adicional
|
|
|
|
Seguir com a **Opcao 5**.
|
|
|
|
Norma proposta para fechamento desta reabertura:
|
|
|
|
- `FrameComposer` deve aceitar scenes/layers cujo `tile_size` resolvido seja `8x8`, `16x16` ou `32x32`;
|
|
- `FrameComposer` nao deve impor `16x16` como pré-condição para bind, cache, resolver ou composição;
|
|
- qualquer validação de compatibilidade deve ser derivada do `tile_size` declarado pela própria layer / glyph bank, nunca de um default rígido no compositor;
|
|
- os planos derivados desta thread precisam citar testes explícitos para `8x8`.
|
|
|
|
## Criterio para Encerrar
|
|
|
|
Esta agenda pode ser encerrada quando estiver explícito:
|
|
|
|
- quem é dono do estado mínimo de câmera;
|
|
- quem é dono da scene/cache/resolver ativos;
|
|
- como funciona o bind/unbind da scene ativa;
|
|
- quando o cache é atualizado;
|
|
- como `render_all()` passa a compor o world path aceito;
|
|
- que o `FrameComposer` permanece agnóstico ao `tile_size` canônico da layer e aceita `8x8` sem downgrade contratual;
|
|
- e qual é a superfície mínima de integração para implementação sem reabrir a arquitetura base.
|
|
|
|
## Resolucao
|
|
|
|
Esta agenda fica aceita com a seguinte direcao:
|
|
|
|
- `Gfx.render_all()` deve ser aposentado;
|
|
- o fluxo operacional deve convergir para `FrameComposer.render_frame()`;
|
|
- `FrameComposer` vive em `hardware/drivers`, ao lado de `Gfx`, e passa a ser dono do estado operacional do frame;
|
|
- `FrameComposer` deve manter:
|
|
- scene ativa opcional;
|
|
- camera/viewport;
|
|
- `SceneViewportCache`;
|
|
- `SceneViewportResolver`;
|
|
- `SpriteController`;
|
|
- scene ativa e acessada por `scene_bank_id + Arc<SceneBank>` via `SceneBankPoolAccess`, sem copias;
|
|
- troca de slot exige novo `bind_scene(...)`;
|
|
- sem scene ativa, o frame continua valido com `sprites + fades`, sem `clear` implicito;
|
|
- sprites passam a ser emitidos por frame, sem `Sprite.active`, com capacidade maxima de `512`, overflow ignorado e ordenacao por `layer`, `priority`, e FIFO em empate;
|
|
- HUD fica fora desta primeira integracao.
|