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

# Game Events & State

> Listen to game events and manage your overlay's game state.

Everything interesting an overlay does is a reaction to gameplay: a kill feed updates when someone dies, a scoreboard
tracks the match, a HUD swaps layouts when the round phase changes. This guide walks through subscribing to game
events, folding them into an in-memory game state, and getting that state to your windows.

<Info>
  This guide covers the event-consumption workflow. For the subscription API reference, see
  [Game Events](/essentials/game-events); for the per-game event lists, see
  [Supported Games](/games/introduction) - e.g. [Rainbow Six Siege](/games/siege-events) and
  [Universal](/games/universal-events).
</Info>

## How Game Events Work

Four things to know before writing any code:

1. **Events arrive per game module.** You subscribe to a game's events by passing its module in your `overlayed()`
   config, and listen on the matching property of the overlay object - `overlay.siege` for `Siege.module()`. The
   `universal` module is always available as `overlay.universal`, whether or not you pass it.
2. **Nothing is emitted until you call `readyForGameEvents()`.** Each module queues incoming events (up to 1,000)
   from startup. The first call to that module's `readyForGameEvents()` replays the queue to your listeners - in
   arrival order, synchronously - and switches the module to live delivery for the rest of the app's run.
3. **Every event is validated and fully typed.** Payloads are validated with [Arktype](https://arktype.io/) before
   your listeners see them. The object your callback receives is an envelope - `{ game, type, creation_time,
   content }` - and the per-game reference tables document the shape of `content`.
4. **Listeners run in your main process.** Game events never arrive in a renderer directly. Keep your canonical game
   state in the main process and forward snapshots to windows over IPC.

## From Events to Game State

The examples use Rainbow Six Siege, but the pattern is identical for any module: reset on `match_started`, build a
roster from `player_joined`/`player_left`, and patch it from the `*_changed` events.

<Steps>
  <Step title="Register the game module">
    Pass the module for each game whose events you want. This also subscribes you to that game's lifecycle events -
    see [Game Launch & Close Events](/guides/game-launch-close-events) for how `gameLaunch`, `gameReady`, and
    `gameClose` fit in.

    ```typescript overlayed.ts theme={null}
    import { overlayed } from "@overlayed/app";
    import * as Siege from "@overlayed/app/siege";
    import electron from "electron";

    export const overlay = overlayed({
    	electron,
    	applicationId: "YOUR_APPLICATION_ID",
    	modules: [Siege.module()],
    });
    ```
  </Step>

  <Step title="Build state from events">
    Keep one plain state object in the main process and treat each listener as a reducer. The event types are
    exported from the module package, so you can derive your state types from them instead of hand-writing shapes:

    ```typescript gameState.ts theme={null}
    import { overlay } from "./overlayed.ts";
    import type { PlayerJoinedEvent, MatchStartedEvent, SiegeGameModePhase } from "@overlayed/app/siege";

    type SiegePlayer = PlayerJoinedEvent["content"];
    type SiegePlayerId = SiegePlayer["player_id"];

    export const state = {
    	match: null as MatchStartedEvent["content"] | null,
    	phase: null as SiegeGameModePhase | null,
    	players: new Map<SiegePlayerId, SiegePlayer>(),
    };

    overlay.siege.on("match_started", (event) => {
    	state.match = event.content;
    	state.players.clear();
    });

    overlay.siege.on("player_joined", (event) => {
    	state.players.set(event.content.player_id, event.content);
    });

    overlay.siege.on("player_left", (event) => {
    	state.players.delete(event.content.player_id);
    });

    overlay.siege.on("player_kills_changed", (event) => {
    	const player = state.players.get(event.content.player_id);
    	if (player) {
    		player.match_stats.kills = event.content.kills;
    	}
    });

    overlay.siege.on("phase_changed", (event) => {
    	state.phase = event.content.phase;
    });
    ```

    Note that the game data is always under `event.content` - the reference tables on the
    [per-game pages](/games/introduction) document that inner shape. The envelope also carries `event.game` and
    `event.type`, which matter once one listener handles several games or event types.
  </Step>

  <Step title="Flush the queue with readyForGameEvents()">
    Once all listeners are attached, tell the module you're ready:

    ```typescript gameState.ts theme={null}
    overlay.siege.readyForGameEvents();
    ```

    Any events that arrived while your app was starting up are replayed to your listeners immediately, then new
    events flow as they happen.

    <Warning>
      Call `readyForGameEvents()` *after* registering your listeners. The queue is replayed synchronously during
      the call, and queued events with no matching listener are discarded - flushing first means the events your
      app started up with are gone by the time your listeners exist.
    </Warning>
  </Step>

  <Step title="Reset state when the session ends">
    A match ending and the game closing are different resets. `match_ended` clears per-match state while the player
    sits in the menus; `gameClose` clears everything for the session:

    ```typescript gameState.ts theme={null}
    overlay.siege.on("match_ended", () => {
    	state.match = null;
    	state.phase = null;
    	state.players.clear();
    });

    overlay.on("gameClose", ({ game }) => {
    	if (game !== "siege") {
    		return;
    	}

    	state.match = null;
    	state.phase = null;
    	state.players.clear();
    });
    ```
  </Step>
</Steps>

## Specific Events vs. Listening to Everything

Every module exposes both `on` for a single event type and `onAny` for all of the module's events. For the same
event, `on` listeners are invoked before `onAny` listeners.

<Tabs>
  <Tab title="Specific events">
    Use `on` when each event has its own handling. The callback is typed to that event, so `event.content` is
    fully typed with no narrowing needed:

    ```typescript theme={null}
    overlay.siege.on("player_died", (event) => {
    	killFeed.push({
    		victim: event.content.player_id,
    		killer: event.content.instigator_player_id,
    		headshot: event.content.headshot,
    	});
    });
    ```
  </Tab>

  <Tab title="All events (onAny)">
    Use `onAny` when you want one funnel - an event log, a debug view, or a single reducer that switches on
    `event.type`:

    ```typescript theme={null}
    overlay.siege.onAny((event) => {
    	overlay.log.debug(`[siege] ${event.type}`);

    	if (event.type === "player_kills_changed") {
    		// event.content is narrowed by the type check
    	}
    });
    ```
  </Tab>
</Tabs>

Both have matching removers - `overlay.siege.off(eventType, callback)` and `overlay.siege.offAny(callback)` - which
take the same callback reference you registered. You can attach multiple listeners to the same event.

## Getting State to Your Windows

Since events land in the main process, your windows never see them directly. Forward a snapshot over IPC whenever
state changes - both `createWindow` and `createInGameWindow` return (a wrapper around) an Electron `BrowserWindow`,
so the standard `webContents.send` flow applies:

```typescript main.ts theme={null}
function broadcastState() {
	hudWindow.webContents.send("game-state", {
		match: state.match,
		phase: state.phase,
		players: Array.from(state.players.values()),
	});
}

overlay.siege.onAny(() => broadcastState());
overlay.siege.readyForGameEvents();
```

<Tip>
  Send the whole snapshot rather than re-emitting individual game events to the renderer. A window that opens
  mid-match then renders correctly from the first message, instead of waiting to accumulate deltas - and your
  renderer stays a pure function of one state object.
</Tip>

## Event Queueing in Depth

The queue exists so your app's startup never races the game: events that arrive while your windows and listeners are
still being set up are held per module until you flush them.

* **The queue is per module.** Each module you want events from needs its own `readyForGameEvents()` call -
  flushing `overlay.siege` does not flush `overlay.universal`.
* **It holds up to 1,000 events per module.** Once full, additional events are dropped until the queue is flushed,
  and a warning is raised - observable via `overlay.on("warning", ...)` (see
  [Issue Handling](/essentials/issue-handling)).
* **Flushing is one-way and once per app run.** After the first call, the module stays in live delivery for the
  lifetime of the process - subsequent calls are no-ops, and the queue does not re-arm when the game closes and
  relaunches. There's no harm in calling it again, but you can't "pause" events with it either; to stop reacting,
  remove your listeners with `off`/`offAny`.

## Universal Events

Alongside per-game events, Overlayed emits a small set of [universal events](/games/universal-events) - such as
`logged_in` - for subscribed games. They arrive on `overlay.universal`, which exists even if you never passed the
universal module, and they need their own `readyForGameEvents()`:

```typescript main.ts theme={null}
overlay.universal.on("logged_in", (event) => {
	// event.game tells you which game this came from
	linkAccount(event.game, event.content.account_id);
});

overlay.universal.readyForGameEvents();
```

<Warning>
  Setting `universal: true` in your config and the universal *events* module are related but different things. The
  option subscribes you to every supported game's lifecycle (`gameLaunch`/`gameReady`/`gameClose`) and universal
  events - but a game's own events (kills, rounds, phases) still only flow for games whose module you passed. Events
  for a game without a registered module are dropped.
</Warning>

## FAQ

<AccordionGroup>
  <Accordion title="Why am I not receiving any events?">
    Check these in order:

    1. **Module registered**: the game's module must be in your `overlayed()` `modules` array - `universal: true`
       alone does not deliver game-specific events.
    2. **readyForGameEvents()**: it must be called on that specific module. Until then, everything sits in the
       queue.
    3. **Rejected launch**: calling `reject()` in a `gameLaunch` listener blocks that game's events for the
       session - see [Overlay Events](/essentials/overlay-events).
    4. **Game support**: some games support rendering and hotkeys but have no events - check
       [Supported Games](/games/introduction).
    5. **Logs**: every incoming event is logged internally, including events that were dropped for having no
       listener. Run with `debug: true` and see [Logging](/logging/introduction).
  </Accordion>

  <Accordion title="Do I call readyForGameEvents() again when the game relaunches?">
    No. It's once per module per app run. Queueing only covers your app's startup window - after the first flush,
    events are delivered live for the rest of the process, across game sessions. Handle relaunches by resetting
    your *state* (on `gameClose` and `match_started`), not by re-flushing.
  </Accordion>

  <Accordion title="Can I register listeners before the game launches?">
    Yes - and you should. Listener registration is independent of the game's lifecycle, and combined with queueing
    it means the usual setup (register listeners, call `readyForGameEvents()`, both at app startup) never misses an
    event regardless of when the game starts.
  </Accordion>

  <Accordion title="What happens if the queue fills up?">
    Each module queues at most 1,000 events. Once the buffer is full, further events are dropped until you flush,
    and a warning is emitted that you can observe with `overlay.on("warning", ...)`. If you're hitting the cap,
    call `readyForGameEvents()` earlier in your startup.
  </Accordion>

  <Accordion title="Can an event have missing or malformed fields?">
    No. Every event is validated against its Arktype schema before your listeners run; events that fail validation
    are dropped with a warning rather than delivered partially. One deliberate exception to the noise: events whose
    *type* your installed package version doesn't know about (e.g. a newer game module adds an event) are silently
    ignored, so shipping apps don't spam warnings when new events roll out.
  </Accordion>

  <Accordion title="Do game events work when in-game rendering isn't supported?">
    Yes. When `gameReady` reports `inGameRenderingSupported: false`, only rendering inside the game is affected -
    events still flow. See
    [Handling inGameRenderingSupported](/guides/game-launch-close-events#handling-ingamerenderingsupported).
  </Accordion>

  <Accordion title="How do round phases work in Siege?">
    Siege emits `phase_changed` with a phase string (`"intro"`, `"planning"`, `"prep"`, `"action"`, `"results"`,
    ...) as the round moves along, and `round_ended` with a full summary - final round state per team, every damage
    event, and each player's final health and operator. The package also exports helpers like
    `isSiegeRoundStateVictory` and `isSiegeRoundStateCompleted` plus constants such as `SIEGE_ROUND_STATE_LABELS`
    for interpreting the numeric team states. See the [Siege events reference](/games/siege-events).
  </Accordion>

  <Accordion title="How do I debug what's actually being emitted?">
    Two options that work well together: attach a `onAny` listener per module that logs `event.type` and
    `event.content`, and enable `debug: true` in your config so Overlayed's internal logs - which include every
    incoming event, validation failures, and "no handlers" drops - appear. See
    [Debug Mode](/logging/debug-mode).
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Game Events Reference" icon="bolt" href="/essentials/game-events">
    The subscription API: on, onAny, queueing, types, and constants.
  </Card>

  <Card title="Supported Games" icon="gamepad" href="/games/introduction">
    Every supported game and its full event reference tables.
  </Card>

  <Card title="Game Launch & Close Events" icon="play" href="/guides/game-launch-close-events">
    The session lifecycle that brackets these events.
  </Card>

  <Card title="Issue Handling" icon="triangle-exclamation" href="/essentials/issue-handling">
    Observe warnings, errors, and fatals from the overlay.
  </Card>
</CardGroup>
