{
 "chunk": "game_trainerSetupEC00_EC00",
 "unit": "game/trainerSetupEC00",
 "unitLabels": {
  "game/trainerSetupEC00": {
   "EC54": "checkForTheBombGameType",
   "EC91": "usePlanFour",
   "ECD4": "buildPlanRecordPointers",
   "ECF3": "copyPlanRecordLoop",
   "ED32": "rollGroupTargetRowJitter",
   "EE4A": "checkGroupStartsAtOnce",
   "EE55": "applyGroupStartDelay",
   "EEB9": "storeGroupCentreRow",
   "EEDD": "storeHeadingOffset",
   "EF10": "stripIdleBitFromDestCol",
   "EF27": "staleDroneSteerTail",
   "EFAB": "staleDroneTargetPickerTail",
   "EFD0": "msgDataDiskName",
   "EFD4": "msgGameDiskName",
   "EFD8": "msgCustomOff",
   "EFF5": "overlayPageTailBytes"
  }
 },
 "notes": {
  "EC00": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Three parallel 5-byte tables indexed by the AI plan number 0-4 that initTrainerAi derives",
    "from the game type and keeps in $8966.  They are read together at $ECB9-$ECD1."
   ],
   "line": "plan 0-4 -> spearhead group id: none for the two 11-group plans, group 4 for the 5-group plans"
  },
  "EC05": {
   "unit": "game/trainerSetupEC00",
   "line": "plan 0-4 -> highest group index: 10 (11 groups) for plans 0/1, 4 (5 groups) for plans 2-4"
  },
  "EC0A": {
   "unit": "game/trainerSetupEC00",
   "line": "plan 0-4 -> drone detonation score threshold: $FF (unreachable score = never detonate) for plans 0/1, 8 or 12 for plans 2-4"
  },
  "EC0F": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "initTrainerAi - Builds the whole opening plan of the solo trainer (the computer opponent, side 1 =",
    "units 50-99).  Clears the AI's scalar work area in the $8800 block, picks a first objective near the",
    "human comcen, turns the game type ($0BA3 bits 0-2) into a plan number 0-4, loads the five 150-byte",
    "opening plays from track 29 sectors 6-8 into $0200-$04FF, copies the chosen play's 50 group ids and",
    "50 destination cells into $8832 and unitDestCol/unitDestRow of units 50-99, re-aims every group at a",
    "random point for the bigger plans, rebuilds the per-group tables and works out the round at which the",
    "AI changes gear ($896E).",
    "In:  $0BA3 game type bits 0-2; $91CB game clock (the game length in rounds, put there by",
    "     setGameClockForType at $7B1A); $92AC TERRAIN PTS setting of side 1; unit arrays unitFlagsTable",
    "     $F708, unitTypeTable $F76C, unitDestColTable $F7D0, unitDestRowTable $F834; the three plan",
    "     tables at $EC00/$EC05/$EC0A; the disk, through setLoadDest/loadSectors; nextGameRandom",
    "Out: $0200-$04FF overwritten with the playbook; the AI work area $8800-$8979 initialised; group ids",
    "     $8832-$8863 and unitDestCol/unitDestRow of units 50-99 set; unitFlagsTable[99]=$21 and",
    "     unitDisplayFlags[99]=$20 for plans 4 (FULL WAR / DEFENDER); unitDestCol[99] bits 6-7 cleared for",
    "     THE BOMB; $E01E=0; one $A5,00,$14 setup command queued for DEFENDER; $91CB left at $FF;",
    "     zp_18-zp_2B and zp_48-zp_4D clobbered",
    "Called from: $7B30 in game/mapGenerator6F00 - the only call, made once, right after $7B24 loads this",
    "     overlay (track 29 sectors 0-3) and immediately before $7B33 puts the modem-driver tail (track 34",
    "     sectors 12-15) back over it.  Everything in this file therefore runs exactly once, in a solo game"
   ],
   "block": [
    "Clear the trainer's scalar variables.  The whole $8800-$89FF page pair still holds the modem",
    "status strings loaded from track 29 s9-10, which are dead in a solo game, so every AI variable",
    "has to be written before it can be read.  X = 0 for the flags and counters, then X = $FF for the",
    "index variables whose 'none' value is $FF."
   ],
   "line": "X = 0: the value for every counter and flag cleared below"
  },
  "EC11": {
   "unit": "game/trainerSetupEC00",
   "line": "no fake incoming packet is pending yet (the comm module's rx packet length)"
  },
  "EC14": {
   "unit": "game/trainerSetupEC00",
   "line": "'human comcen has been spotted' flag = 0; randomiseCoordinate ($E14A) widens its aim while this is clear"
  },
  "EC17": {
   "unit": "game/trainerSetupEC00",
   "line": "AI command-queue length = 0: the queue at $88A0 is empty"
  },
  "EC1A": {
   "unit": "game/trainerSetupEC00",
   "line": "safest compass direction from the last threat sweep round the AI comcen = 0"
  },
  "EC1D": {
   "unit": "game/trainerSetupEC00",
   "line": "rounds left before the AI re-picks its rally point = 0, so it re-picks on the first round"
  },
  "EC20": {
   "unit": "game/trainerSetupEC00",
   "line": "missile-pass scratch of the resident $EC00 tail = 0"
  },
  "EC23": {
   "unit": "game/trainerSetupEC00",
   "line": "commands queued by the AI this round = 0"
  },
  "EC26": {
   "unit": "game/trainerSetupEC00",
   "line": "drone state = 0: no drone airborne and the $EE00 drone-flying overlay is not resident ($FF marks it resident)"
  },
  "EC29": {
   "unit": "game/trainerSetupEC00",
   "line": "previous-round 'AI comcen destroyed' latch = 0"
  },
  "EC2C": {
   "unit": "game/trainerSetupEC00",
   "line": "'AI comcen destroyed' flag tested by planTrainerRound = 0"
  },
  "EC2F": {
   "unit": "game/trainerSetupEC00",
   "line": "X = $FF: the 'none' value for the index variables that follow"
  },
  "EC30": {
   "unit": "game/trainerSetupEC00",
   "line": "no spearhead group yet for the AI comcen to escort"
  },
  "EC33": {
   "unit": "game/trainerSetupEC00",
   "line": "no drone target unit picked"
  },
  "EC36": {
   "unit": "game/trainerSetupEC00",
   "line": "no player unit is being hunted for having shot at the AI comcen"
  },
  "EC39": {
   "unit": "game/trainerSetupEC00",
   "line": "no home-defence group yet; a real one is chosen at $EC9C unless the plan is QB SNEAK"
  },
  "EC3C": {
   "unit": "game/trainerSetupEC00",
   "line": "drone detonation score threshold = $FF (never); replaced from aiPlanParamTable at $ECCC"
  },
  "EC3F": {
   "unit": "game/trainerSetupEC00",
   "line": "and its decaying working copy = $FF"
  },
  "EC42": {
   "unit": "game/trainerSetupEC00",
   "line": "comcen threat threshold"
  },
  "EC44": {
   "unit": "game/trainerSetupEC00",
   "line": "the resident AI only reacts to pressure on its own comcen once the measured threat reaches 5 ($E29A)"
  },
  "EC47": {
   "unit": "game/trainerSetupEC00",
   "line": "really trainerAiE000:pickTrainerRallyPoint ($E12F) - the modem driver's name for this address is a collision; it stores a random cell near the human comcen in $8969/$896A as the AI's first objective"
  },
  "EC4A": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Turn the game type into an AI plan number 0-4 (kept in $8966):",
    "  0 SCRIMAGE -> plan 0, 1 QB SNEAK -> plan 1 (no home-defence group),",
    "  2 THE BOMB -> plan 0 with no playbook at all (only the two comcens exist),",
    "  3 FACE-OFF -> plan 2, 4 SLUGGERS -> plan 3, 5 FULL WAR -> plan 4,",
    "  6 DEFENDER -> plan 4 plus a queued point award for the defending computer."
   ],
   "line": "the game options byte written by the setup screens"
  },
  "EC4D": {
   "unit": "game/trainerSetupEC00",
   "line": "bits 0-2 = game type 0 SCRIMAGE, 1 QB SNEAK, 2 THE BOMB, 3 FACE-OFF, 4 SLUGGERS, 5 FULL WAR, 6 DEFENDER"
  },
  "EC4F": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = game type"
  },
  "EC50": {
   "unit": "game/trainerSetupEC00",
   "line": "is the game type below 2?"
  },
  "EC52": {
   "unit": "game/trainerSetupEC00",
   "line": "SCRIMAGE and QB SNEAK use the plan number that equals the game type"
  },
  "EC54": {
   "unit": "game/trainerSetupEC00",
   "line": "branch if the game type is 3 or higher; fall through only for game type 2 = THE BOMB"
  },
  "EC56": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "THE BOMB: both sides own nothing but their comcen, so there is no formation to deploy.",
    "Free the AI comcen to move and pretend it is the whole of group 0."
   ],
   "line": "ordered destination column of the AI comcen, unitDestCol[99]"
  },
  "EC59": {
   "unit": "game/trainerSetupEC00",
   "line": "keep the 0-39 column only, clearing the idle bit 7 so the engine treats the comcen as still moving"
  },
  "EC5B": {
   "unit": "game/trainerSetupEC00",
   "line": "store it back"
  },
  "EC5E": {
   "unit": "game/trainerSetupEC00",
   "line": "group 0"
  },
  "EC60": {
   "unit": "game/trainerSetupEC00",
   "line": "group id of AI slot 49 = unit 99, the AI comcen: the only live AI unit in THE BOMB"
  },
  "EC63": {
   "unit": "game/trainerSetupEC00",
   "line": "highest group index in use = 0"
  },
  "EC66": {
   "unit": "game/trainerSetupEC00",
   "line": "AI plan number = 0"
  },
  "EC69": {
   "unit": "game/trainerSetupEC00",
   "line": "skip the playbook load and the group scatter entirely and go straight to the table rebuild"
  },
  "EC6C": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Game types 3-6.  Y is biased down by one so that FACE-OFF and SLUGGERS map straight onto",
    "plans 2 and 3, while FULL WAR and DEFENDER both end up on plan 4."
   ],
   "line": "Y = gameType - 1: FACE-OFF -> 2, SLUGGERS -> 3, FULL WAR -> 4, DEFENDER -> 5"
  },
  "EC6D": {
   "unit": "game/trainerSetupEC00",
   "line": "is the biased type below 4?"
  },
  "EC6F": {
   "unit": "game/trainerSetupEC00",
   "line": "FACE-OFF -> plan 2, SLUGGERS -> plan 3"
  },
  "EC71": {
   "unit": "game/trainerSetupEC00",
   "line": "FULL WAR (biased type 4) -> plan 4, without the extra point award"
  },
  "EC73": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "DEFENDER only: stage command $A5 (cmdSetupOption, $55A5) with kind 0 and value 20 and queue it",
    "as if the remote player had sent it, so the handler adds 20 x 10 = 200 purchase points to the",
    "AI's side score - the handicap that pays for the defending computer's extra force."
   ],
   "line": "command byte $A5 = cmdSetupOption"
  },
  "EC75": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 0 of the AI's 3-byte command scratch"
  },
  "EC78": {
   "unit": "game/trainerSetupEC00",
   "line": "argument 1 = kind 0: 'add points to the sender's side'"
  },
  "EC7A": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 1 of the command scratch; the msg... label here is a $8800 message-string address collision"
  },
  "EC7D": {
   "unit": "game/trainerSetupEC00",
   "line": "argument 2 = 20 points"
  },
  "EC7F": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 2 of the command scratch"
  },
  "EC82": {
   "unit": "game/trainerSetupEC00",
   "line": "append the staged 3-byte command to the AI's outgoing queue at $88A0"
  },
  "EC85": {
   "unit": "game/trainerSetupEC00",
   "line": "undo the 'commands queued this round' bump queueAiCommand made: this is a setup option, not a battle order"
  },
  "EC88": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "FULL WAR and DEFENDER: reset the AI comcen's own flags.  unitDisplayFlags mirrors bits 2-6 of",
    "unitFlagsTable, so $21/$20 means facing 1, side bit 5 set (side 1), no group membership, and",
    "the idle bit 7 clear - i.e. the comcen counts as still moving (probably so it relocates during",
    "these two long games)."
   ],
   "line": "$21 = facing 1 (bits 0-1), group id 0 (bits 2-4), side 1 (bit 5), not in a group (bit 6 clear)"
  },
  "EC8A": {
   "unit": "game/trainerSetupEC00",
   "line": "unitFlagsTable[99] - the AI comcen"
  },
  "EC8D": {
   "unit": "game/trainerSetupEC00",
   "line": "$20 = the same value with the facing bits stripped, which is what the engine mirrors into the display flags"
  },
  "EC8E": {
   "unit": "game/trainerSetupEC00",
   "line": "unitDisplayFlags[99]: bit 7 (arrived/idle) clear, bit 6 (group order active) clear"
  },
  "EC91": {
   "unit": "game/trainerSetupEC00",
   "line": "FULL WAR and DEFENDER both use plan 4"
  },
  "EC93": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Record the plan and choose the home-defence group: a random group 1, 2 or 3 that will be parked",
    "on the AI comcen instead of joining the attack.  QB SNEAK deliberately keeps none back."
   ],
   "line": "remember the plan number 0-4 for the rest of the setup and for the resident AI"
  },
  "EC96": {
   "unit": "game/trainerSetupEC00",
   "line": "$FF = 'no home-defence group'"
  },
  "EC98": {
   "unit": "game/trainerSetupEC00",
   "line": "is this plan 1 (QB SNEAK)?"
  },
  "EC9A": {
   "unit": "game/trainerSetupEC00",
   "line": "QB SNEAK throws everything forward: leave the guard group as $FF"
  },
  "EC9C": {
   "unit": "game/trainerSetupEC00",
   "line": "roll for a guard group"
  },
  "EC9F": {
   "unit": "game/trainerSetupEC00",
   "line": "keep the low two bits: 0-3"
  },
  "ECA1": {
   "unit": "game/trainerSetupEC00",
   "line": "reject 0 - group 0 is the one that follows the spearhead, so it is never the guard; re-roll"
  },
  "ECA3": {
   "unit": "game/trainerSetupEC00",
   "line": "home-defence group id 1-3"
  },
  "ECA6": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Load the AI playbook: track 29 sectors 6-8 -> $0200-$04FF, over the terrain tile rules (which the",
    "map generator has already finished with) and the copy-protection answer table.  The 768 bytes hold",
    "five 150-byte records, one per plan: 50 group ids, then 50 destination columns, then 50 destination",
    "rows, all indexed by AI slot 0-49 = unit 50-99.  The caller has already bumped diskLoadDepth ($7B17)."
   ],
   "line": "destination low byte = $00"
  },
  "ECA8": {
   "unit": "game/trainerSetupEC00",
   "line": "destination high byte = $02, i.e. load to $0200"
  },
  "ECAA": {
   "unit": "game/trainerSetupEC00",
   "line": "tell the fast loader where the next sectors go"
  },
  "ECAD": {
   "unit": "game/trainerSetupEC00",
   "line": "track 29"
  },
  "ECAF": {
   "unit": "game/trainerSetupEC00",
   "line": "first sector 6"
  },
  "ECB1": {
   "unit": "game/trainerSetupEC00",
   "line": "three sectors = 768 bytes = the five opening plays"
  },
  "ECB3": {
   "unit": "game/trainerSetupEC00",
   "line": "pull them in over $0200-$04FF"
  },
  "ECB6": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Copy the plan's fixed parameters out of the three tables at the top of this overlay."
   ],
   "line": "Y = plan number, the index into all three 5-byte tables (and still in Y when multiply8x8 is called at $ECD4)"
  },
  "ECB9": {
   "unit": "game/trainerSetupEC00",
   "line": "spearhead group for this plan"
  },
  "ECBC": {
   "unit": "game/trainerSetupEC00",
   "line": "the group the AI comcen escorts and that group 0 is told to follow; $FF for plans 0/1"
  },
  "ECBF": {
   "unit": "game/trainerSetupEC00",
   "line": "highest group index used by this plan"
  },
  "ECC2": {
   "unit": "game/trainerSetupEC00",
   "line": "store it"
  },
  "ECC5": {
   "unit": "game/trainerSetupEC00",
   "line": "+1"
  },
  "ECC6": {
   "unit": "game/trainerSetupEC00",
   "line": "number of groups (11 or 5), the loop limit the resident AI uses at $E6A5/$E960/$EB3C"
  },
  "ECC9": {
   "unit": "game/trainerSetupEC00",
   "line": "drone detonation score threshold for this plan"
  },
  "ECCC": {
   "unit": "game/trainerSetupEC00",
   "line": "the fixed threshold"
  },
  "ECCF": {
   "unit": "game/trainerSetupEC00",
   "line": "and the working copy that the drone code decays to 3/4 as it gets desperate"
  },
  "ECD2": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Point three zero-page pointers at the three 50-byte arrays of the selected 150-byte plan record:",
    "  mapCellPtr ($48) -> group ids,  fillEndPtr ($4A) -> destination columns,  planRowPtr ($4C) -> rows.",
    "Base = $0200 + 150 * planNumber."
   ],
   "line": "150 = the size of one plan record"
  },
  "ECD4": {
   "unit": "game/trainerSetupEC00",
   "line": "A x Y = 150 x plan number; returns the product low byte in A and the high byte in Y"
  },
  "ECD7": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte of the record's offset into the playbook"
  },
  "ECD9": {
   "unit": "game/trainerSetupEC00",
   "line": "add $100 ..."
  },
  "ECDA": {
   "unit": "game/trainerSetupEC00",
   "line": "... and another $100, because the playbook was loaded at $0200"
  },
  "ECDB": {
   "unit": "game/trainerSetupEC00",
   "line": "mapCellPtr now points at the record's 50 group ids"
  },
  "ECDD": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the add below"
  },
  "ECDE": {
   "unit": "game/trainerSetupEC00",
   "line": "+50 bytes: past the group ids"
  },
  "ECE0": {
   "unit": "game/trainerSetupEC00",
   "line": "no page crossing"
  },
  "ECE2": {
   "unit": "game/trainerSetupEC00",
   "line": "carry into the high byte"
  },
  "ECE3": {
   "unit": "game/trainerSetupEC00",
   "line": "fillEndPtr points at the record's 50 destination columns"
  },
  "ECE5": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of that pointer"
  },
  "ECE7": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry again"
  },
  "ECE8": {
   "unit": "game/trainerSetupEC00",
   "line": "+50 more bytes: past the destination columns"
  },
  "ECEA": {
   "unit": "game/trainerSetupEC00",
   "line": "no page crossing"
  },
  "ECEC": {
   "unit": "game/trainerSetupEC00",
   "line": "carry into the high byte"
  },
  "ECED": {
   "unit": "game/trainerSetupEC00",
   "line": "planRowPtr points at the record's 50 destination rows"
  },
  "ECEF": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of that pointer"
  },
  "ECF1": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Copy the plan into the live arrays, AI slot 49 down to 0 (units 99 down to 50)."
   ],
   "line": "49 = the last AI slot; the loop counts down to 0"
  },
  "ECF3": {
   "unit": "game/trainerSetupEC00",
   "line": "group id of this unit in the chosen opening play"
  },
  "ECF5": {
   "unit": "game/trainerSetupEC00",
   "line": "$8832 + slot = the AI's own group-id array (the resident AI addresses it as $8800 + absolute unit index)"
  },
  "ECF8": {
   "unit": "game/trainerSetupEC00",
   "line": "destination column from the play"
  },
  "ECFA": {
   "unit": "game/trainerSetupEC00",
   "line": "unitDestCol[50 + slot]"
  },
  "ECFD": {
   "unit": "game/trainerSetupEC00",
   "line": "destination row from the play"
  },
  "ECFF": {
   "unit": "game/trainerSetupEC00",
   "line": "unitDestRow[50 + slot]"
  },
  "ED02": {
   "unit": "game/trainerSetupEC00",
   "line": "previous slot"
  },
  "ED03": {
   "unit": "game/trainerSetupEC00",
   "line": "loop until all 50 AI units have their group and destination"
  },
  "ED05": {
   "unit": "game/trainerSetupEC00",
   "line": "which plan is this?"
  },
  "ED08": {
   "unit": "game/trainerSetupEC00",
   "line": "plans 2, 3 and 4 re-aim every group; plans 0 and 1 keep the playbook's own destinations"
  },
  "ED0A": {
   "unit": "game/trainerSetupEC00",
   "line": "SCRIMAGE / QB SNEAK: nothing more to place, go and rebuild the tables"
  },
  "ED0C": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Plans 2-4 only: scatter the groups.  Every group from the highest down to 1 is given a random",
    "target - any column 4-35, and a row in an 8-row band 7 rows above the group's own average ordered",
    "row - and the whole formation is translated there.  Group 0 is handled separately afterwards: it",
    "is sent to the same point as the spearhead group."
   ],
   "line": "highest group index of this plan"
  },
  "ED0F": {
   "unit": "game/trainerSetupEC00",
   "line": "group loop counter, counting down to 0"
  },
  "ED12": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = the group being placed"
  },
  "ED15": {
   "unit": "game/trainerSetupEC00",
   "line": "measure group Y ($E531): member count, combat value, bounding box, centre and average ordered cell; C=1 if the group is empty (then zp_1F below is stale, but the group has no members to move either)"
  },
  "ED18": {
   "unit": "game/trainerSetupEC00",
   "line": "roll a column"
  },
  "ED1B": {
   "unit": "game/trainerSetupEC00",
   "line": "0-31 ..."
  },
  "ED1D": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the add below"
  },
  "ED1E": {
   "unit": "game/trainerSetupEC00",
   "line": "... + 4 = column 4-35, keeping the formation clear of both map edges"
  },
  "ED20": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_22 = wanted formation centre column"
  },
  "ED22": {
   "unit": "game/trainerSetupEC00",
   "line": "and remember it as group Y's target column"
  },
  "ED25": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_1F = the group's average ordered row, from computeTrainerGroupStats"
  },
  "ED27": {
   "unit": "game/trainerSetupEC00",
   "line": "is that row above 16?"
  },
  "ED29": {
   "unit": "game/trainerSetupEC00",
   "line": "no clamp needed"
  },
  "ED2B": {
   "unit": "game/trainerSetupEC00",
   "line": "floor it at 15 so the band computed below cannot go negative"
  },
  "ED2D": {
   "unit": "game/trainerSetupEC00",
   "line": "prepare to subtract"
  },
  "ED2E": {
   "unit": "game/trainerSetupEC00",
   "line": "start the 8-row band 7 rows before the group's own row"
  },
  "ED30": {
   "unit": "game/trainerSetupEC00",
   "line": "base row of the band"
  },
  "ED32": {
   "unit": "game/trainerSetupEC00",
   "line": "roll a row offset"
  },
  "ED35": {
   "unit": "game/trainerSetupEC00",
   "line": "0-7 rows of jitter inside the band"
  },
  "ED37": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the add"
  },
  "ED38": {
   "unit": "game/trainerSetupEC00",
   "line": "base + jitter"
  },
  "ED3A": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_24 = wanted formation centre row"
  },
  "ED3C": {
   "unit": "game/trainerSetupEC00",
   "line": "and remember it as group Y's target row"
  },
  "ED3F": {
   "unit": "game/trainerSetupEC00",
   "line": "clip the point to the map and shift every member of the group by the same delta"
  },
  "ED42": {
   "unit": "game/trainerSetupEC00",
   "line": "next group down"
  },
  "ED45": {
   "unit": "game/trainerSetupEC00",
   "line": "counter went negative: every group has been placed"
  },
  "ED47": {
   "unit": "game/trainerSetupEC00",
   "line": "still above group 0: keep scattering"
  },
  "ED49": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "The counter has reached group 0.  Group 0 is the follow-up wave: instead of a random point it is",
    "ordered to the spearhead group's target, so the two arrive together."
   ],
   "line": "spearhead group id for this plan"
  },
  "ED4C": {
   "unit": "game/trainerSetupEC00",
   "line": "$FF = no spearhead (only possible for plans 0/1, which never get here): fall back to a random point"
  },
  "ED4E": {
   "unit": "game/trainerSetupEC00",
   "line": "the spearhead group's target column"
  },
  "ED51": {
   "unit": "game/trainerSetupEC00",
   "line": "and its target row"
  },
  "ED54": {
   "unit": "game/trainerSetupEC00",
   "line": "group 0"
  },
  "ED56": {
   "unit": "game/trainerSetupEC00",
   "line": "order group 0 to the same cell"
  },
  "ED59": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Finally station the home-defence group on the AI comcen's own destination."
   ],
   "line": "home-defence group id"
  },
  "ED5C": {
   "unit": "game/trainerSetupEC00",
   "line": "$FF: QB SNEAK and THE BOMB keep nobody back"
  },
  "ED5E": {
   "unit": "game/trainerSetupEC00",
   "line": "AI comcen's ordered column, unitDestCol[99]"
  },
  "ED61": {
   "unit": "game/trainerSetupEC00",
   "line": "strip the idle bit 7 to leave a plain 0-39 column"
  },
  "ED63": {
   "unit": "game/trainerSetupEC00",
   "line": "X = target column, as orderAiGroupToPoint expects"
  },
  "ED64": {
   "unit": "game/trainerSetupEC00",
   "line": "AI comcen's ordered row, unitDestRow[99]"
  },
  "ED67": {
   "unit": "game/trainerSetupEC00",
   "line": "strip the idle bit"
  },
  "ED69": {
   "unit": "game/trainerSetupEC00",
   "line": "order the guard group (still in Y) onto the comcen"
  },
  "ED6C": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Common tail for every plan: rebuild the per-group bookkeeping, then decide when the AI changes",
    "gear.  gameClock counts DOWN from the game length, so subtracting 32-95 gives the clock value the",
    "game will reach after that many rounds; the resident AI compares $91CB against $896E at $E3E5 and",
    "$E765.  For plans 0/1 the value is the full game length, which makes those comparisons true for the",
    "whole game."
   ],
   "line": "build the group averages, the per-unit activation clocks and the heading search order"
  },
  "ED6F": {
   "unit": "game/trainerSetupEC00",
   "line": "roll the delay before the AI changes gear"
  },
  "ED72": {
   "unit": "game/trainerSetupEC00",
   "line": "0-63 ..."
  },
  "ED74": {
   "unit": "game/trainerSetupEC00",
   "line": "... with bit 5 forced on: 32-95 rounds"
  },
  "ED76": {
   "unit": "game/trainerSetupEC00",
   "line": "keep it"
  },
  "ED78": {
   "unit": "game/trainerSetupEC00",
   "line": "the game length in rounds (setGameClockForType at $7B1A left it here for us: 63/63/31/127/127/254/254 by game type)"
  },
  "ED7B": {
   "unit": "game/trainerSetupEC00",
   "line": "which plan?"
  },
  "ED7E": {
   "unit": "game/trainerSetupEC00",
   "line": "plans 0 and 1 ..."
  },
  "ED80": {
   "unit": "game/trainerSetupEC00",
   "line": "... use the whole game length unchanged"
  },
  "ED82": {
   "unit": "game/trainerSetupEC00",
   "line": "prepare to subtract"
  },
  "ED83": {
   "unit": "game/trainerSetupEC00",
   "line": "plans 2-4: the switch happens once 32-95 rounds have been played"
  },
  "ED85": {
   "unit": "game/trainerSetupEC00",
   "line": "the clock value at which the AI's second phase begins"
  },
  "ED88": {
   "unit": "game/trainerSetupEC00",
   "line": "$FF = 'clock not running'"
  },
  "ED8A": {
   "unit": "game/trainerSetupEC00",
   "line": "put the game clock back the way the setup screen left it at $7AF2; the real countdown is loaded again at $7B86 once play starts"
  },
  "ED8D": {
   "unit": "game/trainerSetupEC00",
   "line": "back to $7B33, which immediately reloads the modem-driver tail over this overlay"
  },
  "ED8E": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "rebuildAiGroupTables - Recomputes all of the trainer's per-group and per-unit bookkeeping from the",
    "current unit arrays.  Clears the twelve group slots and gives each a random 0-15 round start delay,",
    "then walks AI slots 49-0 accumulating each live unit's ordered destination and current cell into its",
    "group's 16-bit sums, stamping the unit's activation clock, flagging units that already stand on their",
    "destination, and dropping dead units out of their group.  Finally it divides the four sums by the",
    "member count so the group arrays hold averages: $88D2/$88DE = average ordered destination, $88EA/$88F6",
    "= centre of the group as it stands now.",
    "In:  $8832 group id per AI slot, $8953 highest group, $8964 home-defence group, $8966 plan number,",
    "     $91CB game length, $92AC TERRAIN PTS of side 1, unitColTable+50 $F672, unitRowTable+50 $F6D6,",
    "     unitTypeTable+50 $F79E, unitDestColTable+50 $F802, unitDestRowTable+50 $F866, nextGameRandom",
    "Out: group arrays $88D2/$88DE/$88EA/$88F6 (averages), $8902 member count, $893E start delay and the",
    "     matching sum high bytes at $0464/$0478/$048C/$04A0, 12 entries each; $8800-$8831 activation",
    "     clock per AI slot; $8864-$8895 zone flag per AI slot; $8832 entries of dead units set to $FF;",
    "     bit 7 of unitDestCol set for units already at their destination; zp_18/1A/1B/1C/22/24/27/28 used",
    "Called from: $ED6C in initTrainerAi (this file) - the only caller in this overlay"
   ],
   "block": [
    "Clear the twelve group slots.  The 16-bit sums are split: the low bytes live in the $8800 block",
    "next to the other group arrays, the high bytes in the $0400 page (which held the copy-protection",
    "answer table and the scan lists and is free once a game is running).  The D_0464/D_0478/D_048C/",
    "writeSectorToDisk labels are address collisions from the $0400 unit and mean nothing here."
   ],
   "line": "refresh the random left/right heading search order at $894A"
  },
  "ED91": {
   "unit": "game/trainerSetupEC00",
   "line": "group slots 11 down to 0 (only 0..$8953 are really used, but all twelve are cleared)"
  },
  "ED93": {
   "unit": "game/trainerSetupEC00",
   "line": "the value written into all nine group arrays below"
  },
  "ED95": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the group's ordered-column sum"
  },
  "ED98": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the ordered-row sum"
  },
  "ED9B": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the current-column sum"
  },
  "ED9E": {
   "unit": "game/trainerSetupEC00",
   "line": "$04A0: high byte of the current-row sum (the writeSectorToDisk label is a $0400-page collision)"
  },
  "EDA1": {
   "unit": "game/trainerSetupEC00",
   "line": "member count"
  },
  "EDA4": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte of the ordered-column sum, which becomes the average"
  },
  "EDA7": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte of the ordered-row sum"
  },
  "EDAA": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte of the current-column sum"
  },
  "EDAD": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte of the current-row sum"
  },
  "EDB0": {
   "unit": "game/trainerSetupEC00",
   "line": "roll this group's start delay"
  },
  "EDB3": {
   "unit": "game/trainerSetupEC00",
   "line": "0-15 rounds"
  },
  "EDB5": {
   "unit": "game/trainerSetupEC00",
   "line": "the group waits that many rounds before it starts obeying its orders"
  },
  "EDB8": {
   "unit": "game/trainerSetupEC00",
   "line": "next group down"
  },
  "EDB9": {
   "unit": "game/trainerSetupEC00",
   "line": "loop over all twelve group slots"
  },
  "EDBB": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Walk the 50 AI slots (unit 50+slot), 49 down to 0.  Each live unit contributes to its group's four",
    "running sums and gets its own activation clock and idle flag; each dead one is unhooked from its",
    "group."
   ],
   "line": "49 = the last AI slot"
  },
  "EDBD": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_18 = the AI slot being processed"
  },
  "EDBF": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = AI slot"
  },
  "EDC1": {
   "unit": "game/trainerSetupEC00",
   "line": "$92AC = the TERRAIN PTS setting of side 1 (the AI), 0-3, set by the setup screens"
  },
  "EDC4": {
   "unit": "game/trainerSetupEC00",
   "line": "terrain points are switched on"
  },
  "EDC6": {
   "unit": "game/trainerSetupEC00",
   "line": "switched off: force the flag on for every unit"
  },
  "EDC8": {
   "unit": "game/trainerSetupEC00",
   "line": "with points on the value is 1-3, so bit 7 is clear and the flag ends up 0"
  },
  "EDCA": {
   "unit": "game/trainerSetupEC00",
   "line": "per-unit zone flag; computeTrainerGroupStats ($E579) counts the members whose bit 7 is CLEAR, i.e. the ones that still have a zone bonus to go and earn"
  },
  "EDCD": {
   "unit": "game/trainerSetupEC00",
   "line": "type byte of this AI unit"
  },
  "EDD0": {
   "unit": "game/trainerSetupEC00",
   "line": "bit 7 = dead; branch for a live unit"
  },
  "EDD2": {
   "unit": "game/trainerSetupEC00",
   "line": "$FF = 'in no group'"
  },
  "EDD4": {
   "unit": "game/trainerSetupEC00",
   "line": "a dead unit is unhooked from its group so no later pass counts it"
  },
  "EDD7": {
   "unit": "game/trainerSetupEC00",
   "line": "leftover: A is never used, the jump target only decrements the slot counter"
  },
  "EDD9": {
   "unit": "game/trainerSetupEC00",
   "line": "on to the next AI slot"
  },
  "EDDC": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Live unit: add its ordered destination and its current cell to its group's four 16-bit sums, and",
    "leave both cells in the zero page for the cellDistance test further down."
   ],
   "line": "X = the unit's group id"
  },
  "EDDF": {
   "unit": "game/trainerSetupEC00",
   "line": "one more member in that group"
  },
  "EDE2": {
   "unit": "game/trainerSetupEC00",
   "line": "ordered destination column"
  },
  "EDE5": {
   "unit": "game/trainerSetupEC00",
   "line": "strip the idle bit 7 to leave a plain 0-39 column"
  },
  "EDE7": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_27 = point B for the cellDistance call at $EE5C"
  },
  "EDE9": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry before the 16-bit add"
  },
  "EDEA": {
   "unit": "game/trainerSetupEC00",
   "line": "add into the group's ordered-column sum ..."
  },
  "EDED": {
   "unit": "game/trainerSetupEC00",
   "line": "... low byte"
  },
  "EDF0": {
   "unit": "game/trainerSetupEC00",
   "line": "A = 0: only the carry is being added on"
  },
  "EDF2": {
   "unit": "game/trainerSetupEC00",
   "line": "propagate the carry ..."
  },
  "EDF5": {
   "unit": "game/trainerSetupEC00",
   "line": "... into the high byte at $0464"
  },
  "EDF8": {
   "unit": "game/trainerSetupEC00",
   "line": "ordered destination row"
  },
  "EDFB": {
   "unit": "game/trainerSetupEC00",
   "line": "strip the idle bit"
  },
  "EDFD": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_28 = row of point B"
  },
  "EDFF": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry before the 16-bit add"
  },
  "EE00": {
   "unit": "game/trainerSetupEC00",
   "line": "add into the group's ordered-row sum ..."
  },
  "EE03": {
   "unit": "game/trainerSetupEC00",
   "line": "... low byte"
  },
  "EE06": {
   "unit": "game/trainerSetupEC00",
   "line": "A = 0: only the carry is being added on"
  },
  "EE08": {
   "unit": "game/trainerSetupEC00",
   "line": "propagate the carry ..."
  },
  "EE0B": {
   "unit": "game/trainerSetupEC00",
   "line": "... into the high byte at $0478"
  },
  "EE0E": {
   "unit": "game/trainerSetupEC00",
   "line": "the unit's current column, unitCol[50 + slot]"
  },
  "EE11": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_22 = point A for cellDistance"
  },
  "EE13": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry before the 16-bit add"
  },
  "EE14": {
   "unit": "game/trainerSetupEC00",
   "line": "add into the group's current-column sum ..."
  },
  "EE17": {
   "unit": "game/trainerSetupEC00",
   "line": "... low byte"
  },
  "EE1A": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte at $048C ..."
  },
  "EE1D": {
   "unit": "game/trainerSetupEC00",
   "line": "... plus the carry"
  },
  "EE1F": {
   "unit": "game/trainerSetupEC00",
   "line": "store it back"
  },
  "EE22": {
   "unit": "game/trainerSetupEC00",
   "line": "the unit's current row, unitRow[50 + slot]"
  },
  "EE25": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_24 = row of point A"
  },
  "EE27": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry before the 16-bit add"
  },
  "EE28": {
   "unit": "game/trainerSetupEC00",
   "line": "add into the group's current-row sum ..."
  },
  "EE2B": {
   "unit": "game/trainerSetupEC00",
   "line": "... low byte"
  },
  "EE2E": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte at $04A0 ..."
  },
  "EE31": {
   "unit": "game/trainerSetupEC00",
   "line": "... plus the carry"
  },
  "EE33": {
   "unit": "game/trainerSetupEC00",
   "line": "store it back"
  },
  "EE36": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Stamp the unit's activation clock.  The clock counts down, so subtracting the group's 0-15 round",
    "delay produces the clock value at which this unit wakes up.  SCRIMAGE (plan 0) starts everybody at",
    "once, and so do group 0 and the home-defence group in every plan."
   ],
   "line": "Y = 0 = 'stagger the groups'"
  },
  "EE38": {
   "unit": "game/trainerSetupEC00",
   "line": "which plan?"
  },
  "EE3B": {
   "unit": "game/trainerSetupEC00",
   "line": "any plan but 0 staggers the start"
  },
  "EE3D": {
   "unit": "game/trainerSetupEC00",
   "line": "plan 0 (SCRIMAGE): Y = $FF = 'no staggered start'"
  },
  "EE3E": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_1A holds the marker; only its bit 7 is tested"
  },
  "EE40": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = the AI slot again"
  },
  "EE42": {
   "unit": "game/trainerSetupEC00",
   "line": "X = its group id"
  },
  "EE45": {
   "unit": "game/trainerSetupEC00",
   "line": "the full game length in rounds - the clock value at the moment play starts"
  },
  "EE48": {
   "unit": "game/trainerSetupEC00",
   "line": "test bit 7 of the plan-0 marker without disturbing A"
  },
  "EE4A": {
   "unit": "game/trainerSetupEC00",
   "line": "plan 0: this unit is active from round one"
  },
  "EE4C": {
   "unit": "game/trainerSetupEC00",
   "line": "is this the home-defence group?"
  },
  "EE4F": {
   "unit": "game/trainerSetupEC00",
   "line": "the guard group never waits - it has to be in place from the start"
  },
  "EE51": {
   "unit": "game/trainerSetupEC00",
   "line": "is this group 0?"
  },
  "EE53": {
   "unit": "game/trainerSetupEC00",
   "line": "group 0 follows the spearhead and never waits either"
  },
  "EE55": {
   "unit": "game/trainerSetupEC00",
   "line": "prepare to subtract"
  },
  "EE56": {
   "unit": "game/trainerSetupEC00",
   "line": "otherwise subtract the group's 0-15 round delay from the clock"
  },
  "EE59": {
   "unit": "game/trainerSetupEC00",
   "line": "$8800 + slot = the clock value at which this unit starts obeying orders (the msg... label is a $8800 message-string address collision)"
  },
  "EE5C": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "A unit that is already standing on its ordered destination gets the idle bit set, so the engine",
    "will not spend movement points shuffling it."
   ],
   "line": "distance between the unit's cell (zp_22/zp_24) and its ordered destination (zp_27/zp_28)"
  },
  "EE5F": {
   "unit": "game/trainerSetupEC00",
   "line": "redundant - cellDistance already returns with the flags set from its own TXA"
  },
  "EE61": {
   "unit": "game/trainerSetupEC00",
   "line": "still has somewhere to go"
  },
  "EE63": {
   "unit": "game/trainerSetupEC00",
   "line": "its ordered column"
  },
  "EE66": {
   "unit": "game/trainerSetupEC00",
   "line": "set bit 7 = 'arrived / idle'"
  },
  "EE68": {
   "unit": "game/trainerSetupEC00",
   "line": "store it back"
  },
  "EE6B": {
   "unit": "game/trainerSetupEC00",
   "line": "next AI slot down"
  },
  "EE6D": {
   "unit": "game/trainerSetupEC00",
   "line": "the counter went negative: all 50 slots done"
  },
  "EE6F": {
   "unit": "game/trainerSetupEC00",
   "line": "round again (too far for a branch)"
  },
  "EE72": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "Turn the four 16-bit sums of every group 0..$8953 into averages by dividing by the member count.",
    "A group with no members divides by zero here; the quotient is meaningless but the group has no",
    "units for anything to act on either."
   ],
   "line": "highest group index in use"
  },
  "EE75": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_1B = the group being averaged, counting down to 0"
  },
  "EE77": {
   "unit": "game/trainerSetupEC00",
   "line": "X = group index"
  },
  "EE79": {
   "unit": "game/trainerSetupEC00",
   "line": "how many members it has"
  },
  "EE7C": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_1C = the divisor, reloaded before each of the four divisions"
  },
  "EE7E": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the ordered-column sum"
  },
  "EE81": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte"
  },
  "EE84": {
   "unit": "game/trainerSetupEC00",
   "line": "X = divisor"
  },
  "EE86": {
   "unit": "game/trainerSetupEC00",
   "line": "Y:A / X"
  },
  "EE89": {
   "unit": "game/trainerSetupEC00",
   "line": "X = group index again"
  },
  "EE8B": {
   "unit": "game/trainerSetupEC00",
   "line": "the group's average ordered column"
  },
  "EE8E": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the ordered-row sum"
  },
  "EE91": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte"
  },
  "EE94": {
   "unit": "game/trainerSetupEC00",
   "line": "X = divisor"
  },
  "EE96": {
   "unit": "game/trainerSetupEC00",
   "line": "Y:A / X"
  },
  "EE99": {
   "unit": "game/trainerSetupEC00",
   "line": "X = group index"
  },
  "EE9B": {
   "unit": "game/trainerSetupEC00",
   "line": "the group's average ordered row"
  },
  "EE9E": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the current-column sum"
  },
  "EEA1": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte"
  },
  "EEA4": {
   "unit": "game/trainerSetupEC00",
   "line": "X = divisor"
  },
  "EEA6": {
   "unit": "game/trainerSetupEC00",
   "line": "Y:A / X"
  },
  "EEA9": {
   "unit": "game/trainerSetupEC00",
   "line": "X = group index"
  },
  "EEAB": {
   "unit": "game/trainerSetupEC00",
   "line": "the group's current centre column"
  },
  "EEAE": {
   "unit": "game/trainerSetupEC00",
   "line": "high byte of the current-row sum at $04A0"
  },
  "EEB1": {
   "unit": "game/trainerSetupEC00",
   "line": "low byte"
  },
  "EEB4": {
   "unit": "game/trainerSetupEC00",
   "line": "X = divisor"
  },
  "EEB6": {
   "unit": "game/trainerSetupEC00",
   "line": "Y:A / X"
  },
  "EEB9": {
   "unit": "game/trainerSetupEC00",
   "line": "X = group index"
  },
  "EEBB": {
   "unit": "game/trainerSetupEC00",
   "line": "the group's current centre row"
  },
  "EEBE": {
   "unit": "game/trainerSetupEC00",
   "line": "next group down"
  },
  "EEC0": {
   "unit": "game/trainerSetupEC00",
   "line": "loop until group 0 has been averaged"
  },
  "EEC2": {
   "unit": "game/trainerSetupEC00",
   "line": "done"
  },
  "EEC3": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "buildAiHeadingSearchOrder - Fills the 8-byte table at $894A with the order in which the resident AI",
    "tries headings: 0 first, then +/-1, +/-2, +/-3 and finally 4 (the opposite heading), every value",
    "folded into 0-7.  The sign of each pair is decided by a coin toss, so a given game has a consistent",
    "random left-or-right bias.  Because -4 == +4 mod 8 the last entry is always 4, and the first entry is",
    "always 0 (its 'negate' at $EED4 acts on a magnitude that is still zero).",
    "In:  nextGameRandom",
    "Out: $894A-$8951 = eight heading offsets; zp_18 scratch; A and Y clobbered",
    "Called from: $ED8E in rebuildAiGroupTables (this file) - the only caller in this overlay"
   ],
   "line": "start from a zero heading offset"
  },
  "EEC5": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_18 = the running heading offset, starting at 0"
  },
  "EEC7": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = 1 writes the first entry, $8949+1 = $894A; the loop runs Y = 1..8"
  },
  "EEC9": {
   "unit": "game/trainerSetupEC00",
   "line": "A = the entry number"
  },
  "EECA": {
   "unit": "game/trainerSetupEC00",
   "line": "A = Y/2 = the magnitude of this pair, and C = 1 on the second (odd) entry of each pair"
  },
  "EECB": {
   "unit": "game/trainerSetupEC00",
   "line": "second entry of a pair: reuse the magnitude with the opposite sign"
  },
  "EECD": {
   "unit": "game/trainerSetupEC00",
   "line": "first entry of a pair: adopt the new magnitude 0, 1, 2, 3 or 4"
  },
  "EECF": {
   "unit": "game/trainerSetupEC00",
   "line": "toss a coin ..."
  },
  "EED2": {
   "unit": "game/trainerSetupEC00",
   "line": "... heads (bit 7 clear): take this pair positive first, tails: fall through and negate"
  },
  "EED4": {
   "unit": "game/trainerSetupEC00",
   "line": "negate the current magnitude ..."
  },
  "EED6": {
   "unit": "game/trainerSetupEC00",
   "line": "... one's complement ..."
  },
  "EED8": {
   "unit": "game/trainerSetupEC00",
   "line": "prepare the +1 of the two's complement negate"
  },
  "EED9": {
   "unit": "game/trainerSetupEC00",
   "line": "... plus one"
  },
  "EEDB": {
   "unit": "game/trainerSetupEC00",
   "line": "keep the signed offset"
  },
  "EEDD": {
   "unit": "game/trainerSetupEC00",
   "line": "the signed offset, -4..+4"
  },
  "EEDF": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the bias below"
  },
  "EEE0": {
   "unit": "game/trainerSetupEC00",
   "line": "bias by 8 so a negative offset stays positive ..."
  },
  "EEE2": {
   "unit": "game/trainerSetupEC00",
   "line": "... and fold it into a heading 0-7"
  },
  "EEE4": {
   "unit": "game/trainerSetupEC00",
   "line": "$8949+Y, i.e. $894A..$8951"
  },
  "EEE7": {
   "unit": "game/trainerSetupEC00",
   "line": "next entry"
  },
  "EEE8": {
   "unit": "game/trainerSetupEC00",
   "line": "eight entries written?"
  },
  "EEEA": {
   "unit": "game/trainerSetupEC00",
   "line": "keep going while Y is below 9"
  },
  "EEEC": {
   "unit": "game/trainerSetupEC00",
   "line": "done"
  },
  "EEED": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "orderAiGroupToPoint - Orders AI group Y to move so that its formation centre ends up on column X,",
    "row A.  The two coordinates are stashed by patching the immediate operands of the two LDA # at",
    "$EEF8 and $EEFC, because computeTrainerGroupStats clobbers A, X and Y.  An empty group is left",
    "alone.  Falls into shiftAiGroupDestinations.",
    "In:  X = target column, A = target row, Y = group index; $8832 group ids, the unit arrays",
    "Out: self-modifies $EEF9 and $EEFD (the operand bytes); $8955 = Y; the group's target ($88D2/$88DE)",
    "     and the unitDestCol/unitDestRow of every member are updated; returns with C=1 and nothing",
    "     changed when the group has no live member",
    "Called from: $ED56 and $ED69 in initTrainerAi (this file)"
   ],
   "block": [
    "Self-modifying code: $EEED writes the target column into the operand byte $EEF9 (of the LDA #",
    "at $EEF8) and $EEF0 writes the target row into $EEFD (of the LDA # at $EEFC).  The $FF operands",
    "shown in the listing are just what the overlay was assembled with."
   ],
   "line": "park the target column in the operand byte of the LDA # at $EEF8"
  },
  "EEF0": {
   "unit": "game/trainerSetupEC00",
   "line": "park the target row in the operand byte of the LDA # at $EEFC"
  },
  "EEF3": {
   "unit": "game/trainerSetupEC00",
   "line": "measure group Y ($E531): member count, bounding box, centre and average ordered cell; clobbers A/X/Y"
  },
  "EEF6": {
   "unit": "game/trainerSetupEC00",
   "line": "C=1 means the group has no live member - nothing to order"
  },
  "EEF8": {
   "unit": "game/trainerSetupEC00",
   "line": "operand patched at $EEED = the target column"
  },
  "EEFA": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_22 = wanted formation centre column"
  },
  "EEFC": {
   "unit": "game/trainerSetupEC00",
   "line": "operand patched at $EEF0 = the target row"
  },
  "EEFE": {
   "unit": "game/trainerSetupEC00",
   "line": "zp_24 = wanted formation centre row"
  },
  "EF00": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "shiftAiGroupDestinations - Moves a whole AI group without breaking its formation.  The wanted centre",
    "in zp_22/zp_24 is first clipped so the group's bounding box still fits on the 40x40 map and stored as",
    "the group's target; the resulting shift is then added to the ordered destination of every unit whose",
    "group id matches $8955, so the formation is translated as a block.  The AND #$3F on the way also",
    "clears the idle bit 7, which restarts any member that had already arrived.",
    "In:  zp_22/zp_24 = wanted centre, $8955 = the group (set by computeTrainerGroupStats), $8832 group",
    "     ids, unitDestColTable+50 $F802, unitDestRowTable+50 $F866, the group's box $890E/$891A/$8926/$8932",
    "Out: $88D2/$88DE = the group's clipped target, unitDestCol/unitDestRow of every member shifted,",
    "     zp_2A/zp_2B = the shift applied, zp_22/zp_24 clipped, $8958",
    "Called from: $ED3F in initTrainerAi and by falling out of orderAiGroupToPoint (both in this file)"
   ],
   "line": "really trainerAiE000:clampGroupTargetToMap ($E5FF) - the modem driver's name for this address is a collision.  It clips zp_22/zp_24 so the whole formation box stays on the map, stores the result as the group's target in $88D2/$88DE and returns the shift in zp_2A (columns) and zp_2B (rows)"
  },
  "EF03": {
   "unit": "game/trainerSetupEC00",
   "line": "AI slots 49 down to 0"
  },
  "EF05": {
   "unit": "game/trainerSetupEC00",
   "line": "group id of this AI unit"
  },
  "EF08": {
   "unit": "game/trainerSetupEC00",
   "line": "the group computeTrainerGroupStats last measured"
  },
  "EF0B": {
   "unit": "game/trainerSetupEC00",
   "line": "not a member: skip it"
  },
  "EF0D": {
   "unit": "game/trainerSetupEC00",
   "line": "its ordered column"
  },
  "EF10": {
   "unit": "game/trainerSetupEC00",
   "line": "keep the 0-39 column, dropping the idle bit 7 so the unit is set moving again"
  },
  "EF12": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the shift add"
  },
  "EF13": {
   "unit": "game/trainerSetupEC00",
   "line": "apply the group's column shift"
  },
  "EF15": {
   "unit": "game/trainerSetupEC00",
   "line": "store the new ordered column"
  },
  "EF18": {
   "unit": "game/trainerSetupEC00",
   "line": "its ordered row"
  },
  "EF1B": {
   "unit": "game/trainerSetupEC00",
   "line": "keep the 0-39 row only"
  },
  "EF1D": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the shift add"
  },
  "EF1E": {
   "unit": "game/trainerSetupEC00",
   "line": "apply the group's row shift"
  },
  "EF20": {
   "unit": "game/trainerSetupEC00",
   "line": "store the new ordered row"
  },
  "EF23": {
   "unit": "game/trainerSetupEC00",
   "line": "next AI slot"
  },
  "EF24": {
   "unit": "game/trainerSetupEC00",
   "line": "loop over all 50"
  },
  "EF26": {
   "unit": "game/trainerSetupEC00",
   "line": "done (also the early exit for an empty group)"
  },
  "EF27": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "staleDroneSteerTail - DEAD CODE in this overlay.  From $EF27 to $EFFF the track 29 sector 0-3 image",
    "is byte for byte identical to the track 29 sector 4-5 drone-flying overlay (game/droneAiEE00), and",
    "these bytes are the tail of that overlay's steerDroneAndEmitMove, whose real entry point is $EF0F.",
    "Nothing in this file reaches $EF27 - the routine above ends with an RTS at $EF26 - and the whole page",
    "is thrown away again by $7B33 a few instructions after initTrainerAi returns.",
    "What the code does where it is live: turns the drone heading $896F by the step in X (C=1 forces one",
    "step anticlockwise), converts the new heading into a column/row delta through headingToDirectionTable",
    "($7316), diagonalDirectionTable ($1FB2) and directionDeltaTable ($20F0), advances the drone by one",
    "cell, and then writes a 4-byte command $8D (cmdDroneMove: heading, column, row) straight into the",
    "comm module's INCOMING packet buffer at $E020 with $E01E = 4 - i.e. the trainer hands the engine a",
    "packet as though the remote player had sent it.  The coordinates are pre-mirrored so that the",
    "engine's mirror-and-decrement of a remote $8D lands them back on the right cell.",
    "In:  X = heading change, C = 1 forces -1, $896F drone heading, $8974/$8975 drone column/row",
    "Out: $896F, $8974/$8975 advanced one cell, $E020-$E023 = $8D + heading + mirrored column/row,",
    "     $E01E = 4",
    "Called from: nothing in this file (live at $EF0F-$EF9D in game/droneAiEE00)"
   ],
   "line": "carry clear: use the turn amount the caller put in X"
  },
  "EF29": {
   "unit": "game/trainerSetupEC00",
   "line": "carry set: turn one step anticlockwise"
  },
  "EF2B": {
   "unit": "game/trainerSetupEC00",
   "line": "A = the signed turn"
  },
  "EF2C": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the heading add"
  },
  "EF2D": {
   "unit": "game/trainerSetupEC00",
   "line": "add it to the current drone heading"
  },
  "EF30": {
   "unit": "game/trainerSetupEC00",
   "line": "wrap into 0-7 (8 compass points)"
  },
  "EF32": {
   "unit": "game/trainerSetupEC00",
   "line": "the new heading"
  },
  "EF35": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = heading 0-7"
  },
  "EF38": {
   "unit": "game/trainerSetupEC00",
   "line": "$7316 in the $6F00 overlay: heading -> direction code (0-1 row only, 2-3 column only, 4-7 diagonal)"
  },
  "EF3B": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = row delta, assume 0"
  },
  "EF3D": {
   "unit": "game/trainerSetupEC00",
   "line": "X = column delta, assume 0"
  },
  "EF3F": {
   "unit": "game/trainerSetupEC00",
   "line": "is the code a diagonal?"
  },
  "EF41": {
   "unit": "game/trainerSetupEC00",
   "line": "codes 4-7 move on both axes"
  },
  "EF43": {
   "unit": "game/trainerSetupEC00",
   "line": "is the code a pure-column move?"
  },
  "EF45": {
   "unit": "game/trainerSetupEC00",
   "line": "codes 2-3 move along the column only"
  },
  "EF47": {
   "unit": "game/trainerSetupEC00",
   "line": "codes 0-1: row only"
  },
  "EF48": {
   "unit": "game/trainerSetupEC00",
   "line": "$20F0: direction code -> signed delta"
  },
  "EF4B": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = row delta"
  },
  "EF4C": {
   "unit": "game/trainerSetupEC00",
   "line": "on to the position update"
  },
  "EF4F": {
   "unit": "game/trainerSetupEC00",
   "line": "codes 2-3"
  },
  "EF50": {
   "unit": "game/trainerSetupEC00",
   "line": "$20F0: direction code -> signed delta"
  },
  "EF53": {
   "unit": "game/trainerSetupEC00",
   "line": "X = column delta"
  },
  "EF54": {
   "unit": "game/trainerSetupEC00",
   "line": "on to the position update"
  },
  "EF57": {
   "unit": "game/trainerSetupEC00",
   "line": "codes 4-7"
  },
  "EF58": {
   "unit": "game/trainerSetupEC00",
   "line": "$1FB2: packs the two component codes of a diagonal into the two nibbles"
  },
  "EF5B": {
   "unit": "game/trainerSetupEC00",
   "line": "keep the packed pair"
  },
  "EF5C": {
   "unit": "game/trainerSetupEC00",
   "line": "low nibble = the row component"
  },
  "EF5E": {
   "unit": "game/trainerSetupEC00",
   "line": "index the delta table with it"
  },
  "EF5F": {
   "unit": "game/trainerSetupEC00",
   "line": "$20F0: -> signed delta"
  },
  "EF62": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = row delta"
  },
  "EF63": {
   "unit": "game/trainerSetupEC00",
   "line": "get the packed pair back"
  },
  "EF64": {
   "unit": "game/trainerSetupEC00",
   "line": "shift the high nibble down ..."
  },
  "EF65": {
   "unit": "game/trainerSetupEC00",
   "line": "..."
  },
  "EF66": {
   "unit": "game/trainerSetupEC00",
   "line": "..."
  },
  "EF67": {
   "unit": "game/trainerSetupEC00",
   "line": "... = the column component"
  },
  "EF68": {
   "unit": "game/trainerSetupEC00",
   "line": "index the delta table with it"
  },
  "EF69": {
   "unit": "game/trainerSetupEC00",
   "line": "$20F0: -> signed delta"
  },
  "EF6C": {
   "unit": "game/trainerSetupEC00",
   "line": "X = column delta"
  },
  "EF6D": {
   "unit": "game/trainerSetupEC00",
   "line": "A = column delta"
  },
  "EF6E": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the position add"
  },
  "EF6F": {
   "unit": "game/trainerSetupEC00",
   "line": "current drone column"
  },
  "EF72": {
   "unit": "game/trainerSetupEC00",
   "line": "advance it one cell"
  },
  "EF75": {
   "unit": "game/trainerSetupEC00",
   "line": "X = the new column, for mirrorBlockCornerForOpponent"
  },
  "EF76": {
   "unit": "game/trainerSetupEC00",
   "line": "A = row delta"
  },
  "EF77": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the position add"
  },
  "EF78": {
   "unit": "game/trainerSetupEC00",
   "line": "current drone row"
  },
  "EF7B": {
   "unit": "game/trainerSetupEC00",
   "line": "advance it one cell"
  },
  "EF7E": {
   "unit": "game/trainerSetupEC00",
   "line": "Y = the new row, for mirrorBlockCornerForOpponent"
  },
  "EF7F": {
   "unit": "game/trainerSetupEC00",
   "line": "command $8D = cmdDroneMove"
  },
  "EF81": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 0 of the comm module's INCOMING packet buffer: the trainer forges the packet the engine is about to read"
  },
  "EF84": {
   "unit": "game/trainerSetupEC00",
   "line": "the drone's heading"
  },
  "EF87": {
   "unit": "game/trainerSetupEC00",
   "line": "clear carry for the add"
  },
  "EF88": {
   "unit": "game/trainerSetupEC00",
   "line": "+4 ..."
  },
  "EF8A": {
   "unit": "game/trainerSetupEC00",
   "line": "... modulo 8 = the same heading seen from the other end of the mirrored map"
  },
  "EF8C": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 1 of the packet"
  },
  "EF8F": {
   "unit": "game/trainerSetupEC00",
   "line": "$EA38: X,Y = 39-(X+1), 39-(Y+1), pre-compensating the mirror-and-decrement the engine applies to a remote $8D"
  },
  "EF92": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 2 of the packet = mirrored column"
  },
  "EF95": {
   "unit": "game/trainerSetupEC00",
   "line": "byte 3 of the packet = mirrored row"
  },
  "EF98": {
   "unit": "game/trainerSetupEC00",
   "line": "packet length 4"
  },
  "EF9A": {
   "unit": "game/trainerSetupEC00",
   "line": "tell the game a 4-byte packet has arrived; mergeIncomingPacket ($4F05) will execute it"
  },
  "EF9D": {
   "unit": "game/trainerSetupEC00",
   "line": "done"
  },
  "EF9E": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "measureDroneDistanceToTarget - DEAD in this overlay (nothing here calls it), but a complete 13-byte",
    "routine: it copies the drone's cell $8974/$8975 into the reference point zp_27/zp_28 and tail-jumps",
    "to cellDistance ($E82A), which returns the integer distance 0-16 between the cell in zp_22/zp_24 and",
    "the drone.  Y is left untouched, which is what lets the live caller keep a unit index in Y across the",
    "call.  Rendered as .byte because no reachable code in this file references it; it disassembles as",
    "LDA $8974 / STA $27 / LDA $8975 / STA $28 / JMP $E82A and is the same routine as droneDistanceToCell",
    "at $EF9E in game/droneAiEE00.",
    "In:  zp_22/zp_24 = cell to measure against, $8974/$8975 = drone cell",
    "Out: A = X = distance 0-16, zp_27/zp_28 = drone cell, Y preserved",
    "Called from: nothing in this file ($EE26 and $EEFC in game/droneAiEE00)"
   ],
   "line": "AD 74 89 = LDA $8974 (drone column), 85 27 = STA zp_27, AD 75 89 = LDA $8975 (drone row)"
  },
  "EFA6": {
   "unit": "game/trainerSetupEC00",
   "line": "85 ..."
  },
  "EFA7": {
   "unit": "game/trainerSetupEC00",
   "line": "... 28 = STA zp_28, then 4C 2A E8 = JMP cellDistance ($E82A)"
  },
  "EFAB": {
   "unit": "game/trainerSetupEC00",
   "routine": [
    "staleDroneTargetPickerTail - 34 stale bytes, unreachable and unusable here.  In the solo trainer's",
    "own $EC00 tail (track 35 sectors 12-15) and in the drone overlay (track 29 sectors 4-5) - both of",
    "which carry these exact bytes - they are the end of the drone's target picker: bump the candidate",
    "index zp_1F, loop back to $EF9B for the next of 15 candidate blocks, then adopt the best player unit",
    "found (zp_1D) as the drone's target $8976 and lower the detonation threshold $8979 to three quarters",
    "so the drone will settle for a weaker target next time round.  In THIS image the BCC at $EFB1 branches",
    "to $EF9B, which is the middle of the STA $E01E at $EF9A, so the fragment could not run even if",
    "something jumped to it.  In the modem driver's tail (track 34 sectors 12-15) the same addresses hold",
    "the middle of the message 'INSERT DATA DISK AND PRESS SPACE.' instead.  The bytes only survive so that",
    "the shared strings from $EFCD on land at their fixed addresses.",
    "In:  zp_1D best candidate unit, zp_1F candidate index, $8979",
    "Out: $8976 drone target, $8979 reduced to 3/4, zp_18 scratch",
    "Called from: nothing - dead in this overlay"
   ],
   "line": "next candidate block"
  },
  "EFAD": {
   "unit": "game/trainerSetupEC00",
   "line": "how many have been scored"
  },
  "EFAF": {
   "unit": "game/trainerSetupEC00",
   "line": "15 candidate blocks per pass"
  },
  "EFB1": {
   "unit": "game/trainerSetupEC00",
   "line": "branches to $EF9B - a real instruction only in the images where this fragment is live; here it is the middle of the STA $E01E at $EF9A"
  },
  "EFB3": {
   "unit": "game/trainerSetupEC00",
   "line": "the best player unit found by the scoring pass"
  },
  "EFB5": {
   "unit": "game/trainerSetupEC00",
   "line": "bit 7 set = no candidate at all"
  },
  "EFB7": {
   "unit": "game/trainerSetupEC00",
   "line": "adopt it as the drone's target unit"
  },
  "EFBA": {
   "unit": "game/trainerSetupEC00",
   "line": "the current detonation score threshold"
  },
  "EFBD": {
   "unit": "game/trainerSetupEC00",
   "line": "/2 ..."
  },
  "EFBE": {
   "unit": "game/trainerSetupEC00",
   "line": "... /4"
  },
  "EFBF": {
   "unit": "game/trainerSetupEC00",
   "line": "keep a quarter of it"
  },
  "EFC1": {
   "unit": "game/trainerSetupEC00",
   "line": "the threshold again"
  },
  "EFC4": {
   "unit": "game/trainerSetupEC00",
   "line": "prepare to subtract"
  },
  "EFC5": {
   "unit": "game/trainerSetupEC00",
   "line": "threshold - threshold/4 = three quarters of it"
  },
  "EFC7": {
   "unit": "game/trainerSetupEC00",
   "line": "underflow: leave the threshold alone"
  },
  "EFC9": {
   "unit": "game/trainerSetupEC00",
   "line": "lower the bar so the drone will accept a weaker target next time"
  },
  "EFCC": {
   "unit": "game/trainerSetupEC00",
   "line": "done"
  },
  "EFCD": {
   "unit": "game/trainerSetupEC00",
   "block": [
    "$EFCD-$EFFE is byte for byte identical in all three images that can occupy this page (the trainer",
    "setup overlay here, the solo trainer's own tail from track 35 and the modem driver's tail from track",
    "34), and only the very last byte at $EFFF differs.  That is deliberate: the setup screens in overlay",
    "A print these strings by absolute address and must not care which image is resident.  Only the T34",
    "image, which $7B33 restores immediately after this overlay has run, also carries the 'INSERT xxxx",
    "DISK AND PRESS SPACE.' template that begins at $EFAF - here those bytes are the stale code above, so",
    "the three characters below are all that is left of that message."
   ],
   "line": "tail of 'INSERT xxxx DISK AND PRESS SPACE.' - the terminator '.' has bit 7 set"
  },
  "EFD0": {
   "unit": "game/trainerSetupEC00",
   "line": "'DATA' - the 4-character disk name promptInsertDataDisk ($81B2) patches into the message template at $EFB6"
  },
  "EFD4": {
   "unit": "game/trainerSetupEC00",
   "line": "'GAME' - the disk name promptInsertGameDisk ($81A5) uses instead"
  },
  "EFD8": {
   "unit": "game/trainerSetupEC00",
   "line": "'CUSTOM OFF' - installed as message slot $1E by overlay A; its ON/OFF patch points at the 'OFF' at $EFDF"
  },
  "EFE2": {
   "unit": "game/trainerSetupEC00",
   "line": "'DESTROY OFF' - message slot $1E for the DESTROY toggle; the ON/OFF patch points at the 'OFF' at $EFEA"
  },
  "EFED": {
   "unit": "game/trainerSetupEC00",
   "line": "'ACCEPTED' - printed centred on row 15 by overlay A when the fire button confirms an edited value"
  },
  "EFF5": {
   "unit": "game/trainerSetupEC00",
   "line": "unreferenced trailing bytes, the same in every image of this page"
  },
  "EFFD": {
   "unit": "game/trainerSetupEC00",
   "line": "the last byte is $FF here and in the track 35 trainer tail, $00 in the track 34 modem tail"
  }
 },
 "dataTypes": {
  "EC00": [
   "byte",
   5
  ],
  "EC05": [
   "byte",
   5
  ],
  "EC0A": [
   "byte",
   5
  ],
  "EFCD": [
   "text",
   3
  ],
  "EFD0": [
   "text",
   4
  ],
  "EFD4": [
   "text",
   4
  ],
  "EFD8": [
   "text",
   10
  ],
  "EFE2": [
   "text",
   11
  ],
  "EFED": [
   "text",
   8
  ],
  "EFF5": [
   "byte",
   11
  ]
 },
 "zpComments": {
  "48": "pointer (lo) to a cell of the $F000 battlefield map, set by readMapCell ($25FA); also the running pointer of the block fill/copy loops, and in the trainer setup overlay the pointer to the 50 group ids of the selected opening play inside the playbook at $0200"
 }
}