二七王文档:同步 design 流程/协议,新增合规核对

design.md
- 修正选主/埋牌顺序笔误(确认「先选主后埋牌」)
- 投降改为选主阶段与选主互斥的选择(不选主/不埋牌直接结算),算奖用庄家36张、无连对链
- 70分暗牌改为「庄家摸牌之前、向所有玩家亮3秒」
- 选主阶段前端显示每花色对数、70分并列投降按钮
- 新增 §12「完整牌局游玩流程」(含大局与阶段速览)

packet_protocol.md(随代码如实同步)
- 结算 aset/seatlist 新结构(multiple=基础子数、upgrade 判定倍率、bangwang/climb、
  chongguan/wang/naward/grade_aw);shangzhuang bottomcards 按70分门控 + ancard3s
- maipai/PushCards 亮牌 liangpai;chupai info/baozhu 按查牌门控;新增 mingpai 收发包
- step 枚举去掉废弃的投降4;删除 Surrender 重连视图

docs/compliance/(新增)
- 00 初步不一致清单、01 design 合规逐节核对(含整改进度与 roomtype 位串约定)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 22:34:06 +08:00
co-authored by Claude Opus 4.8
parent 6120580505
commit dcf8e09f7d
4 changed files with 490 additions and 60 deletions
@@ -42,7 +42,7 @@
| seat | 整数 | 叫分者的位置序号 |
| call | 整数 | 分数,0表示不叫 |
| currcall | 整数 | 当前叫到的分数 |
| multiple | 整数 | 倍数 |
| multiple | 整数 | 叫分对应的基础子数(`get_multiple_bycall`,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0)|
| nextseat | 整数 | 下一个叫分者的位置序号 |
| countdown | 整数 | 叫分倒计时 |
@@ -58,11 +58,12 @@
| call | 整数 | 分数,0表示不叫 |
| banker | 整数 | 庄家的位置序号 |
| grade | 整数 | 庄家的叫分 |
| multiple | 整数 | 倍数 |
| bottomcards | 数组 | 底牌 |
| cards | 数组 | 拿了底牌后手上的牌,包括底牌,庄家才有此属性,闲家没有该属性 |
| multiple | 整数 | 叫分对应的基础子数(`get_multiple_bycall`,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0)|
| bottomcards | 数组 | 8 张暗牌。**庄家恒有**;**闲家仅当 70 分坐庄时才有**(供 3 秒亮牌用),非 70 分时闲家没有该属性(暗牌只有庄家可见,design §4)|
| ancard3s | 整数 | 仅 70 分坐庄时出现且为 `1`:表示庄家把暗牌摸入手前,需将 `bottomcards` 这 8 张暗牌向所有玩家亮出 3 秒(design §4/§7.1);非 70 分无此属性 |
| cards | 数组 | 拿了暗牌后手上的牌(36 张,含暗牌),庄家才有此属性,闲家没有该属性 |
| countdown | 整数 | 选主倒计时 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许(仅 70 分坐庄为 1)。投降与选主互斥、同为选主阶段(step2)的决策,见 touxiang 包与 design §4 |
---
@@ -76,7 +77,7 @@
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 庄家的位置序号(`mod.js` 中校验 `o_paiju.banker != seat`,只有庄家能投降;选主/埋牌/投降阶段均可发起) |
| seat | 整数 | 庄家的位置序号。投降是**选主阶段(step 2)与选主互斥**的选择:`mod.touxiang` 仅在 `step==2`、`banker==seat`、`call==70` 时受理;点投降即直接结算,不选主、不埋牌、不出牌(design §4) |
---
@@ -131,8 +132,9 @@
| --- | --- | --- |
| cards | 数组 | 埋牌后手上的牌,去掉了埋牌,庄家才有此属性,闲家没有该属性 |
| bottomcards | 数组 | 埋的牌,庄家才有此属性,闲家没有该属性 |
| seat | 整数 | 出牌者的位置序号 |
| seat | 整数 | 出牌者的位置序号(即将首出的庄家)|
| countdown | 整数 | 出牌倒计时 |
| liangpai | json | 庄家亮牌信息(design §8.2),**只有闲家、且可查牌模式、且庄家埋牌后手牌达标时才有**。结构(各字段按是否达标出现):`zhu` 主牌总数、`zhupair` 主对子数、`zhutuo` 主拖拉机组数(固定主牌王+2+7 总数 ≥10 时有这三项);`wang` 王数(王≥3 时);`qi` 7 数(7≥6 时);`er` 2 数(2≥6 时)。都不达标则无此属性 |
---
@@ -158,14 +160,15 @@
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 出的牌id列表 |
| info | 数组 | 出牌者的座位牌况 `o_paiju.seatlist[seat]`,5 元素数组:前 4 个对应花色1~4 的 `[无该花色标志, 该花色无对标志]`(0/1),第 5 个为报副情况 `[剩余主牌数, 剩余主对数]`(初始 `[-1,-1]`,报无主后为实际数量) |
| count | 整数 | 出牌数量 |
| cards | 数组 | 实际打出的牌id列表;**甩错时**(见 `shuaicuo`)为被强制打出的那一张最小主牌单张 |
| shuaicuo | 整数 | 甩错标志,仅甩错时出现且为 `1`(design §5.4.5:甩牌未通过最大性判定,整套甩牌收回,本轮只强制打出最小一张、失去本轮甩牌资格);正常出牌无此属性 |
| info | 数组 | 出牌者的座位牌况 `o_paiju.seatlist[seat]`,**仅可查牌模式下发**(不查牌无此属性,design §9)。5 元素数组:前 4 个对应花色1~4 的 `[无该花色标志, 该花色无对标志]`(0/1),第 5 个为报副情况 `[剩余主牌数, 剩余主对数]`(初始 `[-1,-1]`,报无主后为实际数量) |
| count | 整数 | 出牌数量(甩错时为 1) |
| flower | 整数 | 出牌花色 |
| cardtype | 整数 | 出牌牌型 |
| nextseat | 整数 | 下一个出牌者的位置序号 |
| countdown | 整数 | 下一个出牌者的出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0` 即为 1),供客户端在可查牌模式下展示他家主牌数量/对子结构 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0`)。**仅可查牌模式**下才可能为 1,供客户端展示他家主牌数量/对子结构并出现"明牌"按钮;**不查牌模式恒为 0**(design §9)|
| cardsinhand | 数组 | 出牌者出牌后手上剩下的牌id列表,只有出牌者才有此属性 |
---
@@ -178,10 +181,10 @@
| --- | --- | --- |
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 出的牌id列表 |
| info | 数组 | 出牌者的座位牌况 `o_paiju.seatlist[seat]`,5 元素数组:前 4 个对应花色1~4 的 `[无该花色标志, 该花色无对标志]`(0/1),第 5 个为报副情况 `[剩余主牌数, 剩余主对数]`(初始 `[-1,-1]`,报无主后为实际数量) |
| info | 数组 | 出牌者的座位牌况 `o_paiju.seatlist[seat]`,**仅可查牌模式下发**(不查牌无此属性,design §9)。5 元素数组:前 4 个对应花色1~4 的 `[无该花色标志, 该花色无对标志]`(0/1),第 5 个为报副情况 `[剩余主牌数, 剩余主对数]`(初始 `[-1,-1]`,报无主后为实际数量) |
| nextseat | 整数 | 下一个出牌者的位置序号 |
| countdown | 整数 | 出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0` 即为 1),供客户端在可查牌模式下展示他家主牌数量/对子结构 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0`)。**仅可查牌模式**下才可能为 1,供客户端展示他家主牌数量/对子结构并出现"明牌"按钮;**不查牌模式恒为 0**(design §9)|
| cardsinhand | 数组 | 出牌后出牌者手上剩下的牌id列表,只有出牌者才有此属性 |
---
@@ -194,16 +197,43 @@
| --- | --- | --- |
| seat | 整数 | 出牌者的位置序号 |
| cards | 数组 | 出的牌id列表 |
| info | 数组 | 出牌者的座位牌况 `o_paiju.seatlist[seat]`,5 元素数组:前 4 个对应花色1~4 的 `[无该花色标志, 该花色无对标志]`(0/1),第 5 个为报副情况 `[剩余主牌数, 剩余主对数]`(初始 `[-1,-1]`,报无主后为实际数量) |
| info | 数组 | 出牌者的座位牌况 `o_paiju.seatlist[seat]`,**仅可查牌模式下发**(不查牌无此属性,design §9)。5 元素数组:前 4 个对应花色1~4 的 `[无该花色标志, 该花色无对标志]`(0/1),第 5 个为报副情况 `[剩余主牌数, 剩余主对数]`(初始 `[-1,-1]`,报无主后为实际数量) |
| nextseat | 整数 | 下一个出牌者的位置序号 |
| countdown | 整数 | 出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0` 即为 1),供客户端在可查牌模式下展示他家主牌数量/对子结构 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0`)。**仅可查牌模式**下才可能为 1,供客户端展示他家主牌数量/对子结构并出现"明牌"按钮;**不查牌模式恒为 0**(design §9)|
| cardsinhand | 数组 | 出牌后出牌者手上剩下的牌id列表,只有出牌者才有此属性 |
| maxseat | 整数 | 本轮出牌谁最大,只有本轮最后一个玩家出牌后才有此属性 |
| grade | 整数 | 本轮闲家得分,只有本轮最后一个玩家出牌后且闲家有得分才有此属性 |
---
## 13.5 明牌(mingpai,客户端→服务器)
发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:mingpai
查看另外两家手中全部主牌的具体牌面(design §9)。服务端仅在**可查牌模式、出牌阶段(step5)、且已有玩家报无主**(`have_baofu()`)时受理;否则忽略。
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| agentid | 字符 | 代理id |
| playerid | 整数 | 玩家id |
| gameid | 字符 | 游戏id |
| roomcode | 整数 | 房间号 |
| seat | 整数 | 请求者的位置序号 |
## 13.6 明牌(mingpai,服务器→客户端)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:mingpai (只回发给请求者)
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| seat | 整数 | 请求者的位置序号 |
| others | 数组 | 另外两家各自未出的全部主牌,元素 `{ seat: 位置序号, zhucards: [主牌id列表] }` |
> "再点一次取消查看"是客户端的显示开关,无需再请求服务端。
---
## 14. 结算(jiesuan)
发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:jiesuan
@@ -239,27 +269,31 @@
| --- | --- | --- |
| banker | 整数 | 庄家的位置序号,-1:无庄 |
| call | 整数 | 庄家叫分,-1:无叫分 |
| multiple | 整数 | 叫分倍数 `get_multiple_bycall(call)`:call>60→1,call>40→2,call>0→4;无叫分(call≤0)→0 |
| multiple | 整数 | 基础子数 `get_base_bycall(call, climb)`(design §7.1/§7.3.1):常规算子 65→2、60→3、55→4、50 及以下→6、70打牌→2;投降固定 1、解散 0;无叫分 0 |
| flower | 整数 | 主牌花色,-1:无主牌花色 |
| grade | 整数 | 闲家得分 |
| upgrade | 整数 | 升级级数:0中途解散 1过庄 2小光 4大光 -1倒庄 <-1升几级 -99投降 |
| grade | 整数 | 闲家捡分(含抠底) |
| upgrade | 整数 | 判定倍率(带符号,design §7.2.0):3大光 / 2小光 / 1过庄 / -N升N级(倒庄)/ -99投降 / 0解散或无判定 |
| bangwang | 整数 | 本局是否启用傍王规则 0否 1是(roomtype 位2) |
| climb | 整数 | 本局是否启用爬坡规则 0否 1是(roomtype 位3) |
| seatlist | 数组 | 玩家列表,元素结构见下 |
`seatlist` 数组元素结构:
```json
{
"cards": [], // 王牌或冲关牌列表
"obsolete": 0, // 冲关作废标志 0不作废 1作废
"chongguan": 0, // 冲关数
"grade_cg": 0, // 冲关得分
"grade_w": 0, // 傍王得分
"grade_jf": 0, // 捡分得分
"grade": 0, // 本局总分
"cards": [], // 算奖相关牌(王/冲关牌)列表
"chongguan": 0, // 常规算奖奖数(只有庄家才计入自己的 N)
"wang": 0, // 手牌中的王数(傍王按此计奖)
"naward": 0, // 该家总奖数 N =(庄家?常规算奖:0)+(傍王?王数:0)
"grade_aw": 0, // 算奖得分 = X×(2Ni−Nj−Nk),X 为每对子子数
"grade_jf": 0, // 捡分子数得分
"grade": 0, // 本局总分 = grade_aw + grade_jf
"score": 0 // 累计得分
}
```
> 结算数值模型(design §7~§8):每「庄–闲」对子的基础金额 `X = multiple × |upgrade|`(投降 X=1、解散 X=0)。捡分子数:庄赢时两闲家各付庄家 X、庄家收 2X;闲赢(升级)时庄家各付两闲家 X。算奖:持有 N 奖的玩家从另外两人各多收 `X×N`,三家两两独立叠加(含闲–闲),即 `grade_aw = X×(2Ni−Nj−Nk)`。
**account(大局结算包)**
> 仅当打到最后一局(`o_paiju.idx >= o_room.asetcount`)或房间中途解散(解散结算)时,`jiesuan` 包才会带上此分组;普通的中间局结算包没有 `account` 字段(`class.paiju.js` `get_paiju_account`)。
@@ -311,7 +345,7 @@
| count | 整数 | 总局数 |
| idx | 整数 | 当前局数 |
| PlayerInfo | 数组 | 三个玩家目前的总积分 `[0,0,0]` |
| step | 整数 | 牌桌状态:1发完牌叫分 2选主 3埋牌 4投降 5出牌 6结算 |
| step | 整数 | 牌桌状态:1发完牌叫分 2选主/投降 3埋牌 5出牌 6结算(无独立投降阶段——投降在 step2 与选主互斥;埋牌后直接进入 step5 出牌) |
| MyCards | 数组 | 自己手上的牌 |
**CallRun(不在叫分阶段无此属性)**
@@ -321,58 +355,50 @@
| seat | 整数 | 当前叫分位置 |
| countdown | 整数 | 叫分倒计时 |
| nowcall | 整数 | 当前叫分 |
| multiple | 整数 | 倍数 |
| multiple | 整数 | 叫分对应的基础子数(`get_multiple_bycall`,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0)|
| call | 数组 | 三家叫分 `[null,0,65]`,null还未叫分,0不叫,>0叫了多少分 |
**ChooseMain(不在选主阶段无此属性)**
**ChooseMain(step 2 选主/投降阶段,不在此阶段无此属性)**
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 倍数 |
| multiple | 整数 | 叫分对应的基础子数(`get_multiple_bycall`,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0)|
| countdown | 整数 | 选主倒计时 |
| bottomcards | 数组 | 底牌 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许 |
| bottomcards | 数组 | 8 张暗牌,**只有庄家有此属性**(暗牌仅庄家可见;70 分的 3 秒亮牌是上庄时的一次性事件,重连不重放)|
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许(仅 70 分坐庄为 1)。投降与选主互斥、同一决策点,见 touxiang 包与 design §4 |
**BuryCards(不在埋牌阶段无此属性)**
> 选主阶段前端需在每个花色按钮上显示"该花色在庄家手中的对子数"(design §4/§11)——庄家的完整手牌由 `MyCards` 提供(庄家为 36 张),对子数由前端据此计算,服务端不额外下发。
**BuryCards(step 3 埋牌阶段,不在此阶段无此属性)**
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 倍数 |
| multiple | 整数 | 叫分对应的基础子数(`get_multiple_bycall`,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0)|
| flower | 整数 | 主牌花色 |
| countdown | 整数 | 埋牌倒计时 |
| bottomcards | 数组 | 底牌 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许 |
| bottomcards | 数组 | 8 张暗牌,**只有庄家有此属性**(暗牌仅庄家可见;70 分的 3 秒亮牌是上庄时的一次性事件,重连不重放)|
**Surrender(不在投降阶段无此属性)**
> 埋牌阶段无投降(投降是 step2 与选主互斥的选择,选主后即不可再投降)。
**PushCards(step 5 出牌阶段,不在此阶段无此属性)**
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 倍数 |
| flower | 整数 | 主牌花色 |
| countdown | 整数 | 出牌倒计时 |
| bottomcards | 数组 | 埋的牌,只有庄家有此属性 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许 |
**PushCards(不在出牌阶段无此属性)**
| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| banker | 整数 | 庄家 |
| call | 整数 | 叫分 |
| multiple | 整数 | 倍数 |
| multiple | 整数 | 叫分对应的基础子数(`get_multiple_bycall`,按常规算子取值:65→2 60→3 55→4 50及以下→6 70→2 无叫分→0)|
| flower | 整数 | 主牌花色 |
| countdown | 整数 | 出牌倒计时 |
| bottomcards | 数组 | 埋的牌,只有庄家有此属性 |
| grade | 整数 | 当前的捡分分数 |
| gradecards | 数组 | 当前的捡分分牌,只有闲家有此属性 |
| playproc | json | 当前出牌情况 `o_paiju.playproc`:`round` 第几轮、`start` 本轮首出位置、`currseat` 当前出牌位置、`startcount` 首出张数、`startflower` 首出花色、`starttype` 首出牌型、`maxseat` 本轮最大者位置、`maxcard` 最大牌编码、`cards` 本轮三家各自出的牌(数组下标=位置序号) |
| seatlist | 数组 | 三家座位牌况,元素结构同上文 chupai 包的 `info`(每个玩家一个 5 元素数组:4 个花色的 `[无该花色,无对]` + 报副 `[剩余主牌数,剩余主对数]`) |
| seatlist | 数组 | 三家座位牌况,元素结构同上文 chupai 包的 `info`(每个玩家一个 5 元素数组:4 个花色的 `[无该花色,无对]` + 报副 `[剩余主牌数,剩余主对数]`)。**仅可查牌模式下有此属性**(design §9)|
| liangpai | json | 庄家亮牌信息,结构同 maipai 包的 `liangpai`;**仅可查牌模式、且请求者为闲家、且庄家达标时有**(供闲家重连后仍能看到亮牌,design §8.2)|
| pushlist | 数组 | 出牌历史 `[[[], [], []], [[], [], []], ...]`,外层下标=轮次,内层 3 个数组按位置序号存该轮各家出的牌id(已按大小排序) |
**Balance(不在结算阶段无此属性)**