Communitygithub.com

RedCarpG/animation-capture

Grava frames de animações em intervalos fixos, gerando GIFs comparativos lado a lado ou tabelas quadro a quadro para revisão, comparação antes/depois e documentação.

O que é animation-capture?

Esta skill captura animações web quadro a quadro com tempo controlado, garantindo que cada frame corresponda exatamente ao mesmo instante, independentemente da velocidade de renderização. Ela funciona injetando um "relógio falso" que só avança quando comandado, substituindo `performance.now`, `Date.now`, `requestAnimationFrame` e timers. Isso permite comparar duas variantes (ex.: com/sem física) frame a frame, mesmo que a máquina renderize lentamente. A skill suporta CSS transitions/animations, `element.animate()`, JavaScript com rAF, canvas, three.js/VRM, PixiJS e Live2D sem modificar o código da página. Inclui scripts: `record.mjs` (Chrome headless + relógio falso), `make_gif.py` (monta GIFs lado a lado com paleta compartilhada) e `clock-shim.js`. O processo típico é: 1) registrar valores numéricos quadro a quadro para verificar a lógica, 2) capturar os frames como GIF para aprovação visual. A skill também documenta armadilhas conhecidas (tempo virtual do Chrome não confiável, Vite recarregar na primeira abertura, Live2D exigir carregamento prévio da runtime, etc.) e boas práticas de licenciamento de assets de terceiros. Use esta skill sempre que o usuário pedir para "gravar um GIF", "comparar animação antes/depois", "capturar movimento" ou "validar efeitos de animação".

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/RedCarpG/redcarpg-skills/tree/HEAD/skills/animation-capture

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Capture Animação como GIF Comparativo

Problemas e melhorias em animações não são visíveis em uma única captura de tela, e descrevê-los verbalmente não permite que o usuário julgue. É preciso gravar: a mesma animação, antes e depois da modificação (ou ligado/desligado), quadro a quadro ao longo da mesma linha do tempo, lado a lado em um GIF. Assim, o usuário decide rapidamente.

O princípio central é: nós controlamos o tempo, não o relógio real do sistema. Navegadores headless renderizam lentamente (especialmente com renderização por software), podendo produzir apenas alguns quadros por segundo. Usando o tempo real, cada quadro pararia em uma posição diferente, e os lados esquerdo e direito nunca se alinhariam. Portanto, fazemos o tempo da página avançar apenas quando dizemos "avançar 40 ms", capturamos um quadro, avançamos novamente, capturamos outro, e assim por diante. Dessa forma, não importa o quão lenta seja a máquina: o n-ésimo quadro será sempre n × 40 ms, as duas variantes se alinham quadro a quadro, e gravar a mesma variante duas vezes produz resultados idênticos (testado: 75 quadros, todos byte a byte iguais).

1. Planeje: GIF para humanos, números para verificação

GIFs são ótimos para aprovação, comparação e documentação, mas não para depuração. Já aconteceu: em uma animação de levantar a mão lentamente, o joelho ficou flexionado 18° por um segundo — isso foi descoberto registrando ângulos quadro a quadro, não visível no GIF. Portanto:

  1. Primeiro, registre valores numéricos chave quadro a quadro (ângulos, posição, altura, transparência, deslizamento do pé), uma vez para cada variante (ligado/desligado), e verifique os números.
  2. Depois que os números estiverem corretos, grave o GIF para aprovação visual.

O método para registrar valores numéricos está em references/hook.md (escreva uma função de captura na página que coleta os valores de cada quadro junto com a imagem).

2. Escolha o relógio

RelógioQuando usarPrecisa modificar a página?
shim (padrão)Quase sempre: CSS transitions, CSS animations, element.animate(), animações JS com rAF, canvas, three.js, PixiJS, Live2DNão. clock-shim.js é injetado antes dos scripts da página, substituindo performance.now, Date.now, requestAnimationFrame, setTimeout/setInterval; Math.random usa semente fixa. Animações CSS/WAAPI são pausadas e avançadas para o mesmo instante a cada quadro.
hookQuando você precisa ler valores do modelo durante a captura (valores numéricos quadro a quadro), ou quando a aplicação já possui um loop que avança em passos fixos e você quer evitar a captura de tela do navegador.Sim: escreva window.__capture(...) na página, conforme references/hook.md.

Não use o tempo virtual nativo do Chrome (Emulation.setVirtualTimePolicy): testado, o tempo recebido pelo rAF não correspondia ao performance.now (contadores ficavam negativos), e as animações não seguiam o passo determinado.

O que o relógio falso NÃO controla: animações em Web Workers, OffscreenCanvas em workers, elementos <video>, new Date() (apenas Date.now é substituído), crypto.getRandomValues. Se encontrar algum desses, use hook ou substitua-os na página de teste.

3. Passos

  1. Prepare a página e as variantes. Idealmente, use parâmetros de URL para alternar entre variantes (?v=off, ?v=on); a página lê o parâmetro e decide o estado. Alternativamente, use --setup para executar um JS que alterna antes de começar a gravar. Itens que só funcionam localmente (modelos de terceiros, páginas de teste) devem ficar fora do git e ser removidos após o uso.
  2. Inicie a página. Arquivos locais: use file:///…. Para páginas que precisam de bundler (Vite, etc.), inicie um servidor de desenvolvimento em uma porta livre (certifique-se de não interferir em outras sessões na mesma máquina). O Vite otimiza dependências e recarrega a página na primeira abertura — se isso acontecer durante a gravação, um erro do tipo "window.xxx é undefined" ocorrerá: abra a página uma vez no navegador para pré-aquecer, ou simplesmente execute novamente após o erro.
  3. Grave.
    node <skill>/scripts/record.mjs --out frames --fps 25 --seconds 3 --clip "#stage" \
      --ready "window.model != null" --setup "window.model.motion('TapBody', 0)" \
      --variant "off=http://127.0.0.1:5173/lab.html?v=off" --variant "on=http://127.0.0.1:5173/lab.html"
    
    • --clip: captura apenas aquela região (seletor CSS); se omitido, captura a janela inteira (use --width, --height para definir o tamanho).
    • --ready: expressão que indica que o modelo/textura foi carregado. Enquanto espera, o relógio falso não avança: se avançasse, a velocidade de carregamento variaria, e o instante inicial e os números aleatórios seriam diferentes, fazendo com que duas gravações não se alinhassem (testado). Para páginas que dependem de setTimeout para carregar, adicione --ready-advance 1.
    • --wait: quanto tempo falso (ms) avançar depois que a página estiver pronta, para permitir que animações de entrada e molas estabilizem. --setup é executado após essa espera, e a gravação começa imediatamente. Dentro de --setup você pode usar await; o valor retornado é descartado (objetos three.js são grandes demais para serem transferidos via CDP).
    • O script inicia seu próprio Chrome headless (diretório de configuração separado) e o fecha ao final.
    • Por padrão, usa renderização por GPU (--render gpu). No início, ele exibirá "Renderização WebGL: …". Se aparecer "SwiftShader", significa que a GPU não está sendo usada; a CPU será totalmente consumida (vários núcleos), especialmente ao gravar three.js/VRM — o Chrome ficará ocupando CPU constantemente. Em máquinas sem GPU, ou ao comparar com gravações anteriores feitas com renderização por software, use --render cpu.
    • A renderização por GPU e por software produzem bordas diferentes. Compare quadros apenas dentro do mesmo tipo de renderização.
  4. Primeiro veja a tabela de comparação, depois gere o GIF.
    python <skill>/scripts/make_gif.py frames --variants off,on --fps 25 --width 300 --title "Hiyori · Carinho" --sheet sheet.png --out compare.gif
    
    --sheet gera uma imagem PNG com alguns quadros (padrão: 4 quadros uniformemente espaçados; use --pick 0,10,20 para especificar): você mesmo lê esta imagem primeiro para confirmar que a animação está funcionando, os lados estão alinhados e o enquadramento não cortou nada. Depois, entregue o GIF ao usuário. O GIF usa uma única paleta de cores para todos os quadros, evitando oscilações de cor. Os rótulos são escritos em uma faixa branca acima da imagem, sem sobrepor o conteúdo.
  5. Entregue ao usuário: envie o GIF por um meio que suporte arquivos (envie algumas imagens representativas; informe onde estão as demais). Explique claramente o que está à esquerda e à direita, qual versão foi gravada e o que não foi verificado (por exemplo, "usando renderização por software, bordas ligeiramente mais grossas que com GPU").

4. Como definir os parâmetros

  • 25 quadros por segundo, 2–4 segundos por animação; 300 pixels de largura por quadro. Um GIF com dois quadros lado a lado terá aproximadamente 0.6–3 MB (maior para personagens e gradientes).
  • A duração da animação deve corresponder à ação em si: no modo hook, use o comprimento do trecho mais um pequeno acréscimo (a inércia após a parada também deve ser capturada).
  • Enquadre pela bounding box: para personagens 3D, use Box3.setFromObject(scene) para calcular até o topo da cabeça — calcular apenas pelo crânio pode cortar capacetes grandes e fios de cabelo (testado). Deixe também um espaço abaixo dos pés.
  • Cada variante deve ser idêntica em tudo, exceto no aspecto que está sendo comparado: mesmo modelo, mesma câmera, mesma ação, mesma espera. Partes aleatórias são tratadas pelo relógio falso com semente fixa, mas se a página chamar Math.random um número diferente de vezes em cada variante, as sequências subsequentes ficarão dessincronizadas — esteja ciente disso.

5. Abordagens para diferentes tipos de página

  • Páginas web (CSS, WAAPI, rAF): nenhuma modificação necessária. Para capturar interações (abrir menu, hover), dispare-as em --setup (adicionando classes, dispatchEvent). Testado: contadores rAF, CSS transitions com gatilho após 50 ms, animações CSS keyframe, element.animate() funcionam corretamente com o relógio falso; variantes com curvas de easing diferentes se alinham quadro a quadro.
  • PixiJS / Live2D: nenhuma modificação necessária. O ticker do PixiJS usa rAF e performance.now, então segue o relógio falso; a física também é calculada com esse tempo. A runtime Cubism do Live2D (live2dcubismcore.min.js) deve ser carregada via <script> comum ANTES dos módulos que a importam, caso contrário, o motor reclamará "Could not find Cubism runtime" ao ser importado. Testado: variantes com e sem física do Hiyori, duas gravações de 75 quadros, byte a byte idênticas.
  • three.js / VRM: nenhuma modificação necessária. O loop rAF segue o relógio falso (testado: páginas de personagens VRM de mesa funcionam diretamente; diferenças entre ligado/desligado são capturadas). Para ler ângulos de ossos quadro a quadro, use hook (veja references/hook.md).
  • Jogos, outras aplicações canvas: o mesmo princípio se aplica. Se a aplicação usa setTimeout para o loop do jogo, também estará sob o controle do relógio falso.

6. Licenciamento e privacidade

  • GIFs gerados a partir de modelos e assets de terceiros devem respeitar suas licenças: aqueles que proíbem redistribuição (ex.: alguns termos da VN3) só podem ser vistos localmente ou mostrados para efeito de avaliação — não os publique em repositórios públicos ou divulgue amplamente. Se a licença permitir uso mediante atribuição, inclua o crédito do autor ao publicar.
  • Armazene GIFs e quadros fora do git (pasta local dedicada no projeto ou diretório temporário). Remova as páginas de teste após o uso.

7. Relação com outras skills

  • Captura de tela estática, múltiplos viewports, modo escuro: frontend-verification (o shot.mjs dela usa tempo real; para animações, use esta skill).
  • Vídeos promocionais com música para o público: motion-showreel.
  • Como tornar a animação de personagens mais natural (camadas de movimento, física, trechos de ação): live2d-vrm-animation.

8. Origem e manutenção

  • Origem: Cirra (personagem de mesa, RedCarpG/cirra-deskpet) — em 2026-11-10, ao trabalhar com camadas de movimento, quadril e pernas, e balanço de cabelo em personagens 3D, precisei criar comparações visuais de antes/depois para o usuário. As técnicas foram documentadas em docs/vrm-motion.md §11–§12, docs/vrm-spring-force.md §8 daquele repositório ("Como verificar"). As lições estão registradas em um repositório privado: lessons/animation-capture.md (a parte de personagens 3D também está em lessons/threejs-vrm-animation.md).
  • Repositório fonte: RedCarpG/redcarpg-skills, pasta skills/animation-capture/. A cópia em ~/.claude/skills/ é uma instalação; não a modifique diretamente.
  • Como modificar:
    • Faça alterações no repositório redcarpg-skills, via PR; incremente a versão em manifest.json, execute node scripts/validate.mjs e, após aprovação, crie uma tag (animation-capture-v<versão>). Em seguida, reinstale a cópia local.
    • Após alterar os scripts, grave uma animação em uma página CSS, uma Live2D e uma three.js; verifique se as duas variantes se alinham quadro a quadro e se gravar a mesma variante duas vezes produz quadros idênticos (use ImageChops.difference do Pillow para comparar).
    • Se alterar o método ou descobrir novas armadilhas, atualize os arquivos de lições correspondentes. Casos de teste ficam em evals/.
    • Adicione uma linha ao registro de atualizações abaixo.
  • Registro de atualizações:
    • 2026-11-12: 1.0.0, movido para o repositório público redcarpg-skills. Caminhos absolutos locais removidos; conteúdo inalterado.
    • 2026-11-10, Cirra: Versão inicial. record.mjs (relógios shim e hook), clock-shim.js, make_gif.py. Testado em páginas web (CSS transitions, CSS animations, WAAPI, rAF), Live2D (Hiyori, física ligada/desligada) e three.js VRM (camada de movimento ligada/desligada).
    • 2026-11-10, Cirra: Durante testes, descobriu-se que animações CSS/WAAPI iniciadas no carregamento da página já haviam avançado alguns milissegundos no tempo real antes de o relógio falso assumir, resultando em uma diferença de 16.6 ms entre as duas variantes. Transições acionadas por temporizador só eram capturadas após o temporizador completo (50 ms viravam 80 ms). Corrigido: animações encontradas pela primeira vez são reiniciadas a partir de 0; a cada execução de temporizador, rAF e início de passo, os tempos são reavaliados. Após a correção: uma transição de 600 ms acionada em 50 ms estava exatamente em 8.0 px aos 80 ms; as duas variantes tinham o quadro 0 idêntico. Dois casos de teste (comparação de easing em página web, deformação de bola quicando) passaram completamente com e sem a skill — o modelo já era capaz, a skill economiza o trabalho de reescrever os scripts de captura e evita as armadilhas já conhecidas.
    • 2026-11-10, Cirra: Gravou GIF comparativo do balanço de cabelo (força centrífuga, sendo arrastado) usando Yumeka e Sendagaya Shin; personagens 3D em um carrossel (personagem colocado em um Group giratório) com setInterval do relógio falso, sem código de captura na página.
    • 2026-11-10: Seguindo novas regras de documentação, a seção "Manutenção" foi renomeada para "Origem e manutenção".
    • 2026-11-10, Cirra: Usuário notou que o Chrome consumia muita CPU ao gravar cenas 3D. record.mjs alterado para usar renderização por GPU por padrão (--render gpu; --render cpu ainda usa SwiftShader); no início, exibe qual renderizador está sendo usado. Ao sair, o Chrome às vezes gerava erro EPERM ao tentar excluir o diretório de configuração; agora, se não for possível excluir, apenas ignora o erro. Testado: em uma RTX 3060 Ti, a carga do shader por quadro caiu de 154 ms para 8 ms; duas gravações com renderização GPU produziram 10 quadros byte a byte idênticos; renderização GPU e software produzem quadros diferentes (bordas), portanto não podem ser comparadas entre si.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Habilidades Relacionadas