O Astra está cada vez melhor em transformar o que tenho em mente em jogabilidade e direção de arte. Tenho usado o Astra no Codex para criar Void Explorer, um jogo de exploração espacial em que você pode viajar até estrelas distantes e descer para explorar paisagens alienígenas.
Uma seleção de cenas de Void Explorer em ação.
O jogo tem 2.048 sistemas estelares e mais de 10.000 planetas gerados proceduralmente, incluindo mundos do tamanho da Terra. Cada estrela visível faz parte do universo e pode ser selecionada como destino. Você pode escolher um ponto de luz ao longe e viajar até ele.
Você pode se aproximar de um planeta vindo do espaço, atravessar a atmosfera e continuar descendo até sobrevoar o litoral. Pode pousar, sair da nave, caminhar e decolar de novo. Para que esse percurso funcionasse, precisei resolver em conjunto questões de escala, terreno, controles e renderização.
Incluí alguns prompts que usei durante o desenvolvimento, editados para ficarem mais curtos e claros. Eles mostram como descrevi o que queria e como o trabalho técnico avançou a partir daí.
Comece pela experiência de jogo
No pedido inicial, descrevi o que o jogador deveria conseguir fazer:
Quero poder chegar a tudo o que consigo ver. Mantenha as distâncias reais e ajuste a escala e a velocidade para viabilizar as viagens. Quero voar do espaço até a atmosfera de um planeta e descer até o solo. Os planetas podem ser tão grandes quanto a Terra, então vamos precisar de terreno procedural e de um renderizador que trabalhe por blocos.
Isso deu ao Astra algumas restrições concretas. Uma estrela não podia ser apenas um ponto pintado no fundo da cena. Um planeta não podia virar uma fase separada quando eu me aproximasse. Era preciso percorrer distâncias reais, e recursos de ficção científica como a viagem por pulso e a hiperpropulsão tornariam isso viável.
Também usei a geração de imagens para definir o visual quando ainda havia pouco do jogo pronto. As primeiras artes conceituais eram realistas demais. Na tentativa seguinte, o visual ficou simples demais. Foi assim que pedi um dos ajustes:
Agora ficou um pouco simplista demais. Precisamos de um meio-termo, com cores melhores, luz neon e mais contraste para dar a sensação de espaço profundo. Mostre como fica em alta velocidade, com estrelas e poeira ao redor da nave.
Quando gostei das imagens, pedi ao Astra que as salvasse e as usasse como referências visuais para o jogo. Tínhamos referências específicas para o voo orbital, o voo em alta velocidade, a entrada na atmosfera e o pouso. Com elas, ficou muito mais fácil avaliar a versão seguinte.

A arte conceitual gerada que definiu a paleta de cores e a direção visual do jogo.
Deixe o Astra propor a arquitetura
Eu defini as restrições, e o Astra propôs a implementação. O aplicativo usa TypeScript e Vite, com Three.js para renderização. Assim, o código controla diretamente as malhas, os materiais, a iluminação e a geometria procedural, que concentram a maior parte do trabalho visual do jogo.
A primeira versão jogável usava um renderizador WebGL2. Mais tarde, perguntei se o Three.js estava limitando o visual. O Astra recomendou manter a simulação e migrar o renderizador para a arquitetura WebGPU do Three.js. Preservamos os sistemas de universo, navegação e terreno enquanto trocávamos a camada de renderização.
O renderizador usa os materiais baseados em nodes do Three.js e a Three.js Shading Language (TSL) para definir em código a atmosfera, a água e os efeitos de iluminação.
A geração de terreno roda em Web Workers, que preparam a geometria fora da thread responsável pelos comandos do jogador e pelo desenho da cena. O Vitest testa, entre outras coisas, a reprodutibilidade da geração, os cálculos de coordenadas e os contratos do sistema de terreno. O Playwright testa o jogo no navegador. Com isso, o Astra podia verificar os sistemas que sustentam o jogo enquanto eu continuava avaliando a experiência de jogar.
A direção de arte continuou guiando o trabalho. A atmosfera ao redor de cada planeta, o brilho dos anéis, a luz colorida das estrelas e o efeito Neon Phosphor compõem o visual. O filtro acrescenta a textura de uma tela retrô e mantém legíveis as informações de navegação.

Captura de tela do jogo com o efeito Neon Phosphor ativado.
Dê ao Astra meios de inspecionar e jogar
Eu continuava jogando, mas o Astra também precisava de meios de investigar um problema além de ler minha descrição. O jogo disponibiliza uma pequena interface JavaScript, window.__VOID_EXPLORER__, que os testes no navegador podem chamar para consultar o estado atual. Ela informa de qual corpo celeste estou me aproximando, o modo de voo, se o terreno está pronto e qual câmera está ativa. Também fornece contadores de renderização e de carregamento contínuo, como chamadas de desenho, número de triângulos, tarefas de terreno na fila e dados de terreno em buffer.
O jogo tem cenas de teste identificadas por nome para voo em órbita, viagem por pulso, descida pela atmosfera e pouso no litoral. Com elas, o Astra consegue voltar a um ponto de partida adequado sem precisar atravessar o universo a cada teste. O Playwright pode carregar uma cena, esperar que o terreno fique pronto, capturar a tela e verificar o estado do jogo naquele momento. Outros testes percorrem o trajeto com os controles reais e registram posições e mudanças de estado durante a execução. Assim, preparar uma cena de pouso não substitui testar a manobra em si.
Esses testes cobrem o pouso, a saída da nave, a caminhada, o salvamento e o recarregamento do jogo, o embarque e a decolagem. Eles conseguem detectar problemas que uma captura de tela por si só não revelaria, como uma superfície de colisão que ainda não está pronta ou uma nave que salta de uma posição para outra durante uma transição.
Eu também podia pedir ao Astra que inspecionasse a aba do navegador em que eu estava jogando. Quando perguntei se as nuvens de Aurelia ainda estavam visíveis, ele capturou o que eu estava vendo e leu as informações de voo na tela. Eram verificações pontuais. O Astra não acompanhava continuamente cada quadro do voo.
O ciclo consistia em reproduzir o problema, inspecionar as capturas de tela e o estado do jogo, rastrear o código envolvido, fazer um ajuste e repetir a verificação. Em outro jogo, eu criaria essas ferramentas logo no início. Bastam algumas cenas reproduzíveis, informações de estado e contadores de desempenho úteis, além de testes no navegador para as principais interações. Com isso, o Astra consegue investigar e testar mudanças por conta própria enquanto eu continuo avaliando o visual e a resposta dos controles.
Represente o universo em várias escalas
Ter milhares de planetas não exige carregar milhares de malhas detalhadas. Cada planeta começa com uma descrição que inclui raio, órbita, atmosfera e semente de geração. A semente permite reproduzir o terreno. O jogo gera a geometria ao redor da área de que estou me aproximando e consegue gerar a mesma paisagem quando volto.
As coordenadas exigem um cuidado parecido. Distâncias de anos-luz e uma pessoa em pé ao lado de uma nave envolvem escalas muito diferentes. Se o jogo enviasse posições com valores tão grandes diretamente à GPU, perderia a precisão necessária perto do solo.
O Astra separou o endereço físico de cada objeto da posição usada para desenhá-lo. O universo é dividido em células grandes identificadas por números inteiros, com deslocamentos locais menores dentro delas. Antes de renderizar, o jogo subtrai a posição do observador. Assim, a câmera fica na origem, e as coordenadas dos objetos próximos têm valores pequenos. As distâncias exibidas e os tamanhos relativos permanecem consistentes.
Os elementos em movimento também precisam estar sincronizados. Estrelas, planetas e luas seguem órbitas definidas por fórmulas analíticas. O renderizador calcula suas posições no mesmo instante da simulação usado para o observador. Uma nave pousada acompanha a rotação do planeta. As posições e as cores das estrelas determinam a iluminação. Assim, os dois sóis que vejo se pôr em um sistema binário são as mesmas estrelas que vi da órbita.
Faça a descida acontecer sem interrupções
Voo do espaço em direção à superfície de uma lua em Void Explorer.
Foram necessários muitos ajustes para acertar a transição do espaço até o solo. Um dos meus prompts surgiu quando me aproximei de um planeta em um ângulo pouco inclinado:
Quando me aproximo quase em paralelo à superfície do planeta, ainda consigo chegar rápido demais, e o planeta quase desaparece enquanto o terreno carrega. Precisamos ajustar a curva de velocidade e o carregamento contínuo do terreno em conjunto. Será que a atmosfera poderia suavizar a transição entre o nível de detalhe usado à distância e o terreno detalhado? O planeta nunca deve desaparecer durante a aproximação.
O controlador de voo agora aplica um limite de velocidade específico para aproximações pouco inclinadas. Ele considera a altitude e uma estimativa da velocidade orbital calculada a partir do raio e da gravidade do planeta. Mais perto do solo, também coleta amostras do terreno à frente para ajustar a frenagem.
O jogo usa vários níveis de detalhe, ou LODs. Visto de longe, o planeta é representado por uma esfera facetada de baixo custo de renderização, chamada proxy. À medida que o planeta ocupa mais espaço na tela, o renderizador acrescenta detalhes. A aproximação da superfície ativa um terreno construído a partir das seis faces de um cubo projetadas sobre uma esfera. Cada face pode ser dividida em quatro quadrados menores, que por sua vez podem ser subdivididos da mesma forma. Essa estrutura é uma quadtree.
Assim, o jogo consegue refinar o terreno na área visível e na direção do voo sem precisar gerar uma superfície do tamanho da Terra com o nível de detalhe necessário para explorá-la a pé. Perto do solo, trechos locais fornecem uma geometria mais detalhada ao redor da nave e do jogador.
Todas essas representações extraem amostras dos mesmos dados do planeta. Uma função compartilhada combina continentes, cadeias de montanhas, crateras e formas menores de relevo. Ela fornece dados de elevação, água, bioma e outras informações usadas para compor a paisagem. As malhas mais simples e as mais detalhadas representam esses dados em resoluções diferentes e seguem as mesmas regras de cor para manter o visual do planeta consistente.
A troca de uma representação pela outra é tão importante quanto a geração. O planeta visto à distância permanece visível até que as seis faces de baixa resolução do terreno estejam prontas. Cada bloco de terreno permanece no lugar até que os quatro blocos que o subdividem estejam prontos. Durante a transição, máscaras complementares determinam, pixel a pixel, se aparece a superfície anterior ou a nova. Isso evita que o planeta inteiro fique transparente enquanto a nova superfície carrega.
A atmosfera muda de acordo com a altitude física, e as nuvens se deslocam sobre o relevo do planeta. Esses elementos dão continuidade à passagem da vista orbital para a paisagem. O renderizador só remove a cobertura de baixa resolução nos pontos em que o terreno mais detalhado já está pronto. O contorno das montanhas ainda pode mudar à medida que surge uma geometria mais detalhada.
O pouso exige um cuidado a mais. Em cada área de contato, o solo visível, os triângulos de colisão e os líquidos que oferecem risco precisam vir da mesma geração e ficar prontos juntos. Para simular a caminhada, o jogo usa o Rapier em um pequeno mundo físico local. A lógica de pouso da nave verifica separadamente o apoio dos pés de pouso, a inclinação do terreno e o espaço livre entre a nave e o solo.
Há uma limitação intencional aqui. Os planetas usam campos de altura, com uma única elevação para cada direção da superfície. Isso funciona bem para paisagens extensas, mas não permite representar cavernas, saliências suspensas ou túneis destrutíveis.
Meça o trabalho que faz um quadro demorar
Alguns dos meus comentários tratavam do visual e do desempenho ao mesmo tempo, porque eu percebia os dois problemas no mesmo voo:
Você pode verificar como carregamos e renderizamos um planeta enquanto voo do espaço em direção ao solo? O globo visto de longe e o terreno detalhado parecem diferentes demais. A transição é brusca, especialmente quando as cores mudam. Quero entender o que causa esses saltos no visual e onde podemos reduzir o trabalho para carregar mais detalhes, para que a descida fique mais bonita e fluida.
O Astra usou os contadores do renderizador do Three.js para inspecionar chamadas de desenho, triângulos, geometrias e texturas. Um teste com o Playwright coletou 90 intervalos entre quadros e registrou a média e os percentis desses tempos junto com as contagens. Para investigar problemas visuais, os testes também podiam comparar os pixels renderizados com determinado efeito ativado e desativado. Por exemplo, podiam comparar o terreno com e sem a máscara que oculta superfícies de solo sobrepostas. Assim, foi possível isolar a causa do desaparecimento do solo sem depender apenas de uma captura de tela da cena com o problema.
Uma versão anterior da nave mostrou a utilidade dessas medições. A substituição da nave procedural pelo primeiro modelo AURORA criado no Blender aumentou o número de triângulos da cena, mas reduziu o de chamadas de desenho. O Astra executou o mesmo teste orbital de 90 quadros no backend WebGPU antes e depois da mudança:
| Métrica | Antes | Depois |
|---|---|---|
| Total de chamadas de desenho da cena | 119 | 77 |
| Total de triângulos da cena | 48.809 | 61.092 |
| Intervalo médio entre quadros | 251,48 ms | 199,26 ms |
Essa comparação foi feita antes da criação da nave de quatro asas mostrada abaixo, usando o Chromium sem interface gráfica e a renderização por software do SwiftShader. Os números medem a mudança naquele ambiente de teste. A taxa de quadros na minha GPU não foi medida. As ferramentas forneceram ao Astra tempos medidos no navegador, imagens renderizadas e contagens de recursos. Elas não mediram comandos individuais da GPU nem tempos de execução de shaders.
O Astra investigou o que acontecia durante o carregamento e a descida. Verificou quanta geometria era gerada, qual era o volume de dados transferidos entre os workers e o renderizador e com que frequência as tarefas de terreno eram descartadas antes de serem aproveitadas.
Uma das mudanças foi definir o nível de detalhe de um planeta distante de acordo com seu tamanho na tela. Um planeta que ocupa todo o campo de visão precisa ter um contorno bem definido de imediato. Um planeta menor que um pixel dispensa a mesma quantidade de geometria. Em uma comparação controlada na cena inicial, a nova regra usou 7.040 triângulos nas malhas proxy, em vez de 51.200. Como as duas regras usavam o mesmo gerador de geometria, foi possível isolar o efeito da decisão sobre o que carregar.
Outra melhoria manteve exatamente o mesmo número de triângulos de terreno. As malhas indexadas reutilizam os vértices compartilhados por triângulos vizinhos, evitando repetir dados de posição, cor e normal. No teste com a qualidade do terreno definida como alta, o volume de dados transferidos nos buffers das seis camadas de solo caiu de cerca de 35 MB para 15 MB, mantendo os 241.952 triângulos. A geração do solo e a dos elementos de cenário também passaram a ser tarefas separadas, de modo que a geometria do solo pudesse ficar pronta sem esperar pela decoração.
O seletor de terreno também desperdiçava trabalho. Ele podia solicitar blocos, mudar de decisão, descartá-los e pedir outros no lugar. O Astra tornou as decisões de refinamento mais estáveis e passou a refinar um bloco somente quando havia recursos suficientes para os quatro sub-blocos. Em uma simulação controlada com latência fixa dos workers, seis segundos de descida seguidos de três segundos de estabilização resultaram em 13 tarefas descartadas, em vez de 6.074.

Em uma simulação controlada com dois workers e latência simulada de 100 ms por worker, o número de tarefas agendadas e depois descartadas caiu de 6.074 para 13. Essa métrica avalia o agendamento das tarefas de terreno, não a taxa de quadros.
Usar workers por si só não elimina todas as travadas. A geração de terreno que roda na thread principal como alternativa também precisava ceder tempo a outras tarefas. O Astra dividiu a geração em etapas que podem ser pausadas e retomadas, com um limite de cerca de 4 ms por quadro. Assim, uma tarefa grande de terreno não precisa ser concluída de uma só vez. O renderizador também limita a resolução do mundo separadamente da resolução da interface, mantendo o texto de navegação nítido mesmo quando a qualidade visual é reduzida.
Essas medições de terreno mostram que o jogo gera menos geometria, transfere menos dados e descarta menos trabalho. Elas não medem a taxa de quadros do hardware. Jogar no navegador continua sendo importante para avaliar a compilação de shaders, a transferência de dados para a GPU, o movimento e o visual da transição na prática.
Minha contribuição veio da experiência de jogar. Eu apontava o que parecia lento, o que estava visualmente errado e o que queria melhorar. O Astra rastreou o código, escreveu scripts de teste para reproduzir as mesmas condições de terreno e registrou as medições. Quando aprovei a abordagem, ele implementou as mudanças e repetiu os testes para comparar os resultados. O Astra conduziu a investigação e a implementação sem que eu precisasse especificar cada etapa, enquanto eu continuava avaliando o visual e a experiência de jogar.
Transforme arte conceitual em recursos para o jogo
Quase todo o universo é gerado em código, incluindo planetas, terreno, nuvens, anéis e estrelas. A nave do jogador é o principal modelo 3D criado individualmente, e para ela adotei outra abordagem.
Usei artes conceituais geradas e imagens renderizadas no Blender para avaliar a nave antes de colocá-la no jogo. Quando as asas ficaram diferentes do que eu imaginava, pedi referências mais úteis:
Gere imagens que mostrem claramente esta nave de vários ângulos. Preste atenção às asas dianteiras e traseiras. Vamos usar essas imagens para refazer o modelo 3D. Não gosto das asas arredondadas e unidas do modelo atual.
Escolhi a referência com quatro asas separadas, formato simétrico e luzes rosas nas pontas. Depois, pedi ao Astra que criasse um novo modelo no Blender, preservasse o estilo visual do jogo e adicionasse a nave ao jogo.
Para isso, foi preciso transformar a referência em geometria, revisar a silhueta e os materiais e preparar um modelo para uso durante a execução do jogo. O arquivo-fonte do Blender tem 193 malhas editáveis. A nave exportada tem 14.968 triângulos, agrupados em oito lotes de materiais opacos. Eu podia pedir mais detalhes no modelo, e o Astra mantinha sob controle o custo de renderização da versão exportada.

A arte conceitual gerada que aprovei para a nave.

O modelo usado no jogo, renderizado no Blender.
Experimente com água procedural
Também passei bastante tempo experimentando com a água em Sunwake, um jogo em que você pilota um pequeno barco por um oceano procedural. Eu queria ondas facetadas, movimentos suaves e as cores de um vitral. O Astra criou um renderizador de água próprio em Three.js. O mesmo modelo de ondas controla tanto o mar que aparece na tela quanto a flutuação do barco. O casco sobe, inclina-se para a frente e para trás e balança de um lado para o outro com as ondas. Uma simulação local propaga ondulações e espuma, enquanto o barco deixa esteiras e levanta respingos. Depois, pedi ao Astra que criasse um barco mais detalhado no Blender e o colocasse no jogo. É a mesma abordagem de Void Explorer, em que a simulação conecta um ambiente procedural a um veículo modelado individualmente.

Sunwake combina um oceano procedural com um barco modelado no Blender.
Em Hollowflux, explorei a água de outra forma. É um pequeno RPG de ação em 2D que gira em torno de um rio subterrâneo luminoso. O jogo desenha as cavernas, os personagens e os equipamentos em código, sem importar folhas de sprites. Pedi várias vezes ao Astra que tornasse mais convincente a reação da água aos passos, às arrancadas e aos ataques. Uma grade de células acompanha o nível da água, as correntes, a espuma e a carga elétrica. Cada passo produz ondulações, as estocadas de lança abrem esteiras estreitas e os golpes de martelo propagam ondas em círculos. As correntes fazem curvas junto às margens e levam a espuma rio abaixo. Esse mesmo fluxo empurra o jogador e os inimigos. A água também influencia o combate. A descarga de uma enguia se propaga pelas áreas de água interligadas, então subir em uma pedra seca protege o jogador da descarga.

Hollowflux desenha a arte do jogo em código, com água que reage ao movimento e ao combate.
Criação e compartilhamento de jogos
O que eu gosto no trabalho com o Astra é poder começar pela experiência que quero criar, usar imagens para definir a direção visual e dar feedback depois de jogar de verdade. Uma queixa sobre um planeta que desaparece pode levar a mudanças no carregamento contínuo, na geometria e no movimento. Um pedido para tornar a água mais convincente pode dar origem a uma simulação que controla tanto o visual quanto a jogabilidade. Ainda cabe a mim decidir se a experiência ficou como eu queria, mas o Astra consegue aplicar essas decisões ao código, aos recursos do jogo e aos testes.
Também é simples compartilhar um jogo de navegador. Com o plugin Sites, você pode pedir ao Astra:
Publique o jogo com acesso público para que qualquer pessoa possa jogar diretamente no navegador.
Você pode jogar Void Explorer, Sunwake e Hollowflux no seu navegador.
Se você tem uma ideia de jogo em mente, experimente criar uma pequena versão jogável com o Astra. Escolha um aspecto da experiência que você quer acertar, dê ao Astra algumas referências visuais e teste o resultado. Um barco, uma sala ou uma única interação já basta para começar. Jogue, descreva com precisão o que quer mudar e continue desenvolvendo a partir daí.