Files
Genarrative/docs/technical/PUZZLE_RUNTIME_REAL_LEADERBOARD_2026-04-27.md
2026-04-27 22:50:18 +08:00

106 lines
3.8 KiB
Markdown

# 拼图运行时真实排行榜落地说明
更新时间:`2026-04-27`
## 1. 背景
当前拼图关卡结束弹窗里的排行榜数据并不是真实用户成绩。
问题根因有两层:
1. 前端本地运行态 `src/services/puzzle-runtime/puzzleLocalRuntime.ts` 在通关后会直接拼出几条演示昵称数据。
2. `server-rs` 拼图运行时虽然已经预留了 `leaderboardEntries` 字段,但 `module-puzzle``spacetime-client``api-server` 还没有真实成绩表与聚合过程,因此接口层长期返回空数组。
这导致用户在结算弹窗里看到的是“看起来像真实排行榜,但实际上是本地假数据”的结果,和平台“真实用户数据”要求冲突。
## 2. 本次目标
本次改动只解决一个明确问题:
1. 拼图关卡结束后的排行榜必须使用真实用户成绩。
2. 删除现有前端演示昵称、演示耗时等假数据。
3. 不在 UI 中默认塞入任何说明型占位文案。
## 3. 本次落地边界
为了控制改动范围,本次不把整套拼图运行态全部迁回后端,而是在当前“本地棋盘运行态”基础上补一条真实成绩回写链路:
1. 拼图拖拽、交换、合并、拆分、通关判定,仍然沿用当前本地运行态。
2. 玩家一旦通关,前端立即把当前关卡成绩提交到 `server-rs`
3. `server-rs` 将成绩写入 `SpacetimeDB` 成绩表,并返回该关卡的真实排行榜。
4. 结算弹窗只展示后端返回的真实成绩榜单,不再混入本地演示数据。
这意味着:
1. 这次不是“完整后端裁决化”。
2. 这次是“先把排行榜真相源收回后端”,满足真实成绩展示要求。
## 4. 成绩真相源设计
新增拼图成绩表,按“关卡作品 + 网格规格 + 用户”维护最佳成绩。
建议字段:
1. `entry_id`
唯一主键。
2. `profile_id`
当前关卡作品 `profile_id`
3. `grid_size`
当前成绩对应的拼图网格规格,至少区分 `3x3``4x4`
4. `user_id`
成绩所属真实用户 ID。
5. `nickname`
成绩展示昵称。当前优先使用提交时的用户显示名快照。
6. `best_elapsed_ms`
用户在该关卡该规格下的最佳通关耗时。
7. `last_run_id`
最近一次刷新该最佳成绩的运行态 `run_id`
8. `updated_at`
最后一次刷新时间。
## 5. 排行榜口径
排行榜必须遵守下面规则:
1. 只读真实成绩表。
2. 同一用户在同一 `profile_id + grid_size` 下只保留 1 条最佳成绩。
3. 排序按 `best_elapsed_ms` 从小到大。
4. 同耗时按 `updated_at` 更早者优先,再按 `user_id` 稳定排序。
5. 返回前 `N` 条,当前阶段固定 `10` 条即可。
6. 当前用户如果在榜单内,需要标记 `isCurrentPlayer = true`
## 6. 接口落地
新增拼图排行榜提交接口:
`POST /api/runtime/puzzle/runs/:runId/leaderboard`
请求体至少包含:
1. `profileId`
2. `gridSize`
3. `elapsedMs`
4. `nickname`
返回体采用现有 `PuzzleRunResponse`,但要求:
1. `run.currentLevel.leaderboardEntries` 返回真实榜单。
2. `run.leaderboardEntries` 同步返回当前关卡真实榜单,方便现有结算弹窗兼容读取。
## 7. 前端改动规则
1. 删除 `puzzleLocalRuntime.ts` 中本地演示榜单构造逻辑。
2. 本地通关后,运行态只保留真实通关耗时,不再生成假昵称榜单。
3. 结算弹窗显示时,如果真实榜单尚未回写完成,可以显示加载态;但不能回退到假数据。
4. 下一关开始后,当前关卡榜单状态清空。
## 8. 测试要求
至少覆盖:
1. 通关后不会再生成本地假榜单。
2. 同一用户重复通关同一关卡时,只保留更优成绩。
3. 不同用户成绩会按耗时正确排序。
4. `3x3``4x4` 不混榜。
5. 下一关开启后上一关榜单不会污染新关卡。