---
title: "Timers countdowns cooldowns and enemy waves"
description: "A round clock ticks down, enemies arrive in waves, or an ability recharges after a cooldown. `TimerComponent` is a real atomic (pack `timer.v1`); its runtime fires `RuntimeTimerFired` on a server-owned interval. There is no `RespawnTimerComponent` or `SpawnPoolComponent`; those are this same pattern."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/timers-countdowns-and-waves/
---

# Timers countdowns cooldowns and enemy waves

## When to use

A round clock ticks down, enemies arrive in waves, or an ability recharges after
a cooldown. `TimerComponent` is a real atomic (pack `timer.v1`); its runtime
fires `RuntimeTimerFired` on a server-owned interval. There is no
`RespawnTimerComponent` or `SpawnPoolComponent`; those are this same pattern.

## The recipe

Author with `project_apply_script_semantic_patch` using the `ai_safe` op pack.

1. Attach the clock: `project_add_component` a `TimerComponent` with
   `{ "timerKey":"round", "intervalSeconds": 1, "repeating": true,
   "maxFirings": 0 }` (0 means unlimited; `intervalSeconds` minimum is 0.01).
2. Subscribe: `addTimerHandler` `{ "handlerKey":"onRoundTick", "timerKey":"round" }`.
   The runtime delivers the `timerKey` and the firing iteration to the handler.
3. Countdown: seed the remaining seconds with `writeTypedStateValue`
   `{ "field":"timeLeft", "valueType":"number", "value": 60 }`, then each fire
   `incrementNumericState` `{ "field":"timeLeft", "by": -1 }` and
   `wireHudStateBinding` it to the HUD. When it reaches 0, `declareWinCondition`
   or transition the match phase (see match-flow-and-scoreboards).
4. Enemy waves: use a slower timer (for example `intervalSeconds: 20`). Each
   fire, `spawnEntityFromTemplate` `{ "prefabKey": <enemy> }` a batch at spawn
   points, and `incrementNumericState` a `wave` counter to scale the next batch.
5. Cooldown: a one-shot delay is a `TimerComponent` with `repeating: false` and
   `maxFirings: 1`, or a last-used timestamp compared against the current tick;
   set a `ready` boolean with `setBooleanState` when the cooldown elapses.

## Pitfalls

- `intervalSeconds` has a floor of 0.01; do not expect sub-10ms timers.
- The timer is server-owned and authoritative; every client sees the same fires.
  Do not run a parallel client clock.
- Keep the countdown value and the wave counter in durable state, not a local
  variable, so they survive a reload and read consistently on the HUD.
- A repeating timer with `maxFirings: 0` never stops on its own; disable it
  (patch `enabled: false`) when the round ends or waves are cleared, or it keeps
  spawning.
- Give each timer a distinct `timerKey`; one entity can carry several timers and
  `addTimerHandler` routes by that key.

## Verify

- `simulation_run` (`qa.run.start`) to watch the countdown decrement to zero and
  a wave spawn on each interval.
- `project_get_graph_snapshot` to confirm the `TimerComponent` and the timer
  handler are on the entity.
