diff --git a/services/activity-service/internal/service/luckygift/service_test.go b/services/activity-service/internal/service/luckygift/service_test.go index 7bd3d98e..e7f16607 100644 --- a/services/activity-service/internal/service/luckygift/service_test.go +++ b/services/activity-service/internal/service/luckygift/service_test.go @@ -10,7 +10,9 @@ import ( walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" "hyapp/pkg/tencentim" + broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast" domain "hyapp/services/activity-service/internal/domain/luckygift" + broadcastservice "hyapp/services/activity-service/internal/service/broadcast" ) func TestGetConfigReturnsPoolDefault(t *testing.T) { @@ -54,6 +56,7 @@ func TestProcessPendingDrawOutboxCreditsWalletAndPublishesRoomIM(t *testing.T) { "user_id": 42, "sender_user_id": 42, "target_user_id": 99, + "visible_region_id": 210, "room_id": "room-1", "gift_id": "rose", "gift_count": 1, @@ -73,7 +76,8 @@ func TestProcessPendingDrawOutboxCreditsWalletAndPublishesRoomIM(t *testing.T) { } wallet := &fakeLuckyGiftWallet{} publisher := &fakeLuckyGiftPublisher{} - service := New(repository, WithWallet(wallet), WithRoomPublisher(publisher)) + broadcaster := &fakeLuckyGiftRegionBroadcaster{} + service := New(repository, WithWallet(wallet), WithRoomPublisher(publisher), WithRegionBroadcaster(broadcaster)) service.SetClock(func() time.Time { return time.UnixMilli(1760000001000).UTC() }) processed, err := service.ProcessPendingDrawOutbox(context.Background(), WorkerOptions{WorkerID: "worker-1"}) @@ -89,6 +93,16 @@ func TestProcessPendingDrawOutboxCreditsWalletAndPublishesRoomIM(t *testing.T) { if publisher.last.GroupID != "room-1" || publisher.last.Desc != "lucky_gift_drawn" || publisher.last.EventID != "lucky_gift_drawn:lucky_draw_test" { t.Fatalf("room im message mismatch: %+v", publisher.last) } + if broadcaster.last.EventID != "lucky_gift_big_win:lucky_draw_test" || broadcaster.last.BroadcastType != broadcastdomain.TypeLuckyGiftBigWin || broadcaster.last.RegionID != 210 { + t.Fatalf("region lucky broadcast mismatch: %+v", broadcaster.last) + } + var broadcastPayload map[string]any + if err := json.Unmarshal([]byte(broadcaster.last.PayloadJSON), &broadcastPayload); err != nil { + t.Fatalf("decode region broadcast payload failed: %v", err) + } + if broadcastPayload["event_type"] != "lucky_gift_drawn" || broadcastPayload["broadcast_type"] != broadcastdomain.TypeLuckyGiftBigWin || broadcastPayload["room_id"] != "room-1" { + t.Fatalf("region broadcast payload mismatch: %+v", broadcastPayload) + } if repository.grantedDrawID != "lucky_draw_test" || repository.deliveredOutboxID != "lucky_lucky_draw_test" { t.Fatalf("repository settlement mismatch: granted=%s delivered=%s", repository.grantedDrawID, repository.deliveredOutboxID) } @@ -176,3 +190,12 @@ func (p *fakeLuckyGiftPublisher) PublishGroupCustomMessage(_ context.Context, me p.last = message return nil } + +type fakeLuckyGiftRegionBroadcaster struct { + last broadcastservice.PublishInput +} + +func (b *fakeLuckyGiftRegionBroadcaster) PublishRegionBroadcast(_ context.Context, input broadcastservice.PublishInput) (broadcastdomain.PublishResult, error) { + b.last = input + return broadcastdomain.PublishResult{EventID: input.EventID, Status: broadcastdomain.StatusPending, Created: true}, nil +} diff --git a/services/activity-service/internal/storage/mysql/lucky_gift_repository.go b/services/activity-service/internal/storage/mysql/lucky_gift_repository.go index 4292d6c4..b0958db6 100644 --- a/services/activity-service/internal/storage/mysql/lucky_gift_repository.go +++ b/services/activity-service/internal/storage/mysql/lucky_gift_repository.go @@ -1326,10 +1326,9 @@ func (r *Repository) updateLuckyRiskCounters(ctx context.Context, tx *sql.Tx, ap func (r *Repository) selectLuckyCandidate(config domain.Config, pool string, stageFeedback bool, globalWindow, giftWindow luckyRTPWindow, platformPool, roomPool, giftPool, atmosphere luckyPool, activityRemaining int64, counters map[string]luckyCounter, minFutureMax int64) (luckyCandidate, map[string]bool, error) { minRequired := maxInt64(globalWindow.minRequired(minFutureMax), giftWindow.minRequired(minFutureMax)) - rtpMaxAllowed := minInt64(globalWindow.remainingPayout(), giftWindow.remainingPayout()) poolCapacity := luckyWeightedPoolCapacity(platformPool, roomPool, giftPool, config) riskCapacity := luckyRiskCapacity(config, counters) - baseMaxAllowed := minInt64(rtpMaxAllowed, minInt64(poolCapacity, riskCapacity)) + baseMaxAllowed := minInt64(poolCapacity, riskCapacity) limited := map[string]bool{} baseCandidates := make([]luckyCandidate, 0, len(config.Tiers)) diff --git a/services/activity-service/internal/storage/mysql/lucky_gift_repository_test.go b/services/activity-service/internal/storage/mysql/lucky_gift_repository_test.go index ad106ca0..c4f4228d 100644 --- a/services/activity-service/internal/storage/mysql/lucky_gift_repository_test.go +++ b/services/activity-service/internal/storage/mysql/lucky_gift_repository_test.go @@ -100,6 +100,26 @@ func TestLuckyAutoWeightedBaseCandidatesDoesNotDiluteDeficitAcrossLargeWindow(t } } +func TestLuckyAutoWeightedBaseCandidatesInterpolatesSingleDrawBelowOneX(t *testing.T) { + candidates := []luckyCandidate{ + {TierID: "none", BaseReward: 0}, + {TierID: "rebate_1x", BaseReward: 100}, + } + window := luckyRTPWindow{ControlDraws: 100_000, TargetPayoutCoins: 95} + + weighted := luckyAutoWeightedBaseCandidates(candidates, 0, window, window) + + if len(weighted) != 2 { + t.Fatalf("expected 0x and 1x interpolation candidates, got %#v", weighted) + } + if weighted[0].TierID != "none" || weighted[0].Weight != 50_000 { + t.Fatalf("expected 0x to keep 5%% probability, got %#v", weighted[0]) + } + if weighted[1].TierID != "rebate_1x" || weighted[1].Weight != 950_000 { + t.Fatalf("expected 1x to carry 95%% probability, got %#v", weighted[1]) + } +} + func TestSelectLuckyCandidateDoesNotInjectNoneTier(t *testing.T) { config := domain.Config{ GiftID: domain.DefaultPoolID, diff --git a/services/room-service/internal/room/service/lucky_gift_test.go b/services/room-service/internal/room/service/lucky_gift_test.go index 34b4a6b6..f3feff88 100644 --- a/services/room-service/internal/room/service/lucky_gift_test.go +++ b/services/room-service/internal/room/service/lucky_gift_test.go @@ -104,7 +104,11 @@ func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) { t.Fatalf("expected one lucky draw, got %d", len(luckyGift.draws)) } drawMeta := luckyGift.draws[0].GetLuckyGift() - if drawMeta.GetCommandId() != "cmd-lucky" || drawMeta.GetCoinSpent() != 100 || drawMeta.GetDeviceId() != "device-session-1" || drawMeta.GetAnchorId() != "101" { + if drawMeta.GetCommandId() != "cmd-lucky" || + drawMeta.GetCoinSpent() != 100 || + drawMeta.GetDeviceId() != "device-session-1" || + drawMeta.GetAnchorId() != "101" || + drawMeta.GetVisibleRegionId() != 9001 { t.Fatalf("lucky draw meta mismatch: %+v", drawMeta) } }