二七王:S-8 curmultiple 改为带符号

问题:S-1 实现时取了绝对值,而判定的正负号恰恰是「谁赢」的区分——
3 大光 vs -3 升3级、2 小光 vs -2 升2级、1 过庄 vs -1 升1级,取绝对值后
三对完全撞在一起,D-8 的过程中判定动画无从分辨该播哪个。

改法:get_curmultiple 直接返回 get_upgrade 原值,与结算包 aset.upgrade
完全同口径(3/2/1 庄家大光/小光/过庄,-N 闲家升N级,0 叫分未定)。
顶部「抓分」角标只关心大小,取 Math.abs 即可;判定动画靠符号分辨,
前端一套映射通吃「过程中」与「结算前」两条路径。

协议 4 处 curmultiple 说明同步(shangzhuang / chupai1 / chupai2·3 /
PushCards)。

测试(hint 33 → 46 checks):
- 钉住三对「绝对值相同、判定相反」的组合可区分,以及符号语义
  (>0 庄赢 / <0 闲赢 / ==0 叫分未定)。
- 新增一组【直接调 get_curmultiple 本身】覆盖负数分支,含爬坡 Q 分段。

补上一个测试盲点:第一次反向验证(把实现退回取绝对值)竟然全绿——因为
原有断言里算法层的 cm() 走的是 A.get_upgrade、绕过了 get_curmultiple,
而端到端那局恰好是大光(正数),取不取绝对值都一样。补齐直接调用的用例后
再验,4 条断言立刻失败。

至此 §7.5 服务端待补 8 条全部完成,服务端侧无遗留;D-8 只差动画设计稿
与触发细节。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 12:42:29 +08:00
co-authored by Claude Opus 5
parent fd31272bd1
commit f04bff4e9f
4 changed files with 78 additions and 39 deletions
+16 -19
View File
@@ -840,7 +840,7 @@ EQW_RoomOptions = {
**动画播放遵守「数据优先、表现延后」**(client 02 §3):先 `setXxx` 把结算数据写进 `this.data`,再播动画;动画的开始/结束/出错回调里**只刷界面、不写核心数据**。动画缺失或卡住时,结算面板与数据仍须正确。 **动画播放遵守「数据优先、表现延后」**(client 02 §3):先 `setXxx` 把结算数据写进 `this.data`,再播动画;动画的开始/结束/出错回调里**只刷界面、不写核心数据**。动画缺失或卡住时,结算面板与数据仍须正确。
> ⚠️ **过程中的判定动画需要「带符号」的判定值,而 `curmultiple` 目前是绝对值**——`Math.abs` 之后,「大光 ×3」与「闲家升 3 级 ×3」都是 `3`,无法区分该播哪个动画。详见 §7.5 **S-8**。 > `curmultiple` 是**带符号**的(S-8 已改):`3/2/1` = 庄家大光/小光/过庄,`-N` = 闲家升 N 级,`0` = 叫分未定。判定动画按符号 + 绝对值两维映射即可,与结算包 `aset.upgrade` 同一套逻辑。
**投降结算**:只有 `aset`,无 `chupai`、无 `bottom` → 结算面板必须容忍**底牌区缺失**,不能因为读不到 `bottom.cards` 就崩或显示空框。 **投降结算**:只有 `aset`,无 `chupai`、无 `bottom` → 结算面板必须容忍**底牌区缺失**,不能因为读不到 `bottom.cards` 就崩或显示空框。
@@ -922,7 +922,7 @@ EQW_RoomOptions = {
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 1 | `主` | 花色图标 | `xuanzhu.flower` / `aset.flower`(1方块 2梅花 3红心 4黑桃);选主前显示占位 | | 1 | `主` | 花色图标 | `xuanzhu.flower` / `aset.flower`(1方块 2梅花 3红心 4黑桃);选主前显示占位 |
| 2 | `叫分`(绿字) | 分数 + 蓝色角标 | `shangzhuang.grade` + `multiple` | | 2 | `叫分`(绿字) | 分数 + 蓝色角标 | `shangzhuang.grade` + `multiple` |
| 3 | `抓分`(橙字) | 分数 + 橙色角标 | 闲家累计捡分(逐轮 `chupai3.grade` 累加,结算时以 `aset.grade` 为准) | | 3 | `抓分`(橙字) | 分数 + 橙色角标 | 分数 = 闲家累计捡分(逐轮 `chupai3.grade` 累加,结算时以 `aset.grade` 为准);角标 = **`Math.abs(curmultiple)`**(该字段带符号,角标只取大小,见 S-8) |
【待确认 T-16】:`抓分` 列的角标在 `等待叫分.png` 里是 `1子`、在 `小局结算.png` 里是 `1倍`,两图不一致。推定为 `aset.upgrade` 判定倍率(仅结算后有意义),需确认。 【待确认 T-16】:`抓分` 列的角标在 `等待叫分.png` 里是 `1子`、在 `小局结算.png` 里是 `1倍`,两图不一致。推定为 `aset.upgrade` 判定倍率(仅结算后有意义),需确认。
@@ -1791,7 +1791,7 @@ codes/config/
## 7. 待补充清单 ## 7. 待补充清单
> **状态汇总**(2026-08-26):待确认规格 34 项**只剩 T-20 音效**(已明确后补);设计稿只剩 **D-8**(判定结果**动画**)。 > **状态汇总**(2026-08-26):待确认规格 34 项**只剩 T-20 音效**(已明确后补);设计稿只剩 **D-8**(判定结果**动画**)。
> **§7.5 服务端待补项**:S-1…S-7 已处理完毕;**新增 S-8**(`curmultiple` 需带符号,阻塞 D-8 的过程中动画)待处理。 > **§7.5 服务端待补项 8 条全部处理完毕**。**服务端侧无遗留**——D-8 只差动画设计稿与触发细节。
### 7.1 待出设计稿的界面 ### 7.1 待出设计稿的界面
@@ -2053,28 +2053,25 @@ data.bottom.cards // 结算包
> 之前记录的「有主牌的玩家伪造请求也能拿到明牌数据」这一泄露担忧**不成立**:按手册他本来就有权查看,服务端受理是正确行为,不是校验缺失。 > 之前记录的「有主牌的玩家伪造请求也能拿到明牌数据」这一泄露担忧**不成立**:按手册他本来就有权查看,服务端受理是正确行为,不是校验缺失。
#### S-8 · `curmultiple` 需要带符号(新增,⚠️ 阻塞 D-8 的过程中动画) #### S-8 · `curmultiple` 改为带符号 —— ✅ 已完成(2026-08-26)
**背景**:D-8 定为「判定结果由动画呈现」,其中**对局过程中**那一路要靠 `curmultiple`(S-1)判断当前是哪一档判定。 **问题**:S-1 实现时取了绝对值,而判定的**正负号恰恰是「谁赢」的区分**——`3` 大光与 `-3` 升3级、`2` 小光与 `-2` 升2级、`1` 过庄与 `-1` 升1级,取绝对值后三对完全撞在一起,D-8 的过程中动画无从分辨该播哪个。
**问题**:S-1 实现时把 `curmultiple` 取了**绝对值**(`class.paiju.js` `get_curmultiple` 末行 `return (_u < 0) ? (0 - _u) : _u;`)。而判定的**正负号恰恰是「谁赢」的区分**: **改法**:`get_curmultiple` 直接返回 `get_upgrade` 的原值,**与结算包 `aset.upgrade` 完全同口径**:
| `get_upgrade` 原值 | 判定 | 取绝对值后 | | 值 | 含义 |
| --- | --- | --- | | --- | --- |
| `3` | 庄家**大光** | `3` | | `3` / `2` / `1` | 庄家 **大光** / **小光** / **过庄** |
| `-3` | 闲家**升 3 级** | `3` ← **撞了** | | `-N` | 闲家**升 N 级** |
| `2` | 庄家**小光** | `2` | | `0` | 叫分未定 |
| `-2` | 闲家**升 2 级** | `2` ← **撞了** |
| `1` | 庄家**过庄** | `1` |
| `-1` | 闲家**升 1 级** | `1` ← **撞了** |
三对判定在绝对值下完全无法区分,**过程中动画不知道该播哪个**。 **前端用法**:
- 顶部「抓分」角标只关心倍数大小 → `Math.abs(curmultiple)`(§2.1)。
- **判定动画**靠符号分辨该播哪个(§1.8 D-8),一套映射通吃「过程中」与「结算前」两条路径。
**建议**:`curmultiple` 改为**带符号**,与结算包 `aset.upgrade` 完全同口径(正=庄家赢的三档,负=闲家升级)。这样前端一套映射通吃「过程中」与「结算前」两条路径,不必写两份。 **测试**(hint 33 → 46 checks):新增两组——一组直接钉住三对「绝对值相同、判定相反」的组合可区分;另一组**直接调 `get_curmultiple` 本身**覆盖负数分支。
- 顶部「抓分」角标要显示的是倍数大小 → 前端取 `Math.abs(curmultiple)` 即可(§2.1),不受影响。 > 后一组是补上的**测试盲点**:原有断言里,算法层的 `cm()` 走的是 `A.get_upgrade`、**绕过了 `get_curmultiple`**,而端到端那局恰好是大光(正数),取不取绝对值结果一样。第一次反向验证(把实现退回取绝对值)竟然全绿,正是因为这个盲点。补齐后再验,4 条断言立刻失败。
- 改动很小:去掉取绝对值那一步 + 协议说明 + 测试断言同步。
- **为什么当初取了绝对值**:S-1 只服务于顶部角标那一个用途,角标只关心大小。现在多了动画这个消费方,符号成了必需信息。
--- ---
+9 -4
View File
@@ -830,8 +830,8 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
return re; return re;
}, },
//当前抓分倍数(design §7.2.0):按【当前累计捡分】实时算出的判定倍率绝对值, //当前抓分倍数(design §7.2.0):按【当前累计捡分】实时算出的判定倍率,【带符号】。
//供客户端顶部「抓分」列角标显示「若此刻结束是几倍」。 //供客户端顶部「抓分」列角标显示「若此刻结束是几倍」,以及对局中的判定动画(D-8)。
//与结算包 aset.upgrade 同源同算法(get_qvalue + get_upgrade),差别只在: //与结算包 aset.upgrade 同源同算法(get_qvalue + get_upgrade),差别只在:
//这里的捡分【不含扣底】——扣底要到最后一轮打完才产生,出牌过程中尚不存在。 //这里的捡分【不含扣底】——扣底要到最后一轮打完才产生,出牌过程中尚不存在。
//叫分未定(call<=0)时返回 0,前端不显示角标。 //叫分未定(call<=0)时返回 0,前端不显示角标。
@@ -843,8 +843,13 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
var _cfg = cls_youle_erqiwang_config.parse(o_paiju.o_desk.o_room.roomtype); var _cfg = cls_youle_erqiwang_config.parse(o_paiju.o_desk.o_room.roomtype);
var _grade = cls_youle_erqiwang_paiju.get_jian_grade(o_paiju).grade; var _grade = cls_youle_erqiwang_paiju.get_jian_grade(o_paiju).grade;
var _q = cls_youle_erqiwang_arith.get_qvalue(_call, _cfg.climb); var _q = cls_youle_erqiwang_arith.get_qvalue(_call, _cfg.climb);
var _u = cls_youle_erqiwang_arith.get_upgrade(_call, _grade, _q); //【带符号】返回,与结算包 aset.upgrade 完全同口径:
return (_u < 0) ? (0 - _u) : _u; // 正数 3/2/1 = 庄家 大光/小光/过庄;负数 -N = 闲家升 N 级。
//符号就是「谁赢」的区分,不能取绝对值——否则 3(大光) 与 -3(升3级)、
//2(小光) 与 -2(升2级)、1(过庄) 与 -1(升1级) 三对会完全撞在一起,
//客户端的判定动画将无从分辨该播哪个(前端清单 S-8)。
//只关心倍数大小的用途(如顶部「抓分」角标)自行取绝对值即可。
return cls_youle_erqiwang_arith.get_upgrade(_call, _grade, _q);
}, },
//某座位本轮跟牌的「必出牌」(design §5.2),供客户端自动选中。 //某座位本轮跟牌的「必出牌」(design §5.2),供客户端自动选中。
@@ -188,7 +188,7 @@
| cards | 数组 | 拿了底牌后手上的牌(36 张,含底牌),庄家才有此属性,闲家没有该属性 | | cards | 数组 | 拿了底牌后手上的牌(36 张,含底牌),庄家才有此属性,闲家没有该属性 |
| countdown | 整数 | 选主倒计时 | | countdown | 整数 | 选主倒计时 |
| touxiang | 整数 | 是否允许投降 0:不允许 1:允许(仅 70 分坐庄为 1)。投降与选主互斥、同为选主阶段(step2)的决策,见 touxiang 包与 design §4 | | touxiang | 整数 | 是否允许投降 0:不允许 1:允许(仅 70 分坐庄为 1)。投降与选主互斥、同为选主阶段(step2)的决策,见 touxiang 包与 design §4 |
| curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):按此刻累计捡分实时算出的判定倍率**绝对值**(3 大光 / 2 小光 / 1 过庄 / N 升 N 级)。上庄时捡分恒为 0,故必为 3。供客户端顶部「抓分」列角标显示。三家同值(捡分本就公开),叫分未定时为 0。**不含扣底**——扣底要到末轮才产生 | | curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):按此刻累计捡分实时算出的判定倍率,**带符号**,与结算包 `aset.upgrade` 同口径——`3`/`2`/`1` = 庄家 大光/小光/过庄,`-N` = 闲家升 N 级,`0` = 叫分未定。上庄时捡分恒为 0,故必为 `3`。三家同值(捡分本就公开)。**不含扣底**——扣底要到末轮才产生。<br>顶部「抓分」角标只关心倍数大小,取 `Math.abs` 即可;**符号供客户端的判定动画分辨该播哪个**(不能取绝对值,否则 3 大光与 -3 升3级会撞在一起)|
--- ---
@@ -296,7 +296,7 @@
| countdown | 整数 | 下一个出牌者的出牌倒计时 | | countdown | 整数 | 下一个出牌者的出牌倒计时 |
| baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0`)。**仅可查牌模式**下才可能为 1,是**余主公示**与"明牌"按钮的开关;**不查牌模式恒为 0**(design §9)| | baozhu | 整数 | 是否已有玩家报无主(主牌出空)0否 1是;对应服务端 `have_baofu()`(任一玩家 `seatlist[seat][4][0]==0`)。**仅可查牌模式**下才可能为 1,是**余主公示**与"明牌"按钮的开关;**不查牌模式恒为 0**(design §9)|
| cardsinhand | 数组 | 出牌者出牌后手上剩下的牌id列表,只有出牌者才有此属性 | | cardsinhand | 数组 | 出牌者出牌后手上剩下的牌id列表,只有出牌者才有此属性 |
| curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):按当前累计捡分实时算出的判定倍率**绝对值**(3 大光 / 2 小光 / 1 过庄 / N 升 N 级)。与结算包 `aset.upgrade` 同源同算法,差别仅在此处**不含扣底**。三家同值,随本包下发、不另开推送 | | curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):**带符号**,与结算包 `aset.upgrade` 同口径(`3`/`2`/`1` 庄家大光/小光/过庄,`-N` 闲家升 N 级,`0` 叫分未定)。同源同算法,差别仅在此处**不含扣底**。三家同值,随本包下发、不另开推送。符号是「谁赢」的区分,客户端判定动画靠它分辨 |
| mustcard | 数组 | **下一个出牌者本轮跟牌的必出牌**(design §5.2),供其客户端自动选中。**只发给 `nextseat` 那一家**,其余两家无此属性——它是该玩家自己手牌的子集,整表下发会泄露他家手牌结构。以下情形不下发:`nextseat` 是本轮首家、首家为**甩牌**(甩牌跟牌走逐分量匹配,见 design §5.4.4)、或算出的必出牌为空 | | mustcard | 数组 | **下一个出牌者本轮跟牌的必出牌**(design §5.2),供其客户端自动选中。**只发给 `nextseat` 那一家**,其余两家无此属性——它是该玩家自己手牌的子集,整表下发会泄露他家手牌结构。以下情形不下发:`nextseat` 是本轮首家、首家为**甩牌**(甩牌跟牌走逐分量匹配,见 design §5.4.4)、或算出的必出牌为空 |
--- ---
@@ -333,7 +333,7 @@
| cardsinhand | 数组 | 出牌后出牌者手上剩下的牌id列表,只有出牌者才有此属性 | | cardsinhand | 数组 | 出牌后出牌者手上剩下的牌id列表,只有出牌者才有此属性 |
| maxseat | 整数 | 本轮出牌谁最大,只有本轮最后一个玩家出牌后才有此属性 | | maxseat | 整数 | 本轮出牌谁最大,只有本轮最后一个玩家出牌后才有此属性 |
| grade | 整数 | 本轮闲家得分,只有本轮最后一个玩家出牌后且闲家有得分才有此属性 | | grade | 整数 | 本轮闲家得分,只有本轮最后一个玩家出牌后且闲家有得分才有此属性 |
| curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):按当前累计捡分实时算出的判定倍率**绝对值**。与结算包 `aset.upgrade` 同源同算法,差别仅在此处**不含扣底**。三家同值,随本包下发、不另开推送 | | curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):**带符号**,口径同 `aset.upgrade`。同源同算法,差别仅在此处**不含扣底**。三家同值,随本包下发、不另开推送 |
--- ---
@@ -602,7 +602,7 @@
| seatlist | 数组 | 三家座位牌况,与上文 chupai 包的 `seatlist` 同名同结构(每个玩家一个 5 元素数组:4 个花色的 `[无该花色,无对]` + 报副 `[剩余主牌数,剩余主对数]`)。**仅可查牌模式下有此属性**(design §9)| | seatlist | 数组 | 三家座位牌况,与上文 chupai 包的 `seatlist` 同名同结构(每个玩家一个 5 元素数组:4 个花色的 `[无该花色,无对]` + 报副 `[剩余主牌数,剩余主对数]`)。**仅可查牌模式下有此属性**(design §9)|
| liangpai | json | **亮牌**,结构同 maipai 包的 `liangpai`(`{ cards: [牌id...] }`);**仅可查牌模式、且请求者为闲家、且庄家达标时有**(供闲家重连后仍能看到,design §8.2)| | liangpai | json | **亮牌**,结构同 maipai 包的 `liangpai`(`{ cards: [牌id...] }`);**仅可查牌模式、且请求者为闲家、且庄家达标时有**(供闲家重连后仍能看到,design §8.2)|
| pushlist | 数组 | 出牌历史 `[[[], [], []], [[], [], []], ...]`,外层下标=轮次(从第 1 轮起,含进行中的当前轮),内层**恒为 3 个数组**、按位置序号存该轮各家出的牌id,各自已按**本局主牌花色**从大到小排序。**仅可查牌模式下有此属性**——往轮打出、已被收走的牌属于「查牌」范畴,不查牌模式一律不下发(design §9)。注意当前这一轮桌面上的牌由 `playproc.cards` 恢复,**两种模式下都有**,否则后出的人无从跟牌 | | pushlist | 数组 | 出牌历史 `[[[], [], []], [[], [], []], ...]`,外层下标=轮次(从第 1 轮起,含进行中的当前轮),内层**恒为 3 个数组**、按位置序号存该轮各家出的牌id,各自已按**本局主牌花色**从大到小排序。**仅可查牌模式下有此属性**——往轮打出、已被收走的牌属于「查牌」范畴,不查牌模式一律不下发(design §9)。注意当前这一轮桌面上的牌由 `playproc.cards` 恢复,**两种模式下都有**,否则后出的人无从跟牌 |
| curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):与 `chupai1/2/3` 同源同值,供重连后顶部「抓分」角标立即正确 | | curmultiple | 整数 | **当前抓分倍数**(design §7.2.0):**带符号**,与 `chupai1/2/3` 同源同值,供重连后顶部「抓分」角标与判定动画状态立即正确 |
| mustcard | 数组 | **本轮跟牌的必出牌**(design §5.2)。**仅当 `playproc.currseat == 请求者座位` 时才有**,且只算请求者自己的手牌;未轮到本家、本轮首家、甩牌局面、必出牌为空时均无此属性 | | mustcard | 数组 | **本轮跟牌的必出牌**(design §5.2)。**仅当 `playproc.currseat == 请求者座位` 时才有**,且只算请求者自己的手牌;未轮到本家、本轮首家、甩牌局面、必出牌为空时均无此属性 |
**Balance(不在结算阶段无此属性)** **Balance(不在结算阶段无此属性)**
+49 -12
View File
@@ -14,21 +14,58 @@ const pack = (s, d) => ({ conmode: 0, fromid: s, data: Object.assign({ agentid:
// ============================================================================ // ============================================================================
// —— 算法层:与 design §7.2.0 判定表逐档对齐(常规算子 Q=40)—— // —— 算法层:与 design §7.2.0 判定表逐档对齐(常规算子 Q=40)——
// curmultiple 是【带符号】的(S-8):正数=庄家赢的三档,负数=闲家升 N 级。
// 符号就是「谁赢」的区分,客户端判定动画靠它分辨该播哪个。
const cm = (call, grade, climb) => { const cm = (call, grade, climb) => {
const q = A.get_qvalue(call, climb ? 1 : 0); const q = A.get_qvalue(call, climb ? 1 : 0);
const u = A.get_upgrade(call, grade, q); return A.get_upgrade(call, grade, q);
return u < 0 ? -u : u;
}; };
t.eq('curmultiple 65分 捡0 → 大光x3', cm(65, 0), 3); t.eq('curmultiple 65分 捡0 → 大光 +3', cm(65, 0), 3);
t.eq('curmultiple 65分 捡35 → 小光x2', cm(65, 35), 2); t.eq('curmultiple 65分 捡35 → 小光 +2', cm(65, 35), 2);
t.eq('curmultiple 65分 捡40 → 过庄x1', cm(65, 40), 1); t.eq('curmultiple 65分 捡40 → 过庄 +1', cm(65, 40), 1);
t.eq('curmultiple 65分 捡65 → 升1级x1', cm(65, 65), 1); t.eq('curmultiple 65分 捡65 → 升1级 -1', cm(65, 65), -1);
t.eq('curmultiple 65分 捡105 → 升2级x2', cm(65, 105), 2); t.eq('curmultiple 65分 捡105 → 升2级 -2', cm(65, 105), -2);
t.eq('curmultiple 65分 捡145 → 升3级x3', cm(65, 145), 3); t.eq('curmultiple 65分 捡145 → 升3级 -3', cm(65, 145), -3);
// 爬坡:40分档 Q=20,分界线与常规不同(design §7.3.2) // 爬坡:40分档 Q=20,分界线与常规不同(design §7.3.2)
t.eq('curmultiple 爬坡40分 捡15 → 小光x2', cm(40, 15, true), 2); t.eq('curmultiple 爬坡40分 捡15 → 小光 +2', cm(40, 15, true), 2);
t.eq('curmultiple 爬坡40分 捡20 → 过庄x1', cm(40, 20, true), 1); t.eq('curmultiple 爬坡40分 捡20 → 过庄 +1', cm(40, 20, true), 1);
t.eq('curmultiple 爬坡40分 捡60 → 升2级x2', cm(40, 60, true), 2); t.eq('curmultiple 爬坡40分 捡60 → 升2级 -2', cm(40, 60, true), -2);
// —— S-8 的核心:三对「绝对值相同、判定相反」的组合必须靠符号分开 ——
// 取绝对值的话这三对会完全撞在一起,判定动画就无从分辨了
t.eq('S-8 大光(+3) vs 升3级(-3) 可区分', [cm(65, 0), cm(65, 145)], [3, -3]);
t.eq('S-8 小光(+2) vs 升2级(-2) 可区分', [cm(65, 35), cm(65, 105)], [2, -2]);
t.eq('S-8 过庄(+1) vs 升1级(-1) 可区分', [cm(65, 40), cm(65, 65)], [1, -1]);
// 符号语义:>0 庄家赢、<0 闲家赢、==0 叫分未定
t.eq('S-8 符号语义 庄赢为正', [cm(65, 0), cm(65, 35), cm(65, 40)].every(v => v > 0), true);
t.eq('S-8 符号语义 闲赢为负', [cm(65, 65), cm(65, 105), cm(65, 145)].every(v => v < 0), true);
// —— 直接调 get_curmultiple 本身,覆盖【负数】分支 ——
// 必要性:上面的 cm() 走的是 A.get_upgrade,【绕过了 get_curmultiple】;
// 而下面端到端那局恰好是大光(正数),取不取绝对值结果一样。
// 若没有这一组,get_curmultiple 悄悄退回取绝对值时两处都发现不了。
function mkCur(call, xianGrade, roomtype) {
const cards = [];
for (let i = 0; i < 108; i++) {
cards.push({ id: i, number: 5, score: 0, playowner: -1, dealowner: 1, playround: -1 });
}
// 喂给闲家(seat1)足量分牌,凑出指定捡分(每张 5 分,故 xianGrade 须为 5 的倍数)
let need = xianGrade / 5;
for (let i = 0; i < 108 && need > 0; i++) { cards[i].score = 5; cards[i].playowner = 1; need--; }
return P.get_curmultiple({
call: call, banker: 0, flower: 1, cards: cards,
o_desk: { o_room: { roomtype: roomtype || '00000' } }
});
}
t.eq('S-8 get_curmultiple 大光 → +3', mkCur(65, 0), 3);
t.eq('S-8 get_curmultiple 小光 → +2', mkCur(65, 35), 2);
t.eq('S-8 get_curmultiple 过庄 → +1', mkCur(65, 40), 1);
t.eq('S-8 get_curmultiple 升1级 → -1', mkCur(65, 65), -1);
t.eq('S-8 get_curmultiple 升2级 → -2', mkCur(65, 105), -2);
t.eq('S-8 get_curmultiple 升3级 → -3', mkCur(65, 145), -3);
t.eq('S-8 get_curmultiple 叫分未定 → 0', mkCur(0, 0), 0);
// 爬坡走 roomtype 位3=1,Q 分段不同
t.eq('S-8 get_curmultiple 爬坡40分 捡60 → 升2级 -2', mkCur(40, 60, '00010'), -2);
// —— 端到端:走完整流程,验证包里带且值正确 —— // —— 端到端:走完整流程,验证包里带且值正确 ——
function driveToPlay(roomtype) { function driveToPlay(roomtype) {
@@ -72,7 +109,7 @@ t.eq('叫分未定 curmultiple=0', P.get_curmultiple(pjNoCall), 0);
const before = pj.method.get_curmultiple(); const before = pj.method.get_curmultiple();
const q = A.get_qvalue(pj.call, 0); const q = A.get_qvalue(pj.call, 0);
const u = A.get_upgrade(pj.call, P.get_jian_grade(pj).grade, q); const u = A.get_upgrade(pj.call, P.get_jian_grade(pj).grade, q);
t.eq('curmultiple 与 get_upgrade 绝对值一致', before, u < 0 ? -u : u); t.eq('curmultiple 与 get_upgrade 完全一致(含符号)', before, u);
} }
// ============================================================================ // ============================================================================