feat(gamedev): comfyui_build_status_effect_icon_workflow — iconos de estado/buff-debuff (símbolo compacto legible a tamaño reducido, size 256, Rembg alpha)

Builder hermano de item_icon/ui_hud, diferenciado por rol: símbolo de estado
compacto (veneno/escudo/velocidad...) optimizado para 16-32 px, no objeto de
inventario ni chrome de interfaz. Pura (dict API format), 8 tests offline,
1 generación real verificada (poison 256x256 RGBA).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 00:48:24 +02:00
parent 19ad2b3e5d
commit 696148d56b
4 changed files with 470 additions and 0 deletions
@@ -0,0 +1,124 @@
---
name: comfyui_build_status_effect_icon_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_status_effect_icon_workflow(effect: str, *, ui_style: str = \"game status icon, bold symbol, flat\", checkpoint: str = \"dreamshaper_8.safetensors\", size: int = 256, transparent: bool = True, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, rembg_model: str = \"u2net\", negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"status_effect_icon\") -> dict"
description: "Construye el dict (API format) del workflow de UN icono de estado / buff-debuff 2D (veneno, quemadura, congelacion, escudo, regeneracion, aturdimiento, velocidad, sangrado, maldicion): simbolo unico audaz y LEGIBLE A TAMANO REDUCIDO, centrado, fondo limpio uniforme, estilo de UI consistente entre estados, recortable a alpha. Tamano por defecto menor (256) por ser iconos compactos del HUD. DISTINTO de item_icon (objeto de inventario) y ui_hud (chrome grande de interfaz): es un simbolo de estado compacto. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_item_icon/ui_hud_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev, gamedev-2d, ui, status, buff, debuff, icon, rembg, 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: effect
desc: "Nombre del efecto de estado (ej. 'poison', 'burning', 'frozen', 'shield', 'regeneration', 'stun', 'speed boost', 'bleed', 'curse'). Se inserta en un prompt scaffold de icono de estado. No puede estar vacio."
- name: ui_style
desc: "Descriptor de estilo de UI que mantiene consistentes los iconos de un set (ej. 'game status icon, bold symbol, flat', 'sci-fi HUD status, neon glow', 'pixel art status icon', 'minimal flat status icon'). Pasa el MISMO ui_style + checkpoint + lora a todos los iconos de la barra de estados para coherencia visual. keyword-only."
- name: checkpoint
desc: "Checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto; 'juggernaut_xl_v11.safetensors' para SDXL (mas VRAM, subir size). keyword-only."
- name: size
desc: "Lado del cuadrado en px (width = height = size). 256 por defecto: son iconos compactos que se muestran a tamano reducido en el HUD. keyword-only."
- name: transparent
desc: "Si True inyecta Image Rembg y el PNG sale con alpha (fondo recortado). False = simbolo opaco sobre fondo plano, recortable luego por el caller. keyword-only."
- name: seed
desc: "Semilla del KSampler. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.safetensors'). 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: rembg_model
desc: "Modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo se usa si transparent=True. keyword-only."
- name: negative
desc: "Prompt negativo. None usa el negativo por defecto pensado para iconos de estado (un simbolo simple legible, fondo limpio, sin escena/personaje/texto). 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 icono de estado ('{effect} status effect icon, {ui_style}, simple bold symbol, centered, readable at small size, plain background, ...') + LoRA de estilo opcional + Image Rembg (si transparent). UN icono; barra de estados completa -> llamar por effect con mismo ui_style/checkpoint/lora."
tested: true
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; effect + 'status effect icon' + 'centered' + 'readable at small size' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge size: width==height (cuadrado); default 256", "edge ui_style en prompt", "edge effect en prompt", "edge lora: LoraLoader presente con strength", "error effect vacio -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_status_effect_icon_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_status_effect_icon_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_status_effect_icon_workflow import comfyui_build_status_effect_icon_workflow
# Un icono de veneno con fondo transparente (alpha), listo para submit.
wf = comfyui_build_status_effect_icon_workflow(
"poison",
ui_style="game status icon, bold symbol, flat, green",
transparent=True,
seed=42,
)
# Barra de estados coherente: misma firma de estilo para cada efecto.
# for fx in ["burning", "frozen", "shield", "regeneration", "stun", "speed boost"]:
# wf = comfyui_build_status_effect_icon_workflow(fx, ui_style="game status icon, bold symbol, flat",
# lora="detail_tweaker_sd15.safetensors", seed=42)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# Atlas de estados: montar los PNG resultantes con comfyui_build_grid.
```
O lanzable directo con: `./fn run comfyui_build_status_effect_icon_workflow` (imprime nodos + class_types del ejemplo).
## Cuando usarla
Cuando necesites iconos de buff/debuff para la barra de estados de un juego (RPG,
roguelike, MOBA): veneno, quemadura, congelacion, escudo, regeneracion, aturdimiento,
velocidad, sangrado, bendicion, maldicion. El icono se pinta encima del retrato o la
barra del personaje para indicar un efecto activo, asi que la prioridad es la
LEGIBILIDAD a tamano reducido (16-32 px): un simbolo audaz y simple, no una
ilustracion recargada. Pasa el MISMO `ui_style` + `checkpoint` + (`lora`) a todos los
estados del set para que combinen visualmente; varia solo `effect`. `transparent`
recorta el fondo (alpha) listo para el motor. Para un atlas, genera cada icono y monta
los PNG con `comfyui_build_grid`.
Eligela frente a sus hermanos por el ROL del asset:
- **item_icon** -> objeto de inventario (espada, pocion, anillo): ilustracion de un
objeto, no un simbolo de estado.
- **ui_hud** -> chrome grande de la interfaz (botones, marcos, barras de vida): piezas
de layout, no simbolos compactos.
- **status_effect_icon (esta)** -> simbolo de estado compacto que se superpone al HUD,
optimizado para legibilidad a tamano reducido.
## Gotchas
- **Legibilidad > detalle**: el icono se muestra a 16-32 px sobre el personaje; un
simbolo recargado se vuelve ilegible. El prompt empuja a "simple bold symbol,
readable at small size" y el `size` por defecto es menor (256, no 512 como
item_icon/ui_hud). El negativo por defecto rechaza "intricate details / complex /
cluttered" justamente por esto. Si el modelo mete una escena, refuerza `ui_style`
con "single bold icon, minimal".
- **El recorte usa Rembg, NO luma-to-alpha**: un icono de estado es un simbolo solido
con silueta definida, rembg lo recorta limpio. `comfyui_matting_luma_to_alpha` es
para translucidos sobre negro (humo/fuego/magia) y aplanaria el simbolo — no la uses
para estos iconos.
- **Coherencia del set = mismos parametros**: si cambias `ui_style`/`checkpoint`/
`lora`/`seed` entre estados, la barra deja de combinar. Fija esos y varia solo
`effect`. Una convencion util: el color habla del tipo de efecto (verde veneno, rojo
quemadura, azul congelacion) sin sacrificar la consistencia del simbolo.
- **El texto/numero lo pone el motor, no la imagen**: el negativo por defecto empuja a
"no text / no letters" para que la silueta quede limpia; el contador de turnos o el
stack del buff se renderiza en el juego sobre el icono. Si quieres texto horneado,
pasa un `negative` propio sin "text, letters".
- **SDXL pide mas VRAM y resolucion**: con `checkpoint="juggernaut_xl_v11.safetensors"`
sube `size` a 512/768; con dreamshaper_8 (SD1.5) deja 256 (holgado en 8GB lowvram).
- `transparent=False` deja el simbolo opaco sobre fondo plano: util si prefieres
recortar fuera del workflow o el motor compone sobre un slot solido.
- Es una funcion **pura**: solo arma el dict. La generacion real (GPU) la hacen
`comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`.
@@ -0,0 +1,229 @@
"""Construye el workflow ComfyUI de UN icono de estado / buff-debuff (API format).
Iconos de estado de juego (veneno, quemadura, congelacion, escudo, regeneracion,
aturdimiento, velocidad, sangrado, bendicion, maldicion): simbolo unico, audaz y
LEGIBLE A TAMANO REDUCIDO, centrado, fondo limpio y uniforme, estilo de UI
consistente entre estados del mismo set, recortable a alpha. Es el builder hermano
de comfyui_build_item_icon_workflow / comfyui_build_ui_hud_workflow: mismo patron
(PURO, dict API format) que compone funciones existentes del registry, no reescribe
el grafo.
DISTINTO de item_icon y ui_hud: un icono de estado no es un objeto de inventario
(item_icon) ni un elemento de chrome grande de la interfaz (ui_hud, botones/marcos/
barras). Es un SIMBOLO compacto que se pinta encima del retrato/barra del personaje
para indicar un efecto activo: por eso el prompt empuja a "simple bold symbol,
readable at small size" y el tamano por defecto es menor (256), porque el icono se
mostrara a 16-32 px en el HUD y un simbolo recargado se vuelve ilegible.
Cableado:
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
-> CLIPTextEncode (prompt scaffold de icono de estado) ...
-> VAEDecode -> [Image Rembg opcional] -> SaveImage
Compone:
- comfyui_build_txt2img_workflow -> base txt2img cuadrada
- comfyui_inject_lora -> LoRA de estilo opcional (consistencia)
- 'Image Rembg (Remove Background)' (helper local) -> fondo transparente
Por que Rembg y NO comfyui_matting_luma_to_alpha: un icono de estado es un simbolo
SOLIDO con silueta definida; rembg recorta limpio la silueta dejando alpha. La
luma-to-alpha es para translucidos sobre negro (humo/fuego/magia) y aplanaria el
simbolo. Si el caller prefiere recortar fuera del workflow (transparent=False) deja
la imagen opaca sobre fondo plano, recortable luego por el pipeline o el caller.
El mismo ui_style + checkpoint + (lora) en todos los iconos de un set (veneno,
escudo, velocidad...) hace que combinen visualmente: es la clave de una barra de
estados coherente, igual que en los iconos de inventario y los elementos del HUD.
class_types/inputs verificados contra /object_info del servidor (8GB lowvram):
CheckpointLoaderSimple, CLIPTextEncode, EmptyLatentImage, KSampler, VAEDecode,
SaveImage, LoraLoader, 'Image Rembg (Remove Background)' (transparency BOOLEAN).
Funcion pura: sin red, sin I/O. No muta dicts de entrada (copia profunda en el
helper de rembg). Determinista para los mismos argumentos.
"""
from __future__ import annotations
import copy
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
# Negativo por defecto pensado para iconos de estado: un solo simbolo limpio y
# legible, sin escena ni personaje ni texto/marcas que ensucien la silueta
# centrada y recortable. Empuja contra el detalle excesivo que arruina la
# legibilidad cuando el icono se muestra a 16-32 px en el HUD.
_STATUS_NEGATIVE = (
"blurry, lowres, intricate details, complex, busy background, "
"cluttered, multiple symbols, detailed scene, landscape, character, "
"person, face, text, letters, words, watermark, signature, photo, "
"photorealistic, realistic, jpeg artifacts, cropped, out of frame, deformed"
)
def _inject_rembg(workflow: dict, model: str) -> dict:
"""Inserta 'Image Rembg (Remove Background)' (transparency=True) entre VAEDecode y SaveImage.
Mismo helper que usan comfyui_build_item_icon_workflow / comfyui_build_ui_hud_workflow:
el nodo recorta la silueta del simbolo de estado dejando alpha. Repunta
SaveImage.images a la salida del Rembg.
"""
wf = copy.deepcopy(workflow)
vaedecode_id = next(
(nid for nid, n in wf.items() if n.get("class_type") == "VAEDecode"), None
)
save_id = next((nid for nid, n in wf.items() if n.get("class_type") == "SaveImage"), None)
if vaedecode_id is None or save_id is None:
raise ValueError(
"comfyui_build_status_effect_icon_workflow: no se encontro VAEDecode/SaveImage para Rembg"
)
numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
rembg_id = str((max(numeric) + 1) if numeric else len(wf) + 1)
wf[rembg_id] = {
"class_type": "Image Rembg (Remove Background)",
"inputs": {
"images": [vaedecode_id, 0],
"transparency": True,
"model": model,
"post_processing": False,
"only_mask": False,
"alpha_matting": False,
"alpha_matting_foreground_threshold": 240,
"alpha_matting_background_threshold": 10,
"alpha_matting_erode_size": 10,
"background_color": "none",
},
}
wf[save_id]["inputs"]["images"] = [rembg_id, 0]
return wf
def comfyui_build_status_effect_icon_workflow(
effect: str,
*,
ui_style: str = "game status icon, bold symbol, flat",
checkpoint: str = "dreamshaper_8.safetensors",
size: int = 256,
transparent: bool = True,
seed: int = 0,
lora: str | None = None,
lora_strength: float = 1.0,
rembg_model: str = "u2net",
negative: str | None = None,
steps: int = 28,
cfg: float = 7.0,
sampler_name: str = "dpmpp_2m",
scheduler: str = "karras",
filename_prefix: str = "status_effect_icon",
) -> dict:
"""Construye el dict (API format) del workflow de un icono de estado / buff-debuff.
Args:
effect: nombre del efecto de estado (ej. "poison", "burning", "frozen",
"shield", "regeneration", "stun", "speed boost", "bleed", "curse").
Se inserta en un prompt scaffold de icono de estado. No puede estar
vacio.
ui_style: descriptor de estilo de UI que mantiene consistentes los iconos
de un set (ej. "game status icon, bold symbol, flat", "sci-fi HUD
status, neon glow", "pixel art status icon", "minimal flat status
icon"). Pasa el MISMO ui_style + checkpoint + (lora) a todos los iconos
de la barra de estados para coherencia visual. keyword-only.
checkpoint: checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5,
holgado en 8GB lowvram) por defecto; 'juggernaut_xl_v11.safetensors'
para SDXL (mas VRAM, subir size). keyword-only.
size: lado del cuadrado en px (width = height = size). 256 por defecto:
son iconos compactos que se muestran a tamano reducido en el HUD.
keyword-only.
transparent: si True inyecta Rembg y el PNG sale con alpha (fondo
recortado). Si False deja el simbolo opaco sobre fondo plano,
recortable luego por el caller/pipeline. keyword-only.
seed: semilla del KSampler. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
keyword-only.
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime).
Solo se usa si transparent=True. keyword-only.
negative: prompt negativo. None usa el negativo por defecto pensado para
iconos de estado (un simbolo simple legible, fondo limpio, sin
escena/personaje/texto). keyword-only.
steps, cfg, sampler_name, scheduler, filename_prefix: parametros de
generacion. keyword-only.
Returns:
dict en API format listo para comfyui_submit_workflow: txt2img base
cuadrada con prompt scaffold de icono de estado ('{effect} status effect
icon, {ui_style}, simple bold symbol, centered, readable at small size,
plain background, ...') + LoRA de estilo opcional + Rembg (si transparent).
Es UN icono; una barra de estados completa -> llamar por effect con el
mismo ui_style/checkpoint/lora y montar con comfyui_build_grid si se quiere
un atlas.
Raises:
ValueError: si effect esta vacio, o si la base no tiene VAEDecode/SaveImage
donde inyectar el Rembg (propagado por el helper).
"""
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
if not effect or not effect.strip():
raise ValueError(
"comfyui_build_status_effect_icon_workflow: 'effect' no puede estar vacio"
)
lora_strength = max(0.0, min(2.0, float(lora_strength)))
neg = _STATUS_NEGATIVE if negative is None else negative
# Prompt scaffold de icono de estado: simbolo unico, audaz, centrado, legible
# a tamano reducido, fondo plano, recortable.
positive = (
f"{effect.strip()} status effect icon, {ui_style}, simple bold symbol, "
"centered, readable at small size, plain background, clean, high detail"
)
wf = comfyui_build_txt2img_workflow(
checkpoint,
positive,
neg,
steps=steps,
cfg=cfg,
width=size,
height=size,
seed=seed,
sampler_name=sampler_name,
scheduler=scheduler,
filename_prefix=filename_prefix,
)
if lora:
from ml.comfyui_inject_lora import comfyui_inject_lora
wf = comfyui_inject_lora(
wf, lora, strength_model=lora_strength, strength_clip=lora_strength
)
if transparent:
wf = _inject_rembg(wf, rembg_model)
return wf
if __name__ == "__main__":
import json
wf = comfyui_build_status_effect_icon_workflow(
"poison",
ui_style="game status icon, bold symbol, flat, green",
transparent=True,
seed=42,
)
print(
json.dumps(
{
"nodes": list(wf),
"classes": sorted({n["class_type"] for n in wf.values()}),
},
indent=2,
)
)
@@ -0,0 +1,116 @@
"""Tests offline (sin red, sin GPU) de comfyui_build_status_effect_icon_workflow.
Verifican que el dict en API format se construye correctamente: clases presentes,
cableado del Rembg, prompt scaffold de icono de estado, y reflejo de los argumentos
(effect, ui_style, size, transparent, lora). No tocan el servidor ComfyUI.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from ml.comfyui_build_status_effect_icon_workflow import ( # noqa: E402
comfyui_build_status_effect_icon_workflow,
)
def _classes(wf):
return {n["class_type"] for n in wf.values()}
def _positive_prompt(wf):
"""Texto positivo: el CLIPTextEncode al que apunta KSampler.positive."""
ks = next(n for n in wf.values() if n["class_type"] == "KSampler")
pos_id = ks["inputs"]["positive"][0]
return wf[pos_id]["inputs"]["text"]
def test_golden_transparent():
"""Caso feliz: icono transparente -> Rembg cableado, prompt de estado, clases base."""
wf = comfyui_build_status_effect_icon_workflow("poison", transparent=True, seed=42)
cls = _classes(wf)
for expected in {
"CheckpointLoaderSimple",
"KSampler",
"VAEDecode",
"SaveImage",
"Image Rembg (Remove Background)",
}:
assert expected in cls, f"falta clase {expected}"
prompt = _positive_prompt(wf)
assert "poison" in prompt
assert "status effect icon" in prompt
assert "centered" in prompt
assert "readable at small size" in prompt
# SaveImage debe tomar la imagen del Rembg, no del VAEDecode.
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
rembg_id = next(
nid for nid, n in wf.items() if n["class_type"] == "Image Rembg (Remove Background)"
)
assert save["inputs"]["images"][0] == rembg_id
rembg = wf[rembg_id]
assert rembg["inputs"]["transparency"] is True
def test_edge_transparent_false_no_rembg():
"""transparent=False -> sin nodo Rembg; SaveImage cuelga del VAEDecode."""
wf = comfyui_build_status_effect_icon_workflow("shield", transparent=False)
assert "Image Rembg (Remove Background)" not in _classes(wf)
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
vae_id = next(nid for nid, n in wf.items() if n["class_type"] == "VAEDecode")
assert save["inputs"]["images"][0] == vae_id
def test_edge_size_reflected_square():
"""size se refleja como width == height (cuadrado). Default 256 (icono compacto)."""
wf = comfyui_build_status_effect_icon_workflow("frozen", size=128)
latent = next(n for n in wf.values() if n["class_type"] == "EmptyLatentImage")
assert latent["inputs"]["width"] == 128
assert latent["inputs"]["height"] == 128
wf_default = comfyui_build_status_effect_icon_workflow("frozen")
latent_d = next(n for n in wf_default.values() if n["class_type"] == "EmptyLatentImage")
assert latent_d["inputs"]["width"] == 256
assert latent_d["inputs"]["height"] == 256
def test_edge_ui_style_reflected():
"""ui_style se inserta en el prompt positivo."""
wf = comfyui_build_status_effect_icon_workflow(
"burning", ui_style="sci-fi HUD status, neon glow"
)
assert "sci-fi HUD status, neon glow" in _positive_prompt(wf)
def test_edge_effect_reflected():
"""effect se inserta literal en el prompt positivo."""
wf = comfyui_build_status_effect_icon_workflow("regeneration")
assert "regeneration" in _positive_prompt(wf)
def test_edge_lora_injected():
"""lora -> LoraLoader presente con la fuerza dada."""
wf = comfyui_build_status_effect_icon_workflow(
"speed boost", lora="detail_tweaker_sd15.safetensors", lora_strength=0.8
)
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.8)
def test_error_empty_effect():
"""effect vacio -> ValueError."""
with pytest.raises(ValueError):
comfyui_build_status_effect_icon_workflow(" ")
def test_determinism():
"""Mismos argumentos -> mismo dict (funcion pura)."""
a = comfyui_build_status_effect_icon_workflow("stun", seed=7)
b = comfyui_build_status_effect_icon_workflow("stun", seed=7)
assert a == b