Section: Source patterns do not survive as labels.
A C++ programmer may write a class named EnemyFactory, a State interface, and an Observer list. The processor never sees those friendly names. After compilation it sees addresses, loads, stores, comparisons, and calls.
That sounds discouraging, but design patterns still leave shapes in memory and code. A vtable is a row of function pointers. A factory allocates an object and initializes it. An observer loop walks a collection and calls similar functions. One shape alone is common in unrelated code, but several that fit together usually point to one pattern. 🧩
When debug symbols exist for a build, they give the real names and layouts, so they can check a layout recovered from the version-pinned executable.
Section: A pattern name communicates intent.
A design pattern names recurring roles and expected changes, not just dependency removal. Similar machine-code shapes can have different intent.
For example, both State and Strategy may delegate through a function table. State usually represents behavior that changes as an object moves through a lifecycle. Strategy usually selects one replaceable calculation or action, such as how a weapon chooses its aim point. Observed transitions and call sites matter more than the table shape.
In your own tool, use a pattern after repeated change exposes a real problem: duplicated lifecycle code, tangled ownership, or features that cannot be tested separately. Do not add a factory, observer, or trait hierarchy only because the name sounds professional. A smaller function or enum is often the clearer design.
A pattern name is a likely role, not a recovered source name or proof of intent.
Section: Programming paradigms: ways to organize a purchase.
A programming paradigm is a style for organizing instructions and responsibilities. It does not change what a purchase means. Ada still starts with 100 gold, pays 25 if she can, and ends with 75. The styles answer a different question: where should the affordability check and subtraction live, and how should other code use it?
The examples below revisit the guarded purchase from Lesson 1.4. For each isolated fragment, the caller starts with mutable gold of 100 and cost of 25. A minimal player record and the shared calculation are:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
This compares source organization. It does not imply that a Rust record has the C++ virtual-table layout examined later in this lesson.
Section: Procedural: follow the steps.
The direct-update version is procedural. It says how to update the variable, in order:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
That is often the clearest way to begin. It becomes awkward only if every screen and bot copies the same purchase steps, because a later change to the affordability check would have to be repeated everywhere. Our named function solves that duplication.
Narration: If ten screens each copy the purchase steps, one change to the affordability check means ten edits, and missing one leaves the screens disagreeing. A named function keeps a single copy.
Section: Object-oriented: keep behavior with its data.
A Player record already keeps related data together. A method can put the purchase behavior beside that record, so ada.buy(25) reads as an action on Ada. The Rust Primer shows the same organization with impl Runner and explains why a method receives editable access to its runner. Rust methods do not require class inheritance; Lesson 3.8 later examines how a compiled C++ game’s classes and methods appear in memory.
Section: Functional: make the result visible.
Our spend_if_possible function illustrates a functional style. Given 100 and 25, it returns 75. It does not secretly reach out and change Ada. The caller decides whether to store the answer:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
That makes the calculation easy to check with different inputs. A larger program can mix this style with state-changing methods; the two are useful for different jobs.
Section: Event-driven: respond when something happens.
A real game stays open instead of running main once and closing. A Buy click can produce an event, and the interface or game engine can call a registered handler in response:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
This shows the handler’s job, not a complete window setup. A game also updates and draws while no one clicks. Lesson 6.9 explains Windows messages and game loops; Lesson 4.9 compares events with checking for changes on a schedule. For now, notice that the event-driven style decides when the purchase calculation runs.
One game can use all four styles: an event calls a handler, which asks a player method to update state using a small calculation. A paradigm is a way to organize work, not a team you must join forever.
Section: Start from what the code does, not class names.
Suppose three heap objects all begin with the same pointer. That pointer leads into a read-only section containing executable addresses. Several call sites do this:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
What this code shows is small:
Objects at these addresses share a function table, and slot four receives the object address.
Slot four is at +hexadecimal 2 0 because each slot holds one 8-byte function address on 64-bit Windows, and hexadecimal 2 0 is 2 times 16 equals 32 bytes, which is 32 divided by 8 equals 4 slots past slot zero.
Predict first
A 32-bit build of the same game makes the call as call dword ptr [e a x+20h]. A 32-bit process stores each function address in 4 bytes.
Which slot of the table does this call use?
Answer: Slot 8. hexadecimal 2 0 is still 32 bytes, but each slot is now 4 bytes wide, so 32 divided by 4 equals 8 slots past slot zero.

The code does not show the source class name, the inheritance tree, or the method name. Two facts can be checked directly: offset hexadecimal 0 0 points into read-only module memory, and the entries of that table point to executable module pages. Together they match how C++ compilers implement a vptr, so offset hexadecimal 0 0 is very likely one. A name such as Actor is a label chosen from the object’s behavior; nothing in the binary says what the original class was called.
In brief: Objects sharing a first pointer to a table of function addresses is a strong sign of virtual methods, but it never gives you the class name.
Section: Five patterns and the clues they leave.
Section: Singleton or global manager.
A singleton is one shared object. Look for many functions loading the same global pointer before accessing fields:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
One global pointer alone does not make a singleton; it might be a cache or the current scene. A singleton is allocated once during initialization, reused by many unrelated call sites, and released at shutdown.
Section: Factory.
A factory chooses which concrete object to create. A common compiled shape is:
inspect a type ID;
choose an allocation size;
call an allocator;
call one of several constructors;
return a common base pointer.
A switch followed by several constructor calls is more useful than the word “factory.” Follow each constructor and compare its first vptr write and field initialization.
Section: State or strategy.
State and strategy patterns both replace one behavior with another. Memory may contain a pointer to a small behavior object or a function table. The owner delegates work to it each update.
Each arrow is a moment when the owner’s behavior pointer changes. If it changes when an enemy moves from idle to chase to attack, “state” is a useful name. If it changes when a weapon changes its aim-point calculation, “strategy” may fit better. The bytes do not care which textbook word you choose; behavior over time is the deciding clue.
Section: Observer.
An observer system keeps a collection of listeners and calls them when an event occurs. Look for one loop that:
walks a vector, list, or handle table;
passes the same event record to each element;
makes a direct or virtual call;
tolerates an empty collection.
Do not confuse this with an ordinary entity update loop. An observer loop usually starts from an event or notification function and passes similar event data to many unlike objects.
Alternative: Think of a group chat: one message goes out and every subscriber gets the same text. An update loop is more like a teacher visiting each desk in turn.
Section: Component or entity-component system.
Composition stores behavior in separate components rather than one deep inheritance tree. Useful clues include:
an entity ID used to index several pools;
type IDs mapped to separate arrays;
bit masks describing which components an entity owns;
tightly packed arrays of positions or health values;
systems that loop over one component type at a time.
If position records are contiguous while unrelated entity fields live elsewhere, do not force them into one giant Player structure. The game may be data-oriented.
Section: Constructors are layout witnesses.
A constructor often writes the vptr and then initializes fields:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The last store starts at +hexadecimal 1 0 and writes a qword—eight bytes—so it touches offsets +hexadecimal 1 0 through +hexadecimal 1 7; +hexadecimal 1 8 is the first byte after the store. That gives a minimum object size of at least hexadecimal 1 8 bytes, but alignment may make the allocation larger. Compare the allocator’s requested size, constructor stores, destructor reads, and ordinary methods. No single function tells the whole story.
Narration: Bytes a constructor never writes are unknown, not zero. After an allocator reuses memory, they may still hold leftovers from an earlier object.
Section: One object can have several useful identities.
Lesson 3.8 placed a single-inheritance base part at the start of the derived object. Multiple inheritance makes the map less tidy:
Multiple inheritance can add more than one base subobject and more than one vptr. A method may even adjust this before using it.
When inspecting object-oriented code, separate three questions:
Allocation identity: which complete block of memory was created and must eventually be released?
Subobject view: which base-class-shaped part does this particular pointer address?
Dynamic type: which most-derived implementation will virtual dispatch choose at this moment?
These answers can differ. A pointer passed to a base-class method may point a few bytes into the complete allocation. A thunk — a tiny compiler-generated stub that fixes something up and then jumps to the real function — may adjust this before jumping to the real method. Subtracting every observed field from that adjusted pointer can produce negative offsets and a broken layout even though the pointer is valid for its particular base view.
When two candidate bases always differ by the same small constant, they are usually two views of one object. The thunk or call site that adds the constant shows where the adjustment happens, and the constructor, which writes both vptrs, shows how the two views sit inside one allocation.
RTTI stands for run-time type information: extra data the compiler emits so that code can ask what class an object really is while the program is running. It is what makes dynamic_cast possible.
When it is present it can expose type-descriptor clues near virtual tables, sometimes including a readable class name. That name tells you what the class was called and nothing more: release builds can strip RTTI entirely, and a name does not tell you where any of the class’s fields sit.
Section: A repeatable reconstruction method.
Collect several live object addresses from the exact local build.
Group objects by their first pointer and validate that it leads to executable function entries.
Find code that writes that first pointer; it may be a constructor.
Find code that allocates the object and read the requested size.
Label every repeated field access as field_08, field_10, and so on.
Watch which actions change each field.
Find virtual call slots and trace their arguments and side effects.
Name a pattern only when several of these shapes agree.
Compare with source or debug symbols as a final answer key.
The goal is not to make the class diagram look impressive. The goal is to build a model that correctly predicts what the program will do next.
Section: Common mistakes.
❌ Naming a class from one interesting string.
✅ Naming fields provisionally, such as field_08, until their behavior is clear.
❌ Treating every first pointer as a vptr.
✅ Checking page permissions and several table entries.
❌ Assuming inheritance because two objects share fields.
✅ Checking vtables, constructors, conversions, and call behavior.
❌ Forcing an entity-component design into one C-style struct.
✅ Letting access patterns reveal where each component really lives.
Next, we will add ownership and lifetime clues so the recovered model explains not just what an object is, but who keeps it alive.