Análise técnica sem omissões do desenvolvimento, decisões de arquitetura, débitos reais e estado atual do produto.
Capoeira Soul é um jogo arcade 2D single-player desenvolvido por um único desenvolvedor, construído sobre React 18, TypeScript, Phaser 3 e um backend PHP 8.1 + MySQL. O jogo percorre a jornada de um praticante de corda branca até mestre, distribuída em 41 fases organizadas em 3 capítulos ambientados em locais reais do Brasil.
O MVP foi concluído com funcionalidade integral: combate com FSM, 13 inimigos folclóricos com IA individualizada, sistema de progressão persistente, monetização via Google Play Billing, wrapper Android nativo e publicação em Internal Testing na Play Store.
Arte conceitual · Os 4 protagonistas — Raiz, Dourado, Equilibrado e Instrutor Aurora
Demonstração do sistema de combate, cenários vivos, trilha sonora e inimigos folclóricos em ação.
Tela de vitória · Roda de Capoeira no Rio de Janeiro com Cristo Redentor ao fundo
A escolha de combinar React com Phaser 3 dentro de um WebView Android não é ortodoxa. A maioria dos jogos mobile nativos usa Unity, Godot ou engines com pipeline dedicado. Aqui, a decisão foi pragmática: um desenvolvedor solo com domínio de TypeScript e necessidade de reaproveitamento de código entre web e Android.
| Camada | Tecnologia | Justificativa Real |
|---|---|---|
| Engine de Jogo | Phaser 3 | Maturidade, FSM, física 2D, suporte a canvas e WebGL. Curva de aprendizado aceitável em TypeScript. |
| UI/React | React 18 + Vite | Menus, HUD, configurações e bestiário vivem fora do canvas Phaser — React gerencia esse layer. |
| Backend | PHP 8.1 MVC + MySQL | Hospedagem compartilhada disponível e custo zero adicional. Não havia razão para Node/Python. |
| Áudio | Howler.js + ElevenLabs API | Howler resolve streaming e pooling de <audio>. ElevenLabs gerou vozes e SFX de bioma. |
| Android | WebView + Play Billing SDK | Evita duplicação de codebase. A bridge JS↔Java expõe billing, AdMob e hápticos. |
| Assets Visuais | IA generativa + scripts Node.js | Desenvolvimento solo torna inviável arte manual em escala. Pipeline automatizado remove fundos e comprime. |
html5PoolSize = 10 para evitar
travamento do WebView Android com ~15+ elementos <audio> simultâneos.
Isso não é problema em browser desktop, mas é uma constraint real em mobile low-end.
O core de combate opera via moves.ts, onde cada ataque segue o fluxo
Startup → Active → Recovery. A Finite State Machine (FSM) desacopla
lógica de combos, cancelamentos e I-Frames (frames de invencibilidade). Isso é
arquitetura padrão de fighting games — a execução em Phaser/TypeScript é não-convencional,
mas o modelo conceitual é correto.
// Cada fase gera um POST ao encerrar
POST /api/progress
{
"stageIndex": 12,
"hp": 68,
"time": 94,
"combo": 7,
"skin": "raiz",
"difficulty": "medium"
}
// Backend persiste via ProgressRepository::updateMaxStage
// com validação de progressão estrita: só aceita maxStageIndex + 1
// Impede exploits de pulo de fase via requisição direta
Seis golpes técnicos implementados com sprites dedicados por skin:
Martelo, Benção, Rasteira, Cocorinha (esquiva), Aú (com I-Frames) e Meia-lua de Compasso (especial).
A Rasteira inicialmente reutilizava o sprite de esquiva com setTint(0xaaaaaa) — débito
técnico detectado em auditoria e corrigido com sprite próprio e partículas de impacto.
Cada criatura tem comportamento especializado com golpe especial único e VFX exclusivo.
Não são variações de um template genérico — o código de EnemyBrain e
BossBrain foi individualizado:
| Criatura | Arquétipo de IA | Golpe Especial |
|---|---|---|
| Iara | Debuffer | Inverte controles por 2s |
| Boitatá | Zoner | Nova de fogo radial com screen shake |
| Mapinguari | Tank | Terremoto ativa abaixo de 50% HP |
| Pisadeira | Assassin | Dash com invisibilidade parcial + paralisia |
| Corpo-Seco | Drainer | Drena HP e escurece a tela |
| Mãe-do-Ouro | Zoner Elite | 3 explosões douradas sequenciais abaixo de 40% HP |
| Boto | Trickster | Teleporte + espiral de charme duplo |
| Cuca | Summoner | Invoca minions com círculo arcano |
O sistema mais sofisticado tecnicamente do projeto. Cada background gera vida procedural
via GPU sem GIFs ou frames pré-renderizados. O método StageScene.drawBackground()
analisa substrings do nome do arquivo de fundo e injeta 4 pipelines:
// Exemplo: bg_quilombo.png ativa automaticamente:
addAmbientParticles() // faíscas laranja/amarelas, blendMode: ADD
addAmbientTweens() // brilho pulsante de fogueira (alpha 0.4→0.75)
addLivingScenarioElements() // 30 estrelas + estrelas cadentes
getAmbientSFXKey() // → "sfx_fogueira_ambiente"
// Todos os assets visuais são gerados em runtime via canvas HTML5
// e registrados com this.textures.addCanvas()
// Zero bytes adicionais no bundle por novo cenário
depth: 1-2, lutadores em depth: 10.
Essa separação estrita evita z-index conflicts entre elementos procedurais e sprites de combate.
Não é sofisticação desnecessária — sem ela, partículas sobrepõem personagens em determinados biomas.
Documentar apenas o que funcionou é marketing, não engenharia. Os bugs abaixo foram reais, causaram regressões e exigiram correção explícita:
Build release crashava no boot. O R8 removia WorkDatabase_Impl, dependência transitiva do Firebase Crashlytics via WorkManager. A regra -keep class *_*_Impl usava asterisco simples que não atravessa pacotes.
Regra corrigida para -keep class **_Impl { *; } com duplo asterisco, somada a -keep class androidx.work.impl.** { *; }. versionCode bumpado de 4 para 5.
Race condition: o Phaser transitava pela HubScene na inicialização e ativava a regra React de retorno ao /menu, pois startedRef já estava pré-sinalizado antes do jogo iniciar de fato.
Criação do gameplayStartedRef — flag ativada somente ao entrar em cena de combate real (StageScene, BossScene, IntroScene, etc.). Inicialização do Phaser não ativa o guard.
Os sprites das 4 skins do player são desenhados olhando para a direita — convenção oposta à dos inimigos. Player.ts usava flipX = this.facing === 1 (regra do Enemy), fazendo o player chucar visualmente para o lado oposto do inimigo.
Lógica invertida em 5 ocorrências de Player.ts para flipX = this.facing === -1. Enemy.ts e Boss.ts permaneceram inalterados.
RhythmScene usava GlobalAudio.currentMusicMs() (Howler.seek()) para temporização. Como o AudioSystem cacheia Howls entre cenas, se a faixa já tinha tocado, o seek retornava ~14s já decorridos — marcando as primeiras 25+ notas como MISS instantâneo.
Temporização trocada para this.time.now - this.startTime (relógio interno do Phaser). Adicionado emit de time:changed que estava faltando, deixando o cronômetro do HUD em 00:00.
Em builds de produção, cenários ficavam pretos após assistir ou pular introduções de capítulo. O preload() executava durante desvio de tick para IntroScene, registrando entradas corrompidas de textura no gerenciador global do Phaser.
Guard do estado transitioning adicionado em StageScene.ts e BossScene.ts para anular o preload() durante transições. Não reproduzível em desenvolvimento — bug exclusivo de produção minificada.
Desenvolver um jogo com 41 backgrounds, 65+ sprites de inimigos, 4 skins de player e cenários cinematográficos como desenvolvedor solo não é viável com arte manual. A solução foi construir um pipeline de geração e pós-processamento:
scripts/audit_backgrounds.ps1 verifica magic bytes — arquivos .png com assinatura JPEG não têm canal alpha e corrompem transparênciafix_enemy_floodfill.js aplica flood-fill desde as bordas da imagem; chroma-key global remove bolsões internos não alcançados pela bordasharp .png({ palette: true }) reduziu sprites de 700–1400KB para 33–80KB com canal RGBA preservadooptimize_sprites.js remove áreas transparentes — Pisadeira, por exemplo, reduziu de 350px para 150px de altura efetivagenerate_thumbnails.ps1 gera previews de 240px para o menu — redução de 85% nos dados carregados na tela de seleção de fasescheck_assets.js verifica transparência real, detecta cantos opacos e impede PNGs corrompidos de entrar no buildDois vetores de receita implementados:
ca-app-pub-5989700198805170/1895396557 — renderizado no rodapé do MainMenu via <AdView> nativo, controlado pela bridge JS AndroidBilling.showBanner/hideBannercapoeira_soul_premium_lifetime — remove banner e concede selo "Mestre Fundador"
O backend valida o purchase token via Google Play Developer API em
PremiumController.php, persiste is_premium em
player_profiles e audita compras em premium_purchases.
Em ausência do google-service-account.json, o endpoint aceita on-trust (modo staging).
| Item | Status |
|---|---|
| Internal Testing (Play Store) | ✅ Publicado — versionCode 5 |
| Google OAuth Consent | ✅ In Production — sem verificação manual necessária |
| SHA-1 Debug + Play Store | ✅ 2 OAuth Clients configurados |
| Keystore Backup | ✅ Duplicado (HD + pen-drive) — 3º backup (cloud criptografada) pendente |
| Ad Unit de Produção | ⚠️ Ainda usando ID de teste no código — troca imediata antes do AAB de produção |
| Service Account JSON | ⚠️ Ausente no servidor — PremiumController em modo on-trust |
| Migration 008 (is_premium) | ⚠️ Não aplicada no MySQL de produção |
| SKU no Play Console | ⚠️ Produto capoeira_soul_premium_lifetime não criado |
O jogo tem localização completa em Português, Inglês e Espanhol via
LocalizationSystem com hook useL10n.
Isso cobre: 45 nomes de fases, menus, UI, 13 criaturas do bestiário,
narrações de lore (39 MP3s: 13 × 3 idiomas), vozes de combate dos 4 protagonistas
e telas de autenticação.
A trilha sonora do menu principal troca automaticamente por idioma:
berimbau (PT), capoeira_soul_en (EN), alma_de_capoeira_es (ES).
A troca de idioma dispara unloadUnusedMenuMusic no AudioSystem
para evitar acúmulo de 3 faixas de ~4MB na RAM do dispositivo.
A estrutura narrativa de Capoeira Soul segue o arco clássico do aprendizado na capoeira: da corda branca à mestria. Mas o jogo não é uma abstração genérica de artes marciais — cada personagem carrega identidade, motivação e destino específicos, todos resolvidos nos epílogos cinematográficos ao completar a Stage 39.
O personagem de entrada e o mais visceral do elenco. Raiz não é um nome — é uma identidade. Ele representa o praticante que se conecta à capoeira pela via corporal, não pela filosófica. Seu estilo privilegia força bruta, golpes baixos e presença física na roda. A jornada de Raiz é a de um jovem que começa sem entender o que a capoeira significa e termina carregando seu peso histórico. Seu epílogo é o mais austero: não há vitória grandiosa, há pertencimento.
O contraponto técnico de Raiz. Dourado é o jogador que leu sobre capoeira antes de praticá-la — conhece a teoria, os toques do berimbau, a história das rodas na Bahia. Seu estilo é ginga constante, esquivas longas, contragolpes. Menos força, mais mandinga. Narrativamente, Dourado enfrenta a tensão entre performance e autenticidade: ser o melhor na roda ou ser verdadeiro com a roda. Seu epílogo resolve essa tensão com uma escolha que não é óbvia.
O personagem narrativamente mais complexo. Lucas Andrade é um mediador — sua função na história não é vencer inimigos, é unir o mundo fragmentado das rodas de capoeira do Brasil. Em um jogo onde os capítulos percorrem de Salvador ao Amazonas, de Brasília à caatinga, Lucas é o fio condutor. Ele não pertence a nenhuma escola específica. Seu nome completo aparece — único entre os protagonistas — porque ele tem uma identidade civil, não apenas uma identidade de praticante. Seu epílogo é o mais longo e o único com consequências coletivas.
Aurora não é um personagem jogável no sentido tradicional — ele é o mentor do trio. Mestre veterano do Pelourinho, Antônio Carlos narra os prólogos dos três capítulos, estabelece o contexto histórico de cada região visitada e é a voz que abre cada luta com o grito localizado de "VAMO JOGAR!" (PT), "LET'S PLAY!" (EN) e "¡A JUGAR!" (ES). Jogar com Aurora é jogar com o peso da responsabilidade: ele já sabe o que Raiz, Dourado e Lucas ainda vão aprender. Seu epílogo é o mais melancólico — o mestre que formou os outros e agora precisa decidir o que fazer com o silêncio que se segue.
Mestre Pássaro é o guardião do Templo do Pássaro (bg_templo), cenário de
atmosfera roxa mística onde o primeiro capítulo culmina. Ele não é um antagonista no sentido
narrativo — é um teste. A capoeira tem essa tradição: o mestre que barra a passagem não
quer impedir o aluno, quer saber se o aluno está pronto.
Tecnicamente, Mestre Pássaro foi o personagem com mais débito de arte no projeto:
o loader do Phaser caía em fallback genérico em vez de renderizar o sprite correto.
A correção exigiu mapear explicitamente o ID mestre_passaro para carregar
os sprites passaro_*. A arte final foi recriada do zero em HD Digital Painting —
estética diferenciada dos outros inimigos, que seguem pixel art NeoGeo.
Lutador do sertão · Arquétipo regional do Capítulo 2
Curupira é o guardião da floresta no folclore brasileiro — pés virados para trás, cabelos de fogo, protetor dos animais. No jogo, ele é o boss que representa o Brasil que o progresso esqueceu: a Amazônia, o Pantanal, a mata fechada que os cenários do Capítulo 3 mostram sendo percorrida.
Seu arquétipo de IA é Tank com Super Armor e terremoto. Abaixo de 50% de HP, ativa Fúria Primordial — terremoto duplo com rachaduras visuais e pedras voando. A progressão de dificuldade dele é deliberada: o jogador que chegou até aqui já aprendeu a reagir aos especiais dos inimigos anteriores. Curupira exige que esse aprendizado seja aplicado em janelas de tempo mais curtas.
Cada criatura não é apenas um inimigo — é uma representação de uma região, um medo coletivo ou uma força da natureza brasileira. O bestiário funciona como enciclopédia: cada criatura tem ficha de lore textual, narração de áudio em três idiomas (39 MP3s) e arte HD.
| Criatura | Origem do Folclore | O Que Representa no Jogo |
|---|---|---|
| Saci | Pan-brasileiro | O trickster. Caos controlado. Fase de introdução às surpresas. |
| Caipora | Tupi-Guarani · Nordeste | Protetora da mata. Guardião antes de Curupira. Mais ágil, menos poderosa. |
| Curupira | Amazônia · Tupi | Boss principal da floresta. Representa a natureza que reage ao invasor. |
| Iara | Amazônia · rios | A sedução como arma. Inverte controles — desorientação psicológica. |
| Boitatá | Sul · Guarani | O fogo dos campos. Zoner que controla o espaço com rastros persistentes. |
| Mula-sem-cabeça | Centro-Oeste · interior | Violência sem direção. Rusher que atravessa a arena sem parar. |
| Cuca | Pan-brasileiro · infantil | O medo da infância que se torna real. Invoca outros medos (summoner). |
| Boto Encantado | Amazônia · ribeirinho | O charme masculino predatório. Trickster com teleporte e confusão. |
| Mapinguari | Amazônia · pré-histórico | A memória ancestral da floresta. Tank inabalável com ativação tardia. |
| Pisadeira | Sudeste · urbano | Paralisia do sono. Assassin invisível — representa o medo moderno. |
| Corpo-Seco | Sertão · nordeste | O morto que a terra rejeitou. Drainer que enfraquece lentamente. |
| Mãe-do-Ouro | Minas Gerais · colonial | A riqueza que defende a si mesma. Zoner elite ativada pela cobiça do jogador. |
| Malandro | Rio de Janeiro · urbano | A capoeira de rua, sem código. O espelho do que o jogador poderia se tornar. |
| Capítulo | Título | Arco Narrativo |
|---|---|---|
| Cap. 1 | Raízes e Ruas | A origem. Bahia, Lapa, Pelourinho, Quilombo. O aluno descobre que a capoeira não é só luta — é memória de resistência. Culmina no Templo do Pássaro com Mestre Pássaro como prova de prontidão. |
| Cap. 2 | O Despertar da Mestra | A consolidação. Terreiro, cachoeiras, caatinga, carnaval, cais. A jornada sai da Bahia e percorre o Nordeste e o litoral. O personagem aprende que cada roda tem suas próprias regras — e que respeitar isso é mandinga, não fraqueza. |
| Cap. 3 | Lendas Urbanas e Folclore | A síntese. São Paulo, Rio, Brasília, Amazônia, Pantanal. O Brasil inteiro como palco. Os inimigos deixam de ser humanos — são as forças que o país carrega. Concluir esse capítulo não é vencer: é entender. |
Os epílogos são disparados ao completar a Stage 39 (Mina de Ouro Proibida) e apresentam finais personalizados para cada herói com arte pixel art original e trilha solene — quatro histórias distintas para quem chegou ao mesmo lugar por caminhos diferentes.