服务端 8452535 改了下发面(chupai1/2/3 新增 playproc、deskinfo step2/3 新增
curmultiple、maipai 新增 seatlist、bottomcards/burycards 顺序统一),前端真包
夹具 packets.json 已过期,重新导出并逐项核对新字段存在与三处/两处顺序一致。
夹具新增顶层 startwar[](每座位一份,结构=deskwar=deskinfo,取自 do_new_paiju
之后、jiaofen 之前的同一时刻),独立存放、不混入 seats[],避免 packetIndex 对
齐锚点错位;test_fixture.js 补相应断言。
修复 test_consistency.js 的用例间污染:EQW_GameState.reset() 按设计不清
room.playerScores(跨局累积量),旧版增量路径从不写它,实际靠"沿用同进程里
上一次全量路径写入的残留"凑出假绿,换座位顺序或调整执行顺序就会转红。改为
每个对齐点独立起步(buildIncremental/buildFull 各自 reset),且增量路径先喂
StartWar 再喂增量包,与真实客户端时序一致,不再依赖任何隐式残留。已验证正
序/倒序跑出的 PASS/FAIL 集合逐条相同。
修完后 23 checks:15 绿、8 红。红项均已归因:3 处为任务已知的前端未接线缺口
(table.playproc 映射、table.pushlist 累积、maipai.seatlist 消费),另发现两
类同类缺口(ResyncHandler 未映射 deskinfo 新增的 curmultiple 字段、BuryHandler
埋牌后未清空 my.bottomCards),均未改动 client/js 正式代码,留给下一个前端任务。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
3.9 KiB
JavaScript
74 lines
3.9 KiB
JavaScript
// 夹具自检:真包必须覆盖全部阶段,且保留座位差异
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { ROOT } = require('./_load');
|
||
const t = require('./_assert')();
|
||
|
||
const fx = JSON.parse(fs.readFileSync(path.join(ROOT, 'client/tests/fixtures/packets.json'), 'utf8'));
|
||
|
||
t.eq('三个座位', fx.seats.length, 3);
|
||
|
||
const rpcsOf = seat => fx.seats[seat].map(p => p.rpc);
|
||
const allRpcs = [].concat(rpcsOf(0), rpcsOf(1), rpcsOf(2));
|
||
|
||
['fapai','jiaofen','shangzhuang','xuanzhu','maipai','chupai1','chupai2','chupai3','jiesuan']
|
||
.forEach(rpc => t.eq('夹具覆盖 ' + rpc, allRpcs.indexOf(rpc) >= 0, true));
|
||
|
||
// 差异化下发确实存在:bottomcards 恰好只发给庄家一人(不是"少于三家"这种可被空集合蒙混过去的弱断言)
|
||
const withBottom = [0,1,2].filter(s =>
|
||
fx.seats[s].some(p => p.rpc === 'shangzhuang' && p.data.hasOwnProperty('bottomcards')));
|
||
t.eq('底牌恰好发给1个座位(庄家)', withBottom.length, 1);
|
||
|
||
// deskinfo 快照必须覆盖全部 5 个阶段(下游任务要用 step3 埋牌快照做回归断言)
|
||
const steps = fx.deskinfo.map(d => d.step).filter((v, i, a) => a.indexOf(v) === i).sort((a,b) => a - b);
|
||
t.eq('deskinfo 覆盖全部阶段 1/2/3/5/6', steps, [1, 2, 3, 5, 6]);
|
||
|
||
// roomtype 必须存在且非空,Task 12 要用它还原房间选项
|
||
t.eq('roomtype 存在且非空', typeof fx.roomtype === 'string' && fx.roomtype.length > 0, true);
|
||
|
||
// startwar 是增量路径的真实起点(开局握手,结构=deskwar=deskinfo),必须每座位各一份,
|
||
// 且不得混进 seats[] 数组头部——那会让全部 packetIndex 集体错位一位(见夹具补捕说明)
|
||
t.eq('startwar 存在且为 3 份', Array.isArray(fx.startwar) && fx.startwar.length, 3);
|
||
[0, 1, 2].forEach(s => {
|
||
const dw = fx.startwar[s] && fx.startwar[s].deskwar;
|
||
t.eq('startwar[' + s + '].deskwar 存在', !!dw, true);
|
||
t.eq('startwar[' + s + '] 结构=deskinfo(带 step/PlayerInfo/MyCards)',
|
||
dw && dw.hasOwnProperty('step') && dw.hasOwnProperty('PlayerInfo') && dw.hasOwnProperty('MyCards'), true);
|
||
t.eq('startwar[' + s + '].PlayerInfo 为开局初值(三家均 0)', dw && dw.PlayerInfo, [0, 0, 0]);
|
||
});
|
||
// seats[] 的第一包必须仍是 fapai——确认 startwar 没有被误插进 seats[] 头部
|
||
[0, 1, 2].forEach(s => {
|
||
t.eq('seats[' + s + '][0] 仍是 fapai(startwar 未混入 seats)', fx.seats[s][0].rpc, 'fapai');
|
||
});
|
||
|
||
// packetIndex 必须落在 (0, 该座位包总数] 区间内,且与 info.step 自洽
|
||
fx.deskinfo.forEach(d => {
|
||
const inRange = d.packetIndex > 0 && d.packetIndex <= fx.seats[d.seat].length;
|
||
t.eq('packetIndex 落在有效区间 (step' + d.step + ' seat' + d.seat + ')', inRange, true);
|
||
t.eq('deskinfo.step 与 info.step 一致 (seat' + d.seat + ' pi' + d.packetIndex + ')', d.step, d.info.step);
|
||
});
|
||
|
||
// C-1 回归锁:同一座位的两条 step5 快照,playproc(当前轮桌面牌/currseat/round)不得相同——
|
||
// 否则说明 deskinfo 快照存的是活引用,被后续出牌篡改成了终局状态(曾经的真实缺陷)
|
||
const step5BySeat = {};
|
||
fx.deskinfo.filter(d => d.step === 5).forEach(d => {
|
||
(step5BySeat[d.seat] = step5BySeat[d.seat] || []).push(d);
|
||
});
|
||
Object.keys(step5BySeat).forEach(seat => {
|
||
const list = step5BySeat[seat];
|
||
if (list.length < 2) { return; }
|
||
for (let i = 1; i < list.length; i++) {
|
||
const same = JSON.stringify(list[0].info.PushCards.playproc) === JSON.stringify(list[i].info.PushCards.playproc);
|
||
t.eq('seat' + seat + ' 两条 step5 快照 playproc 不相同(未被活引用污染)', same, false);
|
||
}
|
||
});
|
||
|
||
// 每个包都有 success(协议 §0.1:每个下发包的 data 必带 success)
|
||
const noSuccess = [];
|
||
[0,1,2].forEach(s => fx.seats[s].forEach(p => {
|
||
if (!p.data || !p.data.hasOwnProperty('success')) { noSuccess.push(s + ':' + p.rpc); }
|
||
}));
|
||
t.eq('每个下发包都带 success', noSuccess, []);
|
||
|
||
process.exit(t.done('fixture') ? 0 : 1);
|