Generating Random Numbers for Instant Games
Overview
The process for generating random numbers in instant games involves two main steps:
Generate a 512-bit (64-byte) random array using:
RandomBytes = HMACSHA512(Active_Server_Seed, Active_Client_Seed:Nonce:Cursor)
Extract the needed value—either an integer or a floating point number—from the generated bytes.
📌 Note: This process differs from how random numbers are generated for scheduled games.
Generating Random Bytes
The core function is generateRandomBytes()
. Here is the pseudocode:
byte[] byteGenerator(serverSeed, clientSeed, nonce, cursor) {
// 1. Create an HMAC512 hasher with server seed as the key
let hasher = HMAC512(serverSeed)
// 2. Format the message as: clientSeed:nonce:cursor
let message = clientSeed:nonce:cursor
// 3. Hash the message
let randomBytes = hasher.Hash(message)
// 4. Increment the cursor
cursor = cursor + 1
// 5. Return the random bytes
return randomBytes
}
These randomBytes
are then passed into either:
generateDouble()
→ for floating point numbersNextPositiveInt32()
→ for integers
Generating Floating Point Numbers
Floating Point Structure (IEEE 754 - 64-bit double):
1 bit: Sign (positive/negative)
11 bits: Exponent
52 bits: Mantissa/Fraction
The mantissa controls the precision for values between 0 and 1.
Algorithm
Generate 7 random bytes
Combine into a 56-bit number
Mask to keep the lowest 52 bits
Divide by
2^52
→ Gives a uniform value between 0 (inclusive) and 1 (exclusive)
Pseudocode:
double generateDouble() {
bytes = generateRandomBytes(7)
bits = 0
for each byte in bytes:
bits = (bits << 8) OR byte
masked = bits AND (2^52 - 1)
randomDouble = masked / 2^52
return randomDouble
}
✅ This ensures maximum precision and equal probability for all outcomes in the range [0, 1).
Generating Integers
Unlike some operators who derive integers from floats (which can cause bias), this method directly generates uniform integers, ensuring fairness and accuracy.
Why This Matters:
Mapping floats to integers (e.g.,
floor(float * N)
) introduces rounding issuesThis method uses rejection sampling to avoid bias
How It Works:
Draw a 32-bit random unsigned integer (
value
)Define a rejection limit:
limit = MAX_UINT32 - (MAX_UINT32 % maxExclusive)
If
value < limit
, use:result = value % maxExclusive
If not, discard and draw new bytes
🛑 Rejection Sampling ensures perfectly uniform results in [0, maxExclusive)
Pseudocode:
int NextPositiveInt32(maxExclusive) {
if maxExclusive not provided:
maxExclusive = MAX_UINT32
limit = MAX_UINT32 - (MAX_UINT32 mod maxExclusive)
byteCount = HashingBitSize / 8
loop forever:
bytes = GenerateNextBytes(byteCount)
for i from 0 to bytes.length - 4 step 4:
value = convert 4 bytes at i to unsigned int
if value < limit:
return value % maxExclusive
}