; ============================================================================ ; $EC00-$EFFF - solo trainer setup: picks the opening play and seeds the AI (track 29 sectors 0-3) ; ============================================================================ ; Loaded by $7B24 from the map generator overlay, over the tail of the trainer module. .setcpu "6502" .include "c64.inc" .include "zeropage.inc" ; ---- references to code/data outside this file ---- play4Grp12 := $0464 play4Grp32 := $0478 play4Col02 := $048C play4Col22 := $04A0 loadSectors := $0804 setLoadDest := $085C gameTypeOptions := $0BA3 diagonalDirectionTable := $1FB2 directionDeltaTable := $20F0 headingToDirectionTable := $7316 msgGetOpponentOnPhoneAnd := $8800 D_8832 := $8832 D_8863 := $8863 D_8864 := $8864 D_8896 := $8896 msgPickUpPhoneThenPressSpace := $8897 D_8898 := $8898 D_88D2 := $88D2 D_88DE := $88DE D_88EA := $88EA D_88F6 := $88F6 D_8902 := $8902 D_893E := $893E D_8949 := $8949 D_8952 := $8952 D_8953 := $8953 D_8954 := $8954 D_8955 := $8955 D_895D := $895D D_8960 := $8960 D_8964 := $8964 D_8966 := $8966 D_8967 := $8967 D_8968 := $8968 D_896B := $896B D_896C := $896C D_896E := $896E D_896F := $896F D_8970 := $8970 D_8972 := $8972 D_8974 := $8974 D_8975 := $8975 D_8976 := $8976 D_8977 := $8977 D_8978 := $8978 D_8979 := $8979 D_897A := $897A D_897B := $897B D_897C := $897C gameClock := $91CB terrainPointsBySide := $92AC rxPacketLength := $E01E rxPacketBuffer := $E020 rxPacketArg1 := $E021 rxPacketArg2 := $E022 rxPacketArg3 := $E023 serviceCommTick := $E12F computeTrainerGroupStats := $E531 rearmCarrierTimer := $E5FF cellDistance := $E82A mirrorBlockCornerForOpponent := $EA38 queueAiCommand := $EA46 side1UnitColTable := $F672 side1UnitRowTable := $F6D6 side1ComcenFlags := $F76B side1UnitTypeTable := $F79E side1UnitDestColTable := $F802 side1ComcenDestCol := $F833 side1UnitDestRowTable := $F866 side1ComcenDestRow := $F897 side1ComcenDisplayFlags := $FBB7 multiply8x8 := $FCCD divideSigned16by8 := $FCEC nextGameRandom := $FD5D ; Contents ; -------- ; $EC0F initTrainerAi Builds the whole opening plan of the solo trainer (the computer opponent, side ; 1 = ; $ED8E rebuildAiGroupTables Recomputes all of the trainer's per-group and per-unit bookkeeping from the ; $EEC3 buildAiHeadingSearchOrder Fills the 8-byte table at $894A with the order in which the resident AI ; $EEED orderAiGroupToPoint Orders AI group Y to move so that its formation centre ends up on column X, ; $EF00 shiftAiGroupDestinations Moves a whole AI group without breaking its formation. ; $EF27 staleDroneSteerTail DEAD CODE in this overlay. ; $EFAB staleDroneTargetPickerTail 34 stale bytes, unreachable and unusable here. .org $EC00 ; 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. mainGroupByPlanTable: .byte $FF,$FF,$04,$04,$04 ; EC00 ..... plan 0-4 -> spearhead group id: none for the two 11-group plans, group 4 for the 5-group plans ; groupCountByPlanTable: byteTable, 5 bytes. Per AI plan 0-4: highest group index ($0A, $0A, 4, 4, 4) ; stored in $8953; $8954 gets this value + 1. groupCountByPlanTable: .byte $0A,$0A,$04,$04,$04 ; EC05 ..... plan 0-4 -> highest group index: 10 (11 groups) for plans 0/1, 4 (5 groups) for plans 2-4 ; aiPlanParamTable: byteTable, 5 bytes. Per AI plan 0-4: drone score threshold ($FF, $FF, 8, $0C, $0C) ; copied to both $8978 and $8979. aiPlanParamTable: .byte $FF,$FF,$08,$0C,$0C ; EC0A ..... plan 0-4 -> drone detonation score threshold: $FF (unreachable score = never detonate) for plans 0/1, 8 or 12 for plans 2-4 ; ---------------------------------------------------------------------- ; 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 ; 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. ; ---------------------------------------------------------------------- initTrainerAi: ldx #$00 ; EC0F X = 0: the value for every counter and flag cleared below stx rxPacketLength ; EC11 no fake incoming packet is pending yet (the comm module's rx packet length) (also trainerAiE000:rxPacketLength) L_EC14: stx D_896C ; EC14 'human comcen has been spotted' flag = 0; randomiseCoordinate ($E14A) widens its aim while this is clear stx D_8952 ; EC17 AI command-queue length = 0: the queue at $88A0 is empty stx D_8960 ; EC1A safest compass direction from the last threat sweep round the AI comcen = 0 stx D_896B ; EC1D rounds left before the AI re-picks its rally point = 0, so it re-picks on the first round stx D_8970 ; EC20 missile-pass scratch of the resident $EC00 tail = 0 stx D_8968 ; EC23 commands queued by the AI this round = 0 stx D_8977 ; EC26 drone state = 0: no drone airborne and the $EE00 drone-flying overlay is not resident ($FF marks it resident) stx D_897B ; EC29 previous-round 'AI comcen destroyed' latch = 0 stx D_897C ; EC2C 'AI comcen destroyed' flag tested by planTrainerRound = 0 dex ; EC2F X = $FF: the 'none' value for the index variables that follow stx D_895D ; EC30 no spearhead group yet for the AI comcen to escort stx D_8976 ; EC33 no drone target unit picked stx D_8972 ; EC36 no player unit is being hunted for having shot at the AI comcen stx D_8964 ; EC39 no home-defence group yet; a real one is chosen at $EC9C unless the plan is QB SNEAK L_EC3C: stx D_8978 ; EC3C drone detonation score threshold = $FF (never); replaced from aiPlanParamTable at $ECCC stx D_8979 ; EC3F and its decaying working copy = $FF lda #$05 ; EC42 comcen threat threshold sta D_897A ; EC44 the resident AI only reacts to pressure on its own comcen once the measured threat reaches 5 ($E29A) jsr serviceCommTick ; EC47 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 (also trainerAiE000:pickTrainerRallyPoint) ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- lda gameTypeOptions ; EC4A the game options byte written by the setup screens and #$07 ; EC4D bits 0-2 = game type 0 SCRIMAGE, 1 QB SNEAK, 2 THE BOMB, 3 FACE-OFF, 4 SLUGGERS, 5 FULL WAR, 6 DEFENDER tay ; EC4F Y = game type cpy #$02 ; EC50 is the game type below 2? bcc L_EC93 ; EC52 SCRIMAGE and QB SNEAK use the plan number that equals the game type checkForTheBombGameType: bne L_EC6C ; EC54 branch if the game type is 3 or higher; fall through only for game type 2 = THE BOMB ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- lda side1ComcenDestCol ; EC56 ordered destination column of the AI comcen, unitDestCol[99] and #$3F ; EC59 keep the 0-39 column only, clearing the idle bit 7 so the engine treats the comcen as still moving sta side1ComcenDestCol ; EC5B store it back L_EC5E: ldy #$00 ; EC5E group 0 sty D_8863 ; EC60 group id of AI slot 49 = unit 99, the AI comcen: the only live AI unit in THE BOMB sty D_8953 ; EC63 highest group index in use = 0 sty D_8966 ; EC66 AI plan number = 0 jmp L_ED6C ; EC69 skip the playbook load and the group scatter entirely and go straight to the table rebuild ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- L_EC6C: dey ; EC6C Y = gameType - 1: FACE-OFF -> 2, SLUGGERS -> 3, FULL WAR -> 4, DEFENDER -> 5 cpy #$04 ; EC6D is the biased type below 4? L_EC6F: bcc L_EC93 ; EC6F FACE-OFF -> plan 2, SLUGGERS -> plan 3 beq L_EC88 ; EC71 FULL WAR (biased type 4) -> plan 4, without the extra point award ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- lda #$A5 ; EC73 command byte $A5 = cmdSetupOption sta D_8896 ; EC75 byte 0 of the AI's 3-byte command scratch lda #$00 ; EC78 argument 1 = kind 0: 'add points to the sender's side' sta msgPickUpPhoneThenPressSpace; EC7A byte 1 of the command scratch; the msg... label here is a $8800 message-string address collision lda #$14 ; EC7D argument 2 = 20 points sta D_8898 ; EC7F byte 2 of the command scratch L_EC82: jsr queueAiCommand ; EC82 append the staged 3-byte command to the AI's outgoing queue at $88A0 dec D_8968 ; EC85 undo the 'commands queued this round' bump queueAiCommand made: this is a setup option, not a battle order ; ---------------------------------------------------------------------- ; 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). ; ---------------------------------------------------------------------- L_EC88: ldy #$21 ; EC88 $21 = facing 1 (bits 0-1), group id 0 (bits 2-4), side 1 (bit 5), not in a group (bit 6 clear) sty side1ComcenFlags ; EC8A unitFlagsTable[99] - the AI comcen dey ; EC8D $20 = the same value with the facing bits stripped, which is what the engine mirrors into the display flags sty side1ComcenDisplayFlags ; EC8E unitDisplayFlags[99]: bit 7 (arrived/idle) clear, bit 6 (group order active) clear usePlanFour: ldy #$04 ; EC91 FULL WAR and DEFENDER both use plan 4 ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- L_EC93: sty D_8966 ; EC93 remember the plan number 0-4 for the rest of the setup and for the resident AI lda #$FF ; EC96 $FF = 'no home-defence group' cpy #$01 ; EC98 is this plan 1 (QB SNEAK)? L_EC9A: beq L_ECA3 ; EC9A QB SNEAK throws everything forward: leave the guard group as $FF L_EC9C: jsr nextGameRandom ; EC9C roll for a guard group and #$03 ; EC9F keep the low two bits: 0-3 beq L_EC9C ; ECA1 reject 0 - group 0 is the one that follows the spearhead, so it is never the guard; re-roll L_ECA3: sta D_8964 ; ECA3 home-defence group id 1-3 ; ---------------------------------------------------------------------- ; 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). ; ---------------------------------------------------------------------- L_ECA6: ldx #$00 ; ECA6 destination low byte = $00 ldy #$02 ; ECA8 destination high byte = $02, i.e. load to $0200 jsr setLoadDest ; ECAA tell the fast loader where the next sectors go ldy #$1D ; ECAD track 29 ldx #$06 ; ECAF first sector 6 lda #$03 ; ECB1 three sectors = 768 bytes = the five opening plays jsr loadSectors ; ECB3 pull them in over $0200-$04FF ; ---------------------------------------------------------------------- ; Copy the plan's fixed parameters out of the three tables at the top of this overlay. ; ---------------------------------------------------------------------- ldy D_8966 ; ECB6 Y = plan number, the index into all three 5-byte tables (and still in Y when multiply8x8 is called at $ECD4) lda mainGroupByPlanTable,y ; ECB9 spearhead group for this plan sta D_895D ; ECBC the group the AI comcen escorts and that group 0 is told to follow; $FF for plans 0/1 ldx groupCountByPlanTable,y ; ECBF highest group index used by this plan stx D_8953 ; ECC2 store it inx ; ECC5 +1 stx D_8954 ; ECC6 number of groups (11 or 5), the loop limit the resident AI uses at $E6A5/$E960/$EB3C lda aiPlanParamTable,y ; ECC9 drone detonation score threshold for this plan sta D_8978 ; ECCC the fixed threshold sta D_8979 ; ECCF and the working copy that the drone code decays to 3/4 as it gets desperate ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- lda #$96 ; ECD2 150 = the size of one plan record buildPlanRecordPointers: jsr multiply8x8 ; ECD4 A x Y = 150 x plan number; returns the product low byte in A and the high byte in Y sta mapCellPtr ; ECD7 low byte of the record's offset into the playbook iny ; ECD9 add $100 ... iny ; ECDA ... and another $100, because the playbook was loaded at $0200 sty mapCellPtrHi ; ECDB mapCellPtr now points at the record's 50 group ids clc ; ECDD clear carry for the add below L_ECDE: adc #$32 ; ECDE +50 bytes: past the group ids bcc L_ECE3 ; ECE0 no page crossing iny ; ECE2 carry into the high byte L_ECE3: sta fillEndPtr ; ECE3 fillEndPtr points at the record's 50 destination columns sty fillEndPtrHi ; ECE5 high byte of that pointer L_ECE7: clc ; ECE7 clear carry again adc #$32 ; ECE8 +50 more bytes: past the destination columns bcc L_ECED ; ECEA no page crossing iny ; ECEC carry into the high byte L_ECED: sta planRowPtr ; ECED planRowPtr points at the record's 50 destination rows sty planRowPtrHi ; ECEF high byte of that pointer ; ---------------------------------------------------------------------- ; Copy the plan into the live arrays, AI slot 49 down to 0 (units 99 down to 50). ; ---------------------------------------------------------------------- ldy #$31 ; ECF1 49 = the last AI slot; the loop counts down to 0 copyPlanRecordLoop: lda (mapCellPtr),y ; ECF3 group id of this unit in the chosen opening play sta D_8832,y ; ECF5 $8832 + slot = the AI's own group-id array (the resident AI addresses it as $8800 + absolute unit index) lda (fillEndPtr),y ; ECF8 destination column from the play sta side1UnitDestColTable,y ; ECFA unitDestCol[50 + slot] L_ECFD: lda (planRowPtr),y ; ECFD destination row from the play sta side1UnitDestRowTable,y ; ECFF unitDestRow[50 + slot] dey ; ED02 previous slot bpl copyPlanRecordLoop ; ED03 loop until all 50 AI units have their group and destination lda D_8966 ; ED05 which plan is this? cmp #$02 ; ED08 plans 2, 3 and 4 re-aim every group; plans 0 and 1 keep the playbook's own destinations L_ED0A: bcc L_ED6C ; ED0A SCRIMAGE / QB SNEAK: nothing more to place, go and rebuild the tables ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- lda D_8953 ; ED0C highest group index of this plan sta D_8967 ; ED0F group loop counter, counting down to 0 L_ED12: ldy D_8967 ; ED12 Y = the group being placed jsr computeTrainerGroupStats; ED15 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) L_ED18: jsr nextGameRandom ; ED18 roll a column and #$1F ; ED1B 0-31 ... clc ; ED1D clear carry for the add below adc #$04 ; ED1E ... + 4 = column 4-35, keeping the formation clear of both map edges sta cellColA ; ED20 zp_22 = wanted formation centre column L_ED22: sta D_88D2,y ; ED22 and remember it as group Y's target column L_ED25: lda scratch1F ; ED25 zp_1F = the group's average ordered row, from computeTrainerGroupStats cmp #$10 ; ED27 is that row above 16? bcs L_ED2D ; ED29 no clamp needed lda #$0F ; ED2B floor it at 15 so the band computed below cannot go negative L_ED2D: sec ; ED2D prepare to subtract sbc #$07 ; ED2E start the 8-row band 7 rows before the group's own row L_ED30: sta scratch18 ; ED30 base row of the band rollGroupTargetRowJitter: jsr nextGameRandom ; ED32 roll a row offset D_ED35: and #$07 ; ED35 0-7 rows of jitter inside the band clc ; ED37 clear carry for the add adc scratch18 ; ED38 base + jitter sta cellRowA ; ED3A zp_24 = wanted formation centre row sta D_88DE,y ; ED3C and remember it as group Y's target row jsr shiftAiGroupDestinations; ED3F clip the point to the map and shift every member of the group by the same delta L_ED42: dec D_8967 ; ED42 next group down bmi L_ED59 ; ED45 counter went negative: every group has been placed bne L_ED12 ; ED47 still above group 0: keep scattering ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- ldy D_895D ; ED49 spearhead group id for this plan bmi L_ED12 ; ED4C $FF = no spearhead (only possible for plans 0/1, which never get here): fall back to a random point ldx D_88D2,y ; ED4E the spearhead group's target column lda D_88DE,y ; ED51 and its target row L_ED54: ldy #$00 ; ED54 group 0 jsr orderAiGroupToPoint ; ED56 order group 0 to the same cell ; ---------------------------------------------------------------------- ; Finally station the home-defence group on the AI comcen's own destination. ; ---------------------------------------------------------------------- L_ED59: ldy D_8964 ; ED59 home-defence group id bmi L_ED6C ; ED5C $FF: QB SNEAK and THE BOMB keep nobody back lda side1ComcenDestCol ; ED5E AI comcen's ordered column, unitDestCol[99] L_ED61: and #$3F ; ED61 strip the idle bit 7 to leave a plain 0-39 column L_ED63: tax ; ED63 X = target column, as orderAiGroupToPoint expects lda side1ComcenDestRow ; ED64 AI comcen's ordered row, unitDestRow[99] and #$3F ; ED67 strip the idle bit jsr orderAiGroupToPoint ; ED69 order the guard group (still in Y) onto the comcen ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- L_ED6C: jsr rebuildAiGroupTables ; ED6C build the group averages, the per-unit activation clocks and the heading search order L_ED6F: jsr nextGameRandom ; ED6F roll the delay before the AI changes gear and #$3F ; ED72 0-63 ... ora #$20 ; ED74 ... with bit 5 forced on: 32-95 rounds sta scratch18 ; ED76 keep it L_ED78: lda gameClock ; ED78 the game length in rounds (setGameClockForType at $7B1A left it here for us: 63/63/31/127/127/254/254 by game type) ldy D_8966 ; ED7B which plan? cpy #$02 ; ED7E plans 0 and 1 ... bcc L_ED85 ; ED80 ... use the whole game length unchanged sec ; ED82 prepare to subtract sbc scratch18 ; ED83 plans 2-4: the switch happens once 32-95 rounds have been played L_ED85: sta D_896E ; ED85 the clock value at which the AI's second phase begins lda #$FF ; ED88 $FF = 'clock not running' sta gameClock ; ED8A 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 L_ED8D: rts ; ED8D back to $7B33, which immediately reloads the modem-driver tail over this overlay ; ---------------------------------------------------------------------- ; 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 ; 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. ; ---------------------------------------------------------------------- rebuildAiGroupTables: jsr buildAiHeadingSearchOrder; ED8E refresh the random left/right heading search order at $894A ldy #$0B ; ED91 group slots 11 down to 0 (only 0..$8953 are really used, but all twelve are cleared) L_ED93: lda #$00 ; ED93 the value written into all nine group arrays below sta play4Grp12,y ; ED95 high byte of the group's ordered-column sum sta play4Grp32,y ; ED98 high byte of the ordered-row sum sta play4Col02,y ; ED9B high byte of the current-column sum sta play4Col22,y ; ED9E $04A0: high byte of the current-row sum (the writeSectorToDisk label is a $0400-page collision) sta D_8902,y ; EDA1 member count sta D_88D2,y ; EDA4 low byte of the ordered-column sum, which becomes the average sta D_88DE,y ; EDA7 low byte of the ordered-row sum sta D_88EA,y ; EDAA low byte of the current-column sum sta D_88F6,y ; EDAD low byte of the current-row sum jsr nextGameRandom ; EDB0 roll this group's start delay and #$0F ; EDB3 0-15 rounds sta D_893E,y ; EDB5 the group waits that many rounds before it starts obeying its orders dey ; EDB8 next group down bpl L_ED93 ; EDB9 loop over all twelve group slots ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- lda #$31 ; EDBB 49 = the last AI slot sta scratch18 ; EDBD zp_18 = the AI slot being processed L_EDBF: ldy scratch18 ; EDBF Y = AI slot lda terrainPointsBySide ; EDC1 $92AC = the TERRAIN PTS setting of side 1 (the AI), 0-3, set by the setup screens bne L_EDC8 ; EDC4 terrain points are switched on lda #$80 ; EDC6 switched off: force the flag on for every unit L_EDC8: and #$80 ; EDC8 with points on the value is 1-3, so bit 7 is clear and the flag ends up 0 L_EDCA: sta D_8864,y ; EDCA 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 lda side1UnitTypeTable,y ; EDCD type byte of this AI unit bpl L_EDDC ; EDD0 bit 7 = dead; branch for a live unit L_EDD2: lda #$FF ; EDD2 $FF = 'in no group' sta D_8832,y ; EDD4 a dead unit is unhooked from its group so no later pass counts it lda #$00 ; EDD7 leftover: A is never used, the jump target only decrements the slot counter jmp L_EE6B ; EDD9 on to the next AI slot ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- L_EDDC: ldx D_8832,y ; EDDC X = the unit's group id inc D_8902,x ; EDDF one more member in that group lda side1UnitDestColTable,y ; EDE2 ordered destination column and #$3F ; EDE5 strip the idle bit 7 to leave a plain 0-39 column sta cellColB ; EDE7 zp_27 = point B for the cellDistance call at $EE5C clc ; EDE9 clear carry before the 16-bit add adc D_88D2,x ; EDEA add into the group's ordered-column sum ... sta D_88D2,x ; EDED ... low byte lda #$00 ; EDF0 A = 0: only the carry is being added on adc play4Grp12,x ; EDF2 propagate the carry ... sta play4Grp12,x ; EDF5 ... into the high byte at $0464 lda side1UnitDestRowTable,y ; EDF8 ordered destination row and #$3F ; EDFB strip the idle bit sta cellRowB ; EDFD zp_28 = row of point B clc ; EDFF clear carry before the 16-bit add sub_EE00: adc D_88DE,x ; EE00 add into the group's ordered-row sum ... sta D_88DE,x ; EE03 ... low byte lda #$00 ; EE06 A = 0: only the carry is being added on adc play4Grp32,x ; EE08 propagate the carry ... sta play4Grp32,x ; EE0B ... into the high byte at $0478 lda side1UnitColTable,y ; EE0E the unit's current column, unitCol[50 + slot] sta cellColA ; EE11 zp_22 = point A for cellDistance clc ; EE13 clear carry before the 16-bit add L_EE14: adc D_88EA,x ; EE14 add into the group's current-column sum ... sta D_88EA,x ; EE17 ... low byte lda play4Col02,x ; EE1A high byte at $048C ... adc #$00 ; EE1D ... plus the carry sta play4Col02,x ; EE1F store it back lda side1UnitRowTable,y ; EE22 the unit's current row, unitRow[50 + slot] sta cellRowA ; EE25 zp_24 = row of point A clc ; EE27 clear carry before the 16-bit add L_EE28: adc D_88F6,x ; EE28 add into the group's current-row sum ... sta D_88F6,x ; EE2B ... low byte lda play4Col22,x ; EE2E high byte at $04A0 ... adc #$00 ; EE31 ... plus the carry sta play4Col22,x ; EE33 store it back ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- ldy #$00 ; EE36 Y = 0 = 'stagger the groups' lda D_8966 ; EE38 which plan? bne L_EE3E ; EE3B any plan but 0 staggers the start dey ; EE3D plan 0 (SCRIMAGE): Y = $FF = 'no staggered start' L_EE3E: sty scratch1A ; EE3E zp_1A holds the marker; only its bit 7 is tested ldy scratch18 ; EE40 Y = the AI slot again ldx D_8832,y ; EE42 X = its group id lda gameClock ; EE45 the full game length in rounds - the clock value at the moment play starts bit scratch1A ; EE48 test bit 7 of the plan-0 marker without disturbing A checkGroupStartsAtOnce: bmi L_EE59 ; EE4A plan 0: this unit is active from round one cpx D_8964 ; EE4C is this the home-defence group? beq L_EE59 ; EE4F the guard group never waits - it has to be in place from the start cpx #$00 ; EE51 is this group 0? beq L_EE59 ; EE53 group 0 follows the spearhead and never waits either applyGroupStartDelay: sec ; EE55 prepare to subtract sbc D_893E,x ; EE56 otherwise subtract the group's 0-15 round delay from the clock L_EE59: sta msgGetOpponentOnPhoneAnd,y; EE59 $8800 + slot = the clock value at which this unit starts obeying orders (the msg... label is a $8800 message-string address collision) ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- jsr cellDistance ; EE5C distance between the unit's cell (zp_22/zp_24) and its ordered destination (zp_27/zp_28) cmp #$00 ; EE5F redundant - cellDistance already returns with the flags set from its own TXA bne L_EE6B ; EE61 still has somewhere to go lda side1UnitDestColTable,y ; EE63 its ordered column ora #$80 ; EE66 set bit 7 = 'arrived / idle' sta side1UnitDestColTable,y ; EE68 store it back L_EE6B: dec scratch18 ; EE6B next AI slot down bmi L_EE72 ; EE6D the counter went negative: all 50 slots done jmp L_EDBF ; EE6F round again (too far for a branch) ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- L_EE72: lda D_8953 ; EE72 highest group index in use sta scratch1B ; EE75 zp_1B = the group being averaged, counting down to 0 L_EE77: ldx scratch1B ; EE77 X = group index lda D_8902,x ; EE79 how many members it has sta scratch1C ; EE7C zp_1C = the divisor, reloaded before each of the four divisions ldy play4Grp12,x ; EE7E high byte of the ordered-column sum lda D_88D2,x ; EE81 low byte ldx scratch1C ; EE84 X = divisor jsr divideSigned16by8 ; EE86 Y:A / X ldx scratch1B ; EE89 X = group index again L_EE8B: sta D_88D2,x ; EE8B the group's average ordered column ldy play4Grp32,x ; EE8E high byte of the ordered-row sum lda D_88DE,x ; EE91 low byte ldx scratch1C ; EE94 X = divisor jsr divideSigned16by8 ; EE96 Y:A / X ldx scratch1B ; EE99 X = group index sta D_88DE,x ; EE9B the group's average ordered row ldy play4Col02,x ; EE9E high byte of the current-column sum L_EEA1: lda D_88EA,x ; EEA1 low byte ldx scratch1C ; EEA4 X = divisor jsr divideSigned16by8 ; EEA6 Y:A / X ldx scratch1B ; EEA9 X = group index sta D_88EA,x ; EEAB the group's current centre column ldy play4Col22,x ; EEAE high byte of the current-row sum at $04A0 lda D_88F6,x ; EEB1 low byte ldx scratch1C ; EEB4 X = divisor L_EEB6: jsr divideSigned16by8 ; EEB6 Y:A / X storeGroupCentreRow: ldx scratch1B ; EEB9 X = group index sta D_88F6,x ; EEBB the group's current centre row L_EEBE: dec scratch1B ; EEBE next group down bpl L_EE77 ; EEC0 loop until group 0 has been averaged rts ; EEC2 done ; ---------------------------------------------------------------------- ; 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 ; ---------------------------------------------------------------------- buildAiHeadingSearchOrder: lda #$00 ; EEC3 start from a zero heading offset sta scratch18 ; EEC5 zp_18 = the running heading offset, starting at 0 ldy #$01 ; EEC7 Y = 1 writes the first entry, $8949+1 = $894A; the loop runs Y = 1..8 L_EEC9: tya ; EEC9 A = the entry number lsr a ; EECA A = Y/2 = the magnitude of this pair, and C = 1 on the second (odd) entry of each pair bcs L_EED4 ; EECB second entry of a pair: reuse the magnitude with the opposite sign sta scratch18 ; EECD first entry of a pair: adopt the new magnitude 0, 1, 2, 3 or 4 jsr nextGameRandom ; EECF toss a coin ... bpl storeHeadingOffset ; EED2 ... heads (bit 7 clear): take this pair positive first, tails: fall through and negate L_EED4: lda scratch18 ; EED4 negate the current magnitude ... eor #$FF ; EED6 ... one's complement ... clc ; EED8 prepare the +1 of the two's complement negate adc #$01 ; EED9 ... plus one sta scratch18 ; EEDB keep the signed offset storeHeadingOffset: lda scratch18 ; EEDD the signed offset, -4..+4 L_EEDF: clc ; EEDF clear carry for the bias below sub_EEE0: adc #$08 ; EEE0 bias by 8 so a negative offset stays positive ... and #$07 ; EEE2 ... and fold it into a heading 0-7 sta D_8949,y ; EEE4 $8949+Y, i.e. $894A..$8951 iny ; EEE7 next entry cpy #$09 ; EEE8 eight entries written? bcc L_EEC9 ; EEEA keep going while Y is below 9 rts ; EEEC done ; ---------------------------------------------------------------------- ; 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) ; 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. ; ---------------------------------------------------------------------- orderAiGroupToPoint: stx L_EEF8+1 ; EEED park the target column in the operand byte of the LDA # at $EEF8 sta L_EEFC+1 ; EEF0 park the target row in the operand byte of the LDA # at $EEFC jsr computeTrainerGroupStats; EEF3 measure group Y ($E531): member count, bounding box, centre and average ordered cell; clobbers A/X/Y bcs L_EF26 ; EEF6 C=1 means the group has no live member - nothing to order L_EEF8: lda #$FF ; EEF8 operand patched at $EEED = the target column sta cellColA ; EEFA zp_22 = wanted formation centre column L_EEFC: lda #$FF ; EEFC operand patched at $EEF0 = the target row sta cellRowA ; EEFE zp_24 = wanted formation centre row ; ---------------------------------------------------------------------- ; 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) ; ---------------------------------------------------------------------- shiftAiGroupDestinations: jsr rearmCarrierTimer ; EF00 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) (also trainerAiE000:clampGroupTargetToMap) ldy #$31 ; EF03 AI slots 49 down to 0 L_EF05: lda D_8832,y ; EF05 group id of this AI unit cmp D_8955 ; EF08 the group computeTrainerGroupStats last measured bne L_EF23 ; EF0B not a member: skip it lda side1UnitDestColTable,y ; EF0D its ordered column stripIdleBitFromDestCol: and #$3F ; EF10 keep the 0-39 column, dropping the idle bit 7 so the unit is set moving again clc ; EF12 clear carry for the shift add adc scratch2A ; EF13 apply the group's column shift sta side1UnitDestColTable,y ; EF15 store the new ordered column lda side1UnitDestRowTable,y ; EF18 its ordered row and #$3F ; EF1B keep the 0-39 row only clc ; EF1D clear carry for the shift add adc scratch2B ; EF1E apply the group's row shift L_EF20: sta side1UnitDestRowTable,y ; EF20 store the new ordered row L_EF23: dey ; EF23 next AI slot bpl L_EF05 ; EF24 loop over all 50 L_EF26: rts ; EF26 done (also the early exit for an empty group) ; ---------------------------------------------------------------------- ; 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) ; ---------------------------------------------------------------------- staleDroneSteerTail: bcc L_EF2B ; EF27 carry clear: use the turn amount the caller put in X ldx #$FF ; EF29 carry set: turn one step anticlockwise L_EF2B: txa ; EF2B A = the signed turn L_EF2C: clc ; EF2C clear carry for the heading add adc D_896F ; EF2D add it to the current drone heading and #$07 ; EF30 wrap into 0-7 (8 compass points) sta D_896F ; EF32 the new heading L_EF35: ldy D_896F ; EF35 Y = heading 0-7 L_EF38: lda headingToDirectionTable,y; EF38 $7316 in the $6F00 overlay: heading -> direction code (0-1 row only, 2-3 column only, 4-7 diagonal) (also mapGenerator6F00:forestBlockCompareNe) ldy #$00 ; EF3B Y = row delta, assume 0 ldx #$00 ; EF3D X = column delta, assume 0 cmp #$04 ; EF3F is the code a diagonal? bcs L_EF57 ; EF41 codes 4-7 move on both axes cmp #$02 ; EF43 is the code a pure-column move? bcs L_EF4F ; EF45 codes 2-3 move along the column only tay ; EF47 codes 0-1: row only L_EF48: lda directionDeltaTable,y ; EF48 $20F0: direction code -> signed delta tay ; EF4B Y = row delta jmp L_EF6D ; EF4C on to the position update L_EF4F: tax ; EF4F codes 2-3 lda directionDeltaTable,x ; EF50 $20F0: direction code -> signed delta L_EF53: tax ; EF53 X = column delta jmp L_EF6D ; EF54 on to the position update L_EF57: tay ; EF57 codes 4-7 L_EF58: lda diagonalDirectionTable,y; EF58 $1FB2: packs the two component codes of a diagonal into the two nibbles pha ; EF5B keep the packed pair and #$0F ; EF5C low nibble = the row component tay ; EF5E index the delta table with it lda directionDeltaTable,y ; EF5F $20F0: -> signed delta tay ; EF62 Y = row delta pla ; EF63 get the packed pair back lsr a ; EF64 shift the high nibble down ... lsr a ; EF65 ... lsr a ; EF66 ... lsr a ; EF67 ... = the column component tax ; EF68 index the delta table with it lda directionDeltaTable,x ; EF69 $20F0: -> signed delta tax ; EF6C X = column delta L_EF6D: txa ; EF6D A = column delta clc ; EF6E clear carry for the position add adc D_8974 ; EF6F current drone column sta D_8974 ; EF72 advance it one cell tax ; EF75 X = the new column, for mirrorBlockCornerForOpponent tya ; EF76 A = row delta L_EF77: clc ; EF77 clear carry for the position add adc D_8975 ; EF78 current drone row sta D_8975 ; EF7B advance it one cell tay ; EF7E Y = the new row, for mirrorBlockCornerForOpponent lda #$8D ; EF7F command $8D = cmdDroneMove sta rxPacketBuffer ; EF81 byte 0 of the comm module's INCOMING packet buffer: the trainer forges the packet the engine is about to read (also trainerAiE000:rxPacketBuffer) lda D_896F ; EF84 the drone's heading clc ; EF87 clear carry for the add adc #$04 ; EF88 +4 ... and #$07 ; EF8A ... modulo 8 = the same heading seen from the other end of the mirrored map sta rxPacketArg1 ; EF8C byte 1 of the packet (also trainerAiE000:rxPacketArg1) jsr mirrorBlockCornerForOpponent; EF8F $EA38: X,Y = 39-(X+1), 39-(Y+1), pre-compensating the mirror-and-decrement the engine applies to a remote $8D stx rxPacketArg2 ; EF92 byte 2 of the packet = mirrored column (also trainerAiE000:rxPacketArg2) sty rxPacketArg3 ; EF95 byte 3 of the packet = mirrored row (also trainerAiE000:rxPacketArg3) lda #$04 ; EF98 packet length 4 L_EF9A: sta rxPacketLength ; EF9A tell the game a 4-byte packet has arrived; mergeIncomingPacket ($4F05) will execute it (also trainerAiE000:rxPacketLength) rts ; EF9D done ; 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) measureDroneDistanceToTarget: .byte $AD ; EF9E AD 74 89 = LDA $8974 (drone column), 85 27 = STA zp_27, AD 75 89 = LDA $8975 (drone row) .byte $74,$89,$85,$27,$AD,$75,$89; EF9F t..'.u. .byte $85 ; EFA6 85 ... .byte $28 ; EFA7 ... 28 = STA zp_28, then 4C 2A E8 = JMP cellDistance ($E82A) .byte $4C,$2A,$E8 ; EFA8 L*. ; ---------------------------------------------------------------------- ; 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 ; ---------------------------------------------------------------------- staleDroneTargetPickerTail: inc scratch1F ; EFAB next candidate block lda scratch1F ; EFAD how many have been scored cmp #$0F ; EFAF 15 candidate blocks per pass bcc L_EF9A+1 ; EFB1 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 L_EFB3: lda scratch1D ; EFB3 the best player unit found by the scoring pass bmi L_EFCC ; EFB5 bit 7 set = no candidate at all sta D_8976 ; EFB7 adopt it as the drone's target unit lda D_8979 ; EFBA the current detonation score threshold lsr a ; EFBD /2 ... lsr a ; EFBE ... /4 sta scratch18 ; EFBF keep a quarter of it lda D_8979 ; EFC1 the threshold again sec ; EFC4 prepare to subtract sbc scratch18 ; EFC5 threshold - threshold/4 = three quarters of it bcc L_EFCC ; EFC7 underflow: leave the threshold alone sta D_8979 ; EFC9 lower the bar so the drone will accept a weaker target next time L_EFCC: rts ; EFCC done ; $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. msgSpaceTailFragment: .byte "CE",'.'|$80 ; EFCD tail of 'INSERT xxxx DISK AND PRESS SPACE.' - the terminator '.' has bit 7 set ; msgDataGameCustomOff: text, 18 bytes. '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. msgDataDiskName: .byte "DATA" ; EFD0 'DATA' - the 4-character disk name promptInsertDataDisk ($81B2) patches into the message template at $EFB6 msgGameDiskName: .byte "GAME" ; EFD4 'GAME' - the disk name promptInsertGameDisk ($81A5) uses instead msgCustomOff: .byte "CUSTOM OF",'F'|$80 ; EFD8 'CUSTOM OFF' - installed as message slot $1E by overlay A; its ON/OFF patch points at the 'OFF' at $EFDF ; msgDestroyOff: text, 11 bytes. 'DESTROY OF'+'F'|$80. msgDestroyOff: .byte "DESTROY OF",'F'|$80 ; EFE2 'DESTROY OFF' - message slot $1E for the DESTROY toggle; the ON/OFF patch points at the 'OFF' at $EFEA ; msgAccepted: text, 8 bytes. 'ACCEPTE'+'D'|$80. msgAccepted: .byte "ACCEPTE",'D'|$80 ; EFED 'ACCEPTED' - printed centred on row 15 by overlay A when the fire button confirms an edited value ; overlayTailPadding_EFF5: byteTable, 11 bytes. $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. overlayPageTailBytes: .byte $C0,$DF,$00,$FF,$00,$FF,$00,$FF; EFF5 ........ unreferenced trailing bytes, the same in every image of this page .byte $00 ; EFFD the last byte is $FF here and in the track 35 trainer tail, $00 in the track 34 modem tail .byte $FF,$FF ; EFFE ..