The Scale Problem
The programs from the previous chapter — the counter, the button, the tone, the game loop — total about 200 bytes of assembly. Each line is an explicit instruction: load this register, store to this address, branch if this flag. Writing them requires tracking the full machine state in your head at all times: what is in the accumulator, what is in X, which flags the last instruction set, which zero-page addresses are in use.
200 bytes is manageable. 2 kilobytes is harder. 20 kilobytes is the point at which experienced assembly programmers begin to doubt themselves. Combat (1977) is 2KB. Adventure (1980) is 4KB. Space Invaders (1980) is around 4KB. These are programs where assembly remained tractable because the machine demanded it — the 2600's 128 bytes of RAM, the three-instruction-per-scanline TIA constraint, left no room for a compiler's overhead. The hardware was the constraint, not the programmer.
When the hardware got larger, the calculus changed.
The original Doom[1] (1993) is hundreds of kilobytes of compiled code. A small fraction of it — the innermost rendering loops — was hand-written assembly. The rest was C. Not because C was faster. Because maintaining that volume of assembly is not a tractable problem for a team on a deadline. The correctness cost alone would have slipped the ship date by years.
C exists because of that cost. Dennis Ritchie[2] designed it in 1972 at Bell Labs for Unix — a portable systems language that compiled to efficient machine code and let a programmer think in terms of functions and data structures rather than registers and flags.


The compiler's job was to translate; the programmer's job was to be correct. By the mid-1990s, every major console SDK was C-based: Sony's PS1 dev kit, Nintendo's tools for the N64, Sega's Saturn and Mega Drive development environments. The hardware had grown past the point where assembly was feasible as the primary language, and C filled the gap.
This chapter has no implementation pages. Read it and continue to f04 — the next chapter is where C programs begin, and understanding why C exists is what makes that chapter make sense.
The Trade
I understood this concretely when I finished the game loop from the previous chapter. Not in the abstract — I had read the argument before. I understood it when I realised the game loop was about 200 bytes and I could not easily add a second player without mentally retracing every register use, every branch target, every zero-page address I had already claimed. The program was brittle. Not broken — brittle. Adding anything required re-verifying parts I had already verified.
C makes a specific trade. You give the compiler some amount of performance — modern compilers recover most of it through optimisation, but not all of it. In exchange, you get: named variables instead of registers, functions instead of subroutine jump tables, a type system that catches a class of errors at compile time, and the ability to think about what your program does rather than the mechanics of how the CPU executes it.
The trade is not for every context. The innermost loop of a raycaster is still faster in hand-tuned assembly, and there are domains where that gap matters. But for programs larger than a few kilobytes — which is anything worth shipping — the productivity cost of assembly exceeds the performance cost of C by a margin that only grows as the program grows.
The 6502 breadboard you just programmed is the same chip that is inside the Atari 2600. The C you are about to learn is what id Software used to build Doom on the machines that replaced it. The distance between those two things is what this curriculum covers.
The Project
There is no project brief for this chapter. No code to write.
The Tester
Write one paragraph — in your own words, without referring to the text — explaining why you are not going to keep writing assembly as the primary language for anything larger than a page of code. If you can write it, you have understood it.
The Companion Repo
No companion repo for this chapter.