modemwars/tools/phase3_finish.js
2026-08-23 02:09:40 -05:00

151 lines
9.4 KiB
JavaScript

export const meta = {
name: 'modemwars-finish',
description: 'Phase 3: annotate the one code chunk that failed, comment the data files, correct stale claims, verify',
phases: [
{ title: 'Code', detail: 'redo the fast loader chunk whose agent lost its connection' },
{ title: 'Data', detail: 'one agent per pure-data file' },
{ title: 'Fix', detail: 'correct claims the data survey disproved' },
{ title: 'Verify', detail: 'rebuild, reassemble and compare every unit' },
],
}
const RESULT = {
type: 'object',
properties: {
path: { type: 'string' },
linesCommented: { type: 'integer' },
routines: { type: 'integer' },
problems: { type: 'array', items: { type: 'string' } },
},
required: ['path', 'linesCommented', 'routines', 'problems'],
}
const common = `
You are annotating a reverse-engineered disassembly of "Modem Wars" (Commodore 64, Electronic Arts / Ozark
Softscape, Dan Bunten, 1988): a two-player real-time strategy game played over a modem link, with a solo trainer,
"game film" record/playback, a console with STATS / REPAIR / MISC panels and a 40x40 battlefield.
Work in /home/scott/claude/modemwars.
Read first: docs/overview.md and the section of docs/knowledgeBase.md for your unit. disassembly/XREF.txt lists
callers/readers/writers per source file (references are grouped under the file they come from - only the ones under
your own file are meaningful). survey/*.json holds the raw survey findings, including survey/data_units.json and
survey/data_*.json which describe the data blocks in detail.
Annotation JSON format (tools/build.py merges every annotations/*.json; everything is unit-scoped):
{
"chunk": "<id>", "unit": "<unit>",
"unitLabels": { "<unit>": { "XXXX": "camelCaseName" } },
"notes": { "XXXX": { "unit": "<unit>",
"routine": ["name - what it does", "In: ...", "Out: ...", "Called from: ..."],
"block": ["comment block printed before this address"],
"line": "end-of-line comment for the instruction or data at this address" } },
"dataTypes": { "XXXX": ["text"|"addr"|"word"|"byte"|"grid"|"bitmap"|"sprite", length, optionalRowWidth] }
}
"grid" takes a row width and prints that many bytes per line; "bitmap" prints one byte per line as 8 pixels of
bit art; "sprite" prints 21 rows of 3 bytes as 24-pixel bit art. Addresses: uppercase hex, 4 digits, no "$".
Plain ASCII only.
HARD RULES
* Write ONLY your own file annotations/30_<chunk>.json. Never edit disassembly/*.s, tools/*, docs/* or another
agent's annotation file.
* Do NOT run tools/build.py or disassembly/verify.sh - other agents run in parallel and the build rewrites the
whole disassembly directory. A verification agent runs at the end.
* dataTypes are keyed by address across the whole program, so only type an address that is data in EVERY file
that covers it. $6F00-$87FF and $E000-$EFFF each exist in two different files, and $0200-$04FF in two more:
check the sibling file before typing an address in those ranges.
* Never invent facts. Mark uncertainty in the comment itself ("probably", "?").
`
phase('Code')
const codeChunk = await agent(`${common}
YOUR CHUNK: unit "boot/fastLoaderC000", file disassembly/boot/fastLoaderC000.s, addresses $C000-$C3FE.
This is the Electronic Arts fast loader: it installs code in the 1541, then pulls the whole game off the disk
through a two-bit serial protocol on CIA2 port A while the title picture is displayed. The drive side of the same
protocol is disassembly/drive/driveFastLoader.s - read it, the two halves explain each other.
Read the whole file first. Its routine headers already exist; the per-instruction comments do not.
TASK: write annotations/30_boot_fastLoaderC000_C000.json.
1. A "line" comment on EVERY instruction, explaining intent rather than the mnemonic. Name what each branch
tests. Explain every magic number: CIA2 port A bit meanings, the handshake sequence, the raster test that
avoids bad lines, VIC register values for the title picture, KERNAL call arguments, DOS command strings.
2. Improve the routine headers where you understand a routine better, and add "Called from:" lines.
3. "block" comments before each logical section.
4. Point out the self-modifying code: which instruction is patched, by whom, with what, and why (the loader
rebuilds the stack so its final RTS lands at $0461 instead of returning to its caller).
Validate with python3 -c "import json;json.load(open('annotations/30_boot_fastLoaderC000_C000.json'))".
Return path, number of line comments, number of routine headers, problems.`,
{ label: 'annotate:fastLoaderC000', phase: 'Code', schema: RESULT })
phase('Data')
const dataChunks = args.dataChunks
const dataResults = await pipeline(dataChunks, (c) => agent(`${common}
YOUR FILE: unit "${c.unit}", disassembly/${c.file}, addresses $${c.startAddr}-$${c.endAddr}. This file is pure
data - there is no code in it. survey/data_units.json and the other survey/data_*.json files already describe
much of it; use them, verify what they claim against the bytes, and go further.
TASK: write annotations/30_${c.id}.json so that a reader can understand every byte of this file.
1. Give every distinct structure a label and a "block" comment saying exactly what it is: how many entries, how
many bytes per entry, what the index means, which code reads it (from XREF.txt, filtered to the right file),
and what the values mean.
2. Choose the right dataTypes so the listing renders the structure readably: "grid" with the natural row width
for tables (e.g. one screen row, one record, one template row), "bitmap"/"sprite" for graphics so the bit art
lines up, "text" for strings, "addr" for pointer tables. Split a block into several dataTypes entries when it
contains several structures.
3. Where a table is one half of a split low-byte/high-byte pointer pair, say so and say where the other half is;
do NOT type it as "addr".
4. Add "line" comments to individual rows where a row means something specific (a particular glyph, a particular
unit, a particular message id). Do not pad every row with a meaningless comment.
5. Say plainly which parts are unused, leftover or unreferenced, and what the evidence is.
Validate the JSON parses. Return path, number of comments written (linesCommented), number of structures
described (routines), problems.`,
{ label: `data:${c.id}`, phase: 'Data', schema: RESULT }))
phase('Fix')
const fixed = await agent(`${common}
TASK: the data survey (survey/data_units.json, survey/data_high_FBB8.json) disproved several statements that the
earlier survey pass wrote into the listing. Correct them by writing annotations/30_corrections.json (same format,
notes only - it loads after the survey notes and overrides them).
1. game/highMemoryFBB8: the listing says $FD70 and $FE24 have "no static reference anywhere in the disassembly"
and that $FF90 is "most likely unused leftover memory". All of that is wrong: $FD70 and $FE24 are
run-length-encoded sprite streams, unpacked into $0200 by the RLE unpacker at $67CB, which the comcen screens
overlay calls from $7596 and $7FF0. Check this yourself in the listings, then write correct block comments
for $FD70, $FE24 and $FF90 (say what $FF90 really holds if you can determine it; if you cannot, say that
plainly instead of guessing).
2. Search the other listings for any remaining claim that a block is "unreferenced" or "unused" which the data
survey contradicts, and correct those too.
3. Do not touch anything you have not verified.
Return path, number of corrections (linesCommented), 0 routines, problems.`,
{ label: 'corrections', phase: 'Fix', schema: RESULT })
phase('Verify')
const verified = await agent(`Work in /home/scott/claude/modemwars. You are the only agent running now, so you may
run the build.
1. python3 -c "import json,glob;[json.load(open(f)) for f in glob.glob('annotations/*.json')]" - fix invalid JSON.
2. grep -lP '[^\\x00-\\x7F]' annotations/*.json docs/*.md 2>/dev/null - replace any non-ASCII character.
3. python3 tools/build.py 2>&1 | grep -iE "warning|error|traceback"
4. ./disassembly/verify.sh | grep -v "^OK" - all 24 units must reassemble byte-exact.
If a unit fails to ASSEMBLE, find the first ca65 error and fix the annotation entry that caused it.
If a unit assembles but MISCOMPARES, the cause is a dataTypes entry that changes how many bytes are emitted
(for example a typed block that overlaps a label, or a "text"/"sprite" run whose length is wrong): find it with
cmp -l disassembly/build/<unit>.bin disassembly/build/<unit>.orig.bin, locate that address in the listing and fix
the annotation. Never edit disassembly/*.s or tools/*.
Duplicate-label warnings from build.py are auto-resolved but indicate two annotations claimed the same name: fix
the annotation that is wrong.
Repeat until verify prints 24 OK and build.py prints no warnings. Finally report the per-file instruction comment
coverage using:
python3 - <<'PY'
import re, glob
tot=com=0
for f in sorted(glob.glob("disassembly/*/*.s")):
for l in open(f):
m=re.match(r"^\\s+[a-z]{3}\\s.*?;\\s[0-9A-F]{4}(.*)$", l)
if m:
tot+=1
if m.group(1).strip(): com+=1
print(tot, com, round(100*com/tot,1))
PY
Return path="disassembly", linesCommented=<commented instruction count>, routines=<total instruction count>,
problems=[everything you had to fix].`,
{ label: 'verify', phase: 'Verify', schema: RESULT })
return { codeChunk, dataResults: dataResults.filter(Boolean), fixed, verified }