Annotate a GSL decompilation with runtime evidence
You will enrich a decompiled .gsl file with meaningful names and comments
earned by actually running the program — without changing a single byte
of what the file compiles to.
The .gsl is the single source of truth: you compile it and run the
result. The original cartridge image is never needed — the decompiler proved
byte-identity when it produced the file, and you preserve that identity
edit by edit, so the compiled decompilation is the program.
The interactive loop
This is a conversation, not a batch job:
- The user starts the flow with the
.gsl path and whatever they already
know — what the program is, where companion media lives (data disks),
what to focus on, standing orders ("don't touch the sound driver").
Those in-prompt hints are the steering channel; there is no hints file.
- You run a pass: as much annotation as the evidence supports. Don't
interrupt the pass with questions unless you are truly blocked (a
missing file, an ambiguous instruction) — prefer finishing and asking
in the report.
- You end the pass with the report (Step 5): coverage and its delta,
renames with evidence, discoveries, what stayed unreached, and exactly
what hint or media would unlock more.
- The user replies with new hints or directions — run another pass, same
conversation. They stop when returns diminish.
Conversations are ephemeral; the file is not. Record every durable
user-supplied fact (media paths, what the game is, standing orders) in the
notes block's CONTEXT section (Step 4) so later passes — in this
conversation or a fresh one — inherit them.
The four iron rules
- The file must stay byte-identical. Before the first edit, compile
the pristine file into the pass baseline; after every batch of edits,
verify against it:
./target/release/libre99gsl compile <file.gsl> -o <scratch>/baseline.ctg --format ctg
./target/release/libre99gsl verify <file.gsl> <scratch>/baseline.ctg --format ctg
If verify fails, fix or revert the batch before doing anything else;
never finish with a failing verify. (The check is name-blind — renames
and comments cannot break it; touching real statements or data can.
Byte-identity is transitive: the decompiler proved the file against the
original cartridge, each pass proves itself against the previous state,
so the chain back to the original never breaks.)
- Addresses are ground truth — never remove them. Every declaration
keeps its
@ 0xNNNN, every function header keeps its // >NNNN: line.
Renamed functions keep the address suffix (sub_C41E →
store_menu_C41E); variables may take plain names (b_8340 →
party_gold) because their declaration pins the address — the same
convention the decompiler's own static renames use. Never edit the
format/title declarations.
- Evidence discipline. Rename only what you observed: the trace or
coverage placed execution there, the screen showed it, memory changed in
step with it. Prefix comments
observed: for session facts and
likely: for inference. No evidence → no rename; a plausible guess is
worse than an honest sub_XXXX.
- Copyright. Decompilations of commercial cartridges — and everything
derived from them (the enriched
.gsl, the compiled baseline, evidence
files, session scripts) — stay outside this repository. Never commit
them; keep them next to the .gsl or in scratch space.
Step 0 — set up
cargo build --release -p libre99-probe -p libre99-gsl
- Read the
.gsl header comment (the first ~60 lines) and the tail of
the file: an existing EXPLORATION NOTES block means this is a later
pass — read it fully (especially CONTEXT) and build on it. Passes are
cumulative; never discard earlier sessions, renames, or notes.
- Pick a scratch directory for the baseline image, session scripts,
evidence dumps, and screenshots.
- Compile the baseline (iron rule 1) before any edit, and run a
baseline
verify — it must pass (if it doesn't, stop and tell the
user). The baseline .ctg is also the image every probe session runs.
Step 1 — index the file (don't read all of it)
The file may be hundreds of KB. Grep, don't read end to end:
grep -n '^fn ' file.gsl → the function index: every name and @ 0xNNNN
address. A function's byte span runs from its address to the next one's —
this index is what turns trace/coverage addresses into function names.
- The
// >NNNN: headers already carry the static analysis: effects
(formats screen text, reads keyboard, …), // prints:, // calls: /
// called from:. Your job is the layer static analysis cannot reach:
what the code means in the running program.
Step 2 — play (the evidence loop)
libre99probe is the control surface — docs/PROBE.md is the manual — and
<scratch>/baseline.ctg is the image it runs. Two driving modes, both good:
- Script replay: keep
session-N.txt, append commands, rerun
./target/release/libre99probe <scratch>/baseline.ctg --script session-N.txt. The emulator is deterministic, so the script is the
session — and the replayable proof of every claim you make from it.
- Checkpoints:
save/load state files to branch-explore (checkpoint
before a menu, try option 1, load, try option 2) without replaying.
The loop is: screen → decide → press/type → settle → repeat. For
custom-font or sprite-heavy screens where the ASCII decode is unreadable,
shot file.png and view the image. A standard opening:
frames 180 # power-up to the master title
press space # any key → selection menu
settle
press 2 # the cartridge's first program
settle
If the user named companion media, mount it (disk 1 <path.dsk> — live, no
reset needed) and take the program's load path.
Evidence channels, and when to use each:
cover on at session start and leave it on (it's cheap). At session end,
cover for the summary and cover save <file> for the ranges. Bucketed
against the Step-1 function index this yields the coverage measure:
N of M functions observed executing.
trace on only around a moment — "what runs when I press B here?" — then
trace summary / trace save, since the log grows fast; trace on
restarts it. Fetches at >6000+ are the program's own code executing.
peek scratchpad ranges before/after an action to see which cells changed
— the raw material for variable names (observed: decremented each combat round).
audio 30 to confirm an action beeped (sound-driver attribution).
- The strongest naming evidence combines channels: function X executed
while the screen said Y (trace window +
screen in the same moment).
Plan sessions around the user's hints. Probe menus and verbs systematically
— save states make it cheap to try every option of every menu. Reaching all
code is intractable; make reasonable, hint-guided efforts, then document
what was not reached and why rather than guessing.
Step 3 — edit
- Renames: whole-word search-and-replace across the file — declaration,
call sites, and
calls:/called from: cross-references all use the same
spelling, and a missed site is a compile error that verify will catch.
- Comments: terse and in place. Explain what the code means in the
program ("
observed: prints the store menu"), not what the instructions
do. Use likely: sparingly and honestly.
- Batch small (one subsystem at a time) and run
verify after each batch,
so a mistake is easy to bisect.
Step 4 — the EXPLORATION NOTES block
Maintain exactly one block comment at the very end of the file — append it
on the first pass, update it (bump the pass number, merge sessions, renames,
and context) on later passes:
// =====================================================================
// EXPLORATION NOTES — pass 2 (3 sessions) — by the /annotate skill
// =====================================================================
// CONTEXT (durable facts from the user — future passes rely on these)
// This is Tunnels of Doom, TI's 1982 dungeon-crawl RPG.
// Data disk: /path/to/tod-data.dsk (mount DSK1; answer 2 at the
// "LOAD DATA FROM" prompt). Don't rename the sound driver.
// COVERAGE
// functions observed executing: 212/344 (62%)
// cart GROM addresses read: 18412/40960
// SESSIONS (deterministic keystroke scripts — replay to reproduce)
// 1 "boot to party creation" (session-1.txt):
// frames 180 / press space / settle / press 2 / settle / press 1 / ...
// 2 "store + combat" (session-2.txt): ...
// RENAMES (one per line, with evidence)
// sub_C41E -> store_menu_C41E observed: executed while screen read
// "GENERAL STORE"; draws its menu
// b_8340 -> party_gold observed: fell 250->175 on a purchase
// UNREACHED (and why)
// >D9A2->DB00 cassette error path — needs a mid-load cassette fault
// NEXT HINTS (what would unlock more)
// - a data disk in DSK1 would open everything past "LOAD DATA FROM"
The block doubles as the machine-readable record: scripts verbatim, one
rename per line, the user's durable context. It is how the next pass — and
the user — knows where things stand.
Step 5 — report, then iterate
- Final
verify must print verify OK — quote that line in your report.
- Report to the user: coverage numbers (and their delta from the previous
pass), the rename count with the two or three best examples, notable
discoveries about how the program works, what stayed unreached, and —
phrased as questions they can answer in their next message — exactly
what hints, media, or focus areas would make another pass worthwhile.
- If the user replies with more, fold their answers into CONTEXT and run
the next pass.
1---2name: annotate3description: Enrich a GSL decompilation (a .gsl produced by libre99gsl decompile) with runtime evidence, interactively: compile the decompilation itself, play it headlessly with libre99probe, improve symbol names and comments, and prove after every edit that the file still compiles byte-identically (libre99gsl verify). Use when asked to annotate, enrich, or explore a decompiled TI-99/4A cartridge.4---56# Annotate a GSL decompilation with runtime evidence78You will enrich a decompiled `.gsl` file with meaningful names and comments9**earned by actually running the program** — without changing a single byte10of what the file compiles to.1112The `.gsl` is the single source of truth: you compile *it* and run the13result. The original cartridge image is never needed — the decompiler proved14byte-identity when it produced the file, and you preserve that identity15edit by edit, so the compiled decompilation *is* the program.1617## The interactive loop1819This is a conversation, not a batch job:20211. The user starts the flow with the `.gsl` path and whatever they already22 know — what the program is, where companion media lives (data disks),23 what to focus on, standing orders ("don't touch the sound driver").24 Those in-prompt hints are the steering channel; there is no hints file.252. You run a **pass**: as much annotation as the evidence supports. Don't26 interrupt the pass with questions unless you are truly blocked (a27 missing file, an ambiguous instruction) — prefer finishing and asking28 in the report.293. You end the pass with the **report** (Step 5): coverage and its delta,30 renames with evidence, discoveries, what stayed unreached, and exactly31 what hint or media would unlock more.324. The user replies with new hints or directions — run another pass, same33 conversation. They stop when returns diminish.3435Conversations are ephemeral; the file is not. Record every durable36user-supplied fact (media paths, what the game is, standing orders) in the37notes block's CONTEXT section (Step 4) so later passes — in this38conversation or a fresh one — inherit them.3940## The four iron rules41421. **The file must stay byte-identical.** Before the first edit, compile43 the pristine file into the pass baseline; after every batch of edits,44 verify against it:45 ```46 ./target/release/libre99gsl compile <file.gsl> -o <scratch>/baseline.ctg --format ctg47 ./target/release/libre99gsl verify <file.gsl> <scratch>/baseline.ctg --format ctg48 ```49 If verify fails, fix or revert the batch before doing anything else;50 never finish with a failing verify. (The check is name-blind — renames51 and comments cannot break it; touching real statements or data can.52 Byte-identity is transitive: the decompiler proved the file against the53 original cartridge, each pass proves itself against the previous state,54 so the chain back to the original never breaks.)552. **Addresses are ground truth — never remove them.** Every declaration56 keeps its `@ 0xNNNN`, every function header keeps its `// >NNNN:` line.57 Renamed **functions keep the address suffix** (`sub_C41E` →58 `store_menu_C41E`); **variables may take plain names** (`b_8340` →59 `party_gold`) because their declaration pins the address — the same60 convention the decompiler's own static renames use. Never edit the61 `format`/`title` declarations.623. **Evidence discipline.** Rename only what you *observed*: the trace or63 coverage placed execution there, the screen showed it, memory changed in64 step with it. Prefix comments `observed:` for session facts and65 `likely:` for inference. No evidence → no rename; a plausible guess is66 worse than an honest `sub_XXXX`.674. **Copyright.** Decompilations of commercial cartridges — and everything68 derived from them (the enriched `.gsl`, the compiled baseline, evidence69 files, session scripts) — stay **outside this repository**. Never commit70 them; keep them next to the `.gsl` or in scratch space.7172## Step 0 — set up7374- `cargo build --release -p libre99-probe -p libre99-gsl`75- Read the `.gsl` header comment (the first ~60 lines) and the **tail** of76 the file: an existing `EXPLORATION NOTES` block means this is a later77 pass — read it fully (especially CONTEXT) and build on it. Passes are78 cumulative; never discard earlier sessions, renames, or notes.79- Pick a scratch directory for the baseline image, session scripts,80 evidence dumps, and screenshots.81- Compile the baseline (iron rule 1) **before any edit**, and run a82 baseline `verify` — it must pass (if it doesn't, stop and tell the83 user). The baseline `.ctg` is also the image every probe session runs.8485## Step 1 — index the file (don't read all of it)8687The file may be hundreds of KB. Grep, don't read end to end:8889- `grep -n '^fn ' file.gsl` → the function index: every name and `@ 0xNNNN`90 address. A function's byte span runs from its address to the next one's —91 this index is what turns trace/coverage addresses into function names.92- The `// >NNNN:` headers already carry the static analysis: effects93 (`formats screen text`, `reads keyboard`, …), `// prints:`, `// calls:` /94 `// called from:`. Your job is the layer static analysis cannot reach:95 what the code *means* in the running program.9697## Step 2 — play (the evidence loop)9899`libre99probe` is the control surface — `docs/PROBE.md` is the manual — and100`<scratch>/baseline.ctg` is the image it runs. Two driving modes, both good:101102- **Script replay**: keep `session-N.txt`, append commands, rerun103 `./target/release/libre99probe <scratch>/baseline.ctg --script104 session-N.txt`. The emulator is deterministic, so the script *is* the105 session — and the replayable proof of every claim you make from it.106- **Checkpoints**: `save`/`load` state files to branch-explore (checkpoint107 before a menu, try option 1, `load`, try option 2) without replaying.108109The loop is: `screen` → decide → `press`/`type` → `settle` → repeat. For110custom-font or sprite-heavy screens where the ASCII decode is unreadable,111`shot file.png` and view the image. A standard opening:112113```114frames 180 # power-up to the master title115press space # any key → selection menu116settle117press 2 # the cartridge's first program118settle119```120121If the user named companion media, mount it (`disk 1 <path.dsk>` — live, no122reset needed) and take the program's load path.123124Evidence channels, and when to use each:125126- `cover on` at session start and leave it on (it's cheap). At session end,127 `cover` for the summary and `cover save <file>` for the ranges. Bucketed128 against the Step-1 function index this yields the coverage measure:129 *N of M functions observed executing*.130- `trace on` only around a moment — "what runs when I press B here?" — then131 `trace summary` / `trace save`, since the log grows fast; `trace on`132 restarts it. Fetches at `>6000`+ are the program's own code executing.133- `peek` scratchpad ranges before/after an action to see which cells changed134 — the raw material for variable names (`observed: decremented each combat135 round`).136- `audio 30` to confirm an action beeped (sound-driver attribution).137- The strongest naming evidence combines channels: *function X executed138 while the screen said Y* (trace window + `screen` in the same moment).139140Plan sessions around the user's hints. Probe menus and verbs systematically141— save states make it cheap to try every option of every menu. Reaching all142code is intractable; make reasonable, hint-guided efforts, then *document*143what was not reached and why rather than guessing.144145## Step 3 — edit146147- **Renames**: whole-word search-and-replace across the file — declaration,148 call sites, and `calls:`/`called from:` cross-references all use the same149 spelling, and a missed site is a compile error that `verify` will catch.150- **Comments**: terse and in place. Explain what the code *means* in the151 program ("`observed:` prints the store menu"), not what the instructions152 do. Use `likely:` sparingly and honestly.153- Batch small (one subsystem at a time) and run `verify` after each batch,154 so a mistake is easy to bisect.155156## Step 4 — the EXPLORATION NOTES block157158Maintain exactly one block comment at the very end of the file — append it159on the first pass, update it (bump the pass number, merge sessions, renames,160and context) on later passes:161162```163// =====================================================================164// EXPLORATION NOTES — pass 2 (3 sessions) — by the /annotate skill165// =====================================================================166// CONTEXT (durable facts from the user — future passes rely on these)167// This is Tunnels of Doom, TI's 1982 dungeon-crawl RPG.168// Data disk: /path/to/tod-data.dsk (mount DSK1; answer 2 at the169// "LOAD DATA FROM" prompt). Don't rename the sound driver.170// COVERAGE171// functions observed executing: 212/344 (62%)172// cart GROM addresses read: 18412/40960173// SESSIONS (deterministic keystroke scripts — replay to reproduce)174// 1 "boot to party creation" (session-1.txt):175// frames 180 / press space / settle / press 2 / settle / press 1 / ...176// 2 "store + combat" (session-2.txt): ...177// RENAMES (one per line, with evidence)178// sub_C41E -> store_menu_C41E observed: executed while screen read179// "GENERAL STORE"; draws its menu180// b_8340 -> party_gold observed: fell 250->175 on a purchase181// UNREACHED (and why)182// >D9A2->DB00 cassette error path — needs a mid-load cassette fault183// NEXT HINTS (what would unlock more)184// - a data disk in DSK1 would open everything past "LOAD DATA FROM"185```186187The block doubles as the machine-readable record: scripts verbatim, one188rename per line, the user's durable context. It is how the next pass — and189the user — knows where things stand.190191## Step 5 — report, then iterate192193- Final `verify` must print `verify OK` — quote that line in your report.194- Report to the user: coverage numbers (and their delta from the previous195 pass), the rename count with the two or three best examples, notable196 discoveries about how the program works, what stayed unreached, and —197 phrased as questions they can answer in their next message — exactly198 what hints, media, or focus areas would make another pass worthwhile.199- If the user replies with more, fold their answers into CONTEXT and run200 the next pass.