Trace a behaviour
Skill
re-trace. Find and understand how the original game implements something - from a string, a resource name, an import, an export, a C++ class name or a known caller to the function, its inputs, the fields it reads and writes, the constants it uses and the loop that calls it - and write the result down as evidence. Use for any "how does the game do X?" question before reimplementing or documenting it.
Copy this prompt into your coding agent. Replace the part in angle brackets.
In my GameFrame project, use the re-trace skill for <the task>.
Read the skill first and follow its steps in order.
Report the evidence it asks for, with its label: observed, measured, inferred or unknown.Tracing means following the program from something concrete to the code you need, and leaving a written trail. Static reading only: label conclusions observed, inferred or unknown (re-start). When the exact numbers matter, finish with re-emulate.
0. Has it been done?
grep -rn "1400be770\|SaveGame" docs/ labels/ | head1. Find an entry point
For a DOS game, look for debug symbols first (tools/dos/tdinfo.py BINARY summary): with them, the entry point is a name (tdinfo.py BINARY procs enemy), with its parameters, locals and the source line of each address (tdinfo.py BINARY lines UNIT.PAS).
Work outward from something you can see. In rough order of usefulness:
- Strings. File paths (
"Saves/","data/units.xml"), format strings ("%d citizens"), UI text, error messages, debug and log text, script function names.decomp_query.py strings WORDlists them with the functions that use them. Developers leave log messages that name the function they are in. - Exports with real names. An engine DLL that exports C++ symbols (
?Serialize@Stream@IO@@...) names hundreds of functions for free:re-ghidranamed. - Imports. The OS and library calls show what a function does:
CreateFileW/ReadFile(loading),QueryPerformanceCounter(timing),XInputGetState(input),send/recv(network),D3D11CreateDevice(rendering). Find their callers. - RTTI. MSVC binaries with RTTI contain
.?AVClassName@@strings; each points to a type descriptor, which leads to the class's vtable and so to all its methods. - Type registries and hashes. Engines often register classes or resources under a hash of their name (CRC32, FNV-1a, murmur). If a table pairs constants with factories, hash candidate names and look the constants up.
- Data. A value you can see in the game (a price, a duration) may appear as a constant (
readdata.py) or in a data file (re-format). Its readers lead to the rules. - The main loop. From
WinMain/main(or the entry point) find the per-frame update; the systems hang off it in a fixed order, which you will need anyway (seedocs/GAME-PATTERNS.md).
2. Read the function
- Note its inputs: what
param_1is (oftenthis), the globals it reads (DAT_...pointers to singletons), and the callees that touch the OS, threads, files or the GPU. - Name what you understand: a row in
labels/names.tsvfor the function and its callees, a row inlabels/fields.tsvper field (Citizen 0x44 hunger f32 written by FUN_1400be770). - Follow the callers up until you reach a loop, a vtable slot or an event handler: a code path is not proof that it runs.
3. Reconstruct structures
- Serializers first. A
Save/Load/Serializefunction reads or writes every field in order with its type: the cleanest field list you will get. - Who writes a field:
decomp_query.py writers 0x44, then skip constructors and copies. - Nested offsets (
array + i * 0x1f0 + 0x90): find getters that return sub-objects, subtract, and continue inside the sub-object. - vtables: a constructor's first store is
*this = &vtable. List it (re-ghidravtable); slot offsets are what call sites use:(**(code **)(*obj + 0x18))(obj)calls slot +0x18. Base classes share early slots; compare tables to find the hierarchy. - Which argument is which: a call's name does not tell you its argument order. Read the callee before trusting a guess written in a document.
4. Constants
Read every DAT_ constant and table the function uses with tools/pe/readdata.py, and record each with its address. Never round or "tidy" them: 0.33333331 is not 1/3 in a 32-bit float simulation.
5. Write it down (re-land)
A topic document in docs/ (extend one if it exists): an opening paragraph (what, which binary and version, which evidence), then prose and tables keyed by address and offset: function → role, fields, constants, the call order, and Open questions. Never paste pseudocode or disassembly: describe it. Update docs/CHECKLIST.md.
Pitfalls
- Pseudocode drops and mis-types things (see
re-ghidrasection 5). For arithmetic, read the instructions; for exact values, emulate. - A function may be inlined into each caller; a "missing" function is often inside its caller.
- Functions reached only through tables are often absent from the bulk export: create them with
scripts/ghidra-run.sh BINARY decompile OUT 0xADDRESS. - Two subsystems may share a helper with different meanings for its arguments. Name helpers by what they do, not by the first place you saw them.