# Console

> How the Arena games console derives its KPIs, live sessions board, server fleet view, spectator studio, and per-workspace module visibility.

The games console (Arena) is the operator-facing surface for
ClutchCall game sessions. It sits alongside the SDK: the SDK creates
rooms, channels and ticks; the console shows you what those produced —
which matches are live, which servers carry them, what latency players
see, and who is watching.

This page documents what each screen reads, how every displayed number
is derived, and which controls are wired versus not yet enabled.

## What's in the console

| Screen | Nav key | Purpose |
| ------ | ------- | ------- |
| Overview | `overview` | Landing page. Fleet-wide KPIs and next steps. |
| Live sessions board | `sessions` | Grid of currently active matches. |
| Match detail | `match` | A single match. |
| Server fleet | `fleet` | Instances aggregated by region. |
| Spectator studio | `studio` | Broadcast side of a match. |
| Audit log | `audit` | Operations. |
| Alerts | `alerts` | Operations. |
| Billing | `billing` | Operations. |
| Modules | `modules` | Per-workspace visibility override. Always shown. |

The Overview, Live sessions board and Server fleet screens all poll on a
30 second interval while open.

## Read the overview KPIs

The Overview reads a single procedure, `fleetGames.kpiStrip`, scoped to
the current org. It is the same procedure the Live sessions board uses
for its KPI strip, so the two screens cannot disagree about fleet-wide
figures.

| Tile | Field | Notes |
| ---- | ----- | ----- |
| Active matches | `active_matches` | Shows an `ok` severity when greater than zero. |
| Players in game | `total_players` | |
| Global P50 | `global_p50_ms` | Rendered as `—` when zero. |
| Server health | `fleet_health_pct` | `ok` at 90% or above, `warn` when above zero but below 90, no severity when zero. |

While the query is in flight every tile renders `…`.

### Why Global P50 can be blank

`global_p50_ms` prefers fresh ping samples and falls back to the stored
per-match figure. When nothing has been measured, there is no sample to
report — and zero milliseconds would be a false claim for a network
product. The Overview therefore renders `—` rather than `0`. The same
applies to Server health: a zero means "not measured", not "no healthy
servers", so it too renders `—` with no severity colour.

Note that the Live sessions board's KPI strip does *not* apply this
blanking; it prints the raw numeric value. If you see `0 ms` there and
`—` on Overview, that is the same underlying value presented two ways.

The Overview also carries a "Get a match running" panel linking to the
quickstart, the servers screen, the sessions board and the studio. The
sessions link's subtitle reports `total_matches` when it is above zero,
and otherwise says nothing has run yet.

## Read the live sessions board

The board's grid is populated by `fleetGames.matchesList`, called with
`status: 'active'`, `page: 1` and `perPage: 120`. That is a single
capped page — the grid shows at most 120 active matches regardless of
how many are running.

The KPI strip above the grid is a different source. These tiles come
from `fleetGames.kpiStrip`, a fleet-wide aggregate:

| Tile | Source |
| ---- | ------ |
| Active matches | `active_matches`, with `total_matches` as the denominator |
| Players in session | `total_players`, with a denominator summed from the loaded rows' `cap` |
| Global P50 RTT | `global_p50_ms` |
| Server fleet health | `fleet_health_pct` |
| Spectator viewers | summed from the loaded rows' `spectators` |

**The strip and the grid can disagree.** Active matches, players and
P50 are fleet-wide aggregates; the capacity denominator, the spectator
total and the grid itself are derived from the capped page of rows. If
more than 120 matches are active, the strip's counts will exceed what
the grid can account for. Treat the strip as the fleet number and the
grid as a sample of it.

When `kpiStrip` returns nothing for a field, the board falls back to
computing it from the loaded rows.

### Per-row fields

| Field | Meaning |
| ----- | ------- |
| `id` | Match id, shown monospace on the card. |
| `mode` | Game mode. |
| `region` | Region the match is running in. |
| `srv` | Server carrying the match. |
| `status` | Drives the status pip and the card's severity strip. |
| `players` / `cap` | Occupancy. Renders `—` when `players` is absent. |
| `spectators` | Snapshot of the match broadcast's `viewer_count`. Zero when the match has no broadcast. |
| `p50` / `p95` | Round-trip latency percentiles in milliseconds. |
| `player_tier` (or `tier`) | Used by the tier filter. |
| `runtime` | Optional. Rendered in the card's map thumbnail when present. |

The map label on each card is derived client-side from the mode string
— `BR` renders "atoll", `TDM` renders "warehouse", anything else
renders "arena". It is a label, not a reported field.

## How match status and fleet health are derived

Each match card carries a coloured severity strip and a pulsing pip.
Both come straight from the row's `status` field:

| `status` | Severity |
| -------- | -------- |
| `ok` | ok |
| `warn` | warn |
| anything else | bad |

The console does not compute this from latency or occupancy — it
renders what the row reports.

**Server fleet health** is the `fleet_health_pct` figure from
`kpiStrip`. On the Overview, 90% is the boundary between an `ok` tile
and a `warn` tile. That boundary is a display threshold in the console;
it does not gate anything server-side.

The P50 and P95 figures on each card are colour-banded independently of
the match status:

| Metric | ok | warn | bad |
| ------ | -- | ---- | --- |
| P50 | under 50 ms | 50–79 ms | 80 ms and above |
| P95 | under 80 ms | 80–149 ms | 150 ms and above |

The board's Global P50 RTT tile uses the same 50 ms boundary for its
`ok`/`warn` severity.

## Where the latency percentiles are sampled

P50 and P95 are round-trip times measured per match, reported on the
match row and surfaced on the card. The fleet-wide `global_p50_ms`
prefers fresh ping samples and falls back to the stored per-match
figure — so a Global P50 that lags a card's P50 means the global figure
is currently coming from stored values rather than live pings.

Spectator counts are not a live tally. Each row's `spectators` is a
snapshot of the match broadcast's `viewer_count` at the time the row was
produced, and is zero when the match has no broadcast attached.

## Filter, sort and export the match list

Three filter chips sit in the page header: **Region**, **Mode** and
**Player tier**. Each chip cycles through `all` plus the distinct values
present in the currently loaded rows — so the options reflect the capped
page, not the whole fleet. A chip with no values available is disabled
and explains why on hover. Changing any filter resets to page 1.

Filtering happens entirely client-side on the already-fetched rows.

The list is **always sorted by spectator count, descending**. There is
no sort control. The layout is a grid; a grid/list toggle previously
shipped inert and has been removed until a list view exists.

Results are paged at 48 cards per page, over the filtered and sorted
set.

**Export** downloads `live-sessions.csv` covering the full filtered and
sorted set — not just the visible page — with these columns:

`id`, `mode`, `region`, `server`, `status`, `players`, `cap`,
`spectators`, `p50_ms`, `p95_ms`

The button is disabled when the filters match nothing.

When nothing matches, the board shows an empty state pointing you at
starting a session from your netcode SDK integration or widening the
filters.

## Read the server fleet screen

The Server fleet screen reads `fleetGames.serverFleet`, which returns a
`servers` array of per-instance rows. The screen is an admin surface.

Two filter chips — **Provider** and **Pool** — cycle through the
distinct `provider` and `pool` (or `pool_name`) values present in the
returned rows, and filter client-side.

Everything below the filters is then computed by grouping the surviving
rows by `region_id`.

## Regions, pools and providers

| Row field | Used for |
| --------- | -------- |
| `region_id` | Grouping key for the region cards and map pins. |
| `provider` | Provider filter chip. |
| `pool` / `pool_name` | Pool filter chip; listed on the region card. |
| `utilization` | Load, as a 0–1 fraction. |
| `capacity_matches` | Contributes to the region's capacity denominator. |
| `players` / `current_players` / `player_count` | Player total for the region. |
| `autoscale_enabled` | Autoscale indicator on the region card. |

Region display names and map-pin coordinates come from a **static table
in the console**, keyed by `region_id`:

`AP-NE-1` (Tokyo), `AP-SE-1` (Singapore), `AP-S-1` (Mumbai), `EU-W-1`
(Frankfurt), `EU-W-2` (Paris), `US-E-1` (Virginia), `US-W-2` (Oregon),
`SA-E-1` (São Paulo).

A `region_id` not in that table still gets a card, but is pinned at the
centre of the map and displays its raw code as its name. A row with no
`region_id` at all is grouped under `unknown`.

The region card shows the pool name when all its instances share one
pool, `N pools` when they differ, and `—` when none report a pool.

## How load and capacity are computed

**Load** is the mean of the `utilization` values across a region's
instances, expressed as a percentage. Rows that do not report
`utilization` are excluded from the mean rather than counted as zero.
If no instance in the region reports utilization, load is 0.

**Instances** is the count of rows in the region.

**Capacity** (the denominator in `instances/max`) is the sum of
`capacity_matches` across the region's rows. When that sum is zero, the
card falls back to twice the instance count — so an `instances/max`
reading with no capacity data behind it is a placeholder, not a reported
limit.

**Players** is the sum of the first present value among `players`,
`current_players` and `player_count` on each row. Rows reporting none of
these contribute nothing.

**Autoscale** is `on` if any instance in the region has
`autoscale_enabled` truthy, `off` if at least one reports the field and
none are truthy, and `—` if no instance reports the field at all.

### Load colour bands

The same bands drive the map pin colour, the region card's severity
strip, the load figure and the load bar:

| Load | Colour |
| ---- | ------ |
| Under 70% | ok |
| 70–85% | warn |
| Above 85% | bad |

The map legend at the bottom-left of the region presence panel restates
these bands.

The **Region presence** map has two layers. On the `load` layer, pin
size scales with instance count. On the `players` layer, pin size scales
with the region's player total. Pin colour follows the load bands on
both layers. Each pin is labelled with its region code and
`instances/max · load%`.

Region cards are paged at 48 per page.

### KPI strip placeholders

The Server fleet KPI strip currently renders a live **Instances** count
with the region count as its hint. **Avg load**, **Capacity headroom**,
**Auto-scale events 24h**, **Cold starts P95** and **Cost run-rate** all
render `—`: these tiles are laid out but not yet fed by the fleet
endpoint. Do not read them as zero.

## Autoscale events and triggers

Each region card can display an event chip derived from the region's
computed load:

| Condition | Chip |
| --------- | ---- |
| Load above 85% | `alert` |
| Load above 75% | `scale_up` |
| Otherwise | no chip |

This chip is a console-side classification of current load, not a record
of an autoscale action that occurred.

The **Recent autoscale events** panel at the bottom of the screen is
laid out with columns for Time, Region, Event, Δ instances, Trigger and
Detail, and is labelled "last 1 h". It currently renders no rows — the
fleet endpoint does not yet supply an event feed. An empty table here
means "not wired", not "no events".

## Drain and scale an instance

The **Drain** and **Scale** buttons on each region card are present but
disabled. No mutation is wired behind them in the current console, and
this page does not describe behaviour they do not yet have. Use your
provider's own controls until these are enabled.

## The Games module list

The admin **Modules** screen controls which games modules appear in the
navigation for a workspace. It is scoped to the Games vertical and
mirrors the equivalent robotics screen.

The module keys map to the games sub-navigation:

| Group | Key | Label |
| ----- | --- | ----- |
| Live | `sessions` | Live sessions board |
| Live | `match` | Match detail |
| Live | `fleet` | Server fleet |
| Broadcast | `studio` | Spectator studio |
| Operations | `audit` | Audit log |
| Operations | `alerts` | Alerts |
| Operations | `billing` | Billing |

The Modules screen itself is always shown, so a workspace can never hide
its way out of being able to un-hide things.

## Hide modules for a workspace

State is read with `adminModules.get` for the org and written with
`adminModules.setHidden`, passing `vertical: 'games'` and the full list
of hidden keys.

What is persisted is a **denylist**, stored at
`organization.module_prefs.games.hidden`. Modules absent from that list
are visible.

The screen's checkboxes represent *visibility*: checked means shown,
unchecked means hidden. A hidden module also carries a `hidden` chip
next to its label. The header subtitle reports how many of the modules
are currently visible.

Changes are local until you press **Save**. The Save button is enabled
only when your current selection differs from what is stored, and is
disabled again while the mutation is in flight. On success the screen
reports "Module visibility saved." and refetches the stored prefs; on
failure it surfaces the error message from the mutation.

## Presets and what each one shows

Presets define which modules are *shown*; everything else becomes
hidden. Applying a preset only changes the local selection — you still
have to Save.

| Preset | Shows |
| ------ | ----- |
| Full games | Everything. Hidden list becomes empty. |
| Operator only (no broadcast) | Everything except `studio`. |
| Broadcast only | `studio` and `audit`. |

After applying a preset you can fine-tune individual checkboxes before
saving.

## Visibility override vs entitlements

Every module is provisioned and enabled according to the tenant's plan.
This screen is a **visibility override on top of that** — it removes
navigation entries, nothing more.

Hiding a module:

- does not change billing
- does not change entitlements
- does not revoke access to the underlying capability

If you need a capability actually restricted, this is not the control
for it.

## Who the change applies to

The denylist is stored on the organization, not on a user. Hidden
modules are removed from the games chrome **for everyone in the org** —
there is no per-user or per-role variant of this setting on this screen.

## Related

- [Telemetry](/platform/telemetry) — metric, trace and record streams behind these figures
- [Telephony Metrics](/glossary/metrics) — P50/P95, jitter and loss definitions
- [Authentication](/concepts/authentication) — API keys and org scoping for control-plane procedures
