Files
erqiwang_youle/server/games/erqiwang/docs/compliance/02-测试计划.md
T
joywayerandClaude Opus 5 b6aeba412f 二七王:把「无超时托管」确认为规则,写入 design §11
服务端四个阶段只下发 countdown、没有任何定时器,此前被本轮核对记
为待办 D3(等规则设计者定义超时默认动作)。规则设计者已确认:
**维持现状,不需要超时动作**。

这属于规则本身如此、而非实现缺口。若不固化下来,下一轮核对会再次
把它当缺陷提出、甚至有人去补一套「超时自动出牌」,反而违背规则。
故写进 design.md 作为权威约定:

- design §11 新增一条:倒计时只作展示、不触发任何自动操作——不自
  动叫分/选主/埋牌/出牌,也不判负、不跳过;轮到谁而谁不操作,牌局
  就停在这一步等待。并加醒目说明:这是确认过的规则、不是待实现的
  缺口,牌局停住由玩家走房间解散流程收场(按当前累计分结算,
  §12.2),后续核对不要为它补超时托管。
  (解散路径已核实:平台 server_room 侧有 freeroom 解散倒计时,并
  回调子游戏 export.get_disbandRoom → get_paiju_account(2) 出解散
  结算包,与 §12.2 一致。)
- 协议文档新增 0.3「countdown 只是展示用的秒数」:说明服务端无定
  时器、归零不做任何事,并要求客户端不得在归零时做乐观界面推进
  (原 0.3 顺延为 0.4)。
- 合规 01:D3 由「🟧 中等/未修」改为「✅ 规则如此」,并说明 server
  dev-guide「服务端自动操作复用真人链路」约束的是「若要做代打就必
  须复用真人链路」,本局不做代打故不适用;「尚未处置」小节改为
  「无」,11 项全部收口。
- 合规 02:撤掉「仍无自动化覆盖」中的超时项,改为说明不需要超时用
  例,并反向标注:若将来有人给服务端加了超时代打,那才是违反
  design §11、应由核对拦下。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 19:05:24 +08:00

188 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 二七王服务端 · 合规测试计划
> 目标:用 Node 单测/集成测**系统覆盖 `design.md` 的全部服务端可验证规则**,做到"规则 → 用例"可追溯,作为"代码是否符合手册"的**自动化证据**(与 `01-design合规逐节核对.md` 的人工审计互补)。
>
> 运行:`node server/games/erqiwang/test/run.js`。测试原理与双运行时见 `test/README.md`。测试纪律(正/反/边界、不软化断言、失败先裁根因)见 `docs/server/development-guide/04` §10。
---
## 0. 分层与测试类型
| 层 | 含义 | 依赖 | 现状 |
| --- | --- | --- | --- |
| **L1 单元** | 纯函数直接调用(arith/config/paiju 静态计算) | 仅 `_shim` 的平台工具 | 已有 `test_arith`/`test_config`/`test_paiju`(部分) |
| **L2 局内集成** | `cls_youle_erqiwang_paiju.new(o_desk,firstseat)` 造牌局,驱动 `do_callgrade→do_choiceflower→do_burycard→do_playcard…→get_paiju_account` 验证状态/结算 | mock `o_desk`/`o_room` + shim;**不经 mod.js** | 仅 `get_paiju_account` 造了极简对象,**无完整一局驱动** |
| **L3 RPC/广播** | `mod.js` 收包入口 → 校验 → 广播 | mock `check_player`/`SendPack`/`sendpack_toother`,捕获下发包 | **完全未覆盖** |
**发牌随机性处理**:`do_dealpai` 用 `min_random`。L2 需要**可控发牌**——测试 `_shim` 提供可注入的 `min_random`(预置序列或指定发牌结果),使一局可复现。这是 L2 的前置基建。
---
## 1. 覆盖矩阵(按 design 章节)
状态:✅ 已覆盖 · 🟡 部分 · ❌ 待补。用例列标注 正/反/边界 三类(`docs/server/development-guide/04` §10 要求三类齐全)。
### §2 牌局构成
| 规则 | 被测 | 用例(正/反/边界) | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 92 张、去两副 3/4 | `init_cards`/`do_dealpai` | 正:发完 3 家各 28 + 底 8 = 92;反:牌堆不含 number 3/4;边界:两副各花色计数 | L2 | ❌ |
| 分值 5→5、10→10、K→10、余 0 | `init_cards` | 正:各面值 score;边界:非分牌 score=0 | L1/L2 | 🟡(扣底/结算间接) |
### §3 主牌顺序 / 编码
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 主牌 code 降序=设计顺序(大王>小王>正7>副7>正2>副2>主A…主5>副牌) | `id_to_code`/`order_cards` | 正:排序结果逐位;边界:正/副 7、正/副 2 的相对大小;主A vs 副2 | L1 | ❌(间接用到,无专测) |
| 相邻链 + 6/8 可连、7 不与 6/8 连 | `is_continuous` | 正:全相邻段逐对;反:7-6/8-7/9-7 不连;边界:8-6 连、大王-小王连 | L1 | 🟡(甩牌/算奖间接) |
| 副7/副2 跨花色比大小归一 | `trump_rank`/`can_followcard` | 正:副7 vs 副7 不可压;反:正7 压副7 | L1 | 🟡(甩牌间接) |
### §4 开局与坐庄
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 叫分 5~70、步进5、暂定庄必叫、后叫更低/不叫 | `mod.jiaofen` 校验 + `do_callgrade` | 正:合法叫分推进;反:>70/非5倍/首家0/叫≥当前;边界:叫70、叫5 | L3(校验)+L1(do_callgrade) | ❌ |
| 叫5立即上庄、两家不叫上庄 | `do_callgrade` | 正:叫5→banker;正:一家叫另两家不叫→banker;边界:三家叫分序列 | L1 | ❌ |
| 坐庄轮换:庄赢连庄、庄输/投降下家、首局庄=0号 | `desk.do_prepare`/`makewar` | 正:result=0 连庄;正:result=1/2 顺延;边界:首局 firstseat=0 | L2/L3 | ❌ |
| 阶段机 1→2→3→5→6,无 step4 | `do_up_banker`/`do_choiceflower`/`do_burycard` | 正:各 handler 后 step 值;反:错误 step 调用被拒 | L2/L3 | ❌ |
| 投降仅70分/step2/庄家/不埋牌 | `mod.touxiang` | 正:70分 step2 投降→结算;反:≠70/step≠2/非庄;边界:投降后 step=6 | L3 | ❌ |
| 70分暗牌向所有玩家亮3秒(+ancard3s)、非70只发庄 | `mod.jiaofen`(shangzhuang) | 正:70→闲家有 bottomcards+ancard3s;反:非70 闲家无 bottomcards | L3 | ❌ |
| 选主后先选主后埋牌 | `do_choiceflower`/`do_burycard` | 正:flower 记录、step 推进;边界:埋牌须8张且在手 | L2 | 🟡(check_cards_inhand 未测) |
### §5 出牌规则
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| **§5.1/5.2 跟牌/毙牌/垫牌/混合出牌/拖拉机覆盖度** | `get_followcard`/`can_followcard` | 正:同花色跟、有对必出对、有拖必出拖、两对不连必出、尽量长拖优先;反:缺牌型被拒、放长拖只出散对被拒;毙牌(须完全缺门):副单→主单、副对→主对、副N连→主N连;垫牌/混合出牌:cardvalue=0 不争权 | L1 | ✅(`test_follow` 50 例,含 else 欠约束修复回归) |
| §5.3 拖拉机相邻 | `is_continuous`/`get_tuolaji_list` | 见 §3 | L1 | 🟡 |
| §5.4 甩牌:副禁甩/最大性/甩错 | `can_playcard`/`opp_can_beat_flush`/`decompose_trump` | 已覆盖(合法/甩错含smallest/副禁甩/单张/对子/拖拉机) | L1 | ✅ |
| §5.4.4 跟甩牌强制分量拆解 | `flush_follow_ok` | 已覆盖(有对必打对/有拖必打拖/退化/主牌最大化/无主全垫) | L1 | ✅ |
### §6 捡分与扣底
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| §6.1 分值 | `init_cards` | 见 §2 | L1 | 🟡 |
| §6.2 闲家赢归闲、庄赢作废、两闲谁赢都算闲 | `do_playcard`/`get_jian_grade` | 正:闲赢累计分;反:庄赢不计;边界:两闲各赢 | L2 | 🟡(1 集成例) |
| §6.3 扣底倍数 单1/对2/N连2N、全主牌守卫、取最高规格 | `get_bottom_multiple` | 已覆盖 | L1 | ✅ |
| §6.3 触发:闲家用主牌赢末轮才扣底 | `get_bottom_account` | 正:闲家主牌赢末轮翻倍;反:非主牌赢不扣、庄家赢不扣 | L2 | ❌ |
### §7 结算:子数与升级
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 常规算子 基础子数 + 大光×3/小光×2/过庄×1/升N级、Q=40 | `get_base_bycall`/`get_qvalue`/`get_upgrade` | 已覆盖各档代表值 | L1 | ✅(可补每档过庄/小光分界与升3级) |
| 爬坡 基础子数梯度 + 分段Q | 同上(climb=true) | 已覆盖各档 | L1 | ✅ |
| X=基础×倍率、庄赢/闲赢符号、按房间爬坡切换 | `get_paiju_account` | 正:大光/过庄/升级/爬坡局 seatlist;边界:banker=-1 | L2 | 🟡(仅65小光+投降) |
### §8 算奖
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| §8.1 常规算奖只庄家、三/四王+连对链、6-8个7/2、无≥10老主/无作废 | `get_chongguan` | 已覆盖 | L1 | ✅ |
| §8.2 亮牌阈值/统计、只亮结构 | `get_liangpai` | 已覆盖 4 例 | L1 | 🟡(补 王≥3/7≥6 组合、边界9/10) |
| §8.3 傍王按位开关、庄闲每王1奖 | `get_paiju_account`(bangwang) | 正:傍王局 grade_aw 含王奖;反:未勾选不计 | L2 | ❌ |
| §8.4 算奖并入 X×(2Ni−Nj−Nk)、含闲-闲、投降X=1 | `get_paiju_account` | 正:庄1奖/闲傍王2奖的三家净额;边界:三家同时有奖 | L2 | ❌(公式单测有,集成未测) |
| §8 快照:庄埋后28/闲发后28;投降庄36无主 | `get_seat_cards_award` | 正:庄排除已埋;正:投降含底36;边界:无主花色无连对链 | L2 | 🟡(投降 count 间接) |
### §9 查牌 / 明牌
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 报无主统计 info/baozhu 门控 | `mod.chupai` | 正:可查牌下发 info/baozhu;反:不查牌 info 缺失、baozhu=0 | L3 | ❌ |
| 亮牌/PushCards.seatlist 门控 | `mod.maipai`/`get_deskinfo` | 正:可查牌闲家有 liangpai;反:不查牌无 | L3 | ❌ |
| 明牌 mingpai:可查牌+已报无主+出牌阶段 | `mod.mingpai` | 正:条件满足返回他家主牌;反:不查牌/未报无主/非出牌阶段被拒 | L3 | ❌ |
### §10 房间设置
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 位串解析、局数6/12、扣卡房主2·4/AA1·2 | `config.parse`/`export.get_asetcount`/`get_needroomcard` | 已覆盖(config 复刻映射) | L1 | ✅(可改为直接调 export 函数) |
| 傍王/爬坡/查牌三开关生效 | `get_paiju_account`/`mod.*` | 傍王/爬坡见 §7/§8;查牌见 §9 | L2/L3 | 🟡 |
### §12 完整流程
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 一局端到端:发牌→叫分坐庄→选主→埋牌→逐轮出牌→末轮扣底→结算→轮庄 | 全链路 | 正:一局可控发牌打到结算,各家总分正确;含连庄/下家轮转 | L2 | ❌ |
> §11 牌局交互提示中,选主对子数/亮底牌等随对应阶段包下发;闲家三提示(踩/没分/有分)为服务端转发项(`mod.tishi`,见 P1)。
---
## 2. 待补用例清单(按优先级)
### P0 · 规则/金额关键 —— ✅ 已完成
1. ✅ **§5.1/5.2 正常跟牌/毙牌/垫牌/混合出牌/拖拉机覆盖度**(`can_followcard`,`test_follow.js` 50 例)——单张/对子/拖拉机的必出、毙牌数量对应(副单→主单/副对→主对/副N连→主N连、且毙牌须完全缺门)、垫牌与"混合出牌"(有该花色不够+补主牌→`cardvalue=0` 不争权)、缺门/同花色不足/对子不够各分支、多候选拖拉机(D1 崩溃回归)、**降级递归 else 欠约束回归**(三组等长 2 连对时出孤立对→拒),正反边界齐全。
2. ✅ **§7/§8 结算集成扩充**(`get_paiju_account`,`test_paiju.js`)——大光/过庄/升2级、爬坡局、傍王局(庄3王,N=4)、不傍王对照、投降,逐档验证 seatlist 各家 grade。
3. ✅ **§4.2 叫分坐庄**(`do_callgrade`,`test_callgrade.js` 6 例)——叫5立即上庄、两家不叫上庄、后叫更低→最低者上庄、不同首家。
4. ✅ **§6.3 扣底触发**(`get_bottom_account`,`test_paiju.js`)——闲家主对/两连对赢末轮才扣底(×2/×4);副牌赢、庄家赢均不扣。
> 注:§4.2 中 `mod.jiaofen` 的**入参校验**(>70/非5倍/首家必叫/后叫更低)属 L3,随投降/暗牌/查牌门控一起在 P1 补。
### P1 · 规则关键、需 RPC 脚手架 —— ✅ 已完成(`_rpc.js` + `test_rpc.js` / `test_desk.js`)
5. ✅ **§9 查牌门控 + mingpai**——chupai 的 info/baozhu 按 `nocheck` 门控(可查有/不查无);mingpai 合法下发他家主牌,不查牌/未报无主/非出牌阶段均拒。
6. ✅ **§4 投降 + 暗牌亮牌**——touxiang 仅 70分/step2/庄家;真实 jiaofen 驱动到 70分上庄,验证 shangzhuang 闲家有 bottomcards+ancard3s、叫5上庄闲家无。
7. ✅ **§4.2 `mod.jiaofen` 入参校验**——>70/非5倍/首家必叫/后叫更低 全部拒,合法接受。
8. ✅ **§4.6 坐庄轮换 `do_prepare`**——庄赢连庄、闲赢/投降下家。
### P2 · 补强既有 —— ✅ 已完成
9. ✅ §2 牌局构成专测(`test_deal`)、§3 编码/排序专测(`test_arith`:order_cards + is_continuous 链)。
10. ✅ §9 `mod.maipai` 亮牌门控 / `get_deskinfo` PushCards 门控(`test_rpc` 端到端驱动后验证)。
11. ✅ §8.2 亮牌更多阈值(6个7/固定主9-10边界)、§7 算子每档分界+升3级(`test_arith`)。
12. ✅ §12 端到端(`test_rpc` 真实驱动到出牌就绪)。
13. ✅ §10 直接调 `export.get_asetcount`/`get_needroomcard`/`get_needroomcard_joinroom`(`test_config`)。
14. ✅ **§11 闲家三提示 `mod.tishi`**——正:闲家 step5 发 踩/没分/有分,只转发给对家(`3-banker-seat`,验证 fromid/seat/tip);反:庄家发/非出牌阶段/非法 tip/缺 tip 均拒(`test_rpc` 11 例)。
---
## 3. 集成脚手架需求(实现 L2/L3 的前置)
- **L2 局内驱动器 `test/_harness.js`**:
- 可注入发牌的 `min_random`(或直接构造 `o_paiju.cards` 指定各家手牌),使牌局可复现。
- mock `o_desk`(seatlist 累积、`get_desk_account`、`o_room.roomtype/asetcount`)与 `o_room`。
- 提供"驱动一局到某阶段"的辅助:叫分序列 → 选主 → 埋牌 → 出牌序列。
- **L3 RPC 捕获器**:mock `youle_erqiwang.import.check_player`(返回构造的 `o_room`)、`o_room.method.sendpack_toother`/`youle_erqiwang.app.SendPack`(把下发包收集进数组),断言包字段(含 §9 门控、暗牌、投降、chupai1/2/3、jiesuan)。
---
## 4. 完成定义(DoD)—— ✅ 已达成
- ✅ 覆盖矩阵中所有 ❌/🟡 项补到 ✅(详见 §5 现状),每条规则至少含正/反/边界;
- `node test/run.js` 全绿、退出码 0;
- 新增用例遵守测试纪律:不改正式代码去迁就测试、不软化断言、失败先用证据裁根因;
- 本计划与 `01-design合规逐节核对.md` 的结论一致(自动化测试成为人工审计的可回归证据)。
---
## 5. 当前状态小结 —— ✅ DoD 达成,P0/P1/P2 全部完成
**共 361 项断言全绿**(`test_arith` 106 / `test_callgrade` 6 / `test_config` 19 / `test_deal` 12 / `test_desk` 6 / `test_follow` 50 / `test_input` 34 / `test_paiju` 31 / `test_rpc` 68 / `test_success` 29)。
design 全部服务端可验证章节均有正/反/边界单测:
- **§2 构成**:`test_deal` 92张/去3-4/三家28+底8/分值。
- **§3 编码/排序**:`test_arith` order_cards 降序=设计顺序、is_continuous 相邻链逐段+反例。
- **§4**:叫分坐庄 do_callgrade、`mod.jiaofen` 入参校验、投降 touxiang 条件、70分上庄暗牌亮牌下发、坐庄轮换 do_prepare。
- **§5**:**正常跟牌全面覆盖(§5.1/5.2)**——按跟牌方手牌各分支穷举:主牌首出与副牌首出、单张/对子/拖拉机各牌型、有同花必出对应牌型、同花不足则出仅有的+补、缺门毙牌(数量对应)与垫牌(不压)、拖拉机的对子不够/同花无对/**多候选拖拉机(D1崩溃回归)**/3连对/富余对子必出拖、手牌数==首出张数全部必出;**甩牌全面覆盖(§5.4)**——合法(纯单/纯对/单+拖/单+对+拖三混)、甩错(单张/主对/拖拉机分量各自被压、双对之一被压)、副牌禁甩(对手缺门与非缺门)、甩错惩罚**实际执行**(do_playcard 只打最小一张、其余留手);**跟甩牌全面覆盖(§5.4.4)**——对子必出、拖拉机必出/退化对子/退化到单张、多组拖拉机、混合demand逐分量对位、主牌最大化、无主全垫,以及"首出甩牌→跟牌违规→do_playcard 拒"的集成。
- **§6**:捡分(结算集成间接)、扣底倍数+触发。
- **§7**:算子常规+爬坡逐档、每档分界+升3级、结算集成(大光/过庄/升级/爬坡/傍王/投降)。
- **§8**:算奖 get_chongguan、亮牌 get_liangpai(含 6个7/固定主9-10边界)、算奖并入 X×N(傍王对照)。
- **§9**:查牌门控(chupai info/baozhu、maipai liangpai、deskinfo PushCards seatlist)、明牌 mingpai。
- **§10**:位串解析、局数/扣卡(直接调 export 接口)。
- **§12**:端到端(叫分→上庄→选主→埋牌→出牌ready,真实 RPC 驱动)。
> 说明:§12 覆盖到"出牌准备就绪"(各阶段串接正确);完整 28 墩逐墩对打需一个合法出牌选择器,属后续增强,非规则合规缺口(逐墩规则已由 §5/§6 单测覆盖)。
---
## 6. 第三轮补充:design 之外的可验证维度
> 前两轮的覆盖矩阵完全按 design 章节组织,因此**平台接入、客户端入参、下发面一致性、协议红线**这几类根本不在矩阵里——270 项全绿却漏掉了 1 项阻断级与 3 项严重缺陷(见 `01-design合规逐节核对.md`「第三轮核对」)。本节把这些维度补进测试计划,后续复核必须一并跑。
| 维度 | 被测 | 用例(正/反/边界) | 文件 | 状态 |
| --- | --- | --- | --- | --- |
| 客户端入参合法性 | `check_cards_valid` / `check_cards_inhand` | 正:单张/多张/边界 id 0 与 107;反:非数组(undefined/null/字符串/伪数组)、空数组、重复 id、越界、负数、小数、数字字符串;反:不在手上/已出/已埋 | `test_input` | ✅ 21 |
| 重复 id 不得伪造牌型 | `mod.maipai` / `mod.chupai` | 反:8 个重复 id 埋牌被拒且一张未埋;反:重复 id 出牌被拒;正:8 张不同牌埋牌成功且实埋 8 张;反:越界/非数组入参不抛异常且不产生出牌 | `test_input` | ✅ 7 |
| §9 报无主即时刷新 | `do_playcard` | 正:打空瞬间三家统计与主花色标志同时刷新;边界:报无主前三家均为 `[-1,-1]` | `test_input` | ✅ 6 |
| 红线 `data.success`(成功包) | `class.desk`/`mod.js`/`get_paiju_account` | 正:fapai/zhunbei/jiaofen/shangzhuang/xuanzhu/maipai/chupai1/mingpai/tishi 均带 `success:true`;正:jiesuan 的正常/投降/解散三种 type 都带 | `test_success` | ✅ 12 |
| 红线 `data.success`(失败回包) | 各 handler 失败分支 | 反:每个 handler 取代表性失败路径,断言"恰好回 1 包 + `success:false` + errcode 正确 + 回到请求者 fromid",覆盖 STEP/SEAT/PARAM/RULE/PLAYER 五类 | `test_success` | ✅ 17 |
| §7.3 下发面 multiple 与结算同源 | `mod.jiaofen` / `get_deskinfo` / `get_paiju_account` | 正:叫 45 分在常规房/爬坡房分别得 6/7;正:上庄包与重连 ChooseMain 一致;边界:与结算包 `aset.multiple` 逐一对齐 | `test_rpc` | ✅ 7 |
| §5.4 甩牌下发面 | `chupai1` | 正:合法甩牌带 `shuai` 分量;反:非甩牌不带;反:甩错只打最小一张且不带 `shuai` | `test_rpc` | ✅ 8 |
| 重连 pushlist 结构 | `get_deskinfo` | 正:轮数正确、内层恒 3 个座位、无 undefined;正:按本局主牌花色排序(主5 排在副A 前) | `test_rpc` | ✅ 4 |
| §9 出牌历史按查牌模式门控 | `get_deskinfo` | 反:不查牌房无 `pushlist`;正:可查牌房有 `pushlist`;边界:两种模式下当前轮桌面牌 `playproc.cards` 都必须恢复 | `test_rpc` | ✅ 4 |
| §3 相邻链花色 | `is_continuous` | 正:同花色 8-6 连;反:三组跨花色 8-6 不连;反:差 2 但非 8/6 不连 | `test_arith` | ✅ 5 |
**关于超时**:`countdown` 只作展示、服务端不做任何超时动作,这是 design §11 确认的规则(并非缺口),因此**没有也不需要「超时自动操作」的用例**;反过来,若将来有人给服务端加了超时代打,那才是违反 design §11,应由核对拦下。
> 原列在此处的两项「待外部输入」均已由规则设计者拍板:「不查牌模式是否屏蔽出牌历史」确认屏蔽,已改 design §9 + 代码 + 用例(见上表);「超时托管默认动作」确认维持现状、不做超时动作,已写入 design §11。至此测试计划无待外部输入项。