Files
DumiFlex-ComfyUI-Wildcard-P…/engine/seed_derive.py
T
DumiFlex ccbf38af99 feat(nodes): add wp_seedlist + split loop config types + wp_ prefix sweep + tooltips
Three coordinated changes to the WP_* node family.

1. New WP_SeedList node
   Emits a list of N derived seeds for a downstream loop (is_output_list).
   Three independent override toggles (override_seed / override_count /
   override_strategy) each mirror the wired Loop's base_seed / count /
   strategy. Connecting loop_config auto-flips override_count +
   override_strategy ON so the natural intent of wiring in a loop gets
   the defaults right; override_seed stays explicit opt-in.
   All emitted seeds are masked to 50 bits via the new MAX_SAFE_SEED
   constant in engine.seed_derive so values sit inside ComfyUI's
   frontend randomize range and copy-paste reproduces identically in a
   standalone sampler.

2. Context Loop type split + downstream Context seed widget lock
   Split WP_CONTEXT_LOOP_CONFIG (wire payload) from a new
   WP_CONTEXT_LOOP_WIDGET (DOM-widget-only TYPE). Loop's own widget
   mounts on the widget type; the wire type has no factory so consumers
   like WP_SeedList render their loop_config socket as a plain input
   instead of being hidden by a fallback-undefined widget.
   New graph walker hasUpstreamLoopOverridingSeed: WP_Context's widget
   glue greys out the local seed stock widget when an upstream Loop has
   override_seed ON, so users read "this value is dead while the Loop
   drives it" without inspecting the Loop config separately.

3. wp_ prefix sweep + tooltips across every WP_* node input
   Custom DOM widget input names now carry the wp_ prefix so canvas
   socket labels read as branded WP widgets: modules -> wp_modules,
   rows -> wp_rows, viewer -> wp_viewer, cleaner -> wp_cleaner,
   context_loop_config -> wp_context_loop_config. Every stock input
   across the node family gets a tooltip describing what it does and
   when to wire it from another node. Tests + comfy_api stub updated.
   CLAUDE.md notes the wp_ convention for future custom widgets.

Bundle size budget bumped 388 -> 395 KB to fit the SeedList Vue chunk
(~4.9 KB net gzip) + override lock UI; entry stays at ~4 KB gzipped.
2026-05-28 12:40:44 +03:00

72 lines
2.8 KiB
Python

"""Per-iteration seed derivation for WP_ContextLoop.
`derive_loop_seeds` produces the N base seeds emitted by ContextLoop.
`effective_chain_seed` is what the WP_Context wrapper stamps as
`__wp_node_seed__` for each iteration — locked-seed precedence happens
LATER inside per-module handlers, which is why locked modules ignore
the loop automatically (they never read __wp_node_seed__).
Pure Python — zero ComfyUI imports.
"""
import hashlib
MAX_SAFE_SEED = (1 << 50) - 1
"""1_125_899_906_842_623 — matches ComfyUI's frontend randomize cap
(Math.random() * 1125899906842624) so emitted seeds stay copy-pasteable +
visually consistent with stock seed nodes. KSampler accepts up to 64-bit,
but anything past 2^50 falls outside the canvas's typical seed shape and
is unsightly."""
def derive_loop_seeds(base: int, count: int, strategy: str) -> list[int]:
"""Produce `count` base seeds from `base` using the given strategy.
Every emitted seed is masked to ``MAX_SAFE_SEED`` (50 bits) so the
values stay within ComfyUI's frontend randomize range — see the
module docstring for the rationale.
"""
if count <= 1:
return [base & MAX_SAFE_SEED]
if strategy == "sequential":
return [(base + i) & MAX_SAFE_SEED for i in range(count)]
if strategy == "prime_stride":
return [(base + i * 1000003) & MAX_SAFE_SEED for i in range(count)]
if strategy == "hash_index":
out: list[int] = []
for i in range(count):
digest = hashlib.sha256(f"{base}:{i}".encode()).digest()
out.append(int.from_bytes(digest[:8], "big") & MAX_SAFE_SEED)
return out
raise ValueError(f"unknown loop seed strategy: {strategy!r}")
def effective_chain_seed(
*,
widget_seed: int,
seed_override: int | None,
loop_index: int,
) -> int:
"""Compute the chain seed for THIS iteration before module handlers run.
Precedence:
1. ``seed_override`` (from ContextLoop) replaces ``widget_seed`` as
base when present.
2. Otherwise ``widget_seed`` is the base.
3. ``loop_index`` is mixed via XOR with a stable hash. ``loop_index=0``
→ no-op (backwards-compat for chains without ContextLoop).
Locked-module precedence lives in the per-module handlers, NOT here.
Locked modules read ``instance.locked_seed`` directly and ignore the
chain seed — so loop iteration cannot affect them.
Returns a non-negative seed masked to ``MAX_SAFE_SEED`` (50 bits) —
matches the visual shape of ComfyUI's frontend-randomised seeds.
"""
base = seed_override if seed_override is not None else widget_seed
if loop_index == 0:
return base & MAX_SAFE_SEED
digest = hashlib.sha256(f"loop:{loop_index}".encode()).digest()
shift = int.from_bytes(digest[:8], "big")
return (base ^ shift) & MAX_SAFE_SEED