Abstract
Problem: What are the most common sources of frustration for programmers, particularly in game development, and how can they be mitigated?
Approach: Tim Cain draws on decades of experience (Fallout, Arcanum, and beyond) to categorize recurring frustrations and share war stories illustrating each.
Findings: The main frustrations fall into four categories: unimplemented third-party features, bugs in external code, UI programming complexity, and design changes. None of these can be fully predicted or prevented by production planning. Three coping strategies help: switching context, pair debugging, and rewriting with a different approach.
Key insight: Most programming frustrations are unforeseeable and unavoidable β no amount of production buffering can account for all of them, and crunch often results not from bad teams but from inherently unpredictable technical problems.
Unimplemented Third-Party Features
Third-party code β game engines, libraries, the STL β will sometimes have methods that are documented but entirely unimplemented. This isn't about bugs; the functionality simply doesn't exist despite the documentation implying it does.
The Arcanum DirectX Story
During Arcanum's development (a 2D game), Tim's team needed text processing functions in DirectX. The documentation implied they worked in 2D, but a buried note revealed the feature was "not implemented yet" and would come in a later revision. They waited three years. It never shipped β Microsoft deprioritized it because "everybody's going 3D." The team eventually worked around it using drop shadows for text rendering.
Bugs in Third-Party Code
Beyond unimplemented features, third-party code can contain actual bugs that are extraordinarily difficult to diagnose because you naturally assume the problem is in your own code.
The Watcom Realloc Bug
On Fallout, the team used the Watcom compiler for its flat memory mode (full 32-bit pointers, bypassing DOS's 64KB block limitation). About a year and a half into development, they started seeing mysterious memory overwrites β code writing well within allocated boundaries was somehow corrupting other memory blocks.
They traced it to realloc. Tim emailed Watcom support, who dismissed it as a user error. So he wrote a five-line proof-of-concept: malloc a thousand blocks, realloc them to bigger sizes, check for overlaps. It reproduced in under 20 seconds every time. He sent it back with a challenge: "Either tell me where my bug is or admit you have a bug in realloc." Within 48 hours, Watcom confirmed the bug.
Compiler Optimization Bugs
Tim notes this pattern isn't rare. He's frequently seen compilers break code through optimization flags β perfectly working code that runs slowly, then runs fast with optimizations enabled, but is now broken. These are essentially impossible to predict or budget for.
User Interface Code
Tim describes UI programming as consistently the most painful and frustrating code he's written across his entire career, from the 1980s to the 2020s. The frustration has three layers:
- Unimplemented methods in UI APIs (same as above)
- Methods that don't work as documented β subtle behavioral differences from the docs
- The "tiny change" problem β UI APIs are easy to use exactly as documented, but any deviation (transparent buttons, non-rectangular buttons) becomes extraordinarily painful
A one-day task can balloon into weeks because an artist says "I thought we agreed on round buttons" and the API makes that specific thing agonizing. Tim advises quadrupling time estimates for UI work.
Design Changes
Design changes come in two flavors, one more forgivable than the other:
Flavor 1: Mind Changes Before Testing
Designers write a design, you code it, and before it's even tested they change their mind. This is the more frustrating variant.
Flavor 2: Design Bugs Found in QA
The code is a flawless recreation of the design, but QA reveals the design itself has a bug. The design must change, and so must the code. This is more understandable but still leads to the same result.
The Spaghetti Code Spiral
Both flavors create the same downstream problem: repeated changes to initially clean code produce spaghetti code. Then a lead looks at it and asks "why did you write such horrible code?" β when the original code was clean and each change was made under time pressure without opportunity to refactor. This creates friction between programmers and production.
A Message to Production
Tim is direct: no production methodology can foresee and prevent all these frustrations. Design changes, third-party bugs, unimplemented APIs, UI complexity β these are inherent to software development. Large time buffers make estimates look absurdly padded ("two months for a two-day task?"), and even generous buffers won't catch things like a compiler bug in realloc.
This is why crunch happens even with good teams. It's not always management's fault, nor is it always preventable. Game development is half art β it's not all science.
Three Coping Strategies
Tim offers three practical approaches when you're stuck:
Switch Context
Walk away. Jump to another piece of code, go for a bike ride, do something else. You'll be surprised how often you come back a day or two later and immediately see the problem. Tim saw this constantly in engineering school β students couldn't find a missing semicolon in Pascal, but a fresh pair of eyes spotted it instantly.
Pair Debugging (Not Pair Programming)
Tim distinguishes pair debugging from pair programming. He's not a fan of two programmers writing code together, but he's a huge fan of bringing someone in when you're stuck debugging. Another programmer watching you step through code will often immediately spot what you've been blind to: "Oh β that's stomping on your memory" or "that's the wrong method, it doesn't check for capitalization."
Rewrite With a Different Approach
Sometimes the fastest path forward is rewriting from scratch β but critically, don't rewrite it the same way. Use a different algorithm, different data structures. Tim found he could sometimes rewrite code faster than he could debug it.
The Fallout Memory Manager
The best example: when the Watcom realloc bug was discovered, lead programmer Chris Jones didn't just wait for a fix. He rewrote the entire memory allocation system with a fundamentally different approach:
- One giant malloc at game start β all subsequent allocations happen within that block
- Handles instead of pointers β you lock a handle to get a usable pointer, then unlock it when done
- Defragmentation β any unlocked block can be moved, allowing memory to be coalesced on demand
This not only bypassed the realloc bug entirely, it also solved the massive memory fragmentation issues plaguing the DOS version of Fallout. A creative rewrite solved two problems at once.
References
- Tim Cain. YouTube video. https://www.youtube.com/watch?v=lwlRDhPZxLc