# 拼图运行时真实排行榜落地说明 更新时间:`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. 下一关开启后上一关榜单不会污染新关卡。