二七王:协议文档修正解散结算的真实投递方式,补 roomtype 位串表

以 packet_protocol.md 本身为核对对象(前四轮只在改代码时顺手同步、从未反向
验证「文档写的 = 客户端实际会收到的」),查出两处:

- §14 把解散结算写成 route=erqiwang / rpc=jiesuan 的独立包,实际平台是把
  get_disbandRoom 的返回值整个塞进房间路由的 route=room / rpc=free_room 包,
  即 data.deskfree = { rpc:"jiesuan", data:{...} },比文档多一层 data 包装,
  真实取值路径是 data.deskfree.data.aset;前端照原文档写会取空。新增 §14.1
  给出真实包结构、取值要点、两层 success 的归属与 deskfree 可能缺失的情形。
- 文档多处引用「roomtype 位2/位3」,却从未给出位串定义(只存在于 compliance
  文档里),前端无法据协议拼建房串。新增 §0.5 位表 + 缺省示例 + 兜底规则 +
  局数/扣卡 4 组合对照,并指明 class.config.js parse() 为唯一解析入口。

compliance 记第五轮核对:design 维度逐条复核未发现新的不一致;新发现 4 项
(F1 跟牌入参顺序、F2/F3 协议文档、F4 解散空守卫)已全部处置,并留痕本轮
确认无误、勿再重提的三点观察。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-23 12:18:18 +08:00
co-authored by Claude Opus 5
parent ce4c972b7b
commit 2fd5829551
2 changed files with 78 additions and 1 deletions
@@ -57,6 +57,29 @@
`deskinfo` 由平台组装在 `pack.data.deskinfo` 下(见文末「断线重连」),`data.success` 由**平台**填写,子游戏不注入。
### 0.5 `roomtype` 房间选项位串(建房参数,下文多处引用「位2 / 位3」)
`roomtype` 是**建房时**由客户端拼好、经平台建房流程存在 `o_room.roomtype` 上的**定长数字位串(字符串)**,每一位(`charAt`)一个开关 `'0'/'1'`,便于前端逐项勾选。它不是某个 erqiwang 收发包的字段,但下文多个包的 `multiple` / `bangwang` / `climb` / `baozhu` / `seatlist` / `liangpai` / `pushlist` 都按它取值或门控,故在此给出唯一定义。服务端解析入口是 `class.config.js` 的 `parse()`(SSOT,不得在别处按下标硬解析)。
| 位 | 含义 | `'0'` | `'1'` | design |
| --- | --- | --- | --- | --- |
| 0 | 局数 | 6 局(缺省)| 12 局 | §10.1 |
| 1 | 扣卡方式 | 房主扣卡(缺省)| AA 每人扣卡 | §10.1 |
| 2 | 傍王 | 关(缺省)| 开 | §8.3 / §10.3 |
| 3 | 爬坡 | 常规算子(缺省)| 爬坡 | §7.3 / §10.3 |
| 4 | 查牌模式 | 可查牌(缺省)| 不查牌 | §9 / §10.2 |
缺省 `"00000"` = 6局 / 房主扣卡 / 无傍王 / 常规算子 / 可查牌。示例 `"10100"` = 12局 / 房主扣卡 / 傍王开 / 常规算子 / 可查牌。解析对**缺失、非字符串、过短**的 `roomtype` 一律按 `'0'` 处理。
对应的房卡与局数(`export.get_asetcount` / `get_needroomcard` / `get_needroomcard_joinroom`,design §10.1):
| 扣卡方式(位1) | 局数(位0) | 房主开房扣 | 加入者扣 |
| --- | --- | --- | --- |
| 房主扣卡 `'0'` | 6 局 | 2 张 | 0 |
| 房主扣卡 `'0'` | 12 局 | 4 张 | 0 |
| AA 每人 `'1'` | 6 局 | 1 张 | 1 张 |
| AA 每人 `'1'` | 12 局 | 2 张 | 2 张 |
---
## 1. 发牌(fapai)
@@ -322,7 +345,34 @@
- **正常出牌结算**(`mod.chupai` 中最后一张牌出完,`get_paiju_account(0, ...)`):含 `chupai` + `bottom` + `aset`(末局再加 `account`)。
- **投降结算**(`mod.touxiang`,`get_paiju_account(1, ...)`):只含 `aset`(末局再加 `account`),无 `chupai`、无 `bottom`。
- **解散结算**(`export.get_disbandRoom`,`get_paiju_account(2, ...)`):只含 `aset` + `account`,无 `chupai`、无 `bottom`。
- **解散结算**(`export.get_disbandRoom`,`get_paiju_account(2, ...)`):只含 `aset` + `account`,无 `chupai`、无 `bottom`。**投递方式与上面两种不同,见 §14.1。**
上面这张表头(`route: erqiwang / rpc: jiesuan`)**只适用于前两种**——正常出牌结算与投降结算,由子游戏自己 `sendpack_toother` 广播,客户端在 `rpc == "jiesuan"` 的分支里按 `data.aset` 取值。
### 14.1 解散结算的投递方式(与前两种不同,客户端需单独处理)
解散**不是**由子游戏发包,而是平台在解散流程里回调 `youle_erqiwang.export.get_disbandRoom(o_room)`,把**返回值整个对象**塞进**房间路由**的解散包里下发(平台 `server_room/rpc.js`、`server_room/class.room.js`)。因此客户端收到的是:
```json
{
"app": "youle", "route": "room", "rpc": "free_room",
"data": {
"seats": [],
"deskfree": {
"rpc": "jiesuan",
"data": { "success": true, "aset": {}, "account": [] }
}
}
}
```
要点(照 §14 的表去 `data.aset` 取值会取空):
- **rpc 是平台的 `free_room`(route 也是 `room`),不是 `erqiwang/jiesuan`**;子游戏返回的 `rpc: "jiesuan"` 只是嵌在 `deskfree` 里的一个普通字段。
- **多一层 `data` 包装**:真实取值路径是 `data.deskfree.data.aset` 与 `data.deskfree.data.account`。
- 本包的成败标志有两层:外层 `data.success` 由**平台**填写(解散流程本身成功与否),子游戏结算自带的 `success` 在 `data.deskfree.data.success`。§0.1「每个包 `data` 必带 `success`」说的是子游戏自己发的包;本包外层归平台。
- `aset` / `account` 的字段结构与下面 §14 的表完全一致(`aset.multiple = 0`、`upgrade = 0`、各家 `grade = 0`,即解散局不结算子数与算奖;`account` 恒有,见 design §12.2「按当前累计分结算」)。
- 若解散发生在**开战后、首局牌发出前**的窗口内(本游戏首局由 `makewar` 延迟 1 秒创建),`get_disbandRoom` 返回 `null`,平台走「不带 `deskfree`」分支——客户端需容忍 `data.deskfree` 缺失。
**chupai(出牌包,仅正常出牌结算存在)**