Movimento em pixel art: caminhadas, câmeras e portas no iPhone
Pokémon Red, Crystal e Emerald, um jogo de cada uma das três gerações de Game Boy e Game Boy Advance, percorrem todos uma célula de 16 pixels em 16 quadros a cerca de 59,73 hertz, 3,73 células por segundo, e nada no passo fica por conta de um relógio: Emerald move o jogador exatamente um pixel por quadro, mostra um desenho de passada e um desenho parado por passo, e rola a câmera no mesmo quadro, pelos mesmos pixels.123 Red e Crystal chegam aos mesmos dezesseis quadros movendo dois pixels a cada dois quadros.4 A porta de Emerald abre em quatro desenhos mantidos por cinco quadros cada, o jogador é levado para dentro por um passo forçado, a porta fecha e a tela faz um fade em nove níveis escalonados: pelo menos 79 quadros, 1,32 segundo, antes que o próximo mapa possa começar a carregar.15 O mundo do Kiradex caminha pelo tempo decorrido e suaviza a câmera, e um modelo desse código (um modelo, não uma captura de um celular) diz que o sprite dá um salto de dois pixels uma vez por tile a 60 hertz, que a câmera fica cerca de um pixel mais para trás a cada tile de uma caminhada a 60 hertz (9 pixels no quinto, e 13 numa corrida) e para de 4 a 9 pixels antes do jogador, e que os desenhos da caminhada seguem um relógio próprio, um ciclo a cada 3,00 tiles onde o de Emerald cobre dois.6 A Apple dá aos jogos prioridade especial a 30 e 60 hertz e o RealityKit normalmente renderiza a 60, então o briefing é um tick fixo de 60 hertz com passos de pixels inteiros, desenhos de caminhada escolhidos pela distância percorrida (proposta minha, não o que os portáteis faziam), uma câmera travada, a cerimônia de porta de Emerald com a nossa própria arte, três eventos hápticos opcionais, e 60 hertz em vez de 120.78 Este é o cânone medido a partir das descompilações, o lado do iPhone segundo as páginas da Apple, e o briefing com os testes que ele precisa passar.
Resumo
- Um passo é um contrato, não uma velocidade. Toda tabela de passos de Emerald soma 16 pixels: a caminhada é 1 pixel por quadro durante 16 quadros (268 milissegundos por célula), correr e surfar são 2 durante 8, a velocidade máxima da Mach Bike é 4 durante 4, e só o 2-3-3-2-3-3 da Acro Bike é irregular. Red e Crystal percorrem os mesmos 16 quadros em saltos de 2 pixels, com 30 atualizações por segundo.124
- As pernas e o passo são sincronizados de propósito. Emerald conta os desenhos e o passo em quadros, separadamente, e faz as contagens baterem: a caminhada é passada, parado, passada, parado, a 8 quadros por desenho ao longo de duas células de 16 quadros; os modos de andar mais rápidos cortam pela metade a duração de cada desenho em vez de acrescentar desenhos, então há uma pisada a cada 16 pixels em qualquer velocidade. Virar a partir do repouso leva 8 quadros (134 milissegundos), virar andando não leva nenhum, e andar contra uma parede toca uma batida de 32 quadros.19102
- A câmera é o jogador. A câmera de Emerald copia a posição do jogador e rola pelos mesmos pixels no mesmo quadro, sem atraso nem antecipação, e nunca para na borda de um mapa, porque o lado de fora é desenhado com tiles de borda. A Game Freak escreveu uma câmera que se adianta à bicicleta e lançou o jogo com ela desligada.231112
- Uma porta é uma cerimônia. Quatro desenhos de cinco quadros (335 milissegundos), um passo forçado de 16 quadros, a porta fechando em 20, e um fade cuja última mistura cai no 17º quadro e que termina no 22º: pelo menos 79 quadros, 1,32 segundo, sem nenhum comando do jogador antes que o mapa possa carregar. Isso confirma o post desta série sobre estruturas, cerca de 84 milissegundos por desenho, e corrige as notas de pesquisa por trás dele, que diziam “4 ticks each (16 frames, about 0.27 s at 59.7 Hz)” (4 ticks cada, 16 quadros, cerca de 0,27 s a 59,7 Hz). Crystal faz fade para branco em 8 quadros; Red faz fade para preto em 32.1513144
- No iPhone, ritmo uniforme a 60.
preferredFrameRateRange(iOS 15.0) é uma sugestão; iPhones com ProMotion rodam de 10 a 120 hertz em doze degraus; passar de 60 exigeCADisableMinimumFrameDurationOnPhone; jogos recebem “special priority to 30Hz and 60Hz” (prioridade especial a 30 Hz e 60 Hz); o RealityKit “typically limits the refresh rate” (normalmente limita a taxa de atualização) a 60. Uma caminhada de pixels inteiros não ganha nada a 120: cada pixel é simplesmente mantido por duas atualizações de tela.1571686 - O Kiradex hoje, modelado e não medido. O código caminha a 4 tiles por segundo pelo tempo decorrido, então um modelo a 60 hertz constantes leva 15 quadros por tile e move 2 pixels em um deles; ele suaviza e arredonda a câmera, que fica cada vez mais para trás conforme a caminhada avança (5 pixels depois do primeiro tile, 9 depois do quinto, 13 numa corrida) e para a 4 pixels do jogador a 60 hertz e a 9 a 120; sua caminhada de seis desenhos a 8 por segundo cobre 3,00 tiles por ciclo andando e 4,50 correndo. Nenhum celular foi medido.176
- O que isso dá ao Kiradex: um briefing, não uma build lançada. Um tick fixo de 60 hertz com passos de 1 pixel e uma regra explícita para o atraso acumulado, desenhos de caminhada escolhidos pela distância, uma câmera travada em pixels inteiros, a sequência completa de porta com um fade escalonado, um passo recusado que aparece na tela, três eventos hápticos opcionais, nenhum pedido de 120 hertz, e para cada item um teste que um log de movimento, uma captura ou um script consegue verificar.
1. A caminhada de Emerald: dezesseis pixels em dezesseis quadros
Os dois primeiros posts desta série mediram como são um mundo de pixels e as pessoas que vivem nele; o terceiro mediu seus prédios. Este mede o tempo: quantos pixels por quadro, quantos quadros por passo, qual desenho aparece em qual quadro, quanto duram uma virada, uma porta e um fade, e o que a câmera faz enquanto tudo isso acontece. Os jogos são lidos da mesma forma que nos posts anteriores, a partir das descompilações do pret dos jogos lançados para Game Boy e Game Boy Advance, nos mesmos commits que esses posts usaram: pokered d2704a6, pokecrystal 5beda23, pokeemerald 731ad5b.18 Cinco scripts pequenos fizeram as contas; cada um aparece nas notas com a saída salva.
Primeiro um número, porque todo o resto está em quadros. O Game Boy Advance desenha um quadro em 280.896 ciclos de um relógio de 2^24 hertz, o que dá 59,7275 quadros por segundo, 16,743 milissegundos por quadro; o GBATEK arredonda para “ca. 59.737 Hz”.19 O relógio de 4.194.304 hertz do Game Boy original sobre 70.224 pontos por quadro dá os mesmos 59,7275; o Pan Docs diz “@ 59.7 fps”.19 Os milissegundos deste post usam 59,7275.19
Toda velocidade é uma tabela que soma dezesseis
Emerald não move um personagem multiplicando velocidade por tempo. Cada velocidade do mundo aberto é uma tabela de deslocamentos em pixels por quadro, e o código diz o que todas as tabelas têm em comum: “Over the course of the step animation, these sum to 16 pixels (one full metatile).” (ao longo da animação do passo, elas somam 16 pixels, um metatile inteiro).2 sStep1Funcs são dezesseis deslocamentos de um pixel, sStep2Funcs oito de dois pixels, sStep4Funcs quatro de quatro, sStep8Funcs dois de oito, e sStep3Funcs é a diferente, Step2, Step3, Step3, Step2, Step3, Step3.2 Contado por script no código de movimento, isso dá:
| Constante de velocidade | Pixels por quadro | Quadros por célula | Células por segundo | Milissegundos por célula | Usada para |
|---|---|---|---|---|---|
MOVE_SPEED_NORMAL |
1 em todo quadro | 16 | 3,73 | 267,9 | a caminhada; as caminhadas dos NPCs |
MOVE_SPEED_FAST_1 |
2 em todo quadro | 8 | 7,47 | 133,9 | correr, surfar, deslizar no gelo |
MOVE_SPEED_FAST_2 |
2, 3, 3, 2, 3, 3 | 6 | 9,95 | 100,5 | a Acro Bike, as correntes de água |
MOVE_SPEED_FASTER |
4 em todo quadro | 4 | 14,93 | 67,0 | a Mach Bike em velocidade máxima |
MOVE_SPEED_FASTEST |
8, 8 | 2 | 29,86 | 33,5 | ações de movimento de deslize |
Fonte de cada linha: measure_gen3_motion.py sobre src/event_object_movement.c.1
A caminhada é o número para guardar: um pixel em todo quadro, dezesseis quadros por célula, 3,73 células por segundo, 268 milissegundos por célula, usada por PlayerWalkNormal e por todo NPC que anda.1 Correr com o botão B é PlayerRun, e surfar é PlayerWalkFast, que o código anota como “same speed as running” (a mesma velocidade de correr); deslizar no gelo chama a mesma função. As três são dois pixels por quadro, oito quadros por célula, 7,47 células por segundo.110 Existe ainda uma caminhada lenta, UpdateWalkSlowAnim, que avança um pixel nos valores pares do temporizador, de 31 a 32 quadros por célula, para cenas roteirizadas.1
A Acro Bike é a única velocidade com pixels irregulares: 2, 3, 3, 2, 3, 3 em seis quadros, 2,67 pixels por quadro em média, 9,95 células por segundo.1 Ela é alcançada por PlayerRideWaterCurrent a partir de AcroBikeTransition_Moving, a mesma função que as correntes de água usam.120 Na minha leitura, a irregularidade é o preço de uma velocidade que não divide dezesseis: três pixels por quadro passariam da célula, então a tabela alterna dois e três para cair exatamente em dezesseis. Todas as outras velocidades são divisores inteiros da célula.
A Mach Bike acelera por passo, não por quadro. sMachBikeSpeedCallbacks é PlayerWalkNormal, PlayerWalkFast, PlayerWalkFaster, e bikeFrameCounter sobe um por passo até o limite de 2, então o primeiro passo a partir do repouso leva 16 quadros, o segundo 8, e todos os seguintes 4: quatro pixels por quadro, 14,93 células por segundo.120 A bicicleta nunca fica entre duas velocidades. Cada passo é uma das tabelas, executada até o fim, e a velocidade só muda na fronteira de uma célula.
Todo modo de andar medido em Red, Crystal e Emerald é um número inteiro de quadros por célula de 16 pixels; os números do Kiradex são um modelo do código, não uma captura.14621
Uma pisada a cada dezesseis pixels
A animação de caminhada é passada, parado, passada, parado. sAnim_GoSouth é ANIMCMD_FRAME(3, 8), (0, 8), (4, 8), (0, 8): o desenho 3 por oito quadros, o desenho parado 0 por oito, o desenho 4 por oito, o desenho parado de novo por oito, 32 quadros no total, que são duas células de caminhada.91 A duração é exata: sprite.c carrega a duração do quadro menos um no contador de atraso e conta até zero, então um quadro de duração 8 fica na tela por oito quadros.91 Cada passo, portanto, mostra um desenho de passada e um desenho parado, e SetStepAnimHandleAlternation começa cada passo novo na outra metade do ciclo (animPos = {1, 3, 0, 2}), de modo que a perna esquerda e a direita se alternam passo a passo mesmo quando o jogador para e volta a andar.2 O primeiro post desta série descreveu o mesmo ciclo pelo lado da arte: “step, stand, step, stand at eight ticks each, which is the bob everyone remembers.” (passo, parado, passo, parado a oito ticks cada, que é o balanço de que todo mundo se lembra).22
Os modos de andar mais rápidos mantêm os desenhos e encurtam a duração. GoFast segura cada desenho por quatro quadros (um ciclo de 16 quadros), GoFaster por dois (8 quadros), GoFastest por um (4 quadros).1 A corrida tem desenhos próprios, mantidos de forma desigual: sAnim_RunSouth é (12, 5), (9, 3), (13, 5), (9, 3), um ciclo de 16 quadros.19
Ponha as duas tabelas lado a lado e o projeto aparece. O ciclo de 32 quadros da caminhada cobre duas células de 16 quadros; o ciclo de 16 quadros da corrida cobre duas células de 8; o ciclo GoFaster de 8 quadros da Mach Bike cobre duas células de 4.19 Em qualquer velocidade cai uma pisada a cada 16 pixels.1 São dois relógios, não um contador. O desenho avança quando animDelayCounter, carregado com a duração de cada quadro em sprite.c, se esgota; o passo avança quando NpcTakeStep indexa a tabela da velocidade com o sTimer do sprite, uma entrada por quadro; e SetStepAnimHandleAlternation escolhe a animação do modo de andar quando cada passo começa.92 O que os mantém juntos é que ambos são contados nos mesmos quadros e as durações foram escolhidas para bater, modo de andar por modo de andar. Na minha leitura, é isso que vale copiar: a cadência dos desenhos não consegue se descolar do movimento, porque os desenhos de cada passo duram exatamente tantos quadros quanto o próprio passo. Nada nessas tabelas nem no meu modelo mede onde um pé apoiado fica em relação ao chão, então não afirmo mais do que isso.
Virar, bater e quando o controle é lido
Um passo, uma vez começado, termina; o direcional é lido quando ele se completa, então uma direção mantida encadeia passos sem intervalo e uma mudança de direção durante a caminhada não custa nada.10 A partir do repouso é diferente. CheckMovementInputNotOnBike retorna TURN_DIRECTION só quando a nova direção é diferente daquela para onde o jogador está virado e ele ainda não está se movendo, e PlayerTurnInPlace toca a caminhada rápida no lugar, à qual InitMoveInPlace dá uma duração de 8 quadros: 134 milissegundos virando sem sair do lugar.101 Essa é a mecânica que permite ao jogador se virar para uma placa ou para uma pessoa sem dar um passo na direção dela. Andar contra uma parede toca em vez disso a caminhada lenta no lugar, 32 quadros, 536 milissegundos, com um som de batida: um passo recusado aparece na tela, não é engolido.110 As ações de caminhada no lugar normal e mais rápida duram 16 quadros (268 milissegundos) e 4 (67).1
Na minha leitura, esses quatro números são quase tudo o que “responsivo” significa num jogo em grade. O mundo nunca se move uma fração de célula, então a intenção do jogador se expressa em passos inteiros, e a única latência é o que falta do passo em andamento: no máximo 268 milissegundos andando, 134 correndo.1 Virar a partir do repouso não é latência, e sim uma ação à parte, com seu próprio resultado visível.
2. A câmera, as portas, os fades e os tremores de Emerald
A câmera não tem atraso nem antecipação
A câmera de Emerald é um sprite invisível que segue o jogador. CameraObject_UpdateMove copia o x e o y do sprite seguido e guarda a diferença em relação ao quadro anterior em sCamera_MoveX e sCamera_MoveY; CameraUpdateCallback passa essa diferença para CameraUpdate, que rola o mapa exatamente esses pixels.212 O loop do mundo aberto executa RunTasks(); AnimateSprites(); CameraUpdate(); UpdateCameraPanning(); nessa ordem a cada quadro, e como AnimateSprites executa os callbacks dos sprites na ordem dos slots e o objeto da câmera é criado depois do jogador (InitPlayerAvatar, depois InitCameraUpdateCallback(gPlayerAvatar.spriteId)), a câmera lê a posição que o jogador alcançou naquele mesmo quadro.3 Segui essa ordem pelo código em vez de executá-lo, então é uma leitura, não uma medição; mas o resultado que ela implica é simples. O movimento do jogador e a rolagem caem no mesmo quadro, pelos mesmos pixels. Na tela, o jogador nunca se move, enquanto o mundo desliza por baixo dele.
Itay Keren, na sua palestra da GDC 2015 sobre câmeras em jogos de rolagem lateral, chama isso de position-locking (travamento de posição): a câmera fica sobre o jogador, “keeping the car in focus at all times and the camera motion completely predictable” (mantendo o carro em foco o tempo todo e o movimento da câmera completamente previsível).23 Emerald acrescenta uma coisa que a definição dele deixa em aberto, que é o que acontece na borda do mapa. Ela nunca para. As células fora do layout de um mapa são lidas por GetBorderBlockAt, que retorna os metatiles de borda 2 por 2 do layout, repetidos e marcados como MAPGRID_IMPASSABLE, então a visão mantém o jogador na mesma posição da tela e preenche o lado de fora com árvores ou água de borda.11 O edge-snapping (encaixe na borda) de Keren, a alternativa, “simply snaps the camera to the edge of the level, allowing the character to move away from its anchor point.” (simplesmente encaixa a câmera na borda da fase, deixando o personagem se afastar do ponto de ancoragem).23 Emerald desenhou o próprio caminho para não precisar disso.
A câmera que a Game Freak escreveu e desligou
field_camera.c contém uma câmera de antecipação pronta para a bicicleta. CameraPanningCB_PanAhead desloca o enquadramento vertical em 2 pixels por atualização, do valor de repouso 32 em direção a 72 ou a menos 8, conforme a direção do movimento, ou seja, 40 pixels para cada lado: uma câmera que se adianta ao jogador na direção para onde ele vai.12 Ela só roda se gUnusedBikeCameraAheadPanback for verdadeiro, a variável só recebe o valor FALSE (em bike.c), e o trecho traz o comentário “this code is never reached.” (este código nunca é alcançado).1220 No vocabulário de Keren, é um dual-forward-focus (foco duplo à frente) ou target-focus (foco no alvo), a câmera que segue a regra dele “When you walk left, you want to see more to the left.” (quando você anda para a esquerda, quer ver mais à esquerda).23 Qualquer que tenha sido o motivo para deixá-la desligada, o jogo lançado manteve a câmera travada mesmo aos 4 pixels por quadro da Mach Bike.1
Uma porta abre em vinte quadros, não em dezesseis
A tabela de portas em field_door.c dá a impressão de que cada desenho dura quatro quadros: sDoorOpenAnimFrames é {4, -1}, {4, 0}, {4, 0x100}, {4, 0x200}, fechada mais três desenhos abertos.24 Mas AnimateDoorFrame desenha quando o contador vale 0 e avança quando o contador se iguala ao tempo da entrada, então cada desenho é mantido por cinco atualizações: 84 milissegundos por desenho, 335 para os quatro.241 O post desta série sobre estruturas deu o mesmo número, cinco atualizações e cerca de 84 milissegundos.13 As notas de pesquisa a partir das quais aquele post foi escrito estavam erradas, “4 ticks each (16 frames, about 0.27 s at 59.7 Hz),” (4 ticks cada, 16 quadros, cerca de 0,27 s a 59,7 Hz), e também está errado um comentário no próprio código de porta do app, que diz que ela abre “at about Emerald’s four ticks a frame” (mais ou menos nos quatro ticks por quadro de Emerald) e depois mantém o primeiro desenho por 70 milissegundos; os dois são corrigidos aqui e no briefing.1417
A entrada em si, Task_DoDoorWarp, são cinco estados sem nada que se possa pular: congelar os outros objetos, tocar o som da porta e abrir a porta acima do jogador; forçar um passo MOVEMENT_ACTION_WALK_NORMAL_UP para dentro da soleira; quando o jogador parar, fechar a porta e esconder o jogador; quando a tarefa da porta terminar, fazer o fade da música e da tela; carregar o mapa.25 Sair executa a cerimônia ao contrário: Task_ExitDoor mostra a porta já aberta, espera o fade de entrada, força um passo WALK_NORMAL_DOWN, fecha a porta e só então devolve os controles.25
Somando as contagens acima e o rastreamento do fade logo abaixo, a porta abre em 20 quadros, o passo leva 16 e a porta fecha em 20; o fade que vem em seguida faz a última mistura no seu 17º quadro, então a tela está totalmente escura depois de pelo menos 73 quadros, 1,22 segundo. O mapa só pode começar a carregar depois que o fade fica inativo, no seu 22º quadro, e que Task_WarpAndLoadMap percebe isso e segue adiante: pelo menos 79 quadros, 1,32 segundo.525 As mudanças de estado entre as fases da porta e a espera pelo fim da música só podem acrescentar quadros, então os dois números são limites inferiores.5
A entrada de Emerald é um limite inferior a partir das contagens de quadros; a linha do Kiradex foi lida no código de porta do app, não cronometrada num celular.151721
Um fade são nove níveis
Um fade de warp em Emerald não é uma rampa suave. BeginNormalPaletteFade define um passo de 2 num coeficiente de mistura que vai de 0 a 16, UpdateNormalPaletteFade mistura as paletas de fundo numa chamada e as paletas de sprites na seguinte e depois avança o coeficiente, e IsSoftwarePaletteFadeFinishing acrescenta cinco chamadas no final.26 Em qual quadro cada chamada cai depende de quem a faz. A tarefa da porta inicia o fade de dentro de RunTasks, e BeginNormalPaletteFade executa ela mesma uma atualização, copia o resultado para a memória de paletas na hora e limpa a flag que, de outra forma, faria a próxima atualização esperar pelo blank vertical; mais adiante no mesmo quadro, OverworldBasic chama UpdatePaletteFade de novo.25263 Então o primeiro quadro recebe duas atualizações, ambas no nível 0, e cada quadro seguinte recebe uma. Um port em Python de palette.c com esse cronograma, contando o quadro da tarefa como 0, dá nove níveis, 0, 2, 4 e assim por diante até 16: as paletas de fundo chegam ao nível 2 no quadro 1 e ao nível 16 no quadro 15, as paletas de sprites um quadro atrás em cada vez, a última mistura no quadro 16 (285 milissegundos contando o quadro 0), e o fade inativo no quadro 21 (368 milissegundos).5 Uma mistura calculada num quadro chega à tela no blank vertical que fecha esse quadro. O port supõe que nenhum fade estava rodando antes e a atualização normal do mundo aberto; com chuva, neve, neblina, sombra ou seca ativas, o fade de saída funciona do mesmo jeito a partir das cores tingidas pelo clima (FadeScreen copia primeiro o buffer tingido e depois chama a mesma BeginNormalPaletteFade), mas o fade de entrada é executado pelo código do clima, e esse caminho não foi simulado.5
Nove níveis separados por dois dezesseis avos, fundo e sprites com um quadro de diferença: a rampa que o briefing adapta para um único véu.521
Os warps fazem fade para preto, com uma família de exceções, e a exceção tem direção. WarpFadeOutScreen pergunta a GetMapPairFadeToType sobre o par de tipos de mapa, WarpFadeInScreen pergunta a GetMapPairFadeFromType, e cada uma chama FadeScreen com branco quando a resposta é verdadeira e com preto caso contrário.25 As duas procuram o par em sTransitionTypes, cujas 16 linhas são todos os tipos de mapa que entram em MAP_TYPE_UNDERGROUND e saem dele; a primeira retorna a flag de entrada de uma linha e a segunda a flag de saída, que só são verdadeiras nas linhas que entram numa caverna e só nas linhas que saem de uma.27 Então, ao entrar numa caverna, a tela faz fade para branco e volta a partir do preto, e ao sair de uma faz fade para preto e volta a partir do branco. Cada linha também nomeia uma rotina própria de transição de caverna, que não rastreei.27 Um fade branco mais lento, FadeInFromWhite com atraso de 8, fica inativo depois de 86 ou 87 quadros, de 1,44 a 1,46 segundo; ele é iniciado pelo callback de carregamento do mapa, cujo primeiro quadro não rastreei, e o port dá os dois casos.525
O tremor é um deslocamento roteirizado
O tremor de tela em Emerald é um comando de script, não um efeito de física. ShakeCamera lê um deslocamento vertical, um deslocamento horizontal, um número de tremores e os quadros entre eles a partir de quatro variáveis de script, e inverte o deslocamento a cada tremor.28 Em todos os scripts do jogo há 24 chamadas em 9 arquivos, e um script contou todas:29
| Px verticais | Px horizontais | Tremores | Quadros de intervalo | Chamadas | Duração |
|---|---|---|---|---|---|
| 1 | 1 | 8 | 5 | 10 | 40 quadros, 670 ms |
| 1 | 1 | 8 | 3 | 4 | 24 quadros, 402 ms |
| 1 | 2 | 8 | 5 | 3 | 40 quadros, 670 ms |
| 2 | 2 | 8 | 5 | 2 | 40 quadros, 670 ms |
| 0 | 3 | 4 | 2 | 2 | 8 quadros, 134 ms |
| 1 | 3 | 20 | 5 | 1 | 100 quadros, 1.674 ms |
| 1 | 1 | 16 | 3 | 1 | 48 quadros, 804 ms |
| 1 | 1 | 32 | 2 | 1 | 64 quadros, 1.072 ms |
Fonte: measure_gen3_shake.py sobre data/**/*.inc.29
Dez das 24 são o mesmo tremor: um pixel para cada lado, oito inversões, cinco quadros de intervalo, 670 milissegundos.29 O maior é de 3 pixels na horizontal; o mais longo dura 100 quadros, 1,67 segundo.29 O tremor do elevador, que o post sobre estruturas abordou (um tremor a cada três quadros por uma contagem que cresce com os andares percorridos), é uma rotina separada e não entra nesta contagem.13 Na minha leitura, a lição é a contenção: um tremor em Emerald é um ou dois pixels, usado num evento que o roteiro decidiu que importa, nunca num passo nem numa porta.
3. Red e Crystal: o mesmo passo a 30 atualizações por segundo
Red move dois pixels a cada dois quadros
Pokémon Red não tem tabelas de passos. Seu loop do mundo aberto, OverworldLoop, chama DelayFrame e cai em OverworldLoopLessDelay, que a chama de novo, então o mundo atualiza uma vez a cada dois quadros.30 Um passo coloca wWalkCounter em 8, e a cada passagem AdvancePlayerSprite o decrementa e rola os registradores de fundo hSCX e hSCY pelo vetor do passo deslocado uma vez à esquerda: 2 pixels.30 Oito passagens de 2 pixels são 16 pixels em 16 quadros, os mesmos 268 milissegundos e 3,73 células por segundo de Emerald, em saltos de 2 pixels a 30 atualizações por segundo.430 A bicicleta é um segundo avanço por passagem: DoBikeSpeedup chama AdvancePlayerSprite de novo (exceto na Cycling Road enquanto cima, esquerda ou direita estiver pressionado), então um passo leva 8 quadros.430
Os desenhos de caminhada de Red mudam a cada quatro passagens, oito quadros, num ciclo de quatro imagens, parado, passo, parado e o passo espelhado, então ele também mostra um desenho de passada por passo.3122 UpdatePlayerSprite incrementa o contador interno da animação a cada passagem e avança o desenho quando ele chega a 4.3122
Red vira numa passagem do loop, dois quadros: uma nova direção a partir do repouso grava a orientação e volta ao loop sem dar passo.30 A virada de 180 graus tem no código uma orientação intermediária que, pelo próprio comentário do código, ninguém vê: “It is unlikely for it to ever be visible because DelayFrame is called at the start of OverworldLoop.” (é improvável que ela chegue a ser visível, porque DelayFrame é chamado no início de OverworldLoop).30 Li isso no código e não executei num emulador.
O warp é um som e um fade, sem nenhuma porta desenhada. PlayMapChangeSound toca SFX_GO_INSIDE quando o tile é uma porta (o tile $0b) e SFX_GO_OUTSIDE caso contrário, e então GBFadeOutToBlack grava quatro paletas, mantendo cada uma por oito quadros: 32 quadros, 536 milissegundos.432 Não rastreei o fade de volta de Red.
Crystal também atualiza a cada dois quadros
Crystal mantém a cadência de Red com um mecanismo diferente. MaxOverworldDelay é db 2, e o handler de VBlank faz a contagem regressiva de wOverworldDelay, então os objetos do mapa atualizam a cada dois quadros.33 StepVectors dá à caminhada oito atualizações de 2 pixels e à bicicleta quatro de 4: 16 quadros e 8 quadros por célula, igual a Red e Emerald.4 O passo lento são dezesseis atualizações de 1 pixel, 32 quadros, 1,87 célula por segundo.433
As portas de Crystal fazem fade para branco, e rápido. MapSetupScript_Door começa com FadeOutToWhite e MapSetupScript_Warp termina com FadeInFromWhite; cada um são quatro passos de paleta com dois quadros de intervalo, 8 quadros, 134 milissegundos para cada lado.434 A virada é uma função de passo em quatro partes, StepFunction_Turn, e as partes caem umas nas outras. Na primeira atualização, .init1 define um OBJECT_STEP_DURATION de 2 e cai em .step1, que o reduz a 1; na segunda, .step1 o reduz a 0 e cai por .init2, que grava a nova orientação e define 2 de novo, até .step2, que o reduz a 1; na terceira, .step2 chega a 0 e devolve o objeto a STEP_TYPE_FROM_MOVEMENT.33 Reproduzida instrução por instrução, a rotina leva três atualizações, seis quadros a dois quadros por atualização, com a nova orientação gravada na segunda. Isso é a rotina sozinha, lida no código; não rastreei o tempo entre o aperto do botão e o início da rotina.33
Três gerações, três fades
| Red | Crystal | Emerald | |
|---|---|---|---|
| Porta | nenhuma desenhada; o som é escolhido pelo tile | nenhuma desenhada | 4 desenhos de 5 quadros (335 ms), com som de porta de correr ou de dobradiça |
| Entrando na porta | o passo que cai sobre ela | o passo que cai sobre ela | um passo forçado para cima (16 quadros), depois a porta fecha |
| Fade de saída | para preto, 4 paletas mantidas por 8 quadros cada: 32 quadros (536 ms) | para branco, 4 passos com 2 quadros de intervalo: 8 quadros (134 ms) | para preto (para branco ao entrar numa caverna), 9 níveis, última mistura no quadro 16 contando o quadro da tarefa da porta como 0 (285 ms), inativo no quadro 21 |
| Fade de entrada | não rastreado | a partir do branco, 8 quadros | a partir do preto (a partir do branco ao sair de uma caverna), os mesmos 9 níveis |
| Saindo pela porta | um passo forçado para baixo | um passo forçado | a porta aparece aberta, um passo forçado para baixo, a porta fecha, depois o controle |
Fontes: as contagens das seções 1 a 3;14525 o passo forçado de Red ao sair por uma porta, PlayerStepOutFromDoor, vem do post sobre estruturas.13
As linhas que coincidem nos três são as que vale manter: um fade entre mapas, um passo forçado que leva o jogador através da soleira, e nenhum comando durante a cerimônia. As durações dos fades variam num fator de quatro, então, na minha leitura, a duração é uma escolha de design, não um cânone. O fade de nove níveis de Emerald é o que foi construído em torno de portas desenhadas, e o Kiradex desenha suas portas,13 então é ele que o briefing adota.
Red, Crystal e Emerald, um jogo de cada uma das três gerações no Game Boy e no Game Boy Advance, duas máquinas diferentes, chegaram ao mesmo contrato: um passo são 16 pixels, leva 16 quadros a cerca de 59,73 hertz e não pode ser interrompido depois de começar.14 Red chega lá em saltos de 2 pixels porque o loop espera dois quadros por passagem; Crystal, com o mesmo atraso de dois quadros e vetores de 2 pixels; Emerald, na taxa de quadros cheia, com deslocamentos de 1 pixel. Correr, surfar e a bicicleta são o mesmo contrato a 8 ou 4 quadros.14 Quando as pessoas dizem que esses jogos parecem andar “sobre trilhos”, acho que este é o trilho: o passo, o desenho e a câmera são todos contados nos mesmos quadros, com durações escolhidas para bater, então não conseguem se descolar.
4. As referências modernas: a câmera de Celeste, o vocabulário de Keren e as versões para celular
Quando uma câmera deve suavizar, e quando não deve
A câmera travada de Emerald é uma resposta dentro de um vocabulário que Itay Keren apresentou em “Scroll Back: The Theory and Practice of Cameras in Side-Scrollers”, uma versão modificada da sua palestra no Independent Games Summit da GDC 2015, publicada no Game Developer em 11 de maio de 2015.23 Os termos que uso neste post são dele. Position-locking (travamento de posição) prende a câmera ao jogador. Edge-snapping (encaixe na borda) a para na borda da fase. Uma camera-window (janela de câmera) só move a câmera quando o jogador empurra a borda da janela. Lerp-smoothing (suavização por interpolação) aproxima a câmera do alvo aos poucos, e ele a chama de “a standard tool in reducing jarring camera speeds, particularly jumps.” (uma ferramenta padrão para reduzir velocidades bruscas de câmera, principalmente em pulos). Target-focus e dual-forward-focus põem a câmera à frente do jogador na direção do movimento. Ele também cita platform-snapping, region-focus e cue attractors, que não servem para nada num personagem que anda em grade.23
Ele dá a razão pela qual o movimento da câmera importa: “conflicting sensory signals (Visual vs. Vestibular) may lead to discomfort and nausea, and though it’s worse in 3D (especially VR), it is still very much in effect in 2D games.” (sinais sensoriais conflitantes, visuais contra vestibulares, podem causar desconforto e náusea, e embora seja pior em 3D, sobretudo em VR, o efeito continua bem presente em jogos 2D). E dá o caso em que o esquema mais simples é o certo: o travamento de posição, para “a crafting adventure game like Terraria, with a small character relative to the screen with pretty small jumps, it works very well.” (num jogo de aventura e crafting como Terraria, com um personagem pequeno em relação à tela e pulos bem baixos, funciona muito bem).23 Uma cidade vista de cima com um colecionador de 30 pixels, a figura para a qual o post desta série sobre personagens mudou na build 34, é esse caso sem os pulos.35
Celeste é a referência moderna para a outra escolha. Os desenvolvedores publicaram a classe Player “as a learning resource and for general interest” (como material de aprendizado e por interesse geral), com a licença MIT cobrindo só esse código, e a câmera são poucas linhas dentro dela: level.Camera.Position = from + (target - from) * (1f - (float)Math.Pow(0.01f / multiplier, Engine.DeltaTime)), sob o comentário “Camera (lerp by distance using delta-time)” (câmera, interpolação por distância usando o delta de tempo).3637 Com multiplicador 1 e um alvo parado, isso fecha 99 por cento da distância a cada segundo, seja qual for a taxa de quadros; o alvo de Celeste não fica parado, já que é recalculado a cada quadro a partir da posição e do estado do jogador, então esse número descreve a suavização, não onde a câmera vai parar.36 O alvo é o jogador centralizado na visão do jogo (X - Celeste.GameWidth / 2), limitado aos contornos da sala, com deslocamentos para alguns estados: 48 pixels à frente na direção do dash StRedDash, 64 para cima no lançamento do cume.36 O primeiro post desta série observou que Celeste renderiza seu mundo a 320 por 180 e multiplica por seis.22
Na minha leitura, as duas coisas não estão em conflito. Celeste suaviza porque os pulos de um jogo de plataforma arrastariam a visão para cima e para baixo a cada salto; a própria definição de Keren para a suavização por interpolação fala de pulos. Um personagem em grade se move a velocidade constante em linha reta, então a trava não produz nenhum tranco a suavizar, e uma câmera suavizada só acrescenta um rastro atrás de um movimento que já era uniforme. A seção 7 mostra quanto esse rastro mede no modelo do Kiradex.
O que os outros jogos dizem sobre velocidade
Stardew Valley informa a velocidade do jogador como um atributo sem unidade: “2 when walking,” “5 when running,” “6.6 when riding a Horse (7 if the horse was fed a carrot that day)” (2 andando, 5 correndo, 6,6 a cavalo, 7 se o cavalo comeu uma cenoura naquele dia), nunca abaixo de 1.38 A wiki não diz quantos pixels por tick vale uma unidade, então nenhum número de tiles por segundo é dado aqui para Stardew.
Também procurei uma fonte primária sobre as câmeras de Sea of Stars, Eastward e CrossCode, e não encontrei nenhuma: entrevistas sobre deslocamento pelo mundo e iluminação, nada sobre a câmera, e a opção “Pixel Perfect” de Sea of Stars descrita apenas por guias e fóruns. Também não apareceu nenhuma palestra ou artigo de Maddy Thorson sobre câmera, e por isso Celeste é citado a partir do código.39
No celular: tocar para andar, com uma saída de emergência
A versão mobile de Stardew Valley traz nove esquemas de controle, e o padrão é “Tap-to-move & Auto-Attack” (tocar para mover e ataque automático): “Tap anywhere on screen and the farmer will walk to where you tapped.” (toque em qualquer lugar da tela e o fazendeiro vai andar até onde você tocou).40 Manter o dedo pressionado “will cause the character to follow the touch” (faz o personagem seguir o toque), e a wiki avisa que o modo de seguir “is very literal, moving directly towards the finger without routing around blocking objects.” (é muito literal: vai direto em direção ao dedo, sem contornar os objetos que bloqueiam o caminho). O esquema de joystick invisível ocupa “the left half of the screen” (a metade esquerda da tela), com o centro onde você tocar. E a wiki é honesta sobre o limite do padrão: algumas tarefas que exigem posicionamento cuidadoso “can not be completed using the default controls; temporarily switching to a control style with a movement joystick is necessary in such cases.” (não podem ser concluídas com os controles padrão; nesses casos é preciso trocar temporariamente para um estilo de controle com joystick de movimento).40 Os esquemas chegaram numa atualização que o TouchArcade noticiou em 1º de novembro de 2018, com uma opção que volta para “the default tap-to-move and auto-attack controls.” (os controles padrão de tocar para mover e ataque automático).41
O Pixel Remaster do primeiro Final Fantasy, da Square Enix, no iOS ganhou um padrão de andar ou correr na sua versão 1.2.0, datada de 11 de março de 2025 no histórico do app na App Store (a série sai como apps separados, e só consultei este): “In tap based movement mode the character controlled will always run as the default speed when moving.” (no modo de movimento por toque, o personagem controlado sempre vai correr como velocidade padrão ao se mover).42 O suporte a controles tinha chegado às versões mobile numa atualização que o TouchArcade cobriu em 30 de janeiro de 2024.43 Não encontrei o esquema exato de movimento por toque documentado em lugar nenhum além dessas notas, nem uma descrição do movimento de Terraria no mobile na página da wiki que consultei.39
As Human Interface Guidelines da Apple dizem o mesmo do lado da plataforma. Para jogos de toque: “consider letting players tap objects to select them instead of adding a virtual selection button” (considere deixar os jogadores tocarem nos objetos para selecioná-los em vez de acrescentar um botão virtual de seleção); “For movement control, opt to show a virtual thumbstick wherever the player lands their thumb instead of a static thumbstick position” (para o controle de movimento, prefira mostrar um analógico virtual onde o jogador encostar o polegar, em vez de numa posição fixa); “Make sure frequently used controls are a minimum size of 44x44 pt” (garanta que os controles usados com frequência tenham no mínimo 44x44 pt); “Always include visible and tactile press states” (inclua sempre estados de toque visíveis e táteis); e para andar e correr, “consider combining the actions into a single control.” (considere combinar as ações num único controle). O registro de alterações da página data essas práticas de controle por toque de 9 de junho de 2025.44 Na WWDC25, a sessão da Apple sobre controles por toque colocou a premissa sem rodeios: “the vast majority of players won’t have a controller available.” (a grande maioria dos jogadores não vai ter um controle à mão).45
Nenhuma dessas fontes diz que tocar para andar é a regra para jogos de celular, e eu não afirmo isso. O que tiro delas, como recomendação minha para o Kiradex e não como descoberta, é o esquema que o Kiradex já segue pela metade: tocar para andar como padrão, como a versão mobile de Stardew traz; um analógico que aparece onde o polegar encosta, como as HIG recomendam, para precisão; e nenhum direcional fixo na tela.4044
5. O ofício, em regras com números
Estas são as regras com base nas quais o briefing da seção 7 foi escrito, cada uma ligada a uma medição acima. “Tick” significa um passo de simulação de 1/60 de segundo, que é como um quadro de 59,73 hertz vira algo que um iPhone consegue sustentar; a 16 ticks por célula, a caminhada fica em 3,75 células por segundo em vez de 3,73.119
| Elemento | Regra | Fonte |
|---|---|---|
| Caminhada | 1 pixel por tick em tiles de 16 pixels: 16 ticks por tile, 3,75 tiles por segundo | Red, Crystal e Emerald andam todos 16 pixels em 16 quadros, 3,73 células por segundo |
| Corrida | 2 pixels por tick, 8 ticks por tile; nenhuma velocidade que não divida 16 | a corrida e o surf de Emerald são 2, a bicicleta 4; só o 2-3-3 da Acro Bike é irregular |
| Ciclo de caminhada | uma pisada a cada 16 pixels em qualquer velocidade; minha proposta para o Kiradex é escolher o desenho pela distância percorrida, e para seis desenhos em 32 pixels mostrar o desenho floor(distance × 6 / 32) mod 6 |
Emerald cronometra desenhos e passos em quadros, separadamente, com durações que batem: sua caminhada é um ciclo de 32 quadros sobre duas células de 16 quadros, sua corrida um ciclo de 16 quadros sobre duas células de 8 quadros |
| Passo | uma vez começado, termina; o controle é lido no tile | Emerald e Red leem o direcional quando o passo se completa |
| Virada | 8 ticks, cerca de 133 ms (os 8 quadros de Emerald são 134), a partir do repouso; nenhuma andando | WalkInPlaceFast de Emerald é 8; a rotina de virada de Crystal, 6 quadros; a de Red, 2 |
| Passo recusado | aparece, não é ignorado: uma caminhada no lugar de 32 ticks com uma batida | WalkInPlaceSlow de Emerald é 32 |
| Câmera | travada no jogador: sem atraso, sem antecipação, movida no mesmo tick pelos mesmos pixels | o objeto de câmera de Emerald; a única antecipação que a Game Freak escreveu está desligada |
| Borda do mapa | desenhar o lado de fora e manter a trava, ou limitar a câmera (encaixe na borda); nunca suavizar | os tiles de borda de Emerald; o encaixe na borda de Keren; os limites de sala de Celeste |
| Porta | 4 desenhos mantidos por 5 ticks cada: 83 ms por desenho, 333 ms para abrir (os 84 e 335 de Emerald) | field_door.c de Emerald |
| Fade | 9 níveis escalonados, um a cada 2 ticks, o último no tick 16, 18 ticks (0,30 s) no total, na saída e na entrada; preto por padrão. Uma adaptação, não uma cópia | o fade normal de Emerald alcança cada nível nos mesmos quadros pares nas paletas de sprites, um quadro depois das paletas de fundo, faz a última mistura no quadro 16 e fica inativo no quadro 21; ao entrar numa caverna faz fade para branco, e ao sair volta a partir do branco; os 8 quadros de Crystal são o extremo rápido, os 32 de Red o lento |
| Cerimônia de entrada | 74 ticks, cerca de 1,23 s, sem comando do jogador: abrir 20, passo para dentro 16, fechar 20, fade 18 | a entrada pela porta em Emerald: escuro depois de pelo menos 73 quadros, o carregamento do mapa não antes do quadro 79 (1,32 s) |
| Tremor | 1 pixel, 8 inversões, 5 ticks de intervalo (40 ticks, 0,67 s), só para eventos roteirizados | o mais comum dos 24 tremores de Emerald |
| Taxa de quadros | simular a 60, faça a tela o que fizer; pedir 60, não 120 | a prioridade da Apple para jogos a 30 e 60; o RealityKit normalmente renderiza a 60 |
| Háptica | confirmar eventos, não passos; torná-la opcional | as HIG da Apple sobre reprodução de háptica |
| Controle | minha recomendação: tocar para andar como padrão; um analógico flutuante como opção de precisão; nenhum direcional fixo | o padrão do Stardew mobile, o analógico flutuante das HIG; nenhum dos dois enuncia isso como regra |
Fontes da tabela: as contagens de caminhada, animação e porta de Emerald;1 seus temporizadores separados de desenho e de passo;92 sua câmera;123 seu fade e seus tremores;529 Red e Crystal;433 Keren e Celeste;2336 as orientações da Apple sobre ritmo de quadros, RealityKit e háptica;7846 as referências de controle.404244
Duas dessas regras precisam de uma frase cada. A regra do ciclo de caminhada é a mais fácil de errar num motor moderno, porque um sistema de animação conta os próprios segundos enquanto uma caminhada conta a distância no mundo. Os portáteis mantinham as duas coisas juntas contando ambas nos mesmos quadros, com durações escolhidas para bater; num celular, onde o tempo de quadro varia, minha proposta é ler o desenho a partir da distância percorrida, o que dá o mesmo resultado sem um segundo relógio para manter sincronizado. E a regra da taxa de quadros não é falta de ambição. Um mundo que se move um pixel inteiro por tick não tem nada para mostrar entre os ticks, então uma tela mais rápida só consegue repetir a mesma imagem, e a seção 6 mostra que é exatamente isso que ela faz.
6. O jeito Apple: ritmo de quadros, o relógio do RealityKit e a háptica
O primeiro post desta série apresentou o motor em que o mundo do Kiradex roda: uma cena do RealityKit usada como renderizador 2D, uma câmera ortográfica, o chão como uma única malha de quads texturizados, os personagens como quads, e a posição de cada sprite arredondada para unidades inteiras do mundo a cada quadro.22 Esta seção é a parte da documentação da Apple que decide como esse mundo se move no tempo. Cada página abaixo foi lida como a Apple a publica em 4 de outubro de 2026, e a disponibilidade informada é a que cada página declara.
Ritmo de quadros no ProMotion
CADisplayLink.preferredFrameRateRange (iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0) é um pedido, não uma configuração.15 O conselho da página é “Choose a frame rate range that your app can consistently maintain,” (escolha uma faixa de taxa de quadros que seu app consiga manter de forma consistente) e ela descreve o que o sistema faz com o pedido: “The system typically provides a consistent frame rate by choosing one that’s a factor of the display’s maximum refresh rate.” (o sistema normalmente oferece uma taxa de quadros consistente escolhendo uma que seja divisor da taxa máxima de atualização da tela). Por padrão, a faixa é igual ao máximo da tela.15 A faixa em si é um CAFrameRateRange (iOS 15.0) com uma taxa mínima, uma máxima e uma preferida.47
O artigo da Apple sobre ProMotion dá os números. As telas ProMotion alternam entre 24 e 120 hertz no iPad Pro e entre 10 e 120 nos iPhones compatíveis, e as taxas do iPhone são doze degraus: 120, 80, 60, 48, 40, 30, 24, 20, 16, 15, 12 e 10 hertz; as do iPad Pro são cinco delas.7 A lista de dispositivos agora cita o iPhone Air e “iPhone 17 and later” (iPhone 17 e posteriores) ao lado de “iPhone 13 Pro and later” (iPhone 13 Pro e posteriores).7 No iPhone, nada acima de 60 acontece a menos que o Info.plist do app defina CADisableMinimumFrameDurationOnPhone (iOS 15.0) como true: “If you don’t enable this support, Core Animation won’t access higher frame rates (above 60Hz).” (se você não ativar esse suporte, o Core Animation não vai acessar taxas mais altas, acima de 60 Hz).716 Duas frases do artigo importam mais para um jogo do que todo o resto. Uma: “In iOS 15 and later, the system provides games with special priority to 30Hz and 60Hz refresh rates to ensure optimal performance,” (no iOS 15 e posteriores, o sistema dá aos jogos prioridade especial às taxas de 30 Hz e 60 Hz para garantir o melhor desempenho) alcançada com CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60).7 A outra: “Prepare your app to operate at any refresh rate, not just those it requests.” (prepare seu app para funcionar em qualquer taxa de atualização, não só nas que ele pede).7 E para tudo o que é animado: “Always use targetTimestamp to drive any animation, physics, or other time-related content provided in your CADisplayLink callback” (use sempre targetTimestamp para conduzir qualquer animação, física ou outro conteúdo dependente de tempo no seu callback de CADisplayLink) (targetTimestamp é do iOS 10.0).748
A sessão da WWDC21 “Optimize for variable refresh rate displays” cobre tanto o ProMotion no iPad Pro quanto as telas Adaptive-Sync no Mac.49 Para as telas Adaptive-Sync do Mac, ela muda a orientação anterior da Apple: numa tela de taxa fixa, “we’ve previously recommended that you slow down your rendering to hit the next factor of the display’s fastest refresh rate” (antes recomendávamos desacelerar a renderização até o próximo divisor da taxa de atualização mais alta da tela); com Adaptive-Sync, “You should instead attempt to present frames at the highest rate your app can do so evenly.” (em vez disso, tente apresentar quadros na taxa mais alta em que seu app consiga fazer isso de forma uniforme).49 A palavra que levo dali para um celular é evenly, de forma uniforme.
O relógio do RealityKit
O mundo do Kiradex não tem um display link próprio. Ele avança no evento por quadro do RealityKit, SceneEvents.Update (iOS 13.0), “An event invoked once per frame interval,” (um evento invocado uma vez por intervalo de quadro) cujo deltaTime é “The elapsed time since the last update.” (o tempo decorrido desde a última atualização).5051 RealityView (iOS 18.0) documenta exatamente esse caminho para o trabalho por quadro, “you can use a System or directly subscribe to the engine’s SceneEvents.Update,” (você pode usar um System ou assinar diretamente o SceneEvents.Update do motor) e não oferece nenhuma configuração própria de taxa de quadros.52 O artigo da Apple sobre desempenho no RealityKit diz que taxa esperar: “RealityKit typically limits the refresh rate,” (o RealityKit normalmente limita a taxa de atualização), que ele define como a taxa em que o framework renderiza atualizações para a tela, “to 60 frames per second (fps).” (a 60 quadros por segundo).8 Se o RealityView num iPhone 18 Pro Max ou num iPhone Duo de fato renderiza a 60 ou a 120 não é algo que eu tenha medido, e a seção 7 faz dessa medição o primeiro teste do briefing.
Por que 120 hertz não acrescenta nada a uma caminhada de pixels inteiros
Esta é a aritmética, do mesmo script de modelo que a seção 7 usa para o código atual do app, aplicado aqui à proposta: um mundo que avança num tick fixo de 60 hertz, um pixel por tick, com a tela mostrando o que o último tick produziu. Numa tela de 60 hertz, cada pixel do mundo aparece por exatamente uma atualização.6 Numa tela de 120 hertz, ao longo de um segundo, 59 das 61 posições ficam por exatamente duas atualizações e duas por uma.6 Numa tela de 80 hertz, as durações alternam entre uma e duas atualizações, 42 posições mantidas uma vez e 19 duas vezes.6 Na minha leitura, o caso de 120 hertz é o caso de 60 hertz desenhado duas vezes, e o de 80 hertz é um ritmo irregular: um pixel que às vezes fica 12,5 milissegundos e às vezes 25.
Então a questão para um mundo em grade num iPhone não é 120 hertz, e sim um ritmo uniforme a 60: um tick fixo de simulação de 60 hertz, deslocamentos inteiros por tick, animações contadas em ticks e o renderizador mostrando o último tick. Isso também torna o movimento independente da taxa que o RealityKit escolher. A sessão da WWDC21 faz uma observação parecida sobre deltas de tempo. Quando um quadro lento faz o display link pular um callback, o delta a avançar é “not 8ms, but rather 16ms” (não 8 ms, e sim 16 ms); a sessão diz em seguida que um app que “uses time delta to advance the state of your custom drawing” (usa o delta de tempo para avançar o estado do seu desenho personalizado) vai “slow down your custom drawing by one frame” (atrasar seu desenho personalizado em um quadro) toda vez que um callback é pulado, o que leio como um app que avança os 8 milissegundos esperados em vez do tempo que realmente passou, e diz que o app “can instead keep track of a previous targetTimestamp so that you can advance the state correctly.” (pode, em vez disso, manter registro de um targetTimestamp anterior para poder avançar o estado corretamente).49
Háptica
O Core Haptics (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, visionOS 1.0) monta um CHHapticPattern, “An object representing a haptic waveform,” (um objeto que representa uma forma de onda háptica) a partir de dicionários, de arrays de objetos CHHapticEvent ou de um arquivo AHAP.53 Os eventos são de dois tipos hápticos, hapticTransient e hapticContinuous; os transitórios são “brief impulses that occur at a specific point in time.” (impulsos breves que ocorrem num instante específico).54 Cada um recebe os parâmetros hapticIntensity, hapticSharpness, attackTime, decayTime, releaseTime e sustained.55 Um CHHapticEngine os reproduz, e capabilitiesForHardware() diz se o aparelho consegue.56
O caminho mais simples é o UIImpactFeedbackGenerator (iOS 10.0), “A concrete feedback generator subclass that creates haptics to simulate physical impacts,” (uma subclasse concreta de gerador de feedback que cria háptica para simular impactos físicos) cujos estilos descrevem “The mass of the objects in the collision” (a massa dos objetos na colisão) (light, medium, heavy, soft, rigid); impactOccurred(intensity:) é do iOS 13.0, e a página lista init(style:view:) em “Initializing the feedback generator” e init(style:) entre os itens obsoletos.575859 prepare() só reduz a latência se tiver tempo para agir: “Calling prepare() and then immediately triggering feedback (without any time in between) does not improve latency,” (chamar prepare() e disparar o feedback logo em seguida, sem nenhum intervalo, não melhora a latência) e o motor volta ao repouso quando “A short period of time passes (typically seconds).” (passa um curto período, normalmente segundos).60 No SwiftUI, sensoryFeedback(_:trigger:) (iOS 17.0) “Plays the specified feedback when the provided trigger value changes,” (toca o feedback especificado quando o valor de trigger fornecido muda) incluindo .impact(weight:intensity:).61
A página das HIG sobre reprodução de háptica é a metade de design. “Avoid overusing haptics,”46 (evite exagerar na háptica) com o motivo: “Often, the best haptic experience is one that people may not be conscious of, but miss when it’s turned off.” (muitas vezes, a melhor experiência háptica é aquela de que as pessoas nem se dão conta, mas sentem falta quando ela é desligada). “Make haptics optional.” (torne a háptica opcional). Combinar “the intensity and sharpness of a haptic with the intensity and sharpness of the animation it accompanies.” (a intensidade e a nitidez de uma háptica com a intensidade e a nitidez da animação que ela acompanha).46 A nitidez pode transmitir uma experiência “that’s soft, rounded, or organic, or one that’s crisp, precise, or mechanical.” (suave, arredondada ou orgânica, ou então nítida, precisa ou mecânica).46 E a háptica personalizada pode fazer “a collision or a hit” (uma colisão ou uma pancada) parecer bem diferente “from subtle experiences like the approach of footsteps or a looming danger.” (de experiências sutis como passos se aproximando ou um perigo iminente).46 Uma caminhada de 3,75 passos por segundo durante minutos seguidos é o caso de que trata a primeira regra.1
Controle
As APIs de controle por toque, nos termos da seção 4. O Touch Controller (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, visionOS 26.0) é resumido na sua página como “Integrate onscreen touch controls into your Metal-based games” (integre controles por toque na tela aos seus jogos baseados em Metal): botões, direcionais, analógicos, aceleradores e touchpads, expostos por um GCController.62 O TCDirectionPad (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0) pode ser configurado “to behave as either a composite direction pad” (para se comportar como um direcional composto) ou “as four separate buttons.” (como quatro botões separados).6263 O mais antigo GCVirtualController (iOS 15.0) é “A software emulation of a real controller that you configure specifically for your game.” (uma emulação por software de um controle real que você configura especificamente para o seu jogo).64 Um endereço que tentei, developer.apple.com/documentation/touchcontrols, retornou 404; a página do framework é touchcontroller.39
Quanto custa
Nada nesta seção foi cronometrado num celular. O tempo de quadro do mundo no aparelho, o custo de um acumulador de 60 hertz e o custo de manter um motor háptico rodando enquanto o mundo está na tela estão todos sem medição; os testes do briefing na seção 7 foram escritos para medir os dois primeiros.
7. O briefing: o que o Kiradex constrói e os testes que precisa passar
Onde a caminhada está hoje
Li o código de caminhada, câmera e transições do mundo no repositório do Kiradex em 4 de outubro de 2026, sem alterá-lo, e escrevi um modelo quadro a quadro dele. Tudo nesta subseção é lido desse código ou calculado pelo modelo, e sempre digo qual dos dois. O modelo faz cada operação Float do código em 32 bits, na ordem do código, com o arredondamento do Swift, a exatamente 1/60 ou 1/120 de segundo por quadro, e deixa de fora as bordas do mapa, a dobra do iPhone Duo e o deslocamento dos pés, nada disso altera uma caminhada em linha reta no meio do mapa. É um modelo do código. Não é uma captura, e nenhum celular foi medido.176
O que o código faz, pela leitura dele:
- O controle é tocar para andar, e só isso. O palco transforma um toque num ponto do mundo e então toca num colecionador, no quiosque, ou chama
walk(to:). Não há arrastar, nem direcional, nem nenhuma chamada háptica em todo o target do app: uma busca porUIImpactFeedbackGenerator,sensoryFeedbackeCHHapticnão encontra nada.17 - O caminho é uma busca de menor caminho em oito direções, ordenada pelo custo percorrido até ali, sem estimativa da distância que falta, o que faz dela a busca de Dijkstra e não A*; diagonais custam √2 e nunca cortam a quina de uma parede. Um passo na diagonal fica virado de lado e divide seu progresso por √2 depois de aplicado o 1,6 da corrida, então no modelo um passo diagonal leva 22 quadros andando e 14 correndo a 60 hertz, contra 15 e 10 de um passo reto.176
- A velocidade é tempo.
tilesPerSecondvale 4, que são 64 pixels por segundo, e a caminhada vira corrida a 1,6 vez isso quando o caminho tem 6 passos ou mais, então uma caminhada no app tem de 1 a 5 tiles e qualquer coisa mais longa é percorrida correndo. Cada quadro somadt × speed / lengthao progresso de um passo, e quando o progresso chega a 1 o personagem salta para o tile seguinte e o progresso volta a 0, descartando o excedente.17 - O desenho da caminhada é escolhido pelo tempo. A coluna é
cycle[Int(walker.clock * framesPerSecond) % count], comframesPerSecondigual a 8 e os ciclos de caminhada e corrida de seis desenhos da forja. O post desta série sobre personagens informou a mesma cadência, “the walk at eight frames a second” (a caminhada a oito quadros por segundo), e isso descreve o código com precisão.1735 - A câmera suaviza e depois arredonda. A cada quadro ela se move
(target - current) * min(1, dt * 6)em direção ao jogador e depois arredonda os dois eixos para unidades inteiras; ela é limitada ao mapa quando o mapa é maior que a visão. A frase do primeiro post, de que todo sprite e a câmera “are rounded to whole world units each frame, after easing” (são arredondados para unidades inteiras do mundo a cada quadro, depois da suavização), também está correta.1722 - Portas e warps. A porta mostra na hora o desenho entreaberto da folha e o aberto depois de 70 milissegundos, e remove a porta 1,4 segundo depois; o passo sobre o warp espera 220 milissegundos e então troca de lugar. O post sobre estruturas descreveu isso como uma lacuna conhecida.1713
- As transições são pushes de navegação com a animação padrão; uma troca de andar substitui o palco por identidade, sem fade; sair chama
dismiss(). O único véu do app é a sobreposição preta do visualizador de cartas, animada com.easeOut(duration: 0.25).17 - Nenhum pedido de taxa de quadros. Não há
CADisplayLink,preferredFrameRateRangenemCADisableMinimumFrameDurationOnPhoneno app nem no arquivo de projeto; o mundo avança emSceneEvents.Updatecom odeltaTimedo evento.17
O que o modelo diz que esse código faz na tela, com um tempo de quadro constante:
- Um salto de dois pixels uma vez por tile a 60 hertz. A 60 hertz, uma caminhada leva 15 quadros por tile, 4,00 tiles por segundo, e os 16 pixels de cada tile chegam como 14 quadros de 1 pixel e um de 2: um salto de 2 pixels por tile, cinco numa caminhada de cinco tiles. Emerald move exatamente 1 pixel em todo quadro.61
- Zeros e uns irregulares a 120 hertz. A 120 hertz, a caminhada leva 30 quadros por tile e os 16 pixels de cada tile chegam como 16 quadros de 1 pixel e 14 sem nenhum, intercalados de forma irregular.6
- A corrida é mais lenta que sua constante. A 60 hertz, a corrida leva 10 quadros por tile, 6,00 tiles por segundo, quando 4 vezes 1,6 dariam 6,4, porque o excedente de cada tile é jogado fora; os 16 pixels de cada tile chegam como 4 quadros de 1 pixel e 6 de 2. A 120 hertz ela leva 19 quadros por tile, 6,32 tiles por segundo, então a velocidade da corrida depende da taxa de quadros.6
- Uma câmera atrasada que nunca chega. Andando a 60 hertz, a câmera fica cerca de um pixel mais para trás a cada tile, de 5 pixels no fim do primeiro tile a 9 no fim do quinto, a caminhada mais longa que o app faz, e a posição do jogador na tela muda em 8 dos 73 quadros da caminhada depois do primeiro. Numa corrida, que é todo caminho de 6 tiles ou mais, ela fica 13 para trás a partir do segundo tile. A 120 hertz ela chega a 9 já no primeiro tile, andando ou correndo, e então se mantém. Quando o jogador para, a câmera repousa a 4 pixels do jogador a 60 hertz e a 9 a 120, e fica ali: assim que a distância vezes
min(1, dt × 6)fica abaixo de meio pixel, o arredondamento devolve a mesma posição a cada quadro. Em que lado ela para depende da direção da última caminhada. Em Emerald, o atraso e o deslocamento em repouso são ambos zero.6 - Os desenhos seguem um relógio próprio. O ciclo de seis desenhos a 8 por segundo dura 0,75 segundo. Medido pelas posições do modelo entre os inícios de ciclos sucessivos a 60 hertz, ele cobre 3,00 tiles andando e 4,50 correndo (4,80 nos 6,4 tiles por segundo nominais da corrida, que o excedente descartado nunca deixa alcançar); o ciclo de duas pisadas de Emerald cobre 2,00 tiles. Na minha leitura, um ciclo que cobre metade a mais de chão do que suas pisadas aparece como pés deslizando, mas o modelo não mede onde um pé fica no chão. O desenho também fica no fio da navalha a cada 15º quadro, a 60 e a 120 hertz, quando o relógio vezes 8 cai exatamente num número inteiro, a fronteira entre dois desenhos: manter o relógio em 32 bits, como o app faz, em vez de 64, muda qual desenho aparece em 9 dos 11 quadros desse tipo em 12 tiles de caminhada a 60 hertz e em 14 de 23 a 120.6
- Um segundo toque no meio do passo faz o personagem voltar. Isso é lido no código, não modelado nem capturado:
walk(to:)zera o progresso enquanto o tile do personagem ainda é a origem do passo, então o quadro seguinte desenha o personagem até 15 pixels atrás de onde estava; os movimentos de outros colecionadores vindos do servidor fazem o mesmo.17
Pixels por quadro exibido: o cânone é uniforme, o código de hoje (um modelo, não uma captura) não é, e um tick fixo é uniforme também a 120.621
A câmera suavizada e arredondada, no modelo: fica para trás ao andar ou correr e para antes do jogador quando a caminhada termina.621
Nada disso é um julgamento sobre como fica na mão, porque nenhum celular foi medido. Os tempos de quadro reais oscilam, o que vai mudar o padrão exato de uns e dois. O atraso e o deslocamento em repouso também dependem do tempo de quadro, porque o passo de suavização min(1, dt × 6) depende dele, e é por isso que o modelo dá 4 pixels em repouso a 60 hertz e 9 a 120. Na minha leitura, oscilações dentro da faixa que um celular normalmente mostra vão mudar o tamanho deles, não eliminá-los; a condição é que os quadros fiquem abaixo de 1/6 de segundo, cerca de 167 milissegundos, porque com essa duração o fator de suavização chega a 1 e a câmera pousa no jogador num único quadro, então um engasgo longo o bastante fecha a distância naquele quadro.176 O descompasso entre os desenhos e a caminhada não depende da taxa de quadros: um relógio de desenhos de 8 por segundo contra uma caminhada de 4 tiles por segundo dá 3,00 tiles por ciclo a 60 hertz e mais ou menos o mesmo a 120. O da corrida depende, 4,50 tiles por ciclo a 60 hertz e 4,75 a 120, porque depende dela o excedente que é descartado em cada tile.6
O briefing
Cada item é uma mudança em Kiradex/World/, o motivo dela e um teste que uma linha de log, um script ou uma captura consegue verificar. Nenhum asset, nome ou som de qualquer um dos jogos é proposto: os números são mecânica, e a arte, os sons e as palavras são do próprio Kiradex.
1. Andar num tick, não num relógio.
Mudança. Acrescentar um acumulador de 60 hertz à atualização do rig. Cada callback soma o seu dt; se o acumulador passar a ter mais de 8 ticks de tempo (133 milissegundos), o excesso é descartado e registrado no log de movimento como uma linha dropped com os milissegundos; depois, todo tick inteiro no acumulador é executado, cada um subtraindo um tick. O acumulador conta o tempo em unidades inteiras (nanossegundos, ou o 1/60.000.000 de segundo do modelo), nunca em segundos de ponto flutuante: com um acumulador Double ou Float, o 600º tick de um teste de dez segundos cai um arredondamento antes da sua fronteira em algumas taxas e a contagem sai 599. Essa é a regra do atraso acumulado: até 8 ticks de atraso são recuperados no callback seguinte, e o que passar disso é tratado como uma suspensão, de modo que o mundo retoma de onde parou em vez de disparar para alcançar o tempo perdido. O oito foi escolhido para cobrir todas as taxas da lista da Apple para iPhones com ProMotion, até 10 hertz, que precisa de 6 ticks por callback.7 No modelo, dez segundos de callbacks em cada uma das doze taxas executam exatamente 600 ticks e não descartam nada, enquanto um limite de 4 ticks por callback executaria 480 a 12 hertz e 400 a 10.6 Depois de um engasgo de um segundo a 60 hertz, a regra executa 8 ticks no callback seguinte e 1 em cada um dos seguintes, e registra 866,7 milissegundos descartados; o mesmo engasgo com o atraso preservado sob um limite de 4 executa 4 ticks em 19 callbacks seguidos, o avanço acelerado que a regra existe para evitar.6 Cada personagem guarda uma contagem de ticks do passo em vez de um progresso fracionário, e o deslocamento desenhado é essa contagem vezes os pixels por tick: inteiros exatos, então os personagens deixam de precisar de arredondamento. A caminhada é 1 pixel por tick, 16 ticks por tile; a corrida é 2 pixels por tick, 8 ticks, mantendo a regra atual de que um caminho de 6 passos ou mais é uma corrida. Os passos diagonais, que a busca de caminho permite, mantêm o √2 que o código já pretende e a ordem dele, com o ritmo da corrida aplicado antes da divisão por √2: uma diagonal andando leva 23 ticks (16√2 é cerca de 22,6, arredondado para cima) e uma diagonal correndo 12 (8√2 é cerca de 11,3, arredondado para cima), cada uma movendo 16 pixels em cada eixo nos ticks em que floor(16 × t / 23) ou floor(16 × t / 12) muda.17 Andando, isso dá 1 pixel em 16 dos 23 ticks e nenhum nos outros 7; correndo, 1 pixel em 8 dos 12 ticks e 2 em 4, o único modo de andar irregular do briefing, assim como o da Acro Bike é o único irregular de Emerald.1 Não há excedente a descartar.
Por quê. As regras de caminhada e corrida da seção 5; e o salto de 2 pixels por tile do modelo a 60 hertz e os zeros e uns irregulares a 120.61
Testes. Um argumento de inicialização -motionLog registra o tick e o x, y do jogador em cada tick, e cada linha dropped. Na demo de orientação existente, que anda um tile para cada lado, um script verifica que cada tick de caminhada move exatamente 1 pixel, que cada tile leva 16 ticks e que nenhum tick move 2. Uma demo diagonal, com um passo diagonal andando e outro correndo para cada lado, verifica 23 ticks e 12, 16 pixels em cada eixo por passo, deslocamentos por eixo de 0 ou 1 pixel andando e de 1 ou 2 correndo, e uma diagonal correndo mais rápida que uma andando. Testes unitários alimentam o acumulador com sequências inventadas de dt em unidades inteiras exatas: dez segundos em cada uma das doze taxas do iPhone, de 120 a 10 hertz, executam 600 ticks sem nada descartado, e um intervalo de um segundo a 60 hertz executa 8 ticks no callback seguinte, depois 1 por callback, com 866,7 milissegundos registrados como descartados. O mesmo log de movimento num iPhone 18 Pro Max e na tela interna de um iPhone Duo dá os mesmos ticks por tile: a taxa da tela não pode mudar a caminhada.
2. Escolher o desenho da caminhada pela distância.
Mudança. Este item é proposta minha, não uma cópia: Emerald cronometra os desenhos em quadros, ajustados aos passos, e não os lê a partir da distância.92 Durante a caminhada, escolher a coluna a partir do progresso da caminhada contado em passos, os passos concluídos desde o início da caminhada mais os ticks do passo atual divididos pela duração dele: walkCycle[floor(steps × walkCycle.count / 2) % walkCycle.count], duas pisadas a cada dois passos, e o mesmo para a corrida. Num passo reto, isso é a distância percorrida sobre 32 pixels, floor(distancePx × 6 / 32) mod 6. Numa diagonal, conta o passo e não o comprimento de √2, de modo que uma pisada continua caindo no início de cada passo, com uma passada mais longa, escolha minha para desenhos feitos para passadas retas. Mostrar o desenho parado quando a caminhada termina, como Emerald faz. Deixar os relógios de ocioso, piscada e emote como estão. Um ciclo cronometrado em ticks também poderia acompanhar os passos, como o de Emerald, mas seis desenhos em 32 ticks exigiriam durações desiguais de 5 e 6 ticks; contar o progresso não precisa de uma segunda tabela e vale para qualquer modo de andar que o app acrescentar.
Por quê. Uma pisada a cada 16 pixels em qualquer velocidade, e uma cadência que não consegue se descolar do movimento; no modelo, o ciclo atual cobre 3,00 tiles andando e 4,50 correndo.16 Isso também resolve o “eight frames a second” (oito quadros por segundo) do post sobre personagens: a 1 pixel por tick, seis desenhos em 32 pixels mudam a cada 5,33 pixels, o que dá 11,25 desenhos por segundo, não 8.35
Teste. A partir do log de movimento mais a coluna exibida: os dois desenhos de contato (walk_a e walk_d no ciclo da forja) aparecem nos pixels 0 e 16, com margem de um, de cada 32, a 60 e a 120 hertz; na demo diagonal, aparecem no primeiro tick de cada passo.
3. Terminar o passo; manter o toque; acrescentar um analógico flutuante.
Mudança. Um toque no meio do passo planeja a partir do tile para onde o personagem está indo, não a partir da origem, e o passo atual termina primeiro; a contagem do passo nunca é zerada. Os movimentos de outros colecionadores vindos do servidor seguem a mesma regra. A partir do repouso, um toque cujo primeiro passo muda a orientação vira o personagem por 8 ticks antes de ele se mover. Um toque numa célula bloqueada ao lado do jogador, ou no tile à frente de uma, vira o jogador para ela e toca uma batida de 32 ticks com a háptica de recusa do item 6. Manter o dedo pressionado faz o personagem segui-lo, como no padrão do Stardew, mas com a rota recalculada pela busca de caminho existente cada vez que o dedo cruza um tile, já que o modo de seguir do Stardew não contorna obstáculos e o nosso consegue. Um analógico flutuante, de quatro direções, desligado por padrão, aparece onde o polegar encosta, construído sobre um gesto de arrastar do SwiftUI por cima do mundo; tudo o que for desenhado tem pelo menos 44 por 44 pontos. O analógico do Touch Controller (iOS 26) só substitui o arrastar se uma build de teste mostrar que ele é desenhado por cima do RealityView: a Apple descreve o framework como voltado para “Metal-based games” (jogos baseados em Metal), sua sessão da WWDC25 diz que ele “integrates directly with Metal” (se integra diretamente ao Metal), e o mundo do Kiradex é desenhado pelo RealityView sem nenhum render pass próprio.4562
Por quê. As regras de passo, virada, passo recusado e controle da seção 5.14044624517
Testes. Um teste de interface toca um tile 6 a leste e, 120 milissegundos depois, um tile 6 ao norte; o log de movimento não mostra nenhum tick em que o x do jogador diminua, e o tile do primeiro passo é concluído antes da virada. Um teste de interface toca a parede ao lado da casa; o log mostra uma mudança de orientação e uma batida de 32 ticks, sem mudança de posição. Com o analógico ligado, um arrasto mantido para a direita por 60 ticks a partir do primeiro tick de movimento conclui três tiles até o tick 48, começa um quarto enquanto o analógico ainda está pressionado e o termina no tick 64, depois de soltar: o log mostra o jogador 4 tiles a leste, num tile inteiro, com o passo que estava em andamento ao soltar concluído e nenhuma parada entre tiles.
4. Travar a câmera.
Mudança. Trocar a suavização por uma câmera definida no mesmo tick em que o jogador se move, só a partir de pixels inteiros do mundo: camera = clamp(player + foldOffset, low, high). Cada termo é mantido inteiro, porque hoje só o arredondamento final torna a câmera inteira: os limites e o deslocamento da dobra são fracionários. A posição do jogador é inteira pelo item 1. O deslocamento da dobra, que follow calcula em pontos vezes displayScale / pixelScale, é arredondado para pixels inteiros do mundo uma única vez, quando a dobra muda. Os limites do clamp são arredondados para dentro, o inferior para cima e o superior para baixo, porque a metade do tamanho da visão em pixels do mundo não precisa ser inteira: fit divide os pixels de tela da visão por um número inteiro de pixels de tela por texel, então uma visão ilustrativa de 393 por 852 pontos a 3x fica com 7, uma visão de 168,43 pixels do mundo de largura e meia largura de 84,21, e os limites da câmera passam a ser 85 e a largura do mapa menos 85, de modo que nenhum quadro mostra algo além da borda do mapa.176 Quando o mapa é menor que a visão, os limites se invertem, como o clamp atual permite, e o mesmo arredondamento para dentro mantém o mapa inteiro à vista. Manter o limite ao mapa; a regra da seção 5 permite limitar ou desenhar o lado de fora, e a câmera do Kiradex passa da borda do mapa para a floresta só quando o Duo está meio aberto, por uma margem de tiles inteiros.17 Tratar o deslocamento da dobra como fixo até a dobra mudar; quando ela mudar de a para b, fazê-lo passar por a + round((b − a) × i / 8) para i de 0 a 8, um nível a cada 2 ticks, a cadência do fade, de modo que toda posição no caminho seja um pixel inteiro; uma mudança de 0 para menos 37, por exemplo, passa por 0, −5, −9, −14, −19, −23, −28, −32 e −37.6 Manter o parallax da floresta, calculado a partir da câmera travada e arredondado como hoje. Nenhuma antecipação em direção ao destino tocado: a Game Freak construiu uma e a deixou desligada, e o destino de um toque para andar já está na tela.12
Por quê. A regra de câmera da seção 5, e o atraso do modelo, que cresce ao longo de uma caminhada até 9 pixels no quinto tile e é de 13 numa corrida, as mudanças que ele provoca no lugar do jogador na tela e seus deslocamentos em repouso de 4 e 9 pixels.6
Testes. Pelo log de movimento, a câmera menos o jogador é constante em todo tick de uma caminhada pela praça aberta (zero, ou o deslocamento da dobra) e continua igual depois que o jogador para, e todo valor de câmera é um número inteiro. Um teste unitário chama fit com a visão de 393 por 852 pontos a 3x, leva o jogador às duas bordas de um mapa e verifica posições inteiras da câmera que param em 85 e na largura do mapa menos 85; o mesmo teste depois muda a dobra e verifica que a câmera alcança o novo deslocamento por 9 posições de pixels inteiros, com 2 ticks de intervalo, e que nenhum tick intermediário sai dos limites. Para uma captura, os desenhos da caminhada não servem como marcador, porque os pés mudam de forma de um desenho para outro; uma build de depuração desenha um marcador de um texel, numa cor que não é usada em nenhum outro lugar, na posição da entidade do jogador, e um script o encontra em todos os quadros de uma caminhada de 6 tiles pela praça aberta: o mesmo pixel de tela em todo quadro. Perto da borda do mapa, o marcador se move e a câmera não, e sempre em pixels inteiros.
5. A porta, o passo e o fade, com a arte do próprio Kiradex.
Mudança. Entrar por uma porta, que é um warp com uma folha de porta. Hoje a porta abre depois que o personagem já pisou na célula da porta: o onStep do passo encontra o warp naquele tile e chama openDoor ali.17 Emerald nunca deixa o jogador parado numa porta fechada: TryDoorWarp só dispara quando o jogador, na célula de baixo, empurra para o norte contra uma porta, e Task_DoDoorWarp abre a porta uma célula acima e só então força o passo até ela.6525 O briefing recua o gatilho uma célula para bater com isso:
- Quando o próximo passo do caminho é sobre um warp de porta, a caminhada para na célula anterior, e a entrada começa ali, no tick 0. Congelar o controle e tocar o som da porta, um som do Kiradex.
- Abrir a porta em quatro slots de 5 ticks, os quatro desenhos de Emerald a cinco quadros cada (83 milissegundos por slot a 60 ticks por segundo, onde os cinco quadros de Emerald são 84; 20 ticks no total), no lugar do desenho entreaberto imediato e do aberto aos 70 milissegundos de hoje: fechada, entreaberta, aberta, aberta. A folha de porta do Kiradex tem três desenhos, fechada, entreaberta e aberta (o
door_sheet()da forja desenha três, e o app a carrega comSpriteSheet.bundled(name, columns: 3, rows: 1)), enquanto a porta de Emerald é fechada mais três, então o desenho aberto se mantém durante o quarto slot. Um quarto desenho da forja, entre entreaberta e aberta, ocuparia esse slot; é arte opcional, não um requisito. Corrigir o comentário que diz “four ticks” (quatro ticks).2417 - Levar o jogador num passo forçado dessa célula até a célula da porta, 16 ticks. As portas da cidade ficam na fileira de baixo de um prédio, com células do prédio dos dois lados e acima, e o caminho nunca corta a quina de uma parede, então esse passo é sempre para cima a partir da célula de baixo, como o de Emerald.17
- Esconder o personagem e fechar a porta, com os slots ao contrário (aberta, aberta, entreaberta, fechada), 20 ticks.
- Fazer o fade de um véu preto sobre o mundo em 9 níveis escalonados de opacidade (0, 2/16 e assim por diante até 16/16), o nível
ino tick2ido fade, de modo que o último nível cai no tick 16 e se mantém durante o tick 17: 18 ticks. Isso é uma adaptação, escolhida, não a temporização de Emerald. As paletas de sprites de Emerald alcançam cada nível nos mesmos quadros pares que este véu, as paletas de fundo um quadro antes, e depois da última mistura no quadro 16 o jogo roda cinco atualizações finais antes de ficar inativo no quadro 21; um único véu não tem uma segunda camada que fique para trás nem nada para terminar.5 Nunca animar a opacidade da view que contém o próprio RealityView; o visualizador de cartas ensinou essa lição ao primeiro post.22 - Fazer o push do destino com as animações desativadas e retirar o véu nos mesmos 9 níveis.
- Chegar ao capacho do lado de dentro virado para cima. Sair inverte tudo: chegar à célula da porta com a porta aberta, um passo forçado para baixo de 16 ticks, a porta fecha, depois o controle.
Os andares, que hoje trocam o palco sem fade, recebem o mesmo véu sem porta e sem passo forçado. Sair de um cômodo, que hoje chama dismiss(), recebe o véu e o passo forçado para fora.
Por quê. As regras de porta, fade e cerimônia da seção 5; hoje o lugar muda 220 milissegundos depois do passo, a porta mostra dois desenhos com 70 milissegundos de intervalo e a tela desliza para o lado.1517
Testes. O log de movimento conta os ticks a partir daquele em que a caminhada para embaixo da porta. Ele mostra o jogador ainda naquela célula, e os slots da porta nos ticks 0, 5, 10 e 15 (fechada, entreaberta, aberta, aberta); o passo forçado nos ticks 20 a 35, 1 pixel por tick, chegando à célula da porta no tick 35 e nunca antes; os slots de fechamento nos ticks 36, 41, 46 e 51; o primeiro nível do véu no tick 56; e o push não antes do tick 74. Uma caminhada que termina numa célula de porta sem essa sequência falha no teste. O log de movimento também registra a opacidade do véu em cada tick: 9 valores distintos, cada um mantido por 2 ticks, nos ticks 56 a 73 na saída, e os mesmos 9 na entrada. Uma gravação de tela confirma isso numa parte da imagem que fica parada: o quadro inteiro não serve, porque a água do chão, num mapa que tenha água, troca de desenho 4 vezes por segundo e outros colecionadores podem passar andando, então um script amostra um trecho de parede de prédio escolhido de antemão, sem nenhum tile animado nem personagem por cima na gravação, e conta 9 níveis distintos na saída e 9 na entrada, cada um mantido por 2 quadros, com margem de um a 60 hertz.17 Numa gravação de tela de um warp, o mundo nunca se desloca na horizontal: nenhum deslize de navegação.
6. Háptica: três eventos e um interruptor.
Mudança. Um único CHHapticEngine pertencente ao mundo, iniciado quando ele aparece e parado quando ele desaparece, com um interruptor de Háptica nas configurações, ligado por padrão e que respeita a configuração de háptica do sistema. Os padrões, guardados como arquivos AHAP no bundle:
| Evento | Padrão | Por quê |
|---|---|---|
| Passo recusado (a batida) | um hapticTransient, intensidade 0,4, nitidez 0,2 |
o impacto das HIG é “a thud when two heavy objects collide” (um baque quando dois objetos pesados colidem);46 suave, porque a arte é suave |
| A porta abre | um hapticTransient com intensidade 0,3 e nitidez 0,6 em cada slot que muda a imagem ao abrir, o desenho entreaberto no tick 5 e o aberto no tick 10 (83 e 167 ms) |
combinar com a animação que acompanha |
| Carta erguida (a carta 3D) | um hapticTransient com 0,7 e 0,8 quando ela chega ao topo |
o único objeto real do mundo |
| Passos | nenhum por padrão | 3,75 passos por segundo durante minutos é o exagero contra o qual as HIG alertam |
Os valores de intensidade e nitidez são pontos de partida meus, não medições, e servem para ajuste num aparelho. UIImpactFeedbackGenerator(style: .soft, view:), com prepare() chamado quando uma caminhada começa, é a alternativa quando capabilitiesForHardware() indicar que o Core Haptics não está disponível.
Por quê. A regra da háptica; as orientações da Apple citadas na seção 6.46565760
Teste. Um argumento -hapticLog registra cada evento. Uma entrada por porta roteirizada registra exatamente 2 eventos de porta, nos ticks 5 e 10 da contagem do item 5, e nenhum por passo; com o interruptor desligado, nenhum.
7. Taxa de quadros: 60, de forma uniforme.
Mudança. Nenhuma no Info.plist: não acrescentar CADisableMinimumFrameDurationOnPhone, porque o mundo não ganha nada com 120. Manter o RealityView; o tick do item 1 torna o movimento independente da taxa em que ele renderizar. Se algum dia um display link for acrescentado, definir CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60) para obter a prioridade da Apple para jogos.76
Testes. Registrar um histograma de SceneEvents.Update.deltaTime durante uma caminhada de 60 segundos num iPhone 18 Pro Max e num iPhone Duo, nas telas externa e interna. O briefing supõe que a moda seja 16,7 milissegundos; se for 8,3, o acumulador do item 1 ainda mantém a caminhada em 16 ticks por tile, e o log comprova isso. As linhas dropped do log de movimento: nenhuma numa caminhada normal, em nenhuma das duas taxas.
8. Tremor, para um único momento.
Mudança. Um tremor de câmera em texels inteiros, 1 texel, 8 inversões, 5 ticks de intervalo, usado para um único evento que mereça, por exemplo a revelação de uma carta rara na praça, junto com a háptica de carta erguida. Não para portas, passos ou chegadas.
Por quê. O mais comum dos 24 tremores de Emerald, e a contenção deles.29
Teste. O log de movimento mostra o deslocamento da câmera alternando entre mais e menos 1 texel nos ticks 0, 5 e assim por diante até 35, e de volta a 0 no tick 40.
Fora deste briefing
- Suavização ou antecipação de câmera na grade. Não há pulos a suavizar, e a Game Freak lançou o jogo com a antecipação desligada.1223
- 120 hertz para o mundo. O objetivo é um ritmo uniforme a 60; 120 mostra cada pixel duas vezes.6
- Háptica a cada passo por padrão.46
- Um direcional fixo na tela. Minha recomendação é tocar para andar mais um analógico flutuante opcional.44
- Qualquer som ou jingle de porta ou de warp dos jogos. Sons são expressão, e o Kiradex precisa dos seus.
- Bicicletas, surf, gelo e correntes. Os personagens do app andam e correm e nada mais, e as velocidades acima ficam registradas para quando isso mudar.17
A pergunta em aberto: a temporização num aparelho
Todo número do modelo nesta seção supõe 1/60 ou 1/120 de segundo constantes entre quadros. Se o RealityView num iPhone 18 Pro Max ou num iPhone Duo renderiza a 60 ou a 120, e quanto o seu deltaTime oscila, está sem medição; o histograma do item 7 é a primeira coisa a rodar, e o item 1 foi escrito para que a resposta não mude nada na caminhada em nenhuma taxa da lista de iPhones da Apple.687
Principais conclusões
Se você desenha a arte
- Desenhe a caminhada para uma distância. A caminhada de Emerald são duas passadas e duas posições paradas ao longo de 32 pixels, mantidas por quadros que batem com os passos, e os modos de andar mais rápidos reaproveitam os desenhos com durações menores em vez de acrescentar quadros, então cai uma pisada a cada 16 pixels em qualquer velocidade.192
- Um ciclo de seis desenhos funciona bem, desde que o motor o reproduza ao longo de uma distância fixa; tocado a uma taxa fixa contra uma caminhada cronometrada à parte, a cadência dele se descola do movimento, 3,00 tiles por ciclo andando e 4,50 correndo no modelo do nosso código.6
- A porta de Emerald é fechada mais três desenhos, cada um na tela por 84 milissegundos. Uma folha de três, fechada, entreaberta e aberta, como a do Kiradex, preenche os quatro slots mantendo o desenho aberto por dois deles, ou a forja desenha um quarto; de qualquer forma, desenhe o estado aberto como algo que valha a pena ver.12417
Se você constrói o motor
- Avance o mundo num tick fixo com deslocamentos de pixels inteiros que dividam o tile, leia o controle no tile e deixe o renderizador mostrar o último tick. Diga o que acontece com o atraso acumulado: o nosso recupera até 8 ticks e descarta o resto, com registro no log. As tabelas de passos de Emerald são o modelo: todas somam 16.216
- Trave a câmera no jogador no mesmo tick, com limites e deslocamentos em pixels inteiros, para que nada precise ser arredondado depois. Suavizar e depois arredondar deixa a câmera para trás de um jogador que anda e, no modelo do nosso código, a faz parar de 4 a 9 pixels antes até a próxima caminhada.36
- No iPhone, não peça 120 hertz para movimento em pixels. A Apple prioriza 30 e 60 para jogos, o RealityKit normalmente renderiza a 60, e uma caminhada de pixels inteiros a 120 só mantém cada pixel por duas atualizações.786
- Faça o fade em degraus, não numa rampa suave. O fade de porta de Emerald são nove níveis separados por dois dezesseis avos, a última mistura no 17º quadro e o fade terminado no 22º; o nosso véu de 18 ticks é uma adaptação dessa rampa, não uma cópia.5
- Nunca anime a opacidade da view do SwiftUI que contém um RealityView; ponha um véu por cima.22
Se você projeta o loop de jogo
- Uma entrada por porta é, em Emerald, uma cerimônia de pelo menos 1,32 segundo sem comando do jogador, e o passo forçado pela soleira é o que faz isso ser lido como entrar andando.525
- Virar no lugar por 8 quadros deixa o jogador se virar para algo sem se mover; uma batida de 32 quadros diz a ele que um passo foi recusado.1
- No celular, minha recomendação: tocar para andar como padrão, como a versão mobile de Stardew traz, um analógico flutuante como alternativa de precisão, como as HIG recomendam, e nenhum direcional fixo.4044
- A háptica confirma eventos, não passos, e vem com um interruptor.46
Perguntas frequentes
Qual é a velocidade de caminhada do jogador em Pokémon?
Em Red, Crystal e Emerald, um passo andando percorre uma célula de 16 pixels em 16 quadros a 59,7275 quadros por segundo: 268 milissegundos por célula, 3,73 células por segundo. Emerald move 1 pixel em todo quadro; Red e Crystal movem 2 pixels a cada dois quadros. Correr e surfar em Emerald, e as bicicletas em Red e Crystal, levam 8 quadros por célula, 7,47 células por segundo.1419
Quantos quadros tem um ciclo de caminhada em Pokémon Emerald?
Quatro entradas em 32 quadros: um desenho de passada por 8 quadros, o desenho parado por 8, a outra passada por 8, parado por 8. Isso são duas células de caminhada, então cada passo mostra uma passada e uma posição parada, e as pernas se alternam passo a passo. A corrida é um ciclo de 16 quadros sobre duas células de 8 quadros.192
A câmera em Pokémon Emerald fica para trás do jogador?
Não. A câmera de Emerald copia a posição do jogador e rola o mapa pelos mesmos pixels no mesmo quadro, então o jogador fica fixo na tela enquanto o mundo se move. Ela também não para na borda de um mapa: o lado de fora é desenhado com os tiles de borda do layout. Uma câmera de antecipação para a bicicleta existe no código, mas nunca é ligada.231112
Quanto dura a transição de porta e fade em Pokémon Emerald?
A porta abre em quatro desenhos de cinco quadros (335 milissegundos), o jogador dá um passo forçado de 16 quadros para dentro, a porta fecha em 20 quadros, e a tela faz um fade em nove níveis, com a última mistura no 17º quadro do fade e o fade terminado no 22º: pelo menos 79 quadros, 1,32 segundo, antes que o próximo mapa possa começar a carregar. Ao entrar numa caverna, a tela faz fade para branco em vez de preto, e ao sair de uma, volta a partir do branco.152527
Um jogo de pixel art deve rodar a 120 Hz num iPhone com ProMotion?
Não para movimento em pixels inteiros. Um mundo que se move um pixel por tick de 60 hertz só mostra cada pixel por duas atualizações a 120 hertz, e com durações desiguais a 80. O artigo da Apple sobre ProMotion diz que jogos recebem “special priority to 30Hz and 60Hz” (prioridade especial a 30 Hz e 60 Hz), que um app de iPhone precisa definir CADisableMinimumFrameDurationOnPhone para passar de 60, e o RealityKit normalmente renderiza a 60. Simule num tick fixo de 60 hertz e deixe a tela ser o que for.67168
Por que os pés do meu sprite deslizam quando ele anda?
Normalmente porque os desenhos da caminhada seguem um relógio e o movimento segue outro, com durações que não batem. O código atual do Kiradex toca um ciclo de seis desenhos a 8 desenhos por segundo enquanto anda 4 tiles por segundo, então um ciclo cobre 3 tiles em vez de 2, como mostra um modelo do código. Os portáteis contam as duas coisas em quadros e fazem os desenhos de cada passo durarem o mesmo que o passo; escolher o desenho a partir da distância percorrida, que é o que proponho para o Kiradex, chega ao mesmo acerto sem um segundo relógio. De qualquer forma, a cadência não consegue se descolar do movimento; se um pé fica bem plantado depende também dos próprios desenhos.619
Um jogo de pixels para celular deve usar um direcional virtual?
Não como padrão, na minha leitura das fontes. O padrão do Stardew Valley no mobile é tocar para mover, com um joystick invisível entre os outros esquemas para tarefas que exigem precisão; as HIG da Apple recomendam tocar diretamente nos objetos e um analógico que apareça “wherever the player lands their thumb instead of a static thumbstick position.” (onde o jogador encostar o polegar, em vez de numa posição fixa).4044
Relacionados neste site: Mundos em pixel art no iPhone é o primeiro guia desta série, com a caminhada de passo e posição parada de Emerald, a receita de RealityKit em que este mundo roda e a lição de opacidade do visualizador de cartas; Pessoas em pixel art: personagens e um criador no iPhone é o segundo, com a caminhada de seis desenhos cuja temporização este post passa de um relógio para uma distância; Estruturas em pixel art: casas, salões e interiores no iPhone é o terceiro, com as portas, os warps e os andares cuja temporização a seção 2 mede; iPhone Duo para desenvolvedores e Prepare seu app para o iPhone Duo tratam das duas telas e da dobra de que a câmera do briefing se mantém afastada; O modelo mental espacial do RealityKit explica o modelo de entidades e sistemas por trás de SceneEvents.Update.
Fontes
-
Medição do autor, 4 de outubro de 2026:
measure_gen3_motion.py, na pasta de evidências do autor para este post, executado sobre opokeemeralddo pret no commit731ad5b; ele analisa as tabelas de funções de passo e as durações deInitMoveInPlaceemsrc/event_object_movement.c, as tabelas de animação emsrc/data/object_events/object_event_anims.h, a regra do contador de atraso emsrc/sprite.ce os quadros de porta emsrc/field_door.c, e converte os quadros a 59,7275 hertz. Saída salva comomeasure_gen3_motion.out.txtao lado dele (velocidades de 3,73, 7,47, 9,95, 14,93 e 29,86 células por segundo; caminhada no lugar de 32, 16, 8 e 4 quadros;sAnim_GoSouthde 32 quadros; desenhos de porta mantidos por 5 atualizações, 83,7 ms, 335 ms para quatro). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/event_object_movement.c(sStep1FuncsasStep8Funcse o comentário “Over the course of the step animation, these sum to 16 pixels (one full metatile)”;sStepTimeseNpcTakeStep, que indexa a tabela de passos comsTimer, uma entrada por quadro;SetStepAnimHandleAlternation, que define a animação do modo de andar e a alternância quando um passo começa;CameraObject_UpdateMove), commit731ad5b, acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/event_object_movement.c. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/overworld.c(a ordem deOverworldBasic,RunTasks(); AnimateSprites(); CameraUpdate(); UpdateCameraPanning();, seguida mais adiante na mesma função porUpdatePaletteFade();VBlankCB_FieldchamandoTransferPlttBuffer;InitPlayerAvatarantes deInitCameraUpdateCallback(gPlayerAvatar.spriteId)) esrc/sprite.c(AnimateSpritesexecuta os callbacks na ordem dos slots), acessados em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/overworld.c e https://github.com/pret/pokeemerald/blob/master/src/sprite.c. A conclusão de que tudo ocorre no mesmo quadro é uma leitura do código pelo autor, não uma execução. ↩↩↩↩↩↩↩ -
Medição do autor, 4 de outubro de 2026:
measure_gen12_motion.py, na pasta de evidências do autor para este post, executado sobre opokereddo pret emd2704a6(home/overworld.asm,home/fade.asm) e opokecrystalem5beda23(engine/overworld/events.asm,engine/overworld/map_objects.asm,data/maps/setup_scripts.asm,engine/tilesets/timeofday_pals.asm). Saída salva comomeasure_gen12_motion.out.txt(Red: 16 px em 16 quadros, bicicleta 8 quadros, fade de warp 32 quadros; Crystal: caminhada de 8 atualizações de 2 px, bicicleta 4 de 4, passo lento 16 de 1 a 1,87 célula por segundo, fade de porta de 8 quadros em cada sentido). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Medição do autor, 5 de outubro de 2026:
measure_gen3_fade.py, na pasta de evidências do autor para este post, um port em Python deBeginNormalPaletteFade,UpdatePaletteFadecom sua flag de transferência pendente,UpdateNormalPaletteFadeeIsSoftwarePaletteFadeFinishingdepokeemerald/src/palette.cdo pret em731ad5b, executado no cronograma de quem as chama: a tarefa da porta inicia o fade dentro deRunTasks(Task_DoDoorWarp,src/field_screen_effect.c);BeginNormalPaletteFadeatualiza uma vez, copia o buffer para a memória de paletas e limpa a flag;OverworldBasic(src/overworld.c) atualiza de novo no mesmo quadro; cada quadro seguinte atualiza uma vez, e a transferência no VBlank limpa a flag. Os quadros são contados a partir do quadro da tarefa como 0. Supõe que nenhum fade estava rodando; com chuva, neve, neblina, sombra ou seca ativas,FadeScreenemsrc/field_weather.ccopia o buffer tingido pelo clima e depois chama a mesmaBeginNormalPaletteFade(o handler próprio do clima para o fade de saída éDoNothing), então o cronograma do fade de saída se mantém, enquanto o fade de entrada passa pelo código do clima e não foi simulado; o fade de entrada depois de um warp começa no callback de carregamento do mapa, cujo primeiro quadro não foi rastreado, então os dois casos são dados. Saída salva comomeasure_gen3_fade.out.txt(níveis de 0 a 16 em passos de 2; primeira mistura visível no quadro 1; última mistura, as paletas de sprites em 16, no quadro 16, 285 ms contando o quadro 0; inativo no quadro 21, 368 ms;FadeInFromWhitecom atraso 8 inativo no quadro 85 ou 86 contando a partir do quadro 0, ou seja, depois de 86 ou 87 quadros, 1.440 ou 1.457 ms; entrada pela porta: abrir 20, passo 16 e fechar 20 quadros, a última mistura do fade depois de pelo menos 73 quadros, 1,22 s, eWarpIntoMapnão antes do quadro 79, 1,32 s, já queTask_WarpAndLoadMapespera o fade ficar inativo). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Modelo do autor, 5 de outubro de 2026:
measure_kiradex_motion.py, na pasta de evidências do autor para este post, lêtilesPerSecond(4),framesPerSecond(8),runPace(1,6), o limite da corrida (6 passos) e omin(1, dt * 6)da câmera emKiradex/World/PlazaRig.swift,centreemKiradex/World/TileMap.swift, e os cicloswalkerunde seis desenhos emscripts/forge/rig.py, e modelaadvance,place,animateefollowquadro a quadro com cada operaçãoFloatfeita em 32 bits (float32do numpy), na ordem do código e com o arredondamento do Swift que afasta a metade do zero, a exatamente 1/60 e 1/120 s, para uma caminhada de 5 tiles, o ritmo de caminhada mantido por 12 tiles e uma corrida de 12 tiles, cada uma seguida de 3 s parado; o limite do mapa, a dobra e o deslocamento dos pés ficam de fora. É um modelo do código, não uma captura de um aparelho. Saída salva comomeasure_kiradex_motion.out.txt. Caminhada a 60 Hz: 15 quadros por tile, cada tile com 14 quadros de 1 px e um de 2; distância da câmera de 5, 6, 7, 8 e 9 px no fim dos cinco primeiros tiles, 13 a partir do nono quando o ritmo de caminhada é mantido; posição na tela mudando em 8 dos 73 quadros em movimento de uma caminhada de 5 tiles depois do primeiro (a caminhada do modelo tem 74 quadros em movimento, e o último quadro do último tile conta como parado); deslocamento em repouso de 4 px. A 120 Hz: 30 quadros por tile, 16 de 1 px e 14 de 0; distância 9; repouso 9. Corrida a 60 Hz: 10 quadros por tile, 6,00 tiles por segundo, 4 quadros de 1 px e 6 de 2 por tile; distância 13 a partir do segundo tile; repouso 4. Corrida a 120 Hz: 19 quadros por tile, 6,32 tiles por segundo; distância 9; repouso 9. Percurso do ciclo a partir das posições simuladas entre inícios sucessivos de um ciclo a 60 Hz: 3,00 tiles andando, 4,50 correndo (4,80 nos 6,4 tiles por segundo nominais); a 120 Hz, 3,00 e 2,94 andando, 4,75 correndo. Passos diagonais no código atual: 22 quadros andando e 14 correndo a 60 Hz, 43 e 27 a 120. Comparado ao mesmo modelo executado com o relógio, as posições e a câmera em precisão dupla, todas as posições e valores de câmera ficam iguais e diferem 9 desenhos de caminhada a 60 Hz e 14 a 120, todos em quadros em que o relógio vezes 8 é um número inteiro (11 quadros assim em 12 tiles de caminhada a 60 Hz, 23 a 120). A proposta: um tick fixo de 60 Hz mantém cada pixel por 1 atualização a 60 Hz, por 2 atualizações para 59 de 61 posições a 120 Hz, e por 1 ou 2 atualizações, 42 e 19 posições, a 80 Hz. O acumulador: dez segundos de callbacks em cada uma das doze taxas do iPhone, de 120 a 10 Hz, executam 600 ticks com um limite de atraso de 8 ticks e nada descartado, contra 480 a 12 Hz e 400 a 10 Hz com um limite de 4 ticks por callback; depois de um engasgo de 1 s a 60 Hz, a regra de 8 ticks executa 8 ticks, depois 1 por callback, descartando 866,7 ms, enquanto um limite de 4 ticks que preserva o atraso executa 4 ticks em 19 callbacks consecutivos. A aritmética da câmera:fitnuma visão de 393 por 852 pt a 3x dá 7 pixels de tela por texel e uma visão de 168,43 por 365,14 pixels do mundo, meia largura de 84,21, limites arredondados para dentro de 85 e a largura do mapa menos 85; uma mudança de dobra de 0 para −37 em 9 níveis passa por 0, −5, −9, −14, −19, −23, −28, −32, −37. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation, “Optimizing iPhone and iPad apps to support ProMotion displays” (a faixa de 10 a 120 Hz do iPhone e suas doze taxas; a lista de dispositivos;
CADisableMinimumFrameDurationOnPhone; a prioridade para jogos a 30 e 60 Hz; “Prepare your app to operate at any refresh rate”;targetTimestamp), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/quartzcore/optimizing-iphone-and-ipad-apps-to-support-promotion-displays. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation, “Improving the Performance of a RealityKit App” (“RealityKit typically limits the refresh rate” a 60 quadros por segundo), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/realitykit/improving-the-performance-of-a-realitykit-app. ↩↩↩↩↩↩↩
-
pret,
pokeemerald/src/data/object_events/object_event_anims.h(sAnim_GoSouth,sAnim_GoFastSouth,sAnim_GoFasterSouth,sAnim_GoFastestSouth,sAnim_RunSouth) esrc/sprite.c(animDelayCountercarregado com a duração do quadro menos um, decrementado porContinueAnim, e o quadro seguinte tomado quando chega a zero), acessados em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/data/object_events/object_event_anims.h e https://github.com/pret/pokeemerald/blob/master/src/sprite.c. ↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/field_player_avatar.c(PlayerWalkNormal,PlayerRun,PlayerWalkFastcom o comentário “same speed as running”,CheckMovementInputNotOnBikeeTURN_DIRECTION,PlayerTurnInPlace, a batida na parede), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/field_player_avatar.c. ↩↩↩↩↩ -
pret,
pokeemerald/src/fieldmap.c(GetBorderBlockAt: os metatiles de borda 2 por 2 do layout, marcados comoMAPGRID_IMPASSABLE), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/fieldmap.c. ↩↩↩ -
pret,
pokeemerald/src/field_camera.c(CameraUpdate;CameraPanningCB_PanAhead, que movesVerticalCameraPande 2 em 2 em direção a 72 ou a menos 8 a partir de um repouso de 32, protegido porgUnusedBikeCameraAheadPanbacke com o comentário “this code is never reached”), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/field_camera.c. ↩↩↩↩↩↩↩↩ -
Blake Crosley, “Pixel-Art Structures: Houses, Halls and Interiors on iPhone,” blakecrosley.com, 3 de outubro de 2026 (o quadro de porta de Emerald mantido por cinco atualizações, cerca de 84 milissegundos; o
PlayerStepOutFromDoorde Red; o tremor do elevador de Emerald; a porta de 70 milissegundos e o warp de 220 milissegundos do Kiradex listados entre as lacunas), https://blakecrosley.com/blog/pixel-art-structures-on-iphone. ↩↩↩↩↩↩ -
Notas de pesquisa do autor para o post sobre estruturas, repositório do Kiradex (privado),
docs/research/structures/01-structures-in-the-canon.md, 3 de outubro de 2026, que davam os quadros de porta de Emerald como “4 ticks each (16 frames, about 0.27 s at 59.7 Hz)”; corrigidas pela leitura deAnimateDoorFrameemfield_door.capresentada neste post. ↩↩ -
Apple Developer Documentation, “preferredFrameRateRange” (
CADisplayLink; iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/quartzcore/cadisplaylink/preferredframeraterange. ↩↩↩ -
Apple Developer Documentation, “CADisableMinimumFrameDurationOnPhone” (chave do Information Property List; iOS 15.0, iPadOS 15.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/bundleresources/information-property-list/cadisableminimumframedurationonphone. ↩↩↩
-
Leitura do autor do repositório do Kiradex (privado) no commit
b1b78b1, 4 de outubro de 2026, apenas leitura:Kiradex/World/PlazaRig.swift(tilesPerSecond,framesPerSecond,runPace,walk(to:)e sua regra de corridasteps.count >= 6,advance, cujo passo de progresso édt * Self.tilesPerSecond * (walker.running ? Self.runPace : 1) / length,animate,place,fit, que divide os pixels de tela da visão por umpixelScaleinteiro,follow, que converte os pontos da dobra pordisplayScale / pixelScale, suaviza commin(1, dt * 6)e arredonda depois, e cujo clamp deixa a câmera passar da borda do mapa para a floresta só quando o Duo está meio aberto, porbeyondMargin, 12 tiles, enquantobuilddispõe a floresta independentemente da dobra,ripple, que troca o desenho da água do chão 4 vezes por segundo num mapa com água,openDoore seu comentário “at about Emerald’s four ticks a frame”),Kiradex/World/PlazaStage.swift(o tratamento de toques, o atraso de warp de 220 milissegundos, a assinatura deSceneEvents.Update, a troca de andar por.id),Kiradex/World/TileMap.swift(opathde oito direções, que pega o tile aberto de menor custo até ali, soma 1 ou √2 por passo e não usa nenhuma estimativa de distância até o destino, e cujos passos diagonais são ignorados a menos que as duas células laterais sejam transitáveis),Kiradex/World/WorldMap.swift(a folha de porta de um warp, “closed, half open, open”),openDoorcarregando essa folha comSpriteSheet.bundled(name, columns: 3, rows: 1)e mostrando a coluna 1 e depois a coluna 2,scripts/forge/kit.py(door_sheet(), “The three frames side by side, 48 × 32”),scripts/forge/town.pyescripts/forge/buildings.py(cada warp de porta da cidade fica na célula da porta na fileira de baixo de um prédio, e as células do prédio à esquerda, à direita e acima de cada porta são bloqueadas; verificado para as sete portas da cidade notown.jsonpublicado),Kiradex/Views/Card/CardViewer.swift(o.easeOut(duration: 0.25)do véu) escripts/forge/rig.py(os ciclos). A ausência deUIImpactFeedbackGenerator,sensoryFeedback,CHHaptic,CADisplayLink,preferredFrameRateRangeeCADisableMinimumFrameDurationOnPhoneé uma busca emKiradex/eproject.ymlem 4 de outubro de 2026, que não encontrou nenhum; a mesma busca não encontra nenhumMTKView,MTLRenderCommandEncoderouTouchControlleremKiradex/, então o mundo não tem render pass próprio. O recuo num toque no meio do passo é lido emwalk(to:), não capturado. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret, clones rasos de
pokered(commitd2704a6, 22 de setembro de 2026),pokecrystal(5beda23, 29 de setembro de 2026) epokeemerald(731ad5b, 1º de outubro de 2026), as descompilações comunitárias dos jogos lançados, https://github.com/pret. ↩ -
Martin Korth, GBATEK, “LCD Dimensions and Timings” (280.896 ciclos e 16,743 ms por quadro, “ca. 59.737 Hz”), acessado em 4 de outubro de 2026, https://problemkaputt.de/gbatek-lcd-dimensions-and-timings.htm; Pan Docs, “Rendering” (“One frame: 70224 dots @ 59.7 fps”), acessado em 4 de outubro de 2026, https://gbdev.io/pandocs/Rendering.html. O número 59,7275 é um cálculo do autor: 2^24 / 280.896, e 4.194.304 / 70.224. ↩↩↩↩↩
-
pret,
pokeemerald/src/bike.c(sMachBikeSpeedCallbacks,bikeFrameCounterlimitado a 2,AcroBikeTransition_MovingchamandoPlayerRideWaterCurrent, e a única atribuiçãogUnusedBikeCameraAheadPanback = FALSE), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/bike.c. ↩↩↩ -
Figuras desenhadas pelo script do autor
make_figures.py(comsvgkit.py), 4 de outubro de 2026, apenas a partir de dados em disco: as velocidades do cânone demeasure_gen3_motion.out.txtemeasure_gen12_motion.out.txt; a caminhada e a câmera do Kiradex do própriosimulate()demeasure_kiradex_motion.py(o primeiro segundo de uma caminhada de 5 tiles; a câmera para uma caminhada de 5 tiles a 60 e 120 Hz e uma corrida de 12 tiles a 60 Hz), e o loop de ticks da proposta do mesmo script; o fade de Emerald dorun()demeasure_gen3_fade.py, o port com o cronograma de quem o chama, e o fade e o carregamento de mapa mais cedo do gráfico da porta da mesma fonte; as temporizações de porta do Kiradex (70 ms, 1.400 ms, 220 ms) lidas emPlazaRig.swiftePlazaStage.swiftemb1b78b1. Nenhuma arte dos jogos é usada. ↩↩↩↩↩ -
Blake Crosley, “Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew,” blakecrosley.com, 3 de outubro de 2026 (a caminhada de Emerald “step, stand, step, stand at eight ticks each”; o “stand, step, stand, step-flipped” de Red; Celeste a 320 por 180 multiplicado por seis; o motor de RealityKit; posições “rounded to whole world units each frame, after easing”; a lição de opacidade do visualizador de cartas), https://blakecrosley.com/blog/pixel-art-world-on-iphone. ↩↩↩↩↩↩↩↩
-
Itay Keren, “Scroll Back: The Theory and Practice of Cameras in Side-Scrollers,” Game Developer, 11 de maio de 2015, uma versão modificada da sua palestra no Independent Games Summit, GDC 2015 (position-locking, edge-snapping, camera-window, lerp-smoothing, target-focus, dual-forward-focus; as citações), acessado em 4 de outubro de 2026, https://www.gamedeveloper.com/design/scroll-back-the-theory-and-practice-of-cameras-in-side-scrollers. ↩↩↩↩↩↩↩↩
-
pret,
pokeemerald/src/field_door.c(sDoorOpenAnimFrames{4, -1}, {4, 0}, {4, 0x100}, {4, 0x200};AnimateDoorFrame, que desenha com o contador em 0 e avança quando o contador se iguala ao tempo do quadro), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/field_door.c. ↩↩↩↩ -
pret,
pokeemerald/src/field_screen_effect.c(os estados deTask_DoDoorWarp, o quinto chamandoWarpFadeOutScreene passando a tarefa paraTask_WarpAndLoadMap, que esperaPaletteFadeActive(), isto é,gPaletteFade.active, eBGMusicStopped()antes deWarpIntoMap;Task_ExitDoor,WarpFadeOutScreenchamandoGetMapPairFadeToType,WarpFadeInScreenchamandoGetMapPairFadeFromType, eFadeInFromWhitecomFadeScreen(FADE_FROM_WHITE, 8)), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/field_screen_effect.c. ↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/palette.c(BeginNormalPaletteFadecomdeltaY = 2, sua própria chamada aUpdatePaletteFade, seuCpuCopy32para a memória de paletas esPlttBufferTransferPending = FALSE;UpdatePaletteFade, que retorna na hora enquanto essa flag está ativa e a ativa a partir degPaletteFade_selectedPalettesdepois de cada atualização;TransferPlttBuffer, que a limpa;UpdateNormalPaletteFade;IsSoftwarePaletteFadeFinishing), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/palette.c. ↩↩ -
pret,
pokeemerald/src/fldeff_flash.c(sTransitionTypescom suas 16 linhas de e paraMAP_TYPE_UNDERGROUND, cada uma com uma flag de entrada, uma flag de saída e uma rotina de transição;GetMapPairFadeToTyperetornando a flag de entrada de uma linha eGetMapPairFadeFromTypesua flag de saída), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/fldeff_flash.c. ↩↩↩ -
pret,
pokeemerald/src/field_specials.c(ShakeCamera, que lê o deslocamento vertical, o deslocamento horizontal, o número de tremores e o atraso deVAR_0x8004aVAR_0x8007, e inverte o deslocamento a cada tremor), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/field_specials.c. ↩ -
Contagem do autor, 4 de outubro de 2026:
measure_gen3_shake.py, na pasta de evidências do autor para este post, lê cada chamadaspecial ShakeCamerae os quatro valoressetvarque a antecedem emdata/**/*.incnopokeemeralddo pret em731ad5b. Saída salva comomeasure_gen3_shake.out.txt(24 chamadas em 9 arquivos, os oito conjuntos de parâmetros e suas contagens na tabela). ↩↩↩↩↩↩ -
pret,
pokered/home/overworld.asm(OverworldLoop,OverworldLoopLessDelay,wWalkCounter,AdvancePlayerSprite,DoBikeSpeedup, e o comentário da virada de 180 graus), commitd2704a6, acessado em 4 de outubro de 2026, https://github.com/pret/pokered/blob/master/home/overworld.asm. O comportamento da virada é lido no código, não executado num emulador. ↩↩↩↩↩↩ -
pret,
pokered/engine/overworld/movement.asm(UpdatePlayerSprite, o contador interno da animação que avança o desenho ao chegar a 4), acessado em 4 de outubro de 2026, https://github.com/pret/pokered/blob/master/engine/overworld/movement.asm. ↩↩ -
pret,
pokered/home/fade.asm(GBFadeOutToBlack) ehome/overworld.asm(PlayMapChangeSound,SFX_GO_INSIDEpara o tile de porta$0b, senãoSFX_GO_OUTSIDE), acessado em 4 de outubro de 2026, https://github.com/pret/pokered/blob/master/home/fade.asm. ↩ -
pret,
pokecrystal/engine/overworld/events.asm(MaxOverworldDelay: db 2) eengine/overworld/map_objects.asm(StepVectors;StepFunction_Turn, cujos.init1,.step1,.init2e.step2caem uns nos outros, eObjectStep_AnonJumptable), commit5beda23, acessados em 4 de outubro de 2026, https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm e https://github.com/pret/pokecrystal/blob/master/engine/overworld/map_objects.asm. As três atualizações da virada são uma reprodução da rotina instrução por instrução feita pelo autor, não uma execução num emulador. ↩↩↩↩↩ -
pret,
pokecrystal/data/maps/setup_scripts.asm(MapSetupScript_Doorcomeçando comFadeOutToWhite,MapSetupScript_Warpterminando comFadeInFromWhite) eengine/tilesets/timeofday_pals.asm, acessados em 4 de outubro de 2026, https://github.com/pret/pokecrystal/blob/master/data/maps/setup_scripts.asm e https://github.com/pret/pokecrystal/blob/master/engine/tilesets/timeofday_pals.asm. ↩ -
Blake Crosley, “Pixel-Art People: Characters and a Creator on iPhone,” blakecrosley.com, 3 de outubro de 2026 (a caminhada de seis quadros; “the walk at eight frames a second” na praça; em “Desde a publicação”, a figura de 26 pixels substituída na build 34 do TestFlight por uma figura de 30 pixels numa célula de 32 por 40, que
scripts/forge/rig.pyemb1b78b1define comoCELL_W, CELL_H = 32, 40), https://blakecrosley.com/blog/pixel-art-characters-on-iphone. ↩↩↩ -
Os desenvolvedores de Celeste, o repositório
NoelFB/Celesteno GitHub,Source/Player/Player.cs(a atualização da câmera sob “Camera (lerp by distance using delta-time)” e o getterCameraTarget), acessado em 4 de outubro de 2026, https://raw.githubusercontent.com/NoelFB/Celeste/master/Source/Player/Player.cs. ↩↩↩↩ -
Os desenvolvedores de Celeste, repositório
NoelFB/Celeste,README.md(os arquivos de classe publicados “as a learning resource and for general interest”; a licença MIT aplicada só a esse código), acessado em 4 de outubro de 2026, https://raw.githubusercontent.com/NoelFB/Celeste/master/README.md. ↩ -
Stardew Valley Wiki, “Speed” (a velocidade base do jogador: 2 andando, 5 correndo, 6,6 a cavalo, 7 depois de uma cenoura), acessado em 4 de outubro de 2026, https://stardewvalleywiki.com/Speed. ↩
-
Notas de pesquisa do autor para este post, reunidas em 4 de outubro de 2026, registrando o que foi procurado e não encontrado: uma fonte primária sobre as câmeras de Sea of Stars, Eastward ou CrossCode; uma palestra ou artigo de Maddy Thorson sobre câmera; o esquema exato de toque dos Pixel Remasters; o movimento de Terraria no mobile em https://terraria.wiki.gg/wiki/Mobile_version; e
developer.apple.com/documentation/touchcontrols, que retornou 404. ↩↩↩ -
Stardew Valley Wiki, “Mobile Controls” (os esquemas; “Tap-to-move & Auto-Attack” como padrão; as citações sobre tocar para mover, seguir o toque, o joystick invisível e os limites dos controles padrão), acessado em 4 de outubro de 2026, https://stardewvalleywiki.com/Mobile_Controls. ↩↩↩↩↩↩↩
-
Jared Nelson, “’Stardew Valley’ is Getting A TON of New Control Options in the Next Update,” TouchArcade, 1º de novembro de 2018, acessado em 4 de outubro de 2026, https://toucharcade.com/2018/11/01/stardew-valley-mobile-controls-update/. ↩
-
App Store, “FINAL FANTASY” da SQUARE ENIX, histórico de versões (versão 1.2.0, 03/11/2025, e a nota sobre o movimento por toque), acessado em 4 de outubro de 2026, https://apps.apple.com/us/app/final-fantasy/id1492041278. ↩↩
-
Mikhail Madnani, TouchArcade, 30 de janeiro de 2024, sobre a atualização de Final Fantasy Pixel Remaster que trouxe suporte a controles para o mobile, acessado em 4 de outubro de 2026, https://toucharcade.com/2024/01/30/final-fantasy-pixel-remaster-mobile-controller-support-update-boosts-cheats-font-not-fixed-steam-deck-patch-notes/. ↩
-
Apple Human Interface Guidelines, “Game controls” (boas práticas de controle por toque; entrada do registro de alterações de 9 de junho de 2025), acessado em 4 de outubro de 2026, https://developer.apple.com/design/human-interface-guidelines/game-controls. ↩↩↩↩↩↩↩
-
Apple, sessão 209 da WWDC25 (o framework Touch Controls; “the vast majority of players won’t have a controller available”; “integrates directly with Metal”), transcrição acessada em 4 de outubro de 2026, https://developer.apple.com/videos/play/wwdc2025/209/. ↩↩↩
-
Apple Human Interface Guidelines, “Playing haptics” (boas práticas, háptica personalizada e a categoria de impacto do iOS; as citações), acessado em 4 de outubro de 2026, https://developer.apple.com/design/human-interface-guidelines/playing-haptics. ↩↩↩↩↩↩↩↩↩
-
Apple Developer Documentation, “CAFrameRateRange” (iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, visionOS 1.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/quartzcore/caframeraterange. ↩
-
Apple Developer Documentation, “targetTimestamp” (
CADisplayLink; iOS 10.0, iPadOS 10.0, Mac Catalyst 13.1, visionOS 1.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/quartzcore/cadisplaylink/targettimestamp. ↩ -
Apple, sessão 10147 da WWDC21, “Optimize for variable refresh rate displays” (telas Adaptive-Sync no Mac e ProMotion no iPad Pro; as citações), transcrição acessada em 4 de outubro de 2026, https://developer.apple.com/videos/play/wwdc2021/10147/. ↩↩↩
-
Apple Developer Documentation, “SceneEvents.Update” (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/realitykit/sceneevents/update. ↩
-
Apple Developer Documentation, “deltaTime” (
SceneEvents.Update; iOS 13.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/realitykit/sceneevents/update/deltatime. ↩ -
Apple Developer Documentation, “RealityView” (iOS 18.0, iPadOS 18.0, Mac Catalyst 18.0, visionOS 1.0; código por quadro por meio de um
Systemou deSceneEvents.Update), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/realitykit/realityview. ↩ -
Apple Developer Documentation, “CHHapticPattern” (Core Haptics; iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, visionOS 1.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/corehaptics/chhapticpattern; página do framework Core Haptics, https://developer.apple.com/documentation/corehaptics. ↩
-
Apple Developer Documentation, “CHHapticEvent” e “CHHapticEvent.EventType” (
hapticTransient,hapticContinuous; iOS 13.0), acessados em 4 de outubro de 2026, https://developer.apple.com/documentation/corehaptics/chhapticevent e https://developer.apple.com/documentation/corehaptics/chhapticevent/eventtype. ↩ -
Apple Developer Documentation, “CHHapticEvent.ParameterID” (iOS 13.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/corehaptics/chhapticevent/parameterid. ↩
-
Apple Developer Documentation, “CHHapticEngine” (iOS 13.0;
capabilitiesForHardware()), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/corehaptics/chhapticengine. ↩↩ -
Apple Developer Documentation, “UIImpactFeedbackGenerator” (iOS 10.0, iPadOS 10.0, Mac Catalyst 13.1;
init(style:view:)em “Initializing the feedback generator”, einit(style:)entre os itens obsoletos), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator. ↩↩ -
Apple Developer Documentation, “UIImpactFeedbackGenerator.FeedbackStyle” (iOS 10.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/feedbackstyle. ↩
-
Apple Developer Documentation, “impactOccurred(intensity:)” (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.1), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/impactoccurred(intensity:). ↩
-
Apple Developer Documentation, “prepare()” (
UIFeedbackGenerator; iOS 10.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/uikit/uifeedbackgenerator/prepare(). ↩↩ -
Apple Developer Documentation, “sensoryFeedback(:trigger:)” e “SensoryFeedback” (SwiftUI; iOS 17.0, iPadOS 17.0, Mac Catalyst 17.0, visionOS 26.0;
impact(weight:intensity:)), acessados em 4 de outubro de 2026, https://developer.apple.com/documentation/swiftui/view/sensoryfeedback(:trigger:) e https://developer.apple.com/documentation/swiftui/sensoryfeedback. ↩ -
Apple Developer Documentation, “Touch Controller” (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, visionOS 26.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/touchcontroller. ↩↩↩↩
-
Apple Developer Documentation, “TCDirectionPad” (iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/touchcontroller/tcdirectionpad. ↩
-
Apple Developer Documentation, “GCVirtualController” (Game Controller; iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0), acessado em 4 de outubro de 2026, https://developer.apple.com/documentation/gamecontroller/gcvirtualcontroller. ↩
-
pret,
pokeemerald/src/field_control_avatar.c(TryDoorWarp, chamado com a célula à frente do jogador enquanto a direção mantida coincide com a orientação, e que só faz o warp quando essa direção é o norte e a célula é um warp de porta), acessado em 4 de outubro de 2026, https://github.com/pret/pokeemerald/blob/master/src/field_control_avatar.c. ↩