I am currently working on a very fast random generator for a game, that will require thousands of random values per second for background calculations. In addition to the requirement of being fast, the numbers generated should feel random, but don't necessarily have to be random in a mathematical sense.
To explain that: players consider randomness different than mathematicians do. If a player has a 50% chance to succeed and he lost on the last round, he absolutely expects to win the next round, because otherwise the 50% chance is obviously not fulfilled. A mathematician knows that there is a good chance to also loose the second round. In other words: players expect the random numbers to be evenly distributed and consider unlikely situations an error.
To archive both my idea is to take a pool of numbers, let's say 0-1023, put then in an array and shuffle that array with a default random generator. The result will be the numbers 0-1023 in an unspecific order. Now if a number needs to be generated, simply the next number from the table is taken.
On the first round of numbers all the requirements are fulfilled: the numbers are seemingly random, evenly distributed, and just picking the next number from the table is a very fast operation for any CPU. But what happens once I reach the end of the table? I could of course shuffle the table again, but considering the situation: is this even necessary?
In a running game many agents pick numbers for their calculations, and the order in which agents pick numbers is in general undefined. Imagine a group of 20 players clicking buttons: in which order they click those buttons is more or less random. So if every click is picking the next number from the (exact same) table (and we just jump back to the beginning once we are at the end), the order how these numbers are distributed to the players is undefined, because it depends on the order of clicks made by all those players. Now add a huge amount of artificial agents on top, and no one can predict anymore which number is served to whom.
Especially because in any computer system the order of execution of threads is undefined. So if agent A, B, and C run in different threads and pick numbers, the order in which they gain access to the table of numbers is undefined, and therefore the number they will receive is also undefined. They are likely to receive several consecutive numbers in a row if they ask for many in a short time-slice, but the length of that time-slice and when it ends (and when it restarts) is not predictable for the agent.
This brings me to the question: is it even necessary to re-shuffle the table if a sufficient amount of agents pick numbers in an undefined order, or is this already enough entropy to consider the numbers received from the perspective on a singular agent (i.E. the player) to be random?
And what would be a 'sufficient amount of agents' in this case?
I think this is a difficult problem, not for mathematical reasons, but for pinning down the appropriate "feel" for your game. But here's my take.
Suppose you're generating bits for simplicity. (These ideas can be generalized, it's just easier to phrase in this setting.) You want to avoid seeing particularly long strings of the same bit, but you still want the long time average of the number of 0s and 1s to be the same. One way is to generate a few bits in the usual way as a "burn-in", and then use the fraction of 1s that you have already generated to retune the probability of getting a 1. Thus you would have some function $f : [0,1] \to [0,1]$ which is strictly decreasing with $f(0)=1,f(1)=0,f(1/2)=1/2$, and you'd take the probability of getting a 1 in the next step to be $f(q)$ where $q$ is the fraction of the numbers generated so far that were $1$s. As long as $f$ is symmetric (in the sense that $g(x)=f(x+1/2)-1/2$ is an odd function), the long time average of this will be the uniform distribution on $\{ 0,1 \}$.
The problem with this is that eventually $q$ will settle down near 1/2, and then the long strings of 1s that you see in true random sequences will start appearing. So you can instead take only finite memory: take $q$ to be the fraction of 1s in the last $N$ samples, where $N$ is of moderate size, and then take at least $N$ "burn-in" samples at the start. $N$ can be interpreted in this framework as the maximum possible length of a string of all $0$s or all $1$s.
When you start getting into multithreading all of this stuff becomes vastly more complicated; it is well-known that parallel Monte Carlo simulation is quite difficult because of cross-correlation between threads. Also, I would not expect "agent entropy" to resemble "real" entropy.