Skip to content

Another discussion about RNG and dice roll behavior

Hello, so I was playing my paladin in the OC tonight, and I noticed some very suspicious dice roll behavior in my saves against minogon shrieks in the Act II archaeologists' ruins.

My paladin has Luck of Heroes for +1 to all saves, Strong Soul for +1 to will and fortitude saves, and Divine Grace with a charisma bonus of +3 to all saves, for a total of +5 to will saves.

But, at level 8, my listed base will save is only +9. That seems too low when you add in all the feat bonuses.

But wait, there's more.

I kept rolling 6's far more than 50 percent of the time. My will save scroll against minogon shrieks (DC 15), looked like 6, 6, 12, 6, 18, 6, 6, 6, 14, etc.

There was never a roll lower than 6, no matter how many times I had to make a will save against minogon shrieks. Furthermore, there was a clear preponderance of 6's. Never a 1, 2, 3, 4, or 5. *Always* a 6, far more than 50 percent of the time.

So, my first question would be, are the bonuses from Luck of Heroes, Strong Soul, and Divine Grace being added to the dice roll rather than to the base save value? That would explain why my lowest apparent save rolls were 6's.

And the next obvious question would be, why was I rolling 6's over and over and over? It makes me suspect that the game was *trying* to roll critical failures of "1" in a very non-random, predetermined way, where I had a +5 bonus from my feats.

There was another thread here recently by someone else who started to become suspicious of the supposed RNG in the game.

Also, I've noticed something very similar in the Baldur's Gate "RNG" generator.

Low level characters seem to get consistently low rolls. The rolls become seemingly higher with higher levels.

I know about selectively reinforced memory and confirmation bias. But I still remain suspicious that something else is going on with computer generated D&D RNG programs.

There is one objective part of my topic - Does the NWN program add feat bonuses to the dice rolls rather than to the base save values? If not, why is my base save value only a 9 at level 8? I'm pretty sure that's too low with all my feat and class bonuses. (Wisdom is 14, Intelligence is 14.)

Comments

  • DerpCityDerpCity Member, Moderator Posts: 303
    edited February 2018
    A paladin of 8th level has +6 Fort and +2 Ref/Will. The fact you have 9 Will at the moment suggests you're leaving something out if you only have a +5 bonus to it. EDIT: After realizing you probably have 14 wisdom, that adds it back up to 9, so your Will save should be right.

    Also, your bonus to saves doesn't influence your roll, at least it shouldn't. It affects the base save, which is the number added to the roll.
  • BelgarathMTHBelgarathMTH Member Posts: 5,653
    DerpCity said:

    A paladin of 8th level has +6 Fort and +2 Ref/Will. The fact you have 9 Will at the moment suggests you're leaving something out if you only have a +5 bonus to it.

    Also, your bonus to saves doesn't influence your roll, at least it shouldn't. It affects the base save.

    Okay, thanks. The other +2 is from my wisdom bonus. So the will save of 9 is correct. Although, that still doesn't explain why there were so many 6's. Yet another probability curve anomaly, I guess.
  • DerpCityDerpCity Member, Moderator Posts: 303
    edited February 2018
    Yeah, I figured that after I posted. Your post hadn't loaded by the time I edited mine. Whoops.

    This comic sums up my thoughts on the RNG system, though. I try not to let its rolls get to me in the long run.

  • BelgarathMTHBelgarathMTH Member Posts: 5,653
    @DerpCity, ROFL
  • DrHappyAngryDrHappyAngry Member Posts: 1,577
    I've strongly suspected bias myself, as well. I kind of wrote it off to the fact, that at low levels I can actually pay attention to more of the die rolls, whereas once you and most enemies get 2 attacks per round, the console gets spammed up with a lot more stuff and I pay less attention to it. Maybe it's completely in my mind, but it does seem like lower levels are more biases to low rolls and higher levels are more biased to higher rolls. Generally, it seems to go for both players and enemies, so it doesn't seem unfair. But, ya, it does seem pretty improbable to roll three 1s in a row, given how often it seems to happen at low levels.
  • FreshLemonBunFreshLemonBun Member Posts: 909
    In NWN2 I've had monsters never roll below 15 for about 50 rolls in a row, but it's possible they use their own RNG. Sometimes I wonder if other rolls are being used for other things and I'm just not seeing them.
  • MrDamageMrDamage Member Posts: 210
    I’m wondering if it’s something to do with the way random() works. I can’t put my finger on it but say CPU struggling to run everything like effects/ graphics and stuff. Random gets stuck and works buggy, same number rolls. In addition the more rolls it has to do gives it bias toward higher and less toward lower. As others said.
    Yes this idea is weird and strange like me, but I’m really starting to think random is affected by environment of what’s going on and what it has to do when it’s called.
    Of course, we can simply encapsulate the entire process of random quirks which makes the whole process random anyway thus achieving the desired result albeit in a different unintended manner.
  • SherincallSherincall Member Posts: 387
    Like I mentioned in the other thread, there is nothing special about the dice roller. The code literally looks like:

    uint16_t CNWRules::RollDice(uint8_t num_dice, uint8_t dice_sides) { if (dice_sides == 0) return 0; uint16_t result = 0; for (uint8_t i = 0; i < num_dice; i++) result += rand() % dice_sides + 1; return result; }

    Where rand() is the C standard library implementation provided by the compiler or the OS userspace. All the default implementations of rand are a PRNG that's Good Enough. It's not Cryptographically Secure (CSPRNG), but it shouldn't be anyway. If you feel like modding your game, you can change this to be a CSPRNG or even a "True Random", but you will not notice the result.

    We humans are very good at spotting patterns where there are none.
  • highv_priesthighv_priest Member Posts: 50

    Like I mentioned in the other thread, there is nothing special about the dice roller. The code literally looks like:

    uint16_t CNWRules::RollDice(uint8_t num_dice, uint8_t dice_sides) { if (dice_sides == 0) return 0; uint16_t result = 0; for (uint8_t i = 0; i < num_dice; i++) result += rand() % dice_sides + 1; return result; }

    Where rand() is the C standard library implementation provided by the compiler or the OS userspace. All the default implementations of rand are a PRNG that's Good Enough. It's not Cryptographically Secure (CSPRNG), but it shouldn't be anyway. If you feel like modding your game, you can change this to be a CSPRNG or even a "True Random", but you will not notice the result.

    We humans are very good at spotting patterns where there are none.

    I can verify as someone who has used external random generators in NWN extensively that you will most definitely notice a difference if you don't use nwn's standard random number generator.

    I welcome you to use nwnx_rand from creator Terra_777 as a great example to notice how nwn's random is verifyably streaky. I found this out myself using Terra's random plugin. See, in BadLands I developed a random item generator based around the concepts of Borderlands and Diablo before them. I wanted there to be the same properties dispersed amongst items with varying levels of those properties.

    So I use this function as the core generator of item properties and simply invoke it consistently throughout the process:
    //This function determines the value of the prop AND it also determines item rarity. //nWeight is the improvement to the random odds. A weight of 5 for damage means it's a minimum of 1d6. //nAltType is only used when using properties specifying 2 optional parameters(IE DAMAGE VERSUS ALIGNMENT). itemproperty DetermineProp(int nType, int nValue, int nMax=FALSE, int nSubtype=-1, int nWeight=0, int nAltType=-1); itemproperty DetermineProp(int nType, int nValue, int nMax=FALSE, int nSubtype=-1, int nWeight=0, int nAltType=-1) { int nRandom; itemproperty iProp; if(nMax > 1) { while(nMax >= 100) { nMax-=100; nWeight++; } if(nMax != 0 && nMax > d100()) { nWeight++; } } if(nType == ITEM_PROPERTY_DAMAGE_BONUS) { nRandom = ReturnRandomDamage(nValue, nWeight, nMax); iProp = ItemPropertyDamageBonus(nSubtype, nRandom); } else if(nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP) { nRandom = ReturnRandomDamage(nValue, nWeight, nMax); iProp = ItemPropertyDamageBonusVsAlign(nAltType, nSubtype, nRandom); } else if(nType == ITEM_PROPERTY_REGENERATION_VAMPIRIC) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight+nMax)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyVampiricRegeneration(nRandom); } else if(nType == ITEM_PROPERTY_ENHANCEMENT_BONUS) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyEnhancementBonus(nRandom); } else if(nType == ITEM_PROPERTY_ATTACK_BONUS) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyAttackBonus(nRandom); } else if(nType == ITEM_PROPERTY_MASSIVE_CRITICALS) { nRandom = ReturnRandomDamage(nValue, nWeight, nMax); iProp = ItemPropertyMassiveCritical(nRandom); } else if(nType == ITEM_PROPERTY_MIGHTY) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyMaxRangeStrengthMod(nRandom); } else if(nType == ITEM_PROPERTY_SKILL_BONUS) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertySkillBonus(nSubtype, nRandom); } else if(nType == ITEM_PROPERTY_AC_BONUS_VS_RACIAL_GROUP) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyACBonusVsRace(nSubtype, nRandom); } else if(nType == ITEM_PROPERTY_ATTACK_BONUS_VS_RACIAL_GROUP) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyAttackBonusVsRace(nSubtype, nRandom); } else if(nType == ITEM_PROPERTY_SAVING_THROW_BONUS_SPECIFIC) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyBonusSavingThrow(nSubtype, nRandom); } else if(nType == ITEM_PROPERTY_ABILITY_BONUS) { if(nMax == TRUE) { nRandom = nValue; } else { nRandom = Random(nValue+nWeight)+1; } if(nRandom > nValue) { nRandom = nValue; } iProp = ItemPropertyAbilityBonus(nSubtype, nRandom); } return iProp; }

    This function is used to completely randomize an items properties and multiple properties have different weights, see example:

    else if(sResref == "fromthedeep")//Leviathan - Katana
    {
    blueprint.prop1 = DetermineProp(ITEM_PROPERTY_DAMAGE_BONUS, 11, nMax, IP_CONST_DAMAGETYPE_BLUDGEONING, 6);
    blueprint.prop2 = DetermineProp(ITEM_PROPERTY_DAMAGE_BONUS, 19, nMax, IP_CONST_DAMAGETYPE_COLD, 9);
    blueprint.prop3 = DetermineProp(ITEM_PROPERTY_DAMAGE_BONUS, 11, nMax, IP_CONST_DAMAGETYPE_DIVINE, 5);
    if(d2() == 2)
    {
    blueprint.prop4 = DetermineProp(ITEM_PROPERTY_DAMAGE_BONUS, 17, nMax, IP_CONST_DAMAGETYPE_FIRE, 12);
    }
    else
    {
    blueprint.prop4 = DetermineProp(ITEM_PROPERTY_DAMAGE_BONUS, 17, nMax, IP_CONST_DAMAGETYPE_SONIC, 12);
    }
    blueprint.prop5 = DetermineProp(ITEM_PROPERTY_SKILL_BONUS, 20, nMax, SKILL_LISTEN);
    if(d2() == 2)
    {
    blueprint.prop6 = DetermineProp(ITEM_PROPERTY_ENHANCEMENT_BONUS, 6, nMax, -1);
    }
    else
    {
    blueprint.prop6 = DetermineProp(ITEM_PROPERTY_ATTACK_BONUS, 6, nMax, -1);
    }
    blueprint.NumProps = 6;
    blueprint.Max1 = IP_CONST_DAMAGEBONUS_1d10;
    blueprint.Max2 = IP_CONST_DAMAGEBONUS_2d10;
    blueprint.Max3 = IP_CONST_DAMAGEBONUS_1d10;
    blueprint.Max4 = IP_CONST_DAMAGEBONUS_2d8;
    blueprint.Max5 = 20;
    blueprint.Max6 = 6;
    }

    So when an item is generated there is about 8-10 random calls in a single execution just from one item alone.

    Now why this random system even works -at all- when the odds are stacked so high against receiving gold tier items is because NWN will every so often repeat numbers multiple times in a row. This allows there to be a 20 rolled 6 times giving them that gold tier Leviathan katana(a very popular katana on BadLands).

    When utilizing an external random from random.org or infact from almost any other place. For multiple months on end and even with extensive testing... It's simply not streaky enough to allow gold tier items to be generated at all.

    I don't think what's at play has anything to do with nwn's random being inherently broken. I think it has to do with how many things are rolling random at any one time and the actual milliseconds clock it's using for it's random is triggering the repeated numbers within the SAME timings as the actual combat round. Keep in mind combat isn't instantaneous. Attack rolls/damage rolls are firing every 2 seconds so long as combatants stand in place, which means the millisecond clock is almost always hitting the exact same points in time. I'm aware that the actual milliseconds it's using is from the point of reference of the 1900's and not just current milliseconds, but that's irrelevant when the actual random generator is simply going based on the position of certain numbers.

    TL;DR, NWN's random isn't predisposed to spam 1's and 20's. It's simply spamming too much and with how things are tiered in a time order(every 2 seconds roll an attack flurry) that lack of a real discernible random buffer is creating repeated streaks.

    As an aside note, I went back to using nwn's random, because streakiness was actually more fun overall.
  • SherincallSherincall Member Posts: 387
    edited February 2018
    *sigh*

    @highv_priest can you think of any test we can run to (dis)prove your hypothesis?

    I just tried the following:

    - Every .1 second, roll d20 50 times. Count the number of 2, 3 and 4 consecutive same rolls (i.e. rolling same number in a row).
    - Keep doing this for a total of 10k rolls.
    - Repeat the same experiment using a modified random implementation from a "true random" source.

    The vanilla NWN rand() results:
    [Sun Feb 18 22:56:04] Total sequences of two: 511
    [Sun Feb 18 22:56:04] Total sequences of three: 31
    [Sun Feb 18 22:56:04] Total sequences of four: 1
    [Sun Feb 18 22:56:04] Total rolls: 10050

    The true random results:
    [Sun Feb 18 22:57:37] Total sequences of two: 509
    [Sun Feb 18 22:57:37] Total sequences of three: 31
    [Sun Feb 18 22:57:37] Total sequences of four: 2
    [Sun Feb 18 22:57:37] Total rolls: 10050


    I did this in singleplayer, but I'm confident the same results will be done in MP as well. I can try if needed.

    You can find the scripts, the true random preload and the raw data results here:
    https://gist.github.com/mtijanic/3f34259171433d35273ab039dc89c082

    These are the first results, I repeated the experiment a few times and with more rolls, the results are comparable.
    If you run the raw data through a randomness test, like the Chi Squared test, it will show the urandom as a better algorithm, but that is at a much larger scale than what can be noticed in regular play.

    Edit: Oh, and I should also mention that srand() (setting the PRNG seed) is only called once, at the start of the game. The preload will print it out.
  • BelgarathMTHBelgarathMTH Member Posts: 5,653
    Just to add an update to the experiences that led me to post this, when I resumed my play the next day, my first roll against a minogon howl was a failed save of 3. The next roll was a 15. All subsequent rolls looked intuitively random with no streaks. It kind of left me chuckling a bit.

    Someone here once wrote a hilarious poetic essay about the fickleness of "The God of Random Numbers". I wish I could find that again. :)
  • sarevok57sarevok57 Member Posts: 5,975
    the thing to note about "RNG" is that digital computing has a very VERY tough time doing it, since digital computing is based on the fact of 1s and 0s, which means on or off, doesnt really have a whole lot of room for RNG ( in fact there is no room at all, its either on or off )

    so what programmers have done, is they need an "outside" influence of some sort to help make RNG possible, now i dont know how their techno wizardry works 100% but they use disturbances in real life to case RNG ( like noise,temperature and the like ) don't know how it's applied to NWN but the scripting code has be using something to make it random because as i said before, digital computing on it's own cannot do RNG
  • highv_priesthighv_priest Member Posts: 50
    The best test to (dis)prove my hypothesis is to run a pseudo combat scenario. NWN has never had issues running the same numbers over and over, even the worst of random systems in the world don't suffer tremendously on repeated number generations.

    However in real life it doesn't work that way. During combat if a hit lands, it then calculates multiple 1d4,d6,d8,d10,d12 off of the weapons standard damage values(on top of possible scripted effects and on hits). So rather than rolling d20 multiple times, it's rolling like so: (initiate combat)d20(x2)+Swing(d20)+crit roll(d20)+Base(1d8)+fire(1d10)+sonic(1d12)+Cold(1d4+1d4)+electrical(1d10+1d10)... That's with just 1 attack per round, once we add more than 1 the number of random rolls jumps a lot more.

    Combat functions by executing attack rolls and then crit rolls first within the flurry and then assigns damage values afterwards. By then it's cycled through anywhere from 5 to 25 random values depending on item properties and it does all of this all at once, exactly 2 seconds per flurry. This normally is a non-issue, except from what I've observed NWN is actually exceeding it's memory buffer at times which can lead to streaks of the same thing happening when things are executing at the same time.

    Additionally a multiplayer environment puts a hell of a lot more stress on random than single player. One player can only do so much, but many players can have a chaotic inferno of random executing all over.

    Basically the final issue here is, to a player it doesn't matter that the WORLD is pretty random, all the background numbers they don't see are essentially non-existent to them. To them they only see their numbers and nwn's basic random generator most definitely starts doing some wild streaks when it's taxed.

    Ideally numbers should be separate per player, so to avoid the world rolling 5 20's in a row and by the time it reaches them it finally gets that 1, except it does that 10 times in a row, on the first roll, causing their death, instantly, 10 times in a row(this happened to me).
  • sarevok57sarevok57 Member Posts: 5,975
    i've also found out that at lower levels, the RNG is more of a killer than say at level 20+, in the first chapter of the OC it can be very noticeable if you are rolling below 5, 7 times in a row, because at those low levels you need those high numbers ( also in my opinion, there are way too many enemies with too high of AC in NWN which doesn't help )

    but once you hit higher levels ( around 15+ i would say ) even rolling 2s is no different than say rolling 19s, so the RNG is less noticed i found
Sign In or Register to comment.