Sean Barrett wrote the core rendering technology for Thief: The Dark Project, and years later wrote down how it worked — Quake’s software renderer had been documented exhaustively by Michael Abrash, and Thief’s never was. The game shipped in 1998, purely software-rendered, and the same renderer, modified by others for hardware, went on to power System Shock 2 and Thief 2.

The constraints forced different answers to the same problems Quake solved:

  • No z-buffer, no GPU. The world drew back-to-front with a painter’s algorithm, with objects interleaved so they occluded walls correctly.
  • Visibility from portals, evaluated at runtime. Open space was divided into convex cells joined by portals, following Seth Teller’s 1992 dissertation. Quake precomputed a potentially visible set offline; Thief traversed portals every frame.
  • “Bounding octagons” instead of exact frusta. Each portal was projected to 2D and bounded by a normal box plus a 45-degree rotated box. Intersecting those cheap 2D shapes told the engine which slice of the next cell was visible — and doubled as the clipping region that cut overdraw.
  • Culling as a side effect. Because traversal was per-frame and tight, Thief rejected side rooms and objects that Quake’s corridor-level visibility would have kept drawing.
  • The sorter was brutal. Objects and world polygons shared a single back-to-front order. Barrett calls it “the most complicated painter’s algorithm sorter I’ve every heard of,” and says he had to write a small mathematical proof for part of it. Some configurations have no valid order at all — a torch in a niche that partially occludes, and is occluded by, the same wall — so the engine fell back to re-rendering objects once per cell with dynamic clip planes, paying vertex and skinning cost each time.

The mistakes are the most useful part. Barrett built the level CSG on a solid BSP tree, which let early brushes introduce split planes running across the whole level and surface epsilon failures in unrelated geometry — hence the Thief editor’s reputation for a level compiling fine until you touched something else. Intersecting brushes directly, with a spatial structure used only to accelerate overlap queries, is the layering he says he would choose now. He is equally direct that the portal analysis was already “limping” from its own overhead by the time the last game shipped with it.

Real-time occlusion culling still works this way in engines that use portals, but the specific tradeoff no longer exists on modern hardware: every pixel you refuse to give a depth value to has to be paid for somewhere else, in sorting, repeated vertex work, and special cases.