VgScript documentation
Write branching game narratives in VgScript, keep their sidecar metadata in sync, and check every branch from your IDE or the vgs CLI.
On this page
VgScript is a plain-text language for game narrative. A script is made from bracket-tagged lines for scenes, speakers, dialogue, actions, conditions, and decisions. The IDE extensions color each kind of entry, check the script as you type and let you move through the story like code; vgs, the command-line engine they bundle, keeps each script's runtime metadata in sync.
The one rule to remember: edit
.vgsfiles, never.vgs.metafiles. Run Fix + Reconcile after editing and let VgScript regenerate the sidecar.
Quick start
- Install the free VgScript extension for your IDE: the plugin for JetBrains IDEs (Rider, IntelliJ IDEA, WebStorm and the others, 2023.1 or newer) or the extension for VS Code (1.82 or newer). More IDEs are added as people ask for them: tell us which one.
- Create a folder for your narrative, with a
Scenes.txtfile and one.vgsfile per scene. - Write your scene in the
.vgsfile. - Right-click the script and choose VgScript → Fix + Reconcile.
- Choose VgScript → Validate / Find Mistakes before committing your changes, and Travel / Find Loose Ends to walk every branch.
Here is a small but complete scene:
[S1_Kitchen] Kitchen, night[A1] FadeIn ->(T1)[P] Anna -> Marco[T1][1] Are you still awake? ->(D1)[P] Marco -> Anna[D1][1] =(T2-1)'I was reading.' ->(T2)[2] =(T3-1)'I could not sleep.' ->(T3)[T2][1] I was reading. ->(A2)[T3][1] I could not sleep. ->(A2)[A2] FadeOut &(Timer{S})
Reconcile numbers and normalizes the entries, copies the referenced talk text into each decision option (the =(T2-1) disappears and the option keeps the text of T2's first line), preserves stable IDs, and writes Kitchen.vgs.meta next to the script. The story starts at the first action, A1, and every branch reaches the ending A2.
Language basics
Tags must begin a line. Four tags create entries; two provide context for the entries that follow.
| Tag | Meaning | How it is used |
|---|---|---|
[S] | Scene | The scene header, normally one per file. Use [S1_Intro] or [S1] Intro. |
[P] | Person | Sets the speaker of the talks and decisions below it. [P] Anna -> Marco also sets who is addressed, and [P] Anna -> Marco, Lea addresses several. An empty [P] clears the speaker. |
[T] | Talk | A block of visible dialogue or narrative text. |
[A] | Action | A named game action or event. Actions are engine-defined and should be reused where possible. |
[D] | Decision | A player choice with numbered options. Its [P] names who chooses. |
[C] | Condition | A branch gated by game state; its meaning is handled by the game engine. |
Sublines
Numbered lines belong to the nearest entry above them. Reconcile normalizes placeholders such as [X] and long-form tags such as [T1-2] to [1], [2], and so on.
[P] Hero[T1][1] So this is where it begins.[2] Nothing here yet. Only my voice.
Flow and references
->(T2)startsT2after the current line or action finishes.&(Timer{S})starts something alongside the current line or action without waiting for it, like a shellcmd &.->(S2-T1)points to entryT1in sceneS2. Run Resolve Cross-Script Links after adding or changing cross-scene jumps: it replaces the scene code with the destination's stable hash, so renumbering either scene never breaks the jump.- An arrow can also run inline action calls next to its target, as in
->(T7, AddFact{T7}, RemoveFact). The editors highlight these calls as actions. - Everything after
->or&(is flow control, not visible dialogue. - Text inside
{...}is an engine-level parameter or inline marker. Thevgstool treats it as metadata and removes it from visible text.
Do not write {#guid:...} anchors or destination hashes yourself: the tool owns them. {#dst:...} is a legacy form that Resolve Cross-Script Links converts to a hash.
Decisions and conditions
Each numbered option points to the entry that should run when selected:
[D1][1] Accept ->(A_yes)[2] Decline ->(A_no)
Use =(T2-1) when an option should reuse the visible text from the first line of T2. Reconcile materializes that text so the choice and the dialogue cannot drift apart.
An option, or a talk line, may start with if(...); the condition is excluded from the visible text. Combine terms with && and ||, as in if(D2-1 || D2-2); && binds tighter, parentheses are not supported, and a comma still means &&. if!(...) negates the whole condition. What a term means is up to the engine: in Unreal, a bare key such as T5 is true once that entry has played, and CF{name} checks a fact.
Comments
// comment runs to the end of the line, and /* comment */ may span lines. Both are stripped before parsing, so they never change an entry's text or its ID.
Organizing a project
Keep one scene per .vgs file and list the scenes in playback order in Scenes.txt:
// Scenes:
[1] /Intro
[2] /Kitchen
[3] /Finale
A line names a scene by its path (acts/Intro.vgs), its file name (Intro.vgs) or its name without the extension (/Intro), matched without regard to case against the .vgs files under the folder of Scenes.txt. [n] prefixes and // comments are allowed.
The project-wide commands follow this order. Reorder Scenes can either keep filenames unchanged and write [n] prefixes in Scenes.txt, or rename files to S<n>_<Name>.vgs when the editor's rename setting is enabled.
Reconcile and validate
Every .vgs file has a generated <file>.vgs.meta JSON sidecar. Your game reads this stable, machine-friendly representation. A reconcile pass:
- numbers entries and normalizes their sublines;
- retains entry identity across edits using tool-managed anchors, codes, and content matching;
- fills decision text referenced from talk lines;
- annotates cross-script destinations and reports missing ones;
- rewrites the
.metaand may add stable anchors to the.vgs.
Add and remove entries wherever the story needs them, including in the middle of a scene. Give a new entry any number its kind doesn't use yet in that scene, such as [T90], and point the arrows at it: Fix + Reconcile renumbers the scene in file order, rewrites every reference to the new codes and keeps every existing entry's ID. It only changes a code where it is a reference (entry tags, ->(…), &(…), if(…), [C] branches, =(…) and unquoted parameters such as Cancel{C4}), never in dialogue, option text or quoted values, and never in a reference to another scene's entry. A code that is already taken, such as a pasted second [T3], gets a number of its own.
Run the file workflow after writing or editing a scene:
vgs fix Kitchen.vgs
vgs find-mistakes Kitchen.vgs
Before a build or after changing several scenes, run the project workflow:
vgs fix-all .
vgs check-all .
To rename an entry's code by hand, use the editor's Rename: the tag, every reference in every scene and its long-form lines change together, and the entry keeps its ID. If you renamed it in a text editor instead, tell fix: vgs fix Kitchen.vgs --renamed T3=T7 (several: --renamed T3=T7,A2=A5).
Sidecars are written as schemaVersion 2, where directedTo is always a list. Older versions of vgs cannot read them, so update the IDE extension, any vgs on your PATH and the Unreal plugin together.
Find loose ends
Travel / Find Loose Ends walks every jump from the first action of the first scene, or from any entry you name, and reports where the branching can get stuck. It checks the script's structure rather than playing it: actions finish at once, and every decision option and every condition branch is followed, so no condition is evaluated.
vgs travel .
vgs travel . --from S3-A1
The travel nests each branch under the decision or condition that opened it, and the editors let you fold and unfold those branches and jump from any entry to its line. The report at the end lists:
- dead ends: a talk, decision option or condition branch with no
->; - broken links: a jump to an entry, scene or destination that does not exist;
- no fallback: a condition without
else, or a decision whose every option has anif(...), so nothing may follow when no condition holds; - endless loops: entries that only jump among themselves;
- duplicate codes: an entry code written twice in one scene;
- the endings it reached (actions with no
->) and the entries nothing reaches.
It also notes two things that are not errors: a jump to another scene still written as Sx-Code (unresolved links), and talk lines said after a jump, alongside what it starts (lines after jump).
Each kind has its own heading in the report. To look at a few of them, filter the travel: the editors' Filter button lists every kind with how many the travel found, and from the CLI --only does the same. Without the whole travel, only the paths to what you picked stay, so a long story shrinks to the branches that need work:
vgs travel . --only dead-ends,broken-links
vgs travel . --only problems,not-reached
With --only, the exit code is 1 only when one of the problems shown is found. --json prints the travel as one JSON object, for tools and AI agents.
Writing in your IDE
The JetBrains plugin and the VS Code extension share one engine, so they color and underline exactly the same things.
- Semantic coloring. Each kind of entry has its own tint, and a reference takes the color of the entry it points to. Every color can be changed in the settings, and the whole line can be tinted or only its tag.
- Live checks on the unsaved text, across every scene of the project: jumps to scenes, entries, lines or IDs that do not exist, a scene key two files declare, cross-scene links not resolved yet, codes written twice, unpaired brackets, an empty or repeated listener, a speaker talking to themselves, and
TODOmarkers. Codes out of order are hints that say what Fix + Reconcile will renumber them to. - Go to definition and find references from any jump, parallel start, condition,
[C]branch,=(T1-1)copy or call parameter, into other scenes too. - Hover over a reference to see who speaks and the entry's first lines, or why it leads nowhere; over a tag, to count the references to it.
- Completion inside
->(,&(,=(andif(: this scene's entries, other scenes' entries afterS2-, talk lines and decision options, each with what it does. After[P], the speakers already used in the project. - Rename an entry code everywhere at once, keeping its ID.
- Outline of the scene, its entries and their options, and folding per entry and per block comment.
In JetBrains IDEs, navigation, hover, completion, rename and the outline come from Red Hat's free LSP4IJ plugin. Opening a .vgs file without it offers to install or enable it. Hovering an underlined problem shows its message, and the problems also appear in the Problems view.
Editor actions and CLI commands
Right-click a .vgs file for the actions that work on that script plus Travel, or a folder or Scenes.txt for the project-wide ones. In JetBrains they sit in the VgScript submenu, and the VgScript status-bar button, shown while a .vgs file is open, lists all six.
| Editor action | Purpose | CLI equivalent |
|---|---|---|
| Fix + Reconcile | Normalize one script and regenerate its sidecar. | vgs fix <file.vgs> [--renamed T3=T7] |
| Validate / Find Mistakes | Report problems in one script without changing it. | vgs find-mistakes <file.vgs> |
| Fix + Reconcile All (Scenes.txt) | Reconcile every listed scene in order. | vgs fix-all [dir] |
| Resolve Cross-Script Links | Replace ->(Sx-Code) jumps with stable destination hashes. | vgs resolve-links <dir> |
| Reorder Scenes (Scenes.txt) | Apply scene order to headers and references. | vgs reorder-scenes <dir> [--dry-run] [--rename-files] |
| Travel / Find Loose Ends | Follow every branch and report where it gets stuck. | vgs travel [dir] [--from S1-A1] [--only dead-ends,…] [--json] |
| Live checks | What the editor underlines in one script. | vgs analyze <file.vgs> [--json] |
vgs check-all [dir] runs Find Mistakes on every scene. vgs --help lists every command, vgs <command> --help explains one, and vgs help-all prints them all at once. A project's scenes file can have another name: pass --scenes-file <name> to the project-wide commands.
CLI exit codes are stable: 0 means success; 1 means find-mistakes, check-all, analyze or travel found problems; and 2 means the script, Scenes.txt or the travel's start entry could not be found. resolve-links and reorder-scenes report through their console output rather than the exit code.
Installing vgs on your PATH
The IDE extensions bundle vgs for Windows x64. The JetBrains plugin copies it when it opens a project, and the VS Code extension when it opens a .vgs file, to one fixed folder whichever IDE or version you have:
| OS | Installed CLI |
|---|---|
| Windows | %LOCALAPPDATA%\vgscript\bin\vgs.exe |
| macOS / Linux | ${XDG_DATA_HOME:-~/.local/share}/vgscript/bin/vgs |
To run vgs from a terminal, a build script or an AI agent, add that folder to your PATH: on Windows the plugin offers to, and Settings → Tools → VgScript has an Add to PATH button. Open a new terminal afterwards.
On macOS and Linux no vgs is bundled yet, so the editor actions, live checks and navigation are Windows-only for now. If you have a vgs build for your platform, put it on your PATH or set its path in Settings → Tools → VgScript.
Unreal Engine
The VgScript plugin for Unreal Engine 5.8, available on Fab, imports your reconciled scenes and plays them as subtitles, choices and game actions, with per-language subtitle tables and CSV translation. Its import runs vgs fix-all for you, so after editing a script you only click Extract Data. See VgScript for Unreal Engine to set it up.
Working with an AI assistant
Give the assistant project context and the VgScript workflow before asking it to edit narrative files. The complete agent guide documents the language, the tool commands, and the writing conventions in a reusable form: hand your assistant the plain-text version, or copy it into your repo's AGENTS.md. The public VgScript example project is a working sample of a repo set up that way.
An assistant should always:
- edit the
.vgs, never its.meta; - run
vgs fixafter an edit; - run
vgs find-mistakesto verify the result; - reuse existing actions unless you explicitly approve a new one.
vgs analyze <file.vgs> --json and vgs travel --json give an agent the same checks the editor shows, in a form it can read.
Troubleshooting
An editor action says the binary is missing
The plugin bundles vgs for Windows x64 only. On macOS or Linux, put a vgs build for your platform on your PATH, or set its path in Settings → Tools → VgScript.
Go to definition, hover or completion do nothing
In a JetBrains IDE, install or enable LSP4IJ from the Plugins page; the notification on opening a .vgs file links to it. If a custom binary path points at an old vgs, the language features turn off: clear the setting or update that vgs to the version the plugin ships.
Colors or live diagnostics do not appear
Check that semantic decorations and diagnostics are enabled in the plugin settings. Open the IDE's VgScript output or run console for details; if only colors are affected, test another theme because themes can override token colors.
A cross-script jump is missing
Confirm that both scenes have reconciled .meta files, check that Scenes.txt contains the target scene, then run Resolve Cross-Script Links again.
To report a problem or ask a question, write to contact@noumeninteractive.com.