The script language
The complete reference for the story script: the readable, Ren'Py-flavored text form of a story. Every statement, clause, operator, and tag, and how it validates and round-trips.
Overview
Every story is stored as a structured model (scenes, statements, characters, assets). The script is its readable text form: a Ren'Py-flavored, line-oriented language that serializes the model losslessly and parses straight back.
You almost never write the script by hand. The playable editor and Muse author it for you. It exists so you can read a story's shape at a glance, and so the AI rewrite and the importer have a precise, field-complete format.
It round-trips. Two functions bracket the language:
serializeProject(project)turns the model into script text (a deterministic pretty-printer).parseScript(text)turns the text back into a normalized model that the engine applies by resolving names to ids.
Anatomy of a script
A script is a few character definitions, then one labeled scene per node, each holding indented statements. Blocks open with a colon and close by dedenting.
define alice = Character("Alice", color="#ff005d")
define bob = Character("Bob", color="#00aaff")
label "start":
scene forest with dissolve 600
show alice_happy as alice at 0.3,0.5 zoom 1.2
alice "Hi! He said \"go\"."
"The wind blew."
$ love += 1
if love > 0:
bob "You like me."
menu:
"Go left" -> "the_end" $ love += 1
"Stay" -> ""
label "the_end":
"Fin."definelines sit at the top and name each speaker's short var.label "name":opens a scene; its statements are indented four spaces.if,while, andmenuend with a colon and indent their body; the block closes by dedenting (there is noendifkeyword in the text).- Quotes inside a string are backslash-escaped (
\"go\").
Statements
One statement per line. Optional clauses ride as suffixes in a fixed order.
| Statement | Syntax | Meaning |
|---|---|---|
| Character | define var = Character("Name"[, color="#hex"]) | Declares a speaker (top of file). var is its short token. |
| Scene label | label "name": | Opens a scene (a graph node); statements follow, indented. |
| Dialogue | var "text" | A defined character speaks; shows their name tag. |
| Narration | "text" | A bare quoted string: the narrator, no name tag. |
| Narration (styled) | narrator var "text" | Narration styled as a (defined) character; tag still hidden. |
| Background | scene bg-name [clauses] | Changes the stage background. Name is bare (may contain spaces). |
| Show sprite | show sprite-name [clauses] | Puts a character/image on stage. |
| Hide sprite | hide tag | Removes a shown sprite. |
| Set | $ var = value (also += -= *= /=) | Changes an attribute. The $ prefix. |
| Branch | if cond: / elif cond: / else: | Conditional arms; the block closes by dedenting. |
| Loop | while cond: | Repeats its body while the condition holds; closes by dedent. |
| Jump | jump "scene" [if cond] | Goes to another scene (quoted label), optionally if the condition holds. |
| Input | input var "prompt" [ok "label"] [if cond] | Asks the player for text, stored in attribute var. |
| Audio | play music|sound "name" [intro "name"] [fadein=N] [fadeout=N] [volume=N] | Plays a track/effect. Params are key=value (ms; volume 0 to 1). No stop keyword. |
| Menu | menu [if cond] [timeout N [default I]]: | A choice; option lines follow, indented. |
| Menu option | "Caption" [img "a"] [hover "a"] [if cond] -> "scene" [~ "sound"] [$ assign] | The -> "scene" is required (target may be empty for silent routing). |
Scene & show clauses
Backgrounds and sprites take optional suffix clauses. They are stripped from the end, so the leftover head is the asset name.
After scene
| Clause | Meaning |
|---|---|
with none|dissolve|wipe|slide|iris|fade [ms] | Transition + duration in milliseconds (default 600). |
color #rrggbb | The fade color (only with fade; default black). |
filter "<css>" | A persistent CSS filter (e.g. blur(4px)). |
overlay "asset" | An overlay image above the background. |
shake hpunch|vpunch | A one-shot whole-stage punch. |
video "name" loop|cutscene | Plays a video on the stage (always the last clause). |
After show
| Clause | Meaning |
|---|---|
as tag | Names this instance (so a later show with the same tag replaces it). |
at X,Y | Position as a 0 to 1 fraction (0.5,0.5 is centered). |
zoom N · natural · mirror | Scale; native-size mode; horizontal flip. |
effect fade|hop|shake|bounce|movein-left|movein-right | An entrance animation. |
filter "<css>" · zorder N · behind tag | A CSS filter; paint depth (higher in front); draw under a named tag. |
Conditions & expressions
Conditions (and the right side of a set) are a small expression language with a real parser: a lexer, a recursive-descent parser, and a sandboxed evaluator. There is no eval, so author content can never run arbitrary code.
| Token | Meaning |
|---|---|
and/&& · or/|| · not/! | Boolean operators (keyword or symbol). Group with parentheses. |
= / == · != · >= <= > < | Comparisons. A single = in a condition means equality. |
+ - * / | Arithmetic. Divide or modulo by zero yields 0 (never an error). |
love · 42 · "moon" · true | Values: an attribute, a number, a string, or a boolean. |
An unset attribute reads as 0, so not seen_well is true before seen_well exists. A malformed condition is simply false (and a malformed assignment does nothing); neither ever crashes the player.
mia "I knew you'd come." if (love >= 3 and brave) or route = "moon"
jump "secret_end" if seen_all and not betrayed
$ score = (correct * 10) - hintsValidation & round-trip
The parser is fail-soft: it never throws and never hangs. It is the lossless mirror of the model, with a few deliberate normalizations.
- Never crashes. An unrecognized line is skipped rather than corrupting the parse; an unbalanced block auto-closes at the end of the file.
- Lossless fields. Every statement field round-trips, including the opaque per-line
~boxtheme override. This is guarded by property tests. - Deliberate normalizations (not bugs): statement ids are regenerated on apply; a
showwhoseastag equals the asset name drops the tag; a half-specified position fills the missing axis with0.5.
Coming from Ren'Py
The grammar is Ren'Py-flavored, so most lines look nearly identical.
| Ren'Py | VNovels | Note |
|---|---|---|
label start: | label "start": | The label is quoted. A scene here is a graph node. |
scene bg room | scene bg-room | A background change, not a graph node. |
e "Hi!" · $ love += 1 | e "Hi!" · $ love += 1 | Identical: a defined speaker var, and a $ set line. |
window hide | nobox suffix | Hide the say-window for a beat. |
