# Coordinate space and camera conventions

Engine standard (unreleased; no +Z-forward or non-reverse-Z path):

| Property | Choice |
|----------|--------|
| Handedness | **Right-handed** (`+X × +Y = +Z`) |
| Up | **+Y** |
| Forward | **−Z** (identity camera looks down −Z) |
| Matrix storage | **Column-major** |
| Clip / depth | **Reverse-Z** (NDC: near → **1**, far → **0**; infinite-far perspective preferred) |

This matches **glTF / classic GL camera math** (right-handed, Y-up, cameras looking along −Z), plus **reverse-Z** depth as used in contemporary high-end renderers. It is not left-handed +Z-forward, and it is not Z-up.

Source of truth: `src/hikari/src/math/`, `scene/camera.zig`, `sdk/src/camera_rig.zig`, projection consumers, depth/Hi-Z paths.

## Why this set

1. **RH Y-up −Z forward** — Object +X, view-right, and `lookAt`’s X axis coincide at identity. No separate “view-right = −local X” story.
2. **glTF / DCC alignment** — Assets and tools already speak RH Y-up; camera space matches OpenGL-style −Z view.
3. **Reverse-Z** — Best depth precision for large `far` / outdoor scales with 24–32-bit depth; pairs naturally with an infinite-far projection.
4. **Rejected alternatives** — left-handed +Z-forward keeps glTF awkward; Z-up forces constant axis remaps; RH **+Z** forward would force view-right ≠ local +X.

## World axes

| Axis | Direction |
|------|-----------|
| **+X** | Right (`math_vector.right`) — also **view/screen right** for an identity camera |
| **+Y** | Up |
| **−Z** | Forward (identity camera look direction) |
| **+Z** | Backward |

Right-handed: **+X × +Y = +Z** (so forward −Z is “into the screen” in the usual GL view diagram).

## Camera and gameplay

> [!IMPORTANT]
> An identity camera looks along local **−Z**. To look toward the origin with identity rotation, place it on **+Z**; a camera on −Z would look away from the origin.

- Identity orientation: local **+X** right, **+Y** up, **−Z** forward.
- `Camera.getRight` / `Actor.get_right` / `FlyCamera.right` = rotated local **+X** (same as `cross(forward, up)` when forward is the look direction).
- `getForward` / look direction = rotated **−Z** (`math_vector.forward`).
- Ground move basis: yaw about +Y; at yaw 0, forward is (0,0,−1), right is (1,0,0).
- **Camera frustum gizmos** (editor overlay) place near/far on local **−Z**.
- **Look controllers store pitch/yaw degrees as source of truth** (`FlyCamera`, `CameraRig`, play-mode `camera_controller.kawa`) and derive the quaternion. Do not round-trip `toEuler` every look frame — near the pitch clamp that is ill-conditioned in f32.

## Euler angles (degrees)

Triples are **`(pitch, yaw, roll)` in degrees**, composition on column vectors:

```text
R = Ry(yaw) · Rx(pitch) · Rz(roll)
q = qy · qx · qz
```

| Component | Axis | Positive sense (RH, facing −Z) |
|-----------|------|--------------------------------|
| **pitch** | +X | Look up |
| **yaw** | +Y | Turn left (CCW from above) |
| **roll** | +Z | Bank around the view axis |

Yaw-first ordering is what makes this usable for cameras:

- **Yaw is full-range.** `toEuler` reports yaw over (−180°, 180°]; only pitch is limited to [−90°, 90°].
- **Gimbal lock sits at the poles** (pitch ±90°), which look controllers already clamp away from.
- **Roll does not steer.** Because roll is innermost, it rotates around the local Z axis, so banking never changes where a camera or light points.

Keep yaw as the outer rotation when converting authored Euler values. An order with yaw in the middle can turn |yaw| > 90° into a 180° roll; clearing that roll would flip the view. Runtime look controllers should retain pitch/yaw state as described above.

`fromEuler` / `toEuler` must stay inverses. `CameraRig.Look` / mouse deltas use gameplay sense (turn right / look up) and convert into these euler values.

## Quaternions

The runtime type is `{ w, x, y, z }` — **Hamilton, scalar-first** (`math/quaternion.zig`).

This is the minority layout in the wider ecosystem: glTF and most DCC/engine formats serialise `xyzw` scalar-last, and so does the cooked model format (`ImportNode.rotation` in `shinra/src/import/mod.rs`, and skeletal animation keyframes). **Anything crossing that boundary must swizzle.** Scene assets are unaffected — they store `rotation_euler` degrees, not quaternions.

Rotating a vector uses local axes as columns of `toMatrix`, so `rotateVector(q, forward)` is the look direction.

## Units

| Quantity | Unit |
|----------|------|
| Distance | **metres** (1 world unit = 1 m, matching glTF) |
| Time | seconds |
| Mass | kilograms |
| Angles | **radians** everywhere except euler triples and authored/inspector fields, which are **degrees** |

Physics assumes this: tenkai's default gravity is `-9.81` on **+Y**. Authoring content at a different scale silently changes how everything falls, how character motors tune, and how attenuation ranges read.

## Projection and clip space

- CPU builds **reverse-Z** perspective (finite or infinite far) with NDC z already in **[0, 1]** (near → 1, far → 0). `remap_clip_z` is identity.
- After projection, **larger depth buffer values are closer**. Depth tests use **greater**. Clear depth / empty shadow atlas to **0**.
- Metal and D3D12 share the same CPU matrices (no GL −1…+1 remap).
- **Linear** G-buffer / Hi-Z depth (meters, `−view.z`) keeps min=nearer, max=farther. Hardware-depth consumers use reverse-Z ordering.

## vs other engines

| | Hikari | Unity | Godot 4 | Unreal |
|---|---|---|---|---|
| Handedness | Right | Left | Right | Left |
| Up | +Y | +Y | +Y | +Z |
| Forward | **−Z** | +Z | **−Z** | +X |
| Depth | Reverse-Z | Varies | Depends on renderer/version | Reverse-Z (modern) |
