Imported from dlopessales/invasion (
AGENTS.md). Install upstream withnpx skills add dlopessales/invasion. Copyright stays with the author.
AGENTS.md — Last Invasion
Contexto para qualquer assistente (ou para mim mesmo daqui a meses) retomar este projeto. Foca no que não é óbvio lendo o código: as decisões, as fronteiras e as armadilhas que já custaram tempo.
O README.md documenta cada sistema em detalhe. Este arquivo é o mapa mental.
O que é
Jogo de sobrevivência isométrico 3D em Godot 4 + GDScript, temática de invasão alienígena. Estilo Project Zomboid: o jogador é uma presa, não um herói. Os pilares são vulnerabilidade, simulação profunda e permadeath com peso. Solo hoje, mas projetado para coop desde o primeiro dia.
Arte é placeholder (primitivas). Será terceirizada depois; nada no código deve assumir assets específicos.
A regra de ouro: simulação vs apresentação
Esta é a decisão mais importante do projeto. Toda mudança passa por ela.
sim.gdé a VERDADE. Todo estado do mundo vive numWorldState, que é dado puro. O servidor é o único que o altera, e só dentro dostep(), a cada tick fixo (10/s).- O cliente só DESENHA e manda COMANDOS. Nunca altera o estado direto. Input
vira um comando (intenção); quem resolve é o
step(). - No solo, servidor e cliente vivem no mesmo processo. No coop,
submit()vira RPC. É só isso que muda — por isso a fronteira tem que ser respeitada.
Como decidir de que lado uma coisa fica: a fronteira não é sobre o que a coisa é, é sobre o que ela decide. Enquanto algo é puramente visual, mora no cliente. No instante em que vira jogabilidade (afeta o mundo, a IA, o resultado), migra para o Sim. Já aconteceu duas vezes:
- O cone de visão nasceu no cliente (efeito visual). Quando o lock de alvo
passou a depender dele, virou jogabilidade e mudou para o Sim (
sight(),can_see()). O cliente hoje só consulta. - A trilha de sangue ainda está no cliente. Se um dia o alien rastrear sangue, ela migra — porque passa a afetar o mundo.
Comandos são discretos, não contínuos, sempre que possível. Lock de alvo era
SetLockCommand ("travei no alien 3"), uma mensagem. Quando virou mira de mouse,
virou ângulo contínuo — custo de rede real no coop. A mitigação foi quantizar
(AimCommand só quando o ângulo muda >1.5°). Pense nisso antes de criar comando
que dispara todo frame.
Arquivos
| Arquivo | Papel |
|---|---|
sim.gd |
Dados (WorldState e cia.), Commands e criação do mundo. class_name Sim — API pública, referenciada como Sim.X pelo resto do projeto. ~1560 linhas. |
sim_needs.gd |
SimNeeds — necessidades, sono, lanterna, sentar, pulo, movimento, escada. |
sim_combat.gd |
SimCombat — golpe, guarda, resolução de dano, montagem de construção (abort_build). |
sim_alien.gd |
SimAlien — percepção e comportamento do alien. |
game_server.gd |
Autoload GameServer. Tick fixo, dono do estado e submit(). |
game_client.gd |
Input, interpolação, câmera, e desenha tudo. ~1600 linhas. |
hud.gd |
HUD (layer 1), tela de morte, vinheta de sangue. |
inventory_ui.gd |
Inventário em grade (I/Tab, ESC). Layer 2. |
build_ui.gd |
Menu de construção (B), aviso de receita, barra de montagem. Layer 3. |
survivor_body.gd |
Boneco placeholder (grupos Quadril/Pernas, pose sentada). |
vision_overlay.gdshader |
Realça o cone de visão (embaça fora dele). |
wall.gdshader |
Buraco local no personagem. Só a mata usa hoje. Não apagar. |
Setup que não está no código
- Input Map:
move_left/right/up/down(WASD),sprint(Shift),sleep(R),pickup(E),drop(Q),flashlight(F),jump(Espaço),sit(C). Botões do mouse, B, I, Tab, ESC, teclas 1-5 são lidos direto do evento. - Autoload:
GameServer→game_server.gd. Escolher o arquivo ANTES de clicar Add, senão o path fica vazio e falha em silêncio. - Main Scene:
res://main.tscn. - Cena
main.tscn: Main (Node3D,game_client.gd) com Ground (PlaneMesh 70×70), Player (survivor_body.gd), Camera3D, DirectionalLight3D, e os CanvasLayers HUD / InventoryUII / BuildUI. - Câmera: configurada por CÓDIGO (
CAMERA_YAW/PITCH/DISTno cliente). Os valores da cena são sobrescritos no_ready()— de propósito, senão posição e rotação ficam incoerentes e o player sai do centro. - Display: Full Screen; Stretch Mode
canvas_items; Aspectexpand. Fundo além do chão é PRETO.
Armadilhas de GDScript (todas já morderam)
- Inferência de tipo com
:=. Valor vindo deArray/Dictionary, ou de parâmetro sem tipo (as classes internas passamWorldState/Aliencomos/asem tipo), é Variant.:=não infere e o parse quebra. Usevar x: Tipo = .... Foi o erro mais recorrente do projeto. - Nome de classe interna não pode colidir com nativa.
Treeé reservado (controle de UI do Godot) → viramosFoliage. Antes de nomear uma classe, confira se não existe nativa. - Material transparente não escreve profundidade e é ordenado pelo centro
do objeto. Uma parede longa com alpha 1.0 mas marcada como transparente é
desenhada por cima de um alien à frente dela. Solução:
_set_transparency()liga o alpha SÓ durante o fade; opaco no caso comum. Vale para paredes, copas e o alien. @warning_ignore("integer_division")nas divisões inteiras intencionais.- Meus scripts de edição já duplicaram uma linha (
func x():func x():), reportado pelo Godot como "Standalone lambdas cannot be accessed". Se aparecer erro estranho de lambda/função, procure concatenação.
Convenções do mundo (TRAVADAS — passar pro artista)
1 unidade = 1 metro. Sobrevivente adulto = 1.8u. Modelo olha para -Z. Origem
entre os pés. Grid de 1m. Um andar = FLOOR_HEIGHT = 2.8u.
Sistemas implementados
Necessidades (fome/sede/cansaço/vida) · movimento 360° com sprint · sono · colisão dentro do Sim (não usa CharacterBody3D, quebraria o coop) · itens por peso · ciclo dia/noite + lanterna com bateria · alien (percebe por luz, visão em cone, som; paredes e troncos bloqueiam; persegue com memória) · morte/permadeath com estatísticas · sangue via fila de eventos · combate completo (golpe com compromisso, guarda direcional, contra-ataque, telegrafo do alien com preparação de 0.5s) · cone de visão do jogador + shader · lock→mira livre · pulo + obstáculos baixos · sentar (gira o corpo com o mouse) · floresta (troncos bloqueiam linha de visão) · receitas por descoberta + construção com prévia e montagem por tempo · dois andares + escada.
Padrão de eventos: o estado diz como o mundo está; a fila events diz o
que mudou neste tick (hit, death, build, alien_windup...). O cliente e
o HUD consomem, cada um com seu índice. É onde entram som e tremor de tela depois,
sem gambiarra de comparar estado entre frames.
Decisões de design a preservar
- O combate é ruim de propósito. Se bater funcionar bem, o jogo vira ação e o resto perde sentido. Golpe custa cansaço na escala do sprint; o resultado normal é empurrar, não matar. Não "melhore" isso sem perceber o que quebra.
- Luz é sempre uma troca. Lanterna e fogueira iluminam e te entregam ao
alien (
lit_by_structureentra na percepção dele). - O alien sobe escada. Deixou de ser refúgio garantido — agora é o mesmo
jogo pra ele: se perceber você em cima, ele vai atrás, entra na escada e
sobe igual ao jogador (
SimAlien._alien_chase, via a boca da escada do próprio andar — hack de pathing, não navmesh, aceitável porque só existe uma escada na casa). - Andares têm altura CONTÍNUA (
height: float, recalculada tick a tick emWorldState.stair_progress+SimNeeds.update_vertical), não mais teleporte discreto.level(inteiro) sobrevive só como "andar mais próximo", pra filtrar parede/item/luz — nunca mais dispara mudança de altura sozinho. Longe da escada os andares continuam aFLOOR_HEIGHT(2.8) de distância vertical, então visão/ruído/combate entre andares continuam bloqueados como antes (Sim.VERTICAL_REACHé a tolerância que só se abre dentro do vão da escada — é isso que permite lutar no meio dela). - Child safety / tom: o jogo é de terror adulto, mas mantenha a violência no registro de tensão e sobrevivência, não gráfica gratuita.
Estado atual e próximos passos
A fatia vertical está fechada: cria personagem, sobrevive, luta, constrói, morre de verdade. O conselho permanente é jogar ~10 runs antes de adicionar mais — a única métrica que importa é "quis tentar de novo depois de morrer?".
Roadmap sugerido, por retorno sobre esforço:
- Save/load —
WorldStateé dado puro, quase de graça, e transforma o jogo. - Som — a fila de eventos já está pronta; maior retorno em atmosfera.
- Mundo maior/vila — exige carregar por regiões (
wallscomo array único não escala).
Dívida técnica de quebrar sim.gd paga: o comportamento de tick foi
extraído para sim_needs.gd / sim_combat.gd / sim_alien.gd (todas
dependem de Sim, nunca o contrário). sim.gd ficou só com dados, Commands
e criação do mundo — é a API pública que o resto do projeto usa via Sim.X.
Marco grande, depois de estabilizar o solo: coop com servidor dedicado — a arquitetura já permite, mas multiplayer multiplica o custo de toda mudança.
Método de trabalho
Edições costumam ser feitas por scripts que fazem substituição de blocos, depois
o arquivo é entregue para eu colar no projeto Godot local. Após mudanças grandes,
vale rodar duas verificações: (1) todo Sim.X referenciado pelos outros scripts
existe em sim.gd; (2) nenhuma inferência := frágil sobre valor de Array/Dict.
Documentar cada sistema no README.md.