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

# Camera & rendering

> Point the camera, frame an entity, apply a view preset, and capture a frame — and learn exactly which calls are plain CPU pose state (they work headless) versus which need a GPU render host.

Euca's renderer is a real-time PBR forward renderer (wgpu, native Metal on macOS). The camera split follows
the engine's CPU-core/GPU-edge line: the camera **pose** is a plain `Camera` resource (CPU state you can read
and drive headless), while the rendered **output** — and the `ScreenshotChannel` that captures it — is the
GPU edge. The headless host boots a `Camera` resource but no renderer and no `ScreenshotChannel`, so pose
endpoints work and `/screenshot` is host-only. This page drives every camera and screenshot endpoint against
a real [local server](/quickstart) and shows the exact response. Every command is real; the output below is
captured from a headless host.

## Point the camera, frame an entity, capture a frame

**1. Read the camera.** `GET /camera` returns the camera's `eye`, `target`, `fov_y`, and projection. The
headless host carries a `Camera` resource (here positioned by the served project), so you get the pose back:

```bash theme={null}
curl -s http://localhost:3917/camera
# {"eye":[-19.5,25.0,-15.5],"fov_y":0.7853981852531433,"ortho_size":10.0,"orthographic":false,"target":[1.0,0.5,5.0]}
```

The pose is authoritative CPU state — the same value a render host would draw from. What headless can't do is
*render* it; that's the GPU edge, covered by `/screenshot` below.

**2. Set the camera.** `POST /camera` takes optional `eye` and/or `target` as `[x, y, z]`, and pins a
`CameraOverride` so an editor's mouse-orbit won't snap your shot back:

```bash theme={null}
curl -s -X POST http://localhost:3917/camera \
  -H 'Content-Type: application/json' -d '{"eye":[8.0,6.0,8.0],"target":[0.0,0.0,0.0]}'
# {"ok":true,"message":"Camera updated"}
```

This actually moves the pose — re-read with `GET /camera` and `eye`/`target` reflect your write. On a render
host the rendered view moves with it; headless the pose changes but nothing is drawn.

**3. Apply a view preset.** `POST /camera/view` snaps to a named preset — one of `top`, `front`, `back`,
`right`, `left`, `perspective`:

```bash theme={null}
curl -s -X POST http://localhost:3917/camera/view \
  -H 'Content-Type: application/json' -d '{"view":"top"}'
# {"ok":true,"message":"Camera set to top view"}
```

The preset is applied to the pose resource headless. A genuinely unknown name (e.g. `isometric`) returns
`{"ok":false,"message":"Unknown view: isometric. Use: top, front, back, right, left, perspective"}`.

**4. Frame an entity.** `POST /camera/focus` targets an entity and pulls the camera to a clamped 5–20 unit
distance along the current view direction. Spawn something, then focus it:

```bash theme={null}
curl -s -X POST http://localhost:3917/spawn \
  -H 'Content-Type: application/json' -d '{"health":100,"team":1}'
# {"entity_id":1,"entity_generation":0}

curl -s -X POST http://localhost:3917/camera/focus \
  -H 'Content-Type: application/json' -d '{"entity_id":1}'
# {"ok":true,"message":"Focused on entity 1 at (0.0, 0.0, 0.0)"}
```

The focus reframes the pose around the entity's transform. (A missing `entity_id` returns `"Missing
entity_id"`; a nonexistent id returns `"Entity 999 not found"`.)

**5. Capture a frame.** `POST /screenshot` re-renders the viewport offscreen, reads it back, and writes a
PNG. It needs a `ScreenshotChannel` resource wired to a live renderer — the one piece the headless host
doesn't have:

```bash theme={null}
curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST http://localhost:3917/screenshot
# HTTP 503     (the body is empty)
```

Headless returns **503 Service Unavailable** — there is no `ScreenshotChannel`, so there is nothing to
capture. On a render host the same call returns `200` with
`{"ok":true,"path":"/tmp/euca_screenshot_<ts>.png","size_bytes":<n>}`. This is the one endpoint that is
honestly host-only: do not expect a PNG from the headless host.

## Behavior and gotchas

The things you only find out by running it:

* **The camera pose works headless; only rendering doesn't.** `GET /camera` returns the pose, and every camera
  write (`/camera`, `/camera/view`, `/camera/focus`) drives that pose resource. What's absent headless is the
  GPU edge — nothing is drawn from the pose, and `/screenshot` can't capture it.
* **`POST /camera` reports `ok:true` and actually moves the pose.** Confirm with `GET /camera`; the new
  `eye`/`target` are reflected. On a render host the rendered view follows; headless it's pose-only.
* **View presets and `/camera/focus` succeed headless.** They reframe the pose around the named preset or the
  target entity's transform; an unknown preset name or a missing entity still returns the matching `ok:false`.
* **`/screenshot` is 503 headless, not a problem with your request.** It needs a live renderer. Capture from
  the editor or a windowed build.
* **Setting the camera also pins `CameraOverride`** so an interactive editor's mouse-orbit won't fight your
  framing. This matters only on a render host where an orbit camera is running.

## Endpoints

| Method | Path            | Description                                                                           |
| ------ | --------------- | ------------------------------------------------------------------------------------- |
| `GET`  | `/camera`       | Current `eye`, `target`, `fov_y`, projection (pose works headless)                    |
| `POST` | `/camera`       | Set `eye` and/or `target` `[x,y,z]`; pins `CameraOverride` (drives the pose headless) |
| `POST` | `/camera/view`  | Snap to a preset: `top`, `front`, `back`, `right`, `left`, `perspective`              |
| `POST` | `/camera/focus` | Frame `entity_id` at a clamped 5–20 unit distance                                     |
| `POST` | `/screenshot`   | Re-render offscreen and write a PNG; **503 headless** (needs a renderer)              |

Material, post-process, and fog endpoints live on [Materials & post-processing](/systems/materials-and-post-processing);
they share the same render-host-vs-headless split. Keeping appearance out of the authoritative world state is
deliberate — see [Determinism](/concepts/determinism).

## Status

* **Shipped** — camera get/set, view presets, entity framing, and offscreen screenshot capture, all backed by
  the real PBR forward renderer.
* **Host-only output** — every *rendered* result on this page requires a GPU render host (the editor or a
  windowed build). The headless host carries the `Camera` pose resource (so camera reads/writes work) but no
  renderer and no `ScreenshotChannel`, so nothing is drawn and `/screenshot` returns 503. This is by design:
  the headless host runs the simulation and holds the pose, not the renderer.

<Card title="Camera & render endpoints" icon="video" href="/api-reference/introduction" horizontal>
  Every camera and capture endpoint, with request and response schemas.
</Card>
