二七王:服务端下发面四处缺陷修复(增量路径与重连路径对齐)

前端一致性测试(同一局真包:逐包增量回放 vs 一次性 deskinfo 重连)暴露的四处
服务端缺陷,均属「同一份数据两条路径给的不一样 / 有一条根本没给」:

① chupai1/2/3 补发 playproc(本轮进行态)
   与 deskinfo.PushCards 共用新增的 get_playproc() 快照函数,结构取值完全一致。
   playproc 是全程复用的活对象(new_playround 就地重置),故快照做逐字段深拷贝,
   避免已发出的包被后续出牌回改。可见性:全部字段由桌面公开信息推出,
   与既有 PushCards.playproc 一样三家无差别下发。

② deskinfo ChooseMain(step2)/BuryCards(step3) 补发 curmultiple
   与 shangzhuang 推送同源同值;此刻一张牌未出、捡分恒 0,必为 +3(大光)。
   原先重连重建为 0,顶部「抓分」角标掉档。

③ maipai 补发 seatlist
   与 chupai/PushCards 同一张表、同一道查牌模式门控;埋牌完成时为初始化空表。

④ bottomcards / burycards 收敛到单一权威源(SSOT)
   - burycards:maipai 原样回显了客户端请求包里 cards 的顺序,改取 get_burycard()
     权威快照,与 PushCards.burycards 同一函数。请求包只承载「意图」,其顺序不可信。
   - bottomcards:get_bottomcards 按「调用时的 flower」排序,导致上庄推送(flower=-1)
     与重连 BuryCards(flower 已定) 两种顺序。底牌是选主【之前】就翻给玩家看的、
     发牌结束即固定的快照,故改为发牌时算一次并冻结,get_bottomcards 返回其副本
     (order_cards 是原地排序,交出本体会被调用方就地重排)。三个调用点无需改动。

协议文档同步 7 处(新增 playproc/seatlist/curmultiple 字段说明与两处排序口径)。
新增 test_rpc.js「下发面一致性」用例组 21 checks,含 28 轮真实牌局逐包逐座位比对
(249 次、0 不一致)与深拷贝守卫;每条均已用「退回修复必转红」反向验证。
既有 649 checks 未改一条断言,全绿(17 脚本 / 670 checks)。未涉及 shared/。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 19:17:09 +08:00
co-authored by Claude Opus 5
parent c4c061b0e1
commit 84525359fc
5 changed files with 245 additions and 16 deletions
+26 -1
View File
@@ -322,6 +322,19 @@ youle_erqiwang.maipai = function(pack){
msg.data.success = true;
msg.data.seat = o_paiju.playproc.currseat;
msg.data.countdown = o_desk.method.get_countdown_chupai();
if (!_cfg.nocheck){
//三家座位牌况(缺门/无对 + 报副统计),仅可查牌下发——与 chupai1/2/3、重连包
//PushCards.seatlist 同一份数据、同一道门控。埋牌完成时它是刚初始化的空表
//(每家 [[0,0],[0,0],[0,0],[0,0],[-1,-1]]),本身不含任何信息;但「埋牌完成 →
//庄家首出」这段窗口内前端也要有表可画,漏发会让增量路径此刻为空、重连路径却有表。
//可见性:表内只有「某家缺某花色/某花色无对」(由公开的跟牌行为推出)与报无主后
//全场公示的余主数量(design §9 明确要求向全体三人展示),故三家整表下发不构成泄露
msg.data.seatlist = o_paiju.seatlist;
}
//埋牌底牌(本次埋下的 8 张):取服务端权威快照,【不回显客户端提交的 cards 原序】。
//get_burycard 与重连包 PushCards.burycards 是同一个函数、同一套按本局主牌花色的排序,
//两条路径因此顺序一致(SSOT);直接回显 cards 会把客户端的选牌顺序当成下发顺序。
var _burycards = cls_youle_erqiwang_paiju.get_burycard(o_paiju);
for (var i = 0; i < o_room.seatlist.length; i++) {
msg.conmode = o_room.seatlist[i].conmode;
msg.fromid = o_room.seatlist[i].fromid;
@@ -329,7 +342,7 @@ youle_erqiwang.maipai = function(pack){
msg.data.cards = o_paiju.method.get_seat_cards(i);
//埋牌底牌(本次埋下的 8 张)。与 shangzhuang.bottomcards(底牌,发牌留桌 8 张)
//是两批不同的牌,字段名刻意区分,勿混用
msg.data.burycards = cards;
msg.data.burycards = _burycards;
delete msg.data.liangpai;
} else {
delete msg.data.cards;
@@ -474,6 +487,16 @@ youle_erqiwang.chupai = function(pack){
msg.data.countdown = o_desk.method.get_countdown_chupai();
//当前抓分倍数(design §7.2.0):随本包一起下发、不另开推送;三家同值,grade 本就公开
msg.data.curmultiple = o_paiju.method.get_curmultiple();
//本轮进行态(design §5.1):与重连包 PushCards.playproc 同一个快照函数、同一结构。
//没有它,前端在非重连时就拿不到「本轮谁先手/谁最大/三家各出了什么/第几轮」,只能自己
//累积推导——那就是把对局推进逻辑搬到前端(前端红线「数据驱动、前端无对局状态机」)。
//注意 re.idx==3 时 do_playcard 已在内部调用 new_playround 就地开了新一轮,
//故 chupai3 带的是【新一轮】的进行态(与 nextseat 同步),这与此刻重连拿到的完全一致。
//可见性:round/start/currseat/startcount/startflower/starttype/maxseat/maxcard 都是
//由桌面公开信息直接推出的;cards 就是本轮已摊在桌上的牌;shuai_demand 是首家甩牌的
//分量构成,而甩出的牌本身已公开、chupai1 也已把等价的 shuai 下发给三家。全部对三家可见,
//无需逐座位裁剪(重连包 PushCards.playproc 本就对三家无差别下发,两条路径口径一致)
msg.data.playproc = cls_youle_erqiwang_paiju.get_playproc(o_paiju);
if (re.shuaicuo){
msg.data.shuaicuo = 1; //甩错:本次甩牌被收回,只打出了最小一张(§5.4.5)
}
@@ -550,6 +573,8 @@ youle_erqiwang.chupai = function(pack){
delete msg.data.nextseat;
delete msg.data.countdown;
delete msg.data.baozhu;
//本轮进行态只对「还要继续出牌」有意义;本包已转为 jiesuan,不带
delete msg.data.playproc;
delete msg.data.cardsinhand;
delete msg.data.maxseat;
delete msg.data.grade;