Scene Weaver - Fantasy
One Click, and the Prompt Doesn't Contradict Itself
- entity_1_in
- entity_2_in
- entity_3_in
- entity_4_in
- prompt_text
- prompt_json
Drop the node on the canvas, change nothing, hit queue. You get a sentence:
A fantasy scene set in a frostbitten mountain pass. A cave troll is climbing toward a roost on the cliff face. It is hulking, covered in matted grey-green hide. It has a single shattered tusk and four clawed fingers.
That's the pitch. No wildcard files, no LLM call, no API key. Just prose your encoder can follow.
Why you'd reach for it
The usual way to get variety is a wildcard pack, and the usual result is a comma pile whose parts fight each other: a sleeping dragon that's meanwhile mid-pounce, a wizard tower nine hundred storeys tall. The model spends the image reconciling the contradiction, and you get mush.
SceneWeaver's bet is the opposite: a prompt whose every word agrees with every other word leaves the model free to draw one believable scene. It's a prompt writer that knows what it's describing - a longship is clad in its material, a dragon is covered in one, and a thing with no surface is never handed one to describe. That grammar is data, not a template.
What it is not: the pack writes subject description and nothing else. No lighting, lens, film stock or mood. You bring the style, and in exchange it composes cleanly with a style LoRA or a style prefix instead of arguing with one.
How it works
Every genre is the same engine plus a data module, which is why Fantasy, Horror and Sci-Fi are three node pairs and not three codebases. Four things matter:
- Coherence is enforced while it draws.
kindscopes every other field on that slot - a longship asks for open water, a wagon asks for a road, a pixie is six inches and a hill giant twenty feet, nothing burns under the sea. - Locking is the workflow. Every dropdown is
Random/ a value /None. A value pins that field,Randomrolls around it,Noneomits it. A locked field is never cut and never overruled. - There's a detail budget. Four fully-described entities is roughly a 400-token prompt, and encoders drop detail across one - mush exactly when you asked for the most. Slot 1 is described in full, support slots briefly, and anything cut is written back to
None. Locks are never cut. - The node face tells you what you actually rolled - two lines naming the scene and its rough token count, because every widget says "Random" and "Random" is not a description.
Everything is seeded, so a fixed seed reproduces a scene exactly. The node re-runs on every queue by design, which is what makes randomize behave the way you expect.
The controls that matter
seed- leave the control onrandomizeto explore, flip tofixedonce you like something.entity_count- 1 to 4, and the authority on how many subjects exist: slots past it are dropped, slots inside it are filled even if their widgets still sayNone. Two is the sweet spot; three and four read as a crowd.scene_filter-Any,PeacefulorConflict.Peacefulkeeps weapons and hostile actions out of the description entirely. A value you locked is always kept.set_all_fields- a one-shot bulk edit, not a mode:Randomize allturns everyNoneintoRandom,Clear alldoes the reverse, and it snaps back toOff.Clear allplus one lockedenvironmentis how you get a pure landscape.environment, then per slot the morphology fields:entity1_kind(which scopes the rest),subkind,scale,condition,form,material,primary_color,accent_color,markings,surface_detail,appendages+ count,emitters+ count +emitter_color,armament+ count,sensors+ count,aperture,extras, andentity1_situation- an event in progress, never a pose. Slots 2–4 expose only kind, subkind, scale, condition and situation.relation_1_2and friends, plus the_positionvariants - opt-in, defaultNone, rendered only when both entities exist.context- one secondary detail for scale and story. A thing in the world, never a lighting instruction.- Four optional
entity_1_in…entity_4_insockets, for aSCENE_ENTITYpayload.
Outputs: prompt_text goes into your text encoder (and into a Show Text node, so you can read it), and prompt_json is structured scene data - don't feed that one to an encoder.
Install
ComfyUI Manager: search SceneWeaver. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/EnragedAntelope/comfyui-sceneweaver
Keep the folder named comfyui-sceneweaver and restart. No dependencies - standard library only, no requirements.txt step - but it needs ComfyUI 0.25.0+ and Python 3.10+. The nodes appear under conditioning/sceneweaver.
Your own vocabulary goes in a user_options.json beside the pack; it's gitignored, so it survives a git pull, and fantasy values live in a "fantasy" section.
python scripts/builtin_options.py subkind --kind dragon --pack fantasy
Where people get burned
You forgot the style. "The images look flat" is the pack doing its job - with no rendering frame in the prompt, models draw bare content. The maintainer's own graphs prefix every prompt with:
cinematic film-quality slightly-grainy analog film still.
Four entities, one subject's worth of attention. That's the model, not the node.
A locked slot that still reads short. A structure or vessel carries only its silhouette; a creature carries its whole component list, so a slot full of locks can come out shorter than you expected.
user_options.json additions that do nothing. Fields like form, material, markings and situation are overridden by every kind, so a _default pool for them is never read. Put those values under a kind name - the console warns you if you get it backwards.
Old ComfyUI. Below 0.25.0 you get an import failure on comfy_api.latest, not a node.
Inputs (61)
| Name | Type | Default | Description |
|---|---|---|---|
| seed | INT | 00–18446744073709550000 | Seed for reproducible scenes. The control below defaults to 'randomize' so every run differs; set it to 'fixed' to reproduce a scene. |
| entity_count | COMBO | 1 | How many subjects the scene has. Slot 1 is described most fully and later slots a little less, because an image has one subject's worth of attention to share out however many things are in it. Raising this reveals that slot's widgets on the node; every slot it reveals is described, never just named. Two is the sweet spot -- three and four work, and read as a crowd. A Scene Entity wired into a slot occupies it whatever this says. How much a slot actually speaks is also capped by what kind of thing it is: a creature or a person carries its whole component list, a station or a wreck only its silhouette, so a slot full of locked widgets can still read shorter than you expect. |
| scene_filter | COMBO | Any | Restrict what random draws may pick. 'Peaceful' leaves weapons and hostile actions out of the description entirely; 'Conflict' favours them. A value you lock yourself is always kept. |
| set_all_fields | COMBO | Off | Bulk-edit every descriptive widget below. 'Randomize all' turns each one that says 'None' into 'Random'; 'Clear all' does the reverse. A field you locked to a value is never touched, and the control snaps back to 'Off' as soon as it has run, so it stays a one-shot action rather than a mode. |
| environment | COMBO | Random | Where the scene is: wild lands, the water, the sky, underground, a town, a hall, or a realm beyond the world. Scene | Random=randomize, value=lock, None=omit |
| entity1_kind | COMBO | Random | What this entity fundamentally is. Scopes every other field on this slot -- its options and its labels. Set to None to leave the slot empty. Identity | Random=randomize, value=lock, None=omit |
| entity1_subkind | COMBO | Random | The specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit |
| entity1_scale | COMBO | Random | How big it is relative to a person. Identity | Random=randomize, value=lock, None=omit |
| entity1_condition | COMBO | Random | Age and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit |
| entity1_form | COMBO | Random | The silhouette: a body plan, a build, a hull or a tower's shape. The field that makes two dragons actually look different. Form & Material | Random=randomize, value=lock, None=omit |
| entity1_material | COMBO | Random | What it is covered in, made of, or wearing. Form & Material | Random=randomize, value=lock, None=omit |
| entity1_primary_color | COMBO | Random | The colour of the thing itself -- scales, coat, robes, stone. Describes the subject, never the lighting. Surface & Colour | Random=randomize, value=lock, None=omit |
| entity1_accent_color | COMBO | Random | A secondary colour: trim, banding, the underside of a wing. Surface & Colour | Random=randomize, value=lock, None=omit |
| entity1_markings | COMBO | Random | Heraldry, war paint, patterning. Carries no readable lettering. Surface & Colour | Random=randomize, value=lock, None=omit |
| entity1_surface_detail | COMBO | Random | Finish and wear at close range -- scars, moss, weathering, stitching of hide. Surface & Colour | Random=randomize, value=lock, None=omit |
| entity1_appendages | COMBO | Random | What sticks out: horns, wings, tails, tusks, towers and masts. Components | Random=randomize, value=lock, None=omit |
| entity1_appendage_count | COMBO | Random | How many. Composed into the appendage phrase. Components | Random=randomize, value=lock, None=omit |
| entity1_emitters | COMBO | Random | What shines with its own light: enchanted gems, rune tracery, ember eyes, a spell held in the hand. Components | Random=randomize, value=lock, None=omit |
| entity1_emitter_count | COMBO | Random | How many. Components | Random=randomize, value=lock, None=omit |
| entity1_emitter_color | COMBO | Random | The colour of the magic itself. Belongs to the subject, not the lighting. Components | Random=randomize, value=lock, None=omit |
| entity1_armament | COMBO | Random | Weapons, carried or grown: a longsword, a war maul, horns and talons. Components | Random=randomize, value=lock, None=omit |
| entity1_armament_count | COMBO | Random | How many weapons. Components | Random=randomize, value=lock, None=omit |
| entity1_sensors | COMBO | Random | How it sees: eyes, many eyes, a lantern-bearing gaze. Components | Random=randomize, value=lock, None=omit |
| entity1_sensor_count | COMBO | Random | How many. A cyclops and a many-eyed horror are different things. Components | Random=randomize, value=lock, None=omit |
| entity1_aperture | COMBO | Random | The opening: a fanged maw, a visor, a gatehouse arch. Components | Random=randomize, value=lock, None=omit |
| entity1_extras | COMBO | Random | One more distinguishing thing -- a saddle, a satchel, a hanging banner. Components | Random=randomize, value=lock, None=omit |
| entity1_situation | COMBO | Random | What this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit |
| entity2_kind | COMBO | Random | What this entity fundamentally is. Scopes every other field on this slot -- its options and its labels. Set to None to leave the slot empty. Identity | Random=randomize, value=lock, None=omit |
| entity2_subkind | COMBO | Random | The specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit |
| entity2_scale | COMBO | Random | How big it is relative to a person. Identity | Random=randomize, value=lock, None=omit |
| entity2_condition | COMBO | Random | Age and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit |
| entity2_situation | COMBO | Random | What this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit |
| entity3_kind | COMBO | Random | What this entity fundamentally is. Scopes every other field on this slot -- its options and its labels. Set to None to leave the slot empty. Identity | Random=randomize, value=lock, None=omit |
| entity3_subkind | COMBO | Random | The specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit |
| entity3_scale | COMBO | Random | How big it is relative to a person. Identity | Random=randomize, value=lock, None=omit |
| entity3_condition | COMBO | Random | Age and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit |
| entity3_situation | COMBO | Random | What this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit |
| entity4_kind | COMBO | Random | What this entity fundamentally is. Scopes every other field on this slot -- its options and its labels. Set to None to leave the slot empty. Identity | Random=randomize, value=lock, None=omit |
| entity4_subkind | COMBO | Random | The specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit |
| entity4_scale | COMBO | Random | How big it is relative to a person. Identity | Random=randomize, value=lock, None=omit |
| entity4_condition | COMBO | Random | Age and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit |
| entity4_situation | COMBO | Random | What this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit |
| relation_1_2 | COMBO | None | How two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit |
| relation_1_3 | COMBO | None | How two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit |
| relation_1_4 | COMBO | None | How two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit |
| relation_2_3 | COMBO | None | How two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit |
| relation_2_4 | COMBO | None | How two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit |
| relation_3_4 | COMBO | None | How two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit |
| relation_1_2_position | COMBO | None | Where the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit |
| relation_1_3_position | COMBO | None | Where the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit |
| relation_1_4_position | COMBO | None | Where the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit |
| relation_2_3_position | COMBO | None | Where the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit |
| relation_2_4_position | COMBO | None | Where the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit |
| relation_3_4_position | COMBO | None | Where the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit |
| context | COMBO | Random | What else is in the shot besides the subject -- a secondary detail for scale and story. Always a thing in the world, never a lighting instruction. Scene | Random=randomize, value=lock, None=omit |
| staging | COMBO | Random | Where the first subject is in the place -- far off across the moor, at the far end of the hall. Said of the place, never of the picture. Scene | Random=randomize, value=lock, None=omit |
| aspect | COMBO | Random | What is happening to the place -- driving rain, a fresh fall of snow, autumn leaves, freshly ransacked. Weather, season and state; never the time or the light. Scene | Random=randomize, value=lock, None=omit |
| entity_1_inopt | SCENE_ENTITY | Optional. A Scene Entity wired here describes slot 1, replacing that slot's own descriptive widgets and giving it the top allowance for the scene. It is a promotion, not an exemption: how much the slot speaks is still capped by what kind of thing it is, so a station reads shorter than a creature. A field you locked on the Scene Entity is a choice: it wins over these widgets and over a coherence rule, and the rule's reason is reported in prompt_json._meta. A field the Scene Entity drew at random is a draw: this scene first picks a place that can hold the whole wired subject, and re-draws a drawn field only when no place can -- the node face then names what was re-drawn. What the entity is *doing* stays this node's: situation and relations are never taken from a wired entity. | |
| entity_2_inopt | SCENE_ENTITY | Optional. A Scene Entity wired here describes slot 2, replacing that slot's own descriptive widgets and giving it the top allowance for the scene. It is a promotion, not an exemption: how much the slot speaks is still capped by what kind of thing it is, so a station reads shorter than a creature. A field you locked on the Scene Entity is a choice: it wins over these widgets and over a coherence rule, and the rule's reason is reported in prompt_json._meta. A field the Scene Entity drew at random is a draw: this scene first picks a place that can hold the whole wired subject, and re-draws a drawn field only when no place can -- the node face then names what was re-drawn. What the entity is *doing* stays this node's: situation and relations are never taken from a wired entity. | |
| entity_3_inopt | SCENE_ENTITY | Optional. A Scene Entity wired here describes slot 3, replacing that slot's own descriptive widgets and giving it the top allowance for the scene. It is a promotion, not an exemption: how much the slot speaks is still capped by what kind of thing it is, so a station reads shorter than a creature. A field you locked on the Scene Entity is a choice: it wins over these widgets and over a coherence rule, and the rule's reason is reported in prompt_json._meta. A field the Scene Entity drew at random is a draw: this scene first picks a place that can hold the whole wired subject, and re-draws a drawn field only when no place can -- the node face then names what was re-drawn. What the entity is *doing* stays this node's: situation and relations are never taken from a wired entity. | |
| entity_4_inopt | SCENE_ENTITY | Optional. A Scene Entity wired here describes slot 4, replacing that slot's own descriptive widgets and giving it the top allowance for the scene. It is a promotion, not an exemption: how much the slot speaks is still capped by what kind of thing it is, so a station reads shorter than a creature. A field you locked on the Scene Entity is a choice: it wins over these widgets and over a coherence rule, and the rule's reason is reported in prompt_json._meta. A field the Scene Entity drew at random is a draw: this scene first picks a place that can hold the whole wired subject, and re-draws a drawn field only when no place can -- the node face then names what was re-drawn. What the entity is *doing* stays this node's: situation and relations are never taken from a wired entity. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| prompt_text | STRING | The prompt. Wire this into the text encoder. |
| prompt_json | STRING | Structured scene data for other nodes or logs. Not a prompt: do not wire it into a text encoder. |