# 06 — 后端时间轴接缝 + `GET /campaigns/{id}/timeline`

**What to build:** 一个纯函数把某活动的全部子 Run 映射成「逐 Run 时间轴条目」——每条含 run_id、场景 id/名、加速后窗口偏移（`(started_at − campaign.started_at) × time_scale`，clamp 到 `[0, window_seconds]`）、状态、通过率（取子 Run summary，只读不重算）、平均时延、started_at；未开始/取消的子 Run 在函数内有明确定义的处理。再暴露一个只读端点 `GET /campaigns/{id}/timeline` 返回 `{entries: [...]}`（逐 Run 扁平数组，区别于报告的 12 桶聚合），并在前端 `api.ts` 加对应类型与调用。curl / 测试即可验证。

**Blocked by:** 无 —— 可立即开始。

**Status:** ready-for-agent

- [ ] 新增纯函数 `build_campaign_timeline(campaign, runs)`（放 `evaluation/report.py` 或紧邻模块），偏移口径复用现有报告的 `× time_scale` + clamp。
- [ ] 单测覆盖：正常偏移（含 time_scale 压缩）、clamp 到窗口边界、未开始子 Run、取消/失败子 Run、字段映射（pass_rate 取自 summary、场景名映射）、空活动。仿 `test_campaign_report.py`。
- [ ] 新增 `GET /campaigns/{id}/timeline` → `{entries}`；404 当活动不存在。
- [ ] 端点集成测试（200 字段形状 + 404），仿现有 campaigns router 测试。
- [ ] `api.ts` 增加 `CampaignTimelineEntry` 类型与 `campaignsApi.timeline(id)`。
- [ ] `pytest` 全绿、`ruff check backend/` 通过。
