// Command strategy-sim runs the product matrix against the exact pure kernel used by // production. It performs no database or network I/O; fixed seeds make every report // reproducible in CI, during review, and after a rule-version change. package main import ( "encoding/json" "flag" "fmt" "os" domain "hyapp/services/lucky-gift-service/internal/domain/luckygift" ) const defaultSimulationSeed int64 = 20_260_712 type simulationOptions struct { Seed int64 MonteCarloN int LongRunN int GroupLongRunN int } type simulationReport struct { Seed int64 `json:"seed"` MonteCarloDraws int `json:"monte_carlo_draws"` LongRunDraws int `json:"long_run_draws"` GroupLongRunDraws int `json:"group_long_run_draws"` Nominal nominalEVReport `json:"nominal_probability_table"` Scenarios []scenarioResult `json:"scenarios"` AllPassed bool `json:"all_passed"` } type nominalEVReport struct { ExpectedMultiplier float64 `json:"expected_multiplier"` NominalRTPPercent float64 `json:"nominal_rtp_percent"` TargetRTPPercent float64 `json:"target_rtp_percent"` PassesTarget bool `json:"passes_target"` Conclusion string `json:"conclusion"` } type scenarioResult struct { Name string `json:"name"` Passed bool `json:"passed"` Summary string `json:"summary"` Data any `json:"data,omitempty"` } func main() { options := simulationOptions{} jsonOut := "" flag.Int64Var(&options.Seed, "seed", defaultSimulationSeed, "deterministic simulation seed") flag.IntVar(&options.MonteCarloN, "monte-carlo-draws", 100_000, "draws for probability Monte Carlo") flag.IntVar(&options.LongRunN, "long-run-draws", 100_000, "draws for funded RTP long run") flag.IntVar(&options.GroupLongRunN, "group-long-run-draws", 30_000, "draws per recharge-stage group") flag.StringVar(&jsonOut, "json-out", "", "optional path for the same JSON report printed to stdout") flag.Parse() if options.MonteCarloN <= 0 || options.LongRunN <= 0 || options.GroupLongRunN <= 0 { fmt.Fprintln(os.Stderr, "all draw counts must be positive") os.Exit(2) } report := runSimulation(options) encoded, err := json.MarshalIndent(report, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "encode report: %v\n", err) os.Exit(1) } printHumanSummary(report) fmt.Println("\n--- JSON REPORT ---") fmt.Println(string(encoded)) if jsonOut != "" { if err := os.WriteFile(jsonOut, append(encoded, '\n'), 0o644); err != nil { fmt.Fprintf(os.Stderr, "write JSON report: %v\n", err) os.Exit(1) } } if !report.AllPassed { os.Exit(1) } } func runSimulation(options simulationOptions) simulationReport { // The runtime default remains a safe 98% table. Only this simulator opts into // the supplied image's 2650% table so the report can expose why it is unsafe. config := imageExampleConfig(domain.DefaultLuckyGiftStrategyConfig()) nominal := calculateNominalEV(config) scenarios := []scenarioResult{ simulateImage800Redraw(config), simulatePWBoundaries(config), simulateFundSplitAndColdStart(config), simulateBatchCounts(config, options.Seed), simulateSettlementWagerWindow(), simulateWaterBoundaries(config), simulateRechargeWindowBoundaries(config), simulateProbabilityMonteCarlo(config, options.Seed+10, options.MonteCarloN), simulateFundedRTPLongRun(config, options.Seed+20, options.LongRunN), simulateMechanismOneTruthTable(config, options.Seed+30), simulateMechanismTwo(config, options.Seed+40), simulateJackpotSet(config, options.Seed+50), simulateDailyJackpotLimit(config, options.Seed+60), simulateSixRiskCapacities(config), simulateRechargeStages(config), simulateCombinationPriority(config, options.Seed+70), simulateConcurrencyAndIdempotency(config, options.Seed+80), simulateLongRunGroups(config, options.Seed+90, options.GroupLongRunN), } allPassed := !nominal.PassesTarget // 2650% must be explicitly rejected, not relabelled as a 98% pass. for _, scenario := range scenarios { allPassed = allPassed && scenario.Passed } return simulationReport{ Seed: options.Seed, MonteCarloDraws: options.MonteCarloN, LongRunDraws: options.LongRunN, GroupLongRunDraws: options.GroupLongRunN, Nominal: nominal, Scenarios: scenarios, AllPassed: allPassed, } } func calculateNominalEV(config domain.StrategyConfig) nominalEVReport { var expected float64 for _, tier := range config.Tiers { if tier.Enabled { expected += float64(tier.BaseWeightPPM) / float64(domain.StrategyPPMScale) * float64(tier.MultiplierPPM) / float64(domain.StrategyPPMScale) } } return nominalEVReport{ ExpectedMultiplier: expected, NominalRTPPercent: expected * 100, TargetRTPPercent: 98, PassesTarget: expected <= 0.98, Conclusion: "图示固定概率表的名义EV为26.5x(2650%),不能作为98% RTP方案;只有P/W资金约束后的有资金长跑口径可验证不透支。", } } func printHumanSummary(report simulationReport) { fmt.Printf("幸运礼物策略模拟 seed=%d\n", report.Seed) fmt.Printf("名义概率表: EV=%.2fx, RTP=%.2f%%, 98%%校验=%v(预期必须为false)\n", report.Nominal.ExpectedMultiplier, report.Nominal.NominalRTPPercent, report.Nominal.PassesTarget) for _, scenario := range report.Scenarios { status := "PASS" if !scenario.Passed { status = "FAIL" } fmt.Printf("[%s] %s: %s\n", status, scenario.Name, scenario.Summary) } fmt.Printf("总结果: all_passed=%v\n", report.AllPassed) }