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

3.8 KiB

拼图运行时真实排行榜落地说明

更新时间:2026-04-27

1. 背景

当前拼图关卡结束弹窗里的排行榜数据并不是真实用户成绩。

问题根因有两层:

  1. 前端本地运行态 src/services/puzzle-runtime/puzzleLocalRuntime.ts 在通关后会直接拼出几条演示昵称数据。
  2. server-rs 拼图运行时虽然已经预留了 leaderboardEntries 字段,但 module-puzzlespacetime-clientapi-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 当前成绩对应的拼图网格规格,至少区分 3x34x4
  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. 3x34x4 不混榜。
  5. 下一关开启后上一关榜单不会污染新关卡。