---
name: comfyui_build_vfx_spritesheet_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_vfx_spritesheet_workflow(prompt: str, *, checkpoint: str = \"IMG_dreamshaper_8.safetensors\", motion_model: str = \"mm_sd_v15_v2.ckpt\", beta_schedule: str = \"sqrt_linear (AnimateDiff)\", lora: str | None = None, lora_strength: float = 1.1, negative: str = \"low quality, watermark, text, background detail\", width: int = 512, height: int = 512, num_frames: int = 16, context_length: int = 16, context_stride: int = 1, context_overlap: int = 4, closed_loop: bool = True, steps: int = 20, cfg: float = 7.5, sampler_name: str = \"euler\", scheduler: str = \"normal\", seed: int = 0, filename_prefix: str = \"vfx_loop\") -> dict"
description: "Construye el dict (API format) del workflow ComfyUI AnimateDiff loop para generar N frames de un efecto VFX 2D (humo/fuego/magia/portal) en bucle seamless sobre fondo NEGRO. Inserta ADE_LoopedUniformContextOptions + ADE_AnimateDiffLoaderGen1 (motion mm_sd_v15_v2.ckpt) sobre un txt2img base y pone batch_size = num_frames. Los frames son insumo de comfyui_matting_luma_to_alpha + montaje del spritesheet (pipeline, no este builder). Compone comfyui_build_txt2img_workflow (+ comfyui_inject_lora). Pura. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev-2d, gamedev-vfx, animatediff, vfx, spritesheet, workflow]
uses_functions: [comfyui_build_txt2img_workflow_py_ml, comfyui_inject_lora_py_ml]
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: prompt
    desc: "Prompt del efecto. Deberia incluir 'on pure black background' (insumo de luma-as-alpha). No puede estar vacio."
  - name: checkpoint
    desc: "Checkpoint SD1.5 (AnimateDiff SD1.5 cabe en 8GB, SDXL-video no; default 'IMG_dreamshaper_8.safetensors'). keyword-only."
  - name: motion_model
    desc: "Motion module en models/animatediff_models. Default 'mm_sd_v15_v2.ckpt'. Se asigna al input 'model_name' del loader (OJO: no 'motion_model'). keyword-only."
  - name: beta_schedule
    desc: "Schedule del motion. Default 'sqrt_linear (AnimateDiff)'. keyword-only."
  - name: lora
    desc: "LoRA de FX opcional (humo/fuego/explosion) en models/loras. None = sin LoRA. keyword-only."
  - name: lora_strength
    desc: "Fuerza del LoRA FX sobre model y clip (recomendado 1.1). keyword-only."
  - name: negative
    desc: "Prompt negativo. keyword-only."
  - name: width
    desc: "Ancho por frame en px (512 cabe en 8GB). keyword-only."
  - name: height
    desc: "Alto por frame en px (512). keyword-only."
  - name: num_frames
    desc: "Nº de frames del batch (EmptyLatentImage.batch_size). Debe ser >= context_length. keyword-only."
  - name: context_length
    desc: "Tamano de la ventana de contexto temporal (<= num_frames). keyword-only."
  - name: context_stride
    desc: "Paso de las ventanas de contexto. keyword-only."
  - name: context_overlap
    desc: "Solape de las ventanas (alto = transicion mas suave a mas coste). keyword-only."
  - name: closed_loop
    desc: "Si True (recomendado) el ultimo frame enlaza con el primero (loop seamless). False = secuencia abierta. keyword-only."
  - name: steps
    desc: "Pasos del KSampler (AnimateLCM: 6). keyword-only."
  - name: cfg
    desc: "CFG del KSampler (AnimateLCM: 2.0). keyword-only."
  - name: sampler_name
    desc: "Sampler del KSampler (AnimateLCM: 'lcm'). keyword-only."
  - name: scheduler
    desc: "Scheduler del KSampler. keyword-only."
  - name: seed
    desc: "Semilla del KSampler. keyword-only."
  - name: filename_prefix
    desc: "Prefijo de los PNG del batch en output/. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow: txt2img base (+ LoRA FX opcional) con ADE_LoopedUniformContextOptions + ADE_AnimateDiffLoaderGen1 inyectados, KSampler repuntado al MODEL con motion y EmptyLatentImage.batch_size = num_frames. SaveImage escribe los N PNG del batch."
tested: true
tests: ["golden: ADE_AnimateDiffLoaderGen1 + ADE_LoopedUniformContextOptions, model_name mm_sd_v15_v2.ckpt, closed_loop True, batch_size=num_frames, KSampler.model <- ADE loader, context_options <- ctx node", "edge closed_loop=False", "edge lora FX: ADE loader toma MODEL de la LoRA", "edge AnimateLCM (sampler lcm/steps 6/cfg 2.0)", "error prompt vacio / num_frames<context_length -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_vfx_spritesheet_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_vfx_spritesheet_workflow.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_build_vfx_spritesheet_workflow import comfyui_build_vfx_spritesheet_workflow

# Frames de un efecto en bucle sobre fondo negro:
wf = comfyui_build_vfx_spritesheet_workflow(
    "burning campfire flame, glowing, vfx, on pure black background",
    num_frames=16,
    closed_loop=True,
    seed=0,
)
# Pipeline completo (los frames son el insumo):
#   comfyui_submit_workflow -> comfyui_wait_result -> comfyui_fetch_output_image (N PNG)
#   -> [por frame] comfyui_matting_luma_to_alpha (luma -> alpha)
#   -> comfyui_build_grid (spritesheet RGBA)
```

O lanzable directo con: `./fn run comfyui_build_vfx_spritesheet_workflow` (imprime nodos + class_types del ejemplo).

## Cuando usarla

Cuando necesites un spritesheet de un efecto 2D animado en bucle (humo, fuego,
magia, portal, explosión) para `GPUParticles2D`/`AnimatedSprite2D` de Godot o el
VFX Graph de Unity. SIEMPRE genera sobre fondo negro (`on pure black background`
en el prompt) porque los frames se recortan con luma-as-alpha
(`comfyui_matting_luma_to_alpha`), no con rembg (que rompe los translúcidos).

## Gotchas

- **Requiere `ComfyUI-AnimateDiff-Evolved` + el motion module `mm_sd_v15_v2.ckpt`**
  (ya instalados: `ADE_AnimateDiffLoaderGen1`, `ADE_LoopedUniformContextOptions` en
  /object_info). Sin ellos, HTTP 400 al enviar.
- **El input del motion es `model_name`, NO `motion_model`** (el plan inicial lo
  nombraba mal; verificado contra /object_info).
- **`num_frames >= context_length`** o lanza ValueError (la ventana no puede ser
  mayor que el batch).
- **Genera sobre fondo NEGRO**: es deliberado, el negro se vuelve transparente con
  luma-as-alpha. No usar fondo de color ni escena.
- **8GB**: SD1.5 + motion module + 16 frames @ 512² cabe con `--lowvram`.
  AnimateLCM (`sampler 'lcm', steps 6, cfg 2.0`) baja el pico y el tiempo. Con
  ControlNet/IPAdapter encima ya no cabe (bajar a ~49f/512×288 o quitar adapters).
  SDXL-video NO cabe.
- Función pura: no valida contra el server.
