---
name: comfyui_build_particle_texture_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_particle_texture_workflow(particle: str, *, soft: bool = True, style: str = \"particle texture, soft glow\", checkpoint: str = \"IMG_dreamshaper_8.safetensors\", size: int = 256, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, negative: str | None = None, steps: int = 24, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"particle_texture\") -> dict"
description: "Construye el dict (API format) del workflow de UNA textura de particula individual con alpha: chispa, humo, polvo, destello/flare, gota, copo, hoja, circulo de energia — el 'ladrillo' que el sistema de particulas del motor (Godot GPUParticles2D, Unity VFX Graph) instancia a miles. Se genera aislada y centrada sobre fondo NEGRO puro, pensada para extraer alpha por luminancia con comfyui_matting_luma_to_alpha (translucido con falloff: additive blend). soft controla el borde (True=glow difuso/feathered; False=nitido/cristalino). NO inyecta Rembg (el matting de un translucido es luma-to-alpha, no un nodo). Size pequeño (256) por defecto: se replica a miles, no necesita resolucion alta. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora. Hermano de comfyui_build_decal_overlay/vfx_spritesheet_workflow. DISTINTO de vfx_spritesheet (ese es la SECUENCIA animada de un efecto; esto es UNA textura reutilizable estatica). Pura, sin red ni I/O. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev-2d, gamedev-vfx, particle, texture, alpha, luma, spark, smoke, dust, flare, vfx, 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: particle
    desc: "Descripcion del elemento de particula (ej. 'spark', 'smoke puff', 'dust mote', 'glowing flare', 'water droplet', 'snowflake', 'falling leaf', 'energy ring'). Se inserta en un prompt scaffold de particula aislada sobre negro. No puede estar vacio."
  - name: soft
    desc: "Si True (defecto) genera la particula con bordes suaves y glow difuso ('soft glow, soft falloff, feathered edges': humo, polvo, destello, circulo de energia, gota); si False con bordes nitidos y alto contraste ('crisp sharp edges, high contrast': chispa, copo de nieve, hoja con silueta). En ambos casos el fondo es negro plano. keyword-only."
  - name: style
    desc: "Descriptor de estilo de la particula (ej. 'particle texture, soft glow', 'stylized vfx, painterly', 'photoreal smoke', 'anime sparkle'). Pasa el MISMO style + checkpoint + lora a todas las particulas de un set para coherencia visual. keyword-only."
  - name: checkpoint
    desc: "Checkpoint del servidor. 'IMG_dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto; 'IMG_juggernaut_xl_v11.safetensors' para SDXL (mas VRAM, subir size a 512/768). keyword-only."
  - name: size
    desc: "Lado del cuadrado en px (width = height = size). 256 por defecto: una textura de particula es pequeña y se replica a miles, no necesita resolucion alta. keyword-only."
  - name: seed
    desc: "Semilla del KSampler. Misma seed + mismos particle/style -> misma textura; variar seed da variantes del mismo tipo de particula. keyword-only."
  - name: lora
    desc: "LoRA de estilo opcional en models/loras. None = sin LoRA. keyword-only."
  - name: lora_strength
    desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
  - name: negative
    desc: "Prompt negativo. None usa el negativo por defecto pensado para una particula aislada (un solo elemento sobre negro plano, sin escena/objeto solido/multiples elementos/fondo texturizado). keyword-only."
  - name: steps
    desc: "Pasos del KSampler. keyword-only."
  - name: cfg
    desc: "CFG del KSampler. keyword-only."
  - name: sampler_name
    desc: "Sampler del KSampler. keyword-only."
  - name: scheduler
    desc: "Scheduler del KSampler. keyword-only."
  - name: filename_prefix
    desc: "Prefijo del PNG en output/. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow: base txt2img cuadrada con prompt scaffold de particula ('{particle} particle, {style}, isolated on pure black background, <soft|sharp> edges, single element, centered, for game particle system, ...') + el negativo refuerza UN solo elemento sobre fondo negro plano (rechaza escena/objeto solido/multiples particulas/fondo texturizado). NO lleva Rembg: el PNG resultante se convierte a RGBA con comfyui_matting_luma_to_alpha (luma=alpha) en un paso posterior. UNA particula; variar seed da variantes del mismo tipo."
file_path: python/functions/ml/comfyui_build_particle_texture_workflow.py
tested: true
test_file_path: python/functions/ml/comfyui_build_particle_texture_workflow_test.py
tests: [test_golden_particle_on_black_recipe, test_edge_soft_toggles_edge_character, test_edge_particle_reflected, test_edge_style_in_prompt, test_edge_size_reflected_and_small_default, test_edge_negative_isolates_single_particle, test_edge_lora_reflected, test_error_empty_particle, test_determinism]
---

Construye el dict (API format) del workflow de UNA textura de partícula individual con
alpha 2D: el "ladrillo" reutilizable que el sistema de partículas del motor (Godot
`GPUParticles2D`/`GPUParticles3D`, Unity VFX Graph / Shuriken) instancia a miles y anima
(spawn, velocidad, fade, color over lifetime). Chispa, humo, polvo, destello/flare,
gota, copo, hoja, círculo de energía. La pieza se genera AISLADA y centrada sobre fondo
NEGRO puro para poder extraer luego el canal alpha por luminancia. Es el builder hermano
de `comfyui_build_decal_overlay_workflow` / `comfyui_build_vfx_spritesheet_workflow`:
mismo patrón PURO (dict API format) que compone funciones existentes del registry sin
reescribir el grafo.

## Cuando usarla

Cuando necesites UNA textura pequeña y reutilizable para alimentar un sistema de
partículas: la chispa que el motor emite a centenares al golpear metal, el puff de humo
que sube de una antorcha, el mote de polvo de un ambiente, el destello de un pickup, la
gota de una fuente, el copo de una nevada, la hoja de un árbol al viento, el anillo de
energía de un hechizo. Generas UNA textura limpia y el motor hace el resto (instancia,
mueve, fade). Pasa el MISMO `style` + `checkpoint` + (`lora`) a todas las partículas de
un set para que combinen; varía `seed` para sacar variantes del mismo tipo (varias
chispas distintas).

Flujo típico después de generar:

1. `comfyui_build_particle_texture_workflow("spark", soft=False)` -> dict.
2. `comfyui_submit_workflow` -> `comfyui_wait_result` -> `comfyui_fetch_output_image`
   (PNG de la partícula sobre negro).
3. `comfyui_matting_luma_to_alpha(png, gamma=..., black_point=...)` -> PNG RGBA donde la
   luminancia ES el alpha: brillante=opaco, negro=transparente. Listo para el motor con
   additive blend.

**Elige este builder y NO `comfyui_build_vfx_spritesheet_workflow` cuando** quieres UNA
textura estática reutilizable, no una secuencia animada. El spritesheet es la animación
completa de un efecto (humo que arde en bucle, N frames vía AnimateDiff); la particle
texture es el ladrillo individual que el sistema de partículas replica y anima por su
cuenta. **Elige este builder y NO `comfyui_build_decal_overlay_workflow` cuando** lo que
quieres es un elemento de partícula (chispa/destello/gota) para emitir, no una mancha de
desgaste (sangre/óxido/grieta) para superponer estática sobre una superficie.

## Ejemplo

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

# Una chispa nítida sobre fondo negro, lista para submit + luma-to-alpha.
wf = comfyui_build_particle_texture_workflow(
    "spark",
    soft=False,                       # chispa = bordes nítidos / alto contraste
    style="particle texture, glowing ember",
    seed=7,
)
# Pipeline completo de una partícula con alpha:
#   r = comfyui_submit_workflow(wf); comfyui_wait_result(r["prompt_id"])
#   png = comfyui_fetch_output_image(...)            # partícula sobre negro
#   rgba = comfyui_matting_luma_to_alpha(png, gamma=1.2, black_point=0.04)  # luma=alpha
# Variantes del mismo tipo: misma particle/style, cambia seed.
#   for s in range(4):
#       wf = comfyui_build_particle_texture_workflow("smoke puff", soft=True, seed=s)
```

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

## Gotchas

- **Siempre sobre negro, a propósito**: la partícula se genera sobre NEGRO puro para que
  `comfyui_matting_luma_to_alpha` mapee la luminancia a alpha y conserve el degradado de
  los bordes (glow, falloff). Es la técnica gamedev correcta para translúcidos: en el
  motor se compone con additive blend y el solape de partículas brilla de forma natural.
  Un matting binario (rembg) destruiría ese falloff y dejaría un borde duro recortado.
- **NO inyecta Rembg a propósito**: a diferencia de los builders de sprite/prop/item,
  este NO lleva 'Image Rembg (Remove Background)'. El SaveImage toma directo del VAEDecode
  (partícula sobre negro) y el matting es un paso posterior de disco (luma-to-alpha).
- **`soft` cambia el borde, no el fondo**: `soft=True` -> "soft glow, feathered edges"
  (humo, destello, gota, círculo de energía); `soft=False` -> "crisp sharp edges, high
  contrast" (chispa, copo, hoja). El fondo es negro plano en ambos casos. OJO: el `style`
  por defecto incluye "soft glow"; si pides `soft=False` para algo nítido, pasa también un
  `style` sin connotación suave (ej. "particle texture, glowing ember") para que no se
  contradigan.
- **luma->alpha penaliza el rojo/azul saturado**: la luminancia Rec601 pesa el rojo a
  0.299 y el azul a 0.114, así que una partícula muy ROJA o muy AZUL sobre negro sale
  semi-transparente con los pesos por defecto. Para subir su opacidad, pasa a
  `comfyui_matting_luma_to_alpha` unos `luma_weights` con más peso a ese canal (ej.
  (0.6, 0.25, 0.15) para rojo) y sube `gamma`. Ajuste del paso de matting (caller), no del
  builder. Para partículas BRILLANTES/blancas (chispa, destello, humo claro) los pesos por
  defecto van perfectos.
- **El fondo debe quedar PLANO**: si el modelo mete escena/profundidad/múltiples
  partículas detrás, el alpha por luminancia recogerá basura. El positivo fuerza "flat
  solid black backdrop, single element" y el negativo rechaza 'scene, multiple particles,
  textured background, frame'. Si aún así sale ruido de fondo, sube `cfg` o haz reroll de
  `seed`.
- **Size pequeño por diseño**: 256 por defecto porque la textura se replica a miles; subir
  resolución no aporta y gasta VRAM. SDXL pide más (subir a 512/768); con IMG_dreamshaper_8
  (SD1.5) 256 va holgado en 8GB lowvram. Si hay OOM, baja `size` o usa SD1.5.
- **Es una función pura**: solo arma el dict. La generación real (GPU) la hacen
  `comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`; el
  alpha lo hace `comfyui_matting_luma_to_alpha`.
