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:

Screenshot of the main character swinging from a vine over a pit. Screenshot of the main character standing underground with logs on the floor above.

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 BB represent a sequence of nn bits for some n2n \geq 2, and let bi{0,1}b_i \in \{ 0, 1 \} denote the iith bit in BB, with 1in1 \le i \le n. For the 8-bit sequence used in Pitfall!, this looks like:

B=(b1,b2,b3,b4,b5,b6,b7,b8)B = (b_1, b_2, b_3, b_4, b_5, b_6, b_7, b_8)

Pitfall! generates new sequences using something called a linear feedback shift register (LFSR), which is a transformation of BB to another nn-bit sequence by:

  1. Shifting the bits of BB to the left by one.
  2. Calculating a new value for the last bit by XOR’ing some of the bits chosen from BB.

In other words, an LFSR is a function FF of the form

F(B)=(b2,,bn,f(B))F(B) = (b_2, \dots, b_n, f(B))

where f(B)f(B) (the “feedback function”) is derived from the bits of BB

f(B)=i=1ncibif(B) = \bigoplus_{i=1}^n{c_i b_i}

with coefficients ci{0,1}c_i \in \{0, 1 \}. (The \bigoplus operator means summation using XOR (\oplus) instead of addition.)

Fixing the values of cic_i produces a specific LFSR, and applying such an LFSR repeatedly produces a sequence of states, B1,B2,B3,...B_1, B_2, B_3, ... where Bt+1=F(Bt)B_{t+1} = F(B_t).

Maximal-length LFSR

If the LFSR produces every possible nn-bit sequence except all zeroes, then it’s called a maximal-length LFSR. That is, both the domain and range of the function FF are {x{0,1}n:x0n}\{ x \in \{0, 1\}^n : x \ne 0^n\}. The zero bit sequence 0n0^n is excluded because F(0n)=0nF(0^n) = 0^n, 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 F1F^{-1} such that:

F1(F(B))=BF^{-1}(F(B)) = B

A trivial implementation of F1F^{-1} for a maximal-length LFSR FF would repeatedly apply FF until observing output BB, then return the input that produced BB. Since there are 2n12^n-1 reachable states, this has exponential time complexity in nn. Even for small values like n=8n=8, this would execute far too slowly on an Atari. We therefore restrict ourselves to inverses that are at least as efficient as FF. In particular, they should require O(1)O(1) memory and O(n)O(n) operations (since FF executes a left shift plus O(1)O(1) bitwise operations for each non-zero coefficient, of which there are at most nn).2

How rare are maximal-length LFSRs?

The LFSR used in Pitfall! operates on 8-bit sequences. The family of LFSRs with n=8n=8 has 28=2562^8 = 256 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 n=8n=8, 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 n=8n=8, choosing coefficients at random produces a maximal-length LFSR with probability 16/256=6.25%16/256 = 6.25\%. What happens as we vary nn? Since the number of coefficient assignments grows exponentially with nn, brute force search quickly becomes intractable, but we can at least explore up to n=16n=16 in a reasonable amount of time.3

Bar chart showing the number of maximum-length LFSRs for each value of n from 2 through 16. Bar chart showing the percentage of maximum-length LFSRs out of all LFSRs for n from 2 through 16.

Given that the total number of LFSRs is exponential in nn, I would have expected the number of maximum-length LFSRs to strictly increase, but this isn’t how it works out. For example, n=11n=11 has 176 maximum-length LFSRs, but n=12n=12 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!:

F(B)=(b2,b3,b4,b5,b6,b7,(b1b3b4b5))F(B) = (b_2, b_3, b_4, b_5, b_6, b_7, (b_1 \oplus b_3 \oplus b_4 \oplus b_5))F1(B)=((b2b3b4b7),b2,b3,b4,b5,b6,b7)F^{-1}(B) = ((b_2 \oplus b_3 \oplus b_4 \oplus b_7), b_2, b_3, b_4, b_5, b_6, b_7)

Something magical happens when we combine these two functions:

F1(F(B))=((b3b4b5(b1b3b4b5)),b2,b3,b4,b5,b6,b7)=((b1(b3b3)(b4b4)(b5b5)),b2,b3,b4,b5,b6,b7)=(b1,b2,b3,b4,b5,b6,b7)=B \begin{aligned} F^{-1}(F(B)) &= ((b_3 \oplus b_4 \oplus b_5 \oplus (b_1 \oplus b_3 \oplus b_4 \oplus b_5)), b_2, b_3, b_4, b_5, b_6, b_7) \\ &= ((b_1 \oplus (b_3 \oplus b_3) \oplus (b_4 \oplus b_4) \oplus (b_5 \oplus b_5)), b_2, b_3, b_4, b_5, b_6, b_7) \\ &= (b_1, b_2, b_3, b_4, b_5, b_6, b_7) \\ &= B \end{aligned}

All the bits are shifted left by FF, then shifted right by F1F^{-1}, restoring every bit except the first. The first bit b1b_1 gets “smuggled” by the feedback function ff into the last bit of F(B)F(B). By canceling out the other terms of f(B)f(B), the inverse F1(B)F^{-1}(B) recovers b1b_1!

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 FF is a maximal-length LFSR, then for every state Srange(F)S' \in \operatorname{range}(F) (the “successor”), there exists exactly one state Sdomain(F)S \in \operatorname{domain}(F) (the “predecessor”) such that F(S)=SF(S) = S'.

Proof: Suppose to the contrary that there exists a maximal-length LFSR FF and state SS' such that either (1) there is no state SS where F(S)=SF(S) = S' or (2) there are at least two states S1S_1 and S2S_2 where S1S2S_1 \ne S_2 and F(S1)=F(S2)=SF(S_1) = F(S_2) = S'. In case (1), SS would not be within the domain of FF, contradicting the assumption that FF is maximal-length. And in case (2), repeated application of FF must eventually produce either S1S_1 or S2S_2 since FF is maximal-length. If FF outputs S1S_1, then the next application F(S1)F(S_1) produces SS without ever producing S2S_2 (by the assumption that F(S1)=SF(S_1) = S). Likewise, if FF outputs S2S_2, then the next state becomes F(S2)=SF(S_2) = S without ever producing S1S_1. Therefore the range of FF will exclude either S1S_1 or S2S_2, contradicting the assumption that FF is maximal-length. \blacksquare

Lemma: first coefficient

If FF is a maximal-length LFSR, then the first coefficient of its feedback function must be non-zero; that is, c1=1c_1=1.

Proof: Assume by way of contradiction that there exists an FF that is a maximal-length LFSR with c1=0c_1=0. Let S1=(0,x1,,xn1)S_1 = (0, x_1, \dots, x_{n-1}) and S2=(1,x1,,xn1)S_2 = (1, x_1, \dots, x_{n-1}) for any xi{0,1}x_i \in \{0, 1\}. S1S_1 and S2S_2 differ only by the first bit, and since c1=0c_1=0, it follows that f(S1)=f(S2)f(S_1) = f(S_2) and therefore F(S1)=F(S2)F(S_1) = F(S_2). But the unique predecessors lemma implies that no two such states can exist for a maximal-length LFSR, contradicting the original assumption. \blacksquare

Inverse construction

With these lemmas, we can now construct an inverse for any maximal-length LFSR. Let’s define the inverse as:

F1(B)=(f1(B),b1,,bn1)F^{-1}(B) = (f^{-1}(B), b_1, \dots, b_{n-1})

In other words, we shift the bits of BB to the right and calculate an inverse feedback function f1f^{-1} for the first bit:

f1(B)=icibif^{-1}(B) = \bigoplus_i{c_i' b_i}

where

ci={1i=nci+1i<n c_i' = \begin{cases} 1 & i=n \\ c_{i+1} & i<n \end{cases}

(Intuitively: always include the last bit to retrieve the original feedback function f(B)f(B), then include other bits to cancel out the other terms of f(B)f(B) and recover b1b_1.)

Now let’s prove that F1F^{-1} is an inverse of the maximal-length LSRF FF. First, the two efficiency requirements:

  1. F1F^{-1} has O(1)O(1) space complexity, since it needs to copy only the last bit (bnb_n); the remaining bits can be shifted in-place.
  2. F1F^{-1} has O(n)O(n) time complexity, since, like FF, it executes a single bitwise shift plus O(1)O(1) bitwise operations per non-zero coefficient, of which there are at most nn.

All that remains is to show that F1(F(B))=BF^{-1}(F(B)) = B.

FF shifts all the bits left by one, and F1F^{-1} 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, f1(F(B))=b1f^{-1}(F(B)) = b_1.

For notational convenience, let F(B)=(b1,,bn)F(B) = (b_1', \ldots, b_n'), then

f1(F(B))=icibiby definition of f1=bni=1n1ci+1biby definition of ci=f(B)i=2ncibiby definition of F(B)=[i=1ncibi][i=2ncibi]by definition of f(B)=c1b1[i=2n(cici)bi]by associativity and commutativity of XOR=b1by the first coefficient lemma and definition of XOR \begin{aligned} f^{-1}(F(B)) &= \bigoplus_i c_i' b_i' && \text{by definition of } f^{-1} \\ &= b_n' \oplus \bigoplus_{i=1}^{n-1} c_{i+1} b_i' && \text{by definition of } c_i' \\ &= f(B) \oplus \bigoplus_{i=2}^{n} c_i b_i && \text{by definition of } F(B) \\ &= \left[ \bigoplus_{i=1}^n c_i b_i \right] \oplus \left[ \bigoplus_{i=2}^{n} c_i b_i \right] && \text{by definition of } f(B) \\ &= c_1 b_1 \oplus \left[ \bigoplus_{i=2}^{n} (c_i \oplus c_i) b_i \right] && \text{by associativity and commutativity of XOR} \\ &= b_1 && \text{by the first coefficient lemma and definition of XOR} \\ \end{aligned}

Thus, F1(F(B))=(f1(F(B)),,bn)=(b1,,bn)=BF^{-1}(F(B)) = (f^{-1}(F(B)), \ldots, b_n) = (b_1, \ldots, b_n) = B as required. \blacksquare

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!


  1. For more details, see this annotated version of the Pitfall! assembly code and this blog post describing what each bit pattern controls. ↩︎

  2. 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. ↩︎

  3. I wonder if there are clever ways to prune the search space to allow exploration at higher values of nn↩︎

  4. It does not follow that every invertible LFSR is maximal-length. The counter-example is n=2n=2 with coefficients c1=1c_1=1 and c2=0c_2=0 yielding F(B)=(b2,b1)F(B) = (b_2, b_1) and inverse function F1(B)=(b1,b2)F^{-1}(B) = (b_1, b_2). Both FF and F1F^{-1} cycle between states (0,1)(0, 1) and (1,0)(1, 0) without reaching (1,1)(1, 1)↩︎