For the complete documentation index, see llms.txt. This page is also available as Markdown.

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_name

    • prompt: 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 min to max is 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] 0 is the entry page, loaded automatically at game start

    • Other 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, else shows. 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%); percent is an integer 0–100.

    • [set] <key>=<expr> — Sets value at render time: if key is a defined stat_def, writes to stats; otherwise to variables. <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 used

    • Supports condition chains: multiple [text|if=...] followed by one [text|else] fallback

    • Requirement: 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 value END.

      • Feature: 2a, 2b, 2c etc.—the number (e.g. 2) is the actual target page; the letter (e.g. a, b, c) distinguishes bonus or description variants (all jump to 2).

      • When <page id> is END, 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 2

  • But 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 !expr is supported (use parentheses for precedence).

    • Example: if=(Strength>5) && !(Agility>5)

Dice

  • In conditions and assignments, use xDy literals; they roll before evaluation and are replaced by the total, e.g.:

    • if=2d20>25

    • [set] luck=3d6+2

    • Allowed range: x 1–100, y 1–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 from playerVariables, then stats, then variables (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 .json file

  • Update existing script: .st update <alias> [title] with a new file

  • Export 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] 0 is 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 (percent is 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] lines

  • Ensure each ending page offers restart or end options

  • Use 2a, 2b, 2c for choices with different bonuses to the same page

Limits

  • Max pages: 400

  • Max length per [text] line (including ending text): 500 characters

  • At least one page with [ending] is required

  • Max 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: playerVariablesstatsvariables. If a string value contains {...}, one level of nested expansion is performed.

Last updated