二七王:S-4 拆分 bottomcards 字段名,S-5 术语同步收口
S-4(服务端改动):底牌与埋牌底牌不再共用一个字段名。 - bottomcards = 底牌(发牌留桌 8 张)—— shangzhuang / ChooseMain / BuryCards,保持不变 - burycards = 埋牌底牌(庄家埋下 8 张)—— maipai(mod.js)、PushCards(class.export.js) - 结算 bottom.cards 未改名(分组名已表明是抠底相关),文档注明其语义 测试: - test_leak.js 的 CARD_FIELDS 白名单加入 burycards。这一步是必须的——不加的话 新字段会静默脱离下发面审计,闲家误收也发现不了。 - test_rpc.js 新增 11 条正反用例:庄家有 burycards 且为 8 张、庄家不再有 bottomcards(抓改名漏改)、闲家两个字段皆无、重连 PushCards 同上。 验证(不止「跑过了」): - 改动前基线 16 个文件全绿;改动后仍全绿,新增后 rpc 从 89 → 100 checks。 - 反向验证一:把 mod.js 的 burycards 改回 bottomcards → test_rpc 新用例 FAIL, 且 test_leak 报出真实泄露(赋值改了而 delete 没改时,闲家会收到庄家埋的牌)。 - 反向验证二:删掉闲家侧的 delete msg.data.burycards → test_leak 立刻抓出 5 张越权牌,确认 burycards 确已纳入审计白名单。 两次验证后均已还原并复跑全绿。 S-5:术语同步收口。design.md / packet_protocol.md / 代码注释均已改用 「底牌 / 埋牌底牌」;compliance 三份历史核对记录有意不改(反映当时的 design 表述,下轮核对按新 design 重做)。 另:T-9 亮牌条文案暂定「x对 x主」(取 liangpai.zhupair 与 zhu,其余档位 先不渲染、协议不变)。至此 29 项待确认规格全部有结论。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// 下发面泄露审计(server 红线「发全 ≠ 发多,按可见性下发」+ design §4/§9/§11)
|
||||
// 跑完整局,对每一个「服务器 → 某座位」的包做深度扫描,取出其中出现的所有牌 id,
|
||||
// 逐个判定「此刻该座位是否有权知道这张牌」。依据 design §4(暗牌只有庄家可见 / 70分亮3秒)、
|
||||
// §9(查牌模式)、§11(结束亮底牌)与 server 红线「发全 ≠ 发多,按可见性下发」。
|
||||
// 逐个判定「此刻该座位是否有权知道这张牌」。依据 design §4(底牌只有庄家可见 / 70分亮3秒)、
|
||||
// §9(查牌模式)、§11(结束亮埋牌底牌)与 server 红线「发全 ≠ 发多,按可见性下发」。
|
||||
const R = require('./_rpc.js'); const mod = R.mod;
|
||||
const P = global.cls_youle_erqiwang_paiju, A = global.cls_youle_erqiwang_arith;
|
||||
const D = require('../class.desk.js'), EX = require('../class.export.js');
|
||||
@@ -14,7 +14,7 @@ const pk = (s, d) => ({ conmode: 0, fromid: s, data: Object.assign({ agentid: 1,
|
||||
|
||||
// 深度收集一个对象里所有「看起来是牌 id」的整数(0~107)。
|
||||
// 为避免把 seat/count/grade 之类误当牌 id,只扫描已知承载牌 id 的字段名。
|
||||
const CARD_FIELDS = ['cards', 'bottomcards', 'cardsinhand', 'zhucards', 'gradecards', 'pushlist', 'MyCards'];
|
||||
const CARD_FIELDS = ['cards', 'bottomcards', 'burycards', 'cardsinhand', 'zhucards', 'gradecards', 'pushlist', 'MyCards'];
|
||||
function collectCards(node, key, out) {
|
||||
if (node === null || node === undefined) return;
|
||||
if (Array.isArray(node)) { node.forEach(x => collectCards(x, key, out)); return; }
|
||||
@@ -31,9 +31,9 @@ function mayKnow(pj, seat, cid, rpc) {
|
||||
if (c.dealowner === -1) return true; // 规则去除的 3/4,不可能出现
|
||||
if (c.playround > 0) return true; // 已打出,全场可见
|
||||
if (c.dealowner === seat + 1) return true; // 自己的牌
|
||||
if (seat === pj.banker && (c.dealowner === 0 || c.dealowner === seat + 1)) return true; // 庄家可见暗牌/底牌
|
||||
if (seat === pj.banker && (c.dealowner === 0 || c.dealowner === seat + 1)) return true; // 庄家可见底牌/埋牌底牌
|
||||
if (rpc === 'shangzhuang' && pj.call === 70 && c.dealowner === 0) return true; // §4 70分亮3秒
|
||||
if (rpc === 'jiesuan' && c.playround === 0) return true; // §11 结束亮底牌
|
||||
if (rpc === 'jiesuan' && c.playround === 0) return true; // §11 结束亮埋牌底牌
|
||||
if (rpc === 'mingpai') return true; // §9 明牌:单独在下面按内容校验
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,34 @@ t.eq('埋牌 不查牌 闲家无liangpai', mpXian && mpXian.data.liangpai, undef
|
||||
t.eq('重连 可查牌 PushCards有seatlist', E.get_deskinfo(e.o_room, 1).PushCards.seatlist !== undefined, true);
|
||||
t.eq('重连 不查牌 PushCards无seatlist', E.get_deskinfo(en.o_room, 1).PushCards.seatlist, undefined);
|
||||
|
||||
// ===================== 底牌 vs 埋牌底牌:字段名必须分开(S-4)=====================
|
||||
// 术语:底牌 = 发牌时没发给玩家、扣在桌面的 8 张(字段 bottomcards)
|
||||
// 埋牌底牌 = 庄家埋牌扣下的 8 张(字段 burycards)
|
||||
// 两者曾共用 bottomcards、只靠所在包区分语义,已拆分。以下用例锁死拆分结果,防回归。
|
||||
const mpAll = e.sent.filter(m => m.rpc === 'maipai');
|
||||
const mpZhuang = mpAll.find(m => m.data.cards !== undefined); // 庄家包(带手牌)
|
||||
const mpXian2 = mpAll.find(m => m.data.cards === undefined); // 闲家包
|
||||
// 正面:庄家的 maipai 包带 burycards,且就是埋下的那 8 张
|
||||
t.eq('maipai 庄家有burycards', mpZhuang && Array.isArray(mpZhuang.data.burycards), true);
|
||||
t.eq('maipai burycards为8张', mpZhuang && mpZhuang.data.burycards.length, 8);
|
||||
// 反面:maipai 包里不得再出现 bottomcards(改名漏改会被这条抓住)
|
||||
t.eq('maipai 庄家无bottomcards', mpZhuang && mpZhuang.data.bottomcards, undefined);
|
||||
// 可见性:闲家两个字段都不该有(埋牌底牌只有庄家可见)
|
||||
t.eq('maipai 闲家无burycards', mpXian2 && mpXian2.data.burycards, undefined);
|
||||
t.eq('maipai 闲家无bottomcards', mpXian2 && mpXian2.data.bottomcards, undefined);
|
||||
// 重连 PushCards:庄家有 burycards、无 bottomcards;闲家两者皆无
|
||||
const pcZhuang = E.get_deskinfo(e.o_room, 0).PushCards;
|
||||
const pcXian = E.get_deskinfo(e.o_room, 1).PushCards;
|
||||
t.eq('重连 PushCards 庄家有burycards', Array.isArray(pcZhuang.burycards), true);
|
||||
t.eq('重连 PushCards 庄家burycards为8张', pcZhuang.burycards.length, 8);
|
||||
t.eq('重连 PushCards 庄家无bottomcards', pcZhuang.bottomcards, undefined);
|
||||
t.eq('重连 PushCards 闲家无burycards', pcXian.burycards, undefined);
|
||||
t.eq('重连 PushCards 闲家无bottomcards', pcXian.bottomcards, undefined);
|
||||
// 对照:底牌一侧字段名不受影响,仍叫 bottomcards(ChooseMain/BuryCards 为庄家专属)
|
||||
const cmZhuang = E.get_deskinfo(e.o_room, 0);
|
||||
t.eq('重连 ChooseMain/BuryCards 侧仍用bottomcards命名未被误改',
|
||||
cmZhuang.PushCards.burycards !== undefined && cmZhuang.PushCards.bottomcards === undefined, true);
|
||||
|
||||
// ===================== §5.4 甩牌:chupai1 需下发分量构成 shuai(cardtype 表达不了甩牌)=====================
|
||||
// 主花色=♦(1)。庄家(0) 甩「主♦K对 + 主♦5单张」;两闲家手上一张主牌都没有 → 甩牌合法
|
||||
function mkShuai(extraToSeat1) {
|
||||
|
||||
Reference in New Issue
Block a user