Doug's Game Dev Log
Sobre
EN|PT-BR
2026-08-26

Torres Que Escolhem Alvo Juntas

Toda torre do jogo tem um modo de mira: FIRST, STRONG, WEAK. Três regras fixas, cada uma aplicada por cada torre de forma independente, nenhuma delas sabendo que outras onze torres estão atirando no mesmo inimigo. Essa semana entrou um quarto modo, o SMART, que é a primeira regra de mira do jogo que olha pro resto da defesa antes de escolher. Ele chega como uma carta de upgrade com nível chamada Smarter Towers, e de propósito não é nem de graça nem confiável.

A pontuação

O SMART é uma função de pontuação pura sobre os inimigos que a torre está vendo, em lib/smartTargeting.ts. Todo candidato começa com 1 ponto e aí:

  • Imune ao meu tipo de dano: -10. Ou o multiplicador de tipo de dano é zero, ou a máscara de imunidade que o inimigo nasceu cobre o meu tipo. É o maior termo de longe, porque tiro em inimigo imune vale exatamente nada.
  • Boss: +3.
  • Forte contra ele (multiplicador acima de 1): +2.
  • Já mirado por outras torres: -0.5 por torre. Essa é a parte cooperativa. Nove torres travadas no mesmo inimigo da frente é o fracasso clássico de tower defense, e agora a décima coloca isso na conta.
  • Resistente mas descoberto: +1. Se meu tipo é fraco contra ele (multiplicador entre 0 e 1) mas literalmente ninguém mais está atirando nele, tiro ruim ganha de tiro nenhum.
  • Perto de vazar: +3, +2 ou +1 pra menos de 15%, 25% ou 50% de caminho restante.

Empate desempata por quem está mais adiantado no caminho, que é exatamente a regra do FIRST, então o SMART degrada pra algo familiar em vez de algo aleatório.

O pickSmartTarget percorre os candidatos, filtra por alcance e devolve o melhor eid. A coisa toda é pura e feita com TDD: a pontuação nunca encosta no ECS, ela recebe uma lista de candidatos carregando posição, distância no caminho, fração do caminho, mapa de resistências, máscara de imunidade e uma flag de boss.

Lendo a spec contra ela mesma

A spec que eu escrevi pra mim mesmo dizia pra premiar inimigo com "menos de 50% / 25% / 15% do caminho percorrido", em três faixas exclusivas. Lido ao pé da letra, isso premia inimigo que acabou de nascer e está longe do vazamento, e contradiz diretamente o critério de aceite três linhas abaixo, que diz que um boss perto do vazamento tem que ganhar de um inimigo de vida cheia lá longe.

Então as faixas foram implementadas como caminho restante, não percorrido: pathFraction vai de 0 no spawn até 1 no vazamento, e as faixas leem 1 - pathFraction. Essa leitura está documentada no topo do módulo, porque um Doug do futuro vai reler a spec e se perguntar por que o código discorda dela.

Uma chance, não uma garantia

O SMART não é um modo de mira estritamente melhor que você liga e esquece, porque isso simplesmente apagaria os outros três. Ter a carta no nível 1 dá 25% de chance de qualquer reescolha usar de fato a pontuação inteligente; no nível 100 é 100%. A rolagem sai do RNG com seed, então os clientes de co-op concordam sobre se a torre ficou esperta dessa vez. Quando a rolagem falha, ela cai pro FIRST.

Quando a escolha inteligente dá certo, a torre trava naquele alvo por quatro segundos antes de reavaliar. Sem a trava, as torres oscilam: a torre A escolhe um inimigo, isso sobe a penalidade de "já mirado", então a torre B evita, aí a A repontua e a formação inteira fica tremendo a cada frame. A trava custou dois campos novos por torre, currentTarget e smartLockUntil, os dois replicados nos snapshots de co-op pra que os clientes concordem sobre as travas e, por consequência, sobre as contagens de sobre-mira que todo mundo usa na pontuação.

Quem escuta

Os comportamentos standard, frost, poison, cone e confuse, mais a aquisição de alvo do beam, passam todos pela escolha com trava e pontuação. A Tesla é caso especial: ela continua acertando o padrão em estrela dela, mas agora ordena os alvos atingidos por pontuação smart. Morteiro e torres utilitárias ficaram como estavam, já que nenhuma das duas escolhe alvo de um jeito que o SMART pudesse melhorar.

Do lado da UI, o AimMode ganhou um quarto valor e tudo que encosta em modo de mira (herança de mira compartilhada, a ida e volta do aim-pending do co-op, o setAimMode e a variante por tipo) trata ele como válido. Um botão SMART aparece no menu de mira, o painel de inspeção ganha uma linha com a chance atual e o rótulo curto no HUD é SMRT. Tudo isso só aparece quando você tem a carta.

Já que eu estava lá: um console de debug que presta

Dois helpers no window.dbg que já se pagaram:

  • dbg.enemies() imprime o bestiário como tabelas de console: inimigos normais, bosses e os tokens de mutador (fire, frost, electric) que as variantes usam.
  • dbg.spawn(spec, quantidade) solta inimigos no início do caminho, um a cada 0.5s, independente da wave atual. O spec usa a mesma notação com ponto do JSON de waves, então dbg.spawn('splitter.fire', 10) te dá dez splitters imunes a fogo agora, e não dez minutos run adentro.
  • dbg.upgrade() lista toda carta de upgrade numa tabela; com um id, concede níveis direto, furando custo de unlock e as regras de número máximo de cartas (o teto compartilhado de nível 100 continua valendo).

Conseguir invocar exatamente a situação ruim é a diferença entre testar uma mudança de mira em cinco minutos e testar em cinco runs.

E uma faxina: o CLAUDE.md tinha virado um monolito, então agora está fatiado em arquivos por tópico dentro de docs/, com o AGENTS.md como porta de entrada canônica.

Na próxima: tudo sobre como os inimigos morrem.