
FizzleFade - pietrofmaggi
http://fabiensanglard.net/fizzlefade/index.php
======
antirez
An alternative approach that works for every resolution:
[http://antirez.com/news/113](http://antirez.com/news/113)

~~~
dsacco
This is a good post, kudos on writing it up so quickly! In fact, the first
thing I thought of when I read this post on HN was, "Well why not use a
pseudorandom permutation instead of a pseudorandom function, this way we
efficiently fill all pixels without first checking if they're red?"

Being that a feistel network is a pseudorandom permutation, this fulfills the
need (and in my opinion, even more elegantly than a LFSR). For even better
performance you could use AES as the PRF, especially if users have AES-NI
instructions available for acceleration. Then use a basic Feistel network for
the PRP.

~~~
antirez
Thanks! Exactly my thought indeed. Probably now that many people are aware of
crypto primitives, permutation boxes and other related tools it is an
immediate thought to have, but potentially back then when the game was written
it was not so obvious.

~~~
dsacco
Absolutely. One of my recent research interests in cryptography has been
identifying ways to make pseudorandom permutations faster and using them
outside of typical cryptographic contexts. I'm happy to see that you use them
in Rax. They map very well to a lot of data structure problems in networking,
not just encyption (i.e. assigning IDs to users).

------
buzzybee
FizzleFade is also found in Microprose games from the era (e.g. Railroad
Tycoon, Civilization), sometimes in full-screen transitions and other times to
fade in single sprites. But more relevantly to "id software history", you can
find it in Origin's Space Rogue, which John Romero contributed to. A likely
possibility is that he picked up the trick on this or a previous project while
at Origin.

It's also possible to use a slower "arbitrary PRNG and bump" scheme that tests
the VRAM for the desired values(e.g. if it were a sprite, by running the blit
at that pixel address and testing) and walks forwards or backwards until an
unset value is found. If the walk can be done fast enough, it'll execute at
the same framerate as an LFSR cycle-length fade. It can be further
supplemented with an estimation heuristic or a low-resolution map to generate
unique patterns. It's just less speedy and mathematically interesting to do
that.

~~~
ticklemyelmo
I remember seeing it in a variety of mid-80's Commodore 64 games, where it ran
at full speed and looked fantastic. I always wondered how it worked.

------
hyperion2010
If want to know more about cool things you can do with shift registers and
you've never heard of Solomon W. Golomb, check out Shift Register Sequences
(intro at [0]). Most of our fundamental telecommunications is possible because
he solved the mathematics involved.

0\.
[http://jm.planetarydefenses.net/sense/refs/ref14_golomb.pdf](http://jm.planetarydefenses.net/sense/refs/ref14_golomb.pdf)

------
simias
The original GameBoy had a hardware LFSR that could be used to generate white-
noise-like sounds. It was often used for "whooshing" effects and also cymbal
sounds, such as in the famous Super Mario Land theme:
[https://www.youtube.com/watch?v=Gb33Qnbw520](https://www.youtube.com/watch?v=Gb33Qnbw520)

~~~
WillKirkby
For more technical details on the exact LFSR used, there's this:
[http://belogic.com/gba/channel4.shtml](http://belogic.com/gba/channel4.shtml)

------
dmichulke
Related:
[https://en.wikipedia.org/wiki/Linear_congruential_generator](https://en.wikipedia.org/wiki/Linear_congruential_generator)

A pseudo-RNG that cycles through a all elements of a modulo-ring.

Example for a 2^32 bit cycle:

X(n+1) = (a * X(n) + c) mod m

a = 134775813

c = 1

m = 2^32

~~~
leni536
My approach would be something like this, but with a very "poor" generator
with the parameters a=81007, c=0 and m=2^17. This approximates a low
discrepancy sequence (additive recurrence with alpha=1/golden ratio). Then I
would calculate x and y values using the hilbert curve and the calculated
pseudorandom number as the index (more precisely two Hilbert curves next to
each other, so it covers a 512x256 rectangle). On today's CPUs it can be
calculated quite fast (shameless selfplug:
[https://github.com/leni536/fast_hilbert_curve](https://github.com/leni536/fast_hilbert_curve)).
I suspect that the resulting pattern on the screen would be less random
looking, but more uniform without any obvious pattern.

~~~
schindlabua
Cool! I was just looking up hilbert implementations yesterday, so that's super
useful. Thanks!

(quick note: in your source the function is called hilebert instead of
hilbert)

~~~
leni536
Fixed, I also added a Wolfram Mathematica module that can be used with
LibraryFunctionLoad.

------
wott
> asm mov ax ,[ WORD PTR rndval ]

> asm mov dx ,[ WORD PTR rndval +2]

> asm mov bx , ax

> asm dec bl

> asm mov [ BYTE PTR y ], bl // low 8 bits - 1 = y

> _asm mov bx , ax_

> asm mov cx , dx

> asm mov [ BYTE PTR x ], ah // next 9 bits = x

> asm mov [ BYTE PTR x +1] , dl

I don't understand the need for the second _asm mov bx , ax_ : BX is not used
afterwards. Same for CX, it is never used.

> uint32_t rndval = 1;

> uint16_t x,y;

> do

> {

> y = rndval & 0x00F; // Y = low 8 bits

> x = rndval & 0x1F0; // X = High 9 bits

Er... no, if you do that, you only get the lowest _4_ bits in _y_ , and then
you only get _5_ bits in _x_ (and not the right ones, of course).

It should be:

    
    
           y = rndval & 0x000000FF;  // Y = low 8 bits
    

And then you have a 'problem' for x, because you must shift it right,
otherwise it doesn't fit in a 16-bit variable:

    
    
           x = rndval & 0x0001FF00;  // X = bits 8 to... 16 > 15, irk
    

So you should just do :

    
    
           x = rndval >> 8;  // X = bits 8 to 17, in their right place

~~~
nik_0_0
Looks like the author fixed the C translation following your comments:

    
    
      y =  rndval & 0x000FF;  /* Y = low 8 bits */
      x = (rndval & 0x1FF00) >> 8;  /* X = High 9 bits */
    

However, given that the assembly is verbatim from id-software's git, I guess
those extra instructions are part of history now.

------
DecoPerson
I used LFSR to render the red grading in this little experiment:
[https://youtu.be/fUpUrpHLUxo](https://youtu.be/fUpUrpHLUxo) .

I have a feeling the Octane rendering engine uses it too.

It's a good fit for most cases where you want random sampling of a set without
replacement.

------
leni536
> Since 320x200=64000, it could have been implemented with a 16 bits Maximum-
> length register.

But then you have to calculate modulus for 200 or 320.

~~~
tripzilch
except the pixels are in a linear framebuffer, the fizzlePixel(x, y) function
has a multiplication by 320 in it to calculate the address in the buffer.

so you could skip both the modulo and the mul if you go straight for obtaining
a random shuffled framebuffer index.

unless maybe fizzlePixel does additional bookkeeping for some purpose.

~~~
leni536
> has a multiplication by 320

Huh the original implementation seems to have a lookup table instead of that,
interesting.

[https://github.com/id-
Software/wolf3d/blob/05167784ef009d0d0...](https://github.com/id-
Software/wolf3d/blob/05167784ef009d0d0daefe8d012b027f39dc8541/WOLFSRC/ID_VH.C#L521)

------
steventhedev
It never ceases to amaze me how many of the older games were implemented as
circuits first, and then translated to code. Makes you really appreciate how
far we've come, and what sort of background that generation of developers had.

------
dre85
I didn't quite understand why this is guaranteed to reach every pixel
coordinate? Is there something inherent about LFSR that generates complete
sequences within the cycle? So elements are never repeated or omitted?

~~~
fpgaminer
Yeah this wasn't covered well in the article.

Go back to the article and look at the section just below the first mention of
Maximum-Length LFSR.

Take a look at that list of numbers and notice something; every number from
1-15 is output once and only once.

That's a property of Maximum-Length LFSRs; they output each number in their
range once and only once.

So, for example, a 17-bit Maximum-Length LFSR will output every number from
1-131071, just in random order.

The Wolfenstein code separates the output of the LFSR into X and Y
coordinates. Since the LFSR will visit every possible number exactly once, it
will visit every possible combination of X and Y coordinates exactly once.

You can look at the 4-bit Maximum-Length LFSR again. Split each number it
outputs into 2-bit X and Y:

    
    
       0001 | 0,1
       1000 | 2,0
       0100 | 1,0
       0010 | 0,2
       1001 | 2,1
       1100 | 2,0
       0110 | 1,2
       1011 | 2,3
       0101 | 1,1
       1010 | 2,2
       1101 | 3,1
       1110 | 3,2
       1111 | 3,3
       0111 | 1,3
       0011 | 0,3
    

You'll see that it hits every point on a 4x4 screen exactly once, in random
order.

The caveat is that it doesn't seem to hit 0,0. This is because an LFSR can't
go to 0, otherwise it gets stuck there. However, I believe the ASM code was
incorrectly translated by the article author. For example the author seemed to
forget to translate the "dec bl" instruction into the C equivalent, which
would subtract 1 from the y coordinate and allow visiting 0,0.

~~~
dre85
Thanks for that explanation! I just wanted to confirm that it's an inherent
property of the LFSR.

------
pasta
I have the feeling that knowledge about bits is lacking by a lot of younger
coders. And I also think this is what causes bloatware.

CPUs are powerful enough to use a naive fade transition. But coders who are
aware of the internal workings can make it even faster on todays hardware.

Great article and imho still relevant on todays much more powerful computers.

~~~
Illniyar
Why? I mean if you look at the majority of work that programmers do today -
frontend/backend web development and apps, there is no need to have knowledge
about bits.

In fact, if I see someone using binary operators in languages such as
Java,JS,Ruby etc... I'll immediately consider it bad code, regardless of
context - it's just not the right tool for the level of abstraction in these
languages.

The fact is that in these products (frontend/backend web development and apps)
the performance profile is dominated by bad algorithms, wrong data structures,
slow libraries, missing db indexes etc... which knowledge about bits help
absolutely zero with.

Large binaries are also not dominated by code you write but rather by using
too broad libraries or simply from huge assets.

The only thing I'd consider knowledge of bits to be of any help to a run of
the mill developer these days is the knowledge that floating point numbers
don't multiply/divide well - but that kind of knowledge can be imparted
without really diving into how bits work.

~~~
kutkloon7
I remember a recent example. What I wrote was:

    
    
        int x = somefunction();
        int x_dividedby16 = x >> 4;
    

My coworker corrected the second line to something like:

    
    
        int x_dividedby16 = (int)Math.ceil(x / 16.0);

~~~
krzat
Why not `x / 16`?

~~~
sheldor
Integer division will give a rounded result instead of floor

~~~
sp332
I think they're going for the ceiling here, not the floor. Truncation of
positive values should be the same as a floor function.

~~~
kutkloon7
Yes, I wrote the example. Then realized it was different and corrected only
half of it.

------
ishi
Cool, I knew that LFSRs were used in ciphers. I was not aware that they were
also useful for implementing old-school graphical effects.

[https://en.wikipedia.org/wiki/Linear-
feedback_shift_register...](https://en.wikipedia.org/wiki/Linear-
feedback_shift_register#Uses_in_cryptography)

~~~
zimpenfish
I'm not sure I'd call Wolfenstein 3D "old-school".

But then I did start with computers in 1980.

~~~
smcl
It's funny where we draw our lines. Some younger guys will call Half-Life 2
old-skool, which for me felt like it was just a couple of years ago.

------
emmanueloga_
As interesting as this article is, I would also love to know where and how the
author of the code learned about LFSRs!

I wonder if they rediscovered the algorithm, knew they were implementing a
LFSR or even just solved a particular instance of the problem without ever
realizing they were writing a LFSR.

I learned about LFSRs a while ago and wrote a small implementation for ruby as
an exercise [1] using a Wikipedia page as reference [2]. But Wolfenstein 3D
was released in 1992, I'm sure back then information was a lot harder to find
online!

1: [https://github.com/EmmanuelOga/lfsr](https://github.com/EmmanuelOga/lfsr)
2: [https://en.wikipedia.org/wiki/Linear-
feedback_shift_register](https://en.wikipedia.org/wiki/Linear-
feedback_shift_register)

------
thomasahle
Related:
[https://stackoverflow.com/questions/10054732](https://stackoverflow.com/questions/10054732)

Fundamentally you want to make a random permutation, but not spend linear
memory on it, as you would if you did it by shuffling.

~~~
nemo1618
I did something like this in Go:
[https://godoc.org/github.com/lukechampine/randmap/perm](https://godoc.org/github.com/lukechampine/randmap/perm)

------
jstimpfle
What properties are required in an LFSR that it covers the whole range (2^n-1
numbers) before returning? Or are such configurations found experimentally?

~~~
pfedak
Galois generators of the sort in the article can be described naturally using
the finite field Z_2[x]/p(x), that is, polynomials with coefficients taken mod
two, where we consider polynomial p(x) is equivalent to zero. If p has degree
n, this field has 2^n elements - the 2^n polynomials with degree less than n
are distinct, but x^n is equivalent to x^n-p(x), which has degree less than n,
so everything with higher degree is already accounted for. This is, however,
only actually a field if p is irreducible.

Now we can describe the generator as multiplication by x, where the leftmost
bit is the lowest power, since every bit moves right except for the rightmost,
which corresponds to turning x^n into x^n-p(x) as above. The cycle length is
the smallest k such that x^k is equivalent to 1. Now, an interesting property
of finite fields is that there is always a "primitive" element, the powers of
which generate every other element (you can prove this inductively by counting
elements z such that z^d = 1 for different d, and noting that z^d-1, as a
degree d polynomial, has at most d roots in the field). If x is primitive, it
will cycle through all other values before reaching 1. In the specific case of
n=17, however _every_ element of the field is primitive (this is easy group
theory, the nonzero elements form a group under multiplication, and that group
has a prime number of elements).

This means _any_ degree-17 polynomial which is irreducible in Z_2[x] will give
rise to a full-cycle-length generator. Luckily, finding an irreducible
polynomial isn't too hard - of the 2^16 options (ignoring the ones without a
constant term, which are obviously divisible by x), 7710 are irreducible by my
quick Mathematica computation.

------
Jyaif
Note that you can (quite obviously) use this to fade from one image to an
other by writing the color of the next image instead of just "red".

------
transitive_bs
For a Javascript implementation, check out
[https://github.com/fisch0920/dissolve-
generator](https://github.com/fisch0920/dissolve-generator)

This version is based off of the article A Digital Dissolve Effect by Mike
Morton "Graphics Gems", Academic Press, 1990
([http://dl.acm.org/citation.cfm?id=90821](http://dl.acm.org/citation.cfm?id=90821))

------
D_Guidi
as a "senior" business programmer with non-engineering studies (I have a
deegree in byology), I'm feeling an impostor reading this and admitting that
I'm unable to understand basically everything... even
[https://bigmachine.io/products/the-imposters-
handbook/](https://bigmachine.io/products/the-imposters-handbook/) not helped
too much

~~~
cousin_it
It's very simple. Basically you need to cycle through all n-bit integers
(except zero) in a random-looking way, and it turns out there's an arcane-ish
bit twiddling operation that will do it when applied repeatedly. The reason it
works can be explained with some undergrad math. You don't need to know these
things for business programming but they are fun to read about.

Another idea in a similar vein is
[https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dither...](https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering)
It converts an image with many shades of gray to an image with only black and
white pixels. The algorithm is dead simple, but the results are surprisingly
convincing.

------
dopeboy
Fascinating. Who wrote the original ASM? Carmack?

------
userbinator
_My assumption is that LFSR literature was hard to come across in 1991 /1992
and finding the correct tap for a 16 bit maximum length register was not worth
the effort._

I guess it might be more due to the lack of overlap between the problem
domains --- LFSRs were known since the late 60s in relation to CRCs and other
error-correcting codes.
[https://en.wikipedia.org/wiki/Gold_code](https://en.wikipedia.org/wiki/Gold_code)

------
abecedarius
Still a good trick today.
[https://github.com/silentbicycle/greatest](https://github.com/silentbicycle/greatest)
just added a feature of shuffling test executions into random order, after I
suggested the general idea in a Twitter convo. (I think it uses LCGs instead
of an LFSR.)

------
Kiro
Stupid question: Why can't you just take an array of all the pixels, randomize
it and then iterate it to draw?

~~~
gall_anonim
Because with ideal packing it would take 150 kB (320·240·log2(320·240)/8).
Wolf3d needed 640 kB of RAM and using quarter of it just to display death
animation wouldn't be a good idea.

------
drudru11
The Atari 2600 video hardware used these all over the place instead of
traditional counters. It saved them a lot of gates that counters would
require.

------
trollopTheJope
i am interested to know the particulars of any routines people have for
reading and reviewing a codebase, as the author talks about doing in his spare
time. do you take notes? add comments? step through with a debugger?

~~~
kbart
Given you are a seasoned programmer, most of the code written in familiar
language should be obvious just by skimming it. But when it comes to a short
and "smart" algorithms, especially including bit manipulations, I still find
pen&paper the best tool to find out what's really happening.

~~~
thejynxed
Pencil/pen, paper, flow charts, and in some cases one of those TI scientific
calculators can come in real handy.

This being said, I've never gone much away from
C/C++/Pascal/Assembly/COBOL/FORTRAN and various forms of BASIC where what I
mentioned helps immensely (I work on legacy systems in my spare time, CS isn't
my primary field), although Python, Haskell, Rust and GO have piqued my
curiosity.

