Diário de desenvolvimento

Provar que todo nível é solucionável antes de publicar

5 min de leitura

Um nível de puzzle só merece ser publicado se alguém já o venceu. No Sunny Sort nenhum nível chega a um celular antes de uma busca offline jogá-lo até o fim, dar nota e passá-lo por um conjunto de filtros — e os níveis que falham são descartados, não remendados.

A falha que forçou isso

Os níveis vinham direto de uma semente aleatória. O gerador existia como um esboço que imprimia generate: not implemented, e o catálogo era o que as sementes por acaso produzissem.

Testar dez níveis difíceis revelou que quatro deles não tinham como ser vencidos, nem com uma beam search de largura 2000. O jogador chegava ao nível 13, batia numa parede, e o jogo anunciava OUT OF SPACE como se a derrota fosse dele.

Isso é pior que um problema de dificuldade. Um nível difícil ensina alguma coisa quando você tenta de novo. Um nível impossível ensina que o jogo está mentindo.

Por que uma semente crua não pode ser publicada

A semente determina a esteira — quais pilhas chegam e em que ordem. Nada na semente sabe se o nível resultante pode ser limpo dentro do tabuleiro dele, das células bloqueadas e da meta. Ser solucionável é propriedade da configuração inteira, e o único jeito confiável de descobrir é jogando.

Então o catálogo passou a ser gerado, e não amostrado.

O pipeline

O generate roda um laço por estágio da curva:

GENERATE → SOLVE → ANALYZE → SCORE → FILTER → KEEP

Sementes candidatas são produzidas, jogadas por um beam solver, medidas pelo analisador, pontuadas e então filtradas. As doze melhores de cada estágio são mantidas e escritas em LevelCatalog.cs, que é código gerado — nunca editado à mão.

Quatro regras de rejeição fazem o trabalho:

  • impossível — nenhuma linha de busca vence;
  • trivial — fora do tutorial, um nível que todo bot burro vence não é puzzle;
  • dominado — uma estratégia vence a partir de um canto, então a posição deixa de importar;
  • folga zero — existe exatamente uma linha vencedora.

Níveis de tutorial têm filtro próprio: no máximo 14 jogadas e pelo menos uma cascata de três ondas. O primeiro minuto precisa demonstrar a mecânica, não testá-la.

O que ele rejeitou

Na curva publicada, 96 sementes foram validadas. Seis níveis impossíveis e cinquenta e dois triviais foram jogados fora durante a geração.

Mais da metade do que uma semente aleatória produz não é puzzle. Essa proporção sozinha já justifica o pipeline.

Ser solucionável não bastava

O nível ser vencível acabou sendo uma garantia mais fraca do que parece. Uma pessoa jogando a build publicada empacou no nível 13 e disse que era impossível. Não era — mas por pouco:

Linha ótima do nível 13 11 jogadas
Orçamento de jogadas 11 jogadas
Folga zero

Existia exatamente uma sequência vencedora. Uma jogada desperdiçada acabava com o nível. E não era só o 13 — 7, 10, 13, 14 e 15 tinham folga zero.

A causa era uma premissa errada na calibração do orçamento. Ele alargava só até um buscador deliberadamente fraco também vencer. Quando esse buscador acertava a linha ótima de primeira, o orçamento não alargava nada. "O buscador fraco achou fácil" não é "um humano vai achar fácil" — um buscador enxerga o tabuleiro inteiro de uma vez e não está ao mesmo tempo aprendendo as regras. O jogador está fazendo as duas coisas.

A correção foi um piso: MinimumSlack = 2. Todo nível concede pelo menos duas jogadas além da solução mais curta, por mais fácil que um buscador o tenha achado.

O que custou

Eu esperava pagar em profundidade. Uma medição anterior dizia que um orçamento de ótimo+2 leva a largura de decisão a 97%, ou seja, quase nenhuma jogada perde. Não custou isso:

Antes Depois
Níveis exigindo linha perfeita 5 0
Níveis triviais (7-20) 0 0
Níveis que derrotam o bot greedy 5 5

O motivo é que o gerador não só afrouxou o teto — ele reescolheu as sementes sob o teto novo, mantendo as que continuam exigentes com mais ar. Afrouxar o orçamento sozinho teria custado profundidade. Fazer as duas coisas juntas não custou.

O que segura isso no lugar

Um teste, CatalogCoversEveryStage, falha se algum estágio da curva ficar sem semente validada. O pipeline não tem como parar de produzir níveis para um estágio em silêncio e deixar a lacuna para o jogador descobrir.

A garantia que chega ao jogador é estreita e vale enunciar com precisão: nenhum nível publicado é impossível, e nenhum exige uma única sequência perfeita. Você ainda pode perder enchendo o tabuleiro. Essa parte é sua.

Todas as publicações