← Caderno de laboratório

Quando a medição corrigiu o salto

O salto que derivei no papel não coincidiu com o salto produzido pelo motor. O vão de ensino de 5 tiles não exigia o dash que deveria ensinar. Duas plataformas de recuperação tolerantes formavam uma escada que apagava por completo a lição.

As três falhas só apareceram quando simulei a ordem real dos ticks na fase construída. O post sobre a pesquisa havia terminado antes de qualquer valor de movimento ou de fase ser escolhido; aquela menor versão completa agora foi lançada como Skyline Run.

A lição é simples: em um jogo discreto, a medição faz parte do design. A aritmética pode propor uma regra, mas só o sistema em execução pode mostrar se ela é verdadeira.

O personagem branco de Skyline Run cruza um vão de seis tiles entre telhados com o rastro ciano de um dash no ar, enquanto os valores medidos do salto e da fase enquadram a cena.
A primeira fatia jogável é pequena o bastante para que cada valor de movimento possa ser ligado a uma lição específica da fase.

Eu sou o proprietário e desenvolvo este site e o canal AI Maker Lab — este é um registro de construção, não uma análise independente.

A lista virou um contrato executável

As treze perguntas de A1 a C5 deixaram de ser sugestões e se tornaram um registro de design. A1 reduziu o jogo a uma frase: alcance a saída no telhado de um distrito neon correndo, pulando e dando dash; um perigo ou uma queda devolve o jogador ao checkpoint mais recente. O platformer de precisão rege a promessa, portanto movimento exato, falha atribuível e nova tentativa rápida têm prioridade.

A2 fixou a execução em 60 Hz com DT = 1/60. O motor é puro e não depende do DOM: step(state, input, level) retorna um novo estado e nunca altera seu argumento. A3 e A4 se tornaram title → playing ⇄ paused → complete, mantendo a morte dentro da partida como um bloqueio de entrada de 18 ticks seguido pelo reaparecimento no checkpoint, em vez de promovê-la a uma cena separada.

B1 reuniu posição, velocidade, contato, contadores de habilidades, SP, progresso nos checkpoints, mortes, ticks, entrada anterior, estado da câmera e avisos em um único estado autoritativo. B2 e B3 fixaram colisão separada por eixos com uma caixa delimitadora alinhada aos eixos (AABB), uma caixa de colisão que nunca gira, além de plataformas atravessáveis em um sentido que bloqueiam apenas durante a queda por cima e a precedência de gatilhos: primeiro objetivo, depois checkpoint e, por fim, perigo. Os estados concluído e morto bloqueiam a entrada; a nova tentativa preserva o tempo, as mortes e o checkpoint mais recente.

B4 transformou a entrada em um contrato medido. Pressionar, manter e soltar o salto têm significados distintos. O coyote time — alguns quadros de tolerância depois de sair da beirada — e o buffer de salto são armazenados como contadores de 6 e 7 ticks, mas suas janelas efetivas são de 5 ticks na ordem implementada: entradas pressionadas de 1 a 5 ticks além do limite disparam o salto, enquanto 6 ou mais não. Swink forneceu a linguagem da sensação de controle; Pittman, as metas de movimento legíveis para designers; Thorson, os mecanismos de tolerância; e Keren, os mecanismos de câmera coordenados com o salto.

O registro completo das treze perguntas, incluindo o amplo limite C5, está no registro de design de Skyline Run. O objetivo aqui não é repeti-lo. É mostrar onde aquelas respostas registradas estavam erradas antes que a medição as corrigisse.

O salto medido revelou os limites reais

O jogo é executado em um canvas de 640×360, com tiles de 16 px e uma AABB de jogador de 12×20 px. Estes são os valores de ajuste lançados, não estimativas iniciais.

A história depende de três deles: JUMP_VELOCITY define o impulso, GRAVITY_UP molda o ápice medido e RUN_SPEED decide se um vão ensina o dash.

ConstanteValorSignificado
RUN_SPEED130Velocidade horizontal máxima em px/s.
ACCEL_GROUND1400Aceleração no chão em px/s².
DECEL_GROUND1800Desaceleração no chão em px/s².
ACCEL_AIR1100Aceleração e desaceleração no ar em px/s².
JUMP_VELOCITY-500Velocidade para cima quando um salto dispara, em px/s.
GRAVITY_UP2080Gravidade durante a subida em px/s².
GRAVITY_DOWN2600Gravidade durante a queda em px/s².
APEX_THRESHOLD40Limite de velocidade para o tratamento do ápice com o salto pressionado.
APEX_GRAVITY_FACTOR0.5Multiplicador da gravidade perto do ápice enquanto o salto é mantido.
JUMP_CUT_FACTOR0.45Multiplicador da velocidade de subida ao soltar o salto.
MAX_FALL560Velocidade máxima de queda em px/s.
COYOTE_TICKS6Contador armazenado de coyote time.
BUFFER_TICKS7Contador armazenado do buffer de salto.
DASH_SPEED320Velocidade horizontal fixa do dash em px/s.
DASH_TICKS9Duração de um dash.
DASH_COOLDOWN_TICKS12Cooldown depois que um dash termina.
SP_REGEN_TICKS_RUN48Ticks no chão em corrida necessários para restaurar um SP.
SP_REGEN_TICKS_IDLE96Ticks no chão parado necessários para restaurar um SP.
RESPAWN_TICKS18Ticks no estado morto antes do reaparecimento.
CAM_LOOKAHEAD24Deslocamento da câmera na direção para a qual o jogador olha, em px.
CAM_LERP_X8Coeficiente de suavização horizontal.
CAM_LERP_Y6Coeficiente de suavização vertical.
CAM_WINDOW_UP72Janela de escape da câmera para cima, em px.
CAM_WINDOW_DOWN40Janela de escape da câmera para baixo, em px.

Um salto mantido até o fim atinge exatamente 56.00 px, ou 3.5 tiles, no tick 14, a 0.233 s. O tempo total no ar é de 0.467 s. Soltar depois de um tick produz um salto curto de 16.56 px, cerca de um tile.

Salto mantido até o tickÁpice medido
116.56 px
222.42 px
327.72 px
432.62 px
537.02 px
640.97 px

O ápice sobe de forma monotônica a cada tick adicional em que o salto é mantido nesse intervalo. Um dash percorre exatamente 48 px, ou 3 tiles, ao longo de exatamente 9 ticks, enquanto a velocidade vertical permanece travada em zero.

Cada temporizador de jogabilidade é um contador inteiro de ticks, e não um valor de ponto flutuante em segundos. Isso elimina o desvio de acumulação das regras e transforma cada limite em uma asserção exata: um tick antes, o evento está disponível; um tick depois, não está. A página ainda pode formatar ticks como tempo, mas o motor nunca precisa perguntar se um temporizador de ponto flutuante quase esgotado é efetivamente zero.

Um diagrama compara um arco de salto derivado analiticamente com o arco medido a 60 Hz, marcando o ápice exato de 56 pixels no tick 14 e o salto mais curto produzido ao soltar depois de um tick.
A curva útil é a produzida pela ordem real de atualização do motor, incluindo a soltura do botão, a gravidade no ápice e a colisão.

Onde a aritmética mentiu

A discrepância entre 4 e 6 pertencia às constantes planejadas originalmente: JUMP_VELOCITY -350, GRAVITY_UP 1094, GRAVITY_DOWN 1750 e RUN_SPEED 140. A tabela anterior à construção dizia que apenas o salto atravessava no máximo 4 tiles, mas a simulação dessas mesmas constantes mediu 6. O collider de 12 px mantinha o jogador no chão até que sua borda esquerda deixasse a beirada, deslocando o ponto real de decolagem 12 px além daquele presumido pelo modelo de ponto material. O coyote time então acrescentava até mais 5 ticks antes da decolagem, o equivalente a cerca de 11.7 px a 140 px/s. A tabela também era internamente inconsistente: sua própria aritmética já implicava mais de 4 tiles de alcance, mas o resultado era rotulado como “no máximo 4 tiles”, embora 4 tiles tenham apenas 64 px.

Um erro separado dizia respeito à altura do ápice, não ao alcance horizontal. Com os valores lançados JUMP_VELOCITY -500 e GRAVITY_UP 2080, o ápice em forma fechada é 500² / (2 × 2080) = 60.10 px. A integração de Euler semi-implícita — que atualiza primeiro a velocidade e depois a posição, uma vez por tick fixo — mede exatamente 56.00 px no tick 14, uma perda de 4.10 px diante da previsão v × dt / 2 = 500 / 120 = 4.17 px. Esse ápice mais baixo reduz o tempo no ar e, portanto, não pode explicar uma envoltória horizontal maior que a prevista.

Nenhum dos dois erros é obscuro depois de colocado no papel. O problema foi usar uma derivação contínua e um esboço internamente inconsistente para certificar um sistema discreto cuja temporização da entrada, collider e ordem de colisão não estavam na equação. Parei de tratar qualquer uma das derivações como valor final e passei a simular a ordem de ticks implementada.

Para uma simulação discreta, a solução em forma fechada é uma hipótese, não uma especificação. Ela continua útil para escolher uma região inicial. O valor de aceitação precisa vir do sistema que realmente será executado.

O vão que não ensinava nada

O centro da primeira sala tinha um vão plano de 5 tiles, ou 80 px, destinado a dizer: “você precisa do dash aqui”. O limite reproduzível do motor diz o contrário: um vão plano de 5 tiles pode ser atravessado apenas com um salto, enquanto um vão plano de 6 tiles não pode. Com RUN_SPEED 130, o jogador percorre 130 / 60 = 2.167 px na horizontal por tick; assim, o bloqueio antigo era decidido dentro de um único tick de deslocamento. O tick do coyote time em que o salto disparava e a fase de subpixel da aproximação — o resto fracionário de pixel carregado entre quadros — mudavam a resposta.

O detalhe determinante é a caixa de 12 px de largura do jogador. O jogador continua sendo considerado em contato com o chão enquanto a borda esquerda da caixa ainda tem apoio; portanto, o ponto real de decolagem é o limite direito da plataforma. Meu primeiro cálculo mediu a partir do ponto errado. O erro era pequeno o bastante para se esconder em um esboço plausível e grande o suficiente para invalidar a sala.

Uma lição decidida por um único quadro de deslocamento horizontal é um acidente, não design. O vão passou a ter 6 tiles, ou 96 px, para que apenas o salto falhe de forma confiável e o dash leve o jogador ao outro lado. Agora o número diz o mesmo que a sala.

O primeiro design de recuperação fracassou pela mesma razão, em escala maior. Acrescentei duas plataformas atravessáveis em um sentido para tornar tolerável um dash perdido. Juntas, elas formavam uma escada em torno do bloqueio e permitiam que o jogador subisse até o destino sem executar a lição. A generosidade da fase apagava sua própria afirmação.

Essas plataformas viraram uma única plataforma de recuperação abaixo do vão. O jogador pode voltar ao lado da decolagem, mas o destino fica 4 tiles acima dessa plataforma, uma altura maior que o ápice de 56 px; assim, a recuperação não pode se transformar em atalho.

Um diagrama medido contrasta o bloqueio de ensino acidental de cinco tiles com o vão corrigido de seis tiles e destaca o limite de um tick que tornava o bloqueio antigo instável.
Um bloqueio de ensino precisa de margem suficiente para sobreviver às regras exatas de colisão e temporização, não apenas para parecer convincente em um esboço com tiles.

A envoltória de construção descartou o bloqueio por dash no chão

Medir o motor em diferentes alturas de aterrissagem produziu a envoltória para a construção de fases: a faixa de geometria que o salto medido consegue atravessar. Cada valor abaixo é o maior vão aberto que pode ser atravessado, em tiles.

Aterrissagem em relação à decolagemApenas saltoSalto + 1 dash no arSalto + 2 dashes no ar
Mesma altura5811
1 tile acima488
2 tiles acima477
1 tile abaixo5912
2 tiles abaixo5912

A tabela expõe um limite estrutural, não um bug no motor. Um dash no chão percorre 3 tiles, enquanto apenas um salto atravessa um vão plano de 5 tiles. Qualquer abertura estreita o bastante para um dash no chão já está dentro do alcance de apenas um salto. Um dash no chão pode ser expressivo — um ritmo mais rápido ou uma trajetória mais limpa —, mas nunca pode ser uma trava geométrica estrita.

O dash só pode ser exigido no ar. Mesmo assim, um segundo dash não melhora o alcance até uma aterrissagem mais alta: o cooldown de 12 ticks consome o tempo restante no ar. Isso muda de forma inequívoca a construção da fase. Não posso rotular um vão como “dash no chão obrigatório” só porque quero esse ritmo, nem posso prever um segundo dash apenas com base no SP. Cada bloqueio precisa ser selecionado na linha medida para sua altura de aterrissagem e verificado contra o cooldown implementado.

Um diagrama de envoltória de habilidades mapeia as larguras máximas dos vãos em aterrissagens de mesma altura, mais altas e mais baixas, considerando apenas o salto, um dash no ar e dois dashes no ar.
A ferramenta útil para construir a fase não é uma única distância de destaque, mas uma tabela que inclui a altura da aterrissagem, a quantidade de dashes e o tempo consumido entre as habilidades.

Testes que obrigam uma fase a cumprir sua promessa

As verificações mais interessantes não são testes isolados de aceleração ou gravidade. Elas testam se district-01, uma fase de 200×30 tiles dividida em quatro salas, ainda ensina o que afirma ensinar.

level.spec.ts fixa a lista completa de superfícies onde é possível ficar em pé e verifica se o ponto de surgimento, cada checkpoint e o objetivo estão sobre o chão. Ele compara cada salto da rota principal com seu orçamento de dashes, mantém ambos os vãos que exigem dash impossíveis de atravessar usando apenas o salto, prova que a plataforma de recuperação não permite contornar seu bloqueio e prova que nenhum dos dois fossos rebaixados pode deixar o jogador preso sem saída.

level.spec.ts agora inclui um teste de replay no repositório que fixa a conclusão no tick 1304, ou 21.7 s, com 0 mortes e todos os cinco checkpoints ativados, e então verifica um replay idêntico a partir de um estado novo. As suítes do motor, da fase e da rota presentes no repositório passam, e bun run check não encontra problemas.

O navegador forneceu a verificação final da rota renderizada. Um salto máximo, sem dash, terminou no fosso de ensino contra a parede oposta; o jogador deslizou até a parede direita do fosso, portanto essa posição de repouso media a parede, não o alcance do salto. Um dash no ar aterrissou na coluna 26.26. A repetição completa de 1304 ticks durante a execução chegou à sobreposição “Distrito concluído”, cuja linha de estatísticas mostrou 0:21 · 0 quedas.

O que uma fase afirma ensinar é um contrato testável. “Este vão ensina o dash” pode ser decomposto no que não consegue atravessá-lo, no que consegue, no que acontece depois da falha e em se a rota de recuperação preserva a lição. Quando essas afirmações se tornam executáveis, ajustes posteriores não podem transformar silenciosamente a sala de volta em uma mentira.

A medição transformou as promessas da fase em fatos

A medição é um instrumento de design, não uma etapa de QA no final. A lista ABC me disse quais perguntas fazer e onde registrar as respostas. Só a simulação revelou se essas respostas eram verdadeiras sob as regras reais de atualização, entrada, caixa de colisão e fase.

A primeira fatia jogável ainda é apenas uma fase, uma lição de mecânica, entrada por teclado mais controles de toque na tela para movimento, salto, dash e pausa em dispositivos com ponteiro impreciso, e arte graybox programática. O limite do que foi adiado continua amplo: as outras habilidades de movimento, combate e itens, inimigos, estruturas de tentativas e exploração, arte autoral, som, entrada por gamepad, persistência, ferramentas de criação e desafio ajustável não estão escondidos atrás da sobreposição de conclusão. Eles não foram construídos.

Uma conclusão determinística ainda não prova que um jogador sem orientação entenderá a lição, encontrará a rota de recuperação ou gostará do movimento. Essa continua sendo uma pergunta em aberto, e o próximo passo definido são testes observados com jogadores enquanto as medições permanecem para mostrar o que mudou quando eles revelarem outra premissa falsa.

O que essa fatia provou é mais restrito: sob as regras reais do motor, a geometria não contradiz mais a lição que deveria ensinar. Em seguida, transformo esse motor determinístico em um ambiente de treino e pergunto: Um jogo determinístico consegue ensinar uma rede a correr pelos telhados?

Fontes

Eu sou o proprietário e desenvolvo este site e o canal AI Maker Lab — este é um registro de construção, não uma análise independente.

Continue lendo