
What happens if you remove randomness from Doom? - pdw
http://jmtd.net/log/deterministic_doom/
======
pslam
Even the "randomness" is deterministic. The fixed, repeating table is an
optimization, but it's also completely necessary for multiplayer to work.

Doom does things very differently to how more recent multiplayer FPS
multiplayer works. Every player's host machine has identical state. That's not
as superficial as things like score, who is alive/dead, etc - it's exact
positions or all entities, their metadata... all the way down to _the next
number to be picked by the random number generator_.

This is updated every tick (30Hz? can't remember) and every machine needs to
synchronize with every other within that tick, or the game _slows down_. If
you've ever played Doom over a modem, you know what it's like when someone has
bad settings which result in high latency - in this case, poor frame rate. The
data sent between machines consists of the _inputs_ \- e.g rotation, movement,
fire, talk - and not higher level constructions such as motion vectors.

So it's entirely necessary that the "random" number generator is not so random
at all. It's fairly hilarious to see the results of messing with it, though :)

It does make me think that cheats (not that I ever cheated) were missing a
trick. If they knew what the next number to be pulled from the random number
generator was, they could, for example, delay firing a weapon by a frame or
two, to get more damage. Probably other more interesting things I can't think
of immediately. If there was ever a bot vs bot Doom competition, this is
something you'd definitely want to consider.

~~~
dchichkov
Yes. By the way this feature of Doom was allowing nice and symmetric
multiplayer over slow modem or network lines. Only small client-side packets
were send through the network (think keystrokes/mouse movement updates).

As a different example Quake was designed using a client/server model with
hugely asymmetric packet sizes (~40 bytes for client->server packet and ~800
bytes for the opposite direction).

As a teenager at around 1996-1997 I once tried to re-factor Quake to use the
same model as Doom - run server/client combo on both machines with servers
synchronized using exact same random generator seeds / etc. Spend about two
months on that, but it had proved to be very difficult - the sync between the
servers was inevitably lost due to some indeterminism in the code that I
couldn't uncover. Maximum that I was able to achieve was around two seconds
running in sync. And I still don't know where the sync was getting lost ;)

Learned a lot from that experience, big thank you to John Carmack (and the Id
Software team) for writing that code as good and clean as he/(they) did...

~~~
TheLoneWolfling
Probably floating-point oddities. The bane of deterministic programming
everywhere.

~~~
tobinfricke
Floating point is deterministic.

~~~
TheLoneWolfling
If only. Even something as simple as this, where x and y and a are floating-
point values:

    
    
        if( a == x/y )
          assert( a == x/y );
    

Is legally allowed to trigger (!). Worse, it's also legally allowed to not
trigger.

That's right. The above can be legally compiled to always trigger, or never
trigger, or even to sometimes trigger (although this is rare in practice).

Why? C / C++ allow floats to be stored with excess precision. And they lose
that excess precision when spilled to RAM. And GCC isn't deterministic. So x/y
may be pulled out as a common subexpression and spilled to a register after
the first usage, and it triggers. And then the next time it isn't, and it
doesn't. (Effectively, the difference between this:

    
    
        temp = x/y
        if( a == temp) {
          spill(temp);
          assert( a == temp );
        }
    

and this:

    
    
        temp = x/y
        if( a == temp) {
          assert( a == temp );
        }
    

Yes, it'll (generally - though the standard doesn't dictate that it will be!)
be deterministic in terms of always with a given example in an single binary
triggering or not triggering, but that is irrelevant, as binaries aren't
cross-platform and you aren't always loading exactly the same binaries of the
dynamic libraries you've linked to.

You can (most of the time) work around this by manually setting FPU precision
(note that you need to change the precision of both the mantissa and exponent,
as otherwise you can still end up with issues). (Except that you, in C++ at
least, have to work deep magic - you have to set it _before_ main even runs,
because you can end up with problems with static initialization otherwise.)
And in the process losing compatibility. And finding that random code you call
occasionally resets things. Or random code you _don 't_ call (a classic
example: the printer driver!). And using exactly one of float or double
throughout. And hoping that none of the libraries you use ever resets FPU
precision for anything, or uses the other of float or double itself. (And hope
that your OS properly saves / restores the FPU everywhere, though this has
largely become a non-issue).

And, worst of all, you've got to hope that the compiler performs constant
folding with the same level of care as you've just taken.

Look at these:

[https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323)

[https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845)

[http://yosefk.com/blog/consistency-how-to-defeat-the-
purpose...](http://yosefk.com/blog/consistency-how-to-defeat-the-purpose-of-
ieee-floating-point.html)

[http://gafferongames.com/networking-for-game-
programmers/flo...](http://gafferongames.com/networking-for-game-
programmers/floating-point-determinism/)

[http://www.yosoygames.com.ar/wp/2013/07/on-floating-point-
de...](http://www.yosoygames.com.ar/wp/2013/07/on-floating-point-determinism/)

~~~
pjc50
Yes, we got bitten by this when using an algorithm with a k-means clustering
step in the middle; minor perturbation in the least significant bits could
change which cluster an item got assigned to, which would then result in
massive changes to the eventual output.

(Relatedly, several ASIC and FPGA tools suffer from nonlinearity so badly that
they will let you specify the random seed at the start and/or do multiple runs
with different seeds to pick the best result)

~~~
TheLoneWolfling
That bad? Yow.

------
RussellDussel
This instantly reminded me of the (true) story where people were able to cheat
an online poker game because the site released its source to "prove" its
fairness/randomness, showing that the deck was shuffled from a fixed state
using a standard RNG with the time of day (in ms) as the seed.

Of course this meant there were only 86,400,000 possible decks, people could
generate all the possible decks ahead of time and as soon as cards would be
revealed in the game they could easily match the hand being played to the
limited set of possibilities. Essentially they had foresight of which cards
were coming out. Furthermore, once a few decks were matched up you could
vastly reduce the set of possibilities even further by working out the
approximate time of day on the server.

~~~
swang
This is PlanetPoker[0]

The gist of it is their algorithm never chose the last card in the array to
swap with, and the last card was never guaranteed to be the last card. They
also committed the common mistake of implementing the "naive" shuffle, which
is not equally distributed.

[0]
[http://www.cigital.com/papers/download/developer_gambling.ph...](http://www.cigital.com/papers/download/developer_gambling.php)

------
PhasmaFelis
> _Monsters do not make different death noises at different times; only one is
> played for each category of monster._

This, BTW, is a really interesting trick that Doom pulled. There's actually
only a single death sound for each type of monster.* But for some monsters,
the game plays it back at a random speed--also changing the frequency, like an
audiotape on fast-forward--so it becomes a brief shriek, a drawn-out groan, or
something in between. This gives an impressive variety of sounds without
adding extra memory-heavy samples.

Hardly anyone was aware of this as far as I can tell, which is a testament to
how well it worked. I only noticed it when playing a goofy little mod that
turned zombie sergeants into Energizer bunnies and played the "still going"
clip when you killed them. The effect is obvious when there's actual speech
involved.

*Not counting the "splatter" sound when you gib any of the gibbable critters.

~~~
PhasmaFelis
On further consideration, I think it only does this with imps and the various
zombies, which are the only monsters that you're likely to mow down at a rate
of several per second--it might have been specifically intended to stop zombie
fragfests from sounding like an echo chamber.

~~~
jmtd
Hi, article author here: it was the pitch shifting behaviour in particular
that I wanted to explore when I modified the random table. Early versions of
doom (<1.4) did it, but they accidentally removed that feature with a rework
of sound code in 1.4 and onwards (including all versions of doom 2). It was
originally applied to all but two sound effects, with special casing for
chainsaw sounds (less variance afaik).

There were, however, three distinct zombie death noises, independent of pitch
shifting.

~~~
PhasmaFelis
Interesting! Are the three zombie death sounds perhaps speed-shifted versions
of the same original? My recollection (it's been a while) is that zombies made
dramatically different sounds on death but--once I started looking for it--
they all seemed to be speed-shifted versions of the same sound, while pitch-
shifting in other sounds was subtle enough that I never noticed it. If pitch-
shifting normally applied equally to most sounds, that implies that there was
something else going on with the zombie noises I recall.

Or maybe I'm completely confused because I haven't played it since the '90s.
Might be time to give it another try...

~~~
jmtd
The three zombie death sounds are on this page
[http://www.wolfensteingoodies.com/archives/olddoom/music.htm](http://www.wolfensteingoodies.com/archives/olddoom/music.htm)

------
pcvarmint
When I added friendly monsters and Wolf3D dogs to Doom, I tweaked the
randomness to make the dog behavior more life-like.

Friendly monsters go after enemy targets, but absent any targets, they follow
you in semi-random motions, but they stay a comfortable distance away from
you, except when getting onto a platform or other crowded space.

Disclaimer: I'm the author of MBF.

~~~
pcvarmint
Also, demo reproduction was a huge issue. We had to do all kinds of versioning
to make sure that a demo which was recorded with one version of
Doom/Boom/MBF/etc. was faithfully replayed with the current version. One tiny
mistake, and the whole demo could go into a tailspin. This sometimes meant we
had to preserve bugs like Lost Soul stuck-in-wall bugs.

~~~
pcvarmint
Oh, and randomness could have fixed the monster-stuck-in-doorjam bug, which is
easily reproduced in E2M4 with the Baron by the blue key. The code assumed
that if a monster was "in" a door sector and was blocked, then the door simply
needed to be opened, and then the monster could continue in its current
direction. Better would be to rotate the monster's direction randomly to help
them to get out of the sticky situation. That was our fix in Boom.

------
kkl
Beautiful. This article made me smile. I also like that the author gave a
simple "how-to" at the end in case I wanted to try it myself (i do!).

------
nullc
It might be better to call this Low Probability Doom-- indeed, it's
deterministic, but so would changing the random number generator to a counter.

The impacts being discussed are mostly the effect of playing the unluckiest
game of doom possible. :)

------
dfkf
Videos of such experiments would be greatly appreciated.

~~~
jmtd
Took me a while but here you go
[https://www.youtube.com/watch?v=SvfJoy1rjmo](https://www.youtube.com/watch?v=SvfJoy1rjmo)

------
twblalock
I'm always impressed by the simple ingenuity of the Doom source code and the
immersive environment it creates.

------
yuriks
PRNG determinism is widely utilized in TAS (Tool-Assisted Speedrun, basically
a game played using tools such as frame advance and save states to simulate a
"omnipotent" player.) to manipulate games to achieve favourable outcomes.
Usually the game code is reverse engineered and a Lua script is created that
searches forward in the RNG outcomes to find the ones you want, it is then up
to the runner to execute actions in the game that give you that state.

One example of this technique which I particularly like is the "100% Souls"
run of Aria of Sorrow, where the normally very grindy process of getting enemy
soul drops is trivialized by making sure that every enemy will drop a soul the
first time it's killed:
[https://www.youtube.com/watch?v=DfkJpzBoJ-M](https://www.youtube.com/watch?v=DfkJpzBoJ-M)

------
zak_mc_kracken
I'm intrigued by this:

> I hex-edited the binary and replaced the random number lookup table with
> static values

If you have a look up table that you randomly look up, doesn't that mean you
already have a random source? In this case, why need a random look up table at
all?

~~~
JasuM
It reads the next number in the list, it's not a random lookup. See
[https://github.com/id-
Software/DOOM/blob/master/linuxdoom-1....](https://github.com/id-
Software/DOOM/blob/master/linuxdoom-1.10/m_random.c)

~~~
justsid
Doesn’t that already make it deterministic though? It’s always the same
sequence of “random” numbers, even before replacing the lookup table it
should’ve been deterministic.

------
spiritplumber
This is neat... since Doom is open source, why not make the binaries available
for a quick romp?

Also, try $80 as well as $00 and $FF?

(Yeah, I'm lazy)

~~~
mahouse
It's very easy to compile any port. I personally recommend you to try
Chocolate Doom.

~~~
GhotiFish
yaay it compiled!

Just grabbing an IWAD, I'm looking forward to trying this! Thanks for the
suggestion

------
PhasmaFelis
> _The chance of monster in-fighting is never, or a certainty. The player is
> either mute, or cries in pain whenever he 's hurt._

This is interesting. I thought that both of those things were deterministic to
begin with--monsters that get hit by other monsters will go after the offender
until it's dead or something else hits them, and the player makes the "pained
gasp" sound effect every time he's hit.

~~~
shultays
OP is wrong about Doom's determinism I guess, it is already deterministic,
random values are predefined. He just set them to constant, so his random is
literaly "int random() {return 4;}", still deterministic but not 'random'

~~~
PhasmaFelis
Regular Doom may not be random in the mathematical sense, but it is as far as
a player is concerned. In combat, pulls from the "random number" pool are
happening fast enough that I don't believe it could be exploited in any
meaningful way.

What I meant was, I hadn't thought that infighting target choice and player
pain-noise referred to the "random" pool at all. I thought they were 100%
predictable in the original game. I wonder where the randomness actually comes
in.

~~~
shultays
Timings of user input probably.

------
bentcorner
It's interesting that such a simple implementation of randomness works so
well, in particular enemy in-fighting and their decision to pursue or not. I'm
sure when you're playing, you end up anthropomorphizing many of these
decisions.

~~~
anyfoo
Note that these decisions might still be "intelligent" (to a certain degree,
i.e. not actually random but based on reasoning) without that hack.

But there might be a small chance involved where the entity would do another
thing than the algorithmically determined action, and depending on how the
values are interpreted this then triggers, the overriding of the game AI now
just happens every time.

------
azinman2
Wish there had been videos posted!

~~~
jmtd
Yeah a video would be a good way to demonstrate this but it would take a lot
of editing to do properly and I ran out of time last night and really wanted
to get this out. I might post a follow up video later.

------
madsravn
> The chance of monster in-fighting is never, or a certainty.

Still seem a little random, though

