# 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.