A production-grade RTP calculator for probability-based reward systems.
Exact enumeration · Parallel execution · Monte Carlo bonus simulation · Variance analysis
This repository implements a PAR sheet calculator (Probability and Accounting Report) — the mathematical engine used to design and verify slot machines, gacha systems, and loot box mechanics.
Given a set of reel strips and a pay table, it computes:
- RTP (Return to Player) — exact, not simulated
- Hit Frequency — fraction of spins with at least one win
- Variance — mathematical measure of volatility
- Bonus RTP — Free Spins contribution via Monte Carlo
- MaxWin impact — how much RTP the win cap removes
New to PAR sheet math?
Before diving into the code, read the theoretical foundation:
📐 Slot Mathematical Model — PAR Sheet Explained
It covers reel strip design, combination counting, and how RTP is derived analytically.
- ✅ Exact enumeration of all stop combinations (no sampling)
- ✅
Parallel.Forover reel 0 — linear scaling with CPU cores - ✅ Weighted symbol system — control probability via stop counts
- ✅ Wild substitution with full chain evaluation
- ✅ 20 configurable paylines (straight rows, V-shapes, zigzags)
- ✅ MaxWin cap with RTP loss tracking
- ✅ Scatter trigger probability (analytical, not sampled)
- ✅ Free Spins Monte Carlo with retrigger support
- ✅ Real-time progress bar with live RTP estimate
git clone https://github.com/Akubet/RTPsim
dotnet run --project RTPsimOutput:
| Calculating PAR sheet...
[####################] 100%
Combinations : 55,566,000 / 55,566,000
Hits so far : 7,451,340
RTP so far : 90.39%
╔══════════════════════════════════════════════════╗
║ PAR SHEET — FINAL RESULTS ║
╠══════════════════════════════════════════════════╣
║ Base Game RTP : 90.60 % ║
║ Hit Frequency : 13.11 % ║
║ Variance : 571.83 ║
╠══════════════════════════════════════════════════╣
║ Trigger Freq : 1 in 84 spins ║
║ Avg Bonus Win : 76.40x ║
║ Bonus RTP : 5.40 % ║
╠══════════════════════════════════════════════════╣
║ TOTAL RTP : 96.00 % ║
╚══════════════════════════════════════════════════╝
Probability is not assigned directly — it emerges from symbol counts on the reel strip.
A Diamond×1 on a 35-stop reel = 2.86% probability. A Wild×3 = 8.57%.
var weightedReels = new WeightedSymbol[][]
{
// Reel 1
[
new(Symbol.Diamond, 2),
new(Symbol.Ruby, 3),
new(Symbol.Emerald, 2),
new(Symbol.Gold, 3),
new(Symbol.Silver, 3),
new(Symbol.Ace, 4),
new(Symbol.King, 4),
new(Symbol.Queen, 5),
new(Symbol.Jack, 5),
new(Symbol.Wild, 2),
new(Symbol.Scatter, 2),
],
// Reel 3 — center reel, more Wilds for near-miss tension
[
// ...
new(Symbol.Wild, 3), // higher Wild count on center reel is intentional
// ...
],
};var payTable = new PayTable(new()
{
[Symbol.Diamond] = new() { [3] = 80m, [4] = 300m, [5] = 1000m },
[Symbol.Ruby] = new() { [3] = 40m, [4] = 150m, [5] = 500m },
[Symbol.Emerald] = new() { [3] = 25m, [4] = 100m, [5] = 300m },
[Symbol.Gold] = new() { [3] = 15m, [4] = 50m, [5] = 150m },
[Symbol.Silver] = new() { [3] = 10m, [4] = 35m, [5] = 100m },
[Symbol.Ace] = new() { [3] = 6m, [4] = 18m, [5] = 60m },
[Symbol.King] = new() { [3] = 6m, [4] = 18m, [5] = 60m },
[Symbol.Queen] = new() { [3] = 4m, [4] = 12m, [5] = 40m },
[Symbol.Jack] = new() { [3] = 4m, [4] = 12m, [5] = 40m },
[Symbol.Wild] = new() { [5] = 200m },
});var config = new GameConfig(weightedReels, payLines, payTable, MaxWin: 5000m);
var calculator = new ParSheetCalculator(config);
// Full PAR sheet: base game + bonus
var result = calculator.CalculateFull();
Console.WriteLine($"Total RTP: {result.TotalRTP:P2}");Reaching a target RTP (e.g. 96%) requires iterative tuning. The recommended workflow:
| Step | Action | Speed | Accuracy |
|---|---|---|---|
| 1 | Monte Carlo 500K samples | ~0.5 sec | ±0.2% |
| 2 | Adjust Wild weights, rerun | ~0.5 sec | ±0.2% |
| 3 | Exact enumeration | ~35 sec | exact |
| 4 | Tune bonus multiplier | ~3 sec | ±0.1% |
| 5 | Final exact + bonus | ~40 sec | exact |
Sensitivity reference (approximate, varies by pay table):
| Change | RTP delta |
|---|---|
| Wild weight Reel3: +1 stop | +3–5% |
| Wild weight Reel1,2,4,5: +1 stop | +1–2% each |
| Diamond weight: +1 stop per reel | +1–2% |
| Ruby weight: +1 stop per reel | +0.5–1% |
| Bonus multiplier: +0.1 | +0.28% |
Wild on the center reel (Reel 3) is the most sensitive lever because more paylines pass through the center. A single stop difference can swing RTP by 3–5%.
Target breakdown for a 96% game:
Base game RTP : 89–91%
Bonus RTP : 5–7%
─────────────────────
Total RTP : 96%
RTPsim/
├── Core/
│ ├── ParSheetCalculator.cs ← main engine
│ ├── GameConfig.cs ← configuration records
│ ├── PayTable.cs ← payout lookup
│ ├── PayLine.cs ← payline definitions
│ └── ReelConfig/
│ └── WeightedSymbol.cs ← weighted stop model
├── Configs/
│ └── DefaultConfig.cs ← sample 96% RTP config
└── Program.cs
Monte Carlo gives ~±0.2% accuracy at 1M samples. For casino certification, regulators require exact RTP. Exact enumeration is the only method that guarantees correctness — and at 52M combinations it completes in ~35 seconds with parallelisation.
Variance is the mathematical signature of volatility: E[X²] - (E[X])².
| Variance | Profile | Example games |
|---|---|---|
| < 50 | Low | Classic fruit machines |
| 50–150 | Medium | Most video slots |
| 150–400 | Medium-High | This engine's target |
| 400–800 | High | Book of Ra, Dead or Alive |
| > 800 | Very High | Money Train, Razor Shark |
Two games with identical 96% RTP but different variance have completely different player experiences. Variance is a design choice, not a side effect.
Counterintuitively, QUEEN 3× contributes more total RTP than DIAMOND 5× — because probability × payout, and probability always wins. The jackpot feels important but the math is driven by frequent small wins.
- 📐 Slot Mathematical Model — PAR Sheet Explained
The theoretical foundation: reel strip design, combination counting, RTP formula derivation, and how the numbers in this engine were chosen. Start here if you're new to slot math.
MIT — free to use in commercial and open source projects.
Built for developers who want to understand how probability-based reward systems actually work — not just use them.