# State stack Every world owns a stack of named states. Use it for play, pause, menus, cutscenes, and game-over overlays. Each state owns a tag component. A spawn automatically receives the tag of the state currently on top. ```nupp local GameState = world:createState("game", { onBlur = "pause", onFocus = "resume", }) local PauseState = world:createState("pause") world:pushState("game") world:spawn(Player()) -- receives GameState world:pushState("pause") world:spawn(PauseMenu()) -- receives PauseState world:popState() ``` The default exit action despawns the entities tagged with the popped state. Spawn permanent cameras, services, and HUD entities before the first `pushState` so they receive no state tag. ## Transition order Pushing a state performs these steps: 1. Apply the outgoing state's blur policy. 2. Emit `StateBlur`. 3. Push the new state and select its auto-tag. 4. Apply its enter policy. 5. Emit `StateEnter`. Popping performs these steps: 1. Apply the top state's exit policy. 2. Emit `StateExit`. 3. Remove it from the stack. 4. Select the revealed state's auto-tag. 5. Apply its focus policy and emit `StateFocus`. Popping the last state clears the auto-tag. Popping an empty stack or pushing an unregistered name raises. Policy-driven entity mutations stage until the next publication barrier. The state stack changes immediately. ## Reading the stack `world:peekState()` answers the top name, and `world:listStates()` answers the whole stack bottom-first: ```nupp world:pushState("game") world:pushState("pause") world:listStates() -- {"game", "pause"} world:peekState() -- "pause" ``` Read the whole stack to tell a pause pushed over play from a pause that is all there is, which is what a back button, a save prompt and a debugger each need. The returned list is a fresh copy and holds only the states that were pushed, so a state `createState` registered and nothing pushed does not appear. The debug server reports the same stack as the `states` command. ## Lifecycle policies | Hook | Moment | | --------- | ------------------------------------------------------ | | `onEnter` | The state reaches the top through a push. | | `onBlur` | Another state covers it. | | `onFocus` | A pop reveals it. | | `onExit` | The state leaves through a pop. Defaults to `despawn`. | Blur, focus, and exit hooks accept these built-in actions: | Action | Effect on entities carrying the state tag | | --------- | ----------------------------------------- | | `pause` | Add `Paused`. | | `resume` | Remove `Paused`. | | `disable` | Add `Disabled`. | | `despawn` | Despawn the entities. | A hook may instead call a function. A table with `apply` and `call` runs the built-in action first, then the function. Enter accepts a function. `pause` affects queries with an explicit `Paused` exclusion. Render queries continue to match paused entities. Use `disable` when entities should stop drawing, and exclude `Disabled` in game queries. ## State-aware work The tag returned by `createState` works in any query: ```nupp local enemies = world:newQuery({ include = {GameState, Enemy}, exclude = {tecs.ecs.Disabled, tecs.ecs.Paused}, }) ``` Gate a whole system on the current top state: ```nupp world:addSystem({ name = "game.Update", phase = tecs.ecs.phases.Update, runIf = function(_dt: number, exclusive world: tecs.ecs.World): boolean return world:peekState() == "game" end, run = updateGame, }) ``` Observe transition events at address zero when runtime code needs notification: ```nupp world:observe(0, tecs.ecs.StateEnter, function(event: tecs.ecs.StateEnter): nil print("entered", event.state) end ) ``` ## Snapshot setup Snapshots carry the stack, state tags, `Paused`, and `Disabled`. Policies are functions and do not enter the save. Create every state with its policy during plugin setup before loading a snapshot. Load raises when the saved stack names a state this world has not registered. After load, use state-tag queries to rebuild group indexes and `EntityKey` for the few individual entities that code must rediscover. See [Save games](/ecs/save-games.md).