一致性测试(同一局真包:逐包增量回放 vs 一次性 deskinfo 重连)暴露的六处前端缺陷:
① PlayHandler:table.playproc 由「字段映射错误」改为原样拷贝
原实现把这一手的 seat/cards/count/flower/cardtype 灌进 playproc,而服务端 playproc
是本轮进行态(round/start/currseat/.../cards),两者几乎无交集、撞名的 cards 语义还相反。
服务端已在 chupai1/2/3 补发 playproc,前端零推导直接镜像,不自行累积 round/maxseat。
② 本手信息改走 EQW_CARD_PLAYED 事件载荷,不进 GameState
{order, seat, cards, count?, flower?, cardtype?, shuai?, shuaicuo?},带 ? 的缺字段不兜底。
落牌是一次性表现而非对局状态;新立 GameState 字段只会再制造一处增量/全量不一致。
③ PlayHandler:table.pushlist 逐手落盘、按轮归档(GameState.pushPlay)
chupai1 开新的一轮、2/3 落进当前轮,任意时刻都与 deskinfo 的全量重建相等(服务端
pushlist 含进行中的当前轮)。不读 chupai3.playproc.cards——那是下一轮的进行态(协议 §13)。
仅可查牌模式累积,与服务端同一道门控,否则不查牌房会凭空攒出重连没有的历史。
④ ResultHandler:归档收尾轮(最后一手走 jiesuan.chupai,不发 chupai3)
夹具实证 chupai1×28 / chupai2×28 / chupai3×27。jiesuan.chupai 只有 seat/cards/maxseat。
归档随即被结算收敛清空(deskinfo step6 不带 PushCards),但 emit 同步,落牌表现在清空前取值。
⑤ BuryHandler:接住 maipai 新增的 seatlist(此刻为全初值,界面无差别但重连侧有)
⑥ ResyncHandler 的 ChooseMain/BuryCards 补映射 curmultiple(原重连重建为 0,抓分角标掉档);
BuryHandler 埋牌完成后清空 my.bottomCards——底牌上庄时已并入手牌,埋牌后只剩 burycards
有意义,deskinfo 的 PushCards 不下发 bottomcards,重连重建恒为 [](语义已逐条核实)。
一致性测试 8 红 → 3 红。剩余 3 条同源:服务端 maipai 漏发 playproc(与本次服务端补发
seatlist 是同一个窗口、同一个理由),前端无法自补且不得伪造,详见报告 §7 待裁决。
另有 test_handlers_play.js:84 断言的正是 ① 修掉的错误映射,按纪律未改测试,见报告 §8。
未触碰 server/、client/tests/(含夹具与 EXCLUDE);服务端 29 checks 全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
42 lines
2.5 KiB
JavaScript
42 lines
2.5 KiB
JavaScript
///////////////////////////////////////////////////////////////
|
||
////////// EQW_Events: 玩法事件常量 ///////////////////////////
|
||
///////////////////////////////////////////////////////////////
|
||
// 前端红线:框架保持游戏中立,玩法专属事件由子游戏追加到 EventBus.Events。
|
||
//
|
||
// 【对局状态不进事件载荷】——凡是 GameState 里有的,订阅者一律从 EQW_GameState 读,
|
||
// 同一份数据不在事件载荷与状态里各存一份(SSOT)。
|
||
// 例外只有「一次性、不属于对局状态、deskinfo 也不下发」的信息,它们不该进 GameState
|
||
// (进了就会立刻制造一处增量/全量不一致),只能随事件载荷走:
|
||
// EQW_RPC_FAILED {rpc, errcode} 一次性错误信息
|
||
// EQW_CARD_PLAYED {order, seat, cards, count?, flower?, cardtype?, shuai?, shuaicuo?}
|
||
// 这一手的落牌信息(一次性表现)。带 ? 的键【缺字段不兜底】,
|
||
// 包里没有就不带;收尾轮由 jiesuan.chupai 带出时只有 {seat, cards, maxseat?}
|
||
var EQW_Events = EQW_Events || {
|
||
|
||
EQW_RESET: 'eqw.reset', //新一局发牌,全部清场
|
||
EQW_HAND_CHANGED: 'eqw.hand_changed', //自己手牌变化
|
||
EQW_CALL_CHANGED: 'eqw.call_changed', //叫分推进
|
||
EQW_BANKER_SET: 'eqw.banker_set', //上庄
|
||
EQW_MAIN_SET: 'eqw.main_set', //选主
|
||
EQW_BURY_DONE: 'eqw.bury_done', //埋牌完成
|
||
EQW_CARD_PLAYED: 'eqw.card_played', //有人出牌
|
||
EQW_TRICK_END: 'eqw.trick_end', //一轮结束(chupai3 带 maxseat)
|
||
EQW_TURN_CHANGED: 'eqw.turn_changed', //控制权或倒计时变化
|
||
EQW_MINGPAI: 'eqw.mingpai', //收到明牌数据
|
||
EQW_TIP: 'eqw.tip', //收到对家提示
|
||
EQW_ASET_RESULT: 'eqw.aset_result', //小局结算
|
||
EQW_ACCOUNT_RESULT: 'eqw.account_result', //大局/解散结算
|
||
EQW_READY_CHANGED: 'eqw.ready_changed', //有人准备
|
||
EQW_RESYNC_ALL: 'eqw.resync_all', //重连/开局全量重画
|
||
EQW_RPC_FAILED: 'eqw.rpc_failed' //失败回包 {rpc, errcode}
|
||
};
|
||
|
||
//追加到框架事件总线的事件表(框架不认识玩法事件,由子游戏注册)
|
||
if (typeof EventBus !== 'undefined' && EventBus.Events) {
|
||
for (var _eqwEvtKey in EQW_Events) {
|
||
if (EQW_Events.hasOwnProperty(_eqwEvtKey)) {
|
||
EventBus.Events[_eqwEvtKey] = EQW_Events[_eqwEvtKey];
|
||
}
|
||
}
|
||
}
|