export const meta = {
  name: 'modemwars-swiftlink',
  description: 'Build an alternate SwiftLink (6551 ACIA) opponent module for Modem Wars',
  phases: [
    { title: 'Spec', detail: 'extract the exact contract of the routines that will be replaced' },
    { title: 'Implement', detail: 'rewrite the UART layer against the 6551' },
    { title: 'Build', detail: 'assemble, check the ABI, install on a disk image' },
    { title: 'Test', detail: 'boot the patched disk in VICE with SwiftLink emulation' },
    { title: 'Document', detail: 'write the driver README' },
  ],
}

const RESULT = {
  type: 'object',
  properties: {
    path: { type: 'string' },
    ok: { type: 'boolean' },
    summary: { type: 'string' },
    problems: { type: 'array', items: { type: 'string' } },
  },
  required: ['path', 'ok', 'summary', 'problems'],
}

const common = `
PROJECT.  /home/scott/claude/modemwars holds a complete, byte-exact, heavily commented disassembly of
"Modem Wars" (C64, Electronic Arts / Ozark Softscape, 1988).  Read docs/overview.md first, especially
section 9 about the two opponent modules.

THE GOAL.  An alternate opponent module that talks to a SwiftLink cartridge (a 6551 ACIA on the
expansion port) instead of the stock module's bit-banged user-port UART, so the game can play over a
modem or null-modem link at far higher speeds than 300/1200 baud.

THE KEY DESIGN DECISION (already made - do not redesign it).  The stock module
disassembly/game/modemDriverE000.s is layered:

    packet / chat layer   ($E111-$E2FF)   game-facing packets, host rings, exchange flags
    modem + link state    ($E756-$E8xx)   Hayes control, connection phases, carrier
    frame / ARQ layer     ($E909-$EAB2)   length + control + sequence + checksum, retransmit
    ring buffers          ($E40F-$E533)   four descending circular buffers
    UART layer            (the rest)      CIA2 timers, NMI bit-banging, DTR/RTS on the user port

ONLY THE UART LAYER IS HARDWARE SPECIFIC.  Replace just that and the module stays wire compatible
with the stock driver at the same baud rate, because every byte above it is unchanged.

HARD CONSTRAINTS.
 * The module is exactly 4096 bytes and loads at $E000-$EFFF.
 * Every routine you replace must stay at its original address and must not grow past its original
   end, so that nothing else in the module moves.  Pad the leftover space (the 6551 code is much
   smaller than the bit-bang code it replaces).
 * $E000-$E017 (the jump table), $E01D-$E047 (shared variables the game reads and writes) and
   $EC00-$EFFF (utilities the map generator overlay calls) must come out BYTE-IDENTICAL to the stock
   module.  swiftlink/checkAbi.py enforces this.
 * Plain ASCII only.  Comment style matches the rest of the project: explain intent, not the mnemonic.

THE HARDWARE.  SwiftLink is a 6551 ACIA at $DE00 (some are strapped to $DF00), interrupting through
the expansion port NMI line.
    base+0  data       read = received byte, write = byte to transmit
    base+1  status     bit7 IRQ, bit6 DSR, bit5 DCD, bit4 TDRE (transmit register empty),
                       bit3 RDRF (receive register full), bit2 overrun, bit1 framing, bit0 parity
                       (reading the status register clears the IRQ flag)
    base+2  command    bit0 DTR, bit1 receiver IRQ disable, bits3-2 transmit control
                       (00 = TX IRQ off / RTS high, 01 = TX IRQ on / RTS low, 10 = TX IRQ off /
                       RTS low), bit4 echo, bits7-5 parity
    base+3  control    bit7 stop bits, bits6-5 word length (00 = 8), bit4 clock source (1 = internal),
                       bits3-0 baud rate
SwiftLink fits a 3.6864 MHz crystal, twice what the 6551 expects, so every rate in the table comes out
DOUBLED.  Nominal rate bits: $05=150 $06=300 $07=600 $08=1200 $09=1800 $0A=2400 $0B=3600 $0C=4800
$0D=7200 $0E=9600 $0F=19200 - so on SwiftLink $05 gives 300, $07 gives 1200, $08 gives 2400,
$0C gives 9600, $0E gives 19200, $0F gives 38400.  Control byte = $10 (8N1, internal clock) OR the
rate bits.  VERIFY this doubling claim against a second source before relying on it, and say in your
report what you concluded.

DO NOT edit anything under disassembly/, annotations/, tools/ or docs/.  Your work goes in swiftlink/.
`

phase('Spec')
const [uartSpec, linkSpec] = await parallel([
  () => agent(`${common}
TASK: write swiftlink/spec_uart.md - the exact contract of every routine in the stock module that
touches the hardware, so it can be reimplemented against the 6551 without breaking its callers.

Work from disassembly/game/modemDriverE000.s (it is fully commented) and disassembly/XREF.txt.
For EACH of these, give: address range (start and the address of the byte after its last), what it
does, entry conditions (registers, variables), exit conditions (registers, flags, variables), every
caller, and every variable it reads or writes:
  $E353 loadBaudParameters, $E534 flushUartTxRing, $E540 configureUserPortLines, $E574 dropDtrLine,
  $E57B installCommNmiVector, $E586 restartUart, $E593 setCiaNmiMask, $E5B6 clearUartState,
  $E5C1 suspendUartIfRunning, $E5C3 stopCommNmi, $E607 startNextTxChar, $E654 setBitPeriodFull,
  $E657 setBitPeriod, $E663 nmiStartNextChar, $E685 commNmiHandler, $E69C nmiMergeIcrFlags,
  $E73C queueByteForTransmit.
Then list, as a table, every byte of module RAM those routines own ($E5BB-$E5C0 and friends, the
bit period bytes, the transmit pacing byte, the pending-character count $E0A5, the carrier sample
timer) with its meaning, and say which of them a 6551 implementation still needs and which become
dead.  Finally: state exactly how the NMI handler decides an interrupt is not its own and how it
chains to the game's handler through $E031/$E032, since the replacement must do the same.
Check every address you quote against the listing.  Return path, ok, a one-paragraph summary,
problems.`, { label: 'spec:uart', phase: 'Spec', schema: RESULT }),

  () => agent(`${common}
TASK: write swiftlink/spec_link.md covering the parts of the module that surround the UART, so the
replacement keeps behaving the way the game expects.
 1. The suspend/resume handshake: the disk loader sets $E039 and waits on $E03A ($0F5F/$0F71 in
    game/mainProgram0800.s).  Document exactly what values mean what, and what the UART layer must do
    on suspend and on resume.
 2. Carrier detection: how serviceCarrierAndSuspendRequest ($E5D6) and pollCarrierState ($E3CB)
    sample the modem's carrier line, how the result is debounced, and how it reaches $E03C/$E03D and
    isLinkActive $E03B.  Say precisely which user-port bit the stock code reads, so the replacement
    can read the ACIA's DCD bit instead.
 3. The modem state machine ($E756) and connection phases ($E040): what each phase does, where the
    Hayes strings are sent, and what a direct null-modem connection (no dial tone, no CONNECT
    message, DCD possibly not wired) would need to skip.
 4. Baud selection: the table loadBaudParameters reads, what the game stores where, and which
    Commodore-key hot keys readAndHandleModemHotkeys ($E2EC) already uses - list every key it tests
    and what it does, so new baud-rate keys can be added without clashing.
 5. What the module does with $E030 (the build id the game reads at $0AEA/$0B2C/$1A9F/$1AFA/$1B3D) -
    what value the stock modem driver holds there, what the trainer holds, and what the game does
    with the difference.  The SwiftLink module must keep the modem driver's value.
Check every address against the listings.  Return path, ok, summary, problems.`,
    { label: 'spec:link', phase: 'Spec', schema: RESULT }),
])

phase('Implement')
const implemented = await agent(`${common}
The two specifications swiftlink/spec_uart.md and swiftlink/spec_link.md are ready - read them.

TASK: turn swiftlink/swiftlinkDriverE000.s (currently an exact copy of the stock module's source)
into the SwiftLink driver.

METHOD.  Edit that file in place.  For each routine you replace, keep its label at its original
address and pad the tail so the following routine still starts where it did.  The file is ca65
source with a plain ".org $E000" and no segments, so the simplest way to guarantee this is to put
    .assert * <= $XXXX, error, "routine overran"
    .res $XXXX - *, $EA
at the end of each replaced routine, where $XXXX is the original address of the next routine.  Do
that for every routine you touch, and add a line comment on the .res saying what used to be there.

WHAT TO IMPLEMENT.
 1. ACIA base detection at init: try $DE00, fall back to $DF00, by writing a control value and
    reading it back (the data and status registers are not suitable for this).  Keep the detected
    base in a spare byte inside your own replaced region and use absolute,X addressing (X = 0 or the
    $100 offset) or self-modified operands - whichever you can fit.  If you cannot fit detection,
    default to $DE00 and say so.
 2. Initialisation: control = $10 | rateBits (8N1, internal baud generator), command = $09
    (DTR asserted, RTS low, receiver IRQ enabled, transmit IRQ off).
 3. Transmit: enable the transmit interrupt (command bits3-2 = 01, i.e. $05) only while the transmit
    ring has something in it, and drop back to $09 when it drains.  queueByteForTransmit must kick
    the transmitter when it was idle.  Keep the pending-character count the frame layer relies on.
 4. Receive: in the NMI, read the status register, and if it shows a received byte push it into the
    same raw receive ring the stock code uses, keeping the carrier sample timer behaviour.  Report
    framing errors, parity errors and overruns through the same error hook the stock code uses.
 5. NMI chaining: if the ACIA did not raise the interrupt, chain to the game's handler exactly the
    way the stock handler does.
 6. Suspend/resume: on suspend, disable the receiver interrupt but leave DTR asserted so the
    connection survives a disk load; on resume, re-enable it.  Keep the $E039/$E03A protocol.
 7. Carrier: read the ACIA's DCD bit instead of the user-port line, feeding the same debounce.
    Because a null-modem cable often leaves DCD unwired, add a direct-connect mode that reports
    carrier present; make it the default when DCD never goes active, or offer it on a hot key, and
    document which you chose.
 8. Baud: replace the baud parameter table with ACIA control values and support 300, 1200, 2400,
    4800, 9600, 19200 and 38400.  Keep the existing hot keys working and add keys for the new rates
    using keys spec_link.md shows to be free.  Leave the default at the rate the game selects, so a
    SwiftLink player and a stock-driver player can still meet at 300 or 1200 baud.

Do NOT touch anything outside the routines listed in spec_uart.md.  Do NOT change the jump table,
the shared variables or $EC00-$EFFF.
Build and check as you go with:
    ./swiftlink/build.sh /tmp/swiftlinkTest.d64
    python3 swiftlink/checkAbi.py swiftlink/build/swiftlinkDriverE000.bin disassembly/build/game_modemDriverE000.orig.bin
checkAbi must pass, and the changed regions it prints must all be inside the routines you meant to
replace.  Report the list of changed regions in your summary.
Return path, ok, summary, problems.`,
  { label: 'implement', phase: 'Implement', schema: RESULT })

phase('Build')
const built = await agent(`${common}
TASK: verify the build of swiftlink/swiftlinkDriverE000.s rigorously.
 1. ./swiftlink/build.sh ../Modem_Wars_SwiftLink.d64  - must produce exactly 4096 bytes and a disk.
 2. python3 swiftlink/checkAbi.py swiftlink/build/swiftlinkDriverE000.bin
    disassembly/build/game_modemDriverE000.orig.bin  - must pass.
 3. Disassemble the changed regions of the new module and read them back, checking that:
      - every replaced routine still ends before the next routine's address;
      - no routine falls through into padding;
      - the NMI handler saves and restores every register it uses and ends in RTI or a chain;
      - no code writes to the frozen regions.
    Use tools/m6502.py if it helps.
 4. Confirm the disk image differs from the original ONLY in track 18 sector 7 and track 34
    sectors 1-15: decrypt both disks with the project's own code and compare every other sector.
 5. Confirm the stock driver still assembles byte-exact (./disassembly/verify.sh | grep -c OK must
    print 24) - the SwiftLink work must not have disturbed the main project.
Fix what you can in swiftlink/ only.  Return path, ok, summary, problems.`,
  { label: 'build', phase: 'Build', schema: RESULT })

phase('Test')
const tested = await agent(`${common}
TASK: test the patched disk ../Modem_Wars_SwiftLink.d64 in VICE.

VICE emulates SwiftLink: -acia1 -acia1mode 1 -acia1base 0xDE00 -acia1irq 1 -myaciadev <n>, and the
RS232 devices can be pointed at a TCP address (see x64sc -help, the -rsdev* options).
IMPORTANT: other people are using this machine.  Pick a free TCP port and a free X display, never use
a fixed one, and only kill processes you started yourself.  extracted/vice/playSession.py shows the
pattern that works: a private Xvfb display, the remote monitor on a free port, -model ntsc (the
loader's timing needs NTSC), and xdotool for key events.

 1. Boot the patched disk with SwiftLink emulation enabled and confirm it reaches the options menu
    exactly like the stock disk does.  Take a screenshot into swiftlink/shots/.
 2. Select "COMPETE WITH MODEM OPPONENT" and confirm the game gets into the modem screens without
    hanging or crashing - the stock driver would be waiting on the user port, so reaching the same
    prompts proves the module is at least alive and being called.  Screenshot it.
 3. Verify the module really is the SwiftLink one and really talks to the ACIA: with the VICE monitor,
    breakpoint or watch the ACIA registers ($DE00-$DE03) and show that the game writes the control and
    command registers after the link is opened.  Report the actual values written and whether they
    match the intended 8N1 + internal clock + DTR/RTS settings.
 4. If you can, run two instances connected to each other over TCP (one -rsdev configured as a
    server, the other as a client) and see whether they exchange bytes.  This is the ideal test but it
    may not be reachable; if it is not, say exactly how far you got and what blocked it.
Do not claim anything you did not observe.  Put screenshots in swiftlink/shots/ and a log of what you
did in swiftlink/testReport.md.  Return path, ok, summary, problems.`,
  { label: 'test', phase: 'Test', schema: RESULT })

phase('Document')
const documented = await agent(`${common}
The driver, its build script and its test report exist.  Read swiftlink/spec_uart.md,
swiftlink/spec_link.md, swiftlink/testReport.md and the source, then write swiftlink/README.md for
someone who wants to use or modify this:
 * what it is, and the one-sentence reason it works (only the bottom layer is hardware specific);
 * what hardware it expects and what it does NOT need (no user-port cable, no CIA2 timers);
 * exactly how to build and install it, and what disk it produces;
 * the baud rates it supports, the hot keys, and the direct-connect mode;
 * wire compatibility: a SwiftLink player and a stock-driver player CAN play each other at a shared
   rate, because every layer above the UART is untouched - and say what happens if the rates differ;
 * what was verified and what was not (be exact and honest, quoting the test report);
 * the map of what changed inside the module, address by address, and what is guaranteed unchanged.
Also add a short "SwiftLink driver" section near the end of the top-level README.md pointing at it -
that is the ONE file outside swiftlink/ you may edit.
Return path, ok, summary, problems.`,
  { label: 'document', phase: 'Document', schema: RESULT })

return { uartSpec, linkSpec, implemented, built, tested, documented }
