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}, } }