A minimal dynamic prompting + mirrored wildcards node for ComfyUI.
{a|b|{c|d}}nested choice expansion (deterministic by seed).__name__loadswildcards/name.txt.__name-mir__strictly loadswildcards/name-mir.txt.- In negative phase, if
__name__is used andname-mir.txtexists, it auto-mirrors to all options except the chosen one. - Deterministic selection per seed.
- Copy this folder to:
<ComfyUI>/custom_nodes/ComfyUI-DynPromptSimplified - (Optional) Put your wildcard files in
ComfyUI-DynPromptSimplified/wildcards/or setwildcard_dirto any folder. - Restart ComfyUI.
DynPrompt Expand (mirrored wildcards)
- Inputs:
text,negative,seed,wildcard_dir,auto_neg_from_mir - Outputs: expanded
text, expandednegative
- positive: positive prompt
- negative: negative prompt
- Seed: this is used to determine how the node will select tags
- allow neg from mir: automatically adds -mir tags to negative prompt without explicitly needing to add it to the negative prompt
- variety: int between 0-10; creates extra randomness 'lanes' Variety
Wire the outputs into your usual `CLIP Text Encode` node
You can preview what the output is with the "preview any" node in utils
## ✨ Features
-
Nested choices with
{option1|option2|{nested1|nested2}} -
Wildcard expansion with
__name__→ expands fromwildcards/name.txt -
Line-separated wildcard files that may themselves contain braces and wildcards
-
Mirrored wildcards with
__name-mir__:- Positive prompt gets the chosen option
- Negative prompt gets all the other options, comma-separated
- Strict resolution:
__name-mir__reads onlywildcards/name-mir.txt(no fallback toname.txt)
-
Deterministic behavior using the current generation seed
-
Works for both positive and negative prompts
-
Expanded prompts are saved into PNG metadata
-
Located in the
wildcards/directory (configurable). -
Each line is one possible expansion.
-
Lines may contain further braces
{}and wildcard calls.
Example: wildcards/hats.txt
{red hat|blue hat|{green hat|yellow hat|{black hat|gold hat}}}
beret
top hat
- File name:
name-mir.txt(called via__name-mir__). - Ensures complementary picks between positive/negative prompts.
Example: wildcards/hats-mir.txt
{red hat|blue hat|green hat}
{tall hat|short hat|medium hat}
- Positive prompt:
portrait, __hats-mir__ - Negative prompt:
lowres, __hats-mir__- If pos →
red hat, neg →blue hat, green hat - If pos →
short hat, neg →tall hat, medium hat
- If pos →
This ensures that the negative prompt excludes the token chosen in the positive prompt.
Mirrored tokens can be nested inside other wildcards. If a wildcard you use in the positive prompt expands to another token like __foo-mir__, the extension can automatically inject the mirrored complement into the negative prompt (so you don’t have to add __foo-mir__ manually).
- This requires the checkbox in the UI:
“Automatically mirror -mir wildcards without explicitly adding them to the negative prompt.” (enabled by default) - The auto-inject only happens if the negative prompt does not already contain that
__*-mir__token. - Resolution is strict:
__foo-mir__readswildcards/foo-mir.txtonly.
Example (nested):
wildcards/outfits.txt
---------------------
__hats-mir__, {casual|formal}
wildcards/hats-mir.txt
----------------------
{red hat|blue hat|green hat}
Usage:
- Positive:
portrait, __outfits__ - Negative: (leave blank or put your usual negatives)
- add node from: add node -> prompt -> DSynPrompt Expand (deep-mirrored)
Behavior:
- The positive prompt expands
__outfits__→ which contains__hats-mir__. - With the checkbox enabled, the extension auto-injects
__hats-mir__into the negative and expands it there as the comma‑separated “other” options. - If the seed picks
red hatfor positive, the negative getsblue hat, green hatautomatically.
What it is: An integer knob that creates alternate, reproducible randomness lanes without changing your seed or prompt.
Deterministic: With the same (seed, variety, prompt, files), you get the same positive & negative outputs every time.
Independence from fixed text: Only decision points advance randomness—each {…} block and each __wildcard__ / __*-mir__. Fixed words/commas/spaces do not affect picks.
How it works (under the hood):
- Choices use
choice#0, choice#1, … - Wildcards use
wild#0, wild#1, …(also salted with the token name) - We hash
(seed, counter[, token], variety)to pick an index.
When to use: Keep seed fixed and sweep variety = 0,1,2… to explore different, reproducible variants from the same prompt.
Example
Prompt: __color__, {A|B|{C|D}}, __pose-mir__
(seed=123, variety=0) → picks one set of branches (seed=123, variety=1) → different set of branches (still reproducible)
- Write prompts as usual with
{}and__wildcards__. - Wildcards are recursively expanded.
- Mirrored wildcards respect complement logic.
Prompt Example:
Positive: portrait, {cinematic|studio|outdoor}, __hats__, soft lighting
Negative: lowres, bad anatomy, __hats-mir__
Determinism & Counters
- Randomness depends only on decision points:
- Each non-nested
{…}is the next choice#N. - Each
__token__(including__*-mir__) is the next wild#M.
- Each non-nested
- For each pick we hash
(seed, counter[, token], variety)→ index. - Fixed text (words/commas/spaces) does not affect counters or picks.
varietyis an extra integer salt to create reproducible “lanes”.
Expansion Order (Positive)
- Braces-first pass (repeat until none left)
Collapse the left-most non-nested{…}using choice#N. - Wildcard pass (left → right)
Replace each__token__using wild#M:__name__→ readwildcards/name.txt(supports subfolders likea/b.txt).__name-mir__→ strict readwildcards/name-mir.txtonly (no fallback).- If a chosen line itself contains braces/wildcards, it’s injected and will be handled by subsequent passes.
- Missing file ⇒ token disappears (empty).
- Braces-second pass
Collapse any{…}that arrived from wildcard lines. - Cleanup
Normalize commas/spaces.
Deep Mirroring (Negative complement)
- Your user Negative is expanded normally (same algorithm as Positive: braces → wildcards → braces).
- If
auto_neg_from_miris ON, we run a mirror pass over the Positive prompt:- Re-run the same brace + wildcard passes with the same
(seed, variety)and counters. - For every
__*-mir__encountered (even if nested inside other wildcards):- Compute the chosen option.
- Add all non-empty alternatives except the chosen one to a bag.
- Special case: if the chosen alternative is empty (from
{option|}), add all non-empty options.
- Re-run the same brace + wildcard passes with the same
- Merge:
final_negative = dedup(user_negative_expanded + mirrored_bag).
File Semantics
- Single-line files:
- Non-mir: if it’s
{a|b|{c|d}|}, split by top-level|(empties kept as""). *-mir: if it’s{a|b|{c|d}}, flatten to leaf options["a","b","c","d"]so “all-except-chosen” is well-defined.
- Non-mir: if it’s
- Multi-line files: each non-empty, non-comment line is an option (lines may contain braces/wildcards).
- Path safety: allows subfolders (
a/b), dotted names (style.v1); blocks traversal (..).
Pseudocode (conceptual)
counters = {choice: 0, wild: 0}
def pick_choice(options):
i = hash(seed, f"choice#{counters.choice}", variety) % len(options)
counters.choice += 1
return options[i]
def pick_wild(token, options):
i = hash(seed, f"wild#{counters.wild}:{token}", variety) % len(options)
counters.wild += 1
return options[i]
# Positive:
text = collapse_braces(text, pick_choice) # pass 1
text = expand_wildcards(text, pick_wild) # pass 2
text = collapse_braces(text, pick_choice) # pass 3
# Negative:
neg = collapse_braces(neg, pick_choice)
neg = expand_wildcards(neg, pick_wild)
neg = collapse_braces(neg, pick_choice)
# Deep mirror additions:
mirror_bag = run_positive_again_collecting_all_except_chosen_for('*-mir')
final_negative = dedup_join(neg, mirror_bag)
Key Guarantees
- Same
(seed, variety, prompt, files)⇒ same Positive & Negative. - Adding/removing fixed text does not change picks.
- Changing decision points (adding/removing a
{…}or__wildcard__) changes the sequence of draws (by design).
- Missing wildcard files resolve to empty strings.
- Choice/wildcard expansion is capped to prevent runaway recursion.
- If
negativeis blank andauto_neg_from_miris ON, the node scans the positive for__tokens__and auto-builds a mirrored negative when possible.
Why: fixed tokens could cause prompt to converge.
What changed
- Decision-only RNG: Random choices now depend only on decision points (each
{…}block and each__wildcard__/__*-mir__call), not on surrounding fixed text.- Internally we hash
(seed, decision_counter[, token], variety)where counters advance as we hit choices/wildcards left→right. - Adding/removing fixed words, commas, or spacing no longer shifts picks.
- Internally we hash
- Deterministic lanes via
variety: New integer input that adds an extra salt.- Same
(seed, variety, prompt, files)⇒ same outputs. - Sweep
variety = 0,1,2…to get reproducible alternates without touching seed or prompt.
- Same
- Deep mirroring (strict):
__foo-mir__contributes mirrored exclusions to Negative even when nested inside other wildcards, and only readsfoo-mir.txt(never falls back). - Empty-branch support in
*-mir.txt:{option|}is honored.- If Positive picks
option, Negative adds nothing for that token; if Positive picks the empty branch, Negative adds the non-empty options.
- If Positive picks
- Single-line
*-mir.txtwith nested braces:{1|2|{3|4}}is flattened to leaves so mirroring can compute “all-except-chosen” correctly.
Behavior notes
- Fixed text no longer influences randomness. Only the number/order of decision points matters.
- Changing decision points (e.g., adding a
{…}or another__wildcard__) will change the sequence of draws—as expected. - Deep mirror uses the same counters as Positive, ensuring the Negative always gets the correct complement.
Example
Prompt: __color__, {A|B|{C|D}}, __pose-mir__
Same seed, same variety → same picks (pos & neg)
Change only fixed words → picks stay identical
Bump variety (e.g., 0→1) → different, reproducible picks
License: GPLv3 (same as the original)

