Game State and Invariants
A game's state changes through transitions. Invariants describe which states remain valid, while controls and detectors answer different parts of that promise.
Cheatsheet for Lesson 14.5
A one-page summary of “Game State and Invariants” you can keep beside your notes. Plain Markdown.
Lesson 14.5 — Game State and Invariants: cheatsheet
- Remember: A control limits a state change before it happens; a detector reports evidence that a condition may already have failed.
- Also useful: The health bar is a scoreboard copy; the simulation record is the scorekeeper. Changing or checking the display does not necessarily change the deciding value.
Key terms
- invariant: An invariant is a statement that must be true whenever the system is in a valid state.
- bypass: A bypass is a path that reaches an outcome without satisfying the control, the code that was supposed to guard it.
- authoritative: Exactly one copy is authoritative: the one whose value decides what happens next.
- Server authority: Server authority is the same rule at network scale: the server validates every client request against its own record, because anything a client computed can be altered on the client's machine before it is sent.
- trace: A trace is a list of events in the order they were logged, each saying what changed, when, and which observer saw it.
source:sourcenames the observer that produced the event.
Review
- Q: A heal command is checked against the health bar's cached value, then applied to the simulation's entity object. Why can an out-of-range heal still get through? A: The check read one copy and the change hit another, so the deciding copy went unchecked
- Q: Which of these is an invariant? A: An entity ID names at most one live entity in a snapshot
A Lua script, a controller input, and a network message can all ask a game to
change health. Lesson 1.3 used the rule 0 <= health <= max_health to decide
whether a player record made sense. Now imagine one of those requests arrives
when health is 5 and asks for 20 damage. The game should produce health 0,
not -15, and its displayed health bar should eventually agree. This chapter
asks where that rule is enforced, which copy of health the game trusts, and
how to notice when one route around the rule still changes the game.
The earlier chapters established how to recover an object layout and keep a live hook valid. Advanced game hacking now asks what the observed behavior means: record the decision and effect, then inspect integrity and environment checks and encoded values. The final control cases ask whether each check protects the authoritative state it claims to protect.
Four pieces of evidence keep this question answerable: the value and the address temporarily holding it, the exact game build, a timestamped sequence of state changes, and whether the tool observed state or changed it. The following model connects those pieces before looking for a control gap.
An invariant is a property that must stay true
Section titled “An invariant is a property that must stay true”An invariant is a statement that must be true whenever the system is in a valid state. Examples include:
- ammunition never becomes negative;
- an entity ID refers to at most one live entity in a snapshot;
- a patch is applied only to the exact build and bytes it was made for;
- a rendering hook is either fully installed or fully removed;
- a read-only observer never changes the game state.
An invariant does not live at one address. The health bound
0 <= health <= max_health can be enforced by a clamp instruction, depend on a
max_health field, and be reported by a log line, all at the same time. Each of
those is a place where the rule is kept or reported, and a bug in one of them
leaves the rule itself unchanged: a state with health = 120 and
max_health = 100 is invalid whichever of the three failed to catch it.
Predict first
A damage routine guards health with max(new health, 0), and the same routine applies heals as positive changes. Health is 90 and max_health is 100.
A heal of 30 arrives. What health does the game store?
Answer: 120, which breaks the invariant. 90 + 30 = 120, and max(120, 0) = 120 because 120 is not below 0. The rule 0 ≤ health ≤ 100 is now false, and the health bar copies 120 as well.
Request, control, accepted state
The request, then the control
Accepted state
the display follows
Health bar90 + 30 = 120. max(120, 0) only raises values below 0, and 120 is not below 0, so it passes unchanged. 120 breaks 0 ≤ health ≤ 100, and the health bar copies the broken value.
State, transitions, controls, and detectors
Section titled “State, transitions, controls, and detectors”| Term | Precise meaning | Example in a toy game |
|---|---|---|
| State | Values needed to describe the system at one moment | health = 48, mode = Alive |
| Transition | An event and rule that move state to another valid state | 5 damage changes health from 48 to 43 |
| Invariant | A property required of every valid state or transition | 0 <= health <= max_health |
A control keeps an invariant true, usually by refusing or limiting a transition before it takes effect. A damage routine that stops health at 0 instead of letting it go negative is a control. A detector reports that an invariant may already have failed, such as a scan that logs any entity whose health exceeds its maximum. The two are separate pieces of code, so they fail separately: a detector can be noisy while the control still works, or quiet while the control is incomplete.
What bypass analysis means in this chapter
Section titled “What bypass analysis means in this chapter”A bypass is a path that reaches an outcome without satisfying the control, the code that was supposed to guard it. In a game, the mismatch is often between a convenient proxy and the real rule:
| Proxy that gets checked | Property that actually matters |
|---|---|
| Menu button is enabled | Engine policy permits the state change |
| One field’s checksum matches | The complete object relation is current and valid |
| One environment signal is absent | The requested command satisfies game rules |
| An old object still has a plausible address | The identity and generation still match |
The analysis pattern is always the same: state the promised rule, trace the branch that claims to enforce it, follow every path to the effect, and look for a path where the proxy and the real property disagree. Later lessons apply that pattern to integrity checks, anti-debug behavior, encoded values, and requests that cross game or system boundaries. The hook and layout contracts remain the foundation that makes those observations trustworthy.
A control guards one copy of the state
Section titled “A control guards one copy of the state”A game keeps several copies of the same fact. The simulation’s entity object holds health, the health bar keeps its own copy for drawing, a script receives one in a snapshot, and in a networked game the server keeps a record while each client keeps a prediction of it. Exactly one copy is authoritative: the one whose value decides what happens next. In a single-player game that is usually the simulation object. In a multiplayer game with an authoritative server it is the server’s record, and each client’s copy is a prediction that the server can overwrite.
A control protects only the copy it runs against. Code that validates the health bar’s copy and then applies damage to the simulation object has checked one copy and changed another; Lesson 14.7 calls this the wrong source of truth. Server authority is the same rule at network scale: the server validates every client request against its own record, because anything a client computed can be altered on the client’s machine before it is sent.
The copies hold the same number most of the time, so reading a matching value does not say which copy was read. Timing does. The authoritative copy changes first and decides the next transition; every other copy catches up afterward, on its own schedule.
Draw the state machine
Section titled “Draw the state machine”Suppose an entity can be alive, downed, or removed. A state diagram lists every allowed transition:
The diagram raises a question that one memory sample cannot answer: does the
stored health field change before or after the Alive -> Downed transition?
A sample shows one moment. A trace of timestamped events shows the order.
Trace state changes as typed events
Section titled “Trace state changes as typed events”A trace is a list of events in the order they were logged, each saying what changed, when, and which observer saw it. Typed fields let a program check the trace:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]enum Mode { Alive, Downed, Removed,}
#[derive(Debug, Clone, PartialEq, Eq)]struct TraceEvent { sequence: u64, tick: u64, source: &'static str, entity_id: u32, event: Event,}
#[derive(Debug, Clone, PartialEq, Eq)]enum Event { HealthRead { value: i32 }, HealthWrite { before: i32, after: i32 }, ModeChanged { before: Mode, after: Mode },}sequence gives log order. tick connects the event to the game clock.
source names the observer that produced the event. Because an event carries
typed fields rather than a sentence, a test can ask directly whether a
HealthWrite ending at 0 comes before the ModeChanged to Downed for the
same entity. A short trace for entity 7:
sequence tick source entity event 41 900 health watch 7 HealthWrite { before: 6, after: 0 } 42 900 mode watch 7 ModeChanged { before: Alive, after: Downed } 43 901 health bar 7 HealthRead { value: 0 }Health reaches 0 at tick 900 and the mode changes in the same tick. The health bar, which draws from a copy, shows 0 only at tick 901, one tick later.
State, transition, and display are different observations. This step-by-step trace needs JavaScript. Here is the code and where the run ends.
entity.health = 6; state = Alive
apply damage without going below zero
if health is zero: enter Downed
choose whether the display has refreshed
copy health to the display when refreshed
report both the state and observed copyResult: health 0, state Downed, display 0. Similar values do not make these observations the same event.
Final values: damage = 6; refresh = yes; health = 0; state = Downed; display = 0.
Read every step of the lesson’s example
Step 1, line 1. Start with the selected inputs and follow how the next operation changes the state.
damage = 6; refresh = yes.
Step 2, line 2. Damage changes the stored health.
damage = 6; refresh = yes; health = 0.
Step 3, line 3. The transition tests the new health, rather than the old display.
damage = 6; refresh = yes; health = 0; state = Downed.
Step 4, line 4. A display can still contain an earlier value.
damage = 6; refresh = yes; health = 0; state = Downed; display = 6.
Step 5, line 5. Refresh copies the current health.
damage = 6; refresh = yes; health = 0; state = Downed; display = 0.
Step 6, line 6. Result: health 0, state Downed, display 0. Similar values do not make these observations the same event.
damage = 6; refresh = yes; health = 0; state = Downed; display = 0.
Entity 7 health changes from 6 to 0 in event 41 at tick 900. Event 42 changes Alive to Downed in the same tick. Event 43 observes the health-bar copy at 0 at tick 901. The initial display-copy value is left unspecified.
Read the steps
- Entity 7 has recorded before-values health 6 and mode Alive. The health-bar copy has not yet been observed in this trace.
- Sequence 41 at tick 900 records health changing from 6 to 0. The health value and its six filled units become zero.
- Sequence 42 records Alive changing to Downed in the same tick. This is a separate typed event for the same entity.
- At tick 901, sequence 43 observes health 0 in the drawing copy. The display observation follows the simulation observations by one tick.
- The journal keeps entity identity, event type, sequence and tick. It shows the display delay. Log order alone does not prove that one event caused the next.
The state changes and the journal follow the exact three-event trace. Sequence records order; causation needs additional evidence.
Log order is not cause. Event 41 appearing before event 42 fits the damage causing the transition, but it also fits both events sharing one cause, or two observers that flushed their output at different times. Lesson 14.6 adds correlation IDs, which tie together the events of one action even when log order misleads.
Use the same model for a different rule
Section titled “Use the same model for a different rule”Health is an easy starting example because its valid range is visible. The same questions transfer to a rendering hook, though the fields are different:
| Question | Health update | Hook installation |
|---|---|---|
| What is the state? | Health 48, maximum 100 | Original bytes, trampoline, and callback ownership |
| What transition is requested? | Apply 5 damage | Install a detour |
| What must stay true? | Health remains between 0 and 100 | A call sees a complete original path or a complete hook path |
| Where is the control? | The damage routine before changing simulation health | Version check, instruction preparation, and coordinated publication before calls enter the detour |
| What can a detector report? | A snapshot finds health outside the bound | A byte or lifecycle check finds an incomplete installation |
The analogy has a limit: health is one value, while a hook changes executable bytes that other threads may be using. Lesson 8.10 handles that concurrency. The transferable method is to name the state, permitted transition, invariant, control, and observable failure before choosing an implementation.
Glossary terms introduced here
Section titled “Glossary terms introduced here”- Invariant: a property required of every valid state or transition.
- Control: logic intended to preserve an invariant.
- Detector: logic that reports a possible invariant failure.
- Authoritative state: the one copy of a value whose contents decide what happens next.
- Server authority: a design in which the server’s record is authoritative and every client request is validated against it.
- Trace: events listed in the order they were logged, each with its time and source.
The useful model is state changing through transitions under an invariant. A control must guard the authoritative copy when a transition takes effect; a detector can report a violation but does not enforce that rule. Lesson 14.7 uses this distinction to examine what an integrity check actually covers.
Guard the copy that decides
A heal command is checked against the health bar's cached value, then applied to the simulation's entity object. Why can an out-of-range heal still get through?
A control protects only the copy it runs against. The authoritative copy is the one whose value decides what happens next, so the range check belongs where that copy changes.
Finished Lesson 14.5?
Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.
Plain Markdown, saved in this browser.
This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.