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.
72 lines
2.8 KiB
Python
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
|