Communitygithub.com

animation-capture

Graba animaciones cuadro a cuadro con temporización determinista y genera GIFs de comparación antes/después para revisión visual.

¿Qué es animation-capture?

Esta habilidad permite capturar animaciones de páginas web, canvas, three.js, VRM o Live2D cuadro a cuadro con una línea de tiempo determinista, utilizando un reloj falso en lugar del tiempo real del sistema. Esto asegura que cada fotograma se tome exactamente en el mismo instante, sin importar la velocidad de renderizado. El flujo típico consiste en: primero registrar valores numéricos clave (ángulos, posiciones) para verificar el comportamiento, y luego generar un GIF de comparación lado a lado (antes/después o encendido/apagado). Incluye scripts como `record.mjs` (que lanza Chrome sin cabeza con el reloj falso), `clock-shim.js` (que intercepta `performance.now`, `requestAnimationFrame`, etc.) y `make_gif.py` (que genera el GIF final con etiquetas y paleta compartida). Es ideal para revisar transiciones CSS, animaciones JS, juegos en canvas, personajes 3D o animaciones Live2D, y evita problemas comunes como la desincronización por renderizado lento, la variabilidad del tiempo virtual de Chrome o la necesidad de modificar el código de la página.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Graba animaciones como GIF de comparación

Los problemas y mejoras de una animación no se ven con una sola captura de pantalla, y describirlos con palabras no permite al usuario decidir. Hay que grabarla: la misma animación, antes y después del cambio (o activado/desactivado), fotograma a fotograma siguiendo la misma línea de tiempo, y colocarlas lado a lado en un GIF para que el usuario pueda decidir de un vistazo.

El principio central es el tiempo lo movemos nosotros, no el reloj real. El navegador sin cabeza dibuja lentamente (especialmente con renderizado por software), quizás solo una docena de fotogramas por segundo. Si se usa el tiempo real, cada grabación se detiene en una posición diferente y los dos lados nunca coincidirán. Por eso, al grabar, el tiempo en la página solo avanza cuando decimos «adelanta 40 ms», luego capturamos una imagen, luego otra vez, y así. De esta forma, sin importar lo lento que sea la máquina, el fotograma n siempre será n × 40 ms, los dos variantes coincidirán fotograma a fotograma, y grabar el mismo variante dos veces dará exactamente el mismo resultado (75 fotogramas idénticos byte a byte, comprobado empíricamente).

1. Piensa primero: el GIF es para enseñar, los números para revisar

El GIF sirve para revisión visual, comparación e incluirlo en documentación, no para encontrar fallos. En la práctica: al levantar lentamente la mano, la rodilla estuvo doblada 18° durante un segundo, lo que se detectó anotando ángulos fotograma a fotograma, pero no se ve en un GIF. Por lo tanto:

  1. Primero registra los valores clave fotograma a fotograma (ángulos, posición, altura, transparencia, deslizamiento del pie), una vez con el cambio activado y otra desactivado, y comprueba si los números coinciden.
  2. Si los números son correctos, graba el GIF para que el usuario decida.

El método para registrar valores está en references/hook.md (escribe una función de captura en la página que devuelva los valores de cada fotograma junto con la imagen).

2. Elige el reloj

RelojCuándo usarlo¿Requiere modificar la página?
shim (por defecto)Casi siempre: transiciones CSS, animaciones CSS, element.animate(), animaciones JS con rAF, canvas, three.js, PixiJS, Live2DNo. clock-shim.js se inyecta antes de los scripts de la página, reemplazando performance.now, Date.now, requestAnimationFrame, setTimeout/setInterval, y Math.random con una semilla fija; las animaciones CSS/WAAPI se detienen en cada fotograma y se sincronizan al mismo instante
hookCuando necesitas leer valores del modelo mientras grabas (valores fotograma a fotograma), o cuando la aplicación tiene su propio bucle que se puede avanzar en pasos fijos y quieres evitar la captura de pantalla del navegadorLa página debe implementar window.__capture(...), ver references/hook.md

No uses el tiempo virtual de Chrome (Emulation.setVirtualTimePolicy): probado, el tiempo que devuelve rAF no coincide con performance.now (aparecen contadores negativos), y la animación no sigue el paso dado.

El reloj falso no controla: animaciones en Web Workers, OffscreenCanvas dibujado en workers, <video>, new Date() (solo se reemplazó Date.now), crypto.getRandomValues. Si te encuentras con esto, usa hook o reemplázalos en la página de prueba.

3. Pasos

  1. Prepara la página y las variantes. Lo mejor es que las variantes se activen con parámetros de URL (?v=off, ?v=on), y la página los lea para decidir el estado; o usa --setup para ejecutar un JS que los cambie antes de empezar a grabar. El material que solo se usa localmente (modelos de terceros, páginas de prueba) colócalo fuera del repositorio y bórralo al terminar.
  2. Inicia la página. Los archivos locales se abren directamente con file:///…; si necesitas empaquetar (Vite, etc.), inicia un servidor de desarrollo en un puerto libre (otros procesos en la misma máquina pueden estar ocupando puertos de prueba, no toques sus ventanas). Vite al abrir una página nueva por primera vez optimiza dependencias y luego se refresca automáticamente: si la página se recarga a mitad de la grabación, aparecerá un error «window.xxx is undefined»: abre la página una vez en el navegador para precalentarla, o si falla, vuelve a ejecutar la grabación.
  3. Graba.
    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 recorta solo esa área (selector CSS); si no se indica, captura toda la ventana (usa --width, --height para el tamaño).
    • --ready: los modelos y texturas se cargan de forma asíncrona; escribe una expresión que indique cuándo están listos. Mientras esperas, el reloj falso no avanza: si avanzara, el momento de inicio y los números aleatorios usados serían diferentes cada vez que se graba, y dos grabaciones no coincidirían (comprobado empíricamente). Si la página usa setTimeout para la carga, añade --ready-advance 1.
    • --wait: después de que esté listo, avanza un tiempo falso (milisegundos) para que se estabilicen las animaciones de entrada o los resortes; --setup se ejecuta después, y justo después comienza la grabación. --setup puede contener await, y su valor de retorno se ignora (los objetos de three.js son demasiado grandes para transmitirlos por CDP).
    • El script inicia su propio Chrome sin cabeza (con un directorio de configuración separado), y lo cierra al terminar; no lo dejes abierto.
    • Por defecto usa renderizado por GPU (--render gpu), y al inicio imprimirá «WebGL renderer: …». Si imprime SwiftShader, significa que no se usó la GPU, y la CPU se saturará. En máquinas sin GPU, o al comparar con grabaciones anteriores hechas con renderizado por software, añade --render cpu.
    • El renderizado por GPU y por software dibujan los bordes de forma diferente; las comparaciones fotograma a fotograma solo son válidas dentro del mismo tipo de renderizado.
  4. Primero revisa la hoja de referencia, luego genera el GIF.
    python <skill>/scripts/make_gif.py frames --variants off,on --fps 25 --width 300 --title "Hiyori · caricia" --sheet sheet.png --out compare.gif
    
    --sheet organiza varios fotogramas (por defecto 4 equiespaciados, --pick 0,10,20 para elegir específicos) en una sola PNG: revísala tú primero para confirmar que la animación se mueve, que los lados coinciden y que el encuadre no corta nada, y luego entrega el GIF al usuario. Todos los fotogramas del GIF comparten una misma paleta, sin parpadeo de colores; las etiquetas se escriben en una barra blanca en la parte superior de la imagen, sin superponerse al contenido.
  5. Entrega al usuario: envía el GIF mediante una herramienta que pueda manejar archivos (envía solo unas cuantas representativas, el resto indícalo en una carpeta); explica claramente qué representa cada lado (izquierda/derecha) y qué versión se grabó, así como qué no se verificó (p. ej., «se usó renderizado por software, los bordes son ligeramente más gruesos que con GPU»).

4. Cómo elegir los parámetros

  • 25 fotogramas por segundo, 2–4 segundos por imagen; 300 píxeles de ancho por celda. Una imagen de dos celdas ocupa aproximadamente 0.6–3 MB (más si hay personajes y degradados).
  • La duración del movimiento se decide según el movimiento en sí: en hook se puede usar la longitud del fragmento más un poco de cola (hay que grabar también el remanente después de detenerse).
  • Encuadra según la caja delimitadora: para personajes 3D, usa Box3.setFromObject(scene) para calcular hasta la parte superior de la cabeza; si solo usas el cráneo, cortarás cascos grandes o pelos (comprobado empíricamente); deja también un poco de espacio debajo de los pies.
  • Cada variante debe diferir solo en el aspecto que se quiere comparar: mismo modelo, misma cámara, mismo movimiento, misma espera. La parte aleatoria ya está controlada por el reloj falso con semilla fija, pero si en distintas variantes se llama a Math.random un número diferente de veces, la secuencia posterior se desincronizará; tenlo en cuenta.

5. Procedimientos para distintos tipos de página

  • Páginas web (CSS, WAAPI, rAF): no necesitas modificar la página. Para grabar interacciones (abrir menú, hover), actívalas en --setup (añadiendo una clase, dispatchEvent). Comprobado empíricamente: contadores rAF, transiciones CSS que se activan después de 50 ms, animaciones CSS con fotogramas clave, element.animate() funcionan correctamente con el reloj falso; dos variantes con diferentes curvas de aceleración coinciden fotograma a fotograma.
  • PixiJS / Live2D: no necesitas modificar la página. El ticker de Pixi usa rAF y performance.now, por lo que sigue el reloj falso; la física también se calcula con este tiempo. El runtime de Cubism de Live2D (live2dcubismcore.min.js) debe cargarse con un <script> normal antes de los módulos, porque el motor lo comprueba al importarlo; si no, lanza un error «Could not find Cubism runtime». Comprobado empíricamente: con Hiyori, dos variantes (física activada/desactivada), grabadas dos veces, dieron 75 fotogramas idénticos byte a byte.
  • three.js / VRM: no necesitas modificar la página. El bucle rAF sigue el reloj falso (comprobado empíricamente con páginas de mascotas VRM, las diferencias entre variantes se capturan correctamente). Para leer ángulos de huesos fotograma a fotograma, usa hook (ver references/hook.md).
  • Juegos, otras aplicaciones canvas: igual que arriba. Si la aplicación usa setTimeout para el bucle del juego, también está bajo el control del reloj falso.

6. Licencias y privacidad

  • Los GIF grabados a partir de modelos o material de terceros deben respetar su licencia: si prohíbe la redistribución (como algunas cláusulas de VN3), solo puedes verlo tú o mostrarlo para revisión, no lo publiques en repositorios públicos ni lo difundas públicamente. Si permite su uso pidiendo atribución, incluye el crédito al publicarlo.
  • Los GIF y los fotogramas deben guardarse fuera del repositorio (en una carpeta local específica del proyecto o en un directorio temporal), y al terminar, borra las páginas de prueba.

7. Relación con otras habilidades

  • Capturas de pantalla estáticas, múltiples viewports, modo oscuro: frontend-verification (su shot.mjs usa tiempo real; para animaciones usa esta habilidad).
  • Vídeos promocionales para el público, con música: motion-showreel.
  • Cómo hacer que la animación de personajes sea natural (capas de movimiento, física, fragmentos de animación): live2d-vrm-animation.

8. Origen y mantenimiento

  • Origen: Cirra (mascota de escritorio, RedCarpG/cirra-deskpet) — el 2026-11-10, al hacer capas de movimiento, caderas y piernas, y movimiento de pelo para un personaje 3D, se desarrolló este método para mostrar la comparación visual; la implementación inicial está en docs/vrm-motion.md §11–§12 y docs/vrm-spring-force.md §8 («Cómo verificar»). La experiencia se documentó en el repo privado lessons/animation-capture.md (la parte de personajes 3D también en lessons/threejs-vrm-animation.md).
  • Raíz: RedCarpG/redcarpg-skills en skills/animation-capture/. La copia en ~/.claude/skills/ es la instalada; no la modifiques directamente.
  • Cómo modificarlo:
    • Haz los cambios en redcarpg-skills, mediante PR; aumenta la versión en manifest.json, ejecuta node scripts/validate.mjs, y si pasa, publica (tag animation-capture-v<versión>), luego reinstala la copia local.
    • Después de modificar los scripts, graba una vez en una página CSS, una página Live2D y una página three.js, y verifica que los dos variantes coincidan fotograma a fotograma; y que el mismo variante grabado dos veces dé exactamente los mismos fotogramas (compara con ImageChops.difference de Pillow).
    • Si cambias el procedimiento o encuentras nuevos problemas, actualiza los archivos de lecciones; los casos de prueba están en evals/.
    • Añade una línea al registro de cambios a continuación.
  • Registro de cambios:
    • 2026-11-12: 1.0.0, movido a redcarpg-skills y publicado. Se eliminaron rutas absolutas locales; el contenido no cambió.
    • 2026-11-10, Cirra: versión inicial. record.mjs (reloj shim y hook), clock-shim.js, make_gif.py. Probado en páginas web (transiciones CSS, animaciones CSS, WAAPI, rAF), Live2D (Hiyori, física activada/desactivada), three.js VRM (capas de movimiento activadas/desactivadas).
    • 2026-11-10, Cirra: durante las pruebas se descubrió que las animaciones CSS/WAAPI iniciadas durante la carga de la página ya habían avanzado unos milisegundos con tiempo real antes de que el reloj falso las interceptara, con una diferencia de 16.6 ms entre variantes; las transiciones iniciadas por temporizador se capturaban después de que el paso hubiera terminado (50 ms se convertían en 80 ms). Se corrigió: la primera animación vista se cuenta desde 0, y cada vez que se completa un temporizador, rAF o un paso, se revisa de nuevo. Después de la corrección, comprobado: una transición de 600 ms activada a los 50 ms, a los 80 ms se encontraba exactamente en 8.0 px, y el fotograma 0 de ambas variantes era idéntico. Dos casos de prueba (comparación de curvas de aceleración web, deformación por impacto de una pelota) pasaron completamente con y sin la habilidad: este tipo de tarea el modelo ya la sabe hacer, pero la habilidad ahorra tener que reescribir los scripts de grabación cada vez, además de todos los problemas ya superados.
    • 2026-11-10, Cirra: grabación de GIF comparativo del movimiento del pelo (fuerza centrífuga, arrastre) con Yumeka y Sendagaya Shino; personaje 3D en un plato giratorio (grupo rotatorio) impulsado por setInterval bajo el reloj falso, sin escribir código de grabación en la página.
    • 2026-11-10: según las nuevas normas de documentación, la sección «Mantenimiento» pasó a llamarse «Origen y mantenimiento».
    • 2026-11-10, Cirra: el usuario descubrió que al grabar en 3D, Chrome consumía mucha CPU. record.mjs se cambió para usar renderizado por GPU por defecto (--render gpu; --render cpu sigue usando SwiftShader), y al inicio imprime cuál renderizador se está utilizando; al cerrar Chrome, la eliminación del directorio de configuración puede fallar con EPERM, ahora simplemente se ignora el error. Comprobado empíricamente: en una RTX 3060 Ti, la carga del sombreador por fotograma pasó de 154 ms a 8 ms; dos grabaciones con renderizado por GPU dieron 10 fotogramas idénticos byte a byte; el renderizado por GPU y por software dan fotogramas diferentes (bordes), por lo que no se pueden mezclar en las comparaciones.

Individual skills in this repo

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

Skills relacionados