diff --git a/discussion/index.ndjson b/discussion/index.ndjson index e80e3372..8e8748d7 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,4 @@ -{"type":"meta","next_id":{"DSC":26,"AGD":26,"DEC":14,"PLN":17,"LSN":31,"CLSN":1}} +{"type":"meta","next_id":{"DSC":27,"AGD":27,"DEC":15,"PLN":22,"LSN":31,"CLSN":1}} {"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} @@ -18,6 +18,7 @@ {"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0025","status":"done","ticket":"scene-bank-and-viewport-cache-refactor","title":"Scene Bank and Viewport Cache Refactor","created_at":"2026-04-11","updated_at":"2026-04-14","tags":["gfx","tilemap","runtime","render"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md","status":"done","created_at":"2026-04-14","updated_at":"2026-04-14"}]} +{"type":"discussion","id":"DSC-0026","status":"open","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-14","tags":["gfx","runtime","render","camera","scene"],"agendas":[{"id":"AGD-0026","file":"workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14"}],"decisions":[{"id":"DEC-0014","file":"workflow/decisions/DEC-0014-frame-composer-render-integration.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_agenda":"AGD-0026"}],"plans":[{"id":"PLN-0017","file":"workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0018","file":"workflow/plans/PLN-0018-sprite-controller-and-frame-emission-model.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0019","file":"workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0020","file":"workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0021","file":"workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]}],"lessons":[]} {"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} diff --git a/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md b/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md new file mode 100644 index 00000000..1dfa1409 --- /dev/null +++ b/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md @@ -0,0 +1,346 @@ +--- +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. diff --git a/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md b/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md new file mode 100644 index 00000000..ab824e2e --- /dev/null +++ b/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md @@ -0,0 +1,186 @@ +--- +id: DEC-0014 +ticket: render-all-scene-cache-and-camera-integration +title: Frame Composer Render Integration +status: accepted +created: 2026-04-14 +accepted: 2026-04-14 +agenda: AGD-0026 +plans: [PLN-0017, PLN-0018, PLN-0019, PLN-0020, PLN-0021] +tags: [gfx, runtime, render, camera, scene, sprites] +--- + +## Status + +Accepted. + +## Contexto + +`DSC-0025` closed the canonical scene model around `SceneBank`, `SceneViewportCache`, and `SceneViewportResolver`, but the operational frame loop still remained split. `Gfx` still exposed `render_all()`, while the new world path already existed separately as `render_scene_from_cache(...)`. + +This left the runtime with an incomplete composition model: + +- canonical scene/camera/cache architecture had already changed; +- the normal frame entrypoint had not yet been integrated with that architecture; +- sprite ownership was still too coupled to `Gfx` and to a slot-first `active` model. + +This decision closes the ownership and composition model for the next integration phase. + +## Decisao + +The runtime SHALL converge to a `FrameComposer`-owned frame orchestration model. + +Normatively: + +- `Gfx.render_all()` MUST be retired as the canonical frame service. +- The canonical operational frame entrypoint SHALL become `FrameComposer.render_frame()`. +- `FrameComposer` SHALL live in `hardware/drivers`, alongside `Gfx`. +- `Hardware` SHALL aggregate both `FrameComposer` and `Gfx`. +- `FrameComposer` SHALL own the frame-operational state: + - active scene binding; + - camera / viewport state; + - `SceneViewportCache`; + - `SceneViewportResolver`; + - sprite submission state through `SpriteController`. +- `Gfx` SHALL remain a low-level visual backend responsible for composition, blit, and raster execution. +- `Gfx` MUST NOT remain the owner of scene state or sprite submission state. + +## Rationale + +This split preserves a clean ownership model: + +- `FrameComposer` decides what the frame is; +- `Gfx` executes how the frame is drawn. + +Keeping orchestration in `FrameComposer` avoids re-entangling renderer code with camera policy, cache refresh policy, and scene binding. Keeping `FrameComposer` in `hardware/drivers` instead of `hal` preserves room for backend-specific acceleration while avoiding a policy-heavy abstraction in HAL. + +This also preserves future backend freedom: + +- software path today; +- hardware-assisted blit path later; +- or a more PPU-like backend in bare-metal environments. + +## Invariantes / Contrato + +### 1. Frame Entry + +- The canonical public frame orchestration path SHALL be `FrameComposer.render_frame()`. +- `FrameComposer.render_frame()` SHALL be capable of producing a valid frame 100% of the time. +- A valid frame MUST NOT require a scene to be bound. + +### 2. No-Scene Behavior + +- If no scene is bound, `FrameComposer.render_frame()` SHALL compose only: + - emitted sprites; + - fades already owned by the visual backend. +- No implicit clear SHALL be performed. +- Clearing the back buffer SHALL remain the responsibility of the caller / developer. +- In the no-scene state: + - cache MUST be absent or inert; + - resolver MUST be absent or inert; + - the system SHALL expose explicit scene-availability status. + +### 3. Scene Binding + +- Scene binding SHALL be performed by `bind_scene(scene_bank_id)`. +- `FrameComposer` SHALL depend on `SceneBankPoolAccess`. +- `FrameComposer` MUST resolve scenes through the pool, not through copied scene values. +- Scene access MUST be pointer-based / shared-reference based only. +- On bind, `FrameComposer` SHALL store: + - `scene_bank_id`; + - `Arc` for the resolved scene. +- The `SceneViewportCache` SHALL live inside `FrameComposer` while the scene remains bound. +- `unbind_scene()` SHALL: + - remove the active scene; + - discard the associated cache; + - invalidate the world path; + - keep the frame path valid for no-scene composition. +- Replacing the contents of a bound scene slot SHALL require a new explicit bind. +- `FrameComposer` MUST NOT poll the scene bank pool each frame to revalidate the binding. +- A new `bind_scene(...)` SHALL replace the previous bound scene completely. + +### 4. Camera + +- The V1 camera contract SHALL be minimal. +- `set_camera(x, y)` SHALL accept `i32` pixel coordinates. +- `x` and `y` SHALL represent the top-left of the viewport in world space. +- Camera follow, smoothing, shake, cinematic transitions, and similar behaviors are OUT OF SCOPE for this decision. + +### 5. Cache and Resolver + +- `FrameComposer` SHALL own both `SceneViewportCache` and `SceneViewportResolver`. +- `FrameComposer` SHALL apply `CacheRefreshRequest`s to the cache. +- `Gfx` MUST NOT own cache refresh policy. +- `Gfx` MUST only consume already prepared render state. + +### 6. Sprite Model + +- `Sprite.active` MUST be removed from the canonical operational model. +- Sprite submission SHALL become frame-emission based. +- `SpriteController` SHALL be the sprite submission subsystem owned by `FrameComposer`. +- The sprite frame capacity SHALL remain capped at `512` for V1. +- The sprite counter SHALL be reset at the start of each frame. +- The caller MUST NOT provide sprite indices directly. +- Each `emit_sprite(...)` call SHALL occupy the next available internal slot. +- Overflow beyond capacity SHALL be ignored. +- Overflow SHOULD leave room for system logging / telemetry. +- Future certification MAY penalize sprite overflow, but that is not part of this decision. +- `emit_sprite(...)` SHALL NOT require a dedicated reset API beyond the normal frame lifecycle. + +### 7. Sprite Ordering + +- Each sprite SHALL carry: + - `layer`; + - `priority`. +- `layer` SHALL remain numeric for now. +- The sprite `layer` type SHALL match the scene layer reference type used by the scene model. +- Composition SHALL be layer-based. +- Within a layer: + - lower `priority` SHALL render first; + - ties SHALL resolve FIFO by emission order. + +### 8. Composition Scope + +- HUD integration is OUT OF SCOPE for the first integration phase covered by this decision. +- The first integration phase SHALL focus on: + - world scene path; + - sprites; + - fades. + +## Impactos + +### HAL + +- `GfxBridge` and adjacent visual contracts will need to stop treating `render_all()` as the canonical operational frame path. + +### Drivers / Hardware + +- `Hardware` will need to aggregate `FrameComposer` next to `Gfx`. +- `Gfx` will need to lose ownership of scene/sprite operational state. +- Sprite submission state will need to move into `SpriteController`. + +### Runtime / VM + +- The VM runtime will eventually trigger frame composition through the new `FrameComposer` path rather than depending on `Gfx.render_all()`. +- The VM/runtime side should not own the detailed cache or scene orchestration policy directly once `FrameComposer` exists in hardware/drivers. + +### Asset / Scene Flow + +- Scene activation will become explicit through bank-id binding. +- Scene slot replacement will require explicit rebinding behavior from callers. + +## Referencias + +- [AGD-0026-render-all-scene-cache-and-camera-integration.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md) +- [LSN-0030-canonical-scene-cache-and-resolver-split.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md) + +## Propagacao Necessaria + +- A new implementation plan MUST be created from this decision before code changes. +- `FrameComposer` and `SpriteController` need explicit planning and migration sequencing. +- `Gfx.render_all()` retirement MUST be planned rather than removed ad hoc. +- The frame service rename and integration path MUST be propagated through the frame loop callsites. + +## Revision Log + +- 2026-04-14: Initial accepted decision from `AGD-0026`. diff --git a/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md b/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md new file mode 100644 index 00000000..80897371 --- /dev/null +++ b/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md @@ -0,0 +1,117 @@ +--- +id: PLN-0017 +ticket: render-all-scene-cache-and-camera-integration +title: Plan - FrameComposer Core and Hardware Ownership +status: accepted +created: 2026-04-14 +completed: +tags: [gfx, runtime, render, hardware, frame-composer] +--- + +## Objective + +Introduce `FrameComposer` as a first-class hardware-side subsystem and move canonical frame orchestration ownership out of `Gfx`. + +## Background + +`DEC-0014` locks `FrameComposer` as the canonical frame orchestration service. The first implementation step is to create the owning type, place it in `hardware/drivers`, and make `Hardware` aggregate it next to `Gfx` without yet completing the full render-path migration. + +## Scope + +### Included +- Create the `FrameComposer` type in `crates/console/prometeu-drivers`. +- Define the minimal owned state shape: + - active scene binding state; + - camera / viewport state; + - optional cache; + - optional resolver; + - owned `SpriteController`. +- Aggregate `FrameComposer` inside `Hardware`. +- Expose the minimum driver-facing surface required for subsequent plans. + +### Excluded +- full sprite-model migration +- full scene binding implementation +- cache refresh application +- render-path retirement of `Gfx.render_all()` + +## Execution Steps + +### Step 1 - Introduce the `FrameComposer` module and owned state + +**What:** +Create `FrameComposer` as a concrete driver-side subsystem. + +**How:** +- Add a new module such as `crates/console/prometeu-drivers/src/frame_composer.rs`. +- Define a `FrameComposer` struct with explicit placeholders for: + - `active_scene_id` + - `active_scene` + - `scene_status` + - `camera_x_px` + - `camera_y_px` + - `SceneViewportCache` + - `SceneViewportResolver` + - `SpriteController` +- Keep scene/cache/resolver fields optional where no-scene operation is required. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- `crates/console/prometeu-drivers/src/lib.rs` + +### Step 2 - Aggregate `FrameComposer` in `Hardware` + +**What:** +Make `Hardware` own `FrameComposer` alongside `Gfx`. + +**How:** +- Extend `Hardware` with a `frame_composer` field. +- Wire construction so `FrameComposer` receives the shared bank access it needs for later plans. +- Keep ownership boundaries explicit: `FrameComposer` prepares frame state, `Gfx` remains backend. + +**File(s):** +- `crates/console/prometeu-drivers/src/hardware.rs` +- `crates/console/prometeu-drivers/src/memory_banks.rs` + +### Step 3 - Define the minimum public driver-facing surface + +**What:** +Give the driver layer a stable initial surface for `FrameComposer`. + +**How:** +- Expose minimal constructor and accessor paths. +- Do not yet overdesign HAL-facing traits. +- Ensure the code compiles with no implicit dependence on `Gfx.render_all()` ownership for frame state. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- `crates/console/prometeu-drivers/src/hardware.rs` +- `crates/console/prometeu-drivers/src/lib.rs` + +## Test Requirements + +### Unit Tests +- `FrameComposer` can be constructed without a bound scene. +- `Hardware` successfully constructs with both `gfx` and `frame_composer`. + +### Integration Tests +- Shared bank access needed by `FrameComposer` is available through hardware construction. + +### Manual Verification +- Inspect the resulting type ownership and confirm scene/sprite state is no longer being newly introduced into `Gfx`. + +## Acceptance Criteria + +- [ ] `FrameComposer` exists as a dedicated driver-side subsystem. +- [ ] `Hardware` aggregates `FrameComposer` next to `Gfx`. +- [ ] `FrameComposer` has explicit owned placeholders for scene/camera/cache/resolver/sprites. +- [ ] The build remains green with the new ownership structure in place. + +## Dependencies + +- Source decision: `DEC-0014` + +## Risks + +- Introducing `FrameComposer` with too much behavior too early can blur later migration steps. +- Introducing too little owned state can leave ownership ambiguous and force rework in later plans. diff --git a/discussion/workflow/plans/PLN-0018-sprite-controller-and-frame-emission-model.md b/discussion/workflow/plans/PLN-0018-sprite-controller-and-frame-emission-model.md new file mode 100644 index 00000000..c101524e --- /dev/null +++ b/discussion/workflow/plans/PLN-0018-sprite-controller-and-frame-emission-model.md @@ -0,0 +1,127 @@ +--- +id: PLN-0018 +ticket: render-all-scene-cache-and-camera-integration +title: Plan - SpriteController and Frame Emission Model +status: accepted +created: 2026-04-14 +completed: +tags: [gfx, runtime, render, sprites, frame-composer] +--- + +## Objective + +Replace the slot-first sprite model with a `FrameComposer`-owned `SpriteController` that emits sprites per frame instead of relying on `Sprite.active` and caller-provided indices. + +## Background + +`DEC-0014` removes `Sprite.active` from the canonical operational model and locks sprite submission to a frame-emission model owned by `SpriteController`. + +## Scope + +### Included +- Introduce `SpriteController`. +- Remove the operational dependence on `Sprite.active`. +- Remove caller-owned sprite indices from the canonical submission path. +- Add layer + priority ordering with FIFO tie-breaking. +- Preserve the capacity cap of `512` sprites per frame. + +### Excluded +- HUD integration +- scene binding +- cache refresh logic + +## Execution Steps + +### Step 1 - Redefine the sprite operational model + +**What:** +Move canonical sprite submission semantics from slot-first to frame-emission. + +**How:** +- Update `Sprite` and adjacent APIs so the canonical path no longer depends on `active`. +- Keep layer numeric and aligned with the scene layer reference type. +- Preserve `priority` as the within-layer ordering field. + +**File(s):** +- `crates/console/prometeu-hal/src/sprite.rs` +- any adjacent driver-side sprite helpers + +### Step 2 - Implement `SpriteController` + +**What:** +Create the owned sprite subsystem under `FrameComposer`. + +**How:** +- Add a `SpriteController` type with: + - storage capacity `512` + - frame counter + - per-layer buckets + - stable FIFO semantics for equal priority +- Add `begin_frame()` behavior that clears counters and buckets. +- Add `emit_sprite(...)` behavior that appends to the next internal slot. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- optional dedicated `sprite_controller.rs` + +### Step 3 - Handle overflow and logging semantics + +**What:** +Implement overflow behavior without turning it into a hard runtime failure. + +**How:** +- Ignore sprites emitted after capacity is reached. +- Leave explicit room for system logging / telemetry. +- Do not add a special reset API beyond the normal frame lifecycle. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- related telemetry/log hooks if needed + +### Step 4 - Remove stale slot-first sprite entrypoints + +**What:** +Retire the old “set sprite by explicit index” path from the canonical model. + +**How:** +- Identify the current caller-facing `Gfx` sprite mutation surface. +- Migrate it toward `FrameComposer`-owned submission. +- Keep transitional shims only if required to preserve buildability for the next plan. + +**File(s):** +- `crates/console/prometeu-drivers/src/gfx.rs` +- `crates/console/prometeu-hal/src/gfx_bridge.rs` +- `crates/console/prometeu-drivers/src/frame_composer.rs` + +## Test Requirements + +### Unit Tests +- `begin_frame()` resets sprite count and buckets. +- `emit_sprite(...)` appends without caller-provided index. +- lower `priority` renders first within a layer. +- equal `priority` resolves FIFO by registration order. +- overflow drops excess sprites without panicking. + +### Integration Tests +- `FrameComposer` can emit sprites and provide ordered sprite state for rendering. + +### Manual Verification +- Confirm no canonical submission path requires `Sprite.active` or explicit slot index anymore. + +## Acceptance Criteria + +- [ ] `SpriteController` exists under `FrameComposer`. +- [ ] `Sprite.active` is no longer required by the canonical frame path. +- [ ] Caller-provided sprite indices are retired from the canonical submission path. +- [ ] Layer/priority/FIFO ordering is implemented and tested. +- [ ] Overflow is ignored with space left for logging. + +## Dependencies + +- Depends on `PLN-0017` +- Source decision: `DEC-0014` + +## Risks + +- Keeping compatibility shims too long can leave the codebase in a dual sprite model. +- Removing index-based APIs too early may break callsites before `FrameComposer` integration is ready. diff --git a/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md b/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md new file mode 100644 index 00000000..16991d27 --- /dev/null +++ b/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md @@ -0,0 +1,127 @@ +--- +id: PLN-0019 +ticket: render-all-scene-cache-and-camera-integration +title: Plan - Scene Binding, Camera, and Scene Status +status: accepted +created: 2026-04-14 +completed: +tags: [gfx, runtime, render, scene, camera, frame-composer] +--- + +## Objective + +Implement the `FrameComposer` scene-binding contract, minimal camera state, and explicit scene-availability status without yet completing the cache-refresh render path. + +## Background + +`DEC-0014` locks scene activation around `bind_scene(scene_bank_id)` with `SceneBankPoolAccess`, pointer-based access only, and `scene_bank_id + Arc` retained inside `FrameComposer`. + +## Scope + +### Included +- scene bind/unbind contract +- active scene identity and shared reference storage +- scene availability status +- minimal camera state (`i32`, top-left viewport) + +### Excluded +- applying cache refreshes +- full render-path migration +- HUD behavior + +## Execution Steps + +### Step 1 - Add scene binding state to `FrameComposer` + +**What:** +Implement the canonical bind/unbind surface. + +**How:** +- Add `bind_scene(scene_bank_id)` and `unbind_scene()`. +- Resolve scenes from `SceneBankPoolAccess`. +- Store both: + - `scene_bank_id` + - `Arc` +- Replace prior scene binding completely on a new bind. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- `crates/console/prometeu-drivers/src/memory_banks.rs` + +### Step 2 - Add explicit scene status + +**What:** +Expose scene availability through status, not just implicit option checks. + +**How:** +- Define a scene status enum or equivalent status object. +- Distinguish at least: + - no scene bound + - bound and available + - bound but not renderable if such intermediate state is needed +- Ensure no-scene rendering remains valid. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- optional HAL-facing status surface if needed later + +### Step 3 - Add camera contract + +**What:** +Implement the V1 camera ownership inside `FrameComposer`. + +**How:** +- Add `set_camera(x, y)`. +- Store camera coordinates as `i32`. +- Treat them as top-left viewport coordinates in world space. +- Keep all advanced camera behavior out of scope. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` + +### Step 4 - Tie cache/resolver lifetime to scene binding + +**What:** +Align cache/resolver lifetime with the active scene contract. + +**How:** +- Cache and resolver remain `None` / absent when no scene is bound. +- On bind: + - create or reinitialize cache/resolver. +- On unbind: + - discard cache/resolver and invalidate the world path. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` + +## Test Requirements + +### Unit Tests +- bind stores `scene_bank_id + Arc`. +- unbind clears active scene and cache. +- scene status reflects no-scene and active-scene states. +- camera coordinates are stored as top-left pixel-space values. + +### Integration Tests +- `FrameComposer` can resolve a scene from the pool and survive no-scene operation. + +### Manual Verification +- Confirm scene access remains pointer-based and no scene copies are introduced. + +## Acceptance Criteria + +- [ ] `FrameComposer` binds scenes by bank id through `SceneBankPoolAccess`. +- [ ] Active binding stores both scene id and shared scene reference. +- [ ] Scene status is explicit. +- [ ] Camera contract is implemented as `i32` top-left viewport coordinates. +- [ ] Cache/resolver lifetime follows scene bind/unbind. + +## Dependencies + +- Depends on `PLN-0017` +- Source decision: `DEC-0014` + +## Risks + +- Weak scene-status semantics can make no-scene behavior ambiguous in later render integration. +- If cache/resolver lifetime is not tied cleanly to binding, stale world state can leak across scene transitions. diff --git a/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md b/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md new file mode 100644 index 00000000..240118f4 --- /dev/null +++ b/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md @@ -0,0 +1,124 @@ +--- +id: PLN-0020 +ticket: render-all-scene-cache-and-camera-integration +title: Plan - Cache Refresh and render_frame Path +status: accepted +created: 2026-04-14 +completed: +tags: [gfx, runtime, render, cache, resolver, frame-composer] +--- + +## Objective + +Connect `FrameComposer` to `SceneViewportResolver`, apply cache refreshes inside `FrameComposer`, and establish `render_frame()` as the canonical composition path for world + sprites + fades. + +## Background + +`DEC-0014` requires that cache refresh policy remain inside `FrameComposer` and that `FrameComposer.render_frame()` become the canonical frame entry while `Gfx` remains only the low-level execution backend. + +## Scope + +### Included +- apply `CacheRefreshRequest`s in `FrameComposer` +- connect camera/scene state to resolver updates +- use cache-backed world rendering in the frame path +- keep valid no-scene rendering (`sprites + fades`) + +### Excluded +- HUD integration +- final retirement cleanup of legacy callsites + +## Execution Steps + +### Step 1 - Apply resolver refreshes inside `FrameComposer` + +**What:** +Move cache-refresh orchestration fully into `FrameComposer`. + +**How:** +- On active-scene frames: + - call resolver update with current camera and scene + - consume returned `CacheRefreshRequest`s + - apply them to `SceneViewportCache` +- Keep `Gfx` unaware of refresh semantics. + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` + +### Step 2 - Define `render_frame()` as the canonical frame path + +**What:** +Introduce the new frame service on `FrameComposer`. + +**How:** +- Add `render_frame()` to `FrameComposer`. +- If a scene is active and renderable: + - prepare resolver update + - refresh cache + - call the cache-backed world path in `Gfx` +- If no scene is active: + - call the no-scene path for `sprites + fades` + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- `crates/console/prometeu-drivers/src/gfx.rs` + +### Step 3 - Keep `Gfx` as backend only + +**What:** +Narrow `Gfx` to backend-oriented composition responsibilities. + +**How:** +- Ensure `Gfx` consumes prepared state from `FrameComposer`. +- Do not let `Gfx` regain ownership of cache refresh or scene orchestration. +- Keep low-level helpers for cache-backed copy paths, sprite drawing, and fades in `Gfx`. + +**File(s):** +- `crates/console/prometeu-drivers/src/gfx.rs` + +### Step 4 - Cover scene and no-scene frame paths + +**What:** +Protect the two canonical frame modes. + +**How:** +- Add tests for: + - active-scene world composition + - no-scene `sprites + fades` + - scene transition through unbind/rebind + - cache refresh behavior staying inside `FrameComposer` + +**File(s):** +- `crates/console/prometeu-drivers/src/frame_composer.rs` +- `crates/console/prometeu-drivers/src/gfx.rs` + +## Test Requirements + +### Unit Tests +- `render_frame()` with no scene produces valid no-scene composition. +- `render_frame()` with a scene applies resolver refreshes before composition. +- cache refresh requests are applied by `FrameComposer`, not `Gfx`. + +### Integration Tests +- scene bind + camera set + sprite emission + `render_frame()` produces the expected composed frame. + +### Manual Verification +- Verify that no-scene frames still render sprites/fades without crashes or hidden clears. + +## Acceptance Criteria + +- [ ] `FrameComposer.render_frame()` exists and is the canonical frame path. +- [ ] Cache refreshes are applied inside `FrameComposer`. +- [ ] World rendering consumes the cache-backed path. +- [ ] No-scene `sprites + fades` behavior remains valid. +- [ ] `Gfx` remains backend-only for this path. + +## Dependencies + +- Depends on `PLN-0017`, `PLN-0018`, and `PLN-0019` +- Source decision: `DEC-0014` + +## Risks + +- If refresh application leaks into `Gfx`, the ownership split from `DEC-0014` collapses. +- If no-scene behavior is not tested explicitly, scene integration can accidentally make scene binding mandatory. diff --git a/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md b/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md new file mode 100644 index 00000000..e95ecbae --- /dev/null +++ b/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md @@ -0,0 +1,118 @@ +--- +id: PLN-0021 +ticket: render-all-scene-cache-and-camera-integration +title: Plan - Service Retirement, Callsite Migration, and Regression Coverage +status: accepted +created: 2026-04-14 +completed: +tags: [gfx, runtime, render, migration, regression] +--- + +## Objective + +Retire `Gfx.render_all()` from the canonical flow, migrate callsites to `FrameComposer.render_frame()`, and add the regression coverage needed to lock the new service model. + +## Background + +`DEC-0014` is explicit that `Gfx.render_all()` must be retired and that `FrameComposer.render_frame()` becomes the canonical frame orchestration entrypoint. This final plan removes the old canonical service shape and validates the migration end-to-end. + +## Scope + +### Included +- retire `Gfx.render_all()` from the canonical path +- migrate frame-loop callsites +- align bridge surfaces as needed +- add regression coverage for the final service model + +### Excluded +- HUD integration +- future certification behavior for sprite overflow + +## Execution Steps + +### Step 1 - Migrate frame-loop callsites + +**What:** +Switch runtime frame execution from `Gfx.render_all()` to `FrameComposer.render_frame()`. + +**How:** +- Identify all canonical callsites that currently trigger `Gfx.render_all()`. +- Update them to go through `FrameComposer`. +- Preserve present/swap behavior after the render call. + +**File(s):** +- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` +- any additional runtime frame-loop callsites + +### Step 2 - Retire `Gfx.render_all()` from the canonical service surface + +**What:** +Remove the old frame service as the operational entry. + +**How:** +- Remove or deprecate `render_all()` from `Gfx` and `GfxBridge` as the canonical render entry. +- Keep only backend-oriented helpers that `FrameComposer` calls. +- Ensure the naming and public path converge to Rust-style `render_frame()`. + +**File(s):** +- `crates/console/prometeu-hal/src/gfx_bridge.rs` +- `crates/console/prometeu-drivers/src/gfx.rs` + +### Step 3 - Add end-to-end regression coverage + +**What:** +Protect the new service model against fallback to the old renderer path. + +**How:** +- Add tests that prove: + - frame-loop code calls `FrameComposer.render_frame()` + - no-scene frames remain valid + - active-scene frames render through cache-backed composition + - sprite emission and ordering survive the full path +- Add assertions or test failures for accidental continued reliance on `Gfx.render_all()`. + +**File(s):** +- runtime tests +- driver tests +- bridge tests where needed + +### Step 4 - Validate full repository behavior + +**What:** +Confirm the migration did not break unrelated systems. + +**How:** +- Run the repository validation command required by current practice. +- Keep regression evidence attached to the plan execution. + +**File(s):** +- repository-wide CI / validation entrypoints + +## Test Requirements + +### Unit Tests +- `Gfx` no longer exposes `render_all()` as the canonical operational frame path. + +### Integration Tests +- runtime tick path renders through `FrameComposer.render_frame()`. +- no-scene and active-scene frame modes both remain valid. + +### Manual Verification +- Run the repository CI path and confirm the final integrated service model is green. + +## Acceptance Criteria + +- [ ] Frame-loop callsites use `FrameComposer.render_frame()`. +- [ ] `Gfx.render_all()` is retired from the canonical service path. +- [ ] Regression coverage protects against fallback to the old model. +- [ ] Repository validation passes after the migration. + +## Dependencies + +- Depends on `PLN-0017`, `PLN-0018`, `PLN-0019`, and `PLN-0020` +- Source decision: `DEC-0014` + +## Risks + +- Removing `render_all()` too early can strand intermediate callsites. +- Leaving it in place as a canonical path for too long can create a dual-service model that is harder to remove later.