每日、四榜与公开回放API v0.8.1#
2026-09-13 · 设计态;基路径 /api/v1。共用基础API的HTTPS、Cookie会话、CSRF、Origin、expected_user_id、UUID、UTC毫秒、安全整数和错误包络。Redis仅用于每日榜单缓存及官方录像TTL存储。P=公开/匿名会话,G=云游客或注册会话,R=活跃注册账号,O=本人操作资源,V=当前观看会话所有者;所有权限由服务端推导。
1. 公共写入规则#
登录写接口要求 Idempotency-Key;同user/route(含board_kind)/key同内容返回原回执,不同内容409。回执与对象在同一SQLite事务提交,不以网络超时判断“肯定没成功”。接管使用控制epoch/CAS。每条WS输入也重新鉴权和校验控制epoch,不能只在握手检查一次。
压缩官方证据只走本文件upload路由,不走private /blobs、/sync/push 或个人收藏接口。线上结果不能由普通run.finish升级成正式成绩。直播与PVP路由、消息枚举和专用表全部不注册,客户端不展示对应入口。
| 错误码 | HTTP | 含义与玩家处理 |
|---|---|---|
| MAP_UNAVAILABLE / VALIDATION_BUSY | 503 | 题未通过生成验证/验证容量不足,普通单人仍可玩 |
| DATE_NOT_OPEN / BOARD_FROZEN | 409 | 非当前日/截止已过,不排队补榜 |
| NOT_READY / INVALID_ATTEMPT_SET | 422 | 综合三段未齐,或所选榜1/3段归属/日期/难度不符 |
| ACTIVE_COMPETITION_EXISTS / CONTROL_CHANGED | 409 | 返回当前局或新控制epoch,不自动抢占 |
| RANKING_LOCKED / RANKING_WITHDRAWN / EXPOSURE_UNCERTAIN | 409 | 停止竞榜提交,私人进度保留 |
| VIEW_NOT_ELIGIBLE | 403 | 当日三档未确认完成;不返回棋盘 |
| UPLOAD_GRANT_EXPIRED / REPLAY_EXPIRED | 410 | 授权/录像到期,不复活已结束日期 |
| EVIDENCE_MISMATCH / INVALID_REPLAY | 422 | 摘要/时间/重演不符,不能入榜 |
| INSUFFICIENT_TIME | 409 | 剩余时间不满足当前处理预算,不承诺赶上截止 |
| SOURCE_MANIFEST_REQUIRED | 409 | 原设备尚未登记官方段清单,保留段ID等待,不签发无hash授权;清单齐后用新请求key重试 |
| PENDING_SUBMISSION_EXISTS | 409 | 先继续或显式取消当前组合 |
| REPLAY_STORAGE_UNAVAILABLE / REPLAY_STORAGE_FULL | 503 | Redis录像写入/确认不可用或noeviction拒绝写入;保留草稿与原请求身份,不能假报存储成功 |
| REPLAY_MISSING | 410 | 截止前元数据仍在但Redis原正文丢失,不把存储丢失判作弊;名次保留 |
| REPLAY_TOO_LARGE | 413 | 压缩输入超过1MiB或展开输出超过8MiB,立即中止;回包仅含limit/observed与阶段,无正文 |
| REPLAY_EVENT_LIMIT | 422 | 超过20,000条事件,不能截断证据继续入榜 |
| REPLAY_HASH_CONFLICT / UPLOAD_IN_PROGRESS | 409 | 同对局清单冲突,或既有上传接收中;返回本人原上传状态,不另收一份 |
| REPLAY_HASH_MISMATCH | 422 | 服务器计算的压缩或规范原文SHA-256与清单不一致 |
| UPLOAD_RATE_LIMITED | 429 | 账号/全局字节或并发额度不足,附Retry-After;失败字节也消耗额度 |
| PAYLOAD_TOO_LARGE / RATE_LIMITED | 413/429 | 本地保留草稿;受控重试,不无限增队列 |
2. 服务器每日题#
| 路由 / 权限 | 请求 | 返回/规则 |
|---|---|---|
GET /daily/:date/challenge P | 无 | {date,server_time,starts_at,ends_at,status,policy_revision,tiers:[{tier,puzzle_id,map_hash,width,height,mines,rules_revision,no_guess_required}]},没有seed/隐藏地图;未来日期拒绝 |
GET /daily/:date/practice-package P | 无 | 仅日期结束后返回不可变历史地图与规则清单/hash,可本机缓存离线练习;当前/未来拒绝 |
GET /daily/:date/me G | 无 | 三档普通/正式通关、无伤段IDs、日奖励状态、四榜各自提交状态/名次/差距、观看资格/锁、当前局 |
POST /daily/:date/games G/R | `{tier,entry_kind:"normal | ranked",policy_revision,accepted_disclosure:true}` |
GET /daily/:date/leaderboards P | 无 | 四榜摘要、共用date/status/generation/截止,无棋盘 |
GET /daily/:date/leaderboards/:board_kind P | 无 | 所选榜Top10元数据,1或3段引用;board_kind=easy/medium/expert/overall,缺失或未知422;截止后frozen |
POST /daily/:date/withdraw R | {base_version,confirm:true} | 当日四榜撤公开排名并禁止重进,可从已验证候补递补;历史只加注记/撤播放,不改排序 |
GET /daily/:date/replay-sets/:user_id P | board_kind必填 | 返回指定榜当前/冻结Top10的1或3段元数据及观看要求;正文权限由watch独立检查,返回元数据不锁榜 |
历史补打由历史练习包+私人同步完成,不创建新的历史正式attempt。当前日仅有服务器地图不存在合法本地生成降级。今日normal受伤完成可推进下一档及今日观看资格;ranked额外记录零伤段,受伤完成仍可推进下一档。每日原有经验用同一权益账本,不能因normal与ranked各领一次。
3. 四榜成绩预选与官方录像#
POST /daily/:date/leaderboards/:board_kind/scores R:主动选择参榜或已有候选自动上传设置后调用。board_kind为easy/medium/expert/overall;单项只需对应档,综合三档齐全但不要求单项Top10。具体四榜联动见四榜规格。
单项请求例(示意ID/hash,实际须合法UUID/hex):
{
"attempts":{"easy":"uuid-easy"},
"client_total_ms":40000,
"replay_manifest":[{"attempt_id":"uuid-easy","format_version":1,"codec":"gzip",
"compressed_bytes":32000,"uncompressed_bytes":180000,
"compressed_sha256":"64位hex","content_sha256":"64位hex"}],
"allow_public_replay":true
}overall的attempts强制easy/medium/expert三个key,manifest覆盖三段;单项拒绝额外档字段。已有核验段使用 {attempt_id,verified_segment_id} 替代上传描述;其他设备已登记官方清单的段用 {attempt_id,registered_manifest:true},服务器解析为固定双hash/字节数再预选。未登记的跨端段返回SOURCE_MANIFEST_REQUIRED并标记缺失attempt,不发未绑定hash的授权。client_total_ms只是诊断,服务端从终局重算单项用时或三档之和。不能因单项未入围而拒绝独立满足综合榜条件的组合。
响应 {submission_id,board_kind,state,compared_generation,provisional_rank,expires_at,uploads:[{upload_id,attempt_id,max_compressed_bytes}],reused_segments:[]}。未入围state=not_candidate,无uploads;候选名次不等于正式占位。每账号/日期/榜最多一个pending(总计4个),每账号共享上传/请求配额。
不同设备的三段按账号在服务端关联;任何本人登录设备可读取待上传授权并提交自己持有的段,不要求综合申请设备有三份文件。未上线设备缺段时保持等待,截止前缺段不入榜。同段同时被多榜申请时按game_id/hash去重归档,不能复制一份正文到四个目录。
| 路由 / 权限 | 请求 | 响应/约束 |
|---|---|---|
POST /online/games/:id/record-manifest O | {format_version,codec,compressed_bytes,uncompressed_bytes,compressed_sha256,content_sha256} | 仅本人已结束正式段的官方清单;device取会话,绑定锚点,未知字段拒绝,同game不同清单409;不传正文/个人收藏元数据 |
GET /daily/replay-uploads/pending R | 无 | 本人有效缺失段授权/attempt元数据,无个人收藏目录,供同账号各设备恢复上传 |
GET /daily/submissions/:id O | 无 | 当前state、各段state、expires_at、验证错误、published_generation?;自己的提交不泄露他人证据 |
DELETE /daily/submissions/:id O | 无 | 仅awaiting_upload/verifying可取消,置expired、撤授权;已published不能借此撤成绩,须withdraw |
PUT /daily/replay-uploads/:upload_id O | application/octet-stream,body是gzip应用文件,无HTTP Content-Encoding;字节/双hash对照清单 | 完整接收/hash校验后,原子SET Redis正文+绝对TTL,持久化确认并写DB元数据才标received;同hash重复返回原状态,不同文件409;无授权/归属不符拒绝,部分请求不入库 |
POST /daily/submissions/:id/complete O | {manifest_hash} | 202 verifying,幂等创建可靠验证任务;缺段409,不发布该榜缺段成绩;已经合法发布的其他单项保留 |
授权即SQLite记录+会话归属,不使用可转发的永久预签名直链。PUT中途过期/跨日须终止;Redis暂存绑定授权TTL自然回收,无录像清理job。先限制压缩字节,服务器受控解压再查展开上限、事件数、哈希与重演;即使客户端申报小尺寸也强制实际计数。
先manifest/成绩预选,再按本人game+规范内容SHA-256查已验证文件;有效正文仍在则返回reused_segments,不发上传授权。已有上传/验证返回原id和状态,不重建任务。PUT前预检原回执与Redis实际存在状态,重复成功请求不再读文件;并发同段返回UPLOAD_IN_PROGRESS。gzip文件hash用于传输校验,规范内容hash用于识别同一录像,首次接收两者由服务器重算;不同账号不可互查去重状态。具体流式拒收、双hash语义与竞态见Redis上传限制和去重规则。
状态:not_candidate;或 awaiting_upload → verifying → published | outpaced | rejected | expired。没缺失段也仍走complete(或服务端等价调度)和原子再比较,发布前在Redis确认正文及固定public_until TTL,随后在DB事务复制未变化榜、切四榜共用generation,commit后写榜单缓存;跨库不能声称原子,缓存失败回源DB;不凭“以前上传过”跳过当前日期/观看锁校验。验证报告带attempt、输入hash、规则版本,事务内重新检查它们未变。
published表示该组合在该generation正式入榜,不保证之后仍在Top10;UI同时查询当前排名。outpaced表示证据合法但不再足够快;段可在当日复用。排队越过截止返回expired,不能第二天发布昨天名次。推送 daily.submission_changed带board_kind;daily.changed带共用generation/changed_boards,仅通知状态,断线以GET重取,不重复上传。
4. 在线控制与消息#
| 路由 / 权限 | 请求 | 返回 |
|---|---|---|
GET /online/me/active G | 无 | 本人当前每日在线局及设备控制元数据 |
GET /online/games/:id O | 无 | 自身可见快照、game_seq、control_epoch/input_epoch、时间/终局,不含隐藏雷阵 |
POST /online/games/:id/takeover O | {expected_control_epoch,confirm:true} | 新控制epoch,旧设备只读;计时不重置,提示本机若缺录像前缀则不能提交完整回放 |
POST /online/games/:id/resume O | {last_game_seq,control_epoch} | 当前权威投影、恢复nonce和必要短回执;ACK后允许新输入,不补执行离线点击 |
POST /online/games/:id/resign O | {control_epoch,confirm:true} | 放弃当前局、释放活动占用;已结束重试取原结果,不二次结算 |
wss://同源/api/v1/realtime 使用Cookie+精确Origin,hello带CSRF/expected_user_id/device_id;匿名仅能订阅绑定匿名Cookie的watch。退出/改密/删除即时撤授权,命令和敏感投影都复查会话。
{"type":"game.command","game_id":"uuid-game","command_id":"uuid-command",
"control_epoch":2,"input_epoch":3,"client_seq":42,
"action":{"type":"chord","cell":{"x":4,"y":7}}}action只允许 open/flag/unflag/chord+合法cell,另允许明确resign;禁止toggle重放导致反转、任意时间/seed/XP/结果写入。seq按game/user/control_epoch单调,input_epoch在恢复时变化。消息缺口暂停输入,重新获取快照;已提交command重试取回执,未提交离线操作丢弃。
服务器消息 {type,channel_id,channel_seq,server_time,payload},包括 game.started/state_delta/command_receipt/control_changed/result、daily.changed/submission_changed、watch.frame/revoked。ACK提供规范事件与服务器锚点供本地官方草稿追加;数据库只持久最小摘要、去重回执及当前状态。结果在写事务后确认,客户端的动画耗时不参与成绩。
5. 本期接口范围#
本版不注册直播/PVP接口、实时旁观订阅、场次与占位能力;未知旧路由返回404。普通经典/历史练习使用本地游戏与私人同步。保留每日WSS输入确认、本人候选通知与榜单变更通知,不提供任何其他玩家进行中棋盘的实时数据。watch仅接受下节定义的已发布公榜录像。
6. 观看与日切#
| 路由 / 权限 | 请求 | 行为 |
|---|---|---|
POST /watch/sessions P/R | {replay_segment_id,date,board_kind,generation,watch_id?} | 建立本人watch;历史录像允许匿名,今天每日仅R且三档确认完成;还没有可解码内容 |
POST /watch/sessions/:id/prepare V | {permission_epoch,consent_to_rank_lock?} | 今天每日先同意,返回exposure_id和不可解码预备帧;历史无需锁榜同意,直接准备播放 |
POST /watch/sessions/:id/release V | {exposure_id,permission_epoch} | 仅今天每日:事务建曝光屏障后给首帧解码材料 |
POST /watch/sessions/:id/rendered V | {exposure_id,render_receipt} | 今天实际绘制后ACK锁该日期;不是人眼证明 |
POST /watch/sessions/:id/cancel V | {exposure_id} | 仅准备可无锁取消;已释放无ACK→uncertain |
GET /watch/sessions/:id/chunks/:index V | generation,permission_epoch | 今天逐块鉴权/资格/锁流程;历史检查指定榜冻结Top10映射、未撤/未过期,不查通关;正文/块从Redis取,块缺失且body仍在才可重建 |
POST /watch/sessions/:id/close V | {} | 结束观看会话,不解除已有日期锁 |
POST /reports R | {resource_type,resource_id,reason} | 202 case_id,不因举报自动取消别人名次 |
POST /daily/submissions/:id/appeals O | {reason} | 202 case_id;复核可解释拒绝,日切后不补榜 |
匿名watch使用调用方生成watch_id绑定随机匿名HttpOnly Cookie;重复创建检查原资源、close幂等,不写要求user_id的online_http_receipts。登录watch联合绑定user_id/auth_session_id,不能用他人watch_id越权。历史不创建watch_exposures;当天来源跨日后重新评估并切历史路径,不能锁新日期。
四榜GET采用DB generation+Redis同代缓存,miss/Redis故障回源DB;昵称/注记/权限/播放可用性在响应时重查。录像读取不续TTL,DB截止已过返回REPLAY_EXPIRED,Redis连接故障返回503,健康Redis缺原正文返回REPLAY_MISSING。详见Redis契约。
各段授权不可跨资源继承。切换不同日期/榜单/录像段时重新校验;历史录像→今天录像必须先检查当日资格并提示日期锁,不能自动播放绕过确认。未完成当天者只看摘要,不因历史开放看到未结束日期棋盘。解码字节已发无法追回;服务器停止后续发放、官方播放器清缓冲。详情见观看边界。
7. 参数与验证边界#
除UTC日期、Top10容量、用户确认的规则外,以下均建议初值 [待确认][待测试];不是上线承诺。
| 参数 | 初值 | 依据/调节方法 |
|---|---|---|
| 上传授权 | 2分钟,且不晚于ends_at | 足够短请求重试;按真实移动网络P95上传时间调整,截止硬限制不放宽 |
| 每段压缩/解压上限 | 1MiB / 8MiB(1,048,576 / 8,388,608字节) | 含完整gzip封装;以100KiB容量假设留约10倍压缩余量,需真实高级长局P99校准;同步DDL,边界值允许,超1字节拒绝 |
| 每段事件上限 | 20,000条 | 独立于字节限制,约束解析和重演次数;按高级长局与无障碍操作样本校准 |
| 验证单任务 | CPU 5秒;2并发/队列20 | 控制CPU和内存;工作线程硬终止预算,超过显示未核验而非作弊 |
| 近截止保护 | 动态队列预计耗时+验证P99+提交预算 | 需压测取得,不能随意写固定魔法秒数;实际截止仍硬检查 |
| 待办组合 | 每账号/日期/榜1个,合计最多4个 | 允许四榜独立候选;账号总配额不随榜数放大 |
| 正式局重连 | 单次60秒/累计90秒;单档最长60分钟 | 沿用建议,宽限不暂停成绩时钟;到限本次结束,跨日不能入榜 |
| 心跳/失联检测 | 2秒/6秒 | 检测有误差,明确close立即生效,不声称瞬断精确识别 |
| WS操作 | 突发120、恢复60条/秒、帧16KiB | 支持键盘合法批量;队列与磁盘压力另限,不用私人sync低频额度 |
| 正文上传速率 | 每账号突发3MiB、恢复3MiB/分钟 | 一次综合最多3段各1MiB;跨设备/四榜共用,失败及重试接收的字节也计费额 |
| 上传并发/全局字节额度 | 每账号1路、全局4路;全局突发4MiB、恢复16MiB/分钟 | 入口最多同时接收4段,约束多账号累计流量;超限不排队读取正文,按部署带宽调参 |
| 上传连接期限 | 空闲10秒、整请求60秒,且不超过授权截止 | 防慢速占连接;1MiB在60秒需约17KiB/s,需弱网测试,不延长日切 |
| 预选频率 | 每账号突发6次、恢复6次/分钟 | 单档完成/改善即评估单项,三档齐再评估综合,不按帧轮询;另设全局队列保护 |
| 观看授权 | 60秒续租;短缓冲2秒 | 限制权限撤销传播时间;历史也不承诺永久直链 |
| 公榜回放 | 原日结束后30天 | 有界公开存储;到期不改变名次 |
本期单SQLite写协调器,验证线程不能直接绕过裁决器写榜;限流内存有界,重启只允许短突发重置,奖励、截止与授权持久化不可重置。端到端验收见矩阵;DDL通过不等于HTTP/WSS/压缩/地图/重演已实现。
8. 版本记录#
| 版本 | 日期 | 变化 |
|---|---|---|
| 0.8.1 | 2026-09-13 | 收紧录像大小/事件上限;双hash上传前去重;补流式拒收、配额、慢连接及验证资源保护 |
| 0.8.0 | 2026-09-13 | Redis限每日榜单缓存与官方录像TTL存储;录像正文移出SQLite,不设录像到期清理job;补跨库故障与持久化边界 |
| 0.7.0 | 2026-09-13 | 移除直播路由/消息/参数;watch仅接受每日公榜录像 |
| 0.6.0 | 2026-09-13 | 四榜独立候选与共享录像、跨端分段提交 |