Skip to content

Maps system

Manage @map labels that define discoverable map waypoints and regions.

Overview

Map labels are declared in MAST with the @map/path/name "Display" syntax. They appear as markers on the navigation or sector map and can be discovered, hidden, or updated by the mission. The maps module provides the procedural API for interacting with registered map labels at runtime.

map_get retrieves a registered map label by path. map_schedule runs the label as a task (typically to update the map marker's state). Use the //focus/grid route to react when players click on map markers.

Quick example

@map/waypoints/alpha "Waypoint Alpha"
@map/waypoints/beta "Waypoint Beta"

== reveal_waypoints ==
map_schedule("waypoints/alpha")
map_schedule("waypoints/beta")
== waypoints/alpha ==
set_map_pos(5000, 0, 3000)
from sbs_utils.procedural.maps import map_get, map_schedule

# Activate a map marker
map_schedule("waypoints/alpha")

# Get the label object
label = map_get("waypoints/alpha")

API

game_code_decode(code)

Apply a game code: set its shared variables and return the matching map.

Resolves the map by path first; if no current map matches, nothing is changed and None is returned (so a code from a different mission is a safe no-op). Otherwise each VAR=value is written to the shared scope, coerced to the live variable's type, and the map Label is returned. The caller starts the map (e.g. task_schedule(map)).

Parameters:

Name Type Description Default
code str

A code previously produced by :func:game_code_encode.

required

Returns:

Type Description

Label | None: The map to start, or None if the code is empty or

names a map not present in the current story.

game_code_encode(map, with_loadout=False)

Build a shareable, human-readable game code for a map.

Format: "<map_path>;VAR=value;VAR=value;..." where the vars are the map's :func:game_code_vars read from the shared scope. Reproduces the map plus its seed and key option values so another host can recreate the same game.

Parameters:

Name Type Description Default
map Label

The map label whose current option values to encode.

required
with_loadout bool

also carry the crew's ship names and hulls. Pass True when SAVING (a named preset, the last-used slot); leave False for a code meant to be shared, which should not carry another crew's ship names.

False

Returns:

Name Type Description
str

The game code, or "" if map is None.

game_code_label(code)

A short, human-readable label for a game code (for preset menus).

e.g. "siege;PLAYER_COUNT=2;DIFFICULTY=5;seed_value=4242" -> "P2 D5 seed4242". Falls back to the raw code if it has no value pairs.

SHIP_LOADOUT is summarized as a ship count rather than spelled out: its value is every ship's name and hull joined together, which is longer than the rest of the label put together and unreadable in a dropdown.

game_code_last_apply(map, filename=None)

Apply this mission's remembered setup for map, if there is one.

Safe to call unconditionally: it does nothing when nothing was remembered, when the setting that writes them was never on, or when the remembered code names a map this story does not have.

Parameters:

Name Type Description Default
map Label | str

the map label (or its path) about to be shown/started.

required
filename str | None

override the store path (tests).

None

Returns:

Name Type Description
bool

whether a remembered setup was applied.

game_code_last_code(map_path, filename=None)

The last-used code for one map, or "" when there is none.

game_code_last_save(code, filename=None)

Remember code as this mission's last-used setup, keyed by its map.

Called when a game STARTS, not when it ends: that records what was actually played, and it survives a crash or a quit that never reaches a results screen.

Parameters:

Name Type Description Default
code str

a code from :func:game_code_encode.

required
filename str | None

override the store path (tests).

None

Returns:

Type Description

str|None: the code stored, or None if it was empty.

game_code_presets_for_map(map_path, filename=None)

Return one map's saved presets as [{"name", "code"}, ...] (newest last).

game_code_presets_load(filename=None)

Load the saved game-code presets, a dict of {map_path: [entry, ...]}.

Each entry is a {"name": str, "code": str} dict. Legacy files stored a bare code string per entry; those still load (see :func:_preset_normalize). Returns an empty dict if the file is missing or malformed. Presets are kept separated by map so each map only shows its own.

game_code_presets_save_code(code, name=None, filename=None)

Save a game code as a named preset under its map, de-duplicating on code.

The map is taken from the code's first token, so presets land in the right per-map bucket. name defaults to "Preset N" (N = the next slot for that map). Re-saving an identical code is a no-op (keeps the first name). Returns the code saved, or None if code is empty.

game_code_vars(map, with_loadout=False)

Return the var names that make up a map's game code, in order.

By default this is every property var the map exposes - the options panel, exactly as a person set it. A map can pin the set explicitly with a GameCode metadata list (GameCode: [PLAYER_COUNT, DIFFICULTY, ...]), which is then used verbatim.

Parameters:

Name Type Description Default
map Label

The map label object.

required
with_loadout bool

also carry SHIP_LOADOUT - the crew's ship names and hulls. True when SAVING a setup (a preset, or the last-used slot), False when producing a code to share. Appended even to an explicit GameCode list, so a map that pins its options still saves its ships.

False

Returns:

Type Description

list[str]: Ordered var names included in the code.

label_find_by_spec(labels, spec)

Find one label from a loose, human-typed spec - the rule maps_find documents, factored out so every other "name a label on a command line or in a dropdown" lookup resolves IDENTICALLY.

Shared with media_find (skybox and music), which is why it lives here rather than inside maps_find: two copies of a fuzzy matcher drift, and the day they disagree is the day map=siege and MUSIC_SELECT=siege mean different things.

Parameters:

Name Type Description Default
labels list

anything with .path and (optionally) .display_name.

required
spec

an index, a path, a display name, or a unique substring of either.

required

Returns:

Type Description

The label, or None if nothing matched or the spec was AMBIGUOUS.

map_apply_defaults(map)

Apply a map's Defaults: metadata as SET-IF-ABSENT shared variables.

For each VAR: value in the map's Defaults block, set the shared variable to value ONLY if it is not already set - so a value seeded by settings.yaml, the story, or a loaded game code always wins (the same semantics as default shared). This lets a map give its own Properties controls a starting value without promoting a map-local setting (e.g. a JOBS_SELECT only this map uses) to global settings or scattering default through the map body.

The map's Properties panel renders (and binds its controls to SHARED scope) BEFORE the map body runs, so this must be applied at BOTH moments: when the panel is presented, AND again whenever the map is started as a task (AUTO_START and a headless --map runner start the map task without ever presenting the panel). It is idempotent - a map with no Defaults is a no-op, and an already-set var is left untouched - so calling it at both points is safe.

Parameters:

Name Type Description Default
map Label

The map label object (None is a no-op).

required

map_get_defaults(map)

Return the Defaults metadata dict of a map label (fallback defaults).

A sibling of Properties in a map's metadata: block: a flat {VAR: value} map of starting values for the variables the map's Properties controls bind to (and any other var the map wants defaulted). Read the same way as Properties / GameCode.

Parameters:

Name Type Description Default
map Label

The map label object.

required

Returns:

Type Description

dict | None: The defaults dict, or None if the map declares none.

map_get_properties(map)

Return the Properties inventory value of a map label.

Checks "Properties" first, then "properties" as a fallback.

Parameters:

Name Type Description Default
map Label

The map label object.

required

Returns:

Name Type Description
any

The properties value, or None if not set.

map_start(map)

Start a map: apply its defaults, resume the sim, schedule it, announce it.

The canonical launch sequence. It existed twice before this - in LegendaryMissions' server console and in the headless runner - and the two had DRIFTED on things that matter: whether the sim resumes before or after scheduling, and task_schedule versus task_schedule_server. One implementation ends that.

What it does, in order:

  • map_apply_defaults - set-if-absent shared vars, so a value from settings.yaml, the story or a loaded game code still wins. Idempotent, and applied here as well as at panel-render time because a map can be started without a panel ever showing.
  • sim_resume() - the lobby sim is paused; a map body that awaits delay_sim would never advance otherwise.
  • task_schedule(map, defer=True) - deferred so consoles repaint before the map body's first tick.
  • GAME_STARTED and the game_started signal - the contract missions gate on.

What it deliberately does NOT do, because these are LegendaryMissions' own contract and are meaningless (or wrong) in a mission that does not load it: the reconcile_player_roster signal, sbs.set_beam_damages, the GAME_TIME_LIMIT timer, music selection, and the client/server GUI reroutes. LM does those around its own call to this.

Parameters:

Name Type Description Default
map Label | None

The @map label to start. None is a no-op, matching map_apply_defaults.

required

Returns:

Type Description

Label | None: The map that was started, or None.

maps_find(spec)

Find one @map label from a loose, human-typed spec.

Built for launch arguments - map=test_shipdata_probe on the engine command line, or --map 0 under cosmos_dev - where the value is typed by a person or pasted from a script and should not have to be exact.

Accepts, in order of preference so an exact hit always wins over a fuzzy one:

  • an integer, or a string of digits - an index into the map list
  • the label path, case-insensitively
  • the display_name, case-insensitively
  • a unique case-insensitive substring of either; AMBIGUOUS matches return None rather than picking one, because silently starting the wrong map is worse than starting none and saying so.

Returns:

Type Description

Label | None: the map, or None if nothing matched or the spec was ambiguous.

maps_get_init()

Return the __overview__ map label from the current MAST story, or None.

Returns:

Type Description

Label | None: The overview map label, or None if not defined.

maps_get_list(include_hidden=False)

Return the @map labels defined in the current page's story.

If only an __overview__ label exists, it is returned as a single-item list. If no map labels are found at all, returns a placeholder list with a "No maps found" entry.

Parameters:

Name Type Description Default
include_hidden bool

When True, return conditional maps whose if is currently false as well. Callers that are RESOLVING A KNOWN MAP rather than offering a menu want this - game_code_decode looks a map up by path, and a saved code should not stop resolving because a condition happens to be false right now.

False

Returns:

Name Type Description
list

@map Label objects, or a fallback list if none are defined.

player_loadout_active()

Decode the live SHIP_LOADOUT shared var into a slot list ([] if unset).

player_loadout_apply_to_ships(ships=None)

Write the pending SHIP_LOADOUT onto the live player ships, then CLEAR it.

This is what makes a RESTORED setup lose to a person. A restored loadout otherwise sits in SHIP_LOADOUT until the game starts, and the roster reconcile applies it over whatever is on the ships at that moment - which includes the name and hull helm just chose in the lobby. Last session's ships would silently overwrite this session's choice, and the person who made it gets no hint that it happened.

Applying it up front inverts that: the restored names and hulls are what helm SEES in the picker, and anything helm changes from there is simply the newer value. Clearing the var is the other half - it leaves the reconcile nothing to override with.

Ships are matched to slots in id order, the same order :func:player_loadout_from_ships captured them in.

No player ships yet means the restore is too early to land on anything, so the var is left ALONE for the reconcile to apply at start - which is correct, because nobody has had the chance to choose anything either.

Parameters:

Name Type Description Default
ships list | None

the player ships, or None to use the default_player_ship role.

None

Returns:

Name Type Description
int

how many slots were applied.

player_loadout_capture(ships)

Capture ships into the shared SHIP_LOADOUT var; return the token.

Call right before encoding a game code so the code carries the current crew's hulls + names.

player_loadout_decode(token)

Inverse of :func:player_loadout_encode. Empty/None -> [].

player_loadout_encode(slots)

Pack [{"name","hull"}, ...] into one game-code-safe token ("" for none).

player_loadout_from_ships(ships)

Build a loadout token from ship objects, reading .name and .art_id.

ships is sorted by id first so the slot order is stable and matches the rehydrate side (spawn_players walks the player ships in id order too).