Anatomy of a mission
Every story.mast has the same shape. Read Secret Meeting top to bottom and you
see it: shared state → NPCs → sides → media → a @map/ label that builds the
world and runs the game → helper labels. Learn this shape once and the cookbook
pages slot into it.
The order
Top-level code runs first (and once for shared), so define things before the
code that uses them:
- Shared state —
sharedvariables the whole story needs. - Lifeforms — NPCs (an Admiral, station crew) for comms and faces.
- Sides — create sides and set their relations.
- Media —
@media/music/@media/skybox. These labels execute, so put shared data above them. @map/label (server-only) — itsmetadataexposes setup options, then its body builds the world, spawns players, and runs the game.- Helper labels — reusable tasks (send a message, spawn a wave).
Minimal skeleton
Copy this and fill in the blanks:
# 1. shared state
shared home_id = None
# 2. NPCs (for comms faces)
shared admiral = lifeform_spawn("Admiral Harkin", random_terran(), "admiral")
# 3. sides
tsn = await prefab_spawn(prefab_side_generic, data={"key":"tsn", "name":"TSN", "color":"#07F"})
raider = await prefab_spawn(prefab_side_generic, data={"key":"raider", "name":"Raider", "color":"#F00"})
side_set_relations(tsn, raider, sbs.DIPLOMACY.HOSTILE)
# 4. media
@media/music/default "Cosmos Default Music"
@media/skybox/sky-bored-alice "borealis"
# 5. the map (server-only)
@map/my_mission "My Mission"
" Defend the station from raiders.
metadata: ``` yaml
Properties:
Player Ships: 'gui_int_slider("$text:int;low: 1.0;high:8.0;", var="PLAYER_COUNT")'
Difficulty: 'gui_int_slider("$text:int;low: 1.0;high:11.0;", var="DIFFICULTY")'
# player ships + docking (from the LegendaryMissions add-ons)
await task_schedule(spawn_players)
await task_schedule(docking_standard_player_station)
# game end conditions
game_end_condition_add(destroyed_all(role("__player__")), "All ships lost.", False)
game_end_condition_add(destroyed_any(home_id), "Base destroyed.", False)
# kick off, then loop
task_schedule(spawn_wave)
--- game_loop await delay_sim(120) task_schedule(spawn_wave) -> game_loop
6. helper labels
=== spawn_wave pos = Vec3.rand_in_sphere(3000, 6000, False, True) prefab_spawn(prefab_fleet_raider, {"race":"skaraan", "fleet_difficulty": int(DIFFICULTY-1), "START_X": pos.x, "START_Y": pos.y, "START_Z": pos.z}) ->END ```
What each part does
sharedstate is set once (by the server) and visible to every task — see gotchas.- Lifeforms, sides & faces — Sides, lifeforms & faces.
@media/labels execute to set music/skybox, so keep them below your shared data. See the media API.@map/metadataProperties become variables (PLAYER_COUNT,DIFFICULTY,GAME_TIME_LIMIT) driven by the setup-screen widgets. Defaults come fromsettings.yaml.- Building the world — World building and Tile maps.
- Player ships & consoles — Player ships & consoles.
- The game loop is usually an inline
---label that waits, spawns, and jumps back until a timer or condition ends it. Objectives and end conditions. - Telling the story — Story & NPC messages.
Bigger missions: split into modules
Large missions keep story.mast almost empty and put maps in a module folder
(maps/__init__.mast importing the rest) — Drop_Off and MiningDays do this.
See Making add-ons for the module mechanics.
Real references: Secret Meeting, WalkTheLine, LegendaryMissions.