Nodes/SceneWeaver/Scene Weaver - Fantasy
ComfyUI Node

Scene Weaver - Fantasy

One Click, and the Prompt Doesn't Contradict Itself

By EnragedAntelope·Created 29 days ago·Updated a day ago· 4
Scene Weaver - Fantasy
  • entity_1_in
  • entity_2_in
  • entity_3_in
  • entity_4_in
  • prompt_text
  • prompt_json
◄seed0►
◄entity_count1►
◄scene_filterAny►
◄set_all_fieldsOff►
◄environmentRandom►
◄entity1_kindRandom►
◄entity1_subkindRandom►
◄entity1_scaleRandom►
◄entity1_conditionRandom►
◄entity1_formRandom►
◄entity1_materialRandom►
◄entity1_primary_colorRandom►
◄entity1_accent_colorRandom►
◄entity1_markingsRandom►
◄entity1_surface_detailRandom►
◄entity1_appendagesRandom►
◄entity1_appendage_countRandom►
◄entity1_emittersRandom►
◄entity1_emitter_countRandom►
◄entity1_emitter_colorRandom►
◄entity1_armamentRandom►
◄entity1_armament_countRandom►
◄entity1_sensorsRandom►
◄entity1_sensor_countRandom►
◄entity1_apertureRandom►
◄entity1_extrasRandom►
◄entity1_situationRandom►
◄entity2_kindRandom►
◄entity2_subkindRandom►
◄entity2_scaleRandom►
◄entity2_conditionRandom►
◄entity2_situationRandom►
◄entity3_kindRandom►
◄entity3_subkindRandom►
◄entity3_scaleRandom►
◄entity3_conditionRandom►
◄entity3_situationRandom►
◄entity4_kindRandom►
◄entity4_subkindRandom►
◄entity4_scaleRandom►
◄entity4_conditionRandom►
◄entity4_situationRandom►
◄relation_1_2None►
◄relation_1_3None►
◄relation_1_4None►
◄relation_2_3None►
◄relation_2_4None►
◄relation_3_4None►
◄relation_1_2_positionNone►
◄relation_1_3_positionNone►
◄relation_1_4_positionNone►
◄relation_2_3_positionNone►
◄relation_2_4_positionNone►
◄relation_3_4_positionNone►
◄contextRandom►
◄stagingRandom►
◄aspectRandom►

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. kind scopes 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, Random rolls around it, None omits 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 on randomize to explore, flip to fixed once 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 say None. Two is the sweet spot; three and four read as a crowd.
  • scene_filter - Any, Peaceful or Conflict. Peaceful keeps 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 all turns every None into Random, Clear all does the reverse, and it snaps back to Off. Clear all plus one locked environment is 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, and entity1_situation - an event in progress, never a pose. Slots 2–4 expose only kind, subkind, scale, condition and situation.
  • relation_1_2 and friends, plus the _position variants - opt-in, default None, 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_in sockets, for a SCENE_ENTITY payload.

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.

Categoryconditioning/sceneweaver

Inputs (61)

NameTypeDefaultDescription
seedINT00–18446744073709550000Seed for reproducible scenes. The control below defaults to 'randomize' so every run differs; set it to 'fixed' to reproduce a scene.
entity_countCOMBO1How 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_filterCOMBOAnyRestrict 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_fieldsCOMBOOffBulk-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.
environmentCOMBORandomWhere 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_kindCOMBORandomWhat 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_subkindCOMBORandomThe specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit
entity1_scaleCOMBORandomHow big it is relative to a person. Identity | Random=randomize, value=lock, None=omit
entity1_conditionCOMBORandomAge and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit
entity1_formCOMBORandomThe 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_materialCOMBORandomWhat it is covered in, made of, or wearing. Form & Material | Random=randomize, value=lock, None=omit
entity1_primary_colorCOMBORandomThe 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_colorCOMBORandomA secondary colour: trim, banding, the underside of a wing. Surface & Colour | Random=randomize, value=lock, None=omit
entity1_markingsCOMBORandomHeraldry, war paint, patterning. Carries no readable lettering. Surface & Colour | Random=randomize, value=lock, None=omit
entity1_surface_detailCOMBORandomFinish and wear at close range -- scars, moss, weathering, stitching of hide. Surface & Colour | Random=randomize, value=lock, None=omit
entity1_appendagesCOMBORandomWhat sticks out: horns, wings, tails, tusks, towers and masts. Components | Random=randomize, value=lock, None=omit
entity1_appendage_countCOMBORandomHow many. Composed into the appendage phrase. Components | Random=randomize, value=lock, None=omit
entity1_emittersCOMBORandomWhat 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_countCOMBORandomHow many. Components | Random=randomize, value=lock, None=omit
entity1_emitter_colorCOMBORandomThe colour of the magic itself. Belongs to the subject, not the lighting. Components | Random=randomize, value=lock, None=omit
entity1_armamentCOMBORandomWeapons, carried or grown: a longsword, a war maul, horns and talons. Components | Random=randomize, value=lock, None=omit
entity1_armament_countCOMBORandomHow many weapons. Components | Random=randomize, value=lock, None=omit
entity1_sensorsCOMBORandomHow it sees: eyes, many eyes, a lantern-bearing gaze. Components | Random=randomize, value=lock, None=omit
entity1_sensor_countCOMBORandomHow many. A cyclops and a many-eyed horror are different things. Components | Random=randomize, value=lock, None=omit
entity1_apertureCOMBORandomThe opening: a fanged maw, a visor, a gatehouse arch. Components | Random=randomize, value=lock, None=omit
entity1_extrasCOMBORandomOne more distinguishing thing -- a saddle, a satchel, a hanging banner. Components | Random=randomize, value=lock, None=omit
entity1_situationCOMBORandomWhat this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit
entity2_kindCOMBORandomWhat 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_subkindCOMBORandomThe specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit
entity2_scaleCOMBORandomHow big it is relative to a person. Identity | Random=randomize, value=lock, None=omit
entity2_conditionCOMBORandomAge and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit
entity2_situationCOMBORandomWhat this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit
entity3_kindCOMBORandomWhat 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_subkindCOMBORandomThe specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit
entity3_scaleCOMBORandomHow big it is relative to a person. Identity | Random=randomize, value=lock, None=omit
entity3_conditionCOMBORandomAge and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit
entity3_situationCOMBORandomWhat this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit
entity4_kindCOMBORandomWhat 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_subkindCOMBORandomThe specific type within the kind -- a unicorn, a frost troll, a galleon. Identity | Random=randomize, value=lock, None=omit
entity4_scaleCOMBORandomHow big it is relative to a person. Identity | Random=randomize, value=lock, None=omit
entity4_conditionCOMBORandomAge and state -- ancient, battle-scarred, slumbering, petrified. Identity | Random=randomize, value=lock, None=omit
entity4_situationCOMBORandomWhat this entity is doing -- an event in progress, not a pose. Scene | Random=randomize, value=lock, None=omit
relation_1_2COMBONoneHow two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit
relation_1_3COMBONoneHow two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit
relation_1_4COMBONoneHow two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit
relation_2_3COMBONoneHow two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit
relation_2_4COMBONoneHow two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit
relation_3_4COMBONoneHow two slots relate. Rendered only when both entities exist. Opt-in. Relations | Random=randomize, value=lock, None=omit
relation_1_2_positionCOMBONoneWhere the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit
relation_1_3_positionCOMBONoneWhere the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit
relation_1_4_positionCOMBONoneWhere the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit
relation_2_3_positionCOMBONoneWhere the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit
relation_2_4_positionCOMBONoneWhere the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit
relation_3_4_positionCOMBONoneWhere the first entity sits relative to the second. Relations | Random=randomize, value=lock, None=omit
contextCOMBORandomWhat 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
stagingCOMBORandomWhere 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
aspectCOMBORandomWhat 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_inoptSCENE_ENTITYOptional. 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_inoptSCENE_ENTITYOptional. 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_inoptSCENE_ENTITYOptional. 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_inoptSCENE_ENTITYOptional. 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)

NameTypeDescription
prompt_textSTRINGThe prompt. Wire this into the text encoder.
prompt_jsonSTRINGStructured scene data for other nodes or logs. Not a prompt: do not wire it into a text encoder.