Syntax reference
The building blocks of MAST: variables, labels, flow control, tasks, and the rest. MAST evaluates expressions with Python, so Python types and expressions work inside MAST statements — but not identically; see Common gotchas.
Variables
Assign with name = value:
enemy_count = 20
name = "Artemis"
For values the MAST parser struggles with (nested lists, dict/set literals), wrap
the expression in inline python — two or more tildes (~~) on each side:
inventory = ~~ [[2, 3], [4, 5]] ~~
Scope
Variables are scoped to the task by default. Two modifiers change that:
shared enemy_count = 20 # visible to every task in the story
default difficulty = 5 # set only if it isn't already defined
Reading is automatic — you only mark scope at assignment:
shared beer_count = 8
my_beer = beer_count # read the shared value
shared beer_count = 0 # update the shared value
Labels
A label starts at column 0 with two or more = signs. The trailing = is
optional, and the count doesn't matter:
== goto_bar ==
...
=== show_helm
...
Two labels are implicit and don't need defining: main (where every script
starts) and END (ends the current task).
Labels are not functions — execution falls through from one into the next:
== one ==
log("one")
== two ==
log("two")
== three ==
log("three")
one
two
three
Jumps and ending
jump label (or the shortcut -> label) redirects flow. ->END ends the task:
== start ==
log("first")
-> here
== skipped ==
log("got here later")
-> done
== here ==
log("second")
-> skipped
== done ==
log("done")
->END
first
second
got here later
done
Ending the last remaining task ends the story.
Conditionals
MAST supports Python-style if / elif / else (and they can nest):
if value < 300:
log("less")
elif value > 300:
log("more")
else:
log("equal")
...and match / case:
match value:
case 200:
log("200")
case 300:
log("300")
case _:
log("something else")
Conditions also work inline on many statements:
->END if obj is None
jump loop if not is_timer_finished(0, "warmup")
Loops
Standard for ... in, plus a MAST-specific for ... while that loops until a
condition goes false. break and continue work as in Python:
for x in range(3):
log(f"{x}")
y = 10
for z while y < 30:
log(f"{z} {y}")
y += 10
0
1
2
0 10
1 20
2 30
Interpolating variables in log
log() is a function call, so use an f-string to interpolate:
log(f"{x}"). (Plain log("{x}") prints the literal text {x}.) GUI and
comms text — """{x} ships""" — interpolate without the f.
Tasks
Start a background task with task_schedule; the current task keeps running:
== start ==
log("before")
task_schedule(a_task)
log("after")
== a_task ==
log("in task")
->END
before
after
in task
Passing data
Data passed to a task becomes variables in it, separate from the caller's:
== start ==
message = "caller"
task_schedule(a_task, {"message": "Hello"})
log(f"{message}")
->END
== a_task ==
log(f"{message}") # "Hello" - from the passed data
->END
Metadata (label defaults)
A label can carry a metadata: block — a fenced YAML section whose keys are
injected as task variables when a task enters the label. They are
defaults: a variable already in scope (passed data, live state) wins.
== patrol ==
metadata: ``` yaml
speed: 0.5
radius: 4000
```
# `speed` and `radius` are now variables in this task
set_throttle(ship_id, speed)
->END
- Injected on entry — at task creation for spawned tasks (brains, objectives, prefabs) and on a jump/reroute into the label.
- Column-0 rule: the
metadata:line, the top-level YAML keys, and the closing fence must sit at column 0 — only the label's code is indented. - Works on any label type, including
//routes and@decorator labels.
Brain and objective labels lean on this for tunables — see brains and objectives.
Waiting for tasks
Capture a task in a variable and await it, or race/join several with
promise_any / promise_all:
t = task_schedule(a_task)
await t # wait for one task
a = task_schedule(worker, {"say": "A"})
b = task_schedule(worker, {"say": "B"})
await promise_all(a, b) # wait for both
# await promise_any(a, b) # wait for the first to finish
Cancelling
t = task_schedule(a_task)
task_cancel(t)
Comments
Line comments use #. MAST also supports C-style block comments:
fred = 10 # set fred to 10
/*
A block comment.
Handy for disabling several lines at once.
*/
// is not a comment
A line starting with // is a route label, not a comment. Use #.
Importing
Split a mission across files and import them — including from a zip
(the basis of shareable add-ons):
import story_two.mast
from my_lib.zip import bar.mast
Delays
Delays need a clock. delay_app uses the real-time clock (always running);
delay_sim uses sim time (paused when the game is). Both accept minutes and
seconds:
await delay_app(seconds=10)
await delay_app(minutes=1, seconds=5)
await delay_sim(5) # 5 sim-seconds
for x in range(3):
log(f"{x}")
await delay_sim(1)
Logging
logger() enables logging; log() writes to it. Logging can go to stdout, a
string variable, and/or a file, and you can have several named loggers.
logger() # enable stdout logging
logger(file="{mission_dir}/my.log") # also to a file
logger(name="tonnage", var="tonnage") # a second, named logger
log("Hello, world") # default logger
log(f"Tonnage: {tonnage}", name="tonnage") # named logger
log("Careful", level="warning") # with a level
log(message, name=None, level=None) is preferred over print(). See the
execution API.