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:
- 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.
- 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ógio | Quando usar | Precisa modificar a página? |
|---|---|---|
shim (padrão) | Quase sempre: CSS transitions, CSS animations, element.animate(), animações JS com rAF, canvas, three.js, PixiJS, Live2D | Nã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. |
hook | Quando 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
- 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--setuppara 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. - 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. - 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,--heightpara 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 desetTimeoutpara 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--setupvocê pode usarawait; 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.
- 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--sheetgera uma imagem PNG com alguns quadros (padrão: 4 quadros uniformemente espaçados; use--pick 0,10,20para 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. - 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.randomum 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(vejareferences/hook.md). - Jogos, outras aplicações canvas: o mesmo princípio se aplica. Se a aplicação usa
setTimeoutpara 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(oshot.mjsdela 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á emlessons/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, executenode scripts/validate.mjse, 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.differencedo 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.
- Faça alterações no repositório redcarpg-skills, via PR; incremente a versão em
- 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
setIntervaldo 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.mjsalterado para usar renderização por GPU por padrão (--render gpu;--render cpuainda 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.