Files
Genarrative/docs/technical/PUZZLE_RUNTIME_REAL_LEADERBOARD_2026-04-27.md
高物 89ab1bf1c0
Some checks failed
CI / verify (push) Has been cancelled
1
2026-04-29 23:10:43 +08:00

124 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 拼图运行时真实排行榜落地说明
更新时间:`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. 作品自身统计继续更新 `puzzle_work_profile.play_count`
2. 已登录用户写入 `profile_played_world``world_key = puzzle:{profile_id}`
3. `profile_id` 保存拼图作品号,`world_type = PUZZLE`
4. `world_title` 使用关卡名,`world_subtitle` 使用作品摘要,`owner_user_id` 使用拼图作者用户 ID。
5. 下一关切换到新 `profile_id` 时按同一规则再次写入。
6. 排行榜提交携带的 `elapsedMs` 是本关可观测时长,后端按增量累计到 `profile_dashboard_state.total_play_time_ms`
建议字段:
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. 下一关开始后,当前关卡榜单状态清空。
## 7.1 2026-04-29 与前端拖动裁决的对齐
当前拼图拖动、合并、拆分与通关判定完全由前端运行态负责,后端排行榜接口只负责真实成绩表与榜单聚合:
1. 排行榜提交不得依赖 SpacetimeDB 里的旧棋盘快照已经通过后端拖动接口进入 `cleared`
2. 后端仍校验 `profileId``gridSize`、昵称和成绩,并把当前提交写入真实成绩表。
3. 后端响应里的 `leaderboardEntries` 是唯一需要合并回前端当前 run 的数据。
4. 前端不能用排行榜响应里的旧棋盘快照覆盖本地拖动后的棋盘,否则会把刚刚通关的前端状态回滚。
## 8. 测试要求
至少覆盖:
1. 通关后不会再生成本地假榜单。
2. 同一用户重复通关同一关卡时,只保留更优成绩。
3. 不同用户成绩会按耗时正确排序。
4. `3x3``4x4` 不混榜。
5. 下一关开启后上一关榜单不会污染新关卡。