Skip to content

Timers and counters

Delay execution and count events with awaitable promises.

Overview

Timers let MAST scripts wait for a real-time or simulation-time duration before continuing. Two time bases are available:

  • Real time (delay) — wall-clock seconds; unaffected by simulation speed.
  • Simulation time (delay_sim) — scaled by the engine's simulation clock.

Counters (count_goal) are awaitable promises that resolve once a named counter reaches a target value. Increment the counter with increment_count; reset it with reset_count.

All timer and counter functions are designed to be used with await in MAST:

await delay_sim(seconds=5)
await count_goal("enemies_killed", 10)

Quick example

== timed_event ==
    log("Reactor will detonate in 30 seconds!")
    await delay_sim(seconds=30)
    log("The reactor has detonated!")
    explode_player_ship(station_id)
    ->END

== count_kills ==
    await count_goal("kills", 5)
    log("You have destroyed 5 enemies.")
    ->END

//signal/enemy_destroyed
    increment_count("kills")
from sbs_utils.procedural.timers import delay_sim, delay, count_goal, increment_count, reset_count

# Wait 10 simulation seconds before continuing
await delay_sim(seconds=10)

# Wait 5 real seconds
await delay(seconds=5)

# Count-based gate
await count_goal("patrols_complete", 3)

# Increment from signal handlers or events
increment_count("patrols_complete")
reset_count("patrols_complete")

Signals instead of polling

A timer is one value in an agent's inventory and nothing runs on its behalf, which is why a mission can hold hundreds of them for free — but it also means a script has to ask whether one is finished. Pass signal to set_timer and the library emits that signal once, when it expires, so a route can react instead:

== start_repairs ==
    set_timer(SHIP_ID, "repair", seconds=30, signal="repair_done")
    ->END

//shared/signal/repair_done
    repair_ship(TIMER_AGENT_ID)
    ->END

set_interval is the repeating sibling — it emits every so often until it is cleared:

== begin_patrol ==
    set_interval(SHIP_ID, "patrol", "patrol_beat", seconds=30)
    ->END

//shared/signal/patrol_beat
    pick_new_patrol_point(TIMER_AGENT_ID)
    ->END

== stand_down ==
    clear_interval(SHIP_ID, "patrol")
    ->END

Every emit carries three variables:

Variable Meaning
TIMER_AGENT_ID The agent the timer or interval is on
TIMER_NAME The timer or interval name
TIMER_COUNT Which beat this is — always 1 for a set_timer completion

Use //shared/signal for anything that acts

A plain //signal/<name> route runs once per connected console, so a five-console bridge repairs the ship five times — and an interval does it five times a beat. Only per-console display belongs in //signal. See Signals.

Why it is worth using. The alternative — a watcher task per timer — costs a task resumption every tick, forever, for each one. An armed timer knows its deadline as a number, so the library keeps only the earliest and a tick costs a single comparison. A mission that arms none schedules nothing at all.

What does and does not fire

  • Nothing fires early. The signal lands on the same tick is_timer_finished starts answering True.
  • The timer is untouched. is_timer_set_and_finished, get_time_remaining and format_time_remaining all behave exactly as they do without a signal, so a countdown widget and a route can share one timer.
  • Cleared, re-set without a signal, or its agent deleted → no signal. Re-setting with signal again re-arms it.
  • timer_add_time moves the signal with the deadline — extend a repair and the completion follows; shorten it past zero and it fires at once.
  • A paused sim does not expire timers, and interval beats missed while paused are skipped rather than delivered in a burst on resume.
  • Beats do not drift. Each one is scheduled from the interval's start, not from when the last one happened to be noticed.

Real time vs simulation time

Function Time base Pauses with sim?
delay(seconds) Wall clock No
delay_sim(seconds) Simulation clock Yes

API

clear_counter(id_or_obj, name)

Remove a named counter from an agent.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Counter name.

required
Example

clear_counter(SHIP_ID, "docked")

clear_interval(id_or_obj, name)

Stop an interval started by set_interval.

Identical to clear_counter - an interval IS a counter - and named for symmetry so a script that starts one can stop it by the same word.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Interval name.

required
Example

clear_interval(SHIP_ID, "patrol")

clear_timer(id_or_obj, name)

Clear a named timer so it is no longer set.

After clearing, is_timer_set returns False and is_timer_finished returns True.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required
Example

clear_timer(SHIP_ID, "cooldown")

delay_app(seconds=0, minutes=0)

Suspend the current task for a duration measured in real application time.

Application time is not affected by game pause.

Parameters:

Name Type Description Default
seconds int

Duration in seconds. Defaults to 0.

0
minutes int

Additional duration in minutes. Defaults to 0.

0

Returns:

Name Type Description
Delay Delay

A promise that resolves when the time has elapsed.

Example

await delay_app(seconds=3) "Three real seconds have passed (even if paused)."

delay_sim(seconds=0, minutes=0)

Suspend the current task for a duration measured in simulation time.

Simulation time can be paused (e.g. when the game is paused).

Parameters:

Name Type Description Default
seconds int

Duration in seconds. Defaults to 0.

0
minutes int

Additional duration in minutes. Defaults to 0.

0

Returns:

Name Type Description
Delay Delay

A promise that resolves when the time has elapsed.

Example

await delay_sim(seconds=5) "Five simulation seconds have passed."

delay_test(seconds=0, minutes=0)

Suspend a task for use in unit tests (not real-time).

Uses DelayForTests which counts poll iterations rather than wall or sim time, so tests run fast without sleeping.

Parameters:

Name Type Description Default
seconds int

Simulated duration in seconds. Defaults to 0.

0
minutes int

Additional simulated minutes. Defaults to 0.

0

Returns:

Name Type Description
DelayForTests

A promise that resolves after enough poll ticks.

format_time_remaining(id_or_obj, name)

Return the time remaining on a timer as a M:SS string.

Returns an empty string when the timer has expired or is not set.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required

Returns:

Name Type Description
str

Formatted remaining time, e.g. "1:30", or "" if expired.

Example

gui_text("Time: {format_time_remaining(SHIP_ID, 'mission')}")

get_counter_elapsed_seconds(id_or_obj, name, default_value=None)

Return the number of seconds elapsed since a counter was started.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Counter name.

required
default_value optional

Value returned if the counter was never started. Defaults to None.

None

Returns:

Type Description

float | None: Seconds elapsed, or default_value if not set.

Example

elapsed = get_counter_elapsed_seconds(SHIP_ID, "docked", 0) if elapsed > 60: "Docking complete."

get_time_remaining(id_or_obj, name)

Return the number of whole seconds remaining on a timer.

Returns 0 when the timer has expired or is not set.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required

Returns:

Name Type Description
int

Seconds remaining, or 0 if expired or not set.

Example

secs = get_time_remaining(SHIP_ID, "mission") if secs < 60: "Less than a minute remaining!"

is_timer_finished(id_or_obj, name)

Return whether a timer has expired. Returns True if the timer is not set.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required

Returns:

Name Type Description
bool

True if the timer has expired or was never set.

Example

if is_timer_finished(SHIP_ID, "repair"): "Repair bay ready."

is_timer_set(id_or_obj, name)

Return whether a named timer exists on an agent.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required

Returns:

Name Type Description
bool

True if the timer has been set (even if already expired).

Example

if not is_timer_set(SHIP_ID, "cooldown"): set_timer(SHIP_ID, "cooldown", seconds=10)

is_timer_set_and_finished(id_or_obj, name)

Return whether a timer was explicitly set and has since expired.

Unlike is_timer_finished, returns False when the timer was never set. Use this to distinguish "timer done" from "timer never started".

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required

Returns:

Name Type Description
bool

True only if the timer was set and has now expired.

Example

if is_timer_set_and_finished(SHIP_ID, "cooldown"): clear_timer(SHIP_ID, "cooldown") "Weapons ready!"

set_interval(id_or_obj, name, signal, seconds=0, minutes=0)

Emit a signal on an agent every seconds, until it is cleared.

The repeating sibling of set_timer. Beats are scheduled from the start, not from when the last one fired, so the period does not drift. Each emit carries TIMER_AGENT_ID, TIMER_NAME and TIMER_COUNT (1 for the first beat). Handle it with //shared/signal/<name> for anything with a side effect - a plain //signal/<name> runs once per console, so a beat becomes one per console per beat (see SIGNAL_ROUTING.md).

Runs on a counter, so get_counter_elapsed_seconds(id, name) reads the time since it started and clear_interval (or clear_counter) stops it. Stops on its own if the agent is deleted. A paused sim does not advance the beat, and beats missed while paused are skipped rather than caught up on.

Parameters:

Name Type Description Default
id_or_obj Agent | int

The agent to run the interval on.

required
name str

Unique interval name for this agent.

required
signal str

Signal to emit on every beat.

required
seconds int

Seconds between beats. Defaults to 0.

0
minutes int

Additional minutes between beats. Defaults to 0.

0
Example

set_interval(SHIP_ID, "patrol", "patrol_beat", seconds=30)

//shared/signal/patrol_beat runs on the server every 30 seconds

clear_interval(SHIP_ID, "patrol")

set_timer(id_or_obj, name, seconds=0, minutes=0, signal=None)

Start a named countdown timer on an agent.

Records the expiry tick in the agent's inventory. Use is_timer_finished or get_time_remaining to check progress.

Pass signal to have the library emit that signal once, when the timer expires, instead of polling for it. The emit carries TIMER_AGENT_ID and TIMER_NAME. It is purely additive - the timer is still an ordinary timer afterwards, so is_timer_set_and_finished and format_time_remaining behave exactly as they do without it. Handle it with //shared/signal/<name> for anything with a side effect; a plain //signal/<name> runs once per console (see SIGNAL_ROUTING.md).

No signal is emitted if the timer is cleared, re-set without signal, or its agent is deleted before it expires. A paused sim does not advance the timer, so it does not expire while paused.

Parameters:

Name Type Description Default
id_or_obj Agent | int

The agent to set the timer on.

required
name str

Unique timer name for this agent.

required
seconds int

Duration in seconds. Defaults to 0.

0
minutes int

Additional duration in minutes. Defaults to 0.

0
signal str

Signal to emit once when the timer expires. Defaults to None (no signal - poll it instead).

None
Example

set_timer(SHIP_ID, "repair", seconds=30) if is_timer_finished(SHIP_ID, "repair"): "Repairs complete!"

set_timer(SHIP_ID, "repair", seconds=30, signal="repair_done")

//shared/signal/repair_done runs on the server when it expires

start_counter(id_or_obj, name)

Record the current sim tick as the start of a named counter.

Use get_counter_elapsed_seconds to read how many seconds have passed since the counter was started. Use set_interval for a counter that emits a signal every so often instead of being read.

Restarting a counter that set_interval armed restarts its beat too - the next one lands a full interval from now.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Counter name.

required
Example

start_counter(SHIP_ID, "docked")

later...

secs = get_counter_elapsed_seconds(SHIP_ID, "docked")

timeout(seconds=0, minutes=0)

Create a timeout promise measured in real application time.

Identical to delay_app. Typically passed to await comms(timeout=…) or similar constructs that accept a timeout promise.

Parameters:

Name Type Description Default
seconds int

Duration in seconds. Defaults to 0.

0
minutes int

Additional duration in minutes. Defaults to 0.

0

Returns:

Name Type Description
Delay Delay

A promise that resolves when the time has elapsed.

Example

await comms(timeout=timeout(seconds=30))

timeout_sim(seconds=0, minutes=0)

Create a timeout promise measured in simulation time.

Identical to delay_sim. Simulation time can be paused.

Parameters:

Name Type Description Default
seconds int

Duration in seconds. Defaults to 0.

0
minutes int

Additional duration in minutes. Defaults to 0.

0

Returns:

Name Type Description
Delay Delay

A promise that resolves when the time has elapsed.

Example

await comms(timeout=timeout_sim(minutes=2))

timer_add_time(id_or_obj, name, seconds=0, minutes=0)

Add (or subtract) time on a timer that is currently running.

A no-op when the timer was never set or has already expired - use set_timer to start a fresh one. Times may be negative, which shortens the timer and can expire it outright.

Parameters:

Name Type Description Default
id_or_obj Agent | int

Agent ID or object.

required
name str

Timer name.

required
seconds int

Seconds to add. Negative shortens. Defaults to 0.

0
minutes int

Additional minutes to add. Defaults to 0.

0

Returns:

Name Type Description
bool

True if a running timer was adjusted.

Example

set_timer(SHIP_ID, "repair", seconds=30) timer_add_time(SHIP_ID, "repair", seconds=15) # damaged mid-repair

timer_signals_clear()

Drop every armed timer/counter signal (mission reset).

timer_signals_count()

How many timers/counters are armed to emit a signal (reset audit).