MINESWEEPER 系统设计
原型文档 · 非正式发布返回游戏 ↗
工程契约设计 v0.8.1

每日、四榜与公开回放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_BUSY503题未通过生成验证/验证容量不足,普通单人仍可玩
DATE_NOT_OPEN / BOARD_FROZEN409非当前日/截止已过,不排队补榜
NOT_READY / INVALID_ATTEMPT_SET422综合三段未齐,或所选榜1/3段归属/日期/难度不符
ACTIVE_COMPETITION_EXISTS / CONTROL_CHANGED409返回当前局或新控制epoch,不自动抢占
RANKING_LOCKED / RANKING_WITHDRAWN / EXPOSURE_UNCERTAIN409停止竞榜提交,私人进度保留
VIEW_NOT_ELIGIBLE403当日三档未确认完成;不返回棋盘
UPLOAD_GRANT_EXPIRED / REPLAY_EXPIRED410授权/录像到期,不复活已结束日期
EVIDENCE_MISMATCH / INVALID_REPLAY422摘要/时间/重演不符,不能入榜
INSUFFICIENT_TIME409剩余时间不满足当前处理预算,不承诺赶上截止
SOURCE_MANIFEST_REQUIRED409原设备尚未登记官方段清单,保留段ID等待,不签发无hash授权;清单齐后用新请求key重试
PENDING_SUBMISSION_EXISTS409先继续或显式取消当前组合
REPLAY_STORAGE_UNAVAILABLE / REPLAY_STORAGE_FULL503Redis录像写入/确认不可用或noeviction拒绝写入;保留草稿与原请求身份,不能假报存储成功
REPLAY_MISSING410截止前元数据仍在但Redis原正文丢失,不把存储丢失判作弊;名次保留
REPLAY_TOO_LARGE413压缩输入超过1MiB或展开输出超过8MiB,立即中止;回包仅含limit/observed与阶段,无正文
REPLAY_EVENT_LIMIT422超过20,000条事件,不能截断证据继续入榜
REPLAY_HASH_CONFLICT / UPLOAD_IN_PROGRESS409同对局清单冲突,或既有上传接收中;返回本人原上传状态,不另收一份
REPLAY_HASH_MISMATCH422服务器计算的压缩或规范原文SHA-256与清单不一致
UPLOAD_RATE_LIMITED429账号/全局字节或并发额度不足,附Retry-After;失败字节也消耗额度
PAYLOAD_TOO_LARGE / RATE_LIMITED413/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:"normalranked",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 Pboard_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):

json
{
  "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 Oapplication/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。退出/改密/删除即时撤授权,命令和敏感投影都复查会话。

json
{"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/resultdaily.changed/submission_changedwatch.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 Vgeneration,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.12026-09-13收紧录像大小/事件上限;双hash上传前去重;补流式拒收、配额、慢连接及验证资源保护
0.8.02026-09-13Redis限每日榜单缓存与官方录像TTL存储;录像正文移出SQLite,不设录像到期清理job;补跨库故障与持久化边界
0.7.02026-09-13移除直播路由/消息/参数;watch仅接受每日公榜录像
0.6.02026-09-13四榜独立候选与共享录像、跨端分段提交