Skip to main content
In Euca the entire game world is a flat table. Each row is an entity — a stable id. Each column is a component — a group of typed fields. An entity “has” a component when its row has a value in that column. There is no object graph to walk and no scene tree to reverse-engineer: an agent reads the world by reading the table, and changes the world by writing to it. This is the Entity–Component–System (ECS) model. Entities are ids, components are the data, and systems are the logic that runs over the table each tick — physics, combat, and rules.

Reading the table

POST /observe returns the whole table as JSON: a tick, an entity count, and one object per entity carrying its components.
# Build a tiny world: reset, spawn a Kinematic mover on team 1 and a stationary
# target on team 2, step 5 ticks, then read the table back.
curl -s -X POST http://localhost:3917/observe
{
  "tick": 5,
  "entity_count": 2,
  "entities": [
    {
      "id": 0, "generation": 1,
      "transform": { "position": [0.083333336, 1.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0] },
      "velocity": { "linear": [1.0, 0.0, 0.0], "angular": [0.0, 0.0, 0.0] },
      "physics_body": "Kinematic",
      "health": [100.0, 100.0],
      "team": 1
    },
    {
      "id": 1, "generation": 0,
      "transform": { "position": [5.0, 1.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0] },
      "health": [60.0, 60.0],
      "team": 2
    }
  ]
}
Read literally as a table, that is two rows and a handful of typed columns:
entitytransform.positionvelocity.linearphysics_bodyhealth [cur,max]team
0 (gen 1)[0.083333336, 1.0, 0.0][1.0, 0.0, 0.0]Kinematic[100, 100]1
1 (gen 0)[5.0, 1.0, 0.0][60, 60]2
Entity 0 has a velocity and a physics_body, so a system moved it along +x; entity 1 has neither, so it stayed put. Nothing about the world is hidden behind accessors — what you read is the state.

Entities are generational ids

An entity id has two parts: an index and a generation. When an entity is despawned its index can be reused, but the generation bumps — so a stale reference to the old occupant is detected rather than silently pointing at a new one. Thread the id and generation returned by /spawn through later calls.
The /observe view is a flattened, readable projection of the underlying components — for example health is reported as [current, max]. It is built from explicit typed reads, not reflection: the agent gets plain data, not an opaque handle.

Editing the table

Because the world is data, every change is a plain write — no editor, no scripting host:

Add a row

POST /spawn creates an entity with the components you give it.

Edit a cell

POST /entities/{id}/components adds or updates components on an existing entity.

Remove a row

POST /despawn removes an entity by id + generation.

Add logic

POST /rule/create adds a rule — logic is data too.

The same table, forked and replayed

Because the world is a self-contained table of plain data, the engine can copy it to run a counterfactual, and hash it into a stable digest to verify a replay is exact. The canonical, fully-typed form of this table — used by the evaluation track as the answer key — sorts entities by id and components by name so two runs can be compared byte for byte.

Run the loop yourself

Build this table, step it, and fork it in the Quickstart.