Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# Wolfenstein 3-D — Architecture

This is the top-level entry point for understanding the source in this
repository. It was written by reading the code in `WOLFSRC/` directly; every
substantive claim can be cross-checked against `WOLFSRC/<file>:<line>`. Line
numbers refer to the tree at the tip of `main` (`778abe0`).

---

## 1. What this code is

This is the **complete original PC source code for Wolfenstein 3-D**, released
by id Software in 2012. A single source tree builds several products through
conditional compilation, selected by per-game version headers:

| File | Product / flag |
|----------------------|----------------------------------------------------|
| `WOLF1VER.H` | Wolfenstein 3-D v1.0 |
| `WOLFVER.H` | Shareware / Apogee retail flags |
| `SODVER.H` | *Spear of Destiny* (full) |
| `SDMVER.H` | *Spear of Destiny* mission-disk build |
| `WOLFJVER.H` / `WLFJ1VER.H` | Japanese full / shareware |
| `SPANVER.H` | Spanish-language build |
| `WOLFGTV.H` | German-television variant |
| `FOREIGN.H` | Master switch enabling a foreign-language build |
| `F_SPEAR.H` | Spear-of-Destiny feature gates (`SPEAR`) |

The central header **`WL_DEF.H`** (1276 lines) carries the type and constant
definitions that all of `WL_*.C` depend on — the `fixed` typedef, tile and
angle constants, and the actor/state structs (see §6).

## 2. What's in the repository root

```
.
├── README.rst # README from id Software's 2012 release
├── DEICE.EXE # DeIce self-extractor that produced WOLFSRC
├── INSTALL.BAT # DOS bootstrap that invokes DEICE
├── WOLFSRC.1 / WOLFSRC.DAT # split-file continuation data for the extractor
├── WOLFSRC/ # ★ all source code lives here ★
└── docs/ # ★ this documentation ★
```

The `DEICE.EXE` self-extractor split the release across floppy disks; running
it reproduces `WOLFSRC/`. The release note `WOLFSRC/GOODSTUF.TXT` records that
the project was built with **Borland C++ 3.0** and is shipped with no support.

## 3. The WOLFSRC layout

`WOLFSRC/` holds the engine sources plus build assets and pre-built binaries.
The files fall into these classes:

| Class | Examples |
|--------------|------------------------------------------------------------------|
| C source | `WL_*.C`, `ID_*.C`, plus `CONTIGSC.C`, `MUNGE.C`, `OLDSCALE.C`, `WOLFHACK.C` |
| C headers | `WL_*.H`, `ID_*.H`, per-game version flags, asset tables (`AUDIO*.H`, `GFX*.H`, `MAPS*.H`) |
| x86 assembly | `C0.ASM`, `H_LDIV.ASM`, `WL_*_A.ASM`, `ID_*_A.ASM`, `JABHACK.ASM`, `WHACK_A.ASM` |
| Equate tables| `ID_*.EQU`, `GFX*_*.EQU` (assembler `EQU` constants) |
| Build system | `WOLF3D.PRJ`, `WOLF.IDE`, `WOLF.DSW`, `WOLF.OBR`, `GO.BAT`, `RULES.ASI` |
| Binaries | `WOLF3D.EXE`, `WOLF.EXE`, `SV.EXE`, `WOLF3D.MAP`, `OBJ/` |
| Doc / misc | `GOODSTUF.TXT`, `README/`, `PICLIST.H`, `VERSION.H` |

Approximate size, via `wc -l` (largest files):

| File | Lines | File | Lines |
|-------------|-------|-------------|-------|
| `WL_MENU.C` | 3986 | `WL_DRAW.C` | 1403 |
| `WL_ACT2.C` | 3872 | `WL_DEF.H` | 1276 |
| `ID_SD.C` | 2367 | `ID_PM.C` | 1199 |
| `ID_CA.C` | 1767 | `ID_VL.C` | 1084 |
| `WL_INTER.C`| 1718 | `ID_IN.C` | 990 |
| `WL_MAIN.C` | 1616 | `ID_MM.C` | 953 |
| `WL_GAME.C` | 1484 | `WL_ACT1.C` | 900 |
| `WL_STATE.C`| 1480 | `WL_TEXT.C` | 859 |
| `WL_PLAY.C` | 1472 | `ID_US_1.C` | 755 |
| `WL_AGENT.C`| 1421 | `WL_SCALE.C`| 733 |

### Naming convention

The tree follows the **id engine convention** used across id's early games:

* **`ID_*`** — reusable engine modules shared with id's other titles:
`CA` (cache), `VL`/`VH` (video low/high), `IN` (input), `PM` (page
manager), `MM` (memory manager), `SD` (sound), `US` (user/UI). Primarily
the work of **John Carmack** (renderer, cache) and **Jason Blochowiak**
(sound, input, paging, user).
* **`WL_*`** — Wolfenstein-specific gameplay, rendering, and UI, by Carmack
(renderer) and **Dave Taylor** / **John Romero** (gameplay, actors, menus).

This portable/game split is exactly what let the engine be lifted into later
id titles.

## 4. Build system

The code targets **Borland C++ 3.0** for 16-bit DOS. `WOLF3D.PRJ` (with the
older `WOLF.IDE` / `WOLF.DSW`) drives the Borland toolchain; `GO.BAT` and the
Borland rules file `RULES.ASI` set the assembler/compiler rules. The idiomatic
build is:

```bat
CD WOLFSRC
GO.BAT
```

> **This documentation adds no build.** It only describes the existing one.
> Do not expect to compile this without a vintage Borland C 3.0 toolchain and
> a copy of the retail Wolfenstein/Spear data files (the executable needs game
> data it does not ship with — see `README.rst`).

The `IsA386` global (`WL_MAIN.C:47`, detected at `WL_MAIN.C:251-261`) selects
386-specific fast-path integer math at runtime, falling back to a software
path on a 286.

## 5. Runtime architecture

### 5.1 The main loop

The boot path:

1. `main()` — entry, `WL_MAIN.C` (calls follow below).
2. `InitGame()` (`WL_MAIN.C:1145`) — configures memory (MM/PM), video,
audio, input; builds trig tables via `BuildTables()` (`WL_MAIN.C:586`,
called at `WL_MAIN.C:1233`); loads graphics; plays the signon screen.
3. `DemoLoop()` (`WL_MAIN.C:1411`) — the front-end carousel: title, credits,
and attract-mode demos, until the player starts a game. Called at
`WL_MAIN.C:1612`.
4. `GameLoop()` (`WL_GAME.C:1238`) — per-game driver: sets up the level
(`SetupGameLevel`, `WL_GAME.C:625`), draws the play screen
(`DrawPlayScreen`, `WL_GAME.C:868`), then repeatedly calls `PlayLoop()`.
5. `PlayLoop()` (in `WL_PLAY.C`) — the per-tick gameplay driver: polls
controls, moves the player, ticks every actor's state machine, then calls
`ThreeDRefresh()`.
6. `ThreeDRefresh()` (`WL_DRAW.C:72`, defined near the end of `WL_DRAW.C`) —
the raycaster: `TransformActor` (`WL_DRAW.C:210`), the wall cast with
`HitVertWall`/`HitHorizWall` (`WL_DRAW.C:477` / `:550`), then
`DrawScaleds()` (`WL_DRAW.C:1054`) for sprites and the player weapon.

Sound plays asynchronously: `ID_SD.C` installs an IRQ-driven callback that
refills the AdLib / Sound-Blaster / PC-speaker output each timer tick.

### 5.2 Data flow

```
┌───────────────────────────────────────────────┐
│ main → InitGame → DemoLoop (WL_MAIN.C) │
└───────────────────────┬───────────────────────┘
┌───────────────────────────────────────────────┐
│ GameLoop (per game) (WL_GAME.C) │
└───────┬───────────────┬───────────────┬───────┘
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌────────────────────┐
│ ID_IN.C │ │ WL_PLAY.C │ │ ThreeDRefresh │
│ input polling│ │ PlayLoop │ │ (WL_DRAW.C + asm) │
└──────────────┘ └──────┬───────┘ └─────────┬──────────┘
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ WL_STATE/ACT1/ACT2 │ │ DrawScaleds │
│ actor state ticks │ │ (WL_SCALE.C + asm) │
└─────────┬──────────┘ └─────────┬──────────┘
▼ ▼
┌───────────────────────────────────────────────┐
│ ID_VL.C / ID_VH.C Mode-X / VESA page flip │
└───────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────┐
│ ID_CA.C asset cache — Carmack RLE + Huffman decompression │
│ ↑ ID_PM.C (EMS paging) ↑ ID_MM.C (near/far heap) │
└───────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────┐
│ ID_SD.C AdLib + Sound Blaster + PC speaker (ID_SD_A.ASM) │
└───────────────────────────────────────────────────────────┘
```

The whole thing is a **single-threaded polling loop**. The only concurrency
is BIOS/hardware interrupt service routines the engine installs — the timer
IRQ, its own keyboard ISR (`ID_IN.C`), and the sound-card DMA/IRQ path
(`ID_SD.C`). There are no OS threads.

### 5.3 Subsystem map

| Subsystem | Primary files | Guide |
|-------------------------------|--------------------------------------------------------|-------|
| Engine core & types | `WL_MAIN.C`, `WL_DEF.H` | [game-logic](systems/game-logic.md) |
| 3-D raycaster | `WL_DRAW.C`, `WL_DR_A.ASM` | [rendering](systems/rendering.md) |
| Scaled-shape renderer | `WL_SCALE.C`, `WL_AGENT.C` | [rendering](systems/rendering.md) |
| VGA Mode-X driver | `ID_VL.C`, `ID_VL_A.ASM`, `ID_VL.H`, `ID_VL.EQU` | [rendering](systems/rendering.md) |
| VESA/hi-color driver | `ID_VH.C`, `ID_VH_A.ASM`, `ID_VH.H` | [rendering](systems/rendering.md) |
| Actor AI / state machines | `WL_STATE.C`, `WL_ACT1.C`, `WL_ACT2.C` | [actordata](systems/actordata.md) |
| Game loop, save/load, intermissions | `WL_GAME.C`, `WL_PLAY.C`, `WL_INTER.C` | [game-logic](systems/game-logic.md) |
| UI / menus / text | `WL_MENU.C`, `WL_TEXT.C`, `ID_US_1.C` | [ui-menu](systems/ui-menu.md) |
| Input polling | `ID_IN.C`, `ID_IN.H` | [input](systems/input.md) |
| Audio | `ID_SD.C`, `ID_SD_A.ASM`, `ID_SD.H`, `ID_SD.EQU` | [audio](systems/audio.md) |
| Asset cache & decompression | `ID_CA.C`, `ID_CA.H` | [file-cache](systems/file-cache.md) |
| Memory & page managers | `ID_MM.C`, `ID_PM.C` | [game-logic](systems/game-logic.md) |
| Debug overlay | `WL_DEBUG.C` | [game-logic](systems/game-logic.md) |
| Build / tools | `WOLF3D.PRJ`, `GO.BAT`, `RULES.ASI`, `JABHACK.ASM`, `*.EQU` | [tools-build](systems/tools-build.md) |

## 6. The fixed-point model

Wolfenstein 3-D predates cheap consumer floating point. Every position,
velocity, and rotation in the renderer is a **16.16 signed fixed-point
integer**, declared `typedef long fixed;` (`WL_DEF.H:480`).

| Symbol | Value | Meaning |
|---------------|----------------|--------------------------------------------------|
| `fixed` | `long` | 32-bit, 16.16 signed fixed-point |
| `PI` | `3.141592657` | Used only when generating trig tables |
| `GLOBAL1` | `(1l<<16)` | "1.0" in fixed-point (`0x10000`) |
| `TILEGLOBAL` | `GLOBAL1` | one tile == 1.0 |
| `TILESHIFT` | `16` | right-shift converting fixed → tile integer |
| `ANGLES` | `360` | angle units in a full turn (must divide by 4) |
| `FINEANGLES` | `3600` | sub-angle resolution for sine/cosine LUTs |

Trig tables (`sintable`, `costable`, `finetangent`) are precomputed once in
`BuildTables()` (`WL_MAIN.C:586`); the hot path uses table lookup only, never
`sin()`/`cos()`.

## 7. The coordinate system

* Maps are **64 × 64 tiles**; a tile is 64 × 64 units, so `TILEGLOBAL` fixed
units span one tile.
* `MAPSPOT(x,y,plane)` (`WL_DEF.H:35`) indexes a map plane:
`*(mapsegs[plane] + farmapylookup[y] + x)`.
* `viewx,viewy,viewangle` — camera position (fixed) and facing (`ANGLES`).
* Walls are **axis-aligned** (never 45°). The cast is a DDA over horizontal
and vertical tile intercepts: `HitVertWall` and `HitHorizWall`
(`WL_DRAW.C:477` / `:550`). Because every wall is exactly one tile thick,
the raycaster is *tile-aligned* — which is why the game has no angled walls.

## 8. Fixed limits (gotchas)

The engine sizes its tables at compile time:

* `MAXACTORS = 150` (`WL_DEF.H:49`) — live enemies/objects per map.
* `MAXSTATS = 400` (`WL_DEF.H:50`) — static objects (lamps, treasure).
* `MAXDOORS = 64` (`WL_DEF.H:51`) — sliding doors per map.

Bumping any of these ripples into the cache-chunk budget in `ID_CA` /
`ID_PM`. Doors are hard-coded directional variants in `WL_ACT1.C`; adding a
new door type needs both new state-machine entries and new graphics. There is
no multithreading — the interrupt handlers are the only concurrency.

## 9. Where to start reading

1. `WL_MAIN.C` — `main` → `InitGame()` (1145) → `BuildTables()` (586).
2. `WL_GAME.C` — `GameLoop()` (1238) and how it dispatches `PlayLoop()`.
3. `WL_DRAW.C` — `ThreeDRefresh` → `TransformActor` (210) →
`HitVertWall`/`HitHorizWall` (477/550) → `DrawScaleds` (1054).
4. `ID_CA.C` — `CAL_CarmackExpand` (609), `CAL_HuffExpand` (418),
`CA_LoadFile` (347).
5. `ID_US_1.C` + `WL_TEXT.C` — UI/text rendering and IRQ-driven input.

See [`README.md`](README.md) for the full documentation index.
64 changes: 64 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Wolfenstein 3-D — Documentation

An architecture reference for the original 1992 Wolfenstein 3-D source code in
`WOLFSRC/`. Written by reading the source directly and cross-checking each
claim against `WOLFSRC/<file>:<line>` at the tip of `main` (`778abe0`).

## Start here

* **[`ARCHITECTURE.md`](ARCHITECTURE.md)** — top-level overview: what the code
is, directory layout, main loop, data-flow diagram, fixed-point model,
coordinate system, build system, and where to start reading.

## Per-subsystem guides — `systems/`

Each guide explains what a subsystem does, which files it owns, and how it
talks to the rest of the engine.

* [`systems/rendering.md`](systems/rendering.md) — raycaster, scaled-shape
renderer, VGA Mode-X and VESA drivers, view setup.
* [`systems/audio.md`](systems/audio.md) — AdLib (OPL2), Sound Blaster DAC,
PC speaker, and the IRQ-driven mixing callback.
* [`systems/input.md`](systems/input.md) — keyboard ISR, mouse, joystick, and
the control-abstraction layer.
* [`systems/game-logic.md`](systems/game-logic.md) — main loop, level setup,
save/load, memory manager, page manager, debug overlay.
* [`systems/file-cache.md`](systems/file-cache.md) — chunk-based asset cache,
Carmack RLE + Huffman decompression, VGAGRAPH/VSWAP/audio file I/O.
* [`systems/ui-menu.md`](systems/ui-menu.md) — front-end menus, in-game text
pager, font rendering.
* [`systems/actordata.md`](systems/actordata.md) — actor structs, AI state
machines, projectiles, and hitscan.
* [`systems/tools-build.md`](systems/tools-build.md) — project files, `GO.BAT`,
`RULES.ASI`, the JAB HACK patcher, and the asm `EQU` tables.

## Per-file reference — `files/`

A page per major source file, each listing the file's role, its public
functions with a one-line summary, and its key globals. Covers all `WL_*.C`
and `ID_*.C` engine modules.

## Reference indexes — `reference/`

Exhaustive lookup tables:

* [`reference/functions.md`](reference/functions.md) — functions grouped by
file, with signature and role.
* [`reference/constants.md`](reference/constants.md) — `#define`s, `enum`s,
and assembler equates, grouped by semantic family.

## How to read this

**First time:** read `ARCHITECTURE.md` end to end, pick a subsystem from the
§5.3 table, open its `systems/<name>.md` page, then drop into `files/` for the
specific source file.

**Hacking on the code:** find the file you're editing, read its `files/<name>.md`
page for what's already there, and cross-reference `reference/constants.md`
for any constant you encounter.

## Status

This documentation describes the source as released; it does not modify,
build, or run any code. It was checked against the tree at git revision
`778abe0`.
Loading