
# Phases

A system names the phase that runs it:

```teal
world:addSystem({
    name = "game.StepEnemies",
    phase = tecs.ecs.phases.FixedUpdate,
    run = stepEnemies,
})

world:addSystem({
    name = "game.FadeTints",
    phase = tecs.ecs.phases.Update,
    run = fadeTints,
})
```

`FixedUpdate` runs on the simulation clock. `Update` runs once per frame on the
presentation clock. The phase gives game systems an order relative to engine
systems.

[`Application`](/modules/Application) calls `world:startup()` once,
`world:update(dt)` every iteration, and `world:shutdown()` at teardown. Events
do not occupy a phase; see [Observer timing](/modules/ecs/events#observer-timing).

## Lifecycle groups

The phase tree hangs from `tecs.ecs.phases.AllGroups`:

- `startup()` runs `StartupGroup`: `PreStartup`, `Startup`, and `PostStartup`.
- `update(dt)` runs `MainGroup`: `First`, `PreUpdate`, `FixedUpdateGroup`,
  `Update`, `PostUpdate`, `RenderGroup`, and `Last`.
- `shutdown()` runs `ShutdownGroup`: `PreShutdown`, `Shutdown`, and
  `PostShutdown`.

`FixedUpdateGroup` contains `FixedFirst`, `FixedPreUpdate`, `FixedUpdate`,
`FixedPostUpdate`, and `FixedLast`.

`RenderGroup` contains `RenderFirst`, `PreRender`, `Render`, `PostRender`, and
`RenderLast`.

Application startup runs after the entry plugin registers its systems and
entities. Startup work therefore finishes before the first frame and does not
inflate that frame's `dt`.

## Engine system order

The engine installs its work into the same tree:

| Phase             | Engine work                                                                             |
| ----------------- | --------------------------------------------------------------------------------------- |
| `First`           | Advance frame-clock sequences                                                           |
| `FixedFirst`      | Latch fixed input, snapshot transforms, advance fixed-clock sequences                   |
| `FixedUpdate`     | Run TTL and physics                                                                     |
| `FixedPostUpdate` | Copy physics poses                                                                      |
| `FixedLast`       | Leave fixed-input mode                                                                  |
| `Update`          | Advance presentation-clock sequences                                                    |
| `PostUpdate`      | Compose relative transforms, play sounds, encode animation, lay out text, sync emitters |
| `RenderFirst`     | Extract the world into a frame packet                                                   |
| `RenderLast`      | Sample relative-transform dirtiness                                                     |

Plugins install optional rows such as physics, animation, text, and particles.
Every world installs the builtin rows.

Extraction runs in `RenderFirst`. A system that changes what the current frame
draws must run before extraction. `PostUpdate` provides the last general phase
for that work.

A change made after extraction draws one frame late rather than never. Spawning,
despawning, and writing a component the renderer draws from all reach the
instance buffer on the next frame's extraction, even though the frame's dirty
marks are cleared in between. Latency is the whole of the cost, so a system that
has to run in `Render`, `PostRender`, `RenderLast`, or `Last` is free to write;
one that needs the current frame to show its change still belongs earlier.

GPU submission does not run as a system. After `world:update` returns,
`Application` acquires a frame, calls `Renderer:render`, and submits it.
`Render`, `PostRender`, and `RenderLast` remain available to game systems even
though the renderer itself does not submit there.

## Fixed and presentation clocks

Fixed phases receive the configured timestep as `dt`. `world:update` consumes
accumulated time in whole steps and caps one frame at ten steps, so a long
stall cannot create an unbounded catch-up loop.

Variable phases receive the frame `dt`. Use them for presentation work that
should follow display rate rather than simulation rate.

`world:getFixedTiming()` returns the timestep, the unconsumed accumulator, and
an interpolation alpha clamped to `[0, 1]`:

```teal
local timestep, accumulator, alpha = world:getFixedTiming()
```

`PreviousTransform2D` lets the renderer interpolate an entity between its last
two fixed poses. `tecs.SnapshotTransforms` copies the current pose in
`FixedFirst` before simulation changes it.

`world:fixedStepCount()` counts completed fixed steps. The fixed clock and its
count advance even when no fixed system exists or callers disable the fixed
group.

## System placement

Systems within one phase follow insertion order unless `before` or `after`
names another system. The engine table above supplies the names and boundaries
that game plugins commonly order around; [Systems](/modules/ecs/systems) covers those
constraints.

Concrete phases expose `position` for inspection. The world assigns it, and
callers must treat it as read-only. Groups expose a read-only `children` tree
and have no position. Select phases by object instead of storing numeric
positions.

A custom phase must enter the world's pipeline before a system can use it:

```teal
world:registerPhase(MyPhase)
world:addSystem({
    name = "game.CustomStep",
    phase = MyPhase,
    run = customStep,
})
```

`registerPhase` assigns a missing position and enables the phase. Registration
rejects an invalid phase object, and system registration rejects a phase that
the pipeline does not know.

## Disabling phases

Disabling a group also disables its descendants:

```teal
world:disablePhase(tecs.ecs.phases.FixedUpdateGroup)
world:enablePhase(tecs.ecs.phases.FixedUpdateGroup)
```

Disabling `FixedUpdateGroup` stops its systems but not the fixed clock.

Disabling `RenderGroup` also stops `RenderFirst`, so extraction stops updating
the frame packet. GPU submission still draws the last packet because it runs
outside the phase tree. To pause gameplay while presentation continues, use
the [state stack](/modules/ecs/states) and logic
[queries](/modules/ecs/queries/#paused-entities).

## Direct phase execution

`world:runPhase(phase, dt)` dispatches one phase or group immediately:

```teal
world:runPhase(tecs.ecs.phases.RenderGroup, dt)
```

It honors disabled state, including disabled ancestors. Re-enable a phase
before calling it directly.

Unlike `world:update`, `runPhase` neither unwinds existing deferred scopes nor
clears dirty bits afterwards. That contract supports custom loops that run
parts of the phase tree on separate ticks.
