二七王:I-3/I-2/M-2 服务端补齐阶段·控制权·结算重连下发面

I-3 阶段与控制权改由服务端唯一维护并逐包下发:
- fapai / jiaofen / shangzhuang / xuanzhu / maipai / jiesuan 各补 step(取自 o_paiju.step)
- shangzhuang / xuanzhu 补 nextseat(控制权 = o_paiju.banker,选主/投降/埋牌均只由庄家做)
- deskinfo 的 ChooseMain / BuryCards 各补 seat(控制权,与增量推送 nextseat 同源同值)
- 各下发点的真实值先用探针跑真实一局逐个核实;顺带发现 shangzhuang.data.seat 是
  最后一个叫分者而非控制权(本局实测 2 vs 庄家 0),故控制权另起名 nextseat
- get_paiju_account 统一把 step 置 6:解散结算此前无人推进 step,包里给不出诚实的阶段值

I-2 结算阶段的重连包补发抠底明细与大局结算:
- get_paiju_account 末尾一并冻结 tmp_jiesuan_bottom / tmp_jiesuan_account
- get_deskinfo case 6 的 Balance 据此补 bottom / account,与 jiesuan 推送同源同值、
  门控一致(投降/解散无 bottom、非末局无 account);新局开始时三份快照一并清除
- 修复「结算面板开着时断线重连/硬刷新 → 抠底明细与末局大结算空白」

M-2 seatlist 不再以活引用挂进下发包:
- 新增 get_seatlist() 深拷贝快照,maipai / chupai / deskinfo.PushCards 三处改用它

测试:test_rpc 新增 I-3/I-2/M-2 三节(131→166 checks)、test_desk 新增 fapai
阶段与控制权用例(6→10);全部新增守卫已逐条做「退回修复必转红」反向验证(15/15)。
test_leak 的可见性规则同步收紧到「本局结算(step6)后埋牌底牌才全场公开」,
并修掉一处把阶段写死成 step5 的报错文案;整局泄露审计仍全绿。
协议文档同步补充上述字段说明。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 21:30:56 +08:00
co-authored by Claude Opus 5
parent cfa0409d94
commit 5d981a722e
8 changed files with 313 additions and 14 deletions
+23 -1
View File
@@ -95,6 +95,10 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || {
break;
case 2: //选主
deskinfo.ChooseMain = {};
//控制权:选主/投降都只由庄家做(mod.xuanzhu / mod.touxiang 的 SEAT 校验以 banker 为准)。
//与 shangzhuang 推送的 nextseat 同源同值——两条路径都由服务端算好给出,
//前端不再自己套「选主 = 庄家」这条规则(字段名与 CallRun.seat 一致:分组内的 seat = 轮到谁)
deskinfo.ChooseMain.seat = paiju.banker;
//庄家
deskinfo.ChooseMain.banker = paiju.banker;
//庄家叫分
@@ -120,6 +124,9 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || {
break;
case 3: //埋牌
deskinfo.BuryCards = {};
//控制权:埋牌只由庄家做(mod.maipai 的 SEAT 校验以 banker 为准)。
//与 xuanzhu 推送的 nextseat 同源同值
deskinfo.BuryCards.seat = paiju.banker;
//庄家
deskinfo.BuryCards.banker = paiju.banker;
//庄家叫分
@@ -183,7 +190,9 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || {
deskinfo.PushCards.baozhu = (!cfg.nocheck && paiju.method.have_baofu()) ? 1 : 0;
//座位列表(报副统计)与亮牌:仅可查牌模式下展示(§9/§8.2)
if (!cfg.nocheck){
deskinfo.PushCards.seatlist = paiju.seatlist;
//【深拷贝】与 maipai / chupai1/2/3 共用同一处快照函数,且不把活表挂进下发包
//(paiju.seatlist 是 do_playcard 每手就地改写的活对象,见 class.paiju.js get_seatlist)
deskinfo.PushCards.seatlist = cls_youle_erqiwang_paiju.get_seatlist(paiju);
if (seat != paiju.banker){
var _lp = paiju.method.get_liangpai();
if (_lp){
@@ -208,8 +217,21 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || {
//玩家的准备状态
deskinfo.Balance.readystate = o_room.o_desk.prepare;
if (o_room.o_desk.prepare[seat] == 0){
//结算面板的三件套:与 jiesuan 推送【同源同值】(都取 get_paiju_account 末尾
//冻结的那份快照)。只给 aset 会让「结算面板还开着时断线重连/硬刷新」丢掉
//抠底明细与末局大结算——增量路径有、重连路径空白,正是「同一份数据两条路径
//给的不一样」(server 红线:发全下发面,漏发修在服务端发包处)。
//结算情况
deskinfo.Balance.aset = paiju.tmp_jiesuan_aset;
//抠底明细:仅正常出牌结算存在(投降/解散没有抠底这一步),与 jiesuan.bottom 同一份
if (paiju.tmp_jiesuan_bottom){
deskinfo.Balance.bottom = paiju.tmp_jiesuan_bottom;
}
//大局结算:仅末局或解散时存在,与 jiesuan.account 同一份。
//「有就带、没有就不带」与 jiesuan 推送的取舍完全一致,前端两条路径同一套解析
if (paiju.tmp_jiesuan_account){
deskinfo.Balance.account = paiju.tmp_jiesuan_account;
}
}
break;
}