> Cinder 0.1.0 documentation: ADR-0011: Keep the Mojo archetype ECS (benchmarked against flecs)
> Canonical page: https://cinder.ash10.dev/docs/adr/0011-ecs-mojo-vs-flecs/ · Source: `docs/adr/0011-ecs-mojo-vs-flecs.md` in the engine repository (engine commit 47c9352, imported 2026-11-09)
> Index of every page for agents: https://cinder.ash10.dev/llms.txt

# ADR-0011: Keep the Mojo archetype ECS (benchmarked against flecs)

Status: accepted, 2026-11-08.

## Context

The brief requires an archetype ECS with typed queries, change detection, command buffers,
hierarchy and access-scheduled systems, and asks for a benchmark against flecs through its iterator
API: keep the Mojo ECS only if it is within 10% of flecs or faster.

## Benchmark

`bench/ecs_bench.mojo` (run with `pixi run mojo build -I . bench/ecs_bench.mojo`, then
`ecs_bench 100000 100`): 100,000 entities with `Position` and `Velocity` (8 bytes each), half of
them with a third component so both worlds hold two archetypes/tables; 100 frames of
`position += velocity * dt` over a two-component query. flecs 4.2.0 is driven through
`ecs_query_iter`/`ecs_query_next`/`ecs_field_w_size` (no callbacks), both sides from the same Mojo
process on the same core.

Container on the Windows host (lavapipe machine, Ubuntu 24.04, Mojo 1.2.0), three runs:

| Run | Mojo ns / entity-iteration | flecs ns / entity-iteration | ratio |
|---|---|---|---|
| 1 | 0.758 | 0.829 | 0.91 |
| 2 | 0.746 | 0.750 | 1.00 |
| 3 | 0.728 | 0.744 | 0.98 |

Two changes made the difference from the first measurement (1.13 ns, ratio 1.55):

1. Column stride: elements were padded to 16-byte blocks, halving cache density for 8-byte
   components. The stride is now the component's size (a struct's size is a multiple of its
   alignment), while column bases stay 16-byte aligned for SIMD components.
2. Typed column access: `Chunk.column[T]()` returns a tight typed pointer indexed with
   `[unsafe_offset=row]`, which the compiler vectorises like the flecs loop; byte-offset stepping
   with a runtime stride did not.

## Decision

Keep the Mojo ECS (`cinder/ecs`). It is within the 10% band (and slightly faster in every run), it
is the only implementation whose queries are typed at compile time with Mojo parameters, and its
scheduler integrates directly with the Mojo job system. flecs stays vendored and linked for the
benchmark so the comparison can be rerun whenever the ECS changes.

## Consequences

- `bench/ecs_bench.mojo` is part of the benchmark suite; a regression above the band reopens this
  decision.
- Components are plain Mojo structs with a unique `component_id()`; heap-owning components are
  supported through the type-erased move/destroy functions recorded at registration.
- flecs' features the Mojo ECS does not have (relationships, reflection, the explorer) are not
  needed by the brief; the debug UI (Phase 7) inspects entities through the Mojo world directly.
