Scene Spec
One node, one scene, in ComfyUI's visual-novel authoring suite
- scene_id
- scene_body
- graph
- graph
- scene
- scene_ref
Writing a branching narrative in a node editor sounds absurd, and then you remember that a visual novel is basically a directed graph of scenes and choices - which is precisely the kind of thing ComfyUI already models. LF_SceneSpec is the pack's scene-authoring node: one node declares exactly one scene, and you chain scenes together to build a full narrative graph. It's part of a small suite (Scene Spec, VN Switch, VN State, Compile VN) that authors, validates, and compiles deterministic, target-neutral narrative graphs - think "the story as data," with no renderer, engine, or live state attached.
The framing matters: this suite is CPU-only and target-neutral. It produces a validated JSON declaration of a scene - participants, beats of dialogue, choices, art requests - not a playable game. A consumer (a game engine, a workflow runner, a web app) takes the compiled bundle and does the actual rendering. What you get here is the authoring contract: describe the story precisely once, in a form that can be validated and reused.
What a scene body looks like
The scene_body textarea takes one strict-JSON object with exactly five root fields:
{
"participants": [],
"entryPredicate": {},
"beats": [ { "text": "A new story begins." } ],
"choices": [],
"artRequests": []
}
- participants - semantic IDs for who's in the scene.
- entryPredicate - optional condition for entering the scene.
- beats - dialogue beats; each needs an
idand eithertextor alocalizationKey. - choices - branching options; each needs an
idand eitherlabelor alocalizationKey, and may carry visibility/availability predicates, effects, and anextSceneId. - artRequests - requests for images per beat/choice (this is a ComfyUI suite, after all - art is data too).
Missing LF-owned child IDs get materialized only by the authoring UI once the JSON is valid, so headless/API users must supply their own IDs.
The identity system - read this
scene_id isn't a free-form label; it's an LF_ID, a persistent identity generated once by the authoring UI and kept stable in the node. The title becomes the friendly label, and IDs must follow the contract (ASCII letters/digits plus . _ : / -, max 200 chars). You also get a graph input/output: leave it empty for the first scene, then chain each preceding graph output into the next scene's graph input. Outputs are graph (the declaration chain), scene (the validated scene as generic JSON), and scene_ref (a typed reference other nodes use to point at this scene). Narrative transitions stay symbolic - the execution chain can remain linear even as the graph branches.
Installing and where it fits
It's in the LF Nodes pack:
cd ComfyUI/custom_nodes
git clone https://github.com/lucafoscili/lf-nodes.git
restart (or ComfyUI Manager → "LF Nodes"). No model downloads - the whole VN suite is pure Python. Pack notes as usual: Pillow pinned 12.2.0, and the repo was rewritten/archived in 2025, so clone lucafoscili/lf-nodes.
Reach for this when you're building a story pipeline in ComfyUI: describe scenes here, branch with LF_VNSwitch, define fixture state with LF_VNState, and finish at LF_VNCompile. The main gotcha is invalid JSON - the body must parse strictly, and the authoring UI is where missing child IDs get filled in, so hand-writing bodies for headless runs means being scrupulous about IDs. If that's your setup, keep the contract doc (the pack's docs/VISUAL_NOVEL.md) open. It's a niche tool, but for deterministic narrative-as-data it's a surprisingly clean one.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| scene_id | LF_ID | LF-owned persistent scene identity. Generated once by the authoring UI. | |
| title | STRING | Opening | Optional author-facing scene title. |
| scene_body | LF_TEXTAREA | { "participants": [], "entryPredicate": {}, "beats": [ { "text": "A new story begins." } ], "choices": [], "artRequests": [] } | One strict-JSON scene body containing participants, entryPredicate, beats, choices, and artRequests. Missing LF-owned child IDs are materialized only by the authoring UI after valid JSON. |
| graphopt | LF_VN_GRAPH | An existing declaration chain. Narrative transitions remain symbolic, so this execution chain may stay linear. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| graph | LF_VN_GRAPH | Declaration chain with this scene appended in authored order. |
| scene | JSON | Validated scene declaration through a generic JSON socket. |
| scene_ref | LF_REF | Persistent scene reference for typed LF_REF connections. |