lfsr math in pitfall!
A few weeks ago I attended VCF West at the Computer History Museum in Mountain View. One of my favorite talks was Making Games in the 1980s by David Crane and Garry Kitchen about the unique constraints of programming for the Atari 2600. Crane mentioned one hack in particular from his game Pitfall! that inspired this blog post.
Pitfall! is a platform game in which you control a character named Harry through a jungle filled with crocodiles, logs, ladders, and, of course, pits. One of its main innovations is allowing movement back and forth between different screens, creating the illusion of an enormous world (at least by the standards of the time).
Each screen is unique, with different arrangements of obstacles and enemies. Even the patterns of the trees in the background aren’t static; you can see the difference if you look closely at the treelines of the two screenshots below:

Each screen is represented by a single byte, with different bits controlling the objects to spawn,
the layout of the holes, and so on. With 8 bits, it was possible to represent 255 unique screens (0x0 isn’t used, for reasons we’ll come to shortly).1
The challenge is that the Pitfall! cartridge had only 4KB of read-only memory, most of which was needed to store the game executable. There was simply no space to store an additional 255 bytes in ROM.
So David Crane invented an ingenious hack. Instead of storing every screen’s configuration individually, he wrote code
to generate a sequence of each possible byte from an initial value (0xC4) representing the first screen.
Then he devised a way to reverse the generation so Harry could return to the previous screen.
I left the talk super curious about the math behind this trick. How exactly does the procedural generation and reversal in Pitfall! work? Let’s explore!
Definitions
Linear Feedback Shift Register (LFSR)
Let represent a sequence of bits for some , and let denote the th bit in , with . For the 8-bit sequence used in Pitfall!, this looks like:
Pitfall! generates new sequences using something called a linear feedback shift register (LFSR), which is a transformation of to another -bit sequence by:
- Shifting the bits of to the left by one.
- Calculating a new value for the last bit by XOR’ing some of the bits chosen from .
In other words, an LFSR is a function of the form
where (the “feedback function”) is derived from the bits of
with coefficients . (The operator means summation using XOR () instead of addition.)
Fixing the values of produces a specific LFSR, and applying such an LFSR repeatedly produces a sequence of states, where .
Maximal-length LFSR
If the LFSR produces every possible -bit sequence except all zeroes, then it’s called a maximal-length LFSR. That is, both the domain and range of the function are . The zero bit sequence is excluded because , preventing progression to any other state.
Not all LFSRs are maximal-length; for example, it’s possible to choose the coefficients such that the LFSR “loops” through a subset of possible states. Crane intentionally chose a maximal-length LFSR for Pitfall! in order to generate all 255 possible screens.
Invertibility
In Pitfall!, the player is allowed to return to the previous screen by walking left. This requires a mechanism for reversing an LFSR to recover the previous state. Let’s call an LFSR invertible if there exists some operation such that:
A trivial implementation of for a maximal-length LFSR would repeatedly apply until observing output , then return the input that produced . Since there are reachable states, this has exponential time complexity in . Even for small values like , this would execute far too slowly on an Atari. We therefore restrict ourselves to inverses that are at least as efficient as . In particular, they should require memory and operations (since executes a left shift plus bitwise operations for each non-zero coefficient, of which there are at most ).2
How rare are maximal-length LFSRs?
The LFSR used in Pitfall! operates on 8-bit sequences. The family of LFSRs with has members, one for each possible assignment of the 8 coefficients. How many of these are maximal-length LFSRs? In other words, if Crane had flipped a coin for each coefficient, what is the probability that he would have found a maximal-length LFSR?
Since there are only 256 possible LFSRs to consider, we can find the answer by brute force using a short Python program.
First, we iterate through all possible assignments of n coefficients to generate a specific LFSR function f:
from itertools import product
def iter_lfsr(n):
mask = (1 << n) - 1
for coefficients in product([0, 1], repeat=n):
def f(x):
last_bit = 0
for i, c in enumerate(coefficients):
last_bit ^= c & (x >> (n - 1 - i)) & 0x01
return ((x << 1) | last_bit) & mask
yield coefficients, f
Then, for each LFSR, we check if it is maximal-length by counting the number of states reached from a non-zero initial state:
def is_max_len_lfsr(f, n):
x = 1 # always a valid non-zero starting state for any n >= 2
visited = set()
while x not in visited and x != 0:
visited.add(x)
x = f(x)
return len(visited) == (1 << n) - 1
This yields exactly sixteen maximal-length LFSRs for , including the one that Crane chose for Pitfall!. Their coefficients are:
(1, 0, 0, 0, 1, 1, 1, 0)
(1, 0, 0, 1, 0, 1, 0, 1)
(1, 0, 0, 1, 0, 1, 1, 0)
(1, 0, 1, 0, 0, 1, 1, 0)
(1, 0, 1, 0, 1, 1, 1, 1)
(1, 0, 1, 1, 0, 0, 0, 1)
(1, 0, 1, 1, 0, 0, 1, 0)
(1, 0, 1, 1, 0, 1, 0, 0)
(1, 0, 1, 1, 1, 0, 0, 0) <-- this is the one used in Pitfall!
(1, 1, 0, 0, 0, 0, 1, 1)
(1, 1, 0, 0, 0, 1, 1, 0)
(1, 1, 0, 1, 0, 1, 0, 0)
(1, 1, 1, 0, 0, 0, 0, 1)
(1, 1, 1, 0, 0, 1, 1, 1)
(1, 1, 1, 1, 0, 0, 1, 1)
(1, 1, 1, 1, 1, 0, 1, 0)
(Notice that the first coefficient is always one. This will become important later.)
With , choosing coefficients at random produces a maximal-length LFSR with probability . What happens as we vary ? Since the number of coefficient assignments grows exponentially with , brute force search quickly becomes intractable, but we can at least explore up to in a reasonable amount of time.3
Given that the total number of LFSRs is exponential in , I would have expected the number of maximum-length LFSRs to strictly increase, but this isn’t how it works out. For example, has 176 maximum-length LFSRs, but has only 144.
Are all maximal-length LFSRs invertible?
How did Crane discover an LFSR that was both maximal-length and invertible? It may seem like an incredible coincidence that the LFSR he chose for Pitfall! happened to have an inverse. However, as I’ll show below, it is possible to construct an inverse function for any maximal-length LFSR by following a simple procedure.4
Intuition
Before giving the proof, I want to build some intuition from an example. Let’s use the exact LFSR and inverse from Pitfall!:
Something magical happens when we combine these two functions:
All the bits are shifted left by , then shifted right by , restoring every bit except the first. The first bit gets “smuggled” by the feedback function into the last bit of . By canceling out the other terms of , the inverse recovers !
This seems pretty amazing, but it turns out that the same trick works for every maximal-length LFSR. To prove it, we first need two lemmas.
Lemma: unique predecessors
If is a maximal-length LFSR, then for every state (the “successor”), there exists exactly one state (the “predecessor”) such that .
Proof: Suppose to the contrary that there exists a maximal-length LFSR and state such that either (1) there is no state where or (2) there are at least two states and where and . In case (1), would not be within the domain of , contradicting the assumption that is maximal-length. And in case (2), repeated application of must eventually produce either or since is maximal-length. If outputs , then the next application produces without ever producing (by the assumption that ). Likewise, if outputs , then the next state becomes without ever producing . Therefore the range of will exclude either or , contradicting the assumption that is maximal-length.
Lemma: first coefficient
If is a maximal-length LFSR, then the first coefficient of its feedback function must be non-zero; that is, .
Proof: Assume by way of contradiction that there exists an that is a maximal-length LFSR with . Let and for any . and differ only by the first bit, and since , it follows that and therefore . But the unique predecessors lemma implies that no two such states can exist for a maximal-length LFSR, contradicting the original assumption.
Inverse construction
With these lemmas, we can now construct an inverse for any maximal-length LFSR. Let’s define the inverse as:
In other words, we shift the bits of to the right and calculate an inverse feedback function for the first bit:
where
(Intuitively: always include the last bit to retrieve the original feedback function , then include other bits to cancel out the other terms of and recover .)
Now let’s prove that is an inverse of the maximal-length LSRF . First, the two efficiency requirements:
- has space complexity, since it needs to copy only the last bit (); the remaining bits can be shifted in-place.
- has time complexity, since, like , it executes a single bitwise shift plus bitwise operations per non-zero coefficient, of which there are at most .
All that remains is to show that .
shifts all the bits left by one, and shifts them back right by one, restoring every bit except the first. So we just need to prove that the inverse feedback function restores the first bit; that is, .
For notational convenience, let , then
Thus, as required.
Conclusion
Learning the math behind the LFSR trick in Pitfall! leaves me with even greater admiration for the ingenuity of programmers from that era. There is something deeply satisfying about the way a maximal-length LFSR chooses just the right bits to generate all possible states and how this property guarantees that the last bit preserves enough information to recover the previous state. What an elegant hack!
For more details, see this annotated version of the Pitfall! assembly code and this blog post describing what each bit pattern controls. ↩︎
I was amazed to learn that the Atari had only 128 bytes of RAM and no dedicated graphics processor. That meant that the program had to generate a frame’s pixels one scanline at a time, precisely synchronizing CPU cycles with the timing of the raster beam. ↩︎
I wonder if there are clever ways to prune the search space to allow exploration at higher values of ? ↩︎
It does not follow that every invertible LFSR is maximal-length. The counter-example is with coefficients and yielding and inverse function . Both and cycle between states and without reaching . ↩︎