--- 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-14 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 - 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` 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`; - 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. ## 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; - 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` 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.