Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix rng normalization #352

Merged
merged 1 commit into from
Jul 29, 2024
Merged

Conversation

phylter-qw
Copy link
Contributor

Issue

The function g_random, which

  • normalizes the output of the underlying PRNG to the range [0,1)
  • is the basis of all random calls in the game

performs sub-optimally because it utilizes the poorest performing bits (in terms of randomness) from the PRNG; namely, the lower bits.

Solution

Use the upper bits instead.

They perform much better. The designers of the PRNG say so, and the following benchmarks agree.

Analysis

Each output from the PRNG is 32 bits wide.

The ENT — Fourmilab Random Sequence Tester was used to quickly compare the random qualities of 3 MB of data generated using

  1. All 32 bits; the ideal, but not possible in-game without loss of precision or switching to double everywhere.

  2. The lower 15 bits; the current implementation.

  3. The upper 24 bits; the greatest number of bits that can be utilized without loss of precision or switching to double everywhere.

The results are shown below:

Full 32 bits

Entropy = 7.999941 bits per byte.

Optimum compression would reduce the size of this 3000000 byte file by 0 percent.

Chi square distribution for 3000000 samples is 243.54, and randomly would exceed this value 68.65 percent of the times.

Arithmetic mean value of data bytes is 127.4336 (127.5 = random).

Monte Carlo value for Pi is 3.139488000 (error 0.07 percent).

Serial correlation coefficient is -0.000545 (totally uncorrelated = 0.0).

Lower 15 bits

Entropy = 7.811046 bits per byte.

Optimum compression would reduce the size of this 3000000 byte file by 2 percent.

Chi square distribution for 3000000 samples is 750912.74, and randomly would exceed this value less than 0.01 percent of the times. Almost certainly not random.

Arithmetic mean value of data bytes is 95.5068 (127.5 = random).

Monte Carlo value for Pi is 3.830280000 (error 21.92 percent).

Serial correlation coefficient is -0.230827 (totally uncorrelated = 0.0).

Upper 24 bits

Entropy = 7.999941 bits per byte.

Optimum compression would reduce the size of this 3000000 byte file by 0 percent.

Chi square distribution for 3000000 samples is 243.18, and randomly would exceed this value 69.22 percent of the times.

Arithmetic mean value of data bytes is 127.4507 (127.5 = random).

Monte Carlo value for Pi is 3.145248000 (error 0.12 percent).

Serial correlation coefficient is 0.000591 (totally uncorrelated = 0.0).

Remarks

There are many sophisticated tests that the ENT tool does NOT perform.

However, it is good enough to identify strongly suspicious NON-random sequences, and that has been sufficient in the case above.

The proposed fix is very simple, just one line of code, and allows random actions to perform closer to the ideal, even if players aren't capable of noticing much of the time.

This change has been tested on NA servers.

@dsvensson
Copy link
Collaborator

#231 🙃

@phylter-qw
Copy link
Contributor Author

hello?

@phylter-qw phylter-qw force-pushed the rng-normalization-fix branch 2 times, most recently from 75125f1 to b8f9936 Compare July 28, 2024 06:30
old method used lower 15 bits; performs poorly on tests for randomness

new method uses upper 24 bits; performs well on tests for randomness

cannot use more than 24 bits without loss of precision during float conversion
@dsvensson
Copy link
Collaborator

Could use RAND_MAX instead of 0xffffffff, which is not defined for qvm case, and define RAND_MAX to the old value in bglib.h

@dsvensson
Copy link
Collaborator

dsvensson commented Jul 28, 2024

Ah, maybe bad take. It's always 64 bit when native, which system RAND_MAX is perhaps not? If so, perhaps a G_RAND_MAX that differs between 32/64 bit, see recent Rand changes.

0391736

@phylter-qw
Copy link
Contributor Author

The 0xffffff in this code is not strictly equal to RAND_MAX.

RAND_MAX would be 0xffffffff (2 more fs) for this generator, which produces 32-bit integers regardless of platform.

Using RAND_MAX instead of 0xffffff here would

  • Obscure the intent behind the code, which is to extract the upper 24 bits (not 32).
  • Break the code if rng_next_global ever returned more than 32 bits.

Perhaps you dislike having an integer literal, and would rather see a macro or variable name. I understand that concern. That's generally good when the macro will be used in multiple places or somehow clarifies the meaning of the constant. I'm not sure that's the case here.

@dsvensson
Copy link
Collaborator

dsvensson commented Jul 29, 2024

I just confused things. It's the seed that's 64 bits on native, the rest is the same on both qvm and native. All good.

Should avoid commenting when only phone available :)

@dsvensson dsvensson merged commit 8a9caca into QW-Group:master Jul 29, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants