diff --git a/docs/capabilities/gamedev-2d.md b/docs/capabilities/gamedev-2d.md index 30a19d26..22cca2fd 100644 --- a/docs/capabilities/gamedev-2d.md +++ b/docs/capabilities/gamedev-2d.md @@ -58,6 +58,7 @@ VFX (ver `reports/0143`). | `comfyui_build_foliage_set_workflow_py_ml` | `(plant, *, view="side", style="game foliage, stylized", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN elemento de **vegetación/foliage** de escenario (árbol, arbusto, hierba alta, flores, helecho, hongo, cactus, tronco caído, juncos, hiedra): UN elemento de **naturaleza ORGÁNICA AISLADO** y centrado a perspectiva de juego (`{view} view`, `side` por defecto), fondo limpio recortable a alpha (`{plant}, {view} view, {style}, single plant element, centered, plain background, game nature asset, natural vegetation, organic, isolated plant…`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). **Vegetación que viste el terreno**, distinta del **objeto MANUFACTURADO** suelto (≠ `prop_object`: barril/cofre/mueble) y del **EDIFICIO** (≠ `structure`: casa/torre); el negativo rechaza `building / manmade object / barrel / furniture / person` y `multiple plants / dense forest / jungle / landscape` (UN elemento, no un bosque) + `pot / planter / vase` (planta en maceta = `prop_object`). Recorte por **Rembg** (planta opaca de silueta definida), no luma→alpha. Set coherente = mismo `view`/`style`/`checkpoint`/`lora`, varía solo `plant`. ⚠️ **dos gotchas reales SD1.5+Rembg**: (1) **plantas grandes (árbol) tienden a PAISAJE** (cielo+campo) en lugar de fondo plano → re-roll de seeds buscando fondo uniforme (`comfyui_batch_generate`); (2) **follaje verde claro sobre fondo claro → Rembg se come las hojas** y deja solo tronco/ramas → preferir elementos de **silueta compacta y color saturado** (hongo, arbusto denso) o `transparent=False` + matting manual. Probado e2e en GPU con SD1.5 — golden `a glowing mushroom` seed 7 512×512 RGBA, hongo centrado recortado a alpha limpio (centroide 0.51/0.58, opaco 19%, `prompt_id 8fb65a51`); evidencia del gotcha del roble en `reports/0170`. SD1.5. | | `comfyui_build_trap_hazard_workflow_py_ml` | `(hazard, *, view="side", style="game hazard trap", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UNA **trampa/peligro JUGABLE** de nivel (pinchos del suelo, sierra giratoria, foso de lava, placa de presión, columna de llamas, trampa de flechas, charco ácido, descarga eléctrica, prensa, estaca cayendo): UN objeto de **peligro AISLADO** y centrado a perspectiva de juego (`{view} view`, `side` por defecto), fondo limpio recortable a alpha (`{hazard}, {view} view, {style}, single hazard object, trap, dangerous, centered, plain background, game asset, high detail`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). **Peligro al que el motor asigna hitbox de daño + estado activo/inactivo**, distinto del **objeto INERTE de decoración** (≠ `prop_object`: barril/cofre que solo ambienta) y del **enemigo VIVO** (≠ `enemy_creature`); el negativo rechaza `character / person / creature / multiple objects` para que salga el mecanismo, no un enemigo ni una escena. Recorte por **Rembg** (trampa sólida de silueta definida: pinchos/sierra/placa); ⚠️ para hazards **puramente etéreos** (columna de llamas, arco eléctrico, gas) usar `transparent=False` + `comfyui_matting_luma_to_alpha` (conserva el falloff translúcido para blend aditivo), no Rembg. `view` fija la perspectiva del nivel (side/top-down/iso); set coherente = mismo `view`/`style`/`checkpoint`/`lora`, varía solo `hazard`. Probado e2e en GPU con SD1.5 — `spiked floor trap` side seed 7 512×512 RGBA, mecanismo de peligro centrado recortado a alpha (alpha extrema 0–255, fondo transparente real, `prompt_id ab1b1560`, `reports/0174`). SD1.5. | | `comfyui_build_particle_texture_workflow_py_ml` | `(particle, *, soft=True, style="particle texture, soft glow", checkpoint="dreamshaper_8…", size=256, seed=0, lora=None, …) -> dict` | UNA textura de **partícula individual** reutilizable (chispa, humo, polvo, destello/flare, gota, copo, hoja, círculo de energía) — el "ladrillo" que el sistema de partículas del motor (Godot `GPUParticles2D`, Unity VFX Graph) instancia a **miles** y anima (spawn/fade/color over lifetime). Aislada y centrada **sobre fondo NEGRO** (`{particle} particle, {style}, isolated on pure black background, edges, single element, for game particle system…`) → txt2img cuadrado + LoRA estilo opcional. **`soft` controla el borde**: `soft=True` (defecto) → `soft glow, feathered edges` (humo/destello/gota); `soft=False` → `crisp sharp edges, high contrast` (chispa/copo/hoja). **NO inyecta Rembg** (rompería el falloff translúcido): insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, additive blend en el motor). **`size` por defecto pequeño (256)** porque se replica a miles. **DISTINTO de `vfx_spritesheet`** (ese es la SECUENCIA animada de un efecto; esto es UNA textura estática reutilizable) **y de `decal_overlay`** (ése es una mancha de desgaste estática para superponer; éste es un emisor de partículas). ⚠️ el `style` por defecto trae "soft glow" → si pides `soft=False` para algo nítido, usa un `style` sin connotación suave. Probado e2e en GPU con SD1.5 — `spark` 256×256 sobre negro plano (dark 85%) + luma→alpha RGBA con falloff preservado (`reports/0163`). SD1.5. | +| `comfyui_build_weather_overlay_workflow_py_ml` | `(weather, *, on_black=True, style="weather overlay, atmospheric", checkpoint="dreamshaper_8…", width=1024, height=576, seed=0, lora=None, …) -> dict` | UNA **capa de clima/atmósfera a PANTALLA COMPLETA** que cubre toda la vista del jugador y se superpone sobre la escena con blend del motor (lluvia, niebla, nieve, rayos de sol/god rays, polvo, viñeta de tormenta): cobertura **uniforme de borde a borde**, generada **APAISADA a resolución de pantalla** (16:9, 1024×576 por defecto — `width>height`, NO cuadrado) (`{weather} overlay, {style}, full screen atmospheric layer, , seamless full screen coverage, edge to edge, game VFX…`) → txt2img apaisado + LoRA estilo opcional. **`on_black` elige el modo de blend**: `on_black=True` (defecto) = clima BRILLANTE sobre **NEGRO puro** (estrías de lluvia, copos, haces de luz, motas), **sin Rembg**, insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, **blend aditivo/screen** — el negro desaparece, el clima brilla sobre la escena); `on_black=False` = **película TRANSLÚCIDA** semi-transparente (niebla densa, tinte de tormenta) para blend multiply/overlay o alpha global. El negativo rechaza `solid object/single subject/character/building/landscape scene/horizon line/frame` (cobertura total, NO un sujeto centrado) + (si `on_black`) `blue sky/gray/white background` para forzar negro plano. **DISTINTO de `decal_overlay`** (ése es una mancha LOCALIZADA que se pega en un punto de una superficie) **y de `vfx_spritesheet`** (ése es la SECUENCIA animada de UN efecto puntual): la capa de clima es UNA película estática de cobertura full-screen que el motor anima por scroll/loop/shader. Set coherente = mismo `style`/`checkpoint`/`lora`, varía `weather`/`seed`. ⚠️ algunos climas (lluvia/niebla) pintan cielo azul de fondo en SD1.5 aunque pidas negro → subir `cfg` + re-roll de `seed`; climas brillantes-sobre-negro (god rays, snow, sparks) salen más limpios que los difusos (fog). luma Rec601 penaliza el azul → para lluvia azulada ajustar `luma_weights`/`gamma` en el matting. Probado e2e en GPU con SD1.5 — `heavy rain` on_black seed 11 **1024×576** (16:9 exacto), estrías de lluvia brillantes sobre **negro plano** (esquinas luma 0.00, dark 89.6%, lluvia 1.4% brillante) apto luma→alpha aditivo (`prompt_id 5d2300d1`, `reports/0176`). SD1.5/SDXL. | | `comfyui_build_rune_glyph_workflow_py_ml` | `(glyph, *, glow=True, style="arcane glowing rune", checkpoint="dreamshaper_8…", size=512, seed=0, lora=None, …) -> dict` | UNA **runa / glifo / sigilo mágico** (glifos rúnicos, círculos mágicos, sigilos de invocación, inscripciones brillantes) para hechizos, portales, marcas de conjuro y efectos de magia: símbolo arcano **aislado** sobre fondo uniforme (`{glyph}, {style}, magic symbol, single isolated glyph, centered, glowing on a solid pure black background, occult sigil, arcane inscription, no scenery, game asset…`) → txt2img cuadrado + LoRA estilo opcional. **`glow` elige el camino a alpha**: `glow=True` (defecto) = runa BRILLANTE sobre **NEGRO puro**, **sin Rembg** (recortaría el halo del resplandor), insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, **blend aditivo** en el motor — conserva el glow); `glow=False` = runa MATE/grabada sobre fondo plano (el negativo rechaza `glow/neon/bloom`), recorte/inversión por el caller. El negativo rechaza `realistic text/readable words/latin alphabet` (un glifo arcano, **no letras reales**) + fondo texturizado/niebla. **DISTINTO de `status_effect_icon`** (símbolo SÓLIDO de UI, recorte Rembg, legible a 16-32 px en el HUD): la runa es una marca translúcida que **emite luz** e se inscribe en el mundo. Grimorio coherente = mismo `style`/`checkpoint`/`lora`, varía `glyph`/`seed`. ⚠️ luma Rec601 penaliza el rojo → para runas rojas (sigilo demoníaco) pasar `luma_weights` con más peso al rojo + subir `gamma`; runas blancas/azules/doradas van con pesos por defecto. Probado e2e en GPU con SD1.5 — `circular summoning rune` glow seed 11 512×512, círculo de invocación brillante sobre **negro puro** (esquinas luma 0.00, dark 83%, runa 3.4% brillante, max 255) apto luma→alpha (`prompt_id 701d149a`, `reports/0172`). SD1.5. | | `comfyui_build_title_lettering_workflow_py_ml` | `(text, *, letter_style="epic fantasy metallic", checkpoint="juggernaut_xl_v11…", width=1024, height=512, transparent=True, seed=0, lora=None, …) -> dict` | EL texto/logo de **título** de un juego (el nombre del juego o una palabra) renderizado con un **tratamiento de lettering** (metálico, tallado en fuego/piedra/madera, neón, cristal, oro), formato **apaisado** (`width>height`, 1024×512 por defecto), fondo plano recortable a alpha (`the word "{text}" as a game logo, {letter_style} lettering, stylized typography, centered, plain background…`) → txt2img apaisado + LoRA estilo opcional + Rembg (alpha). El **negativo NO rechaza texto** (el lettering es el sujeto) y empuja contra el ruido textual (`extra letters/jumbled text/deformed letters`). El VALOR es el ESTILO del lettering, **NO** la fidelidad tipográfica: ⚠️ la difusión renderiza texto de forma imperfecta — letras de más, deformadas o mal escritas; mitigar con palabras CORTAS en MAYÚSCULA, **re-roll de seeds** (`comfyui_batch_generate`), SDXL > SD1.5 para texto, o pintar el texto real con una fuente en el motor. **Una palabra que es un objeto concreto (DRAGON) → el modelo dibuja el objeto, no las letras** — usar palabras abstractas o reforzar `letter_style`. Marca coherente = mismo `letter_style`/`checkpoint`/`lora`, varía solo `text`. Recorte por **Rembg** (logo sólido), no luma→alpha. Probado e2e en GPU: `DRAGON`/`fire engraved` SD1.5 1024×512 → ilustró dragones rojos (alpha OK, confirma el gotcha de palabra-objeto, `prompt_id 6f3920b7`); `AETHER`/`epic fantasy metallic` SDXL 768×384 → **logo de texto metálico dorado** legible con ortografía imperfecta + alpha (`prompt_id 2a7fe8ba`, `reports/0165`). SD1.5/SDXL. | diff --git a/python/functions/ml/comfyui_build_weather_overlay_workflow.md b/python/functions/ml/comfyui_build_weather_overlay_workflow.md new file mode 100644 index 00000000..0ecd4312 --- /dev/null +++ b/python/functions/ml/comfyui_build_weather_overlay_workflow.md @@ -0,0 +1,174 @@ +--- +name: comfyui_build_weather_overlay_workflow +kind: function +lang: py +domain: ml +purity: pure +version: 1.0.0 +signature: 'def comfyui_build_weather_overlay_workflow(weather: str, *, on_black: bool = True, style: str = "weather overlay, atmospheric", checkpoint: str = "dreamshaper_8.safetensors", width: int = 1024, height: int = 576, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = "dpmpp_2m", scheduler: str = "karras", filename_prefix: str = "weather_overlay") -> dict' +description: "Construye el dict (API format) del workflow de UN overlay de clima/atmosfera a PANTALLA COMPLETA: una capa que cubre toda la vista y se superpone sobre la escena con un blend mode del motor — lluvia, niebla, nieve, rayos de sol (god rays), polvo, viñeta de tormenta. Cobertura uniforme de borde a borde, generada APAISADA a resolucion de pantalla (16:9, 1024x576 por defecto). on_black=True (defecto) la pone sobre NEGRO puro con el clima brillante encima, para extraer alpha por luminancia con comfyui_matting_luma_to_alpha y blend additive/screen; on_black=False = pelicula translucida semi-transparente (niebla, tinte) para multiply/overlay o alpha global. NO inyecta Rembg (el matting de una capa es luma-to-alpha o alpha global, no un nodo). Compone comfyui_build_txt2img_workflow + comfyui_inject_lora. Hermano de comfyui_build_decal_overlay/particle_texture/vfx_spritesheet_workflow; distinto: capa de cobertura TOTAL, no mancha localizada ni secuencia animada. Pura, sin red ni I/O. class_types verificados contra /object_info." +uses_functions: + - comfyui_build_txt2img_workflow_py_ml + - comfyui_inject_lora_py_ml +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +tags: [comfyui, ml, gamedev, gamedev-2d, weather, overlay, atmosphere, rain, fog, snow, godrays, fullscreen, alpha, blend, luma, workflow] +tested: true +test_file_path: python/functions/ml/comfyui_build_weather_overlay_workflow_test.py +tests: + - test_golden_weather_on_black_recipe + - test_golden_landscape_dims_default + - test_edge_on_black_toggles_layer_mode + - test_edge_weather_reflected + - test_edge_style_in_prompt + - test_edge_dims_reflected + - test_edge_negative_isolates_layer + - test_edge_lora_reflected + - test_error_empty_weather + - test_determinism +file_path: python/functions/ml/comfyui_build_weather_overlay_workflow.py +imports: [] +params: + - name: weather + desc: "Descripcion del clima/atmosfera a generar como capa (ej. 'heavy rain', 'thick fog', 'falling snow', 'god rays', 'sun shafts', 'dust haze', 'storm vignette', 'sandstorm', 'ash fall', 'blizzard'). Se inserta en un prompt scaffold de capa de cobertura total. No puede estar vacio." + - name: on_black + desc: "Si True (defecto) genera la capa sobre fondo NEGRO puro con el clima BRILLANTE encima (estrias de lluvia, copos, haces de luz, motas), pensada para luma->alpha con comfyui_matting_luma_to_alpha y blend additive/screen en el motor (el negro desaparece, el clima brilla sobre la escena). False = pelicula TRANSLUCIDA semi-transparente (niebla densa, tinte de tormenta, bruma) para blend multiply/overlay o alpha global. keyword-only." + - name: style + desc: "Descriptor de estilo de la capa (ej. 'weather overlay, atmospheric', 'stylized rain, painterly', 'photoreal fog', 'anime sun rays'). Pasa el MISMO style + checkpoint + lora a todas las capas de un set 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 width/height a 1344x768). keyword-only." + - name: width + desc: "Ancho de la capa en px (multiplo de 8). 1024 por defecto (16:9 con height 576: resolucion de pantalla apaisada). keyword-only." + - name: height + desc: "Alto de la capa en px (multiplo de 8). 576 por defecto. keyword-only." + - name: seed + desc: "Semilla del KSampler. Misma seed + mismos weather/style -> misma capa; variar seed da variantes del mismo tipo de clima. 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 capa de cobertura total (sin sujeto/objeto/escena/horizonte/profundidad/marco que rompan la pelicula atmosferica) MAS, si on_black=True, el refuerzo de fondo negro plano. 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 apaisada (16:9) con prompt scaffold de capa de clima ('{weather} overlay, {style}, full screen atmospheric layer, , seamless full screen coverage, edge to edge, game VFX, ...') + el negativo rechaza sujeto/objeto/escena/horizonte/profundidad/marco que romperian la pelicula de cobertura (y refuerza fondo negro plano cuando on_black=True) + LoRA de estilo opcional. NO lleva Rembg: con on_black=True el PNG resultante se convierte a RGBA con comfyui_matting_luma_to_alpha (luma=alpha) en un paso posterior para blend aditivo. UNA capa; variar seed da variantes del mismo clima." +--- + +Construye el dict (API format) del workflow de UN overlay de clima/atmosfera a +**pantalla completa**: una CAPA que cubre TODA la vista del jugador y se superpone +sobre la escena del juego con un blend mode del motor — lluvia, niebla, nieve, rayos +de sol (god rays), polvo en suspension, viñeta de tormenta. No es un sujeto centrado +ni un efecto puntual: es una pelicula de cobertura uniforme de borde a borde, generada +APAISADA a resolucion de pantalla (16:9, 1024x576 por defecto). `on_black=True` +(defecto) la pone sobre NEGRO puro con el clima brillante encima, pensada para extraer +alpha por luminancia con `comfyui_matting_luma_to_alpha` y blendear additive/screen +(el negro desaparece, el clima brilla sobre la escena); `on_black=False` la genera como +pelicula translucida semi-transparente (niebla densa, tinte de tormenta) para blend +multiply/overlay o alpha global. NO inyecta Rembg (el matting de una capa es +luma-to-alpha o un alpha global, no un nodo). Compone `comfyui_build_txt2img_workflow` ++ `comfyui_inject_lora` (estilo opcional). Hermano de +`comfyui_build_decal_overlay_workflow` / `comfyui_build_particle_texture_workflow` / +`comfyui_build_vfx_spritesheet_workflow`. Pura, sin red ni I/O. class_types verificados +contra /object_info. + +## Cuando usarla + +Cuando necesites una CAPA de clima/atmosfera que cubra toda la pantalla y se superponga +sobre la escena del juego: lluvia cayendo sobre todo el nivel, niebla densa que tiñe la +vista, nieve, haces de luz volumetrica (god rays), polvo en suspension, un oscurecimiento +de bordes de tormenta. La capa se genera apaisada a resolucion de pantalla y se compone en +el motor con un blend mode (additive/screen para particulas brillantes sobre negro; +multiply/overlay o alpha global para una pelicula translucida). + +Flujo tipico despues de generar: + +1. `comfyui_build_weather_overlay_workflow("heavy rain", on_black=True)` -> dict. +2. `comfyui_submit_workflow` -> `comfyui_wait_result` -> `comfyui_fetch_output_image` + (PNG de la capa de lluvia sobre negro). +3. `comfyui_matting_luma_to_alpha(png, gamma=..., black_point=...)` -> PNG RGBA donde la + luminancia ES el alpha: estrias brillantes=opacas, negro=transparente. Blend additive + en el motor encima de la escena. + +Pasa el MISMO `style` + `checkpoint` + (`lora`) a todas las capas de un set de clima para +que combinen; varia `seed` para sacar variantes del mismo clima (varias capas de lluvia +distintas para alternar/loopear en el motor). + +**Elige este builder y NO `comfyui_build_decal_overlay_workflow` ni +`comfyui_build_vfx_spritesheet_workflow` cuando** lo que quieres es una capa de cobertura +TOTAL de pantalla. Un decal es una mancha LOCALIZADA y recortable que se pega en un punto +de una superficie; un spritesheet es una SECUENCIA animada de N frames de UN efecto +puntual. El weather overlay es UNA pelicula estatica de cobertura full-screen que el motor +anima por scroll/loop/shader. + +## Gotchas + +- **on_black=True esta pensado para luma->alpha, no es opcional de adorno**: la capa se + genera sobre NEGRO puro precisamente para que `comfyui_matting_luma_to_alpha` mapee la + luminancia a alpha y conserve el falloff de las estrias/particulas (lluvia, nieve, haces + de luz). Con additive/screen el negro desaparece y solo el clima brilla sobre la escena. + Si extraes el alpha con un matting binario (rembg) pierdes el degradado. Para climas que + OSCURECEN/tiñen toda la vista (niebla densa, tinte de tormenta) usa `on_black=False` + (pelicula translucida) y blendea multiply/overlay o con un alpha global bajo. +- **NO inyecta Rembg a proposito**: a diferencia de los builders de sprite/prop/item, este + NO lleva 'Image Rembg (Remove Background)'. El SaveImage toma directo del VAEDecode (capa + sobre fondo uniforme) y el matting es un paso posterior de disco o un alpha del motor. +- **Es una funcion pura**: solo arma el dict. La generacion real (GPU) la hacen + `comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`; el alpha + lo hace `comfyui_matting_luma_to_alpha`. +- **La capa debe quedar de COBERTURA TOTAL, no un sujeto centrado**: si el modelo mete una + escena/objeto/personaje/horizonte, deja de ser una pelicula de cobertura y rompe el blend. + El positivo fuerza "full screen atmospheric layer, seamless full screen coverage, edge to + edge, uniform across frame" y el negativo rechaza "solid object, single subject, character, + building, landscape scene, horizon line, depth, frame, border". Si aun sale un sujeto, sube + `cfg` o reroll de `seed`. +- **on_black con cielo de fondo**: con SD1.5 algunos climas (lluvia, niebla) tienden a pintar + un cielo azul/gris de fondo aunque pidas negro, lo que arruina el luma->alpha. El negativo + por defecto (cuando on_black=True) rechaza "blue sky background, gray/white background, + background scenery". Si aun sale fondo no-negro, sube `cfg` y haz reroll de `seed`; los + climas mas brillantes-sobre-negro (god rays, snow, sparks) salen mas limpios que los + difusos (fog). +- **luma->alpha penaliza colores oscuros/saturados**: la luminancia Rec601 pesa poco el azul + (0.114). Una capa de lluvia AZULADA sobre negro sale semi-transparente con los pesos por + defecto. Para subir opacidad ajusta `luma_weights`/`gamma` en `comfyui_matting_luma_to_alpha` + (es ajuste del paso de matting, no del builder). Para clima blanco/brillante (nieve, haces + de luz, polvo claro) los pesos por defecto van perfectos. +- **Apaisado 16:9, no cuadrado**: por defecto 1024x576 (resolucion de pantalla). Para 21:9 u + otra relacion, ajusta width/height (multiplos de 8). En 8GB lowvram SD1.5 aguanta 1024x576 + holgado; si subes a SDXL (juggernaut_xl) ve a 1344x768 y vigila OOM (baja resolucion si peta). + +## Ejemplo + +```python +import sys, os +sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions")) +from ml.comfyui_build_weather_overlay_workflow import comfyui_build_weather_overlay_workflow + +# Una capa de lluvia full-screen sobre negro, lista para submit + luma-to-alpha. +wf = comfyui_build_weather_overlay_workflow( + "heavy rain", + on_black=True, + style="weather overlay, atmospheric", + seed=7, +) +# Pipeline completo de una capa de clima con alpha: +# r = comfyui_submit_workflow(wf); comfyui_wait_result(r["prompt_id"]) +# png = comfyui_fetch_output_image(...) # capa de lluvia sobre negro +# rgba = comfyui_matting_luma_to_alpha(png, gamma=1.2, black_point=0.05) # luma=alpha +# Capa translucida (niebla que tiñe la vista, blend multiply): +# wf = comfyui_build_weather_overlay_workflow("thick fog", on_black=False) +# Variantes del mismo clima para loopear en el motor: mismo weather/style, cambia seed. +# for s in range(4): +# wf = comfyui_build_weather_overlay_workflow("falling snow", seed=s) +``` + +O lanzable directo con: `./fn run comfyui_build_weather_overlay_workflow` (imprime nodos + class_types del ejemplo). diff --git a/python/functions/ml/comfyui_build_weather_overlay_workflow.py b/python/functions/ml/comfyui_build_weather_overlay_workflow.py new file mode 100644 index 00000000..ec0e4075 --- /dev/null +++ b/python/functions/ml/comfyui_build_weather_overlay_workflow.py @@ -0,0 +1,239 @@ +"""Construye el workflow ComfyUI de UN overlay de clima a pantalla completa (API format). + +Un weather overlay es una CAPA de atmosfera/clima que cubre TODA la pantalla y se +superpone sobre la escena del juego con un blend mode del motor: lluvia, niebla, +nieve, rayos de sol (god rays), polvo en suspension, viñeta de tormenta. NO es un +sujeto centrado ni un efecto puntual: es una pelicula de cobertura uniforme de borde +a borde que tiñe/anima toda la vista del jugador. Por eso se genera APAISADA a +resolucion de pantalla (16:9, 1024x576 por defecto), no cuadrada. + +Dos modos segun como se vaya a componer en el motor: + +- `on_black=True` (defecto): la capa se genera sobre fondo NEGRO puro, con las + particulas/estrias del clima BRILLANTES sobre negro (estrias de lluvia, copos de + nieve, haces de luz, motas de polvo). El PNG resultante esta pensado para pasar por + `comfyui_matting_luma_to_alpha` (brillante -> opaco, negro -> transparente) y + blendearse con additive/screen en el motor: el negro desaparece y solo el clima + brilla encima de la escena. Conserva el degradado/falloff que un matting binario + (rembg) destruiria. +- `on_black=False`: la capa se genera como una pelicula TRANSLUCIDA semi-transparente + (niebla densa, tinte de tormenta, bruma) lista para blendearse con multiply/overlay + o un alpha global bajo. Util para climas que oscurecen/tiñen toda la vista en vez de + añadir particulas brillantes. + +Es el builder hermano de comfyui_build_decal_overlay_workflow / +comfyui_build_particle_texture_workflow / comfyui_build_vfx_spritesheet_workflow: +mismo patron PURO (dict API format) que compone funciones existentes del registry, no +reescribe el grafo. A diferencia de los builders de sprite/prop/item, este NO inyecta +Rembg: el matting de una capa de clima es luma-to-alpha (post-proceso de disco) o un +alpha global del motor, no un nodo del workflow. + +Diferencia con sus hermanos (NO confundir): + - comfyui_build_vfx_spritesheet_workflow: una SECUENCIA animada de N frames de UN + efecto puntual (una explosion, una llamarada) via AnimateDiff. El weather overlay + es UNA capa estatica de cobertura total (el motor la anima por scroll/loop/shader). + - comfyui_build_decal_overlay_workflow: una mancha LOCALIZADA y recortable (sangre, + grieta) que se pega en un punto de una superficie. El weather overlay cubre la + pantalla ENTERA de borde a borde, no es una mancha centrada. + +Cableado: + + CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler + -> CLIPTextEncode (prompt scaffold de capa full-screen) ... + -> VAEDecode -> SaveImage (capa de clima apaisada) + +Compone: + - comfyui_build_txt2img_workflow -> base txt2img apaisada (16:9) + - comfyui_inject_lora -> LoRA de estilo opcional + +Pipeline despues de generar (no en este builder): + comfyui_submit_workflow -> comfyui_wait_result -> comfyui_fetch_output_image + -> comfyui_matting_luma_to_alpha (con on_black=True) -> PNG RGBA para blend aditivo + +class_types/inputs verificados contra /object_info del servidor (8GB lowvram): +CheckpointLoaderSimple, CLIPTextEncode, EmptyLatentImage, KSampler, VAEDecode, +SaveImage, LoraLoader. + +Funcion pura: sin red, sin I/O. Determinista para los mismos argumentos. +""" +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +# Negativo comun a cualquier capa de clima full-screen: queremos COBERTURA uniforme de +# borde a borde, SIN un sujeto/objeto solido, personaje, edificio, escena o horizonte +# que rompan la capa atmosferica; sin profundidad/marco/borde que delaten una +# composicion en vez de una pelicula de cobertura. +_WEATHER_NEGATIVE_COMMON = ( + "solid object, single subject, centered object, foreground object, " + "character, person, creature, building, vehicle, landscape scene, " + "horizon line, depth of field, perspective, vignette frame, frame, border, " + "drop shadow, text, watermark, signature, logo, " + "blurry, low quality, jpeg artifacts" +) + +# Refuerzo de fondo cuando on_black=True: el fondo DEBE quedar NEGRO plano para que el +# luma-to-alpha mapee la luminancia a alpha y conserve el falloff de las particulas/ +# estrias. Sin esto el modelo puede pintar una escena/cielo de fondo que arruina el +# alpha. Rechaza explicitamente fondos de otro color/texturizados. +_WEATHER_BG_NEG_ON_BLACK = ( + "gray background, white background, blue sky background, textured background, " + "busy background, background scenery, background pattern, gradient background" +) + + +def comfyui_build_weather_overlay_workflow( + weather: str, + *, + on_black: bool = True, + style: str = "weather overlay, atmospheric", + checkpoint: str = "dreamshaper_8.safetensors", + width: int = 1024, + height: int = 576, + seed: int = 0, + lora: str | None = None, + lora_strength: float = 1.0, + negative: str | None = None, + steps: int = 28, + cfg: float = 7.0, + sampler_name: str = "dpmpp_2m", + scheduler: str = "karras", + filename_prefix: str = "weather_overlay", +) -> dict: + """Construye el dict (API format) del workflow de UNA capa de clima full-screen. + + Args: + weather: descripcion del clima/atmosfera a generar como capa (ej. "heavy + rain", "thick fog", "falling snow", "god rays", "sun shafts", "dust + haze", "storm vignette", "sandstorm", "ash fall", "blizzard"). Se + inserta en un prompt scaffold de capa de cobertura total. No puede + estar vacio. + on_black: si True (defecto) la capa se genera sobre fondo NEGRO puro con el + clima BRILLANTE encima (estrias de lluvia, copos, haces de luz, motas), + pensada para extraer alpha por luminancia con + comfyui_matting_luma_to_alpha y blendear additive/screen en el motor + (el negro desaparece, el clima brilla sobre la escena). Si False la capa + se genera como pelicula TRANSLUCIDA semi-transparente (niebla densa, + tinte de tormenta, bruma) para blend multiply/overlay o alpha global. + keyword-only. + style: descriptor de estilo de la capa (ej. "weather overlay, atmospheric", + "stylized rain, painterly", "photoreal fog", "anime sun rays"). Pasa el + MISMO style + checkpoint + (lora) a todas las capas de un set 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 width/height a 1344x768). keyword-only. + width: ancho de la capa en px (multiplo de 8). 1024 por defecto (16:9 con + height 576: resolucion de pantalla apaisada). keyword-only. + height: alto de la capa en px (multiplo de 8). 576 por defecto. keyword-only. + seed: semilla del KSampler. Misma seed + mismos weather/style -> misma capa; + variar seed da variantes del mismo tipo de clima. keyword-only. + lora: LoRA de estilo opcional en models/loras. None = sin LoRA. keyword-only. + lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. + keyword-only. + negative: prompt negativo. None usa el negativo por defecto pensado para una + capa de cobertura total (sin sujeto/objeto/escena/profundidad/marco que + rompan la pelicula atmosferica) MAS, si on_black=True, el refuerzo de + fondo negro plano. 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 apaisada + (16:9) con prompt scaffold de capa de clima ('{weather} overlay, {style}, + full screen atmospheric layer, , seamless coverage, game VFX, ...') + LoRA de estilo + opcional. NO lleva Rembg: con on_black=True el PNG resultante se convierte a + RGBA con comfyui_matting_luma_to_alpha (luma=alpha) en un paso posterior para + blend aditivo. UNA capa; variar seed da variantes del mismo clima. + + Raises: + ValueError: si weather esta vacio. + """ + from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow + + if not weather or not weather.strip(): + raise ValueError( + "comfyui_build_weather_overlay_workflow: 'weather' no puede estar vacio" + ) + + weather = weather.strip() + lora_strength = max(0.0, min(2.0, float(lora_strength))) + if negative is None: + if on_black: + neg = f"{_WEATHER_NEGATIVE_COMMON}, {_WEATHER_BG_NEG_ON_BLACK}" + else: + neg = _WEATHER_NEGATIVE_COMMON + else: + neg = negative + + # Modo de composicion: sobre negro puro (particulas/estrias brillantes -> luma->alpha + # additive) o pelicula translucida semi-transparente. El segmento literal del scaffold + # ('particles/streaks on pure black background' | 'translucent layer') marca la + # intencion para el motor. + if on_black: + layer = ( + "particles and streaks on a solid pure black background, " + "bright weather elements over flat pure black, glowing on black backdrop" + ) + else: + layer = ( + "translucent layer, semi-transparent atmospheric film, " + "soft alpha blend over the scene" + ) + + # Prompt scaffold de capa de clima full-screen: cobertura UNIFORME de borde a borde + # (no un sujeto centrado), apaisada a resolucion de pantalla, lista como overlay para + # blendear en el motor. Se diferencia de un decal (mancha localizada) y de un + # spritesheet (secuencia animada): aqui es UNA pelicula de cobertura total. + positive = ( + f"{weather} overlay, {style}, full screen atmospheric layer, " + f"{layer}, seamless full screen coverage, edge to edge, uniform across frame, " + "game VFX, weather effect, high detail" + ) + + wf = comfyui_build_txt2img_workflow( + checkpoint, + positive, + neg, + steps=steps, + cfg=cfg, + width=width, + height=height, + 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 + ) + + return wf + + +if __name__ == "__main__": + import json + + wf = comfyui_build_weather_overlay_workflow( + "heavy rain", + on_black=True, + style="weather overlay, atmospheric", + seed=7, + ) + print( + json.dumps( + { + "nodes": list(wf), + "classes": sorted({n["class_type"] for n in wf.values()}), + }, + indent=2, + ) + ) diff --git a/python/functions/ml/comfyui_build_weather_overlay_workflow_test.py b/python/functions/ml/comfyui_build_weather_overlay_workflow_test.py new file mode 100644 index 00000000..6c1e0b2a --- /dev/null +++ b/python/functions/ml/comfyui_build_weather_overlay_workflow_test.py @@ -0,0 +1,145 @@ +"""Tests offline de comfyui_build_weather_overlay_workflow (estructura del dict, sin GPU).""" + +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from ml.comfyui_build_weather_overlay_workflow import ( # noqa: E402 + comfyui_build_weather_overlay_workflow, +) + + +def _classes(wf): + return sorted({n["class_type"] for n in wf.values()}) + + +def _by_class(wf, cls): + return [n for n in wf.values() if n["class_type"] == cls] + + +def _id_of(wf, cls): + return next(nid for nid, n in wf.items() if n["class_type"] == cls) + + +def _pos_with(wf, needle): + return next( + n for n in wf.values() + if n["class_type"] == "CLIPTextEncode" and needle in n["inputs"]["text"] + ) + + +def _neg(wf): + return next( + n["inputs"]["text"] for n in wf.values() + if n["class_type"] == "CLIPTextEncode" and "solid object" in n["inputs"]["text"] + ) + + +def test_golden_weather_on_black_recipe(): + wf = comfyui_build_weather_overlay_workflow("heavy rain", on_black=True, seed=7) + cls = _classes(wf) + # Cadena base txt2img pura: NO lleva Rembg (el matting es luma-to-alpha aparte). + assert "CheckpointLoaderSimple" in cls + assert "KSampler" in cls + assert "VAEDecode" in cls + assert "SaveImage" in cls + assert "Image Rembg (Remove Background)" not in cls + # El clima + la cobertura full-screen + el fondo negro (apto luma->alpha) aparecen. + pos = _pos_with(wf, "heavy rain") + txt = pos["inputs"]["text"] + assert "full screen atmospheric layer" in txt + assert "solid pure black background" in txt + assert "seamless full screen coverage" in txt + assert "game VFX" in txt + # SaveImage toma del VAEDecode directamente (sin recorte intermedio). + vd_id = _id_of(wf, "VAEDecode") + save = next(n for n in wf.values() if n["class_type"] == "SaveImage") + assert save["inputs"]["images"] == [vd_id, 0] + + +def test_golden_landscape_dims_default(): + # Apaisado 16:9 a resolucion de pantalla por defecto (NO cuadrado: es una capa + # full-screen, no un sujeto centrado). + wf = comfyui_build_weather_overlay_workflow("falling snow") + latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"] + assert latent["width"] == 1024 + assert latent["height"] == 576 + assert latent["width"] > latent["height"] # apaisado + + +def test_edge_on_black_toggles_layer_mode(): + # on_black=True -> particulas/estrias sobre negro plano (luma->alpha additive) + + # el negativo refuerza fondo negro (rechaza gris/blanco/cielo). + wf_black = comfyui_build_weather_overlay_workflow("god rays", on_black=True) + pos_black = _pos_with(wf_black, "god rays")["inputs"]["text"] + assert "solid pure black background" in pos_black + assert "translucent layer" not in pos_black + assert "white background" in _neg(wf_black) # refuerzo de fondo negro plano + # on_black=False -> pelicula translucida semi-transparente (sin refuerzo de fondo). + wf_film = comfyui_build_weather_overlay_workflow("god rays", on_black=False) + pos_film = _pos_with(wf_film, "god rays")["inputs"]["text"] + assert "translucent layer" in pos_film + assert "solid pure black background" not in pos_film + assert "white background" not in _neg(wf_film) + + +def test_edge_weather_reflected(): + for w in ["thick fog", "dust haze", "storm vignette"]: + wf = comfyui_build_weather_overlay_workflow(w) + pos = _pos_with(wf, w) + assert w in pos["inputs"]["text"] + + +def test_edge_style_in_prompt(): + wf = comfyui_build_weather_overlay_workflow( + "heavy rain", style="stylized rain, painterly" + ) + pos = _pos_with(wf, "heavy rain") + assert "stylized rain, painterly" in pos["inputs"]["text"] + + +def test_edge_dims_reflected(): + wf = comfyui_build_weather_overlay_workflow("blizzard", width=1344, height=768) + latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"] + assert latent["width"] == 1344 + assert latent["height"] == 768 + + +def test_edge_negative_isolates_layer(): + # El negativo por defecto rechaza sujeto/objeto/escena/horizonte/profundidad/marco + # que romperian la pelicula de cobertura total. + wf = comfyui_build_weather_overlay_workflow("sandstorm") + neg = _neg(wf) + assert "solid object" in neg + assert "landscape scene" in neg + assert "horizon line" in neg + assert "frame" in neg + assert "gray background" in neg # fondo negro plano forzado (on_black por defecto) + + +def test_edge_lora_reflected(): + wf = comfyui_build_weather_overlay_workflow( + "falling snow", lora="vfx_sd15.safetensors", lora_strength=0.8 + ) + loras = _by_class(wf, "LoraLoader") + assert len(loras) == 1 + assert loras[0]["inputs"]["lora_name"] == "vfx_sd15.safetensors" + assert loras[0]["inputs"]["strength_model"] == 0.8 + + +def test_error_empty_weather(): + try: + comfyui_build_weather_overlay_workflow(" ") + assert False + except ValueError as e: + assert "weather" in str(e) + + +def test_determinism(): + a = comfyui_build_weather_overlay_workflow( + "heavy rain", on_black=True, lora="vfx_sd15.safetensors", seed=7 + ) + b = comfyui_build_weather_overlay_workflow( + "heavy rain", on_black=True, lora="vfx_sd15.safetensors", seed=7 + ) + assert a == b