47 KiB
| id | ticket | title | status | created | resolved | decision | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| AGD-0040 | vm-render-parallel-execution-boundary | VM and Render Parallel Execution Boundary | accepted | 2026-06-04 |
|
Agenda - VM and Render Parallel Execution Boundary
Contexto
DSC-0038 estabeleceu RenderSubmission como snapshot fechado. A lesson LSN-0047 registra explicitamente que uma submissao fechada e o primitivo util para concorrencia futura, mesmo que o v1 ainda consuma tudo de forma sincrona.
No codigo atual, RenderManager guarda apenas latest_complete_submission, fecha frames no fluxo da VM/runtime e publica para uma RenderSurface via chamada direta. Isso e correto para o v1, mas ainda nao decide como mover rasterizacao/publicacao para outra thread ou core.
A demanda aqui e separar a VM continuando em um core/thread de execucao logica enquanto o pipeline grafico consome submissions fechadas em outro worker/core, sem acesso mutavel vivo a VM, Hardware ou Gfx.
Problema
Prometeu ainda nao tem contrato operacional para paralelismo VM/render.
A fronteira de dados ja aponta a direcao, mas faltam decisoes sobre:
- quem cria e possui a fila ou slot de submissao;
- se a politica
latest complete submission winsbasta quando ha worker real; - como lidar com frame pacing, backpressure e queda de frames;
- quais recursos podem ser compartilhados read-only;
- como manter determinismo da VM;
- como representar isso em desktop host, handheld proprio e testes;
- se o render worker e parte do runtime, do host, da HAL ou apenas uma abstracao futura.
Pontos Criticos
1. A VM nao pode depender do consumidor de render
A VM deve produzir estado/comandos e avançar segundo seu proprio budget. O render worker pode atrasar, pular frames ou publicar a ultima submissao completa, mas nao deve exigir acesso mutavel ao estado vivo da VM para rasterizar.
2. Snapshot fechado nao e automaticamente thread-safe
RenderSubmission owned ajuda, mas qualquer referencia indireta a assets, scene cache, bancos de glyphs ou surfaces precisa ter politica clara: copia, handle estavel, Arc/read-only, snapshot de recurso, ou acesso mediado.
3. latest complete submission wins precisa virar protocolo
Como principio, nao queremos fila infinita. Mas uma implementacao com worker precisa decidir se usa single-slot atomico, double/triple buffering, canal bounded, sequence numbers, acknowledgements ou outro mecanismo.
4. Frame pacing muda quando render sai do fluxo principal
Hoje tick, fechamento de frame, consumo e host presentation estao acoplados. Com worker separado, a VM pode produzir mais rapido que o render ou o render pode apresentar em cadencia diferente. Isso precisa de uma regra de tempo.
5. Desktop thread nao e automaticamente modelo de handheld core
No host desktop, a implementacao natural pode ser uma thread. No hardware proprio, pode ser outro core, DMA, coprocessador, scanout ou rotina cooperativa. A decisao deve separar contrato runtime de implementacao host.
Opcoes
Opcao A - Manter consumo sincrono, apenas preservar contrato de snapshot
Abordagem:
Nao criar worker agora. Consolidar somente as invariantes: submissions owned/fechadas, sem acesso mutavel vivo durante consumo, e testes que impedem regressao para immediate rendering.
Pro:
- menor risco;
- evita concorrencia prematura;
- mantem foco em estabilizar o novo renderer;
- ainda prepara a arquitetura.
Con:
- nao reduz latencia/custo no curto prazo;
- pode adiar decisoes dificeis sobre recursos compartilhados;
- o codigo pode voltar a assumir consumo sincrono se nao houver guardrails.
Maintainability:
Alta como etapa intermediaria, mas incompleta para a demanda de core/thread separado.
Opcao B - Render worker com slot bounded latest-complete
Abordagem:
Criar um worker de render que consome um slot/canal bounded de RenderSubmission. O produtor substitui a submissao pendente quando uma mais nova fica pronta; o consumidor sempre pega a ultima completa disponivel.
Pro:
- implementa a politica ja aceita;
- evita fila infinita;
- desacopla VM e rasterizacao;
- aproxima desktop e hardware proprio por contrato de single/latest slot.
Con:
- exige
Send/ownership/read-only real nos dados; - precisa regra de shutdown, erro e sincronizacao;
- pode mascarar frames descartados sem telemetria adequada.
Maintainability:
Boa se o protocolo for pequeno e testavel.
Opcao C - Fila bounded com N submissions
Abordagem:
Usar canal bounded com 2 ou 3 submissions para suavizar picos e permitir que o render trabalhe atrasado.
Pro:
- simples em desktop;
- reduz bloqueios imediatos;
- permite medicao de atraso.
Con:
- contradiz parcialmente latest-wins se N crescer;
- aumenta memoria e latencia;
- pode apresentar frames velhos;
- menos adequado para handheld com memoria restrita.
Maintainability:
Media. Pode ser util como implementacao, mas precisa de regra forte para descarte.
Opcao D - Render thread como detalhe exclusivo do host desktop
Abordagem:
Mover rasterizacao/publicacao para thread do host, sem mudar contrato runtime/HAL.
Pro:
- melhora desktop sem mexer no console;
- menor impacto no runtime;
- facilita experimentos.
Con:
- pode criar divergencia entre host e alvo real;
- nao resolve contrato para hardware proprio;
- risco de esconder dependencia mutavel por baixo do host.
Maintainability:
Media a baixa como solucao canonica. Boa apenas para experimento ou profiling.
Sugestao / Recomendacao
A recomendacao inicial e uma decisao em duas fases:
- Fechar agora as invariantes da Opcao A como contrato obrigatorio.
- Planejar a Opcao B como primeiro modelo real de worker quando a base estiver pronta.
O contrato desejado deve ser:
VM/runtime core
builds domain buffers
closes RenderSubmission snapshot
publishes latest complete submission to bounded handoff
render worker/core
takes latest complete submission
consumes immutable packet/resource handles
rasterizes/publishes through RenderSurface
reports telemetry/backpressure
O worker nao deve receber &mut Hardware, &mut Gfx, &mut VirtualMachineRuntime ou ponte que permita voltar ao estado vivo da VM.
Perguntas Respondidas
- O handoff deve ser single-slot latest-wins, canal bounded N=2, ou outro protocolo?
- Resposta: single-slot latest-wins, sem bloquear VM/produtor.
- Quais tipos dentro de
RenderSubmissionprecisam serSend + Syncou owned?- Resposta: submissions/packets cruzam a fronteira como dados owned pequenos; APIs read-only compartilhadas de bancos/cache precisam provar seguranca de compartilhamento antes do worker real.
- Como assets, scene cache e glyph banks cruzam a fronteira sem copia pesada?
- Resposta: por IDs/handles estaveis e APIs read-only; bancos sao imutaveis por contrato, e o viewport cache pertence ao core logico.
- Quem e dono do worker:
RenderManager, host desktop, SystemOS service ou HAL?- Resposta:
SystemOS/lifecycle decide politica/AppMode;RenderManagercoordena handoff, epoch, ownership e telemetry; host/HAL fornece capacidades e execucao concreta.
- Resposta:
- O render worker pode bloquear a VM ou apenas descartar/substituir submissions?
- Resposta: nao bloqueia a VM/produtor; descarta/substitui pending submissions conforme latest-wins.
- Como medir frames produzidos, consumidos, descartados e apresentados?
- Resposta: counters e ultimos frame IDs separados para produced, replaced/dropped, consumed, presented, repeated, errors e stale epoch discards.
- Como shutdown, crash, mode transition e troca de cart interagem com worker?
- Resposta: transicoes incrementam epoch/generation, invalidam pending/current obsoletos e impedem present de frames do owner anterior.
- Qual parte deve ser especificada agora e qual deve esperar implementacao real?
- Resposta: especificar agora o contrato de packet fechado, handoff latest-wins, frame pacing, AppMode policy, epoch ownership e telemetry minima; implementacao concreta do worker/render backend fica para decision/plan posterior.
Grandes Problemas Esperados
Topico 1 - Game2DFramePacket como snapshot completo
Status: direcao respondida; execucao pendente.
Pergunta de fechamento: o consumidor de RenderSubmissionPacket::Game2D consegue compor o frame completo a partir do packet fechado, sem depender de caminho alternativo por estado vivo?
Hoje Hardware::publish_render_submission ignora parte do Game2DFramePacket quando ha scene ativa, porque chama frame_composer.render_frame(&mut self.gfx) em vez de consumir o packet completo. Isso quebra a premissa de que o worker pode receber um snapshot fechado e rasterizar sem estado vivo.
Antes de qualquer render worker, Game2DFramePacket precisa ser a fonte completa de composicao: scene/layers/sprites, gfx2d overlay e publicacao.
Direcao atual: nao ha debate arquitetural relevante restante para este topico. gfx2d deve ser overlay apos a composicao Game 2D, e o consumidor de RenderSubmissionPacket::Game2D deve aplicar a mesma ordem em frames com scene ativa e sem scene ativa. O que falta e corrigir o caminho atual que contorna o packet quando ha scene ativa e adicionar teste de regressao cobrindo scene ativa + primitive overlay.
Topico 2 - Recursos pesados e fronteira read-only
Status: direcao respondida.
Pergunta de fechamento: como scene cache, glyph banks, memory banks, assets e recursos futuros cruzam a fronteira sem &mut Hardware, &mut Gfx ou FrameComposer vivo?
RenderSubmission e owned, mas a composicao real ainda depende de FrameComposer, scene cache, resolver, glyph banks, memory banks e Gfx. Um worker nao pode receber &mut Hardware nem consultar FrameComposer vivo. Precisamos decidir como recursos pesados entram no snapshot: handles estaveis, Arc read-only, snapshots pequenos, ou acesso mediado.
O que falta discutir: o ponto nao e copiar todos os recursos pesados para dentro do packet. Isso seria caro e provavelmente errado. O que falta e separar tres categorias:
- estado de frame que deve estar no packet fechado: camera, scene binding, sprites, HUD, primitives, copy/update requests ja calculados;
- recursos residentes grandes que podem ser referenciados por handle/slot e lidos via
Arc/read-only: glyph banks, scene banks, caches materializados; - estado mutavel de preparacao que deve ficar no core logico antes do handoff: resolver update, cache refresh, asset installation, bank residency changes.
Direcao provavel: o render worker deve receber um snapshot pequeno com comandos e handles estaveis, mais acesso read-only aos bancos/caches necessarios. A atualizacao de resolver/cache e a instalacao/troca de assets devem acontecer antes do handoff, no core logico ou em um servico proprietario, nunca durante rasterizacao no worker.
Perguntas respondidas:
- O
Game2DFramePacketdeve carregar apenasBoundScenePacket { bank_id }ou tambem um snapshot/read handle do cache materializado?- Resposta: IDs somente. Os bancos sao imutaveis por definicao; o packet nao deve capturar handles pesados por frame.
- O cache de viewport pertence ao produtor logico, ao render worker, ou a um recurso compartilhado read-only apos refresh?
- Resposta: o viewport cache pertence ao core logico. O render worker deve acessa-lo apenas por uma API de leitura.
- Uma troca de asset/bank pode invalidar recursos enquanto uma submission antiga ainda esta sendo rasterizada?
- Resposta: a submission aponta por ID para o recurso. Se o recurso for trocado, a submission ainda pode executar e o resultado pode ficar confuso/diferente. Disciplina de troca de assets e responsabilidade do dev ou framework.
- O handoff deve capturar
Arc<GlyphBank>/Arc<SceneBank>por frame para garantir liveness, ou apenas IDs de slot com regra de nao substituir enquanto houver frame em voo?- Resposta: somente IDs. Nao vamos garantir integridade do resultado por captura de recurso; disciplina de troca e responsabilidade do dev ou framework.
- Quais tipos precisam provar
Send + Syncantes de qualquer worker real?- Esclarecimento: esta pergunta e tecnica. Em Rust, qualquer dado/API compartilhado entre threads precisa ser seguro para envio/leitura concorrente. Com a resposta "IDs somente" no packet, a exigencia sai do
Game2DFramePacketpesado e recai principalmente sobre a API de leitura dos bancos/cache e sobre o protocolo de handoff. - Resposta: essa prova fica como criterio tecnico de implementacao do worker real; a decisao arquitetural e que a fronteira compartilhada deve ser owned ou read-only, sem
&mut Hardware,&mut Gfx,FrameComposervivo ou acesso mutavel a VM.
- Esclarecimento: esta pergunta e tecnica. Em Rust, qualquer dado/API compartilhado entre threads precisa ser seguro para envio/leitura concorrente. Com a resposta "IDs somente" no packet, a exigencia sai do
Direcao respondida: packet pequeno com IDs, bancos imutaveis por contrato, viewport cache pertencente ao core logico e acesso do worker por API read-only. O sistema nao deve tentar garantir integridade visual de submissions antigas contra troca disciplinarmente incorreta de assets/banks; essa disciplina pertence ao dev/framework.
Topico 3 - Protocolo de handoff latest-wins
Status: direcao respondida.
Pergunta de fechamento: qual protocolo substitui o Option<RenderSubmission> local quando houver outro core/thread?
O latest_complete_submission atual e apenas um Option<RenderSubmission> dentro do runtime. Para outro core/thread, isso precisa virar protocolo: single-slot latest-wins, canal bounded, sequence numbers, descarte, acknowledgement e telemetria.
O que falta discutir: este topico define o contrato de handoff, nao a politica de frame pacing completa. Precisamos escolher como uma submission sai do core logico e chega ao render worker:
- single-slot latest-wins: produtor substitui a submission pendente por uma mais nova, e o worker consome a ultima disponivel;
- canal bounded N=1/2: produtor tenta enviar, e o protocolo decide se descarta antigo, descarta novo ou bloqueia;
- acknowledgement: worker informa qual
frame_idconsumiu/apresentou, ou o produtor apenas publica e esquece; - ownership: a submission e movida para o handoff ou clonada do
RenderManager; - sequence numbers:
frame_idbasta ou precisamos de counters separados para produzido/consumido/apresentado; - escopo por pipeline: Game pode usar handoff; Shell pode publicar sincrono.
Direcao provavel: para o primeiro modelo real, preferir single-slot latest-wins, sem fila crescente e sem bloquear a VM. RenderManager deixa de ser apenas dono de Option<RenderSubmission> e passa a publicar uma submission fechada para um handoff pequeno. O worker pega a ultima submission disponivel; se uma submission antiga for substituida antes do consumo, isso conta como drop/substituicao mensuravel.
Direcao aceita: single-slot latest-wins, sem bloquear a VM/produtor.
Perguntas respondidas:
- O slot guarda exatamente uma pending submission alem da currently rendering, ou apenas a ultima submission atomica disponivel?
- Resposta: o handoff guarda uma pending submission owned. Se o worker ja pegou uma submission, ela esta fora do slot e em consumo. O slot representa apenas a proxima/latest pending.
- Quando o worker esta renderizando frame N e o core produz N+1 e N+2, N+1 deve ser descartado?
- Resposta: sim.
N+2substituiN+1seN+1ainda nao foi retirado pelo worker. Isso e counted como replaced/dropped-before-consume.
- Resposta: sim.
- O produtor precisa saber que uma submission foi consumida, ou so que foi substituida/publicada?
- Resposta: o produtor nao espera acknowledgement para continuar. O worker deve reportar consumed/presented como telemetria assíncrona, mas isso nao bloqueia nem participa da progressao da VM.
- Observacao: a preocupacao restante e a semantica do
tick. Hoje VM e render estao dentro do mesmo tick sincrono. Com worker, o tick deve fechar/publicar a submission, mas nao esperar ACK de render. A relacao entre tick, present efetivo e telemetria fica para o Topico 4.
frame_ide suficiente para telemetria, ou precisamos de contadores separados de produced/dropped/consumed/presented?- Resposta:
frame_ide suficiente para ordenacao, mas nao para observabilidade. Precisamos de contadores separados: produced, replaced/dropped-before-consume, consumed, presented, render_errors/present_errors, e ultimos frame ids por etapa.
- Resposta:
- Shell UI fica fora do protocolo worker por default, ou usa o mesmo handoff quando configurado?
- Resposta: Shell UI fica fora por default e pode continuar sincrono/local. O mesmo protocolo pode ser reutilizado se uma configuracao futura quiser worker para Shell, mas a politica inicial e por pipeline/AppMode.
Direcao respondida: o protocolo base e single-slot latest-wins com ownership moved para o handoff, pending slot substituivel, worker consumindo owned submission, VM/produtor nunca bloqueado, ack apenas para telemetria e politica por pipeline.
Confirmacoes de 2026-06-04:
- ownership moved para o handoff: aceito;
- produtor sem ACK bloqueante: aceito, com a preocupacao de definir a nova semantica de tick;
- contadores separados: seguir com essa direcao;
- Shell UI fora do worker por default: aceito.
Topico 4 - Frame pacing e backpressure
Status: direcao respondida.
Pergunta de fechamento: a VM bloqueia, descarta, substitui ou apenas mede atraso quando o render worker esta atrasado?
Hoje tick, fechamento, raster e present acontecem no mesmo fluxo. Com worker separado, a VM pode produzir frames mais rapido que o render consome, ou o render pode apresentar em outra cadencia. Precisamos decidir se a VM bloqueia, descarta, substitui, ou apenas mede atraso.
Direcao em discussao: se o render real for para worker/core, o present passa a ser responsabilidade do worker/render surface e deve mirar a cadencia de display, inicialmente 60Hz. O worker nao consome uma fila crescente; ele consome a latest submission disponivel pelo protocolo single-slot e apresenta em sua cadencia.
O core logico/VM deixa de precisar reservar tempo de render dentro do mesmo tick sincrono, mas nao deve rodar livremente. O frame logico da VM e parte do modelo de tempo do jogo: animacoes, input, scripts e simulacao dependem dele. Portanto, a VM deve continuar paced por frame logico, mirando 1:1 com a cadencia do worker/display sempre que possivel.
Idealmente, ha relacao 1:1:
1 logical VM frame
-> 1 RenderSubmission
-> 1 presented worker frame
Mas o contrato nao deve depender de sempre conseguir isso. A direcao revisada e:
- o worker/render surface e dono do
presentem 60Hz; - o worker apresenta o ultimo frame processado/disponivel;
- quando o worker aceita/pega uma nova submission para apresentacao, isso avanca o frame counter visivel;
- se a VM ainda nao disponibilizou uma nova submission, o worker mantem a ultima renderizacao e registra skip/no-new-frame;
- a VM nao roda frames logicos ilimitados a frente do display; ela so deve iniciar novo frame logico quando a cadencia permitir produzir o proximo frame esperado;
- se o worker ainda esta processando um frame anterior, a VM pode preparar/publicar o proximo frame logico dentro do limite 1:1, mas nao deve acumular multiplos frames logicos pendentes.
Isso muda a leitura do latest-wins: ele continua util para substituir uma pending submission que ainda nao foi pega pelo worker, mas nao deve virar autorizacao para a VM rodar muito a frente do display. O objetivo e desacoplar custo de raster/present, nao desacoplar o tempo logico do jogo da cadencia de frame.
Se o worker nao rasterizar dentro do budget de 60Hz, o sistema deve contar atraso/drop/skip em telemetria. A VM nao deve bloquear esperando ACK de render, mas deve respeitar o pace de frame logico definido pelo display/worker.
Refinamento importante:
A VM nao espera ACK de render/present de uma submission especifica, mas deve esperar a autorizacao de pace derivada do frame counter/display. Ou seja, o render worker nao confirma "terminei seu frame N, pode seguir"; em vez disso, o sistema avanca uma cadencia de frame e a VM so inicia o proximo logical frame quando essa cadencia permite. Isso evita que a VM rode livremente sem transformar present em bloqueio sincrono.
Corner cases de frame pacing
-
Worker em 60Hz, VM pronta antes do proximo refresh.
- A VM deve ficar aguardando o proximo frame pace para iniciar novo logical frame.
- Nao deve produzir multiplas submissions futuras.
-
Worker apresenta refresh sem nova submission.
- O worker repete/mantem a ultima renderizacao.
- O sistema registra skip/no-new-frame.
- O frame counter visivel pode avancar, mas a VM nao deve simular um novo logical frame retroativamente.
-
VM produz submission, worker ainda esta rasterizando frame anterior.
- A submission nova fica no single-slot pending.
- Se outra submission fosse produzida antes do worker pegar a pending, ela substituiria a pending; porem o pace da VM deve impedir esse acúmulo na pratica.
-
Worker pega pending submission para rasterizar.
- A pending sai do slot e passa a pertencer ao worker.
- A VM nao espera ACK dessa submission, mas so inicia novo logical frame quando o pace permitir.
-
Worker perde prazo de 16.67ms.
- O sistema registra atraso/render-overrun.
- A VM nao deve tentar compensar produzindo frames extras.
- A proxima permissao de logical frame deve seguir a politica de pace, nao backlog.
-
VM demora mais que a janela esperada de logical frame.
- O runtime nao deve encerrar o frame por budget como mecanismo normal.
FRAME_SYNCcontinua sendo o fim canonico do logical frame.- O sistema registra logical-frame-overrun para certificacao/watchdog/diagnostico.
- O worker continua apresentando a ultima renderizacao.
- Quando a VM finalmente publica a submission, ela representa o proximo logical frame sequencial, nao um frame pulado.
-
Mode transition Game/Shell enquanto ha pending ou currently-rendering submission.
- A submission antiga nao deve ser apresentada depois que o modo mudou, a menos que a politica de transition permita explicitamente.
- O handoff precisa carregar generation/mode epoch ou equivalente para invalidar frames obsoletos.
-
Cartridge/app troca enquanto worker tem frame antigo.
- Submissions de app/cart anterior devem ser descartadas por generation/epoch.
- O worker deve limpar ou substituir a surface conforme politica de lifecycle.
-
Crash/panic durante logical frame antes de publicar submission.
- O worker segue apresentando ultimo frame valido ate receber crash/shell submission.
- O crash path deve poder invalidar pending game frame.
-
Crash/panic no render worker.
- Nao pode corromper determinismo da VM.
- Deve virar telemetria/erro de render e possivelmente fallback para sync/local render ou crash surface.
- Input sampling.
- Input deve ser amostrado no inicio do logical frame autorizado pelo pace.
- Se o worker repetir frame por falta de submission nova, nao deve haver novo input sampling/logical update correspondente.
- Frame counter semantics.
- Precisamos separar pelo menos logical_frame_id, produced_submission_frame_id e presented_frame_id.
- O frame counter que autoriza a VM deve representar cadencia logica/display, nao ACK de uma submission especifica.
Avaliacao de desenho
O desenho fica mais complexo se o render worker tambem virar a autoridade do frame counter que libera a VM. Isso mistura duas responsabilidades:
- cadencia logica do sistema;
- consumo/rasterizacao/publicacao de submissions.
Uma alternativa mais limpa e introduzir uma autoridade de pace separada, por exemplo FrameClock ou scheduler de frame, que emite ticks de 60Hz. A VM e o render worker observam essa cadencia, mas nenhum dos dois e dono exclusivo dela.
Modelo preferido para reduzir complexidade:
FrameClock / frame scheduler
emits 60Hz frame ticks
VM/core logico
runs at most one logical frame per frame tick
samples input on authorized logical frame
publishes at most one latest submission
render worker/surface
presents on display cadence
consumes latest available submission
repeats last rendered frame if no new submission exists
Assim, a VM nao espera ACK do worker e tambem nao roda livremente. Ela espera o frame clock. O worker nao precisa "desbloquear" a VM; ele apenas consome e apresenta. Isso preserva 1:1 como alvo sem acoplar progresso logico a sucesso/falha de rasterizacao.
Conclusao provisoria: preferir FrameClock/scheduler como autoridade de cadencia, RenderManager como produtor de submissions, e render worker como consumidor/presenter. Evitar que o worker seja dono do frame counter canonico.
Refinamento sobre Shell/App lifecycle:
O FrameClock/scheduler nao deve impor um game loop ao Shell. O loop de um game e diferente do lifecycle de um app UI, e em Prometeu o game loop e entregue no hardware/runtime como parte do modelo de console. Shell/SystemOS deve ser tratado como lifecycle/event-driven: input, janelas, foco, app lifecycle e invalidacao visual determinam quando ha trabalho logico ou render novo.
Portanto, FrameClock deve autorizar cadencia de logical frame para pipelines de game, mas Shell UI nao deve necessariamente consumir um frame logico de VM a cada 60Hz. Para Shell:
SystemOS/Shell lifecycle events
-> input/window/app updates when needed
-> build ShellUiFramePacket on invalidation/event
-> present/repeat last surface as needed
Isso reforca que FrameScheduler nao e "scheduler da VM". Ele e uma fonte de cadencia para workloads que precisam de frame pacing, especialmente Game2D/Game3D. Shell/App lifecycle pode usar outro modelo, com render por invalidacao ou por eventos, ainda que o host/display continue apresentando em 60Hz.
Resposta sobre budget e frames lentos:
FRAME_SYNC e o fim canonico do logical frame. O budget antigo era um mecanismo grosseiro para pausar a VM e permitir que o tick continuasse; nesse desenho ele nao deve ser usado como forma normal de encerrar ou cortar um frame logico.
Budget passa a existir para certificacao, watchdog, diagnostico e deteccao de overrun. Se um logical frame demora mais que aproximadamente 16.67ms, nao pulamos logical frames da VM para alcançar o display. Frames logicos devem ser sequenciais. O resultado visivel e stutter/repeticao do ultimo frame pelo worker, nao time-skip semantico.
VM logical frame N demora demais
-> worker repete ultimo frame disponivel
-> runtime registra overrun
-> VM continua ate FRAME_SYNC/trap/halt/watchdog fatal
-> submission publicada e N, nao N+k
Perguntas respondidas:
- O worker deve repetir o ultimo frame quando nao houver submission nova no refresh de 60Hz?
- Resposta: sim. O worker mantem a ultima renderizacao e registra skip/no-new-frame.
- A VM deve continuar limitada a um logical frame por tick de sistema, ou pode produzir multiplas submissions entre presents?
- Resposta: a VM nao deve produzir multiplos frames logicos a frente do display. O alvo e 1 logical frame por frame worker/display, com no maximo uma pending submission pelo protocolo latest-wins.
FRAME_SYNCcontinua sendo o limite canonico de logical frame mesmo sem present sincrono?- Resposta: sim.
FRAME_SYNCcontinua delimitando logical frame da VM. O present deixa de ser sincrono, mas o frame logico continua paced.
- Resposta: sim.
- Qual budget permanece na VM: ciclos/instrucoes por logical frame, tempo real, ou ambos?
- Resposta:
FRAME_SYNCencerra o logical frame. Budget nao e mecanismo normal de tick/frame advance. Budget/ciclos/tempo real ficam para certificacao, watchdog, diagnostico e overrun policy.
- Resposta:
- Como detectar game rodando rapido demais se o render worker esta desacoplado?
- Resposta: o runtime nao permite que a VM rode livremente a frente do frame counter/worker. A telemetria deve contar produced, accepted/presented e skips para detectar desalinhamento.
Topico 5 - Politica por pipeline/AppMode
Status: direcao respondida; detalhamento de lifecycle movido para DSC-0041.
Pergunta de fechamento: quais pipelines devem poder usar worker e quais podem continuar sincronos/localmente?
Shell UI pode continuar sincrona/local. O contrato comum deve ser packet fechado, mas a politica de execucao deve variar por pipeline. Forcar Shell UI para render worker pode aumentar complexidade sem ganho.
O que precisamos acertar: este topico define a matriz de politica por AppMode/pipeline. O sistema hoje tem AppMode::Game e AppMode::Shell, mas esses modos nao devem receber a mesma semantica de loop/render.
Direcao ja encaminhada:
Game2De futuroGame3Dsao workloads frame-paced. Devem poder usarFrameSchedulere render worker.ShellUie Shell/SystemOS sao lifecycle/event-driven. Devem poder continuar sincronos/localmente e renderizar por invalidacao/evento.- O contrato comum continua sendo
RenderSubmissionfechado. - A politica de execucao do consumidor pode variar por pipeline/AppMode.
Perguntas respondidas:
AppMode::Gamesempre usaFrameScheduler, mesmo se o game nao tiver render pesado?- Resposta: sim. Game e frame-paced por definicao. Futuramente
AppMode::Gamepode virar algo mais explicito comoAppMode::Game2D, mas cada AppMode deve ter uma politica de render explicita.
- Resposta: sim. Game e frame-paced por definicao. Futuramente
AppMode::Gamedeve usar render worker por default, ou isso deve ser capability/configuracao de host?- Resposta: Game usa render worker por default quando o host/runtime suporta, com fallback sincrono/local.
AppMode::Shelldeve ficar explicitamente fora doFrameSchedulerlogico de game?- Resposta: sim. Shell nao herda game loop.
- Shell UI deve publicar sincrono/local por default, com worker apenas opt-in?
- Resposta: sim. Shell UI e sincrono/local por default; worker so como opt-in futuro.
- Apps VM-backed dentro do Shell seguem lifecycle de Shell ou podem declarar workload frame-paced proprio?
- Resposta: seguem lifecycle de Shell, sem direito a declarar workload frame-paced proprio.
- Firmware screens como splash/crash seguem politica Shell/local ou politica propria?
- Resposta: seguem politica Shell/local.
- A escolha de politica fica no
RenderManager, no SystemOS/lifecycle, ou em uma camada de host/runtime configuration?- Resposta: SystemOS/lifecycle decide o perfil/AppMode;
RenderManagerexecuta a politica; host/runtime fornece capacidades e fallback.
- Resposta: SystemOS/lifecycle decide o perfil/AppMode;
Direcao respondida: cada AppMode deve ter politica explicita de render. Game atual e frame-paced e pode usar worker por default quando disponivel. Shell e lifecycle/event-driven e sincrono/local por default. Apps VM-backed dentro do Shell seguem lifecycle de Shell, sem opt-in para game pacing proprio.
Topico 6 - Lifecycle, transitions e ownership da surface
Status: direcao respondida; detalhamento de lifecycle movido para DSC-0041.
Pergunta de fechamento: quem possui a surface/submission durante troca Game/Shell, crash, splash, troca de cart e shutdown?
Troca Game/Shell, crash screen, splash, troca de cart e shutdown precisam saber quem possui a surface e qual submission ainda pode ser publicada. O worker nao pode ficar publicando frame velho depois que o modo ou cartridge mudou.
O que precisamos acertar: quando existe worker/render assíncrono, podem existir submissions em tres estados:
- pending no single-slot;
- currently-rendering no worker;
- ultimo frame apresentado/mantido na surface.
Lifecycle precisa dizer o que acontece com cada um quando o sistema muda de dono visual.
Direcao provavel: toda submission deve carregar um contexto de ownership alem de frame_id e app_mode, por exemplo render_epoch/generation, app_id ou surface_owner. O RenderManager/SystemOS incrementa essa generation quando troca app, cart, modo ou crash state. O worker deve descartar pending/current frames cujo ownership nao combine com o epoch ativo antes de apresentar.
Perguntas respondidas para o escopo desta agenda:
- Troca Game -> Shell invalida imediatamente pending/current Game submissions?
- Resposta: sim. A troca de foreground visual owner incrementa epoch/generation e torna obsoletas as submissions do owner anterior.
- Troca Shell -> Game deve limpar surface ou pode manter ultimo Shell frame ate primeiro Game frame?
- Resposta: pode manter a ultima surface apresentada ate a primeira submission valida do novo owner, desde que nenhum frame antigo seja apresentado novamente como nova submission. Limpar e uma politica visual opcional.
- Crash screen invalida pending/current Game frame antes de publicar crash Shell/local frame?
- Resposta: sim. Crash troca o owner visual para a tela de erro/sistema e deve invalidar frames pendentes/correntes do Game.
- Splash/crash/hub usam uma surface compartilhada ou ownership proprio?
- Resposta: podem usar a mesma surface fisica, mas precisam de ownership logico proprio via epoch/generation/surface owner.
- Troca de cartridge/app incrementa
render_epochmesmo seAppModecontinuar igual?- Resposta: sim.
AppModenao e identidade suficiente; troca de app/cart precisa invalidar submissions antigas.
- Resposta: sim.
- Worker pode terminar raster de um frame obsoleto, mas deve checar epoch antes de present?
- Resposta: sim. O worker pode desperdiçar trabalho ja iniciado, mas nao pode apresentar se o epoch/owner nao for mais atual.
- Quem incrementa epoch: SystemOS/lifecycle,
RenderManager, host, ou todos via API central?- Resposta:
SystemOS/lifecycle decide a transicao semantica;RenderManagerdeve oferecer a API central que incrementa/aplica epoch. Host nao decide ownership.
- Resposta:
- Shutdown deve drenar worker, cancelar pending/current, ou apenas parar present?
- Resposta: shutdown deve parar present, descartar pending, invalidar current por epoch/stop token e encerrar/joinar o worker de forma bounded. Nao deve drenar frames obsoletos para a tela.
Levantamento de material existente:
DSC-0031/LSN-0040ja estabeleceuAppModecomo discriminador de perfil. Game e Shell/System usam a mesma VM/transporte internamente, mas nao compartilham o mesmo perfil autoral. Game segue pipeline de jogo; Shell/System segue pipeline Runtime/Hub.DSC-0032/LSN-0041ja estabeleceuSystemOScomo autoridade semantica de lifecycle para task/process. Firmware nao deve coordenar manualmente task/process fora dessa fronteira.DSC-0035/LSN-0044ja estabeleceu que Shell liveness depende da janela focada pertencer ao proprio task; perder a janela elegivel fecha o Shell task via lifecycle e volta ao Hub.DSC-0036/LSN-0045ja estabeleceu o fluxo visual/lifecycleHub/Home -> Shell app -> task-owned window -> close -> lifecycle close -> Hub/Home.DSC-0038/LSN-0047ja estabeleceu queRenderManagercoordena active app mode, submission closure, transitions, capabilities e publication flow, mas sem virar renderer.
Estado atual de implementacao:
- Firmware possui estados explicitos (
GameRunning,ShellRunning,AppCrashes, splash/hub/load) echange_stateexecutaon_exit/on_enterimediatamente para evitar frames vazios. LoadCartridgeSteproteiaAppMode::Shellpara task/window Shell eShellRunning; caso contrario cria task de game e entra emGameRunning.GameRunningStepexige task foreground e executactx.os.vm().tick(...).ShellRunningStepexige task foreground e janela focada pertencente ao task; caso contrario fecha pelo lifecycle e volta ao Hub.RenderManageratual temRenderTransitionState::Pending { from, to }, mas isso e placeholder/no-op: nao ha epoch/generation, invalidacao de pending/current frames, nem ownership de surface.
Gap para este topico: as politicas de lifecycle Game/Shell existem, mas ainda nao foram propagadas para ownership visual assíncrono. Para DSC-0040, a direcao e suficiente: toda transicao de foreground visual owner passa por uma API central do RenderManager, incrementa epoch/generation, invalida pending/current do owner anterior e impede present de frames obsoletos.
Caso principal - Home durante Game e retorno ao Game
Cenario esperado:
GameRunning
-> usuario aperta Home
-> Game e suspenso/pausado como task foreground
-> Hub/Home assume Shell UI
-> usuario navega/checa algo no Shell
-> usuario retorna ao Game
-> Game volta como foreground
Mesmo sem transicoes suaves, esse ciclo precisa funcionar. O estado atual cobre partes do modelo, mas nao o ciclo completo:
- ja existe
GameRunningSteppara game fullscreen foreground; - ja existe
HubHomeStepeShellRunningSteppara Hub/Shell lifecycle; - ja existe lifecycle authority em
SystemOS; - ja existe Shell task/window liveness;
- ainda nao ha evidencia de um fluxo Home durante
GameRunningque suspenda o game, entre no Hub, e depois retorne ao mesmo game; - o
RenderManagerso possuiRenderTransitionState::Pending { from, to }como placeholder; nao ha ownership/epoch para invalidar frames de Game enquanto o Hub assume a surface.
Politica provavel para esse caso:
- Home em Game nao fecha o game; suspende/pausa o game task/process.
- Ao entrar no Hub, qualquer pending/current Game submission deve ser invalidada por epoch/generation antes de Shell/Hub assumir a surface.
- Hub/Shell renderiza local/sincrono por politica Shell.
- Retornar ao Game incrementa epoch novamente e retoma o game task/process.
- O primeiro frame de retorno ao Game deve vir de uma nova Game submission; ate la a surface pode manter ultimo Hub frame ou limpar conforme politica visual.
- Transicoes suaves ficam fora do escopo; o requisito minimo e ownership correto e ausencia de frame obsoleto apresentado apos troca de dono.
Questao de escopo levantada:
No ciclo Game -> Shell -> Game, pode existir um game pausado e um app Shell VM-backed aberto ao mesmo tempo. Isso cruza render ownership com lifecycle/process policy.
Direcao provisoria para esta agenda:
- somente um Game foreground/pausado por vez;
- somente um Shell foreground aberto por vez no v1;
- background execution fica fora deste contrato;
- Shell VM-backed durante Game pausado deve seguir lifecycle de Shell e nao ganha politica frame-paced propria;
- a VM/game pausado nao deve continuar executando frames logicos enquanto o Shell VM-backed esta foreground;
- a existencia simultanea de um Game pausado e um Shell VM-backed foreground precisa ser tratada como politica de SystemOS/lifecycle, nao como detalhe do render worker.
Essa pergunta foi separada na DSC-0041. Para DSC-0040, o ponto minimo esta respondido: render ownership deve acompanhar o foreground visual owner e invalidar submissions do owner anterior por epoch/generation.
Topico 7 - Erros, drops e telemetria de render
Status: direcao respondida.
Pergunta de fechamento: quais eventos minimos o sistema registra para render produzido, consumido, descartado, atrasado, apresentado e com erro?
Quando rasterizacao sai do fluxo da VM, panic/erro de render, frame drop, atraso, consumo e present precisam ser reportados sem quebrar determinismo da VM. Sem telemetria minima, latest-wins pode esconder perda de frames.
Direcao aceita: render assíncrono e best-effort observavel, nao handshake semantico com a VM. A VM produz frames sequenciais no ritmo do FrameScheduler; o worker consome/apresenta o mais recente possivel; drops, repeats, erros e descartes por epoch/stale ownership aparecem em telemetry, mas nao alteram diretamente a semantica do programa dentro da VM.
Contrato minimo:
- a VM nao observa erro/drop de render como retorno sincrono nem como semantica de programa;
- render telemetry e preocupacao de host/runtime/system, debugger, profiler e certificacao;
- erro de raster descarta a submission afetada, registra telemetry e preserva o ultimo frame valido/surface atual quando possivel;
- erro de present registra telemetry e pode escalar para politica de host/system fault se for recorrente;
- panic no worker deve virar falha controlada de worker/backend, nao panic propagado para a VM;
- o programa dentro da VM nao deve depender de confirmacao de present para avancar logica.
Counters minimos:
produced_submissions;replaced_before_consume;consumed_submissions;presented_frames;repeated_presents;render_errors;present_errors;stale_epoch_discards;shutdown_discards.
IDs/estado minimo:
last_produced_frame_id;last_consumed_frame_id;last_presented_frame_id;last_dropped_frame_id;last_error_frame_id;active_render_epoch.
Eventos minimos:
- submission substituida no single-slot antes de consumo;
- worker consumiu uma submission;
- worker descartou submission por epoch/owner obsoleto;
- worker apresentou frame;
- worker repetiu ultimo frame por ausencia de submission nova;
- erro de raster;
- erro de present;
- shutdown/cancel descartou pending/current.
Fechamento dos Topicos
Esta agenda pode ser considerada madura para decisao quando os sete topicos acima tiverem uma resposta de direcao. As respostas nao precisam implementar render worker agora, mas precisam ser concretas o suficiente para produzir uma decision sem reabrir a arquitetura base durante o plan.
Criterio para Encerrar
Esta agenda pode virar decisao quando houver consenso sobre:
- protocolo de handoff VM/render;
- ownership de submissions e recursos compartilhados;
- politica de backpressure;
- fronteira entre runtime contract e implementacao host/hardware;
- telemetria minima;
- o que fica fora de escopo antes do primeiro worker real.
Discussion
Aberta em 2026-06-04 apos verificar que DSC-0038 preparou snapshots fechados e latest-wins, mas nao decidiu thread/core separado.
Atualizacao de 2026-06-04:
Antes de discutir worker/thread de render, precisamos fechar uma pre-condicao de composicao do pacote atual. A pergunta operacional foi: comandos de primitivas em gfx2d sao desenhados apos qualquer blit/raster dos layers?
O estado atual e misto:
- No caminho direto
Gfx::render_game2d_frame_packet,render_no_scene_frame()roda antes e depoispacket.gfx2de aplicado. Nesse caso, primitivas ficam depois da composicao base. - No caminho real de
Hardware::publish_render_submission, quandoframe_composer.active_scene_id().is_some(), o codigo ignora oGame2DFramePacketrecebido e chamaframe_composer.render_frame(&mut self.gfx). FrameComposer::render_frameatualiza cache/resolver e chamagfx.render_scene_from_cache(...), que faz o blit/raster dos layers, mas nao aplicapacket.gfx2d.
Conclusao provisoria: para frames com scene ativa, ainda nao ha garantia de que gfx2d seja desenhado apos os layer blits; na pratica, esses comandos podem ser descartados nesse branch. Isso deve ser resolvido antes de transformar RenderSubmission em fronteira de handoff para outro worker/core, porque o worker precisa consumir o pacote fechado completo, nao reconstruir comportamento a partir de estado vivo do FrameComposer.
Resposta de direcao em 2026-06-04:
As primitivas gfx2d devem funcionar como overlay do frame Game 2D. Elas precisam ser desenhadas apos a composicao canonical de scene/layers/sprites tanto em frames sem scene ativa quanto em frames com scene ativa. O comportamento esperado e:
Game2D composer/layers/sprites
-> gfx2d primitives overlay
-> present/publication
Isso implica que o branch com scene ativa em Hardware::publish_render_submission nao deve contornar o Game2DFramePacket nem perder packet.gfx2d. A solucao futura deve fazer o consumidor de RenderSubmissionPacket::Game2D aplicar a mesma ordem de composicao nos dois caminhos.
Essa regra tambem reforca a fronteira necessaria para render worker/thread: o pacote fechado deve conter tudo que o consumidor precisa para compor o frame visivel. O worker nao deve depender de chamar um caminho alternativo baseado em estado vivo do FrameComposer que ignora parte do packet.
Atualizacao sobre Shell UI em 2026-06-04:
ShellUi e Game2D sao separados no contrato e no roteamento, mas compartilham os mesmos helpers finais de desenho no Gfx. ShellUiFramePacket contem comandos gfxui, enquanto Game2DFramePacket contem composer + gfx2d. No consumidor local, apply_gfxui_command e apply_gfx2d_command chamam os mesmos primitives finais (clear, fill_rect, draw_line, draw_text, etc.).
Mover o render real para outro core/thread nao deve atrapalhar Shell UI se a fronteira for respeitada: Shell update/layout/input continuam no core logico, produzem um ShellUiFramePacket fechado, e o render worker consome apenas esse snapshot imutavel.
O risco especifico do Shell UI nao e rasterizar em outro core. O risco e deixar o worker depender de estado vivo de Shell/SystemOS, janelas, ponteiros ou input enquanto desenha. Hoje o Hub monta o packet lendo estado de OS/janelas/input e publica imediatamente. Num modelo paralelo, essa montagem deve continuar antes do handoff; o worker deve receber so comandos fechados e recursos estaveis.
Portanto, para Shell UI, a regra de handoff deve ser:
Shell/SystemOS update + input sampling
-> build ShellUiFramePacket from current UI state
-> hand off immutable ShellUi submission
-> render worker consumes commands only
Isso preserva a UI sobre primitivas sem exigir que o render worker entenda window manager, foco, input ou lifecycle.
Direcao refinada em 2026-06-04:
Render paralelo faz mais sentido como capacidade para workloads de game 2D/3D do que como obrigacao universal do runtime. Um app UI/Shell pode nao se beneficiar de separar rasterizacao em outro core, porque seu custo principal tende a estar em estado de UI, layout, input, foco, janelas e lifecycle, nao em um pipeline grafico pesado.
Portanto, a decisao futura nao deve assumir que todo RenderSubmission passa obrigatoriamente por um worker dedicado. A politica deve poder variar por AppMode/pipeline:
Game2D/futuroGame3Dpodem usar handoff para render worker/core quando houver ganho real de frame time ou isolamento.ShellUipode continuar em consumo sincrono/local se isso for mais simples, deterministico e barato.- O contrato comum deve continuar sendo o packet fechado; a politica de execucao do consumidor pode ser diferente por pipeline.
Essa distincao evita otimizar a Shell UI como se fosse um renderer de jogo. Para UI, o valor principal de ShellUiFramePacket e manter a fronteira limpa e testavel; paralelismo deve ser opcional.
Resolution
Direcao da agenda respondida. Os sete topicos tem recomendacao suficiente para virar decision:
Game2DFramePacketdeve ser snapshot completo, comgfx2dcomo overlay apos composicao de scene/layers/sprites;- recursos pesados cruzam a fronteira por IDs/handles estaveis e APIs read-only, nao por handles mutaveis vivos;
- handoff base e single-slot latest-wins, sem bloquear a VM/produtor;
- frame pacing fica sob
FrameClock/FrameScheduler, nao sob ACK do render worker; - politica de render varia por
AppMode/pipeline: Game frame-paced; Shell lifecycle/event-driven e local/sincrono por default; - transicoes de foreground visual owner usam epoch/generation para invalidar submissions obsoletas;
- telemetria minima torna drops, repeats, erros e stale discards observaveis sem virar semantica da VM.
Detalhes de foreground stack, Game pausado e coexistencia com Shell VM-backed foram separados na DSC-0041.
Next Step
Transformar esta agenda em decision antes de qualquer spec ou codigo de render worker.