From 2b69be0f11006fe48e0e33b8c3c1532642ce8ff5 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 11:33:34 +0100 Subject: [PATCH 01/16] Logical Render Pipelines and Command Packets --- discussion/index.ndjson | 3 +- ...packet-boundary-for-classic-2d-renderer.md | 446 ++++++++++++++++++ ...ogical-render-pipeline-command-boundary.md | 236 +++++++++ .../plans/PLN-0073-render-contract-specs.md | 67 +++ .../PLN-0074-hal-render-submission-types.md | 66 +++ .../plans/PLN-0075-rendermanager-core.md | 68 +++ ...-0076-capabilities-and-abi-domain-split.md | 67 +++ ...-0077-composer-buffer-and-game2d-packet.md | 66 +++ ...N-0078-classic2d-game-renderer-consumer.md | 64 +++ .../plans/PLN-0079-gfx2d-primitive-domain.md | 66 +++ ...N-0080-shell-ui-packet-and-gfxui-domain.md | 66 +++ .../plans/PLN-0081-shell-hub-migration.md | 67 +++ ...-frame-publication-and-present-boundary.md | 69 +++ .../workflow/plans/PLN-0083-fade-removal.md | 63 +++ .../PLN-0084-host-debug-overlay-removal.md | 65 +++ ...085-pbs-stdlib-and-syscall-declarations.md | 67 +++ ...6-end-to-end-render-boundary-validation.md | 71 +++ 17 files changed, 1616 insertions(+), 1 deletion(-) create mode 100644 discussion/workflow/agendas/AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md create mode 100644 discussion/workflow/decisions/DEC-0030-logical-render-pipeline-command-boundary.md create mode 100644 discussion/workflow/plans/PLN-0073-render-contract-specs.md create mode 100644 discussion/workflow/plans/PLN-0074-hal-render-submission-types.md create mode 100644 discussion/workflow/plans/PLN-0075-rendermanager-core.md create mode 100644 discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md create mode 100644 discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md create mode 100644 discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md create mode 100644 discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md create mode 100644 discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md create mode 100644 discussion/workflow/plans/PLN-0081-shell-hub-migration.md create mode 100644 discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md create mode 100644 discussion/workflow/plans/PLN-0083-fade-removal.md create mode 100644 discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md create mode 100644 discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md create mode 100644 discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 41e283dd..7819370f 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,5 @@ -{"type":"meta","next_id":{"DSC":38,"AGD":38,"DEC":30,"PLN":73,"LSN":47,"CLSN":1}} +{"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/agendas/AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md b/discussion/workflow/agendas/AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md new file mode 100644 index 00000000..69feada6 --- /dev/null +++ b/discussion/workflow/agendas/AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md @@ -0,0 +1,446 @@ +--- +id: AGD-0038 +ticket: render-frame-packet-boundary +title: Logical Render Pipelines and Command Packets +status: accepted +created: 2026-05-25 +resolved: +decision: +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +# Agenda - Logical Render Pipelines and Command Packets + +## Contexto + +Depois da migracao para RGBA8888, o proximo passo arquitetural do renderer nao deve ser GPU, 3D, multiplos backends ou troca dinamica complexa de pipeline. O objetivo imediato e separar melhor tres responsabilidades: + +1. pipelines logicos produzem comandos de alto nivel; +2. uma implementacao de hardware/render surface consome esses comandos; +3. o framebuffer RGBA8888 atual continua sendo a superficie final no desktop. + +A direcao desejada agora e explicitar pelo menos dois pipelines logicos: + +```text +Game 2D Pipeline + scene + sprites + HUD + -> Game2DFramePacket / Game2DCommandSet + -> hardware/render implementation + -> framebuffer RGBA8888 + +Prometeu UI Pipeline + shell/home/system UI/windows + -> UiFramePacket / UiCommandSet + -> hardware/render implementation + -> framebuffer RGBA8888 +``` + +Esses pipelines podem funcionar de formas totalmente diferentes. O ponto comum nao deve ser uma API unica pobre que force tudo a parecer igual. O ponto comum deve ser a fronteira operacional: cada pipeline publica seu proprio conjunto logico de comandos para uma implementacao de hardware/superficie. + +Com isso, se depois surgir um pipeline 3D, outro pipeline 2D, ou um renderer especializado para hardware barato/DIY, o trabalho fica concentrado no novo pipeline e/ou no consumidor de comandos, sem reabrir a VM inteira. + +## Problema + +Hoje o runtime ainda mistura producao logica de frame com desenho imediato no `Gfx`/framebuffer. + +O codigo ja tem separacoes parciais: + +- `FrameComposer` coordena scene binding, camera, cache/resolver e sprites do jogo. +- `Gfx` rasteriza em `back`, faz `present()` para `front`, e expoe `front_buffer()` para o host. +- `VirtualMachineRuntime` chama `hw.render_frame()` quando o frame logico termina. +- Prometeu Hub, splash/crash e varias chamadas `gfx.*` ainda desenham diretamente via `gfx_mut()`. +- O host desktop copia o `front_buffer()` RGBA8888 para `pixels`. + +O limite atual ainda e "codigo logico chama renderer", nao "pipeline publica comandos para uma implementacao". + +Isso cria custo futuro: + +- o pipeline 2D de jogo fica preso ao formato operacional atual de `Gfx`; +- a UI do Prometeu fica misturada ao mesmo caminho imperativo de primitives; +- um futuro pipeline 3D teria que disputar o mesmo contrato em vez de definir sua propria saida logica; +- hardware barato/DIY teria que emular detalhes de `Gfx` em vez de consumir um command set adequado; +- testes ficam presos a pixels finais mesmo quando o que interessa e o pacote logico produzido. + +## Pontos Criticos + +### 1. Pipeline nao e backend + +Pipeline deve significar "modelo logico de producao de comandos". Backend/implementacao deve significar "como esses comandos viram pixels/superficie". + +Nao queremos agora: + +- registry sofisticado de backends; +- troca dinamica de pipeline; +- GPU backend; +- 3D; +- refatoracao agressiva de asset pipeline; +- mudanca visual. + +Queremos uma fronteira para que esses itens possam existir depois sem contaminar o passo atual. + +### 2. Game 2D e Prometeu UI nao precisam compartilhar o mesmo command set + +O pipeline 2D de jogos naturalmente fala de scene/cache/camera/sprites/HUD. + +O pipeline de UI do Prometeu naturalmente fala de janelas, paineis, texto, retangulos, estados de foco, home/shell/system UI, talvez layout e clipping. + +Forcar ambos a um `RenderCommand` generico demais no v1 pode criar uma abstracao fraca. A agenda deve decidir se o contrato comum sera: + +- um enum top-level de pacotes por pipeline; ou +- uma trait de submissao ao hardware; ou +- um `FrameSubmission` que contem command sets tipados por dominio. + +### 3. `FrameComposer` continua sendo bom candidato para o Game 2D Pipeline + +`FrameComposer` ja e o ponto natural para produzir o pacote de jogo: + +- scene binding; +- camera; +- cache/resolver; +- sprites por frame; +- HUD/primitives de jogo quando aplicavel. + +O menor corte para jogo ainda parece ser transformar `FrameComposer.render_frame(&mut dyn GfxBridge)` em uma producao de `Game2DFramePacket`, consumida em seguida pelo renderer Classic2D atual. + +### 4. Prometeu UI precisa sair do desenho direto, mas talvez nao no mesmo PR + +Prometeu Hub e system UI hoje usam `hw.gfx_mut()` diretamente. Isso e exatamente o tipo de acoplamento que a arquitetura quer remover, mas migrar UI junto com Game 2D pode deixar a primeira mudanca grande demais. + +A decisao precisa escolher entre: + +- definir os dois contratos agora e implementar em fases; +- implementar primeiro Game 2D e registrar UI como segundo plan; +- ou implementar um envelope comum e dois producers minimos no mesmo plano. + +### 5. HUD pertence ao pipeline 2D de jogo, nao ao Prometeu UI por padrao + +Para esta agenda, "HUD" significa HUD do jogo como parte do frame de jogo: overlays do cart/gameplay, texto/debug de jogo e primitives 2D associadas ao jogo. + +Prometeu UI significa UI do sistema: hub, shell, janelas, home, crash/splash se forem tratados como telas do sistema. + +Essa separacao evita confundir UI de jogo com UI do console. + +### 6. A implementacao inicial ainda pode ser uma so + +Mesmo com dois pipelines logicos, a implementacao inicial pode continuar sendo o `Gfx`/Classic2D atual gravando em RGBA8888. O ganho arquitetural nao exige multiplos backends. Exige que a entrada da implementacao deixe de ser acesso direto ao framebuffer e passe a ser submissao de comandos logicos. + +### 7. A fronteira deve preparar VM e render pipeline paralelos + +Render thread nao e objetivo do v1, mas a fronteira nao deve impedir que a VM e o renderer rodem em paralelo depois. + +Isso implica que a submissao de frame deve ser tratada como um snapshot fechado do trabalho de render daquele frame. Depois de publicada, a VM nao deve continuar mutando estruturas que o renderer precisa ler. A implementacao inicial pode consumir o snapshot imediatamente e no mesmo thread, mas o contrato deve evitar dependencias como `&mut Hardware`, `&mut Gfx`, ou acesso mutavel ao estado vivo da VM durante a rasterizacao. + +Para o v1, isso sugere: + +- `RenderSubmission` deve carregar `frame_id`, `app_mode` e dados suficientes para identificar a ordem de publicacao; +- command sets pequenos devem preferir dados owned ou buffers owned pelo produtor ate a publicacao; +- referencias pesadas, como cache/assets, devem ser read-only e ter ownership compartilhado ou lifetime claramente limitado; +- a camada de hardware/render surface deve ser a unica dona de publicacao/present; +- a VM deve produzir/submeter comandos, nao desenhar nem esperar acesso ao framebuffer; +- testes devem conseguir validar o packet/submission sem depender apenas do framebuffer final. + +### 8. Deve existir um `RenderManager` acima dos pipelines + +Mesmo evitando registry sofisticado de backends no v1, a arquitetura precisa de uma camada acima dos pipelines individuais. Essa camada nao deve ser "mais um pipeline"; ela deve coordenar: + +- qual `AppMode` esta ativo (`Game` ou `Shell`); +- quais command buffers/submissions estao validos para esse modo; +- transicoes entre modos; +- publicacao da superficie; +- politica de latest frame; +- capacidades disponiveis por modo. + +Motivacao principal: transicoes entre Shell e Game nao pertencem completamente a nenhum dos dois pipelines. Exemplo: o Shell UI pode executar uma transicao visual de saida ao abrir um jogo, mas depois que o Game 2D assume, o Shell UI ja nao existe mais para executar a transicao de entrada do jogo. Isso sugere uma base comum de render/control plane acima dos pipelines, capaz de manter transicoes e estado de superficie durante a troca de modo. + +Essa camada deve se chamar `RenderManager`. O nome indica coordenacao de render, nao implementacao de desenho. A decisao precisa separar com cuidado: + +- `RenderManager` como abstracao/runtime contract de coordenacao; +- host/render-surface implementation como adaptador local que sabe publicar em uma superficie real. + +O `RenderManager` coordena pipelines e superficie, mas nao produz comandos de jogo/UI e nao deve conter detalhes de host desktop. + +### 9. Command buffers e submissions sao conceitos diferentes + +Cada dominio/pipeline acumula estado e comandos em seu proprio buffer mutavel durante o frame logico. Exemplos: + +- `ComposerBuffer` para scene, camera e sprites; +- `Gfx2dBuffer` para primitivas 2D de jogo; +- `GfxUiBuffer` para primitivas de Shell UI; +- `ComposerBuffer` tambem cobre HUD do Game 2D no v1. + +No fechamento do frame, o `RenderManager` transforma esses buffers na `RenderSubmission` valida para o `AppMode` atual. + +Depois do fechamento, a submission e um snapshot fechado. Produtores logicos nao podem continuar mutando a submission fechada. Essa regra deve ser cravada agora porque e a base para VM e render pipeline rodarem em paralelo no futuro. + +Politica de backlog: nao existe fila infinita. A regra canonica e `latest complete submission wins`. + +## Opcoes + +### Opcao A - Dois command sets tipados, uma implementacao Classic2D inicial + +**Abordagem:** + +- Criar `Game2DFramePacket` para scene/sprites/HUD. +- Criar `UiFramePacket` para Prometeu UI/system UI. +- Criar um envelope simples, por exemplo `RenderSubmission`, que identifica qual pacote esta sendo submetido. +- Fazer a implementacao Classic2D atual consumir os pacotes e continuar escrevendo no framebuffer RGBA8888. +- Implementar em fases: primeiro Game 2D, depois Prometeu UI, ou ambos com producers minimos se o plano ficar pequeno o suficiente. + +**Pro:** + +- preserva modelos logicos diferentes para jogo e UI; +- evita uma command list generica demais; +- prepara 3D ou outros pipelines sem forcar eles a parecerem com 2D; +- deixa claro que pipeline e backend sao coisas diferentes. + +**Con:** + +- cria mais tipos no inicio; +- precisa definir como o hardware recebe pacotes heterogeneos; +- se so Game 2D for migrado no primeiro plan, UI continua acoplada por um tempo. + +**Maintainability:** + +Alta. Esta opcao combina com a direcao de longo prazo sem exigir varios backends agora. + +### Opcao B - Um `RenderCommand` universal para tudo + +**Abordagem:** + +- Criar uma display list unica com comandos como `Clear`, `DrawSprite`, `DrawText`, `FillRect`, `SceneCache`, `Window`, `Transition`. +- Fazer Game 2D e Prometeu UI emitirem a mesma lista. + +**Pro:** + +- facil de passar por uma unica fila; +- simples para logging/replay inicial; +- reduz o numero de tipos. + +**Con:** + +- tende a virar um denominador comum fraco; +- mistura semantica de jogo, UI de sistema e futuros pipelines; +- pode forcar o pipeline 3D futuro a um contrato 2D inadequado; +- aumenta chance de reabrir a arquitetura quando outro pipeline surgir. + +**Maintainability:** + +Media a baixa como arquitetura principal. Pode ser util internamente dentro de um pipeline, mas nao como contrato unico do runtime. + +### Opcao C - Trait comum de pipeline, sem pacote explicito + +**Abordagem:** + +- Definir algo como `RenderPipeline` com metodo `render(&mut HardwareSurface)`. +- Game 2D e UI implementam a trait, mas continuam desenhando por callbacks imediatos. + +**Pro:** + +- pequena mudanca inicial; +- organiza nomes e ownership; +- reduz um pouco o acoplamento por modulo. + +**Con:** + +- nao cria pacote logico observavel; +- continua misturando producao e execucao; +- nao ajuda tanto render thread, replay, testes de comandos ou hardware alternativo. + +**Maintainability:** + +Insuficiente se virar a decisao final. Pode ser uma API auxiliar, mas nao substitui command packets. + +### Opcao D - Definir todos os pipelines e backend abstraction agora + +**Abordagem:** + +- Criar pipeline registry, backend interfaces, CPU/GPU abstractions, surfaces, scheduling, command queues e talvez render thread. + +**Pro:** + +- parece completo no papel; +- antecipa muitos cenarios futuros. + +**Con:** + +- grande demais para o objetivo atual; +- alto risco de abstrair sem uso real; +- pode atrasar a estabilizacao do 2D atual; +- tende a introduzir nomes e contratos que depois precisarao ser quebrados. + +**Maintainability:** + +Baixa para o momento atual. Melhor deixar essa camada emergir depois de pelo menos dois pipelines reais funcionando. + +## Sugestao / Recomendacao + +A recomendacao atual e a Opcao A: definir dois pipelines logicos com command sets proprios e uma implementacao Classic2D inicial. + +Modelo recomendado: + +```text +Game2DPipeline + produces Game2DFramePacket + owns/uses FrameComposer concepts: + scene/cache/camera + sprites + game HUD + HUD/primitives + +PrometeuUiPipeline + produces UiFramePacket + owns/uses system UI concepts: + home/hub + shell windows + panels/rects/text + focus/input visual state + +RenderHardware / RenderSurface implementation + consumes typed submissions: + Game2D(Game2DFramePacket) + PrometeuUi(UiFramePacket) + future: Game3D(...), Custom2D(...), etc. + writes to current RGBA8888 framebuffer for now + +RenderManager + owns current AppMode render routing + closes per-domain command buffers into a submission + manages mode transitions + keeps only the latest complete submission + owns publish/present policy through the render surface +``` + +O primeiro plano de execucao pode ser incremental: + +1. Definir nomes e contratos dos pacotes sem criar backend registry. +2. Migrar o Game 2D path para `Game2DFramePacket -> Classic2D`. +3. Manter comportamento pixel-level identico. +4. Em seguida migrar Prometeu UI para `UiFramePacket -> Classic2D`. +5. So depois discutir outros backends, render thread ou 3D. + +Se quisermos reduzir ainda mais risco, a decisao pode determinar que o primeiro PR implementa apenas o Game 2D packet, mas ja reserva a arquitetura e nomes para `UiFramePacket`. O importante e nao fingir que Prometeu UI e apenas "mais primitives gfx" dentro do pipeline de jogo. + +## Riscos + +- **Abstracao comum errada:** um command set universal pode parecer simples agora, mas virar bloqueio para UI e 3D depois. +- **Escopo grande demais:** migrar Game 2D, UI, firmware screens e host presentation em um unico PR aumenta risco de regressao visual. +- **Dual path permanente:** se `gfx_mut()` continuar como escape hatch normal, a fronteira de comandos nao vira contrato real. +- **Ownership/lifetime dos pacotes:** pacotes borrowed reduzem copia no v1; pacotes owned ajudam render thread futura. A decisao precisa declarar o alvo do primeiro passo. +- **HUD ambiguo:** HUD de jogo e UI do Prometeu precisam ficar separados por contrato, mesmo que ambos sejam 2D. +- **Present/publication:** `present()` precisa ser tratado como publicacao de superficie, nao como parte aleatoria de cada produtor logico. +- **Asset/cache leakage:** command packets devem referenciar recursos logicos/resolvidos sem copiar asset pipeline nem expor detalhes demais do `Gfx`. +- **Paralelismo futuro:** se o packet v1 depender de estado mutavel vivo da VM/hardware, uma render thread futura exigira nova quebra arquitetural. A submissao precisa parecer um snapshot fechado mesmo quando consumida sincronicamente no v1. +- **Transicoes entre modos:** se Shell e Game forem totalmente independentes sem uma coordenacao mestre, efeitos de transicao e manutencao de superficie ficam sem dono claro. +- **Backpressure:** render futuro nao deve acumular fila infinita. A politica aceita e manter somente a submissao completa mais recente. +- **Abstracao vs host:** `RenderManager` deve ser contrato runtime/abstracao de coordenacao. Host desktop/render surface deve ser implementacao local separada, sem vazar detalhes para os pipelines. +- **HUD ownership:** HUD nao deve ficar escondido dentro de `Gfx2D`. `Gfx2D` deve conter apenas primitivas. No v1, HUD pertence explicitamente ao `composer`. + +## Arquivos / Areas Provavelmente Afetados em uma Plan Futura + +Nucleo Game 2D: + +- `crates/console/prometeu-hal/src/` para tipos compartilhados de packet/submission, se virarem contrato de hardware. +- `crates/console/prometeu-hal/src/lib.rs` para exportar novos modulos. +- `crates/console/prometeu-drivers/src/frame_composer.rs` para produzir `Game2DFramePacket`. +- `crates/console/prometeu-drivers/src/gfx.rs` para consumir `Game2DFramePacket` no Classic2D atual. +- `crates/console/prometeu-drivers/src/hardware.rs` para conectar pipeline/submission/implementacao. +- `crates/console/prometeu-hal/src/hardware_bridge.rs` para reduzir acesso direto a `GfxBridge` no fluxo de frame. + +Nucleo Prometeu UI: + +- `crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs` +- `crates/console/prometeu-system/src/services/windows/*` +- `crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs` +- `crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs` +- possivelmente um novo modulo de UI commands em `prometeu-hal` ou `prometeu-system`. + +Possivel, mas deve ser minimizado no primeiro passo: + +- `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs` se `gfx.*` primitives forem convertidas em command submission. +- `crates/console/prometeu-hal/src/gfx_bridge.rs` se a ponte antiga for estreitada. +- `crates/host/prometeu-host-desktop-winit/src/runner.rs` se o acesso ao framebuffer for encapsulado, mas host presentation idealmente fica igual no v1. + +## Perguntas em Aberto + +- [x] O primeiro plano deve implementar Game 2D e Prometeu UI juntos, ou apenas Game 2D com contrato preparado para UI? + - Resposta: faseado. O primeiro plano deve priorizar Game 2D, mas o contrato deve nomear Prometeu UI como pipeline separado desde o inicio. +- [x] O contrato comum deve ser um enum `RenderSubmission`, uma trait de submissao, ou uma fila tipada por pipeline? + - Resposta: `AppMode` e `RenderSubmission` devem andar juntos. `AppMode::Game` e `AppMode::Shell` podem ter cadencias e pipelines diferentes; alguns renderers pertencem a um modo e nao ao outro. O envelope inicial pode ser um `RenderSubmission` tipado por modo/pipeline, sem registry dinamico. +- [x] `Game2DFramePacket` deve incluir HUD de jogo no v1 ou apenas reservar o campo? + - Resposta: deve incluir HUD de jogo no v1. O pipeline 2D de jogos abrange scene, sprites e HUD. +- [x] `UiFramePacket` deve representar UI em comandos baixos (`rect`, `text`, `clip`) ou em comandos mais semanticos (`window`, `panel`, `button`)? + - Resposta: comandos de UI podem ser diferentes dos comandos Game 2D, mas widget/layout deve ficar fora da implementacao do host. No host, `UiFramePacket` pode ser reduzido a diretivas semelhantes ou equivalentes as primitivas finais de desenho. +- [x] Pacotes v1 devem ser borrowed, owned, ou hibridos? + - Resposta: hibridos. O modelo precisa suportar transicoes de modo, como clicar no jogo, sair da UI, abrir o jogo e alternar entre `Game` e `Shell`, sem exigir copia pesada de todo recurso. +- [x] Onde fica `present()` no novo modelo: no hardware/render surface, no consumidor Classic2D, ou temporariamente no firmware? + - Resposta: `present()` pertence a implementacao de hardware/render surface quando ela precisa publicar/trocar buffers. Ele faz sentido em um blit/double-buffer path como Game 2D, mas nao deve ser parte obrigatoria do path canonico de todos os pipelines. +- [x] O acesso publico a `gfx.*` deve virar submissao de comandos, ser mantido como debug overlay temporario, ou ser separado em outra discussao? + - Resposta: separar apenas as primitivas de desenho por contrato de pipeline. O que ja e `composer.*` deve continuar no dominio `composer`, porque scene, camera, sprites, HUD e orquestracao de frame sao um contrato mais alto nivel do Game 2D, nao primitivas `gfx2d`. `Gfx2D` deve conter somente primitivas. O atual `gfx.*` deve ser dividido em primitivas apropriadas para Game e Shell, por exemplo `gfx2d.*` para primitivas do Game 2D e `gfxui.*` para Prometeu UI/Shell. As stdlibs podem continuar expondo nomes de alto nivel como `gfx`, desde que apontem para syscalls diferentes por `AppMode`. +- [x] A agenda aberta `DSC-0011` de dirty regions deve depender desta fronteira antes de continuar? + - Resposta: sim. `DSC-0011` vem depois desta fronteira, para nao otimizar o acoplamento errado. +- [x] O v1 precisa implementar render thread? + - Resposta: nao. Mas o v1 deve preparar a fronteira para isso: `RenderSubmission` deve ser um snapshot fechado por `frame_id`/`AppMode`, sem exigir `&mut Hardware` ou framebuffer mutavel no produtor logico. O consumo pode continuar sincrono na primeira implementacao. +- [x] Quem e dono do frame builder? + - Resposta: cada dominio/pipeline deve ter seus proprios buffers de comando/estado. O fechamento do frame deve ser centralizado no host/render coordinator, que transforma esses buffers na `RenderSubmission` valida para o `AppMode` atual. +- [x] Qual e a ordem de composicao Game 2D? + - Resposta: scene e sprites intercalam por `layer` inteiro usando os 4 layers disponiveis do Game 2D, normalmente como `sprite -> scene -> sprite -> scene -> ...` conforme ordenacao por layer. Depois vem um layer de HUD sobre tudo e entao publish. Transicoes visuais devem pertencer ao `RenderManager`, nao ao Game 2D frame path. +- [x] Como lidar com transicoes Game/Shell? + - Resposta: a arquitetura precisa de um `RenderManager` acima dos pipelines. Ele gerencia troca de modo, manutencao da superficie, capacidades ativas e transicoes que nao pertencem integralmente nem ao Shell UI nem ao Game 2D. +- [x] Qual politica de backpressure para render paralelo futuro? + - Resposta: sem fila infinita. Manter somente a submissao completa mais recente. +- [x] Capabilities devem ser separadas? + - Resposta: sim. O contrato deve prever capabilities separadas para `composer`, `gfx2d` e `gfxui`, reforcando o isolamento por `AppMode`. +- [x] O host debug overlay deve continuar? + - Resposta: nao como parte desta arquitetura. O overlay atual pode ser removido por completo em uma execucao futura; se um console/debug UI voltar a ser necessario, deve ser rediscutido como produto proprio, nao como overlay host-owned herdado. +- [x] Fade deve permanecer em algum contrato? + - Resposta: nao. Fade deve sair completamente. Ele era uma ideia de hardware inicial e nao deve permanecer como campo de packet, syscall ou habito de implementacao. Transicoes visuais futuras pertencem ao `RenderManager`, nao a `composer`, `gfx2d` ou `gfxui`. +- [x] HUD deve ser dominio proprio ou parte do `composer`? + - Resposta: HUD fica dentro de `composer` no v1, por ser parte alta do frame Game 2D junto de scene, camera e sprites. `Gfx2D` continua limitado a primitivas. + +## Criterio para Encerrar + +Esta agenda pode virar decisao quando houver consenso sobre: + +- quais pipelines existem no v1; +- qual command set pertence ao Game 2D; +- qual command set pertence a Prometeu UI; +- qual e a fronteira comum entre pipeline e implementacao de hardware/superficie; +- o que fica explicitamente fora de escopo; +- estrategia de migracao incremental sem mudanca visual; +- testes de equivalencia esperados. + +## Discussion + +Atualizacao de 2026-05-25: + +O foco nao deve ser apenas "introduzir `RenderFramePacket` para Classic2D". O foco deve ser separar pipelines logicos por dominio. Game 2D e Prometeu UI podem ter modelos de comando distintos, mas ambos devem submeter comandos a uma implementacao de hardware/render surface. + +A menor refatoracao segura provavelmente continua partindo do Game 2D, porque `FrameComposer` ja concentra scene/cache/sprites. Mas a decisao deve nomear Prometeu UI como pipeline separado desde agora, para evitar que a UI do sistema vire uma extensao acidental do pipeline de jogo. + +Respostas fechadas em discussao: + +- `AppMode::Game` e `AppMode::Shell` continuam sendo os modos canonicos atuais; nao ha rename para `System`. +- `RenderSubmission` deve ser coerente com `AppMode`, porque a atualizacao de frame e os renderers disponiveis podem diferir entre Game e Shell. +- `composer.*` deve continuar representando scene, camera, sprites, HUD e orquestracao de frame do Game 2D. +- `Gfx2D` deve representar apenas primitivas/drawing commands do pipeline Game 2D. +- `GfxUI` deve representar primitivas/drawing commands do pipeline Prometeu UI/Shell. +- As stdlibs PBS de Game e Shell podem usar nomes parecidos em alto nivel, inclusive `gfx`, mas devem mapear para dominios ABI diferentes conforme `AppMode`. +- O host/render implementation pode reduzir command sets diferentes a diretivas locais parecidas, mas widget/layout nao deve morar no host. +- `present()` e publicacao de superficie, nao parte obrigatoria de todo pipeline logico. +- `DSC-0011` deve aguardar esta fronteira. +- Render thread futura nao deve ser implementada agora, mas a fronteira deve nascer compativel com VM e renderer rodando em paralelo: submissions fechadas, leitura imutavel de recursos compartilhados e publicacao isolada na render surface. +- Buffers de comando/estado devem pertencer aos dominios/pipelines; o fechamento em `RenderSubmission` deve ser centralizado no `RenderManager`. +- `RenderManager` e abstracao/runtime contract; host/render-surface implementation e adaptador local separado. +- Game 2D intercalara scene e sprites por layer. HUD fica acima de tudo e pertence ao `composer` no v1, ainda com contrato minimo e evolutivo. +- Fade sai completamente. Transicoes de entrada/saida entre Shell e Game pertencem ao `RenderManager`. +- Backpressure futuro: latest complete submission wins. +- Capabilities devem ser separadas para `composer`, `gfx2d` e `gfxui`. +- O debug overlay host-owned atual nao deve ser preservado como requisito; pode ser removido quando a execucao chegar nessa area. + +## Resolution + +As perguntas principais de escopo foram respondidas. A agenda ainda precisa ser aceita explicitamente antes de virar decisao normativa. + +## Next Step + +Fechar as perguntas sobre escopo do v1. Se a direcao de dois pipelines logicos for aceita, transformar esta agenda em uma decisao normativa antes de qualquer plan ou alteracao de codigo. diff --git a/discussion/workflow/decisions/DEC-0030-logical-render-pipeline-command-boundary.md b/discussion/workflow/decisions/DEC-0030-logical-render-pipeline-command-boundary.md new file mode 100644 index 00000000..49683c13 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0030-logical-render-pipeline-command-boundary.md @@ -0,0 +1,236 @@ +--- +id: DEC-0030 +ticket: render-frame-packet-boundary +title: Logical Render Pipeline Command Boundary +status: accepted +created: 2026-05-25 +ref_agenda: AGD-0038 +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +# Decision - Logical Render Pipeline Command Boundary + +## Status + +Accepted. + +## Contexto + +Prometeu ja concluiu a migracao do framebuffer/runtime para RGBA8888. O proximo passo do renderer nao e introduzir GPU, 3D, multiplos backends, troca dinamica complexa de pipeline ou render thread. O objetivo imediato e criar uma fronteira arquitetural limpa entre: + +- producao logica de comandos de render; +- fechamento/publicacao de frame; +- coordenacao entre `AppMode::Game` e `AppMode::Shell`; +- rasterizacao/publicacao em uma superficie real. + +O estado atual ainda acopla codigo logico ao `Gfx`/framebuffer. `FrameComposer` ja centraliza parte da orquestracao do frame de jogo, mas ainda desenha por `GfxBridge`. Prometeu Hub, shell/system UI, splash/crash e primitivas `gfx.*` tambem acessam desenho imperativo direto. Essa decisao normatiza a nova fronteira de comandos e o papel de cada dominio. + +## Decisao + +Prometeu MUST separar render pipeline logico de render implementation. + +O runtime SHALL introduce a `RenderManager` as the runtime-level render coordination abstraction. `RenderManager` MUST NOT be a host-specific renderer and MUST NOT contain desktop presentation details. Host/render-surface implementations SHALL adapt `RenderManager` submissions to the local hardware or host surface. + +The canonical render flow SHALL be: + +```text +domain buffers during logical frame +-> RenderManager closes buffers +-> RenderSubmission snapshot +-> render surface / implementation consumes submission +-> RGBA8888 surface publication +``` + +The v1 logical render domains are: + +- `composer.*` for Game 2D high-level frame composition: scene, camera, sprites, HUD, and Game 2D frame orchestration. +- `gfx2d.*` for Game 2D primitives only. +- `gfxui.*` for Shell UI primitives. + +The v1 app modes are: + +- `AppMode::Game`, which SHALL route to Game 2D submissions. +- `AppMode::Shell`, which SHALL route to Shell UI submissions. + +No `AppMode::System` rename is accepted by this decision. + +`RenderSubmission` MUST be coherent with `AppMode`. The initial submission model SHOULD be a typed envelope rather than a generic universal command list: + +```text +RenderSubmission +- frame_id +- app_mode +- packet: + - Game2D(Game2DFramePacket) + - ShellUi(UiFramePacket) +``` + +The exact Rust layout may evolve during planning, but the contract MUST preserve typed per-pipeline command sets. + +## Rationale + +Typed command sets preserve domain meaning. Game 2D and Shell UI have different responsibilities and should not be forced into a weak universal command list. A future 3D or custom 2D pipeline should add its own packet rather than reinterpret a 2D/UI command model. + +`RenderManager` is required because mode transitions do not belong completely to either Shell UI or Game 2D. Shell can render an exit transition before launching a game, but once Game mode owns the frame, Shell UI is no longer available to render the entry transition. Therefore transition coordination, surface maintenance, active render capabilities, and frame publication need a manager above individual pipelines. + +Command buffers and submissions must be distinct. Domain buffers are mutable while the logical frame is being built. A `RenderSubmission` is a closed snapshot. This makes future VM/render parallelism possible without requiring a render thread in v1. + +The current fade model is rejected. Fade was a hardware-direction idea that no longer belongs in the Prometeu render contract after RGBA8888. Fade MUST NOT remain as a packet field, syscall, or implementation habit. Future visual transitions belong to `RenderManager`. + +## Invariantes / Contrato + +### Pipeline and Implementation Boundary + +- Pipelines MUST produce logical commands or state. +- Render implementations MUST consume submissions and write/publish to a surface. +- Pipeline code MUST NOT require direct mutable access to the framebuffer. +- Pipeline code MUST NOT depend on host-specific presentation details. + +### RenderManager + +- `RenderManager` MUST coordinate active `AppMode`, submission closure, mode transitions, capabilities, and publication policy. +- `RenderManager` MUST be a runtime abstraction, not a host renderer. +- Host/render-surface implementations MUST be separate adapters. +- `RenderManager` MUST own transition coordination between Game and Shell. +- `RenderManager` MUST own the policy for surface publication/present through the render surface. + +### Command Buffers and Submissions + +- Each render domain/pipeline MUST own its own mutable command/state buffer during a logical frame. +- `RenderManager` MUST close the active buffers into a `RenderSubmission` at frame boundary. +- Once closed, `RenderSubmission` MUST be treated as immutable by producers. +- `RenderSubmission` MUST include `frame_id` and `app_mode`. +- Future parallel render MUST be supported by the contract: consuming a submission MUST NOT require `&mut Hardware`, `&mut Gfx`, or live mutable VM state. +- Backpressure policy MUST be latest-complete-submission-wins. The system MUST NOT accumulate an unbounded frame queue. + +### AppMode Routing + +- `AppMode::Game` SHALL expose and route Game render domains. +- `AppMode::Shell` SHALL expose and route Shell UI render domains. +- Renderers/capabilities MAY differ by app mode. +- PBS stdlibs MAY expose similar high-level names, including `gfx`, but they MUST map to mode-appropriate ABI domains. + +### ABI Domains + +- `composer.*` MUST remain the high-level Game 2D composition domain. +- `composer.*` MUST own scene, camera, sprites, HUD, and Game 2D frame orchestration. +- `gfx2d.*` MUST contain Game 2D primitives only. +- `gfx2d.*` MUST NOT own scene, camera, sprites, HUD, or frame orchestration. +- `gfxui.*` MUST contain Shell UI primitives. +- `gfxui.*` MUST NOT contain widget/layout policy. Widget/layout belongs in Shell/UI code or stdlib, not the host implementation. +- Capabilities MUST be separated for `composer`, `gfx2d`, and `gfxui`. + +### Game 2D Composition + +- Game 2D scene and sprites MUST compose by integer `layer`. +- The v1 Game 2D model has four available layers. +- Scene and sprite composition MAY interleave by layer, normally as sprite/scene ordering across the four layers. +- HUD MUST render above scene/sprite composition. +- HUD belongs to `composer` in v1 and remains a minimal, evolvable contract. +- HUD in this decision means Game HUD only. Shell/system UI is always part of the Shell UI pipeline. +- Game 2D publish happens after scene/sprite/HUD composition. + +### Shell UI Composition + +- Shell UI MUST use its own `gfxui.*` primitive command set. +- Shell UI commands MAY reduce to the same local drawing directives as Game 2D primitives inside a host/render-surface implementation. +- That reduction MUST remain an implementation detail and MUST NOT collapse the ABI domains. + +### Fade and Transitions + +- Fade MUST be removed completely from canonical pipeline contracts. +- Fade MUST NOT appear as a `Game2DFramePacket`, `UiFramePacket`, `RenderSubmission`, `composer`, `gfx2d`, or `gfxui` field or syscall. +- Future visual transitions MUST be coordinated by `RenderManager`. + +### Debug Overlay + +- The current host-owned debug overlay is not part of the new render architecture. +- It MAY be removed during execution. +- A future console/debug UI MUST be discussed as a separate product/domain, not preserved as inherited host overlay behavior. + +## Impactos + +### Spec + +Specs must define the logical render pipeline model, `RenderManager`, `RenderSubmission`, app-mode routing, domain buffers, and publication boundary in English. + +The public syscall/ABI specs must replace the old primitive `gfx.*` surface with mode-appropriate `gfx2d.*` and `gfxui.*` primitive domains while preserving `composer.*` as the high-level Game 2D composition domain. + +### Runtime + +Runtime frame execution must stop treating `Gfx`/framebuffer as the direct target of logical render calls. It must accumulate domain state/commands and let `RenderManager` close submissions. + +`VirtualMachineRuntime` and firmware flow must route frame closure through `RenderManager`. + +### HAL + +HAL must expose distinct contracts for: + +- `composer`; +- `gfx2d`; +- `gfxui`; +- render submissions; +- render manager/surface boundary. + +Capability flags must be split accordingly. + +### Host + +The desktop host remains a concrete render-surface implementation. It may continue publishing RGBA8888 to `pixels`, but that behavior must sit below the render-surface adapter, not inside logical pipeline APIs. + +Host debug overlay may be removed. + +### Firmware / Shell + +Shell UI must migrate away from direct `gfx_mut()` drawing toward `gfxui.*`/Shell UI command buffers. + +Game mode must migrate toward `composer.*` plus `gfx2d.*` buffers closed by `RenderManager`. + +### PBS / Stdlibs + +PBS Game and Shell stdlibs may present similar high-level APIs to authors. They must map to different ABI domains according to `AppMode`. + +Game stdlib should map scene/camera/sprites/HUD to `composer.*` and primitive Game drawing to `gfx2d.*`. + +Shell stdlib should map UI primitives to `gfxui.*`. + +## Alternativas Descartadas + +### Universal RenderCommand + +A single `RenderCommand` display list for Game, Shell, and future pipelines is rejected. It collapses domain meaning and would likely force future 3D or custom pipelines into a 2D/UI-shaped contract. + +### Trait-Only RenderPipeline + +A trait that calls `render(&mut Surface)` without a closed packet is rejected as the primary boundary. It preserves immediate rendering and does not prepare VM/render parallelism. + +### Backend Registry Now + +A full backend registry, GPU/CPU backend abstraction, dynamic pipeline switching, render thread, or 3D implementation is out of scope. Those may come later after the command boundary is stable. + +### Moving Composer into Gfx2D + +Moving scene, camera, sprites, HUD, or frame orchestration into `gfx2d.*` is rejected. `gfx2d.*` is primitives only. + +## Referencias + +- Agenda: `AGD-0038` +- Related dependency: `DSC-0011` dirty regions must wait until this boundary exists. +- Related prior lessons: frame composition belongs above the render backend; public ABI must follow canonical service boundaries; RGBA8888 is the canonical framebuffer contract. + +## Propagacao Necessaria + +- Create a plan before any spec or code changes. +- Update canonical specs in English. +- Update HAL syscall registry, capability definitions, and resolver tests. +- Update PBS/stdlib syscall declarations for Game and Shell. +- Update runtime dispatch so render syscalls mutate domain buffers instead of drawing directly. +- Introduce `RenderManager`, domain buffers, `RenderSubmission`, and render-surface implementation boundary. +- Migrate Game 2D first, then Shell UI, unless the plan proves both can be safely migrated together. +- Remove fade surfaces from render contracts. +- Remove or explicitly retire the host debug overlay. +- Add packet/submission tests, app-mode capability tests, and pixel-equivalence tests for unchanged visible behavior. + +## Revision Log + +- 2026-05-25: Initial decision draft from `AGD-0038`. diff --git a/discussion/workflow/plans/PLN-0073-render-contract-specs.md b/discussion/workflow/plans/PLN-0073-render-contract-specs.md new file mode 100644 index 00000000..b7fdbe33 --- /dev/null +++ b/discussion/workflow/plans/PLN-0073-render-contract-specs.md @@ -0,0 +1,67 @@ +--- +id: PLN-0073 +ticket: render-frame-packet-boundary +title: Render Contract Specs +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Publish canonical English spec language for the logical render pipeline boundary before code migration. The specs must make `RenderManager`, domain buffers, typed `RenderSubmission`, app-mode routing, render-surface publication, latest-complete-submission policy, and fade removal explicit. + +## Target + +Canonical specs describe the DEC-0030 render contract well enough that HAL, runtime, host, firmware, and PBS work can proceed without reopening architecture. + +## Scope + +- Define the logical flow from mutable domain buffers to closed `RenderSubmission` to render-surface publication. +- Define `RenderManager` as runtime coordination, not a host renderer. +- Define `RenderSubmission` with `frame_id`, `app_mode`, and typed Game 2D or Shell UI packet. +- Define `composer.*`, `gfx2d.*`, and `gfxui.*` as separate ABI domains. +- Specify latest-complete-submission-wins backpressure. +- Remove fade from canonical render contracts. + +## Out of Scope + +- Rust implementation. +- PBS stdlib implementation. +- Pixel output changes. +- Backend registry, GPU backend, render thread, 3D pipeline, or debug console design. + +## Execution Sequence + +1. Inventory existing render, graphics, framebuffer, composer, and syscall specs. +2. Replace old primitive `gfx.*` ABI language with the DEC-0030 domain split. +3. Add sections for `RenderManager`, domain buffers, submission closure, immutability, and publication. +4. Add app-mode and capability routing language for Game and Shell. +5. Remove normative fade fields, syscalls, and packet references from render specs. +6. Cross-link impacted specs for HAL, runtime, host, firmware, and PBS propagation. + +## Acceptance Criteria + +- Specs are in English and reference `DEC-0030`. +- No canonical render spec presents direct framebuffer mutation as the logical render API. +- No canonical render spec keeps fade as a packet field, syscall, or ABI habit. +- Specs distinguish logical pipeline production from render-surface implementation. + +## Tests / Validation + +- Run repository documentation/spec validation if available. +- Inspect `rg -n "fade|gfx\\.\\*|RenderSubmission|RenderManager|present\\(" specs discussion/workflow/decisions/DEC-0030-logical-render-pipeline-command-boundary.md`. +- Run `discussion validate` if discussion references change. + +## Risks + +- Existing specs may use `gfx.*` as both author API and ABI name; preserve author clarity while changing ABI names. +- Removing fade text can accidentally delete transition requirements; move those requirements to `RenderManager` language. + +## Affected Artifacts + +- `specs/` +- `discussion/workflow/decisions/DEC-0030-logical-render-pipeline-command-boundary.md` diff --git a/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md b/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md new file mode 100644 index 00000000..9909f207 --- /dev/null +++ b/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md @@ -0,0 +1,66 @@ +--- +id: PLN-0074 +ticket: render-frame-packet-boundary +title: HAL Render Submission Types +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Introduce HAL-level submission types that encode the logical render boundary without binding consumers to live `Hardware`, `Gfx`, framebuffer, or VM state. + +## Target + +HAL exposes typed contracts for `RenderSubmission`, `Game2DFramePacket`, `ShellUiFramePacket`, frame ids, app-mode routing, and immutable closed submissions. + +## Scope + +- Add typed packet structures for Game 2D and Shell UI. +- Add a `RenderSubmission` envelope with `frame_id`, `app_mode`, and typed packet variant. +- Add or reuse a monotonic frame id type. +- Encode closed-submission immutability through ownership and API shape. +- Keep packet types free of host presentation details and live mutable runtime references. + +## Out of Scope + +- `RenderManager` behavior beyond type integration. +- Renderer/rasterizer implementation. +- Backend registry, render thread, GPU path, or 3D packet. +- PBS stdlib updates. + +## Execution Sequence + +1. Locate current HAL graphics, composer, capability, and app-mode modules. +2. Add packet and submission types where ABI-facing render data belongs. +3. Represent packets as typed variants: `Game2D(Game2DFramePacket)` and `ShellUi(ShellUiFramePacket)`. +4. Ensure constructors or closure APIs consume or clone domain buffer contents. +5. Add tests for app-mode and packet coherence. +6. Update internal references to use the new types without changing runtime rendering yet. + +## Acceptance Criteria + +- HAL contains the DEC-0030 submission and packet vocabulary. +- A closed `RenderSubmission` can be handed to a consumer without borrowing live runtime rendering state. +- Game and Shell packets are distinguishable at the type level. +- The HAL API does not include fade fields. + +## Tests / Validation + +- Run HAL crate unit tests. +- Add tests for packet construction, app-mode coherence, and immutable ownership semantics. +- Inspect `rg -n "fade|UiFramePacket|ShellUiFramePacket|Game2DFramePacket|RenderSubmission" crates specs`. + +## Risks + +- New types must live at the ABI/contract level, not inside a host renderer. +- Borrowing shortcuts could leak mutability and violate the future parallel render contract. + +## Affected Artifacts + +- HAL crate/module under `crates/` +- HAL tests diff --git a/discussion/workflow/plans/PLN-0075-rendermanager-core.md b/discussion/workflow/plans/PLN-0075-rendermanager-core.md new file mode 100644 index 00000000..a2274b63 --- /dev/null +++ b/discussion/workflow/plans/PLN-0075-rendermanager-core.md @@ -0,0 +1,68 @@ +--- +id: PLN-0075 +ticket: render-frame-packet-boundary +title: RenderManager Core +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Add `RenderManager` as the runtime abstraction that closes domain buffers into typed submissions, tracks active app mode, owns transition coordination placeholders, and hands the latest complete submission to the render surface. + +## Target + +Runtime frame execution routes render closure through `RenderManager` without making it a host renderer or direct framebuffer writer. + +## Scope + +- Add the `RenderManager` module and public runtime API. +- Track active `AppMode` and frame id generation. +- Close the active domain buffer into a `RenderSubmission`. +- Enforce latest-complete-submission-wins rather than an unbounded queue. +- Add transition coordination placeholders owned by `RenderManager`. +- Provide a publication handoff to a render-surface abstraction. + +## Out of Scope + +- Full visual transition implementation. +- Host-specific presentation details. +- Domain buffer implementation beyond integration points. +- Render thread, GPU backend, or backend registry. + +## Execution Sequence + +1. Add a runtime render module that owns `RenderManager` and related state. +2. Wire `VirtualMachineRuntime` frame closure through `RenderManager`. +3. Add active app-mode routing and rejection for incoherent packet closure. +4. Add latest-complete-submission storage and replacement semantics. +5. Add a render-surface handoff interface without calling host-specific `present()` from producers. +6. Add explicit no-op transition hooks for future work. + +## Acceptance Criteria + +- Runtime has one frame closure authority for render submissions. +- Producers can mutate domain buffers before closure but cannot mutate the closed submission. +- `RenderManager` does not depend on desktop host types. +- Backpressure is latest-complete-submission-wins. +- App-mode routing is explicit and tested. + +## Tests / Validation + +- Add unit tests for frame id increment, latest submission replacement, app-mode routing, and no-op transition state. +- Add runtime integration coverage proving frame closure passes through `RenderManager`. +- Run affected crate tests. + +## Risks + +- Existing frame lifecycle code may assume immediate framebuffer mutation; isolate compatibility shims and remove them in later plans. +- Transition placeholders must not become hidden fade behavior. + +## Affected Artifacts + +- Runtime crate/modules under `crates/` +- Runtime tests diff --git a/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md b/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md new file mode 100644 index 00000000..6105c803 --- /dev/null +++ b/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md @@ -0,0 +1,67 @@ +--- +id: PLN-0076 +ticket: render-frame-packet-boundary +title: Capabilities and ABI Domain Split +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Split render capabilities and syscall metadata so `composer`, `gfx2d`, and `gfxui` are independently addressable and enforceable according to `AppMode`. + +## Target + +Capability gates reject wrong-domain render syscalls, and metadata no longer treats the old primitive `gfx.*` ABI as the single render surface. + +## Scope + +- Add or rename capability flags for `COMPOSER`, `GFX2D`, and `GFXUI`. +- Update syscall metadata for all render-domain calls. +- Enforce domain availability by `AppMode::Game` and `AppMode::Shell`. +- Update resolver and capability tests. +- Preserve `composer.*` as Game 2D high-level composition. + +## Out of Scope + +- Implementing command buffers. +- PBS stdlib author-facing wrappers. +- Host rendering. +- Backward compatibility for the old primitive `gfx.*` ABI. + +## Execution Sequence + +1. Locate capability definitions, syscall registry metadata, and resolver tests. +2. Introduce separate render capability identifiers for `composer`, `gfx2d`, and `gfxui`. +3. Update metadata for existing composer calls and new or renamed primitive domains. +4. Add app-mode checks so Game exposes `composer` and `gfx2d`, while Shell exposes `gfxui`. +5. Replace stale tests that assume one graphics capability. +6. Run resolver and runtime syscall tests. + +## Acceptance Criteria + +- Wrong-domain calls fail before mutating render state. +- Game mode cannot use `gfxui.*`. +- Shell mode cannot use `composer.*` or `gfx2d.*`. +- Capability metadata uses the split domain names. +- No compatibility promise remains for the old primitive `gfx.*` ABI. + +## Tests / Validation + +- Add resolver tests for all three capabilities. +- Add app-mode enforcement tests for allowed and rejected calls. +- Run affected unit and integration tests. + +## Risks + +- A large rename can hide semantic mistakes; verify every syscall maps to the correct domain responsibility. +- Author-facing stdlib names may still use `gfx`; keep this plan focused on ABI metadata. + +## Affected Artifacts + +- Syscall metadata and resolver modules under `crates/` +- Capability tests diff --git a/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md b/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md new file mode 100644 index 00000000..2adb510b --- /dev/null +++ b/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md @@ -0,0 +1,66 @@ +--- +id: PLN-0077 +ticket: render-frame-packet-boundary +title: Composer Buffer and Game2D Packet +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Convert current `composer.*` frame state into a mutable `ComposerBuffer` that closes into `Game2DFramePacket` at frame boundary. + +## Target + +Game 2D scene, camera, sprites, HUD, and frame orchestration are represented as domain-owned state until `RenderManager` closes them into a typed packet. + +## Scope + +- Add `ComposerBuffer`. +- Move scene, camera, sprite, HUD, layer ordering, and Game 2D orchestration state into the buffer. +- Close the buffer into `Game2DFramePacket`. +- Preserve the v1 four-layer scene/sprite composition model and HUD-above-game rule. +- Ensure fade is absent from Game 2D packet state. + +## Out of Scope + +- Primitive `gfx2d.*` command buffering. +- Shell UI packet work. +- Renderer consumption refactor beyond packet shape. +- New Game 2D features. + +## Execution Sequence + +1. Locate current `FrameComposer`, composer syscalls, scene/camera/sprite/HUD state, and tests. +2. Introduce `ComposerBuffer` and migrate composer mutation paths into it. +3. Implement closure into `Game2DFramePacket` with immutable packet contents. +4. Preserve integer layer ordering across the four Game 2D layers and HUD final composition. +5. Route frame closure through `RenderManager`. +6. Update tests that currently inspect `FrameComposer` internals. + +## Acceptance Criteria + +- `composer.*` owns Game 2D high-level frame state. +- Closed `Game2DFramePacket` contains all data required by the Game 2D renderer without live VM or `Gfx` access. +- Scene/sprite layer behavior and HUD ordering are preserved. +- Fade is not represented. + +## Tests / Validation + +- Add unit tests for buffer mutation and packet closure. +- Add composition ordering tests for scene, sprites, and HUD. +- Run current frame composer/render tests and compare existing expected output. + +## Risks + +- Existing composer code may mix orchestration with drawing; split state capture from rendering before deeper migration. +- Layer behavior is user-visible and must not drift. + +## Affected Artifacts + +- Composer/frame-composer modules under `crates/` +- Game 2D tests diff --git a/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md b/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md new file mode 100644 index 00000000..9ed855f7 --- /dev/null +++ b/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md @@ -0,0 +1,64 @@ +--- +id: PLN-0078 +ticket: render-frame-packet-boundary +title: Classic2D Game Renderer Consumer +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Refactor the Classic2D renderer path so it consumes `Game2DFramePacket` instead of being called directly by `FrameComposer` or logical producers. + +## Target + +Classic2D remains the concrete Game 2D rasterizer, but its input is a closed packet and its visible pixel output remains equivalent. + +## Scope + +- Add a packet consumer entry point for the current Classic2D/Gfx renderer. +- Replace direct `FrameComposer` to `Gfx` calls with packet consumption. +- Keep RGBA8888 output unchanged for existing supported scenes. +- Keep renderer implementation below the logical pipeline boundary. + +## Out of Scope + +- Redesigning scene, sprite, HUD, or primitive semantics. +- Shell UI rendering. +- Backend registry or GPU work. +- Dirty region optimization. + +## Execution Sequence + +1. Locate the current Classic2D/Gfx rendering entry points and `FrameComposer` call graph. +2. Add a `Game2DFramePacket` consumer API. +3. Move rendering reads from live composer state to packet data. +4. Update frame execution to pass the closed packet into the renderer through the render surface path. +5. Preserve existing framebuffer dimensions, RGBA8888 format, and pixel behavior. +6. Remove direct producer-to-renderer calls that bypass the packet boundary. + +## Acceptance Criteria + +- Classic2D renders from `Game2DFramePacket`. +- Logical composer code no longer directly mutates `Gfx` during frame production. +- Existing Game 2D visual fixtures or tests remain pixel-equivalent except where explicitly updated for the new boundary. + +## Tests / Validation + +- Add packet-to-pixels tests for representative scene, sprite, primitive, and HUD cases. +- Run existing render tests. +- Add regression coverage comparing old expected pixel buffers where available. + +## Risks + +- Packet conversion can accidentally alter draw order; keep tests focused on ordering and final pixels. +- Some current code may depend on side effects from direct `Gfx` mutation; identify and replace those effects explicitly. + +## Affected Artifacts + +- Classic2D/Gfx renderer modules under `crates/` +- Render tests and fixtures diff --git a/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md b/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md new file mode 100644 index 00000000..58eaea61 --- /dev/null +++ b/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md @@ -0,0 +1,66 @@ +--- +id: PLN-0079 +ticket: render-frame-packet-boundary +title: Gfx2D Primitive Domain +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Replace primitive Game drawing paths with `gfx2d.*` command buffering while keeping `gfx2d` limited to primitives only. + +## Target + +Game primitive drawing becomes a Game-mode ABI domain that records commands into a buffer consumed as part of Game 2D rendering. + +## Scope + +- Add `gfx2d.*` syscall domain and command buffer. +- Migrate primitive Game drawing calls from old `gfx.*` behavior to `gfx2d.*`. +- Ensure `gfx2d` contains primitives only. +- Integrate `gfx2d` commands into the Game 2D packet/render path without taking ownership of scene, camera, sprites, HUD, or frame orchestration. + +## Out of Scope + +- Shell UI primitive domain. +- `composer.*` high-level Game state. +- Widget/layout policy. +- Compatibility with old primitive `gfx.*` ABI names. + +## Execution Sequence + +1. Inventory old primitive `gfx.*` syscalls and direct drawing callsites used by Game mode. +2. Define the `gfx2d.*` command enum and mutable command buffer. +3. Route Game primitive syscalls into the buffer. +4. Add app-mode and capability enforcement for Game-only access. +5. Integrate buffered primitives into Game 2D packet closure or renderer consumption at the DEC-0030-approved layer. +6. Remove or retire old primitive `gfx.*` ABI registrations. + +## Acceptance Criteria + +- Game primitive commands are buffered as `gfx2d.*`. +- `gfx2d` does not own scene, camera, sprites, HUD, or frame orchestration. +- Shell mode cannot call `gfx2d.*`. +- No logical primitive path directly mutates framebuffer during production. + +## Tests / Validation + +- Add syscall tests for each migrated primitive. +- Add command buffer ordering tests. +- Add capability rejection tests for Shell mode. +- Run pixel-equivalence tests for unchanged primitive output. + +## Risks + +- The old `gfx` naming may appear in PBS wrappers; distinguish author API names from ABI domain names. +- Primitive ordering relative to composer output must be explicit. + +## Affected Artifacts + +- Syscall registry and runtime render domain modules under `crates/` +- Game primitive tests diff --git a/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md b/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md new file mode 100644 index 00000000..bec557d6 --- /dev/null +++ b/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md @@ -0,0 +1,66 @@ +--- +id: PLN-0080 +ticket: render-frame-packet-boundary +title: Shell UI Packet and GfxUI Domain +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Add `gfxui.*` command buffering and `ShellUiFramePacket` for Shell UI primitives while keeping widget/layout policy outside the host/render-surface implementation. + +## Target + +Shell UI emits typed UI primitive commands into a Shell-only packet that render-surface implementations can consume without collapsing UI and Game ABI domains. + +## Scope + +- Define `gfxui.*` primitive commands. +- Add a mutable Shell UI command buffer. +- Close Shell UI commands into `ShellUiFramePacket`. +- Enforce Shell-only capability and app-mode access. +- Keep widget/layout policy in Shell/UI code or stdlib layers. + +## Out of Scope + +- Migrating all Shell callsites; that is handled by the Shell/Hub migration plan. +- Game primitive rendering. +- New widget framework. +- Host-owned debug overlay. + +## Execution Sequence + +1. Define the `gfxui.*` syscall and command vocabulary needed for current Shell UI primitives. +2. Add a Shell UI command buffer and closure into `ShellUiFramePacket`. +3. Add capability metadata and resolver enforcement for `GFXUI`. +4. Add a render-surface consumer path that can rasterize Shell UI commands as an implementation detail. +5. Keep layout/widget decisions above the packet boundary. +6. Add tests for closure, app-mode gating, and packet consumption. + +## Acceptance Criteria + +- Shell UI commands close into `ShellUiFramePacket`. +- `gfxui.*` does not contain widget/layout policy. +- Game mode cannot call `gfxui.*`. +- Host/render-surface code consumes commands without redefining Shell UI product policy. + +## Tests / Validation + +- Add Shell UI command buffer unit tests. +- Add app-mode rejection tests for Game mode. +- Add render-surface consumer tests for representative primitives. + +## Risks + +- It is easy to migrate widget policy into host rendering while adding UI primitives; keep commands low-level and policy-free. +- Shell UI and Game primitives may rasterize similarly, but ABI domains must remain distinct. + +## Affected Artifacts + +- Shell UI render domain modules under `crates/` +- Syscall metadata and tests diff --git a/discussion/workflow/plans/PLN-0081-shell-hub-migration.md b/discussion/workflow/plans/PLN-0081-shell-hub-migration.md new file mode 100644 index 00000000..dc39c7d7 --- /dev/null +++ b/discussion/workflow/plans/PLN-0081-shell-hub-migration.md @@ -0,0 +1,67 @@ +--- +id: PLN-0081 +ticket: render-frame-packet-boundary +title: Shell Hub Migration +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Move Prometeu Hub, shell windows, splash/crash UI, and Shell drawing away from `gfx_mut()` into `gfxui.*` and Shell UI command buffers. + +## Target + +Shell-facing UI no longer draws directly into `Gfx`; it produces Shell UI commands that close into `ShellUiFramePacket`. + +## Scope + +- Migrate Prometeu Hub UI drawing. +- Migrate shell windows and window-manager drawing. +- Migrate splash and crash UI drawing. +- Remove direct Shell access to `gfx_mut()` for render production. +- Preserve visible Shell output where behavior is not intentionally changed. + +## Out of Scope + +- Designing new Hub visuals. +- New widget/layout framework. +- Game mode migration. +- Debug console replacement. + +## Execution Sequence + +1. Inventory Shell, Hub, splash, crash, and firmware drawing callsites that use `gfx_mut()` or direct framebuffer access. +2. Replace direct primitive drawing with `gfxui.*` buffer writes. +3. Route Shell frame closure through `RenderManager`. +4. Ensure Shell UI packets are consumed by the render-surface implementation. +5. Remove now-unused direct Shell rendering hooks. +6. Update tests and snapshots for unchanged visible behavior. + +## Acceptance Criteria + +- Shell UI production no longer requires mutable `Gfx` access. +- Hub, shell windows, splash, and crash UI can render through `ShellUiFramePacket`. +- Shell mode does not use `composer.*` or `gfx2d.*`. +- Visible UI output is preserved except for approved fixture updates caused by boundary-only refactoring. + +## Tests / Validation + +- Add integration tests for Hub/shell frame production. +- Add tests for splash and crash UI packet output. +- Run host smoke tests where available. +- Use pixel or snapshot comparisons for representative Shell screens. + +## Risks + +- Firmware and Shell code may mix lifecycle side effects with drawing; split command production without changing lifecycle semantics. +- Some host tests may assume immediate framebuffer writes; update them to observe submissions or final pixels. + +## Affected Artifacts + +- Shell, Hub, firmware, and host integration modules under `crates/` +- Shell UI tests and fixtures diff --git a/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md b/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md new file mode 100644 index 00000000..3afae455 --- /dev/null +++ b/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md @@ -0,0 +1,69 @@ +--- +id: PLN-0082 +ticket: render-frame-packet-boundary +title: Frame Publication and Present Boundary +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Move `present()` and surface publication behind the render-surface implementation boundary so runtime producers no longer publish buffers directly. + +## Target + +Only the render-surface implementation publishes RGBA8888 output; logical producers and `RenderManager` hand off closed submissions through a boundary. + +## Scope + +- Identify all direct `present()` and surface publication callsites. +- Define the render-surface adapter boundary. +- Route publication through the adapter from `RenderManager`. +- Keep desktop `pixels` publication as a host implementation detail. +- Preserve frame pacing and invalidation behavior. + +## Out of Scope + +- New backend registry. +- GPU implementation. +- Render thread. +- Dirty regions. +- Visual transition design. + +## Execution Sequence + +1. Inventory `present()`, framebuffer publication, and host invalidation callsites. +2. Add or refine a render-surface trait/adapter that consumes `RenderSubmission`. +3. Move host desktop publication below that adapter. +4. Update runtime frame loop to publish only through `RenderManager` and the adapter. +5. Remove direct publication from composer, `gfx2d`, `gfxui`, Shell, and Game producers. +6. Verify frame pacing and host invalidation still occur at the correct boundary. + +## Acceptance Criteria + +- Logical render domains cannot call `present()` or publish buffers directly. +- Host desktop publication remains functional below the render-surface boundary. +- `RenderManager` owns the policy for selecting the latest complete submission. +- Existing frame pacing behavior is not regressed. + +## Tests / Validation + +- Add unit tests for render-surface handoff. +- Add integration tests proving producers cannot publish directly. +- Run host desktop render/pacing tests. +- Inspect `rg -n "present\\(|publish|pixels" crates` for boundary violations. + +## Risks + +- Publication and invalidation may be coupled in current host code; preserve timing semantics while moving ownership. +- A too-broad adapter can become a backend registry prematurely. + +## Affected Artifacts + +- Runtime render manager modules +- Host desktop render surface modules +- Frame pacing tests diff --git a/discussion/workflow/plans/PLN-0083-fade-removal.md b/discussion/workflow/plans/PLN-0083-fade-removal.md new file mode 100644 index 00000000..0ebbd5bb --- /dev/null +++ b/discussion/workflow/plans/PLN-0083-fade-removal.md @@ -0,0 +1,63 @@ +--- +id: PLN-0083 +ticket: render-frame-packet-boundary +title: Fade Removal +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Remove fade fields, syscalls, packet members, renderer state, tests, and docs from canonical render contracts. + +## Target + +Fade is no longer a render pipeline feature. Future visual transitions remain the responsibility of `RenderManager` and require separate product/domain work. + +## Scope + +- Remove fade from specs, HAL packet types, syscall metadata, runtime state, renderer state, tests, and PBS declarations. +- Remove or update tests that assert fade behavior. +- Add guard tests or searches that prevent reintroducing fade in canonical render contracts. + +## Out of Scope + +- Implementing replacement visual transitions. +- Replacing fade with another transition API. +- Preserving backward compatibility for fade syscalls. + +## Execution Sequence + +1. Inventory all fade references in specs, crates, tests, examples, and PBS stdlibs. +2. Delete fade fields and syscalls from canonical ABI and packet definitions. +3. Remove renderer/runtime fade state and behavior. +4. Update callers and tests to stop setting or expecting fade. +5. Add documentation language that transitions belong to `RenderManager`. +6. Run targeted and full affected tests. + +## Acceptance Criteria + +- No canonical packet, syscall, or render contract contains fade. +- Runtime and renderer code do not maintain fade state. +- Tests no longer depend on fade behavior. +- Specs clearly state future transitions are `RenderManager` responsibility. + +## Tests / Validation + +- Run `rg -n "\\bfade\\b|Fade" specs crates discussion/workflow` and verify remaining references are only historical decision/agenda text or explicit removal notes. +- Run affected unit and integration tests. +- Add regression tests preventing fade fields in current packet types. + +## Risks + +- Removing tests can reduce coverage; replace fade-specific assertions with packet/transition boundary assertions where appropriate. +- Historical discussion files may keep fade references; do not rewrite history unless the framework requires it. + +## Affected Artifacts + +- Specs +- HAL, runtime, renderer, syscall, PBS, and tests under `crates/` diff --git a/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md b/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md new file mode 100644 index 00000000..0b30fe7c --- /dev/null +++ b/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md @@ -0,0 +1,65 @@ +--- +id: PLN-0084 +ticket: render-frame-packet-boundary +title: Host Debug Overlay Removal +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Remove the current host-owned debug overlay from the render path. Any future console/debug UI is a separate product/domain discussion. + +## Target + +Host rendering no longer injects an inherited debug overlay into Game or Shell output. + +## Scope + +- Identify host-owned debug overlay code and render hooks. +- Remove overlay rendering from the host render path. +- Remove overlay toggles/tests that assume inherited host drawing. +- Preserve non-render telemetry and logging where they are not tied to overlay drawing. + +## Out of Scope + +- Designing a replacement debug console. +- Moving debug UI into `gfxui.*`. +- Changing telemetry collection. +- Removing unrelated developer diagnostics. + +## Execution Sequence + +1. Inventory debug overlay rendering callsites and tests. +2. Remove host overlay injection from Game and Shell render paths. +3. Delete overlay-specific drawing state, toggles, and fixture expectations. +4. Keep telemetry/logging APIs that do not render overlay pixels. +5. Update docs/specs to state that future debug UI requires a separate decision. +6. Run host and render tests. + +## Acceptance Criteria + +- Host render path does not draw debug overlay pixels. +- Game and Shell output do not depend on host overlay state. +- Remaining diagnostics do not violate the render pipeline boundary. +- Docs do not preserve host overlay as an architectural requirement. + +## Tests / Validation + +- Run host render tests. +- Add or update tests proving overlay state does not affect final rendered output. +- Run `rg -n "debug overlay|overlay|DebugOverlay" crates specs discussion/workflow` and inspect remaining references. + +## Risks + +- Removing overlay code can accidentally remove useful telemetry; keep non-render diagnostics intact. +- The word overlay may also refer to Game HUD/primitives; distinguish those from host debug overlay. + +## Affected Artifacts + +- Host desktop modules under `crates/` +- Host/render tests diff --git a/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md b/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md new file mode 100644 index 00000000..5b928399 --- /dev/null +++ b/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md @@ -0,0 +1,67 @@ +--- +id: PLN-0085 +ticket: render-frame-packet-boundary +title: PBS Stdlib and Syscall Declarations +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Update PBS Game and Shell stdlibs so high-level APIs map to app-mode-specific ABI domains: Game uses `composer.*` and `gfx2d.*`; Shell uses `gfxui.*`. + +## Target + +PBS authors can keep ergonomic high-level APIs where appropriate, but generated or declared syscalls use the correct DEC-0030 ABI domain for the active app mode. + +## Scope + +- Update Game stdlib declarations for `composer.*` and `gfx2d.*`. +- Update Shell stdlib declarations for `gfxui.*`. +- Remove old primitive `gfx.*` ABI declarations. +- Keep author-facing wrappers clear about mode-specific behavior. +- Update examples/tests that compile PBS code. + +## Out of Scope + +- Runtime implementation of the syscall handlers. +- New visual APIs beyond the domain split. +- Backward compatibility for old primitive `gfx.*` ABI declarations. +- Widget/layout redesign. + +## Execution Sequence + +1. Inventory PBS stdlib files and syscall declaration generation. +2. Map Game scene/camera/sprites/HUD APIs to `composer.*`. +3. Map Game primitive APIs to `gfx2d.*`. +4. Map Shell primitive APIs to `gfxui.*`. +5. Remove or fail old primitive `gfx.*` ABI declarations. +6. Update PBS compile tests, examples, and syscall metadata expectations. + +## Acceptance Criteria + +- PBS Game code emits only Game-allowed render ABI domains. +- PBS Shell code emits only Shell-allowed render ABI domains. +- Old primitive `gfx.*` ABI declarations are gone or explicitly rejected. +- Existing examples are updated to the new declarations. + +## Tests / Validation + +- Run PBS stdlib and compiler tests. +- Add Game and Shell declaration tests for emitted syscall names. +- Add negative tests for wrong-domain declarations. + +## Risks + +- User-facing API names may remain similar while ABI names change; tests must assert emitted syscall domains, not just wrapper names. +- Examples can mask stale declarations if they do not compile in both Game and Shell contexts. + +## Affected Artifacts + +- PBS stdlib files +- Syscall declaration generation +- PBS tests and examples diff --git a/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md b/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md new file mode 100644 index 00000000..e148e987 --- /dev/null +++ b/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md @@ -0,0 +1,71 @@ +--- +id: PLN-0086 +ticket: render-frame-packet-boundary +title: End-to-End Render Boundary Validation +status: open +created: 2026-05-25 +ref_decisions: [DEC-0030] +tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] +--- + +## Briefing + +Source decision: `DEC-0030`. + +Add end-to-end validation proving the new boundary works across Game and Shell modes after the granular implementation plans land. + +## Target + +Integration coverage demonstrates typed submissions, app-mode capability gates, immutable closed submissions, render-surface publication, fade removal, and visible output preservation. + +## Scope + +- Add Game end-to-end tests from PBS/syscall production to `Game2DFramePacket` and final pixels. +- Add Shell end-to-end tests from UI production to `ShellUiFramePacket` and final pixels. +- Test wrong-domain capability failures. +- Test closed-submission immutability. +- Test latest-complete-submission-wins behavior. +- Test absence of fade and host debug overlay from canonical paths. + +## Out of Scope + +- Implementing missing production code. +- New rendering features. +- Performance tuning. +- Dirty regions. + +## Execution Sequence + +1. Define representative Game and Shell scenarios that cover composer, `gfx2d`, and `gfxui`. +2. Add integration harness access to inspect closed submissions where appropriate. +3. Assert Game produces `RenderSubmission::Game2D` and Shell produces `RenderSubmission::ShellUi`. +4. Assert wrong-domain syscalls fail before mutating buffers. +5. Assert closed submissions remain unchanged after producers continue mutating the next frame. +6. Assert final pixels match existing expected output for unchanged scenarios. +7. Add searches or tests proving fade and host debug overlay are absent from the active render path. + +## Acceptance Criteria + +- Game and Shell each have end-to-end typed submission tests. +- Capability gates reject `gfxui` in Game and `composer`/`gfx2d` in Shell. +- Closed submissions are immutable snapshots. +- Latest complete submission replaces older unpublished submissions without queue growth. +- Unchanged Game and Shell scenarios remain visually equivalent. + +## Tests / Validation + +- Run full affected Rust test suites. +- Run PBS compile/runtime integration tests. +- Run pixel-equivalence tests for representative Game and Shell output. +- Run `discussion validate` once execution discussion artifacts are updated. + +## Risks + +- End-to-end tests may become brittle if they assert host details; assert the logical contract and final pixels, not internal host implementation names. +- Pixel-equivalence fixtures must account for intentional removals such as host debug overlay. + +## Affected Artifacts + +- Integration tests under `crates/` +- PBS tests +- Render fixtures From 96a23e541911a7e930827c4775879f34b2c0ec27 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 11:35:49 +0100 Subject: [PATCH 02/16] implements PLN-0073 render contract specs --- discussion/index.ndjson | 2 +- .../plans/PLN-0073-render-contract-specs.md | 2 +- docs/specs/runtime/04-gfx-peripheral.md | 220 +++++++----------- ...ortability-and-cross-platform-execution.md | 12 +- .../specs/runtime/16-host-abi-and-syscalls.md | 78 ++++++- 5 files changed, 165 insertions(+), 149 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 7819370f..44cf0312 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0073-render-contract-specs.md b/discussion/workflow/plans/PLN-0073-render-contract-specs.md index b7fdbe33..e71859dc 100644 --- a/discussion/workflow/plans/PLN-0073-render-contract-specs.md +++ b/discussion/workflow/plans/PLN-0073-render-contract-specs.md @@ -2,7 +2,7 @@ id: PLN-0073 ticket: render-frame-packet-boundary title: Render Contract Specs -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] diff --git a/docs/specs/runtime/04-gfx-peripheral.md b/docs/specs/runtime/04-gfx-peripheral.md index 9783fa50..ea591aaf 100644 --- a/docs/specs/runtime/04-gfx-peripheral.md +++ b/docs/specs/runtime/04-gfx-peripheral.md @@ -9,7 +9,29 @@ Didactic companion: [`../learn/mental-model-gfx.md`](../runtime/learn/mental-mod The **GFX** peripheral is responsible for generating images in PROMETEU. -It is an explicit 2D graphics device based on: +`DEC-0030` defines the current logical render boundary. PROMETEU render +production is split from render implementation: + +```text +domain buffers during logical frame +-> RenderManager closes buffers +-> RenderSubmission snapshot +-> render surface / implementation consumes submission +-> RGBA8888 surface publication +``` + +The GFX peripheral remains the classic local raster implementation for +PROMETEU's 2D output. Logical render APIs do not target `Gfx` or the +framebuffer directly. + +`RenderSubmission` is the closed snapshot passed across the render boundary. It +MUST contain a frame id, the active app mode, and one typed packet: +`Game2DFramePacket` for `AppMode::Game` or `ShellUiFramePacket` for +`AppMode::Shell`. Once closed, producers MUST treat the submission as +immutable. The runtime backpressure policy is latest-complete-submission-wins; +it MUST NOT accumulate an unbounded frame queue. + +The current 2D graphics model is based on: - framebuffer - tilemaps @@ -48,19 +70,19 @@ palette, host presentation, or compatibility contract. ## 3. Double Buffering -The GFX maintains two buffers: +The render surface implementation maintains the concrete buffers needed to +publish a frame. The classic software implementation uses: - **Back Buffer** — where the frame is built - **Front Buffer** — where the frame is displayed Per-frame flow: -1. The system prepares the logical frame -2. Canonical game composition is rendered into the back buffer -3. Deferred final overlay/debug primitives are drained on top of the completed game frame -4. Calls `present()` -5. Buffers are swapped -6. The host displays the front buffer +1. Game or Shell code mutates mode-specific domain buffers during the logical frame. +2. `RenderManager` closes the active buffers into an immutable `RenderSubmission`. +3. The render surface implementation consumes the submission and rasterizes to its back buffer. +4. The render surface publishes the completed RGBA8888 surface. +5. The host displays the published surface. This guarantees: @@ -192,14 +214,20 @@ Access: --- -## 10. Canonical Game Projection to the Back Buffer +## 10. Canonical Game Projection -For each frame: +Game mode uses a typed Game 2D submission. `composer.*` owns high-level Game 2D +frame composition: scene binding, camera, sprites, HUD, and frame orchestration. +`gfx2d.*` owns Game 2D primitives only. Both domains are mutable while the +logical frame is being produced and are closed by `RenderManager` into a +`Game2DFramePacket`. + +For each Game 2D packet: 1. For each Tile Layer, in order: - Rasterize visible tiles from the cache - Apply scroll, flip, and transparency - - Write to the back buffer + - Write to the render surface's working buffer 2. Draw sprites: - With priority between layers @@ -207,9 +235,11 @@ For each frame: 3. Draw HUD layer last -This section describes only the canonical game composition path. +4. Draw buffered `gfx2d.*` primitives according to the Game 2D packet contract. -`gfx.*` primitives such as `draw_text`, `draw_line`, and `draw_disc` are not part of this canonical game projection order. In v1 they belong to a deferred final overlay/debug stage that is drained after canonical game composition is complete. +This section describes only the Game 2D packet rendering path. Shell/system UI +uses `gfxui.*` and `ShellUiFramePacket`; it is never part of Game HUD or +`composer.*`. --- @@ -227,15 +257,15 @@ Base order: 4. Tile Layer 3 5. Sprites (by priority between layers) 6. HUD Layer -7. Scene Fade -8. HUD Fade -9. Deferred `gfx.*` overlay/debug primitives +7. Buffered `gfx2d.*` Game primitives Normative boundary: -- Items 1 through 8 belong to canonical game-frame composition. -- Item 9 is a separate overlay/debug stage. -- Deferred `gfx.*` primitives MUST NOT be interpreted as scene, sprite, or canonical HUD content. +- Items 1 through 6 belong to `composer.*` Game-frame composition. +- Item 7 belongs to `gfx2d.*`. +- `gfx2d.*` primitives MUST NOT be interpreted as scene, sprite, camera, HUD, + or frame orchestration. +- Shell/system UI belongs to `gfxui.*` and `ShellUiFramePacket`, not Game HUD. --- @@ -287,7 +317,8 @@ Everything is: - Blending occurs during drawing - For canonical game composition, the result goes to the back buffer during composition -- For deferred `gfx.*` overlay/debug primitives, the result is applied during the final overlay/debug drain stage +- For `gfx2d.*` and `gfxui.*` primitives, the result is applied when the render + surface consumes the closed packet for the active app mode - There is no automatic GPU-style post-processing pipeline --- @@ -301,6 +332,7 @@ By design: - HDR - Gamma correction - RGB565 compatibility framebuffers +- fade fields, fade syscalls, or fade packet members - multi-format backend selection - render-thread ownership as part of this contract @@ -318,104 +350,24 @@ By design: --- -## 17. Special PostFX — Fade (Scene and HUD) +## 17. Transitions and Removed Fade Contract -PROMETEU supports **gradual fade** as a special PostFX, with two independent -controls: +The previous special fade model is not part of the canonical render contract. +Current render packets, syscalls, and ABI domains MUST NOT expose scene fade, +HUD fade, fade levels, fade colors, or equivalent inherited fade state. -- **Scene Fade**: affects the entire scene (Tile Layers 0–3 + Sprites) -- **HUD Fade**: affects only the HUD Layer (always composed last) - -In v1, deferred `gfx.*` overlay/debug primitives are drained after both fades and therefore are not themselves part of scene or HUD fade application. - -The fade uses a **discrete integer level** (0..31), which in practice produces an -"almost continuous" visual result in Prometeu's `480x270` pixel-art framebuffer. - ---- - -### 17.1 Fade Representation - -Each fade is represented by: - -- `fade_level: u8` in the range **[0..31]** - - `0` → fully replaced by the fade color - - `31` → fully visible (no fade) -- `fade_color: RGBA8888` - - color the image will be blended into - -Registers: - -- `SCENE_FADE_LEVEL` (0..31) -- `SCENE_FADE_COLOR` (RGBA8888) -- `HUD_FADE_LEVEL` (0..31) -- `HUD_FADE_COLOR` (RGBA8888) - -Common cases: - -- Fade-out: `fade_color = BLACK` -- Flash/teleport: `fade_color = WHITE` -- Special effects: any RGBA8888 color - ---- - -### 17.2 Fade Operation (Blending with Arbitrary Color) - -For each RGBA8888 pixel `src` and fade color `fc`, the final pixel `dst` is calculated per channel. - -1) Extract components: - -- `src_r8`, `src_g8`, `src_b8`, `src_a8` -- `fc_r8`, `fc_g8`, `fc_b8`, `fc_a8` - -2) Apply integer blending: - -``` -src_weight = fade_level // 0..31 -fc_weight = 31 - fade_level - -r8 = (src_r8 * src_weight + fc_r8 * fc_weight) / 31 -g8 = (src_g8 * src_weight + fc_g8 * fc_weight) / 31 -b8 = (src_b8 * src_weight + fc_b8 * fc_weight) / 31 -a8 = (src_a8 * src_weight + fc_a8 * fc_weight) / 31 -``` -- `src_r8`, `src_g8`, `src_b8`, `src_a8` -- `fc_r8`, `fc_g8`, `fc_b8`, `fc_a8` - -3) Repack: - -``` -dst = pack_rgba8888_rgba(r8, g8, b8, a8) -``` - - -Notes: - -- Deterministic operation -- Integers only -- Can be optimized via LUT - ---- - -### 17.3 Order of Application in the Frame - -The frame composition follows this order: - -1. Rasterize **Tile Layers 0–3** → Back Buffer -2. Rasterize **Sprites** according to priority -3. (Optional) Extra pipeline (Emission/Light/Glow etc.) -4. Apply **Scene Fade** using: - - `SCENE_FADE_LEVEL` - - `SCENE_FADE_COLOR` -5. Rasterize **HUD Layer** -6. Apply **HUD Fade** using: - - `HUD_FADE_LEVEL` - - `HUD_FADE_COLOR` -7. `present()` +Future visual transitions between Shell and Game belong to `RenderManager`. +They are not owned by `composer.*`, `gfx2d.*`, `gfxui.*`, `Game2DFramePacket`, +or `ShellUiFramePacket`. Rules: -- Scene Fade never affects the HUD -- HUD Fade never affects the scene +- render domain packets MUST NOT contain fade fields; +- public render syscalls MUST NOT expose fade controls; +- render implementations MUST NOT treat fade as an inherited post-processing + habit; +- transition work requires an explicit `RenderManager` contract or a later + decision. --- @@ -570,8 +522,13 @@ The system can measure: Graphics-related public ABI in v1 is split between: -- `gfx.*` for direct drawing/backend-oriented operations; -- `composer.*` for frame orchestration operations. +- `composer.*` for Game 2D high-level frame composition; +- `gfx2d.*` for Game 2D primitives only; +- `gfxui.*` for Shell UI primitives only. + +`composer.*` and `gfx2d.*` are available to `AppMode::Game`. +`gfxui.*` is available to `AppMode::Shell`. Renderers and capabilities may +differ by app mode. Only operations with real operational rejection paths return explicit status values. @@ -585,29 +542,32 @@ Fault boundary: | Syscall | Return | Policy basis | | ----------------------- | ------------- | --------------------------------------------------- | -| `gfx.clear` | `void` | no real operational failure path in v1 | -| `gfx.fill_rect` | `void` | no real operational failure path in v1 | -| `gfx.draw_line` | `void` | no real operational failure path in v1 | -| `gfx.draw_circle` | `void` | no real operational failure path in v1 | -| `gfx.draw_disc` | `void` | no real operational failure path in v1 | -| `gfx.draw_square` | `void` | no real operational failure path in v1 | -| `gfx.draw_text` | `void` | no real operational failure path in v1 | +| `gfx2d.clear` | `void` | no real operational failure path in v1 | +| `gfx2d.fill_rect` | `void` | no real operational failure path in v1 | +| `gfx2d.draw_line` | `void` | no real operational failure path in v1 | +| `gfx2d.draw_circle` | `void` | no real operational failure path in v1 | +| `gfx2d.draw_disc` | `void` | no real operational failure path in v1 | +| `gfx2d.draw_square` | `void` | no real operational failure path in v1 | +| `gfx2d.draw_text` | `void` | no real operational failure path in v1 | +| `gfxui.*` primitives | `void` | no real operational failure path in v1 | | `composer.bind_scene` | `status:int` | status-returning API, but missing scene glyph dependencies are fatal runtime errors | | `composer.unbind_scene` | `status:int` | explicit orchestration-domain operational result | | `composer.set_camera` | `void` | no real operational failure path in v1 | | `composer.emit_sprite` | `status:int` | explicit orchestration-domain operational rejection | -### 19.1.a Deferred overlay/debug semantics for `gfx.*` +### 19.1.a Primitive domain semantics -The public `gfx.*` primitive family remains valid in v1, but its stable operational meaning is: +The primitive domains have stable operational meaning: -- deferred final overlay/debug composition; -- screen-space and pipeline-agnostic relative to `composer.*`; -- outside `FrameComposer`; -- above scene, sprites, and canonical HUD; -- drained after `hud_fade`. +- `gfx2d.*` is Game 2D primitive command buffering; +- `gfx2d.*` is screen-space and primitive-only relative to `composer.*`; +- `gfx2d.*` is outside scene, camera, sprites, HUD, and Game 2D frame orchestration; +- `gfxui.*` is Shell UI primitive command buffering; +- `gfxui.*` does not contain widget or layout policy. -This means callers MUST NOT rely on stable immediate writes to the working back buffer as the public contract for `gfx.draw_text(...)` or sibling primitives. +Callers MUST NOT rely on stable immediate writes to the working back buffer as +the public contract for primitive drawing. Primitive calls mutate domain command +buffers that close into the active typed submission. ### 19.1.b Scene dependency fatal boundary diff --git a/docs/specs/runtime/11-portability-and-cross-platform-execution.md b/docs/specs/runtime/11-portability-and-cross-platform-execution.md index 46317f0c..4b8b8a5b 100644 --- a/docs/specs/runtime/11-portability-and-cross-platform-execution.md +++ b/docs/specs/runtime/11-portability-and-cross-platform-execution.md @@ -116,26 +116,26 @@ Hardware differences: The graphics system: -- operates on a logical RGBA8888 framebuffer +- produces typed logical render submissions that render to RGBA8888 output - uses an indexed palette - does not depend on a specific GPU The platform layer: -- only displays the framebuffer -- does not reinterpret graphics commands +- consumes closed render submissions through a render-surface implementation - **may overlay technical HUDs without modifying the logical framebuffer** -- may transport the logical framebuffer into a host presentation surface where a host-only overlay layer is composed +- may transport published RGBA8888 output into a host presentation surface where a host-only overlay layer is composed The host presentation layer MUST treat RGBA8888 as the canonical logical framebuffer format. RGB565 conversion is not part of the normal host presentation contract. -Host presentation SHOULD be driven by published logical frames and explicit host-owned invalidation, not by perpetual redraw polling. +Host presentation SHOULD be driven by published render submissions and explicit +host-owned invalidation, not by perpetual redraw polling. In particular: -- a stable logical frame MAY remain visible across multiple host wakeups without recomposition; +- a stable published submission MAY remain visible across multiple host wakeups without recomposition; - a host MAY redraw the same logical frame again when its own surface is invalidated by resize, expose, or host-only overlay/debug changes; - a host MUST NOT invent intermediate logical frames or require continuous redraw merely to discover whether a new logical frame exists. diff --git a/docs/specs/runtime/16-host-abi-and-syscalls.md b/docs/specs/runtime/16-host-abi-and-syscalls.md index 8a0ddb34..689feada 100644 --- a/docs/specs/runtime/16-host-abi-and-syscalls.md +++ b/docs/specs/runtime/16-host-abi-and-syscalls.md @@ -220,29 +220,78 @@ For `asset.load`: - JSON-on-the-wire bank inspection payloads are not valid public ABI; - `bank.info` returns stack values, not textual structured payloads. -### GFX surface (`gfx`, v1) +### Render surfaces (`composer`, `gfx2d`, `gfxui`, v1) -The public GFX ABI uses format-neutral operation names. API and syscall names -MUST NOT encode RGB565 or any other pixel format suffix. +`DEC-0030` splits logical render production from render implementation. Public +render syscalls mutate app-mode-specific domain buffers. They do not directly +publish a framebuffer and do not call host presentation APIs. + +The canonical flow is: + +```text +domain buffers during logical frame +-> RenderManager closes buffers +-> RenderSubmission snapshot +-> render surface / implementation consumes submission +-> RGBA8888 surface publication +``` + +`RenderSubmission` is a typed closed snapshot containing `frame_id`, `app_mode`, +and exactly one packet: `Game2DFramePacket` for `AppMode::Game` or +`ShellUiFramePacket` for `AppMode::Shell`. Producers may mutate domain buffers +before closure only. Once `RenderManager` closes a submission, producers MUST +NOT mutate it. Backpressure is latest-complete-submission-wins; the runtime +MUST NOT expose an unbounded submission queue as part of the ABI. + +Render API and syscall names MUST NOT encode RGB565 or any other pixel format +suffix. + +#### Game 2D primitive surface (`gfx2d`, v1) + +The `gfx2d` module is a Game profile primitive surface. It is limited to Game +2D primitive commands and does not own scene, camera, sprites, HUD, or frame +orchestration. Canonical operations in v1 include: -- `gfx.clear(color) -> void` -- `gfx.fill_rect(x, y, w, h, color) -> void` -- `gfx.draw_line(x0, y0, x1, y1, color) -> void` -- `gfx.draw_circle(x, y, radius, color) -> void` -- `gfx.draw_disc(x, y, radius, color) -> void` -- `gfx.draw_square(x, y, size, color) -> void` -- `gfx.draw_text(x, y, text, color) -> void` +- `gfx2d.clear(color) -> void` +- `gfx2d.fill_rect(x, y, w, h, color) -> void` +- `gfx2d.draw_line(x0, y0, x1, y1, color) -> void` +- `gfx2d.draw_circle(x, y, radius, color) -> void` +- `gfx2d.draw_disc(x, y, radius, color) -> void` +- `gfx2d.draw_square(x, y, size, color) -> void` +- `gfx2d.draw_text(x, y, text, color) -> void` + +#### Shell UI primitive surface (`gfxui`, v1) + +The `gfxui` module is a Shell profile primitive surface. It contains Shell UI +primitive commands only. Widget and layout policy belongs in Shell/UI code or +stdlib code, not in the host/render-surface implementation. + +Canonical operations in v1 include primitive shapes equivalent to the subset +needed by Shell UI: + +- `gfxui.clear(color) -> void` +- `gfxui.fill_rect(x, y, w, h, color) -> void` +- `gfxui.draw_line(x0, y0, x1, y1, color) -> void` +- `gfxui.draw_circle(x, y, radius, color) -> void` +- `gfxui.draw_disc(x, y, radius, color) -> void` +- `gfxui.draw_square(x, y, size, color) -> void` +- `gfxui.draw_text(x, y, text, color) -> void` Rules: - `color` arguments are packed RGBA8888 values using RGBA channel order. - RGB565 raw values are not a public ABI contract. -- `Gfx*565` syscall names and `gfx.*_565` canonical identities are not valid +- `Gfx*565` syscall names and `gfx*.*_565` canonical identities are not valid current ABI. - The runtime framebuffer exposed across the host boundary is RGBA8888, not `u16` RGB565. +- The old primitive `gfx.*` ABI is not the canonical render domain. PBS stdlibs + may expose ergonomic high-level names, but generated host bindings must map + to `gfx2d.*` in Game mode or `gfxui.*` in Shell mode. +- Render packets and public render syscalls MUST NOT expose fade fields or fade + controls. ### Composition surface (`composer`, v1) @@ -251,6 +300,11 @@ The canonical frame-orchestration public ABI uses module `composer`. The `composer` module is a game profile surface. It is not inherited by `System` profile apps as their public UI/composition API. +`composer` owns Game 2D high-level frame composition: scene binding, camera, +sprites, HUD, and Game 2D frame orchestration. It closes into a +`Game2DFramePacket` through `RenderManager`; it does not publish or present a +framebuffer directly. + Canonical operations in v1 are: - `composer.bind_scene(bank_id) -> (status)` @@ -264,6 +318,8 @@ For mutating composer operations: - `bind_scene`, `unbind_scene`, and `emit_sprite` are status-returning; - `set_camera` remains `void` in v1; - no caller-provided sprite index or `active` flag is part of the canonical contract. +- no fade level, fade color, or transition control is part of the canonical + composer ABI. Fatal boundary clarification for `bind_scene`: From 105a6de11e661386604fc1a2873ef14bd55568e0 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 11:37:58 +0100 Subject: [PATCH 03/16] implements PLN-0074 HAL render submissions --- crates/console/prometeu-hal/src/lib.rs | 6 + .../prometeu-hal/src/render_submission.rs | 203 ++++++++++++++++++ discussion/index.ndjson | 2 +- .../PLN-0074-hal-render-submission-types.md | 2 +- 4 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 crates/console/prometeu-hal/src/render_submission.rs diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index fe0cd2e9..a9e65eef 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -20,6 +20,7 @@ pub mod native_helpers; pub mod native_interface; pub mod pad_bridge; pub mod primitives; +pub mod render_submission; pub mod sample; pub mod scene_bank; pub mod scene_layer; @@ -45,4 +46,9 @@ pub use input_signals::InputSignals; pub use native_helpers::{expect_bool, expect_int}; pub use native_interface::{NativeInterface, SyscallId}; pub use pad_bridge::PadBridge; +pub use render_submission::{ + BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket, + Gfx2dCommand, GfxUiCommand, HudCommand, HudPacket, RenderSubmission, RenderSubmissionPacket, + ShellUiFramePacket, +}; pub use touch_bridge::TouchBridge; diff --git a/crates/console/prometeu-hal/src/render_submission.rs b/crates/console/prometeu-hal/src/render_submission.rs new file mode 100644 index 00000000..3427f581 --- /dev/null +++ b/crates/console/prometeu-hal/src/render_submission.rs @@ -0,0 +1,203 @@ +use crate::app_mode::AppMode; +use crate::color::Color; +use crate::primitives::Rect; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct FrameId(pub u64); + +impl FrameId { + pub const ZERO: Self = Self(0); + + pub const fn new(value: u64) -> Self { + Self(value) + } + + pub const fn get(self) -> u64 { + self.0 + } + + pub const fn next(self) -> Self { + Self(self.0.wrapping_add(1)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RenderSubmission { + pub frame_id: FrameId, + pub app_mode: AppMode, + pub packet: RenderSubmissionPacket, +} + +impl RenderSubmission { + pub const fn game2d(frame_id: FrameId, packet: Game2DFramePacket) -> Self { + Self { frame_id, app_mode: AppMode::Game, packet: RenderSubmissionPacket::Game2D(packet) } + } + + pub const fn shell_ui(frame_id: FrameId, packet: ShellUiFramePacket) -> Self { + Self { frame_id, app_mode: AppMode::Shell, packet: RenderSubmissionPacket::ShellUi(packet) } + } + + pub const fn is_coherent(&self) -> bool { + matches!( + (self.app_mode, &self.packet), + (AppMode::Game, RenderSubmissionPacket::Game2D(_)) + | (AppMode::Shell, RenderSubmissionPacket::ShellUi(_)) + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RenderSubmissionPacket { + Game2D(Game2DFramePacket), + ShellUi(ShellUiFramePacket), +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Game2DFramePacket { + pub composer: ComposerFramePacket, + pub gfx2d: Vec, +} + +impl Game2DFramePacket { + pub fn new(composer: ComposerFramePacket, gfx2d: Vec) -> Self { + Self { composer, gfx2d } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ShellUiFramePacket { + pub commands: Vec, +} + +impl ShellUiFramePacket { + pub fn new(commands: Vec) -> Self { + Self { commands } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ComposerFramePacket { + pub bound_scene: Option, + pub camera: Camera2D, + pub sprites: Vec, + pub hud: HudPacket, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BoundScenePacket { + pub bank_id: i32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Camera2D { + pub x: i32, + pub y: i32, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GameSpritePacket { + pub glyph_id: i32, + pub palette_id: i32, + pub x: i32, + pub y: i32, + pub layer: i32, + pub bank_id: i32, + pub flip_x: bool, + pub flip_y: bool, + pub priority: i32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct HudPacket { + pub commands: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum HudCommand { + Clear { color: Color }, + FillRect { rect: Rect, color: Color }, + DrawText { x: i32, y: i32, text: String, color: Color }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Gfx2dCommand { + Clear { color: Color }, + FillRect { rect: Rect, color: Color }, + DrawLine { x0: i32, y0: i32, x1: i32, y1: i32, color: Color }, + DrawCircle { x: i32, y: i32, radius: i32, color: Color }, + DrawDisc { x: i32, y: i32, radius: i32, border_color: Color, fill_color: Color }, + DrawSquare { rect: Rect, border_color: Color, fill_color: Color }, + DrawText { x: i32, y: i32, text: String, color: Color }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GfxUiCommand { + Clear { color: Color }, + FillRect { rect: Rect, color: Color }, + DrawLine { x0: i32, y0: i32, x1: i32, y1: i32, color: Color }, + DrawCircle { x: i32, y: i32, radius: i32, color: Color }, + DrawDisc { x: i32, y: i32, radius: i32, border_color: Color, fill_color: Color }, + DrawSquare { rect: Rect, border_color: Color, fill_color: Color }, + DrawText { x: i32, y: i32, text: String, color: Color }, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn constructors_pin_app_mode_to_packet_kind() { + let game = RenderSubmission::game2d(FrameId::new(7), Game2DFramePacket::default()); + assert_eq!(game.app_mode, AppMode::Game); + assert!(matches!(game.packet, RenderSubmissionPacket::Game2D(_))); + assert!(game.is_coherent()); + + let shell = + RenderSubmission::shell_ui(FrameId::new(8), ShellUiFramePacket::new(Vec::new())); + assert_eq!(shell.app_mode, AppMode::Shell); + assert!(matches!(shell.packet, RenderSubmissionPacket::ShellUi(_))); + assert!(shell.is_coherent()); + } + + #[test] + fn coherence_check_rejects_mismatched_envelope() { + let submission = RenderSubmission { + frame_id: FrameId::new(1), + app_mode: AppMode::Game, + packet: RenderSubmissionPacket::ShellUi(ShellUiFramePacket::new(Vec::new())), + }; + + assert!(!submission.is_coherent()); + } + + #[test] + fn closed_submission_owns_command_payload() { + let mut commands = vec![Gfx2dCommand::DrawText { + x: 1, + y: 2, + text: "before".to_string(), + color: Color::WHITE, + }]; + let submission = RenderSubmission::game2d( + FrameId::new(2), + Game2DFramePacket::new(Default::default(), commands.clone()), + ); + + commands.push(Gfx2dCommand::Clear { color: Color::BLACK }); + + let RenderSubmissionPacket::Game2D(packet) = submission.packet else { + panic!("expected game packet"); + }; + assert_eq!(packet.gfx2d.len(), 1); + assert_eq!( + packet.gfx2d[0], + Gfx2dCommand::DrawText { x: 1, y: 2, text: "before".to_string(), color: Color::WHITE } + ); + } + + #[test] + fn frame_id_advances_monotonically_with_wrapping_arithmetic() { + assert_eq!(FrameId::new(41).next(), FrameId::new(42)); + assert_eq!(FrameId::new(u64::MAX).next(), FrameId::ZERO); + } +} diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 44cf0312..e6882b90 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md b/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md index 9909f207..d405e426 100644 --- a/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md +++ b/discussion/workflow/plans/PLN-0074-hal-render-submission-types.md @@ -2,7 +2,7 @@ id: PLN-0074 ticket: render-frame-packet-boundary title: HAL Render Submission Types -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From c3396f5a613fc04f010f9e64d30301b6f3667210 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 13:57:46 +0100 Subject: [PATCH 04/16] implements PLN-0075 RenderManager core --- .../src/services/vm_runtime/lifecycle.rs | 3 + .../src/services/vm_runtime/mod.rs | 3 + .../src/services/vm_runtime/render_manager.rs | 188 ++++++++++++++++++ .../src/services/vm_runtime/tick.rs | 3 + discussion/index.ndjson | 2 +- .../plans/PLN-0075-rendermanager-core.md | 2 +- 6 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 crates/console/prometeu-system/src/services/vm_runtime/render_manager.rs diff --git a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs index f74810f8..da1da345 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs @@ -22,6 +22,7 @@ impl VirtualMachineRuntime { current_cartridge_title: String::new(), current_cartridge_app_version: String::new(), current_cartridge_app_mode: AppMode::Game, + render_manager: RenderManager::new(AppMode::Game), logs_written_this_frame: HashMap::new(), atomic_telemetry, last_crash_report: None, @@ -116,6 +117,7 @@ impl VirtualMachineRuntime { self.current_cartridge_title.clear(); self.current_cartridge_app_version.clear(); self.current_cartridge_app_mode = AppMode::Game; + self.render_manager = RenderManager::new(AppMode::Game); self.logs_written_this_frame.clear(); self.last_crash_report = None; @@ -148,6 +150,7 @@ impl VirtualMachineRuntime { self.current_cartridge_title = cartridge.title.clone(); self.current_cartridge_app_version = cartridge.app_version.clone(); self.current_cartridge_app_mode = cartridge.app_mode; + self.render_manager.set_active_app_mode(cartridge.app_mode); Ok(()) } Err(e) => { diff --git a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs index 19e55af7..85bbf336 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs @@ -1,5 +1,6 @@ mod dispatch; mod lifecycle; +pub mod render_manager; #[cfg(test)] mod tests; mod tick; @@ -10,6 +11,7 @@ use prometeu_hal::app_mode::AppMode; use prometeu_hal::log::LogService; use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier}; use prometeu_vm::VirtualMachine; +pub use render_manager::RenderManager; use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::AtomicU32; @@ -25,6 +27,7 @@ pub struct VirtualMachineRuntime { pub current_cartridge_title: String, pub current_cartridge_app_version: String, pub current_cartridge_app_mode: AppMode, + pub render_manager: RenderManager, pub logs_written_this_frame: HashMap, pub atomic_telemetry: Arc, pub last_crash_report: Option, diff --git a/crates/console/prometeu-system/src/services/vm_runtime/render_manager.rs b/crates/console/prometeu-system/src/services/vm_runtime/render_manager.rs new file mode 100644 index 00000000..10d24bdc --- /dev/null +++ b/crates/console/prometeu-system/src/services/vm_runtime/render_manager.rs @@ -0,0 +1,188 @@ +use prometeu_hal::app_mode::AppMode; +use prometeu_hal::{ + FrameId, Game2DFramePacket, RenderSubmission, RenderSubmissionPacket, ShellUiFramePacket, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RenderTransitionState { + Idle, + Pending { from: AppMode, to: AppMode }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RenderSubmissionError { + PacketAppModeMismatch { active: AppMode }, +} + +pub trait RenderSurface { + fn consume_submission(&mut self, submission: &RenderSubmission); +} + +#[derive(Debug, Clone)] +pub struct RenderManager { + active_app_mode: AppMode, + next_frame_id: FrameId, + latest_complete_submission: Option, + transition_state: RenderTransitionState, +} + +impl RenderManager { + pub fn new(active_app_mode: AppMode) -> Self { + Self { + active_app_mode, + next_frame_id: FrameId::ZERO, + latest_complete_submission: None, + transition_state: RenderTransitionState::Idle, + } + } + + pub fn active_app_mode(&self) -> AppMode { + self.active_app_mode + } + + pub fn transition_state(&self) -> RenderTransitionState { + self.transition_state + } + + pub fn latest_complete_submission(&self) -> Option<&RenderSubmission> { + self.latest_complete_submission.as_ref() + } + + pub fn set_active_app_mode(&mut self, app_mode: AppMode) { + if self.active_app_mode == app_mode { + return; + } + + let previous = self.active_app_mode; + self.active_app_mode = app_mode; + self.transition_state = RenderTransitionState::Pending { from: previous, to: app_mode }; + } + + pub fn acknowledge_transition(&mut self) { + self.transition_state = RenderTransitionState::Idle; + } + + pub fn close_frame_with_packet( + &mut self, + packet: RenderSubmissionPacket, + ) -> Result<&RenderSubmission, RenderSubmissionError> { + let frame_id = self.next_frame_id; + let submission = match (self.active_app_mode, packet) { + (AppMode::Game, RenderSubmissionPacket::Game2D(packet)) => { + RenderSubmission::game2d(frame_id, packet) + } + (AppMode::Shell, RenderSubmissionPacket::ShellUi(packet)) => { + RenderSubmission::shell_ui(frame_id, packet) + } + (active, _) => { + return Err(RenderSubmissionError::PacketAppModeMismatch { active }); + } + }; + + self.next_frame_id = self.next_frame_id.next(); + self.latest_complete_submission = Some(submission); + Ok(self.latest_complete_submission.as_ref().expect("submission was just stored")) + } + + pub fn close_compat_frame(&mut self) -> &RenderSubmission { + let packet = match self.active_app_mode { + AppMode::Game => RenderSubmissionPacket::Game2D(Game2DFramePacket::default()), + AppMode::Shell => RenderSubmissionPacket::ShellUi(ShellUiFramePacket::new(Vec::new())), + }; + + self.close_frame_with_packet(packet).expect("compat packet matches active app mode") + } + + pub fn publish_latest(&self, surface: &mut S) -> bool { + let Some(submission) = self.latest_complete_submission.as_ref() else { + return false; + }; + surface.consume_submission(submission); + true + } +} + +impl Default for RenderManager { + fn default() -> Self { + Self::new(AppMode::Game) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Default)] + struct RecordingSurface { + seen: Vec, + } + + impl RenderSurface for RecordingSurface { + fn consume_submission(&mut self, submission: &RenderSubmission) { + self.seen.push(submission.frame_id); + } + } + + #[test] + fn frame_closure_assigns_monotonic_frame_ids() { + let mut manager = RenderManager::new(AppMode::Game); + + let first = manager.close_compat_frame().frame_id; + let second = manager.close_compat_frame().frame_id; + + assert_eq!(first, FrameId::new(0)); + assert_eq!(second, FrameId::new(1)); + } + + #[test] + fn latest_complete_submission_wins_without_queue_growth() { + let mut manager = RenderManager::new(AppMode::Game); + + manager.close_compat_frame(); + manager.close_compat_frame(); + + let latest = manager.latest_complete_submission().expect("latest submission"); + assert_eq!(latest.frame_id, FrameId::new(1)); + } + + #[test] + fn rejects_packet_that_does_not_match_active_app_mode() { + let mut manager = RenderManager::new(AppMode::Game); + + let err = manager + .close_frame_with_packet(RenderSubmissionPacket::ShellUi(ShellUiFramePacket::new( + Vec::new(), + ))) + .expect_err("shell packet is invalid in game mode"); + + assert_eq!(err, RenderSubmissionError::PacketAppModeMismatch { active: AppMode::Game }); + } + + #[test] + fn app_mode_switch_records_noop_transition_placeholder() { + let mut manager = RenderManager::new(AppMode::Game); + + manager.set_active_app_mode(AppMode::Shell); + + assert_eq!(manager.active_app_mode(), AppMode::Shell); + assert_eq!( + manager.transition_state(), + RenderTransitionState::Pending { from: AppMode::Game, to: AppMode::Shell } + ); + + manager.acknowledge_transition(); + assert_eq!(manager.transition_state(), RenderTransitionState::Idle); + } + + #[test] + fn publish_latest_hands_submission_to_surface() { + let mut manager = RenderManager::new(AppMode::Game); + let mut surface = RecordingSurface::default(); + + assert!(!manager.publish_latest(&mut surface)); + manager.close_compat_frame(); + assert!(manager.publish_latest(&mut surface)); + + assert_eq!(surface.seen, vec![FrameId::new(0)]); + } +} diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index 3456bf7a..a4e189e7 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -220,6 +220,9 @@ impl VirtualMachineRuntime { if run.reason == LogicalFrameEndingReason::FrameSync || run.reason == LogicalFrameEndingReason::EndOfRom { + self.render_manager.set_active_app_mode(self.current_cartridge_app_mode); + self.render_manager.close_compat_frame(); + if let Err(payload) = catch_unwind(AssertUnwindSafe(|| hw.render_frame())) { let message = Self::host_panic_payload_to_string(payload); let report = CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) }; diff --git a/discussion/index.ndjson b/discussion/index.ndjson index e6882b90..bae22d2d 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0075-rendermanager-core.md b/discussion/workflow/plans/PLN-0075-rendermanager-core.md index a2274b63..c5dba1a6 100644 --- a/discussion/workflow/plans/PLN-0075-rendermanager-core.md +++ b/discussion/workflow/plans/PLN-0075-rendermanager-core.md @@ -2,7 +2,7 @@ id: PLN-0075 ticket: render-frame-packet-boundary title: RenderManager Core -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 7c35edcb847d33afa13e70fd0ad000ea1b4e4cde Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:02:37 +0100 Subject: [PATCH 05/16] implements PLN-0076 render capability split --- crates/console/prometeu-hal/src/cartridge.rs | 3 + .../prometeu-hal/src/cartridge_loader.rs | 3 + crates/console/prometeu-hal/src/syscalls.rs | 9 +- .../console/prometeu-hal/src/syscalls/caps.rs | 7 +- .../src/syscalls/domains/composer.rs | 8 +- .../prometeu-hal/src/syscalls/domains/gfx.rs | 28 ++--- .../src/syscalls/domains/gfxui.rs | 32 ++++++ .../prometeu-hal/src/syscalls/domains/mod.rs | 2 + .../prometeu-hal/src/syscalls/registry.rs | 14 +++ .../prometeu-hal/src/syscalls/tests.rs | 61 +++++++---- .../src/services/vm_runtime/dispatch.rs | 100 ++++++++++++++++++ .../src/services/vm_runtime/tests.rs | 65 ++++++++++-- .../prometeu-vm/src/virtual_machine.rs | 26 ++--- crates/tools/pbxgen-stress/src/lib.rs | 4 +- discussion/index.ndjson | 2 +- ...-0076-capabilities-and-abi-domain-split.md | 2 +- 16 files changed, 297 insertions(+), 69 deletions(-) create mode 100644 crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs diff --git a/crates/console/prometeu-hal/src/cartridge.rs b/crates/console/prometeu-hal/src/cartridge.rs index 42c7f735..972c755f 100644 --- a/crates/console/prometeu-hal/src/cartridge.rs +++ b/crates/console/prometeu-hal/src/cartridge.rs @@ -286,6 +286,9 @@ pub enum Capability { None, System, Gfx, + Composer, + Gfx2d, + Gfxui, Audio, Fs, Log, diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index 2b287f96..2fe4f836 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -117,6 +117,9 @@ fn normalize_capabilities(capabilities: &[Capability]) -> Result caps::NONE, Capability::System => caps::SYSTEM, Capability::Gfx => caps::GFX, + Capability::Composer => caps::COMPOSER, + Capability::Gfx2d => caps::GFX2D, + Capability::Gfxui => caps::GFXUI, Capability::Audio => caps::AUDIO, Capability::Fs => caps::FS, Capability::Log => caps::LOG, diff --git a/crates/console/prometeu-hal/src/syscalls.rs b/crates/console/prometeu-hal/src/syscalls.rs index 4098ed40..73ae59cf 100644 --- a/crates/console/prometeu-hal/src/syscalls.rs +++ b/crates/console/prometeu-hal/src/syscalls.rs @@ -18,7 +18,7 @@ pub use resolver::{ /// /// Each Syscall has a unique 32-bit ID. The IDs are grouped by category: /// - **0x0xxx**: System & OS Control -/// - **0x1xxx**: Graphics (GFX) +/// - **0x1xxx**: Game 2D primitives (`gfx2d`) and Shell UI primitives (`gfxui`) /// - **0x11xx**: Frame Composer orchestration /// - **0x2xxx**: Reserved for legacy input syscalls (disabled for v1 VM-owned input) /// - **0x3xxx**: Audio (PCM & Mixing) @@ -37,6 +37,13 @@ pub enum Syscall { GfxDrawDisc = 0x1005, GfxDrawSquare = 0x1006, GfxDrawText = 0x1008, + GfxUiClear = 0x1201, + GfxUiFillRect = 0x1202, + GfxUiDrawLine = 0x1203, + GfxUiDrawCircle = 0x1204, + GfxUiDrawDisc = 0x1205, + GfxUiDrawSquare = 0x1206, + GfxUiDrawText = 0x1208, ComposerBindScene = 0x1101, ComposerUnbindScene = 0x1102, ComposerSetCamera = 0x1103, diff --git a/crates/console/prometeu-hal/src/syscalls/caps.rs b/crates/console/prometeu-hal/src/syscalls/caps.rs index 3230b1ce..8e2e0611 100644 --- a/crates/console/prometeu-hal/src/syscalls/caps.rs +++ b/crates/console/prometeu-hal/src/syscalls/caps.rs @@ -2,10 +2,13 @@ use super::CapFlags; pub const NONE: CapFlags = 0; pub const SYSTEM: CapFlags = 1 << 0; -pub const GFX: CapFlags = 1 << 1; +pub const COMPOSER: CapFlags = 1 << 1; pub const AUDIO: CapFlags = 1 << 2; pub const FS: CapFlags = 1 << 3; pub const LOG: CapFlags = 1 << 4; pub const ASSET: CapFlags = 1 << 5; pub const BANK: CapFlags = 1 << 6; -pub const ALL: CapFlags = SYSTEM | GFX | AUDIO | FS | LOG | ASSET | BANK; +pub const GFX2D: CapFlags = 1 << 7; +pub const GFXUI: CapFlags = 1 << 8; +pub const GFX: CapFlags = COMPOSER | GFX2D; +pub const ALL: CapFlags = SYSTEM | COMPOSER | AUDIO | FS | LOG | ASSET | BANK | GFX2D | GFXUI; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/composer.rs b/crates/console/prometeu-hal/src/syscalls/domains/composer.rs index 32a53a95..cdeb4d2d 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/composer.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/composer.rs @@ -4,19 +4,19 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ SyscallRegistryEntry::builder(Syscall::ComposerBindScene, "composer", "bind_scene") .args(1) .rets(1) - .caps(caps::GFX) + .caps(caps::COMPOSER) .cost(5), SyscallRegistryEntry::builder(Syscall::ComposerUnbindScene, "composer", "unbind_scene") .rets(1) - .caps(caps::GFX) + .caps(caps::COMPOSER) .cost(2), SyscallRegistryEntry::builder(Syscall::ComposerSetCamera, "composer", "set_camera") .args(2) - .caps(caps::GFX) + .caps(caps::COMPOSER) .cost(2), SyscallRegistryEntry::builder(Syscall::ComposerEmitSprite, "composer", "emit_sprite") .args(9) .rets(1) - .caps(caps::GFX) + .caps(caps::COMPOSER) .cost(5), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs b/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs index d45bc619..9f531993 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs @@ -1,32 +1,32 @@ use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - SyscallRegistryEntry::builder(Syscall::GfxClear, "gfx", "clear") + SyscallRegistryEntry::builder(Syscall::GfxClear, "gfx2d", "clear") .args(1) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(20), - SyscallRegistryEntry::builder(Syscall::GfxFillRect, "gfx", "fill_rect") + SyscallRegistryEntry::builder(Syscall::GfxFillRect, "gfx2d", "fill_rect") .args(5) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(20), - SyscallRegistryEntry::builder(Syscall::GfxDrawLine, "gfx", "draw_line") + SyscallRegistryEntry::builder(Syscall::GfxDrawLine, "gfx2d", "draw_line") .args(5) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(5), - SyscallRegistryEntry::builder(Syscall::GfxDrawCircle, "gfx", "draw_circle") + SyscallRegistryEntry::builder(Syscall::GfxDrawCircle, "gfx2d", "draw_circle") .args(4) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(5), - SyscallRegistryEntry::builder(Syscall::GfxDrawDisc, "gfx", "draw_disc") + SyscallRegistryEntry::builder(Syscall::GfxDrawDisc, "gfx2d", "draw_disc") .args(5) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(5), - SyscallRegistryEntry::builder(Syscall::GfxDrawSquare, "gfx", "draw_square") + SyscallRegistryEntry::builder(Syscall::GfxDrawSquare, "gfx2d", "draw_square") .args(6) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(5), - SyscallRegistryEntry::builder(Syscall::GfxDrawText, "gfx", "draw_text") + SyscallRegistryEntry::builder(Syscall::GfxDrawText, "gfx2d", "draw_text") .args(4) - .caps(caps::GFX) + .caps(caps::GFX2D) .cost(20), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs b/crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs new file mode 100644 index 00000000..59e96c45 --- /dev/null +++ b/crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs @@ -0,0 +1,32 @@ +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; + +pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ + SyscallRegistryEntry::builder(Syscall::GfxUiClear, "gfxui", "clear") + .args(1) + .caps(caps::GFXUI) + .cost(20), + SyscallRegistryEntry::builder(Syscall::GfxUiFillRect, "gfxui", "fill_rect") + .args(5) + .caps(caps::GFXUI) + .cost(20), + SyscallRegistryEntry::builder(Syscall::GfxUiDrawLine, "gfxui", "draw_line") + .args(5) + .caps(caps::GFXUI) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxUiDrawCircle, "gfxui", "draw_circle") + .args(4) + .caps(caps::GFXUI) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxUiDrawDisc, "gfxui", "draw_disc") + .args(5) + .caps(caps::GFXUI) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxUiDrawSquare, "gfxui", "draw_square") + .args(6) + .caps(caps::GFXUI) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxUiDrawText, "gfxui", "draw_text") + .args(4) + .caps(caps::GFXUI) + .cost(20), +]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/mod.rs b/crates/console/prometeu-hal/src/syscalls/domains/mod.rs index 00e896f1..9a2b6428 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/mod.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/mod.rs @@ -4,6 +4,7 @@ mod bank; mod composer; mod fs; mod gfx; +mod gfxui; mod log; mod system; @@ -13,6 +14,7 @@ pub(crate) fn all_entries() -> impl Iterator Some(Self::GfxDrawDisc), 0x1006 => Some(Self::GfxDrawSquare), 0x1008 => Some(Self::GfxDrawText), + 0x1201 => Some(Self::GfxUiClear), + 0x1202 => Some(Self::GfxUiFillRect), + 0x1203 => Some(Self::GfxUiDrawLine), + 0x1204 => Some(Self::GfxUiDrawCircle), + 0x1205 => Some(Self::GfxUiDrawDisc), + 0x1206 => Some(Self::GfxUiDrawSquare), + 0x1208 => Some(Self::GfxUiDrawText), 0x1101 => Some(Self::ComposerBindScene), 0x1102 => Some(Self::ComposerUnbindScene), 0x1103 => Some(Self::ComposerSetCamera), @@ -70,6 +77,13 @@ impl Syscall { Self::GfxDrawDisc => "GfxDrawDisc", Self::GfxDrawSquare => "GfxDrawSquare", Self::GfxDrawText => "GfxDrawText", + Self::GfxUiClear => "GfxUiClear", + Self::GfxUiFillRect => "GfxUiFillRect", + Self::GfxUiDrawLine => "GfxUiDrawLine", + Self::GfxUiDrawCircle => "GfxUiDrawCircle", + Self::GfxUiDrawDisc => "GfxUiDrawDisc", + Self::GfxUiDrawSquare => "GfxUiDrawSquare", + Self::GfxUiDrawText => "GfxUiDrawText", Self::ComposerBindScene => "ComposerBindScene", Self::ComposerUnbindScene => "ComposerUnbindScene", Self::ComposerSetCamera => "ComposerSetCamera", diff --git a/crates/console/prometeu-hal/src/syscalls/tests.rs b/crates/console/prometeu-hal/src/syscalls/tests.rs index 97677f16..64e7434a 100644 --- a/crates/console/prometeu-hal/src/syscalls/tests.rs +++ b/crates/console/prometeu-hal/src/syscalls/tests.rs @@ -39,23 +39,23 @@ fn every_syscall_has_metadata() { #[test] fn resolver_returns_expected_id_for_known_identity() { - let id = resolve_syscall("gfx", "clear", 1).expect("known identity must resolve"); + let id = resolve_syscall("gfx2d", "clear", 1).expect("known identity must resolve"); assert_eq!(id.id, 0x1001); - assert_eq!(id.meta.module, "gfx"); + assert_eq!(id.meta.module, "gfx2d"); assert_eq!(id.meta.name, "clear"); assert_eq!(id.meta.version, 1); } #[test] fn resolver_rejects_unknown_identity() { - let res = resolve_syscall("gfx", "nonexistent", 1); + let res = resolve_syscall("gfx2d", "nonexistent", 1); assert!(res.is_none()); - let requested = [SyscallIdentity { module: "gfx", name: "nonexistent", version: 1 }]; + let requested = [SyscallIdentity { module: "gfx2d", name: "nonexistent", version: 1 }]; let err = resolve_program_syscalls(&requested, 0).unwrap_err(); match err { LoadError::UnknownSyscall { module, name, version } => { - assert_eq!(module, "gfx"); + assert_eq!(module, "gfx2d"); assert_eq!(name, "nonexistent"); assert_eq!(version, 1); } @@ -65,23 +65,23 @@ fn resolver_rejects_unknown_identity() { #[test] fn resolver_rejects_removed_legacy_gfx_set_sprite_identity() { - assert!(resolve_syscall("gfx", "set_sprite", 1).is_none()); + assert!(resolve_syscall("gfx2d", "set_sprite", 1).is_none()); - let requested = [SyscallIdentity { module: "gfx", name: "set_sprite", version: 1 }]; + let requested = [SyscallIdentity { module: "gfx2d", name: "set_sprite", version: 1 }]; let err = resolve_program_syscalls(&requested, caps::ALL).unwrap_err(); assert_eq!( err, - LoadError::UnknownSyscall { module: "gfx".into(), name: "set_sprite".into(), version: 1 } + LoadError::UnknownSyscall { module: "gfx2d".into(), name: "set_sprite".into(), version: 1 } ); } #[test] fn resolver_enforces_capabilities() { - let requested = [SyscallIdentity { module: "gfx", name: "clear", version: 1 }]; + let requested = [SyscallIdentity { module: "gfx2d", name: "clear", version: 1 }]; let err = resolve_program_syscalls(&requested, 0).unwrap_err(); match err { LoadError::MissingCapability { required, provided, module, name, version } => { - assert_eq!(module, "gfx"); + assert_eq!(module, "gfx2d"); assert_eq!(name, "clear"); assert_eq!(version, 1); assert_ne!(required, 0); @@ -90,15 +90,30 @@ fn resolver_enforces_capabilities() { _ => panic!("expected MissingCapability error"), } - let ok = resolve_program_syscalls(&requested, caps::GFX).expect("must resolve with caps"); + let ok = resolve_program_syscalls(&requested, caps::GFX2D).expect("must resolve with caps"); assert_eq!(ok.len(), 1); assert_eq!(ok[0].id, 0x1001); } +#[test] +fn resolver_exposes_split_render_capabilities() { + let composer = resolve_syscall("composer", "bind_scene", 1).expect("composer resolves"); + assert_eq!(composer.meta.caps, caps::COMPOSER); + + let gfx2d = resolve_syscall("gfx2d", "clear", 1).expect("gfx2d resolves"); + assert_eq!(gfx2d.meta.caps, caps::GFX2D); + + let gfxui = resolve_syscall("gfxui", "clear", 1).expect("gfxui resolves"); + assert_eq!(gfxui.id, 0x1201); + assert_eq!(gfxui.meta.caps, caps::GFXUI); + + assert!(resolve_syscall("gfx", "clear", 1).is_none()); +} + #[test] fn declared_resolver_returns_expected_id_for_known_identity() { let declared = [prometeu_bytecode::SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -106,7 +121,7 @@ fn declared_resolver_returns_expected_id_for_known_identity() { }]; let ok = - resolve_declared_program_syscalls(&declared, caps::GFX).expect("must resolve with ABI"); + resolve_declared_program_syscalls(&declared, caps::GFX2D).expect("must resolve with ABI"); assert_eq!(ok.len(), 1); assert_eq!(ok[0].id, 0x1001); } @@ -114,18 +129,18 @@ fn declared_resolver_returns_expected_id_for_known_identity() { #[test] fn declared_resolver_rejects_unknown_identity() { let declared = [prometeu_bytecode::SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "nonexistent".into(), version: 1, arg_slots: 1, ret_slots: 0, }]; - let err = resolve_declared_program_syscalls(&declared, caps::GFX).unwrap_err(); + let err = resolve_declared_program_syscalls(&declared, caps::GFX2D).unwrap_err(); assert_eq!( err, DeclaredLoadError::UnknownSyscall { - module: "gfx".into(), + module: "gfx2d".into(), name: "nonexistent".into(), version: 1, } @@ -135,7 +150,7 @@ fn declared_resolver_rejects_unknown_identity() { #[test] fn declared_resolver_rejects_missing_capability() { let declared = [prometeu_bytecode::SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -146,9 +161,9 @@ fn declared_resolver_rejects_missing_capability() { assert_eq!( err, DeclaredLoadError::MissingCapability { - required: caps::GFX, + required: caps::GFX2D, provided: caps::NONE, - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, } @@ -158,18 +173,18 @@ fn declared_resolver_rejects_missing_capability() { #[test] fn declared_resolver_rejects_abi_mismatch() { let declared = [prometeu_bytecode::SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_line".into(), version: 1, arg_slots: 4, ret_slots: 0, }]; - let err = resolve_declared_program_syscalls(&declared, caps::GFX).unwrap_err(); + let err = resolve_declared_program_syscalls(&declared, caps::GFX2D).unwrap_err(); assert_eq!( err, DeclaredLoadError::AbiMismatch { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_line".into(), version: 1, declared_arg_slots: 4, @@ -386,7 +401,7 @@ fn declared_resolver_accepts_mixed_status_first_surface_as_a_single_module() { ]; let resolved = - resolve_declared_program_syscalls(&declared, caps::GFX | caps::AUDIO | caps::ASSET) + resolve_declared_program_syscalls(&declared, caps::COMPOSER | caps::AUDIO | caps::ASSET) .expect("mixed status-first surface must resolve together"); assert_eq!(resolved.len(), declared.len()); diff --git a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs index 7501ced7..ef13ad2c 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -188,6 +188,17 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().clear(color); Ok(()) } + Syscall::GfxUiClear => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let color = self.get_color(expect_int(args, 0)?)?; + hw.gfx_mut().clear(color); + Ok(()) + } Syscall::GfxFillRect => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; @@ -197,6 +208,21 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().fill_rect(x, y, w, h, color); Ok(()) } + Syscall::GfxUiFillRect => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let x = expect_int(args, 0)? as i32; + let y = expect_int(args, 1)? as i32; + let w = expect_int(args, 2)? as i32; + let h = expect_int(args, 3)? as i32; + let color = self.get_color(expect_int(args, 4)?)?; + hw.gfx_mut().fill_rect(x, y, w, h, color); + Ok(()) + } Syscall::GfxDrawLine => { let x1 = expect_int(args, 0)? as i32; let y1 = expect_int(args, 1)? as i32; @@ -206,6 +232,21 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().draw_line(x1, y1, x2, y2, color); Ok(()) } + Syscall::GfxUiDrawLine => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let x1 = expect_int(args, 0)? as i32; + let y1 = expect_int(args, 1)? as i32; + let x2 = expect_int(args, 2)? as i32; + let y2 = expect_int(args, 3)? as i32; + let color = self.get_color(expect_int(args, 4)?)?; + hw.gfx_mut().draw_line(x1, y1, x2, y2, color); + Ok(()) + } Syscall::GfxDrawCircle => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; @@ -214,6 +255,20 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().draw_circle(x, y, r, color); Ok(()) } + Syscall::GfxUiDrawCircle => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let x = expect_int(args, 0)? as i32; + let y = expect_int(args, 1)? as i32; + let r = expect_int(args, 2)? as i32; + let color = self.get_color(expect_int(args, 3)?)?; + hw.gfx_mut().draw_circle(x, y, r, color); + Ok(()) + } Syscall::GfxDrawDisc => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; @@ -223,6 +278,21 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color); Ok(()) } + Syscall::GfxUiDrawDisc => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let x = expect_int(args, 0)? as i32; + let y = expect_int(args, 1)? as i32; + let r = expect_int(args, 2)? as i32; + let border_color = self.get_color(expect_int(args, 3)?)?; + let fill_color = self.get_color(expect_int(args, 4)?)?; + hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color); + Ok(()) + } Syscall::GfxDrawSquare => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; @@ -233,6 +303,22 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color); Ok(()) } + Syscall::GfxUiDrawSquare => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let x = expect_int(args, 0)? as i32; + let y = expect_int(args, 1)? as i32; + let w = expect_int(args, 2)? as i32; + let h = expect_int(args, 3)? as i32; + let border_color = self.get_color(expect_int(args, 4)?)?; + let fill_color = self.get_color(expect_int(args, 5)?)?; + hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color); + Ok(()) + } Syscall::GfxDrawText => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; @@ -241,6 +327,20 @@ impl NativeInterface for VmRuntimeHost<'_> { hw.gfx_mut().draw_text(x, y, &msg, color); Ok(()) } + Syscall::GfxUiDrawText => { + if self.current_cartridge_app_mode != AppMode::Shell { + return Err(VmFault::Trap( + TRAP_INVALID_SYSCALL, + format!("{} is only available to the Shell profile", syscall.name()), + )); + } + let x = expect_int(args, 0)? as i32; + let y = expect_int(args, 1)? as i32; + let msg = expect_string(args, 2, "message")?; + let color = self.get_color(expect_int(args, 3)?)?; + hw.gfx_mut().draw_text(x, y, &msg, color); + Ok(()) + } Syscall::ComposerBindScene => { let scene_bank_id = match Self::int_arg_to_usize_status(expect_int(args, 0)?) { Ok(id) => id, diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index 61ba95ed..7b0ec1fd 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -195,7 +195,7 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { let program = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -221,7 +221,7 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() { let program = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -253,7 +253,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() { let program = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -304,7 +304,7 @@ fn tick_system_profile_rejects_gfx_game_surface() { let program = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -388,6 +388,55 @@ fn tick_system_profile_rejects_composer_game_surface() { } } +#[test] +fn tick_game_profile_rejects_gfxui_shell_surface() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; + let mut vm = VirtualMachine::default(); + let mut hardware = Hardware::new(); + let signals = InputSignals::default(); + let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); + let program = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfxui".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }], + ); + let cartridge = cartridge_with_program_and_mode(program, caps::GFXUI, AppMode::Game); + + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) + .expect("game gfxui call must trap"); + + match report { + CrashReport::VmTrap { trap } => { + assert_eq!(trap.code, prometeu_bytecode::TRAP_INVALID_SYSCALL); + assert!(trap.message.contains("GfxUiClear is only available to the Shell profile")); + } + other => panic!("expected VmTrap crash report, got {:?}", other), + } +} + #[test] fn tick_system_profile_rejects_bank_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); @@ -655,7 +704,7 @@ fn tick_draw_text_survives_no_scene_frame_path() { code, vec![ConstantPoolEntry::String("I".into())], vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_text".into(), version: 1, arg_slots: 4, @@ -696,7 +745,7 @@ fn initialize_vm_success_clears_previous_crash_report() { let program = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -856,7 +905,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { let good_program = serialized_single_function_module( assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"), vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -878,7 +927,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { let bad_program = serialized_single_function_module( assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"), vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 09282fe9..7cbd7a0a 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -2707,7 +2707,7 @@ mod tests { let bytes = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -2733,7 +2733,7 @@ mod tests { let bytes = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -2765,14 +2765,14 @@ mod tests { code, vec![ SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, ret_slots: 0, }, SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_text".into(), version: 1, arg_slots: 4, @@ -2788,7 +2788,7 @@ mod tests { Err(VmInitError::LoaderPatchFailed( crate::vm_init_error::LoaderPatchError::UnusedSyscallDecl { sysc_index: 1, - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_text".into(), version: 1, }, @@ -2805,7 +2805,7 @@ mod tests { let bytes = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -2835,7 +2835,7 @@ mod tests { let bytes = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "missing".into(), version: 1, arg_slots: 0, @@ -2850,7 +2850,7 @@ mod tests { Err(VmInitError::LoaderPatchFailed( crate::vm_init_error::LoaderPatchError::ResolveFailed( prometeu_hal::syscalls::DeclaredLoadError::UnknownSyscall { - module: "gfx".into(), + module: "gfx2d".into(), name: "missing".into(), version: 1, }, @@ -2868,7 +2868,7 @@ mod tests { let bytes = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, @@ -2883,9 +2883,9 @@ mod tests { Err(VmInitError::LoaderPatchFailed( crate::vm_init_error::LoaderPatchError::ResolveFailed( prometeu_hal::syscalls::DeclaredLoadError::MissingCapability { - required: prometeu_hal::syscalls::caps::GFX, + required: prometeu_hal::syscalls::caps::GFX2D, provided: prometeu_hal::syscalls::caps::NONE, - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, }, @@ -2903,7 +2903,7 @@ mod tests { let bytes = serialized_single_function_module( code, vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_line".into(), version: 1, arg_slots: 4, @@ -2918,7 +2918,7 @@ mod tests { Err(VmInitError::LoaderPatchFailed( crate::vm_init_error::LoaderPatchError::ResolveFailed( prometeu_hal::syscalls::DeclaredLoadError::AbiMismatch { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_line".into(), version: 1, declared_arg_slots: 4, diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index ca304c0c..97e1773a 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -42,14 +42,14 @@ pub fn generate() -> Result<()> { let mut rom: Vec = Vec::new(); let syscalls = vec![ SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, ret_slots: 0, }, SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_text".into(), version: 1, arg_slots: 4, diff --git a/discussion/index.ndjson b/discussion/index.ndjson index bae22d2d..660dc750 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md b/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md index 6105c803..b62ce076 100644 --- a/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md +++ b/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md @@ -2,7 +2,7 @@ id: PLN-0076 ticket: render-frame-packet-boundary title: Capabilities and ABI Domain Split -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 5e3cb87b57930d20e4e0ef6a4971c33e8f8fa77c Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:05:28 +0100 Subject: [PATCH 06/16] implements PLN-0077 composer buffer packet --- .../prometeu-drivers/src/frame_composer.rs | 123 +++++++++++++++++- .../console/prometeu-drivers/src/hardware.rs | 5 + crates/console/prometeu-drivers/src/lib.rs | 2 +- .../prometeu-hal/src/hardware_bridge.rs | 2 + .../src/services/vm_runtime/tick.rs | 12 +- discussion/index.ndjson | 2 +- ...-0077-composer-buffer-and-game2d-packet.md | 2 +- 7 files changed, 142 insertions(+), 6 deletions(-) diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs index 55d424be..0ddc660e 100644 --- a/crates/console/prometeu-drivers/src/frame_composer.rs +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -1,6 +1,5 @@ use crate::asset::GlyphAssetSlotIndex; use crate::memory_banks::SceneBankPoolAccess; -use prometeu_hal::GfxBridge; use prometeu_hal::glyph::Glyph; use prometeu_hal::scene_bank::SceneBank; use prometeu_hal::scene_viewport_cache::SceneViewportCache; @@ -8,6 +7,10 @@ use prometeu_hal::scene_viewport_resolver::{ CacheRefreshRequest, ResolverUpdate, SceneViewportResolver, }; use prometeu_hal::sprite::Sprite; +use prometeu_hal::{ + BoundScenePacket, Camera2D, ComposerFramePacket, Game2DFramePacket, GameSpritePacket, + GfxBridge, HudPacket, +}; use std::sync::Arc; const EMPTY_SPRITE: Sprite = Sprite { @@ -132,6 +135,28 @@ pub struct FrameComposer { sprite_controller: SpriteController, } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ComposerBuffer { + pub bound_scene: Option, + pub camera: Camera2D, + pub sprites: Vec, + pub hud: HudPacket, +} + +impl ComposerBuffer { + pub fn close(self) -> Game2DFramePacket { + Game2DFramePacket::new( + ComposerFramePacket { + bound_scene: self.bound_scene, + camera: self.camera, + sprites: self.sprites, + hud: self.hud, + }, + Vec::new(), + ) + } +} + impl FrameComposer { pub fn new( viewport_width_px: usize, @@ -246,6 +271,35 @@ impl FrameComposer { self.sprite_controller.ordered_sprites() } + pub fn composer_buffer_snapshot(&self) -> ComposerBuffer { + ComposerBuffer { + bound_scene: self + .active_scene_id + .map(|scene_bank_id| BoundScenePacket { bank_id: scene_bank_id as i32 }), + camera: Camera2D { x: self.camera_x_px, y: self.camera_y_px }, + sprites: self + .ordered_sprites() + .into_iter() + .map(|sprite| GameSpritePacket { + glyph_id: sprite.glyph.glyph_id as i32, + palette_id: sprite.glyph.palette_id as i32, + x: sprite.x, + y: sprite.y, + layer: sprite.layer as i32, + bank_id: sprite.bank_id as i32, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + priority: sprite.priority as i32, + }) + .collect(), + hud: HudPacket::default(), + } + } + + pub fn close_game2d_packet(&self) -> Game2DFramePacket { + self.composer_buffer_snapshot().close() + } + pub fn render_frame(&mut self, gfx: &mut dyn GfxBridge) { let ordered_sprites = self.ordered_sprites(); gfx.load_frame_sprites(&ordered_sprites); @@ -759,6 +813,73 @@ mod tests { assert_eq!(gfx.front_buffer()[0], Color::WHITE.raw()); } + #[test] + fn composer_buffer_snapshot_closes_into_game2d_packet_without_fade_state() { + let mut frame_composer = FrameComposer::new( + Hardware::W, + Hardware::H, + Arc::new(MemoryBanks::default()), + make_glyph_slot_index(&[]), + ); + + frame_composer.set_camera(10, 20); + frame_composer.begin_frame(); + assert!(frame_composer.emit_sprite(Sprite { + glyph: Glyph { glyph_id: 7, palette_id: 3 }, + x: 4, + y: 5, + layer: 2, + bank_id: 1, + active: false, + flip_x: true, + flip_y: false, + priority: 9, + })); + + let packet = frame_composer.close_game2d_packet(); + + assert_eq!(packet.composer.bound_scene, None); + assert_eq!(packet.composer.camera, Camera2D { x: 10, y: 20 }); + assert_eq!(packet.composer.sprites.len(), 1); + assert_eq!(packet.composer.sprites[0].glyph_id, 7); + assert_eq!(packet.composer.sprites[0].palette_id, 3); + assert_eq!(packet.composer.sprites[0].layer, 2); + assert!(packet.composer.sprites[0].flip_x); + assert!(packet.composer.hud.commands.is_empty()); + assert!(packet.gfx2d.is_empty()); + } + + #[test] + fn composer_buffer_preserves_integer_layer_ordering() { + let mut frame_composer = FrameComposer::new( + Hardware::W, + Hardware::H, + Arc::new(MemoryBanks::default()), + make_glyph_slot_index(&[]), + ); + frame_composer.begin_frame(); + + for (glyph_id, layer, priority) in [(1, 3, 1), (2, 1, 2), (3, 1, 1), (4, 0, 9)] { + assert!(frame_composer.emit_sprite(Sprite { + glyph: Glyph { glyph_id, palette_id: 0 }, + x: 0, + y: 0, + layer, + bank_id: 0, + active: false, + flip_x: false, + flip_y: false, + priority, + })); + } + + let packet = frame_composer.close_game2d_packet(); + let glyph_order: Vec = + packet.composer.sprites.iter().map(|sprite| sprite.glyph_id).collect(); + + assert_eq!(glyph_order, vec![4, 3, 2, 1]); + } + #[test] fn render_frame_with_scene_applies_refreshes_before_composition() { let banks = Arc::new(MemoryBanks::new()); diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 71720ce1..590002f0 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -8,6 +8,7 @@ use crate::memory_banks::{ }; use crate::pad::Pad; use crate::touch::Touch; +use prometeu_hal::Game2DFramePacket; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; @@ -67,6 +68,10 @@ impl HardwareBridge for Hardware { self.frame_composer.emit_sprite(sprite) } + fn close_game2d_packet(&self) -> Game2DFramePacket { + self.frame_composer.close_game2d_packet() + } + fn render_frame(&mut self) { self.frame_composer.render_frame(&mut self.gfx); } diff --git a/crates/console/prometeu-drivers/src/lib.rs b/crates/console/prometeu-drivers/src/lib.rs index 9a088424..c0e4b650 100644 --- a/crates/console/prometeu-drivers/src/lib.rs +++ b/crates/console/prometeu-drivers/src/lib.rs @@ -9,7 +9,7 @@ mod touch; pub use crate::asset::AssetManager; pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE}; -pub use crate::frame_composer::{FrameComposer, SceneStatus, SpriteController}; +pub use crate::frame_composer::{ComposerBuffer, FrameComposer, SceneStatus, SpriteController}; pub use crate::gfx::Gfx; pub use crate::memory_banks::{ GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess, diff --git a/crates/console/prometeu-hal/src/hardware_bridge.rs b/crates/console/prometeu-hal/src/hardware_bridge.rs index 28ccb77f..639cbffa 100644 --- a/crates/console/prometeu-hal/src/hardware_bridge.rs +++ b/crates/console/prometeu-hal/src/hardware_bridge.rs @@ -2,6 +2,7 @@ use crate::asset_bridge::AssetBridge; use crate::audio_bridge::AudioBridge; use crate::gfx_bridge::GfxBridge; use crate::pad_bridge::PadBridge; +use crate::render_submission::Game2DFramePacket; use crate::sprite::Sprite; use crate::touch_bridge::TouchBridge; @@ -11,6 +12,7 @@ pub trait HardwareBridge { fn unbind_scene(&mut self); fn set_camera(&mut self, x: i32, y: i32); fn emit_sprite(&mut self, sprite: Sprite) -> bool; + fn close_game2d_packet(&self) -> Game2DFramePacket; fn render_frame(&mut self); fn has_glyph_bank(&self, bank_id: usize) -> bool; diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index a4e189e7..43510f52 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -5,7 +5,7 @@ use crate::fs::{FsState, VirtualFS}; use crate::services::memcard::MemcardService; use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogService, LogSource}; -use prometeu_hal::{HardwareBridge, HostContext, InputSignals}; +use prometeu_hal::{HardwareBridge, HostContext, InputSignals, RenderSubmissionPacket}; use prometeu_vm::LogicalFrameEndingReason; use std::collections::HashMap; use std::panic::{AssertUnwindSafe, catch_unwind}; @@ -221,7 +221,15 @@ impl VirtualMachineRuntime { || run.reason == LogicalFrameEndingReason::EndOfRom { self.render_manager.set_active_app_mode(self.current_cartridge_app_mode); - self.render_manager.close_compat_frame(); + if self.current_cartridge_app_mode == AppMode::Game { + self.render_manager + .close_frame_with_packet(RenderSubmissionPacket::Game2D( + hw.close_game2d_packet(), + )) + .expect("game packet must match Game app mode"); + } else { + self.render_manager.close_compat_frame(); + } if let Err(payload) = catch_unwind(AssertUnwindSafe(|| hw.render_frame())) { let message = Self::host_panic_payload_to_string(payload); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 660dc750..5be067d1 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md b/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md index 2adb510b..6d9b1c23 100644 --- a/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md +++ b/discussion/workflow/plans/PLN-0077-composer-buffer-and-game2d-packet.md @@ -2,7 +2,7 @@ id: PLN-0077 ticket: render-frame-packet-boundary title: Composer Buffer and Game2D Packet -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 57439846c5284a0be1f0eac459d7f9c4a963d507 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:07:41 +0100 Subject: [PATCH 07/16] implements PLN-0078 Classic2D packet consumer --- crates/console/prometeu-drivers/src/gfx.rs | 72 ++++++++++++++++++- .../console/prometeu-drivers/src/hardware.rs | 7 +- discussion/index.ndjson | 2 +- ...N-0078-classic2d-game-renderer-consumer.md | 2 +- 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index 3ebf43f3..22b6ff47 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -1,11 +1,11 @@ use crate::memory_banks::GlyphBankPoolAccess; -use prometeu_hal::GfxBridge; use prometeu_hal::color::Color; use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph_bank::GlyphBank; use prometeu_hal::scene_viewport_cache::{CachedTileEntry, SceneViewportCache}; use prometeu_hal::scene_viewport_resolver::{LayerCopyRequest, ResolverUpdate}; use prometeu_hal::sprite::Sprite; +use prometeu_hal::{Game2DFramePacket, Gfx2dCommand, GfxBridge}; use std::sync::Arc; /// Blending modes inspired by classic 16-bit hardware. @@ -686,6 +686,58 @@ impl Gfx { Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color); } + pub fn render_game2d_frame_packet(&mut self, packet: &Game2DFramePacket) { + let sprites: Vec = packet + .composer + .sprites + .iter() + .map(|sprite| Sprite { + glyph: Glyph { + glyph_id: sprite.glyph_id as u16, + palette_id: sprite.palette_id as u8, + }, + x: sprite.x, + y: sprite.y, + layer: sprite.layer as u8, + bank_id: sprite.bank_id as u8, + active: true, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + priority: sprite.priority as u8, + }) + .collect(); + self.load_frame_sprites(&sprites); + self.render_no_scene_frame(); + + for command in &packet.gfx2d { + self.apply_gfx2d_command(command); + } + } + + fn apply_gfx2d_command(&mut self, command: &Gfx2dCommand) { + match command { + Gfx2dCommand::Clear { color } => self.clear(*color), + Gfx2dCommand::FillRect { rect, color } => { + self.fill_rect(rect.x, rect.y, rect.w, rect.h, *color); + } + Gfx2dCommand::DrawLine { x0, y0, x1, y1, color } => { + self.draw_line(*x0, *y0, *x1, *y1, *color); + } + Gfx2dCommand::DrawCircle { x, y, radius, color } => { + self.draw_circle(*x, *y, *radius, *color); + } + Gfx2dCommand::DrawDisc { x, y, radius, border_color, fill_color } => { + self.draw_disc(*x, *y, *radius, *border_color, *fill_color); + } + Gfx2dCommand::DrawSquare { rect, border_color, fill_color } => { + self.draw_square(rect.x, rect.y, rect.w, rect.h, *border_color, *fill_color); + } + Gfx2dCommand::DrawText { x, y, text, color } => { + self.draw_text(*x, *y, text, *color); + } + } + } + fn populate_layer_buckets(&mut self) { for bucket in self.layer_buckets.iter_mut() { bucket.clear(); @@ -1331,4 +1383,22 @@ mod tests { assert_eq!(gfx.sprites[0].layer, 1); assert_eq!(gfx.sprites[1].glyph.glyph_id, 5); } + + #[test] + fn render_game2d_frame_packet_applies_primitive_commands() { + let mut gfx = Gfx::new(8, 8, Arc::new(MemoryBanks::default())); + let packet = Game2DFramePacket::new( + Default::default(), + vec![Gfx2dCommand::FillRect { + rect: prometeu_hal::primitives::Rect { x: 1, y: 1, w: 2, h: 2 }, + color: Color::RED, + }], + ); + + gfx.render_game2d_frame_packet(&packet); + gfx.present(); + + assert_eq!(gfx.front_buffer()[1 + 8], Color::RED.raw()); + assert_eq!(gfx.front_buffer()[2 + 2 * 8], Color::RED.raw()); + } } diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 590002f0..724ba47f 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -73,7 +73,12 @@ impl HardwareBridge for Hardware { } fn render_frame(&mut self) { - self.frame_composer.render_frame(&mut self.gfx); + if self.frame_composer.active_scene_id().is_none() { + let packet = self.frame_composer.close_game2d_packet(); + self.gfx.render_game2d_frame_packet(&packet); + } else { + self.frame_composer.render_frame(&mut self.gfx); + } } fn has_glyph_bank(&self, bank_id: usize) -> bool { diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 5be067d1..c39fc9e5 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md b/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md index 9ed855f7..2fd592db 100644 --- a/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md +++ b/discussion/workflow/plans/PLN-0078-classic2d-game-renderer-consumer.md @@ -2,7 +2,7 @@ id: PLN-0078 ticket: render-frame-packet-boundary title: Classic2D Game Renderer Consumer -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 5e00f6bb0dfafbeb7e373458de4737fa5cd56347 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:10:52 +0100 Subject: [PATCH 08/16] implements PLN-0079 gfx2d primitive buffering --- .../src/services/vm_runtime/dispatch.rs | 29 +++++++++++++++++-- .../src/services/vm_runtime/lifecycle.rs | 2 ++ .../src/services/vm_runtime/mod.rs | 2 ++ .../src/services/vm_runtime/tests.rs | 7 +++++ .../src/services/vm_runtime/tick.rs | 6 ++-- discussion/index.ndjson | 2 +- .../plans/PLN-0079-gfx2d-primitive-domain.md | 2 +- 7 files changed, 43 insertions(+), 7 deletions(-) diff --git a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs index ef13ad2c..413ff606 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -7,12 +7,13 @@ use prometeu_hal::asset::{AssetId, AssetOpStatus, BankType, SlotRef}; use prometeu_hal::color::Color; use prometeu_hal::glyph::Glyph; use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::primitives::Rect; use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; use prometeu_hal::vm_fault::VmFault; use prometeu_hal::{ - AudioOpStatus, ComposerOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, - expect_bool, expect_int, + AudioOpStatus, ComposerOpStatus, Gfx2dCommand, HostContext, HostReturn, NativeInterface, + SyscallId, expect_bool, expect_int, }; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; @@ -185,6 +186,7 @@ impl NativeInterface for VmRuntimeHost<'_> { Syscall::SystemRunCart => unreachable!(), Syscall::GfxClear => { let color = self.get_color(expect_int(args, 0)?)?; + self.gfx2d_commands.push(Gfx2dCommand::Clear { color }); hw.gfx_mut().clear(color); Ok(()) } @@ -205,6 +207,8 @@ impl NativeInterface for VmRuntimeHost<'_> { let w = expect_int(args, 2)? as i32; let h = expect_int(args, 3)? as i32; let color = self.get_color(expect_int(args, 4)?)?; + self.gfx2d_commands + .push(Gfx2dCommand::FillRect { rect: Rect { x, y, w, h }, color }); hw.gfx_mut().fill_rect(x, y, w, h, color); Ok(()) } @@ -229,6 +233,13 @@ impl NativeInterface for VmRuntimeHost<'_> { let x2 = expect_int(args, 2)? as i32; let y2 = expect_int(args, 3)? as i32; let color = self.get_color(expect_int(args, 4)?)?; + self.gfx2d_commands.push(Gfx2dCommand::DrawLine { + x0: x1, + y0: y1, + x1: x2, + y1: y2, + color, + }); hw.gfx_mut().draw_line(x1, y1, x2, y2, color); Ok(()) } @@ -252,6 +263,7 @@ impl NativeInterface for VmRuntimeHost<'_> { let y = expect_int(args, 1)? as i32; let r = expect_int(args, 2)? as i32; let color = self.get_color(expect_int(args, 3)?)?; + self.gfx2d_commands.push(Gfx2dCommand::DrawCircle { x, y, radius: r, color }); hw.gfx_mut().draw_circle(x, y, r, color); Ok(()) } @@ -275,6 +287,13 @@ impl NativeInterface for VmRuntimeHost<'_> { let r = expect_int(args, 2)? as i32; let border_color = self.get_color(expect_int(args, 3)?)?; let fill_color = self.get_color(expect_int(args, 4)?)?; + self.gfx2d_commands.push(Gfx2dCommand::DrawDisc { + x, + y, + radius: r, + border_color, + fill_color, + }); hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color); Ok(()) } @@ -300,6 +319,11 @@ impl NativeInterface for VmRuntimeHost<'_> { let h = expect_int(args, 3)? as i32; let border_color = self.get_color(expect_int(args, 4)?)?; let fill_color = self.get_color(expect_int(args, 5)?)?; + self.gfx2d_commands.push(Gfx2dCommand::DrawSquare { + rect: Rect { x, y, w, h }, + border_color, + fill_color, + }); hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color); Ok(()) } @@ -324,6 +348,7 @@ impl NativeInterface for VmRuntimeHost<'_> { let y = expect_int(args, 1)? as i32; let msg = expect_string(args, 2, "message")?; let color = self.get_color(expect_int(args, 3)?)?; + self.gfx2d_commands.push(Gfx2dCommand::DrawText { x, y, text: msg.clone(), color }); hw.gfx_mut().draw_text(x, y, &msg, color); Ok(()) } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs index da1da345..8cc35c04 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs @@ -23,6 +23,7 @@ impl VirtualMachineRuntime { current_cartridge_app_version: String::new(), current_cartridge_app_mode: AppMode::Game, render_manager: RenderManager::new(AppMode::Game), + gfx2d_commands: Vec::new(), logs_written_this_frame: HashMap::new(), atomic_telemetry, last_crash_report: None, @@ -118,6 +119,7 @@ impl VirtualMachineRuntime { self.current_cartridge_app_version.clear(); self.current_cartridge_app_mode = AppMode::Game; self.render_manager = RenderManager::new(AppMode::Game); + self.gfx2d_commands.clear(); self.logs_written_this_frame.clear(); self.last_crash_report = None; diff --git a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs index 85bbf336..c7029314 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs @@ -7,6 +7,7 @@ mod tick; use crate::CrashReport; use prometeu_bytecode::string_materialization_count; +use prometeu_hal::Gfx2dCommand; use prometeu_hal::app_mode::AppMode; use prometeu_hal::log::LogService; use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier}; @@ -28,6 +29,7 @@ pub struct VirtualMachineRuntime { pub current_cartridge_app_version: String, pub current_cartridge_app_mode: AppMode, pub render_manager: RenderManager, + pub gfx2d_commands: Vec, pub logs_written_this_frame: HashMap, pub atomic_telemetry: Arc, pub last_crash_report: Option, diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index 7b0ec1fd..3da461e8 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -730,6 +730,13 @@ fn tick_draw_text_survives_no_scene_frame_path() { ); assert!(report.is_none(), "no-scene overlay text must not crash"); + let submission = + runtime.render_manager.latest_complete_submission().expect("closed submission"); + let prometeu_hal::RenderSubmissionPacket::Game2D(packet) = &submission.packet else { + panic!("expected Game2D submission"); + }; + assert_eq!(packet.gfx2d.len(), 1); + assert!(matches!(packet.gfx2d[0], prometeu_hal::Gfx2dCommand::DrawText { .. })); hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index 43510f52..0184469d 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -222,10 +222,10 @@ impl VirtualMachineRuntime { { self.render_manager.set_active_app_mode(self.current_cartridge_app_mode); if self.current_cartridge_app_mode == AppMode::Game { + let mut packet = hw.close_game2d_packet(); + packet.gfx2d = std::mem::take(&mut self.gfx2d_commands); self.render_manager - .close_frame_with_packet(RenderSubmissionPacket::Game2D( - hw.close_game2d_packet(), - )) + .close_frame_with_packet(RenderSubmissionPacket::Game2D(packet)) .expect("game packet must match Game app mode"); } else { self.render_manager.close_compat_frame(); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index c39fc9e5..ae7c93fa 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md b/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md index 58eaea61..3979af28 100644 --- a/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md +++ b/discussion/workflow/plans/PLN-0079-gfx2d-primitive-domain.md @@ -2,7 +2,7 @@ id: PLN-0079 ticket: render-frame-packet-boundary title: Gfx2D Primitive Domain -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From ae85ed4efff66298449d1f845289f030ff5e0feb Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:13:06 +0100 Subject: [PATCH 09/16] implements PLN-0080 shell ui gfxui packet --- .../src/services/vm_runtime/dispatch.rs | 28 +++++++++- .../src/services/vm_runtime/lifecycle.rs | 2 + .../src/services/vm_runtime/mod.rs | 3 +- .../src/services/vm_runtime/tests.rs | 52 +++++++++++++++++++ .../src/services/vm_runtime/tick.rs | 10 +++- discussion/index.ndjson | 2 +- ...N-0080-shell-ui-packet-and-gfxui-domain.md | 2 +- 7 files changed, 92 insertions(+), 7 deletions(-) diff --git a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs index 413ff606..723ad03c 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -12,8 +12,8 @@ use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; use prometeu_hal::vm_fault::VmFault; use prometeu_hal::{ - AudioOpStatus, ComposerOpStatus, Gfx2dCommand, HostContext, HostReturn, NativeInterface, - SyscallId, expect_bool, expect_int, + AudioOpStatus, ComposerOpStatus, Gfx2dCommand, GfxUiCommand, HostContext, HostReturn, + NativeInterface, SyscallId, expect_bool, expect_int, }; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; @@ -198,6 +198,7 @@ impl NativeInterface for VmRuntimeHost<'_> { )); } let color = self.get_color(expect_int(args, 0)?)?; + self.gfxui_commands.push(GfxUiCommand::Clear { color }); hw.gfx_mut().clear(color); Ok(()) } @@ -224,6 +225,8 @@ impl NativeInterface for VmRuntimeHost<'_> { let w = expect_int(args, 2)? as i32; let h = expect_int(args, 3)? as i32; let color = self.get_color(expect_int(args, 4)?)?; + self.gfxui_commands + .push(GfxUiCommand::FillRect { rect: Rect { x, y, w, h }, color }); hw.gfx_mut().fill_rect(x, y, w, h, color); Ok(()) } @@ -255,6 +258,13 @@ impl NativeInterface for VmRuntimeHost<'_> { let x2 = expect_int(args, 2)? as i32; let y2 = expect_int(args, 3)? as i32; let color = self.get_color(expect_int(args, 4)?)?; + self.gfxui_commands.push(GfxUiCommand::DrawLine { + x0: x1, + y0: y1, + x1: x2, + y1: y2, + color, + }); hw.gfx_mut().draw_line(x1, y1, x2, y2, color); Ok(()) } @@ -278,6 +288,7 @@ impl NativeInterface for VmRuntimeHost<'_> { let y = expect_int(args, 1)? as i32; let r = expect_int(args, 2)? as i32; let color = self.get_color(expect_int(args, 3)?)?; + self.gfxui_commands.push(GfxUiCommand::DrawCircle { x, y, radius: r, color }); hw.gfx_mut().draw_circle(x, y, r, color); Ok(()) } @@ -309,6 +320,13 @@ impl NativeInterface for VmRuntimeHost<'_> { let r = expect_int(args, 2)? as i32; let border_color = self.get_color(expect_int(args, 3)?)?; let fill_color = self.get_color(expect_int(args, 4)?)?; + self.gfxui_commands.push(GfxUiCommand::DrawDisc { + x, + y, + radius: r, + border_color, + fill_color, + }); hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color); Ok(()) } @@ -340,6 +358,11 @@ impl NativeInterface for VmRuntimeHost<'_> { let h = expect_int(args, 3)? as i32; let border_color = self.get_color(expect_int(args, 4)?)?; let fill_color = self.get_color(expect_int(args, 5)?)?; + self.gfxui_commands.push(GfxUiCommand::DrawSquare { + rect: Rect { x, y, w, h }, + border_color, + fill_color, + }); hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color); Ok(()) } @@ -363,6 +386,7 @@ impl NativeInterface for VmRuntimeHost<'_> { let y = expect_int(args, 1)? as i32; let msg = expect_string(args, 2, "message")?; let color = self.get_color(expect_int(args, 3)?)?; + self.gfxui_commands.push(GfxUiCommand::DrawText { x, y, text: msg.clone(), color }); hw.gfx_mut().draw_text(x, y, &msg, color); Ok(()) } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs index 8cc35c04..8fab82b8 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs @@ -24,6 +24,7 @@ impl VirtualMachineRuntime { current_cartridge_app_mode: AppMode::Game, render_manager: RenderManager::new(AppMode::Game), gfx2d_commands: Vec::new(), + gfxui_commands: Vec::new(), logs_written_this_frame: HashMap::new(), atomic_telemetry, last_crash_report: None, @@ -120,6 +121,7 @@ impl VirtualMachineRuntime { self.current_cartridge_app_mode = AppMode::Game; self.render_manager = RenderManager::new(AppMode::Game); self.gfx2d_commands.clear(); + self.gfxui_commands.clear(); self.logs_written_this_frame.clear(); self.last_crash_report = None; diff --git a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs index c7029314..41aeeb6f 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs @@ -7,10 +7,10 @@ mod tick; use crate::CrashReport; use prometeu_bytecode::string_materialization_count; -use prometeu_hal::Gfx2dCommand; use prometeu_hal::app_mode::AppMode; use prometeu_hal::log::LogService; use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier}; +use prometeu_hal::{Gfx2dCommand, GfxUiCommand}; use prometeu_vm::VirtualMachine; pub use render_manager::RenderManager; use std::collections::HashMap; @@ -30,6 +30,7 @@ pub struct VirtualMachineRuntime { pub current_cartridge_app_mode: AppMode, pub render_manager: RenderManager, pub gfx2d_commands: Vec, + pub gfxui_commands: Vec, pub logs_written_this_frame: HashMap, pub atomic_telemetry: Arc, pub last_crash_report: Option, diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index 3da461e8..acdd5dd3 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -437,6 +437,54 @@ fn tick_game_profile_rejects_gfxui_shell_surface() { } } +#[test] +fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; + let mut vm = VirtualMachine::default(); + let mut hardware = Hardware::new(); + let signals = InputSignals::default(); + let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nFRAME_SYNC\nHALT").expect("assemble"); + let program = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfxui".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }], + ); + let cartridge = cartridge_with_program_and_mode(program, caps::GFXUI, AppMode::Shell); + + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); + + assert!(report.is_none()); + let submission = + runtime.render_manager.latest_complete_submission().expect("closed submission"); + let prometeu_hal::RenderSubmissionPacket::ShellUi(packet) = &submission.packet else { + panic!("expected ShellUi submission"); + }; + assert_eq!(packet.commands.len(), 1); + assert!(matches!(packet.commands[0], prometeu_hal::GfxUiCommand::Clear { .. })); +} + #[test] fn tick_system_profile_rejects_bank_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); @@ -796,6 +844,9 @@ fn reset_clears_cartridge_scoped_runtime_state() { runtime.frame_start_heap_allocations = 11; runtime.frame_start_string_materializations = 22; runtime.needs_prepare_entry_call = true; + runtime + .gfxui_commands + .push(prometeu_hal::GfxUiCommand::Clear { color: Color::from_raw(0x11223344) }); runtime.reset(&mut vm); @@ -812,6 +863,7 @@ fn reset_clears_cartridge_scoped_runtime_state() { assert!(runtime.current_cartridge_app_version.is_empty()); assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game); assert!(runtime.logs_written_this_frame.is_empty()); + assert!(runtime.gfxui_commands.is_empty()); assert_eq!(runtime.atomic_telemetry.logs_count.load(Ordering::Relaxed), 0); assert_eq!(runtime.atomic_telemetry.current_logs_count.load(Ordering::Relaxed), 0); assert_eq!(runtime.atomic_telemetry.frame_index.load(Ordering::Relaxed), 0); diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index 0184469d..43cbf5a2 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -5,7 +5,9 @@ use crate::fs::{FsState, VirtualFS}; use crate::services::memcard::MemcardService; use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogService, LogSource}; -use prometeu_hal::{HardwareBridge, HostContext, InputSignals, RenderSubmissionPacket}; +use prometeu_hal::{ + HardwareBridge, HostContext, InputSignals, RenderSubmissionPacket, ShellUiFramePacket, +}; use prometeu_vm::LogicalFrameEndingReason; use std::collections::HashMap; use std::panic::{AssertUnwindSafe, catch_unwind}; @@ -228,7 +230,11 @@ impl VirtualMachineRuntime { .close_frame_with_packet(RenderSubmissionPacket::Game2D(packet)) .expect("game packet must match Game app mode"); } else { - self.render_manager.close_compat_frame(); + let packet = + ShellUiFramePacket::new(std::mem::take(&mut self.gfxui_commands)); + self.render_manager + .close_frame_with_packet(RenderSubmissionPacket::ShellUi(packet)) + .expect("shell packet must match Shell app mode"); } if let Err(payload) = catch_unwind(AssertUnwindSafe(|| hw.render_frame())) { diff --git a/discussion/index.ndjson b/discussion/index.ndjson index ae7c93fa..f0776047 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md b/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md index bec557d6..87a02b3e 100644 --- a/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md +++ b/discussion/workflow/plans/PLN-0080-shell-ui-packet-and-gfxui-domain.md @@ -2,7 +2,7 @@ id: PLN-0080 ticket: render-frame-packet-boundary title: Shell UI Packet and GfxUI Domain -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 74df4b89ac1539a8143d25b23da9070777db5107 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:16:07 +0100 Subject: [PATCH 10/16] implements PLN-0081 shell hub packet migration --- crates/console/prometeu-drivers/src/gfx.rs | 32 ++- .../console/prometeu-drivers/src/hardware.rs | 10 +- .../prometeu-hal/src/hardware_bridge.rs | 4 +- .../src/programs/prometeu_hub/prometeu_hub.rs | 182 +++++++++++++----- discussion/index.ndjson | 2 +- .../plans/PLN-0081-shell-hub-migration.md | 2 +- 6 files changed, 175 insertions(+), 57 deletions(-) diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index 22b6ff47..e3334e83 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -5,7 +5,7 @@ use prometeu_hal::glyph_bank::GlyphBank; use prometeu_hal::scene_viewport_cache::{CachedTileEntry, SceneViewportCache}; use prometeu_hal::scene_viewport_resolver::{LayerCopyRequest, ResolverUpdate}; use prometeu_hal::sprite::Sprite; -use prometeu_hal::{Game2DFramePacket, Gfx2dCommand, GfxBridge}; +use prometeu_hal::{Game2DFramePacket, Gfx2dCommand, GfxBridge, GfxUiCommand, ShellUiFramePacket}; use std::sync::Arc; /// Blending modes inspired by classic 16-bit hardware. @@ -714,6 +714,12 @@ impl Gfx { } } + pub fn render_shell_ui_frame_packet(&mut self, packet: &ShellUiFramePacket) { + for command in &packet.commands { + self.apply_gfxui_command(command); + } + } + fn apply_gfx2d_command(&mut self, command: &Gfx2dCommand) { match command { Gfx2dCommand::Clear { color } => self.clear(*color), @@ -738,6 +744,30 @@ impl Gfx { } } + fn apply_gfxui_command(&mut self, command: &GfxUiCommand) { + match command { + GfxUiCommand::Clear { color } => self.clear(*color), + GfxUiCommand::FillRect { rect, color } => { + self.fill_rect(rect.x, rect.y, rect.w, rect.h, *color); + } + GfxUiCommand::DrawLine { x0, y0, x1, y1, color } => { + self.draw_line(*x0, *y0, *x1, *y1, *color); + } + GfxUiCommand::DrawCircle { x, y, radius, color } => { + self.draw_circle(*x, *y, *radius, *color); + } + GfxUiCommand::DrawDisc { x, y, radius, border_color, fill_color } => { + self.draw_disc(*x, *y, *radius, *border_color, *fill_color); + } + GfxUiCommand::DrawSquare { rect, border_color, fill_color } => { + self.draw_square(rect.x, rect.y, rect.w, rect.h, *border_color, *fill_color); + } + GfxUiCommand::DrawText { x, y, text, color } => { + self.draw_text(*x, *y, text, *color); + } + } + } + fn populate_layer_buckets(&mut self) { for bucket in self.layer_buckets.iter_mut() { bucket.clear(); diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 724ba47f..44852431 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -8,10 +8,10 @@ use crate::memory_banks::{ }; use crate::pad::Pad; use crate::touch::Touch; -use prometeu_hal::Game2DFramePacket; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; +use prometeu_hal::{Game2DFramePacket, ShellUiFramePacket}; use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. @@ -72,6 +72,10 @@ impl HardwareBridge for Hardware { self.frame_composer.close_game2d_packet() } + fn render_shell_ui_packet(&mut self, packet: &ShellUiFramePacket) { + self.gfx.render_shell_ui_frame_packet(packet); + } + fn render_frame(&mut self) { if self.frame_composer.active_scene_id().is_none() { let packet = self.frame_composer.close_game2d_packet(); @@ -81,6 +85,10 @@ impl HardwareBridge for Hardware { } } + fn present(&mut self) { + self.gfx.present(); + } + fn has_glyph_bank(&self, bank_id: usize) -> bool { self.gfx.glyph_banks.glyph_bank_slot(bank_id).is_some() } diff --git a/crates/console/prometeu-hal/src/hardware_bridge.rs b/crates/console/prometeu-hal/src/hardware_bridge.rs index 639cbffa..5c74454d 100644 --- a/crates/console/prometeu-hal/src/hardware_bridge.rs +++ b/crates/console/prometeu-hal/src/hardware_bridge.rs @@ -2,7 +2,7 @@ use crate::asset_bridge::AssetBridge; use crate::audio_bridge::AudioBridge; use crate::gfx_bridge::GfxBridge; use crate::pad_bridge::PadBridge; -use crate::render_submission::Game2DFramePacket; +use crate::render_submission::{Game2DFramePacket, ShellUiFramePacket}; use crate::sprite::Sprite; use crate::touch_bridge::TouchBridge; @@ -13,7 +13,9 @@ pub trait HardwareBridge { fn set_camera(&mut self, x: i32, y: i32); fn emit_sprite(&mut self, sprite: Sprite) -> bool; fn close_game2d_packet(&self) -> Game2DFramePacket; + fn render_shell_ui_packet(&mut self, packet: &ShellUiFramePacket); fn render_frame(&mut self); + fn present(&mut self); fn has_glyph_bank(&self, bank_id: usize) -> bool; fn gfx(&self) -> &dyn GfxBridge; diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index 89e30089..86c59616 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -1,8 +1,7 @@ use crate::{CrashReport, SystemOS}; use prometeu_hal::color::Color; -use prometeu_hal::gfx_bridge::GfxBridge; use prometeu_hal::primitives::Rect; -use prometeu_hal::{HardwareBridge, InputSignals}; +use prometeu_hal::{GfxUiCommand, HardwareBridge, InputSignals, ShellUiFramePacket}; use prometeu_vm::VirtualMachine; const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 108, w: 112, h: 48 }; @@ -101,8 +100,6 @@ impl PrometeuHub { os: &mut SystemOS, hw: &mut dyn HardwareBridge, ) -> Option { - hw.gfx_mut().clear(Color::INDIGO); - let in_shell = os.windows().focused_window().is_some(); if !in_shell { @@ -127,21 +124,23 @@ impl PrometeuHub { let pointer_x = hw.touch().x(); let pointer_y = hw.touch().y(); - if os.windows().window_count() == 0 { - render_home(hw.gfx_mut(), pointer_x, pointer_y); - return; - } - - for window in os.windows().windows() { - render_shell_window( - hw.gfx_mut(), - window.viewport, - window.color, - &window.title, - pointer_x, - pointer_y, - ); - } + let packet = if os.windows().window_count() == 0 { + render_home_packet(pointer_x, pointer_y) + } else { + let mut commands = Vec::new(); + for window in os.windows().windows() { + append_shell_window_commands( + &mut commands, + window.viewport, + window.color, + &window.title, + pointer_x, + pointer_y, + ); + } + ShellUiFramePacket::new(commands) + }; + hw.render_shell_ui_packet(&packet); } pub fn update_shell_profile( @@ -163,7 +162,7 @@ impl PrometeuHub { } self.render(os, hw); - hw.gfx_mut().present(); + hw.present(); SystemProfileUpdate { crash, action } } @@ -192,15 +191,29 @@ fn activation_input_down(hw: &dyn HardwareBridge) -> bool { hw.touch().f().down || hw.pad().a().down || hw.pad().b().down || hw.pad().start().down } -fn render_home(gfx: &mut dyn GfxBridge, pointer_x: i32, pointer_y: i32) { - gfx.fill_rect(VIEWPORT.x, VIEWPORT.y, VIEWPORT.w, VIEWPORT.h, COLOR_BG); - draw_panel(gfx, TITLE_PANEL, COLOR_PANEL_DARK, COLOR_BORDER); - draw_buffer_text(gfx, TITLE_PANEL.x + 58, TITLE_PANEL.y + 9, "PROMETEU HUB", COLOR_TEXT); - - draw_panel(gfx, HOME_PANEL, COLOR_PANEL, COLOR_BORDER); - draw_buffer_text(gfx, HOME_PANEL.x + 26, HOME_PANEL.y + 22, "Native Shell Apps", COLOR_TEXT); +fn render_home_packet(pointer_x: i32, pointer_y: i32) -> ShellUiFramePacket { + let mut commands = Vec::new(); + commands.push(GfxUiCommand::Clear { color: COLOR_BG }); + fill_rect(&mut commands, VIEWPORT, COLOR_BG); + draw_panel(&mut commands, TITLE_PANEL, COLOR_PANEL_DARK, COLOR_BORDER); draw_buffer_text( - gfx, + &mut commands, + TITLE_PANEL.x + 58, + TITLE_PANEL.y + 9, + "PROMETEU HUB", + COLOR_TEXT, + ); + + draw_panel(&mut commands, HOME_PANEL, COLOR_PANEL, COLOR_BORDER); + draw_buffer_text( + &mut commands, + HOME_PANEL.x + 26, + HOME_PANEL.y + 22, + "Native Shell Apps", + COLOR_TEXT, + ); + draw_buffer_text( + &mut commands, HOME_PANEL.x + 26, HOME_PANEL.y + 40, "first retro ui slice", @@ -209,50 +222,105 @@ fn render_home(gfx: &mut dyn GfxBridge, pointer_x: i32, pointer_y: i32) { for button in HOME_BUTTONS { let hovered = rect_contains(button.rect, pointer_x, pointer_y); - draw_button(gfx, button.rect, button.label, hovered); + draw_button(&mut commands, button.rect, button.label, hovered); } + + ShellUiFramePacket::new(commands) } -fn render_shell_window( - gfx: &mut dyn GfxBridge, +fn append_shell_window_commands( + commands: &mut Vec, viewport: Rect, color: Color, title: &str, pointer_x: i32, pointer_y: i32, ) { - gfx.fill_rect(VIEWPORT.x, VIEWPORT.y, VIEWPORT.w, VIEWPORT.h, COLOR_BG); - draw_panel(gfx, viewport, COLOR_PANEL_DARK, COLOR_BORDER); - gfx.fill_rect(viewport.x + 4, viewport.y + 24, viewport.w - 8, viewport.h - 28, color); - draw_buffer_text(gfx, viewport.x + 10, viewport.y + 8, title, COLOR_TEXT); - draw_buffer_text(gfx, viewport.x + 18, viewport.y + 44, "native fake shell", COLOR_PANEL_DARK); + fill_rect(commands, VIEWPORT, COLOR_BG); + draw_panel(commands, viewport, COLOR_PANEL_DARK, COLOR_BORDER); + fill_rect( + commands, + Rect { x: viewport.x + 4, y: viewport.y + 24, w: viewport.w - 8, h: viewport.h - 28 }, + color, + ); + draw_buffer_text(commands, viewport.x + 10, viewport.y + 8, title, COLOR_TEXT); + draw_buffer_text( + commands, + viewport.x + 18, + viewport.y + 44, + "native fake shell", + COLOR_PANEL_DARK, + ); let close_hovered = rect_contains(CLOSE_BUTTON, pointer_x, pointer_y); let close_fill = if close_hovered { COLOR_HILITE } else { COLOR_BUTTON }; - gfx.fill_rect(CLOSE_BUTTON.x, CLOSE_BUTTON.y, CLOSE_BUTTON.w, CLOSE_BUTTON.h, close_fill); - gfx.draw_rect(CLOSE_BUTTON.x, CLOSE_BUTTON.y, CLOSE_BUTTON.w, CLOSE_BUTTON.h, COLOR_BORDER); - draw_buffer_text(gfx, CLOSE_BUTTON.x + 5, CLOSE_BUTTON.y + 4, "x", COLOR_TEXT); + fill_rect(commands, CLOSE_BUTTON, close_fill); + draw_rect(commands, CLOSE_BUTTON, COLOR_BORDER); + draw_buffer_text(commands, CLOSE_BUTTON.x + 5, CLOSE_BUTTON.y + 4, "x", COLOR_TEXT); } -fn draw_panel(gfx: &mut dyn GfxBridge, rect: Rect, fill: Color, border: Color) { - gfx.fill_rect(rect.x, rect.y, rect.w, rect.h, fill); - gfx.draw_rect(rect.x, rect.y, rect.w, rect.h, border); - gfx.draw_rect(rect.x + 2, rect.y + 2, rect.w - 4, rect.h - 4, COLOR_MUTED); +fn fill_rect(commands: &mut Vec, rect: Rect, color: Color) { + commands.push(GfxUiCommand::FillRect { rect, color }); } -fn draw_button(gfx: &mut dyn GfxBridge, rect: Rect, label: &str, hovered: bool) { +fn draw_rect(commands: &mut Vec, rect: Rect, color: Color) { + commands.push(GfxUiCommand::DrawLine { + x0: rect.x, + y0: rect.y, + x1: rect.x + rect.w - 1, + y1: rect.y, + color, + }); + commands.push(GfxUiCommand::DrawLine { + x0: rect.x, + y0: rect.y + rect.h - 1, + x1: rect.x + rect.w - 1, + y1: rect.y + rect.h - 1, + color, + }); + commands.push(GfxUiCommand::DrawLine { + x0: rect.x, + y0: rect.y, + x1: rect.x, + y1: rect.y + rect.h - 1, + color, + }); + commands.push(GfxUiCommand::DrawLine { + x0: rect.x + rect.w - 1, + y0: rect.y, + x1: rect.x + rect.w - 1, + y1: rect.y + rect.h - 1, + color, + }); +} + +fn draw_panel(commands: &mut Vec, rect: Rect, fill: Color, border: Color) { + fill_rect(commands, rect, fill); + draw_rect(commands, rect, border); + draw_rect( + commands, + Rect { x: rect.x + 2, y: rect.y + 2, w: rect.w - 4, h: rect.h - 4 }, + COLOR_MUTED, + ); +} + +fn draw_button(commands: &mut Vec, rect: Rect, label: &str, hovered: bool) { let fill = if hovered { COLOR_BUTTON_ACTIVE } else { COLOR_BUTTON }; let border = if hovered { COLOR_HILITE } else { COLOR_BORDER }; - gfx.fill_rect(rect.x, rect.y, rect.w, rect.h, fill); - gfx.draw_rect(rect.x, rect.y, rect.w, rect.h, border); - gfx.draw_rect(rect.x + 3, rect.y + 3, rect.w - 6, rect.h - 6, COLOR_MUTED); - gfx.fill_rect(rect.x + 12, rect.y + 12, 16, 24, border); - gfx.draw_rect(rect.x + 34, rect.y + 12, 62, 24, COLOR_PANEL_DARK); - draw_buffer_text(gfx, rect.x + 40, rect.y + 21, label, COLOR_TEXT); + fill_rect(commands, rect, fill); + draw_rect(commands, rect, border); + draw_rect( + commands, + Rect { x: rect.x + 3, y: rect.y + 3, w: rect.w - 6, h: rect.h - 6 }, + COLOR_MUTED, + ); + fill_rect(commands, Rect { x: rect.x + 12, y: rect.y + 12, w: 16, h: 24 }, border); + draw_rect(commands, Rect { x: rect.x + 34, y: rect.y + 12, w: 62, h: 24 }, COLOR_PANEL_DARK); + draw_buffer_text(commands, rect.x + 40, rect.y + 21, label, COLOR_TEXT); } -fn draw_buffer_text(gfx: &mut dyn GfxBridge, x: i32, y: i32, text: &str, color: Color) { - gfx.draw_text(x, y, text, color); +fn draw_buffer_text(commands: &mut Vec, x: i32, y: i32, text: &str, color: Color) { + commands.push(GfxUiCommand::DrawText { x, y, text: text.to_string(), color }); } #[cfg(test)] @@ -321,6 +389,16 @@ mod tests { assert_eq!(HOME_BUTTONS[1].app, NativeShellApp::ShellB); } + #[test] + fn home_render_produces_shell_ui_packet_commands() { + let packet = render_home_packet(SHELL_A_BUTTON.x, SHELL_A_BUTTON.y); + + assert!(matches!(packet.commands.first(), Some(GfxUiCommand::Clear { .. }))); + assert!(packet.commands.iter().any(|command| { + matches!(command, GfxUiCommand::DrawText { text, .. } if text == "PROMETEU HUB") + })); + } + #[test] fn shell_close_button_sits_inside_shell_frame() { assert!(rect_contains(SHELL_FRAME, CLOSE_BUTTON.x, CLOSE_BUTTON.y)); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index f0776047..a2cd986c 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0081-shell-hub-migration.md b/discussion/workflow/plans/PLN-0081-shell-hub-migration.md index dc39c7d7..3ecfaef2 100644 --- a/discussion/workflow/plans/PLN-0081-shell-hub-migration.md +++ b/discussion/workflow/plans/PLN-0081-shell-hub-migration.md @@ -2,7 +2,7 @@ id: PLN-0081 ticket: render-frame-packet-boundary title: Shell Hub Migration -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 41266d3fd5f1e12f342546cf3c4ab5ffd1076e13 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:18:40 +0100 Subject: [PATCH 11/16] implements PLN-0082 render publication boundary --- .../console/prometeu-drivers/src/hardware.rs | 22 +++++++++++++------ .../src/firmware/firmware.rs | 2 +- .../firmware/firmware_step_crash_screen.rs | 8 +++---- .../firmware/firmware_step_game_running.rs | 4 ---- .../firmware/firmware_step_splash_screen.rs | 15 ++++++++----- .../prometeu-hal/src/hardware_bridge.rs | 5 ++--- .../src/programs/prometeu_hub/prometeu_hub.rs | 7 +++--- .../src/services/vm_runtime/tests.rs | 3 --- .../src/services/vm_runtime/tick.rs | 19 ++++++++++++++-- discussion/index.ndjson | 2 +- ...-frame-publication-and-present-boundary.md | 2 +- 11 files changed, 54 insertions(+), 35 deletions(-) diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 44852431..8148df82 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -11,7 +11,7 @@ use crate::touch::Touch; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; -use prometeu_hal::{Game2DFramePacket, ShellUiFramePacket}; +use prometeu_hal::{Game2DFramePacket, RenderSubmission, RenderSubmissionPacket}; use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. @@ -72,8 +72,20 @@ impl HardwareBridge for Hardware { self.frame_composer.close_game2d_packet() } - fn render_shell_ui_packet(&mut self, packet: &ShellUiFramePacket) { - self.gfx.render_shell_ui_frame_packet(packet); + fn publish_render_submission(&mut self, submission: &RenderSubmission) { + match &submission.packet { + RenderSubmissionPacket::Game2D(packet) => { + if self.frame_composer.active_scene_id().is_none() { + self.gfx.render_game2d_frame_packet(packet); + } else { + self.frame_composer.render_frame(&mut self.gfx); + } + } + RenderSubmissionPacket::ShellUi(packet) => { + self.gfx.render_shell_ui_frame_packet(packet); + } + } + self.gfx.present(); } fn render_frame(&mut self) { @@ -85,10 +97,6 @@ impl HardwareBridge for Hardware { } } - fn present(&mut self) { - self.gfx.present(); - } - fn has_glyph_bank(&self, bank_id: usize) -> bool { self.gfx.glyph_banks.glyph_bank_slot(bank_id).is_some() } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 3679f393..f7e4a1c8 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -231,7 +231,7 @@ mod tests { debug_info: None, exports: vec![], syscalls: vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs index 8133a3cc..8c5d6a52 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs @@ -2,6 +2,7 @@ use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::color::Color; use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::{FrameId, GfxUiCommand, RenderSubmission, ShellUiFramePacket}; use prometeu_system::CrashReport; #[derive(Debug, Clone)] @@ -22,11 +23,8 @@ impl AppCrashesStep { // Update peripherals for input on the crash screen ctx.hw.pad_mut().begin_frame(ctx.signals); - // Error screen: red background, white text - ctx.hw.gfx_mut().clear(Color::RED); - // For now we just log or show something simple - // In the future, use draw_text - ctx.hw.gfx_mut().present(); + let packet = ShellUiFramePacket::new(vec![GfxUiCommand::Clear { color: Color::RED }]); + ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); // If START is pressed, return to the Hub if ctx.hw.pad().start().down { diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs index 5bd9579f..5a286f63 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs @@ -38,10 +38,6 @@ impl GameRunningStep { let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.hw); - if !ctx.os.vm().logical_frame_active() { - ctx.hw.gfx_mut().present(); - } - if let Some(report) = result { let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs index 0ca52730..5ba02ae9 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs @@ -2,6 +2,8 @@ use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::color::Color; use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::primitives::Rect; +use prometeu_hal::{FrameId, GfxUiCommand, RenderSubmission, ShellUiFramePacket}; #[derive(Debug, Clone)] pub struct SplashScreenStep { @@ -22,9 +24,6 @@ impl SplashScreenStep { // Update peripherals for input ctx.hw.pad_mut().begin_frame(ctx.signals); - // Clear screen - ctx.hw.gfx_mut().clear(Color::ORANGE); - // Draw expanding square let (sw, sh) = ctx.hw.gfx().size(); let max_size = (sw.min(sh) as i32 / 2).max(1); @@ -38,8 +37,14 @@ impl SplashScreenStep { let x = (sw as i32 - current_size) / 2; let y = (sh as i32 - current_size) / 2; - ctx.hw.gfx_mut().fill_rect(x, y, current_size, current_size, Color::WHITE); - ctx.hw.gfx_mut().present(); + let packet = ShellUiFramePacket::new(vec![ + GfxUiCommand::Clear { color: Color::ORANGE }, + GfxUiCommand::FillRect { + rect: Rect { x, y, w: current_size, h: current_size }, + color: Color::WHITE, + }, + ]); + ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); // Transition logic // If any button is pressed at any time after the animation ends diff --git a/crates/console/prometeu-hal/src/hardware_bridge.rs b/crates/console/prometeu-hal/src/hardware_bridge.rs index 5c74454d..fc06d80c 100644 --- a/crates/console/prometeu-hal/src/hardware_bridge.rs +++ b/crates/console/prometeu-hal/src/hardware_bridge.rs @@ -2,7 +2,7 @@ use crate::asset_bridge::AssetBridge; use crate::audio_bridge::AudioBridge; use crate::gfx_bridge::GfxBridge; use crate::pad_bridge::PadBridge; -use crate::render_submission::{Game2DFramePacket, ShellUiFramePacket}; +use crate::render_submission::{Game2DFramePacket, RenderSubmission}; use crate::sprite::Sprite; use crate::touch_bridge::TouchBridge; @@ -13,9 +13,8 @@ pub trait HardwareBridge { fn set_camera(&mut self, x: i32, y: i32); fn emit_sprite(&mut self, sprite: Sprite) -> bool; fn close_game2d_packet(&self) -> Game2DFramePacket; - fn render_shell_ui_packet(&mut self, packet: &ShellUiFramePacket); + fn publish_render_submission(&mut self, submission: &RenderSubmission); fn render_frame(&mut self); - fn present(&mut self); fn has_glyph_bank(&self, bank_id: usize) -> bool; fn gfx(&self) -> &dyn GfxBridge; diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index 86c59616..f2fb3624 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -1,7 +1,9 @@ use crate::{CrashReport, SystemOS}; use prometeu_hal::color::Color; use prometeu_hal::primitives::Rect; -use prometeu_hal::{GfxUiCommand, HardwareBridge, InputSignals, ShellUiFramePacket}; +use prometeu_hal::{ + FrameId, GfxUiCommand, HardwareBridge, InputSignals, RenderSubmission, ShellUiFramePacket, +}; use prometeu_vm::VirtualMachine; const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 108, w: 112, h: 48 }; @@ -140,7 +142,7 @@ impl PrometeuHub { } ShellUiFramePacket::new(commands) }; - hw.render_shell_ui_packet(&packet); + hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); } pub fn update_shell_profile( @@ -162,7 +164,6 @@ impl PrometeuHub { } self.render(os, hw); - hw.present(); SystemProfileUpdate { crash, action } } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index acdd5dd3..a1c748da 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -653,7 +653,6 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { ); assert!(report.is_none(), "frame render path must not crash"); - hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); } @@ -729,7 +728,6 @@ fn tick_renders_scene_through_public_composer_syscalls() { assert!(report.is_none(), "public composer path must not crash"); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]); - hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); } @@ -785,7 +783,6 @@ fn tick_draw_text_survives_no_scene_frame_path() { }; assert_eq!(packet.gfx2d.len(), 1); assert!(matches!(packet.gfx2d[0], prometeu_hal::Gfx2dCommand::DrawText { .. })); - hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index 43cbf5a2..fa26df0b 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -1,4 +1,5 @@ use super::dispatch::VmRuntimeHost; +use super::render_manager::RenderSurface; use super::*; use crate::CrashReport; use crate::fs::{FsState, VirtualFS}; @@ -6,13 +7,24 @@ use crate::services::memcard::MemcardService; use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::{ - HardwareBridge, HostContext, InputSignals, RenderSubmissionPacket, ShellUiFramePacket, + HardwareBridge, HostContext, InputSignals, RenderSubmission, RenderSubmissionPacket, + ShellUiFramePacket, }; use prometeu_vm::LogicalFrameEndingReason; use std::collections::HashMap; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::sync::atomic::Ordering; +struct HardwareRenderSurface<'a> { + hw: &'a mut dyn HardwareBridge, +} + +impl RenderSurface for HardwareRenderSurface<'_> { + fn consume_submission(&mut self, submission: &RenderSubmission) { + self.hw.publish_render_submission(submission); + } +} + impl VirtualMachineRuntime { fn host_panic_payload_to_string(payload: Box) -> String { if let Some(message) = payload.downcast_ref::() { @@ -237,7 +249,10 @@ impl VirtualMachineRuntime { .expect("shell packet must match Shell app mode"); } - if let Err(payload) = catch_unwind(AssertUnwindSafe(|| hw.render_frame())) { + if let Err(payload) = catch_unwind(AssertUnwindSafe(|| { + let mut surface = HardwareRenderSurface { hw }; + self.render_manager.publish_latest(&mut surface); + })) { let message = Self::host_panic_payload_to_string(payload); let report = CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) }; self.log( diff --git a/discussion/index.ndjson b/discussion/index.ndjson index a2cd986c..0f5f2c16 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md b/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md index 3afae455..93e5d0bf 100644 --- a/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md +++ b/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md @@ -2,7 +2,7 @@ id: PLN-0082 ticket: render-frame-packet-boundary title: Frame Publication and Present Boundary -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 295666b31038bba24bda046b1a3e7d6693ad129a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:21:12 +0100 Subject: [PATCH 12/16] implements PLN-0083 remove fade pipeline state --- .../prometeu-drivers/src/frame_composer.rs | 10 --- crates/console/prometeu-drivers/src/gfx.rs | 80 +------------------ .../console/prometeu-drivers/src/hardware.rs | 2 - crates/console/prometeu-hal/src/gfx_bridge.rs | 9 --- .../src/services/vm_runtime/tests.rs | 4 - discussion/index.ndjson | 2 +- .../workflow/plans/PLN-0083-fade-removal.md | 2 +- 7 files changed, 4 insertions(+), 105 deletions(-) diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs index 0ddc660e..bdbdebc9 100644 --- a/crates/console/prometeu-drivers/src/frame_composer.rs +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -804,8 +804,6 @@ mod tests { })); let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; frame_composer.render_frame(&mut gfx); gfx.present(); @@ -895,8 +893,6 @@ mod tests { assert!(frame_composer.bind_scene(0)); let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; frame_composer.render_frame(&mut gfx); gfx.present(); @@ -932,8 +928,6 @@ mod tests { let expected_update = expected_resolver.update(&scene, 40, 0); let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; frame_composer.render_frame(&mut gfx); frame_composer.set_camera(40, 0); @@ -965,8 +959,6 @@ mod tests { make_glyph_slot_index(&[(0, 0), (1, 1)]), ); let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; assert!(frame_composer.bind_scene(0)); frame_composer.render_frame(&mut gfx); @@ -1044,8 +1036,6 @@ mod tests { assert!(frame_composer.bind_scene(0)); let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; frame_composer.render_frame(&mut gfx); gfx.present(); diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index e3334e83..958e1806 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -33,7 +33,7 @@ pub enum BlendMode { /// /// `Gfx` owns the framebuffer backend and the canonical game-frame raster path /// consumed by `FrameComposer`. That canonical path covers scene composition, -/// sprite composition, and fades. Public `gfx.*` primitives remain valid. +/// sprite composition, and primitive packet consumption. pub struct Gfx { /// Width of the internal framebuffer in pixels. w: usize, @@ -49,15 +49,6 @@ pub struct Gfx { /// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory). pub sprites: [Sprite; 512], - /// Scene brightness/fade level (0 = black/invisible, 31 = fully visible). - pub scene_fade_level: u8, - /// Target color for the scene fade effect (usually Black). - pub scene_fade_color: Color, - /// HUD brightness/fade level (independent from the scene). - pub hud_fade_level: u8, - /// Target color for the HUD fade effect. - pub hud_fade_color: Color, - /// Internal sprite count for the current frame state. sprite_count: usize, /// Internal cache used to sort sprites by layer while keeping stable priority order. @@ -305,31 +296,6 @@ impl GfxBridge for Gfx { self.sprite_count = self.sprite_count.max(index.saturating_add(1)).min(self.sprites.len()); &mut self.sprites[index] } - - fn scene_fade_level(&self) -> u8 { - self.scene_fade_level - } - fn set_scene_fade_level(&mut self, level: u8) { - self.scene_fade_level = level; - } - fn scene_fade_color(&self) -> Color { - self.scene_fade_color - } - fn set_scene_fade_color(&mut self, color: Color) { - self.scene_fade_color = color; - } - fn hud_fade_level(&self) -> u8 { - self.hud_fade_level - } - fn set_hud_fade_level(&mut self, level: u8) { - self.hud_fade_level = level; - } - fn hud_fade_color(&self) -> Color { - self.hud_fade_color - } - fn set_hud_fade_color(&mut self, color: Color) { - self.hud_fade_color = color; - } } impl Gfx { @@ -358,10 +324,6 @@ impl Gfx { glyph_banks, sprites: [EMPTY_SPRITE; 512], sprite_count: 0, - scene_fade_level: 31, - scene_fade_color: Color::BLACK, - hud_fade_level: 31, - hud_fade_color: Color::BLACK, layer_buckets: [ Vec::with_capacity(128), Vec::with_capacity(128), @@ -637,20 +599,13 @@ impl Gfx { &*self.glyph_banks, ); } - - // 2. Scene-only fallback path: sprites and fades still work even before a - // cache-backed world composition request is issued for the frame. - Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color); - - // 3. HUD Fade: independent from scene fade; HUD composition itself remains external. - Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color); } /// Composes the world from the viewport cache using resolver copy requests. /// /// This is the cache-backed world path accepted by DEC-0013. The canonical scene /// is not consulted here; the renderer only consumes prepared cache materialization - /// plus sprite state and fade controls. + /// plus sprite state. pub fn render_scene_from_cache( &mut self, cache: &SceneViewportCache, @@ -681,9 +636,6 @@ impl Gfx { &*self.glyph_banks, ); } - - Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color); - Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color); } pub fn render_game2d_frame_packet(&mut self, packet: &Game2DFramePacket) { @@ -916,30 +868,6 @@ impl Gfx { } } - /// Applies the fade effect to the entire back buffer. - /// level: 0 (full color) to 31 (visible) - fn apply_fade_to_buffer(back: &mut [u32], level: u8, fade_color: Color) { - if level >= 31 { - return; - } // Fully visible, skip processing - - let weight = level as u16; - let inv_weight = 31 - weight; - let (fr, fg, fb, fa) = unpack_rgba8888(fade_color.raw()); - - for px in back.iter_mut() { - let (sr, sg, sb, sa) = unpack_rgba8888(*px); - - // Formula: (src * weight + fade * inv_weight) / 31 - let r = ((sr as u16 * weight + fr as u16 * inv_weight) / 31) as u8; - let g = ((sg as u16 * weight + fg as u16 * inv_weight) / 31) as u8; - let b = ((sb as u16 * weight + fb as u16 * inv_weight) / 31) as u8; - let a = ((sa as u16 * weight + fa as u16 * inv_weight) / 31) as u8; - - *px = pack_rgba8888(r, g, b, a); - } - } - pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) { let mut cx = x; let mut cy = y; @@ -1312,8 +1240,6 @@ mod tests { let update = resolver.update(&scene, 0, 0); let mut gfx = Gfx::new(16, 16, banks); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]); assert_eq!(gfx.back[0], Color::RED.raw()); @@ -1344,8 +1270,6 @@ mod tests { let update = resolver.update(&scene, 0, 0); let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); - gfx.scene_fade_level = 31; - gfx.hud_fade_level = 31; gfx.sprites[0] = Sprite { glyph: Glyph { glyph_id: 0, palette_id: 4 }, diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 8148df82..1cdaa9c9 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -241,8 +241,6 @@ mod tests { let mut resolver = SceneViewportResolver::new(16, 16, 4, 4, 12, 20); let update = resolver.update(&scene, 0, 0); - hardware.gfx.scene_fade_level = 31; - hardware.gfx.hud_fade_level = 31; hardware.gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]); hardware.gfx.present(); diff --git a/crates/console/prometeu-hal/src/gfx_bridge.rs b/crates/console/prometeu-hal/src/gfx_bridge.rs index ad0b7424..7dd85190 100644 --- a/crates/console/prometeu-hal/src/gfx_bridge.rs +++ b/crates/console/prometeu-hal/src/gfx_bridge.rs @@ -73,13 +73,4 @@ pub trait GfxBridge { fn sprite(&self, index: usize) -> &Sprite; fn sprite_mut(&mut self, index: usize) -> &mut Sprite; - - fn scene_fade_level(&self) -> u8; - fn set_scene_fade_level(&mut self, level: u8); - fn scene_fade_color(&self) -> Color; - fn set_scene_fade_color(&mut self, color: Color); - fn hud_fade_level(&self) -> u8; - fn set_hud_fade_level(&mut self, level: u8); - fn hud_fade_color(&self) -> Color; - fn set_hud_fade_color(&mut self, color: Color); } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index a1c748da..dd1526e4 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -710,8 +710,6 @@ fn tick_renders_scene_through_public_composer_syscalls() { let mut slots = [None; 16]; slots[0] = Some(0); glyph_slot_index.rebuild_from_slots(&slots); - hardware.gfx.scene_fade_level = 31; - hardware.gfx.hud_fade_level = 31; runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); let report = runtime.tick( @@ -759,8 +757,6 @@ fn tick_draw_text_survives_no_scene_frame_path() { ); let cartridge = cartridge_with_program(program, caps::GFX); let mut hardware = Hardware::new(); - hardware.gfx.scene_fade_level = 31; - hardware.gfx.hud_fade_level = 31; runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); let report = runtime.tick( diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 0f5f2c16..1fcf0b5c 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0083-fade-removal.md b/discussion/workflow/plans/PLN-0083-fade-removal.md index 0ebbd5bb..ce47a208 100644 --- a/discussion/workflow/plans/PLN-0083-fade-removal.md +++ b/discussion/workflow/plans/PLN-0083-fade-removal.md @@ -2,7 +2,7 @@ id: PLN-0083 ticket: render-frame-packet-boundary title: Fade Removal -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From d79c11faa847e3d2973e124ed05638fb4c5c10ad Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:23:18 +0100 Subject: [PATCH 13/16] implements PLN-0084 remove host debug overlay --- .../prometeu-host-desktop-winit/src/lib.rs | 1 - .../src/overlay.rs | 487 ------------------ .../prometeu-host-desktop-winit/src/runner.rs | 26 +- discussion/index.ndjson | 2 +- .../PLN-0084-host-debug-overlay-removal.md | 2 +- .../10-debug-inspection-and-profiling.md | 36 +- ...ortability-and-cross-platform-execution.md | 15 +- 7 files changed, 33 insertions(+), 536 deletions(-) delete mode 100644 crates/host/prometeu-host-desktop-winit/src/overlay.rs diff --git a/crates/host/prometeu-host-desktop-winit/src/lib.rs b/crates/host/prometeu-host-desktop-winit/src/lib.rs index be84a693..a91c231f 100644 --- a/crates/host/prometeu-host-desktop-winit/src/lib.rs +++ b/crates/host/prometeu-host-desktop-winit/src/lib.rs @@ -4,7 +4,6 @@ pub mod debugger; pub mod fs_backend; pub mod input; pub mod log_sink; -pub mod overlay; pub mod runner; pub mod stats; pub mod utilities; diff --git a/crates/host/prometeu-host-desktop-winit/src/overlay.rs b/crates/host/prometeu-host-desktop-winit/src/overlay.rs deleted file mode 100644 index a18795c0..00000000 --- a/crates/host/prometeu-host-desktop-winit/src/overlay.rs +++ /dev/null @@ -1,487 +0,0 @@ -use crate::stats::HostStats; -use prometeu_firmware::Firmware; - -const PANEL_X: usize = 6; -const PANEL_Y: usize = 3; -const PANEL_WIDTH: usize = 170; -const PANEL_PADDING_X: usize = 8; -const PANEL_PADDING_Y: usize = 3; -const LINE_HEIGHT: usize = 12; -const CHAR_SCALE: usize = 1; -const BAR_WIDTH: usize = PANEL_WIDTH - (PANEL_PADDING_X * 2); -const BAR_HEIGHT: usize = 6; - -const BG: [u8; 4] = [10, 18, 32, 208]; -const BORDER: [u8; 4] = [90, 126, 170, 255]; -const TEXT: [u8; 4] = [235, 240, 255, 255]; -const DIM: [u8; 4] = [150, 168, 196, 255]; -const WARN: [u8; 4] = [255, 104, 104, 255]; -const BAR_BG: [u8; 4] = [30, 42, 61, 255]; -const BAR_FILL: [u8; 4] = [91, 184, 255, 255]; -const BAR_WARN: [u8; 4] = [255, 150, 102, 255]; -const OVERLAY_HEAP_FALLBACK_BYTES: usize = 8 * 1024 * 1024; - -#[derive(Debug, Clone)] -pub(crate) struct OverlayMetric { - label: &'static str, - value: String, - warn: bool, -} - -#[derive(Debug, Clone)] -pub(crate) struct OverlayBar { - label: &'static str, - value: String, - ratio: f32, - warn: bool, -} - -#[derive(Debug, Clone)] -pub(crate) struct OverlaySnapshot { - rows: Vec<(OverlayMetric, OverlayMetric)>, - bars: Vec, - footer: Vec, -} - -#[derive(Clone, Copy)] -struct Rect { - x: usize, - y: usize, - width: usize, - height: usize, -} - -struct FrameCanvas<'a> { - frame: &'a mut [u8], - width: usize, - height: usize, -} - -pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &mut Firmware) -> OverlaySnapshot { - let tel = firmware.os.vm().telemetry_snapshot(); - let recent_logs = firmware.os.recent_logs(10); - let violations_count = - recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); - - let mut footer = Vec::new(); - if violations_count > 0 - && let Some(event) = - recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) - { - footer.push(OverlayMetric { - label: "CERT", - value: truncate_value(&event.msg, 28), - warn: true, - }); - } - - if let Some(report) = firmware.os.vm().last_crash_report() { - footer.push(OverlayMetric { - label: "CRASH", - value: truncate_value(&report.summary(), 28), - warn: true, - }); - } - - let cycles_ratio = ratio(tel.cycles_used, tel.cycles_budget); - let heap_total_bytes = firmware - .os - .vm() - .cert_config() - .max_heap_bytes - .or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None }) - .unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES); - let heap_ratio = ratio(tel.heap_used_bytes as u64, heap_total_bytes as u64); - let glyph_ratio = ratio(tel.glyph_slots_used as u64, tel.glyph_slots_total as u64); - let sound_ratio = ratio(tel.sound_slots_used as u64, tel.sound_slots_total as u64); - let scene_ratio = ratio(tel.scene_slots_used as u64, tel.scene_slots_total as u64); - - OverlaySnapshot { - rows: vec![ - ( - OverlayMetric { - label: "FPS", - value: format!("{:.1}", stats.current_fps), - warn: false, - }, - OverlayMetric { - label: "CERT", - value: violations_count.to_string(), - warn: violations_count > 0, - }, - ), - ( - OverlayMetric { - label: "HOST", - value: format!("{:.2}ms", stats.average_host_cpu_ms()), - warn: false, - }, - OverlayMetric { label: "STEPS", value: tel.vm_steps.to_string(), warn: false }, - ), - ( - OverlayMetric { label: "SYSC", value: tel.syscalls.to_string(), warn: false }, - OverlayMetric { label: "LOGS", value: tel.logs_count.to_string(), warn: false }, - ), - ], - bars: vec![ - OverlayBar { - label: "HEAP", - value: format!( - "{}K{}", - tel.heap_used_bytes.div_ceil(1024), - if heap_total_bytes > 0 { - format!(" / {}K", heap_total_bytes.div_ceil(1024)) - } else { - String::new() - } - ), - ratio: heap_ratio, - warn: tel.heap_used_bytes >= heap_total_bytes, - }, - OverlayBar { - label: "BUDGET", - value: if tel.cycles_budget > 0 { - format!( - "{:.1}K/{:.1}K", - tel.cycles_used as f64 / 1000.0, - tel.cycles_budget as f64 / 1000.0 - ) - } else { - "0.0K/0.0K".to_string() - }, - ratio: cycles_ratio, - warn: cycles_ratio >= 0.9, - }, - OverlayBar { - label: "GLYPH", - value: format!("{} / {} slots", tel.glyph_slots_used, tel.glyph_slots_total), - ratio: glyph_ratio, - warn: tel.glyph_slots_total > 0 && tel.glyph_slots_used >= tel.glyph_slots_total, - }, - OverlayBar { - label: "SOUNDS", - value: format!("{} / {} slots", tel.sound_slots_used, tel.sound_slots_total), - ratio: sound_ratio, - warn: tel.sound_slots_total > 0 && tel.sound_slots_used >= tel.sound_slots_total, - }, - OverlayBar { - label: "SCENE", - value: format!("{} / {} slots", tel.scene_slots_used, tel.scene_slots_total), - ratio: scene_ratio, - warn: tel.scene_slots_total > 0 && tel.scene_slots_used >= tel.scene_slots_total, - }, - ], - footer, - } -} - -pub(crate) fn draw_overlay( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - snapshot: &OverlaySnapshot, -) { - let mut canvas = FrameCanvas { frame, width: frame_width, height: frame_height }; - let panel_height = frame_height.saturating_sub(PANEL_Y * 2); - let panel_rect = Rect { x: PANEL_X, y: PANEL_Y, width: PANEL_WIDTH, height: panel_height }; - - fill_rect_alpha(&mut canvas, panel_rect, BG); - stroke_rect(&mut canvas, panel_rect, BORDER); - - let mut y = PANEL_Y + PANEL_PADDING_Y; - for (left, right) in &snapshot.rows { - draw_metric_pair(canvas.frame, canvas.width, canvas.height, y, left, right); - y += LINE_HEIGHT; - } - - for bar in &snapshot.bars { - let color = if bar.warn { WARN } else { TEXT }; - draw_text( - canvas.frame, - canvas.width, - canvas.height, - PANEL_X + PANEL_PADDING_X, - y, - bar.label, - DIM, - ); - draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &bar.value, color); - y += LINE_HEIGHT - 2; - - let bar_x = PANEL_X + PANEL_PADDING_X; - let bar_rect = Rect { x: bar_x, y, width: BAR_WIDTH, height: BAR_HEIGHT }; - fill_rect(&mut canvas, bar_rect, BAR_BG); - let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize; - fill_rect( - &mut canvas, - Rect { x: bar_x, y, width: fill_width, height: BAR_HEIGHT }, - if bar.warn { BAR_WARN } else { BAR_FILL }, - ); - stroke_rect(&mut canvas, bar_rect, BORDER); - y += BAR_HEIGHT + 6; - } - - for line in &snapshot.footer { - let color = if line.warn { WARN } else { TEXT }; - draw_text( - canvas.frame, - canvas.width, - canvas.height, - PANEL_X + PANEL_PADDING_X, - y, - line.label, - DIM, - ); - draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &line.value, color); - y += LINE_HEIGHT; - } -} - -fn draw_metric_pair( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - y: usize, - left: &OverlayMetric, - right: &OverlayMetric, -) { - let left_x = PANEL_X + PANEL_PADDING_X; - let right_x = PANEL_X + 86; - - draw_metric(frame, frame_width, frame_height, left_x, y, left); - draw_metric(frame, frame_width, frame_height, right_x, y, right); -} - -fn draw_metric( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - x: usize, - y: usize, - metric: &OverlayMetric, -) { - let color = if metric.warn { WARN } else { TEXT }; - draw_text(frame, frame_width, frame_height, x, y, metric.label, DIM); - draw_text(frame, frame_width, frame_height, x + 30, y, &metric.value, color); -} - -fn ratio(value: u64, total: u64) -> f32 { - if total == 0 { 0.0 } else { (value as f32 / total as f32).clamp(0.0, 1.0) } -} - -fn truncate_value(value: &str, max_len: usize) -> String { - let mut upper = value.to_ascii_uppercase(); - if upper.len() > max_len { - upper.truncate(max_len); - } - upper -} - -fn draw_text( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - x: usize, - y: usize, - text: &str, - color: [u8; 4], -) { - let mut cursor_x = x; - for ch in text.chars() { - if ch == '\n' { - continue; - } - draw_char(frame, frame_width, frame_height, cursor_x, y, ch.to_ascii_uppercase(), color); - cursor_x += 6 * CHAR_SCALE; - } -} - -fn draw_char( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - x: usize, - y: usize, - ch: char, - color: [u8; 4], -) { - let glyph = glyph_bits(ch); - for (row, bits) in glyph.iter().enumerate() { - for col in 0..5 { - if bits & (1 << (4 - col)) != 0 { - let mut canvas = FrameCanvas { frame, width: frame_width, height: frame_height }; - fill_rect( - &mut canvas, - Rect { - x: x + col * CHAR_SCALE, - y: y + row * CHAR_SCALE, - width: CHAR_SCALE, - height: CHAR_SCALE, - }, - color, - ); - } - } - } -} - -fn fill_rect_alpha(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) { - let max_x = (rect.x + rect.width).min(canvas.width); - let max_y = (rect.y + rect.height).min(canvas.height); - for py in rect.y..max_y { - for px in rect.x..max_x { - blend_pixel(canvas.frame, canvas.width, px, py, color); - } - } -} - -fn fill_rect(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) { - let max_x = (rect.x + rect.width).min(canvas.width); - let max_y = (rect.y + rect.height).min(canvas.height); - for py in rect.y..max_y { - for px in rect.x..max_x { - write_pixel(canvas.frame, canvas.width, px, py, color); - } - } -} - -fn stroke_rect(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) { - if rect.width == 0 || rect.height == 0 { - return; - } - fill_rect(canvas, Rect { x: rect.x, y: rect.y, width: rect.width, height: 1 }, color); - fill_rect( - canvas, - Rect { x: rect.x, y: rect.y + rect.height.saturating_sub(1), width: rect.width, height: 1 }, - color, - ); - fill_rect(canvas, Rect { x: rect.x, y: rect.y, width: 1, height: rect.height }, color); - fill_rect( - canvas, - Rect { x: rect.x + rect.width.saturating_sub(1), y: rect.y, width: 1, height: rect.height }, - color, - ); -} - -fn blend_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) { - let idx = (y * frame_width + x) * 4; - let alpha = color[3] as f32 / 255.0; - let inv = 1.0 - alpha; - frame[idx] = (frame[idx] as f32 * inv + color[0] as f32 * alpha).round() as u8; - frame[idx + 1] = (frame[idx + 1] as f32 * inv + color[1] as f32 * alpha).round() as u8; - frame[idx + 2] = (frame[idx + 2] as f32 * inv + color[2] as f32 * alpha).round() as u8; - frame[idx + 3] = 0xFF; -} - -fn write_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) { - let idx = (y * frame_width + x) * 4; - frame[idx] = color[0]; - frame[idx + 1] = color[1]; - frame[idx + 2] = color[2]; - frame[idx + 3] = color[3]; -} - -fn glyph_bits(ch: char) -> [u8; 7] { - match ch { - 'A' => [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], - 'B' => [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E], - 'C' => [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E], - 'D' => [0x1E, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1E], - 'E' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F], - 'F' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10], - 'G' => [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0E], - 'H' => [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], - 'I' => [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E], - 'J' => [0x01, 0x01, 0x01, 0x01, 0x11, 0x11, 0x0E], - 'K' => [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11], - 'L' => [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F], - 'M' => [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11], - 'N' => [0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11], - 'O' => [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], - 'P' => [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10], - 'Q' => [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D], - 'R' => [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11], - 'S' => [0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E], - 'T' => [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04], - 'U' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], - 'V' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04], - 'W' => [0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A], - 'X' => [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11], - 'Y' => [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04], - 'Z' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F], - '0' => [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E], - '1' => [0x04, 0x0C, 0x14, 0x04, 0x04, 0x04, 0x1F], - '2' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F], - '3' => [0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E], - '4' => [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02], - '5' => [0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E], - '6' => [0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E], - '7' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08], - '8' => [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E], - '9' => [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E], - ':' => [0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00], - '.' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06], - '/' => [0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10], - '%' => [0x19, 0x19, 0x02, 0x04, 0x08, 0x13, 0x13], - '(' => [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02], - ')' => [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08], - '-' => [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00], - '_' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F], - '+' => [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00], - ' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], - '?' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04], - _ => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04], - } -} - -#[cfg(test)] -mod tests { - use super::*; - use prometeu_drivers::hardware::Hardware; - - fn sample_snapshot() -> OverlaySnapshot { - OverlaySnapshot { - rows: vec![ - ( - OverlayMetric { label: "FPS", value: "60.0".to_string(), warn: false }, - OverlayMetric { label: "CERT", value: "2".to_string(), warn: true }, - ), - ( - OverlayMetric { label: "HOST", value: "1.23ms".to_string(), warn: false }, - OverlayMetric { label: "STEPS", value: "420".to_string(), warn: false }, - ), - ], - bars: vec![ - OverlayBar { - label: "HEAP", - value: "1024K / 8192K".to_string(), - ratio: 0.125, - warn: false, - }, - OverlayBar { - label: "BUDGET", - value: "9.0K/10.0K".to_string(), - ratio: 0.9, - warn: true, - }, - ], - footer: vec![OverlayMetric { - label: "CRASH", - value: "VM PANIC".to_string(), - warn: true, - }], - } - } - - #[test] - fn draw_overlay_writes_to_host_rgba_frame() { - let mut frame = vec![0u8; Hardware::W * Hardware::H * 4]; - draw_overlay(&mut frame, Hardware::W, Hardware::H, &sample_snapshot()); - assert!(frame.iter().any(|&byte| byte != 0)); - } - - #[test] - fn truncate_value_normalizes_and_caps() { - assert_eq!(truncate_value("panic: lowercase", 12), "PANIC: LOWER"); - } -} diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index a5044674..035d251b 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -3,7 +3,6 @@ use crate::debugger::HostDebugger; use crate::fs_backend::HostDirBackend; use crate::input::HostInputHandler; use crate::log_sink::HostConsoleSink; -use crate::overlay::{capture_snapshot, draw_overlay}; use crate::stats::HostStats; use crate::utilities::draw_rgba8888_to_rgba8; use pixels::wgpu::PresentMode; @@ -128,9 +127,6 @@ pub struct HostRunner { /// directly instead of depending on guest-visible inspection syscalls. debugger: HostDebugger, - /// Flag to enable/disable the technical telemetry display. - overlay_enabled: bool, - /// The physical audio driver. audio: HostAudio, /// Last known pause state to sync with audio. @@ -170,7 +166,6 @@ impl HostRunner { stats: HostStats::new(), debugger: HostDebugger::new(), - overlay_enabled: false, audio: HostAudio::new(), last_paused_state: false, presentation: PresentationState::default(), @@ -272,9 +267,6 @@ impl ApplicationHandler for HostRunner { } WindowEvent::RedrawRequested => { - let overlay_snapshot = - self.overlay_enabled.then(|| capture_snapshot(&self.stats, &mut self.firmware)); - // Get Pixels directly from the field (not via helper that gets the entire &mut self) let pixels = self.pixels.as_mut().expect("pixels not initialized"); @@ -286,10 +278,6 @@ impl ApplicationHandler for HostRunner { let src = self.hardware.gfx.front_buffer(); draw_rgba8888_to_rgba8(src, frame); - - if let Some(snapshot) = overlay_snapshot.as_ref() { - draw_overlay(frame, Hardware::W, Hardware::H, snapshot); - } } // <- frame borrow ends here if pixels.render().is_err() { @@ -309,12 +297,6 @@ impl ApplicationHandler for HostRunner { self.request_redraw_if_needed(); println!("[Debugger] Execution started!"); } - - if is_down && code == KeyCode::F1 { - self.overlay_enabled = !self.overlay_enabled; - self.invalidate_host_surface(); - self.request_redraw_if_needed(); - } } } @@ -332,12 +314,8 @@ impl ApplicationHandler for HostRunner { // 1. Process pending debug commands from the network. self.debugger.check_commands(&mut self.firmware, &mut self.hardware); - // Sync inspection mode state. This is host-owned overlay/debugger control, - // not a guest-visible debug ABI switch. - self.firmware - .os - .vm() - .set_inspection_active(self.overlay_enabled || self.debugger.stream.is_some()); + // Sync debugger inspection state; this is not a guest-visible debug ABI switch. + self.firmware.os.vm().set_inspection_active(self.debugger.stream.is_some()); // 2. Maintain a filesystem connection if it was lost (e.g., directory removed). if let Some(root) = &self.fs_root diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 1fcf0b5c..5ef00f93 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md b/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md index 0b30fe7c..31607e5a 100644 --- a/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md +++ b/discussion/workflow/plans/PLN-0084-host-debug-overlay-removal.md @@ -2,7 +2,7 @@ id: PLN-0084 ticket: render-frame-packet-boundary title: Host Debug Overlay Removal -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] diff --git a/docs/specs/runtime/10-debug-inspection-and-profiling.md b/docs/specs/runtime/10-debug-inspection-and-profiling.md index 4221ab0f..4c9ed75a 100644 --- a/docs/specs/runtime/10-debug-inspection-and-profiling.md +++ b/docs/specs/runtime/10-debug-inspection-and-profiling.md @@ -18,7 +18,7 @@ It covers: - profiling; - event and fault visibility; - certification input data; -- host-side debug overlay (HUD) isolation. +- host-side debug UI isolation. ## 2 Execution Modes @@ -131,15 +131,15 @@ The host may: - observe buffers separately - identify excessive redraw -Host-owned overlay or inspection surfaces MUST observe the last published logical frame rather than force continuous redraw to probe for changes. +Host-owned inspection surfaces MUST observe the last published logical frame rather than force continuous redraw to probe for changes. -When overlay, debugger, or other host-only diagnostics become visually stable: +When debugger or other host-only diagnostics become stable: - the host MUST preserve the last valid visible image; - the host MUST NOT keep recomposing the surface by continuous polling alone; -- additional redraw is only justified by a newly published logical frame or a host-owned invalidation event such as overlay toggle, resize, expose, or explicit debugger-visible state transition. +- additional redraw is only justified by a newly published logical frame or a host-owned invalidation event such as resize, expose, or explicit debugger-visible state transition. -Paused or breakpointed execution does not grant permission to swap logical machine buffers just to sustain a host-only HUD. +Paused or breakpointed execution does not grant permission to swap logical machine buffers just to sustain host-only diagnostics. ## 6 Time Profiling (Cycles) @@ -285,28 +285,32 @@ Each event has: - cost - consequence -## 11 Host-Side Debug Overlay (HUD) Isolation +## 11 Host-Side Debug UI Isolation -The visual Debug Overlay (HUD) for technical inspection is not part of the emulated machine pipeline. +The desktop host MUST NOT inject a debug overlay into Game or Shell output. Any +future visual debug UI is a separate product/domain concern and requires its own +decision before it can become part of host presentation. ### 10.1 Responsibilities 1. **Runtime:** Only exposes telemetry data via the machine diagnostics surface. It does not perform HUD rendering or string formatting. 2. **Emulated graphics contract:** Machine graphics primitives such as `fill_rect` and `draw_text` remain valid parts of the emulated graphics/syscall contract. They are not host overlay APIs. -3. **Host overlay module:** The Desktop Host owns a dedicated overlay module that performs host-side text, panel, and simple bar composition. -4. **Composition boundary:** The overlay is composed on the Host presentation surface after the emulated frame is ready. Overlay pixels must not be written back into the emulated framebuffer. -5. **Host control:** Overlay visibility and presentation policy remain under Host control. -6. **Host (Desktop):** Responsible for collecting telemetry from the runtime and rendering the HUD as a native, transparent layer. +3. **Host diagnostics:** The Desktop Host may consume telemetry for logs, + debugger streams, and non-render diagnostics, but it does not compose debug + pixels into the presentation surface. +4. **Composition boundary:** Published Game and Shell output must be presented + without host-owned debug overlay injection. ### 10.2 Principles -- **Zero Pipeline Interference:** HUD rendering must not inject pixels into the emulated framebuffer. It is applied after upscaling or as a separate display surface. -- **Zero Cycle Impact:** HUD-related processing (like formatting technical text) must occur outside the emulated machine cycles. -- **Toggle Control:** The activation of the overlay (typically via **F1**) is managed by the Host layer. +- **Zero Pipeline Interference:** Host diagnostics must not inject pixels into + the emulated framebuffer or the host presentation surface. +- **Zero Cycle Impact:** Diagnostic processing must occur outside the emulated + machine cycles. ### 10.3 Atomic Telemetry Model -To ensure zero-impact synchronization between the VM and the Host Debug Overlay, PROMETEU uses a **push-based atomic model**: +To ensure zero-impact synchronization between the VM and host diagnostics, PROMETEU uses a **push-based atomic model**: 1. **Atomic Storage:** Metrics such as cycles, syscalls, and memory usage are stored in a dedicated `AtomicTelemetry` structure using thread-safe atomic types (`AtomicU64`, `AtomicU32`, etc.). 2. **Lockless Access:** The Host (Desktop) reads these metrics asynchronously and without locks by taking a `snapshot()` of the atomic state. @@ -320,4 +324,4 @@ All machine diagnostics and profiling data: - feed the host-owned certification report; - are collected deterministically; - do not require a guest-visible debug ABI; -- are consistent regardless of whether the Host HUD is active or not. +- are consistent regardless of whether host diagnostics are active or not. diff --git a/docs/specs/runtime/11-portability-and-cross-platform-execution.md b/docs/specs/runtime/11-portability-and-cross-platform-execution.md index 4b8b8a5b..bb6a9d33 100644 --- a/docs/specs/runtime/11-portability-and-cross-platform-execution.md +++ b/docs/specs/runtime/11-portability-and-cross-platform-execution.md @@ -51,7 +51,8 @@ The contract is about logical behavior, not identical physical latency or throug - audio output - physical input collection - access to the sandbox file system -- **technical inspection surfaces (Debug Overlay/HUD)** +- technical inspection and debugger integrations that do not modify presented + Game or Shell pixels The host provides realization of machine surfaces. It does not redefine cartridge semantics. @@ -123,8 +124,8 @@ The graphics system: The platform layer: - consumes closed render submissions through a render-surface implementation -- **may overlay technical HUDs without modifying the logical framebuffer** -- may transport published RGBA8888 output into a host presentation surface where a host-only overlay layer is composed +- transports published RGBA8888 output into a host presentation surface without + injecting host-owned debug overlay pixels The host presentation layer MUST treat RGBA8888 as the canonical logical framebuffer format. RGB565 conversion is not part of the normal host @@ -136,17 +137,19 @@ host-owned invalidation, not by perpetual redraw polling. In particular: - a stable published submission MAY remain visible across multiple host wakeups without recomposition; -- a host MAY redraw the same logical frame again when its own surface is invalidated by resize, expose, or host-only overlay/debug changes; +- a host MAY redraw the same logical frame again when its own surface is invalidated by resize, expose, or debugger-visible host changes; - a host MUST NOT invent intermediate logical frames or require continuous redraw merely to discover whether a new logical frame exists. ## 9 Debug and Inspection Isolation -To preserve portability and certification purity, technical inspection tools (like the Debug Overlay) are moved to the Host layer. +To preserve portability and certification purity, technical inspection tools are host-owned and must not inject pixels into Game or Shell output. - **Host-exclusive:** These tools are only implemented where they are relevant (e.g., Desktop) and do not exist in the logical machine. - **Non-intrusive:** They must not consume machine cycles or alter memory state. - **Consistent Results:** A cartridge will produce the same logical results and certification metrics regardless of the Host's inspection capabilities. -- **Contract Boundary:** Emulated graphics primitives such as `fill_rect` and `draw_text` remain part of the machine contract, but Host overlays must not depend on them. +- **Contract Boundary:** Emulated graphics primitives such as `fill_rect` and + `draw_text` remain part of the machine contract; host diagnostics must not + depend on them for visual injection. ### 9.1 Atomic Telemetry Interface From 232fb7106d57cc994c6b9bbcec050a7e97b98570 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:25:35 +0100 Subject: [PATCH 14/16] implements PLN-0085 pbs render syscall declarations --- crates/console/prometeu-bytecode/src/model.rs | 2 +- crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs | 2 +- crates/tools/pbxgen-stress/src/lib.rs | 2 +- discussion/index.ndjson | 2 +- .../plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md | 2 +- docs/specs/runtime/16-host-abi-and-syscalls.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/console/prometeu-bytecode/src/model.rs b/crates/console/prometeu-bytecode/src/model.rs index 31825e56..df2d41a9 100644 --- a/crates/console/prometeu-bytecode/src/model.rs +++ b/crates/console/prometeu-bytecode/src/model.rs @@ -882,7 +882,7 @@ mod tests { fn test_valid_sysc_roundtrip() { let mut module = minimal_module(); module.syscalls = vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "draw_line".into(), version: 1, arg_slots: 4, diff --git a/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs b/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs index 7199f960..73c6b7b2 100644 --- a/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs +++ b/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs @@ -33,7 +33,7 @@ fn roundtrip_disasm_assemble_byte_equal_with_closures_and_coroutines() { emit(CoreOpCode::Sleep, Some(&3u32.to_le_bytes()), &mut prog); // HOSTCALL sysc[2] emit(CoreOpCode::Hostcall, Some(&2u32.to_le_bytes()), &mut prog); - // SYSCALL gfx.draw_line (0x1003) + // SYSCALL gfx2d.draw_line (0x1003) emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog); // FRAME_SYNC emit(CoreOpCode::FrameSync, None, &mut prog); diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index 97e1773a..cb00152f 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -126,7 +126,7 @@ pub fn generate() -> Result<()> { fs::create_dir_all(&out_dir)?; fs::write(out_dir.join("program.pbx"), bytes)?; fs::write(out_dir.join("assets.pa"), build_assets_pack()?)?; - fs::write(out_dir.join("manifest.json"), b"{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": 1,\n \"title\": \"Stress Console\",\n \"app_version\": \"0.1.0\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"gfx\", \"log\", \"asset\"]\n}\n")?; + fs::write(out_dir.join("manifest.json"), b"{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": 1,\n \"title\": \"Stress Console\",\n \"app_version\": \"0.1.0\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"composer\", \"gfx2d\", \"log\", \"asset\"]\n}\n")?; Ok(()) } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 5ef00f93..f6144c2e 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md b/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md index 5b928399..032f4d90 100644 --- a/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md +++ b/discussion/workflow/plans/PLN-0085-pbs-stdlib-and-syscall-declarations.md @@ -2,7 +2,7 @@ id: PLN-0085 ticket: render-frame-packet-boundary title: PBS Stdlib and Syscall Declarations -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] diff --git a/docs/specs/runtime/16-host-abi-and-syscalls.md b/docs/specs/runtime/16-host-abi-and-syscalls.md index 689feada..34ac736f 100644 --- a/docs/specs/runtime/16-host-abi-and-syscalls.md +++ b/docs/specs/runtime/16-host-abi-and-syscalls.md @@ -39,7 +39,7 @@ Syscalls are identified by a canonical triple: Example: ``` -("gfx", "present", 1) +("gfx2d", "draw_line", 1) ("audio", "play", 2) ("composer", "emit_sprite", 1) ``` From 91fab7de9660a5e824bce8c4afc2adfe6d9a8e69 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:29:02 +0100 Subject: [PATCH 15/16] implements PLN-0086 render boundary validation --- .../src/services/vm_runtime/tests.rs | 77 +++++++++++++++++++ discussion/index.ndjson | 2 +- ...6-end-to-end-render-boundary-validation.md | 2 +- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index dd1526e4..b931c981 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -483,6 +483,7 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() { }; assert_eq!(packet.commands.len(), 1); assert!(matches!(packet.commands[0], prometeu_hal::GfxUiCommand::Clear { .. })); + assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } #[test] @@ -782,6 +783,82 @@ fn tick_draw_text_survives_no_scene_frame_path() { assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } +#[test] +fn tick_game_submissions_are_immutable_and_latest_complete_wins() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; + let mut vm = VirtualMachine::default(); + let signals = InputSignals::default(); + let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nFRAME_SYNC\nHALT").expect("assemble"); + let program = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfx2d".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }], + ); + let cartridge = cartridge_with_program(program, caps::GFX); + let mut hardware = Hardware::new(); + + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let first_report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); + assert!(first_report.is_none()); + let first_submission = + runtime.render_manager.latest_complete_submission().expect("first submission").clone(); + let prometeu_hal::RenderSubmissionPacket::Game2D(first_packet) = &first_submission.packet + else { + panic!("expected first Game2D submission"); + }; + assert_eq!(first_packet.gfx2d.len(), 1); + assert!(matches!( + first_packet.gfx2d[0], + prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344) + )); + assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); + + let second_packet = prometeu_hal::Game2DFramePacket::new( + Default::default(), + vec![prometeu_hal::Gfx2dCommand::Clear { color: Color::from_raw(0x55667788) }], + ); + runtime + .render_manager + .close_frame_with_packet(prometeu_hal::RenderSubmissionPacket::Game2D(second_packet)) + .expect("manual Game2D packet matches active mode"); + let latest = runtime.render_manager.latest_complete_submission().expect("latest submission"); + assert_ne!(latest.frame_id, first_submission.frame_id); + let prometeu_hal::RenderSubmissionPacket::Game2D(latest_packet) = &latest.packet else { + panic!("expected latest Game2D submission"); + }; + let prometeu_hal::Gfx2dCommand::Clear { color: latest_color } = latest_packet.gfx2d[0] else { + panic!("expected latest clear command"); + }; + assert_eq!(latest_color, Color::from_raw(0x55667788)); + assert!(matches!( + first_packet.gfx2d[0], + prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344) + )); + prometeu_hal::HardwareBridge::publish_render_submission(&mut hardware, latest); + assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x55667788).raw()); +} + #[test] fn initialize_vm_success_clears_previous_crash_report() { let mut runtime = VirtualMachineRuntime::new(None); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index f6144c2e..e00a58c9 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"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":"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":"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":"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":"discussion/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"}]} diff --git a/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md b/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md index e148e987..acc9350b 100644 --- a/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md +++ b/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md @@ -2,7 +2,7 @@ id: PLN-0086 ticket: render-frame-packet-boundary title: End-to-End Render Boundary Validation -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline] From 5ba47eb20adaa9b88e1677f24ab524c469a1820d Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:52:58 +0100 Subject: [PATCH 16/16] Logical Render Pipelines and Command Packets --- .../config/runtime-edge-coverage-domains.json | 2 +- test-cartridges/stress-console/manifest.json | 2 +- test-cartridges/stress-console/program.pbx | Bin 1201 -> 1205 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/config/runtime-edge-coverage-domains.json b/files/config/runtime-edge-coverage-domains.json index 8a4a77b4..59716b17 100644 --- a/files/config/runtime-edge-coverage-domains.json +++ b/files/config/runtime-edge-coverage-domains.json @@ -46,7 +46,7 @@ ] }, "host-dependent": { - "baseline_percent": 43, + "baseline_percent": 35, "paths": [ "crates/host/prometeu-host-desktop-winit/src/" ] diff --git a/test-cartridges/stress-console/manifest.json b/test-cartridges/stress-console/manifest.json index 6d77693e..238ecdcf 100644 --- a/test-cartridges/stress-console/manifest.json +++ b/test-cartridges/stress-console/manifest.json @@ -5,5 +5,5 @@ "title": "Stress Console", "app_version": "0.1.0", "app_mode": "Game", - "capabilities": ["gfx", "log", "asset"] + "capabilities": ["composer", "gfx2d", "log", "asset"] } diff --git a/test-cartridges/stress-console/program.pbx b/test-cartridges/stress-console/program.pbx index aedc5922d613c461d17cbf34fb775a0dcc015793..a06e0838fad5e6503e5232c06faa65e4b9febd9e 100644 GIT binary patch delta 44 ucmdnUxs`K53gh&Rse75FSsBvPDvVNC8Ip5S6N?xb7#SECpkk9PSt**Z=?k