# 多赛道 Variant 五层设计草案
> 文档版本：v0.1
> 最后更新：2026-04-02 18:24:00

本文档用于定义“一个活动对应多版 KML / 多条赛道”时的推荐架构。

目标：

- 不从页面交互倒推系统结构
- 先把多赛道能力按平台层次拆清
- 让前端、后端、后台、恢复、结果页都围绕同一套事实工作
- 为后续“手动选择 / 随机指定 / 后端指定”留出统一伸缩空间

说明：

- 本文档是设计草案，不是最终接口契约
- 本文档优先定义分层、边界、事实和约束
- 具体页面表现、后台表单细节、字段命名可在后续实现阶段微调

---

## 1. 背景与核心判断

当前项目里，一场活动后续可能不止一版 KML。

常见需求包括：

- 同一活动下有 A / B / C 多条赛道
- 准备阶段允许玩家手动选择
- 准备阶段由系统随机分配
- 后续可能由后台或裁判端直接指定

这里最关键的判断是：

**赛道版本不是页面临时状态，而是 session 级事实。**

也就是说，一旦某局比赛绑定了某个赛道版本，这个事实必须贯穿：

- 准备阶段
- launch
- session start / finish
- 故障恢复
- result
- ongoing session
- 历史结果

如果这一点不先定住，后面多端、多页面、多恢复链会很快乱掉。

---

## 2. 总体原则

多赛道能力建议固定遵守下面 6 条原则：

1. 一局比赛只绑定一个赛道版本
2. 前端可以参与选择，但最终绑定以后端 session 为准
3. 客户端不应各自实现不同的随机分配规则
4. 恢复链必须记住赛道版本
5. 结果页、历史结果和 ongoing 摘要必须可追溯赛道版本
6. 扩展新分配模式时，不破坏现有分层

一句话总结：

**前端负责交互，后端负责最终绑定，session 负责真实落账。**

---

## 3. 五层模型

多赛道能力建议拆成 5 层。

### 3.1 资源层

职责：

- 只定义赛道素材本身
- 不讨论谁来选，不讨论 session

典型内容：

- KML 文件
- 赛道元数据
- 可选地图资源
- 可选赛道封面、说明图

这一层回答的是：

- 这个赛道版本本身是什么
- 它的原始素材和元信息是什么

建议约束：

- 每个赛道版本都应有稳定的 `variantId`
- 每个赛道版本都应能独立定位到自己的 KML / manifest 入口
- 资源层不应混入“本局随机到谁”这种运行时逻辑

### 3.2 活动编排层

职责：

- 定义一个活动下有哪些赛道版本可用
- 定义这些版本如何被分配

典型内容：

- `assignmentMode`
- `courseVariants[]`
- 权重
- 可选分组规则
- 可选活动级覆盖

这一层回答的是：

- 当前活动允许哪些赛道版本
- 当前活动按什么模式分配赛道

推荐至少支持 3 种模式：

1. `manual`
   - 用户手选
2. `random`
   - 系统随机指定
3. `server-assigned`
   - 后端预先指定，前端只展示

后续可扩展模式：

- 按分组分配
- 按批次轮换
- 团队共用赛道
- 避免重复赛道

### 3.3 会话绑定层

职责：

- 真正决定“这局比赛到底绑定哪条赛道”
- 作为跨端、跨页面、跨恢复的一致事实

这一层回答的是：

- 当前 session 最终绑定的是哪个 `variantId`
- 这个绑定是手选、随机还是后端直接指定

这一层必须落账的最小事实建议包括：

- `sessionId`
- `eventId`
- `variantId`
- `assignmentMode`
- `assignedAt`
- 可选的 `assignmentSource`

核心约束：

- `variantId` 一旦绑定，不应在本局内漂移
- launch、恢复、结果、排行榜都要引用同一 `variantId`
- 客户端本地恢复快照必须保存 `variantId`

### 3.4 客户端呈现层

职责：

- 负责向玩家展示可选项或绑定结果
- 负责发起用户选择
- 负责消费最终绑定后的赛道配置

这一层回答的是：

- 准备页要不要展示赛道列表
- 是否允许点击手选
- 是否显示“本局随机分配结果”
- 地图页加载哪一个 manifest

推荐规则：

- `manual`：准备页展示赛道列表，允许选择
- `random`：准备页展示“随机分配”结果，不允许随意更改
- `server-assigned`：准备页只展示最终结果，不提供选择

强约束：

- 客户端不能把“页面选择结果”当成最终事实
- 客户端必须以后端返回的 `variantId` 为准
- 地图页只消费最终绑定后的 manifest / runtime profile

### 3.5 后台运营层

职责：

- 管理赛道版本
- 管理活动编排
- 管理发布与审计

这一层回答的是：

- 活动有哪些赛道版本
- 每个版本的素材、说明和发布状态是什么
- 当前活动采用哪种分配策略
- 某局最终是怎么绑定出来的

后台层建议逐步承担：

- 赛道版本管理
- 活动绑定多个 variant
- 权重配置
- 发布检查
- 历史审计

---

## 4. 多端协作边界

多赛道能力一旦牵涉多端，最容易出问题的地方是边界不清。

建议固定边界如下：

### 4.1 前端负责

- 展示赛道选择或展示赛道结果
- 发起选择请求或发起随机请求
- 消费 launch 返回的赛道绑定结果
- 在地图、恢复、结果页中显示当前 `variantId` 或赛道名

### 4.2 后端负责

- 最终确认本局绑定哪个 `variantId`
- 将 `variantId` 写入 session
- 确保 launch 返回的 release / manifest 与 `variantId` 对应
- 确保 result / ongoing / recovery 均能反查 `variantId`

### 4.3 后台负责

- 管理活动可用的 variants
- 管理 assignment mode
- 管理发布、上下线和审计

约束：

- 不允许前端自己定义“随机分配算法”后直接当成最终结果
- 不允许某端私自改 `variantId` 但不落 session
- 不允许恢复链丢失 `variantId`

---

## 5. 配置与发布建议

多赛道不是只改一个 `kmlUrl`。

它建议进入活动编排配置，而不是页面临时字段。

推荐抽象：

- 活动级：
  - `assignmentMode`
  - `courseVariants[]`
- variant 级：
  - `id`
  - `name`
  - `kmlUrl`
  - `weight`
  - `overrides`

如果某些版本不仅 KML 不同，连分值、样式、规则也不同，推荐允许 variant 带局部覆盖。

推荐覆盖顺序：

`系统默认值 -> 玩法默认值 -> 活动默认值 -> variant 覆盖 -> 单点覆盖`

这样未来不会因为多赛道把既有继承体系打乱。

---

## 6. 恢复、结果与摘要约束

### 6.1 故障恢复

恢复快照中必须加入：

- `variantId`
- 可选 `variantName`

恢复原则：

- 恢复的是这局绑定的赛道事实
- 不重新随机
- 不重新询问选择

### 6.2 结果页

结果页建议至少展示：

- 活动名
- 赛道版本名或 `variantId`
- 本局结果摘要

### 6.3 ongoing / recent / 历史成绩

这些摘要建议都能反映：

- 本局属于哪个赛道版本

否则后续：

- 用户无法判断自己玩的哪版
- 运营无法解释同活动下不同赛道差异
- 排名、复盘、申诉都困难

---

## 7. 推荐实施顺序

这块不建议一上来就做全套复杂功能。

推荐分三期：

### 第一期：架构定型

目标：

- 定义 `courseVariants`
- 定义 `assignmentMode`
- 定义 session 绑定 `variantId`
- 明确恢复、结果、launch 必须带上 `variantId`

### 第二期：前后端最小闭环

目标：

- 支持 `manual`
- 支持 `random`
- 准备页可展示赛道选择或随机结果
- launch 能消费最终绑定结果

### 第三期：运营扩展

目标：

- 接后台编排
- 增加 `server-assigned`
- 扩展更多分配模式
- 做更完整的审计、排行榜和统计

---

## 8. 六层检查在多赛道能力中的应用

后续只要多赛道相关配置或契约有变更，建议继续执行当前约定的六层检查：

1. 文档
2. 配置源
3. 解析层
4. 编译层
5. 消费层
6. 发布与联调层

具体到多赛道能力，检查点通常包括：

- 文档是否同步 `assignmentMode / variants / variantId`
- 配置源是否新增或调整 variant 结构
- 解析层是否能读取多赛道结构
- 编译层是否能生成最终绑定后的 runtime profile
- 地图、结果页、恢复链是否都消费了 `variantId`
- launch / release / manifest 是否和最终 variant 绑定一致

---

## 9. 当前建议结论

当前阶段，建议把“多 KML / 多赛道”先当成**平台能力设计**，而不是页面功能。

当前最重要的不是先做某个选择 UI，而是先定住以下 4 个事实：

1. 一个活动可以有多个 variants
2. 一个 session 只能绑定一个 `variantId`
3. 最终绑定以后端 session 为准
4. 恢复、结果、ongoing、历史结果都必须能追溯该 `variantId`

---

## 10. 一句话总结

**多赛道能力建议固定采用“资源层、活动编排层、会话绑定层、客户端呈现层、后台运营层”五层模型，先把 `variantId` 做成 session 级事实，再去实现准备页手选、随机分配和后台指定等具体交互。**
