1 pontos por GN⁺ 2024-05-09 | 1 comentários | Compartilhar no WhatsApp

Notas de desenvolvimento de "Machine" do xkcd

Ideia inicial

  • A ideia foi pensada até o fim de março e decidida no começo de abril
  • "Daria para criar um dispositivo gigante em mosaico, como os GIFs de blueball feitos por usuários do Something Awful? Todo mundo contribuiria com um pequeno quadrado"
  • No começo, parecia que a ideia já estava totalmente formada, mas, ao conversar de fato sobre ela, perceberam que ainda havia muitas decisões a tomar
  • Tinham visões diferentes sobre partes centrais, como de onde as bolas saem, se todos veem a mesma máquina, qual é o objetivo e como os jogadores interagem

Lições aprendidas com tentativas anteriores

  • Já havia experiência na criação de quadrinhos interativos centrados em conteúdo feito por usuários
    • Lorenz: um exquisite corpse em que leitores escreviam o texto dos painéis para desenvolver piadas e a história (foi muito divertido)
    • Collector's Edition: um jogo em que leitores encontravam adesivos escondidos no arquivo do xkcd e os colavam permanentemente em uma tela compartilhada (não produziu o resultado esperado)
      • Começar com um mapa central vazio no início levava ao caos
      • Faltavam incentivos para posicionar os adesivos, então era difícil que ações individuais avançassem o enredo, e só surgiam padrões simples
      • Não havia uma história geral nem um objetivo, e a relação entre os adesivos também era pouco clara
  • Para uma tela coletiva dar certo, é preciso ensinar por exemplo o que pode ser criado e haver contexto e objetivo compartilhados

Projeto das restrições

  • Depois de decidir criar uma grande máquina de queda de bolinhas, surgiram escolhas demais
  • Decidiram usar uma grade de 100x100
    • Parecia arriscado simular 10 mil tiles em tempo real no cliente
    • Não havia certeza de como jogadores, sem comunicação direta, poderiam criar subseções de uma máquina complexa que funcionassem quando tiles separados fossem integrados
  • Após vários experimentos mentais, definiram 3 princípios centrais:

1. Maximizar a expressividade dos jogadores, mesmo sacrificando a precisão

  • Até que ponto a máquina deveria ser previsível?
    • Consideraram executar tudo no servidor ou validar tiles individuais, mas confirmaram no editor de protótipo que era fácil criar padrões caóticos de colisão de bolinhas
    • Se as bolas não se movessem em linha reta sem interferência, era fácil criar máquinas imprevisíveis
  • Tornar a máquina mais previsível entrava em conflito com a liberdade dos jogadores
    • O prazo apertado de desenvolvimento também favoreceu uma abordagem com menos previsão/simulação
  • Decidiram dar aos jogadores uma liberdade de criação muito flexível, incluindo máquinas extremamente não determinísticas ou quebradas
    • Isso exigia revisão ativa para verificar se as restrições eram cumpridas e remover conteúdo impróprio

2. Fornecer restrições rígidas que incentivem máquinas compatíveis e intercambiáveis

  • A aceitação por revisão e as máquinas imprevisíveis dos jogadores acabaram exigindo ainda mais ordem
  • No início, pensaram em entradas e saídas totalmente livres, mas perceberam no processo de revisão que, se fosse preciso substituir tiles iniciais, isso poderia causar grandes falhas em cadeia
  • Projetaram restrições fortes o bastante para que vários jogadores pudessem criar designs compatíveis no mesmo espaço de tile
    • Aplicaram o princípio da Robustez: "seja conservador no que envia, seja tolerante no que recebe"
  • Para impor restrições de entrada e saída, precisavam de um mapa da máquina inteira desde o início
    • A geração do mapa também permitia ajustar a dificuldade da máquina (de simples 1 entrada/1 saída até mesclas complexas de 4 entradas/4 saídas)
  • Para dar feedback em tempo real, limitaram cada tile a expelir bolinhas numa velocidade parecida com a que recebe
    • Restringiram máquinas que engolem ou atrasam bolinhas
    • Submeteram os tiles a testes caóticos com velocidades de entrada aleatórias
  • Estabeleceram o princípio de "rodar a máquina por um tempo e verificar se, em média, ela cumpre as restrições"

3. A máquina deve alcançar estado estável nos primeiros 30 segundos

  • Surgiu a pergunta de quanto tempo um revisor precisaria observar
    • Calcularam o tempo de revisão da máquina inteira (83,3 horas para 10 mil tiles)
    • Decidiram arbitrariamente que ela deveria entrar em estado estável em até 30 segundos
  • Configuraram as bolinhas para desaparecerem após 30 segundos
    • No início, não havia tempo de expiração, então, enquanto os jogadores aprendiam o jogo, as bolinhas se acumulavam e enchiam a tela
    • Com muitos corpos rígidos ativos, a simulação física ficava mais lenta
    • As bolinhas acabavam mais atrapalhando do que divertindo
  • A expiração das bolinhas impediu que a máquina acumulasse erros ao longo do tempo
    • O revisor só precisava observar 30 segundos para entender, em grande parte, para onde as bolinhas poderiam ir

Simulação e surrealismo

  • Dois grandes desafios da arquitetura de Machine:
    1. Será que, com essas restrições de projeto, tiles heterogêneos conectados em uma máquina inteira funcionariam?
      • Validaram isso gerando e resolvendo alguns mapas pequenos
    2. Se não fosse possível executar a máquina gigante em tempo real nem no servidor nem no cliente, como exibi-la?

O objetivo era permitir rolar a tela acompanhando uma única bolinha

  • Mesmo que a máquina inteira não fosse simulada, a área ao redor da região vista pelo jogador precisava ser simulada
  • No começo, testaram simular apenas a área visível em um mapa infinito
    • Funcionou bem, mas, ao rolar a tela, os tiles entravam na simulação em um estado inicial vazio, criando lacunas no fluxo
  • Em vez de tiles vazios, eles precisavam parecer já estar em atividade

Segundo desafio: tirar snapshots dos tiles só depois de alcançarem estado estável, para que só existam pouco antes de aparecer com a rolagem

  • Na versão final do quadrinho, a visualização com clipping de display desativado (CSS overflow:hidden, contain:paint desativado):
    • Você percebeu os snapshots? Sem prestar muita atenção, é difícil notar
    • Só os tiles renderizados existem na simulação física
    • Otimização de display: só as bolinhas dentro da área de visualização aparecem, mas a simulação roda em toda a extensão do tile
    • Para simular o topo da máquina, geravam e alimentavam bolinhas na linha mais alta da simulação (com base na velocidade esperada pelas restrições de entrada)
  • Integraram a geração de snapshots à UI de revisão
    • O revisor precisava esperar pelo menos 30 segundos antes de aprovar um tile
    • Ao clicar no botão de aprovar, era gerado um snapshot
    • O revisor também podia, a seu critério, esperar um pouco mais até a máquina parecer boa
  • A abordagem com snapshots funcionou melhor do que o esperado
    • Teve o efeito positivo de resetar erros acumulados na máquina
    • A primeira impressão dos tiles vistos com a rolagem era um estado limpo e bonito que agradou ao revisor
    • Na prática, se observadas por muito tempo, muitas máquinas poderiam falhar ou quebrar, mas isso nunca era visto porque, ao continuar explorando, entrava-se em novos snapshots
  • A máquina que aparece rolando no quadrinho não é real. É surreal
    • O conjunto não é simulado de uma vez só, mas isso acabou produzindo um resultado melhor

Renderizando milhares de bolinhas com React e DOM

  • Construído com base no motor físico Rapier
    • A boa documentação, a API limpa com componentes básicos úteis e a implementação em Rust (executada como WASM no navegador) entregaram um desempenho impressionante
    • No início, a garantia de determinismo do Rapier chamou atenção, mas não houve simulação no lado do servidor
  • Escreveram um contexto React personalizado <PhysicsContext> sobre o Rapier
    • Ele cria objetos físicos do Rapier e os gerencia dentro do ciclo de vida de componentes React
    • Facilitou desenvolver componentes "widget" para cada objeto posicionável com física ou superfícies de colisão
    • O React serviu como um scene graph simples e meio improvisado
    • Também simplificou carregar/descarregar tiles ao rolar a visualização: ao desmontar um tile, toda a física e o DOM eram limpos
    • Como bônus, ficou fácil conectar hot reloading ao fast refresh (ótimo para ajustar formas de colisão)
  • Outra vantagem da abordagem com contexto React:
    • Se um hook de física não estiver dentro de <PhysicsContext>, ele vira noop
    • Isso foi usado para renderizar prévias estáticas de tiles na UI de revisão
  • Teria sido melhor usar componentes em vez de hooks para criar objetos Rapier (como faz o react-three-rapier)
    • Isso se encaixa melhor no diffing do React (quando dependências mudam, useEffect remove a instância anterior e cria outra)
  • Machine é renderizado inteiramente com DOM
    • No início do desenvolvimento, havia preocupação de atingir o limite de desempenho da renderização em DOM
    • Se ficasse lento demais, a expectativa era migrar para PixiJS ou canvas, mas queriam ver até onde o DOM conseguia ir
  • Otimização de desempenho de renderização:
    • O loop de frames aplica estilos diretamente aos widgets que têm simulação física
      • O diff do React só roda quando há mudanças estruturais no scene graph
    • No início, as bolinhas eram renderizadas com React

1 comentários

 
GN⁺ 2024-05-09
Comentários do Hacker News

Reunindo vários comentários, o conteúdo pode ser resumido assim:

  • "The Incredible Machine" do XKCD, evento de 1º de abril, foi um jogo de quebra-cabeça colaborativo que aconteceu durante um único dia, em 1º de abril
    • Os usuários podiam resolver quebra-cabeças usando elementos mecânicos implementados com um motor de física e também criar e enviar seus próprios quebra-cabeças
    • Mas parece que muitos usuários ficaram confusos por falta de explicações sobre como a experiência funcionava
  • O formato do jogo de quebra-cabeça é semelhante ao antigo jogo de DOS "The Incredible Machine"
    • A ideia é atingir um objetivo específico usando um conjunto limitado de peças mecânicas
  • No processo de desenvolvimento, foi usado o motor de física Rapier, mas também houve crashes causados por erros recursivos
  • Após o fim do evento, surgiu a sugestão de haver uma função de permalink para compartilhar os quebra-cabeças criados pelos usuários
    • Como isso poderia ser difícil por causa de armazenamento, foi proposta a ideia de codificar o JSON em Base64 e passá-lo como parâmetro na URL
  • Houve também a avaliação de que concluir um projeto desse porte em apenas 3 semanas foi um grande feito
  • Algumas pessoas acharam curioso, por pensarem que o XKCD era tocado apenas por Randall Munroe, mas parece que várias pessoas participaram
  • Informações mais detalhadas sobre o evento podem ser encontradas no Reddit, no Explain XKCD e no repositório do Github