# 多赛道 Variant 前后端最小契约 > 文档版本:v0.1 > 最后更新:2026-04-02 18:33:00 本文档用于定义“多 KML / 多赛道 variant”第一阶段联调所需的最小前后端契约。 目标: - 先定最小可联调字段,不一开始追求大而全 - 保证准备页、launch、地图、恢复、结果页能围绕同一 `variantId` 工作 - 避免前端从页面交互反推后端字段 说明: - 本文档只定义最小契约建议 - 不等同于最终后台配置模型 - 不等同于最终数据库模型 - 本文档优先服务前后端第一阶段联调 --- ## 1. 最小联调目标 第一阶段只解决下面 4 件事: 1. 一个活动可声明多个 `variant` 2. 准备页能知道当前活动是否允许手选 / 随机 / 后端指定 3. `launch` 能明确返回本局最终绑定的 `variantId` 4. `result / ongoing / recovery` 能持续追溯同一个 `variantId` 一句话目标: **让 `variantId` 成为贯穿一局的稳定事实。** --- ## 2. 契约原则 ### 2.1 session 绑定优先 - 前端可参与选择 - 最终绑定以后端 session 为准 ### 2.2 launch 是最终真相 - 前端准备页即便做了手选或随机请求 - 地图页真正消费的仍应是 `launch` 返回的最终绑定结果 ### 2.3 恢复不重新分配 - 恢复链只恢复既有 `variantId` - 不重新随机 - 不重新提示选择 ### 2.4 结果必须可追溯 - 结果页 - ongoing session - 历史成绩 都建议能反查: - `variantId` - 可选 `variantName` --- ## 3. 活动级最小字段建议 建议活动可玩信息中增加一个最小赛道编排块,例如在 `play` 返回里体现: ### 3.1 assignmentMode 含义: - 当前活动的赛道分配模式 建议最小取值: - `manual` - `random` - `server-assigned` ### 3.2 courseVariants 含义: - 当前活动可用赛道版本列表 建议最小字段: - `id` - `name` - `description` - `routeCode` - `selectable` 备注: - 第一阶段不一定要在 `play` 里返回所有复杂资源 - 只要足够准备页展示选择即可 推荐最小形态示意: ```json { "play": { "assignmentMode": "manual", "courseVariants": [ { "id": "variant_a", "name": "A 线", "description": "适合首次体验", "routeCode": "A", "selectable": true }, { "id": "variant_b", "name": "B 线", "description": "稍长路线", "routeCode": "B", "selectable": true } ] } } ``` --- ## 4. launch 最小字段建议 launch 必须承担“最终绑定本局赛道”的责任。 建议在现有 `launch` 返回中增加一个明确的 variant 绑定块。 ### 4.1 建议字段 - `launch.variant.id` - `launch.variant.name` - `launch.variant.routeCode` - `launch.variant.assignmentMode` 如需保守,也可挂到 `business` 下,但建议语义上单独成块,避免和 release/session 混淆。 ### 4.2 前端输入建议 如果是手选模式,前端建议向 `launch` 传: - `variantId` 如果是随机模式,前端可以: - 不传,由后端分配 - 或显式传一个 `assign=random` 请求意图 ### 4.3 输出约束 无论前端是否传入 `variantId`,launch 返回都必须给出最终绑定结果。 因为地图页只应消费: - 最终 `variantId` - 对应 manifest / config 不应再依赖准备页上的临时选择状态。 推荐最小形态示意: ```json { "launch": { "variant": { "id": "variant_b", "name": "B 线", "routeCode": "B", "assignmentMode": "manual" }, "resolvedRelease": { "releaseId": "rel_xxx", "manifestUrl": "https://..." }, "business": { "sessionId": "ses_xxx", "sessionToken": "..." } } } ``` --- ## 5. session / result 最小字段建议 ### 5.1 session 摘要 建议在以下位置都可见: - `ongoingSession` - `recentSession` - `session detail` 最小补充: - `variantId` - `variantName` - `routeCode` ### 5.2 result 建议 `GET /sessions/{sessionPublicID}/result` 至少返回: - `result.session.variantId` - `result.session.variantName` - `result.session.routeCode` 这样前端单局结果页和历史结果页都能统一展示。 --- ## 6. 前端第一阶段落点 前端第一阶段建议只做下面几件事: ### 6.1 准备页 - 读取 `assignmentMode` - 读取 `courseVariants[]` - 在 `manual` 下展示可选赛道列表 - 在 `random` 下展示“随机分配” - 在 `server-assigned` 下只展示结果 ### 6.2 launch 适配层 - 将 `launch.variant.*` 写入 `GameLaunchEnvelope` - 将 `variantId` 一起进入地图页和恢复快照 ### 6.3 结果与历史页 - 显示本局 `variantName / routeCode` ### 6.4 故障恢复 - 快照中补 `variantId` - 恢复时继续使用既有 `variantId` --- ## 7. 第一阶段后端落点 后端第一阶段建议只做下面几件事: ### 7.1 play - 返回 `assignmentMode` - 返回 `courseVariants[]` ### 7.2 launch - 接收可选 `variantId` - 返回最终绑定后的 `variant` 信息 ### 7.3 session / result - 在 session 摘要和结果里带出 `variantId` 这样就足够完成第一阶段联调。 --- ## 8. 和现有体系的关系 这份最小契约不替代现有六层检查,后续一旦开始实现,仍建议按六层检查推进: 1. 文档 2. 配置源 3. 解析层 4. 编译层 5. 消费层 6. 发布与联调层 特别是: - 配置层后续如果引入 `courseVariants` - 解析层如果开始读取多 variant 结构 - 编译层如果开始按 `variantId` 产出 runtime profile 这三层都不能跳。 --- ## 9. 一句话结论 **多赛道第一阶段联调只需要先定住 `assignmentMode`、`courseVariants[]`、`launch.variant.*`、`session/result.variant*` 这四组最小字段,让 `variantId` 成为贯穿一局的稳定事实。**