feat(gamedev-2d): pipeline walk_cycle_oneshot — personaje andando en pixel-art animado

Promueve el caso 1 del report 0217 (animacion de sprites de personaje) a un
pipeline one-shot: de un prompt de personaje a un sprite sheet + GIF/WEBP en loop,
frame-by-frame dirigido por pose (ControlNet OpenPose + seed fija + Rembg) con cada
frame pixelizado a NxN RGBA.

Nuevas funciones reutilizables (issue 0087, crecimiento por composicion):
- comfyui_walk_cycle_oneshot (pipeline): orquesta poses -> generacion -> pixelizado
  -> ensamblado. No-throw, salta frames que fallan. Modo openpose (esqueletos reales)
  con fallback prompt-pose.
- render_openpose_walk_skeletons: dibuja N esqueletos OpenPose COCO-18 del walk cycle
  (el insumo que el report 0217 marco como faltante).
- comfyui_pixelize_sprite_png: PNG existente -> NxN RGBA pixel-art real (compone
  crop_to_content + pixeloe_downscale + comfyui_pixelize_image).
- assemble_animated_sprite: frames RGBA -> sprite sheet horizontal + WEBP/GIF loop.
- comfyui_build_walk_cycle_workflow (pura): grafo API del workflow animado para la UI
  (ControlNet OpenPose -> KSampler xN seed fija -> ImageBatch -> Rembg -> SaveAnimatedWEBP).

Verificado en GPU: GIF/WEBP de caballero andando, 4 frames 32x32 (y 64x64) RGBA con
fondo transparente y 16 colores, identidad de silueta consistente, piernas que cambian.
Metodo de poses usado: OpenPose real (sin fallback). Evidencia en report 0221.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 18:14:46 +02:00
parent 36a725ba10
commit 6cc90558d4
10 changed files with 1960 additions and 0 deletions
@@ -0,0 +1,110 @@
---
name: comfyui_walk_cycle_oneshot
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def comfyui_walk_cycle_oneshot(character: str, *, frames: int = 4, size: int = 32, colors: int = 16, fps: int = 8, checkpoint: str = 'IMG_dreamshaper_8.safetensors', ref_image: str | None = None, server: str = '127.0.0.1:8188', dest_dir: str = '~/ComfyUI/output', seed: int = 0, pose_method: str = 'auto', controlnet_strength: float = 0.7, engine: str = 'pixeloe', palette=None, fmt: str = 'webp', **gen_kwargs) -> dict"
description: "Pipeline one-shot: de un prompt de personaje a una animacion de walk cycle en pixel-art (sprite sheet + GIF/WEBP en loop). Genera N frames frame-by-frame dirigidos por pose (ControlNet OpenPose con esqueletos del walk cycle, o fase del paso por prompt como fallback), con seed fija para identidad consistente y Rembg para alpha, y pixeliza cada frame a un grid duro size x size RGBA. Materializa el caso 1 de la investigacion de animacion de sprites (report 0217): personaje = frame-by-frame pose-driven, NUNCA modelos de video. Compone render_openpose_walk_skeletons + comfyui_build_sprite_sheet_workflow + submit/wait/fetch + comfyui_pixelize_sprite_png + assemble_animated_sprite. Impuro: red + GPU + disco. No-throw, salta frames que fallan."
tags: [gamedev-2d, comfyui, pixelart, sprite, animation, walk-cycle, controlnet, openpose, launcher]
uses_functions: ["render_openpose_walk_skeletons_py_ml", "comfyui_build_sprite_sheet_workflow_py_ml", "comfyui_submit_workflow_py_ml", "comfyui_wait_result_py_ml", "comfyui_fetch_output_image_py_ml", "comfyui_pixelize_sprite_png_py_ml", "assemble_animated_sprite_py_ml"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["os", "sys"]
params:
- name: character
desc: "Prompt del personaje (ej. 'pixel art knight, full body, side view'). No vacio. La identidad se mantiene entre frames con seed fija."
- name: frames
desc: "Numero de frames del ciclo (>=2, recomendado 4-8). 4 = las 4 fases canonicas contact-L / passing / contact-R / passing."
- name: size
desc: "Lado del grid pixel-art final por frame en pixeles (32 sprites pequenos, 64 personajes con mas detalle)."
- name: colors
desc: "Numero de colores de la paleta libre por frame (cuantizacion MEDIANCUT) cuando palette es None."
- name: fps
desc: "Cadencia de la animacion en frames por segundo (duration = 1000/fps ms por frame)."
- name: checkpoint
desc: "Checkpoint SD1.5 (ControlNet OpenPose + IPAdapter-FaceID solo instalados en SD1.5; default 'IMG_dreamshaper_8.safetensors')."
- name: ref_image
desc: "Imagen de cara de referencia en el input/ del servidor para IPAdapter-FaceID (segunda ancla de identidad). None = solo seed + prompt."
- name: server
desc: "host:port del servidor ComfyUI (sin esquema). Default 127.0.0.1:8188."
- name: dest_dir
desc: "Directorio donde guardar frames + sprite sheet + animacion (se expande ~)."
- name: seed
desc: "Semilla FIJA del KSampler para TODOS los frames (identidad estable entre poses)."
- name: pose_method
desc: "'openpose' (esqueletos OpenPose -> ControlNet, control exacto), 'prompt' (fase del paso descrita en el prompt, sin esqueletos) o 'auto' (intenta openpose, cae a prompt si el render falla)."
- name: controlnet_strength
desc: "Fuerza del ControlNet OpenPose (0.7 da buen control sin aplastar el estilo). Solo aplica en modo openpose."
- name: engine
desc: "Motor de downscale del pixelizado: 'pixeloe' (contrast-aware, conserva silueta) o 'nearest'."
- name: palette
desc: "None (paleta libre a colors), nombre builtin ('pico-8','nes','game-boy') o lista de hex. Fija ignora colors."
- name: fmt
desc: "Formato de la animacion: 'webp' (recomendado, alpha real) o 'gif' (alpha binario)."
output: "dict {ok, frames:[paths], spritesheet_path, animation_path, size, n_frames, seed, pose_method_used, skipped:[idx], error}. ok=True si se produjo la animacion con >=1 frame. n_frames puede ser < frames si alguno fallo (se salta y se sigue)."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/comfyui_walk_cycle_oneshot.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from pipelines.comfyui_walk_cycle_oneshot import comfyui_walk_cycle_oneshot
# Requiere el servidor ComfyUI vivo en 127.0.0.1:8188 (GPU).
res = comfyui_walk_cycle_oneshot(
"pixel art knight, full body, side view",
frames=4, size=32, colors=16, fps=8, seed=42,
dest_dir="/tmp/comfy_walk_cycle",
)
print(res["ok"], res["n_frames"], res["animation_path"], res["pose_method_used"])
# -> True 4 /tmp/comfy_walk_cycle/walk_cycle.webp openpose
```
## Cuando usarla
Cuando quieras una animacion de un personaje en pixel-art (caminar, correr) lista
para un juego 2D, en un solo paso: das el prompt del personaje y recibes el sprite
sheet + el GIF/WEBP en loop. Es la promocion a pipeline (issue 0087) de la receta
del caso 1 del report 0217 — el camino correcto para sprites limpios de personaje
con alpha, frente a AnimateDiff o modelos de video (que ensucian el alpha y no
clavan la pose). Para una sola pose estatica usa `comfyui_pixelart_real_oneshot`;
para varias vistas direccionales (8-way) usa
`comfyui_build_directional_sprite_workflow`.
## Gotchas
- **Server vivo + GPU**: requiere ComfyUI en `server` con la GPU libre. El report
recomienda `POST /free` antes de cargas pesadas de modelo. Cada frame reusa el
mismo checkpoint, asi que el modelo solo se carga una vez.
- **Poses OpenPose**: en modo `openpose` los esqueletos se escriben en el `input/`
del servidor (asume server local; para un server remoto haria falta subirlos con
`POST /upload/image`). Si el ControlNet no produce variacion de piernas
reconocible, usa `pose_method="prompt"`: a 32x32 el detalle de pose se simplifica
y la fase del paso por prompt + seed fija da un walk reconocible.
- **Identidad**: la `seed` es FIJA para todos los frames — esa es la ancla de
identidad. Cambiar la seed entre frames rompe la consistencia del personaje.
`ref_image` (IPAdapter-FaceID) es una segunda ancla opcional; sobre un sprite de
cuerpo entero pequeno aporta sobre todo paleta/ropa (ver report 0217).
- **No-throw, salta frames**: si un frame falla (red, GPU, build) se anade a
`skipped` y la animacion se monta con los que queden. ok=False solo si NINGUN
frame sale.
- **Loop suave**: con `frames=4` el ciclo (contact-L, passing, contact-R, passing)
ya cierra el bucle — el frame siguiente al ultimo vuelve a la primera fase.
- **WEBP vs GIF**: `fmt="webp"` conserva alpha real (lossless); `fmt="gif"` solo
tiene alpha binario (1 bit). Para sprites con transparencia, usa WEBP.
## Capability growth log
- v1.0.0 (2026-06-28) — version inicial. Caso 1 del report 0217 promovido a
pipeline one-shot: walk cycle pixel-art con poses OpenPose (o fallback prompt),
seed fija para identidad, Rembg para alpha, pixelizado a NxN RGBA, sprite sheet +
WEBP/GIF en loop.