> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eucaengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Particles

> Create a particle emitter, list it, and read its particle count — and learn why the count stays 0 unless the project schedules the particle simulation, and where the CPU sim actually advances.

Euca's particle system (`euca-particle`) is a CPU emitter pool: each `ParticleEmitter` spawns particles at a
rate, integrates velocity and gravity, ages them, and interpolates color over life — then emits billboard
quads a GPU host draws. Two pieces have to be present for counts to climb: the emitter *entity* (any server
can create one) **and** the two simulation systems in the step schedule (`emit_particles_system` +
`particle_update_system`). The headless host runs the **project's** schedule — and the example MOBA project
served headless doesn't include those two systems — so this page shows you the honest result against it: you
can create and list emitters, but `particle_count` stays 0 unless the project (or a render host) wires the sim
in. Every command is real; output is from a headless host (entity ids shown illustratively).

## Create an emitter and read its count

**1. Create an emitter.** `POST /particle/create` spawns a `ParticleEmitter` entity. The fields are
`position` `[x,y,z]` (default origin), `rate` particles/sec (default 50), `lifetime` seconds (default 2),
and **`max`** — the particle cap (default 1000). The cap field is `max`, not `max_particles`:

```bash theme={null}
curl -s -X POST http://localhost:3917/particle/create -H 'Content-Type: application/json' \
  -d '{"position":[0.0,2.0,0.0],"rate":50.0,"lifetime":2.0,"max":1000}'
# {"entity_id":2,"ok":true}
```

**2. List it.** `GET /particle/list` returns every entity carrying a `ParticleEmitter`, each with its
`active` flag, live `particle_count`, and `rate`:

```bash theme={null}
curl -s http://localhost:3917/particle/list
# {"count":1,"emitters":[{"active":true,"entity_id":2,"particle_count":0,"rate":50.0}]}
```

The emitter is `active` at 50/sec — but `particle_count` is 0, which is expected before any step.

**3. Step the simulation, then read again.** This is the honest part. Advancing the headless host (running
the MOBA project) does **not** move the count:

```bash theme={null}
curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":10}'
# {"ticks_advanced":10,"new_tick":11,"entity_count":3}
curl -s http://localhost:3917/particle/list
# {"count":1,"emitters":[{"active":true,"entity_id":2,"particle_count":0,"rate":50.0}]}

curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":30}'
curl -s http://localhost:3917/particle/list
# {"count":1,"emitters":[{"active":true,"entity_id":2,"particle_count":0,"rate":50.0}]}
```

Forty ticks, still 0. The emitter is real and active, but **the MOBA project's step schedule does not
include `emit_particles_system` or `particle_update_system`** — so nothing emits or ages. On a host whose
project schedules both systems (the editor wires them into its schedule), the same emitter accumulates
particles each step: `particle_count` climbs toward `rate × lifetime` (here \~100) and holds as new particles
balance retiring ones. The CPU simulation is real and deterministic — it just isn't scheduled by this
project.

**4. Stop an emitter.** `POST /particle/stop` flips `active` to false; spawning ceases. Existing particles
keep aging out on a host that runs the sim:

```bash theme={null}
curl -s -X POST http://localhost:3917/particle/stop -H 'Content-Type: application/json' -d '{"entity_id":2}'
# {"ok":true,"message":"Stopped particle emitter 2"}

curl -s -X POST http://localhost:3917/particle/stop -H 'Content-Type: application/json' -d '{"entity_id":999}'
# {"ok":false,"message":"Entity 999 not found or not an emitter"}

curl -s http://localhost:3917/particle/list
# {"count":2,"emitters":[{"active":false,"entity_id":2,"particle_count":0,"rate":50.0},
#                        {"active":true,"entity_id":3,"particle_count":0,"rate":20.0}]}
```

`active` flips to false immediately — that part is real headless. The drain-out of existing particles needs
the sim host.

## Behavior and gotchas

The things you only find out by running it:

* **The cap field is `max`, not `max_particles`.** Send `max_particles` and it's ignored; the cap falls back
  to the default 1000. (`rate`, `lifetime`, `position` are the other fields.)
* **`particle_count` stays 0 unless the project schedules the particle systems** — and the MOBA project served
  headless doesn't, so no matter how many ticks you step against it, the count holds at 0. The create/list
  calls work; the simulation just isn't wired into that project's schedule.
* **Counts climb where the project schedules the sim.** The editor registers `emit_particles_system` +
  `particle_update_system`; there `particle_count` rises toward `rate × lifetime` and stabilizes. The sim
  itself is real, seeded, and deterministic — confirmed by the crate's own tests — it's a *scheduling*
  question (which systems the project runs), not a broken simulator.
* **`/particle/stop` works headless for the `active` flag.** It flips `active:false` immediately and listing
  reflects it. Draining existing particles afterward, again, needs the sim host.
* **`/particle/stop` on a non-emitter or missing id returns `ok:false`** with "not found or not an emitter."
* **Rendering the quads always needs a GPU host.** Even where the sim runs (the editor), drawing the billboard
  `ParticleRenderBatch` is a GPU step — the headless host never draws particles, regardless of the project.

## Endpoints

| Method | Path               | Description                                                            |
| ------ | ------------------ | ---------------------------------------------------------------------- |
| `POST` | `/particle/create` | Create an emitter: `position`, `rate`, `lifetime`, **`max`** (the cap) |
| `POST` | `/particle/stop`   | Flip an emitter's `active` to false (`entity_id`)                      |
| `GET`  | `/particle/list`   | Every `ParticleEmitter` with `active`, live `particle_count`, `rate`   |

The emitter is just an entity with a `ParticleEmitter` component — observe it like any other through
[Simulation](/systems/simulation). Appearance never touches the authoritative world state — see
[Determinism](/concepts/determinism).

## Status

* **Shipped** — emitter create/stop/list, and a real CPU simulation (rate-based emission, gravity
  integration, aging, color-over-life, point/sphere/cone shapes, deterministic and seeded) that emits a
  `ParticleRenderBatch` for a host to draw.
* **Project-dependent sim** — creating and listing emitters always works, but `particle_count` advances only
  if the served project schedules the particle simulation systems. The example MOBA project doesn't, so its
  count holds at 0; run a project that schedules them (or the editor) to see counts climb.
* **Host-only render** — drawing the particle quads is always a GPU step; the headless host does not render
  them, whatever the project.

<Card title="Particle endpoints" icon="sparkles" href="/api-reference/introduction" horizontal>
  Create, stop, and list emitter endpoints, with request and response schemas.
</Card>
