VN Switch
Branching that keeps every route in the graph
- state
- switch_id
- switch_body
- graph
- graph
- selected_case
- target_scene_id
- preview_report
A branching narrative is just a switch statement with feelings. In the pack's visual-novel suite, that switch is LF_VNSwitch: it evaluates an ordered list of when-conditions against a fixture state and picks the first case that matches - while keeping every case and the fallback in the authored graph. The "keeps every route" part is the point. A naive branch would throw away the routes you didn't take; this node preserves the whole decision structure so the compiled narrative still contains all the possibilities.
That "first match wins" evaluation is worth internalizing, because it's the whole mechanic. Each case has an id, a when predicate, and a targetSceneId. Cases are checked in order; the first whose predicate passes against the fixture state wins. If nothing matches, the explicit fallback object (or null) handles it. Because ordering decides the outcome, a case list that reads fine top-to-bottom can behave differently than you intended if a broad predicate sits above a specific one - put specific cases first.
What you write
The switch_body textarea is strict JSON with exactly two fields:
{
"cases": [
{ "when": {}, "targetSceneId": "" }
],
"fallback": null
}
- cases - ordered; each has
id,when(the predicate, evaluated against fixture state), andtargetSceneId. - fallback - either
nullor an object withidandtargetSceneId. It has nowhen- it's the unconditional last resort.
Inputs are state (the LF_VN_STATE fixture - used only to preview which route would be selected, not embedded in the story), switch_id (an LF_ID, persistent, generated once by the authoring UI), the switch_body, and an optional graph to append this switch to the declaration chain.
Outputs: graph (the chain with this switch's full case list preserved), selected_case (the winning case, or an empty object when nothing matched), target_scene_id (the winning scene reference, or an empty string), and preview_report - a machine-readable evaluation trace showing exactly which cases were tested and why the selection happened. That trace is the debugging gift; when a route goes somewhere surprising, the report tells you which predicate fired.
Where it sits in the workflow
Typical chain: define state with LF_VNState → branch on it here → describe scenes with LF_SceneSpec → compile everything with LF_VNCompile. The switch's job is symbolic: it declares the branch in the narrative data. The actual runtime decision, for a real player, belongs to whatever consumer renders the compiled bundle.
Installing and gotchas
It's in LF Nodes:
cd ComfyUI/custom_nodes
git clone https://github.com/lucafoscili/lf-nodes.git
restart (ComfyUI Manager → "LF Nodes"). Pure Python, no models. Pack notes as usual: Pillow pinned 12.2.0; rewritten repo with old one archived in 2025 - install lucafoscili/lf-nodes.
Where people get burned: malformed switch_body JSON (strict), forgetting the fallback and then being surprised an unmatched case returns an empty selection, and predicate ordering (broad case first swallows the specific ones after it). Also remember the preview is against your fixture - if the "wrong" case wins, check both your predicate and your fixture state before suspecting the node. It's a sharp little tool, and the preview_report makes it a debuggable one.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| state | LF_VN_STATE | Fixture state used only to preview route selection. | |
| switch_id | LF_ID | LF-owned persistent switch identity. Generated once by the authoring UI. | |
| switch_body | LF_TEXTAREA | { "cases": [ { "when": {}, "targetSceneId": "" } ], "fallback": null } | One strict-JSON switch body containing ordered cases and an explicit fallback object or null. |
| graphopt | LF_VN_GRAPH | Existing declaration chain. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| graph | LF_VN_GRAPH | Declaration chain retaining every switch case and explicit fallback. |
| selected_case | JSON | Selected preview case, or an empty object when unmatched. |
| target_scene_id | LF_REF | Selected target scene reference, or an empty string when unmatched. |
| preview_report | JSON | Machine-readable case evaluation trace. |