二七王: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
+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)——
// curmultiple 是【带符号】的(S-8):正数=庄家赢的三档,负数=闲家升 N 级。
// 符号就是「谁赢」的区分,客户端判定动画靠它分辨该播哪个。
const cm = (call, grade, climb) => {
const q = A.get_qvalue(call, climb ? 1 : 0);
const u = A.get_upgrade(call, grade, q);
return u < 0 ? -u : u;
return A.get_upgrade(call, grade, q);
};
t.eq('curmultiple 65分 捡0 → 大光x3', cm(65, 0), 3);
t.eq('curmultiple 65分 捡35 → 小光x2', cm(65, 35), 2);
t.eq('curmultiple 65分 捡40 → 过庄x1', cm(65, 40), 1);
t.eq('curmultiple 65分 捡65 → 升1级x1', cm(65, 65), 1);
t.eq('curmultiple 65分 捡105 → 升2级x2', cm(65, 105), 2);
t.eq('curmultiple 65分 捡145 → 升3级x3', cm(65, 145), 3);
t.eq('curmultiple 65分 捡0 → 大光 +3', cm(65, 0), 3);
t.eq('curmultiple 65分 捡35 → 小光 +2', cm(65, 35), 2);
t.eq('curmultiple 65分 捡40 → 过庄 +1', cm(65, 40), 1);
t.eq('curmultiple 65分 捡65 → 升1级 -1', cm(65, 65), -1);
t.eq('curmultiple 65分 捡105 → 升2级 -2', cm(65, 105), -2);
t.eq('curmultiple 65分 捡145 → 升3级 -3', cm(65, 145), -3);
// 爬坡:40分档 Q=20,分界线与常规不同(design §7.3.2)
t.eq('curmultiple 爬坡40分 捡15 → 小光x2', cm(40, 15, true), 2);
t.eq('curmultiple 爬坡40分 捡20 → 过庄x1', cm(40, 20, true), 1);
t.eq('curmultiple 爬坡40分 捡60 → 升2级x2', cm(40, 60, true), 2);
t.eq('curmultiple 爬坡40分 捡15 → 小光 +2', cm(40, 15, true), 2);
t.eq('curmultiple 爬坡40分 捡20 → 过庄 +1', cm(40, 20, true), 1);
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) {
@@ -72,7 +109,7 @@ t.eq('叫分未定 curmultiple=0', P.get_curmultiple(pjNoCall), 0);
const before = pj.method.get_curmultiple();
const q = A.get_qvalue(pj.call, 0);
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);
}
// ============================================================================