package selfgame import "testing" func TestEvaluateRobotStrategyBlackPoolSkipsRobot(t *testing.T) { decision, err := EvaluateRobotStrategy(baseStrategyInput(87, 88), func(int) (int, error) { t.Fatal("black pool must not roll strategy probability") return 0, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.StrategyCode != StrategyCodePoolGuard || decision.ReasonCode != "POOL_BLACK" || decision.Decision != DecisionNone { t.Fatalf("black pool decision mismatch: %+v", decision) } } func TestEvaluateRobotStrategyBlackPoolCanForceRobotWin(t *testing.T) { input := baseStrategyInput(87, 88) input.NewUserPolicy.BlackPoolRobotForceWinEnabled = true decision, err := EvaluateRobotStrategy(input, func(int) (int, error) { t.Fatal("black pool robot force-win must not roll strategy probability") return 0, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.StrategyCode != StrategyCodePoolGuard || decision.ReasonCode != "POOL_BLACK_ROBOT_FORCE_WIN" || decision.Decision != DecisionForceHumanLose || decision.ForceResult != ForceResultHumanLose { t.Fatalf("black pool force robot win decision mismatch: %+v", decision) } } func TestEvaluateRobotStrategyDisabledPoolSkipsEvenWhenBlackForceEnabled(t *testing.T) { input := baseStrategyInput(200, 88) input.StakePool.Status = "disabled" input.NewUserPolicy.BlackPoolRobotForceWinEnabled = true decision, err := EvaluateRobotStrategy(input, func(int) (int, error) { t.Fatal("disabled pool must not roll strategy probability") return 0, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.StrategyCode != StrategyCodePoolGuard || decision.ReasonCode != "POOL_BLACK" || decision.Decision != DecisionNone { t.Fatalf("disabled pool should still skip robot, got %+v", decision) } } func TestEvaluateRobotStrategyNewUserProtectionHit(t *testing.T) { decision, err := EvaluateRobotStrategy(baseStrategyInput(200, 88), func(max int) (int, error) { if max != 100 { t.Fatalf("strategy roll max = %d, want 100", max) } return 0, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.Decision != DecisionForceHumanWin || decision.ForceResult != ForceResultHumanWin || !decision.ShouldConsumeProtection { t.Fatalf("new user protection decision mismatch: %+v", decision) } } func TestEvaluateRobotStrategyNewUserLoseStreakProtection(t *testing.T) { input := baseStrategyInput(200, 88) input.UserStats.LoseStreak = 2 decision, err := EvaluateRobotStrategy(input, func(int) (int, error) { t.Fatal("lose streak protection must not roll positive-feedback probability") return 0, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.Decision != DecisionForceHumanWin || decision.ForceResult != ForceResultHumanWin || !decision.ShouldConsumeProtection { t.Fatalf("lose streak protection decision mismatch: %+v", decision) } if decision.ReasonCode != "NEW_USER_LOSE_STREAK_PROTECTION" { t.Fatalf("lose streak protection reason mismatch: %+v", decision) } } func TestEvaluateRobotStrategyQuotaShortFallsBack(t *testing.T) { input := baseStrategyInput(200, 88) input.ProtectionState.LifetimeSubsidyCoin = 29950 decision, err := EvaluateRobotStrategy(input, func(int) (int, error) { t.Fatal("quota short must not roll strategy probability") return 0, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.Decision != DecisionNone || decision.ReasonCode != "NEW_USER_LIFETIME_QUOTA_SHORT" { t.Fatalf("quota short decision mismatch: %+v", decision) } } func TestEvaluateRobotStrategyOldUserFallsBackToNormalRandom(t *testing.T) { input := baseStrategyInput(200, 88) input.UserStats = UserStats{EffectiveBotGameCount: 31, WinStreak: 9, TodayNetWinCoin: 900000, SevenDayNetWinCoin: 1800000} input.ProtectionState.UsedRounds = 30 input.NewUserPolicy.NormalPhaseTargetWinRatePercent = 50 decision, err := EvaluateRobotStrategy(input, nil) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.Decision != DecisionNone || decision.ForceResult != "" || decision.ReasonCode != "NORMAL_PHASE_TARGET_BASELINE" { t.Fatalf("old user must keep normal random decision, got %+v", decision) } } func TestEvaluateRobotStrategyNormalPhaseDiceTargetHit(t *testing.T) { input := baseStrategyInput(200, 88) input.UserStats = UserStats{EffectiveBotGameCount: 31} input.ProtectionState.UsedRounds = 30 input.NewUserPolicy.NormalPhaseTargetWinRatePercent = 53 decision, err := EvaluateRobotStrategy(input, func(max int) (int, error) { if max != 10_000 { t.Fatalf("normal phase should roll bps, max=%d", max) } return 599, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.Decision != DecisionForceHumanWin || decision.ForceResult != ForceResultHumanWinWithoutProtection || decision.ShouldConsumeProtection { t.Fatalf("normal phase target hit must force win without subsidy consume: %+v", decision) } if decision.ReasonCode != "NORMAL_PHASE_TARGET_HIT" { t.Fatalf("normal phase hit reason mismatch: %+v", decision) } } func TestEvaluateRobotStrategyNormalPhaseRockTargetMiss(t *testing.T) { input := baseStrategyInput(200, 88) input.AllowsDraw = true input.UserStats = UserStats{EffectiveBotGameCount: 31} input.ProtectionState.UsedRounds = 30 input.NewUserPolicy.NormalPhaseTargetWinRatePercent = 53 decision, err := EvaluateRobotStrategy(input, func(max int) (int, error) { if max != 10_000 { t.Fatalf("normal phase should roll bps, max=%d", max) } return 409, nil }) if err != nil { t.Fatalf("evaluate strategy failed: %v", err) } if decision.Decision != DecisionNone || decision.ForceResult != "" || decision.ReasonCode != "NORMAL_PHASE_TARGET_MISS" { t.Fatalf("normal phase target miss must stay random: %+v", decision) } } func baseStrategyInput(balance int64, required int64) StrategyInput { return StrategyInput{ AppCode: "lalu", GameID: "dice", MatchID: "match_1", UserID: 101, StakeCoin: 100, RequiredPoolCoin: required, StakePool: StakePool{AppCode: "lalu", GameID: "dice", StakeCoin: 100, BalanceCoin: balance, Status: "active"}, NewUserPolicy: DefaultNewUserPolicy("lalu", "dice", 1700000000000), ProtectionState: ProtectionState{AppCode: "lalu", GameID: "dice", UserID: 101, Status: "active"}, } }