2026-05-20 10:22:35 +08:00

135 lines
6.2 KiB
Go

package luckygift
import (
"fmt"
"strings"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/luckygift"
)
const ppmScale int64 = 1_000_000
// DefaultConfig 给后台首屏提供整体规则草稿;真正生效必须由后台显式 enabled。
func DefaultConfig(appCode string) domain.Config {
const giftPrice int64 = 10_000
return domain.Config{
AppCode: appCode,
GiftID: domain.GlobalConfigGiftID,
Enabled: false,
GiftPrice: giftPrice,
TargetRTPPPM: 950_000,
PoolRatePPM: 950_000,
GlobalWindowDraws: 100_000,
GiftWindowDraws: 100_000,
NoviceDrawLimit: 2_000,
IntermediateDrawLimit: 20_000,
HighMultiplier: 100,
HighWaterPoolMultiple: 2,
PlatformPoolWeightPPM: 200_000,
RoomPoolWeightPPM: 300_000,
GiftPoolWeightPPM: 500_000,
InitialPlatformPool: 9_500_000,
InitialGiftPool: 23_750_000,
InitialRoomPool: 5_000_000,
PlatformReserve: 1_000_000,
GiftReserve: 1_000_000,
RoomReserve: 100_000,
MaxSinglePayout: giftPrice * 500,
UserHourlyPayoutCap: 3_420_000,
UserDailyPayoutCap: 61_560_000,
DeviceDailyPayoutCap: 102_600_000,
RoomHourlyPayoutCap: 68_400_000,
AnchorDailyPayoutCap: 1_231_200_000,
RoomAtmosphereRatePPM: 10_000,
RoomAtmosphereInitial: 100_000,
RoomAtmosphereReserve: 10_000,
ActivityBudget: 0,
ActivityDailyLimit: 0,
LargeTierEnabled: true,
Tiers: defaultTiers(giftPrice),
}
}
func validateConfig(config domain.Config) error {
if config.GiftID != domain.GlobalConfigGiftID {
return xerr.New(xerr.InvalidArgument, "lucky gift config scope is invalid")
}
if config.GiftPrice <= 0 || config.TargetRTPPPM <= 0 || config.TargetRTPPPM > ppmScale {
return xerr.New(xerr.InvalidArgument, "gift price or target RTP is invalid")
}
if config.PoolRatePPM < config.TargetRTPPPM || config.PoolRatePPM > ppmScale {
return xerr.New(xerr.InvalidArgument, "pool rate must be between target RTP and 100%")
}
if config.GlobalWindowDraws <= 0 || config.GiftWindowDraws <= 0 {
return xerr.New(xerr.InvalidArgument, "RTP window draws must be positive")
}
if config.NoviceDrawLimit < 0 || config.IntermediateDrawLimit < config.NoviceDrawLimit {
return xerr.New(xerr.InvalidArgument, "experience pool draw limits are invalid")
}
if config.HighMultiplier <= 0 || config.HighWaterPoolMultiple <= 0 {
return xerr.New(xerr.InvalidArgument, "high multiplier config is invalid")
}
if config.PlatformPoolWeightPPM < 0 || config.RoomPoolWeightPPM < 0 || config.GiftPoolWeightPPM < 0 ||
config.PlatformPoolWeightPPM+config.RoomPoolWeightPPM+config.GiftPoolWeightPPM != ppmScale {
return xerr.New(xerr.InvalidArgument, "pool weights must sum to 1000000")
}
if config.MaxSinglePayout <= 0 || config.UserHourlyPayoutCap <= 0 || config.UserDailyPayoutCap <= 0 ||
config.DeviceDailyPayoutCap <= 0 || config.RoomHourlyPayoutCap <= 0 || config.AnchorDailyPayoutCap <= 0 {
return xerr.New(xerr.InvalidArgument, "risk payout caps must be positive")
}
if len(config.Tiers) == 0 {
return xerr.New(xerr.InvalidArgument, "reward tiers are required")
}
for _, tier := range config.Tiers {
if tier.Pool == "" || tier.TierID == "" || tier.Weight < 0 || tier.RewardCoins < 0 {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("invalid reward tier: %s", tier.TierID))
}
}
return nil
}
func normalizeTiers(tiers []domain.Tier, giftPrice int64) []domain.Tier {
if len(tiers) == 0 {
return defaultTiers(giftPrice)
}
out := make([]domain.Tier, 0, len(tiers))
for _, tier := range tiers {
tier.Pool = strings.TrimSpace(tier.Pool)
tier.TierID = strings.TrimSpace(tier.TierID)
if tier.Pool == "" || tier.TierID == "" {
continue
}
out = append(out, tier)
}
return out
}
func defaultTiers(cost int64) []domain.Tier {
return []domain.Tier{
{Pool: domain.PoolNovice, TierID: "none", RewardCoins: 0, Weight: 720_000, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_feedback_0_2x", RewardCoins: cost / 5, Weight: 90_000, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_rebate_0_5x", RewardCoins: cost / 2, Weight: 70_000, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_rebate_1x", RewardCoins: cost, Weight: 70_000, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_small_2x", RewardCoins: cost * 2, Weight: 35_000, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_small_5x", RewardCoins: cost * 5, Weight: 12_000, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_medium_10x", RewardCoins: cost * 10, Weight: 2_500, Enabled: true},
{Pool: domain.PoolNovice, TierID: "novice_cap_20x", RewardCoins: cost * 20, Weight: 500, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "none", RewardCoins: 0, Weight: 830_000, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "inter_rebate_0_5x", RewardCoins: cost / 2, Weight: 25_000, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "inter_rebate_1x", RewardCoins: cost, Weight: 40_000, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "inter_small_2x", RewardCoins: cost * 2, Weight: 45_000, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "inter_medium_5x", RewardCoins: cost * 5, Weight: 35_000, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "inter_large_20x", RewardCoins: cost * 20, Weight: 20_000, HighWaterOnly: true, Enabled: true},
{Pool: domain.PoolIntermediate, TierID: "inter_large_50x", RewardCoins: cost * 50, Weight: 5_000, HighWaterOnly: true, Enabled: true},
{Pool: domain.PoolAdvanced, TierID: "none", RewardCoins: 0, Weight: 900_000, Enabled: true},
{Pool: domain.PoolAdvanced, TierID: "adv_small_2x", RewardCoins: cost * 2, Weight: 30_000, Enabled: true},
{Pool: domain.PoolAdvanced, TierID: "adv_medium_5x", RewardCoins: cost * 5, Weight: 30_000, Enabled: true},
{Pool: domain.PoolAdvanced, TierID: "adv_large_20x", RewardCoins: cost * 20, Weight: 30_000, HighWaterOnly: true, Enabled: true},
{Pool: domain.PoolAdvanced, TierID: "adv_large_100x", RewardCoins: cost * 100, Weight: 9_000, HighWaterOnly: true, Enabled: true},
{Pool: domain.PoolAdvanced, TierID: "adv_jackpot_500x", RewardCoins: cost * 500, Weight: 1_000, HighWaterOnly: true, Enabled: true},
}
}