Get AI summaries of any video or article — Sign up free
Doom In TypeScript Types??? thumbnail

Doom In TypeScript Types???

The PrimeTime·
5 min read

Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Doom was run by encoding a complete virtual machine inside TypeScript’s type system and using the type checker as the execution engine.

Briefing

Running Doom inside TypeScript’s type system is no longer a punchline—it’s been built, booted, and made to render the first frame using types as a full virtual machine. The core achievement is an isolated “computer” whose components—RAM, caches, execution contexts, and tooling—are encoded entirely in TypeScript types, then evaluated by the TypeScript type checker to produce pixel output. The payoff is both technical and symbolic: it demonstrates that TypeScript’s type system can be pushed far beyond typical static checking into something that behaves like computation.

The project’s scale is staggering. The first frame required roughly 177 terabytes of generated type data after extreme optimizations, down from an initial estimate of 1.25 petabytes of type work. Getting there took 12 days of continual runs, with the type checker sustaining about 20 million type instantiations per second. The author also hand-wrote 12,364 tests, built custom tooling, and learned multiple new languages to overcome the gaps between “types as types” and “types as an execution substrate.”

Mechanically, the system treats TypeScript type evaluation as the engine cycle. The virtual machine implements the 116 WebAssembly instructions Doom needs, with the logic alone totaling about 18 million instructions. Each type-check pass corresponds to a machine state, and the final output becomes a TypeScript object where each value maps to a line of pixels—128,000 pixels for a 320×200 frame. Keyboard input is possible, but the emphasis is on the hard constraint: everything required for Doom—maps, textures, sprites, sounds, enemy AI, weapons, items, physics, and text—must be encodable in types.

To make this feasible, the usual safety rails of TypeScript had to be removed or raised. The compiler’s limits on excessive stack depth, deep type instantiation, union size, and recursion were lifted, and the author also increased the runtime stack limit on their machine. The cost was severe: type checking could consume around 100 GB of memory, syntax highlighting barely worked with files up to 1 GB, and language-server integrations and linters crashed. Even small mistakes—like misspelling an import—could trigger infinite loops that eventually exhausted heap memory and crashed the system.

The build also required low-level engineering normally handled by runtime features: a type-level garbage collector, L1 cache simulation, dead code elimination, a global value stack with stack-underflow protection, and dynamic dispatch tables. Linear memory is represented as a huge unsortable object, and binary arithmetic is implemented using two’s-complement numbers stored in string literals—plus reverse-iteration workarounds because TypeScript can only iterate strings from left to right. Doom’s engine uses only 64-bit and 32-bit integers, neither signed nor unsigned, forcing careful handling of bit-level semantics.

Beyond the technical feat, the discussion frames the effort as a “side quest” that teaches more than routine work: the project’s value comes from repeatedly jumping hurdles, learning compiler and VM internals from scratch, and refusing to accept the initial assumption that Doom couldn’t run in types. The creator signals that deeper technical breakdowns are coming in follow-up videos, with one aimed at a higher-level condensed journey and another focused on the super-technical details.

Cornell Notes

Doom has been made to run using only TypeScript’s type system, by building a full virtual machine out of types and letting the TypeScript type checker execute it. The system implements the WebAssembly instruction set Doom needs, then converts the final type-evaluated result into pixel data for a 320×200 frame. Achieving the first frame required extreme scale—about 177 terabytes of generated type work after optimization, with type checking sustaining roughly 20 million type instantiations per second for 12 days. The project required removing or raising TypeScript compiler limits (recursion, stack depth, union size, and instantiation complexity), at the cost of massive memory use and fragile tooling. The result is a proof that TypeScript types can function as a computation engine, not just static analysis.

How does the project turn TypeScript’s type checker into a “computer” capable of running Doom?

It encodes a virtual machine entirely in TypeScript types. The type checker evaluates those types as if they were machine instructions, with the VM implementing the 116 WebAssembly instructions Doom needs. Each type evaluation corresponds to a machine state, and the final output is produced as a TypeScript object whose values map to pixel lines—128,000 pixels total for a 320×200 frame.

What scale and performance numbers are cited for getting the first Doom frame?

Initial calculations suggested up to 1.25 petabytes of type work, but extreme optimizations reduced it to about 177 terabytes. The first frame arrived after 12 days of continually running the type checker, with sustained throughput around 20 million type instantiations per second. The author also describes a long, brutal process: 18-hour days early on and a year-long journey overall to reach the moment.

What kinds of TypeScript “guard rails” had to be changed to make this possible?

The compiler’s limits were modified: the maximum size for unions was raised, truncation limits were removed, and the recursion limit was eliminated. Stack depth limits and accessibly deep (potentially infinite) type instantiation limits were also lifted. On the machine side, the runtime stack limit was increased too. The tradeoff was instability: type checking could consume ~100 GB of memory, and small errors could trigger infinite loops that eventually crashed the heap.

Why is binary math and memory handling so hard in this setup?

The engine is implemented with binary two’s-complement numbers stored in string literals, and TypeScript only iterates strings from the left. That forces reverse-iteration techniques for binary algorithms. Additionally, the engine avoids typical runtime structures inside the engine logic—no arrays/objects/strings/booleans—so memory and control flow must be represented using types and binary encodings.

What “systems” had to be rebuilt inside the type-level VM to run a full game?

The project had to recreate core runtime capabilities at the type level: a type-level garbage collector, an L1 CPU cache simulation, dead code elimination, a global value stack with depth counting for stack-underflow protection, dynamic dispatch tables, and module-level globals with labeled go-tos. Linear RAM is represented as a huge unsortable object, and the entire Doom asset pipeline (maps, textures, sprites, sounds, AI, weapons, items, physics, and text) must be encodable in types.

Review Questions

  1. What does it mean, concretely, for TypeScript types to act like a virtual machine in this project?
  2. Which TypeScript compiler limits were removed or raised, and what failure modes resulted from those changes?
  3. How does the project produce pixel output from type evaluation, and what frame resolution is referenced?

Key Points

  1. 1

    Doom was run by encoding a complete virtual machine inside TypeScript’s type system and using the type checker as the execution engine.

  2. 2

    The VM implements the 116 WebAssembly instructions Doom needs, with the logic totaling about 18 million instructions.

  3. 3

    The first rendered frame required extreme type generation—about 177 terabytes after optimization—achieved over 12 days with roughly 20 million type instantiations per second.

  4. 4

    To make evaluation possible, the TypeScript compiler’s recursion, stack-depth, union-size, and deep instantiation limits were removed or raised, and machine stack limits were increased.

  5. 5

    The approach is fragile: tooling like syntax highlighting and language servers struggled with very large files, and small mistakes could trigger infinite evaluation and heap exhaustion.

  6. 6

    Running Doom required rebuilding runtime-like systems at the type level, including garbage collection, caching, dead code elimination, dispatch tables, and a type-level memory model.

  7. 7

    The project’s broader takeaway emphasizes learning through out-of-depth side quests that force deep dives into compilers and virtual machine internals.

Highlights

Doom’s first frame was produced by evaluating TypeScript types—pixel output emerges from the final type-checked result.
The build required lifting TypeScript’s recursion and complexity limits, trading correctness and stability for the ability to compute at type-check time.
The VM implements 116 WebAssembly instructions and turns the evaluated machine state into a 320×200 frame (128,000 pixels).
Binary arithmetic and memory behavior are implemented using two’s-complement numbers stored in string literals, with reverse-iteration workarounds due to TypeScript’s string iteration rules.

Topics

  • TypeScript Types
  • Doom
  • Type-Level Virtual Machine
  • WebAssembly Instructions
  • Compiler Limits

Mentioned

  • Zach Overlow
  • Josh Goldberg
  • Casey
  • TSC
  • L1
  • TI
  • AI
  • VM
  • HTML
  • HTM
  • TTS
  • RAM
  • CPU
  • L1 cache