> ## Documentation Index
> Fetch the complete documentation index at: https://docs.summerengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Summer analytics skill for AI agents

> Send game events to Summer analytics from the authority or client: fire-and-forget, never blocking gameplay. A Summer Engine skill for AI agents.

`summer-analytics` is a skill from the [Summer skill library](/mcp/skills): a guide your AI agent reads when the task matches, so it builds the good version on the first try. Send game events to Summer analytics from the authority or client: fire-and-forget, never blocking gameplay.

| | |
| - | - |
| Status | Stable |
| Domains | Multiplayer, Scripting |
| Version | `1.0.1` |
| Engine version | `>=4.6` |
| Recommended set | No, installed by `--all` or by name |

## When your agent uses it

* Adding analytics, telemetry or event tracking to a Summer game: level completed, coin collected, tutorial funnel
* Deciding what to track on the client versus the authority, and how to name analytics events and properties
* Analytics capture fails with unavailable, capacity\_exceeded or analytics\_storage\_limit\_exceeded

## Install

`summer setup` installs every skill for your agent. To add only this one:

```bash theme={null}
npx -y summer-engine@latest skills install summer-analytics --agent claude-code
```

Swap `claude-code` for your agent, such as `codex` or `cursor`. See [Skills](/mcp/skills#quick-start) for every option.

## The skill

## Overview

Summer analytics stores named events, such as `level_completed` or
`coin_collected`, for the creator to query in Studio. There are two ways in:

| Captured from | Call | What it means |
| - | - | - |
| The player's client | `Summer.client.analytics.capture(name, properties)` | An **untrusted** observation from that player's device. Summer attaches the player, game and build; you never pass ids. |
| The authority | `Summer.authority.analytics.capture(name, properties)` | An observation about this World's runtime. It describes the server, not a player or a match. |

Analytics is for **looking at** your game, never for running it:

* Capture returns immediately.
* Gameplay never waits for capture and never depends on its result.
* **Capture is never retried.** An `unavailable` result may already have stored the event, and capturing again records a second event.

**Where it works:** hosted only. Under Local Play both calls complete with
`ok == false` and code `unavailable`, and nothing is stored. Also print each
event while testing, so you can check it in the logs.

## Steps

### 1. Choose a small set of events

Name events after things that happened, in the past tense:
`match_started`, `coin_collected`, `tutorial_step_completed`.

* **Names**:
  * 1 to 64 characters, matching `^[A-Za-z][A-Za-z0-9_.:-]{0,63}$`.
  * Case-sensitive, and never starting with `summer.`.
* **Never put changing values in names** (`coin_7_collected`). Put them in properties: `{"coin": 7}`.
  * New event names are rate-limited per player and per game per day.
  * Known names keep working after that limit.
* **Properties** are a JSON `Dictionary`:
  * at most 64 scalar values;
  * nesting at most 4 deep;
  * property names up to 128 bytes;
  * one event at most 16 KiB.
* **Never send personal data or secrets.** Names such as `email`, `password`, `phone`, `address`, `ip_address` and `*_token` are rejected at any depth, as is anything starting with `$summer_`.

Capture authoritative facts on the authority, where the game decided them.
Capture interface facts on the client: menu opened, tutorial step shown,
settings changed.

### 2. Add a fire-and-forget helper

Authority, in `authority/main.gd`:

```gdscript theme={null}
## Fire-and-forget: gameplay never waits on analytics, and a failure is
## logged once, never retried (a retry would count the event twice).
var _analytics_warned := false

func _track(event: String, properties: Dictionary = {}) -> void:
	print("ANALYTICS ", event, " ", JSON.stringify(properties))
	var capture := Summer.authority.analytics.capture(event, properties)
	var result: SummerResult = await capture.get_result_or_completed_signal()
	if not result.ok and not _analytics_warned:
		_analytics_warned = true
		push_warning("analytics unavailable: %s" % result.code)
```

Client, in `client/main.gd`, the same shape with the client facade:

```gdscript theme={null}
var _analytics_warned := false

func _track(event: String, properties: Dictionary = {}) -> void:
	print("ANALYTICS ", event, " ", JSON.stringify(properties))
	var capture := Summer.client.analytics.capture(event, properties)
	var result: SummerResult = await capture.get_result_or_completed_signal()
	if not result.ok and not _analytics_warned:
		_analytics_warned = true
		push_warning("analytics unavailable: %s" % result.code)
```

### 3. Call it without awaiting

Call `_track(...)` as a statement and do **not** `await` it, so the caller
continues immediately. For example, in the authority's `_collect`, after the
coin is applied:

```gdscript theme={null}
	_track("coin_collected", {"coin": coin, "players": seat_of.size()})
```

On the client, after a menu choice:

```gdscript theme={null}
	_track("menu_opened", {"from": "pause"})
```

### 4. Read the results

`analytics.summary` (hosted, after `summer login`) returns the
game's top custom events next to its store page views, launches and play
time; the `grow-analytics` skill says how to read them. In Studio:
`studio.open` with target `game`, params `gameId` (your game's id) and
`section: "analytics"`. Events show up after a short delay.

## Result codes

| Code | Meaning | What to do |
| - | - | - |
| `unavailable` | No analytics service here (Local Play, an old Host), or a lost answer | Nothing. Do not capture again. |
| `capacity_exceeded` | More than 32 captures are pending | Capture less often; batch counts into one event |
| `analytics_storage_limit_exceeded` | The game's analytics quota is used up | Stop capturing this event type; final for now |
| `not_configured` | An older Supervisor without authority analytics | Nothing |

Every failure is final: none of them asks you to capture again.

## Checklist

* [ ] Every capture is a plain `_track(...)` call; no gameplay code awaits it.
* [ ] Event names are fixed strings; values live in properties.
* [ ] No personal data, tokens or free-typed player text in properties.
* [ ] Authority-decided facts (won, collected, bought) are captured on the authority.
* [ ] Under Local Play the game plays normally while captures report `unavailable`.

## Common mistakes

| Don't | Do | Why |
| - | - | - |
| `await _track(...)` in gameplay code | Call it and move on | A slow or failing capture would stall the game |
| Capture again after `unavailable` | Log once and drop it | It may already be stored; a second capture double-counts |
| `coin_%d_collected` names | `coin_collected` with `{"coin": 7}` | New names are rate-limited; values belong in properties |
| Trust client events for rewards or rankings | Decide on the authority; capture there too | Client events are untrusted observations |
| Put emails, ids or tokens in properties | Summer attaches player and game identity itself | Blocked names are rejected; personal data must not be collected |
| Make gameplay depend on analytics working | Treat it as optional | It is unavailable locally and may be unavailable hosted |
| Start names with `summer.` | Your own names | That namespace is reserved |

## See also

* `multiplayer` — where the client and the authority run
* `multiplayer-state` — the authority's Command handlers, where most events happen
* `summer-match-results` — results and ratings, which Summer records itself

## From a shell

No Summer MCP tools in this session? Run each one as a command; when `summer --version` is missing or below 4.0, use `npx -y summer-engine@latest` in place of `summer`. Fields are flags in kebab-case (`--scene-path res://main.tscn`) or one `--input '<json>'`, and `summer ops describe <op>` prints the exact input. The `summer-cli` skill has the rest.

| In this skill | Command |
| - | - |
| `analytics.summary` | `summer analytics summary` |
| `summer_get_diagnostics` | `summer engine get-diagnostics` |
| `summer_get_script_errors` | `summer engine get-script-errors` |
| `summer_play` | `summer engine play` |
| `summer_stop` | `summer engine stop` |
| `studio.open` | `summer studio open` |

## Related skills

* [`multiplayer`](/skills/multiplayer): Start here for Summer multiplayer: how authority, clients, Worlds and Sessions fit, and what comes next.
* [`multiplayer-state`](/skills/multiplayer-state): Authority-owned state in Summer multiplayer: shared and private State groups, Commands, Events.
* [`summer-match-results`](/skills/summer-match-results): End a Summer match: report each player's win, loss or draw, show results on clients, then close the World.
* [`summer-store`](/skills/summer-store): Sell Summer items for Sparks in a multiplayer game: shop, checkout, owned items, authority entitlement checks.
* [`grow-analytics`](/skills/grow-analytics): Read a creator's Summer Grow numbers (page views, launches, sessions, play time), compare windows.

***

Source: [`library/skills/summer-analytics/SKILL.md`](https://github.com/SummerEngine/summer/blob/main/library/skills/summer-analytics/SKILL.md), rendered from `summer-engine@4.3.0` on npm.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.