> For the complete documentation index, see [llms.txt](https://morningheartgames.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://morningheartgames.gitbook.io/docs/gameplay-ability-toolkit-ecs/build-with-it/concepts.md).

# How the ECS runtime works

This page explains how the runtime is put together: where the rules live, where the state lives, how your code talks to the runtime, and in what order everything runs each frame. Once you know this, the rest of the manual reads as variations on one pattern.

Before you start, build the small example in [Getting started](/docs/gameplay-ability-toolkit-ecs/start/getting-started.md), or at least know what a SubScene is: the place where GameObjects are baked into entities. You need no other Entities knowledge.

## The model in short

* The **catalog** holds the rules: every attribute, tag, effect and ability your assets define, baked into plain data.
* **Actors** hold the state: attributes, tags, ability slots and active effects, in buffers on an entity.
* Your code **asks** by adding requests to queues.
* The runtime **answers** with activation results and cue events, which your code reads.
* Everything happens at fixed points in the **frame**: your requests first, then the runtime, then your readers.

## The catalog

Your assets are ScriptableObjects, and Burst-compiled code cannot read those. When a SubScene bakes, the `GasCatalogAuthoring` in it turns every attribute set, effect, ability and tag it lists into one block of plain data. That block is the catalog. It lives on one entity as the `GasCatalog` component, and at run time nothing reads an asset again. [Authoring](/docs/gameplay-ability-toolkit-ecs/build-with-it/authoring.md#the-catalog-gascatalogauthoring) describes the component's fields.

Inside the catalog, everything is addressed by **index**: the fourth ability, the twelfth tag. Every request you make and every buffer you read uses these indices. Get them from names once, when a system starts:

```csharp
var catalog = SystemAPI.GetSingleton<GasCatalog>().Value;
int fireball = catalog.Value.FindAbilityIndex("GA_Fireball");            // the ability asset's name
int burn = catalog.Value.FindEffectIndex("GE_Burn");                     // the effect asset's name
int stunned = catalog.Value.FindTagIndex("State.Stunned");               // the full tag name
int health = catalog.Value.FindAttributeIndex("HeroAttributes", "Health"); // the set's asset name, then the attribute
```

Each call returns -1 when the catalog has nothing of that name. Keep the index instead of looking it up every frame: every lookup walks a table. Do not write indices into code as numbers. An index is a position, and it moves when assets are added to the catalog.

Tags are numbered after they are sorted by name, so two builds that bake the same assets, such as a server and a client, give every tag the same index.

A world has **exactly one** catalog. Two catalog components in loaded scenes is a setup error, and the Dashboard's **ECS Runtime** page reports it.

## Actors

An actor is an entity with the runtime's components on it. You get one by baking a GameObject that has `GasEntityAuthoring`, by instantiating a baked actor (a prefab or a template entity), or in code with `GasEntityFactory.CreateActor`. [Authoring](/docs/gameplay-ability-toolkit-ecs/build-with-it/authoring.md#actors-gasentityauthoring) shows each way. An actor keeps its state in buffers:

| Buffer              | Holds                                                                                                                 |
| ------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `GasAttributeValue` | One `BaseValue` and `CurrentValue` per attribute in the catalog, in catalog order. Index it with the attribute index. |
| `GasTagCount`       | The tags the actor owns, counted, plus the ability tags it currently blocks.                                          |
| `GasAbilitySlot`    | One slot per granted ability: its level, the cooldown remaining and how many executions are running.                  |
| `GasActiveEffect`   | The duration and infinite effects on the actor, with stack count, start time, duration and next period time.          |
| `GasCueEvent`       | Cue events raised on the actor, for your presentation code to read and clear.                                         |

Every actor has a value for every attribute in the catalog, even attributes from sets it does not use. A catalog that collects many attribute sets gives each actor more values to carry; see [Performance](/docs/gameplay-ability-toolkit-ecs/reference/performance.md).

To see one actor's buffers by name in Play mode, open **Tools > Gameplay Ability Toolkit > ECS > Actor Debugger**.

## Asking

Gameplay code does not change an actor's state itself. It appends a request to a queue, and the runtime serves the queue in a fixed order, applying the rules the GameObject runtime applies. The queues are singleton entities. `GasRuntimeBootstrapSystem` creates them, and the runtime's clock, on the first frame a catalog exists:

| Queue                       | Request                       | Asks for                                                           |
| --------------------------- | ----------------------------- | ------------------------------------------------------------------ |
| `GasAbilityActivationQueue` | `GasAbilityActivationRequest` | an ability to activate; checks cooldown, cost and tag requirements |
| `GasInstantEffectQueue`     | `GasInstantEffectRequest`     | an effect to be applied; any type, instant, duration or infinite   |
| `GasAbilityEndQueue`        | `GasAbilityEndRequest`        | a running ability to end, or to be cancelled                       |
| `GasAbilityCommitQueue`     | `GasAbilityCommitRequest`     | an explicit-commit ability to pay its cost and start its cooldown  |
| `GasGameplayEventQueue`     | `GasGameplayEvent`            | a gameplay event, which can trigger abilities and complete waits   |

`GasEntityFactory` has a method for four of them: `RequestActivation`, `RequestInstantEffect`, `RequestEnd` and `SendGameplayEvent`. Each finds the queue for you and returns false if the world has none. For a commit request you add to the queue yourself; [Writing gameplay code](/docs/gameplay-ability-toolkit-ecs/build-with-it/gameplay-code.md) shows how.

Requests are queued instead of applied on the spot for three reasons:

* They are served in the same order every time, which client prediction depends on.
* Asking never changes an entity's archetype, so a job can decide what to ask for.
* One system serves all of them, so each rule is checked in one place.

## Answering

The runtime answers in two places. In both it appends and leaves the clearing to you:

* **Activation results.** Every activation request you queue gets one `GasAbilityActivationEvent` in the `GasAbilityActivationEventQueue` singleton: `Succeeded`, and when it did not succeed, a `FailureReason` such as `Cooldown`, `Cost`, `TagRequirements` or `InvalidRequest`. An ability started by a trigger also gets a result, unless it was already running and cannot run twice. Read the results after the runtime, then clear the buffer. If several systems read them, order them and let the last one clear. In a world with delivery abilities, the delivery module clears them last in `GasResultSystemGroup`; see [Activation results](/docs/gameplay-ability-toolkit-ecs/build-with-it/delivery.md#activation-results).
* **Cue events.** Each actor's `GasCueEvent` buffer receives the cues its effects raise, for sound, particles and UI. See [Cues and presentation](/docs/gameplay-ability-toolkit-ecs/build-with-it/presentation.md).

A buffer nobody clears grows every frame. The Dashboard's **ECS Runtime** page shows how deep the activation, result, instant-effect and gameplay-event queues are and how many cues are waiting, and marks a result or cue queue that has passed 100 entries as a warning. `GasRuntimeSnapshot.Capture` returns the same numbers to code.

## The frame

Asking and answering happen inside `SimulationSystemGroup`, in three groups that always run in this order:

```
GasRequestSystemGroup      your systems that ask
GasSimulationSystemGroup   the runtime
GasResultSystemGroup       your systems that read what happened
```

Put a system in one of the outer two with `[UpdateInGroup(typeof(GasRequestSystemGroup))]` or `[UpdateInGroup(typeof(GasResultSystemGroup))]`. Do not order a system against `GasSimulationSystemGroup` yourself: with Netcode for Entities the three groups move into the prediction loop together, and an order stated against the runtime's group from outside it stops holding.

Inside the runtime, the systems run in this order:

| System                          | Does                                                                                                                                          |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `GasTimeSystem`                 | Copies the world's time into the runtime's clock, `GasTime`.                                                                                  |
| `GasAbilityCooldownSystem`      | Counts cooldowns down, so an ability that comes off cooldown this frame can run this frame.                                                   |
| `GasAbilityEndSystem`           | Serves end and cancel requests.                                                                                                               |
| `GasAbilityActivationSystem`    | Serves activation requests and writes one result for each.                                                                                    |
| `GasAbilityCommitSystem`        | Serves commit requests.                                                                                                                       |
| `GasMontageSystem`              | Starts the montages this frame's activations play, advances every playing montage, and sends the gameplay events their notifies are bound to. |
| `GasInstantEffectSystem`        | Serves effect requests.                                                                                                                       |
| `GasEffectTickSystem`           | Checks each effect's ongoing requirements, expires effects, and runs periodic ones.                                                           |
| `GasAttributeRecalculateSystem` | Recomputes each changed actor's current values from base values and modifiers, then clamps them.                                              |
| `GasAbilityWaitSystem`          | Checks the waits of running abilities, and does what a wait says when it holds.                                                               |
| `GasAbilityTriggerSystem`       | Starts abilities from gameplay events and from tags being added or removed.                                                                   |

What that order means in practice:

* A request queued in `GasRequestSystemGroup` is served in the same frame, and its result is readable in `GasResultSystemGroup` in the same frame.
* A request queued in `GasResultSystemGroup` is served on the next frame.
* An ability started by a trigger is served on the next frame, because triggers run last.
* An attribute read in `GasResultSystemGroup` already includes this frame's effects.

The delivery systems for abilities that hit other actors use the two outer groups too. See [Abilities that reach other actors](/docs/gameplay-ability-toolkit-ecs/build-with-it/delivery.md).

## Time

The runtime reads time from its own clock, the `GasTime` singleton, not from `UnityEngine.Time`. `GasTimeSystem` keeps it in step with the world. You can take it over: set `Manual` and write `Elapsed` and `DeltaTime` yourself, or call `GasEntityFactory.SetTime`. With Netcode for Entities the clock follows the network tick instead.

A world with no clock runs at time zero, which looks like effects that never expire. `GasRuntimeBootstrapSystem` creates the clock, so this only happens in a world you build by hand.

Durations, periods and cooldowns are float seconds, as in the GameObject runtime. `GasTime.Elapsed` is a double, but the times stored on effects are floats, and a float has fewer digits the larger it gets. From about 36 hours of continuous running (131,072 seconds), the gap between two neighbouring float values is 15.6 ms, about one frame at 60 Hz. An effect can then end up to a frame early or late, and the gap doubles each time the running time doubles. This matters only for a server that runs for days without a restart.

## Two habits that keep it fast

The runtime skips actors that nothing changed. To know what changed, it relies on Entities' change versions: any system that takes a component or buffer **read-write** marks the whole chunk as changed, whether it writes anything or not. So:

* **Read the runtime's buffers read-only.** Use `SystemAPI.GetBufferLookup<GasAttributeValue>(true)`, or take the buffer as an `in` parameter of an `IJobEntity`'s `Execute`. A read-write access to `GasAttributeValue` on every actor makes the runtime recalculate every actor every frame.
* **Ask; do not write.** Changing an actor's attributes or tags directly skips the rules and the change tracking. Use a request, or one of the direct calls in [Writing gameplay code](/docs/gameplay-ability-toolkit-ecs/build-with-it/gameplay-code.md), which keep both.

## Where problems show up

* **When baking.** Anything the catalog cannot represent faithfully is skipped or changed, and a message says so. The messages appear in the Console, in the catalog's Inspector under **What it bakes**, and on the Dashboard's **ECS Runtime** page under **Catalog bake**. [Authoring](/docs/gameplay-ability-toolkit-ecs/build-with-it/authoring.md#what-the-catalog-reports) lists the messages and what to do about each.
* **When asking.** Every refusal has a reason: `GasAbilityFailureReason` for activations, `GasEffectFailureReason` for effects applied from code.
* **While running.** The **ECS Runtime** page lists the actors, active effects, running waits and the depth of the queues. The Actor Debugger shows one actor in detail. [Troubleshooting](/docs/gameplay-ability-toolkit-ecs/reference/troubleshooting.md) walks through the usual causes when nothing happens.

## Where to go next

* [Authoring](/docs/gameplay-ability-toolkit-ecs/build-with-it/authoring.md): set up the catalog and actors, and author abilities, waits, montages and effects.
* [Abilities that reach other actors](/docs/gameplay-ability-toolkit-ecs/build-with-it/delivery.md): areas, arcs, nearest targets and projectiles, without code.
* [Writing gameplay code](/docs/gameplay-ability-toolkit-ecs/build-with-it/gameplay-code.md): every way to make requests and read state.
* [Cues and presentation](/docs/gameplay-ability-toolkit-ecs/build-with-it/presentation.md): turn cue events into visuals and sound.
