Scripting Syntax
The idea behind Drafft is that it should be engine- and language-agnostic. You are free to use whatever language you prefer for your game logic and runtime. However, certain features — such as dialogue flow, narration, and commands — require identifying the purpose of each line.
For this reason, the Drafft scripting syntax (UAF) exists.
UAF is intentionally minimal. It is designed to add structure and meaning to plain text without turning it into a full programming language. If you do not need these features, the syntax can be safely ignored.
Scripting Rules
- Scripts are read top to bottom, one line at a time.
- Each line has a single, clear purpose (dialogue, comment, command, or control flow).
- Control flow statements (
@if,@else,@endif) never produce output on their own. - Lines inside inactive control blocks are skipped entirely.
- State changes persist forward for the remainder of the script.
Evaluation Contexts
Drafft scripts can be interpreted in different contexts depending on how they are used.
The scripting syntax always describes structure and intent.
Whether that intent is evaluated or executed depends on the context.
Export Context
During export:
- No game state is assumed
- Control flow is preserved, not evaluated
- Conditions are not resolved
- Commands are emitted as descriptive instructions
This ensures scripts remain engine-agnostic, portable, and safe to transform.
Simulation Context (Dialogue Simulator)
The Dialogue Simulator evaluates scripts using a temporary, simulated state.
In this context:
- Conditions are evaluated
- Control flow is executed
- Variable assignments affect simulated state
- Commands may be visualized or logged, but not executed
This allows authors to preview dialogue flow and branching logic without running the game.
Runtime Context
At runtime:
- The game engine owns the state
- Conditions are evaluated using live game data
- Commands are executed according to engine logic
Context Summary
| Context | State Exists | Conditions Evaluated | Commands Executed |
|---|---|---|---|
| Export | No | No | No |
| Dialogue Simulator | Yes | Yes | Simulated |
| Game Runtime | Yes | Yes | Yes |
Drafft defines structure and intent.
Evaluation depends on context.
Comments
// Comment
Comments are lines that are commonly ignored in the target engine. Drafft will include comments in the screenplay.
Example
// INT. SUBURBAN HOME - KITCHEN - NIGHT
// FILBERT (9), wiry, lost in his own imaginary world. Dressed as a Knight. A toy sword in his other hand.
Actor Line (with Speech Tag)
::Actor:: Actor Line [expression]
[#speechTag]::Actor:: Actor Line [expression]
Actor lines are the core building block of Drafft scripting. They represent a line of dialogue spoken by an actor and carry semantic meaning beyond simple text.
An actor line is still valid, readable text on its own, but the syntax allows Drafft to identify who is speaking, what is being said, and how that line should be interpreted or exported.
Purpose
Actor lines serve multiple roles:
- Identify actors to build and maintain the actor database
- Identify spoken dialogue to generate voice-over references
- Include dialogue lines in the generated screenplay
- Attach expressions or annotations to dialogue without breaking readability
- Allow remapping to engine-specific formats during export
Structure
Actor Identifier
::Actor::
Actoris treated as an identifier, not executable code- The name is not validated or interpreted by the scripting system
- The same actor name can appear across multiple scripts
Dialogue Text
The dialogue text is the spoken content of the line and may include inline expressions.
::Aries:: I have ${v.player.totalCoins} coins.
Speech Tags
Speech tags are optional identifiers used to uniquely reference a specific line of dialogue.
[#speechTag_abcd]::Actor:: Actor Line
In most cases, speech tags are automatically generated by Drafft and do not need to be written manually.
Purpose of Speech Tags
Speech tags are primarily intended for:
- Voice-over pipelines
- Audio asset linking
- Localization workflows
- Stable references across exports and revisions
They allow dialogue lines to be tracked independently of their position in a script or their literal text.
Automatic Generation
When desired, Drafft automatically generates a speech tag for actor lines that do not already have one.
Generated tags are based on:
- The document alias
- A short, random identifier
This ensures:
- Tags are stable within a document
- Collisions are extremely unlikely
- Tags remain human-readable and diff-friendly
Example of an auto-generated tag:
[#intro_a9F3]::Aries:: It's quieter than I expected.
Manual Tags (Optional)
While speech tags are usually auto-generated, they may also be written manually when explicit control is required.
[#line001_abcd]::Travis:: You talkin' to me?
Manual tags must follow the same format and uniqueness rules as generated tags.
Format Rules
Speech tags must:
- Be wrapped in square brackets
- Start with
# - Contain only alphanumeric characters, underscores, or dots
- Have a minimum length of 6 characters (excluding
#)
Valid examples:
[#intro_4aZ9]
[#scene01.line02]
[#npcGreeting]
Invalid examples:
[#hi] // too short
[#hello-world] // invalid character
[# hello ] // spaces not allowed
Execution Behavior
Speech tags:
- Do not affect control flow
- Do not affect execution
- Are ignored at runtime
- Are preserved during export
They exist purely as metadata.
Audio Tag Extraction
During export or processing, the speech tag identifier can be extracted without the surrounding brackets for use in audio or localization systems.
For example:
[#library_8F2a]::Kenji:: Wow, it’s peaceful in here.
May produce:
library_8F2a
as an audio or localization key.
Design Rationale
Speech tags are designed to be:
- Mostly invisible to writers
- Automatically managed by the tool
- Stable across script edits
- Friendly to external pipelines
This keeps scripts readable while still supporting production workflows.
Expression Annotation
An (Optional) expression annotation can be added at the end of the line to provide contextual information, such as tone, emotion, or delivery.
::Actor:: Actor Line [expression]
Expression annotations:
- Are treated as metadata
- Do not affect control flow or execution
- Are included in screenplay and export outputs
Examples
::Tyler:: The first rule of Fight Club is: You do not talk about Fight Club. [serious]
[#line001_abcd]::Travis:: You talkin' to me?
[#greeting_soft]::Aries:: It's nice to see you again. [warm]
Export Behavior
Actor lines can be transformed during export to match the syntax or conventions of the target engine or pipeline.
For example:
- Renaming actors
- Converting speech tags to engine IDs
- Stripping or mapping expression annotations
See Export Mappings for more details.
Design Notes
Actor lines are intentionally:
- Human-readable
- Diff-friendly
- Independent of any specific engine or runtime
This allows Drafft scripts to function both as working data and as long-term narrative documentation.
Commands
Commands represent engine-defined actions expressed declaratively inside a Drafft script.
Drafft does not execute commands. Instead, commands are preserved, identified, and exported so they can be interpreted by the target game engine, runtime, or pipeline.
Syntax
<CommandName(arg1, arg2)>
CommandNameis an identifier- Arguments are passed as plain values or expressions
- Drafft does not validate command names or arguments
Examples
<PlayBackgroundMusic("calm_library")>
<Pause(2)>
<ShowCharacter("Aries", "smiling")>
Special Cases
Fade commands <FadeOut(?params)> and <FadeIn(?params)> also get some special treatment in the screenplay output.
Execution Semantics
From Drafft’s perspective:
- Commands are descriptive, not executable
- They are emitted in order during export
- They are included in simulation only if their surrounding control flow is active
- Their meaning is defined entirely by the target engine or toolchain
Drafft treats commands as structured intent, not behavior.
Export Mappings
Commands may be transformed during export to match the conventions or APIs of the target engine.
For example:
- Renaming commands
- Rewriting arguments
- Converting commands into function calls, events, or data structures
- Removing unsupported commands
See Export Mappings for details.
Design Rationale
Commands are intentionally:
- Engine-agnostic
- Non-executable within Drafft
- Easy to identify and transform
- Safe to ignore if unsupported
This allows Drafft scripts to remain portable, readable, and adaptable across different engines and workflows without embedding engine logic into the scripting language itself.
Control Flow Statements
Control flow statements define conditional structure inside a script.
They describe intent and structure, not execution. Whether conditions are evaluated depends on the evaluation context (export, simulation, or runtime).
Expressions and Resolution Model
All expressions reference values from the evaluation context.
Values are always accessed using the same structural pattern:
<collection>.<_sid>.<property>
The _sid is a human-readable, author-defined identifier used exclusively for scripting and expression resolution.
_sid: forestTrial
title: "The Forest Trial"
difficulty: Easy
Uniqueness and Validation
\_sid must be unique within its collection
- Conflicts are reported by Drafft during validation
- Scripts referencing ambiguous or missing
\_sidvalues cannot be simulated or exported - Drafft provides tooling assistance to help identify and resolve conflicts early.
Common roots include:
defaults(ord) — authored default valuesvariables(orv) — variables propertiesquest(orq) — quest properties and state (available in future versions)items(ori) — item properties (available in future versions)actors(ora) — actor properties (available in future versions)
Examples:
variables.player.courage
quest.forestTrial.courage
Examples below will be available in future versions.
items.healthPotion.count
actors.Aries.mood
v.settings.speechVolume
i.sword.damage
a.Aries.mood
This consistency keeps scripts predictable, diff-friendly, and engine-agnostic.
@if
@if <expression>
Defines a conditional block. The enclosed lines belong to the branch that should be taken if the expression evaluates to true.
Example:
@if variables.player.courage > 5
::Aries:: You seem more confident.
@else
::Aries:: Still working on your courage?
@endif
Evaluation Context
- Export context The condition is preserved as-is. No evaluation occurs.
- Simulation context The expression is evaluated using simulated state to preview dialogue flow.
- Runtime context The expression is evaluated by the game engine if required
Multi-Way Branching
For 3+ options, use nested @if blocks:
@if variables.player.courage > 7
::Aries:: You are fearless now.
@else
@if variables.player.courage > 3
::Aries:: You are getting braver.
@else
::Aries:: You still seem unsure.
@endif
@endif
@else
@else
Defines a fallback branch that applies if no previous condition matched.
- Optional
- May appear only once per
@ifblock
@endif
@endif
Ends a conditional block.
Every @if must have a matching @endif.
Script Player Feedback
In the Script Player:
@if,@else, and@endiflines are visible- The meta box shows condition results (TRUE/FALSE)
- Inactive lines are dimmed and show which condition is blocking them
State Assignment
@set
@set <target> = <expression>
Defines an assignment that may modify runtime state.
The assignment target must follow the standard resolution pattern:
<collection>.<_sid>.<property>
Example:
@set variables.player.courage = variables.player.courage + 1
@set items.sword.condition = broken
@set defaults.quest.forestTrial.started = true
Evaluation Context
- Export context The assignment is preserved but not executed.
- Simulation context The assignment updates simulated state.
- Runtime context The assignment updates game state as defined by the engine.
Rules
- One assignment per line
- Left-hand side must be a writable reference
- Right-hand side must be a valid expression
- No side effects outside of the assignment itself
Compound Assignment Operators
You can use compound assignment operators for brevity when performing arithmetic operations:
| Operator | Meaning | Example |
|---|---|---|
+= | Add and assign | @set variables.player.hp += 5 (same as = variables.player.hp + 5) |
-= | Subtract and assign | @set variables.player.hp -= 3 |
*= | Multiply and assign | @set variables.player.level *= 2 |
/= | Divide and assign | @set variables.player.mp /= 2 |
^= | Exponentiate and assign | @set variables.player.power ^= 2 |
Benefits for dialogue writers:
- Avoid repeating long variable paths
- Reduce opportunities for typos
- Make intent clearer at a glance
Example comparison:
// Verbose form (still supported)
@set variables.player.quest.forestTrial.jobsDone = variables.player.quest.forestTrial.jobsDone + 1
// Compact form (equivalent)
@set variables.player.quest.forestTrial.jobsDone += 1
Inline Interpolation (${})
Inline interpolation allows you to embed values from the evaluation context directly into text.
It is commonly used inside actor lines, narration, and other plain-text content.
${ expression }
Expressions inside ${} follow the same resolution rules as control flow and assignments.
Currently variables collection is supported for interpolation, but support for quest, items, and actors is planned for future versions.
Purpose
Inline interpolation is designed to:
- Insert dynamic values into dialogue and narration
- Reference variables and document properties
- Keep scripts readable and close to natural language
It does not execute logic and does not mutate state.
Examples
::Aries:: I feel like my courage is at ${variables.player.courage} today.
::Narrator:: You have ${variables.player.gold} coins left.
::Narrator:: Quest completed: ${quest.libraryVisit.title}
Supported Expressions
Inline interpolation supports simple, read-only expressions:
-
Property access
${variables.player.courage}${quest.libraryVisit.courage}
The exact capabilities depend on the target engine or simulation environment.
What ${} Is Not
Inline interpolation is intentionally limited.
It does not support:
- Control flow (
if,else, loops) - Assignments or state mutation
- Function calls
- Access to engine APIs
All logic and decision-making must be expressed using @ directives or handled by the target engine.
Relationship to Control Flow
Inline interpolation does not affect whether a line is included or excluded.
Control flow is handled exclusively by @if, @else, and @endif.
@if quest.libraryVisit.courage > 2
::Aries:: I feel confident (${quest.libraryVisit.courage}).
@endif
In this example:
@ifdetermines whether the line exists${}only affects what is displayed
Data Resolution: State vs Defaults
By design, all property lookups resolve to runtime state by default.
${variables.player.courage}
To explicitly reference the authored default value, use the defaults root:
${defaults.variables.player.courage}
Resolution Summary
| Expression | Resolves To |
|---|---|
variables.x.y | Runtime variable state |
defaults.variables.x.y | Authored variable default |
quest.x.y | Runtime quest state (not available yet) |
defaults.quest.x.y | Authored quest default (not available yet) |
Notes
- Whitespace inside
${}is ignored - Unresolved expressions may be:
- left untouched
- replaced with a fallback value
- handled by the target engine (implementation-specific)
Mental Model
If it starts with
@, it changes or decides something. If it’s inside${}, it only displays something.
This separation is intentional and central to Drafft’s scripting philosophy.
Inline JSON
Since v1.0.13 it is possible to inline standard JSON in both speech and commands lines. %json variable will contain parsed content.
Examples:
::Reporter:: Now let's take a look at the weather for today.
<CutTo(Location1)> {"weather":"rainy"}
::FieldReporter:: Not looking very good here. {"props":["umbrella","raincoat", "microphone"]}
<CutTo(Studio)>
::Reporter:: What's the traffic on your side Mike?
<CutTo(Location2)> {"traffic": {"cars": "low", "trucks":"none"}}
::Mike:: Low traffic in this area.
JSON can be transformed at export time as well, see Export Mappings
Common Misconceptions
A few clarifications that help avoid common sources of confusion:
- “My
@ifdidn’t run during export.” This is expected. Control flow directives are not evaluated during export. They describe intent and are preserved for the target engine or simulation environment. - “Commands execute when the script is loaded.” Drafft never executes commands. Commands are descriptive markers whose execution is entirely up to the game engine or tools consuming the exported data.
- “The Dialogue Simulator defines how scripts behave in-game.” The Dialogue Simulator is a preview and debugging tool. It evaluates scripts using its own evaluation context, but the game engine is always the final authority.
- “Drafft replaces game logic.” Drafft describes narrative structure, flow, and intent. Game logic, rules, and systems live in the engine.