Validation
A story-driven game fails in quiet ways: a choice that targets a deleted node, a flag set that nothing ever reads, a quest whose completion condition can never fire, an ending no path reaches. Parlance's position is that these are build errors — findable statically, reportable precisely, and fixable before a playtester ever trips over them.
One validator, three surfaces
The same rule set runs in three places, and they are kept in agreement:
-
On every save. Each write triggers a re-validation, and the results are pushed over a WebSocket to every open editor window. The pass is incremental: it re-checks the entities that changed and the entities that refer to them, and reuses everything else, so its result is the same one a full pass would give (a change to project-wide configuration such as
rules.jsonstill runs everything). The validation bar shows live error/warning counts with per-code filters, and appears only while there is something to report. Clicking an issue row takes you to the place the issue is about, not just the entity (hover a row to see where it will land):- a dialogue node or choice — the canvas selects the node and scrolls it into view, and a choice-level issue opens that choice in the inspector;
- a quest stage, outcome or objective — the quest canvas selects the stage or outcome and scrolls the objective into view;
- a field on any other entity — its form opens with that field outlined and focused, expanding Metadata first if it is collapsed;
FLAG,REP,REL— the variable, faction or character opens with its Flow panel, listing every place that checks it and every place that changes it; each row jumps to the node, which is where a read-but-never-set flag gets fixed.
Issues are listed entity by entity, errors before warnings; only an issue from a file the editor could not read at all sits above them. You never refresh, and you never validate "later."
The pass is also scheduled rather than run inside the save itself: a save returns as soon as the bytes are on disk, rapid saves coalesce into one validation instead of one each, and the pass runs on a worker thread off the editor's serving thread. On a small project the difference is invisible; on a large one it is the difference between a save that answers instantly and one that waits on a whole-project pass. See Performance.
-
In CI.
parlance ci-checkruns the identical code path headless: exit1on errors,--strictto fail on warnings too. See the CI tutorial. -
Independently.
validate.pyis a standalone Python reimplementation of the same rules — usable in pipelines with no Node toolchain, and doubling as a second opinion on the rules themselves. A parity test in the main suite asserts both implementations report the same issues, so they can't drift apart silently.
What gets checked
Thirty check families, spanning shape
(SCHEMA), wiring (REF, DUP), flow (FLOW, REACH, GATE), state
(FLAG, REP), structure (QUEST, ENDING, COVERAGE, LOC, CUT,
OFFER), content (LORE), and
progression math (PROG, XP, CHECK). The reference page lists every family
with what it scans and how to fix what it finds.
Errors, warnings, and why saves never block
- Errors are broken wiring — a reference to something that doesn't exist, data that violates schema. CI fails on these.
- Warnings are story smells — an offer that can never win, an unreachable ending, a
write-only flag. They're often work in progress, which is exactly why the
editor never blocks a save on validation: a half-built quest should be
savable, committable, and shareable mid-thought. The discipline point is CI,
where
--strictdraws whatever line your team wants.
Beyond the checklist: Reports
The Reports panel is validation's exploratory twin:
- Coverage & structure — issues grouped by family, clickable through to each entity: characters with no dialogue, unreachable nodes, orphaned flags, endings with no path in.
- The reference index — for any id in the project: where it's defined, every place it's read, every place it's written, each with its entity and JSON path. This is "find usages" for your story, and it's what makes renaming or retiring a variable safe. (Variables, items, factions and characters also get an inline Flow panel on their own detail page, whose rows click through to each use.)
Validation as a feature of the format
Because the data is schema-first plain JSON with published semantics, deep static analysis is possible at all — you can't statically trace flag flow through a pile of engine-side script. The validator is the payoff of the format's discipline, and via the open spec, third parties can implement the same checks.
Next: wire it into CI in ten minutes · every check family, explained