fix(ops): validate lucky gift jackpot tiers

This commit is contained in:
zhx 2026-07-16 10:35:05 +08:00
parent 17a94eaf94
commit d2898f3fae
2 changed files with 46 additions and 5 deletions

View File

@ -284,6 +284,9 @@ export function validateLuckyGiftForm(form) {
)
errors.push("等价抽数阶段门槛不正确");
const jackpotMultipliers =
form.strategy_version === DYNAMIC_STRATEGY ? new Set(multiplierListFromForm(form.jackpot_multipliers)) : null;
// 阶段概率与 RTP 是两代策略共用的 owner 校验fixed_v2 也不能跳过。
for (const stageKey of ["novice", "normal", "advanced"]) {
const stage = form.stages?.find((item) => item.stage === stageKey);
@ -302,6 +305,19 @@ export function validateLuckyGiftForm(form) {
errors.push(`${stageLabel(stageKey)}启用奖档概率必须大于 0`);
if (enabled.some((tier) => Number(tier.multiplier) >= 10 && !tier.highWaterOnly))
errors.push(`${stageLabel(stageKey)}10x 及以上奖档必须限制为高水位`);
const jackpotStageMultipliers =
jackpotMultipliers &&
enabled
.filter(
(tier) =>
jackpotMultipliers.has(numberFromForm(tier.multiplier)) &&
percentToPPM(tier.probabilityPercent) > 0,
)
.map((tier) => formatDecimal(numberFromForm(tier.multiplier)));
if (jackpotStageMultipliers?.length) {
// dynamic_v3 的大奖倍率只能走大奖补偿路径;普通阶段再给正概率会绕过 owner 的双 RTP 门槛。
errors.push(`${stageLabel(stageKey)}普通奖档不能包含大奖倍率:${unique(jackpotStageMultipliers).join("x、")}x`);
}
const expected = expectedRTPPercent(enabled, { multiplier: (item) => item.multiplier });
if (
expected < number("target_rtp_percent") - number("control_band_percent") ||
@ -333,11 +349,11 @@ export function validateLuckyGiftForm(form) {
const jackpotTokens = String(form.jackpot_multipliers || "")
.split(/[,\s]+/)
.filter(Boolean);
const jackpotMultipliers = multiplierListFromForm(form.jackpot_multipliers);
const jackpotMultiplierValues = multiplierListFromForm(form.jackpot_multipliers);
if (
jackpotTokens.length !== jackpotMultipliers.length ||
!jackpotMultipliers.length ||
jackpotMultipliers.some((value, index) => value <= 1 || (index > 0 && value <= jackpotMultipliers[index - 1]))
jackpotTokens.length !== jackpotMultiplierValues.length ||
!jackpotMultiplierValues.length ||
jackpotMultiplierValues.some((value, index) => value <= 1 || (index > 0 && value <= jackpotMultiplierValues[index - 1]))
) {
errors.push("大奖倍率必须大于 1x 并严格递增");
}

View File

@ -70,7 +70,6 @@ describe("ops center lucky gift dynamic_v3 form", () => {
const enabled = { ...draft, enabled: true };
const errors = validateLuckyGiftForm(enabled);
expect(errors).toContain("启用前必须填写用户日累计消费触发门槛(金币)");
expect(errors).toContain("启用前普通阶段至少一个最低充值门槛必须大于 0");
expect(errors).toContain("高阶充值门槛必须逐维不低于正常阶段且至少一项更高");
expect(errors).toContain("启用前必须填写单次返奖上限");
@ -151,6 +150,32 @@ describe("ops center lucky gift dynamic_v3 form", () => {
expect(errors).toContain("单次返奖上限不能小于 0");
expect(errors).toContain("充值阶段门槛不能小于 0");
});
test("rejects jackpot multipliers that still have ordinary stage probability", () => {
const form = configToForm(completeDynamicConfig());
const errors = validateLuckyGiftForm({
...form,
stages: form.stages.map((stage) =>
stage.stage === "normal"
? {
...stage,
tiers: [
...stage.tiers,
{
enabled: true,
highWaterOnly: true,
multiplier: "200",
probabilityPercent: "0.01",
tierId: "normal_200x",
},
],
}
: stage,
),
});
expect(errors).toContain("正常阶段普通奖档不能包含大奖倍率200x");
});
});
function completeDynamicConfig() {