football-manager · motor de simulação

A pergunta não é se a interface está bonita. É se a tabela final é crível.

Um motor de carreira escrito em Python que simula temporadas inteiras de futebol com um modelo de Poisson calibrado contra distribuições reais. Abaixo ele roda no seu navegador: simule uma temporada, depois rode o portão de calibração — milhares de temporadas medidas contra onze faixas de futebol de verdade.

Simule uma temporada

Returno duplo, 380 partidas, três pontos por vitória. As forças dos clubes são as do repositório. A mesma semente sempre devolve a mesma temporada — o motor é determinístico.

# Clube PJVED GPGCSGForça
★ campeão ▾ rebaixado Força = overall do elenco declarado no pack

O portão de calibração

Esta é a peça central do projeto. O motor roda milhares de temporadas e compara onze métricas com faixas de futebol real. Quebrar um alvo é build vermelho. Se o melhor elenco é campeão em 90% das temporadas, o jogo é uma planilha; se é em 10%, é um dado.

Quem levantou a taça

Rode o portão para ver a distribuição de títulos.

O motor

Poisson com diferença de força saturada. Três constantes são o coração do jogo — mexer nelas exige rodar o portão de novo.

// fm/match.py — reproduzido linha a linha nesta página
z            = (ovr_efetivo - 68) / 20
d            = tanh((z_casa - z_fora) / 1.20)   ← a saturação
lambda_casa  = gols_base * exp(+0.58*d + mando/2)
lambda_fora  = gols_base * exp(-0.58*d - mando/2)
gols         ~ Poisson(max(lambda, 0.34))     ← ninguém é nulo

A saturação é o que impede o 9-0

Sem o tanh, um gap de 26 pontos de overall gera 22% de partidas com 3+ de margem, contra ~14% no futebol real. A saturação reproduz o que acontece de verdade: time goleando administra o resultado, time perdendo se fecha.

Não existe ruído anônimo

A versão com mistura Gamma foi testada e o otimizador empurrou a dispersão para zero. Toda a variância vem de causa com nome — desgaste, moral, forma, clássico, mando. O jogador entende “meu time estava morto”; não entende “o multiplicador deu 0,6”.

O piso mantém o pequeno vivo

O lado fraco nunca cai abaixo de ~0,60 gol esperado. Em 82 contra 56, o pequeno ainda vence 9,3% das vezes e o jogo ainda termina 0-0 em 5%.

O caráter da liga vem do dado, não do código

Não existe if country == "BRA" em lugar nenhum. As três ligas rodam com as mesmas constantes de motor. O que muda é a distribuição de força dos clubes, declarada no arquivo da liga — e daí emergem equilíbrio, empates, pontuação do campeão e previsibilidade do título.

LigaCampeãoLanternaEmpate Gols/jogoMargem 3+Maior é campeão
Série A73,832,125,5%2,5213,3%36,1%
Série B68,735,226,8%2,3711,5%16,0%
La Liga79,230,424,6%2,6114,4%41,1%

O caso mais bonito é a Série B: mesmos parâmetros de motor, elencos quase equivalentes, e o resultado é campeão com 68 pontos e o melhor elenco do papel levando o título em apenas 16% das temporadas. A Série B ser maluca não foi programado — é consequência de a liga ser achatada.

Ressalva honesta

Esta página reimplementa fm/match.py em JavaScript para rodar sem instalar nada. O motor real gera elencos completos por clube e usa o overall do onze titular; aqui a força declarada do clube entra direto. Medido em 400 temporadas, a diferença fica dentro do ruído de Monte Carlo: Série A com campeão em 73,7 contra 73,8 do motor real, 2,52 gols por jogo nos dois, margem 3+ em 13,3% nos dois.

As faixas dos alvos e as forças dos clubes são estimativa editorial marcada verificado = false no repositório, não dado conferido. Re-derivar com as tabelas reais é o primeiro item da lista de pendências.