Blackthorn

Join the conversation

Log in with your Blackthorn account to create topics and reply.

Guides

How RNG works

0 replies · 411 views

Every so often, someone posts a clip of an unlucky sequence of misses against a mob or another player, but nobody clips the opposite. Streaks that cost you something get noticed, remembered, and shared; equally unlikely streaks that go your way don't. This perception of unfairness often leads to suspicions that the dice are loaded or simply aren't working.

First, nothing here is "true" randomness. The systems described below run on a standard pseudo-random number generator (PRNG): a deterministic algorithm that produces a sequence of numbers indistinguishable from randomness for any practical purpose. The team didn't custom-write the generator itself, Microsoft engineers did. What is custom is how that underlying randomness gets used.

There are two ways the game handles probability:
  1. Plain rolls:
    The game calls the generator, gets a number, applies it, and then throws it away. Every roll is completely independent of the last.
  2. Deck system:
    The game uses the generator to shuffle a fixed set of outcomes, then deals them out one at a time without replacing them, only reshuffling when the deck is emptied.



1. Plain rolls

Let's assume plain rolls are used for combat-related events. If an attack has an 18% miss chance, then over a very large number of swings, the observed rate will converge to exactly 18%. This is known as the law of large numbers. But a single fight isn't the long run; you might only see ten swings before someone dies. The "shape" of outcomes in these shorter bursts is called a binomial distribution:

chart_binomial.png


2. Decks

For the rolls that shape how combat feels, the game uses a data structure that acts like a physical deck of cards.


a. Guaranteed distribution:
A deck contains 100 cards (numbered 0 to 99). It is shuffled using the Fisher–Yates shuffle, and then the cards are dealt one by one. If you have an 18% miss chance, cards 0–17 act as misses, and 18–99 act as hits. Once all 100 cards are dealt, the deck is rebuilt and reshuffled.

Because the deck always contains exactly one of every number, a full 100-draw cycle will always produce exactly 18 misses.


b. Guaranteed rates:
In many games, your true chance to hit starts well below the tooltip value and ramps up every time you fail (example). A deck system has no ramp. Every position in a shuffled deck is equally likely to hold a success or failure, meaning your very first swing carries your full, printed percentage.


c. Anti-clumping mechanism:
Guaranteeing 18 misses per 100 swings, and ensuring every position in the deck has an equal chance to hold any number, doesn't inherently prevent those misses from clumping together. To solve this, the 100 cards are dealt out into ten buckets of ten cards in a manner that guarantees every bucket gets exactly one card from each decile (0–9, 10–19, 20–29, up to 90–99). The buckets are then concatenated. At an 18% miss chance, this means:
  • Every bucket gets at least one miss (because the 0–9 card is always a miss).
  • Eight of those buckets will get a second miss (from the 10–17 portion of the 10–19 decile).
  • The remaining two buckets will have only one miss (because they drew the 18 or 19).
This structure dramatically smooths out the RNG, ensuring no single bucket ever differs from another by more than one miss.


d. Creation and usage:
A deck is created for each roll type on a per-player basis. Which deck is used depends on the roll type:
  • Miss, resist, dual wield activation, offensive procs, variance, crits -> Attacker's deck.
  • Evade, parry, block, defensive procs, intercept (primarily for Spiritmaster) -> Defender's deck.
Some types are further split into two distinct decks: physical and magical.

Important caveats:
  • Off-hand swings are excluded:
    Dual wield and hand-to-hand extra attacks roll on plain independent randomness. This stops them from eating through the main-hand's carefully structured decks.
  • Style chains get separate decks:
    If every stage of a combo shared the same set of decks, landing your opener would consume a card from the same pool your follow-up needs. This would subtly alter the follow-up's actual odds.
  • NPCs don't use decks:
    The only exception is Necromancer pets. They use their owner's decks and are effectively treated as players.
  • Decks don't eliminate variance entirely:
    The anti-streak guarantee is only ironclad within a specific bucket. The game doesn't heavily constrain the "seams" where one bucket ends and another begins, or where an empty deck is reshuffled. These boundaries introduce some localized variance. Additionally, other game events may clear the memory of your current deck, forcing a premature reshuffle.
Here is what that distribution actually looks like across many windows, including the bucket-boundary effects mentioned above:

chart_deck.png

e. Code:
https://github.com/OpenDAoC/OpenDAoC-Co ... ils/Random




3. Comparisons and simulations

Here is the aggregate difference when you simulate 20,000 ten-swing fight windows, comparing independent rolls to the decks:

chart_compare.png

a. Plain rolls:
Zero misses in a rolling window of ten swings happens about 14% of the time, and four or more misses happens about 9% of the time. The two most likely outcomes (1 and 2 misses) only account for 60% of encounters. The worst single window saw 8 misses total.

While a 5-miss streak starting on the first swing has only a 0.02% chance of happening (incredibly rare in isolation), across a 200-swing combat session, the chance of one occurring somewhere in that sequence is roughly 3%. Across a whole server of players fighting every day, these streaks happen constantly purely by unmodified probability.


b. Decks:
Zero misses in a rolling window of ten swings drops to about 4%, and 5 or more consecutive misses becomes mathematically impossible. The two most likely outcomes (1 and 2 misses) now account for almost 80% of encounters.

Here is a generated deck, showing how the grouping prevents those wild streaks:
Player wrote: Draws 1- 10: 47 91 20 87 13 56 67 35 5 72 -> misses: 2
Draws 11- 20: 49 76 16 62 36 90 28 85 6 50 -> misses: 2
Draws 21- 30: 81 2 32 66 18 95 55 27 74 41 -> misses: 1
Draws 31- 40: 25 94 59 31 63 86 45 17 77 4 -> misses: 2
Draws 41- 50: 82 22 57 34 92 71 43 11 7 68 -> misses: 2
Draws 51- 60: 24 38 69 98 40 80 52 75 15 8 -> misses: 2
Draws 61- 70: 21 93 1 79 14 65 58 30 46 83 -> misses: 2
Draws 71- 80: 78 61 44 51 3 23 39 97 89 12 -> misses: 2
Draws 81- 90: 33 0 48 10 88 29 60 96 70 53 -> misses: 2
Draws 91-100: 99 84 73 26 42 37 54 64 9 19 -> misses: 1

Total: 18 misses out of 100.
Notice that every fixed 10-swing bucket lands on exactly 1 or 2 misses. Reshuffle the deck, and the specific numbers will change, but that distribution won't. The deck system essentially cuts the variance in half.




4. Testing it yourself

If you want to test the RNG system, it's important to know whether the events you are parsing use decks or plain rolls.


a. Testing decks (main-hand, dodges, blocks...):
Because the deck system guarantees an exact distribution, a pure, uninterrupted sequence of 100 main-hand swings will yield exactly your printed percentage. If you have an 18% miss chance, you will see exactly 18 misses every 100 swings.

If your parser shows 25 misses in 100 swings, you likely included off-hand attacks, included swings that bypassed miss checks (like blocked swings), triggered a style chain (which uses a separate deck), experienced a forced reshuffle, or found an actual bug. Additionally, if you see streaks longer or more frequent than the deck's structure should mathematically allow, flag it.


b. Testing plain rolls (off-hand attacks, mixed logs, non-combat events...):
This is more complex. The process requires collecting enough samples and then determining if the measured odds fall within an acceptable statistical range.

Because we know the target rate in the game's code, we can calculate the range of results we'd normally expect to see from a player's logs due to random variation:
Player wrote: MOE (margin of error) = z * sqrt((p * (1 - p)) / n)
Expected range = p ± MOE

Where p is the target rate as a decimal (e.g. 0.18), n is the sample size, and z is the z-score corresponding to the desired confidence level:

Code: Select all

Two-tailed CL | Z-score
-----------------------
        80.0% |   1.282
        85.0% |   1.440
        90.0% |   1.645
        95.0% |   1.960
        99.0% |   2.576
        99.5% |   2.807
        99.9% |   3.291
A 95% confidence level means that, if the true rate really is 18%, approximately 95% of samples of this size would be expected to fall within the calculated range.
Using this formula for an 18% target rate and a 95% confidence level, here is the acceptable margin of error for a given sample size:

Code: Select all

Sample size | Expected Range (95%)
-------------------------------------
        100 | 10.5% – 25.5%  (± 7.5%)
      1,000 | 15.6% – 20.4%  (± 2.4%)
     10,000 | 17.2% – 18.8%  (± 0.8%)
     50,000 | 17.7% – 18.3%  (± 0.3%)
Therefore, with a sample size of 100, observing 25% does not by itself provide statistically significant evidence that the rate is incorrect.

The margin of error shrinks proportionally to 1 / sqrt(n), meaning that halving the margin of error requires approximately 4x as many samples, while reducing it by 10x requires approximately 100x as many samples.
Back to forums