---
title: "Match flow phases scoreboards and win conditions"
description: "A match moves through lobby, playing, and game-over, tracks scores, and ends when someone wins. This is the `match.v1` MatchHost plus the `lifecycle.v1` events. Scores and objectives are warehouse-backed (`objective.v1`), so they persist and replicate consistently."
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/match-flow-and-scoreboards/
---

# Match flow phases scoreboards and win conditions

## When to use

A match moves through lobby, playing, and game-over, tracks scores, and ends when
someone wins. This is the `match.v1` MatchHost plus the `lifecycle.v1` events.
Scores and objectives are warehouse-backed (`objective.v1`), so they persist and
replicate consistently.

## The recipe

Author with `project_apply_script_semantic_patch` using the `ai_safe` op pack;
the MatchHost calls live inside whole-body GessaScript
(`replaceScriptFromGessaScript`).

1. Phases: call `ctx.match.setPhase` with a phase key (for example `lobby`,
   `playing`, `ended`). React to phase changes in handlers; drive the transition
   from a ready check, a timer (see timers-countdowns-and-waves), or a win.
2. Scoreboard: on a scoring event, `ctx.match.addScore` for a subject key (the
   player id, or a team key for team games) and read standings with
   `ctx.match.getScore`. Mirror the number to the HUD with `wireHudStateBinding`.
3. Objectives: `ctx.match.patchObjective` to update a named goal (captures,
   flags taken, waves cleared) that the UI and win check read.
4. Win by points: when a `getScore` crosses the target, `ctx.match.setPhase`
   `ended` and record the winner. Win by elimination: `findEntitiesByTag` the
   living players or targets and `declareWinCondition` (or `compareRemainingCount`
   then `setPhase`) when the remaining count hits the threshold.
5. Join / leave: handle `event.player_joined`, `event.player_left`, and
   `event.match_ended` in a GessaScript body to seat or drop players, pause a
   round that lost quorum, and finalize the scoreboard at the end.

## Pitfalls

- Phase transitions and score writes are authority-side; do not also flip the
  phase or add score on a client. `declareWinCondition` already carries
  `writeState`/`emitEvent`/`requiresAuthority`.
- The four `ai_safe` handler shortcuts cover action, trigger, timer, tick, and
  custom events. Lifecycle handlers (`player_joined`, `player_left`,
  `match_ended`, phase enter/exit) are authored through whole-body GessaScript,
  not a dedicated shortcut op.
- Scores live in the warehouse via the MatchHost; do not keep a parallel local
  tally, it will drift from what other clients see.
- Use a stable subject key for score. A per-session id resets each match; use the
  durable player id for a persistent leaderboard.

## Verify

- `simulation_run` (`qa.run.start`) to prove the match advances through its
  phases, scores accrue, and the win phase fires at the threshold.
- `project_get_graph_snapshot` to confirm the scoring and lifecycle handlers are
  on the graph.
