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: |
required |
Returns:
| Type | Description |
|---|---|
|
Label | None: The map to start, 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 |
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: |
required |
filename
|
str | None
|
override the store path (tests). |
None
|
Returns:
| Type | Description |
|---|---|
|
str|None: the code stored, or |
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 |
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 |
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 ( |
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 |
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 |
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 awaitsdelay_simwould never advance otherwise.task_schedule(map, defer=True)- deferred so consoles repaint before the map body's first tick.GAME_STARTEDand thegame_startedsignal - 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 |
required |
Returns:
| Type | Description |
|---|---|
|
Label | None: The map that was started, or |
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 |
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 |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
|
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 |
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).