Article 7

Lovingly reproduced from the Game Engine Black Book: Wolfenstein 3D by Fabien Sanglard.


How Enemies Think, and How Sound Works

Lovingly reproduced from the Game Engine Black Book: Wolfenstein 3D by Fabien Sanglard.


The AI: State Machines and Ambushes

Every enemy in Wolfenstein 3D is controlled by a state machine. Each actor has a current state (standing, patrolling, chasing, attacking, dying) and each state has associated think and action functions, along with a pointer to the next state. When a state's timer expires, the actor transitions.

A guard standing in place stays in a loop: the standing state's next pointer points back to itself. A guard chasing the player cycles through four walk-frame states before repeating. The state chains are defined in code and look something like a linked list:

Chase state 1 leads to chase state 1-standing (a pause), which leads to chase state 2, which leads to chase state 3, then 3-standing, then chase state 4, then back to 1.

All this produces the characteristic jerky walk cycle of the Wolf3D guard.

Enemies detect the player in three ways: by proximity, by sight, or by sound. Of these, sound is by far the most important, and its implementation is cleverly efficient.

Sound Propagation (and Non-Propagation)

When you fire a weapon, every enemy that can "hear" it should react. Running a full acoustic simulation to determine sound paths through a level would be expensive. Instead, the engine pre-processes each map.

Maps are divided into areas (essentially rooms). At runtime, the engine maintains a matrix tracking which areas are connected to each other, and updates it whenever a door opens or closes. When the player fires, the engine marks all areas reachable from the player's current area as "active." Checking whether an enemy can hear the sound is then a single array lookup: is the enemy's area marked active?

The map pre-processing assigns an area number to each tile. Doors between areas are the connection points. Up to 37 areas are supported.

This system works elegantly. But it has one deliberate flaw.

Some tiles in the map are marked AMBUSH. Enemies standing on these tiles do not respond to sound propagation. They only activate when they directly see the player. This gives level designers a cheap way to create ambush moments: enemies that appear to have "not heard" the commotion nearby, waiting silently until the player walks into view.

The hint book describes it diplomatically as: "Some are ordered to immediately attack, while others are trained to act only upon visual contact." Technically, a guard on an AMBUSH tile won't even react to watching another guard die right in front of it.

Patrolling enemies follow waypoints manually placed in TED5. There's no pathfinding. An enemy walks until it hits a change-direction tile, turns, and keeps walking. This simple system was used creatively: in E1M1, dogs are given a figure-eight pattern of waypoints to make them appear to be running around excitedly.

The Sound System: Interrupts All the Way Down

The audio system runs concurrently with the rest of the engine. On an OS that knows nothing about threads, the only way to achieve this is through hardware interrupts.

Two chips handle the work: the Intel 8254 (a programmable interval timer) and the Intel 8259 (a programmable interrupt controller). The PIT crystal runs at 1.193182 MHz, a frequency that traces back to early television oscillators used in the first IBM PCs for cost reasons. By 1991, better oscillators were cheap, but backward compatibility kept this odd number.

The engine programs the PIT to trigger an interrupt at a chosen frequency and installs its own handler in the Interrupt Vector Table. When the timer fires, the CPU pauses whatever it was doing (usually rendering a frame), runs the audio handler, and resumes. The handler runs at 140Hz, 700Hz, or 7000Hz depending on what audio system is active. The 70-unit-per-second heartbeat used by everything else in the engine (AI timing, player movement, input) is maintained here too.

A side effect of all this: because the engine installs its own timer handler at address 8 in the Interrupt Vector Table (the system timer), DOS's own clock doesn't update while the game runs. When you exit Wolfenstein 3D, the system clock will be behind by the time you spent playing.

Music

Music playback is relatively simple because it targets one piece of hardware: the Yamaha YM3812 FM synthesizer, present in both the AdLib and Sound Blaster cards. The two cards use the same interface, so there's one code path for both.

Music is stored in IMF (id Music Format), a proprietary format that is essentially a raw stream of register/data pairs for the YM3812, each with an associated delay value. At playback time, the audio system sends these pairs directly to the card's I/O ports, waiting the specified number of ticks between them. There is zero abstraction: IMF is exactly what the synthesizer hardware expects.

The YM3812 has 9 channels, each capable of simulating an instrument using two oscillators. Bobby Prince was asked to use only channels 1 through 8, keeping channel 0 permanently available for sound effect playback on AdLib cards.

Sound Effects: Five Different Methods

Sound effects are more complicated, because they differ completely across devices.

On the AdLib, sound effects are short IMF streams played through channel 0. On the Sound Blaster, 8-bit PCM audio (digitized samples at 7kHz) is streamed via DMA, freeing the CPU from the transfer entirely. On the Sound Blaster Pro, the same 7kHz PCM is used, but stereo panning is applied: the player's position relative to the sound source is translated into left/right attenuation values using the same rotation math that corrects the fisheye effect in rendering. On the Disney Sound Source, PCM is pushed byte by byte through the parallel port into a 16-byte hardware buffer.

On the PC Speaker, with no dedicated hardware, the engine approximates tones by rapidly changing the speaker's output frequency every 1/140th of a second. The result isn't great, but it's better than pure beeps.

The source code actually contains a fifth method: full PCM playback through the PC Speaker, driving it at 7000Hz and converting 8-bit samples to 1-bit output. The visual representation of a "Mein Leben" sample at 1-bit looks like mashed potatoes, but by all accounts sounds surprisingly good. This code path was shipped but never enabled. Running at 7000Hz would have caused the game to freeze on slower machines while the sample played.


To return to the articles section, click here. 

Scroll to top
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.