Indices & Stable IDs

Every agent in a crowd has two handles: an index and a stable id. Knowing which to use, and when, is the single most important thing to get right when scripting against the crowd.

Index - fast, but not stable

An index is the agent's slot in the crowd's arrays (0 … GetAgentCount()-1). All the per-agent getters and order functions take indices, and the query functions (GetAgentsInRect, GetAgentsInRadius, GetAgentsInScreenRect) return them.

Indices are not stable across removals. When an agent is removed, the simulation fills its slot with the last agent (swap-and-pop), so some other agent's index changes. An index is only guaranteed valid within the same tick you obtained it.

Stable id - durable, for tracking over time

A stable id is unique per crowd actor, never reused (not even across ResetSimulation), and fixed for the agent's whole lifetime. Use ids whenever you need to remember a particular agent between frames - a unit's health bar, a selection that persists, a "go here when you arrive" follow-up.

Translating between the two

// Single:
int32 Id    = Crowd->GetAgentId(Index);   // -1 if Index out of range
int32 Index = Crowd->GetAgentIndex(Id);   // -1 if no such agent (e.g. removed)

// Batch (one call for a whole selection):
TArray<int32> Ids     = Crowd->GetAgentsIds(Indices);     // -1 per out-of-range entry
TArray<int32> Indices = Crowd->GetAgentsIndices(Ids);     // -1 per unknown id

A returned -1 means the agent is gone - the standard way to detect that a tracked unit was removed.

The pattern

Selection time:   indices ──GetAgentsIds──► store ids        (durable handle)
                                              │
Each later use:   stored ids ──GetAgentsIndices──► indices    (re-resolve)
                                              │
                                              ▼
                            drop the -1s (removed), use the rest

So: convert to ids when you store, convert back to indices when you act.

Events speak ids, broadcast as indices

The movement events (OnAgentsReachedGoal, OnAgentsStuck, OnAgentsOrdersCanceled) are buffered internally as stable ids - because an event can outlive the tick that produced it while indices reshuffle - but the actor resolves them to current indices before broadcasting, so your handler receives ready-to-use indices. Agents removed between the event and the broadcast are simply dropped from the batch.

The spawn/remove events (OnAgentsSpawned, OnAgentsRemoved) also deliver indices, and fire before the tick that applies the change, so those indices are still valid in the handler (see Events for the exact timing).

Custom per-agent data

The crowd doesn't store arbitrary gameplay data per agent. The intended pattern is to key your own Map<int32, ...> by the agent's stable id - ids survive the swap-and-pop shuffles that would scramble an index-keyed map.