VN State
The immutable 'save file' your narrative graph evaluates against
- fixture_id
- state_body
- profile_ref
- state
- state_object
- canonical_json
Every branching story needs a way to answer "what's true right now?" - which flag got set, which choice the player made, what the world knows. In the pack's visual-novel suite, that question is answered by LF_VNState, a small node that wraps a chunk of JSON into an immutable, versioned "fixture state" envelope. Think of it as the save file for your narrative graph, except it's deliberately not a real save file: it's a fixed test fixture you run the story against.
The distinction matters because it's the whole design. The suite is target-neutral and CPU-only - it authors and validates narrative data, not a playable game. LF_VNState creates the state object that LF_VNSwitch evaluates and LF_VNCompile previews against. The output envelope (lf.vn.state.v1) carries a fixtureId, the values you provided, an appliedEffectIds list, and an optional profileId. It's immutable once created - no mutation, no in-place edits, just a stable document.
What you actually set
- fixture_id - an
LF_ID(persistent identity, kindfixture), generated once by the authoring UI. Headless prompts must supply it. - state_body - the actual state, as strict JSON. The default is
{ "markers": [] }- a bare-bones start, and honestly a fine one for simple stories. Predicate paths in your switches and scenes are evaluated relative to this object, so this is where the flags, counters, and facts of your narrative live. - profile_ref (optional) - a consumer-owned profile reference, chosen from a catalogue or via a typed connection. Ignore it until you actually need per-consumer profiles.
Outputs are the same state three ways: state (the typed LF_VN_STATE), state_object (the same envelope through the generic JSON socket - useful if you're feeding downstream JSON tooling), and canonical_json (the envelope as canonical JSON text, for hashing, inspection, and API clients).
How it fits
The typical chain: define a fixture here, branch on it with LF_VNSwitch (whose cases test predicates against this state), declare scenes with LF_SceneSpec, and finally compile the whole thing with LF_VNCompile - which uses this same state purely to preview the story, and explicitly does not embed it in the shipped bundle. That separation is the author's own rule, and it's a good one: your test fixture shouldn't leak into production narrative data.
Installing and gotchas
It's part of LF Nodes:
cd ComfyUI/custom_nodes
git clone https://github.com/lucafoscili/lf-nodes.git
restart (or ComfyUI Manager → "LF Nodes"). Pure Python, no models. Pack notes as usual: Pillow pinned 12.2.0; repo rewritten and old one archived in 2025 - clone lucafoscili/lf-nodes.
The realistic pitfalls: invalid JSON in state_body (it must parse strictly - the whole suite is strict about this), and treating this node as actual game save-state machinery. It's a fixture, not runtime state - if you need a story that remembers what the player did at runtime, that's the consumer engine's job, not this node's. Keep that framing straight and LF_VNState is dead simple: state in, state out, nothing clever in between. That's precisely what makes it reliable.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| fixture_id | LF_ID | LF-owned persistent fixture identity. Generated once by the authoring UI; headless prompts must provide it. | |
| state_body | LF_TEXTAREA | { "markers": [] } | Opaque strict-JSON fixture values. Predicate paths are evaluated relative to this object. |
| profile_refopt | LF_REF | Optional consumer-owned profile reference. Choose it from a consumer catalogue or supply it through a typed connection. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| state | LF_VN_STATE | Versioned immutable VN fixture state. |
| state_object | JSON | The same state envelope through the generic JSON socket. |
| canonical_json | STRING | Canonical JSON text for hashing, inspection, and API clients. |