# Migration to orbit-camera
Status: **complete.** All six games migrated (magic-journey, bad-fates,
cyborg-evolution, oneira-woods, ruffy-wonder, open-world-minigolf). This file is
kept as a record of how each game maps onto the crate and why the API looks the
way it does.
## Two integration tiers
**Direct swap** — the game's camera *is* this crate. Delete the local struct and
constants, build a `CameraConfig` once, call `facing`/`rotate`/`follow`/`clip`/
`view`. magic-journey and bad-fates are zero-config (defaults match exactly).
**Escape-hatch wrapper** — the game keeps a bespoke camera model and uses
`OrbitCamera` as the orbit core, driving its state directly. These methods bypass
the forced smoothing of `follow()`/`clip()`:
- `set_focus(Vector)` — drive the look-at target yourself (cutscenes, per-frame
look-at, ground-focus state machines).
- `set_distance(f32)` / `distance()` — drive distance from a custom model
(absolute zoom, aim modes, 3-point distance curves).
- `eye_at(distance) -> Vector` — probe the eye at a candidate distance before
committing it (custom clip passes, e.g. sphere push-out).
- `set_yaw(f32)` / `set_pitch(f32)` — set orientation absolutely (spherical
coordinates, snap-to-step yaw). `set_pitch` clamps to the configured range.
## Per-game record
### magic-journey — direct swap, zero-config
Defaults matched the local constants bit-for-bit. `steer_toward` takes the strength
explicitly (`LOCK_ON_STEER_STRENGTH` 6.0). camera.rs deleted.
### bad-fates — direct swap, zero-config
Same stack; FoV/near/far also match the defaults. camera.rs deleted.
### cyborg-evolution — escape-hatch
Absolute distance (2–25, pitch-independent), linear zoom, and an aim-mode that
lerps toward a first-person distance, then pushes the eye out of geometry with
`world.resolve_sphere` (sphere push-out, **not** a raycast). All of this stays in
the game and drives the camera via `eye_at`/`set_distance`. No `collide-mesh`
feature — the crate's raycast `clip()` is not used here. (The original
[f32;16]/[f32;3] math was only at the renderer boundary and converts trivially.)
### oneira-woods — escape-hatch
The 45° step-yaw (Q/E snap in discrete steps with a lerp between) stays in the
game and drives `set_yaw`. The game uses `collide_mesh::CollisionWorld` directly,
so the `collide-mesh` feature applies — but the game's collide deps had to move
from a git source to the crates.io registry (verified byte-identical) so a single
`CollisionWorld` type exists. Note the sign convention: the crate's
`forward_xz`/`eye` use `(-sin yaw, -cos yaw)`; the game drives `set_yaw(yaw + π)`
to match while keeping its own yaw semantics for lock-on.
### ruffy-wonder — escape-hatch wrapper
The local camera is a superset: screen shake, run-direction look-ahead, yaw-follow,
a ground-focus state machine (jump/fall clamp, up/down speeds), pitch height offset,
and a 3-point (low/mid/high) distance curve. These are *behaviours*, not constants,
so they live in a wrapper struct that owns an `OrbitCamera`. The wrapper computes
final focus/distance/yaw and writes them through the setters; shake is added on top
of `eye()`. Clip is a local `Clip` impl wrapping `level.raycast`. The project uses
`simple_vectors`/[f32;3], so a thin ga3 conversion sits at the boundary.
### open-world-minigolf — escape-hatch
Spherical orbit (azimuth/elevation) with a per-frame look-at target computed by the
renderer. azimuth→`set_yaw`, elevation→`set_pitch`, target→`set_focus` (no
smoothing). Pitch-distance is neutralised with `low_distance == high_distance`.
Required an ecosystem bump (`inner-space` 0.2→0.3) to align on vector-space 0.7
with the current ga3 — otherwise two vector-space versions coexist and ga3 methods
fail to resolve.