Programador líder · Signum Ludens · no estúdio desde abril de 2024
Sky Paws Awakening
Um action-platformer em duas eras: a mesma visão de game design no hardware do Mega Drive e no Unity, com gráficos modernos.
Sobre o NDA. Este projeto está sob acordo de confidencialidade. Esta página usa apenas informações já públicas (a página da Steam) e descrições técnicas escritas por mim, sem código-fonte, nomes internos ou conteúdo inédito do jogo.
O projeto
Sky Paws Awakening é um action-adventure de plataforma em que o jogador alterna em tempo real entre dois cadetes com estilos de combate diferentes, o Twin Tag System.
Publicado pela Signum Ludens, com lançamento ainda a anunciar, para Windows, Linux/SteamOS e Steam Deck, em inglês e português do Brasil. A versão Unity é fiel à visão de game design do original, mas apresenta gráficos modernos, sem se prender ao visual retrô. O original, para Sega Mega Drive, também anunciado publicamente, é onde o design nasceu.
- 10+ fases
- 20+ tipos de inimigos
- Vários chefes
- 6 cutscenes
Dados da página pública da Steam.

Meu papel
Sou o programador líder e respondo pelo código de gameplay e de sistemas das duas versões. Trabalho ao lado de um game designer, dois ilustradores e um artista de VFX, que implementa os efeitos visuais.
- Arquitetura e sistemas de gameplay: movimento, combate, troca de personagem, IA de inimigos e chefes, diálogo e cutscenes, HUD, áudio, save e progressão.
- Porte Mega Drive → Unity com paridade de comportamento entre as versões.
- Depuração e otimização em hardware real, com foco em memória de vídeo, temporização e estabilidade.
- Ferramentas de desenvolvimento sob medida: monitor de VRAM, menu de debug e etapas de build automatizadas.
- Publicação e integração com a Steam.
- Documentação técnica: planos, guias de configuração e relatórios de causa raiz, mantidos junto ao código.
Números aproximados; servem como indicador de escala, não como medida de qualidade.
Como eu planejo: SOLID
O Mega Drive impõe lógica procedural e acoplada. No Unity, a mesma lógica vira componentes pequenos, intercambiáveis e configuráveis. Antes de escrever código, decomponho o sistema em responsabilidades, e sigo este roteiro:
- Entender o comportamento antes do código: o original e os documentos de design são a especificação.
- Listar as responsabilidades do sistema e dar a cada uma um dono.
- Definir interfaces pequenas nas fronteiras entre os donos.
- Separar dados de lógica: tudo o que o game design pode querer ajustar sai do código e vai para ScriptableObjects, exposto no inspector.
- Regra de parada: se uma tradução vira um arquivo grande com muitas responsabilidades, paro e redesenho antes de implementar.
Responsabilidade única
Um orquestrador de personagem só coordena; movimento, combate e animação vivem em componentes próprios. Nenhuma classe “faz-tudo”.
Aberto/fechado
Inimigos, ataques e personagens novos entram como dados (ScriptableObjects), prefabs e, quando preciso, um estado novo, sem editar o que já funciona.
Substituição de Liskov
Estados de jogador, de inimigo e de jogo seguem o mesmo contrato e são intercambiáveis: a máquina de estados não sabe qual está rodando.
Segregação de interfaces
Contratos pequenos e específicos (receber knockback, ser agarrável, ter vida, tocar música, tocar efeito sonoro) em vez de uma interface gorda.
Inversão de dependência
Os sistemas dependem de abstrações resolvidas por um registro de serviços; dependências concretas chegam pelo inspector ou por dados, sem buscas globais em runtime.
O ganho prático: valores de tuning ficam no inspector, pensados para o game design ajustar sem mexer em código, e conteúdo novo não coloca em risco o que já funciona.
Estudos de caso
04 casos01 Crashes que só existiam no hardware real
Problema
O Mega Drive (Motorola 68000) é muito menos tolerante que os emuladores. O jogo rodava perfeito no emulador, mas o console desligava de forma aleatória, geralmente na segunda metade das fases.
Investigação
Como não dava para reproduzir no emulador, auditei o código por classes de erro típicas de hardware: índices fora dos limites, retornos de alocação sem checagem de nulo, buffers de texto de debug estourando a pilha (no 68000, sobrescrever o endereço de retorno não gera erro; o jogo simplesmente trava) e escrita no chip de vídeo durante a interrupção de vídeo.
Solução
Validação de limites nos pontos de entrada, checagem de retorno nulo em toda alocação de sprite, regras de tamanho para buffers de formatação e interrupções desabilitadas nas janelas críticas de escrita de vídeo (em transições, com a tela já preta, então sem custo visível).
Aprendizado
“Passa no emulador” não prova nada. Cada causa virou regra de código e item de checklist de revisão, para o problema não voltar.
02 Memória de vídeo sobrando, e mesmo assim sem espaço
Sintoma
Sprites do jogador, inimigos e efeitos corrompiam ao mesmo tempo, de forma intermitente, mesmo com muita VRAM livre no visualizador de depuração.
Hipótese descartada
Orçamento. Medi o pico de uso do pool de sprites em combate denso e ele ficava perto de 85%: nunca estourava o total.
Causa raiz
Fragmentação. O motor precisa de um bloco contíguo. Sprites nascendo e morrendo deixam o espaço livre espalhado em buracos pequenos; quando o pedido é maior que o maior buraco, a alocação falha e o fallback do motor sobrepõe endereços, corrompendo tudo de uma vez.
Diagnóstico
Instrumentei o alocador para registrar, a cada falha, o total livre versus o maior bloco livre. A assinatura ficou óbvia: total alto, maior bloco pequeno.
Solução
Alocação tolerante a fragmentação (desfragmenta e tenta de novo) nos pontos críticos. O custo, cerca de um frame, só aparece como fallback raro. Achado relacionado: sprites pequenos e frequentes não podem usar compressão, pois a fila de DMA lia um buffer já liberado.
Status
Correção confirmada em emulador.
03 De C procedural a componentes SOLID
Contexto
O código original do Mega Drive é procedural: estruturas globais, funções grandes e uma ordem de atualização fixa, com cerca de vinte etapas por frame. Traduzir isso linha a linha para C# levaria o acoplamento junto.
Decisão
Traduzir a lógica, não as limitações do hardware. O que existia só por restrição da plataforma não foi portado:
| Mega Drive | Unity |
|---|---|
| Pools estáticos, sem alocação dinâmica | Pooling normal, sem limite artificial |
| Ponto fixo 16.16 | Float / Vector2 |
| Colisão empacotada em tiles de 16×16 | Tilemap + Collider2D |
| Fases divididas em sessões por limite de VRAM | Fase inteira em uma cena |
Execução
Cada sistema foi redesenhado com um orquestrador enxuto (só coordena), estados intercambiáveis, interfaces pequenas, serviços resolvidos por abstração e valores de tuning em ScriptableObjects.
Resultado
Novos tipos de inimigo reaproveitam a mesma IA com dados e estados próprios, sem editar o núcleo. A arquitetura foi desenhada para que chefes e ataques novos entrem do mesmo jeito.
04 Um jogo, dois stacks: mantendo a paridade
Desafio
Manter o mesmo design em C/SGDK e em C#/Unity sem que virasse dois jogos diferentes.
Abordagem
O código original serve como especificação executável: valores de física, padrões de inimigos, fases de chefe e cenas de diálogo já estão definidos e testados nele. Cada feature portada é validada contra esse comportamento. A fidelidade é à visão de game design, não ao visual: a versão Unity usa gráficos modernos e não se compromete com a estética retrô. As diferenças intencionais são listadas em vez de deixadas ao acaso.
Aprendizado
Paridade se define pelo comportamento observável, não pela semelhança do código. Ter as duas versões também expõe bugs de design que uma só esconderia.
Stack deste projeto
// mega drive
- SGDK (C, GCC / 68000)
- Ferramentas de build em Python
- Monitor de VRAM e menu de debug próprios
// unity
- Unity 6 (URP, 2D Tilemap)
- C# · Cinemachine · Input System
- Addressables · Localization
- Odin Inspector · Steamworks.NET
// padrões
- Máquinas de estado
- Mediator, service registry
- Object pooling
- Design orientado a dados (ScriptableObjects)
// método
- Plano, implementação e guia de configuração, por sistema
- Relatório de causa raiz para cada bug crítico
- Uso assistentes de IA como ferramenta; as decisões de arquitetura e a revisão do código são minhas