{
  "chunk": "game_ovl_EC00_T29_EC00",
  "unit": "game/ovl_EC00_T29",
  "routines": [
    {
      "addr": "EC0F",
      "name": "initTrainerAi",
      "summary": "Only entry point of this sub-overlay; called once from overlay A at $7B30 right after the overlay was loaded (T29 s0-3) and just before $7B33 restores the comm tail. It builds the whole opening plan of the solo-trainer computer opponent (side 1, units 50-99): clears the AI work area in the $8800 block, picks an objective near the human comcen (sub_E12F), selects a plan index 0-4 from the game type ($0BA3 & 7), loads the 5 x 150 byte deployment template from track 29 s6-8 into $0200-$04FF, copies the plan's 50 group ids to $8832 and its 50 destination cells to unitDestCol+50/unitDestRow+50, gives every AI group a random destination, then calls rebuildAiGroupTables and computes the phase-change clock $896E.",
      "inputs": "$0BA3 game type bits 0-2, $91CB game clock (set by setGameClockForType at $7B1A), $92AC zone-score setting, unit arrays $F640/$F6A4/$F708/$F76C/$F7D0/$F834, tables at $EC00/$EC05/$EC0A, disk (loadSectors/setLoadDest), nextGameRandom",
      "outputs": "$0200-$04FF overwritten with the deployment template; AI work area $8800-$8979 initialised; unitDestCol/unitDestRow of units 50-99 set; unitFlagsTable[99]=$21 and unitStatusFlags[99]=$20 for plans 4; $F7D0[99] bits 6-7 cleared for THE BOMB; $E01E=0; one $A5 00 $14 setup command queued for DEFENDER; $91CB restored to $FF; zp_18-zp_2B and zp_48-zp_4D clobbered",
      "confidence": "high"
    },
    {
      "addr": "ED8E",
      "name": "rebuildAiGroupTables",
      "summary": "Recomputes the per-group bookkeeping of the AI's 12 groups. First calls buildAiHeadingSearchOrder, then zeroes the 12-entry group arrays ($88D2/$88DE/$88EA/$88F6/$8902 and their high-byte halves at $0464/$0478/$048C/$04A0) and gives every group a random start delay 0-15 in $893E. It then walks the 50 AI units, drops dead ones ($8832=$FF), accumulates each unit's destination and current cell into its group's 16-bit sums, sets the per-unit zone flag $8864, stores the unit's activation clock in $8800 (game clock minus the group delay, except group 0 and the guard group), and marks a unit that already stands on its destination by setting bit 7 of unitDestCol. Finally it divides all four sums by the member count so $88D2/$88DE hold the group's average destination and $88EA/$88F6 its average position.",
      "inputs": "$8832 group ids, $8864, $8953 highest group, $8964 guard group, $8966 plan index, $91CB clock, $92AC, unit arrays $F672/$F6D6 (col/row of units 50-99), $F802/$F866 (dest col/row), $F79E (types)",
      "outputs": "$0464/$0478/$048C/$04A0 (12 bytes each), $88D2/$88DE/$88EA/$88F6/$8902/$893E, $8800-$8831 activation clocks, $8864-$8895 flags, bit 7 of $F802 entries; zp_18/zp_1A/zp_1B/zp_1C/zp_22/zp_24/zp_27/zp_28 clobbered",
      "confidence": "high"
    },
    {
      "addr": "EEC3",
      "name": "buildAiHeadingSearchOrder",
      "summary": "Fills the 8-byte table at $894A with heading offsets 0, +/-1, +/-1, +/-2, +/-2, +/-3, +/-3, +/-4 taken mod 8, the sign of each pair being chosen at random. The resident AI walks this table to try headings in order of increasing deviation from the wanted direction, with a random left/right bias per game.",
      "inputs": "nextGameRandom",
      "outputs": "$894A-$8951; zp_18 scratch, A/Y clobbered",
      "confidence": "medium"
    },
    {
      "addr": "EEED",
      "name": "orderAiGroupToPoint",
      "summary": "Orders AI group Y to move so that its formation centre ends up at column X, row A. Patches the two immediate operands at $EEF9/$EEFD with X and A, calls sub_E531 to measure the group (returns C=1 and does nothing when the group is empty), stores the point in zp_22/zp_24 and falls into shiftAiGroupDestinations. Called from initTrainerAi to make group 0 follow the main group and to send the guard group to the AI comcen.",
      "inputs": "X = target column, A = target row, Y = group index, $8832 group ids",
      "outputs": "self-modifies $EEF9/$EEFD; group destination arrays and unitDestCol/unitDestRow of the group's units updated; C=1 (untouched) when the group has no live unit",
      "confidence": "high"
    },
    {
      "addr": "EF00",
      "name": "shiftAiGroupDestinations",
      "summary": "Tail of orderAiGroupToPoint, also called directly at $ED3F. Calls sub_E5FF, which clips the wanted point (zp_22/zp_24) so the group's bounding box stays on the 40x40 map, stores the result as the group destination ($88D2/$88DE) and returns the delta from the group's current centre in zp_2A/zp_2B. It then adds that delta to unitDestCol/unitDestRow of every unit whose group id matches $8955, so the whole group translates while keeping its formation (the AND #$3F also clears the idle/mirror bits).",
      "inputs": "zp_22/zp_24 wanted centre, $8955 current group (set by sub_E531), $8832, $F802/$F866",
      "outputs": "$88D2/$88DE for the group, unitDestCol+50/unitDestRow+50 of its members, zp_2A/zp_2B",
      "confidence": "high"
    },
    {
      "addr": "EF27",
      "name": "stepAiDroneAndQueueMove",
      "summary": "Drone-steering step of the trainer AI, shown as .byte in this file. Turns the drone heading $896F by X steps (carry set forces -1), looks the heading up in headingToDirectionTable ($7316 in the $6F00 overlay) and converts it through directionDeltaTable ($20F0) / diagonalDirectionTable ($1FB2) into dx/dy, advances the drone position $8974/$8975 by one cell, then fabricates a 4-byte command $8D (drone move: heading+4 mod 8, column, row) in the comm module's INCOMING packet buffer $E020-$E023 and sets $E01E = 4, i.e. the trainer feeds the engine a packet as if the remote player had sent it. Coordinates are mirrored with sub_EA38 first. These bytes are identical to $EF27 in game/ovl_EE00_T29, where the routine is live; in this overlay nothing reaches it, so here it is a dead copy.",
      "inputs": "X = heading change, C = 1 forces -1, $896F heading, $8974/$8975 drone column/row",
      "outputs": "$896F, $8974/$8975 advanced, $E020=$8D, $E021=heading+4, $E022/$E023 = mirrored column/row, $E01E=4",
      "confidence": "medium"
    },
    {
      "addr": "EF9E",
      "name": "measureDroneDistanceToTarget",
      "summary": "Copies the drone position $8974/$8975 into zp_27/zp_28 and jumps into sub_E82A, which returns the distance bucket (0-$10, via distanceSquared and squaresTable) between the cell in zp_22/zp_24 and the drone. Shown as .byte here; live in game/ovl_EE00_T29 where it is called from $EE26 and $EEFC.",
      "inputs": "zp_22/zp_24 target cell, $8974/$8975 drone position",
      "outputs": "A = distance bucket, zp_27/zp_28 set, X clobbered",
      "confidence": "high"
    },
    {
      "addr": "EFAB",
      "name": "decayDroneScoreThreshold",
      "summary": "34-byte code fragment (shown as .byte) shared byte for byte with the $EE00/$EC00 tails of the other builds: increments zp_1F, loops back to $EF9B while it is below $0F, then stores zp_1D into $8976 and reduces $8979 to three quarters of itself. In the T35 comm build the loop entry $EF9B is a real instruction; with this file's instruction alignment the BCC at $EFB1 lands inside the operand of the STA $E01E at $EF9A, so the fragment is stale code left over from the shared source module and is not usable here.",
      "inputs": "zp_1D, zp_1F, $8979",
      "outputs": "$8976, $8979 (x 3/4), zp_18",
      "confidence": "low"
    }
  ],
  "variables": [
    {"addr": "8800", "scope": "abs", "name": "aiUnitNextActionClock", "meaning": "50 bytes: game-clock value at which AI unit 50+i may act again; written by rebuildAiGroupTables as clock - groupDelay and compared with $91CB by the resident AI ($E2E0, $E4DE). Physically the low half of the 100-entry array whose upper half ($8832) holds group ids, so the entries for units 0-49 are free for this use. Overwrites the message strings loaded at $8800.", "confidence": "high"},
    {"addr": "8832", "scope": "abs", "name": "aiUnitGroup", "meaning": "50 bytes: group id (0-11) of AI unit 50+i, copied from the deployment template; $FF marks a dead/removed unit. The resident AI addresses the same bytes as $8800+absoluteUnitIndex.", "confidence": "high"},
    {"addr": "8863", "scope": "abs", "name": "aiComcenGroup", "meaning": "aiUnitGroup[49] = group of unit 99, the AI comcen; forced to 0 on the THE BOMB path ($EC60).", "confidence": "high"},
    {"addr": "8864", "scope": "abs", "name": "aiUnitZoneDone", "meaning": "50 bytes: per AI unit, bit 7 set = unit is not counted as an attacker any more (set to $80 for all units when the zone-score option $92AC is 0, and by the resident AI at $E0FA once a unit has scored both zones). sub_E531 counts the units with bit 7 clear.", "confidence": "medium"},
    {"addr": "8896", "scope": "abs", "name": "aiCommandScratch", "meaning": "Command byte + arguments assembled before sub_EA46 appends them to the AI command queue; $EC73 builds $A5,$00,$14 (setup option 0 = 20 points) for the DEFENDER game type.", "confidence": "high"},
    {"addr": "88A0", "scope": "abs", "name": "aiCommandQueue", "meaning": "50-byte queue of command bytes the trainer AI still has to inject into the comm module's incoming packet buffer (filled by sub_EA46, drained by sub_EA7B).", "confidence": "medium"},
    {"addr": "88D2", "scope": "abs", "name": "aiGroupDestCol", "meaning": "12 bytes: destination column of each AI group (16-bit sum during rebuildAiGroupTables, high bytes in $0464, then divided by the member count).", "confidence": "high"},
    {"addr": "88DE", "scope": "abs", "name": "aiGroupDestRow", "meaning": "12 bytes: destination row of each AI group (high bytes of the sum in $0478).", "confidence": "high"},
    {"addr": "88EA", "scope": "abs", "name": "aiGroupCentreCol", "meaning": "12 bytes: average current column of each AI group (high bytes of the sum in $048C).", "confidence": "high"},
    {"addr": "88F6", "scope": "abs", "name": "aiGroupCentreRow", "meaning": "12 bytes: average current row of each AI group (high bytes of the sum in $04A0).", "confidence": "high"},
    {"addr": "8902", "scope": "abs", "name": "aiGroupUnitCount", "meaning": "12 bytes: number of live units in each AI group; used as the divisor for the four averages and returned by sub_E531.", "confidence": "high"},
    {"addr": "890E", "scope": "abs", "name": "aiGroupBoxLeft", "meaning": "12 bytes: centre column minus leftmost destination column of the group (bounding-box extent, written by sub_E531, used by sub_E5FF to keep the group on the map).", "confidence": "medium"},
    {"addr": "891A", "scope": "abs", "name": "aiGroupBoxTop", "meaning": "12 bytes: centre row minus topmost destination row of the group.", "confidence": "medium"},
    {"addr": "8926", "scope": "abs", "name": "aiGroupBoxRight", "meaning": "12 bytes: rightmost destination column minus centre column, plus 1.", "confidence": "medium"},
    {"addr": "8932", "scope": "abs", "name": "aiGroupBoxBottom", "meaning": "12 bytes: bottommost destination row minus centre row, plus 1.", "confidence": "medium"},
    {"addr": "893E", "scope": "abs", "name": "aiGroupStartDelay", "meaning": "12 bytes: random 0-15 delay in rounds before each AI group starts moving; subtracted from the game clock to form aiUnitNextActionClock. Group 0 and the guard group get no delay.", "confidence": "high"},
    {"addr": "894A", "scope": "abs", "name": "aiHeadingSearchOrder", "meaning": "8 bytes built by buildAiHeadingSearchOrder: heading offsets 0,+/-1,+/-1,+/-2,+/-2,+/-3,+/-3,+/-4 (mod 8) with random signs, used to try alternative headings.", "confidence": "medium"},
    {"addr": "8952", "scope": "abs", "name": "aiCommandQueueLen", "meaning": "Number of bytes currently in aiCommandQueue; cleared by initTrainerAi.", "confidence": "high"},
    {"addr": "8953", "scope": "abs", "name": "aiHighestGroup", "meaning": "Index of the highest AI group in use ($0A for plans 0/1, $04 for plans 2-4), from groupCountByPlanTable.", "confidence": "high"},
    {"addr": "8954", "scope": "abs", "name": "aiGroupCount", "meaning": "aiHighestGroup + 1 = number of AI groups (11 or 5).", "confidence": "high"},
    {"addr": "8955", "scope": "abs", "name": "aiCurrentGroup", "meaning": "Group index sub_E531 last measured; shiftAiGroupDestinations uses it to select the units to move.", "confidence": "high"},
    {"addr": "895D", "scope": "abs", "name": "aiMainGroupIndex", "meaning": "Index of the AI's main/spearhead group from mainGroupByPlanTable ($FF = none for plans 0/1, else 4). Group 0 is ordered to the same point as this group at $ED49.", "confidence": "medium"},
    {"addr": "8964", "scope": "abs", "name": "aiGuardGroupIndex", "meaning": "Group told to stay on the AI comcen: $FF (none) for plan 1, otherwise a random 1-3. Its units also get no start delay.", "confidence": "medium"},
    {"addr": "8966", "scope": "abs", "name": "aiPlanIndex", "meaning": "0-4, selects the AI's deployment template and group layout: 0 SCRIMAGE/THE BOMB, 1 QB SNEAK, 2 FACE-OFF, 3 SLUGGERS, 4 FULL WAR/DEFENDER. Reused as scratch by the resident AI after the game starts.", "confidence": "high"},
    {"addr": "8967", "scope": "abs", "name": "aiGroupLoopIndex", "meaning": "Down-counter over the AI groups while initTrainerAi hands out the opening destinations.", "confidence": "high"},
    {"addr": "8968", "scope": "abs", "name": "aiCommandsThisRound", "meaning": "Counter of commands the AI has queued; the resident AI stops issuing orders once it reaches 4-5 ($E205, $E2C2). Incremented by sub_EA46, so $EC85 decrements it again after the DEFENDER setup command.", "confidence": "medium"},
    {"addr": "8969", "scope": "abs", "name": "aiObjectiveCol", "meaning": "Column of the AI's current objective, set by sub_E12F to the human comcen's column ($F671 = unitCol[49]) jittered by -2..+2 and clipped to 0-39.", "confidence": "high"},
    {"addr": "896A", "scope": "abs", "name": "aiObjectiveRow", "meaning": "Row of the AI's current objective (unitRow[49] jittered the same way).", "confidence": "high"},
    {"addr": "896B", "scope": "abs", "name": "aiObjectiveTimer", "meaning": "Rounds until the AI picks a new objective (reloaded with 5 at $E18D); cleared here.", "confidence": "medium"},
    {"addr": "896C", "scope": "abs", "name": "aiObjectiveMode", "meaning": "Flag tested with BIT at $E15C that widens the jitter applied to the objective; cleared here, set from $F9F5 at $E17E.", "confidence": "low"},
    {"addr": "896E", "scope": "abs", "name": "aiPhaseChangeClock", "meaning": "Clock value (start clock minus a random 32-95 rounds, only for plans >= 2) at which the resident AI changes behaviour; compared with $91CB at $E3E2 and $E762.", "confidence": "medium"},
    {"addr": "896F", "scope": "abs", "name": "aiDroneHeading", "meaning": "Current heading (0-7) of the AI's drone.", "confidence": "high"},
    {"addr": "8970", "scope": "abs", "name": "aiDroneHoldTimer", "meaning": "Counter decremented by the resident AI while a drone run is in progress ($EE10/$EE29); cleared here.", "confidence": "low"},
    {"addr": "8972", "scope": "abs", "name": "aiDroneTargetCandidate", "meaning": "Unit index the AI is considering for a drone strike, $FF = none; initialised to $FF here.", "confidence": "low"},
    {"addr": "8974", "scope": "abs", "name": "aiDroneCol", "meaning": "Current column of the AI's drone.", "confidence": "high"},
    {"addr": "8975", "scope": "abs", "name": "aiDroneRow", "meaning": "Current row of the AI's drone.", "confidence": "high"},
    {"addr": "8976", "scope": "abs", "name": "aiDroneTargetUnit", "meaning": "Unit index the drone is flying at ($EE19 in the drone module); set to $FF (none) by initTrainerAi.", "confidence": "medium"},
    {"addr": "8977", "scope": "abs", "name": "aiSubOverlayState", "meaning": "Which $EE00 sub-overlay the comm build 1 has resident: 0 = none/idle, 3 = counted down after loading T35 s14-15, $FF = the drone module T29 s4-5 is loaded ($E0AB-$E0DC). Cleared here.", "confidence": "medium"},
    {"addr": "8978", "scope": "abs", "name": "aiDroneScoreThreshold", "meaning": "Score a candidate cell must beat before the AI commits its drone; $FF (never) for plans 0/1, 8 or 12 otherwise, from aiPlanParamTable.", "confidence": "medium"},
    {"addr": "8979", "scope": "abs", "name": "aiDroneScoreThresholdWork", "meaning": "Working copy of aiDroneScoreThreshold that the drone code decays to 3/4 when no good target is found ($EFC1).", "confidence": "medium"},
    {"addr": "897A", "scope": "abs", "name": "aiRegroupThreshold", "meaning": "Constant 5 stored by initTrainerAi and compared with a computed value at $E29A before the AI regroups its main group.", "confidence": "low"},
    {"addr": "897B", "scope": "abs", "name": "aiComcenStateSaved", "meaning": "Comcen-related AI state copied to $897C at $E106 when unit 99 becomes visible; cleared here.", "confidence": "low"},
    {"addr": "897C", "scope": "abs", "name": "aiComcenState", "meaning": "Comcen-related AI state derived from unitStatusFlags[99] ($FBB7) at $E109-$E119; cleared here.", "confidence": "low"},
    {"addr": "0464", "scope": "abs", "name": "aiGroupDestColHigh", "meaning": "12 high bytes of the 16-bit destination-column sums; lives in the $0400 page that held the copy-protection lists and is free once the game runs.", "confidence": "high"},
    {"addr": "0478", "scope": "abs", "name": "aiGroupDestRowHigh", "meaning": "12 high bytes of the destination-row sums.", "confidence": "high"},
    {"addr": "048C", "scope": "abs", "name": "aiGroupCentreColHigh", "meaning": "12 high bytes of the current-column sums.", "confidence": "high"},
    {"addr": "04A0", "scope": "abs", "name": "aiGroupCentreRowHigh", "meaning": "12 high bytes of the current-row sums. The file's symbol writeSectorToDisk for $04A0 is an address collision with the $0400 block's loader code and is wrong here.", "confidence": "high"},
    {"addr": "0BA3", "scope": "abs", "name": "gameTypeOptions", "meaning": "Bits 0-2 = game type (0 SCRIMAGE .. 6 DEFENDER); read at $EC4A to choose the AI plan.", "confidence": "high"},
    {"addr": "91CB", "scope": "abs", "name": "gameClock", "meaning": "Rounds remaining (counts down). Read here to build the per-unit activation clocks and the phase-change clock, then set back to $FF because the real clock is only started later at $7B86.", "confidence": "high"},
    {"addr": "92AC", "scope": "abs", "name": "zoneScoreValue", "meaning": "Points awarded for entering the enemy zone (0 = territory scoring off). When 0 the AI marks all its units as 'zone done' so it does not chase territory.", "confidence": "medium"},
    {"addr": "E01E", "scope": "abs", "name": "commIncomingPacketLen", "meaning": "Length of the packet the comm module presents as 'received from the opponent'. Cleared by initTrainerAi and set to 4 by the drone code, which writes fake opponent commands into $E020-$E023.", "confidence": "high"},
    {"addr": "F672", "scope": "abs", "name": "aiUnitCol", "meaning": "unitCol + 50: current column of AI unit 50+i.", "confidence": "high"},
    {"addr": "F6D6", "scope": "abs", "name": "aiUnitRow", "meaning": "unitRow + 50: current row of AI unit 50+i.", "confidence": "high"},
    {"addr": "F76B", "scope": "abs", "name": "aiComcenFlags", "meaning": "unitFlagsTable[99]; set to $21 (side bit 5 + facing 1) for the FULL WAR/DEFENDER plans.", "confidence": "medium"},
    {"addr": "F79E", "scope": "abs", "name": "aiUnitType", "meaning": "unitTypeTable + 50: type of AI unit 50+i, bit 7 = removed from play.", "confidence": "high"},
    {"addr": "F802", "scope": "abs", "name": "aiUnitDestCol", "meaning": "unitDestCol + 50: destination column of AI unit 50+i (bit 7 = no destination/idle).", "confidence": "high"},
    {"addr": "F833", "scope": "abs", "name": "aiComcenDestCol", "meaning": "unitDestCol[99]; bits 6-7 cleared for THE BOMB, and used as the guard group's rally point.", "confidence": "high"},
    {"addr": "F866", "scope": "abs", "name": "aiUnitDestRow", "meaning": "unitDestRow + 50: destination row of AI unit 50+i.", "confidence": "high"},
    {"addr": "F897", "scope": "abs", "name": "aiComcenDestRow", "meaning": "unitDestRow[99], rally row of the guard group.", "confidence": "high"},
    {"addr": "FBB7", "scope": "abs", "name": "aiComcenStatusFlags", "meaning": "unitStatusFlags[99] ($FB54 + 99); set to $20 for the FULL WAR/DEFENDER plans.", "confidence": "medium"},
    {"addr": "18", "scope": "zp", "name": "tempByte18", "meaning": "General scratch: unit down-counter in rebuildAiGroupTables, row base in the destination randomiser, sign/half values in buildAiHeadingSearchOrder.", "confidence": "high"},
    {"addr": "1A", "scope": "zp", "name": "tempFlag1A", "meaning": "$FF when the plan index is 0, tested with BIT so that plan 0 gets no staggered unit activation.", "confidence": "high"},
    {"addr": "1B", "scope": "zp", "name": "tempGroupIndex", "meaning": "Group down-counter of the averaging loop at $EE72.", "confidence": "high"},
    {"addr": "1C", "scope": "zp", "name": "tempGroupCount", "meaning": "Member count of the group currently being averaged (divisor for divideSigned16by8).", "confidence": "high"},
    {"addr": "1D", "scope": "zp", "name": "aiGroupActiveCount", "meaning": "Count of units of a group that still count as attackers; produced by sub_E531, only read by the leftover fragment at $EFB3 in this file.", "confidence": "medium"},
    {"addr": "1E", "scope": "zp", "name": "aiGroupCentreColWork", "meaning": "Group centre column returned by sub_E531/sub_E882.", "confidence": "high"},
    {"addr": "1F", "scope": "zp", "name": "aiGroupCentreRowWork", "meaning": "Group centre row returned by sub_E531/sub_E882; the opening destination row is derived from it at $ED25.", "confidence": "high"},
    {"addr": "22", "scope": "zp", "name": "targetCol", "meaning": "Column of the point being ordered / cell column argument of distanceSquared.", "confidence": "high"},
    {"addr": "24", "scope": "zp", "name": "targetRow", "meaning": "Row of the point being ordered / cell row argument of distanceSquared.", "confidence": "high"},
    {"addr": "27", "scope": "zp", "name": "centreCol", "meaning": "Second column argument of distanceSquared (unit destination column, or the drone column at $EF9E).", "confidence": "high"},
    {"addr": "28", "scope": "zp", "name": "centreRow", "meaning": "Second row argument of distanceSquared.", "confidence": "high"},
    {"addr": "2A", "scope": "zp", "name": "groupShiftCol", "meaning": "Column delta from the group's current centre to its new destination, computed by sub_E5FF and added to every member's destination.", "confidence": "high"},
    {"addr": "2B", "scope": "zp", "name": "groupShiftRow", "meaning": "Row delta from the group's current centre to its new destination.", "confidence": "high"},
    {"addr": "48", "scope": "zp", "name": "templateGroupPtr", "meaning": "Pointer to the 50 group ids of the selected plan inside the loaded template ($0200 + 150*plan).", "confidence": "high"},
    {"addr": "4A", "scope": "zp", "name": "templateColPtr", "meaning": "Pointer to the plan's 50 destination columns (templateGroupPtr + 50).", "confidence": "high"},
    {"addr": "4C", "scope": "zp", "name": "templateRowPtr", "meaning": "Pointer to the plan's 50 destination rows (templateGroupPtr + 100).", "confidence": "high"}
  ],
  "dataBlocks": [
    {"addr": "EC00", "length": 5, "type": "byteTable", "name": "mainGroupByPlanTable", "description": "Per AI plan 0-4: index of the main/spearhead group ($FF, $FF, 4, 4, 4) stored in $895D."},
    {"addr": "EC05", "length": 5, "type": "byteTable", "name": "groupCountByPlanTable", "description": "Per AI plan 0-4: highest group index ($0A, $0A, 4, 4, 4) stored in $8953; $8954 gets this value + 1."},
    {"addr": "EC0A", "length": 5, "type": "byteTable", "name": "aiPlanParamTable", "description": "Per AI plan 0-4: drone score threshold ($FF, $FF, 8, $0C, $0C) copied to both $8978 and $8979."},
    {"addr": "EFCD", "length": 3, "type": "text", "name": "msgSpaceTailFragment", "description": "'CE' + '.'|$80 - the tail of the string 'AND PRESS SPACE.' that the T34 comm tail keeps at this address; only the last three bytes survive in this variant, which is why the shared block starts here."},
    {"addr": "EFD0", "length": 18, "type": "text", "name": "msgDataGameCustomOff", "description": "'DATAGAMECUSTOM OF'+'F'|$80; almost certainly addressed at two offsets ('DATAGAME' at $EFD0 and 'CUSTOM OFF' at $EFD8), only the final F carries the bit-7 terminator."},
    {"addr": "EFE2", "length": 11, "type": "text", "name": "msgDestroyOff", "description": "'DESTROY OF'+'F'|$80."},
    {"addr": "EFED", "length": 8, "type": "text", "name": "msgAccepted", "description": "'ACCEPTE'+'D'|$80."},
    {"addr": "EFF5", "length": 11, "type": "byteTable", "name": "overlayTailPadding", "description": "$C0,$DF then four $00,$FF pairs and a trailing byte ($FF here, $00 in the T34 tail). Purpose unknown; the same bytes end every $EC00/$EE00 overlay variant."}
  ],
  "misclassified": [
    {"addr": "EF27", "length": 132, "actual": "code", "evidence": "$EF27-$EFAA disassembles cleanly (BCC $EF2B / LDX #$FF / TXA / CLC / ADC $896F ... LDA #$8D / STA $E020 ... RTS) and is byte-for-byte identical to $EF27-$EFAA of game/ovl_EE00_T29 (track 29 s4-5), where the same bytes are correctly traced as the drone routine called from $EF1D/$EF21/$EF27 and $EE26/$EEFC. The tracer left it as data here because nothing in this variant reaches it."},
    {"addr": "EFAB", "length": 34, "actual": "code", "evidence": "$EFAB-$EFCC decodes as INC $1F / LDA $1F / CMP #$0F / BCC $EF9B / LDA $1D / BMI $EFCC / STA $8976 / LDA $8979 / LSR / LSR / STA $18 / LDA $8979 / SEC / SBC $18 / BCC $EFCC / STA $8979 / RTS and is identical in the T34 and T35 comm tails, where the T35 file traces exactly the same instructions. With this file's alignment the BCC at $EFB1 targets the operand of STA $E01E at $EF9A, so the fragment is stale/unreachable here."}
  ],
  "insights": [
    "This overlay is the opening-plan generator of the solo trainer. Overlay A runs it only when linkTypeFlag $0BA5 bit 7 is set (PRACTICE WITH SOLO TRAINER): $7B24 loads track 29 s0-3 to $EC00, $7B30 calls $EC0F once, $7B33 immediately restores the build-2 comm tail from track 34 s12-15. Everything in this file therefore executes exactly once, and only the $EC0F call graph ($EC0F, $ED8E, $EEC3, $EEED, $EF00 plus helpers in $E000-$EBFF) is live; $EF27-$EFCC is a dead copy of code that belongs to the $EE00 drone sub-overlay.",
    "The trainer AI plays side 1 (units 50-99, the human is forced to side 0 by cmdChooseSide) and keeps all its state in the $8800-$89FF page pair, i.e. it overwrites the message strings that were loaded there from track 29 s9-10 and the 5x5 game-type default tables at $892C/$8945 that applyGameTypeDefaults ($7C2E) reads earlier. Map of the work area: $8800 activation clock per AI unit (50), $8832 group id per AI unit (50, addressed as $8800+absoluteUnitIndex by the resident AI), $8864 'zone done' flag per unit (50), $8896 command scratch, $88A0 command queue (50), then twelve 12-byte per-group arrays $88D2 destCol, $88DE destRow, $88EA centreCol, $88F6 centreRow, $8902 unitCount, $890E/$891A/$8926/$8932 bounding box, $893E start delay, $894A heading search order (8), and the scalars $8952-$8979.",
    "The 16-bit halves of the four per-group sums live at $0464/$0478/$048C/$04A0 - inside the $0400 page that held the copy-protection answer table and the scan lists, which is free once the game is running. The existing labels D_0464/D_0478/D_048C/writeSectorToDisk for those addresses are address collisions from the table_0400 unit and mean nothing here.",
    "AI plan selection from the game type ($0BA3 & 7): 0 SCRIMAGE -> plan 0, 1 QB SNEAK -> plan 1 (no guard group), 2 THE BOMB -> plan 0 but the deployment template is skipped entirely (only units 49 and 99 exist), 3 FACE-OFF -> plan 2, 4 SLUGGERS -> plan 3, 5 FULL WAR -> plan 4, 6 DEFENDER -> plan 4 plus a queued $A5,$00,$14 setup-option command (20 extra points). Plans 0/1 use 11 groups, plans 2-4 use 5 groups. Plans 4 also force unitFlagsTable[99] = $21 and unitStatusFlags[99] = $20 for the AI comcen.",
    "Track 29 sectors 6-8 are the AI deployment template, not just a 'default setup block': $ECA6-$ECB3 loads them to $0200-$04FF and treats them as five 150-byte records (one per plan, base = $0200 + 150*plan), each holding 50 group ids, 50 destination columns and 50 destination rows for units 50-99. The tail of the third record runs into $04xx, which is why the group sum high bytes were placed at $0464+.",
    "How the trainer fakes an opponent: the AI never goes through the modem. It assembles command bytes in $8896, queues them in $88A0 with sub_EA46 and later copies them into the comm module's INCOMING packet buffer $E020.. while setting $E01E to the packet length, so mergeIncomingPacket ($4F05) executes them as if the remote player had sent them. The drone code copied at $EF27 does the same directly, writing a 4-byte $8D heading,col,row packet after mirroring the coordinates with sub_EA38.",
    "Group geometry helpers used from this chunk live in the resident comm build 1: sub_E531(Y=group) measures a group (member count, strength sum via the per-type weight table $E2B7 = 2,2,3,0,0 for GRUNT/RIDER/BOOMER/SPY/COMCEN, bounding box, centre in zp_1E/zp_1F), sub_E5FF clips a wanted centre so the whole box stays on the 40x40 map and returns the shift in zp_2A/zp_2B, sub_E82A converts a cell pair into a distance bucket 0-$10, sub_E12F re-picks the AI objective near the human comcen, sub_EA46 queues a command.",
    "Timing: rebuildAiGroupTables staggers the AI - each group gets a random 0-15 round delay ($893E), every unit's activation clock is gameClock - groupDelay ($8800), and group 0 plus the guard group start at once. initTrainerAi also computes $896E = gameClock - random(32..95) for plans >= 2, the round at which the resident AI switches behaviour ($E3E2/$E762 compare it with $91CB). $91CB itself is left at $FF because the real clock is only started later at $7B86.",
    "Label warning for this file: because labels are keyed by address across all overlays, several symbols here come from the T35/T34 comm tails and are not routine starts in this variant - sub_EC00/L_EC05/D_EC0A are three data tables, and sub_EC54, sub_EC91, sub_ECD4, sub_ECF3, sub_EE00, sub_EE4A, sub_EE55, sub_EEB9, sub_EEE0 are all mid-routine fall-through points of $EC0F/$ED8E. Conversely $EC58/$EC91/$EC1C called by overlay A at $7AE1-$7AEC are the T35 tail's map-rotation routines, not this file's code.",
    "$EFCD-$EFFE is byte-identical in all three $EC00 tail builds (T29 s0-3, T34 s12-15, T35 s12-15) and $EF27-$EFFF is identical between the two track-29 sub-overlays, so the message strings DATAGAME/CUSTOM OFF/DESTROY OFF/ACCEPTED sit at a fixed address that survives every swap of the $EC00-$EFFF window."
  ]
}
