Devlog #006 - When the AI Catches Its Own Mistakes
Published April 2, 2026
One of the questions people ask about AI-assisted reverse engineering is whether the model can be trusted to stay accurate across sessions. Today’s session is a useful data point, because the agent caught a significant mistake it had made in a previous run, and corrected the record before it could propagate further.
The Misidentification
In an earlier session, the agent identified function 0x1873a as party_add_member, the
routine that adds a new character to your adventuring party. The reasoning looked plausible
at the time: the function iterates an array of slots, parses a sequence of single-character
tokens (‘r’, ‘w’, ‘a’, ‘b’, ’t’, ‘+’), and increments a counter at 0x77b6. The agent
labelled those characters as “class codes” for party members and filed the finding in the
knowledge base.
This session, the agent went back to that function for a different reason, it was trying to
cross-reference known Borland C runtime patterns, and the context clicked differently.
Those characters are not class codes. They are standard C file mode string characters:
‘r’ = read, ‘w’ = write, ‘a’ = append, ‘+’ = update, ‘b’ = binary, ’t’ = text. The
function is crt_fdopen, the Borland runtime’s file-open implementation.
The same correction applied to a nearby function. 0x17c82, previously labelled
find_free_char_slot, is actually crt_find_free_file_slot, it scans the C runtime’s
internal FILE struct array looking for an unused descriptor. The globals the agent had
attributed to party tracking (0x77b8, 0x77b6, 0x7767) are CRT internals: the FILE
array pointer, the open file count, and the maximum descriptor count respectively. None of
them have anything to do with the party.
The real party_add_member is still unidentified.
Why This Happens
This kind of misidentification is an inherent risk in AI-assisted reverse engineering. The model reasons about functions by pattern-matching their structure against things it already knows. A function that iterates an array of fixed-size slots and parses character tokens looks, at surface level, like it could be many things. If the model’s working hypothesis at the time is “find the party management code”, it will weight that interpretation more heavily.
The correction happened because the agent re-examined the function from a different angle. It already had detailed notes about the Borland CRT layout, file descriptors, the fopen family, the FILE struct array. When it tried to reconcile those notes with the surrounding code, the match was unambiguous. CRT file modes, not party classes.
This is one argument for the approach we’re taking: keeping a structured knowledge base that the agent reads back on every session, rather than relying on the model’s memory of what it concluded previously. The prior findings became evidence to reason against, not just conclusions to accept.
The lesson for anyone doing similar work: treat any AI-generated label as provisional until it has been cross-referenced against at least two independent lines of evidence. One pattern match is a hypothesis. Two independent matches start to look like a finding.
The Character Hot-Slot Array
On a more productive note, the session produced a cleaner picture of how the game tracks characters in memory during play.
There is a “hot-slot” array at 0x9C00: five slots, each 0x80 (128) bytes, one per
party member. These are the active, in-use representations of your characters while the
game is running.
Within each slot, two fields are of particular interest:
- Offset +0x65, the character’s current action or state.
0means idle.0x400means in-combat. The main loop reads this to determine which characters need processing after each state transition. - Offset +0x69, an RTLink overlay load state byte.
0means the character’s overlay data is already loaded.3means it needs to be loaded.5means skip. After every state handler returns,game_main_loopscans this field for each slot and calls the overlay loader for any character marked3.
Running alongside these slots, not embedded in them, but indexed in parallel by character number, are three flat byte arrays:
0x9064 + index, health or presence flag0xa3d8 + index, combat binding status0xa6c4 + index, combat condition flags (unconscious, down, dead, and similar states)
The parallel array pattern is consistent with what we already knew about the entity status block from the previous session. The game keeps high-frequency, per-tick data in tight, cache-friendly flat arrays rather than embedded in larger structs.
The Full Character Struct Is Still Elsewhere
The important implication of the 128-byte slot size: these hot slots cannot contain the full character record.
The save file analysis established that each character’s persistent data is 0x22a (554)
bytes, name, attributes, skills, equipment, inventory. That does not fit in 128 bytes.
The hot-slot holds only the runtime bookkeeping the game loop needs to operate moment to
moment. The full struct lives somewhere else in memory, at an address the agent has not
yet pinned down.
What’s Next
Two clear priorities for the next session:
Find the real
party_add_member. Now that the CRT file-management functions are correctly identified and removed from consideration, the search space for genuine party management code is narrower.Locate the base address of the full
0x22a-byte character struct in memory. The hot slots at0x9C00reference or point to it, following those references is the most direct path. Once the base address is confirmed, the in-memory struct layout can be cross-referenced against the save file layout to validate both.
Progress is not always a straight line. Sometimes a session’s most valuable output is finding out that last session’s output was wrong. That still counts.