Skip to content
Contents

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.

A complete little script
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, and menu end with a colon and indent their body; the block closes by dedenting (there is no endif keyword 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.

StatementSyntaxMeaning
Characterdefine var = Character("Name"[, color="#hex"])Declares a speaker (top of file). var is its short token.
Scene labellabel "name":Opens a scene (a graph node); statements follow, indented.
Dialoguevar "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.
Backgroundscene bg-name [clauses]Changes the stage background. Name is bare (may contain spaces).
Show spriteshow sprite-name [clauses]Puts a character/image on stage.
Hide spritehide tagRemoves a shown sprite.
Set$ var = value (also += -= *= /=)Changes an attribute. The $ prefix.
Branchif cond: / elif cond: / else:Conditional arms; the block closes by dedenting.
Loopwhile cond:Repeats its body while the condition holds; closes by dedent.
Jumpjump "scene" [if cond]Goes to another scene (quoted label), optionally if the condition holds.
Inputinput var "prompt" [ok "label"] [if cond]Asks the player for text, stored in attribute var.
Audioplay 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.
Menumenu [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

ClauseMeaning
with none|dissolve|wipe|slide|iris|fade [ms]Transition + duration in milliseconds (default 600).
color #rrggbbThe 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|vpunchA one-shot whole-stage punch.
video "name" loop|cutscenePlays a video on the stage (always the last clause).

After show

ClauseMeaning
as tagNames this instance (so a later show with the same tag replaces it).
at X,YPosition as a 0 to 1 fraction (0.5,0.5 is centered).
zoom N · natural · mirrorScale; native-size mode; horizontal flip.
effect fade|hop|shake|bounce|movein-left|movein-rightAn entrance animation.
filter "<css>" · zorder N · behind tagA 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.

TokenMeaning
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" · trueValues: 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.

Conditions in context
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) - hints

Inline text tags

Tags ride inside a quoted string and round-trip losslessly. Unknown braces are left as literal text.

TagEffect
{b} {i} {u} {s}Bold, italic, underline, strikethrough (closed with {/b} etc.).
{color=#hex} {size=N} {font=NAME}Color, size, and font for the wrapped span.
{w} {w=1.5}Wait for a click; or pause the reveal N seconds (auto-advance if at the end).
{nw} {p} {cps=N}Auto-advance now; a paragraph break; per-span reveal speed.
${var}Inserts the value of an attribute (great after an input).

Validation & 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 ~box theme override. This is guarded by property tests.
  • Deliberate normalizations (not bugs): statement ids are regenerated on apply; a show whose as tag equals the asset name drops the tag; a half-specified position fills the missing axis with 0.5.

Coming from Ren'Py

The grammar is Ren'Py-flavored, so most lines look nearly identical.

Ren'PyVNovelsNote
label start:label "start":The label is quoted. A scene here is a graph node.
scene bg roomscene bg-roomA background change, not a graph node.
e "Hi!" · $ love += 1e "Hi!" · $ love += 1Identical: a defined speaker var, and a $ set line.
window hidenobox suffixHide the say-window for a beat.
Back to topLast updated June 2026