> ## 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.

# Scenes & levels

> Save the world, change it, reload it — and learn exactly what round-trips and what is lost across Euca's three persistence paths, on a local server.

Euca has three ways to persist and rebuild a world, and the most important thing to know is that **they
are lossy in different amounts**. The mental model: each path serializes a *subset* of an entity's
components and restores a *subset* of what it wrote — so a saved world is an authoring artifact, not a
byte-exact snapshot. This page saves a world, changes it, reloads it, and reads back exactly what
survives, for each path. Every command is real and runnable against a [local server](/quickstart), with
output captured from one. Run it as a single session, top to bottom: each section resets the world, but
the `tick` and entity `generation` carry forward (which is why later sections show higher numbers).

The three paths, thinnest restore first:

* **Scene** (`/scene/save`, `/scene/load`) — restores transform + physics body only.
* **Scenario** (`POST`/`GET /scenario`) — restores transform + health + team; reassigns ids.
* **Level** (`/level/save`, `/level/load`) — saves a rich file, but the save and load formats don't
  match, so a saved level reloads **zero** entities.

## Scene round-trip — what survives

Reset (a fresh server holds one component-less bootstrap entity), spawn a rich entity (position, health,
team, role, gold, a dynamic physics body), step once so the transform propagates, and read it back.
`health` is reported as `[current, max]`:

```bash theme={null}
curl -s -X POST http://localhost:3917/reset
# {"ok":true,"message":"Reset: despawned 1 entities. Tick: 0"}

curl -s -X POST http://localhost:3917/spawn -H 'Content-Type: application/json' \
  -d '{"position":[5,1,-2],"health":600,"team":1,"role":"hero","gold":1000,"physics_body":"Dynamic"}'
# {"entity_id":0,"entity_generation":1}
curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":1}'

curl -s -X POST http://localhost:3917/observe
# {"tick":1,"entity_count":1,"entities":[{"id":0,"generation":1,
#   "transform":{"position":[5.0,0.997275,-2.0], ...},
#   "velocity":{"linear":[0.0,-0.16350001,0.0], ...},"physics_body":"Dynamic",
#   "health":[600.0,600.0],"team":1,"gold":1000,"level":1,"role":"Hero"}]}
```

Now save the scene, load it straight back (load wipes the world first, then restores), and step once so
the restored transform propagates:

```bash theme={null}
curl -s -X POST http://localhost:3917/scene/save -H 'Content-Type: application/json' -d '{"path":"demo_scene.json"}'
# {"ok":true,"message":"Scene saved to demo_scene.json"}

curl -s -X POST http://localhost:3917/scene/load -H 'Content-Type: application/json' -d '{"path":"demo_scene.json"}'
# {"ok":true,"message":"Loaded 1 entities from demo_scene.json"}
curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":1}'

curl -s -X POST http://localhost:3917/observe
# {"tick":2,"entity_count":1,"entities":[{"id":0,"generation":2,
#   "transform":{"position":[5.0,0.997275,-2.0], ...},"physics_body":"Dynamic"}]}
```

The position (`[5, 0.997, -2]`), scale, and physics body round-tripped. **Everything else is gone** —
no `health`, `team`, `gold`, `level`, `role`, or `velocity`. The save file *contains* those fields, but
`scene/load` only reads `transform.position`, `transform.scale`, and `physics_body` back. The
`generation` bumped (here `1`→`2`) because load wipes and re-spawns.

## Scenario round-trip — richer, but still partial

A scenario is one document for a complete setup (templates, entities, rules, assertions, camera, game
config). `GET /scenario` exports the current world; `POST /scenario` wipes and rebuilds from a document.
Reset, spawn a unit, step, and export it:

```bash theme={null}
curl -s -X POST http://localhost:3917/reset
curl -s -X POST http://localhost:3917/spawn -H 'Content-Type: application/json' \
  -d '{"position":[3,1,0],"health":600,"team":1,"role":"hero","gold":1000}'
curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":1}'

curl -s http://localhost:3917/scenario
# the one entity, non-null fields only:
#   {"position":[3.0,1.0,0.0],"scale":[1.0,1.0,1.0],"health":600.0,"team":1}
# every other field — role, gold, color, velocity, physics_body, combat, ... — is null
```

Re-applying that export rebuilds the world. `health` and `team` survive (scene/load drops both), but
`role` and `gold` are still lost, and ids are reassigned:

```bash theme={null}
curl -s http://localhost:3917/scenario > scenario.json
curl -s -X POST http://localhost:3917/scenario -H 'Content-Type: application/json' --data @scenario.json
# {"ok":true,"entities_spawned":1,"templates":0,"rules":0,"assertions":0}
curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":1}'

curl -s -X POST http://localhost:3917/observe
# {"tick":4,"entity_count":1,"entities":[{"id":0,"generation":4,
#   "transform":{"position":[3.0,1.0,0.0], ...},"health":[600.0,600.0],"team":1}]}
```

The world→scenario extractor reconstructs `position`, `scale`, `health`, and `team` from each entity's
components — that's the full set it knows how to round-trip. `role`, `gold`, `color`, `mesh`, and the
combat/AI fields are read as `null` even when the components are present, so they're dropped on
re-apply. Entities are inlined with no `id`, so applying assigns fresh ids.

## Level save/load — a saved level reloads zero entities

`POST /level/save` writes a level file, and `POST /level/load` reads one. But they speak different
shapes, so a level you saved cannot be reloaded. Reset, spawn a unit, step, then save and reload it:

```bash theme={null}
curl -s -X POST http://localhost:3917/reset
curl -s -X POST http://localhost:3917/spawn -H 'Content-Type: application/json' \
  -d '{"position":[5,1,-2],"health":600,"team":1,"role":"hero"}'
curl -s -X POST http://localhost:3917/step -H 'Content-Type: application/json' -d '{"ticks":1}'

curl -s -X POST http://localhost:3917/level/save -H 'Content-Type: application/json' -d '{"path":"demo_level.json"}'
# {"ok":true,"message":"Level saved to demo_level.json"}
# the saved entity writes health as an ARRAY: "health":[600.0,600.0]

curl -s -X POST http://localhost:3917/level/load -H 'Content-Type: application/json' -d '{"path":"demo_level.json"}'
# {"ok":true,"entities_created":0}      ← loaded nothing
```

`level/save` writes the same rich per-entity record as `/observe` (with `health` as a `[current, max]`
array). But `level/load` parses each entity as a spawn request, where `health` is a single number. The
array shape fails to deserialize, and the loader **silently** falls back to an empty entity list — so
`entities_created` is `0`, with no error.

The loader itself works fine; it's the save format that's incompatible. A hand-authored level (with
scalar `health`) loads correctly:

```bash theme={null}
echo '{"entities":[{"position":[7,1,7],"health":250,"team":1,"role":"hero"}]}' > handwritten_level.json
curl -s -X POST http://localhost:3917/level/load -H 'Content-Type: application/json' -d '{"path":"handwritten_level.json"}'
# {"ok":true,"entities_created":1}
```

## Behavior and gotchas

The things you only find out by running it:

* **Restore depth differs by path.** Scene keeps **transform + physics body**; scenario keeps
  **transform + health + team**; level save→load keeps **nothing** (format mismatch). No path keeps
  `role`, `gold`, `color`, `velocity`, or ability/AI state. None is byte-exact.
* **Step after a load to see positions.** Load writes the local transform; the world-space transform
  `/observe` reports propagates on the next tick. Read immediately after a load and a dynamic body still
  shows `[0,0,0]` until you `/step`.
* **`scene/load` and `POST /scenario` wipe the world first; `level/load` appends.** Loading a scene or
  applying a scenario despawns everything, then rebuilds. `level/load` adds to whatever is already
  there.
* **A saved level file does not reload (`entities_created:0`, silently).** Use a scenario for a
  save→reload round-trip, or hand-author level files in the spawn-request shape (scalar `health`).
* **Paths are sandboxed and relative.** Absolute paths and `..` are rejected; files resolve against the
  server's working directory.

## Endpoints

| Method | Path                          | Description                                                                     |
| ------ | ----------------------------- | ------------------------------------------------------------------------------- |
| `POST` | `/scene/save` · `/scene/load` | Save / load a scene file (restores transform + physics body)                    |
| `GET`  | `/scenario`                   | Export the current world as a scenario document                                 |
| `POST` | `/scenario`                   | Apply a scenario (wipes + rebuilds; restores transform + health + team)         |
| `POST` | `/fork/{id}/scenario`         | Apply a scenario to a [fork](/concepts/forks), leaving the main world untouched |
| `POST` | `/level/save` · `/level/load` | Save / load a level file (auto-detects format; save→load does not round-trip)   |

For exact, byte-comparable reproducibility, use the structured state and digest on
[Datasets](/systems/datasets) — scene/scenario/level files are for authoring and layout.

## Status

* **Shipped** — scene save/load, scenario apply/export (including into forks), level save/load with
  format auto-detect and a procedural MOBA map. All headless.
* **Partial** — every save/load path is a *subset* round-trip. Scene keeps transform + physics body;
  scenario keeps transform + health + team; a saved level file currently reloads zero entities (the
  save format is the `/observe` record, which the loader doesn't accept).

<Card title="Scene & scenario endpoints" icon="map" href="/api-reference/introduction" horizontal>
  Scenario, level, and scene endpoints with request and response schemas.
</Card>
