Como o clube rende com a aba fechada sem derreter o celular

Oito horas offline não são simuladas segundo a segundo. Elas são calculadas de uma vez, e o resultado precisa bater com o de quem ficou com o jogo aberto.

O pedido que parece simples

Num jogo idle, a promessa central é esta: você fecha o jogo, vive sua vida, volta depois e o clube rendeu. É o que faz o gênero existir. E é onde mora um problema de engenharia que não aparece de cara.

O jogo roda avançando o estado em passos pequenos, de uns 100 milissegundos, porque é assim que a barra de progresso anda suave na tela. Agora faça a conta: oito horas são 28.800 segundos, ou seja, 288.000 passos. Se ao abrir o jogo a gente simplesmente rodasse o mesmo laço 288.000 vezes, o aparelho travaria por vários segundos numa tela branca, e em celular mais fraco o navegador mataria a aba.

Pior: quem ficou dois dias fora teria que esperar mais ainda. O tempo de abertura cresceria junto com a ausência, que é exatamente ao contrário do que o jogador espera, porque quanto mais tempo fora, maior a vontade de ver o resultado logo.

A saída é não simular

A resposta é calcular o resultado de uma vez, em tempo constante, sem iterar. Se um ciclo de produção dura 3 segundos e se passaram 8 horas, não é preciso contar os ciclos um a um: dá para dividir.

// a ideia, sem os detalhes const ciclos = Math.floor((restante + dt) / duracao) const ganho = porCiclo.times(ciclos) const sobra = (restante + dt) % duracao

Três linhas resolvem qualquer intervalo: cinco segundos ou dois dias custam o mesmo. Isso é o que a gente chama de avanço em tempo constante, e é a propriedade que torna o ganho offline possível sem penalizar quem ficou mais tempo fora.

A parte difícil não é a fórmula. É garantir que ela continue dando o mesmo resultado que o caminho longo, porque agora existem dois caminhos para o mesmo estado, e dois caminhos que divergem em silêncio são a pior categoria de bug de jogo: o jogador com a aba aberta e o jogador que voltou depois teriam saldos diferentes, e ninguém saberia qual está certo.

A prova é um teste de equivalência

O projeto tem um teste que faz exatamente isso: pega um estado, avança oito horas de uma vez, e depois pega o mesmo estado e avança 288.000 passos de 100 milissegundos. No fim, compara os dois.

E aqui aparece o detalhe que o post sobre números grandes já contava: os dois resultados não são idênticos. A contagem de ciclos é, porque ciclo é inteiro. Já o caixa difere na casa de 10^-15 relativos, porque somar 288.000 parcelas pequenas arredonda diferente de somar uma grande.

Dois critérios, de propósito

Ciclos são comparados com igualdade exata. Caixa é comparado com tolerância. Usar igualdade nos dois dá um teste que reprova código correto; usar tolerância nos dois deixa passar um erro de contagem de ciclo, que é um erro de verdade.

Duas regras que o teste cobra e não são óbvias

A primeira: a duração de um ciclo precisa ser um número inteiro de milissegundos. Se um negócio rendesse a cada 3,33 segundos, a divisão e o resto deixariam de fechar entre os dois caminhos, e a equivalência quebraria por um motivo que ninguém encontraria olhando o código.

A segunda: o tempo entra por parâmetro. Nenhuma linha do núcleo do jogo chama o relógio do sistema. Quem sabe que horas são é a camada de fora, que passa o instante para dentro. Isso soa burocrático até você precisar testar oito horas de jogo: com o relógio dentro do código, esse teste exigiria esperar oito horas.

A mesma regra vale para sorteio. Nada de aleatório solto: o mercado de jogadores e o resultado das partidas saem de um gerador semeado, o que faz o mesmo save produzir sempre a mesma sequência em qualquer aparelho. É o que permite, mais tarde, verificar no servidor se um ranking foi jogado ou inventado.

O teto, e por que ele é dito em voz alta

O acúmulo offline tem um limite de oito horas. Não é limitação técnica: é escolha de jogo, porque idle que rende para sempre sozinho deixa de ter motivo para você voltar.

O que importa aqui é como isso é apresentado. A versão inicial só mostrava o ganho, e quem ficou dois dias fora via um número que não batia com a ausência, sem explicação. Parecia bug. Hoje o relatório de volta diz as duas coisas: quanto foi creditado e quanto ficou de fora do teto. E o aviso do limite aparece antes de cobrar, na primeira volta, não só na volta em que o jogador já perdeu algo.

É uma regra de produto que vale para além deste jogo: um limite que só se revela no momento em que pune é lido como defeito, mesmo quando é intencional.

← Todos os posts

Também dá pra só jogar.

Jogar grátis agora