@swrpg-online/monte-carlo
Version:
A library for performing Monte Carlo simulations with the Star Wars RPG narrative dice system by Fantasy Flight Games
561 lines (455 loc) • 18.7 kB
Markdown
# @swrpg-online/monte-carlo


[](https://codecov.io/gh/swrpg-online/monte-carlo)
[](https://github.com/semantic-release/semantic-release)
A utility library for Star Wars RPG by [Fantasy Flight Games](https://www.fantasyflightgames.com/en/starwarsrpg/) and [Edge Studio](https://www.edge-studio.net/categories-games/starwarsrpg/). Provides statistical analysis for the [narrative dice system](https://star-wars-rpg-ffg.fandom.com/wiki/Narrative_Dice).
## Installation
```bash
npm install @swrpg-online/monte-carlo
```
## Features
### Monte Carlo Simulation
The `MonteCarlo` class provides statistical analysis of dice pools through simulation. It helps to understand the probabilities and distributions of different outcomes.
```typescript
import { MonteCarlo, DicePool } from "@swrpg-online/monte-carlo";
// Create a dice pool
const pool: DicePool = {
abilityDice: 2, // 2 green (Ability) dice
proficiencyDice: 1, // 1 yellow (Proficiency) die
};
// Create a Monte Carlo simulation with 10000 iterations (default)
// Optional: disable automatic simulation with runSimulate=false
const simulation = new MonteCarlo(pool, 10000, false);
const results = simulation.simulate();
console.log("Success Probability:", results.successProbability);
console.log("Average Successes:", results.averages.successes);
console.log(
"Standard Deviation of Successes:",
results.standardDeviations.successes,
);
```
### Dice Pool Modifiers (New in v2.2.0)
The Monte Carlo library now supports dice pool modifiers including automatic symbols, dice upgrades, and downgrades. These features integrate with @swrpg-online/dice v1.4.0+.
#### Automatic Symbols
Add automatic successes, failures, advantages, threats, triumphs, or despairs to your dice pools:
```typescript
import { MonteCarlo, SimulationConfig } from "@swrpg-online/monte-carlo";
const config: SimulationConfig = {
dicePool: {
abilityDice: 2,
difficultyDice: 2,
},
iterations: 10000,
modifiers: {
automaticSuccesses: 1, // Add 1 automatic success
automaticAdvantages: 2, // Add 2 automatic advantages
},
};
const simulation = new MonteCarlo(config);
const results = simulation.simulate();
// Results include modifier analysis
console.log(
"Automatic Contributions:",
results.modifierAnalysis?.automaticSymbolContribution,
);
console.log(
"Rolled Contributions:",
results.modifierAnalysis?.rolledSymbolContribution,
);
```
#### Dice Upgrades and Downgrades
Upgrade ability dice to proficiency dice, or downgrade challenge dice to difficulty dice:
```typescript
const config: SimulationConfig = {
dicePool: {
abilityDice: 3,
difficultyDice: 2,
},
modifiers: {
upgradeAbility: 2, // Upgrade 2 ability dice to proficiency
upgradeDifficulty: 1, // Upgrade 1 difficulty die to challenge
},
};
```
#### Player vs Opposition Modifiers
Apply different modifiers to player and opposition rolls:
```typescript
const config: SimulationConfig = {
dicePool: {
abilityDice: 2,
proficiencyDice: 1,
difficultyDice: 2,
},
playerModifiers: {
automaticSuccesses: 1,
upgradeAbility: 1,
},
oppositionModifiers: {
automaticThreats: 1,
upgradeDifficulty: 1,
},
};
```
#### Modifier Analysis
When using modifiers, the results include additional analysis:
```typescript
interface ModifierAnalysis {
automaticSymbolContribution: {
successes: number;
failures: number;
advantages: number;
threats: number;
triumphs: number;
despairs: number;
};
rolledSymbolContribution: {
// Same structure, showing dice-rolled symbols
};
upgradeImpact: {
abilityUpgrades: number;
difficultyUpgrades: number;
proficiencyDowngrades: number;
challengeDowngrades: number;
};
}
```
### Results Include
- **Averages**: Mean values for success, advantage, triumph, failure, threat, and despair
- **Medians**: Median values for all symbols
- **Standard Deviations**: Standard deviation for all symbols
- **Probabilities**:
- Success probability (net successes > 0)
- Critical success probability (at least one triumph)
- Critical failure probability (at least one despair)
- Net positive probability (both net successes and net advantages > 0)
- **Distribution Analysis**:
- Skewness (distribution asymmetry)
- Kurtosis (distribution "tailedness")
- Outliers (values > 2 standard deviations from mean)
- Modes (most common values)
- Percentiles (25th, 50th, 75th, 90th)
## Development
```bash
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
```
## Use Cases
### Basic Combat Check
```typescript
import { DicePool } from "@swrpg-online/dice";
import { MonteCarlo } from "@swrpg-online/monte-carlo";
// Combat check with 2 green (Ability) and 1 yellow (Proficiency)
const combatPool: DicePool = {
abilityDice: 2,
proficiencyDice: 2,
difficultyDice: 2,
challengeDice: 1,
};
const combatSim = new MonteCarlo(combatPool);
const combatResults = combatSim.simulate();
console.log("Combat Check Results:", JSON.stringify(combatResults, null, 2));
```
```JSON
{
"averages": {
"successes": 1.4205,
"advantages": 2.5969,
"triumphs": 0.1717,
"failures": 0.3253,
"threats": 2.1799,
"despair": 0.0827,
"lightSide": 0.0,
"darkSide": 0.0
},
"medians": {
"successs": 1,
"advantages": 3,
"triumphs": 0,
"failures": 0,
"threats": 2,
"despair": 0,
"lightSide": 0,
"darkSide": 0
},
"standardDeviations": {
"successes": 1.4573536804770009,
"advantages": 1.4342978735255518,
"triumphs": 0.396256369033995,
"failures": 0.7638585667516965,
"threats": 1.1950464384281536,
"despair": 0.2754282302161655,
"lightSide": 0.0,
"darkSide": 0.0
},
"successProbability": 0.6273,
"criticalSuccessProbability": 0.1643,
"criticalFailureProbability": 0.0827,
"netPositiveProbability": 0.219,
"forceStatistics": {
"averageNetForce": 0.0021,
"medianNetForce": 0,
"standardDeviationNetForce": 1.31,
"maxForcePipsProbability": 0.3331
},
"histogram": {
"netSuccesses": { /* distribution data */ },
"netAdvantages": { /* distribution data */ },
"triumphs": { /* distribution data */ },
"despairs": { /* distribution data */ },
"lightSide": { /* distribution data */ },
"darkSide": { /* distribution data */ },
"netForce": { /* distribution data */ }
},
"analysis": {
"netSuccesses": {
"skewness": 0.12,
"kurtosis": -0.20,
"outliers": [-2, 4],
"modes": [1],
"percentiles": {
"25": 0,
"50": 1,
"75": 2,
"90": 3
}
},
"netAdvantages": { /* similar structure */ },
"triumphs": { /* similar structure */ },
"despairs": { /* similar structure */ },
"lightSide": { /* similar structure */ },
"darkSide": { /* similar structure */ },
"netForce": { /* similar structure */ }
}
}
```
### Statistical Analysis Features
The Monte Carlo simulation provides detailed statistical information:
1. **Basic Probabilities**
- Success rate (net positive successes)
- Triumph and Despair chances
- Net positive results (both success and advantage)
2. **Detailed Statistics**
- Mean values for all symbols
- Median values for all symbols
- Standard deviations
- Distribution analysis (skewness, kurtosis, outliers)
- Mode detection
- Percentile calculations
### How to Interpret Results
When analyzing your dice pool using the Monte Carlo simulation, here's how to interpret each result:
#### Probability Metrics
- **successProbability**: Represents the chance of achieving a net success (successes minus failures > 0). For example, a value of 0.65 means you have a 65% chance of succeeding at the task. In SWRPG terms, this is your chance of meeting or exceeding the difficulty of the check.
- **criticalSuccessProbability**: The chance of rolling at least one triumph (⊺). In SWRPG, triumphs can trigger powerful narrative effects or special abilities regardless of success/failure. A value of 0.167 means you have about a 1-in-6 chance of scoring a triumph.
- **criticalFailureProbability**: The chance of rolling at least one despair (⊝). Similar to triumphs, despairs can trigger significant negative narrative events. A probability of 0.083 indicates about a 1-in-12 chance of a despair occurring.
- **netPositiveProbability**: The likelihood of achieving both net successes and net advantages. This is particularly useful for social encounters or situations where both succeeding and gaining advantage are important. A value of 0.432 means you have a 43.2% chance of both succeeding and gaining advantage.
#### Force Statistics
`forceStatistics` summarizes Force dice (white d12) outcomes. Net Force is `lightSide - darkSide`, so positive values lean Light Side and negative values lean Dark Side.
- **averageNetForce** ("Avg Net Force Pips"): The mean net Force pips per roll. A single Force die is pip-symmetric (8 Light and 8 Dark pips across its 12 faces), so a pure Force pool trends toward 0; flat `automaticLightSide` / `automaticDarkSide` modifiers shift it.
- **medianNetForce** / **standardDeviationNetForce**: The typical net Force result and how swingy it is, mirroring the `medians`/`standardDeviations` measures for other symbols.
- **maxForcePipsProbability** ("Max Force Pips %"): The probability of rolling the maximum possible pips — every Force die landing on a 2-pip face. Each die has a 4-in-12 (1/3) chance, so 1 Force die ≈ 0.333 and 2 dice ≈ 0.111. This is 0 for pools without Force dice.
The `histogram.netForce` and `analysis.netForce` entries provide the full net-Force distribution (buckets, skewness, kurtosis, percentiles, modes, outliers) in the same shape as the other symbols.
#### Statistical Measures
- **averages**: The expected number of each symbol you'll get on average:
- A successes average of 1.5 means you typically get 1-2 successes
- An advantages average of 0.8 means you usually get about 1 advantage
- Triumphs/Despair averages are typically low (e.g., 0.083 = 1 triumph per 12 rolls)
- Light/Dark side averages are typically 0 unless using Force dice
- **medians**: The middle value when all results are sorted. Useful for understanding the "typical" roll:
- If different from the average, indicates skewed results
- More resistant to extreme rolls than averages
- Helps understand what a "normal" roll looks like
- **standardDeviations**: Measures how much variation exists from the average:
- Lower values (e.g., 0.5) indicate consistent results
- Higher values (e.g., 1.5) indicate more volatile/swingy rolls
- About 68% of rolls fall within ±1 standard deviation of the average
- About 95% of rolls fall within ±2 standard deviations
#### Histogram Data
The `histogram` field provides a detailed frequency distribution for key outcomes:
```typescript
"histogram": {
"netSuccesses": { // Distribution of (Successes - Failures)
"0": 37445, // 37445 rolls resulted in 0 net Successes
"1": 26516, // 26516 rolls resulted in 1 net Success
"2": 8298, // 8298 rolls resulted in 2 net Successes
"-1": 20853, // 20853 rolls resulted in -1 net Success (1 net Failure)
"-2": 6888 // 6888 rolls resulted in -2 net Successes (2 net Failures)
},
"netAdvantages": { // Distribution of (Advantages - Threats)
"0": 39009, // 39009 rolls resulted in 0 net Advantages
"1": 22149, // 22149 rolls resulted in 1 net Advantage
"2": 8463, // 8463 rolls resulted in 2 net Advantages
"-1": 22030, // 22030 rolls resulted in -1 net Advantage (1 net Threat)
"-2": 8349 // 8349 rolls resulted in -2 net Advantages (2 net Threats)
},
"triumphs": { // Distribution of Triumph counts
"0": 83570, // 83570 rolls had 0 Triumphs
"1": 15430, // 15430 rolls had 1 Triumph
"2": 1000 // 1000 rolls had 2 Triumphs
},
"despairs": { // Distribution of Despair counts (similar structure) },
"lightSide": { // Distribution of Light Side point counts (Force Dice) },
"darkSide": { // Distribution of Dark Side point counts (Force Dice) }
}
```
- **Keys**: Represent the specific outcome value (e.g., the net number of successes/advantages, or the count of triumphs/despairs).
- For `netSuccesses`, negative keys indicate **net Failures** (e.g., `-1` means 1 net Failure).
- For `netAdvantages`, negative keys indicate **net Threats** (e.g., `-2` means 2 net Threats).
- **Values**: Represent the number of simulation iterations (rolls) that resulted in that specific outcome.
This data allows for a detailed view of the likelihood of every possible outcome, going beyond the summary statistics. It's the basis for calculating the `analysis` metrics like skewness, kurtosis, and percentiles.
#### Distribution Analysis
- **skewness**: Measures distribution asymmetry:
- Positive: More extreme positive results than negative
- Negative: More extreme negative results than positive
- Near 0: Roughly symmetric distribution
- **kurtosis**: Measures distribution "tailedness":
- Positive: More extreme results than a normal distribution
- Negative: Fewer extreme results than a normal distribution
- Near 0: Similar to a normal distribution
- **outliers**: Values more than 2 standard deviations from the mean:
- Useful for identifying unusual or extreme results
- Helps understand the range of possible outcomes
- Important for risk assessment
- **modes**: Most common values in the distribution:
- Single mode: One clear "most common" result
- Multiple modes: Several equally common results
- Helps identify typical outcomes
- **percentiles**: Value thresholds for different percentiles:
- 25th: "Worst quarter" threshold
- 50th: Median result
- 75th: "Best quarter" threshold
- 90th: "Exceptional" threshold
For example, if you have:
```typescript
{
averages: { successes: 2.0, advantages: 1.5 },
standardDeviations: { successes: 1.2, advantages: 1.1 },
analysis: {
netSuccesses: {
skewness: 0.12,
kurtosis: -0.20,
outliers: [-2, 4],
modes: [1],
percentiles: {
"25": 0,
"50": 1,
"75": 2,
"90": 3
}
}
}
}
```
This means:
- You typically get 2 successes, but about 68% of rolls will give you 0.8 to 3.2 successes
- You usually get 1-2 advantages, with 68% of rolls giving you 0.4 to 2.6 advantages
- The distribution is slightly right-skewed (0.12) and has lighter tails than normal (-0.20)
- Unusual results include -2 and 4 successes
- The most common result is 1 success
- 25% of rolls give 0 or fewer successes
- 50% of rolls give 1 or fewer successes
- 75% of rolls give 2 or fewer successes
- 90% of rolls give 3 or fewer successes
### Performance Considerations
- Default 10,000 iterations provide a good balance of accuracy and speed
- Increase iterations for more precise probability calculations:
```typescript
const preciseSim = new MonteCarlo(pool, 100000);
```
- Memory-efficient implementation:
- Uses running statistics to avoid storing all roll results
- Sparse histogram storage for distribution analysis
- Cached calculations for frequently accessed statistics
- Direct array access for histogram updates
- Optimized for large datasets and long-running simulations
- Selector-based caching for improved performance
- Performance optimizations:
- Single-pass calculations for all statistics
- Efficient histogram-based calculations
- Running statistics for improved accuracy
- Minimized memory allocations
- Optimized for large iteration counts
- Prefixed cache keys for better organization
- Selector-based caching for reuse
### Available Statistics
The `simulate()` method returns a `MonteCarloResult` with the following information:
```typescript
interface MonteCarloResult {
// Mean values for each symbol (calculated using running statistics)
averages: {
successes: number;
advantages: number;
triumphs: number;
failures: number;
threats: number;
despair: number;
lightSide: number;
darkSide: number;
};
// Median values for each symbol (calculated from histogram)
medians: {
successes: number;
advantages: number;
triumphs: number;
failures: number;
threats: number;
despair: number;
lightSide: number;
darkSide: number;
};
// Standard deviations for each symbol (calculated using running statistics)
standardDeviations: {
successes: number;
advantages: number;
triumphs: number;
failures: number;
threats: number;
despair: number;
lightSide: number;
darkSide: number;
};
// Probability of net successes > 0 (calculated during simulation)
successProbability: number;
// Probability of at least one triumph (calculated during simulation)
criticalSuccessProbability: number;
// Probability of at least one despair (calculated during simulation)
criticalFailureProbability: number;
// Probability of both net successes and net advantages > 0 (calculated during simulation)
netPositiveProbability: number;
// Histogram data for distribution analysis (sparse storage)
histogram: HistogramData;
// Detailed analysis of distributions (calculated from histogram)
analysis: {
netSuccesses: DistributionAnalysis;
netAdvantages: DistributionAnalysis;
triumphs: DistributionAnalysis;
despairs: DistributionAnalysis;
lightSide: DistributionAnalysis;
darkSide: DistributionAnalysis;
};
}
```
### Error Handling
The `MonteCarlo` class includes built-in validation:
- Validates that the dice pool contains at least one die
- Ensures all die counts are non-negative integers
- Validates iteration count (minimum 100, maximum 1,000,000)
- Handles unknown selector types in statistical calculations
- Manages cache misses with valid selectors
- Handles empty histograms and incomplete data
```typescript
try {
const simulation = new MonteCarlo(pool);
const results = simulation.simulate();
} catch (error) {
if (error instanceof MonteCarloError) {
console.error("Simulation error:", error.message);
}
}
```
## License
MIT