RUN_DESIGN Text Format Guide
This guide describes a concise, hand-editable interactive story text format (RUN_DESIGN).
RUN_DESIGN Text Format Guide
This guide describes a concise, hand-editable interactive story text format (RUN_DESIGN). It supports reversible conversion: JSON → RUN_DESIGN → JSON with no semantic loss within supported fields.
Goals
Simple, readable text format suited to version control
Compiles into a JSON story structure
Exports JSON back to text without semantic differences
File Encoding
UTF-8
Whitespace & Comments
Blank lines are allowed.
Lines starting with
//are single-line comments and are ignored at compile time. Put them on their own line.
Top-Level Metadata
[meta] title "<Title>"— Set story title[meta] author "<Author>"— Set author (required; import/update rejected if missing)[intro] <text>— Append one line of introduction (repeatable). Shown when the script starts.
Display behavior:
In
.st list:With a specific alias, shows the full introduction (if any).
When listing all playable scripts, shows a preview of the first intro line (max 80 characters) under each title.
In
.st mylist:Each script shows a preview of the first intro line (max 80 characters).
After starting a game, before player variables are set:
A 【Introduction】 block with the full intro is shown before character setup prompts.
Example:
Player Variables
[player_var] <key> "<prompt>" ["<placeholder>"]key: identifier, e.g.
cat_nameprompt: question shown to the player
placeholder: optional hint text
Example:
Game Stats
[stat_def] <key> <min> <max> ["<label>"]Initialization: if not explicitly set, a random integer from
mintomaxis chosen at first start.Lock: once set by
[set]in content, random initialization no longer overwrites that stat.
Example:
Variables
[var_def] <key> <min> <max> ["<label>"]
Example:
Pages
A story is made of multiple pages.
Define page label (ID):
[label] <id>Limit: Page IDs must be numeric only (e.g.
0,1,2,10)Starting page:
[label] 0is the entry page, loaded automatically at game startOther pages may use any numeric ID; consecutive numbers improve readability
Optional page title:
[title] <text>Page content (unlimited lines):
[text] <content>[text|if=<expr>] <content>— Conditional display[text|else] <content>— Forms a chain with preceding[text|if=...]lines; only the first match shows; if none match,elseshows. Also works in ending blocks.[text|ifs=<expr>] <content>— Independent conditional display: shows whenever true; does not chain with nearby[text|if=...]/[text|else].[text|speaker=<key>] <content>— Specify speaker[text|speaker=<key>,if=<expr>] <content>— Speaker with condition[random] <percent>%— Affects only the next[text]line (e.g. 30%);percentis an integer 0–100.[set] <key>=<expr>— Sets value at render time: ifkeyis a definedstat_def, writes tostats; otherwise tovariables.<expr>supports basic expressions (see below).Once a stat is set by
[set], random initialization no longer overwrites it.
Inline dice in text:
{xDy}rolls at display time and is replaced by the total, e.g.{1D100},{2d20},{3d6}.
Ending marker:
[text]lines after[ending]are ending text; the first matching condition is usedSupports condition chains: multiple
[text|if=...]followed by one[text|else]fallbackRequirement: a valid RUN_DESIGN must include at least one page with
[ending]; upload/update is rejected without an ending.
Choice block:
[choice]— Start defining choices-> <text> | <page id> [| if=<expr>] [| stat=a+1,b-2]<page id>must be numeric, or a letter-suffixed variant (e.g.2a,2b,2c); or special valueEND.Feature:
2a,2b,2cetc.—the number (e.g.2) is the actual target page; the letter (e.g.a,b,c) distinguishes bonus or description variants (all jump to2).When
<page id>isEND, the UI offers a “.st end” button to finish the game.stat=supports integer add/subtract only, applied when successfully moving to that choice’s target (e.g.Cuteness+1,Energy-2).
Feature: Multiple Choices to the Same Page
Using 2a, 2b, 2c lets several choices jump to the same page (e.g. page 2) with different bonuses:
When the player uses .st goto 2a, .st goto 2b, or .st goto 2c:
All jump to page
2But apply different bonuses: Mischief+1, Cuteness+1, Energy+1
Expressions
Conditions use a small JS-like subset evaluated in scope (
variables+stats+playerVariables)Operators:
&& || ! < <= > >= == === != !== + - * / % ()Safety: no function calls; no access to
globalThis,global,process,this,Function,constructor,require, etc.Example:
if=Cuteness>=8 && Energy>3
Unary negation
!expris supported (use parentheses for precedence).Example:
if=(Strength>5) && !(Agility>5)
Dice
In conditions and assignments, use
xDyliterals; they roll before evaluation and are replaced by the total, e.g.:if=2d20>25[set] luck=3d6+2Allowed range:
x1–100,y1–10000; out-of-range values are clamped.
Conditional Set
[set]supports conditions:[set|if=<expr>] key=<expr>.Combined with dice, this supports common check flows.
Example:
Example Page
Ending Page
Placeholders
In
[text],{key}is resolved fromplayerVariables, thenstats, thenvariables(later sources can override earlier).If no key is found, the placeholder is left as-is (e.g.
{unknown_key}outputs literally).
Compatibility Notes
Plain-string page IDs are not supported; use numeric IDs (e.g.
0,1,2).[set]lines may have trailing//comments after the value; import ignores them without affecting the assignment.
Round Trip
Import text (Discord attachment): send
.st import <alias> [title]with a.txt(RUN_DESIGN) or.jsonfileUpdate existing script:
.st update <alias> [title]with a new fileExport text:
.st exportfile <alias>(bot sends the text file via DM)Verify reversibility:
.st verify <alias>
Normalization
The compiler treats adjacent
[random]and the following first[text]as “probability display.”On export, if a page is an ending page,
[ending]follows[label]on that page to preserve reversibility.
Conventions
Starting page:
[label] 0is loaded at game start. You may adjust in compiled JSON if needed.Page ID limit: numeric IDs only.
Speakers are optional; examples use plain text.
For randomness in body text, place
[random] <percent>%immediately before the[text]it should affect (percentis an integer).To change stats on a choice, use
stat=a+1,b-2(integer add/subtract only).For multiple choices to one page with different bonuses, use
2a,2b,2c.
Best Practices
Use numeric, preferably consecutive IDs for readability
Keep conditions simple and based on defined keys
Avoid very long lines; split into multiple
[text]linesEnsure each ending page offers restart or end options
Use
2a,2b,2cfor choices with different bonuses to the same page
Limits
Max pages: 400
Max length per
[text]line (including ending text): 500 charactersAt least one page with
[ending]is requiredMax attachment size on import/update: about 1 MB
Page IDs must be numeric
More Examples
Speakers and Condition Chains
Speakers are optional; at render time they are stored as metadata and do not change text output.
Random Display
[random] <percent>% applies only to the next [text] line; the compiler preserves reversibility on export.
Inline Dice and Conditional Checks
Use xDy in expressions; use {xDy} in text for inline rolls showing the total.
Conditional Set and String Values
Quoted RHS values are string constants; otherwise the RHS is evaluated as an expression.
Multiple Choices to Same Page (With Bonuses)
.st goto 2a/2b/2c all reach page 2 with different bonuses.
Independent Conditional Display (ifs)
[text|ifs=...] does not chain with adjacent if/else; each matching line can appear; multiple lines may show at once.
Ending Block Condition Chain
Multiple [text|if=...] plus one [text|else] in an ending form a chain; only the first match shows; unconditional lines may appear above as preamble.
Placeholder Priority and Nesting
{key} lookup order: playerVariables → stats → variables. If a string value contains {...}, one level of nested expansion is performed.
Last updated