## Goal
`scene.json` gives a `trajectory`: a time-ordered list of poses, each with a timestamp
`t`, a unit quaternion `q = [w, x, y, z]` (rotation), and a translation `p = [x, y, z]`.
It also gives a list of `queries` (timestamps, each within the trajectory's time span).

For every query timestamp, interpolate the pose between its two **bracketing** trajectory
samples:
- **Rotation:** spherical linear interpolation (SLERP), taking the **shortest path**
  across the quaternion double cover.
- **Translation:** linear interpolation.

Also report the geodesic angle (degrees) between each query's rotation and the
**previous query's** rotation.

## Conventions
- Quaternions are `[w, x, y, z]`, unit norm. Because `q` and `−q` are the same rotation,
  report every output quaternion in **canonical form with `w ≥ 0`** (negate if `w < 0`),
  then renormalize.
- SLERP between `q0`, `q1` at parameter `u ∈ [0,1]`: if `dot(q0,q1) < 0`, negate `q1`
  first (shortest path). If nearly parallel (`dot > 0.9995`), fall back to normalized
  linear interpolation. Otherwise `θ = acos(dot)`,
  `q = sin((1−u)θ)/sinθ · q0 + sin(uθ)/sinθ · q1`, renormalized.
- Interpolation parameter for a query `tq` bracketed by `t0 ≤ tq ≤ t1` is
  `u = (tq − t0)/(t1 − t0)`.
- Geodesic angle between unit quaternions: `2·acos(min(1, |dot(q_a, q_b)|))`, in degrees.

## Input: `scene.json`
```
{
  "trajectory": [ { "t": <float>, "q": [w,x,y,z], "p": [x,y,z] }, ... ],
  "queries": [ <float>, ... ]
}
```

## Output: `result.json`
```
{
  "interpolated": [
    { "t": <query t>, "q": [w,x,y,z], "p": [x,y,z], "angle_from_prev_deg": <float>|null },
    ...
  ]
}
```
One object per query, in query order. `q` must be unit-norm and canonical (`w ≥ 0`).
`angle_from_prev_deg` is `null` for the first query and the geodesic angle (deg) to the
previous query's rotation otherwise. Python 3.11 + NumPy only. No network access.
