Section: Do not turn Lua into an untyped memory A P I, application programming interface.
After building a memory reader, it is tempting to expose this:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
That design gives a typo the power of the process handle. It also couples scripts to exact offsets and makes every mod responsible for pointer validation.
Expose domain meaning instead:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Section: The host owns collection and validation.
The Windows layer should:
open the target with the least required rights;
verify game build and architecture;
resolve versioned roots and pointer paths;
copy fields with bounds and plausibility checks;
create ordinary owned records;
close or retain handles according to one clear owner.
Only step 5 crosses into Lua.
The boundary turns unstable bytes into stable meaning, then turns script intent back into checked data:
Addresses stop at the memory reader; Lua receives fields with names, units, and a snapshot version.
In brief: Expose named, validated values to Lua, and keep build-specific addresses inside the host observer.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
There are no process addresses in this type. A script cannot keep a stale pointer because it never receives one.
Why avoid raw pointers here? Lua can represent native objects through userdata. Full userdata has Lua-managed storage and a host-defined metatable; light userdata is essentially an unowned pointer value. If a native entity dies while a script retains its raw address, the same address may later belong to a different entity. A copied snapshot avoids that lifetime problem for observation. When a request needs persistent identity, use a validated handle rather than a remembered address.
A handle can pair a numbered slot with a generation counter. Every time a slot is reused for a new entity, its generation changes. Suppose Lua remembers slot 3, generation 6. If that entity dies and slot 3 is reused at generation 7, the host rejects the stale pair rather than acting on the new occupant:

Lesson 3.10 developed the same identity rule for a game collection. Here it protects the boundary between a script’s request and a changing game world.
Here is one entity as a hypothetical build lays it out in game memory, and the same entity after the observer has copied it into a snapshot and the host has turned that into a Lua table:
Section: A snapshot has a time and version.
Real snapshots should carry metadata:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Section: Put an abstraction barrier between bytes and game meaning.
An abstraction barrier is a rule about who may know a representation. The observer below the barrier knows that one build stores an entity position at a particular pointer path. Lua above the barrier knows only that an entity has an id, name, position, and alive state.
Alternative: The abstraction barrier is a translation desk: scripts ask about entities, while the observer translates those names into one build’s storage layout.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
If a later build moves the position field, only the observer and its target profile should change. Every script should not learn a new offset.
Make the source of snapshots replaceable too:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
A live Windows observer and a replay-file reader can both implement SnapshotSource. The same Lua state machine can then run against recorded data in a test and against the verified game in the lab. That is not just reuse: it lets you test automation without giving the test a process handle.
When Lua requests an action, include the sequence it observed:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The host can reject a request based on a snapshot that is too old. This is safer than pretending copied data is live.
The script did nothing wrong: entity 7 really was alive in snapshot 41. The game moved on while the script was deciding, and based_on lets the host see that the request describes a moment that has already passed.
Section: Requests are data, not commands.
Bad interface:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Better interface:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The host matches an explicit enum:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Unknown kind values fail. No string becomes PowerShell, a console command, or arbitrary game code.
Section: Validate again at action time.
Between snapshot and request processing, an entity may disappear. Check:
the sequence is recent;
the entity still exists;
its current state permits the action;
all numbers are finite;
coordinates remain within known map bounds;
the per-script action budget is not exhausted.
This is a time-of-check/time-of-use issue. Validation during snapshot creation does not guarantee future state.
Narration: A unit can disappear after the snapshot was copied. Rechecking its identity when processing a request prevents acting on an outdated observation.
Section: Keep read and write capabilities separate.
Most analysis scripts need only snapshots and logs. Give them an observer A P I with no request function at all.
When an offline automation lab needs actions, load it with a different capability set:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Do not decide permissions from a script-provided name. The host chooses the A P I before executing the script.
Section: Test the boundary without a game.
The lua-labs host uses a simulated snapshot. Add tests where Lua requests:
a missing entity ID;
0/0 or infinity as a coordinate;
an unknown action;
too many actions;
an action from an expired sequence.
The host should reject each request with a useful error and remain alive.
This architecture also makes the script portable. The same Lua logic can consume a recorded replay, a simulated world, or a live observer because it depends on the snapshot contract rather than Windows memory details.