Write, reconcile, ship

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 .vgs files, never .vgs.meta files. Run Fix + Reconcile after editing and let VgScript regenerate the sidecar.

Quick start

  1. 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.
  2. Create a folder for your narrative, with a Scenes.txt file and one .vgs file per scene.
  3. Write your scene in the .vgs file.
  4. Right-click the script and choose VgScript → Fix + Reconcile.
  5. 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.

TagMeaningHow it is used
[S]SceneThe scene header, normally one per file. Use [S1_Intro] or [S1] Intro.
[P]PersonSets 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]TalkA block of visible dialogue or narrative text.
[A]ActionA named game action or event. Actions are engine-defined and should be reused where possible.
[D]DecisionA player choice with numbered options. Its [P] names who chooses.
[C]ConditionA 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) starts T2 after the current line or action finishes.
  • &(Timer{S}) starts something alongside the current line or action without waiting for it, like a shell cmd &.
  • ->(S2-T1) points to entry T1 in scene S2. 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. The vgs tool 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 .meta and 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 an if(...), 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 TODO markers. 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 ->(, &(, =( and if(: this scene's entries, other scenes' entries after S2-, 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 actionPurposeCLI equivalent
Fix + ReconcileNormalize one script and regenerate its sidecar.vgs fix <file.vgs> [--renamed T3=T7]
Validate / Find MistakesReport 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 LinksReplace ->(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 EndsFollow every branch and report where it gets stuck.vgs travel [dir] [--from S1-A1] [--only dead-ends,…] [--json]
Live checksWhat 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:

OSInstalled 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:

  1. edit the .vgs, never its .meta;
  2. run vgs fix after an edit;
  3. run vgs find-mistakes to verify the result;
  4. 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.