---
title: "Infidel goes wild"
slug: infidel-goes-wild
url: https://listedarticles.com/articles/infidel-goes-wild
canonical_url: https://blog.zarfhome.com/2026/10/infidel-goes-wild
content_type: blog_post
language: en
published_at: 2026-10-02T00:00:00.000Z
updated_at: 2026-10-04T23:17:21.472Z
author: "Andrew Plotkin"
author_url: https://blog.zarfhome.com/
authored_by: human
publisher: "Zarf Updates"
publisher_url: https://blog.zarfhome.com/
topics: ["Programming", "History", "Software Engineering"]
license: all-rights-reserved
word_count: 2179
reading_minutes: 9
citation: "Andrew Plotkin, Zarf Updates. \"Infidel goes wild.\" 2 Oct 2026. https://blog.zarfhome.com/2026/10/infidel-goes-wild (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Infidel goes wild

> Andrew Plotkin (Zarf) dissects a wild-pointer memory-corruption bug in Infocom's Infidel, nearly invisible in play, that he spotted with a memory-level debugger while working on Visible Zorker, down to the ZIL code and Z-machine details.

*[Planetfall](https://eblong.com/infocom/visi/planetfall/)* was yesterday's public release, but of course I've spent the past few weeks concentrating on *Infidel*. A couple of days I came across the most egregious bug I've yet seen in an Infocom game.

This bug is extremely difficult to detect by playing the game. I must the first person to notice it, just because I'm the first person to play the game with (the equivalent of) a memory-level debugger.

So why do I call it an *egregious* bug? Most people would call it a "trivial" bug, since it almost never impacts gameplay. But look you: it's a *wild pointer bug* which scribbles over memory in an unintended way. As a C programmer, I am legally required to regard memory corruption as the worst of all possible sins. On top of that, it's a compiler bug!


(Warning: This will get into ZIL code and Z-machine implementation details. Note that I will be analyzing *Infidel* release 22 serial 840522, the update for Macintosh. The original release, serial 830916, has the same bug with slightly different memory addresses.)

Let's set the scene. *Infidel* takes place in the remote Egyptian desert. You start out in your camp. The Nile is to the west. To the east are nine desert locations, a 3x3 grid. (One of these holds the buried pyramid you're searching for.)

If you venture outside that region, or leave your camp in any other direction, you are officially lost in the desert. You can travel as far as you want; you'll just find more desert. (Until sunstroke finds you.) This is a known trick, first seen in *Enchanter;* a single room called `ENDLESS-DESERT` represents every unmapped desert location. When you move, if you haven't blundered back to your camp or the mapped area, you just loop back to `ENDLESS-DESERT`. The game keeps track of your latitude and longitude.

To make this convincing, the game has to juggle objects. If you drop stuff in `ENDLESS-DESERT` and then move, those items are shifted offstage. Their lat/long coordinates are recorded in an array called `DESERT-TABLE`. If you return to those coordinates later, the game can shuffle those objects from `DESERT-TABLE` back to `ENDLESS-DESERT`, and you will find them waiting for you.

(Probably. When you drop an item in `ENDLESS-DESERT`, there's a one-in-three chance that it is immediately covered up by sand and lost forever. That's not relevant here, except that it makes the bug harder to uncover, as we'll see.)

Okay, so far the plan looks solid. Let's see it in action:

Desert You are in the desert, a vast wasteland of sand and heat.

>DROP AXE, SHOVEL pick axe: Dropped. shovel: Dropped. A brief but strong gust of wind comes up off a dune, whipping sand in your face, blinding you for long enough to lose track of the shovel.

>EAST Desert You are in the desert, a vast wasteland of sand and heat.

>WEST Desert You are in the desert, a vast wasteland of sand and heat. There is a pick axe here.


Both locations are the same `ENDLESS-DESERT`, but the pickaxe maintains the illusion of distinct rooms.

(Wouldn't it be more realistic if the shovel disappeared silently, buried in sand, while your back was turned? Maybe that playtested badly.)

Anyhow, that seems to work fine. Where's the bug?

Let's take a look at the [`DESERT-TO-TABLE`](https://github.com/historicalsource/infidel/blob/dca8235af85e3d69bd49119cade8fff5ac8eb271/camp.zil#L1751) routine that makes this work:

```
<ROUTINE DESERT-TO-TABLE (
                         SLOC 
                   "AUX" (TBL ,DESERT-TABLE) 
                         (CNT 0)
                         (F <FIRST? ,ENDLESS-DESERT>)
                         N)
  <REPEAT ()
      <COND (.F <SET N <NEXT? .F>>)
            (ELSE <RETURN>)>
      <COND (<EQUAL? .F ,WINNER>)
            (<FSET? .F ,TAKEBIT>
             <REPEAT ()
                 <COND (<==? <GET .TBL .CNT> 0>
                        <PUT .TBL .CNT .SLOC>
                        <PUT .TBL <+ .CNT 1> .F>
                        <SET CNT <+ .CNT 2>>
                        <REMOVE .F>
                        <RETURN>)
                       (ELSE
                        <SET CNT <+ .CNT 2>>)>>)>
      <SET F .N>>>
```
There's a similar `TABLE-TO-DESERT` routine to bring stuff back. We'll focus on this one.

The routine has one required argument: `SLOC`, the lat/long coordinate. (This is encoded as a single number, but never mind that.) Then `"AUX"` marks the four optional arguments, which also serve as local variables; the Z-machine doesn't distinguish. `TBL` is the array address we'll be writing to. It defaults to `DESERT-TABLE`. `F` is initialized to the first object in the `ENDLESS-DESERT` contents list. `CNT` and `N` are initialized to zero.

As it happens, when this is called, the game only passes one argument, `SLOC`. It relies on the default value of `TBL=DESERT-TABLE`. (If there were *two* endless deserts in the game, it would want to call this routine with two separate tables. But there ain't.) `DESERT-TABLE` is an array of 100 values (200 bytes) starting at address 11129.

The function body loops through all the objects in `ENDLESS-DESERT` (starting with `F`). Every *portable* object (ignoring scenery and the player) is removed, and we add two values to the `TBL` array: the coordinate `SLOC` and the object ID. The table might already have entries (you can drop stuff in multiple places), so we're careful to find empty slots to fill in. When we reach the end of the list we're done. If the logic looks a bit convoluted, remember that we're dealing with a linked list.

All sound good so far? It sounded good to me. "Hey," I said to myself, "I should display the `DESERT-TABLE` contents in the State tab! That way people can watch the objects being shuffled in and out."

So I set that up... and it didn't work. The `DESERT-TABLE` array remained resolutely empty. I could *see* the objects disappearing and appearing -- just look at that pickaxe above! -- but where were they going?

Any C nerd looking at a fixed-size array will ask "What about array overflow?" But `DESERT-TABLE` has a comment:

```
;"length should be 2*number of takeable objects"
```
Indeed, 100 values at two values per entry is enough space for 50 objects. Piling up every portable object in the game would get you (I think) 45. Anyhow, nothing is being stored *anywhere* in the array.

Time to disassemble the `DESERT-TO-TABLE` function from the game file and take a look. (I warned you we'd go deep...)

```
Routine 109bc, 5 locals (0000, 001e, 0000, 0000, 0000)
109c7:  GET_CHILD     ENDLESS-DESERT -> .F [TRUE] 109cb
109cb:  JZ            .F [TRUE] RTRUE
109ce:  GET_SIBLING   .F -> .N [TRUE] 109d2
109d2:  JE            .F, G70 [FALSE] 109d9
109d6:  JUMP          10a02
109d9:  TEST_ATTR     .F, #0f [FALSE] 10a02
109dd:  LOADW         .TBL, .CNT -> -(SP)
109e1:  JZ            (SP)+ [FALSE] 109fb
109e4:  STOREW        .TBL, .CNT, .SLOC
109e9:  ADD           .CNT, #01 -> -(SP)
109ed:  STOREW        .TBL, (SP)+, .F
109f2:  ADD           .CNT, #02 -> .CNT
109f6:  REMOVE_OBJ    .F
109f8:  JUMP          10a02
109fb:  ADD           .CNT, #02 -> .CNT
109ff:  JUMP          109dd
10a02:  STORE         .F, .N
10a05:  JUMP          109cb
```
(If you're looking at the 1983 release, this routine is at address `1051a` but is otherwise identical.)

I'm using the `txd` disassembly tool from [ztools](https://ifarchive.org/indexes/if-archive/infocom/tools/ztools/), but I've edited the output to show variable names. Note that the opcode names do not match ZIL terminology. `txd` dates from the early 1990s. We had no ZIL manuals so we had to make up our own opcode names.

The first line sets the initial value of `F` to the first object in the room (`<FIRST? ,ENDLESS-DESERT>`). The second line tests whether `F` is zero, which is the condition of the function's `REPEAT` loop. The loop continues from there.

Notice anything missing? Where do we initialize `TBL` to its default value of `DESERT-TABLE` (11129)? That seems like an obvious hole.

Aha, says ZIL, we've got you covered. A Z-machine function has slots to initialize its local variables (or optional arguments) to constants. That's shown in the header lines:

```
Routine 109bc, 5 locals (0000, 001e, 0000, 0000, 0000)
```
`CNT` is initialized to zero, as is `N`, implicitly. `F` is *not* initialized from a slot because it's calculated, not a constant. ZIL generates that `FIRST?` (`GET_CHILD`) line at the top of the function. `DESERT-TABLE` is a constant, so...

...Whoops. No it isn't. `DESERT-TABLE` is a global *variable*. Its value will never vary -- it will be 11129 through the whole game -- but ZIL doesn't know that! `TBL`  is initialized to the constant value 30 (hex `1e`), which is the *index number* of `DESERT-TABLE`.

(Global variables are numbered from 16 to 255 for boring reasons. `DESERT-TABLE` is the 15th global, so it's number 30.)

It seems clear that nobody thought about this case (an argument defaulting to a global variable value). If they had, ZIL would *either* display a compiler error ("Default value is not a constant!") or generate a `STORE` instruction at the top of the function (same as how `F` is initialized). 30 is simply wrong.

The upshot is that when `DESERT-TO-TABLE` start writing object numbers to memory, it doesn't write to address 11129. It overwrites memory addresses 30 and up. Oh dear.

The `TABLE-TO-DESERT` routine uses a default global argument in exactly the same way. So it pulls the object list *back* from address 30, producing apparently correct results. But *memory has been stomped.*

So what's at that address?

The first 64 bytes of memory are header data, but not all of the header is used. By sheer luck, only addresses 0-29 are meaningful in Z-machine version 3. If `DESERT-TABLE` had been the third global, index 18, this bug would have corrupted the game's serial number! Which would be a lot more obvious.

So addresses 30-63 are unused, but address 64 is the beginning of the abbreviations data. This is part of the Z-machine text compression scheme: a [list of 96 words](https://github.com/historicalsource/infidel/blob/master/infidelfreq.xzap) which occur frequently and can therefore be pulled out and replaced by a few bits.

Prediction: if we drop nine objects in the desert (and then walk away), the last one will overwrite the start of the abbreviations table.

This is a bit of a nuisance to arrange. Remember that dropped objects have a one-in-three chance of simply disappearing. I had to drag a lot of stuff out there and try it a few times. But I succeeded, and the next text the game printed looked like this:

Desert You are in the desert, a vast wasteland of sand and heat. A small lizard pokes its head up from You sands and asks You shortest route to Times Square. You scratch your head for a moment and when you remember You proper subway line to recommend to it, you notice it's disappeared.


This is of course a heat-driven hallucination, but notice that the word "the" (the first abbreviation) has been replaced by "You" (the second). This indicates that the early string data has been overwritten by junk which happens to have no terminator, so the print routine continues to the next valid string.

In another test I managed to crash the interpreter. I suspect that it overwrote the first abbreviation with the bits *representing* the first abbreviation, so the print routine went into an infinite loop. You know. As one does.

Okay! Bug demonstrated! What have we learned?

This bug shipped and absolutely nobody noticed. When I played *Infidel* as a kid and explored the desert, my game was corrupting memory. Same with every other player, including Infocom's playtesters.

It's very likely that nobody tried dropping more than eight objects out in the Endless Desert. Why would you? There's nothing out there! It's not on the way to anywhere. The entire mechanism exists only to occupy players who have not yet discovered the pyramid. The attention to detail is typical of Infocom, but that area can't have been playtested very hard.

(I would love to peruse an Infocom testing log to see if any trace of the bug appeared. The [Infocom Cabinet](https://archive.org/details/infocomcabinet) doesn't seem to have anything relevant. Let know if you come across such a thing.)

The 1984 revision of the game did nothing to address the bug. Infocom did not have anything like the Visible Zorker. They might have had debugging tools to inspect memory, but nobody thought to apply them to these routines and their storage.

I have not surveyed later games to see if a similar bug exists -- or is correctly handled by the compiler. I'll keep an eye out.

How would one fix the bug? Simple: remove the argument default for `TBL`, and instead start the routine with `<SET TBL ,DESERT-TABLE>`. Or else pass in `DESERT-TABLE` as a second argument to the routine. Either would work.

There's another pair of routines `PROP-TO-TBL`, `TBL-TO-PROP` which have a similar bug with the `PROP-TBL` table (address 11329, global number 22). I am honestly a bit baffled by these routines. They look like they're doing a similar shuffle-job with the holes you can dig in the sand. (These are stored in desert rooms as the `CAPACITY` property.) But the game doesn't let you dig in `ENDLESS-DESERT`; it says "The ground is too hard here." So the routines appear to be useless. They may be a remnant of an earlier feature which was removed during development. Perhaps because it corrupted the serial number!

If I were updating *Infidel* (as opposed to [riffing it](https://ifdb.org/viewgame?id=wvs2vmbigm9unlpd)), I would definitely restore the ability to dig useless holes out in the desert. That kind of pointless detail is one of the great joys of IF.
