# Conventions

Cross-cutting conventions every Oxide system follows. These are decided once,
here, so individual systems don't each invent their own.

## Coordinate system

Oxide uses a **right-handed** coordinate system, consistent with `glam`'s
`*_rh` matrix constructors and the glTF asset format the engine will load.

In a default (identity) orientation:

| Axis | Direction | Local accessor on `Transform` |
|------|-----------|-------------------------------|
| `+X` | right | `Transform::right()` |
| `+Y` | up | `Transform::up()` |
| `-Z` | forward (the direction a camera/object looks) | `Transform::forward()` |

So **forward is `-Z`**. This matches the convention used by glTF, OpenGL, and
`glam`'s view-matrix helpers, which keeps asset import and camera math
consistent.

`Transform::looking_at(eye, target, up)` produces an orientation whose
`forward()` points from `eye` toward `target`.

## Rotations

- Rotations are stored as **unit quaternions** (`glam::Quat`), not Euler angles
  or matrices, to avoid gimbal lock and accumulate cleanly under composition.
- Euler-angle helpers (`Quat::from_euler`) are available for authoring, but the
  canonical stored form is always a quaternion.
- Angles are in **radians**. Convert from degrees explicitly at the boundary
  (`90_f32.to_radians()`).

## Transform composition

- Transforms compose **parent-first**: `parent.mul_transform(&child)` yields the
  child resolved in the parent's space, matching `parent_matrix * child_matrix`.
- The effective matrix order is `T * R * S` — scale is applied first, then
  rotation, then translation.
- Composition is **exact** for uniform scale. With non-uniform scale plus
  rotation the true product is not representable as a single translation /
  rotation / scale triple, so the result is the closest TRS approximation
  (re-decomposed from the matrix). Prefer uniform scale in deep hierarchies.

See [math.md](math.md#transform) for details.

## Units

- **Length:** meters. Physics (`rapier3d`, Stage 6) tunes its solver for
  meter-scale geometry, so the whole engine adopts meters to avoid conversions.
- **Time:** seconds (`f32` for per-frame deltas; a fixed timestep drives physics
  from Stage 6).
- **Angles:** radians (see above).
- **Mass:** kilograms (Stage 6+).

## Numeric type

- The engine is **`f32`-first**. `glam`'s `f32` types are the default throughout;
  `f64` is used only where a specific algorithm demands it.
- Comparisons use explicit epsilons rather than `==` on floats. Helpers and tests
  use a small tolerance (commonly `1e-4`–`1e-6`) appropriate to the operation.

## Color and color space

- `Color` stores **linear** RGBA as `f32`. Lighting and blending math is correct
  only in linear space, so that is the engine's working space.
- Values are nominally in `[0, 1]` but are **not clamped** — values above `1.0`
  represent HDR / emissive intensity.
- Conversions to and from 8-bit **sRGB** (the space of color pickers, image
  files, and `#RRGGBB` hex) are explicit: `Color::from_srgb_u8`,
  `Color::from_hex`, `Color::to_srgb_u8`. Never treat raw 8-bit values as linear.

## Geometry primitives

- An [`Aabb`](math.md#aabb) is *empty* when any `min` component exceeds the
  corresponding `max`; `Aabb::EMPTY` is the identity for `union`.
- A [`Ray`](math.md#ray) always stores a **normalized** direction, so its
  parameter `t` is a true distance.
- A [`Plane`](math.md#plane) is stored in **Hessian normal form** (`normal·p + d
  = 0`) with a unit normal; the positive half-space is the side the normal points
  toward.
- A [`Frustum`](math.md#frustum) stores six planes with **inward-facing**
  normals; a point is inside when it is in the positive half-space of all six.

## Determinism

Procedural systems (Stage 11+) must be **deterministic**: the same seed always
produces the same output. Tests that need randomness use a small, explicit,
seeded PRNG rather than a system RNG, so failures are reproducible.
