# Lane H — Ghost replay + title screen

**Owns:** `src/ghost/`, `src/ui/`, `demos/lane-h.html`, `src/demo/lane-h.ts`.
**Never touches:** contracts, world, game, main, package.json, other lanes' dirs.
No new deps.

## Mission
Two pieces that turn a tech slice into a GAME you return to:
a ghost of your best run to race against, and a title card that explains the
game in five seconds (and unlocks audio).

## Deliverables
1. `src/ghost/recorder.ts` — `GhostRecorder`: on `race:started`, record the
   blob root's position + uniform scale at 15Hz into a flat Float32Array-backed
   buffer (cap ~5 min). On `race:finished`, if this run is the new best, save
   quantized (int16 centimeters is plenty) + the time to localStorage
   `blobbo:ghost` (base64). Keep total under ~200KB.
2. `src/ghost/player.ts` — `GhostPlayer`: if a saved ghost exists, on
   `race:started` spawn a translucent ghost blob (simple sphere + eyes is fine,
   opacity ~0.35, no physics — kinematic visual only) and replay it in race
   time with linear interpolation between samples. Despawn on finish/respawn.
   The ghost must be OBVIOUSLY not-you (ghostly white/desaturated, slight bob).
3. `src/ghost/install.ts` — `installGhost(world, blob)` wiring both to events.
4. `src/ui/title.ts` — `installTitle(world)`: full-screen overlay on boot:
   - big BLOBBO wordmark (styled CSS bubble text is fine — thick white letters,
     rainbow paint-drip accents, Comic-adjacent but tasteful),
   - one-line pitch: "Race. Get painted. Paint is power.",
   - controls card: WASD/stick · Space/A jump · R/Start restart,
   - a fat **PLAY** button. On click/keypress/gamepad-button: fade out,
     emit **`ui:play`** on world.events (audio unlocks on this), start allowed.
   - While Rapier/world is still booting, show the same overlay with a wobbling
     "loading…" blob dot instead of PLAY (installTitle can be handed a promise).
   - Show saved best time + "👻 ghost ready" if `blobbo:ghost` exists.
5. Demo `demos/lane-h.html` + `src/demo/lane-h.ts`: fake a 20s scripted "run"
   (a ball on a circle), record it, finish, then restart and race the ghost.
   Title screen in front. Debug key G wipes the saved ghost.

## Acceptance
- `npm run build` passes; demo shows title → play → run 1 records → run 2 shows
  interpolated ghost racing the circle; localStorage survives reload; wipe works.
- `ui:play` emitted exactly once per page life; overlay never reappears mid-game.
- Ghost replay drift vs recorded path < one blob radius at all sample points
  (it's the SAME deterministic path in the demo — verify numerically).
- No edits outside owned paths.

## Events
Consume `race:started`, `race:finished {time,best}`. Emit `ui:play` only.
