Skip to content

Patterns you will meet in game code ​

Most games are built from the same few parts. When you recognise a part, you know what to look for next and which details an exact reimplementation must copy.

Start with the kind of game. Who drives (the game or the player), the families of games and their rule sets, and the systems they share are in the taxonomy and Who drives. This page describes the parts themselves. To capture them from the running original, see Observing the original.

The main loop ​

WinMain → engine set-up → a loop that, each frame, reads input, updates the game, draws, and presents. Find it from the entry point, or from PeekMessage/GetMessage (the Windows message pump sits inside it) and the present call (IDirect3DDevice9::Present, IDXGISwapChain::Present, SwapBuffers).

  • Fixed step: the update runs at a constant rate (often 30 or 60 per second) and an accumulator decides how many steps a frame runs. Game speed settings usually multiply the number of steps, not the step size. Reimplement the accumulator exactly, because the number of steps per frame changes the game's results.
  • Variable step: the update takes the frame time (dt). Exact replay is then impossible without recording dt; tests feed a fixed dt.
  • Loop objects: engines often have a stack or state machine of loops (menu, loading, game), each with load, start, update, draw, input and stop methods in a vtable.

The order of systems inside one update changes the results, because a later system sees the changes an earlier system made in the same step. Record the order early, in a table, from the update function's callees.

Random number generators ​

Random number generators are usually small and self-contained. Emulate them first.

KindHow to recognise it
LCG (MSVC rand)state = state * 214013 + 2531011; return (state >> 16) & 0x7fff
LCG (glibc, Java, numerical recipes)multipliers 1103515245, 0x5DEECE66D, 1664525
xorshiftx ^= x << 13; x ^= x >> 17; x ^= x << 5 and variants
Mersenne Twistera 624-word state, the constant 0x9908B0DF, tempering masks 0x9D2C5680, 0xEFC60000
WELL, PCG, SplitMixconstants such as 0x5851F42D4C957F2D (PCG), 0x9E3779B97F4A7C15 (SplitMix)

Games often have several generators (one for the simulation, one for effects or sound) and may seed them from the clock. Find every instance and who draws from each. A draw from the wrong generator changes the results as much as a draw out of order. Helpers built on the generator (a float in [0,1), a range, a "tolerance" around a value) each have their own rounding; emulate them too.

Entities and components ​

  • Class hierarchies: Entity → Unit → Soldier, with behaviour in virtual methods. Recover the hierarchy from vtables (shared early slots) and RTTI.
  • Entity-component systems: an entity is an id or a small object holding a list of components; each component type has a type id (often a hash of its name) and a vtable with lifecycle slots (create, attach, update, serialize, destroy). Systems iterate components of one type in a fixed order. Keep that order in the reimplementation.
  • Handles: an index plus a generation counter, packed into 32 or 64 bits.

Resources and data ​

  • Packages: an archive (header, entry table, data), possibly compressed per entry. Entry names may be stored as hashes; the hash function is in the lookup code.
  • Type ids by hash: a CRC32 or FNV-1a of the class name, sometimes of the name in UTF-16. Compute candidates from strings in the binary and match them against the constants in the registration table.
  • Serializers: a Serialize(Stream&) or Load(Reader&) per class, base class first, with version checks. The same function often both reads and writes, depending on a flag in the stream.
  • Scripts and tables: rules may live in data (XML, JSON, Lua, custom binary tables) rather than code. Read the data before reimplementing a rule by hand.

Time ​

Games keep time as integer ticks (milliseconds or microseconds) or as float seconds, and the calendar of a simulation (days, seasons, years) is derived from it. Float time gathers rounding error. If the original keeps time as a float, the reimplementation must do the same.

Use the game's own calendar and its own names. A game's year may have twelve "months" that are seasons (Early Spring to Late Winter), or eight, or none; its tenth month is not October. Read the names from the game's string tables. Use them in docs, tests and examples.

Messages and text ​

Event logs, notifications and tooltips usually store a message type and its arguments (a person, a building, a number), and format the text from a string table when it is shown. Saves may keep only the formatted text. A reimplementation that keeps the arguments formats the text the same way; one that loads a save shows the stored text as it is. Text in the player's language comes from data, so never hard-code an English string where the original reads a table.

Saves ​

A save is the serialized world: often the same serializers as the data files, plus the random generator states, the clock, and object references written as indices or ids. Pointers between objects need a fix-up pass on load. Loading a real save is the best test of a reimplementation, because a save holds the state of every system.

Rendering and UI ​

  • Shaders ship compiled (DXBC for Direct3D 9-11, DXIL for 12, SPIR-V for Vulkan): disassemble them (fxc's /dumpbin, dxc -dumpbin, spirv-dis) to read the maths. Check constant buffer layouts against the CPU code that fills them.
  • UI is often custom code in older games and data-driven in newer ones (layouts in data files, a widget tree, events by id), with screens as loop objects. See Capturing the interface.
  • Frame capture tools (RenderDoc, PIX, apitrace) show the draw calls, their states and the constant values of a real frame. A capture is the fastest way to understand a rendering pass.

Networking (if any) ​

Messages are usually serialized with the same stream classes as saves. Lockstep games send only inputs and depend on exact determinism; client-server games send state. Never test against a live service without its owner's permission.

If you add lockstep multiplayer to a single-player game, look for state that belongs to one player but is read by the simulation: settings, unlocked achievements, difficulty options, the local clock, the window size. Two players with different values then compute different game states. Give every player the same values for a shared game (for example, count achievements from none in multiplayer). Check with digests that two players stay identical.

Code: Apache 2.0. Content: CC0 1.0. No game material: bring your own copy.