AR
AgentRuss
ECS game engine project structure showing separate folders for core ECS code, components, systems, events, factories, and game data.
AgentRuss Guide

How to Structure an ECS Game Engine Project

Written by
AgentRuss
Published

An Entity Component System can start with only a handful of files.

You might begin with:

  • World.js
  • Component.js
  • System.js
  • one or two components
  • one movement system
  • main.js

That is enough to understand the architecture.

But a real game does not stay that small.

Soon you add input, gravity, collision, cameras, interaction, inventory, equipment, stats, combat, targeting, health bars, damage numbers, status effects, abilities, death, respawning, UI, and eventually networking.

At that point, the problem is no longer:

How do I build an ECS?

The problem becomes:

How do I organize all of this without turning the project into a giant folder full of unrelated files?

A good project structure does not make the game better by itself, but it makes the architecture easier to understand, maintain, debug, and extend.

In this guide, we'll look at a practical way to structure an ECS game engine as it grows from a small experiment into a larger game project.


Start by Separating the ECS Core From the Game

One of the most useful boundaries is between:

  • reusable ECS infrastructure
  • game-specific code

The ECS core should not know what a sword, player, potion, enemy, health bar, or checkpoint is.

It should only understand concepts such as:

  • entities
  • components
  • systems
  • queries
  • events
  • world updates

That means a folder such as src/ecs can remain small and generic.

For example:

  • src/ecs/World.js
  • src/ecs/Component.js
  • src/ecs/System.js
  • src/ecs/Query.js
  • src/ecs/EventBus.js

These files form the foundation of the architecture.

Everything else builds on top of them.


What Belongs in the ECS Core?

The ECS core should contain infrastructure that could theoretically be reused in another game.

World

World.js might be responsible for:

  • creating entities
  • destroying entities
  • adding components
  • removing components
  • retrieving components
  • registering systems
  • managing queries
  • updating systems
  • owning the Event Bus

It should not contain code such as:

JavaScript
1if (itemId === 'healthPotion') {
2 // heal player
3}

That is gameplay logic.

The world should not care what a health potion is.


Component Base Class

If your architecture uses class-based components, a base component can live in the ECS core:

JavaScript
1export class Component {}

Game components can then extend it:

JavaScript
1import { Component } from '../ecs/Component.js'
2
3export class Health extends Component {
4 constructor(max = 100) {
5 super()
6
7 this.current = max
8 this.max = max
9 }
10}

The ECS core knows what a component is.

It does not need to know what Health means.


System Base Class

The same applies to systems.

A reusable base class might look like:

JavaScript
1export class System {
2 constructor(world) {
3 this.world = world
4 }
5
6 update(deltaTime) {}
7}

The ECS core understands how a system participates in the update loop.

Specific behaviour belongs elsewhere.


Queries Belong in the Core

Queries are part of the ECS infrastructure because every gameplay domain may use them.

For example:

JavaScript
1world.createQuery([
2 Transform,
3 Velocity,
4])

Movement may use queries.

Combat may use queries.

UI may use queries.

Interaction may use queries.

The query implementation itself does not need to know anything about those features.

That makes Query.js a natural part of src/ecs.


EventBus Belongs in the Core Too

An Event Bus is another reusable infrastructure piece.

It might provide operations such as:

JavaScript
1events.emit(
2 EVT_DAMAGE_RESOLVED,
3 payload,
4)

or:

JavaScript
1events.emitNext(
2 EVT_RESPAWN,
3 payload,
4)

The Event Bus manages queues and timing.

It should not define what damage, healing, inventory, or combat actually mean.

That distinction keeps the core reusable.


Put Runtime State in Components

The next major folder is usually src/components.

This contains the data attached to entities.

A growing game might contain components such as:

  • Transform
  • Velocity
  • Mesh
  • Health
  • Input
  • PlayerControlled
  • ControllerSettings
  • Grounded
  • CapsuleCollider
  • ColliderAABB
  • StaticBody
  • DynamicBody
  • ControlMode
  • CameraRig
  • CameraFollow
  • CameraCollision
  • Inventory
  • Equipment
  • BaseStats
  • FinalStats
  • Quickbar
  • Interactable
  • Checkpoint
  • Dead
  • Pickup
  • HealthBarVisibility

These files describe the current state of entities.

They should remain focused on data rather than accumulating gameplay rules.


Keep Component Files Small

A component file should usually be one of the simplest files in the project.

For example:

JavaScript
1import { Component } from '../ecs/Component.js'
2
3export class Equipment extends Component {
4 constructor() {
5 super()
6
7 this.weapon = null
8 this.head = null
9 this.body = null
10 this.accessory = null
11 }
12}

If Equipment.js begins importing:

  • inventory systems
  • item databases
  • UI managers
  • damage systems
  • event buses

that is a warning sign.

The component is probably becoming responsible for behaviour that belongs somewhere else.


Systems Contain the Game Behaviour

The src/systems folder is where most runtime behaviour lives.

A larger game may eventually contain systems such as:

  • InputSystem
  • MouseLookSystem
  • PlayerControllerSystem
  • GravitySystem
  • MovementSystem
  • CapsuleVsAABBCollisionSystem
  • AABBCollisionSystem
  • CameraSystem
  • CameraCollisionSystem
  • InteractionSystem
  • InteractionResolveSystem
  • InventorySystem
  • ItemUseRequestSystem
  • ItemUseSystem
  • HealSystem
  • StatsSystem
  • DamageSystem
  • DeathSystem
  • RespawnSystem
  • TargetingSystem
  • HitFlashSystem
  • RenderSystem

At first, keeping all systems in one folder is perfectly reasonable.

It makes them easy to find and keeps the architecture simple.


Don't Organize Too Early

When a project only has ten systems, this structure is fine:

  • src/systems/InputSystem.js
  • src/systems/MovementSystem.js
  • src/systems/DamageSystem.js
  • src/systems/RenderSystem.js

You do not need to immediately create twenty subfolders.

Over-organizing a tiny project can make navigation harder rather than easier.

A useful rule is:

Add structure when the current structure becomes uncomfortable.

Not before.


Split Systems Into Domains When the Folder Becomes Crowded

Once the system count becomes large, domain folders can help.

For example, src/systems could eventually contain groups such as:

  • input
  • movement
  • physics
  • camera
  • interaction
  • inventory
  • combat
  • stats
  • targeting
  • ui
  • rendering

Then the movement domain might contain:

  • PlayerControllerSystem.js
  • GravitySystem.js
  • MovementSystem.js

Combat might contain:

  • DamageSystem.js
  • DeathSystem.js
  • HealSystem.js

UI might contain:

  • StatsUISystem.js
  • QuickbarUISystem.js
  • DamageNumberUISystem.js
  • TargetHealthBarUISystem.js

The important thing is that the folders reflect real responsibilities rather than arbitrary file grouping.


Keep UI Systems Separate by Responsibility

UI can become one of the largest parts of a game.

It is tempting to create one giant:

UISystem.js

that handles everything.

That usually becomes difficult to maintain.

A better approach is to keep UI responsibilities separate.

For example:

  • InventoryToggleSystem
  • InventoryUISystem
  • QuickbarUISystem
  • StatsUISystem
  • TargetHealthBarUISystem
  • FloatingHealthBarSystem
  • DamageNumberUISystem

These systems may all affect the UI, but they do different jobs.

One may control visibility.

Another renders inventory data.

Another displays combat feedback.

Another updates stats.

This makes each system easier to reason about.


UI Does Not Need to Become a Separate ECS

Keeping UI systems separate does not mean building another ECS specifically for the UI.

A DOM-based game UI can still react to ECS state.

For example, StatsUISystem might query:

  • PlayerControlled
  • FinalStats

Then render:

  • Attack
  • Defense

The ECS remains the gameplay source of truth.

The UI observes that state.


Events Deserve Their Own Folder

As the game grows, event names should not be scattered as strings throughout the codebase.

A folder such as src/events gives them a clear home.

For example:

src/events/EventTypes.js

might contain:

JavaScript
1export const EVT_DAMAGE_RESOLVED =
2 'damage-resolved'
3
4export const EVT_STATS_CHANGED =
5 'stats-changed'
6
7export const EVT_PICKUP =
8 'pickup'
9
10export const EVT_USE_ITEM_REQUEST =
11 'use-item-request'

Now systems import shared event identifiers instead of recreating strings.

This gives the project one source of truth for event names.


Events and the Event Bus Are Different Things

It is useful to distinguish between:

src/ecs/EventBus.js

and:

src/events/EventTypes.js

The Event Bus is infrastructure.

It knows how to:

  • queue events
  • consume events
  • move next-frame events
  • manage event lifetime

EventTypes.js is game-specific.

It knows that the game has events such as:

  • damage resolved
  • pickup
  • use-item request
  • stats changed

That separation keeps the reusable engine layer independent from the game.


Use Factories to Compose Entities

Entity creation can become complicated very quickly.

Creating a player might require:

  • Transform
  • Velocity
  • Mesh
  • Health
  • Input
  • PlayerControlled
  • ControllerSettings
  • Grounded
  • CapsuleCollider
  • Inventory
  • Equipment
  • BaseStats
  • FinalStats
  • Quickbar

Putting all of that directly into main.js makes the bootstrap file enormous.

Factories solve that problem.

For example:

src/game/PlayerFactory.js

could be responsible for creating and composing the player.

A simplified version might look like:

JavaScript
1export function createPlayer(
2 world,
3 options = {},
4) {
5 const entity =
6 world.createEntity()
7
8 world.addComponent(
9 entity,
10 new Transform(0, 1, 0),
11 )
12
13 world.addComponent(
14 entity,
15 new Velocity(),
16 )
17
18 world.addComponent(
19 entity,
20 new Health(100),
21 )
22
23 world.addComponent(
24 entity,
25 new PlayerControlled(),
26 )
27
28 return entity
29}

The factory knows how a player is assembled.

The player entity itself remains only an ID.


Factories Are Composition Recipes

A useful way to think about factories is:

A factory describes how to assemble a particular game entity.

For example:

PlayerFactory

might compose the player.

DummyFactory

might compose a test enemy.

GroundFactory

might create ground geometry and collision.

WallFactory

might create static walls.

CameraFactory

might create the camera entity.

TestLevelFactory

might assemble a test scene.

Factories let the rest of the project say:

JavaScript
1const player =
2 createPlayer(world)

instead of repeating a large component setup everywhere.


Factories Should Not Become Gameplay Systems

A factory should usually assemble initial state.

It should not remain responsible for the entity after creation.

For example, PlayerFactory can create:

  • health
  • inventory
  • controller settings

But it should not later:

  • move the player
  • apply damage
  • process input
  • update inventory
  • respawn the player

Those behaviours belong in systems.

The factory builds the entity.

Systems operate on it afterward.


Keep Static Definitions Separate From Runtime State

Some data describes reusable game content rather than entity state.

Examples include:

  • item definitions
  • ability definitions
  • loot tables
  • enemy templates
  • weapon modifiers
  • status-effect definitions

Those do not necessarily belong in components.

For example:

JavaScript
1export const ItemDatabase = {
2 bronzeSword: {
3 name: 'Bronze Sword',
4
5 modifiers: {
6 attack: 5,
7 },
8 },
9
10 healthPotion: {
11 name: 'Health Potion',
12
13 type: 'heal',
14 amount: 25,
15 },
16}

An Inventory component may store the item ID.

A system can look up the reusable definition.

This keeps static configuration separate from mutable runtime state.


Give Definitions a Clear Home

As the project grows, static definitions can live somewhere such as:

  • src/data
  • src/definitions
  • src/game/data

The exact folder name is less important than the boundary.

The important distinction is:

  • components describe current entity state
  • definitions describe reusable game configuration
  • systems interpret those values

Keep the Bootstrap File Focused

Eventually main.js becomes the place where the engine is assembled.

That file should not contain the implementation of every system.

Instead, it should mainly:

  • create the world
  • create the scene
  • create initial entities
  • register systems
  • establish system order
  • start the game loop

A simplified setup could look like:

JavaScript
1const world = new World()
2
3createTestLevel(world)
4createPlayer(world)
5createCamera(world)
6
7world.addSystem(
8 new InputSystem(world),
9)
10
11world.addSystem(
12 new PlayerControllerSystem(world),
13)
14
15world.addSystem(
16 new GravitySystem(world),
17)
18
19world.addSystem(
20 new MovementSystem(world),
21)
22
23world.addSystem(
24 new CollisionSystem(world),
25)
26
27world.addSystem(
28 new CameraSystem(world),
29)
30
31world.addSystem(
32 new RenderSystem(world),
33)

The bootstrap shows how the engine is wired together without containing the implementation details.


System Registration Documents the Pipeline

The order in which systems are registered is part of the architecture.

That means main.js or another bootstrap file becomes useful documentation.

Someone reading it can see:

  1. input happens first
  2. control logic follows
  3. movement and physics run
  4. camera updates
  5. UI reacts
  6. rendering happens last

Grouping the registration by phase can make this even clearer.


Consider a Dedicated System Registration Function

As the system list grows, even main.js can become crowded.

At that point, you might extract system setup:

JavaScript
1export function registerSystems(
2 world,
3 dependencies,
4) {
5 world.addSystem(
6 new InputSystem(world),
7 )
8
9 world.addSystem(
10 new PlayerControllerSystem(world),
11 )
12
13 world.addSystem(
14 new GravitySystem(world),
15 )
16
17 world.addSystem(
18 new MovementSystem(world),
19 )
20}

Then main.js remains focused on application startup.

This is optional.

Do it when the registration list becomes difficult to manage.


Keep Engine Dependencies Flowing in One Direction

A useful project rule is to make dependencies flow from generic code toward specific code.

For example:

  • ECS core knows nothing about gameplay
  • components may depend on ECS base classes
  • systems depend on ECS infrastructure and components
  • factories depend on the world and components
  • gameplay code may depend on static definitions
  • bootstrap code assembles everything

The dangerous direction is when the ECS core starts importing game-specific systems or components.

For example, World.js should not import PlayerControlled or DamageSystem.

If it does, the supposedly reusable core now depends on one particular game.


Think in Layers

A useful high-level structure is:

Core ECS

  • World
  • Component
  • System
  • Query
  • EventBus

Game State

  • components

Game Rules

  • systems

Communication

  • event types

Entity Composition

  • factories

Static Content

  • item, ability, enemy, and loot definitions

Application Bootstrap

  • world creation
  • system registration
  • game loop

Each layer has a different responsibility.


Avoid Circular Dependencies

Poor project boundaries often produce circular imports.

For example:

DamageSystem imports Health.

Then Health imports DamageSystem.

Then another component imports both.

That usually means behaviour and data have become too tightly coupled.

Data-focused components help avoid this.

Components generally do not need to import systems.

Systems can import components.

That creates a much cleaner dependency direction.


Components Should Rarely Know About Systems

A simple rule that catches many architectural problems is:

Components should almost never import systems.

For example, this should make you suspicious:

JavaScript
1import { DamageSystem }
2 from '../systems/DamageSystem.js'

inside Health.js.

The component should not need to know which system operates on it.

The relationship should go the other way.

DamageSystem knows it needs Health.

Health does not know DamageSystem exists.


Systems Should Not Directly Own Other Systems Either

Another common coupling problem is:

JavaScript
1class DamageSystem {
2 constructor(
3 world,
4 damageNumberSystem,
5 hitFlashSystem,
6 ) {
7 // ...
8 }
9}

Sometimes direct dependencies are justified.

But when many systems begin owning references to other systems, the architecture becomes difficult to change.

Events can often provide a cleaner boundary.

For example, DamageSystem can emit:

EVT_DAMAGE_RESOLVED

Then:

  • DamageNumberUISystem
  • HitFlashSystem
  • health-bar systems

can react independently.


Put Test Systems Somewhere Obvious

During development, temporary systems are extremely useful.

Examples might include:

  • DamageTestEmitterSystem
  • MeleeAttackTestSystem
  • debug spawning systems
  • collision visualizers
  • stat-debug systems

There is nothing wrong with having development helpers.

The important thing is making them obvious.

A test system should not look like permanent production gameplay by accident.

You could:

  • keep Test in the filename
  • place them under a debug or test folder later
  • clearly group them during system registration

That makes cleanup easier when the project matures.


Debug UI Can Be Separate From Player UI

Debug information may use the same DOM technology as gameplay UI, but it serves a different purpose.

For example:

StatsUISystem

might initially be useful as a development HUD.

Later, the real player interface may show stats somewhere else.

Keeping debugging responsibilities easy to identify prevents temporary tools from becoming permanent architecture accidentally.


Don't Put Everything in utils

A common project smell is a growing folder called:

utils

containing hundreds of unrelated files.

Utility folders are fine for genuinely generic helpers.

But gameplay concepts deserve meaningful homes.

For example:

ItemDatabase.js

is not just a utility.

DamageSystem.js

is not a utility.

PlayerFactory.js

is not a utility.

Good names and folders make architecture visible.


Prefer Domain Names Over Vague Names

Files such as:

  • Manager.js
  • Helper.js
  • Utils.js
  • Handler.js

often become dumping grounds.

Prefer names that explain the responsibility.

For example:

  • InventorySystem
  • StatsSystem
  • TargetingSystem
  • PlayerFactory
  • EventTypes
  • ItemDatabase

The filename itself should help someone understand the architecture.


When Should You Split a Large System?

Folder structure cannot fix a system that owns too many responsibilities.

Suppose InventorySystem eventually handles:

  • pickups
  • item consumption
  • equipment
  • quickbar
  • UI
  • crafting
  • vendors

Moving it into a folder called inventory does not solve the problem.

The responsibilities may need to become separate systems.

For example:

  • InventorySystem
  • ItemUseSystem
  • EquipmentSystem
  • QuickbarSystem
  • InventoryUISystem
  • VendorSystem

Good project structure reflects good responsibility boundaries.

It does not replace them.


When Should You Combine Systems?

The opposite problem is also possible.

You do not need a new system for every tiny operation.

If two pieces of logic:

  • always run together
  • operate on the same data
  • share the same responsibility
  • are unlikely to evolve independently

keeping them together may be simpler.

Project structure should make the code easier to understand, not maximize the number of files.


One Folder Per Feature Is Another Valid Approach

So far, we've mostly discussed organizing by technical type:

  • components
  • systems
  • events
  • game

Another approach is organizing by feature.

For example:

  • combat/
  • inventory/
  • movement/
  • camera/

A combat folder might contain:

  • Health.js
  • DamageSystem.js
  • DeathSystem.js
  • combat events

This can work very well in large projects.

Neither approach is automatically correct.


Type-Based vs Feature-Based Structure

A type-based structure groups similar architectural objects:

  • all components together
  • all systems together
  • all events together

Advantages:

  • easy to learn
  • easy to browse when the project is small
  • architecture is obvious

A feature-based structure groups everything related to one gameplay domain.

Advantages:

  • easier to work on one feature in a large project
  • related files stay close together
  • fewer giant global folders

A practical project can even use a hybrid approach.


Start Type-Based, Move Toward Domains When Needed

For a growing ECS engine, a sensible progression is:

  1. start with ecs, components, systems, events, and game
  2. keep the structure simple while the project is small
  3. introduce subfolders when a category becomes crowded
  4. group by gameplay domain when that improves navigation

You do not need to predict the final folder structure on day one.

Let the project tell you when organization needs to evolve.


Keep Factories Near Game-Specific Composition

Factories usually belong closer to game code than ECS infrastructure.

For example:

  • PlayerFactory
  • DummyFactory
  • GroundFactory
  • WallFactory
  • CameraFactory
  • TestLevelFactory

These are specific to the game being built.

A generic ECS library should not know what any of those things mean.

That is why something like src/game is a useful boundary.


Separate Engine Architecture From Level Content

A level factory can create:

  • ground
  • walls
  • enemies
  • checkpoints
  • pickups
  • lights

But that content should not leak into the ECS infrastructure.

World.js should not know that the test level contains three hostile dummies.

The level creates entities using the world.

The world simply manages those entities afterward.


Static Data Can Eventually Become Authoring Data

At first, game definitions may be JavaScript objects.

Later, they may come from:

  • JSON
  • an editor
  • a database
  • external content files
  • server data

If the ECS core does not depend on where those definitions came from, changing the authoring pipeline becomes much easier.

That is another benefit of keeping data definitions separate from systems.


Structure Helps Future Editor Development

A clear architecture also helps if you eventually build game-engine editor tools.

An editor may need to discover:

  • components
  • entities
  • component fields
  • levels
  • static definitions

If runtime state and behaviour are cleanly separated, it becomes easier to build:

  • scene trees
  • inspectors
  • component editors
  • level creation tools
  • property panels
  • debug visualizers

Good folder structure is not enough by itself, but clean architectural boundaries make tooling much easier.


Structure Helps Multiplayer Too

Multiplayer adds another major domain:

  • network input
  • snapshots
  • replication
  • prediction
  • reconciliation
  • authority

If core gameplay already has clear boundaries, networking can be introduced without rewriting every feature.

For example:

  • components remain state
  • systems remain simulation rules
  • request events represent intent
  • networking transports selected information
  • server-side systems remain authoritative

A project with everything mixed into main.js is much harder to evolve in this direction.


A Practical Project Layout

A growing ECS game might eventually have a structure like:

  • src/ecs
  • core ECS infrastructure
  • src/components
  • runtime entity state
  • src/systems
  • gameplay and presentation behaviour
  • src/events
  • game-specific event definitions
  • src/game
  • entity factories and level creation
  • src/data
  • static item, ability, loot, and enemy definitions
  • src/components/ui
  • optional UI-specific helpers if needed
  • src/debug
  • optional debug and test tools
  • main.js
  • application startup and system registration

This is only one possible structure.

The important part is the responsibility of each area.


A Practical Dependency Direction

A healthy dependency flow might look like this:

ECS core
does not depend on game code.

Components
may depend on basic ECS types.

Systems
depend on ECS infrastructure, components, events, and definitions.

Factories
depend on the world and components to compose entities.

Bootstrap
depends on everything necessary to assemble the application.

Dependencies flow toward the application layer.

The generic layers do not need to know about the specific game built on top of them.


Signs Your Project Structure Needs Work

A few warning signs are worth watching for.

main.js contains thousands of lines

Entity construction or gameplay logic probably needs to move elsewhere.

Components import systems

State and behaviour are becoming coupled.

One system imports ten other systems

Direct system dependencies may be getting out of control.

Every file lives in systems

It may be time for domain subfolders.

Everything lives in utils

Responsibilities are not being named clearly.

Factories contain update logic

Entity creation and runtime behaviour are becoming mixed.

Item definitions are duplicated everywhere

Static configuration needs a shared home.

You cannot find where an event is defined

Event names probably need central organization.


Don't Refactor Structure Constantly

There is also a danger in spending too much time reorganizing folders.

A project can become stuck in endless architecture refactors.

If the current structure is easy to understand and easy to work with, it may already be good enough.

Refactor when you have a real problem:

  • navigation is becoming difficult
  • responsibilities are unclear
  • circular dependencies are appearing
  • files are becoming enormous
  • multiple features are interfering with each other

Structure should support development rather than replace it.


A Useful Rule for New Files

Whenever you add a new piece of functionality, ask what kind of thing it is.

ECS infrastructure?

Put it in the ECS core.

Examples:

  • query implementation
  • Event Bus
  • world management

Runtime state?

Create a component.

Examples:

  • health
  • inventory
  • velocity
  • status effects

Behaviour?

Create or extend a system.

Examples:

  • damage resolution
  • movement
  • targeting

Something that happened?

Define an event.

Examples:

  • damage resolved
  • item picked up
  • stats changed

A recipe for creating an entity?

Use a factory.

Examples:

  • player
  • enemy
  • camera
  • checkpoint

Reusable static configuration?

Use a definition or data file.

Examples:

  • item database
  • abilities
  • loot tables

That simple classification keeps many files from ending up in the wrong layer.


The Bigger Architectural Picture

A well-structured ECS project has a clear flow.

The ECS core provides the machinery.

Components provide runtime state.

Factories compose entities from components.

Systems process those entities through queries.

Events communicate meaningful requests and outcomes.

Static definitions provide reusable configuration.

The bootstrap establishes system order and starts the simulation.

Each part has a clear responsibility.


Conclusion

A good ECS project structure does not need to be complicated.

A strong starting point is simply:

  • ecs for reusable infrastructure
  • components for state
  • systems for behaviour
  • events for communication contracts
  • game for factories and level composition
  • a dedicated location for static definitions
  • a small bootstrap responsible for assembling everything

The most important rule is not the exact folder names.

It is the direction of responsibility.

The ECS core should remain generic.

Components should remain data-focused.

Systems should own gameplay behaviour.

Factories should compose entities.

Events should describe meaningful requests and outcomes.

Definitions should describe reusable static content.

And the bootstrap should make the update pipeline easy to understand.

As the project grows, folders can evolve into domains such as combat, movement, inventory, UI, and networking.

But those changes should happen because the project needs them, not because a perfect folder structure was designed before the game existed.

Good organization should make the architecture easier to see.

When you can open a project and quickly understand where state lives, where behaviour lives, how entities are created, how systems communicate, and where the update pipeline is assembled, the project becomes much easier to keep growing.