AR
AgentRuss
ECS update pipeline showing input, controller, gravity, movement, collision, camera, combat, UI, and rendering systems in order.
AgentRuss Guide

ECS System Order Explained: Why Update Order Matters

Written by
AgentRuss
Published

One of the easiest ECS problems to overlook is system order.

You can build perfectly reasonable components and perfectly reasonable systems, yet still end up with broken movement, one-frame delays, incorrect collisions, jittery cameras, or UI that always seems slightly behind.

The reason is simple:

Systems may be separated by responsibility, but they still share and modify the same world state.

If one system expects another system to update some data first, the order in which those systems run becomes part of your game logic.

In this guide, we'll look at why system order matters, how to reason about dependencies between systems, and how to build a predictable ECS update pipeline for things like input, movement, physics, combat, cameras, UI, and rendering.


The Basic Problem

Imagine we have three systems:

  • PlayerControllerSystem
  • GravitySystem
  • MovementSystem

They all work with an entity's velocity or position.

The controller changes horizontal velocity.

Gravity changes vertical velocity.

Movement applies velocity to position.

A sensible order is:

JavaScript
1PlayerControllerSystem
2
3GravitySystem
4
5MovementSystem

That gives us:

  1. player input changes velocity
  2. gravity changes velocity
  3. movement uses the final velocity

Now imagine this instead:

JavaScript
1MovementSystem
2
3PlayerControllerSystem
4
5GravitySystem

The systems themselves have not changed.

But movement now happens before the controller and gravity update velocity.

The result is that position uses last frame's velocity.

That can create a subtle one-frame delay.


System Order Is Really Data Order

A useful way to think about system order is not:

Which system is most important?

Instead ask:

Which data must exist before another system can use it?

For example:

JavaScript
1InputSystem

produces input state.

Then:

JavaScript
1PlayerControllerSystem

reads that input and modifies velocity.

Then:

JavaScript
1MovementSystem

reads velocity and modifies position.

So the dependency is really:

JavaScript
1Input data
2
3Velocity
4
5Position

The systems simply represent the stages that transform that data.

This is why system order can often be understood as a data pipeline.


A Simple Player Movement Pipeline

Let's imagine a player entity has:

Input, Velocity, Transform, Grounded, and ControllerSettings.

A basic movement frame might look like this:

JavaScript
1InputSystem
2
3PlayerControllerSystem
4
5GravitySystem
6
7MovementSystem
8
9CollisionSystem

Each stage has a clear responsibility.

InputSystem

Captures raw input:

  • W
  • A
  • S
  • D
  • jump
  • sprint
  • attack
  • interact

It should usually run early because many other systems depend on input state.

PlayerControllerSystem

Interprets that input as gameplay intent.

For example:

JavaScript
1velocity.x = moveDirection.x * moveSpeed
2velocity.z = moveDirection.z * moveSpeed

It may also decide whether the player is trying to jump.

GravitySystem

Updates vertical velocity:

JavaScript
1velocity.y += gravity * deltaTime

MovementSystem

Applies velocity:

JavaScript
1transform.position.x += velocity.x * deltaTime
2transform.position.y += velocity.y * deltaTime
3transform.position.z += velocity.z * deltaTime

CollisionSystem

Checks whether the resulting position intersects the world and corrects it if necessary.

The important thing is that each system receives data in the state it expects.


What Happens If Collision Runs Too Early?

Suppose collision runs before movement:

JavaScript
1CollisionSystem
2
3MovementSystem

Collision checks the entity's current position.

Everything looks valid.

Then MovementSystem moves the entity directly into a wall.

No system runs afterward to resolve that penetration.

The entity may remain inside the wall until the next frame.

A better order is:

JavaScript
1MovementSystem
2
3CollisionSystem

Movement proposes the new position.

Collision validates and corrects it.

This gives us a useful general pattern:

JavaScript
1Generate movement
2
3Apply movement
4
5Resolve movement

Simulation Before Presentation

One of the most useful high-level rules is:

Update gameplay state before updating presentation.

Presentation systems include things such as:

  • cameras
  • health bars
  • damage numbers
  • target indicators
  • UI panels
  • rendering

Those systems should normally observe the final state for the frame.

For example, consider:

JavaScript
1MovementSystem
2CameraSystem

The player moves first.

Then the camera follows the player's new position.

That makes sense.

If we reverse them:

JavaScript
1CameraSystem
2MovementSystem

the camera follows the player's previous position.

The player then moves afterward.

That can create visible lag or jitter because the camera is always one simulation step behind.


Camera Order Matters More Than It Seems

Camera systems often depend on several earlier stages.

A third-person camera may need:

  • the player's corrected position
  • the player's rotation
  • the current control mode
  • the selected target
  • camera collision information

A reasonable pipeline might be:

JavaScript
1Input
2
3Player Controller
4
5Movement
6
7Player Collision
8
9Camera
10
11Camera Collision
12
13Render

First, the player reaches a valid position.

Then the camera calculates where it wants to be.

Then camera collision makes sure the camera does not clip through walls.

Finally, the renderer draws the scene.

If CameraCollisionSystem ran before CameraSystem, it would be trying to correct a camera position that has not yet been calculated for the current frame.


Rendering Should Usually Be Last

A render system should normally be near the end of the frame.

Why?

Because rendering is the final presentation of the game state.

Ideally, the renderer sees:

  • the final player position
  • resolved collisions
  • updated camera
  • updated lights
  • current UI state
  • current target
  • latest combat state

A simplified frame could look like:

JavaScript
1Input
2
3Gameplay
4
5Physics
6
7Camera
8
9UI
10
11Render

If rendering happens halfway through the pipeline, anything updated afterward will not become visible until the next frame.

That can introduce visual inconsistencies.


A More Complete ECS Pipeline

As a game grows, the system list becomes much larger.

A practical pipeline might look something like:

JavaScript
11. Input
22. Gameplay requests
33. Character control
44. Physics and movement
55. Collision
66. Camera
77. Combat and stats
88. Gameplay UI
99. Render

We can expand that further.

JavaScript
1InputSystem
2MouseLookSystem
3
4Gameplay Requests
5
6InteractionSystem
7ItemUseRequestSystem
8AbilityRequestSystem
9
10Character Control
11
12ModeSwitchSystem
13PlayerControllerSystem
14GravitySystem
15MovementSystem
16
17Collision
18
19CapsuleCollisionSystem
20AABBCollisionSystem
21
22Camera
23
24CameraSystem
25CameraCollisionSystem
26LightSystem
27
28Combat / Stats
29
30DamageSystem
31StatsSystem
32DeathSystem
33RespawnSystem
34
35UI
36
37StatsUISystem
38TargetHealthBarUISystem
39FloatingHealthBarSystem
40DamageNumberUISystem
41QuickbarUISystem
42
43Final Presentation
44
45RenderSystem

This is not the one universally correct ECS order.

Different games will need different pipelines.

What matters is that the order reflects the dependencies between systems.


Input Should Usually Be Early

Input is often the starting point for an entire frame.

Imagine the player presses F to attack.

An InputSystem might update:

JavaScript
1input.attackPressed = true

Then an attack-related system can read it.

If attack processing runs before input:

JavaScript
1AttackSystem
2
3InputSystem

then AttackSystem won't see the button press until the next update.

Instead:

JavaScript
1InputSystem
2
3AttackSystem

lets gameplay respond during the same frame.

This principle applies to:

  • movement
  • jumping
  • interaction
  • inventory toggles
  • abilities
  • targeting
  • camera controls

Input and Gameplay Requests Can Be Separate

A useful architecture is to separate raw input from gameplay intent.

For example:

JavaScript
1InputSystem
2
3ItemUseRequestSystem
4
5ItemUseSystem

InputSystem knows that the player pressed slot 1.

ItemUseRequestSystem translates that into:

The player wants to use the item in quickbar slot 1.

ItemUseSystem handles the actual gameplay rules.

This makes the order clear:

JavaScript
1Capture input
2
3Create intent
4
5Resolve gameplay

It also means the gameplay logic does not have to depend directly on keyboard input.

Later, controller input, AI, scripts, or network messages could potentially generate the same gameplay request.


Events Can Also Have Ordering Requirements

Using an EventBus reduces direct system coupling, but it does not eliminate ordering concerns.

Suppose DamageSystem emits:

JavaScript
1EVT_DAMAGE_RESOLVED

and DamageNumberUISystem consumes that event.

Then we need:

JavaScript
1DamageSystem
2
3DamageNumberUISystem

if both operate during the same frame.

If the UI system runs first, the event does not exist yet.

Depending on the EventBus architecture, the event may:

  • be consumed next frame
  • remain queued
  • be missed entirely

This is why event timing should be designed together with system timing.


Same-Frame Events vs Next-Frame Events

There are two useful event patterns.

Same-frame event

A system emits an event and a later system consumes it during the same frame.

JavaScript
1DamageSystem
2 ↓ emit
3EVT_DAMAGE_RESOLVED
4 ↓ consume
5DamageNumberUISystem

This is useful when immediate reaction is expected.

Next-frame event

A system intentionally queues something for the next update.

JavaScript
1System A
2
3emitNext(...)
4
5Next frame
6
7System B

This can help avoid situations where modifying state halfway through a pipeline would cause inconsistent behaviour.

Neither pattern is automatically better.

The important thing is that the behaviour is intentional.


Death and Damage Ordering

Combat is another area where order matters.

Imagine an entity has:

JavaScript
1Health.current = 5

and receives:

JavaScript
110 damage

A sensible flow is:

JavaScript
1Attack
2
3DamageSystem
4
5Health becomes 0
6
7DeathSystem
8
9Dead state added

If DeathSystem runs before DamageSystem, it checks the entity while health is still 5.

The entity survives until the next frame.

That may not be a serious bug, but it introduces unnecessary delay and can complicate other systems.

A cleaner order is:

JavaScript
1Damage resolution
2
3Death detection
4
5Death behaviour

Respawn Must Happen After Death

The same idea applies to respawning.

You usually do not want:

JavaScript
1RespawnSystem
2
3DeathSystem

because the respawn system may evaluate an entity before the death system has marked it as dead.

Instead:

JavaScript
1DamageSystem
2
3DeathSystem
4
5RespawnSystem

creates a clear lifecycle.

Depending on the game's design, respawn may occur many frames later, but the dependency remains:

Death must be established before something can respawn.

Stats Should Be Ready Before Systems Use Them

Suppose you have:

BaseStats, FinalStats, and Equipment.

A StatsSystem calculates:

JavaScript
1Base attack
2+
3Equipment modifiers
4=
5Final attack

Now suppose an attack system uses FinalStats.attack.

Which should run first?

If equipment can change during the frame:

JavaScript
1EquipmentSystem
2
3StatsSystem
4
5AttackSystem

is safer than:

JavaScript
1AttackSystem
2
3StatsSystem

Otherwise, the attack could use stale stats for one frame.

This becomes particularly important when gameplay state can change dynamically through:

  • equipment
  • buffs
  • debuffs
  • status effects
  • temporary abilities
  • level changes

UI Usually Reads State Rather Than Producing It

Most UI systems should observe gameplay state rather than control it directly.

For example:

JavaScript
1StatsSystem
2
3StatsUISystem

The stats system calculates the real values.

Then the UI displays them.

Likewise:

JavaScript
1DamageSystem
2
3Health updated
4
5HealthBarUISystem

The UI should not be responsible for deciding what the player's health actually is.

That separation makes ordering easier to reason about:

JavaScript
1Gameplay first
2Presentation second

There Are Exceptions

System order rules are not absolute.

For example, some UI systems produce gameplay requests.

An inventory UI might detect that the player clicked Equip.

That UI interaction could produce:

JavaScript
1EVT_EQUIP_ITEM_REQUEST

Then gameplay systems resolve the request.

So the pipeline may sometimes look like:

JavaScript
1UI input
2
3Gameplay request
4
5Gameplay resolution
6
7UI refresh

The important distinction is that the UI can request something without becoming the authority that applies the gameplay rule.


Avoid Hidden Dependencies

System order becomes dangerous when dependencies are not obvious.

Suppose SystemB only works correctly if SystemA happened first, but nothing in the code makes that relationship clear.

Six months later, someone rearranges systems and suddenly introduces a bug.

This is why it helps to document the pipeline clearly.

For example:

JavaScript
1world.addSystem(new InputSystem(world))
2world.addSystem(new PlayerControllerSystem(world))
3world.addSystem(new GravitySystem(world))
4world.addSystem(new MovementSystem(world))
5world.addSystem(new CollisionSystem(world))
6world.addSystem(new CameraSystem(world))
7world.addSystem(new RenderSystem(world))

The registration itself documents the execution order.

A short comment around major groups can make it even clearer.


Group Systems by Phase

As the engine grows, it helps to think in phases rather than one giant list.

For example:

JavaScript
1INPUT PHASE
2
3InputSystem
4MouseLookSystem
5
6GAMEPLAY PHASE
7
8InteractionSystem
9ItemUseRequestSystem
10ItemUseSystem
11
12MOVEMENT PHASE
13
14PlayerControllerSystem
15GravitySystem
16MovementSystem
17
18PHYSICS PHASE
19
20CapsuleCollisionSystem
21AABBCollisionSystem
22
23CAMERA PHASE
24
25CameraSystem
26CameraCollisionSystem
27
28COMBAT PHASE
29
30DamageSystem
31StatsSystem
32DeathSystem
33
34UI PHASE
35
36StatsUISystem
37TargetHealthBarUISystem
38DamageNumberUISystem
39
40RENDER PHASE
41
42RenderSystem

This makes the architecture much easier to understand.

You can immediately see both:

  • what each system does
  • roughly when it should run

Don't Create Too Many Phases Too Early

Phases are useful, but you don't need an enormous scheduler for a small project.

A simple array is often enough:

JavaScript
1this.systems = []

and:

JavaScript
1update(deltaTime) {
2 for (const system of this.systems) {
3 system.update(deltaTime)
4 }
5}

Then registration order defines execution order.

That approach is extremely easy to debug.

Only introduce more complicated scheduling when the engine actually needs it.


Fixed Timestep Adds Another Layer

Once physics or deterministic simulation becomes important, you may introduce a fixed timestep.

Instead of updating simulation at whatever FPS the renderer happens to achieve, gameplay might update at a fixed interval.

Conceptually:

JavaScript
1Render Frame
2
3 ├── Fixed Update
4 ├── Fixed Update
5 └── Render

Some frames may perform one simulation update.

Others may perform multiple updates.

Now system order exists at two levels:

  1. the order of systems inside the fixed simulation
  2. the relationship between simulation and rendering

A common structure is:

JavaScript
1Fixed Simulation
2
3Input state
4Controller
5Gravity
6Movement
7Collision
8Combat
9
10
11
12Presentation
13
14Camera
15UI
16Render

Exactly how this is divided depends on the game, but the principle remains the same:

Systems consuming simulation data should see a consistent state.

Multiplayer Makes Order Even More Important

System order also becomes important when preparing an ECS for multiplayer.

Networked games may need stages such as:

JavaScript
1Receive network input
2
3Validate input
4
5Simulation
6
7Resolve gameplay
8
9Create network snapshot
10
11Send state

If snapshots are created before gameplay finishes updating, clients may receive incomplete state.

Deterministic simulation also requires systems to run in a predictable sequence.

That makes an explicit update pipeline valuable even before multiplayer exists.


Common System Order Bugs

Several bugs frequently come from incorrect ordering.

One-frame input delay

Cause:

PlayerControllerSystem runs before InputSystem.

Camera feels like it is lagging behind

Cause:

CameraSystem runs before movement or collision.

Player temporarily clips into walls

Cause:

collision runs before movement instead of after it.

UI shows old values

Cause:

UI renders before gameplay systems calculate the new state.

Death occurs one frame late

Cause:

DeathSystem checks health before DamageSystem modifies it.

Equipment bonuses apply one frame late

Cause:

combat uses FinalStats before StatsSystem recalculates them.

Visual state does not match simulation

Cause:

RenderSystem runs before the simulation is finished.

When a bug feels like the game is one frame behind, system order should be one of the first things you inspect.


How to Decide Where a New System Goes

When adding a system, ask three questions.

What data does this system read?

For example, MovementSystem reads Velocity.

What data does this system write?

It writes Transform.position.

Which systems produce or consume that data?

PlayerControllerSystem may produce velocity.

CollisionSystem may consume the resulting transform.

That tells us:

JavaScript
1PlayerControllerSystem
2
3MovementSystem
4
5CollisionSystem

This simple read/write analysis makes many ordering decisions obvious.


A Useful Dependency Example

Suppose we add:

InputSystem, PlayerControllerSystem, GravitySystem, MovementSystem, CollisionSystem, CameraSystem, and RenderSystem.

We can describe their dependencies like this:

JavaScript
1InputSystem
2writes Input
3
4PlayerControllerSystem
5reads Input
6writes Velocity
7
8GravitySystem
9writes Velocity
10
11MovementSystem
12reads Velocity
13writes Transform
14
15CollisionSystem
16reads/writes Transform
17
18CameraSystem
19reads final Transform
20
21RenderSystem
22reads final world/camera state

That naturally produces:

JavaScript
1Input
2
3Controller
4
5Gravity
6
7Movement
8
9Collision
10
11Camera
12
13Render

The pipeline is not arbitrary.

It follows the flow of data.


A Practical ECS Pipeline

For a larger third-person RPG-style ECS, a practical structure could be:

JavaScript
1INPUT
2InputSystem
3MouseLookSystem
4
5
6
7REQUESTS / INTERACTION
8InteractionSystem
9ItemUseRequestSystem
10AbilityRequestSystem
11
12
13
14CHARACTER CONTROL
15ModeSwitchSystem
16PlayerControllerSystem
17GravitySystem
18MovementSystem
19
20
21
22PHYSICS
23CapsuleVsAABBCollisionSystem
24AABBCollisionSystem
25
26
27
28CAMERA
29CameraSystem
30CameraCollisionSystem
31LightSystem
32
33
34
35COMBAT / GAMEPLAY
36DamageSystem
37StatsSystem
38DeathSystem
39RespawnSystem
40
41
42
43TARGETING / FEEDBACK
44TargetingSystem
45HitFlashSystem
46DamageNumberUISystem
47
48
49
50UI
51InventoryUISystem
52StatsUISystem
53QuickbarUISystem
54TargetHealthBarUISystem
55FloatingHealthBarSystem
56
57
58
59RENDER
60RenderSystem

The exact order within some groups can change depending on implementation.

For example, targeting may need to run earlier if combat directly depends on the selected target during the same frame.

What matters is that those dependencies are intentional.


Keep the Pipeline Easy to Read

A system pipeline should not require archaeology to understand.

If you open the engine bootstrap code, it should be reasonably obvious what happens first and what happens last.

That makes debugging much easier.

When something goes wrong, you can ask:

Is this system receiving the state from before or after another system runs?

Often, simply printing the system sequence reveals the issue.


Avoid Solving Every Dependency With Direct Calls

One temptation is to avoid ordering issues by calling systems directly.

For example:

JavaScript
1movementSystem.update()
2collisionSystem.update()
3cameraSystem.update()

inside another system.

That creates tight coupling.

Now one system is effectively managing the others.

A cleaner approach is usually to let the world own the pipeline:

JavaScript
1world.addSystem(new MovementSystem(world))
2world.addSystem(new CollisionSystem(world))
3world.addSystem(new CameraSystem(world))

Each system remains independent.

The world determines execution order.


A Simple Rule of Thumb

When you're unsure how to order systems, think:

JavaScript
1CAPTURE
2
3INTERPRET
4
5SIMULATE
6
7RESOLVE
8
9PRESENT

For example:

JavaScript
1Capture
2InputSystem
3
4Interpret
5PlayerControllerSystem
6
7Simulate
8GravitySystem
9MovementSystem
10
11Resolve
12CollisionSystem
13DamageSystem
14DeathSystem
15
16Present
17CameraSystem
18UISystems
19RenderSystem

Not every game fits this perfectly, but it is a useful starting point.


System Order Is Part of Your Architecture

It is easy to think of ECS architecture as only:

  • entities
  • components
  • systems
  • queries
  • events

But execution order is also part of the architecture.

Two projects can contain the exact same systems and components yet behave differently because their pipelines are different.

That makes system registration something worth designing deliberately rather than treating as boilerplate.


Conclusion

Separating gameplay into systems is one of the strengths of ECS, but those systems do not operate in isolation.

They form a pipeline.

One system may create the data that another system consumes later in the same frame.

That means update order directly affects gameplay behaviour.

A useful way to reason about the pipeline is:

JavaScript
1What does this system read?
2What does this system write?
3Who needs that data next?

From there, many ordering decisions become straightforward.

For a typical game, the high-level flow often becomes something like:

JavaScript
1Input
2
3Gameplay Requests
4
5Character Control
6
7Movement
8
9Collision
10
11Camera
12
13Combat / Stats
14
15UI
16
17Render

The exact systems will change as the game grows, but the principle remains the same:

Produce state before another system consumes it, resolve gameplay before presentation, and make dependencies explicit.

A well-designed ECS pipeline makes behaviour easier to predict, bugs easier to diagnose, and future systems easier to integrate.

And as the engine grows into physics, abilities, status effects, AI, multiplayer, and more advanced combat, having a clear update order becomes increasingly valuable.